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

8.4 KiB
Raw Blame History

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。

「账户与点数」:

  1. page.tsx const BillingPanel = dynamic(() => import("@/components/billing-panel"), { ssr: false }),无 loading、无预取 → 首次打开整块空白直到 chunk 下完。
  2. 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)。
  3. 每次打开重挂、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)。

开工前置命令

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,开工时核对。