From e0db23f47a7a77e4197cf1bcc2f7225362150836 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Thu, 24 Sep 2026 14:42:49 +0800 Subject: [PATCH] docs(tasks): briefs for slow-network home, chart/ephemeris fixes, report actions and reader_main Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8 --- docs/tasks/README.md | 4 + .../TASK-chart-ephemeris-fixes-20260924.md | 93 +++++++++++++++++++ docs/tasks/TASK-home-slow-network-20260924.md | 88 ++++++++++++++++++ .../TASK-report-reader-actions-20260924.md | 43 +++++++++ .../tasks/TASK-report-reader-main-20260924.md | 73 +++++++++++++++ 5 files changed, 301 insertions(+) create mode 100644 docs/tasks/TASK-chart-ephemeris-fixes-20260924.md create mode 100644 docs/tasks/TASK-home-slow-network-20260924.md create mode 100644 docs/tasks/TASK-report-reader-actions-20260924.md create mode 100644 docs/tasks/TASK-report-reader-main-20260924.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index bf32ce74..cd714c9f 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -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` | diff --git a/docs/tasks/TASK-chart-ephemeris-fixes-20260924.md b/docs/tasks/TASK-chart-ephemeris-fixes-20260924.md new file mode 100644 index 00000000..1ca63f0d --- /dev/null +++ b/docs/tasks/TASK-chart-ephemeris-fixes-20260924.md @@ -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 ` 会把整页(含日期条)换成等待句。 +- 请求失败(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 状态只渲染 `

{view.message}

`;`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 由慢网单预留),开工时核对。 diff --git a/docs/tasks/TASK-home-slow-network-20260924.md b/docs/tasks/TASK-home-slow-network-20260924.md new file mode 100644 index 00000000..3618ec8e --- /dev/null +++ b/docs/tasks/TASK-home-slow-network-20260924.md @@ -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**,开工时核对。 diff --git a/docs/tasks/TASK-report-reader-actions-20260924.md b/docs/tasks/TASK-report-reader-actions-20260924.md new file mode 100644 index 00000000..97dba930 --- /dev/null +++ b/docs/tasks/TASK-report-reader-actions-20260924.md @@ -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` **若全仓再无调用者一并删除**(含其测试,三栏说明)。报告页 `