docs(tasks): 账户弹窗被 inert 罩住(BUG-968)与校正快照两端静默(BUG-969)任务书

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
Jesse_Chen
2026-09-18 10:40:11 +00:00
co-authored by Claude Fable 5.1
parent 1061514fc0
commit bed961c4a1
3 changed files with 128 additions and 0 deletions
+32
View File
@@ -12617,3 +12617,35 @@
- 修复版本:`5b6abc23`
## BUG-968 | 账户弹窗打开后整个弹窗点不动,退出登录做不了
- 状态:investigating(根因已由代码确认,修复未做)
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:`src/app/(app)/layout.tsx` `AppShell``src/app/(app)/page.tsx` `AccountDialogOverlay` 渲染位置、`use-home-shell-registration.ts` `insetInert`staging `877128ce`
- 用户现象:点侧栏「个人资料 / 退出登录」等任一账户入口,弹窗出现但关闭按钮、表单、确认退出全部点不动;正文区同时点不动。刷新后依旧。
- 触发条件:任何让 `modalOpen` 为真的弹窗(五个账户弹窗、onboarding 付费墙)。
- 根因:`e4e73f56``SidebarInset` 搬进 `(app)/layout.tsx` 后,`inert={modalOpen}` 罩住了整个 `children`,而 `AccountDialogOverlay``page.tsx` 渲染、没有 portal,落在 inert 子树里。搬家前弹窗是 `SidebarInset` 的兄弟节点。
- 修复:待做,见 `docs/tasks/TASK-account-dialog-inert-20260918.md` §3.1。
- 验证:待做。临时脱困:按 Esc`window` 级监听不受 inert 影响)。
- 防复发:合同测试断言 `role="dialog"` 不在 inert 祖先内。
- 关联记录:BUG-744~746(侧栏统一)、BUG-926~929(外壳搬家)、BUG-965(跨部署标签页,另一问题)
- 复发自:无
- 修复版本:待发布
## BUG-969 | 生时校正「换一件事问」之后无下文:快照拿不到,两端都不吭声
- 状态:investigating
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:`GET /api/rectification/cases/[caseId]``rectification-agentic-chat.tsx` `loadCaseSnapshot``rectification-surface-state.ts` `hydrateRectificationCase`、确定性回复的题干投递
- 用户现象:口述两轮经历后说「想不到了」,回复「已记录。这题先不计分,换一件事问。」然后没有题卡;右侧盘面停在占位、时间轴无候选点、输入框占位是「再说一件带年月的事」。
- 触发条件:真实 Case 上服务端已落库下一道财务探针(active,绑在该回复轮),GET 快照本地用真实数据行重算完整、客户端解析函数全部通过;客户端却仍是开场时的快照状态。
- 根因:未定。已确认的是链路两端都静默:GET 路由所有异常 503 不写日志,客户端快照失败一律吞掉,边缘与应用层都无 GET 访问日志,PG 未开函数统计。事后无法证明请求是否到达服务端。
- 修复:待做,见 `docs/tasks/TASK-account-dialog-inert-20260918.md` §3.2(服务端非 200 记日志、客户端失败显示状态、确定性回复正文带题干)。
- 验证:待做。
- 防复发:确定性回复不得只靠 GET 挂卡投递题干;快照失败不得静默。
- 关联记录:BUG-525(题干经 asked_turn_id 挂上)、BUG-930(回答钉顶:只见最后一轮是设计)、BUG-965
- 复发自:待确认
- 修复版本:待发布
+1
View File
@@ -142,6 +142,7 @@
| `TASK-contract-degraded-pass4-20260918.md` | — | 验收 review:BUG-956 新增的降级交付路径绕过 Pass 4,保证句原样送达(BUG-959);`uncontractedText` 跨 attempt 不清零,同一轮正文说两遍(960);降级后还空跑一轮 compose(961)。基线 `877128ce` | 待领取 | — |
| `TASK-chat-markdown-list-20260918.md` | — | 真机排版反馈:聊天正文列表**没有项目符号**Tailwind v4 preflight 清了 `list-style``.markdown-list` 没恢复,BUG-962);`promoteDefinitionLists` 把四标题口语体的散文误判成并列项,三段正文被改写成列表(BUG-963,判据太松,收紧而非删除——BUG-356 的场景要留);思考条与正文之间实测 56px=报告 grid gap 24 + 首标题 margin-top 32,后者是被特指度压掉的「首元素清零」规则(BUG-964)。基线 `ea0280c1` | 待领取 | — |
| `TASK-secondary-page-entry-20260918.md` | — | 真机反馈:星盘 / 星历 / 报告进入时抖一下——三页都是「矮的等待文案块 → 高的正文块」一次推挤,且 `use-chart-page` 无缓存所以每次进入都抖(BUG-966)。产品已拍板方案一:统一外壳 + 缓存 + 预取消灭中间态,**不加 spinner**,红线不动。另含 BUG-967:标签页跨过部署后客户端导航静默失效(BUG-965 已复现确认,刷新即恢复),要按 `NEXT_PUBLIC_GIT_COMMIT``/api/health` 比对自愈。基线 `41902067` | 待领取 | — |
| `TASK-account-dialog-inert-20260918.md` | — | 真机:账户弹窗打开后整个弹窗点不动、退出登录做不了,刷新依旧——`e4e73f56``SidebarInset` 搬进 layout 后 `inert={modalOpen}` 罩住了没有 portal 的 `AccountDialogOverlay`(BUG-968P0,代码级确认)。另含 BUG-969:校正「换一件事问」后无下文,服务端已出下一题且快照重算完整,客户端停在开场状态,GET 路由与客户端两端静默,本单只做可观测 + 不静默 + 题干进正文。基线 `1061514f` | 待领取 | — |
| `TASK-first-paint-dead-screen-fallback-20260917.md` | — | 真机:首页永远停在「正在载入账户」,兜底全在没跑起来的 bundle 里(BUG-936 investigating)。根 layout 加与 bundle 无关的内联兜底 + 去掉本仓正则后行断言 | 待领取 | — |
| `TASK-consultation-answer-start-anchor-20260917.md` | `PROGRESS-consultation-answer-start-anchor-20260917.md` | 主会话回答落在结尾:`useConversationScrollAnchor` 是贴底跟随,流式期间视口钉在最后一个字,回答开头滚出视口;改为发送后问题钉顶、回答向下长、长出视口显示「跳到最新」、末尾动态留白;产品追加拍板:校正面同一语义(推翻 BUG-041/048 贴底),本轮开头 = 用户行或新助手行。BUG 段 930 起 | 已验收(经修复单) | `worktree/green-harbor-5be3` |
| `TASK-consultation-answer-start-anchor-fix-20260917.md` | `PROGRESS-consultation-answer-start-anchor-fix-20260917.md` | 验收修复单:F1 头就是留白行时留白按整视口算(BUG-931);F2 留白只在钉住期间存在(BUG-932);前置:先修 e4e73f56 的两处 TS 错否则门禁不过 | 已验收 | `cc1a8980`Claude 验收:tsc 0 / lint 0 error / npm test 3457 条 39 红与 11c0028d 逐条一致、新增 2 条绿 / `next build --webpack` 通过、`/` Static、首屏 gzip 591,242(较 09-16 基线 582,800 +1.45%,含会话列表单)/ Chrome 真实布局 S1–S6 全部通过,S6 新助手行距顶 16px 且增高不动,S5 不再写留白);真机六条欠 |
@@ -0,0 +1,95 @@
# 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-968P0,代码级确认)
| 位置 | 事实 |
| --- | --- |
| `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-969P1investigating
真实 Casestaging2026-09-18 17:4117:49)服务端链路逐段核过:
1. 开场采集题 → 两轮口述经历(各 3 步,末步 `rectification-set-focus`;第一轮已把采集焦点 `resolved`,并挂出服务端财务探针 2024-08,绑在该轮回复上)。
2. 「想不到了」走 `route.ts` 的 focus+choice 分支:分类器判 `answer_current_focus / unsure` → 映射 D → `applyRectificationChoice` 确定性回复「已记录。这题先不计分,换一件事问。」→ `persistNextInterviewAfterChoice` 同一瞬间落库财务探针 2025-09active`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`AD 四项)、`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 子树之外**,不是去掉 inertinert 是 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-968P0)弹窗移出 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-969P1)快照失败不再静默
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` 状态板那一行改掉。