diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 36981ef4..42590027 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -106,7 +106,7 @@ | `TASK-rectification-dead-d9-choice-fix-20260916.md` | `PROGRESS-rectification-dead-d9-choice-fix-20260916.md` | **验收修复单**:自建分盘探针照 `withNakshatraBoundaryProbe` 模式在每个读 state 的地方(盖戳 / 答题 / `previousInferenceFromReceipt` / GET 投影 / idle 过期判定 / 计划承接)从 receipt 确定性重建(BUG-915);测试改走生产路径、删手工塞探针的 fixture;顺带 host 前置挪到会话校验之后、登记 phase(BUG-916);死卡不得配 `collect_waiting` 占位(BUG-917);**产品决策 1c**:风格题默认不作全额计分(沿用 09-09 ±1 拍板),无事件探针就走下一条线。**staging 现状:风格题答不了,先跳过**。§1d 已记 **产品决策 (b)** | 已验收 | `5113d457`(BUG-915~917,产品拍板 (b) 风格题不计分)。Claude 独立验收:生产路径测试(receipt 不含自建探针)答题 applied / GET 出卡 / idle 不误伤;合并候选门禁 tsc 0 / lint 0 error / npm test 3427 条 31 红与基线逐条相同 / `/` Static / 首屏 gzip 620,107→620,193(+0.01%)/ pytest 63 绿 / 快速门 Python 798 绿。真机清单 `docs/testing/rectification-dead-d9-choice-fix-20260916.md` | | `TASK-rectification-mobile-timeline-readout-20260917.md` | `PROGRESS-rectification-mobile-timeline-readout-20260917.md` | 手机截图:时间轴读数第四项被裁成「已…」(nowrap + inset 内边距,BUG-918);「跳到最新」浮层压住选项 C(BUG-919)。灰卡与「再说一件」矛盾归修复单 BUG-915/917 | 已验收 | `e8e98bbd`(合入时重放为本分支提交,BUG-918/919)。时间轴相关 71 条 + 全量同上;手机上隐藏「已对照 N 件」符合 DESIGN §10;桌面是否也移除待产品拍板。真机清单 `docs/testing/rectification-mobile-timeline-readout-20260917.md` | | `TASK-mobile-viewport-scroll-lock-20260917.md` | `PROGRESS-mobile-viewport-scroll-lock-20260917.md` | iPhone 上键盘收起 / 刷新后整页上移、顶栏点不到(BUG-920):iOS 不支持 `interactive-widget`,键盘弹出时 Safari 滚动 window,`html/body overflow: hidden` 让用户拉不回来,reload 又还原 `scrollY`;代码里无任何 window 级复位。补 `ViewportScrollLock`(`scrollRestoration=manual` + `visualViewport` 复位)。与今天两单无关,既有缺陷。**T3 顶栏积分块「有点扁」(BUG-921)**:两枚芯片 44px 塞在 46px 顶栏里、字号/内边距/圆角各一套、积分块强制 64px 最小宽度;统一尺寸并把手机顶栏放到 52px | 待验收 | `codex/mobile-viewport-scroll-lock-20260917` | -| `TASK-rectification-session-composer-guard-20260917.md` | `PROGRESS-rectification-session-composer-guard-20260917.md` | 校正会话激活但校正面未开(刷新揭幕超时 / 打开失败不重试 / 删除与 popstate 回退落到列表第一条)时露出可用的普通输入框,问题发到 `/api/consult` 回 404「咨询会话不存在」;`send()` 无会话类型守卫;服务端无独立错误码。关联 BUG-505。BUG 段 924 起 | 待验收 | `codex/rectification-session-composer-guard-20260917` | +| `TASK-rectification-session-composer-guard-20260917.md` | `PROGRESS-rectification-session-composer-guard-20260917.md` | 校正会话激活但校正面未开(刷新揭幕超时 / 打开失败不重试 / 删除与 popstate 回退落到列表第一条)时露出可用的普通输入框,问题发到 `/api/consult` 回 404「咨询会话不存在」;`send()` 无会话类型守卫;服务端无独立错误码。关联 BUG-505。BUG 段 924 起 | 已验收 | `6d81062b`(Claude 验收:T1–T5 全通过;409 `session_not_consultation` 在扣点前、不扣点;回退路径多修了归档一条;page.tsx 1830→1830。残留 P3:列表只剩校正会话时 `activeSession` 为空,输入框可用但发送静默无效 → BUG-935 在 fix 单);真机三条欠 | ### 聊天主链路与首页 @@ -131,7 +131,8 @@ | `TASK-rectification-p0-fix-20260915.md` | `PROGRESS-rectification-p0-fix-20260915.md` | **验收修复单**:`f51e494c` 六条缺陷全部实现且方式正确,但 `page.tsx` 从 1951 涨到 1964 行,撞了 `chart-view-route.test.ts` 的 `<= 1951` 上限(AGENTS.md §6 增长冻结)。全量 fail 32→33,就这一条。门禁红很可能是 staging 停在 `2d7698ea`、6 个提交未部署的原因。修法是把 BUG-705 的十来行接线搬出 page.tsx,不放宽上限 | 待验收 | `codex/rectification-p0-fix-20260915` | | `TASK-settings-dialog-size-and-nav-20260915.md` | — | **复发单**:设置弹窗四个分区尺寸仍随内容跳变(BUG-698,复发自 BUG-554——旧防复发只查「有没有写 height」,查不到「写了没生效」);首要嫌疑是 `.settings-modal` 的 `dvh` 没有 `vh` 回退,不支持时整条 `height` 作废退化成内容高度,需先复现确认。另按产品要求去掉分区菜单左侧强调条,并拆开与悬停共用的选中态 | 待领取 | `codex/settings-dialog-size-and-nav-20260915` | | `TASK-consult-followup-tool-contract-20260917.md` | `PROGRESS-consult-followup-tool-contract-20260917.md` | 真机:申报时段会话连发「?」「你在说什么鬼」都 `run.failed runtime_contract_incomplete`,回执无任何 `tool` 步骤。根因是 Agent 系统指令写明「简单追问可复用已有 packet / context、不调工具」,而 `contractReady()` 要求每次请求恰好一次成功排盘调用;「已有 packet」跨请求并不存在(缓存只在单次请求内)。本命与窗口两个 Agent 同构。**产品拍板方案 1**:每轮必调工具(BUG-922 删例外句 + BUG-923 第 0 步 `toolChoice: required`);否决「没调工具就走不扣点纯对话」。第一轮正经问题为何失败留 T4 取证(回执只在 web 容器日志) | 待验收 | `codex/consult-followup-tool-contract-20260917` | -| `TASK-session-list-single-source-20260917.md` | `PROGRESS-session-list-single-source-20260917.md` | 会话列表一处数据源:本地 PG 兼容层 `order()` 只保留最后一键,`/api/sessions` 实际按 `id` 排、与游标不一致;`/` 与次级页两份数据源、`/` 每次回来重启动(产品拍板:首页与三个次级页进同一路由组,侧栏外壳与列表 provider 常驻 layout,不做 store);空「新对话」落库堆积(首页 50 条里 28 条);标题类别在后、同名靠墙钟 HH:MM。串行在 composer-guard 单之后。BUG 段 926 起 | 待验收(T1–T4;延迟落库让步) | `codex/session-list-single-source-20260917` | +| `TASK-session-list-single-source-20260917.md` | `PROGRESS-session-list-single-source-20260917.md` | 会话列表一处数据源:本地 PG 兼容层 `order()` 只保留最后一键,`/api/sessions` 实际按 `id` 排、与游标不一致;`/` 与次级页两份数据源、`/` 每次回来重启动(产品拍板:首页与三个次级页进同一路由组,侧栏外壳与列表 provider 常驻 layout,不做 store);空「新对话」落库堆积(首页 50 条里 28 条);标题类别在后、同名靠墙钟 HH:MM。串行在 composer-guard 单之后。BUG 段 926 起 | 验收未通过 | `e4e73f56`(实现按任务书落地:多键排序 / `(app)` 路由组常驻外壳 / provider / 空会话不入列 + draft 复用 / 标题类别在前;两条让步任务书允许。**P1:4 条针对 `page.tsx` 的源码合同没跟着外壳搬家,`npm test` 在门禁里 `set -e` 下必红,staging 至今停在 `dc2f2a16`**。另:进度记录报「tsc 0 错」实为 2 错,只跑了定向测试。修复单 `TASK-session-list-single-source-fix-20260917.md`) | +| `TASK-session-list-single-source-fix-20260917.md` | — | 验收修复单:F1 四条源码合同搬到 `(app)/layout.tsx` 两端对断(BUG-933);F2 两条陈旧 Python 入口断言(BUG-934,既有欠账);F3 无活跃会话时输入框静默吞发送(BUG-935);交付前必须跑全量测试 | 待领取 | — | | `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 不再写留白);真机六条欠 | diff --git a/docs/tasks/TASK-session-list-single-source-fix-20260917.md b/docs/tasks/TASK-session-list-single-source-fix-20260917.md new file mode 100644 index 00000000..2900baff --- /dev/null +++ b/docs/tasks/TASK-session-list-single-source-fix-20260917.md @@ -0,0 +1,97 @@ +# 验收修复单 · 会话列表单一数据源:四条源码合同没跟着外壳搬家,门禁卡住(2026-09-17) + +## 0. 基线 + +- 验收对象:`e4e73f56`(会话列表单)与 `6d81062b`(校正输入框守卫单),当前都在 `origin/staging`,head `cc1a8980`。 +- 分支:`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` | `
` 紧跟 `replyAnnouncement` 的 sr-only 播报 | `
` 在 `(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` 里有且只有一处 `
`,且 `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 lint` 0 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. 硬红线 + +1. 不得靠删测试或放宽断言过门;四条合同必须仍然守住原来的性质。 +2. 不动排序、provider、路由组的既有行为。 +3. `page.tsx` 不增行。 +4. 交付前必须跑**全量** `npm test --prefix frontend`、`tsc --noEmit`、`next build --webpack`,并把失败清单与 `cc1a8980` 逐条比对后写进进度记录。 + +## 4. 开工前置命令 + +```bash +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 提交——今天四个代码提交至今一个都没上线。