Files
Jyotisha/docs/tasks/PROGRESS-chart-glyphs-20260918.md
T
Jesse_ChenandClaude Fable 5.1 5a1dcbd2a1
Independent Staging Quality Gate / validate (push) Failing after 8m58s
Independent Staging Quality Gate / publish (push) Skipped
feat(chart): 星盘改用行星符号与颜色,度数进行星表
北印星盘(星盘页 / 校正右栏 / 我的报告共用 VedicChartSvg)宫内不再画
「水 19°」「罗逆 29°」这类显示文本——一宫三颗星就叠三行、字号压到 8。
改成九个彩色符号横排,每行最多 3 个;逆行是符号下方一道同色横线,
第 1 宫加 As 小标,盘下方两行图例。度数、星宿、顺逆下沉到星盘页的
行星表,列从四列扩到八列,首行是上升,度数精确到分。

- 符号字体:vendored 的 9 字形 Noto Sans Symbols 子集(OFL,1.5 KB),
  经 next/font/local 挂成 --font-planet-glyphs;♀ ♂ 带 U+FE0E 防 iOS
  画成表情。构建镜像不能联网,所以必须自带,与 Inter 同一条理由。
- 颜色:--color-planet-* 九个 token,:root 与两个深色块各一份。只用于
  认星不表吉凶,罗计共用中性灰。挂 is-* 修饰类而非行内 style——报告
  文档的标记合同禁止渲染结果出现 style=,星盘不做例外。
- 数据来源:toNorthIndianChart() 加结构化 occupantGlyphs(星盘页与
  markdown fence 走这条);校正与报告两条路线的模型只有引擎 / golden
  显示文本,按首字解析,解析由测试锁住。
- 星宿沿用引擎返回名:仓库无中文宿名表,印度 27 宿与中国二十八宿不是
  同一套,不自造对照。

tsc 0 错,lint 0 error,npm test 3471→3484 条、36 红与基线逐条相同,
/ 仍 Static,首屏 JS gzip +0.29%。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-18 00:39:13 +00:00

