diff --git a/TASK-unified-loading-20260902.md b/TASK-unified-loading-20260902.md new file mode 100644 index 00000000..b28e72d8 --- /dev/null +++ b/TASK-unified-loading-20260902.md @@ -0,0 +1,82 @@ +# 任务书 · 首页加载统一:一次等待、一次揭幕(2026-09-02) + +基线:`origin/staging` 最新。**前置条件:`codex/streaming-ux-20260901`(c846c44a)必须先合入**——它改了消息区渲染链路,本轮改揭幕时机,两者同时改 `page.tsx` 渲染路径必然冲突。未合入前不得开工,登记 `BLOCKED.md`。与其它改 `page.tsx` 的轮次不得并行。 + +产品要求原话:"外面一层加载进去之后里面组件又有加载动画"→ 改成"外层加载显示不同状态,进去之后直接显示完整的,不加组件加载动画"。 + +--- + +## 为什么要做(事故实证) + +行号基于 `701d4f92`,按符号定位。 + +1. **外层加载只等三样。** `page.tsx:862` 的 `Promise.all` 只拉账户、模型目录、会话列表,拿到就 `setHydrated(true)` 揭幕(`main.app-loading` 在 1503)。 +2. **揭幕后还有四处各自转圈**: + - **当前会话消息**:BUG-464 后列表不带消息,揭幕时当前会话(默认最近一条或 `?c=` 指定)的消息尚未加载,`ensureSessionMessages`(485–487 的 effect)才开始拉,消息区先显示 `session-messages-loading` + `InlineSpinner`(1867–1868)。**这是用户最先看见的第二层等待**。 + - **starter 推荐问题**:profile 完整时 1153–1187 的 effect 拉 `/api/onboarding`,期间显示 `starter-loading`(1836)。 + - **每日星语**:1205 起拉 `fetchDailyStarlanguage`,卡片 `aria-busy`(`starter-home.tsx:88`,`dailyStarlanguageBusy` 630–634)。 + - **校正入口摘要**:523 拉 `entry-summary`,影响校正卡文案;打开校正时 `BirthTimeRectification` 动态分包还有一行"正在加载出生时间评估..."(230–235)。 +3. 这些请求彼此独立,全部可以在揭幕前并行完成——现在是串行体验(先等外层、再逐个等内层),并非数据依赖所迫。 + +## 决策记录(产品授权,2026-09-02) + +1. **单一加载动画**:全站首页只保留 `AppLoadingIndicator`(轨道动画)这一种加载表现。揭幕后不得再出现任何 spinner/骨架/"正在加载"文案,**流式回答的生成中状态除外**(那是内容本身在生成,不是加载)。 +2. **两阶段揭幕**:阶段一(账户/模型/会话列表)→ 阶段二并行(当前会话消息、starter 问题、每日星语、校正入口摘要、校正分包预热 `import()`)→ 全部就绪才揭幕。外层加载屏的 `detail` 文案随阶段切换(例如"正在读取账户…"→"正在准备对话…"),文案由执行方按现有 `AppLoadingIndicator` 的 title/detail 口径拟,浅深两套主题下检查。 +3. **超时降级,不无限等**:阶段二整体上限 **4 秒**(常量,可配)。超时未到的项一律进入其**静态最终态**,不是加载态:starter 用现有安全默认问题(1836 附近已有"个性化问题暂时不可用"路径)、每日星语用非个性化"查看今日运势"文案、校正卡用无摘要文案、当前会话消息未到则揭幕后**静默补上**(消息区空白但不转圈,到达即渲染)。阶段一失败仍走现有 `app-loading-error`。 +4. **后续切换会话**:揭幕后在后台按侧栏顺序预取最近 5 条会话的消息(低优先级、串行、复用 `ensureSessionMessages` 与 hydrated 缓存),让常见切换零等待;缓存未命中时消息区**不显示 spinner**,仅保留静态占位(无动画)直到消息到达。这一条是折中,产品若要"未命中也完全空白"可在验收时改口。 +5. 打开校正的动态分包 loading 文案保留为兜底(分包已预热,正常不会看到)。 + +## 硬红线 + +1. **外层加载屏的 DOM 合同不变**:`main.app-loading[aria-busy="true"]` 与 `main.app-loading-error` 选择器被 `stale-client-recovery.tsx:34-35` 依赖,必须原样保留。 +2. 阶段二**不得阻塞阶段一的错误处理**;401 仍走 `redirectToLogin`,`?c=` 恢复、sessionStorage 回跳(BUG-465)语义不变。 +3. 揭幕后不得新增任何 `InlineSpinner` / `role="status"` 加载态;被移除的加载态所对应的合同测试(先 `grep -rn 'starter-loading\|session-messages-loading\|dailyStarlanguageBusy\|正在加载出生时间' frontend/tests tests`)按例外条款修改,注明原值与原因;其余断言不得改。 +4. 不得手写 `useCallback` / `useMemo`;`npm run lint` 0 error(BUG-470 后 react-hooks 规则已能分析 Home,effect 内同步 setState、render 写 ref 都会被拦)。 +5. `./node_modules/.bin/tsc --noEmit` 通过;测试总数不低于开工时 `origin/staging` 实测(Docker 环境 fail=0/skipped=0,无 Docker 逐条比对既有缺口);`next build` 后 `/` 仍 `○ Static`;首屏 gzip ±2%。 +6. 不改 `.gitea/workflows/**`;不动数据库;不在脏工作树切分支;不自行提升 main。 + +让步顺序:功能与测试不回归 > 一次揭幕的体验 > 揭幕耗时 > 代码整洁。 + +## 开工前置 + +```bash +git fetch origin --prune +git worktree add -b codex/unified-loading-20260902 \ + ../.worktrees/unified-loading-20260902 origin/staging +``` + +确认 streaming-ux 已合入。读 `pre_work_error_ledger.md`、`frontend/AGENTS.md`、`docs/BUG_HISTORY.md` 的 BUG-464/465/470。先读:`page.tsx` 启动 effect(840–960)、`ensureSessionMessages`(`use-session-management.ts`)、starter/每日星语/入口摘要三个 effect、`app-loading-indicator.tsx`、`starter-home.tsx`。 + +## 任务分解 + +### 任务 1(P0)· 两阶段揭幕 + +- 启动 effect 改为:阶段一现有 `Promise.all` → 计算选中会话(复用 `resolveBootstrapSessionSelection`)→ 阶段二 `Promise.allSettled` 并行拉当前会话消息、starter 问题(仅 profile 完整时)、每日星语、校正入口摘要,并 `void import("@/components/birth-time-rectification")` 预热 → 4 秒上限 → `setHydrated(true)`。 +- 加载屏 `detail` 按阶段切换文案。 +- 原来揭幕后才触发的三个 effect 改为"若已在阶段二拿到则跳过",避免重复请求(观测:首屏网络面板同一端点不得出现两次)。 + +### 任务 2(P0)· 移除揭幕后的加载态 + +- 删除首屏路径上的 `session-messages-loading`、`starter-loading`、每日星语首屏 `aria-busy`;按决策记录 3 落各自的静态最终态。 +- 保留:流式生成中的状态;校正分包兜底文案。 + +### 任务 3(P1)· 切换预取 + +- 揭幕后按侧栏顺序串行预取最近 5 条会话消息;缓存未命中时的静态占位(决策记录 4)。 + +### 任务 4(P1)· 合同测试 + +- 锁:阶段二包含四项请求与预热;4 秒上限常量存在;揭幕后 `page.tsx` + `starter-home.tsx` 无 `InlineSpinner`/`role="status"` 加载态(流式区除外);`stale-client-recovery` 依赖的选择器仍在。 + +## 总验收 + +1. tsc / lint / 测试基线 / `/` Static / gzip(红线 4–5)。 +2. 首屏时序说明写进 PROGRESS:阶段一、阶段二各自耗时(本地测量即可),并附网络面板截图或请求清单证明无重复请求。 +3. 行为:登录后从加载屏到完整界面**只有一次揭幕**,消息、推荐问题、每日星语、校正卡同时就位;断网或慢速(DevTools 节流 Slow 3G)下 4 秒后揭幕且各项为静态兜底态、无任何转圈。无登录态环境则合同测试覆盖并如实标注。 + +## 明确不做 + +- 不改流式回答的生成中表现(streaming-ux 轮已定)。 +- 不改校正会话面内部的加载/busy 语义(`rectification-agentic-chat` 的 `aria-busy` 是生成中,不是加载)。 +- 不做骨架屏——产品要的是"不显示,直到完整"。 +- 不动报告页、会员页的加载表现(另议)。