Files
Jyotisha/docs/tasks/PROGRESS-secondary-page-entry-20260918.md
T

5.6 KiB
Raw Blame History

PROGRESS · 次级页进入时的等待态与抖动(2026-09-18)

分支:codex/secondary-page-entry-20260918 基线:origin/staging @ 1061514f(任务书写的 41902067 是写单时的 head;本 worktree 按开工指令跟当前 origin/staging) 方案:产品 2026-09-18 选定方案一。方案 B 未做。

BUG-967 调查:Next 16.3.1 的 mismatch fallback 为什么没开火

对照 next@16.3.1 源码(GitHub tag v16.3.1),不是猜的。

Next 自己会在什么时候整页跳

packages/next/src/client/components/router-reducer/fetch-server-response.ts:

  1. RSC 响应 Content-Type 不是 text/x-component(export 模式下还接受 text/plain),或 !res.ok,或没有 body → doMpaNavigation。
  2. 解出 Flight 之后:(res.headers.get(NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? flightResponse.b) !== getNavigationBuildId() → 同样 MPA。
  3. app-router.tsx 看到 pushRef.mpaNavigation 才 location.assign / replace,并 throw unresolvedThenable 卡住当前树。

另有 nav-failure-handler.ts 的 handleHardNavError:导航过程中未捕获错误则 window.location.href = window.next.__pendingUrl。整份文件包在 process.env.__NEXT_APP_NAV_FAIL_HANDLING 里,standalone 默认不打开。

既有 StaleClientRecovery 只在 window.error / unhandledrejection 文本像 chunk 失败、且 sessionStorage 还没记过一次时 reload()。router reducer 如果把 rejection 吃掉,它看不见。

为什么这次三条都不触发

  1. 预取缓存让检查根本不跑。 侧栏三项是 <Link>。标签页在部署前已经打开对话页时,Next 已经把 /chart /ephemeris /reports 的 Flight 预取进内存。点击走这段缓存,不再 fetchServerResponse。构建 id 比对、非 2xx、非 Flight Content-Type,全部不执行。
  2. 自托管没有 Vercel 那种 ?dpl= 分流。 next.config.ts 写了 deploymentId: process.env.NEXT_DEPLOYMENT_ID,静态资源带 ?dpl=。在 Vercel 上这个 query 把请求打到对应部署,旧部署不在了就是 404,于是走条件 1。本仓 Caddy(deploy/Caddyfile.staging / Caddyfile.production.selfhosted)对 /_next/static 没有任何特殊 404 改写,只 reverse_proxy web:3000。主机上只有当前镜像。旧 tab 就算真的去 fetch /chart,新服务器仍对这条路径返回 200 + 新的 Flight。条件 1 的 !res.ok 不成立。BUG-204 当年靠 deploymentId 修好的是 Vercel 形状,不是这台 VPS。
  3. 错误回退开关是关的。 旧 chunk 404 变成 ChunkLoadError 时,默认 standalone 不会把它升级成 MPA。点击看起来像没反应。app-router.tsx 若已经丢出 unresolvedThenable 或 startTransition 一直 pending,同一页上的账户按钮(本就不是 Link)也会一起没反应——这与「对话还能发」不矛盾:对话代码在内存里,路由切换被卡住。

Caddy 不是元凶:它没有把 404 变成 200 HTML。问题是 200 的新 Flight 加上 根本不再 fetch 的预取缓存。

自愈怎么做(才没有废掉正常客户端导航)

  • 客户端 commit:构建期 NEXT_PUBLIC_GIT_COMMIT(Dockerfile 里与 NEXT_DEPLOYMENT_ID 同值写入,不改 workflow)。
  • 服务端 commit:已有的 GET /api/health → .deployment.gitCommit(运行时 GITHUB_SHA)。
  • StaleBuildGuard 在挂载和 visibilitychange → visible 时拉一次 health。不轮询、不定时 reload。
  • 导航前读这份快照。不一致:window.location.assign。一致:原样走 Link / router.push。health 失败或任一侧是空/unknown:不强制跳。

BUG-966

三页改为 SecondaryPageShell。.secondary-page 是 chat-panel 第二行(minmax(0,1fr)),等待与正文同一格子;等待句居中。SecondaryHeader reserveNote 让出生行晚到时标题行不塌。模块级缓存 + 侧栏 pointerenter/pointerdown 预取。报告列表首屏去掉 InlineSpinner「正在读取报告…」,改静态句「报告列表还没拿到。」刷新按钮上的 spinner 仍是用户点的动作,DESIGN 允许。

未做方案 B(延迟揭幕)。

测试

命令 结果
npx tsc --noEmit 0 错
npm run lint 0 error(既有 120 warning,本轮文件未新增)
定向:secondary-page-entry / chart-page-view / ephemeris-page / sidebar-contract / stale-client-recovery / personal-report-entry / chat-navigation-a11y-contract / class-name-definition-contract 114 / 114
personal-report-view.test.ts 通过(详情页仍用 SecondaryHeader)
next build compile + tsc 过;收集 /api/daily-starlanguage 时 Windows EPERM 无法 symlink skill runtime。环境缺口,与本轮无关。/ Static 与 gzip 未在本机量到。

无 Chrome:高度真机清单在 docs/testing/secondary-page-entry-20260918.md。无登录态:缓存第二次进入与跨部署自愈的浏览器步骤同样写在那里。

改过的既有断言:

测试 原值 新值 原因
sidebar-contract 只读行 / 页脚 <Link className="session-main">、<Link className="profile-trigger" href="/"> AppLink,class 与 href 不变 BUG-967 跨部署自愈;只读行仍是链接、页脚仍去 /

偏离

  • 任务书写 app/(secondary)/layout.tsx,当前树是 app/(app)/layout.tsx(会话列表单源之后)。外壳挂在这里,没有把路由组改回去。
  • 报告详情 /reports/[reportId] 仍用 SecondaryHeader 各阶段一份,不进三页进入抖动的范围;loading.tsx 里的 spinner 本轮不动。