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