# TASK · 生时校正时间轴修复单:宽度读数与卡片/报告口径不一致;被排除的分钟根本到不了客户端,"空心点"画不出来(2026-09-09) - 基线:`origin/staging` @ `724a1215`(时间轴 `ce91a0d2` 已合入并部署) - 分支:`codex/rectification-timeline-fix-20260909`,基于 `origin/staging` - 执行方:coding agent;验收:Claude - 涉及文件:`frontend/src/lib/rectification-timeline-scale.ts`、`frontend/src/components/rectification-agentic-chat.tsx`(`timelineView` 的 `candidateTimes` 来源)、`frontend/src/lib/rectification-candidate-result.ts`(或直接读 `decisionReceipt.inference_state.candidates`)、`frontend/tests/rectification-timeline-20260909.test.ts`、`frontend/DESIGN.md` §10 - BUG 编号起点:**BUG-602**(`docs/BUG_HISTORY.md` 当前最大 BUG-601) - 优先级:**P1**(两处都是"条上写的和别处不一样",用户会当成算错) ## 1. 事实 ### 1.1 BUG-602:宽度按差值算,卡片与报告按含两端算 `timelineDurationLabel(bandEnd - bandStart)`:04:51–04:59 显示"8 分钟",05:07–05:09 显示"2 分钟",整天窗口显示"23 小时 59 分"。而 BUG-593 已把交付报告与三列卡的宽度定为**含两端的分钟数**:04:51–04:59 = 9 分钟、04:51–04:53 = 3 分钟(`rectification-delivery-report-facts.test.ts`)。同一屏上条写 8、卡写 9。任务书 §4.3 的示例"05:07–05:09 · 2 分钟"本身就是差值口径,执行方照抄了。 ### 1.2 BUG-603:客户端候选列表只含 active 分钟,被排除的分钟不会变空心、而是消失 `inference-adapter.ts` L292 `candidates = active.flatMap(...)`:投影只保留 `status !== "eliminated"` 的候选;`rectification-agentic-chat.tsx` 把 `candidateResult.candidates.map(time)` 喂给时间轴,`buildRectificationTimeline` 再按"是否落在区间带内"判 in/out。结果:分钟阶段所有到达客户端的点都在带内、全是实心;答完一题被淘汰的分钟直接从条上消失。DESIGN.md §10 写的"被排除的点留在原地变空心、不消失——用户由此看见自己刚才那一答排掉了哪几分钟"在真实数据下不成立;测试里的 `["out","in","in","in","out"]` 是合成输入。另外 `credible_range` 是 active 候选的跨度,带内若有被淘汰分钟(可能出现)也会被按位置判成 in。 ## 2. 决策记录 1. **宽度统一为含两端分钟数**:`widthLabel = timelineDurationLabel(bandEnd - bandStart + 1)`;整天窗口显示"24 小时";单分钟显示"1 分钟"。DESIGN.md §10 示例改为"05:07–05:09 · 3 分钟"。 2. **标记来源改为推断层候选全集**:客户端从 `decisionReceipt.inference_state.candidates`(有 `time`、`status`)取点;`state = status === "eliminated" ? "out" : "in"`,不再按位置判。没有 `inference_state` 时退回现有行为(引擎候选全部实心)。`RectificationCandidateResult` 加只读字段 `inferenceMarks: [{time, eliminated}]`,解析放在 `rectification-candidate-result.ts`,组件不碰 receipt。 3. 二元编码、只读、固定条高、grid 行、transform-only 全部不动;`use-conversation-scroll-anchor.ts` 仍一行不改。 ## 3. 验收 - (a) 04:51–04:59 → "9 分钟";05:07–05:09 → "3 分钟";00:00–23:59 整天 → "24 小时";`rectification-delivery-report-facts.test.ts` 的同一形状与条读数一致。 - (b) 推断层 9 个候选、7 个 eliminated、`credible_range` 04:51–04:53 → 条上 9 个点,7 空心 2 实心;下一轮再淘汰 1 个 → 8 空心 1 实心,点的 key 不变(过渡不重建)。 - (c) 无 `inference_state`(时段阶段刚选定)→ 引擎候选全部实心,行为同今天。 - (d) 三栏:`widthLabel` 用例原值/新值/原因;合成 marks 用例改为按 status。 - (e) 记录:BUG-602(关联 BUG-593)、BUG-603(关联 BUG-560、DESIGN §10);`CHANGELOG.md`;`PROGRESS-…`;`docs/testing/rectification-timeline-20260909.md` §4 第一条改为"被排除的分钟变空心且不消失(数据来自推断层)"。 ## 4. 开工前置命令 ```bash git fetch origin --prune git worktree add -b codex/rectification-timeline-fix-20260909 .worktrees/rectification-timeline-fix-20260909 origin/staging cd .worktrees/rectification-timeline-fix-20260909 ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules cd frontend && ./node_modules/.bin/tsc --noEmit; npm run lint 2>&1 | tail -1 npx tsx --test tests/rectification-timeline-20260909.test.ts tests/rectification-delivery-report-facts.test.ts 2>&1 | grep -E "^# (tests|pass|fail)" ``` ## 验收(Claude,2026-09-09,`origin/staging` @ `04ad3325`;staging 当时部署 `fa293b31`) | 门 | 结果 | | --- | --- | | tsc | 0 错 | | lint | 0 error / 108 warning | | 前端 rectification + consultation + session + voice + skill-registry(非 DB) | 1347 / 0 | | Python v5_services + event_probes + growth contract | 72 passed / 0 failed | | 项 | 结论 | | --- | --- | | BUG-602 宽度含两端 | 通过。`bandEnd - bandStart + 1`;七组三栏齐全,与 BUG-593 报告口径一致 | | BUG-603 空心点来自推断层 | 通过。`inferenceMarks` 由 `decisionReceipt.inference_state.candidates` 解析,`eliminated` 是唯一判据;无推断层退回全实心 | | 偏离 2(`inferenceMarks` 可选、解析器始终赋值) | 接受 | 结论:通过(实现 `92e3d5e7`)。