Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
8.9 KiB
8.9 KiB
TASK · 天空封面追加:报告封面绑定(方案 A)+ 首次建盘群星汇聚过场(2026-09-28)
基线与依赖
- 前置任务书:
TASK-birth-sky-cover-20260928.md。它的实现在本地分支codex/birth-sky-cover-20260928,有三个提交(d277cbd8T1–T4+T7、028aef27T5 blocked、14ef18acT6 弹窗),尚未推送、尚未由 Claude 验收。 - 本单在同一分支上继续,再追加两个提交;工作树
.worktrees/birth-sky-cover-20260928。开工前git fetch origin --prune:origin/staging若前进了,先git rebase origin/staging(这个分支只有本系列在用);- 基线 SHA 写进 PROGRESS。
- 进度记录继续写
docs/tasks/PROGRESS-birth-sky-cover-20260928.md,新增「追加单」一节。 - 前置的宋体单
TASK-serif-headings-20260928已在 staging(d670521f)。
事实
- 报告封面被挡住:报告没有记录生成时用的是哪一套出生资料,所以无法判断封面和报告是否对得上。
personal_reports行、报告文档、job / section / appendix 都没有出生资料指纹;request_fingerprint不含出生资料;calculation_hash是引擎结果或 markdown 的哈希。- 这些证据见分支上的
BLOCKED.md「TASK-birth-sky-cover T5」。
- 第一次建盘揭幕目前是一个带「继续」按钮的弹窗(
components/birth-sky/birth-sky-dialog.tsx的 reveal 变体,由hooks/use-birth-sky-reveal.ts与lib/birth-sky/reveal.ts驱动)。
决策记录(产品 2026-09-28 授权)
- 报告封面选方案 A:
- 从现在起,生成报告时写入当时的出生资料指纹;
- 只有指纹与当前一致的报告才显示封面;
- 旧报告没有这个字段,永远不显示封面,不补算、不回填;
- 不改数据库结构。
- 第一次建盘的揭幕改成群星汇聚过场,替换现在的弹窗:
- 星星从散开的位置汇聚到出生那一刻的真实位置;
- 然后显现文字,文字停留一会儿后整层淡出,进入下一步;
- 过场里不放任何按钮。
- 平时打开首页的加载动画不改。群星过场只在「本人第一次保存出生地」时放一次。
硬红线
- 出生资料与指纹都不下发客户端,比对在服务端完成。
- 不改表结构、不加迁移。
JyotishAPIHandler类方法数与__new__伪造点不增长;Home()的useState/useRef数不增长。- 过场不得变成等待:
- 天空数据必须先就绪才开始放;
- 沿用现有的 3 秒预算,超时就静默跳过;
- 不加 spinner 或骨架(DESIGN §9)。
- 汇聚完成的最后一帧必须与
drawBirthSky导出的封面完全一致。复用同一个绘制函数,只对位置做插值。 - 不新增 npm 依赖。
- 移动或改写组件前,先在
frontend/tests/和tests/里 grep 按源码文本断言的合同测试(AGENTS §7.8);任何改动的断言都写「原值 / 新值 / 原因」三栏。 - 隐私:只用样稿里的虚构资料(1994-05-18 07:40 UTC+8,30.27N 120.15E)。
任务分解
F1 · 报告写入出生资料指纹(方案 A)
- 普通报告、长报告,每一条生成路径都在报告文档的
subject里写入可选字段birthFingerprint,取值为resolveServerOwnedChartBirth(...).fingerprint。 - 先核实它与星盘页下发的
profileFingerprint是同一种算法;如果不是,两边统一用同一种,并在 PROGRESS 里说明。 - v2 evidence bundle / 报告文档的 schema 加这个可选字段,旧报告照常能读。
验收:每条生成路径各有一条测试,断言写入了指纹;缺这个字段的旧文档照常能解析。
F2 · 服务端比对,下发封面对象
- 报告视图(
reportView,在lib/personal-report-route-core.ts)新增coverSubject字段:- 报告存的指纹与该人物当前的指纹一致时,取值为 subject id(
chart_profile_id为 null 时取"self"); - 其余一律为
null,包括不一致、缺字段、不是本人的报告、人物已删除。
- 报告存的指纹与该人物当前的指纹一致时,取值为 subject id(
- 不下发指纹原文。
验收:一致、不一致、缺字段、非本人四种情况各一条测试;序列化后的响应里不含指纹与出生资料。
F3 · 报告封面(原任务书 T5)
- 屏幕上:
coverSubject不为 null 时,在正文最上方放封面块。- 用
nightpalette,宽度随正文列,比例 3:4; - 不放按钮;
- 正文出现之后才取数;取数失败就不显示封面,不留占位,不加等待态。
- 用
- 打印 / PDF:
- 封面改用
paperpalette; - 封面独占 A4 第一页(
break-after: page); - 不新增打印入口。
- 封面改用
- 把分支上
BLOCKED.md的 T5 条目划掉(不删),并注明「→ 已解除:产品 2026-09-28 选 A」。
验收:
- 指纹一致时有封面;不一致或缺字段时无封面;取数失败时正文不受影响;
- 打印样式断言
paperpalette 与分页。
F4 · 首次建盘群星汇聚过场(替换 T6 弹窗)
- 触发条件不变:只在首页 onboarding 里本人的出生地保存成功后触发一次。以下情况都不触发:新建家人档案、修改资料、平时打开首页。
- 时序(总长约 3 秒):
- 汇聚约 1.5 秒:可见的恒星与行星从随机散开的位置出发,ease-out 汇聚到真实投影位置。散开位置由固定种子生成,同一张盘每次一样;地平线圆、方位字、高度圈同步淡入。
- 显现文字约 1 秒:主句与事实句淡入,并停留。
- 淡出约 0.5 秒:整层淡出,露出原本的下一步(问候 / 首页,或者出生时间校正引导),不改变下一步是什么。
- 全屏覆盖层,不放按钮:
- 点击、触摸或按 Esc,立刻结束并进入下一步;
- 系统开了
prefers-reduced-motion时,不做汇聚,只整体淡入、停留、淡出。
- 实现要求:
- 画布用
requestAnimationFrame,按视口尺寸与 DPR 绘制; - 组件卸载时取消动画;
- 插值写成纯函数:t=0 是散开位置,t=1 是真实位置。
- 画布用
- 结束后的提示:在首页轻提示一次「这片天空在星盘页可以保存。」。复用已有的 sonner Toaster,不写新组件;这句收进 VOICE.md。
- 删除不再使用的弹窗 reveal 变体(「继续」按钮、
is-reveal样式)。星盘页那个「那一刻的天空」弹窗不动。
验收:
- 插值纯函数有测试:t=0 等于种子决定的散开位置,t=1 与真实位置逐点相等;
- 点击或 Esc 能立即结束;reduced-motion 走淡入淡出;
- 触发条件与原 T6 相同(本人保存出生地才触发,家人档案、
saveProfile、chart-profile-form都不触发);3 秒内数据没到就不放; - 提示只出现一次;
- 用无头 Chrome 从 golden 夹具取 t=0、0.5、1.0 与停留四帧 PNG,放进 scratchpad,路径写进 PROGRESS。
F5 · 文档
frontend/DESIGN.md§17:- 报告封面一段,写屏幕与打印的做法;
- 群星过场一段,写时序、跳过、减少动态效果、「平时的首页加载动画不改」。
frontend/docs/VOICE.md:收录提示句。CHANGELOG.md:追加一条。docs/testing/birth-sky-cover-checklist.md,追加以下真机条目:- 新账号首次建盘,完整看一遍过场;
- 过场中点一下能跳过;
- 手机开「减少动态效果」后只淡入淡出;
- 断网或慢网时过场不出现,也不卡住;
- 新生成的报告有封面、打印 PDF 第一页是封面;
- 旧报告没有封面;
- 改过出生时间后,改之前生成的报告没有封面。
验证(两个提交都完成后)
tsc --noEmit0 错;npm run lint0 error。npm test全量:- 与分支 PROGRESS 记录的基线对比测试名清单,不得有测试名消失(被删除的弹窗测试逐条列出原因);
- 失败清单与基线逐条相同。
- Python:
python3 -m pytest tests/test_birth_sky.py tests/test_api_server_growth_contract.py;python3 scripts/run_quality_gate.py --profile quick。
rm -rf .next && next build:/仍是○ Static;首屏 JS / CSS gzip 与基线 651,590 / 66,346 对比,变化在 ±2% 内。- 隐私:
python3 -m pytest tests/test_repo_privacy_markers.py -q。
让步顺序
- 结束后的提示可以砍。
- 过场里地平线、方位字的淡入可以简化成与星点同时出现。
- F1~F3 的方案 A 绑定、「最后一帧与导出封面一致」、「不等待」不可让步。
交付
- 在同一分支上再加两个提交:F1–F3 + 对应文档;F4 + 对应文档。
- 不推送,由 Claude 独立验收后把整个分支(五个提交)一起快进推上 staging。
BUG 编号
这是新功能,不开 BUG。实现中若发现既有缺陷,从 docs/BUG_HISTORY.md 开工时的最大号 +1 起编(写作时是 BUG-1079)。