Files
Jyotisha/docs/tasks/TASK-home-slow-network-20260924.md
T

89 lines
8.4 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.
# TASK · 慢网首页半残 + 「账户与点数」打开慢(2026-09-24)
## 基线
- `origin/staging = 9c9b66d6`(代码同已部署 `1420471a`)。
- 分支 `codex/home-slow-network-20260924`,worktree `.worktrees/home-slow-network-20260924`。
- **串行**:本单改 `frontend/src/app/(app)/page.tsx`;`TASK-chart-ephemeris-fixes-20260924.md` 的 T3(设置深链)也碰 `page.tsx` / `settings-url.ts`,**本单先合,那一项后合**。
## 事故实证
产品真机:Chrome DevTools「Slow 3G」强刷 `/` → 先转加载环 → 揭幕后出现报错条「连接云端服务超时。请检查网络后重试,或返回登录页重新建立会话。」→ 历史列表稍后自己出来,但模型显示「暂不可用」、「新建对话」灰掉、开场又问「请问我该怎么称呼你?」(账号早有称呼与资料)。
代码链(`origin/staging`):
1. `frontend/src/app/(app)/page.tsx` bootstrap `useEffect` 内联 `8000` ms 定时器:`controller.abort(); setAccountError("连接云端服务超时。…"); setHydrated(true);`。无命名常量、无重试(effect 依赖 `[]`,只有全屏错误页的「重试」= `location.reload()`)。
2. 8 秒要覆盖的是**串行链**:`await sessionListReady`(`SessionListProvider` 的 `/api/sessions` + `/api/account`)→ `fetchModelCatalog`(`/api/models`)→ `/api/consult/status` → 可选 lookup / `fetchSessionDetail`。慢网每跳 ≥2s,必然超时。
3. `SessionListProvider`(`frontend/src/lib/session-list-context.tsx` `loadSessionList`)用自己的 `AbortController`,**不受 8 秒 abort 影响**,稍后成功 → `setAccount` / `setSessions`。于是渲染门 `!hydrated || (!account && !accountError)` 走到"正常首页 + 报错条",而不是错误屏。BUG-927 把账户 / 会话读取搬进 provider 时,没有和这个 8 秒超时对齐。
4. `setModelCatalog` / `setProfile` / `setStartGreeting` / `setOnboardingStep` 只在成功路径最后执行,超时后永远不执行:`modelCatalog = null` → `model-selector.tsx`「暂不可用」、`use-home-shell-registration.ts` `newChatDisabled: … || !modelCatalog`;`profile = emptyProfile`、`onboardingStep` 初值 `"name"` → `home-types.ts` `presetOnboardingMessage`。
「账户与点数」:
5. `page.tsx` `const BillingPanel = dynamic(() => import("@/components/billing-panel"), { ssr: false })`,**无 `loading`、无预取** → 首次打开整块空白直到 chunk 下完。
6. `frontend/src/hooks/use-billing-panel.ts` 挂载时 `Promise.allSettled([fetchAccountData(), fetchPackages()])`:`fetchAccountData` 请求完整 `/api/account`(auth + 4–6 次串行查询),**结果被丢弃**(面板用的是 Home 传入的 `account`);`/api/payment/packages` 串行 4 次 DB 查询(`readEpayAvailability` → feature flags → `billing_products` → `product_entitlements`)。
7. 每次打开重挂、`cache: "no-store"`,零缓存。
## 根因
- A:首页账户阶段用一个固定 8 秒总预算包住多跳串行请求,超时后放弃且不补拉;账户 / 会话由独立 provider 读取,与该预算脱节,产生"半揭幕"状态。
- B:账单面板懒加载无占位无预取 + 多一次无用的完整账户请求 + 套餐接口串行查库 + 零缓存。
## 决策记录(产品 2026-09-24)
- **D1 推翻 8 秒硬超时(BUG-479 "8 秒 bootstrap 超时不变")与 09-22 `TASK-home-bootstrap-reliability` 的"不自动重试"在本场景的适用**:首页账户阶段**不设 8 秒硬超时**,加载环一直转到账户、模型列表、资料都到齐再一次揭幕("一次等待一次揭幕"不变)。
- **D2 兜底**:等待超过 `HOME_BOOTSTRAP_SLOW_MS = 20_000` 时切到现有全屏错误屏(「暂时无法进入 Jyotisha」+ 现有文案),「重试」改为**局部重新执行 bootstrap**(重新发请求,不 `location.reload()`);「返回登录」保留。首屏内联 13 秒 ES5 兜底(BUG-936,`first-paint-fallback.ts`)只管 bundle 没跑起来的情况,**阈值需同步上调到 > 20 秒 + 余量**,保证 bundle 正常运行时不会被它抢先。
- **D3 不再出现半揭幕**:任何情况下都不得出现"首页已揭幕但 `modelCatalog` / `profile` 未填"的组合。若 provider 已成功而模型列表失败,走 D2 错误屏,不走报错条。
- **D4 模型列表与账户并行**:`/api/models` 不再等 `sessionListReady`,与 provider 并行发出;`/api/consult/status` 同理并行。
- **D5 账单面板**:去掉 `use-billing-panel` 里的 `fetchAccountData`(无用请求);`BillingPanel` 在设置弹窗打开(或指针移到「账户与点数」导航)时预取 chunk;`/api/payment/packages` 结果做模块级缓存(同会话 60 秒内复用、后台刷新,参考 `secondary-page-data.ts` 的 `createMemoryCache`);服务端 4 次查询中无依赖的并行(`Promise.all`)。chunk 未到时面板区域显示现有 `SecondaryPageShell` 同类静态句「账户信息还没拿到。」,不加 spinner。
## 硬红线
1. `/` 保持 `○ Static`;首屏 gzip ±2%。
2. 揭幕后不得出现 spinner / 骨架 / 「正在加载」;等待期仍只用现有 `AppLoadingIndicator`,不造第二套。
3. 不得整页自动刷新(`location.reload` 只允许用户点击);D2 的局部重试是重新发请求,不是刷新。
4. `Home()` 不新增 useState / useRef(`home-shell-growth-contract`);新逻辑进 `frontend/src/lib/` 或 hook。`page.tsx` 不得净增行。
5. 不改 `/api/account`、`/api/sessions` 的响应结构;不改数据库。
6. `first-paint-fallback` 的六类失败分类(`hydrate-timeout` … `api-error`)保持可区分。
7. 既有断言改动写三栏;测试总数不低于基线。
## 任务分解
### T1 · 首页启动(`page.tsx` + `frontend/src/lib/home-bootstrap.ts` + `session-list-context.tsx`)
- 删除内联 8000 定时器;新增 `HOME_BOOTSTRAP_SLOW_MS`;模型列表 / consult status 与 provider 并行;到齐后一次性 `set*`;超过 20 秒 → 错误屏;「重试」调用可重入的 bootstrap 函数(需要 abort 上一轮、重置 provider 读取)。
- `first-paint-fallback.ts` 阈值同步调整并更新其合同测试(三栏)。
验收:新测试(真实 React 生命周期或纯函数状态机)覆盖——慢网 15 秒全部到齐 → 正常揭幕、无报错条、模型可选、不问称呼;模型接口失败而 provider 成功 → 错误屏而不是半揭幕;>20 秒 → 错误屏,点重试重新请求且成功后揭幕,`location.reload` 未被调用。`first-paint-fallback-contract.test.ts` 更新阈值断言。
### T2 · 账单面板(`use-billing-panel.ts`、`billing-panel.tsx`、`account-dialog-overlay.tsx`、`app/api/payment/packages/route.ts`)
- 删 `fetchAccountData`;预取 chunk;packages 模块缓存;服务端并行查询;静态占位句。
验收:打开面板时网络只有 1 个请求(`/api/payment/packages`,缓存命中时 0 个);第二次打开立即显示套餐;`use-billing-panel` 源码不再请求 `/api/account`;packages route 测试断言并行后结果不变。
### T3 · 记录
- `docs/BUG_HISTORY.md`:**BUG-1021** 慢网首页半揭幕(关联 BUG-479、927、936;复发自:无,BUG-927 引入的时序脱节);**BUG-1022** 账户与点数打开慢。
- `frontend/DESIGN.md` §9 首页揭幕规则更新(无硬超时、20 秒错误屏、局部重试);`CHANGELOG.md`;PROGRESS;`docs/testing/home-slow-network-20260924.md`:Slow 3G 强刷首页 → 只有加载环、揭幕后正常;Offline 强刷 → 20 秒后错误屏,恢复网络点重试 → 进入;设置 → 账户与点数首次 / 第二次打开耗时。
## 让步顺序
1. 若 provider 无法干净地"重新执行",允许重试时重建 `SessionListProvider` 的读取 key,但不得整页刷新。
2. `/api/payment/packages` 并行若受事务约束,允许保持串行,只做客户端缓存与去掉多余请求。
3. 不得让步:D3(不再半揭幕)、红线 3(不自动整页刷新)、红线 1(Static)。
## 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/home-slow-network-20260924 .worktrees/home-slow-network-20260924 origin/staging
cd .worktrees/home-slow-network-20260924/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -5
```
完成后 tsc 0 / lint 0 error / 失败名单同基线 / `next build` `/` 仍 `○` / gzip ±2%。
## BUG 编号
写单时最大 BUG-1020;本单 **BUG-1021 / 1022**,开工时核对。