Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
9.4 KiB
验收修复单 · 会话列表单一数据源:四条源码合同没跟着外壳搬家,门禁卡住(2026-09-17)
0. 基线
- 验收对象:
e4e73f56(会话列表单)与6d81062b(校正输入框守卫单),当前都在origin/staging,headcc1a8980。 - 分支:
codex/session-list-single-source-fix-20260917,从origin/staging起。 - 范围:
frontend/tests/四个合同测试;tests/test_daily_and_rectification_entrypoints.py;frontend/src/app/(app)/page.tsx(F3,只允许减行或持平);docs/。不动 provider、不动排序、不动路由组。 - BUG 段:BUG-933 起(基线最大号 BUG-932)。
- 本单是 staging 能否部署的唯一拦路石:
/api/health仍是dc2f2a16,今天四个代码提交一个都没上线。
1. 验收结论
校正输入框守卫单 6d81062b:通过
| 任务 | 结果 |
|---|---|
T1 send() 会话类型守卫 |
通过。consultSendBlockedByRectificationSession 在扣点与撤回窗口之前拦下,保留草稿,转 openRectificationSession;刷新恢复重放同样受守卫,pending 存储里指向校正会话的记录直接丢弃 |
| T2 普通输入框只有禁用态 | 通过。inputDisabled / submitBlocked 都加了 activeRectificationSession,占位文案「正在打开生时校正…」,打开失败出「重新打开生时校正」按钮并先清 rectificationError |
| T3 三条回退路径 | 通过,且多修了归档一条。fallbackSessionId 优先非校正会话,落到校正会话时走 selectSession 的延迟切换 |
| T4 服务端错误码 | 通过。类型不对回 409 session_not_consultation(在扣点预留之前,不扣点),真查不到仍 404;客户端 payloadCode 把 code 带到 ConsultationResponseError,命中后回滚乐观消息、不显示报错、直接开校正面 |
| T5 记录 | 通过。BUG-924 / 925、CHANGELOG、DESIGN、VOICE、真机清单齐 |
page.tsx 行数 |
1830 → 1830 |
残留一条 P3,见 F3。
会话列表单 e4e73f56:未通过(P1)
实现本身按任务书落地:多键排序(ordering 改数组 + formatOrderClause)、(app) 路由组 + (app)/layout.tsx 常驻外壳 + SessionListProvider、app/ 下 SidebarProvider 恰好一次、use-sidebar-data.ts 与 sidebar-data-cache.ts 已删、服务端排除 messages = [] 并单独回一条 draft 供启动复用、标题改「类别 · M月D日」并删 uniquify、toSidebarSessionRow 两处侧栏共用。两条让步(元数据操作未上移、延迟落库未做)任务书都允许。
但 npm test 多出 4 条红,且这 4 条会让 staging 门禁必红。
.gitea/workflows/backend-quality-gate.yml 的 Validate backend, package, frontend, and database contracts 步骤在 set -euo pipefail 下依次跑 npm test --prefix frontend、npm run lint --prefix frontend。这 4 条是纯源码正则合同,不依赖 Docker、不依赖环境,在门禁机上照样红:
| 测试文件 | 断言找不到的东西 | 它现在在哪 |
|---|---|---|
chat-navigation-a11y-contract.test.ts |
<main className="chat-app"> 紧跟 replyAnnouncement 的 sr-only 播报 |
<main> 在 (app)/layout.tsx,播报仍在 page.tsx,只是不再相邻 |
rectification-history-open-20260909.test.ts |
page.tsx 里 openErrorSessionId={rectificationErrorSessionId} |
(app)/layout.tsx 从 registration.openErrorSessionId 传给 AppSidebar |
rectification-surface-contract.test.ts |
page.tsx 里 openingSessionId={rectificationOpeningSessionId} |
同上,经 registration.openingSessionId |
chat-session-url.test.ts |
page.tsx 里 const [nextAccount, modelCatalogResult, sessionsPayload] |
启动改成 await sessionListReady + sessionListBoot(),这个解构没了 |
四条被保护的性质都还成立(/ 仍 ○ Static,侧栏行仍拿得到 opening / openError,播报仍在),所以是合同陈旧、不是行为回归。但它们被留在红里,等于把门禁堵死。
根因是流程:执行方只跑了定向测试(进度记录写「定向 26 + 先前 114/115」),没跑全量 npm test,也因此没发现同批次带进的两处 TS 错(session-sidebar-row.ts(30,41)、chart-library-session.test.ts(89,42),进度记录写着「tsc 0 错」,实测 2 错,后来由滚动单的 cc1a8980 顺手修掉)。
2. 任务分解
F1 四条源码合同跟着外壳搬家(BUG-933)
四条都按「原值 / 新值 / 原因」三栏改写,不得删除测试、不得弱化成只判存在:
chat-navigation-a11y-contract.test.ts:改为分别断言(app)/layout.tsx里有且只有一处<main className="chat-app">,且page.tsx里replyAnnouncement的 sr-only 播报仍带role="status" aria-live="polite" aria-atomic="true",并且它是SidebarInset内容的第一个子节点。rectification-history-open-20260909.test.ts、rectification-surface-contract.test.ts:改为断言(app)/layout.tsx把openErrorSessionId/openingSessionId/openErrorMessage传给AppSidebar,且page.tsx的registerShellControls载荷里带这三个字段(两端都断,缺一端就等于断链)。chat-session-url.test.ts:把「首页自己并发拉账户/目录/列表」的断言改成「首页await sessionListReady后读sessionListBoot(),并且page.tsx里不再出现fetchSessions(」,保留它原本要守的「/是静态路由上的客户端读」这一性质。- 验收:
npm test --prefix frontend的失败清单与cc1a8980比少这 4 条、不多任何一条;npm run lint0 error。
F2 两条陈旧的 Python 入口合同(BUG-934)
tests/test_daily_and_rectification_entrypoints.py 断言首页出现 daily-starlanguage-card / birth-rectification-card,这两个类名在 62903b7a(空状态改成「问候 + 输入框 + 两枚入口 pill」)就已删除,全仓已无。这是既有欠账,不是本批引入,也不在 CORE_PYTEST_TARGETS 里,所以不挡门禁,但任何一次 Python 定向跑都会红。
- 改为断言当前真实的入口形态(
StarterEntries的两枚 pill 与startDailyStarlanguageConsultation/ 校正入口),或在确认无对应实现后连同该断言一并删除并在测试里写明原因。选哪种由执行方判断后写进进度记录。 - 验收:
.venv/bin/python -m pytest tests/test_daily_and_rectification_entrypoints.py全绿。
F3 校正守卫的残留死角:一个会话都不活跃时输入框静默吞发送(BUG-935)
page.tsx 的 activeSession 现在是 sessions.find(id === activeSessionId) ?? sessions.find(非校正)。当列表里只剩校正会话时它是 undefined(旧代码 ?? sessions[0] 不会)。此时:activeRectificationSession 为假 → 普通输入框可用;按发送 send() 因 !currentSession 直接 return false,没有任何提示;因为 activeRectificationSession 为假,「重新打开生时校正」按钮也不出现。
触发路径:账号里只有校正会话,或删除当前普通会话后剩下全是校正会话,且此时校正面正在打开或打开失败。窄,但正是本单要消灭的那类死角。
- 修法:
activeSession为空且列表里存在校正会话时,按「校正会话占位」处理——输入框禁用、占位文案「正在打开生时校正…」、打开失败出重开按钮;派生值放进现有 hook 或rectification-session-composer-guard.ts,page.tsx不增行。 - 验收:
rectification-session-composer-guard.test.ts加一条纯函数断言(输入「列表全是校正会话、activeSession 为空」→ 锁定态为真);send()在无活跃会话时不得静默返回而不给任何反馈。
F4 记录
docs/BUG_HISTORY.md新增 BUG-933 / 934 / 935(933 关联 BUG-927 与 BUG-745;935 关联 BUG-924)。- BUG-927 的「防复发」补一条:外壳搬家必须同步搬走针对
page.tsx的源码合同,且交付前必须跑全量npm test与tsc --noEmit,不得只跑定向。 docs/tasks/PROGRESS-session-list-single-source-fix-20260917.md;状态板三行改状态。- T2 的「元数据操作未上移到 provider」目前只写在进度记录里,补进
BLOCKED.md,与延迟落库那条并列。
3. 硬红线
- 不得靠删测试或放宽断言过门;四条合同必须仍然守住原来的性质。
- 不动排序、provider、路由组的既有行为。
page.tsx不增行。- 交付前必须跑全量
npm test --prefix frontend、tsc --noEmit、next build --webpack,并把失败清单与cc1a8980逐条比对后写进进度记录。
4. 开工前置命令
git fetch origin --prune
git worktree add -b codex/session-list-single-source-fix-20260917 .worktrees/session-list-single-source-fix-20260917 origin/staging
cd .worktrees/session-list-single-source-fix-20260917/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -20
5. 验收口径
tsc --noEmit 0 错;npm run lint 0 error;npm test 相对 cc1a8980 少 4 条红、不新增;next build --webpack 通过且 / 仍 ○ Static、首屏 gzip ±2%;pytest tests/test_daily_and_rectification_entrypoints.py 全绿。合入后必须核对 /api/health 的 deployment.gitCommit 等于最近一次含门禁路径的 staging 提交——今天四个代码提交至今一个都没上线。