From 1810f1a81fb982071130d4773c7b3eb7a9eb2735 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Wed, 30 Sep 2026 23:51:42 +0800 Subject: [PATCH] =?UTF-8?q?docs(tasks):=20home=20first=20load=20=E2=80=94?= =?UTF-8?q?=20serial=20boot=20requests,=20per-deploy=20cache=20loss,=20ser?= =?UTF-8?q?if=20on=20the=20loading=20screen=20(BUG-1127..1129)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE --- docs/BUG_HISTORY.md | 42 ++++++ docs/tasks/README.md | 1 + docs/tasks/TASK-home-first-load-20260930.md | 135 ++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 docs/tasks/TASK-home-first-load-20260930.md diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index b9eac0b0..fb730295 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -15045,3 +15045,45 @@ - 防复发:同一父级下按语言重新挂载的多个子元素,key 必须带各自前缀;语言切换要有整页测试,数据带齐核对表。 - 相关记录:BUG-1106~1108(跨语言保留章节) - 修复版本:分支 `codex/report-language-switch-stale-20260930` + +## BUG-1127 | 首页冷启动接口串行四轮,「正在载入账户」等得久 + +- 状态:investigating(根因已测量确认;待执行,任务书 `docs/tasks/TASK-home-first-load-20260930.md` T1) +- 首次发现 / 最近更新:2026-09-30 / 2026-09-30 +- 影响面:`frontend/src/lib/session-list-context.tsx`(`loadSessionList`)、`(app)/page.tsx` 取校正入口摘要的 effect。 +- 现象:真机进站长时间停在「正在载入账户」。 +- 触发条件:新开页面的冷启动(暖快照 BUG-1040 只在同一文档内生效)。 +- 根因:账户 → 人物目录 → 会话列表 → 校正入口摘要依次等待,其中只有「会话列表要知道当前人物」是真依赖。Claude 在 staging 线上页面用 CDP 给接口统一加 600 ms 延迟实测:从首个接口到揭幕共 4 轮串行,约 2.5 s。 +- 修复:待执行。 +- 验证:待执行。 +- 防复发:待执行后补。 +- 相关记录:BUG-936、BUG-1021、BUG-1040、BUG-1104 +- 修复版本:— + +## BUG-1128 | 每次部署后浏览器重新下载全部 JS 与宋体字体 + +- 状态:investigating(待执行,任务书 `docs/tasks/TASK-home-first-load-20260930.md` T3/T4) +- 首次发现 / 最近更新:2026-09-30 / 2026-09-30 +- 影响面:`frontend/next.config.ts`(`deploymentId`)、`src/app/fonts/serif-sc/`。 +- 现象:进站慢、标题宋体很久才出来;staging 一天部署多次,用户几乎每次都是冷缓存。 +- 触发条件:任何一次新部署之后首次打开。 +- 根因:`deploymentId` 让 Next 给所有 `/_next/static` 资源(含 CSS 引用的字体)加 `?dpl=`。内容没变,地址也会变,缓存随之失效。首屏 JS 约 690 KB(压缩后),宋体切片按页面用字 130~170 KB 起。 +- 修复:待执行(保留 `deploymentId`;验证 `supportsImmutableAssets`,不成立时字体改为自管的不变地址)。 +- 验证:待执行。 +- 防复发:待执行后补。 +- 相关记录:BUG-936(引入 `deploymentId` 的原因)、BUG-737 +- 修复版本:— + +## BUG-1129 | 加载屏标题用宋体,JS 就绪前先下载 3~4 个字体切片 + +- 状态:investigating(待执行,任务书 `docs/tasks/TASK-home-first-load-20260930.md` T2) +- 首次发现 / 最近更新:2026-09-30 / 2026-09-30 +- 影响面:`frontend/src/app/globals.css` `.app-loading-content strong`。 +- 现象:加载屏标题换体晚、会跳一下;字体下载和关键 JS 抢带宽。 +- 触发条件:任何冷启动。 +- 根因:加载屏标题用 `--font-display`。「正在载入账户」一行命中 3 个切片(约 130 KB),实测连同副标题共触发 4 个切片请求。 +- 修复:待执行(产品 2026-09-30 定:加载屏改用黑体)。 +- 验证:待执行。 +- 防复发:待执行后补。 +- 相关记录:BUG-737、BUG-1128 +- 修复版本:— diff --git a/docs/tasks/README.md b/docs/tasks/README.md index e28278d8..a3517160 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -383,6 +383,7 @@ | `TASK-rectification-midnight-date-anchor-20260920.md` | `PROGRESS-rectification-midnight-date-anchor-20260920.md` | **跨午夜的日期锚点与簇跨度(BUG-982 + BUG-983 合并一单)**:两条同根——线上契约只传钟点、日期不在契约里,后端按钟点大小**猜**哪端跨日。**BUG-983 是静默算错盘**:`_candidate_datetimes()` 把起始钟点无条件绑在申报日期上,前端零补偿。Claude 实测(申报日 2000-06-15):申报 `00:10` → 候选 `06-15 23:55`…`06-16 00:25`,**申报分钟本身落在 `06-16 00:10`,+1 天**;`00:02` 同样 +1 天;`12:00` 对照正确。受影响:申报落在午夜后半径内者(±15≈1.0%、±30≈2.1%、±60≈4.2%、±120≈8.3%);`late_night`(23:00–03:59) 更重,其 `00:00`–`03:59` 共 240 分钟**全部落在用户没申报过的日期上**。**BUG-982 影响面比原记录大**:同缺陷在链路上出现三处(`_cluster_span` / `unionStillValidRange` / `indistinguishableWidthMinutes`),原记录只写了 Python 两处。实测跨午夜簇 `23:58/23:59/00:00` 报告宽度 **1440 分钟**(真实 3)、`23:50/23:55/00:05` 报 **1431**(真实 16);该链经 `reportWidth` 直达 **交付卡「范围 X 分钟」**,深夜用户会看到候选其实差几分钟却被告知范围一千多分钟。**与 BUG-981 的关系**:981 让每个候选按自己日期算 Dasha 是对的,但 983 给的日期本身就错,**983 不修则 981 在申报近午夜的场景等于没上线**。**产品 2026-09-20 三点全部拍板**:D1 `late_night` **先问用户「午夜前还是午夜后」,答不上退「同日两段」`[D 00:00–03:59] ∪ [D 23:00–23:59]`,一律不得跨到 `D+1`**(连锁范围:退路产生**不连续候选集**,枚举/聚类/簇跨度/交付区间/宽度/交付卡都要支持两段,**不得用「取两段最小到最大」糊成一段**——那正是 982 的同型错误);D2 **允许窗口跨到前一天**,但交付层必须说明「若落在 23:5x 则出生日期是前一天」,不得静默改用户的出生日期(依据 Part B B4 真值覆盖率优先);D3 **加窗口相对序号**,排序/取首尾/算跨度一律用序号、钟点只作展示 —— 序号入契约会改候选身份与缓存指纹,**必须核对 BUG-984 的 `scoringIdentityMatches` 并确认历史结果只读不被重标**。硬红线:不调打分常数/确认门阈值;不得以排除真值换窄宽度;不得只修 `_cluster_span` 就宣称 982 已修;非跨午夜必须逐位不变且用同机 A/B(不得写死跨机浮点哈希,见 BUG-985)。沿用 BUG-982/983,不新开号 | **已验收通过并合入 staging**(2026-09-21,`b85c4a68`);已部署 staging(run 2831);真人验收待完成 | Claude 独立复核(用当初复现两条 Bug 的同一探针重跑):**BUG-983** 新契约下申报 `00:10`/`00:02` 的申报分钟落在**申报日**(+0 天),`late_night` 300 个候选**全在同一日**、两段;**BUG-982** 跨午夜 3 分钟簇 → **3 分钟**(原 1440),同日两段 → **300 分钟**(未糊成 1440)。三处(`decision_policy.py`/`credible-range.ts`/`candidate-plateau.ts`)均已改。**非跨午夜分数同机 A/B 6/6 逐位相同**;新测试在基线 6 条红、修复后全绿;Python `test_rectification_*` 基线 216 → **272 passed / 0 failed**;`tsc --noEmit` 退出 0。红线全清:打分常数零改动、`status=not_ready`、coverage 0、`holdout_passed()` False、Skill 未动、`official_eval_trial_count` 仍 0。D1 追问 4 选项含「不知道」「跳过」,均退同日两段、从不去 `D+1`,明写不计分可跳过;D2 交付层显示「(前一天)」;D3 用 `window_index`/`segment_index`,`interval_union_width` 按段求和(注释 “never fill a declared segment gap”)。超出要求的设计:`candidate_intervals` 拒绝重复钟点;engine-client 对 dated 路径 fail-closed 抛 `engine_invalid_candidate_decisions`。环境缺口:3 条前端测试在 Claude 机器红,但**同样的 `@/` 别名错误在既有测试上一模一样复现**(`ephemeris-route`、`api-service-unavailable`),非回归;无 Docker 故 `test:db` 未跑。**遗留待确认**:无 `cluster_intervals` 的行仍走旧按钟点算法(宽度仍 1440),应确认除修复前旧缓存外没有别的生产路径会产生无日期行 | | (直接执行,无任务书) | `PROGRESS-notice-loading-polish-20260930.md` | 性别只在档案(1119)、星盘气泡频闪与表格圆角(1120)、全站顶部浮层提示(1121/1122)、我的报告提速与分页(1123/1124)、宫位点亮等待与今日一句不跳(1125) | **已验收,推 staging**;真机清单 `docs/testing/notice-loading-polish-20260930.md` 待产品 | 合并分支 `codex/polish-integrate-20260930` | | `TASK-report-language-switch-stale-20260930.md` | `PROGRESS-report-language-switch-stale-20260930.md` | 报告阅读页切换中文 / English 后正文不换:正文与核对表同级重复 key(`key={shown}`),旧语言节点残留(BUG-1126);改 key + 整页切换回归测试 | **已验收,推 staging**(Claude 直接执行);真机第 10、11 步待产品 | 分支 `codex/report-language-switch-stale-20260930` | +| `TASK-home-first-load-20260930.md` | — | 进站等待太久:冷启动接口串行 4 轮(BUG-1127)、每次部署全部 JS 与宋体缓存失效(BUG-1128)、加载屏宋体抢带宽(BUG-1129);产品定加载屏用黑体、宋体只下一次;`supportsImmutableAssets` 先验证后上线 | 待领取 | — | ## 命名与归档 diff --git a/docs/tasks/TASK-home-first-load-20260930.md b/docs/tasks/TASK-home-first-load-20260930.md new file mode 100644 index 00000000..f61a9cec --- /dev/null +++ b/docs/tasks/TASK-home-first-load-20260930.md @@ -0,0 +1,135 @@ +# TASK · 进站等待太久:首页首开与字体加载 · 2026-09-30 + +> 执行方:coding agent。验收:Claude。 +> 分支 `codex/home-first-load-20260930`,worktree `.worktrees/home-first-load-20260930`,基线 `origin/staging`。 +> BUG 编号:**BUG-1127~1129**(Claude 写单时已登记为 investigating;开工时核对 `docs/BUG_HISTORY.md` 最大号,被占用就顺延并同步改本单)。 +> 涉及发布形态(静态资源缓存、`next.config.ts`),开工前读 AGENTS §9 与 `docs/research/pre_work_error_ledger.md`,跑 `python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45`。 +> **串行**:T1 改 `session-list-context.tsx` / `home-bootstrap-run.ts`,同期有别的单改这两个文件时先合入对方再开工。 + +## 0. 基线 + +- 写单时 `origin/staging` = `d87ec44a`(已部署,health `deployment.gitCommit` 同值)。开工以实测为准,写进 `docs/tasks/PROGRESS-home-first-load-20260930.md`。 + +## 1. 事故实证 + +产品 2026-09-30 真机(iPhone,5G):进站长时间停在「正在载入账户 / 同步个人资料与对话记录」,浏览器进度条走到约八成还在转;标题的宋体也要过很久才出来。 + +Claude 的测量方法:无头 Chrome 打开 staging 线上 `/`,用 CDP 拦截所有 `/api/*`,统一延迟 600 ms 后返回虚构账户数据,记录每个请求的起止时间和揭幕时刻。这台机器走代理,**绝对秒数不代表用户网络**,只用来看要等几轮、谁排在谁后面。连测多次,结果稳定: + +| 时刻(ms) | 事件 | +| --- | --- | +| ~2100–2300 | DOMContentLoaded | +| 2300–3100 | 首屏 29 个 JS 陆续下完(压缩后约 690 KB)。「正在载入账户」这一行还触发 4 个宋体切片(00/01/03/04,共约 170 KB),和 JS 抢带宽 | +| ~3100 | 首个接口才发出:`/api/account` 与 `/api/models`、`/api/consult/status` 并行 | +| +600 | `/api/chart-profiles`(人物目录),**等账户回来才发** | +| +600 | `/api/sessions?subject=self`,**等人物目录回来才发** | +| +600 | `/api/rectification/cases/entry-summary` 等,**等会话列表回来才发** | +| 4800–5300 | 准备阶段再按需下载 6~7 个小 JS | +| ~5700 | 揭幕 | + +接口延迟设为 0 时约 3300 ms 揭幕:JS 下载是第一段大头,4 轮串行接口是第二段。 + +另外三条静态事实: + +1. **每次部署都会让浏览器缓存全部失效。** `next.config.ts` 设了 `deploymentId: process.env.NEXT_DEPLOYMENT_ID`(= git SHA,见 `deploy/railway-web.Dockerfile`)。Next 因此给所有 `/_next/static` 资源(JS、CSS,以及 CSS 里 `url()` 引用的字体)加上 `?dpl=`。实测线上字体地址形如 `…/jyotisha-serif-sc-00.3aonnjsygjcpp.woff2?dpl=d87ec44a…`。文件内容没变,地址也会变,所以每次部署后都要重新下载约 690 KB JS 和页面用到的所有宋体切片。staging 一天部署多次,用户几乎每次打开都是冷启动。Next 文档 `supportsImmutableAssets.md` 原话:"browsers have to download static assets again after each new deployment, even when those assets have not changed"。 +2. **宋体的体积。** `src/app/fonts/serif-sc/` 共 30 个切片、2.1 MB,`font-display: swap`、不 preload。切片 01–19 按字频分组,每片 40–62 KB。一行 6 个汉字的标题通常命中 3~4 片(「正在载入账户」3 片约 130 KB;「印度占星星盘报告出生资料与图盘」3 片约 129 KB)。 +3. **4 轮串行的来源**(按符号定位): + - `session-list-context.tsx` → `loadSessionList`:`await readBootAccount()` → `await loadSubjectCatalog()` → 才 `fetch('/api/sessions?…&subject=' + readCurrentSubjectId())`。 + - `(app)/page.tsx` 里取 `fetchRectificationEntrySummary` 的 effect,条件是 `bootstrapPhase !== "account" && accountId && sessionListSettled`,所以要等会话列表。 + - 冷启动的暖快照(BUG-1040 `home-warm-start.ts`)只在同一个页面文档里回首页时生效,新开页面永远走冷路径。 + +## 2. 根因 + +- **BUG-1127**:首页冷启动的接口是串行的(账户 → 人物目录 → 会话列表 → 校正入口),其实只有「会话列表要知道当前人物」这一条是真依赖,而当前人物在绝大多数情况下是本地已记住的 `self`。 +- **BUG-1128**:`deploymentId` 的 `?dpl=` 让内容寻址的静态资源在每次部署后都失效,字体也一样。 +- **BUG-1129**:加载屏标题 `.app-loading-content strong` 用 `--font-display`(宋体),JS 还没跑,就先触发 3~4 个宋体切片下载,和关键 JS 抢带宽;换体时标题还会跳一下。 + +## 3. 决策记录(产品 2026-09-30) + +1. **加载屏改用黑体**:「正在载入账户 / 正在准备对话」这一屏的标题不再用宋体。其他标题保持 `TASK-serif-headings-20260928` 定下的宋体,不改字形。 +2. **宋体只下一次**:宋体切片在部署之间必须保持同一地址、长期缓存,部署后不再重新下载。为此允许改变切片的存放和引用方式(见 T3)。如果最终走 T3-b,就**推翻** `frontend/tests/serif-headings-contract.test.ts` 里「slices are bundled by Next, not served from public/」这条断言(原因:Next 打包的资源带 `?dpl=`,每次部署都失效)。改断言要写「原值 / 新值 / 原因」三栏。 +3. 没有采用的方案:苹果设备优先用系统自带宋体、标题全部改回黑体。 +4. 接口并行化、按需 JS 预取属于纯工程优化,不改变可见行为,按本单红线执行即可。 + +## 4. 硬红线 + +1. **不得去掉 `deploymentId`**:它修的是 BUG-936 与报告页 ChunkLoadError(旧标签页在发布后加载不到新 chunk)。T4 只能在保留它的前提下做。 +2. 不得出现半揭幕(BUG-1021),不得在切换账号时先画出上一个账号的数据(BUG-1040 `bindCurrentSubjectAccount` 逻辑),加载屏不得新增 spinner 或骨架(AGENTS §6)。 +3. 并行预取出来的「本人会话列表」只能在当前人物确认为 `self` 时使用;确认是他人时必须丢弃,按他人重取,界面上不得闪出本人的会话。 +4. `(app)/page.tsx` 的 `Home()` 状态数不得增长(`home-shell-growth-contract.test.ts`);新逻辑进 `src/lib/` 或 hook。 +5. 不改 `.gitea/workflows/**`、不改 DNS、不升级依赖。改 `next.config.ts` 可以,但必须真跑生产构建和 standalone 验证(见 T4)。 +6. 宋体切片文件的字节内容不变(`scripts/fonts/build_serif_slices.py` 的产物逐字节一致),只改存放位置、文件名和引用方式。 +7. 改动或删除断言前,按 AGENTS §7-8 在 `frontend/tests/` 和 `tests/` 里 grep;已知相关:`serif-headings-contract.test.ts`、`font-stack-loadable-contract.test.ts`、`tests/test_serif_font_slices.py`、`home-shell-growth-contract.test.ts`,以及 BUG-1040 / BUG-1104 的暖快照测试。 + +## 5. 任务分解 + +### T1 · 冷启动接口并行(BUG-1127) + +- `loadSessionList`:`readBootAccount()`、`loadSubjectCatalog()`,以及「按本地记住的当前人物(缺省 `self`)取会话列表」三者同时发出。账户回来后校验账户 id(BUG-1040);人物目录回来后如果当前人物与预取时用的不同,丢弃预取结果,按正确人物再取一次。 +- 校正入口摘要(`fetchRectificationEntrySummary`)在账户 id 已知时就发,不再等 `sessionListSettled`。先确认它的结果不依赖会话列表;如果有依赖,在进度记录里写清并保留该依赖。 +- 验收: + 1. 单元测试:mock fetch,断言冷启动时 account、chart-profiles、sessions 三个请求在同一轮发出(没有一个要等另一个 resolve)。另加一条「人物目录回来发现是他人」的用例:本人会话不上屏,最终列表是他人的。 + 2. 用 §7 的测量脚本,接口统一延迟 600 ms:从首个接口发出到揭幕,**串行轮数由 4 降到不超过 2**,进度记录贴前后对照表。 + 3. 既有 BUG-1021 / 1040 / 1052 / 1104 相关测试全绿、断言不动。 + +### T2 · 加载屏用黑体(BUG-1129) + +- `.app-loading-content strong`(以及同一加载屏里其他用 `--font-display` 的元素)改用正文黑体栈。 +- `frontend/DESIGN.md` 的加载屏一节同一提交写明:加载屏只用黑体,理由是 JS 就绪前不触发网络字体下载。 +- 验收:测量脚本里,`load` 事件之前没有任何 `jyotisha-serif-sc-*` 请求。 + +### T3 · 宋体切片只下一次(BUG-1128 字体部分) + +按顺序尝试,以 T4 的结果为准: + +- **T3-a(优先)**:T4 成立时,确认字体切片也走不带 `?dpl` 的不变地址。成立就不用搬文件。 +- **T3-b(T4 不成立时)**:切片搬到 `public/fonts/serif-sc/`,文件名带内容哈希(例如 `jyotisha-serif-sc-00..woff2`),`serif-sc.css` 改用绝对路径引用。`next.config.ts` 的 `headers()` 给 `/fonts/serif-sc/:path*` 加 `Cache-Control: public, max-age=31536000, immutable`。`build_serif_slices.py`、`SOURCE.txt` 跟着更新命名规则。 +- 验收:staging 部署后,字体地址不带 `?dpl=`,响应头是 `immutable`;连续两次部署,同一切片地址不变(进度记录贴两次的地址);`test_serif_font_slices.py` 等合同测试按三栏更新后通过。 + +### T4 · JS 不随部署失效(BUG-1128 JS 部分,先验证、不成立就不上) + +- 在 `next.config.ts` 试 `supportsImmutableAssets: true`(Next 16.3 起提供,见 `node_modules/next/dist/docs/01-app/03-api-reference/05-config/01-next-config-js/supportsImmutableAssets.md` 与 `07-adapters/12-immutable-static-assets.md`)。文档说明没有 adapter 支持时启用可能让部署坏掉,所以必须实测: + 1. `next build` 后产物里有 `/_next/static/immutable/*`,standalone `next start` 能以不带 `?dpl` 的地址返回这些文件,响应头 `immutable`; + 2. 用同一份代码、两个不同的 `NEXT_DEPLOYMENT_ID` 各 build 一次,未改动的 chunk 在两次构建中路径相同; + 3. 旧标签页场景不回退:用 A 版本的页面,服务端换成 B 版本,再做一次客户端导航,仍然整页刷新、不报 ChunkLoadError(对照 BUG-936 与报告页那条的复现方式)。 +- 三条都满足才上线;任一条不满足就不改配置,把实测结果写进 `BLOCKED.md`,并走 T3-b。 +- 验收:进度记录贴三条的原始证据(目录列表、curl 响应头、两次构建的路径对照)。 + +### T5 · 准备阶段的按需 JS 提前下载(可选) + +- 找出揭幕前按需加载的 6~7 个 chunk(测量脚本里 4800–5300 ms 那批),能在首屏 JS 就绪后立刻预取的就预取(参考 `chat-chunk-prefetch.ts` 的做法)。首屏 gzip 仍须在 ±2% 内。 +- 验收:测量脚本里这批 chunk 的完成时间早于账户接口返回。 + +### T6 · 记录 + +- BUG-1127~1129 补齐修复、验证、防复发,改为 resolved;CHANGELOG 一条;`DESIGN.md`(T2);新增 `docs/testing/home-first-load-20260930.md` 真机清单:①部署后第一次打开首页,计时到能输入;②刷新再计时;③隔一次部署再打开,计时并看标题宋体是否瞬间出现;④切到他人档案后刷新,确认没有闪出本人的会话。 + +## 6. 验收口径 + +- `tsc --noEmit` 0 错;`npm run lint` 0 error;`npm test` 失败清单与基线逐条一致,测试总数不减少(按测试名比对)。 +- `next build` 后 `/` 仍是 `○ Static`;首屏 gzip ±2%。 +- Python 有改动(`scripts/fonts/`、`tests/test_serif_font_slices.py`)时跑快速门全量。 +- 部署后 Claude 用同一测量脚本在 staging 复测,贴前后对照。 + +## 7. 测量脚本 + +Claude 的脚本在会话 scratchpad,不入库;执行方按以下口径自己写一个放进 `frontend/scripts/`(只做测量,不进 CI): + +- 用 `/exec-daemon/node` + Node 22 自带 `WebSocket` 连无头 Chrome 的 CDP。 +- `Fetch.enable` 拦截全部请求:`/api/*` 按固定延迟返回虚构数据(账户、`/api/models` 目录、`/api/sessions` 空列表,其余返回 404),静态资源放行。 +- 记录 `Network.requestWillBeSent` / `loadingFinished`、`Page.domContentEventFired` / `loadEventFired`,每 100 ms 检查 `.app-loading`(用 `getClientRects().length`,不能用 `offsetParent`,因为它是 fixed 定位),以它消失为揭幕时刻。 + +## 8. 让步顺序 + +T2 → T1 → T3 → T4 → T5。T2、T1 必做;T3 必须以 a 或 b 之一落地;T4 不成立就记 BLOCKED;T5 可以留到下一单。 + +## 9. 开工前置命令 + +```bash +cd /workspace/Jyotisha && git status -sb | head -1 +git fetch origin --prune +git worktree add -b codex/home-first-load-20260930 .worktrees/home-first-load-20260930 origin/staging +cd .worktrees/home-first-load-20260930/frontend && npm ci # Node 22 在 /exec-daemon/node +grep -o "BUG-[0-9]\{3,4\}" ../docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 +python3 ../scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45 +```