R7 / 决策 D10。任务书 docs/tasks/TASK-cend-surfaces-claude-alignment-20260916.md。 T7.1 常驻条。任务书 E9 说区间只在盘面标题行,实际读代码:对话区顶部 已有常驻条 RectificationTimeline 且已显示区间与宽度,窄屏 .is-compact 下 也可见。真正只存在于 .rectification-board__clock 的是代表分钟——窄屏盘面 是默认关闭的 overlay,代表分钟就此看不见。所以没有另造第二条(会撞固定条 高这条正确性约束),而是在既有条上补齐缺的两项: 05:07–05:09 · 3 分钟 · 代表分钟 05:08 · 已答 6 题 代表分钟与盘面标题行是同一次 workingRectificationTime(result) 调用,一个 值两处用,不是两条推导路径。已答题数逐字读服务端的 inference_state.answered_probe_count,投影没给就整项不显示,不补「已答 0 题」。 条上不写确认,边界句仍只在交付/采用旁白里出现一次。读数只增宽不增高:条高 在滚动容器外,变高会让贴底读者的内容无声滑出视野。 收窄进度未做,触发让步顺序第 5 条。没有这个字段(服务端只把这句话拼进旁白 正文),且前端自己减会算错——candidate_range 是当前窗口且会放宽(BUG-572), 拿它当「最初」相减会把一次放宽报成一次收窄。缺口与解除条件写进 BLOCKED.md。 T7.2 盘面折叠。实际有 8 个 details(任务书记的五处漏了交界节奏、错标了两块), 按内容归成「候选分钟」(换升时刻 + 次级候选)与「参数与口径」(宫位表、确认门、 本轮技法、两段本命上升、经历与大运对照含双轨一致性、交界节奏)。每块保留原有 渲染条件,engineMeaningToDisplayCopy() 仍是 5 处,逐分钟行的 is-changed / 当前时间 一字未改。共享组件 technique-audit-disclosure.tsx 未动。 顺带修掉一个静默失效的断言:rectification-agentic-entry 用 board.slice(indexOf(A), indexOf(B)),B 改名后 indexOf 返回 -1,slice(i, -1) 会悄悄退化成几乎整份文件,底下四条 doesNotMatch 照样绿。已换成带越界断言的 sliceBetween()。两处改动的断言都在测试里写了原值/新值/原因。 tsc 0 错;lint 0 error / 118 warning(同基线);npm test 3350 → 3360,失败 仍是 31 条无 Docker 用例且与基线逐条一致;next build exit 0,/ 仍 ○ Static; 产物 CSS gzip 39,017 → 38,973 B(−0.11%)。 窄屏 .is-compact 下常驻条的实际可见性无 Chrome 无法验证,已按任务书写成环境 缺口,未标通过;CSS 上已确认无任何规则在该状态下隐藏该条,并写成测试断言。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
17 KiB
PROGRESS · R7 生时校正(2026-09-16)
任务书:docs/tasks/TASK-cend-surfaces-claude-alignment-20260916.md 的 R7(T7.1 + T7.2,决策 D10)。
- 基线:
origin/staging=50ce02c8 - 分支:
codex/cend-rectification-20260916 - worktree:
/workspace/Jyotisha/.claude/worktrees/agent-a8ca21a2f5ced3002(本会话被隔离在这个 agent worktree,不是任务书里的.worktrees/cend-rectification-20260916) - BUG 编号:未占用。本轮是产品改造,未发现需要建档的产品缺陷,
docs/BUG_HISTORY.md当前最大号仍是 BUG-738(已核对)。
1. 开工基线数字
在 50ce02c8 上 npm ci 后实测:
| 项 | 基线 |
|---|---|
tsc --noEmit |
0 错 |
npm run lint |
0 error / 118 warning |
npm test |
# tests 3350 / # pass 3304 / # fail 31 |
| 失败清单 | 31 条,全部是无 Docker 的数据库 / 部署 / 迁移类;存档在 base.txt |
next build |
exit 0,/ = ○ Static |
| 产物 CSS gzip | .next/static/chunks/*.css 合计 39,017 B(raw 251,634 B) |
2. 逐条验收对照
T7.1 可信区间提成常驻条
先说一个与任务书事故实证不一致的发现,它改变了本轮的做法。
任务书 E9 写「区间只以 .rectification-board__clock 的形式出现在右侧盘面板的标题行」。实际读代码:rectification-agentic-chat.tsx:1645 起,对话区顶部已经有一条常驻条 RectificationTimeline(BUG-602/603 那一轮做的),它是 .rectification-workspace__chat 的第一个 grid 行,已经在显示当前区间与宽度(view.rangeLabel / view.widthLabel)。窄屏 .is-compact 下 .rectification-workspace__chat 取 grid-area: 1 / 1 且三行俱在,所以这条本来就可见。
真正只存在于 .rectification-board__clock 的是代表分钟——窄屏下盘面变成默认关闭的 overlay,代表分钟就此看不见。这才是 E9 描述的那个死角。
因此本轮没有另造第二条常驻条(那会撞上固定条高这条正确性约束,也等于手写第二套),而是在既有常驻条上补齐缺的两项。
| 验收标准 | 结论 | 证据 |
|---|---|---|
窄屏 .is-compact 下常驻条仍然可见 |
CSS 上确认,真机未验(环境缺口) | 见 §5 |
| 边界语义不弱化 | 通过 | 条上只写「代表分钟 HH:MM」/「已采用 HH:MM」,不写确认;REPRESENTATIVE_MINUTE_DISCLAIMER 与交付/采用旁白一行未改。测试 an adopted minute is labelled adopted... 断言组件与 scale 模块都不出现「已确认 / 确认的唯一」 |
区间数字与 .rectification-board__clock 同源 |
通过 | 同一次调用:盘面 rectification-board.tsx:59 workingRectificationTime(result);条由 rectification-agentic-chat.tsx 传 workingTime: workingRectificationTime(candidateResult)。不是两条推导路径,是一个值两处用。测试第 1 条锁死这三行源码 |
条上最终四项,一行排完:
05:07–05:09 · 3 分钟 · 代表分钟 05:08 · 已答 6 题
每项的服务端来源:
| 读数 | 来源 | 是否前端口算 |
|---|---|---|
| 当前区间 | credible_range(服务端投影) |
否 |
| 宽度 | 该区间自身的闭区间宽度,timelineDurationLabel(),既有函数未改 |
否(任务书让步顺序第 5 条明确允许「只显示当前区间与宽度」) |
| 代表分钟 | workingRectificationTime(result) = selectedTime ?? representativeTime ?? houseTable.time,与盘面同源 |
否 |
| 已答题数 | decisionReceipt.inference_state.answered_probe_count,服务端自己的计数,逐字读出 |
否 |
| 收窄进度 | — | 未做,触发让步顺序第 5 条,见 §3 |
answered_probe_count 此前已经在线上(compactInferenceProjection() 写进 inference_state,随 receipt 下发),只是客户端没解析。本轮加 parseAnsweredProbeCount() 读出来,投影没给就整项不显示,不补 已答 0 题。
T7.2 盘面板折叠块收敛
任务书说五块(:117/:132/:160/:182/:198)。实际数下来盘面里有 8 个 <details className="technique-audit">:本命宫位表、唯一分钟确认门、本轮技法、换升时刻(内嵌显示层)、两段本命上升、经历与大运对照、交界节奏。任务书把 :117 记成「盘面技法审计」、:182 记成「口径披露」,并漏了 :212 的交界节奏。按实际内容归的类:
| 新段 | 装进去的原块 |
|---|---|
候选分钟(aria-label="候选分钟") |
换升时刻(改为段内组标题 + 原列表)、次级候选(原「显示层」子折叠) |
参数与口径(aria-label="参数与口径") |
本命宫位表、唯一分钟确认门、本轮技法、两段本命上升、经历与大运对照(含双轨一致性那句)、交界节奏 |
| 验收标准 | 结论 | 证据 |
|---|---|---|
| 五块内容一条不丢,只换归属 | 通过 | 测试 the board folds to two sections and loses none of the five blocks 在两段的源码切片里逐块断言 |
engineMeaningToDisplayCopy() 调用点全部保留 |
通过 | 改前 5 处,改后 5 处,测试里 assert.equal(..., 5) 锁死 |
候选分钟列表 is-changed / 「当前时间」行为不变 |
通过 | 逐分钟行的 JSX 原样搬运未改;测试 regrouping changed no minute-row behaviour 锁 is-changed / is-working / 「当前时间」/「已更新」四处 |
| 键盘可达性不弱于改前 | 通过(静态) | 每块仍是真 <summary>,无 role="button"、无 onClick 开合;测试断言 <summary> 数 ≥ 6 且无 open。嵌套多了一层 → 到最深一块要多按一次 Enter,这是收敛的固有代价,不是可达性缺失 |
渲染条件逐块保持原样。 确认门与本轮技法原本写在 {table && ...} 里面,现在仍跟着 table 走——没有星盘时它们本来就不出现,改后也一样。不是「一条不丢」的反面,是不多出一条。
硬约束核对
| # | 约束 | 结论 |
|---|---|---|
| 1 | 数字只来自服务端投影 | 通过,四项来源见上表;收窄进度按让步顺序不做 |
| 2 | 边界语义不弱化、候选不得写成 confirmed | 通过 |
| 3 | 常驻条区间与盘面 clock 同源 | 通过 |
| 4 | 五块一条不丢、engineMeaningToDisplayCopy() 全留、is-changed/「当前时间」不变 |
通过 |
| 5 | 窄屏 .is-compact 下常驻条可见 |
CSS 确认,真机是缺口(§5) |
| 6 | 只动 rectification-* 与 globals.css 的 .rectification-* / .technique-audit 区段 |
通过,见 §6 |
| 7 | 复用 ChatComposer / useConversationScrollAnchor |
通过,两者一行未改;未新增输入框与滚动跟随 |
| 8 | tsc 0 错、lint 0 error、warning ≤ 118 |
通过:0 错 / 0 error / 118 warning(与基线同数) |
| 9 | npm test 失败清单与基线逐条一致 |
通过:# tests 3360 / # pass 3314 / # fail 31,diff base.txt after.txt 无差异 |
| 10 | 改断言写「原值/新值/原因」 | 通过,见 §4 |
| 11 | next build 后 / 仍 ○ Static |
通过,exit 0,┌ ○ / |
| 12 | 揭幕后无 spinner / 骨架 /「正在加载」 | 通过,未改 data-state="pending" 等高空骨架;新增元素都是静态 <span> |
| 13 | 同提交更新 DESIGN.md、对照 VOICE.md |
通过,§10 与 §12 重写;文案对照见下 |
首屏体积(红线:/ gzip ±2%)。 Next 16.3.1 的构建输出没有 Size / First Load 两列,沿用 BUG-601 那轮的口径直接量产物:.next/static/chunks/*.css 合计 gzip 39,017 → 38,973 B(−44 B,−0.11%),在 ±2% 内。raw 从 251,634 涨到 252,540 B(+906 B),gzip 反而略降属正常。新增 JS(rectification-timeline-scale.ts 的两个 label 函数、parseAnsweredProbeCount)全部落在校正面的 dynamic() 分包里,不进 / 首屏。
VOICE.md 对照。 「代表分钟 05:08」「已采用 05:08」「已答 6 题」三句都是短事实,无公文腔,不含被禁的「概率 / 置信度 / 确定 / 相对支持度」,也不碰 DESIGN.md 里已明令砍掉的「已对照 N 件经历」——那是经历吻合数,归交付卡;条上的「已答 N 题」是问答题数,两个量不同,已在 DESIGN.md 表里写明区别。
3. 让步顺序:触发了第 5 条
让步顺序第 5 条「T7.1 的收窄进度:若服务端投影里没有现成的『已从 N 分钟收到 M 分钟』字段 —— 只显示当前区间与宽度,不要前端口算进度」——已触发。
依据,两条,第二条比第一条更要紧:
- 没有这个字段。 服务端确实能拼出这句话——
user-copy.ts的progressClause(),由answer-choice.ts:1885/2060与agent-run.ts:1189传openingRangeFromDossier(dossier)调用——但它只作为旁白正文落库,没有结构化出口。客户端要拿到只能解析消息文本。 - 前端自己减会算错,不只是违规。 开场窗口在客户端确实有(
case.candidate_range→searchWindow),但它是当前搜索窗口,会放宽(BUG-572:±15 → ±30 → ±60 → ±120)。拿它当「最初」去减,一次放宽会被报成一次收窄。这条写进了rectification-timeline-scale.ts的模块注释,免得下一个人「顺手补上」。
缺口已写进 BLOCKED.md(顶部新条目),含解除条件:投影里加一个记开场窗口、放宽时不跟着变的宽度/进度字段。rectification-timeline-scale.ts 已留好接入位置。收窄进度这句话服务端照旧写进旁白,用户仍看得到,只是不在常驻条上——不是回归。
让步顺序第 6 条(T7.2 退到三段)未触发,收敛成两段。
4. 被改断言:原值 / 新值 / 原因
两处,都在测试文件里就地写了三行注释。
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
frontend/tests/rectification-board-model.test.ts("board shows a north-indian chart and folds 换升时刻 by default") |
assert.match(html, /换升时刻 · 2 个/) |
assert.match(html, /候选分钟/) + /2 个时刻/ + /换升时刻/,另加 /参数与口径/ |
D10 五段收敛成两段,「换升时刻」从 summary 降为段内组标题,计数移到「候选分钟」的 summary。计数仍是 grouped.length,仍默认折叠。没有弱化:原来断言 1 个字符串,现在断言 4 个 |
frontend/tests/rectification-agentic-entry.test.ts("the natal house table is a live board beside the chat, not a message") |
snapshot 切到 "{scoringMinutes.length > 0 || displayMinutes.length > 0",并断言 <RectificationHouseTableView 在 snapshot 里 |
snapshot 切到 "{hasMinuteDetail && (";宫位表改在 parameterSection 切片里断言;新增 assert.match(snapshot, /<VedicChartSvg/) 与 assert.doesNotMatch(snapshot, /<RectificationHouseTableView/) |
本命宫位表随 D10 从 .rectification-snapshot 移进「参数与口径」,旧终点标记已不存在。测试原意(宫位表在盘面、不在消息流)原样保留 |
第二条附带修掉一个静默失效。 旧写法是 board.slice(indexOf(A), indexOf(B)):标记 B 改名后 indexOf 返回 -1,slice(i, -1) 会静默退化成「从 A 到文件倒数第二个字符」,底下四条 doesNotMatch 就变成在整份文件上断言,测试照样绿。实测确认过:只改组件不改测试时,这条测试假通过。已改成带 assert.ok(start >= 0) / assert.ok(end > start) 的 sliceBetween() 辅助函数,标记改名会直接报错并打印找不到的那个标记。
新增测试文件 frontend/tests/rectification-range-bar-20260916.test.ts,10 项:同源锁、采用标签与边界、无值不猜、计数来自投影、无收窄进度口算、一行四读数与固定条高、窄屏不隐藏、两段收敛不丢块、分钟行行为不变、折叠默认收起且可达。测试总数 3350 → 3360,未降。
5. 环境缺口(不得写成通过)
- 「窄屏
.is-compact下常驻条的实际可见性」——无 Chrome、无登录态,真机未验。 这是本轮的核心收益,按任务书要求写成缺口。 CSS 上确认到的事实(不等于真机通过):.rectification-workspace.is-compact把.rectification-workspace__chat与.rectification-board-overlay都放进grid-area: 1 / 1叠起来;__chat在窄屏保留三行栅格,第一行就是常驻条;overlay 只在boardOpen时渲染,默认关闭。全表扫过一遍,没有任何规则在.is-compact下对.rectification-timeline写display: none/visibility: hidden/height: 0;这条写成了测试断言(遍历所有.is-compact ... .rectification-timeline规则块)。 真机仍需确认的: 四项读数在 375px 实机字体下是否真的一行放得下(算下来约 269px,容器约 343px,留有余量,但中文字宽随字体变);被裁时是不是先裁「已答 N 题」;以及贴底跟随在条变宽后仍然正常(条高未变,理论上不受影响)。 - 盘面两段折叠的键盘走查未做。 静态上每块仍是
<summary>、默认收起、无假按钮;「Tab / Enter 依次展开两段并进到最深一块」需要真人按一遍。 - 无 Docker。
npm run test:db未跑。本轮未动表、未动迁移。 - 本轮未推送(按任务要求)。staging 部署与
/api/health的deployment.gitCommit核对不在本轮。
前两条建议并入 docs/testing/ 的校正走查清单;本轮未新建清单文件,避免与 R4–R8 其它轮次的清单重复。
6. 改动范围
只碰了这些,没有越界到 R5/R6/R8 的地盘(.chart-page-* / .ephemeris-* / .report-center-* / .personal-report-* / .settings-* / .membership-* 一个字符未动):
| 文件 | 改了什么 |
|---|---|
frontend/src/components/rectification-timeline.tsx |
读数行加代表分钟与已答题数两个 <span>;轴的 aria-label 跟着补齐 |
frontend/src/lib/rectification-timeline-scale.ts |
视图加 workingLabel / answeredLabel;新增 workingMinuteLabel() / answeredProbeLabel();模块注释写明为何没有收窄进度 |
frontend/src/lib/rectification-candidate-result.ts |
加 answeredProbeCount 字段与 parseAnsweredProbeCount() |
frontend/src/components/rectification-agentic-chat.tsx |
给 buildRectificationTimeline() 多传三个参数;import workingRectificationTime |
frontend/src/components/rectification-board.tsx |
五段折叠收敛成两段;加 hasMinuteDetail / hasParameterDetail |
frontend/src/app/globals.css |
.rectification-timeline__readout 改 nowrap + 新增 __working / __answered;.rectification-board__group;盘面嵌套 .technique-audit 的间距;.is-compact 规则加注释 |
frontend/DESIGN.md |
§10 重写(四项读数、只增宽不增高、代表分钟上条的理由与同源要求、收窄进度为何不在条上、砍掉清单加两行);§12 重写(两段折叠表) |
BLOCKED.md |
顶部新增收窄进度字段缺口 |
| 三个测试文件 | 见 §4 |
technique-audit-disclosure.tsx 未改——它是普通聊天也在用的共享组件,两个 <details> 原样嵌进「参数与口径」里。
7. 一件必须交代的事故:共享 stash 栈
量首屏 gzip 需要基线构建,本会话用了 git stash push -u → 构建基线 → git stash pop。这是错的:git 的 stash 栈是整个仓库共用的,不按 worktree 分。同一时刻 R5(cend-chart-eph)和 R6(cend-report)两个会话也在 stash。结果是我的 pop 弹出的不是我自己的 stash,而是 R5 的 chart-page / ephemeris 改动(9 个文件),落进了我的 worktree。
处理:先把 R5 的改动原样备份成 patch,再 git stash push -u -m "RECOVERED cend-chart-eph work..." 整份放回栈上,一个字符没丢。查 git log -g refs/stash 时发现 R6 会话也对我的 stash 做了同样的事(RECOVERED cend-r7-wip ...),互相救了回来。我自己的改动最后用 git diff <stash-sha> 生成 patch、patch -p1 打回,未跟踪的新测试文件用 git show <untracked-parent>:<path> 取回,11 个文件与 stash 的 diffstat 逐行一致,随后立刻提交,不再留在栈上。
给后面轮次的教训:多 worktree 并行时不要用 git stash。 要基线构建就另开一个 worktree 或临时 commit,别碰共享栈。
8. 最终数字
| 项 | 基线 | 本轮 | 结论 |
|---|---|---|---|
tsc --noEmit |
0 错 | 0 错 | 通过 |
npm run lint |
0 error / 118 warning | 0 error / 118 warning | 通过 |
npm test 总数 |
3350 | 3360(+10) | 未降 |
npm test 失败 |
31 | 31 | 清单逐条一致,diff 无差异 |
next build |
exit 0,/ ○ Static |
exit 0,/ ○ Static |
通过 |
| 产物 CSS gzip | 39,017 B | 38,973 B | −0.11%,在 ±2% 内 |