# 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 由慢网单预留),开工时核对。