Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
121 lines
12 KiB
Markdown
121 lines
12 KiB
Markdown
# 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 线上复测。
|