100 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PROGRESS · 星盘改用行星符号,度数进行星表(2026-09-18)
- 分支:`codex/chart-glyphs-20260918`,worktree `.worktrees/chart-glyphs-20260918`
- 基线:`origin/staging` = `84b293fb`(feat(consult): 对话口气改成反差与扮演象的形状)
- 类型:产品拍板的界面改造,非 Bug,未写 `docs/BUG_HISTORY.md`
- Skill 版本:不变
## 1. 做了什么
盘里(星盘页 / 校正右栏 / 我的报告,同一个 `VedicChartSvg`)不再画「水 19°」「罗逆 29°」这类显示文本,改画彩色行星符号;度数、星宿、顺逆下沉到**星盘页**的行星表。
| 项 | 落点 |
| --- | --- |
| 符号字体 | `frontend/src/app/fonts/NotoSansSymbols-planets.woff2`(1,536 B,OFL,9 字形子集),来源与取法追加在 `SOURCE.txt` |
| 字体挂载 | `layout.tsx` 第二个 `localFont`,`variable: "--font-planet-glyphs"`、`display: "swap"`、`adjustFontFallback: false` |
| 符号 / 名称 / 缩写 / 颜色类 / 解析 | 新模块 `frontend/src/lib/planet-glyphs.ts` |
| 盘面渲染 | `frontend/src/components/personal-report/vedic-chart-svg.tsx` 重写占位层 |
| 宫内锚点 | `north-indian-chart-geometry.ts` 新增 `occupantAnchor()` |
| 结构化字段 | `report-chart-block.ts` 的 `toNorthIndianChart()` 产出 `occupantGlyphs`;`chart-view-contract.ts` 的 `northIndianChartSchema` 加同名可选字段 |
| 上升行 | `chart-view-contract.ts` 新增 `chartViewAscendantRowSchema`;`chart-view-mapper.ts` 新增 `ascendantRow()`,挂在 `vedic.ascendant` |
| 行星表 | `chart-vedic-tab.tsx` 四列 → 八列;`chart-format.ts` 新增 `formatDegreeMinutes()` |
| 颜色与样式 | `globals.css`:9 个 `--color-planet-*`(`:root` + 两个深色块 + `@theme inline` 各一份)、符号 / 图例 / 逆行横线 / As / 表格列样式 |
### 数据来源:结构化优先,两条路线仍按显示文本解析
任务书要求「优先加结构化字段」。实际分成两条:
- **加了结构化字段**:`toNorthIndianChart()` 在格式化字符串之前就知道星体与逆行,直接挂 `occupantGlyphs: { key, retrograde, degree }[]`。星盘页(`chart-view-mapper` → `buildVargas`)与 markdown 里的 `jyotish-chart` fence 都走这条,渲染层不解析字符串。`chartViewOkSchema.parse()` 会剥未声明字段,所以同步在 `northIndianChartSchema` 里加了可选字段,否则结构化信息会在序列化边界丢掉。
- **仍按显示文本解析**:
- **校正右栏**:`houseTableToNorthIndianChart()` 的输入是 `parseRectificationHouseTable()` 解出来的引擎 `house_table`,只有 `{house, sign, occupants: string[]}`,且 `scripts/rectification/house_table.py` 给的是**全名**(`太阳` / `水星` / `罗睺`),没有逆行位、没有度数。
- **我的报告**:`ChartV1.houses` 是 `personal-report-contract.ts` 里的 `z.strictObject`,形状来自 Python golden(`personal_report_contract.py`),按 AGENTS §7.4 不得手造。
这两条走 `parseChartOccupant()`:首字 ∈ `日太月火水木金土罗计`(`太` 是为 `太阳`),或首段拉丁字母 ∈ `Sun…Ketu`,逆行看是否含「逆」,并与 `chart.planets[].retrograde` 取或。认不出来的标签按原样画字(回归到旧行为,不吞数据)。解析本身由 `tests/chart-planet-glyphs.test.tsx` 的三条用例锁住(`水 19°` / `罗逆 29°`、全名、英文名、未知名)。
### 两个非任务书写法的选择
1. **颜色挂修饰类而不是行内 `style`。** 第一版按预览稿写了 `style={{ fill: … }}`,撞到 `tests/personal-report-view.test.ts` 的 `assert.doesNotMatch(markup, /style\s*=/i)`——报告文档的标记合同禁止渲染结果里出现 `style=`,这条是挡 agent 产出的内容自带 CSS 的,不该为星盘破例。改成 `is-sun`…`is-node` / `is-ascendant` 修饰类,`text` 吃 `fill`、`line` 吃 `stroke`(合并成一条会给符号描边)。
2. **星宿名沿用引擎返回值,不译中文。** 任务书写「星宿中文」,但仓库里(前端 `chart-view-labels.ts`、Python `scripts/`)都没有星宿中文名表,`chart-basics-tab.tsx` 现在也是直接显示引擎返回的名字。印度 27 宿与中国二十八宿不是同一套,现造一张对照表是在下一个领域判断,超出本单授权,已写进 `DESIGN.md` §15。
### 其它细节
- 图例两行(5 + 4 + 「横线=逆行」)。九对符号 + 汉字加那句话在 400 单位一行里会挤,按任务书让步走两行;图例落在方阵下方的 52 单位条带里,viewBox 高度由 400 变 452,不压宫位。
- `As` 不占 `MAX_RENDERED_OCCUPANTS` 名额:它来自几何不来自数据,占名额会让第 1 宫比别的宫少显示一颗星。
- `.chart-page-vedic-stage … text { font-size: 16px }` 这条通选被拆开(见下节)。
- 打印:`@media print` 里 `* { print-color-adjust: exact !important }` 已在,符号是普通 `<text>`,颜色 token 在 `:root` 定义、打印块不覆盖,符号照常可见且带色。
## 2. 测试数字(同机、同命令,各测一次)
| 项 | 基线 `84b293fb` | 本轮 | 差 |
| --- | --- | --- | --- |
| `./node_modules/.bin/tsc --noEmit` | 0 错 | 0 错 | — |
| `npm run lint` | 0 error / 118 warning | 0 error / 118 warning | 未新增 warning |
| `npm test` tests | 3471 | 3484 | +13(新文件 `tests/chart-planet-glyphs.test.tsx`) |
| `npm test` pass | 3420 | 3433 | +13 |
| `npm test` fail | 36 | 36 | 排序后 `diff` 为空 |
| `npm test` skipped | 15 | 15 | — |
| `next build` 的 `/` | `○ Static` | `○ Static` | 通过 |
| 首屏 gzip(`.next/static` 全部 `*.js` 逐个 gzip 合计,68 个文件) | 1,450,774 B | 1,455,017 B | +4,243 B,**+0.29%**,在 ±2% 内 |
| CSS gzip(`.next/static` 全部 `*.css`,4 个文件) | 40,215 B | 41,110 B | +895 B,+2.23% |
| 字体资产 | — | `NotoSansSymbols_planets.*.woff2` 1,536 B | 独立 media 资产,`display: swap`,不计入首屏 JS |
CSS +2.23% 的构成:9 个颜色 token × 3 个块 + `@theme inline` 一份(36 行)、17 条 `is-*` 上色规则(盘面 `text`/`line` 各一套)、9 条表格上色规则、符号 / 图例 / 逆行 / As / 表格列的字号与对齐。这一节的口径按 `PROGRESS-starter-greeting-20260917.md` 的先例是 `*.js`,CSS 单列出来是因为本轮变化确实落在 CSS 上,JS 与 CSS 合计 1,490,989 → 1,496,127 = +0.34%。
失败清单:基线 36 条与本轮 36 条排序后 `diff` 为空(`database` / `live staging sync` / `ephemeris` / `admin` 等套件)。全部是无 Docker、无数据库、无外部引擎的既有环境缺口,与本轮无关。
`next build` 取基线数字的方法(未用 `git stash`,AGENTS §3.5):`git diff > /tmp/…/chart-glyphs.patch` → `git checkout -- frontend/` → 把三个**未跟踪**新文件临时移出 worktree(否则 `tsc` 会在基线上报 `occupantGlyphs` 不存在,构建 exit 1、`.next/static` 是脏的)→ `rm -rf .next && npm run build` → 取数 → `git apply` + 移回。两侧都是干净 `.next` 的全量构建。
## 3. 被改的既有断言(原值 / 新值 / 原因)
| 文件 · 测试 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `tests/chart-page-view.test.tsx` · `the parameters are one key/value table…` | `assert.match(globalStyles, /\.chart-page-vedic-stage \.personal-report-chart-svg text \{ font-size: 16px; \}/)` | 改断 `.personal-report-chart-muted` 那一条仍是 16px,并**新增** `assert.doesNotMatch(globalStyles, /\.chart-page-vedic-stage \.personal-report-chart-svg text \{/)` | 盘内文字不再只有一种字号:符号按窄盘宽度定在 22 viewBox 单位。通选 `text` 规则会把符号一起压回 16,正是要避免的。DESIGN §15 的原意是「小字在 ~318px 盘宽下不低于 12px」,不是「所有字一样大」。断言主语(宫位序号仍 16)未改,新增的反向断言比原断言更强 |
| `tests/personal-report-markdown-view.test.tsx` · `markdown view draws the North Indian component…` | `assert.match(markup, new RegExp(planetDisplayLabel("Saturn", 3.4, true)))`(盘里必须出现「土逆 3°」) | 改断盘里出现 `♄` 且带 `class="personal-report-chart-retrograde is-saturn"`;**新增** `doesNotMatch` 那串文本、`doesNotMatch(/°/)`、「逆」在整份标记里只剩图例那一个 | 产品拍板把盘内文字换成符号,原断言守的正是本轮要去掉的东西,无法原样保留。断言主语(这颗逆行土星来自 fence 数据、画在盘里)未改;新增的三条反向断言把「不再写字」这一半也锁住。`planetDisplayLabel()` 本身没删——`occupants` 字符串仍喂 `aria-label` 与宫位表 |
| `tests/site-style-isolation-contract.test.ts` · `Inter is loaded through next/font…` | `assert.match(layout, /className=\{inter\.variable\}/)` | `assert.match(layout, /className=\{`\$\{inter\.variable\}[^`]*`\}/)` | 本轮新增第二个 `next/font` 变量(`--font-planet-glyphs`),两个都要挂在同一个 `<html>` 上。断言主语(Inter 以 CSS 变量形式挂在根元素、且是第一个)未改,只是不再要求 className 只有它一个 |
被**扩写**但未弱化的既有断言:`tests/personal-report-view.test.ts` 的 `each house owns its occupant coordinates…` 与 `no dangerous HTML…` 两条**一字未改**且仍绿——前者的 `+7` 是靠「`As` 不占溢出名额」保住的,后者的 `doesNotMatch(/style\s*=/i)` 是本轮改用修饰类的直接原因。
## 4. 新增测试(`tests/chart-planet-glyphs.test.tsx`,13 条)
1. 含「水 19°」「罗逆 29°」的宫渲染出 ☿ 与 ☊,☊ 带 `personal-report-chart-retrograde`,☿ 不带;盘里不再有「°」,「逆」只剩图例那一个。
2. 校正面的全名(太阳 / 水星 / 罗睺 / 计都)映射到同一组符号。
3. `PLANET_GLYPH.venus` / `.mars` 逐字等于 `♀`+U+FE0E / `♂`+U+FE0E,且渲染结果里带。
4. 第 1 宫有且只有一个 `As`,挂 `is-ascendant`。
5. 显式写出的上升占位(`上升点` / `Lagna`)并入同一个 `As`,不画两次。
6. 图例只在有星时出现(空盘 viewBox 仍是 `0 0 400 400`),出现时九个符号 + 九个汉字齐全。
7. 一宫 6 颗星恰好两行(`y` 去重后 2 个);第 7 颗起出现 `+2`。
8. `toNorthIndianChart()` 挂上 `occupantGlyphs`,同时 `occupants` 字符串原样保留。
9. 认不出的占位名按原样画字。
10. `parseChartOccupant()` 覆盖四种在用写法 + 未知名。
11. 9 个颜色 token 在 `:root`、`prefers-color-scheme` 深色块、`[data-theme="dark"]` 深色块**各恰好一次**,且深浅值不相同。
12. 字体是 vendored `wOF2`、`--font-planet-glyphs` 存在、没有 `next/font/google`、字体栈逐字正确、`SOURCE.txt` 记了许可证。
13. 星盘页表头恰为「行星 黄道经度 速度 宫位 星宿 宿主 pada 状态」,上升是第一行,度数带分,符号列上色,旧表头「星座与度数」已消失。
## 5. 没做 / 做不了
- **星宿中文名**:见 §1.2。仓库无对照表,不自造。
- **参照里其它页签**(宫位 / 大运 / 星力 / 瑜伽):任务书明确本单不做,未做。
- **真机走查**:无登录态、无 Chrome,以下留给 `docs/testing/`——iOS Safari 上 ♀ ♂ 是否确为文本呈现(U+FE0E 生效)、Android / Linux 上九个符号是否都落在 vendored 子集、深浅两主题下九种颜色在盘面与表格的实际可读性、375px 下行星表在自己的盒子里横滚而页面不横滚、报告打印时符号与颜色是否随纸出。
- **`height="auto"`**:`VedicChartSvg` 上这个非法属性是 `TASK-console-noise-20260916`(BUG-906)的范围,本轮未顺手动。