docs(tasks): briefs for slow-network home, chart/ephemeris fixes, report actions and reader_main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-24 14:42:49 +08:00
co-authored by Claude Opus 5.5
parent 9c9b66d64c
commit e0db23f47a
5 changed files with 301 additions and 0 deletions
+4
View File
@@ -117,6 +117,7 @@
| 任务书 | 进度 | 主题 | 状态 | 落点 |
| --- | --- | --- | --- | --- |
| `TASK-home-slow-network-20260924.md` | — | **慢网首页半揭幕 + 账户与点数打开慢(BUG-1021/1022)**:8 秒硬超时包住多跳串行请求、超时不补拉,独立的会话列表 provider 晚到 → 正常首页+报错条、模型「暂不可用」、新建对话灰、重问称呼。产品 09-24 拍板:推翻 8 秒硬超时,等齐再揭幕,20 秒才出错误屏且重试为局部重请求(不整页刷新);账单面板删无用 `/api/account`、预取 chunk、套餐缓存。先于 chart-ephemeris T3 合入 | 待领取 | — |
| `TASK-secondary-new-chat-intent-20260923.md` | `PROGRESS-secondary-new-chat-intent-20260923.md` | **次级页「新建对话」落到最近一次对话**:`/chart` `/ephemeris` `/reports` 的侧栏没有 `controls`,「新建对话」只是 `href="/"` 的回首页链接;首页无参启动的落点是 `nextSessions[0]`(最近更新那条),只有最近一条是校正会话才改落空咨询。BUG-745 修过同一入口的「慢」,没修「回到哪」。产品 09-23 拍板:链接改 `/?new=1`,首页看到 `new` 就本地建空咨询并 `replaceState` 抹掉参数;`new` 胜过 `c` 与登录返回存根;沿用 BUG-989 不落库不写 `?c=`;页脚与品牌行的 `/` 不改;刷新无参 `/` 仍落最近对话属既有设计。红线:`/` 保持 Static、不得用 `useSearchParams`;解析与生成只在 `chat-session-url.ts` 一处。BUG-1015;执行中追加 D7:保留旧任务恢复但不抢新建落点。 | 已验收(Claude 09-24:tsc/lint/95 定向/全量失败名单同基线/Static/gzip 同基线;BUG-1015 resolved) | `8902e484`,含于已部署 `1420471a`;真机清单欠 |
| `TASK-chat-message-authority-20260901.md` | — | 消息服务端权威化 | 已验收 | `b6989c3e`(BUG-464) |
| `TASK-session-url-20260901.md` | — | 会话 URL 化 | 已验收 | `924f4202` |
@@ -170,6 +171,8 @@
| 任务书 | 进度 | 主题 | 状态 | 落点 |
| --- | --- | --- | --- | --- |
| `TASK-report-reader-main-20260924.md` | — | **报告正文换上游读者版 reader_main + KP / Muntha 计算缺陷 + 投影截断(BUG-1026/1027/1028)**:正文一直是上游审计版(读者版 09-09 才出、从未同步),本仓又自加「结论等级规则」/英文段/异常原文;投影黑名单不认 PL9 词汇且截断半句、删表留说明;KP args `vars()` 拷贝为空恒失败;Muntha 漏合 `birth_asc_sign_idx` → Year Lord 恒火星。产品拍板接入读者版、旧报告不回填。引擎类需 pre_work_check;排在 BUG-1020 fix 之后 | 待领取 | — |
| `TASK-report-reader-actions-20260924.md` | — | **报告操作收敛**:列表只留「查看报告」;阅读页删「下载原始附录」「打印」,导出改图标按钮;导出对话框 ≥860 居中、窄屏仍贴底(修订 chapter-export D9/D10)。产品变更,不开 BUG 号 | 待领取 | — |
| `TASK-report-chapter-export-fix-20260924.md` | `PROGRESS-report-chapter-export-20260924.md` | **CRLF图盘JSON残留(BUG-1020)**:旧剥离器只匹配LF;建议D5对错误围栏残留作有限例外,D13不构成该授权。 | 待领取(产品 09-24 批准 D5 有限例外 D14) | — |
| `TASK-report-chapter-export-20260924.md` | `PROGRESS-report-chapter-export-20260924.md` | **我的报告:分块导出 + 中文元信息 / 状态色 / 行内确认删除(BUG-1018/1019)**。旧整篇按钮由抽屉取代;常用 / 全部正文保持 D5 逐字节,所有组全选另附盘和核对表。后续产品裁决 D13:分盘必须带安全 SVG,有限替代原无 SVG 约束;JSON 围栏及内部字段仍禁。原生底部 dialog、760px、70%、860 两列、零额外请求、保留打印 / 原始附录。 | 已验收(Claude 09-24:同上;D13 SVG 与列表页删导出按钮均按产品口径;BUG-1018/1019 resolved;CRLF 见 fix 单) | `1420471a`(staging 已部署);真机清单欠 |
| `TASK-report-density-20260922.md` | `PROGRESS-report-density-20260922.md` | **报告信息密度与原始附录(BUG-1003~1007)**:与源头仓 PL9 全量报告同资料实测:对照 847,632 字符 / 8,044 表格行,我方 309,357 / 1,478。缺口不在引擎——20 张分盘全表、KP 四表、Ashtakavarga 四类、Shadbala 六分量、Avastha、Sahams、年度都已算得出,但 `personal-report-contract.ts` 的 `REPORT_SECTION_KINDS` **没有表格类型**、`CHART_IDS` 只有 9 种,这些表在产品页报告里没有落脚字段。另测出对外原始附录泄漏 925 种 / 3,797 处工程标识符(`parameter_sensitive` 697、`cmd_full_reading` 68、`PyJHora`/`JHora` 35、`PL9` 页码 59),对照物同项为 0。**产品 2026-09-22 三点拍板**:层 1 按 8 组表 + 分盘扩到 16 张;层 2 原始附录直接给 C 端且先全显示;B 类大运族要显示。**决策记录已写明推翻 BUG-999 的两条红线**(专业参考不得作普通下载 fallback;`parameter_sensitive`/「参数敏感」命中整行剔除)——**仅限附录通道**,普通正文投影不动。硬红线:表格层服务端装配、writer 不得写表;对照物的替代大运族日期与 Shadbala 分量在源头仍未闭环(506 行 date mismatch、`production_tuning_allowed=false`),不得照搬升级。让步顺序与串行依赖(任务 3 → 任务 4 同改契约)见任务书 | **验收未通过**(2026-09-23):任务 1/2/4/5 通过;任务 3 事实表在普通报告里显示 447 个引擎键路径(P1);隐私门新增失败(任务书自身写入真实出生资料 BUG-1008 已清 HEAD,fixture 数值碰撞待登记);当时 BUG-1003~1007、PROGRESS、testing 清单缺失;F3 现已补前两项,testing 新建受工具边界阻塞仍缺。Node 22 前端 0 新增失败、`/` Static、gzip +0.007% | 实现 `bbd96d3b`(原 `f968cb21`,09-23 为清除 BUG-1008 重写历史;已在 staging,未部署);修复单 `TASK-report-density-fix-20260923.md` |
@@ -196,6 +199,7 @@
| 任务书 | 进度 | 主题 | 状态 | 落点 |
| --- | --- | --- | --- | --- |
| `TASK-chart-ephemeris-fixes-20260924.md` | — | **Chara 0 年大运崩溃 + 星历切日期旧数据 + 星盘无资料空状态(BUG-1023/1024/1025)**:mapper 0/0 → `RangeError`(约 7.8% 星盘,产品定照实显示 0 年、不改引擎);BUG-966 删掉重置致日期与数据分叉(回归);无资料只有一句话,加「去填写出生资料」+ `?settings=chart` 深链(T3 排在 home-slow-network 之后)。大运 / 西洋版式重做不在本单 | 待领取 | — |
| `TASK-chart-page-skeleton-wait-20260924.md` | `PROGRESS-chart-page-skeleton-wait-20260924.md` | **星盘页骨架盘等待态 + 三个「永远还没拿到」死角**:「这一张盘还没拿到。」只代表 `view === null`,是唯一等待态,失败各有句子;但客户端无超时(A)、401 跳转前(B)、分盘层失败 / 429 后 `ChartVedicTab` 只看 `!selected` 不看 pending(C)三条路都让这句永远挂着。**产品 09-24 拍板推翻 09-18 方案一(BUG-966)**:星盘页盘位用骨架盘 + 呼吸动效(复用 `VedicChartSvg` 空模型,不复制几何;`prefers-reduced-motion` 静止),范围只限星盘页会出盘轮的位置,首页 / 侧栏 / 星历 / 报告的「不放骨架」合同不动;`AGENTS.md` §6 与 `DESIGN.md` §9 / §15 追加例外。三死角出口:客户端 25s 超时 → `timedOut` 句、401 先写失败体、按层失败态 `layerFailures`(429 独立档,BUG-715/723)。BUG-1016 / 1017 | 已验收(Claude 09-24:红线全绿、+46 测试、失败名单同基线、四路由标记与 gzip 同基线;BUG-1016/1017 resolved) | `1420471a`(staging 已部署);真机清单欠 |
| `TASK-smalltalk-test-stdout-mock-20260921.md` | `PROGRESS-smalltalk-test-stdout-mock-20260921.md` | **测试基础设施(BUG-995)**:`consultation-smalltalk.test.ts` 为验证「SDK 不得把私密文本写进日志」,用 `t.mock.method(process.stdout/stderr, "write", …)` 全局接管标准输出——而 `node:test` 的 TAP 报告也走 `process.stdout`,于是运行器自己的 `# Subtest:` / `ok` 行被吞进测试的 `logs` 数组。实测四次全量:`default Mastra adapter` 四条用例只上报 4 / 2 / 1 / 1 条(`Subtest` 声明本身就少,不是 grep 锚点问题),`# tests` 因此 ±3 抖;单跑该文件 33 条静态展开只报 30 条,仅循环最后一个 `provider_error` 稳定可见。**更要紧**:注入必失败探针后,`invalid_schema` / `bad_json` 只剩一行 `not ok 1 - <文件绝对路径>`,**没有用例名、没有断言消息**(退出码仍是 1,门禁不会漏掉失败,不夸大)。定位 BUG-987 时靠的就是从门禁日志 grep `not ok` 抓用例名,红在这三条上那条路会断。**决策**:mock 改为记录后**透传给原始 `write`**(保留隐私断言,不换报告通道,不改 `npm test` 脚本);F3 把「比用例名列表 diff、不比 `# tests` 总数」写进 `frontend/AGENTS.md`。全仓只此一个文件用了这个手法(已 grep 确认)| 待验收 | `2503c019` |
| `TASK-frontend-optimization-20260828.md` | `PROGRESS-frontend-optimization-20260828.md` | 前端优化九条 | 待核对 | 分支 `codex/frontend-optimization-20260828` |
@@ -0,0 +1,93 @@
# TASK · Chara 0 年大运崩溃 + 星历切日期旧数据 + 星盘无资料空状态(2026-09-24)
## 基线
- `origin/staging = 9c9b66d6`。分支 `codex/chart-ephemeris-fixes-20260924`。
- **串行**:T1、T2 可立即做;T3 碰 `page.tsx` / `settings-url.ts`,**排在 `TASK-home-slow-network-20260924.md` 合入之后**(或 T3 单独后合)。大运页、西洋盘 UI 重做**不在本单**(等原型定稿另出单),本单只修 bug 不改版式。
## 事故实证
**T1 · 大运右栏「这张盘算不出来,我们已经记录下来了。」**
- `frontend/src/lib/chart-view-mapper.ts` `charaTrack()` → `addWeightedSpan(start, end, index, weights)`,权重 `finite(duration_years) ?? 1`;`?? 1` 不替换 `0`。某段 Chara 大运为 0 年时 12 个子运全为 0 → `total = 0` → `0/0 = NaN` → `new Date(NaN).toISOString()` 抛 `RangeError`。
- `chart-view-load.ts` `assembleChartView` catch 非 ZodError → `engine_bad_payload` → `use-chart-page.ts` `requestLayer` `fail(...)` → `chartLayerFailureCopy` → `unavailable` 句。
- 0 来自 `scripts/jaimini.py` `_chara_dasha_duration_profile`(kn_rao):`years = count-1`,`<=0` 改 12,**之后**再按旺 +1 / 弱 -1,于是"一宫之隔且落陷"= 0。排查实测随机 2000 个生日(1960–2005)有 **7.8%** 至少一段 0 年;虚构 1985-08-03 03:59 北京可复现(Sagittarius 0 年)。
- 引擎响应形状与 mapper 读取路径一致,不是请求或超时问题。
**T2 · 星历切换日期,下方仍是前一天数据**
- `frontend/src/components/ephemeris/ephemeris-page.tsx` `changeDate`:`setDate(next); const cached = …; if (cached) setState(ready…)`,**缓存未命中无 else**,`state.payload` 保留旧日;顶部日期已是新日。
- 回归来源:`9ccb65b7`(BUG-966,09-18)删掉原 `else setState({ phase: "open" })`,因为同时加的 `if (!payload) return <SecondaryPageShell waiting…>` 会把整页(含日期条)换成等待句。
- 请求失败(429 / 非 JSON / 坏包)时 `load` 直接 `return`,旧数据**永久**留着,且不重试(effect 只随 `date` 变)。
- 挂载时 `refreshEphemerisPage()`(今天、无 date)不可中止,其回写守卫只比 `payload.date`,会在"切到未缓存日期"期间把今天的数据写回。
- `app/api/ephemeris/route.ts` 每切一天都并行重算本命 `/api/chart`(与日期无关),且不把 `request.signal` 传给引擎调用。
- 违反 `frontend/DESIGN.md` §16:「切日期立刻换五要素和行运……数据未到时写静态句「这一天的五要素还没拿到。」「这一天的行运还没拿到。」」。
- 无 BUG 记录、无测试覆盖 `EphemerisPage` 状态机(`ephemeris-page.test.tsx` 只静态渲染 `EphemerisView`)。
**T3 · 星盘页无出生资料时只有一句话**
- `chart-page-view.tsx`:任何非 ok 状态只渲染 `<section className="chart-page-message"><p>{view.message}</p></section>`;`birth_profile_incomplete` 文案「还没有可用来排盘的出生资料。把日期、时间和地点填进星盘资料后,打开就能看见盘。」但**没有按钮**;`lib/settings-url.ts` `parseSettingsQuery` 只支持 `?settings=billing`,没有跳到星盘资料的深链。
## 根因
- T1:mapper 按权重分配时间,未处理总权重为 0;引擎 kn_rao 口径可产出 0 年(合法值,不是引擎错误)。
- T2:BUG-966 为避免整页换等待句,删掉了"未命中就清空",导致日期与数据分叉;失败无出口。
- T3:失败页是纯文案设计,缺行动入口与深链。
## 决策记录(产品 2026-09-24)
- **D1** 0 年大运**照实显示**:该段标注「0 年」,不占时间轴、不展开子运(子运区写「本段时长为 0,不展开」),前后段日期连续;**不改引擎口径**(kn_rao 规则不动)。
- **D2** 星历切到未缓存日期:日期条与布局保留,五要素 / 行运区立即换成现有静态句(「这一天的五要素还没拿到。」「这一天的行运还没拿到。」),事件区写新句「换座和停滞还在取。」(不再误用「还没接上」)。**不加 spinner / 骨架**(星盘骨架例外不外溢到星历)。请求失败 → 各区显示「这一天的星历没取到,点日期再试一次。」,再点同一日期或左右箭头即重试。
- **D3** 星盘无资料:`chart-page-message` 加标题「还没有星盘资料」+ 现有文案 + 按钮「去填写出生资料」→ 打开设置弹窗的星盘资料面板(新增深链 `?settings=chart`,与 `?settings=billing` 同一 helper)。`timezone_resolver_failure` / `adopted_calculation_incomplete` / `profile_query_error` 也给同一按钮(文案各自不变)。样式:居中、标题 `--type-title-md`、按钮为主按钮,其余沿用现有 token。
- **D4** `/api/ephemeris` 本命盘按账户 + 资料指纹做服务端短缓存(复用 `chart-view-engine` 的 `cachedPostEngine` 或同等 5 分钟 TTL),并把 `request.signal` 传入引擎调用。
## 硬红线
1. 不改 `scripts/jaimini.py` 口径;不改 `HEAVY_COMPUTE_PATHS` 与并发(BUG-717)。
2. 星历不得出现骨架 / spinner / 「正在加载」(DESIGN §9 例外只限星盘)。
3. 旧日期响应不得覆盖新日期(含挂载时的今天刷新)。
4. 深链解析只在 `settings-url.ts` 一处;`/`、`/chart`、`/ephemeris` 保持 `○ Static`。
5. 既有断言三栏;测试总数不低于基线;不改 Python。
## 任务分解
### T1 · Chara 0 年(`chart-view-mapper.ts`、`chart-dasha-tab.tsx`)
- `addWeightedSpan` / `charaTrack` 处理 0 权重与 0 年大运;0 年段在 `Track` 里显示「0 年」且无子运列表。
验收:`chart-view-mapper` 测试用**真实引擎响应** golden(虚构 1985-08-03 03:59 北京,按 AGENTS §7.4 由真实 `_compute_chara_dasha` 生成并入库 fixture)→ 12 段 periods、Sagittarius 段 0 年、无 `RangeError`、前后日期连续;`assembleChartView({layers:["chara"]})` 返回 ok;UI 测试 0 年段不渲染子运。
### T2 · 星历(`ephemeris-page.tsx`、`ephemeris-view.ts`、`app/api/ephemeris/route.ts`)
- `state` 只在 `payload.date === date` 时视为 ready,否则 `EphemerisView payload={null}`(保留日期条);事件区 pending 文案;失败态与重试;挂载刷新守卫比对 `date`;路由本命缓存 + 传 signal。
验收:新增 `EphemerisPage` 生命周期测试——切到未缓存日期立即显示静态句且日期条仍在;旧响应迟到不覆盖;429 → 失败句,再点重试成功;挂载时今天刷新不覆盖正在看的其它日期。路由测试:同账户连续两天只调一次本命 `/api/chart`。
### T3 · 星盘空状态 + 深链(`chart-page-view.tsx`、`settings-url.ts`、设置弹窗打开入口;**排在 home-slow-network 之后**)
验收:四种资料类失败都渲染按钮;点按钮 → `/?settings=chart` → 设置弹窗打开到星盘资料;`?settings=billing` 行为不变;`/chart` 仍 `○`。
### T4 · 记录
- `docs/BUG_HISTORY.md`:**BUG-1023** Chara 0 年大运前端崩溃(关联 BUG-715、1017);**BUG-1024** 星历切日期旧数据(**复发自 BUG-966 的修复副作用**,写明旧测试为何没拦住);**BUG-1025** 星盘无资料无入口。
- DESIGN §15 / §16、VOICE(新增三句)、CHANGELOG、PROGRESS、`docs/testing/chart-ephemeris-fixes-20260924.md`(Slow 3G 下星历连点三天、断网切日期、0 年大运样例、无资料账号点按钮)。
## 让步顺序
1. T1 若 golden 生成需要 swisseph 而执行环境无,允许用排查时的虚构生日在有 swisseph 的机器生成后入库,注明生成命令。
2. D4 缓存若与路由结构冲突,可只做传 signal,缓存写入 BLOCKED。
3. 不得让步:D1 不改引擎、红线 2、红线 3。
## 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/chart-ephemeris-fixes-20260924 .worktrees/chart-ephemeris-fixes-20260924 origin/staging
cd .worktrees/chart-ephemeris-fixes-20260924/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -5
npx tsx --test tests/ephemeris-page.test.tsx tests/ephemeris-route.test.ts tests/chart-page-view.test.tsx tests/chart-page-hook.test.ts
```
## BUG 编号
本单 **BUG-1023 / 1024 / 1025**(1021/1022 由慢网单预留),开工时核对。
@@ -0,0 +1,88 @@
# TASK · 慢网首页半残 + 「账户与点数」打开慢(2026-09-24)
## 基线
- `origin/staging = 9c9b66d6`(代码同已部署 `1420471a`)。
- 分支 `codex/home-slow-network-20260924`,worktree `.worktrees/home-slow-network-20260924`。
- **串行**:本单改 `frontend/src/app/(app)/page.tsx`;`TASK-chart-ephemeris-fixes-20260924.md` 的 T3(设置深链)也碰 `page.tsx` / `settings-url.ts`,**本单先合,那一项后合**。
## 事故实证
产品真机:Chrome DevTools「Slow 3G」强刷 `/` → 先转加载环 → 揭幕后出现报错条「连接云端服务超时。请检查网络后重试,或返回登录页重新建立会话。」→ 历史列表稍后自己出来,但模型显示「暂不可用」、「新建对话」灰掉、开场又问「请问我该怎么称呼你?」(账号早有称呼与资料)。
代码链(`origin/staging`):
1. `frontend/src/app/(app)/page.tsx` bootstrap `useEffect` 内联 `8000` ms 定时器:`controller.abort(); setAccountError("连接云端服务超时。…"); setHydrated(true);`。无命名常量、无重试(effect 依赖 `[]`,只有全屏错误页的「重试」= `location.reload()`)。
2. 8 秒要覆盖的是**串行链**:`await sessionListReady`(`SessionListProvider` 的 `/api/sessions` + `/api/account`)→ `fetchModelCatalog`(`/api/models`)→ `/api/consult/status` → 可选 lookup / `fetchSessionDetail`。慢网每跳 ≥2s,必然超时。
3. `SessionListProvider`(`frontend/src/lib/session-list-context.tsx` `loadSessionList`)用自己的 `AbortController`,**不受 8 秒 abort 影响**,稍后成功 → `setAccount` / `setSessions`。于是渲染门 `!hydrated || (!account && !accountError)` 走到"正常首页 + 报错条",而不是错误屏。BUG-927 把账户 / 会话读取搬进 provider 时,没有和这个 8 秒超时对齐。
4. `setModelCatalog` / `setProfile` / `setStartGreeting` / `setOnboardingStep` 只在成功路径最后执行,超时后永远不执行:`modelCatalog = null` → `model-selector.tsx`「暂不可用」、`use-home-shell-registration.ts` `newChatDisabled: … || !modelCatalog`;`profile = emptyProfile`、`onboardingStep` 初值 `"name"` → `home-types.ts` `presetOnboardingMessage`。
「账户与点数」:
5. `page.tsx` `const BillingPanel = dynamic(() => import("@/components/billing-panel"), { ssr: false })`,**无 `loading`、无预取** → 首次打开整块空白直到 chunk 下完。
6. `frontend/src/hooks/use-billing-panel.ts` 挂载时 `Promise.allSettled([fetchAccountData(), fetchPackages()])`:`fetchAccountData` 请求完整 `/api/account`(auth + 4–6 次串行查询),**结果被丢弃**(面板用的是 Home 传入的 `account`);`/api/payment/packages` 串行 4 次 DB 查询(`readEpayAvailability` → feature flags → `billing_products` → `product_entitlements`)。
7. 每次打开重挂、`cache: "no-store"`,零缓存。
## 根因
- A:首页账户阶段用一个固定 8 秒总预算包住多跳串行请求,超时后放弃且不补拉;账户 / 会话由独立 provider 读取,与该预算脱节,产生"半揭幕"状态。
- B:账单面板懒加载无占位无预取 + 多一次无用的完整账户请求 + 套餐接口串行查库 + 零缓存。
## 决策记录(产品 2026-09-24)
- **D1 推翻 8 秒硬超时(BUG-479 "8 秒 bootstrap 超时不变")与 09-22 `TASK-home-bootstrap-reliability` 的"不自动重试"在本场景的适用**:首页账户阶段**不设 8 秒硬超时**,加载环一直转到账户、模型列表、资料都到齐再一次揭幕("一次等待一次揭幕"不变)。
- **D2 兜底**:等待超过 `HOME_BOOTSTRAP_SLOW_MS = 20_000` 时切到现有全屏错误屏(「暂时无法进入 Jyotisha」+ 现有文案),「重试」改为**局部重新执行 bootstrap**(重新发请求,不 `location.reload()`);「返回登录」保留。首屏内联 13 秒 ES5 兜底(BUG-936,`first-paint-fallback.ts`)只管 bundle 没跑起来的情况,**阈值需同步上调到 > 20 秒 + 余量**,保证 bundle 正常运行时不会被它抢先。
- **D3 不再出现半揭幕**:任何情况下都不得出现"首页已揭幕但 `modelCatalog` / `profile` 未填"的组合。若 provider 已成功而模型列表失败,走 D2 错误屏,不走报错条。
- **D4 模型列表与账户并行**:`/api/models` 不再等 `sessionListReady`,与 provider 并行发出;`/api/consult/status` 同理并行。
- **D5 账单面板**:去掉 `use-billing-panel` 里的 `fetchAccountData`(无用请求);`BillingPanel` 在设置弹窗打开(或指针移到「账户与点数」导航)时预取 chunk;`/api/payment/packages` 结果做模块级缓存(同会话 60 秒内复用、后台刷新,参考 `secondary-page-data.ts` 的 `createMemoryCache`);服务端 4 次查询中无依赖的并行(`Promise.all`)。chunk 未到时面板区域显示现有 `SecondaryPageShell` 同类静态句「账户信息还没拿到。」,不加 spinner。
## 硬红线
1. `/` 保持 `○ Static`;首屏 gzip ±2%。
2. 揭幕后不得出现 spinner / 骨架 / 「正在加载」;等待期仍只用现有 `AppLoadingIndicator`,不造第二套。
3. 不得整页自动刷新(`location.reload` 只允许用户点击);D2 的局部重试是重新发请求,不是刷新。
4. `Home()` 不新增 useState / useRef(`home-shell-growth-contract`);新逻辑进 `frontend/src/lib/` 或 hook。`page.tsx` 不得净增行。
5. 不改 `/api/account`、`/api/sessions` 的响应结构;不改数据库。
6. `first-paint-fallback` 的六类失败分类(`hydrate-timeout` … `api-error`)保持可区分。
7. 既有断言改动写三栏;测试总数不低于基线。
## 任务分解
### T1 · 首页启动(`page.tsx` + `frontend/src/lib/home-bootstrap.ts` + `session-list-context.tsx`)
- 删除内联 8000 定时器;新增 `HOME_BOOTSTRAP_SLOW_MS`;模型列表 / consult status 与 provider 并行;到齐后一次性 `set*`;超过 20 秒 → 错误屏;「重试」调用可重入的 bootstrap 函数(需要 abort 上一轮、重置 provider 读取)。
- `first-paint-fallback.ts` 阈值同步调整并更新其合同测试(三栏)。
验收:新测试(真实 React 生命周期或纯函数状态机)覆盖——慢网 15 秒全部到齐 → 正常揭幕、无报错条、模型可选、不问称呼;模型接口失败而 provider 成功 → 错误屏而不是半揭幕;>20 秒 → 错误屏,点重试重新请求且成功后揭幕,`location.reload` 未被调用。`first-paint-fallback-contract.test.ts` 更新阈值断言。
### T2 · 账单面板(`use-billing-panel.ts`、`billing-panel.tsx`、`account-dialog-overlay.tsx`、`app/api/payment/packages/route.ts`)
- 删 `fetchAccountData`;预取 chunk;packages 模块缓存;服务端并行查询;静态占位句。
验收:打开面板时网络只有 1 个请求(`/api/payment/packages`,缓存命中时 0 个);第二次打开立即显示套餐;`use-billing-panel` 源码不再请求 `/api/account`;packages route 测试断言并行后结果不变。
### T3 · 记录
- `docs/BUG_HISTORY.md`:**BUG-1021** 慢网首页半揭幕(关联 BUG-479、927、936;复发自:无,BUG-927 引入的时序脱节);**BUG-1022** 账户与点数打开慢。
- `frontend/DESIGN.md` §9 首页揭幕规则更新(无硬超时、20 秒错误屏、局部重试);`CHANGELOG.md`;PROGRESS;`docs/testing/home-slow-network-20260924.md`:Slow 3G 强刷首页 → 只有加载环、揭幕后正常;Offline 强刷 → 20 秒后错误屏,恢复网络点重试 → 进入;设置 → 账户与点数首次 / 第二次打开耗时。
## 让步顺序
1. 若 provider 无法干净地"重新执行",允许重试时重建 `SessionListProvider` 的读取 key,但不得整页刷新。
2. `/api/payment/packages` 并行若受事务约束,允许保持串行,只做客户端缓存与去掉多余请求。
3. 不得让步:D3(不再半揭幕)、红线 3(不自动整页刷新)、红线 1(Static)。
## 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/home-slow-network-20260924 .worktrees/home-slow-network-20260924 origin/staging
cd .worktrees/home-slow-network-20260924/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -5
```
完成后 tsc 0 / lint 0 error / 失败名单同基线 / `next build` `/` 仍 `○` / gzip ±2%。
## BUG 编号
写单时最大 BUG-1020;本单 **BUG-1021 / 1022**,开工时核对。
@@ -0,0 +1,43 @@
# TASK · 报告操作收敛:列表只留「查看报告」、阅读页只留导出、Web 端导出改居中对话框(2026-09-24)
## 基线
- `origin/staging = 9c9b66d6`(含已部署 `1420471a` 的分块导出)。分支 `codex/report-reader-actions-20260924`。
- 串行:与 `TASK-report-chapter-export-fix-20260924.md`(BUG-1020,改 `report-chart-block.ts` / 导出块)文件不交叉,可并行;与 `TASK-report-reader-main-20260924.md` 不交叉(那单改引擎与投影)。改 `globals.css` 报告段,后合者 rebase。
## 事故实证(产品 09-24 真机)
1. 列表页已完成行:「查看报告」+「分块导出」(`personal-report-center.tsx`,后者链到 `/reports/:id#export`)。产品:进报告后本来就要勾选,列表上的「分块导出」多余。
2. 阅读页操作条 `report-actions.tsx` `ReportActions`:`RawAppendixDownload`(「下载原始附录」)、打印(`printPersonalReport`)、「分块导出」三个入口。产品:去掉前两个,「分块导出」改成导出图标按钮。
3. 导出抽屉 `report-export-drawer.tsx` 在桌面也是贴底弹出(CSS `.report-export-drawer` `inset: auto 0 0 …`)。产品:Web 端贴底弹窗"反人类"。
## 决策记录(产品 2026-09-24)
- **D1** 列表页已完成行只保留「查看报告」;删除「分块导出」链接。`#export` 深链的自动打开逻辑随之删除(无入口即死代码)。**修订** `TASK-report-chapter-export-20260924.md` D10。
- **D2** 阅读页删除「下载原始附录」与「打印」:删 `raw-appendix-download.tsx`、`ReportActions` 里的打印按钮与 `printPersonalReport` 调用、打印能力探测;`/api/reports/[reportId]/raw-appendix` 路由与 `releaseRawReportAppendix` **若全仓再无调用者一并删除**(含其测试,三栏说明)。报告页 `<style media="print">` 与核对表 `beforeprint` 展开逻辑:无打印入口后保留浏览器 Ctrl+P 的排版不变(不删 print CSS,只删按钮)。
- **D3** 「分块导出」按钮改为图标按钮:lucide `Download` 图标 + `aria-label="导出报告"` + tooltip「导出」,44×44 触控区;操作条只剩「返回报告中心」和它。
- **D4** 导出对话框:**≥ 860px(报告域既有断点)为居中模态**:宽 `min(720px, 92vw)`、最高 `80vh`、圆角 `--radius-lg`、四边留白,遮罩同现;**< 860px 保持底部抽屉**。仍为同一个原生 `<dialog>` 组件,只换 CSS,不引第三方。修订原单 D9。
- **D5** 其余不变:套餐、全选、字数、D5 逐字节锁、`ordinaryOutputLeaks` 检查、文件名规则。
## 硬红线
1. 不改导出块生成逻辑(`report-export-blocks.ts`)与下载器。
2. 断点只用 860;44px 触控;无 spinner / 骨架。
3. 删除要干净:`grep -rn "RawAppendixDownload\|printPersonalReport\|#export" frontend/src` 除注释外 0 命中(若 `printPersonalReport` 仍被其它页面使用则保留函数,只删本页调用)。
4. `/reports*` 渲染标记与基线一致;既有断言三栏;测试总数不低于基线。
## 任务分解
- T1 列表页删链接 + 删 `#export` 消费逻辑(`personal-report-center.tsx`、`report-actions.tsx`)。验收:`personal-report-entry.test.ts` ready 行只有一个动作;三栏更新。
- T2 操作条删原始附录与打印、导出改图标(`report-actions.tsx`、`raw-appendix-download.tsx`、raw-appendix 路由及测试)。验收:`personal-report-view.test.ts` / `personal-report-export.test.ts` / `personal-report-raw-appendix.test.ts` 相关断言三栏更新或随文件删除;图标按钮有 aria-label。
- T3 对话框响应式(`globals.css` 报告段)。验收:CSS 合同测试断言 `@media (min-width: 860px)` 下为居中(`inset: 0; margin: auto` 或等价)且 `max-height: 80vh`,窄屏仍贴底。
- T4 记录:DESIGN(报告中心动作、阅读页操作条、导出对话框形态)、VOICE(删除两条入口文案)、CHANGELOG、PROGRESS、`docs/testing/report-reader-actions-20260924.md`(桌面居中、手机贴底、Esc/遮罩关闭、列表无导出、操作条只剩返回和导出图标)。本单是产品变更,不新开 BUG 号。
## 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/report-reader-actions-20260924 .worktrees/report-reader-actions-20260924 origin/staging
cd .worktrees/report-reader-actions-20260924/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -5
```
@@ -0,0 +1,73 @@
# TASK · 报告正文换成上游读者版 + KP / Muntha 计算缺陷 + 投影截断(2026-09-24)
## 基线
- 本仓 `origin/staging = 9c9b66d6`。上游 `https://github.com/732642856/yinduzhanxing.git`,本机检出 `/workspace/yinduzhanxing` @ `19195f6e`;读者版引入提交 `f3b5196f`(2026-09-09「fix: add clean high-density PL9 reader export」)。
- 分支 `codex/report-reader-main-20260924`。**引擎 / 上游同步类任务:开工前必须按 AGENTS §9 读 `docs/research/pre_work_error_ledger.md` 并跑 `python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45`**;不得把 `.workbuddy` 镜像当主仓。
- 串行:**`TASK-report-chapter-export-fix-20260924.md`(BUG-1020)先合**,本单后合(两者都会动报告 golden 与导出锁)。与 `TASK-report-reader-actions-20260924.md` 文件不交叉。
## 事故实证
产品 09-24 真机报告正文出现大量非人读内容,例如:「结论等级规则 - executed / partial_verified…」「Sudarshana Chakra(三参考点原始结构)…PL9 第 8 页的三层图格尚未逐格对照…不输出 producer 自带的复合评分」「本节仅披露 PL9 图盘提取…OCR 候选」「Domi 受控样本的 16 个星座行已在 PL9 第 42 页 fixture 中匹配」「…所有行保持」(半句)、「KP Lord / Sub 原始表(本地计算) blocked / kp_lord_sub_unavailable KP producer failed: 'types.SimpleNamespace' object has no attribute 'year'」、报告身份块(`full-report://…`、`pl9_personal_long_report.v2`、hash、`质量检查: blocked`、`Chart Identity: birth=user_provided…`)、英文「This reference export combines…」、年度章「annual_tajika_pack」「kp_monthly_report_packet」「Muntha 当前优先候选指向conflict between vs Capricorn」、「Patyayini Dasha 外部回放(PyJHora/JHora) blocked / annual_replay_unavailable year=2027」。
三层根因(`origin/staging` 实证):
1. **用错格式**:正文 = `scripts/professional_report_reference.py` → `render_pl9_markdown`(`scripts/jyotish_engine.py`),这是上游的**专业参考 / 审计版**。上游另有读者版 `reader_main`:`_render_pl9_user_markdown`(上游 `scripts/jyotish_engine.py:11722`,docstring "user-facing … without parity/audit scaffolding")、`_pl9_export_markdown_for_edition`(:13099)、`render_pl9_parity_markdown`(:13148);上游审计 `docs/research/pl9_reader_main_information_coverage_audit_2026_09_09.md` 声明读者版不含 `blocked` / `parameter_sensitive` / `PyJHora` / `producer` / `schema` / `source_path`。本仓引擎这四个符号 **0 命中**(最后一次引擎同步 `f2241463` 在 09-03,早于 `f3b5196f`)。`cfcd369d`(09-06)把参考版设为正文,而 `TASK-report-longform-parity-20260905` 当初只批准它作附录。
2. **本仓自加的泄漏**:`_render_finished_reading_navigation`(「结论等级规则」,`bab07187` 引入,上游无);`_professional_report_reference_boundary_notice` 的英文段(`3b09bbbe`);`_kp_lord_sub_section` / `_year_replay_subblocks` 走 `_blocked_planned_section` 打印原始 `error/reason`(`cfcd369d`,上游在空结果时返回 `[]`)。
3. **投影是短黑名单且有破坏性**:`frontend/src/lib/report-public-projection.ts` `INTERNAL_HEADINGS` / `INTERNAL_KEY_TOKEN` / `ORDINARY_OUTPUT_LEAK_PATTERNS` 不认识 PL9 词汇;`projectProseLine` 在 `parameter_sensitive` 处**截断句子**(产生「…所有行保持」和孤立的「- `」),`tableIsInternal` 删整表而保留其标题和说明段。`personal-report-density-retry.test.ts` 把这份泄漏输出的 sha256 锁成 golden。
两个真实计算缺陷:
4. **KP 永远失败**:`scripts/jyotish_api_server.py` `_compute_full_reading_for_thematic` 用 `type('Args', (), {...})()` 构造参数(字段在类上、实例 `__dict__` 为空);`jyotish_engine.py` `cmd_kp` 执行 `SimpleNamespace(**vars(args))` 只拷到 `ayanamsa`,随后读 `.year` 抛错。同型写法还在 `transit_args = SimpleNamespace(**vars(args))`(double_transit_pac / transit_ll7l / planetary_congregation),网页路径同样会失败。上游有同样潜伏缺陷,只是空结果时不输出。
5. **Muntha / Year Lord 恒错**:`scripts/solar_return.py` 读 `sr.get('birth_asc_sign_idx')`,本仓 `calc_solar_return_chart` 返回字典**从不设置该键**(上游在 solar_return.py:501 设置;`f2241463` 合并时漏掉这半)。结果 Muntha 恒为 `blocked`,Year Lord 回退 `muntha_sign_idx=0` → **恒为火星 / 白羊座**;`annual_tajika_pack.py` `_field_with_conflict_gate` 用整字典比较,blocked 与正常值必判冲突,`_field_brief` 生成「conflict between vs X」,引擎 `_humanize_reader_summary_line` 的冲突正则因空组匹配失败,落到 `simple_match` 渲染成「当前优先候选指向conflict between vs X」。**这是年运结论的正确性缺陷**,不只是文案。
## 决策记录(产品 2026-09-24)
- **D1 新报告正文改用上游读者版 `reader_main`**:从上游 `f3b5196f..19195f6e` 同步 `_render_pl9_user_markdown`、`_pl9_export_markdown_for_edition`、`render_pl9_parity_markdown` 及其依赖;`professional_report_reference.py` 增加 `edition` 参数,网页生成链(`frontend/src/lib/personal-report-longform-generate.ts`)请求 `edition: "reader_main"`。专业参考版不再作为正文,也**不再提供用户入口**(原始附录入口已由 `TASK-report-reader-actions` 删除)。
- **D2 旧报告不回填**:已生成的报告保持原正文(投影修复后会比现在干净);用户可重新生成。不写迁移、不批量重算。
- **D3 删除本仓自加的三处**:「结论等级规则」块、英文 boundary 段、`_blocked_planned_section` 打印原始 error/reason 的做法(改为:数据不可用的节整节不输出,与上游一致)。这些即使在参考版里也删。
- **D4 修两个计算缺陷**:KP / transit 的 args 拷贝改为不依赖 `vars()`(显式读取所需字段,或 `_compute_full_reading_for_thematic` 改用真 `SimpleNamespace`);补回 `birth_asc_sign_idx`;`_field_with_conflict_gate` 在一方 `status == "blocked"` 时不判冲突、取另一方;比较只比关键字段(星座 / 主星),不比整字典。
- **D5 投影修正**:`projectProseLine` 遇内部 token **删整行**而不是截断;`tableIsInternal` 删表时连同紧邻的表头说明段一起删;新增 PL9 词汇防线(`blocked`、`executed`、`partial_verified`、`missing_in_local`、`producer`、`parity`、`PL9 第 N 页`、`full-report://`、`pl9_*`、`*_unavailable`、`*_pack` / `*_packet` 标识符、Python 异常形态 `object has no attribute`、`Traceback`、整段英文),用于 `ordinaryOutputLeaks` 检测(读者版输出必须 0 命中)。
- **D6 Skill / 报告版本**:报告版本号从 `pl9_personal_long_report.v2` 升 v3(读者版);Skill 是否 bump 由执行方按 CHANGELOG 规则判断并写明。
## 硬红线
1. 许可证边界:上游代码按既有同步方式整段引入并注明来源提交,不改动上游算法口径(除 D4 两个缺陷,且须在 BUG 记录说明上游同样存在)。
2. 不改 Ayanamsa / Node / 宫制 / 大运口径与置信度边界(AGENTS §8.4)。
3. 读者版 golden **必须由真实引擎生成**(AGENTS §7.4),用明确虚构生日;旧 `report-density-fictional-reader.json` 保留给旧报告回归,新增 reader_main fixture。
4. `ordinaryOutputLeaks(读者版正文) = []`,且 D5 新增的每一类词汇在读者版 golden 中 0 命中;该断言不得弱化。
5. 旧 golden sha 断言(`personal-report-density-retry.test.ts` 等)改动必须写三栏,说明"原 sha 锁的是泄漏输出"。
6. `scripts/jyotish_api_server.py` 不得增长(`test_api_server_growth_contract.py`);新逻辑进独立模块。
7. 快速门 `run_quality_gate.py --profile quick` 与相关 Python 定向测试必跑;不改 workflow / 并发 / 数据库结构。
## 任务分解
- **T1 同步读者版**(`scripts/jyotish_engine.py` 或新模块、`scripts/professional_report_reference.py`)。验收:Python 测试——同一虚构 packet,`edition="reader_main"` 输出不含 D5 词汇、含上游审计文档列出的核心章节;`edition` 缺省仍为参考版(后向兼容)。
- **T2 生成链切换**(`personal-report-longform-generate.ts`、快照 schema 若需版本字段)。验收:`professional-report-reference-route.test.ts` 断言请求体带 `edition: "reader_main"`;新报告版本 v3。
- **T3 删自加泄漏**(D3)。验收:参考版输出也不再含「结论等级规则」「This reference export」「producer failed」。
- **T4 KP / transit args**(D4 前半)。验收:Python 测试用 `type('Args', (), {...})()` 形态的参数调用 `cmd_full_reading` 路径,KP 与三项 transit 结果非 blocked。
- **T5 Muntha / Year Lord**(D4 后半)。验收:虚构生日的太阳回归 `birth_asc_sign_idx` 存在,Muntha 非 blocked,Year Lord 随年份变化(至少两年不同);`annual_tajika_pack` 在一方 blocked 时无「conflict between」。
- **T6 投影**(D5)。验收:`report-public-projection.test.ts` 新增 PL9 词汇用例;「…所有行保持」类截断不再出现(整行删除);旧 fixture 投影后不再含孤立「- `」。
- **T7 记录**:`docs/BUG_HISTORY.md` **BUG-1026** 报告正文用了审计版 + 投影截断(关联 BUG-999、1003、1009;复发自 BUG-999——写明为何当时的测试只覆盖聊天键没拦住);**BUG-1027** KP args 拷贝失败;**BUG-1028** Muntha 缺 `birth_asc_sign_idx` 致 Year Lord 恒火星(关联 `f2241463` 同步漏合)。`docs/research/pre_work_error_ledger.md` 追加"上游同步漏合"条目。CHANGELOG、PROGRESS、`docs/testing/report-reader-main-20260924.md`(生成一份新报告,逐章检查无英文 / 页码 / blocked / 异常原文;年度章 Muntha 与 Year Lord 每年不同;KP 节存在)。
## 让步顺序
1. 上游读者版依赖过多、整段引入会让本仓引擎失控时,允许以新模块 `scripts/pl9_reader_export.py` 引入并在引擎中薄注册。
2. D5 的英文整段检测若误伤行星英文名 / 术语,允许改为"连续 ≥ 12 个英文单词的段落"判定。
3. 不得让步:D1(正文换读者版)、D4(两个计算缺陷)、红线 3、红线 4。
## 开工前置命令
```bash
git fetch origin --prune
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
git -C /workspace/yinduzhanxing fetch origin && git -C /workspace/yinduzhanxing log --oneline -1 origin/main
git worktree add -b codex/report-reader-main-20260924 .worktrees/report-reader-main-20260924 origin/staging
cd .worktrees/report-reader-main-20260924
.venv/bin/python scripts/run_quality_gate.py --profile quick # 记基线
cd frontend && ./node_modules/.bin/tsc --noEmit && npm test 2>&1 | tail -5
```
## BUG 编号
本单 **BUG-1026 / 1027 / 1028**(1021–1025 已被同日其它单预留),开工时核对。