Files
Jyotisha/docs/tasks/TASK-account-dialog-inert-20260918.md
T

96 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 账户弹窗被 `inert` 罩住点不动 + 校正快照失败两端静默(2026-09-18 第八轮)
> 基线:`origin/staging` @ `1061514f`(代码部分 = 已部署的 `877128ce`)。
> 触发:产品真机反馈「弹窗都没法点击,我都没法点击退出登录」;同一会话里生时校正在「已记录。这题先不计分,换一件事问。」之后再无下文,右侧盘面与时间轴候选也没有。刷新后弹窗仍点不动。
> BUG 编号起点:当前最大号 **BUG-965**;`TASK-secondary-page-entry-20260918.md` 已占 966~967,本单占 **BUG-968 ~ BUG-969**。
## 0. 事故实证
### 0.1 弹窗点不动(BUG-968,P0,代码级确认)
| 位置 | 事实 |
| --- | --- |
| `src/app/(app)/layout.tsx` `AppShell` | `<SidebarInset inert={registration?.insetInert}>{children}</SidebarInset>`,整个页面正文(`children`)都在这个 section 里 |
| `src/hooks/use-home-shell-registration.ts` `registerShellControls` | `insetInert: modalOpen` |
| `src/app/(app)/page.tsx` `modalOpen` | `activeAccountDialog !== null \|\| onboardingPaywallOpen` |
| `src/app/(app)/page.tsx` 渲染 `<AccountDialogOverlay>` | 在页面组件 JSX 里,**也就是在 `SidebarInset` 之内** |
| `src/components/account-dialog-overlay.tsx` | 纯 `<div className="account-modal-overlay">` + `<section role="dialog">`,没有 `createPortal` |
| `src/components/ui/sidebar.tsx` `SidebarInset` | `inert={Boolean(inert) \|\| (isMobile && openMobile)}` |
链条:点侧栏「个人资料 / 图库 / 设置 / 点数 / 退出登录」→ `activeAccountDialog` 非空 → `modalOpen=true` → `SidebarInset` 整块 `inert` → **弹窗自己也在 `inert` 子树里** → 关闭按钮、表单、「确认退出」全部不可点、不可聚焦。正文区同样全部死掉。`window` 级的 Esc 监听(`page.tsx` `closeOnEscape`)还能关掉弹窗,所以用户按 Esc 能脱困,但退出登录做不了。
回归点:`e4e73f56`(2026-09-17,「四个页面共用一份会话列表」)把 `SidebarInset` 从 `page.tsx` 搬进 `(app)/layout.tsx`。搬之前 `page.tsx` 是 `<SidebarInset inert={modalOpen}>…</SidebarInset>` **之后**才渲染 `<AccountDialogOverlay>`,弹窗在 inert 之外;搬之后弹窗变成 `children` 的一部分,落进了 inert 里。`877128ce` 含这个提交,staging 上 100% 复现,与标签页新旧无关(BUG-965 那条是另一个问题)。
### 0.2 校正快照拿不到,用户被留在开场状态(BUG-969,P1,investigating)
真实 Case(staging,2026-09-18 17:41–17:49)服务端链路逐段核过:
1. 开场采集题 → 两轮口述经历(各 3 步,末步 `rectification-set-focus`;第一轮已把采集焦点 `resolved`,并挂出服务端财务探针 2024-08,绑在该轮回复上)。
2. 「想不到了」走 `route.ts` 的 focus+choice 分支:分类器判 `answer_current_focus / unsure` → 映射 D → `applyRectificationChoice` 确定性回复「已记录。这题先不计分,换一件事问。」→ `persistNextInterviewAfterChoice` 同一瞬间落库财务探针 2025-09(active,`asked_turn_id` 绑该回复轮)。模型没跑,日志零告警。
3. 用 psql 取 `get_agentic_rectification_case_dossier` / `list_agentic_rectification_conversation_focuses` / `get_agentic_rectification_turn_receipt` 真实行,在本地用同版本代码跑 `dossierResponse`:`current_question`、`choice_card`(A–D 四项)、`turns[].question`(7 条轮次)、`latest_result`(17 个候选)、`question_source=focus` 全部完整;三个 RPC 合计 < 70ms,投影 ~45ms。
4. 客户端解析函数(`isRectificationCaseSnapshotPayload` / `parsePersistedRectificationTurns` / `parseRectificationChoiceCard` / `parseRectificationCandidateResult` / `currentQuestionFromSnapshot`)对这份载荷全部通过。
5. 产品截图(17:49,未刷新):无卡;右侧「补充经历后,这里会显示…」占位;时间轴只有 14:00–15:00 无候选点;输入框占位是 `collect_waiting` 才会出现的「再说一件带年月的事」。即客户端仍停在**开场时**的快照(`collect_evidence`、无结果、无当前题),17:46 之后三次 `loadCaseSnapshot` 都没有生效。
查不下去的原因(这就是本单要修的):
- `GET /api/rectification/cases/[caseId]` 任何异常都返回 503,**不写日志**;`case_session_mismatch` 409 也不写。
- 客户端 `loadCaseSnapshot` 与 `hydrateRectificationCase` 把非 2xx / 抛错一律吞掉,页面不显示任何提示,停在旧状态。
- Caddy 与 Next 都不记这条 GET 的访问日志;PG `track_functions=none`、`log_statement=none`。事后无法证明请求到没到服务端。
- 确定性回复只流 ack,题干靠 `asked_turn_id` 经 GET 挂上(BUG-525 设计)。快照一失败,用户看到的就是「换一件事问」后空白。
补充观察(产品口径,本单不做):「想不到了」在语义上是在答「想到几件说几件」(采集题已被服务端悄悄 resolved),却被绑到用户没在看的财务探针上判成 D,然后又出一道同家族财务题。是否允许"刚记完经历的下一句"优先按采集语义解释,需要产品拍板后另立单。
## 1. 决策记录
- BUG-968 是 `e4e73f56` 引入的回归,不推翻任何既有红线;修法是**让弹窗回到 inert 子树之外**,不是去掉 inert(inert 是 BUG-744~746 侧栏统一时定下的焦点合同,保留)。
- BUG-969 本轮只做"可观测 + 不静默 + 题干进正文"三件事,不改分类器、不改采集/探针状态机;产品口径另立单。
- 「刷新即恢复」不再作为任何一条的关闭依据:这次刷新后弹窗依旧点不动。
## 2. 硬红线
1. 揭幕后不得出现 spinner / 骨架 / 「正在加载」;快照失败的提示用一句状态文案 + 重试入口,对照 `frontend/docs/VOICE.md`。
2. 不得为救 BUG-968 去掉 `SidebarInset` 的 `inert` 或改成 `pointer-events` 糊弄;移动端抽屉的 `isMobile && openMobile` 分支不动。
3. 不得动 `.gitea/workflows/**`、DNS、数据库结构;不顺手升级依赖。
4. `tsc --noEmit` 0 错、`npm run lint` 0 error、`npm test` 失败数不超过基线 31 条且清单一致;测试总数不低于开工实测。
5. 改 UI 的同一提交更新 `frontend/DESIGN.md`。
## 3. 任务分解
### 3.1 BUG-968(P0)弹窗移出 inert 子树
- 把 `AccountDialogOverlay`(及同类:onboarding 付费墙)渲染到 `SidebarInset` 之外。两种做法二选一,进度记录里写明选了哪个和原因:
- (a) `createPortal(…, document.body)`,保留现有 DOM 结构与样式;
- (b) 通过 `registerShellControls` 把弹窗节点交给 `AppShell`,由 layout 在 `SidebarInset` 的兄弟位置渲染。
- 验收:
- 合同测试:`modalOpen=true` 时,`role="dialog"` 元素不在带 `inert` 的祖先之内(用 `renderToString` 或 DOM 断言均可);`inert` 仍落在 `SidebarInset` 上。
- 现有 `tests/account-dialog-overlay.test.ts`、`tests/sidebar-contract.test.ts`、`tests/chat-navigation-a11y-contract.test.ts` 全绿;焦点圈禁(`keepFocusWithin`)与 Esc 关闭行为不变。
- 真机清单(`docs/testing/`):五个账户弹窗每个都能点关闭、能提交、「退出登录」能确认退出;弹窗开着时正文区仍不可点。
### 3.2 BUG-969(P1)快照失败不再静默
1. `GET /api/rectification/cases/[caseId]`:每一个非 200 分支 `console.warn` 一条 JSON(`event`、`case_id`、`status`、`code`、`reason=safeToolErrorCode(error)`,不含用户资料),包括 401、400、409、404、503。
2. 客户端 `loadCaseSnapshot` / `hydrateRectificationCase` / `refreshRectificationCase`:非 2xx 或抛错时把结果带回调用方(状态码或 `network`),页面在校正面显示一句状态文案 + 「重新读取」按钮,不得继续装作无事;已有的 `questionGap === "unavailable"` 分支可复用文案。
3. 确定性回复(`persistApplied` 与 `persistCollectDenialTurn` 的 `assistantMessage`)在下一题是可渲染选择题时,正文追加一句题干(沿用 `composeCollectSpokenAssistantText` 的口径),流给客户端的文本也带上;卡片仍由 GET 挂上,`stripQuestionSentences` 负责去重,不得出现题干两遍(BUG-559/609 的去重合同要过)。
4. 验收:
- 合同测试:GET 路由 503 分支产生 warn;客户端 fetch mock 返回 503 时校正面出现状态文案与按钮;确定性回复文本含题干且 `attachQuestionsToTurns` 后题干只出现一次。
- 真机清单:在校正会话里连发两轮经历后,卡片、右侧盘面、时间轴候选点必须在回复落定后出现;拔网线/断网再发一轮,页面要出现状态文案而不是空白。
- 若真机仍复现"快照拿不到",凭第 1 条的服务端日志把 BUG-969 转成 confirmed 并补根因;拿不到日志不得写 resolved。
## 4. 让步顺序
3.1 必须做完。3.2 按 1 → 2 → 3 的顺序,做不完的写进 `BLOCKED.md` 与进度记录,不得砍 1。
## 5. 开工前置
```bash
git fetch origin --prune
git worktree add -b codex/account-dialog-inert-20260918 \
.worktrees/account-dialog-inert-20260918 origin/staging
cd .worktrees/account-dialog-inert-20260918/frontend
npm test 2>&1 | grep -E "^# (tests|pass|fail)" # 记下开工基线
```
与 `TASK-secondary-page-entry-20260918.md` 同时改 `(app)/layout.tsx` 时,本单先合,对方 rebase。
收工:`docs/tasks/PROGRESS-account-dialog-inert-20260918.md` + `docs/BUG_HISTORY.md`(BUG-968 转 resolved、BUG-969 按证据定状态)+ `frontend/DESIGN.md` + `docs/testing/account-dialog-inert-20260918.md`,与代码同一批推 `staging`,并把 `docs/tasks/README.md` 状态板那一行改掉。