diff --git a/BLOCKED.md b/BLOCKED.md index 29eb033e..ccb405a2 100644 --- a/BLOCKED.md +++ b/BLOCKED.md @@ -11,6 +11,7 @@ - 按任务书:本单**不显示**报告封面,不拿当前资料冒充当时资料;`reportView` 未改、报告页未改、打印样式未改,旧报告不补算不补写库。 - 解除需要一个产品决定(二选一,都不改表结构):① 生成报告时把当时的 `profileFingerprint`(`resolveServerOwnedChartBirth(...).fingerprint`)写进报告文档(如 `subject.birthFingerprint`,v2 schema 加可选字段),只对新报告生效,旧报告永远无封面;② 接受「当前资料」作为封面依据并在封面上说明——与任务书红线冲突,需产品推翻。 - `draw.ts` 的 `paper` palette 已实现并有单测(打印用),解除后报告封面可直接复用。 +- 同单其余环境缺口:无登录态与真机(星盘页按钮、保存 / 分享、首次揭幕按 `docs/testing/birth-sky-cover-checklist.md` 走);无 Docker(24 条 DB / 部署套件失败与基线逐条同名)。未推送、未部署。 ## TASK-site-button-contrast(BUG-1079):真机、登录态与两条待决发现(2026-09-28) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c10e46..1ee84f59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - 星盘页顶栏右侧多一个按钮「那一刻的天空」:出生地、出生那一刻头顶的真实天空,画成一张 3:4 的图。天顶在圆心、东在左,地平线以上的行星和亮星(北斗、仙后、猎户连线)照实画。 - 图上只有一句描述天空的话(四句之一,按当时是白天、清晨、傍晚还是夜里选),加一行「太阳在东,月亮在脚下,火星正悬在东南」这样的事实句;不讲运势、不贴标签,不印姓名、日期、时刻、经纬度和地名。出生时间没采用校正时,多一行「按你填写的时间」。 - 「保存图片」:iPhone 弹系统分享(可「存储图像」),其他浏览器直接下载 `jyotisha-sky.png`;手机长按预览图也能存。家人的盘也能生成。不扣点、不调模型。 +- 首次建完自己的盘时,这张图会在 3 秒内淡入一次,点「继续」回到原来的下一步;拿不到就跳过。 - 报告封面本期不上:报告里没有存生成时的出生资料指纹,对不上就不画(见 BLOCKED.md)。 - Skill 版本不 bump(Skill 文本未改)。不改数据库。 diff --git a/docs/tasks/PROGRESS-birth-sky-cover-20260928.md b/docs/tasks/PROGRESS-birth-sky-cover-20260928.md index e310ad85..4e3a0152 100644 --- a/docs/tasks/PROGRESS-birth-sky-cover-20260928.md +++ b/docs/tasks/PROGRESS-birth-sky-cover-20260928.md @@ -2,7 +2,110 @@ 任务书:`docs/tasks/TASK-birth-sky-cover-20260928.md`。产品授权直接执行(Claude 子代理)。 -- 基线:`origin/staging` = `d670521f`(serif-headings 已合入) -- 分支 / 工作树:`codex/birth-sky-cover-20260928` / `.worktrees/birth-sky-cover-20260928`(本地提交,**未推送**) -- 状态:提交 1(T1–T4 + T7)已完成;T5、T6 见后续提交。完整数字在最后一个提交里补齐。 -- 提交 2(T5):报告封面 **blocked**,不显示;缺口与解除方案写进 `BLOCKED.md`。 +- 基线:`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`,sha256 `3dc44b1e90be8fbe5bcc7656032560f51275f985c7e3f783c9028e1838ec7bed`;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`:太阳高度 > 0 `day`;−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.ts` `assembleBirthSky`:`loadSubjectBirth` → `prepareChartViewProfile`(与星盘页同一条路)→ 引擎 → `birthSkyFromEngine`(zod 合同 `lib/birth-sky/contract.ts`)。`timeBasis` = `birthTimeStatus` 为 `accepted` / `confirmed` 时 `adopted`,否则 `reported`。 +- 状态:未登录 401 `unauthenticated`;别人的 / 不存在的 subject 404 `not_found`;资料不全 200 `birth_profile_incomplete`;引擎失败、坏载荷、时区解析失败 503 `unavailable`;每用户每分钟 20 次,超了 429 `unavailable`。只读 `?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.ts` `drawBirthSky(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` 新增可选 `birthSky` prop,`view.status === "ok" && birthSky` 时往 `actions` 放 `BirthSkyEntry`(「那一刻的天空」描边小按钮)。弹层 `components/birth-sky/birth-sky-dialog.tsx`(原生 ``,窄屏全屏、≥860px 居中 520px;`` + 「保存图片」),与其 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.ts` `startBirthSkyReveal`:3 秒计时;期限内拿到画好的封面才 `onReady`;超时 abort 请求、迟到的封面释放 URL;失败静默,不重试。 +- `hooks/use-birth-sky-reveal.ts` 自持 `cover` 状态;`useProfileOnboarding` 新增可选参数 `onOwnBirthPlaceSaved`,只在 `saveOnboardingPlace` 保存成功、且下一步已设好之后调用(fire-and-forget)。`page.tsx` 只接线:+1 个自定义 hook 调用、+1 个参数、+1 个 ``、+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 定) + +1. **星盘页按钮本身不懒加载**:`BirthSkyEntry` 是星盘页 chunk 里一个普通 `