Files
Jyotisha/docs/tasks/PROGRESS-home-first-load-20260930.md
T
Jesse_ChenandClaude Opus 5.5 e600cff55f
Independent Staging Quality Gate / validate (push) Successful in 17m54s
Independent Staging Quality Gate / publish (push) Successful in 4m6s
docs(tasks): Claude acceptance of the home first-load round
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-01 01:02:56 +08:00

121 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-30
> 任务书:`docs/tasks/TASK-home-first-load-20260930.md`。产品 2026-09-30「请你调用 subagent 执行」,由 Claude 派 fork 子代理直接执行;验收由 Claude 另做。
> 分支 `codex/home-first-load-20260930`(**未推送**)。基线 `origin/staging` = `6a74a230`(开工实测;任务书写单时是 `d87ec44a`,中间只多了文档提交)。BUG-1127~1130(开工核对最大号 BUG-1130;另有 BUG-1131 已被别的任务书预留,本单没有新开号)。
## 提交
| 提交 | 内容 |
| --- | --- |
| `80ea7b0b` | T2 加载屏标题用黑体(BUG-1129) |
| `ab656e0d` | T1 冷启动账户 / 人物目录 / 会话列表 / 校正入口同一轮(BUG-1127) |
| `2fd1e2b0` | T8 HTML 内联脚本早发四个请求(BUG-1127) |
| `b743b16b` | T7 城市表按需、封存 JSON 只留 6 字段、引导页按需 + 揭幕前预加载(BUG-1130) |
| `ed4f5133` | 测试存储桩类型修正 |
| `8112f62b` | T3-b 宋体切片搬 public/ + 内容哈希 + immutable(BUG-1128) |
| `31d07378` | 测量脚本 `frontend/scripts/measure-home-first-load.mjs` |
| `e2c3791f` | 引导页改为模块级 `React.lazy`(lint 规则);暖快照测试按启动流程先加载城市表 |
| 分支最后一个提交 | 记录:BUG 历史、CHANGELOG、DESIGN、BLOCKED、真机清单、本进度、任务板 |
## 每项结果
| 任务 | 结果 | 说明 |
| --- | --- | --- |
| T2 加载屏黑体 | 做了 | `.app-loading-content strong` → `--font-body`;合同测试钉住;DESIGN.md 已写。`load` 前宋体请求 4 → 1,剩下 1 个是侧栏品牌字「Jyotisha」(见偏离 1) |
| T1 接口并行 | 做了 | 账户、人物目录、会话列表(按本地记住的人物猜)同一轮;人物变了或换了账户就丢弃会话列表重取;首登不再作废刚读到的人物目录;校正入口摘要在账户阶段就发。**先确认了摘要不依赖会话列表**:它是按用户读的独立接口,原来的等待只来自 effect 的 `sessionListSettled` 条件,该条件保留(只改了请求发出时机) |
| T8 早发 | 做了 | 内联 ES5 脚本放在根 layout 的 `<head>`,脚本里用 `location.pathname !== "/"` 直接返回(见偏离 2);四个请求挂在 `window.__jyotishaBootReads`;只取用一次、30 s 失效、失败回退正常请求、401 照样登出、另一人物或另一账户的会话列表丢弃;路径和参数由 `boot-early-reads.ts` 共用 |
| T7 首屏瘦身 | 部分做了 | 城市表、封存评测 JSON、引导页(及其下的出生地选择器、生时录入、日期选择器)已离开首屏。**校正界面、markdown 渲染没做**:需要在「打开校正、打开会话、落在 `?c=` 会话」几个入口都加揭幕前等待,改动面大,按让步顺序(至少完成第 1、2 项)留给下一单 |
| T3 宋体只下一次 | 做了(T3-b) | T4 不成立,走 T3-b;决策 2 授权推翻的那条断言已按三栏改 |
| T4 JS 不随部署失效 | 没上(blocked) | 第一条证据不成立,见下;写入 BLOCKED.md |
| T5 准备阶段 JS 预取 | 没做 | 可选项;T7 已把引导页挪走,剩下的按需 chunk 大多是揭幕后的次要面板,留给下一单 |
| T6 记录 | 做了 | BUG 历史四条、CHANGELOG、DESIGN、BLOCKED、真机清单 `docs/testing/home-first-load-20260930.md`、任务书决策 6 补一句、任务板改「待验收」 |
## 前后对照
测量:本机 `next build` + `next start`(基线在一次性 detached worktree `6a74a230`,最终版在本分支),`frontend/scripts/measure-home-first-load.mjs`,`/api/*` 统一延迟 600 ms 返回虚构数据,窗口 390×844,每组 3 次。机器走代理且有波动,**只看轮数与先后,不代表真机秒数**。
模拟慢网(`--throttle slow`:1.6 Mbps / 150 ms,最稳定的一组):
| 指标 | 基线 | 最终 |
| --- | --- | --- |
| 揭幕时刻 | 7942 / 7954 / 7956 ms | 5617 / 5630 / 5630 ms(−29%) |
| 最后一个首屏 JS 下完 | ≈5.43 s | ≈4.11 s |
| 首个接口发出 | 首屏 JS 下完**之后** +25~29 ms | 首屏 JS 下完**之前** ≈3.17 s |
| 从首个接口到揭幕的串行轮数 | 4 | 2 |
| `load` 前的宋体切片请求 | 4(00/01/03/04) | 1(00,侧栏品牌字) |
不限速(本机波动较大):基线 3.06~4.36 s(更早一次空闲时测得 2.71~2.74 s),最终 0.92~1.31 s(更早一次空闲时测得 0.90~0.97 s);轮数 4 → 2(有一次记为 3,是某个请求晚发了半轮以上)。
首页首屏 JS(排除 noModule,按预渲染 `/` 引用的 chunk 计):
| | 文件数 | 未压缩 | brotli | gzip |
| --- | --- | --- | --- | --- |
| 基线 | 29 | 1994 KB | 516 KB | 611 KB |
| 最终 | 28 | 1608 KB | 431 KB | 504 KB |
首屏 gzip −17.5%,超出 ±2%,属任务书 T7 验收 3 所述的预期下降。城市表(`梅河口市`)、封存 JSON(`previous_pilot_id`)、`react-day-picker`(`rdp-`)三个标记都不再出现在首屏 chunk 中,markdown(`micromark`)仍在。归因构建(`productionBrowserSourceMaps`)只在一次性 worktree 里做,没有提交。
宋体:本地生产构建里 CSS 的切片地址是 `url(/fonts/serif-sc/jyotisha-serif-sc-00.1812be0085.woff2)`,换部署编号(`new00001` → `new00002`)后地址不变、不带 `?dpl`;`next start` 返回 `Cache-Control: public, max-age=31536000, immutable`。「staging 连续两次部署地址不变」要部署后由 Claude 核对。
## T4 三条证据
1. 在一次性 worktree 里给 `next.config.ts` 加 `supportsImmutableAssets: true`,用 `NEXT_DEPLOYMENT_ID=t4aaaa01` 做生产构建:`.next/static` 下只有 `build-*` / `chunks` / `media`,**没有 `immutable` 目录**;预渲染 `/` 的 HTML 里 133 处资源地址仍是 `?dpl=t4aaaa01`。**不成立。**
2. 未测:第 1 条已不成立,两次构建路径是否相同没有意义。
3. 未测:同上。
结论:不改配置,写入 `BLOCKED.md`;Next 源码里 `supportsImmutableAssets` 的生效依赖 adapter(`build/adapter/*`),自托管 standalone 没有这一步。
## 测试数字
| 项 | 基线(`6a74a230` 干净 worktree) | 最终 |
| --- | --- | --- |
| `tsc --noEmit` | — | 0 错 |
| `npm run lint` | 0 error,126 warning | 0 error,126 warning(与基线同数,本单新文件 0 告警) |
| `npm test`(Node 22) | 4489 / fail 24 / cancelled 0 | 4513 / fail 24 / cancelled 0 |
| 失败名单 | 24 条 | 与基线逐条一致(全是要 Docker 的数据库、部署、备份套件) |
| 测试名 | — | 新增 24 条,没有消失的 |
| `next build` | `/` Static | `/` Static |
| 快速门 Python 子段 | 1001 passed / 1 skipped | 1002 passed / 1 skipped(+1 = `test_slice_names_carry_their_content_hash…`) |
| 快速门 npm 子段(系统 Node 20) | 4489 / fail 66 | 4513 / fail 66,名单逐条一致(环境:Node 20) |
| `tests/test_repo_privacy_markers.py` | — | 通过(快速门内) |
改动的既有断言与夹具(每处都写了原值 / 新值 / 原因):
- `serif-headings-contract.test.ts`:切片 url 从 `./jyotisha-serif-sc-NN.woff2` 改为 `/fonts/serif-sc/…<哈希>.woff2`(决策 2 授权推翻);新增加载屏黑体、长期缓存两条。
- `font-stack-loadable-contract.test.ts`:绝对地址按 public/ 解析,「url() 文件必须存在」不变。
- `tests/test_serif_font_slices.py`:切片目录改为 public;新增文件名哈希与内容一致、public 旁有 OFL。
- `chart-page-view.test.tsx`、`session-list-provider.test.ts`:会话、账户请求的源码断言改为共用常量的写法,并断言常量值不变。
- `home-warm-snapshot.test.ts`:换账户分支的源码断言改为 `boundAccount` 写法;用例前先加载城市表(夹具是只有编码的老资料)。
- `home-bootstrap-run.test.ts`:夹具 io 补 `preloadOnboardingShell` / `startEntrySummaryRead` 空操作,断言不动。
- `rectification-confirmation-gate.test.ts`:新增「6 个字段逐字段等于封存 JSON」。
## 偏离任务书之处
1. **T2 验收「`load` 前 0 个宋体请求」未完全达到**:剩 1 个,是侧栏品牌字「Jyotisha」(`.brand-row`,按 serif-headings 方案属于标题,静态 HTML 里可见),只用 00 号切片(38 KB,拉丁字母)。它不在决策 1「加载屏改黑体」的范围内,没有擅自改;切片现在长期缓存,每台设备只下一次。是否也改黑体,由产品决定。
2. **T8 内联脚本放在根 layout,用路径判断只在 `/` 生效**,而不是「只在首页 HTML 里」。首页 `(app)/page.tsx` 是客户端组件,在里面渲染 `<script>` 会进 `<body>`、执行更晚,还有水合风险;根 layout 已有同类的 BUG-936 兜底脚本。其他路径上脚本第一行就返回,不发请求(有测试覆盖 `/chart` `/reports` `/login` `/people` `/admin`)。
3. **T1 里账户切换时,早发 / 预取的会话列表一律丢弃**(按红线 8),即使人物同为本人;代价是换账户那次多一轮,很少见。
4. **城市表的「揭幕前加载」挂在人物目录加载里**(目录总包含本人资料),而不是 `loadSessionList`:`upsertSelfChart` 本身就要判断资料是否完整,必须在它之前加载。加载失败时,老资料会像没有表时一样被当成「地点未完整」(原来不会发生);网络断开才会遇到,没有另做兜底。
5. 引导页若在揭幕后才需要(例如揭幕后资料被重置),走普通按需加载,空白一瞬(fallback 为空,不是 spinner)。
## 环境缺口
- 没有登录态与真机:浏览器级验收见 `docs/testing/home-first-load-20260930.md`(七条)。本地测量用虚构账户接管接口,真机网络下的秒数以产品掐表为准。
- 没有 Docker:DB / 部署套件 24 条失败,与基线逐条一致。
- worktree 里没有 `.venv`:`pre_work_check.py` 退出码 0,但外部引擎诊断那一项报「找不到 `.venv/bin/python`」;快速门借用本机 `.worktrees/rectification-holdout-expansion-20260929/.venv`(临时软链接,已删)。
- 「staging 连续两次部署宋体地址不变」「staging 上的揭幕时间」要部署后由 Claude 复核。
## Claude 独立验收(2026-10-01)
| 项 | 结果 |
| --- | --- |
| `tsc --noEmit` / `npm run lint` | 0 错 / 0 error(126 warning,与基线相同) |
| `npm test` | 4513 条,失败 24 条,与 Claude 在 `d87ec44a` 上跑的失败清单逐条一致(均为需要 Docker 的套件);按测试名比对没有消失的,新增 24 条 |
| `next build` | 通过,`/` 仍是 `○ Static` |
| Python | `tests/test_serif_font_slices.py`、`tests/test_repo_privacy_markers.py` 通过 |
| 字体 | 本地生产服务:CSS 里的宋体地址为 `/fonts/serif-sc/<名>.<哈希>.woff2`,不带 `?dpl`;响应头 `Cache-Control: public, max-age=31536000, immutable`、`Content-Type: font/woff2` |
| T8 早发 | Claude 自己的 CDP 脚本(接口统一 600 ms):account、chart-profiles、models、sessions 在导航后约 50 ms 同时发出,早于首屏 JS;揭幕前只剩 2 轮(第二轮是 entry-summary / consult status) |
| 未登录 | 所有接口返回 401 时,约 0.44 s 跳到 `/login` |
| 代码审阅 | 早发脚本只在 `/` 生效,每个结果只用一次、30 s 后作废、网络失败回退到正常请求;猜错的人物、换账号时丢弃预取的会话列表;确认门槛把研究 JSON 换成用到的 6 个值,并有测试与原 JSON 比对。Caddy 原样转发 Next 的缓存头,镜像会复制 `public/`,线上能生效 |
遗留:T2 侧栏品牌字「Jyotisha」在 `load` 前仍会下载宋体切片 00(38 KB,不在决策 1 的范围内);T7 校正界面与 markdown 仍在首屏;T4 已记入 BLOCKED;T5 未做。部署后要核对:连续两次部署,字体地址不变;在 staging 线上复测。