Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
29 KiB
PROGRESS · 「那一刻的天空」封面(2026-09-28)
任务书:docs/tasks/TASK-birth-sky-cover-20260928.md。产品授权直接执行(Claude 子代理)。
- 基线:
origin/staging=d670521f(任务书写作时为dc640931;TASK-serif-headings与 BUG-1079 按钮配色都已在基线里) - 分支 / 工作树:
codex/birth-sky-cover-20260928/.worktrees/birth-sky-cover-20260928(三个本地提交,未推送,由 Claude 验收后推) - 环境:Node v22.14.0(
/exec-daemon/node);无.venv,Python 一律系统python3(3.13,pyswisseph 2.10.03);无 Docker;有/usr/bin/google-chrome(无头)。 - BUG 编号:开工时最大 BUG-1079。新功能,本单未开 BUG。
结论
| 项 | 结果 | 提交 |
|---|---|---|
| T1 天空计算模块 | 完成 | 1 |
| T2 BFF 路由与合同 | 完成 | 1 |
| T3 封面渲染与星盘页入口 | 完成 | 1 |
| T4 保存图片 | 完成 | 1 |
| T7 文档 | 完成(DESIGN §17、VOICE、CONTEXT、CHANGELOG、真机清单、任务索引) | 1(后两提交各自补本期段落) |
| T5 报告封面 | blocked,未实现:报告没有出生资料绑定,按任务书不显示,写进 BLOCKED.md |
2 |
| T6 首次建盘揭幕 | 完成 | 3 |
| 让步 | 未砍 T6;T5 是 blocked 不是砍;三组连线都保留 | — |
T1 · scripts/birth_sky.py
compute_birth_sky(date, time, tz_offset_hours, lat, lon):本地时刻减时区得 UT,swe.julday得 JD。- 行星:
swe.calc_ut(默认旗标,回归黄道视位置)→swe.azalt(jd, ECL2HOR, (lon, lat, 0), 0, 10, …),取几何高度(atpress = 0,不加折射)。方位 = swe 方位 + 180°(swe 自正南起算;注释写明)。七颗都返回(月亮在地平线下也要进事实句),前端只画地平线以上的。 - 恒星:
scripts/data/bright_stars.json,耶鲁亮星表第 5 版(VizieR V/50,https://cdsarc.cds.unistra.fr/ftp/V/50/catalog.gz,sha2563dc44b1e90be8fbe5bcc7656032560f51275f985c7e3f783c9028e1838ec7bed;NASA ADC 产品,CDS 无限制分发,公有领域)。V ≤ 4.0 共 518 颗,J2000,不加自行。生成脚本scripts/build_bright_stars.py校验 sha256,原始星表不进仓。IAU 1976(Lieske)岁差换到出生历元,再swe.azalt(EQU2HOR);只返回地平线以上的星。连线用 HR 号:北斗(含 Megrez–Dubhe 闭合)、仙后 W、猎户(肩、腰带、脚、头)。 phase:太阳高度 > 0day;−18°~0° 按太阳本地时角分上午 / 下午(真太阳时,0~180° 为下午);< −18°night。- 返回不带任何输入字段。
POST /api/birth_sky在do_POST里 7 行薄注册(与/api/ephemeris_events同样式):JyotishAPIHandler方法数 218 不变,__new__计数不变,文件 10935 → 10942 行(上限 11224)。不是重计算路径,不进HEAVY_COMPUTE_PATHS。 tests/test_birth_sky.py(14 条):样稿 golden(太阳 31.71° / 84.69°,土星 50.31° / 191.14°,月亮、木星在地平线下);三个纬度 / 日期的真太阳时正午高度 = 90 − |纬度 − 赤纬|(±0.5°,赤纬取 swe 赤道坐标,逐分钟扫最高点);北极星高度 30.8°(±1°)、方位近正北;四种 phase;输出不含输入;星表来源与许可;岁差对 Meeus 例 21.b 精确到 1e-5°;参数校验;HTTP 薄注册 200 / 400。已加入 quick 门禁列表(run_quality_gate.py+2 行)。- golden 生成脚本
scripts/generate_birth_sky_golden.py→frontend/tests/fixtures/birth-sky-golden.json(样稿虚构资料四个时刻的真实引擎输出,前端测试原样读取)。
T2 · GET /api/birth-sky?subject=
lib/birth-sky/service-core.tsassembleBirthSky:loadSubjectBirth→prepareChartViewProfile(与星盘页同一条路)→ 引擎 →birthSkyFromEngine(zod 合同lib/birth-sky/contract.ts)。timeBasis=birthTimeStatus为accepted/confirmed时adopted,否则reported。- 状态:未登录 401
unauthenticated;别人的 / 不存在的 subject 404not_found;资料不全 200birth_profile_incomplete;引擎失败、坏载荷、时区解析失败 503unavailable;每用户每分钟 20 次,超了 429unavailable。只读?subject=,不接受客户端日期经纬度;不计费、不写库;响应no-store。 frontend/tests/birth-sky-route.test.ts(6 条):401、404、incomplete(本人缺坐标 / 家人subject_incomplete)、正常返回过合同且 JSON 不含latitude/longitude/date/time/timezone/placeLabel/name键与 1994、07:40、30.27、120.15、虚构名等值;adopted 标注;引擎抛错与坏载荷 503;路由源码只读subject、服务端取出生资料、无计费与写库。
T3 / T4 · 渲染、入口、保存
lib/birth-sky/sentence.ts:四句主句表与事实句规则原样照任务书;样稿资料得「太阳在东,月亮在脚下,火星正悬在东南」。lib/birth-sky/draw.tsdrawBirthSky(ctx, sky, { palette, width, height, footnote?, serif? }):Canvas 2D 纯函数,600 × 800 设计格按宽度缩放,导出 1080 × 1440。两套 palette 取 DESIGN token(night深色主题页底 / 墨 / 细线 / 深色 action 画太阳;paper报告纸色 / 墨 /--report-rule/--report-accent)。行星名做了简单避让(四个候选位)。lib/birth-sky/render.ts:document.fonts.load('600 30px "Jyotisha Serif SC"', 主句+顶部小字),最多等 1.5 秒,超时或失败用黑体画;离屏 canvas →toBlob("image/png")。lib/birth-sky/export.ts:文件名jyotisha-sky.png;navigator.canShare({ files })真 →navigator.share;AbortError(用户取消)返回cancelled不提示;其余走 Blob +a.download。hooks/use-birth-sky-cover.ts:星盘页ok后按subjectId:profileFingerprint预取并画好 PNG,画好才返回;切人物 / 改资料键变即重取,旧请求 abort、旧 blob URL 释放。ChartPageView新增可选birthSkyprop,view.status === "ok" && birthSky时往actions放BirthSkyEntry(「那一刻的天空」描边小按钮)。弹层components/birth-sky/birth-sky-dialog.tsx(原生<dialog>,窄屏全屏、≥860px 居中 520px;<img>+ 「保存图片」),与其 CSS、画布、导出一起懒加载。- 测试:
birth-sky-sentence.test.ts(6)、birth-sky-draw.test.ts(8:1080×1440、全部文字不含日期/时刻/经纬度/地名/名字、footnote 两态、东在左且真实太阳画在圆心左侧、地平线下不画、宋体与超时退回黑体、paper palette、连线只连可见星)、birth-sky-export.test.ts(5:share 可用 / 不可用 / 用户取消 / share 失败回落下载 / 字体等待超时)、chart-page-view.test.tsx追加 2(就绪才有按钮;未就绪、失败、资料不全、视图未到都无按钮;预取键与懒加载源码合同)。
海报(样稿虚构资料,真实引擎 golden → 同一个 drawBirthSky → 无头 Chrome 里 canvas.toDataURL 取出的 PNG,含宋体切片):
/tmp/claude-1000/-workspace-Jyotisha/d49dfa04-4e7a-47b2-80a7-8529c4c38f86/scratchpad/birth-sky-poster-day-night.png(07:40,白天,night palette,「按你填写的时间」)- 同目录
birth-sky-poster-night-night.png(00:00,夜里,adopted,北斗与仙后可见) - 同目录
birth-sky-poster-twilight_evening-paper.png(19:10,傍晚,paper palette,猎户可见) - 同目录
dialog-390x844.png、dialog-1280x900.png(弹层版式静态样张,截在淡入途中)
T5 · 报告封面:blocked
证据与解除方案见 BLOCKED.md「TASK-birth-sky-cover T5」。要点:personal_reports 行、报告文档、job / section / appendix 表都没有出生资料指纹;request_fingerprint 不含出生资料;calculation_hash 是引擎结果哈希或 markdown 哈希。按任务书不显示封面,reportView、报告页、打印样式都未改。paper palette 已在 T3 实现并有单测,解除后可复用。
T6 · 首次建盘揭幕
lib/birth-sky/reveal.tsstartBirthSkyReveal:3 秒计时;期限内拿到画好的封面才onReady;超时 abort 请求、迟到的封面释放 URL;失败静默,不重试。hooks/use-birth-sky-reveal.ts自持cover状态;useProfileOnboarding新增可选参数onOwnBirthPlaceSaved,只在saveOnboardingPlace保存成功、且下一步已设好之后调用(fire-and-forget)。page.tsx只接线:+1 个自定义 hook 调用、+1 个参数、+1 个<BirthSkyReveal>、+2 行 import(1370 → 1375 行;Home()的useState/useRef数不变)。- 弹层与星盘页同一个组件,
reveal时加is-reveal(600ms 透明度淡入,prefers-reduced-motion关闭)与「继续」。关闭只清 reveal 自己的状态。 frontend/tests/profile-onboarding-birth-sky-reveal.test.ts(5):期限内出现、关闭不碰 onboarding;超时无弹层且迟到封面被释放、请求被 abort;失败无弹层无重试;只由本人 onboarding 出生地保存成功触发(saveProfile、星盘档案、chart-profile-form不触发);弹层「继续」/ Esc / 遮罩与淡入 CSS。
验证
| 项 | 基线 d670521f |
提交 1 d277cbd8 |
交付(提交 3) |
|---|---|---|---|
tsc --noEmit |
0 错 | 0 错 | 0 错 |
npm run lint |
0 error / 127 warning | — | 0 error / 127 warning |
npm test |
tests 4255 · pass 4200 · fail 24 · cancelled 0 · skipped 31 | tests 4282 · pass 4227 · fail 24 | tests 4287 · pass 4232 · fail 24 · cancelled 0 · skipped 31 |
| 失败清单 | 24 条(全是需要 Docker / PostgreSQL 的数据库与部署套件) | 与基线逐条同名 | 与基线逐条同名 |
测试名对比(frontend/AGENTS.md 方法) |
— | 消失 0 | 消失 0,新增 32 |
python3 -m pytest tests/test_birth_sky.py tests/test_api_server_growth_contract.py tests/test_readonly_chart_endpoints.py |
— | — | 全部通过 |
python3 scripts/run_quality_gate.py --profile quick(Node 22 在 PATH) |
— | — | 所有 Python 步骤通过(pytest 997 passed / 1 skipped,含新加的 tests/test_birth_sky.py);最后一步 npm test 的 24 条失败与基线逐条同名(无 Docker)→ 退出码 1,属环境缺口 |
python3 -m pytest tests/test_repo_privacy_markers.py -q |
— | — | 通过 |
next build(rm -rf .next 后全量) |
/ ○ Static |
— | / ○ Static;路由表与基线逐行相同,只多一行 ƒ /api/birth-sky |
首屏 gzip(index.html 引用的全部 js / css,gzip -9 求和,两边都是干净全量构建)
| 基线 | 交付 | 变化 | |
|---|---|---|---|
| JS | 651,590 B | 652,363 B | +773 B(+0.12%) |
| CSS | 66,346 B | 66,346 B | 0 |
JS 的 +773 B 是首页为 T6 引入的 useBirthSkyReveal、startBirthSkyReveal、loadBirthSkyCover / fetchBirthSky 与 next/dynamic 占位;弹层、画布、导出、birth-sky.css 都在懒 chunk。
改动的既有断言
无。chart-page-view.test.tsx 只追加了两条测试和一个 import,未改任何既有断言。
偏离任务书(请 Claude 定)
- 星盘页按钮本身不懒加载:
BirthSkyEntry是星盘页 chunk 里一个普通<Button>(只在图画好后渲染),弹层、CSS、画布、导出都懒加载。原因:按钮要能在 SSR 测试里断言「就绪才出现」,且它不在首页/。 - 事实句里的「最高的一颗可见行星」同时排除太阳与月亮(月亮已有自己的一段)。任务书写「除太阳外」;样稿结果不变。
- 晨昏按太阳本地时角分上午 / 下午(真太阳时),不按钟表时间。
- 顶部小字的字距用逐字绘制实现,不用 canvas
letterSpacing(部分 Safari 不支持;无头 Chrome 里设了又复位后后续文字仍带字距)。 - BFF 增加每用户每分钟 20 次的限流(与星盘页同量级),任务书未写。
- 宽屏弹层 520px(报告导出抽屉是 720px),3:4 的图在 720 宽时会被高度卡住、两侧空白。
- T6 的 3 秒包含取数与画 PNG(含最多 1.5 秒字体等待)。
- VOICE 对照:四句主句与事实句未发现与 VOICE 其余条款冲突(短句、无 emoji、无揣测、不讲运势);主句带句号、事实句不带,照样稿。新增一句保存失败文案「没能保存。长按或右键上面的图,也能存下来。」已收进 VOICE。
环境缺口
- 无登录态与真机:星盘页按钮、弹层、分享 / 下载、首次揭幕只在单元测试、SSR 渲染、无头 Chrome 静态样张上验证;真机清单
docs/testing/birth-sky-cover-checklist.md(17 条)。 - 无 Docker:24 条 DB / 部署套件失败与基线逐条同名;quick 门禁最后一步
npm test因此退出码 1。 - 无
.venv:Python 用系统python3。 - 无头 Chrome 的
--screenshot在本机对这页时好时坏(挂起),海报改用canvas.toDataURL+--dump-dom取出,是导出 PNG 的真实像素。 - 未推送、未部署。
追加单(TASK-birth-sky-followup-20260928)
- 任务书:
docs/tasks/TASK-birth-sky-followup-20260928.md(staging-docs)。产品授权直接执行(Claude 子代理)。 - 基线:开工
git fetch origin --prune后origin/staging仍是d670521f,无需 rebase;本分支在前三个提交(d277cbd8、028aef27、14ef18ac)之上再加两个提交。 - BUG 编号:开工时最大 BUG-1079;新功能,未开 BUG。
F1 · 报告写入出生资料指纹
- 新模块
frontend/src/lib/report-birth-binding.ts:subjectBirthFingerprint(row)=resolveServerOwnedChartBirth(row).fingerprint。 - 与星盘页同一种算法、同一种输入:星盘页的
profileFingerprint来自prepareChartViewProfile,它取的是存储行的指纹(stored.fingerprint,在内存补全时区之前)。报告两处加载点(POST /api/reports与 worker 的loadReportWorkerProfile)都先loadSubjectBirth取存储行、再补时区,所以在补全之前用stampReportBirthFingerprint(resolved.row, filled)把指纹挂在内存行的report_birth_fingerprint上(只在服务端内存里,不落库、不进引擎输入)。测试证明:缺时区的行,补全前后指纹不同,补全前的等于星盘页。 - 三条生成路径都写
subject.birthFingerprint:- 路由内联生成(
resolveReportCreate→generatePersonalReport); - worker writer 路径(
generateWriterReport→generatePersonalReport,单次调用与分章两种组装都在 guard 之前写入); - worker 长报告路径(
generatePersonalReportLongform→buildLongformCoverDocument)。
- 路由内联生成(
- 指纹不进证据包(bundle 会被发给模型),只在组装后的文档上写:
generatePersonalReport新增可选birthFingerprint,在applyReportGuard之前写到候选文档上,随后照常走最终 server parse。 - Schema:TS
subjectSchema(v1/v2 共用)、contracts/personal-report/report-document.v1|v2.schema.json、Pythonscripts/personal_report_contract.py都加可选birthFingerprint(^[0-9a-f]{32}$)。旧文档不含该字段照常解析;格式不对的拒绝。 - 测试:
frontend/tests/report-birth-binding.test.ts(7 条:同算法 / 补时区前取;worker 加载器写入;三条路径接线;长报告写入与缺省;旧文档解析与坏格式;coverSubject 判定;GET 路由在服务端比对)、personal-report-api.test.ts追加「inline generation writes subject.birthFingerprint」、personal-report-generation-v2.test.ts追加「single-call and sectioned generation write subject.birthFingerprint only when given」(并断言分章调用发给模型的 bundle 不含指纹)、tests/test_personal_report_contract.py追加 1 条。
F2 · 服务端比对
reportView(row, job, failure, sections, coverSubject?):新增第 5 个可选参数,传入时输出coverSubject。resolveReportRead只在ready分支计算:读row.reportDocument.subject.birthFingerprint→ 没有就直接null(不查库)→ 有则调新依赖loadCurrentBirthFingerprint(userId, subjectId)(subjectId=chart_profile_id ?? "self")→ 相等给 subject id,否则null;加载器抛错(不是自己的人物、已删除、读不到)一律null。GET /api/reports/:id用loadSubjectBirth(supabase, userId, subjectId)(与星盘页同一个人物加载器,按user_id限定)取存储行再算指纹。- 响应里不含指纹:
reportDocument走projectOrdinaryReportDocument,本来就不带subject。 - 测试(
personal-report-api.test.ts追加 4 条):一致(本人self、家人给人物 id,序列化响应不含指纹、出生日期、经纬度、时区、地名);不一致;缺字段(且不查库);非本人(加载器抛错 / 返回空)。
F3 · 报告封面
classifyReportEnvelope在 ready 态读report.coverSubject(非空字符串才带上);PersonalReportPage在.personal-report-reader-body正文最上方渲染<ReportSkyCover subjectId=…/>(next/dynamic,ssr: false)。components/birth-sky/report-sky-cover.tsx+report-sky-cover.css:挂载后(正文已上屏)才取数;lib/birth-sky/report-cover.tsloadReportSkyCover取天空并用同一个renderBirthSkyPng画night(屏幕)与paper(打印)两张;任何失败返回null,组件什么也不渲染。无按钮、无占位、无等待态。- 打印:
.report-sky-cover-screen隐藏、.report-sky-cover-print显示;.report-sky-cover { break-after: page },图高 246mm 居中(A4 减报告@page边距后版心 186 × 270mm,3:4 在 186mm 宽时 248mm 高)。 BLOCKED.md的 T5 条目已划掉并注明「→ 已解除:产品 2026-09-28 选 A」。- 测试:
frontend/tests/report-sky-cover.test.ts(5 条:coverSubject 有 / 无;封面在正文之上且懒加载、无按钮无等待;night + paper 两张;取数失败 / 空 / 画图失败 / abort 都返回 null;打印 CSS 的 paper 与分页)。
F4 · 首次建盘群星汇聚过场(替换 T6 弹窗)
- 触发不变:仍只由
saveOnboardingPlace成功后的onOwnBirthPlaceSaved?.()触发;saveProfile、星盘档案、chart-profile-form不触发(原合同测试保留)。page.tsx未改一行(接线<BirthSkyReveal cover=… onClose=…/>与onOwnBirthPlaceSaved: birthSkyReveal.begin原样),Home()的useState/useRef数不变。 - 不等待:
startBirthSkyReveal改成泛型,仍是 3 秒预算;预算内要到齐三样——/api/birth-sky?subject=self、宋体(waitForHeadingSerif,最多 1.5 秒)、过场 chunk(import("@/components/birth-sky/birth-sky-converge"))。任一失败或超时就静默跳过,无 spinner / 骨架。 - 纯函数
lib/birth-sky/converge.ts:birthSkyScatter(种子取自这张盘可见天体的投影坐标,mulberry32,均匀撒在地平线圆 95% 半径内)、birthSkyPositionsAt(sky, scatter, t)、interpolateSkyPoint(在draw.ts,t ≥ 1 精确返回真实点)、easeOutCubic、birthSkyRevealAt(elapsed, reduced)时间轴:0–1.5 s 汇聚(ease-out)+ 地平线 / 高度圈 / 方位字 / 连线 / 行星名淡入、整层前 200ms 淡入;1.5–2.5 s 文字 400ms 淡入后停留;2.5–3 s 整层淡出;reduced-motion 不汇聚,400ms 淡入、停留、同时刻淡出。 drawBirthSky(ctx, sky, options, frame?):新增可选frame,只对位置插值、对透明度乘系数;不传或全为 1 时调用序列与原来逐条相同。- 过场组件
components/birth-sky/birth-sky-converge.tsx+.css(懒加载):原生<dialog>全屏、底色取nightpalette;画布取视口内最大 3:4 区域 ×devicePixelRatio;requestAnimationFrame每帧调drawBirthSky;pointerdown(点击 / 触摸)与 Esc(cancel)立即结束;卸载时cancelAnimationFrame、摘监听、close()。无按钮。 - 结束提示:
useBirthSkyReveal.close只在过场真的放过后执行一次(playingref 守卫),toast("这片天空在星盘页可以保存。"),复用layout.tsx已挂的 sonner Toaster;这句收进 VOICE.md。失败 / 超时没有过场也没有提示。 - 删除:
BirthSkyDialog的reveal属性、「继续」按钮、birth-sky.css的is-reveal样式与@keyframes birth-sky-reveal。星盘页弹窗其余不动。 - 四帧 PNG(样稿虚构资料的真实引擎 golden → 同一个
drawBirthSky,1080 × 1440,无头 Chromecanvas.toDataURL+--dump-dom取出;为排除字体加载时序,这组帧与对照导出都用黑体画):/tmp/claude-1000/-workspace-Jyotisha/fb88e01e-1ced-4bcc-92fe-44c5bdcc878e/scratchpad/birth-sky-converge-night-t0.png(0 ms,散开)- 同目录
birth-sky-converge-night-t050.png(750 ms,汇聚过半,地平线与方位字半透明) - 同目录
birth-sky-converge-night-t100.png(1500 ms,到位,文字未出) - 同目录
birth-sky-converge-night-hold.png(2400 ms,停留) - 另有
day一组同名四张。停留帧与drawBirthSky导出封面的 PNG dataURL 逐字节相同(night、day 两组都是true)。
- 测试:
profile-onboarding-birth-sky-reveal.test.ts由 5 条改为 10 条(见下「改动的既有断言」)。
改动的既有断言(本分支第 3 个提交 14ef18ac 新增的测试;基线 d670521f 里没有这些测试)
| 测试 | 原值 | 新值 | 原因 |
|---|---|---|---|
| saved in time…(改名 → 「the convergence starts once its data is ready; ending touches nothing else」) | 载荷类型 BirthSkyCover,名字说 dialog |
载荷类型 BirthSkyRevealSky(天空 + 宋体结论),断言不变 |
F4 把弹窗换成过场,载荷不再是画好的 PNG |
| timeout…(改名 → 「no overlay, the late data is dropped…」) | 同上 | 同上,断言不变 | 同上 |
| failure…(改名 → 「no overlay, no retry, no error surfaced」) | doesNotMatch(revealHook, /toast|setError|console…/) |
doesNotMatch(revealHook, /setError|console…/),另加:hook 里 toast( 恰好 1 处,且不在 onReady 里 |
F4 要求过场结束后提示一次;失败路径仍然不提示(close 只在 playing 为真时执行,另有新测试锁住) |
| only the reader's own onboarding place save triggers it… | revealHook 匹配 loadBirthSkyCover("self:reveal", "self", signal) |
匹配 loadBirthSkyRevealSky(signal) |
过场不再需要 PNG,只取天空与字体 |
| the reveal is the chart page's dialog plus 「继续」, faded in 600ms…(删除) | 断言弹窗 reveal 变体、「继续」、is-reveal 600ms 淡入 CSS |
由「overlay: full screen, no buttons…」「timeline…」「the chart page dialog no longer has a reveal variant」三条取代 | 任务书 F4 明令删除弹窗 reveal 变体 |
基线 d670521f 的既有断言:无改动。
追加单验证(两个提交完成后)
| 项 | 基线 d670521f |
交付(第 5 个提交) |
|---|---|---|
tsc --noEmit |
0 错 | 0 错 |
npm run lint |
0 error / 127 warning | 0 error / 127 warning |
npm test |
tests 4255 · pass 4200 · fail 24 · cancelled 0 · skipped 31 | tests 4310 · pass 4255 · fail 24 · cancelled 0 · skipped 31 |
测试名对比(frontend/AGENTS.md 方法) |
— | 对基线:消失 0;对本分支第 3 个提交:消失 4(上表 4 条改名 / 删除,均有替代),新增 28 |
| 失败清单 | 24 条(Docker / PostgreSQL 数据库与部署套件) | 与基线逐条同名 |
python3 -m pytest tests/test_birth_sky.py tests/test_api_server_growth_contract.py tests/test_personal_report_contract.py |
— | 63 passed |
python3 scripts/run_quality_gate.py --profile quick(Node 22 在 PATH) |
— | 所有 Python 步骤通过(pytest 997 passed / 1 skipped);最后一步 npm test 的 24 条失败与基线逐条同名 → 退出码 1,属无 Docker 环境缺口 |
python3 -m pytest tests/test_repo_privacy_markers.py -q |
— | 通过 |
rm -rf .next && next build |
/ ○ Static |
/ ○ Static;路由表与第 3 个提交交付时逐行相同 |
JyotishAPIHandler 方法数 / __new__ 计数 |
218 / 不变 | 未改 Python API 服务文件 |
Home() 的 useState / useRef 数 |
— | page.tsx 未改 |
首屏 gzip(index.html 引用的全部 js / css,gzip -9 求和,干净全量构建)
| 基线 | 第 3 个提交交付 | 本次交付 | 对基线变化 | |
|---|---|---|---|---|
| JS | 651,590 B | 652,363 B | 652,605 B | +1,015 B(+0.16%) |
| CSS | 66,346 B | 66,346 B | 66,346 B | 0 |
比第 3 个提交多的 242 B 是首页 hook 里新增的 sonner 提示调用、泛型 startBirthSkyReveal、loadBirthSkyRevealSky;过场组件、converge.ts、draw.ts、样式都在懒 chunk。报告封面组件在报告页的懒 chunk,不进首页。
追加单偏离任务书(请 Claude 定)
- 指纹只写进报告文档,不写进 v2 evidence bundle。 任务书写「v2 evidence bundle / 报告文档的 schema 加这个可选字段」。bundle 会原样发给模型(分章写作),把指纹放进去等于把一个出生资料派生值送给供应商,也会改变 bundle 哈希;所以只在组装后的文档(guard 之前)写入,TS / JSON Schema / Python 三处文档合同都加了可选字段。测试断言分章调用发给模型的 bundle 不含指纹。
- 指纹覆盖姓名。 同一算法(
resolveServerOwnedChartBirth)的指纹里本来就含name、岁差等字段,所以改了人物名字或岁差之后,旧报告也会失去封面。这是「与星盘页同一种算法」的直接结果,未另造一个只含出生时刻 / 地点的指纹。 coverSubject只在 ready 读取时下发(创建响应、replay、失败 / 生成中都不带该键)。- 过场用原生
<dialog>而不是普通div:保证在兑换弹窗(同为顶层)之上,Esc 走cancel事件;无按钮、无焦点陷阱内容。 - 整层前 200ms 淡入(任务书未写),避免保存后页面瞬间变黑;reduced-motion 下整体淡入 400ms。
- 四帧的「t=0.5」取时间过半(750 ms),此时 ease-out 位置进度约 0.875;纯函数测试里的 t 是位置插值参数本身(t=0 散开、t=1 真实)。
- 报告封面打印高 246mm(版心 186 × 270mm,3:4 满宽是 248mm,留 2mm 余量防止溢出到第二页)。
追加单环境缺口
- 无登录态与真机:报告封面(屏幕 / 打印 PDF)、过场(真机点击跳过、减弱动态效果、慢网)只在单测、源码合同与无头 Chrome 画布取帧上验证;真机清单
docs/testing/birth-sky-cover-checklist.md新增 18–30 条(11、12、13、14、16 作废)。 - 无 Docker:24 条 DB / 部署套件失败与基线逐条同名;quick 门禁最后一步因此退出码 1。本单不改表结构、不加迁移。
- 无模型凭据:真实报告生成(含写入指纹的三条路径)只在注入假代理的单测里跑过,部署后需新生成一份报告复核(清单第 18 条)。
- 未推送、未部署(按任务书由 Claude 验收后整体快进推 staging)。
Claude 验收(2026-09-28,分支五个提交,731c3b36)
在同一工作树上独立重跑,不采信执行方数字。
| 项 | 结论 | 证据 |
|---|---|---|
tsc --noEmit |
通过 | 0 错 |
npm run lint |
通过 | 0 error / 127 warning(与基线同) |
npm test |
通过(环境缺口除外) | tests 4310 · pass 4255 · fail 24 · skipped 31;24 条失败逐条同名于基线,全部是 Docker / PostgreSQL / 部署套件 |
pytest test_birth_sky + test_api_server_growth_contract + test_personal_report_contract + test_repo_privacy_markers |
通过 | 全部 passed |
| quick 门禁 | Python 部分通过 | 997 passed / 1 skipped;末步 npm test 因无 Docker 退出码 1 |
rm -rf .next && next build |
通过 | / ○ Static |
| 首屏 gzip | 通过 | Claude 口径(index.html 里 `/_next/static/*.js |
| 硬红线 1 指纹不下发 | 通过 | 报告详情只下发 coverSubject;reportDocument 走白名单投影不含 subject;测试断言响应体不含指纹与出生字段;指纹不进模型 bundle |
| 硬红线 2 不改表 | 通过 | 无迁移、无 schema 目录改动 |
| 硬红线 3 增长 | 通过 | jyotish_api_server.py 本系列仅 T2 的 7 行薄注册;page.tsx 追加单未改 |
| 硬红线 4 不等待 | 通过 | 3 秒预算含取数 / 字体 / chunk,超时静默;报告封面失败返回 null 不占位 |
| 硬红线 5 末帧一致 | 通过 | interpolateSkyPoint t≥1 精确返回真实点、alpha 乘 1;测试逐条比对画布调用序列;执行方取帧停留帧与导出 PNG dataURL 相同 |
| 四帧 | 已看 | scratchpad birth-sky-converge-night-{t0,t050,t100,hold}.png:散开 → 半程 → 到位无字 → 停留有字 |
偏离裁定:1(指纹不进 bundle)采纳,比任务书更稳;2(指纹含姓名 / 岁差,改名后旧报告失去封面)接受,属「同一种算法」的直接后果,已写给产品;3–7 接受。
环境缺口不变:真机 / 登录态(清单 18–30 条)、Docker、模型凭据。