docs(tasks): report reader polish and zh/en report editions briefs (BUG-1092+ reserved)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-09-29 14:27:36 +08:00
co-authored by Claude Opus 5.5
parent 5febb11159
commit 6e393780d9
3 changed files with 171 additions and 0 deletions
+2
View File
@@ -257,6 +257,8 @@
| `TASK-rectification-holdout-expansion-20260929.md` | — | **开放评价集扩到 ≥60 例(v5)**:所有打分判定都在 20 例上做,1 例 = 5pp,KP / 精度闸 / V1n 的 no_benefit 都只差 1–3 例。只用 Rodden AA 公开名人,事件人工核对出处,分层(年代 / 纬度 / 南半球 / 跨午夜 / UTC+8),v4 不动;基线成绩单 v4 子集须与已发布数字逐格一致。研究单的判定以 v5 为准 | 待领取 | — |
| `TASK-rectification-scoring-research-20260929.md` | — | **打分方法研究(离线)**:R-A 似然比校准权重(leave-one-case-out,KP / Pranapada / D60 作为特征由数据定权重、允许负权重)、R-B 缺席证据(口述时间线覆盖时段内的空白年扣分,必测漏说稳健性)、R-C 精度追问可行性(对已说事件追问月份,算在定向追问 2 条额度内)。判定必须在 v5 上做;有收益才立实现单并按 ERR-110 重冻结 | 待领取(脚手架可先在 v4 上开发) | — |
| `TASK-rectification-typed-event-scoring-research-20260929.md` | `PROGRESS-rectification-typed-event-research-20260929.md` | **打字经历按选择题规则计分(离线研究,不上线)**:计分通道不对称 + 已入账年份挡题;R0 学业质量题措辞 / 年精度显示成 1 月(冻结文件,需重新冻结) | 已验收(Claude 2026-09-29 合并验收:全量前端 4302/24 与基线同 24 条环境失败、tsc 0、lint 0 error、`/` Static、gzip +0.16%、快速门 pytest 1001 passed、两份回放复跑一致),待部署核对 | BUG-1088、1089 |
| `TASK-report-reader-polish-20260929.md` | — | **报告页打磨**:生成入口挪进页面主体(删标题栏按钮)、详情页到底部按钮(懒渲染一次到底)、导出按钮带文字、目录一级/二级分层、去掉「字段」与 RL/NL 缩写表头、状态列同义重复去重、报告表格淡底色、分块导出显示真实文件大小 | 待领取(先于英文版单) | BUG-1092 起 |
| `TASK-report-english-edition-20260929.md` | — | **报告中英两版**:同一次引擎计算渲染 zh/en 两遍(不用模型翻译),瑜伽库 406 条补英文、模板与前端表头英文化;中文逐字节不变、英文零汉字、两版数字序列一致;阅读页 `?lang=en` 切换,导出当前语言 + 「问 AI 建议导出英文版」提示;旧报告不补英文 | 待领取(依赖 reader-polish 合入) | — |
| `TASK-serif-headings-20260928.md` | `PROGRESS-serif-headings-20260928.md` | **全站标题改用自托管宋体、正文保持黑体**:产品推翻 BUG-737「CJK 不用衬线」结论(保留「声明的字体必须可加载」「不落系统宋体」两条);Noto Serif SC SemiBold 按通用规范汉字表 6500 字 unicode-range 切片自托管(改名 Jyotisha Serif SC),swap 不 preload;首页宋体流量 ≤300 KB;先于天空封面单 | 已实现待验收(分支 `codex/serif-headings-20260928`,未推送) | 不开新 BUG;BUG-737 追加说明 |
| `TASK-cend-ui-claude-alignment-20260916.md` | `PROGRESS-cend-ui-r1/r2/r3-20260916.md` | **C 端界面向 claude.ai 产品界面对齐(三轮串行 R1→R2→R3,都动 `globals.css`,不得并行)**:根因是 `frontend/CLAUDE_DESIGN.md` 扒的是 **claude.com 营销官网**,它自己在 Known Gaps 里写明 claude.ai 产品界面不在范围内,而 `DESIGN.md:3` 把它当成了产品界面的实现契约。**R1**:`--font-display` 里 Tiempos Headline / StyreneB **从未加载**(无 `@font-face`、`public/` 无字体、`layout.tsx` 只 vendor 了 Inter),中文标题全站落到 **宋体 / SimSun**,波及 20 处含助手回答的 h2/h3(BUG-737);亮色强调色 `#85432f` 与暗色 `#d78064` 不同源,产品拍板亮色换 **Claude coral `#cc785c`**,**易漏点**是 `globals.css:16` 的 `--color-ring` 硬编码在 `@theme inline` 里不跟随 `:root`,另有第四个 `:root` 亮色块(`:4358`)必须同步(BUG-738);`.composer-footer` 常驻 44px + 顶栏 68px + `--composer-reserve` 148px,每屏固定吃掉 216px,模型选择器移进输入框内部、删掉底栏、顶栏收到 46px 并删「分析对象」副标题。**R2**:空状态是营销落地页(hero 卡 + 两张 132px 入口大卡 + 3 列 156px 主题卡),输入框被压在 **800px 以上**内容之下,重排成「问候 + 居中输入框 + 两枚入口 pill + 一排 chip」。**R3**:侧栏两个 `<details>` 拍平成一条「最近」、星盘的两个入口(侧栏分组 + 账户菜单)收敛到一处、删掉逐条助手头像。**决策记录 D3 推翻 DESIGN.md「报告强调色与应用同源」一句**(报告刻意保留深棕)。原型图 https://claude.ai/code/artifact/da275da6-2954-4f50-99aa-32bb8694d38b(三套画面 + 明暗,页面标题就是建议字体栈的实际渲染)。环境缺口:无登录态无 Chrome,四项真机观感留 `docs/testing/`。BUG 段 737–738 | 待领取 | — |
| `TASK-cend-surfaces-claude-alignment-20260916.md` | `PROGRESS-cend-shell-20260916.md`、`PROGRESS-cend-report-20260916.md`、`PROGRESS-cend-rectification-20260916.md`、`PROGRESS-cend-chart-eph-20260916.md` | **次级页面对齐(上一单的续篇,R4→R5/R6,R7、R8 可并行)**:星盘 `/chart`、星历 `/ephemeris`、报告 `/reports` **各是脱离 app 外壳的独立全屏页**,顶部只有一个「返回对话」链接、侧栏整个消失,且三家各写了一套一模一样的 `*-shell`/`*-topbar`/`*-hero` 骨架——与上一单 E5 同根因(营销站 band 结构被套到产品界面)。**R4** 抽只读导航外壳 `AppNavRail`(只用现成的 `GET /api/sessions` + `GET /api/account`,会话行走 `sessionHref` 跳 `/?c=<uuid>`;**刻意不带**重命名/删除/收藏/归档——那套连着 `Home()` 的乐观更新与回滚,搬过来会撞 useState 增长门禁)。**R5** 星盘五 tab 下划线化 + 参数合表 + 行星表横向滚动;星历日期导航改 `‹ 日期 ›`。**R6** 报告中心卡片网格改行式列表;阅读页加常驻目录。**R7** 生时校正把可信区间从盘面板标题行提成常驻条(窄屏 `.is-compact` 下盘面板是 overlay,现在默认看不到区间),五个 `technique-audit` 折叠块收成两段。**R8** 设置内容区收窄(880px 弹窗里表单铺了 690px)、套餐卡三修饰符收敛成两态。**已解锁**:原挡路的设置单已于 `111b4a84`(BUG-698)合入。**两条不得回退**:BUG-698 的 `@supports (height: 1dvh)` 写法(重复声明回退会被 Lightning CSS 折叠)、BUG-616/617 的报告盘面 grid 实现。默认不占 BUG 号 | **R4–R8 全部已实现并验收合入** | R4:抽出 `AppNavRail`(只读,两个 GET,零写操作)+ `SecondaryShell`,三个次级页并入 app 外壳并删掉各自的 shell/topbar/hero;四个路由渲染标记**完全不变**(`/` `/chart` `/ephemeris` 仍 Static);CSS gzip −0.25%。`/reports/[reportId]` 留给 R6 与目录一起做。两处自身健壮性问题被测试抓到:`usePathname()` 可为 null、`fetch` 可能不存在。差点弄丢 BUG-717 的 eyebrow 文案(已放回)。R8:表单分区收窄到 440px(列表分区不变)、套餐卡三修饰符收敛成互斥的 `is-current` / `is-recommended`,`--highlighted` 删除改为滚动定位;手机端 `order:-1` 改挂 `[data-plan-alias]`(版位不是状态)。测试 3350→3354(净增 4),失败清单与基线逐条一致;`/` 仍 Static;我的干净构建实测 CSS gzip −3 字节。**遗留待产品拍板**:`?plan=` 深链现在完全没有视觉指向,只有滚动位置。R6:报告中心卡片网格改行式列表、阅读页并入外壳并把目录挪到右侧常驻。**任务书 E10 过期**——目录在 `cfcd369d` 就已存在,本轮是挪位置定稿而非从零加。挂外壳带出一个真实打印风险已处理:`.chat-app`/`.chat-panel` 是 `height:100%;overflow:hidden`,裸 `window.print()` 会把九节报告裁成一页,阅读页因此多挂一条只在挂载期生效的 print 样式解锁外壳。「生成中的分节进度」做不了——`REPORT_LIST_COLUMNS` 不返回节数,按 VOICE.md 不许前端编。R7:区间常驻条与盘面折叠收敛。**任务书 E9 也不准确**——对话区顶部早有常驻条 `RectificationTimeline` 且窄屏可见,真正只在盘面标题行的是**代表分钟**;因此没另造第二条,在既有条上补齐代表分钟与已答题数(与盘面同一次 `workingRectificationTime()` 调用)。折叠块实际是 **8 个**不是 5 个。**触发让步顺序第 5 条**:收窄进度未做——服务端无该字段,且 `candidate_range` 会放宽(BUG-572),前端相减会把一次放宽报成收窄,已写进 `BLOCKED.md`。顺带修掉一个**静默失效的旧断言**(`slice(indexOf(A), indexOf(B))` 在 B 改名后变成几乎整份文件,四条 `doesNotMatch` 假通过)|
@@ -0,0 +1,79 @@
# TASK · 个人报告中英两版 · 2026-09-29
> 执行方:coding agent。验收:Claude。
> 分支 `codex/report-english-edition-20260929`,worktree `.worktrees/report-english-edition-20260929`。
> **串行依赖**:必须在 `TASK-report-reader-polish-20260929.md` 合入 staging **之后**,基于当时的 `origin/staging` 开工。两单都改 `report-actions.tsx`、`report-export-drawer.tsx`、`scripts/pl9_reader_export.py`。
## 0. 基线
- 写单时的 `origin/staging` 是 `5febb111`;开工基线以 reader-polish 合入后的实测 SHA 为准,写进 `docs/tasks/PROGRESS-report-english-edition-20260929.md`。
- 开工前必读 AGENTS §9 预检(涉及引擎输出),跑 `python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45`。
## 1. 事实(行号按符号定位)
1. **报告全文由程序生成,不经过模型。** `personal-report-longform-generate.ts` → `fetchLongformMarkdown` 调 Python `/api/professional_report_reference`(`edition: "reader_main"`)。`scripts/professional_report_reference.py` → `build_professional_report_reference` 只算一次 `full_reading`,再由 `pl9_reader_export._pl9_export_markdown_for_edition` 渲染成 Markdown。
2. **中文是「先按 PL9 英文口径渲染,再逐词汉化」得到的。** `pl9_reader_export.py` → `_render_pl9_user_markdown` → `_sanitize_pl9_public_markdown` → `_strip_pl9_user_engineering_markers`:这几个函数用 `replacements`、`nakshatra_terms`、`glossary_terms`、`natural_replacements`、`cleanups` 几张表,把英文术语、工程词替换成中文。模板里还有约 440 行直接写死的中文(章节标题,例如 `'## 强度、关系与 Ashtakavarga 技术页(PL9 p30-p60 对标)'`)。
3. 语言开关已经埋了,但基本没用上:`_pl9_report_language(packet)` 读 `packet['report_language']`,只有 2 处用到。
4. 瑜伽规则库 `references/yoga_rules.json`:共 477 条,启用 406 条。每条有英文 `name`,但 `effects`、条件描述、`combo_template`(如 `"{a}与{b}同在第{h}宫"`)**只有中文**。
5. `scripts/reader_dasha_applicability.py` 有 20 行中文。前端的 fact tables、图盘标签(`report-fact-table-display`、`chartAriaLabel`、`chartBlockText` 里的「第 N 宫」)是中文写死的。
6. 存储:计算快照存在 `personal_report_sections`,保留行 `section_id = "longform-calculation-snapshot-v1"`,内容是 `personal-report-longform-snapshot.ts` → `snapshotSchema`(`strictObject`,`version: 1`,字段有 `markdown`、`contentSha256`、`factTables`、`charts`、`readerDashaApplicability`),经 `complete_personal_report_section` RPC 写入。另有 `personal_report_longform_appendices` 表,每份报告存一份 markdown。
7. 虚构案例 golden `frontend/tests/fixtures/report-reader-main-fictional.json`:77,208 字符,其中汉字 10,418 个,不同的中文片段 381 个。
## 2. 决策记录(产品 2026-09-29 授权)
1. **每份新报告都同时有中文版和英文版**,用户不用单独操作。
2. 英文版**不用模型逐份翻译**。做法是同一次引擎计算、同一个 packet 渲染两遍:`report_language = zh` 一遍,`= en` 一遍。这样两版数字必然一致,也不增加模型费用和计算次数。(Claude 原先按「报告由模型撰写」估的 A/B/C 三个方案,前提不成立,全部作废。)
3. 词典类中文内容(瑜伽效果和条件、模板标题、Dasha 适用性说明、前端表头)要补**静态英文对照**,提交进仓库。执行方可以借助模型起草译文,但运行时**不得调用模型**。术语采用 PL9 / BPHS 通行的英文写法(Exalted、Debilitated、Own Sign、Mooltrikona、Sub-lord …)。
4. 英文版是从属产物:英文渲染失败或检查不通过时,**只把英文版标成不可用,中文报告照常 ready**,不能因为英文版失败让整份报告失败。
5. 英文版上线前生成的报告**不补英文版**(旧快照只存了中文 markdown,没有 packet,补译需要重算,数字可能漂移)。这类报告的阅读页**不显示语言切换**,导出弹窗里提示一句「这份报告生成于英文版上线前,重新生成即可获得中英两版」。
6. 阅读页操作栏加「中文 / English」切换,语言写进 URL(`?lang=en`),刷新后保持。切换只换报告正文、目录、图盘标签、核对表;应用外壳(侧栏、标题栏、按钮)保持中文。
7. 导出弹窗导出**当前语言**的版本,英文文件名加 `-EN`。当前是中文且有英文版时,弹窗顶部显示提示:「要拿去问 ChatGPT、Claude 等 AI?建议切到 English 再导出,术语与原典一致,AI 读得更准。」旁边给一个按钮直接切到英文。文案最终版对照 `VOICE.md`。
## 3. 硬红线
1. **中文版逐字节不变**:用同一 packet 渲染,本单前后 golden 的中文 markdown 必须完全相同(diff 为空)。
2. **英文版不得含汉字**:`re.search(r'\p{Script=Han}', md_en)` 必须为空(Python 用 `regex` 模块,或用 `[㐀-鿿豈-﫿]`)。检查不通过时,按决策 4 把英文版标成不可用,**不得带着汉字发出去**。
3. **两版数字一致**:从中、英 markdown 按顺序抽取数字 token(度数、日期、分数、宫号),两个序列必须相等。这条写成合同测试。
4. 英文版同样要过公开投影与泄漏检查(`projectOrdinaryReportMarkdown`、`ordinaryOutputLeaks`),以及 Python 侧的英文版工程词清洗。`parameter_sensitive`、`source_path` 这类工程词在英文版里也要换成公开措辞,不得原样透出。
5. 不新增重计算:引擎只算一次,一次请求返回两种语言;不得前端再发一次引擎请求。
6. **优先不动数据库**:英文内容放进快照 payload(`snapshotSchema` 升 `version: 2`,新增 `en` 子对象,包含 markdown、sha、可选的 readerDashaApplicability)。v1 快照必须仍能读取(旧报告)。确实需要加列时,按 AGENTS §7-6 只能加、不能改或删,并真跑 `npm run test:db`。
7. golden 必须来自真实引擎输出(AGENTS §7-4),用虚构出生资料;不得手造形状。
8. `scripts/jyotish_api_server.py` 不得增长;新代码放进 `professional_report_reference.py`、`pl9_reader_export.py` 或新模块。
## 4. 任务分解
| 编号 | 内容 | 验收标准 |
|---|---|---|
| E1 | 引擎:`/api/professional_report_reference` 接受 `languages: ["zh","en"]`,同一 packet 渲染两遍,响应新增 `markdown_en`(和 `reader_dasha_applicability_en`)。不传时行为与现在完全相同 | 定向测试:不传 `languages` 时响应逐字节等于基线;传入时中文版等于基线,英文版无汉字,数字序列相等 |
| E2 | `pl9_reader_export` 英文路径:`report_language == 'en'` 时跳过汉化表,只做英文版工程词清洗;模板标题补英文(去掉 `PL9 pNN` 页码,与中文版一致);reader-polish 定下的新标题和新表头也要有英文对应(Planet / Sign lord / Star lord / Sub lord / Sub-sub lord / Status / Strength) | 多个虚构案例(至少 3 个:昼生、夜生、南半球)英文 golden 无汉字;人工抽读 20 处并在进度记录列出 |
| E3 | `yoga_rules.json` 406 条启用规则补 `effects_en`、`combo_template_en`(以及渲染中用到的其他中文字段的英文版);`reader_dasha_applicability.py` 补英文 | 合同测试:每条启用规则都有非空英文字段且不含汉字;英文模板的占位符集合与中文模板一致 |
| E4 | 前端生成链:`fetchLongformMarkdown` 请求两种语言;快照 v2 存英文版;英文检查失败时只写中文,并记录 `en_unavailable` 原因码 | 单测:英文缺失或含汉字时,报告仍然 ready,英文标为不可用;v1 快照可读 |
| E5 | 路由:`GET /api/reports/:id` 在有英文版时返回 `longformMarkdownEn`(经公开投影) | route-core 单测覆盖有英文、无英文、旧报告三种情况 |
| E6 | 阅读页:语言切换(`?lang=en`);目录、懒渲染、到底部按钮在英文版下正常;`buildLongformOutline` 的 `EAGER_H2` 等中文正则补英文对应;核对表、图盘标签的英文映射 | 组件测试:切换后正文、目录、表头全部变成英文;无英文版时不渲染切换 |
| E7 | 导出弹窗:导出当前语言,英文文件名加 `-EN`;AI 提示与「切到 English」按钮(§2-7);旧报告显示提示文案(§2-5);预估文件大小按当前语言计算 | 单测:导出内容等于当前语言的块;提示只在「中文 + 有英文版」时出现 |
| E8 | 文档:`DESIGN.md`(切换控件、提示条)、`VOICE.md`(新增英文口径说明)、`CONTEXT.md`(术语:报告语言版本)、`CHANGELOG.md` | 同一提交内更新 |
## 5. 让步顺序
E7 的 AI 提示和 E6 的 URL 持久化可以最后做。E1~E5 与红线 1~4 不让步。英文译文质量不达标时,**宁可英文版标成不可用,也不发半中半英的报告**。
## 6. 开工前置命令
```bash
git fetch origin --prune
git log --oneline origin/staging | grep -i "reader-polish\|report-reader-polish" # 确认前置单已合入
git worktree add -b codex/report-english-edition-20260929 .worktrees/report-english-edition-20260929 origin/staging
cd .worktrees/report-english-edition-20260929
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
grep -oE "BUG-[0-9]{4}" docs/BUG_HISTORY.md | sort -u | tail -1
cd frontend && npm test 2>&1 | grep -E "^(not )?ok [0-9]+ - " | sed -E 's/^(not )?ok [0-9]+ - //' | sort > /tmp/names-before.txt
```
## 7. BUG 编号
本单是新功能,原则上不占 BUG 号。开发中发现的缺陷(例如汉化替换误伤、快照读取问题)从 reader-polish 用完后的下一个号起,按实际占用顺延,并写进进度记录。
## 8. 交付与验收口径
- 前端四件套(tsc、lint、test 名单只增不减、build 后 `/` 仍 Static,首屏 gzip ±2%);Python 定向测试加 `run_quality_gate.py --profile quick`。
- 部署后在 staging 用受控账号生成一份新报告,确认:中文版与改动前观感一致;English 切换可用;导出的英文文件不含汉字。没有受控账号时,写进 `BLOCKED.md` 与 `docs/testing/report-english-edition-20260929.md`,不得写成「通过」。
@@ -0,0 +1,90 @@
# TASK · 报告页打磨(生成入口、到底部、导出、目录、表格、字段文案)· 2026-09-29
> 执行方:coding agent。验收:Claude。
> 分支 `codex/report-reader-polish-20260929`,worktree `.worktrees/report-reader-polish-20260929`,基于 `origin/staging`。
> **串行关系**:本单先做、先合入 staging。`TASK-report-english-edition-20260929.md` 改同一批文件(`report-actions.tsx`、`report-export-drawer.tsx`、`pl9_reader_export.py`),必须基于本单合入后的 staging 开工。
## 0. 基线
- 基线 commit:`origin/staging` = `5febb111`(开工时以实测为准,写进进度记录)。
- 进度记录:`docs/tasks/PROGRESS-report-reader-polish-20260929.md`。
## 1. 事故实证(产品截图 2026-09-29,行号按符号定位)
| # | 现象 | 代码位置 |
|---|---|---|
| P1 | `/reports` 的「生成报告」是标题栏右上角的小描边按钮,空状态里又写「点击'生成完整报告'」,按钮名和文案对不上 | `personal-report-center.tsx` → `PersonalReportCenter` 中的 `<SecondaryPageShell actions={<GeneratePersonalReportButton …/>}>` 与 `.report-center-empty` 段;`generate-personal-report-button.tsx` → `GeneratePersonalReportButton`(`variant="outline" size="sm"`) |
| P2 | 报告详情页没有「到底部」的入口;正文按章节懒渲染,未渲染的章节只占一个 h2 高度,直接 `scrollTo(bottom)` 会停在半路 | `personal-report-markdown-view.tsx` → `LazyMarkdownSection`(IntersectionObserver,`rootMargin: "280px 0px"`) |
| P3 | 导出入口是 ghost 样式、只有图标的按钮,很难发现 | `report-actions.tsx` → `ReportActions`(`size="icon" variant="ghost" aria-label="导出报告"`) |
| P4 | 目录里一级标题(如「强度、关系与 Ashtakavarga 技术页」)和二级标题字号、颜色完全相同,二级只缩进 `--space-3`。一级标题没被选中时,看上去像上一组的子项 | `globals.css` → `.personal-report-toc a`、`.personal-report-toc li[data-level="3"]` |
| P5 | 标题里出现用户看不懂的「字段」:「Birth Chart 星主与状态字段」「Birth Particulars 原版补充字段」「Special Lagnas and Points 原版补充字段」,以及替换表里的 `field_状态 → 字段状态` | `scripts/pl9_reader_export.py`:`'#### p6 Birth Chart 星主与状态字段'`、`'#### p3 Birth Particulars 原版补充字段'`、`'#### p17 Special Lagnas and Points 原版补充字段'`;`_strip_pl9_user_engineering_markers` 末尾的 `text.replace("field_状态", "字段状态")` |
| P6 | 同一张表的「状态」列出现重复:「入旺(入旺)」「落陷(落陷)」「入敌(敌对星座)」。表头 `RL / NL / SL / SS / SB`、`Planet` 用户看不懂 | 同上表的行渲染(`row.get('status')`)经 `_strip_pl9_user_engineering_markers` 的 `glossary_terms` 逐词替换后,括号内外被翻成同一个词。**上游 status 字符串的原始形状由执行方先打印确认再修**,不得猜 |
| P7 | 报告内表格没有任何底色,只有很淡的横线,行与行分不清 | `globals.css` → `.markdown-table th/td`(仅 `border-bottom`);报告页包在 `.personal-report-table-wrap` 里 |
| P8 | 分块导出弹窗只显示字数,不显示文件大小;图盘块里带着整段 SVG,字数和文件大小差得很远 | `report-export-drawer.tsx` → `ReportExportDrawer` 的 `.report-export-count`;`report-export-blocks.ts` → `buildReportExportBlocks`(`chars = Array.from(text).length`,图盘块 text 含 SVG) |
## 2. 根因
都是呈现层的遗漏,不涉及计算:入口放在了标题栏的次要位置;懒渲染没有配套「到底部」;导出做成了只有图标的次要按钮;目录层级只靠 12px 缩进区分;引擎侧的工程词(field、字段)和英文缩写表头直接透给了用户;中文化是逐词替换,没有处理「译文(原文)」译完后两边相同的情况;表格样式沿用了聊天气泡里的极简样式。
## 3. 决策记录(产品 2026-09-29 授权)
1. **生成入口挪进页面主体,按产品图一的布局**:页面上半部分是一块「生成报告」卡片,按钮居中,说明文字放在按钮下方;下半部分是「过往的报告」列表。**标题栏右上角的「生成报告」按钮删除**,不保留两个入口(产品偏好:多余入口宁可删除)。卡片在有报告、没报告时都显示;没报告时,下半部分显示一句轻提示,不再重复一个大空状态。
2. 报告详情页加「到底部」悬浮按钮,位置在右下角。点击时先让所有 `LazyMarkdownSection` 立即渲染,**在同一次点击内**滚到文末。不得出现先停在半路、要再点一次的情况。滚到接近底部时按钮隐藏;可以同时提供「回到顶部」,但**一次只显示一个**按钮。
3. 导出按钮改成带文字的「导出」按钮(带图标、描边或实心,按 `DESIGN.md` 的次级主操作规格),留在操作栏右侧。英文版导出提示**不在本单做**,由英文版任务书负责。
4. 目录:一级标题加粗、用 `--color-ink`,组与组之间留间距;二级标题缩进加大到约 `--space-5`、用 `--color-ink-secondary`。当前项高亮样式不变。
5. 「字段」一词从用户可见标题里全部去掉,用下面的新标题(中文口径对照 `VOICE.md`):
| 原标题 | 新标题 |
|---|---|
| p6 Birth Chart 星主与状态字段 | 行星主星与状态 |
| p3 Birth Particulars 原版补充字段 | 出生资料补充 |
| p17 Special Lagnas and Points 原版补充字段 | 特殊上升与派生点补充 |
| `field_状态 → 字段状态` | 改为「状态」 |
6. 「行星主星与状态」一表的表头改成中文:`Planet → 行星`、`RL → 星座主`、`NL → 星宿主`、`SL → 分主`、`SS → 分分主`、`SB → 力量`。表下加一行小字说明这几个名称的含义,不超过一句。状态列同义重复时只保留一个(「入旺(入旺)」→「入旺」);括号内是不同信息的保留(如「入敌(敌对星座)」要先确认上游原文再决定)。
7. 表格加淡底色,**只作用于报告阅读页和打印**,聊天气泡里的 `.markdown-table` 不动:表头一层淡底,正文隔行一层更淡的底(斑马纹),颜色取现有 token 的 `color-mix`,不新增硬编码色值;深色模式同样适用。打印时底色保留,但要更浅,保证黑白打印可读。
8. 分块导出弹窗显示预估文件大小:取**真实要下载的字节数**,即 `new TextEncoder().encode(joinExportBlocks(selected)).length`,按 KB / MB 取整显示,例如「约 186 KB」。不按字数估算。全篇大小和已选大小都显示。
## 4. 硬红线
1. 不改报告的计算与数值;Python 改动只限 §3-5、§3-6 的标题、表头和状态去重。`report-reader-main-fictional.json` golden 除这几处文案外,逐字节不变(执行方用 diff 证明)。
2. 不新增第二套滚动跟随;「到底部」是报告页自己的按钮,不接入 `useConversationScrollAnchor`。揭幕后不得出现 spinner 或骨架。
3. 不动数据库、不动迁移、不动 `.gitea/`。
4. 改 UI 的同一提交内更新 `frontend/DESIGN.md`(报告中心、报告阅读页、表格三处);新文案对照 `frontend/docs/VOICE.md`。
5. AGENTS §7-8:动类名、文案、组件之前先 `git grep -n "<符号>" -- tests/ frontend/`。至少要查:`GeneratePersonalReportButton`、`report-center-empty`、`还没有个人报告`、`生成完整报告`、`导出报告`、`report-export-count`、`personal-report-toc`、`星主与状态`、`原版补充字段`、`字段状态`、`markdown-table`。被牵连的断言按「原值 / 新值 / 原因」三栏写进进度记录。
## 5. 任务分解
| 编号 | 内容 | 验收标准 |
|---|---|---|
| R1 | 报告中心改版(§3-1) | 标题栏不再有生成按钮;主体上方是生成卡片,下方是「过往的报告」;无报告、有报告、生成中三种状态的截图或组件测试各一份;原按钮的防重复点击、错误提示逻辑不变(`classifyCreateResponse` 测试照过) |
| R2 | 到底部按钮(§3-2) | 组件测试:未渲染章节存在时点击,所有章节变成已渲染,且触发一次滚到底部;接近底部时按钮不显示 |
| R3 | 导出按钮(§3-3) | 按钮有可见文字「导出」;键盘可达;打印时隐藏(沿用 `personal-report-screen-only`) |
| R4 | 目录层级(§3-4) | 一级、二级样式可区分;`DESIGN.md` 写明规格 |
| R5 | 字段文案与表头(§3-5、§3-6) | Python 定向测试断言新标题和新表头;golden 中不再出现「字段」二字,也没有 `X(X)` 形式的同义重复 |
| R6 | 表格底色(§3-7) | 仅报告页生效;聊天页表格的计算样式不变(写一条选择器作用域断言或截图对比) |
| R7 | 导出大小(§3-8) | 单测:选中块的显示大小等于 `TextEncoder` 字节数的格式化结果;含 SVG 的图盘块大小明显大于字数 |
## 6. 让步顺序
时间不够时按这个顺序往后砍:R6 → R4 → R2。R1、R3、R5、R7 必须交付。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/report-reader-polish-20260929 .worktrees/report-reader-polish-20260929 origin/staging
cd .worktrees/report-reader-polish-20260929
grep -oE "BUG-[0-9]{4}" docs/BUG_HISTORY.md | sort -u | tail -1 # 核对最大号
cd frontend && npm test 2>&1 | grep -E "^(not )?ok [0-9]+ - " | sed -E 's/^(not )?ok [0-9]+ - //' | sort > /tmp/names-before.txt
```
## 8. BUG 编号
开工时 `BUG_HISTORY.md` 最大号为 BUG-1089,BUG-1090/1091 已被 holdout 任务书预留。本单从 **BUG-1092** 起,预计用到 BUG-1094:P5 + P6 各记一条,P1~P4、P7、P8 合记一条「报告页可用性」。开工时如果号已被占用,顺延,并在进度记录写明。
## 9. 交付
- 推 staging 前:`tsc --noEmit` 0 错;`npm run lint` 0 error;`npm test` 测试名单与开工时对比,只增不减;`npm run build` 后 `/` 仍是 Static;首屏 gzip 在 ±2% 内;`.venv/bin/python -m pytest tests/<相关文件>` 通过;`run_quality_gate.py --profile quick` 通过。
- 真机清单写进 `docs/testing/report-reader-polish-20260929.md`(桌面端、手机端各走一遍 R1~R3、R6)。
- `CHANGELOG.md` 记一条。