Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
19 KiB
PROGRESS · 手机星盘 / 确认时间可改 / 天空定格 / 去掉报告封面(2026-09-29)
任务书:docs/tasks/TASK-mobile-chart-and-confirmed-edit-20260929.md(M1–M4 在 53c0b9ea,M5 在 47eda7e1 追加)。产品授权直接执行(Claude 子代理)。
- 基线:
origin/staging=53c0b9ea(开工时;其后47eda7e1只加了任务书 M5 段,纯文档,分支未 rebase) - 分支 / 工作树:
codex/mobile-chart-confirmed-edit-20260929/.worktrees/mobile-chart-confirmed-edit-20260929,未推送 - 环境:Node v22.14.0(
/exec-daemon/node);系统python3;无 Docker;无头 Chrome 151(--disable-dev-shm-usage,CDP 设备模拟) - BUG 编号:开工时最大 BUG-1082;本单 M1 = BUG-1083;M2 是 BUG-264 下的产品修订段,不另开号
结论
| 项 | 结果 | 提交 |
|---|---|---|
| M1 手机星盘完整显示(BUG-1083) | 完成 | 1 |
| M2 已确认时间,改声明字段后重置 | 完成(数据库侧未跑 test:db,见环境缺口) |
2 |
| M4 「那一刻的天空」重放 → 定格 → 右上角分享 | 完成 | 3 |
| M5 去掉报告星图封面 | 完成 | 4 |
| M3 文档 | 完成(DESIGN、VOICE、CHANGELOG、BUG_HISTORY、BLOCKED、真机清单) | 1–4 各自带 |
M1 · 根因与修复(BUG-1083)
复现方法:真实组件(ChartPageView / ChartVedicTab / AppSidebar / SidebarInset)+ golden 夹具(chart-view-golden.json、chart-view-bhava-golden.json)SSR 成静态页,套上本次构建产物里 /chart 引用的全部 CSS,无头 Chrome 用 CDP Emulation.setDeviceMetricsOverride 模拟 390×844(新版无头窗口最小 500 宽,--window-size 不够用),从盘面 SVG 往上逐层量 getBoundingClientRect().width。
修复前(基线 CSS,本命与 Bhava 相同):
| 层 | 宽度 |
|---|---|
svg.personal-report-chart-svg |
1043 |
.chart-page-vedic-stage / -vedic-layout / .chart-page-vedic / .chart-page-panel |
1069 |
.chart-page-body(第一个超宽祖先) |
1101 |
.secondary-page |
390 |
.secondary-panel / main.chat-app |
390 |
- 根因:
.secondary-page是display: grid且没有grid-template-columns,隐式列是auto;子项.chart-page-body保持默认min-width: auto,这一列就按正文最小内容宽度定宽(不折行的 23 个盘型 chip、34rem 行星表)。内层表格与盘型条自己的min-width: 0+ 横滚都在,撑宽发生在它们外面。面板overflow-x: hidden把溢出直接裁掉,所以看起来是「盘很大、右边没了」。 - 修复:
.secondary-page { grid-template-columns: minmax(0, 1fr); }。修在四个次级页共用的这一层。 - 修复后(本次构建 CSS):390×844 下本命 / D9 / Bhava / 月亮 / D10 都是
documentElement.scrollWidth= 390、没有超宽祖先、盘面 332px。大运 / 基础信息 / 西洋盘三个 Tab 修复前就不溢出(让步 1:未另改)。 - 桌面:1280×900 下修复前其实也被裁(
.chart-page-vedic-layout1069px 塞在约 1024px 的内容区,参数卡右边被切、「≡」看不到);修复后两栏布局不变,盘面 549 → 480px,参数卡与「≡」完整可见。 - 回归测试:
frontend/tests/chart-page-mobile-width-20260929.test.ts(2 条)。
截图(/tmp/claude-1000/-workspace-Jyotisha/fb88e01e-1ced-4bcc-92fe-44c5bdcc878e/scratchpad/mobilechart/):
- 修复前:
before-D1-390.png、before-BHAVA-390.png、before-D1-1280.png - 修复后(本次构建 CSS):
after-D1-390.png、after-D9-390.png、after-BHAVA-390.png、after-MOON-390.png、after-D10-390.png、after-D1-1280.png - 桌面前后对比:
cmp-1280.png
M2 · 结论
- 改法:
resolveAccountBirthTimeApplicationPatch去掉「confirmed 直接{}」。confirmed(含状态为空、有遗留分钟的 legacy 行)只有声明字段真的变了才重置,之后与未确认完全同一套派生:零误差家人时间 →accepted+ 新分钟;否则reported、active_birth_time = null;rectification_case_id = null。只改称呼、岁差、性别、头像 →{}。 - 跨午夜三列(硬红线 3):
- 写入方:采用 / 确认候选的 RPC(
20260920020000_adopted_birth_date.sql起的 dated-v1 路径);读取方:server-owned-birth-profile.ts(active_birth_timezone_offset覆盖声明时区)、chart-birth-truth.ts(adoption = "adopted"、指纹)。 - 数据库触发器
zz_guard_adopted_birth_date(BEFORE INSERT/UPDATE)在任一声明字段(日期、时间、来源、时段、范围、误差、经纬度、时区)变化或状态不在 accepted/confirmed 时把三列置空;前端镜像是applyAdoptedBirthDateGuard。 - 本次仍在派生补丁里显式写三列为 null(仅当当前行有值),不只依赖触发器。
- 写入方:采用 / 确认候选的 RPC(
- 遗留列
birth_time:chart-birth-truth.ts与server-owned-birth-profile.ts在active_birth_time为空时会回落读birth_time。confirmed / legacy 重置成「只有时段」时,若不清它,星盘仍会按旧分钟排。所以当前行birth_time非空时一并写 null。 - 触发器会不会改回 confirmed:不会。
guard_birth_time_journey最新版(20260804020000)只在「状态为空且有 active 分钟」时补confirmed,本修订总写明确状态。agentic_rectification_profiles_rebaseline_guard与invalidate_agentic_rectification_results_on_profile_change(最新版20260827010000)在声明变化时把进行中的校正 Case 标成needs_rebaseline、把结果标invalidated_at,这是期望行为。- 没有改触发器、没有加迁移。
- 并发守卫:派生补丁非空时,路由照旧用
applyAccountProfileConcurrencyGuards按当前行做条件更新,confirmed 行现在也会走到这条,能挡住与一次并发采用的竞争。 - 客户端:
/people保存本人走saveSelfProfile→applyPersistedBirthTime,用路由返回的status/activeTime(取自写回的行);新增单测证明「确认 07:52 → 改 08:05」后本地是accepted/08:05,改成时段后是reported/ 空分钟。缓存失效沿用 BUG-1081 那一套。 - 偏离(请 Claude 定):声明比较由
!==改成按值比较(sameDeclarationValue:库里HH:MM:SS与表单HH:MM、数字与数字字符串、空串与 null 视为相同)。原因:/people每次保存都整表重提全部出生字段,库里的time列回来是HH:MM:SS,原来的!==会把「只改称呼」判成新声明,于是本单改完后会把已确认分钟清掉。副作用:未确认状态(例如accepted)下「只改称呼」以前也会被误判并退回reported,现在不会了——这是修正,不是新行为。
M4 · 完成情况
- 纯函数:
birthSkyRevealAt(elapsed, reduced, mode),新增mode = "hold":汇聚与文字与首页过场完全相同,文字淡入完(3.6 s;减少动态效果时 0.6 s)就held,此后永远是定格帧、layer = 1、done = false。birthSkyHoldAtMs(reduced)给出定格时刻。首页过场(reveal)时间轴不变。 - 组件:
BirthSkyConverge加mode与controls两个可选参数(默认reveal、无控件,首页过场行为不变)。hold模式:- 定格后停止
requestAnimationFrame;resize 会清空画布,所以定格后 resize 时重画一次; - 点画面(不在控件上)直接跳到定格帧,不关闭;
- Esc 关闭;卸载时取消动画。
- 定格后停止
- 新组件
components/birth-sky/birth-sky-replay.tsx:左上 ×(读屏名「关闭」),右上「分享」(saveBirthSkyImage(cover.blob):能分享文件就调系统分享,不能就下载;用户取消不提示;其他失败提示「没能分享,再试一次。」)。样式在birth-sky-converge.css,44px,顶部让出安全区。入口birth-sky-entry.tsx懒加载它,每次打开都重放。 - 定格帧 = 海报:定格帧画布调用序列与导出 PNG 逐条相同(单测,三个 phase);并且
BirthSkyCover新增serif(render.ts新增paintBirthSkyPng同时返回 PNG 与宋体判定),重放用画 PNG 时同一个字体判定,避免「PNG 用黑体、屏幕用宋体」。 - 删除:静态海报弹层
birth-sky-dialog.tsx与birth-sky.css(不再有引用)。 - 无头 Chrome 实测(真实组件 esbuild 打包 + golden 夜盘 + 「中国 · 浙江省 · 杭州市 · 西湖区」标签),390×844 与 1280×900 都通过:
- 1.5 s 在播放;
- 5 s 已定格、不透明;
- 8 s 仍打开;
- 0.7 s 时点画面 → 跳到定格、仍打开;
- Esc → 关闭。
截图:
replay-390-playing.png、replay-390-held.png、replay-390-tapped.png、replay-1280-playing.png、replay-1280-held.png、replay-1280-tapped.png、replay-390-strip.png(同一目录)。
- 「分享」按钮「PNG 未就绪时禁用」:没有实现禁用态。星盘页只在 PNG 画好后才渲染入口按钮,重放层打开时 PNG 一定已经在手,这一状态不存在(偏离,见下)。
M5 · 完成情况
- 删除:
components/birth-sky/report-sky-cover.tsx/.css、lib/birth-sky/report-cover.ts、personal-report-page.tsx的动态导入与挂载点(连同只给它用的next/dynamicimport)、classifyReportEnvelope的coverSubject解析、ReportLoadState/ReportEnvelopeView上的字段。 - 服务端:
reportView的coverSubject参数、readyReportCoverSubject、ReportReadCoreDeps.loadCurrentBirthFingerprint、详情路由里的loadCurrentBirthFingerprint(以及随之无用的subjectBirthFingerprint/loadSubjectBirthimport)全部删除;读报告不再多查一次人物资料。 lib/report-birth-binding.ts只保留 F1 写入用到的:REPORT_BIRTH_FINGERPRINT_KEY、subjectBirthFingerprint、stampReportBirthFingerprint、readReportBirthFingerprint、withReportBirthFingerprint;删掉reportDocumentBirthFingerprint、reportSubjectId、reportCoverSubject。- 保留:三条生成路径写
subject.birthFingerprint、TS / JSON Schema / Python 三处 schema 可选字段及其测试。 grep -rn "ReportSkyCover\|report-sky-cover\|coverSubject" frontend/src= 0。BLOCKED.mdT5 保持划掉,追加「→ 2026-09-29 产品决定去掉报告封面」。
验证
| 项 | 基线 53c0b9ea |
交付 |
|---|---|---|
tsc --noEmit |
0 错 | 0 错 |
npm run lint |
0 error / 126 warning | 0 error / 126 warning |
npm test |
tests 4335 · pass 4280 · fail 24 · cancelled 0 · skipped 31 | tests 4335 · pass 4280 · fail 24 · cancelled 0 · skipped 31 |
| 失败清单 | 24 条(Docker / PostgreSQL / 部署) | 与基线逐条同名 |
| 测试名对比(同机同命令跑基线) | — | 消失 15、新增 15(下表逐条) |
run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime(门禁同参数) |
— | 997 passed / 1 skipped,Quality gate passed.,退出 0 |
commercial_privacy_artifact_scan.py --json |
— | status: pass,finding_count: 0 |
pytest test_repo_privacy_markers + test_birth_sky + test_api_server_growth_contract + test_personal_report_contract |
— | 126 passed |
rm -rf .next && next build |
/ ○ Static |
/ ○ Static |
page.tsx / jyotish_api_server.py |
— | 未改 |
首屏 gzip(index.html 引用的全部 /_next/static/*.js|css 逐个 gzip -9 求和;同一工作树先在基线代码上全量构建一次,再在交付代码上全量构建)
| 基线 | 交付 | 变化 | |
|---|---|---|---|
| JS | 651,520 B | 651,512 B | −8 B(−0.001%) |
| CSS | 66,031 B | 66,035 B | +4 B(+0.006%) |
消失的测试名(15 条,§7.3)
| 消失的测试名 | 去向 / 原因 |
|---|---|
| ordinary declaration edits clear stale candidate application but never overwrite confirmed active time | 改名为 “…and a new declaration drops a confirmed minute too”;产品 2026-09-29 推翻 BUG-264 约定(M2) |
| confirmed ordinary edits keep the active minute and clear a complete adopted tuple together | 改名为 “confirmed declaration edits drop the minute and clear a complete adopted tuple together”;同上(M2) |
| chart page dialog shows only the picture: no title bar, no panel; icon × and 「保存图片」 | 由 “chart page replay stays on the poster: × top left, 「分享」 top right…” 取代;静态弹层删除(M4) |
| the chart page dialog no longer has a reveal variant (「继续」, is-reveal) | 由 “the chart page replay has no 「继续」 and the poster dialog is gone” 取代;读的两个文件已删除(M4) |
| GET /api/reports/:id compares on the server through the chart page's subject loader | 由 “GET /api/reports/:id no longer looks up the subject for a cover (M5)” 取代;产品决定去掉报告封面(M5) |
| coverSubject: only a matching fingerprint yields the subject id, self when chart_profile_id is null | 删除:比对函数 reportCoverSubject 已删(M5) |
| core read: coverSubject is the subject id when the stored fingerprint matches the current one | 删除;由 “core read: the detail response has no coverSubject, and never the fingerprint or birth fields” 取代(M5) |
| core read: coverSubject is null when the birth data changed since the report | 同上(M5) |
| core read: coverSubject is null for a legacy report without the field, and nothing is looked up | 同上(M5) |
| core read: coverSubject is null for a subject that is not the reader's (foreign, deleted, unreadable) | 同上(M5) |
| matching fingerprint → the ready state carries coverSubject; mismatch or legacy → no cover | 删除:report-sky-cover.test.ts 整个文件随组件删除(M5) |
| the cover sits above the body, lazy, and only when coverSubject is set | 同上(M5) |
| loader paints night for the screen and paper for print with the same sky | 同上(M5) |
| fetch failure, an empty sky or an abort → no cover, and nothing throws into the report | 同上(M5) |
| print: paper picture alone on the first A4 page; the screen picture is hidden | 同上(M5) |
新增 15 条:M1 2 条(chart-page-mobile-width-20260929.test.ts);M2 4 条(两条改名 + “confirmed minute follows a new declaration and survives everything else” + “saving a new time over a confirmed minute takes the server's new status and minute”);M4 7 条(birth-sky-replay-20260929.test.ts 5 条 + 两条替代);M5 2 条(替代)。总数 4335 不变。
改动的既有断言(原值 / 新值 / 原因)
| 测试(文件) | 原值 | 新值 | 原因 |
|---|---|---|---|
account-api.test.ts 第一条 confirmed + 新声明(两处) |
{} |
{ active_birth_time: null, birth_time_status: "reported", rectification_case_id: null, birth_time: null } |
M2:新声明丢掉已确认分钟;夹具 birth_time 有值,所以一并清 |
account-api.test.ts confirmed / legacy + 新零误差家人时间 |
{} |
accepted + 05:40(legacy 另加 birth_time: null) |
M2 |
account-api.test.ts “PATCH answers…” confirmed / legacy + 改 district_code |
confirmed / 05:18 |
accepted / 05:00(夹具声明是零误差家人时间 05:00);另加两条「只改称呼 / 岁差 → 仍 confirmed 05:18」 |
M2:改地点属于新声明 |
account-api.test.ts confirmed + 只改岁差 |
{} |
不变 | —— |
chart-profile-update-consistency.test.ts 跨午夜 confirmed + 新声明 |
{},排盘仍 confirmed 23:55(legacy) |
回到 reported 并显式清三列,排盘按新声明 00:20(declared) |
M2 |
profile-onboarding-birth-sky-reveal.test.ts overlay |
layer.addEventListener("pointerdown", finish) |
onPointerDown = mode === "hold" ? skip : finish,默认 mode = "reveal" |
M4:定格模式点一下只跳到定格帧 |
chart-page-view.test.tsx 入口懒加载 |
import("./birth-sky-dialog") |
import("./birth-sky-replay") |
M4 |
chart-page-view.test.tsx 封面夹具 |
无 serif |
serif: false |
BirthSkyCover 新增字段(夹具形状,断言未变) |
report-birth-binding.test.ts longform cover |
另用 reportDocumentBirthFingerprint 读回 |
只看 subject.birthFingerprint |
M5:读回函数已删;F1 写入断言保留 |
改动的断言都在测试文件里就地写了三栏注释。
偏离任务书(请 Claude 定)
- M2 声明比较改成按值比较(见 M2 节)。不改的话,本单会让「只改称呼」清掉已确认分钟,违反决策记录第 1 条。
- M2 同时清遗留
birth_time(任务书只写了三列)。原因:图表在active_birth_time为空时会回落读它。 - M4 「分享」没有做禁用态:PNG 在入口按钮出现前已就绪,这个状态不存在。
- M4 失败文案改为「没能分享,再试一次。」,原「没能保存。长按或右键上面的图,也能存下来。」停用,因为画面是画布,不能长按存图。已收进 VOICE。
- M1 修在
.secondary-page(四个次级页共用),而不是只给.chart-page-body补min-width: 0。桌面宽度下被裁的问题也随之消失。 - 首页过场结束后的提示「这片天空在星盘页可以保存。」未改;星盘页现在的按钮字是「分享」,保存要经分享面板或下载,句意仍成立。请 Claude 判断是否改句。
- 分支基于
53c0b9ea,未 rebase 到只加了文档的47eda7e1(按协调要求);docs/tasks/README.md索引行的状态未改,留给 Claude 交付时处理,避免冲突。
环境缺口
- 无 Docker:
- 24 条 DB / 部署套件失败,与基线逐条同名;
- 没跑
npm run test:db,所以 M2 在真实 PostgreSQL 上的效果(触发器链、条件更新)只做了静态核对。staging 实测见真机清单第 5–7 条。
- 无登录态与真机:M1 只在无头 Chrome 设备模拟上验证;M4 的系统分享面板、M5 的打印 PDF 没有实测。见
docs/testing/mobile-chart-confirmed-edit-20260929.md(18 条)。 - 无模型凭据:本单不涉及模型输出。
- 未推送、未部署。
Claude 验收(2026-09-29)
分支已 rebase 到 origin/staging 56722063(另一会话同期写入 BUG-1084~1089,BUG_HISTORY.md 冲突按编号顺序合并,BUG-1083 在前)。独立重跑:tsc 0 错;lint 0 error / 126 warning;npm test 4335 · pass 4280 · fail 24 · cancelled 0,失败清单与上一单逐条同名;门禁同参数 quick 门禁 997 passed、Quality gate passed.;隐私制品扫描 pass;rm -rf .next && next build / ○ Static;首屏 gzip 651,512 / 66,035(基线 651,520 / 66,031,−8 B / +4 B);src/ 下 grep ReportSkyCover|report-sky-cover|coverSubject = 0。
看过截图:before-BHAVA-390 复现产品真机现象;after-{BHAVA,D1}-390 盘面完整、表格在框内;replay-390-held 定格帧有 × 与「分享」。
M2 复核:最新 guard_birth_time_journey(20260804020000)已不在 birth_time 与 active_birth_time 间互相同步,因此同时清 birth_time 不会连带清掉新写入的 active_birth_time。偏离 1–7 接受;偏离 6(首页提示句「这片天空在星盘页可以保存。」)保留,待产品定。