Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
17 KiB
PROGRESS · 全站标题改用自托管宋体,正文保持黑体(2026-09-28)
任务书:docs/tasks/TASK-serif-headings-20260928.md。产品 2026-09-28 授权直接执行(Claude 子代理)。
- 基线:
origin/staging=ebcee584(任务书写作时为aa33399a,开工时已前进到任务书自身的提交) - 分支 / 工作树:
codex/serif-headings-20260928/.worktrees/serif-headings-20260928(本地两个提交,未推送,由 Claude 验收后推) - 环境:Node v22.14.0(
/exec-daemon/node);无.venv,Python 一律/usr/bin/python3(3.13,含 pyswisseph);本机临时装fonttools 4.65.0+brotli 1.2.0(pip --user,不进仓库依赖);无 Docker;有/usr/bin/google-chrome(无头)。 - BUG 编号:开工时最大 BUG-1079;本单不开新 BUG,只给 BUG-737 追加说明。
结论
| 项 | 结果 |
|---|---|
| T1 切片、脚本、Python 合同测试 | 完成(提交 1) |
| T2 接入字体栈 | 完成;serif-sc.css 由 site-styles.ts 引入(偏离,见下) |
| T3 合同测试改写 | 完成;三栏见下 |
| T4 性能与截图 | 未登录页真实测量;已登录首页 / 咨询 / 报告页用构建产物搭的静态页测量(环境缺口,见下) |
| T5 文档 | DESIGN §3、BUG-737、CHANGELOG、真机清单、任务索引已改 |
| 让步 | 未动用。报告页 493 KB(≤ 900 KB),截图已做 |
T1 · 切片
- 上游:noto-cjk
Serif2.003(tag SHA9b0f1436)的Serif/SubsetOTF/SC/NotoSerifSC-SemiBold.otf,11,771,476 B,sha256517d9736…055bf5,许可证为同 tag 的Serif/LICENSE(OFL 1.1)原样放OFL.txt。完整 URL、版本、sha256、改名方式写在frontend/src/app/fonts/serif-sc/SOURCE.txt。源文件不进仓,脚本校验 sha256。 - 改名:family
Jyotisha Serif SC、PostScript / CFF 名JyotishaSerifSC-SemiBold;保留版权、设计者、厂商、许可证记录,删掉 Noto 商标记录和上游本地化名。 - 字表:《通用规范汉字表》一级 3500 + 二级 3000,取自
shengdoushi/common-standard-chinese-characters-table(commitd9b599a9,与 gov.cn 公布的 PDF 同源整理),逐字节原样放scripts/fonts/tygfhzb-level-{1,2}.txt,来源与 sha256 写在scripts/fonts/CHARSET_SOURCE.txt。脚本断言 6500 个互不重复的单字。 - 字频顺序(偏离任务书的「按字表顺序」):字表是按笔画排的,不是字频,拿它排前 1000 字首页会下载一堆无关切片。改用
scripts/fonts/char_order.txt(进仓,脚本--refresh-order生成):- 本仓语料(git 跟踪的
frontend/src、frontend/docs、references/、SKILL.md)用得最多的 300 字排最前——星、宫、盘、运这类标题常用字; - 其后按 Google Fonts 自己公开的简体中文字频分档(
googlefonts/nam-files的slices/simplified-chinese_default.txt,commit2a68014b,Apache-2.0,前 20 档为 FreqRange); - 同档内按本仓语料次数,再按字表顺序。只进仓派生结果,不进仓原文件。 只用本仓语料的第一版实测:首页问候 + 人名常落到 5~6 片、最多 495 KB,超 300 KB 上限,所以加了第 2 层。
- 本仓语料(git 跟踪的
- 切片(共 30 片,
unicode-range互不重叠):00基本拉丁、Latin-1 符号(不含带重音字母)、× ÷、通用标点、中文与全角标点、数字与常用符号,358 字,38.5 KB;01~19有字频信号的 3635 字,按名次每片 192 字,40~62 KB;20~29无字频信号的 2865 字按码位连续切块,每块约 287 字,72~109 KB。它们的unicode-range写成「块首到块尾,扣掉 00~19 已占的码位」,这样 CSS 小一半多。代价:6500 字以外、恰好落在某块跨度里的生僻字,会让浏览器下载那一片,但片里没有它的字形,照样退回黑体。- 任务书写的「前 1000 字切 2~3 片、其余每片 300~400 字」按实测改细:前 1000 字 3 片时,任何有 10 个汉字的标题都会把 3 片(≈241 KB)全拉下来,加上拉丁片就碰 300 KB 上限。
- 单片最大 109,040 B(≤ 120 KB)。
- fontTools 参数:woff2、desubroutinize(CJK CFF 的 woff2 小约 13%)、保留 hinting(Windows 渲染)、默认 layout features、不重算时间戳。同一输入跑两遍,30 个 woff2 + CSS 的 sha256 逐一相同。
tests/test_serif_font_slices.py(8 条):字表 6500 字;每字都被覆盖;拉丁 / 数字 / 中文标点被覆盖;各片不重叠;CSS 引用的文件都存在、是 woff2、≤ 120 KB、没有多余文件;family / 字重 / swap;OFL 与 SOURCE 在;装了 fontTools 时再核对每片字形覆盖其字表字且家族名不含 Noto。已加入run_quality_gate.py的 quick 列表(否则门禁永远不跑它;偏离,见下)。
T2 · 接入
-
--font-display按硬红线第 3 条改为"Jyotisha Serif SC", var(--font-inter, Inter), -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;--font-body未改。注释改写。 -
引入位置(偏离):任务书写「由
layout.tsx引入」。根layout.tsx同时是后台/admin的根布局,site-style-isolation-contract明确禁止它引站点样式;所以serif-sc.css放在src/app/site-styles.ts里、紧跟globals.css——(app)/layout.tsx与登录页都经它引入,后台不加载。单独文件而不并进globals.css,因为@font-face的unicode-range数据 gzip 后约 22 KB,超过硬红线 5 的 +8%。 -
报告标题规则清单(改之前 grep):
规则 改前 处理 .personal-report-cover h1--font-display不动 .personal-report-section-heading h2--font-display不动 .personal-report-md-article h2(长文报告章节)--font-display不动 .personal-report-md-article h1(长文报告标题,markdown#)无规则,继承正文黑体 新增 font-family: var(--font-display),只加字体不改字号.personal-report-subheading h2/h3、.personal-report-appendix h3、.personal-report-disclaimer h2、.personal-report-state h1、.personal-report-facts dd、进度标题--font-display不动 .personal-report-md-article h3/h4无规则,继承正文 不动,请 Claude 定 .personal-report-theme h3显式 --font-body不动,请 Claude 定(不在任务书例外清单里,但写法是刻意的) .personal-report-chart-card h3--font-body例外,保留 另:
.membership-*标题(点数 / 订阅页)显式--font-body600,不在例外清单也未改,请 Claude 定。 -
打印:
@media print里没有任何font-family覆盖,打印沿用同一条栈,屏幕上已加载的切片直接可用;没有实际打 PDF(见环境缺口)。 -
例外保留:
.conversational-narrative .message-markdown h2, h3、.personal-report-chart-card h3、后台全部、四个根错误页(内联无衬线栈)。 -
var(--font-display)在globals.css里 40 → 41 处(多出的就是 md h1)。
T3 · 合同测试
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
font-stack-loadable-contract.test.ts every quoted family … vendored or a system face |
带引号 family 只能在 SYSTEM_FACES 里 |
也可以是 serif-sc.css 里 @font-face 声明过、且每个 url() 文件都存在的 family |
决策 1、3;「声明的字体必须可加载」这半条保留并扩展 |
同文件 no CJK serif is reachable from the display stack |
栈里不得有 Songti / STSong / SimSun / Noto Serif / Source Han Serif,不得以 serif 结尾 |
删除,替换为 the display stack leads with the self-hosted serif and never reaches a system 宋体:首位是 Jyotisha Serif SC;不含 Songti / STSong / SimSun / Noto Serif CJK SC / Noto Serif SC / Source Han Serif;不以通用 serif 结尾、以 sans-serif 结尾;首位之后与 --font-body 完全相同 |
任务书 T3 明确要求删除并替换;BUG-737「不得落到系统宋体」保留 |
同文件 display rank comes from weight, not from a second family |
注释:共用一条无衬线栈 | 只改注释与报错文案;断言不变 | 字体没到时仍按无衬线显示,字重仍须承担层级 |
personal-report-theme-contract.test.ts |
注释「--font-display 现在是无衬线」 |
注释改为历史说法并补 2026-09-28 说明;字重 500 断言不变 | T3 |
consultation-entrypoint.test.ts |
同上 | 补一段注释;断言不变 | T3 |
新增 frontend/tests/serif-headings-contract.test.ts(7 条):校正叙述 h2/h3 与报告分盘卡 h3 仍用 --font-body(精确选择器,避免被通用 .message-markdown h2 蒙混);回答 h2、报告封面 / 章节 / 长文 h1 h2 用 --font-display;正文栈不含衬线;后台三文件不出现 heading face,site-styles.ts 引 serif-sc.css 而根 layout.tsx 不引;四个根错误页不引;每个 @font-face 是 swap、500 700、相对 url()(进 Next 打包),layout.tsx 无字体 preload。
测试名对比(frontend/AGENTS.md 的方法):消失 1 条 = 上表被任务书要求删除的那条;新增 8 条(上面 7 条 + 替换断言 1 条)。
验证
| 项 | 基线 ebcee584 |
交付 |
|---|---|---|
tsc --noEmit |
0 错 | 0 错 |
npm run lint |
0 error / 127 warning | 0 error / 127 warning |
npm test |
tests 4248 · pass 4193 · fail 24 · cancelled 0 · skipped 31 | tests 4255 · pass 4200 · fail 24 · cancelled 0 · skipped 31 |
| 失败清单 | 24 条(全是需要 Docker / PostgreSQL 的数据库与部署套件) | 与基线逐条 diff 一致 |
python3 -m pytest tests/test_serif_font_slices.py |
— | 8 passed |
python3 scripts/run_quality_gate.py --profile quick(Node 22 在 PATH) |
— | 所有 Python 步骤通过(pytest 982 passed / 1 skipped);最后一步 npm test 的 24 条失败与基线逐条一致(无 Docker)→ 退出码 1,属环境缺口。首次误用 Node 20 跑出 66 条失败(mock.module),已换 Node 22 重跑 |
python3 -m pytest tests/test_repo_privacy_markers.py -q |
— | 通过 |
next build(npm run build,Turbopack) |
/ ○ Static |
/ ○ Static;全部路由的 ○ / ƒ 标记与基线逐行相同 |
构建备忘:Turbopack 在旧 .next 上增量构建时,曾输出旧的 --font-display 值(CSS chunk 未失效);测量一律 rm -rf .next 后全量构建。
首屏 gzip(index.html 引用的全部 js / css,gzip -9 求和)
| 基线 | 交付 | 变化 | |
|---|---|---|---|
| JS | 651,590 B | 651,590 B | 0(0%) |
| CSS 合计 | 43,493 B | 66,346 B | +22,853 B(+52.5%) |
| 其中 globals chunk | 42,248 B | 42,268 B | +20 B |
| 其中字体 chunk | 859 B(next/font 的 Inter / 行星符号 @font-face) |
23,692 B(上述 + serif-sc.css,Turbopack 合成一个 chunk) |
+22,833 B |
CSS 超 +8%,按硬红线 5 已拆成单独文件(单独 chunk)。原因:30 片的 unicode-range 里 3635 个高频字只能逐个码位列出(字频顺序与码位无关,合不成区间),这是按字频切片、让首页只下 3~5 片的代价;无字频信号的 2865 字已改写成区间。宋体本身不 preload、swap,不阻塞首屏;这 23 KB CSS 是渲染阻塞的,4G 下约数十毫秒。
宋体切片流量(无头 Chrome,禁缓存,统计 jyotisha-serif-sc-*.woff2 的 encodedDataLength)
| 页面 | 测法 | 片数 | 字节 |
|---|---|---|---|
/(未登录;本机无后端,显示「暂时无法进入 Jyotisha」卡与侧栏) |
next start 真页面 |
5 | 226,060 |
/login(未登录落地页) |
next start 真页面 |
5 | 220,696 |
| 已登录空白首页(品牌字 + 问候「下午好,王芳」,虚构名) | 构建产物 CSS 搭的静态页 | 5 | 235,833 |
咨询回答(h2「D10 事业盘怎么读」+ h3) |
同上 | 4 | 171,696 |
完整个人报告(封面 h1 + 虚构读者 fixture report-density-fictional-reader.json 的全部 h1/h2,共 24 个标题;长文报告未进视口的章节也会先渲染 h2 占位,所以全部 h2 都算) |
同上 | 10 | 493,058 |
上限:首页 ≤ 300 KB、报告 ≤ 900 KB,均满足。离线估算(按切片成员与文件大小):首页问候池 10 句 × 虚构人名为 131~309 KB,只有「周末愉快,赵磊」一句(6 片)略超 300 KB——问候随机、人名因人而异,首页会在这附近浮动;另一份 fixture report-reader-main-fictional.json 的报告为 7 片 ≈ 341 KB。
截图(不进仓):/tmp/claude-1000/-workspace-Jyotisha/d49dfa04-4e7a-47b2-80a7-8529c4c38f86/scratchpad/ 下 shot_login.png(登录页)、final-home-loggedin.png(首页问候)、harness-consult.png(回答 h2/h3)、final-report.png(报告封面与章节)。四张都确认中文标题(含同行的 D10、Jyotisha)为宋体、正文为黑体。
T5 · 文档
frontend/DESIGN.md§3:重写 Display 条目(新栈、自托管切片、Next 打包、500~700、swap 不 preload、中英混排、只许可加载字体、例外清单);删除「CJK never takes a serif here」,改为「CJK 标题用自托管 Jyotisha Serif SC;绝不落到系统宋体」;根错误页条目补一句「标题也是刻意无衬线」;修掉表格下「Display headings use the serif stack at weight 400」。docs/BUG_HISTORY.mdBUG-737:追加 2026-09-28 一行(任务书指定文字 + 实现摘要),状态仍 resolved,「最近更新」改为 2026-09-28。CHANGELOG.md:「标题改用宋体,正文仍是黑体」,Skill 不 bump,不改数据库。docs/testing/serif-headings-checklist.md:iPhone Safari、Windows Chrome / Edge、安卓 Chrome(至少一台国产机)× 首页标题 / 回答小标题 / 弹窗标题 / 报告封面 / 打印 PDF;SimSun 判别;Slow 4G 先黑体后宋体、无空白字;Network 里只有本站/_next/static/media/的小切片;生僻字退回黑体。docs/tasks/README.md:本单状态改为「已实现待验收」。
偏离任务书
serif-sc.css由site-styles.ts引入,不在根layout.tsx(理由见 T2:根布局也管后台)。- 字频顺序不用字表顺序,用「本仓语料前 300 + Google Fonts 字频分档」(理由见 T1);因此多引用了一份 Apache-2.0 的派生数据,来源已记录。
- 切片粒度比任务书建议的细:高频 19 片 × 192 字,低频 10 片 × ~287 字(理由见 T1,为满足首页 300 KB)。
- 拉丁片不收 Latin-1 带重音字母(À–ÿ),只收 Latin-1 符号与 × ÷;任务书要求的「基本拉丁、中文与全角标点、数字与常用符号」都在。
- 新增
.personal-report-md-article h1用--font-display(长文报告的标题行就是它的「封面」)。 tests/test_serif_font_slices.py加进 quick 门禁列表(scripts/run_quality_gate.py+2 行)。
请 Claude 定
.personal-report-md-article h3(长文报告小节,现继承黑体)、.personal-report-theme h3(显式黑体)、.membership-*标题(显式黑体 600)是否跟随改宋体。本单均未改。
环境缺口
- 已登录首页、咨询回答、报告页没有登录态与数据库,无法在真页面上测;改用「同一次构建的 CSS chunk + 真实类名 + 虚构内容」的静态页测流量与截图。真页面的最终数字要在 staging 登录后用 DevTools 复核(真机清单第 7 条)。
- 打印 PDF 未实际导出:
@media print无字体覆盖,理论上沿用宋体;留给真机清单。 - 真机(iPhone / Windows / 安卓)与 Slow 4G 观感:
docs/testing/serif-headings-checklist.md。 - 无 Docker:数据库 / 部署 24 条前端测试与基线同样失败;quick 门禁因此退出码 1。
- 未推送:按指示不推 staging、不推分支;部署核对由 Claude 做。
Claude 独立验收(2026-09-28)
| 项 | 结论 | 证据 |
|---|---|---|
| T1 切片 | 通过 | tests/test_serif_font_slices.py 8 passed;字体 name 表 ID1/16 = Jyotisha Serif SC,OFL 与 SOURCE 齐全 |
| T2 字体栈 | 通过 | --font-display 与硬红线 3 逐字一致;--font-body 未动 |
| T3 合同测试 | 通过 | Node 22:tests 4255 / pass 4200 / fail 24 / cancelled 0;24 条均为需 Docker 的数据库/部署套件,与 PROGRESS 基线一致 |
| tsc / lint | 通过 | tsc 0 错;lint 0 error / 127 warning(同基线) |
/ Static |
通过 | 干净 rm -rf .next 后 next build:○ / |
| 首屏 CSS gzip | 接受偏离 | +22.9 KB gzip(+52.5%),来自 3,635 个高频字逐字写进 unicode-range;已按任务书拆成独立 CSS。产品要的是全设备一致的宋体标题,这是它的直接成本,不再压缩 |
| 视觉 | 通过(静态) | 回答 h2/h3 与报告标题为宋体、正文黑体(执行方截图);真机清单 docs/testing/serif-headings-checklist.md 欠 |
执行方提问:.personal-report-md-article h3、.personal-report-theme h3、.membership-* |
维持黑体 | 这些规则本来就不走 --font-display,不在任务书范围;产品看真机后再定 |