fix(frontend): isolate sidebar registration to unblock navigation
Separate shell subscriptions from Home session data and cover effect convergence, transition cleanup, current callbacks and signed-out fallback with real React lifecycle tests. Record baseline-equivalent local failures and outstanding browser/build verification. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -12797,3 +12797,20 @@
|
||||
- 相关记录:BUG-554、BUG-698
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-971 | 首页侧栏注册反馈循环阻塞客户端导航
|
||||
|
||||
- 状态:investigating(代码与定向回归完成;部署和登录态真人闭环待核对)
|
||||
- 首次发现:2026-09-19
|
||||
- 最近更新:2026-09-19
|
||||
- 影响面:首页侧栏「星盘 / 星历 / 我的报告」客户端导航与共享外壳更新。
|
||||
- 用户现象:测试站刷新后仍点了不切页,Network 出现 `/api/ephemeris`;该请求来自 pointerenter/pointerdown 数据预取,不代表 Next 导航成功。
|
||||
- 触发条件:登录态 Home hydrated 后注册侧栏操作;useSessionManagement 每次 render 产生新的 visibleSessions/操作函数,注册 effect 随之再次发布。
|
||||
- 根因:SessionListContext 同时承载业务数据和 registration。Home 写 registration 又订阅同一 value,setRegistration 广播反向使 Home 重渲染,形成默认优先级更新循环,阻塞 Next Link 使用的 startTransition。旧共享外壳测试只有源码正则,不能执行 effect,未拦住运行时反馈。
|
||||
- 修复:会话数据 context 保留稳定 registrar,registration 独立只读 context 仅供 AppShell 订阅;映射显式接收 registration,children 原样透传。保留注册 effect 完整依赖、最新回调及卸载注销,不冻结首次闭包、不改成整页刷新。
|
||||
- 验证:真实 React 最小原版实验 122 render / 118 注册 / 2 maximum-depth 告警(保护阈值主动终止);稳定数组和回调的因果对照为 3/1/0。另一次 transition 实验直到主动停止反馈后目的地才 commit。正式新生命周期测试旧实现 3 fail / 1 pass、修复 4/4 pass;含普通/StrictMode 空闲收敛、transition 注销重入、最新业务数据与回调、401。实际 SidebarProvider/Inset 包裹,但不模拟浏览器点击/布局;完整主会话复验、基线失败对照与发布结果见进度记录。
|
||||
- 防复发:Home 不订阅自己生产的 shell registration,外壳不重建 children;保留真实客户端 React 回归而非仅 SSR/源码正则。未稳定数组/函数的测试输入不得改成固定常量来掩盖回归。
|
||||
- 相关记录:BUG-927(共享外壳)、BUG-965 / BUG-967(同名导航现象但不同跨部署机制);本轮不扩修 health 快照时序漏洞。
|
||||
- 复发自:BUG-927 共享外壳订阅边界的新增运行时回归,非已证实的旧构建不匹配复发。
|
||||
- 修复版本:本轮 `fix(frontend): isolate sidebar registration to unblock navigation` 提交;发布状态见 `docs/tasks/PROGRESS-sidebar-navigation-loop-20260919.md`。
|
||||
- 验收缺口:Windows Skill runtime symlink EPERM 阻止完整基线构建;Static/gzip 与真实登录态浏览器检查不得标通过。见 `BLOCKED.md` 和 `docs/testing/sidebar-navigation-loop-20260919.md`。
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# 执行与验收 · 侧栏导航注册循环(2026-09-19)
|
||||
|
||||
## 授权、基线与范围
|
||||
|
||||
- 产品明确授权修复并 push staging,实施计划已批准;主代理负责诊断/验收/交付,子代理实施限定源码与测试。
|
||||
- 基线 `origin/staging = 507ef959f519218d190574fa588fc7b92c60229b`;独立工作树 `.worktrees/sidebar-navigation-loop-20260919`,分支 `codex/sidebar-navigation-loop-20260919`。同 SHA 的 detached 基线工作树用于测试和构建比较,不改主检出、不 stash。
|
||||
- 开工核对 Bug 最大号 970,本轮暂用 BUG-971,提交前再次检查编号冲突。
|
||||
- 只拆分会话数据与侧栏注册订阅;保持单侧栏、单列表数据源、最新操作回调、卸载注销、inert 和客户端导航。无样式/文案、数据库、依赖、workflow、DNS、main 改动。不扩修 BUG-967 的 health 时序问题。
|
||||
|
||||
## 事故实证与根因
|
||||
|
||||
产品确认测试站三个入口刷新后仍不切页;看到的 `/api/ephemeris` 是 `AppSidebar` 的 pointerenter/pointerdown 数据预取,不代表导航成功。匿名三个页面和 RSC 均 200;线上 `d6fc4fb8` 与本轮基线的关键导航源码相同。
|
||||
|
||||
`Home -> useSessionList -> useSessionManagement(新 visibleSessions/回调) -> useHomeShellRegistration effect -> setRegistration -> 同一 context 通知 Home` 形成反馈。真实 React 最小运行原版 122 render / 118 注册 / 2 个 maximum-depth 告警(保护阈值主动终止);只稳定回调无效,数组与回调稳定对照 3/1/0。另在第 3 render 发 startTransition,循环直到第 301 render 主动停止才允许目标 commit;普通优先级与稳定数组对照均可正常提交。Next Link 实际通过 startTransition 导航。
|
||||
|
||||
这是新的共享外壳运行时缺陷,关联 BUG-927,不把本轮刷新无效强归为旧 BUG-965/967。旧 provider 测试只有源码正则,未执行 effects。
|
||||
|
||||
## 实施进度
|
||||
|
||||
- [x] 隔离基线与实施工作树、复用现有依赖(junction,不安装或升级)。
|
||||
- [x] 注册订阅读写隔离与外壳消费调整(3 个源码文件,Home 状态与原注册 effect 未改)。
|
||||
- [x] 真实 React 生命周期/transition 回归及反向验证。
|
||||
- [x] 独立审查及可运行验收、完整测试/构建基线比较;Static/gzip/真人浏览器仍为明确缺口。
|
||||
- [ ] 提交与快进 push staging、远端 SHA/部署核对。
|
||||
|
||||
独立审查确认 data value 不依赖 registration、registrar 保持稳定、shell 单独读取、children 原样透传;账户/session 真正变动仍能通知 Home 并发布最新闭包。新 host helper 只运行 React reconciler/effect,不模拟点击、几何或浏览器可访问性。可见分页 observer、SidebarTrigger 操作与完整 Next 浏览器仍列真人缺口,没有声称全覆盖。
|
||||
|
||||
## 验证记录
|
||||
|
||||
| 项目 | 基线 | 修复后 | 结论 |
|
||||
| --- | --- | --- | --- |
|
||||
| 全量前端测试 | 3481 tests / 3403 pass / 78 fail | 3486 tests / 3408 pass / 78 fail | 新增 5 个通过;78 个失败测试名/次数完全一致,非全绿 |
|
||||
| TypeScript | webpack 类型检查完成 | 独立 `tsc --noEmit --incremental false` 0 错 | 通过 |
|
||||
| 完整 lint | 未重跑(未触碰原 warnings) | 0 error / 120 warnings,exit 0 | 零 error 通过,未顺手修 warning |
|
||||
| 生产构建 / 首页 Static / 首屏 gzip | 编译/类型完成,collect page data: Skill symlink EPERM | 同阶段同根因 EPERM(含共同 /api/birth-time-guide) | 构建失败;Static/gzip 未验收 |
|
||||
| 生命周期反向 | 新 4 条在旧源码 3 fail / 1 pass(50 render 保护) | 4/4 pass | 证明能抓回归 |
|
||||
| 主会话独立五文件定向 | 原四文件 60/60 | 65/65,fail 0 | 通过 |
|
||||
| staging 部署 | 诊断时 d6fc4fb8 | 未推送 | 未验收 |
|
||||
|
||||
- 全量逐项证据:[78 项失败对照](../testing/sidebar-navigation-baseline-failures-20260919.md)。全量与构建使用同一 node_modules、相同命令;不读取用户凭据,不改生产源码绕过 Windows 权限。
|
||||
- 定向:`tsx --test tests/session-list-lifecycle.test.tsx tests/session-list-provider.test.ts tests/secondary-page-entry.test.ts tests/sidebar-contract.test.ts tests/account-dialog-inert-20260918.test.ts`。
|
||||
- 构建:`npm run build -- --webpack`。Next 16.3.1 编译与类型通过不等于构建通过。
|
||||
- 推送前 fetch 得到 `a2bcdee006397e2417f2e42d30ca030dd41ec480`,相对初始基线仅 2 个文档提交、2 处文档文件变化,Bug 最大号仍 970;整合后代码测试基线不变。
|
||||
- Gitea Actions runs 匿名只读 API 返回 401;未借用/搜寻其他账号凭据,门禁详情查询为环境缺口。
|
||||
|
||||
## 既有断言改动
|
||||
|
||||
| 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| 既有断言全部保留 | 不修改任何旧断言;追加 1 条结构合同和 4 条生命周期测试 | 锁定订阅边界并执行真实 effect/transition,测试总数 +5;没有弱化或减少覆盖 |
|
||||
|
||||
## 环境边界
|
||||
|
||||
- 不使用真实用户 Cookie/凭据,不接管故障标签页;登录态完整 Next 点击验收待 `docs/testing/sidebar-navigation-loop-20260919.md`。
|
||||
- Windows 基线与修复树均复用相同 node_modules;为避免 Turbopack 外部 junction 限制,用相同 `npm run build -- --webpack` 对照,具体结果如实记录。
|
||||
- 本会话此前 `pre_work_check.py` 远端 verified,但两条碎片扫描断言失败(镜像路径字面量与候选/残留统计边界),不属于本轮前端修复,不改断言掩盖失败。
|
||||
- Skill 版本不变。
|
||||
@@ -7,6 +7,8 @@
|
||||
- 被环境或依赖挡住的事项写进根目录 `BLOCKED.md`;Bug 事实写进 `docs/BUG_HISTORY.md`;两者不在这里重复。
|
||||
- 纯文档改动不触发 staging 门禁(见 `deploy/gated-paths.txt`)。
|
||||
|
||||
- [侧栏导航注册循环修复与验收](PROGRESS-sidebar-navigation-loop-20260919.md) — 2026-09-19,修复/回归已完成,完整验收与 staging 交付状态见记录。
|
||||
|
||||
## 状态板
|
||||
|
||||
这一节是所有任务的总板。任务书推上来时加一行,状态变化时改这一行,不另开文件。
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# 侧栏修复 · 同环境全量失败逐项对照
|
||||
|
||||
基线 `507ef959`:3481 tests / 3403 pass / 78 fail。修复:3486 tests / 3408 pass / 78 fail。按测试名及出现次数比较,新增/消失失败均为 0;下列 78 项在两边均失败,不作为通过。未收录原始输出或任何凭据。
|
||||
|
||||
| 测试名 | 基线 | 修复 |
|
||||
| --- | --- | --- |
|
||||
| `admin code functions reject immutable codes, revoked redemption, and roll back on audit failure` | fail | fail |
|
||||
| `operation-level admin email reauthentication is removed from routes and UI` | fail | fail |
|
||||
| `rectification agent maps setup failures without calling the rest of the handler` | fail | fail |
|
||||
| `renders the persisted Beam avatar in the sidebar, account menu, and profile editor` | fail | fail |
|
||||
| `public code never imports the internal dynamic choice contract` | fail | fail |
|
||||
| `tests\\birth-time-guide-agent.test.ts` | fail | fail |
|
||||
| `candidate scores stay out of the specified client ownership boundary` | fail | fail |
|
||||
| `personal Agent exposes the Jyotish Skill and named server tool` | fail | fail |
|
||||
| `general agent runtime has no Jyotish skill package and no personal chart tool` | fail | fail |
|
||||
| `the route's own strict checklist reaches the answer, not just the package listing` | fail | fail |
|
||||
| `a route the skill declares no checklist for is reported, not filled in with another route's` | fail | fail |
|
||||
| `health uses the skill's health-timing-strict checklist` | fail | fail |
|
||||
| `wealth uses finance-timing-strict as the live checklist, with the wealth alias in the heading` | fail | fail |
|
||||
| `a multi-domain plan carries every executed route's checklist once` | fail | fail |
|
||||
| `no plan can spend the answer's context on method` | fail | fail |
|
||||
| `the widest legal plan still fits the budget` | fail | fail |
|
||||
| `further reading offers the references the skill names, and only ones that exist` | fail | fail |
|
||||
| `the delivered method is quoted from the live skill tree` | fail | fail |
|
||||
| `tests\\consultation-workflow-request.test.ts` | fail | fail |
|
||||
| `admin customer reset clears only rebuildable application state` | fail | fail |
|
||||
| `service and restricted admin database identities stay separated` | fail | fail |
|
||||
| `Owner recovery grants only one currently loginable synced identity admin` | fail | fail |
|
||||
| `staging backups are encrypted, atomic, private, and retain the newest three` | fail | fail |
|
||||
| `rejects destructive backup directory aliases and symlink components before mutation` | fail | fail |
|
||||
| `rejects unsafe writable backup parents before creating the target` | fail | fail |
|
||||
| `rejects a direct canonical sticky shared backup directory before chmod` | fail | fail |
|
||||
| `creates every absent backup path component privately despite a permissive caller umask` | fail | fail |
|
||||
| `same-second backups publish once without overwriting the completed archive` | fail | fail |
|
||||
| `find enumeration failures preserve existing backups and do not report completion` | fail | fail |
|
||||
| `refuses full disks and removes a failed-pipeline partial file` | fail | fail |
|
||||
| `billing order adjustments and redemption reasons are atomic and audited` | fail | fail |
|
||||
| `billing, subscriptions, usage authorization, RBAC, and model publication remain transactional` | fail | fail |
|
||||
| `saving a published product forks a draft and delete retires or removes it` | fail | fail |
|
||||
| `append_consultation_question ignores thinking fields and enforces the physical JSON cap` | fail | fail |
|
||||
| `database env validator accepts punctuated literal required secrets` | fail | fail |
|
||||
| `database env validator rejects symlinks and unsafe modes` | fail | fail |
|
||||
| `database env validator enforces an explicit staging owner uid without printing values` | fail | fail |
|
||||
| `database env validator accepts a private valid file without printing values` | fail | fail |
|
||||
| `local PostgreSQL applies the reviewed business schema and serves authenticated business calls` | fail | fail |
|
||||
| `personal_reports.card_summary is nullable, owner-read, service-written, and length-capped` | fail | fail |
|
||||
| `personal report job migration is atomic, lease-bound, recoverable, and owner read-only` | fail | fail |
|
||||
| `longform appendices are owner-read, service-written, and never change report status` | fail | fail |
|
||||
| `personal report sections enforce owner-read RLS and service-owned durable transitions` | fail | fail |
|
||||
| `block_scan RPCs are service_role-only and advance a declared period` | fail | fail |
|
||||
| `redeem security: case-sensitive hashing, rate limiting, idempotency and order ownership` | fail | fail |
|
||||
| `read_report_candidate_range is service_role-only, returns only the window, and stays empty without rows` | fail | fail |
|
||||
| `self-hosted identity migration creates Better Auth tables with least privilege` | fail | fail |
|
||||
| `every color token used as a Tailwind utility is exposed through @theme` | fail | fail |
|
||||
| `staging model provider env preparation removes legacy settings and keeps one stable key` | fail | fail |
|
||||
| `staging env validator rejects selector drift, duplicates, and unsafe permissions` | fail | fail |
|
||||
| `production env validators accept only self-hosted production selectors and role URLs` | fail | fail |
|
||||
| `Better Auth supports shared user OTP/password sessions for admins` | fail | fail |
|
||||
| `job migration mirror is exact and preserves personal_reports as the document projection` | fail | fail |
|
||||
| `document v2 and durable job migrations are exact db/supabase mirrors` | fail | fail |
|
||||
| `document v2 migration preserves v1 reads and adds explicit product depth` | fail | fail |
|
||||
| `durable job migration defines the complete state, lease, retry, and identity contract` | fail | fail |
|
||||
| `durable job migration atomically enqueues reports and exposes lease RPCs only to service_role` | fail | fail |
|
||||
| `ingest P0: education kinds, batch confirm, opening focus reuse, precision lock` | fail | fail |
|
||||
| `PR-4 candidate decisions use server UUIDs, receipt-derived gates and separate acceptance/confirmation` | fail | fail |
|
||||
| `tests\\rectification-v9-agent.test.ts` | fail | fail |
|
||||
| `v9 migration applies on a fresh database and re-applies idempotently` | fail | fail |
|
||||
| `v9 open is atomic, idempotent and allows separate homepage cases` | fail | fail |
|
||||
| `v9 enforces profile gating, ownership and terminal read-only` | fail | fail |
|
||||
| `v9 evidence lifecycle: quote grounding, idempotency, confirm and revision lineage` | fail | fail |
|
||||
| `v9 legacy backfill maps statuses, keeps one resumable per user and is idempotent` | fail | fail |
|
||||
| `v9 agent api migration applies, seeds the runtime flag and guards consent` | fail | fail |
|
||||
| `v9 ignores a historical active minute and allows reselection within the live result` | fail | fail |
|
||||
| `tests\\skill-binding.test.ts` | fail | fail |
|
||||
| `immutable Skill migration enforces RPC-only identity, legacy adoption, and cascade-safe receipts` | fail | fail |
|
||||
| `checked-in registry verifies hashed product packages and leaves consult on the live skill` | fail | fail |
|
||||
| `path traversal and symlink escape fail closed` | fail | fail |
|
||||
| `symbolic links are rejected even when their target stays inside the project root` | fail | fail |
|
||||
| `live consult skill reads a hand-updated tree without a registry hash` | fail | fail |
|
||||
| `changed staging workflows are syntactically valid YAML` | fail | fail |
|
||||
| `live staging sync preserves env, state, incoming files, and encrypted backups` | fail | fail |
|
||||
| `live staging sync repairs nested deploy-tree drift without preserving foreign ownership` | fail | fail |
|
||||
| `first immutable deployment rolls back to validated local image IDs` | fail | fail |
|
||||
| `is-docs-only-range.sh decides from local history and refuses non-ancestor ranges` | fail | fail |
|
||||
@@ -0,0 +1,25 @@
|
||||
# 侧栏导航循环 · 测试站真人验收(2026-09-19)
|
||||
|
||||
关联 BUG-971;自动化/交付结果见 [进度记录](../tasks/PROGRESS-sidebar-navigation-loop-20260919.md)。以下项目尚未真人验收,不以匿名 HTTP 200 或最小 React host 测试替代。
|
||||
|
||||
## 前置
|
||||
|
||||
- 仅测试站 `https://staging.jyotisha.chat`;先核对 `/api/health` 的 `deployment.gitCommit` 为本轮代码提交。
|
||||
- 使用本人受控测试账号,不转交 Cookie、JWT、出生资料、会话内容或完整 HAR/curl;曾外发的会话凭据先退出登录并重新登录。
|
||||
- 开 Console 与 Network,清空已有记录。截图只保留脱敏错误、请求路径/状态码、部署 SHA。
|
||||
|
||||
## 核心复现与回归
|
||||
|
||||
1. 刷新首页,静置后点击「星盘」;地址应变 `/chart`,正文与高亮同时切换。回首页后分别点「星历」「我的报告」,验证 `/ephemeris`、`/reports`。
|
||||
2. 三个次级页互跳、浏览器前进/后退、回首页再点三入口,不能停在旧页或必须二次点击。
|
||||
3. Console 不应持续刷 `Maximum update depth exceeded`;静置时 Network 不应持续发送同类请求。
|
||||
4. `/api/ephemeris` 可能在指针进入/按下时预取,**不代表导航完成**。以 URL、目的页正文和路由响应共同判断;按需记录 `_rsc` 请求状态,不记录敏感查询参数。
|
||||
5. 切换受控测试会话、创建空会话、改名/置顶/归档/删除测试会话,确认高亮、列表和操作结果仍同步,不出现旧回调操作错误对象。不要操作真实重要内容。
|
||||
6. 有下一页会话时将列表滚至底部,验证分页可见、加载一次后稳定,没有 observer 重挂导致请求风暴;来回切页不重新拉全列表。
|
||||
7. 打开并关闭账户菜单及设置弹窗,验证正文 inert/恢复、Escape 和导航恢复正常;次级页侧栏保持只读且返回首页后恢复操作。
|
||||
8. 桌面展开/折叠侧栏、移动端抽屉各试三入口;点击后抽屉关闭、正文切换。若登录失效,应正常到登录页/只读回退,不持续刷新。
|
||||
|
||||
## 回报格式
|
||||
|
||||
- 部署 SHA、浏览器/视口、通过/失败的步骤编号。
|
||||
- 失败时仅提供脱敏 Console 错误和请求路径/状态码、URL 是否变化;不贴凭据或原始用户内容。
|
||||
Reference in New Issue
Block a user