Files
Jyotisha/TASK-unified-loading-20260902.md
T
2026-09-02 04:20:35 +00:00

7.6 KiB
Raw Blame History

任务书 · 首页加载统一:一次等待、一次揭幕(2026-09-02)

基线:origin/staging 最新。前置条件:codex/streaming-ux-20260901c846c44a)必须先合入——它改了消息区渲染链路,本轮改揭幕时机,两者同时改 page.tsx 渲染路径必然冲突。未合入前不得开工,登记 BLOCKED.md。与其它改 page.tsx 的轮次不得并行。

产品要求原话:"外面一层加载进去之后里面组件又有加载动画"→ 改成"外层加载显示不同状态,进去之后直接显示完整的,不加组件加载动画"。


为什么要做(事故实证)

行号基于 701d4f92,按符号定位。

  1. 外层加载只等三样。 page.tsx:862Promise.all 只拉账户、模型目录、会话列表,拿到就 setHydrated(true) 揭幕(main.app-loading 在 1503)。
  2. 揭幕后还有四处各自转圈
    • 当前会话消息:BUG-464 后列表不带消息,揭幕时当前会话(默认最近一条或 ?c= 指定)的消息尚未加载,ensureSessionMessages485487 的 effect)才开始拉,消息区先显示 session-messages-loading + InlineSpinner18671868)。这是用户最先看见的第二层等待
    • starter 推荐问题profile 完整时 11531187 的 effect 拉 /api/onboarding,期间显示 starter-loading1836)。
    • 每日星语1205 起拉 fetchDailyStarlanguage,卡片 aria-busystarter-home.tsx:88dailyStarlanguageBusy 630634)。
    • 校正入口摘要523 拉 entry-summary,影响校正卡文案;打开校正时 BirthTimeRectification 动态分包还有一行"正在加载出生时间评估..."230235)。
  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 / useMemonpm run lint 0 errorBUG-470 后 react-hooks 规则已能分析 Homeeffect 内同步 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。

让步顺序:功能与测试不回归 > 一次揭幕的体验 > 揭幕耗时 > 代码整洁。

开工前置

git fetch origin --prune
git worktree add -b codex/unified-loading-20260902 \
  ../.worktrees/unified-loading-20260902 origin/staging

确认 streaming-ux 已合入。读 pre_work_error_ledger.mdfrontend/AGENTS.mddocs/BUG_HISTORY.md 的 BUG-464/465/470。先读:page.tsx 启动 effect840960)、ensureSessionMessagesuse-session-management.ts)、starter/每日星语/入口摘要三个 effect、app-loading-indicator.tsxstarter-home.tsx

任务分解

任务 1P0)· 两阶段揭幕

  • 启动 effect 改为:阶段一现有 Promise.all → 计算选中会话(复用 resolveBootstrapSessionSelection)→ 阶段二 Promise.allSettled 并行拉当前会话消息、starter 问题(仅 profile 完整时)、每日星语、校正入口摘要,并 void import("@/components/birth-time-rectification") 预热 → 4 秒上限 → setHydrated(true)
  • 加载屏 detail 按阶段切换文案。
  • 原来揭幕后才触发的三个 effect 改为"若已在阶段二拿到则跳过",避免重复请求(观测:首屏网络面板同一端点不得出现两次)。

任务 2P0)· 移除揭幕后的加载态

  • 删除首屏路径上的 session-messages-loadingstarter-loading、每日星语首屏 aria-busy;按决策记录 3 落各自的静态最终态。
  • 保留:流式生成中的状态;校正分包兜底文案。

任务 3P1)· 切换预取

  • 揭幕后按侧栏顺序串行预取最近 5 条会话消息;缓存未命中时的静态占位(决策记录 4)。

任务 4P1)· 合同测试

  • 锁:阶段二包含四项请求与预热;4 秒上限常量存在;揭幕后 page.tsx + starter-home.tsxInlineSpinner/role="status" 加载态(流式区除外);stale-client-recovery 依赖的选择器仍在。

总验收

  1. tsc / lint / 测试基线 / / Static / gzip(红线 45)。
  2. 首屏时序说明写进 PROGRESS:阶段一、阶段二各自耗时(本地测量即可),并附网络面板截图或请求清单证明无重复请求。
  3. 行为:登录后从加载屏到完整界面只有一次揭幕,消息、推荐问题、每日星语、校正卡同时就位;断网或慢速(DevTools 节流 Slow 3G)下 4 秒后揭幕且各项为静态兜底态、无任何转圈。无登录态环境则合同测试覆盖并如实标注。

明确不做

  • 不改流式回答的生成中表现(streaming-ux 轮已定)。
  • 不改校正会话面内部的加载/busy 语义(rectification-agentic-chataria-busy 是生成中,不是加载)。
  • 不做骨架屏——产品要的是"不显示,直到完整"。
  • 不动报告页、会员页的加载表现(另议)。