Files
Jyotisha/docs/tasks/TASK-secondary-page-entry-20260918.md
T
Jesse_ChenandClaude Opus 5 1061514fc0 docs: BUG-965 触发条件确认(跨部署标签页),产品选定方案一
产品复现:刷新后恢复正常。BUG-965 根因定为标签页跨过 17:12 部署后
客户端导航拉不到已被替换的构建产物。修复方向进 BUG-967:用编译期
NEXT_PUBLIC_GIT_COMMIT 与 /api/health 比对,旧版本在下次导航整页重载。
次级页抖动按方案一做(统一外壳 + 缓存 + 预取),不加 spinner。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
2026-09-18 10:00:42 +00:00

74 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 次级页进入时的等待态与抖动(2026-09-18 第七轮)
> 基线:`origin/staging` @ `41902067`(代码部分 = 已部署的 `877128ce`)。
> 触发:产品真机反馈「星盘 / 星历 / 我的报告进去时需要一个统一的加载动画,现在进去的时候有一瞬间抖动」。
> BUG 编号起点:当前最大号 **BUG-965**(investigating),本单占 **BUG-966 ~ BUG-967**。
## 0. 抖动是怎么来的(已定位)
三页是同一套形状:先渲染标题 + 一行等待文案,数据到达后**整块换成完整内容**。
| 位置 | 事实 |
| --- | --- |
| `src/components/chart-page/chart-page-view.tsx:65-77` | `view == null` 时渲染 `<SecondaryHeader>` + `<section class="chart-page-message">` 一行文案;数据到达后换成 `chart-page-tabs` + 盘图正文 |
| `src/components/ephemeris/ephemeris-page.tsx:70` | 同形状(`ephemeris-message`) |
| `src/hooks/use-chart-page.ts:22-45` | 进页 `useEffect` 才发 `fetchChartView`,**没有任何缓存**:每次进入都重新请求、都重放一次中间态 |
| `SecondaryHeader` 的 `note={birthline}` | 出生行是后到的,标题区的文字二次填充 |
所以「一瞬间抖动」= 矮的等待块 → 高的正文块,一次布局推挤;而且因为没有缓存,**每次进入都抖一次**。
## 1. 与既有红线的冲突(必须先由产品裁决)
**决策记录(产品负责人 2026-09-18 授权):选 §2 的方案一——把中间态消灭掉,不加 spinner,现行红线不动。** §3 的方案 B 不做。
产品最初要的是「统一的加载动画」。`AGENTS.md §6` 与 `frontend/DESIGN.md` 的现行红线是:
> 揭幕后不得出现 spinner / 骨架 / 「正在加载」(流式生成中除外);一次等待一次揭幕。
直接加 spinner 会推翻这条。本单**默认不加 spinner**,按下面 §2 做「稳定容器 + 缓存 + 预取」,效果是把中间态消灭掉而不是把它做得好看。若产品坚持要可见动画,选 §3 的 B 方案(延迟揭幕),并在本节写明授权。
## 2. BUG-966(P1)默认方案:把中间态消灭掉
1. **统一外壳**:三页共用一个 `SecondaryPageShell`(标题 + 内容区)。标题区高度固定,`note` 未到时保留占位不塌;内容区 `min-height` 撑到视口剩余高度,等待文案居中在这块区域里,**从等待态换到内容态不推挤布局**。
2. **缓存**:星盘 / 星历 / 报告的首屏数据进模块级缓存(与 `use-session-list` 的缓存同一口径),同一会话内再次进入直接用上次结果并在后台静默刷新。第二次起没有中间态。
3. **预取**:侧栏三个入口在 `pointerenter` / `pointerdown` 时预热数据(`<Link>` 只预取路由代码,不预取数据)。第一次进入也大概率在数据到位后才看到内容。
4. 验收标准:
- 用 harness 或真机量「进入 → 内容出现」期间 `.secondary-page` 容器的高度变化:**首屏容器高度不得变化**(等待态与内容态同高);
- 同一会话内第二次进入三页,不得再出现等待文案;
- `next build` 后 `/` 仍 `○ Static`,首屏 gzip ±2%。
## 3. BUG-967(P1)标签页跨过部署后,客户端导航静默失效
产品复现确认(BUG-965):2026-09-18 17:12 的部署之前打开的标签页,点侧栏「星盘 / 星历 / 我的报告」与账户入口**全部没反应**;**刷新后恢复正常**。已经加载好的对话页照常可用,因为它的代码都在内存里。
**要求**:
1. 查证 Next 16.3.1 自带的「构建不匹配就整页导航」回退这次为什么没生效(404 形状?Caddy?RSC 负载判定?)。结论写进进度记录,不得跳过这一步直接加补丁。
2. 加自愈:客户端拿编译期注入的 `NEXT_PUBLIC_GIT_COMMIT`(`src/app/api/health/route.ts:140` 已在用同一个值)与 `/api/health` 的 `deployment.gitCommit` 比对——发现自己是旧版本,就在**下一次导航时整页重载**,而不是静默失败。比对频率取"页面重新可见时 + 导航前",不要做轮询。
3. 不得用「定时强制刷新」或「每次导航都整页重载」来糊过去:那会把正常情况下的客户端导航也一起废掉。
4. 验收标准:合同测试——版本不一致时导航走 `window.location.assign`,一致时走客户端路由;真机验收清单写进 `docs/testing/`(需要一次真实部署才能复现,属环境依赖项)。
## 4. 方案 B(本轮不做,仅存档):延迟揭幕
不显示半成品:点击后**留在当前页**,数据到位再一次性切换到次级页(等待期间只有侧栏入口的按下态)。这与首页「一次等待一次揭幕」同构,不引入 spinner。实现上是把首屏数据取到路由切换之前(预取 + `useTransition`,或把首屏数据改成服务端取、用 Suspense 让路由切换等数据)。
代价:慢网络下点击后有一段"没反应",需要按下态兜住。
## 5. 硬红线
1. 不得在揭幕后引入 spinner / 骨架 / 「正在加载」文案。产品 2026-09-18 已选定方案一,红线不动。
2. 三页必须共用同一个外壳与同一套等待口径,不得各写各的(这次抖动就是三页各写各的结果)。
3. `tsc --noEmit` 0 错、`npm run lint` 0 error、`npm test` 失败数不超过基线 31 条且清单一致;测试总数不低于 3501。
4. 改 UI 的同一提交更新 `frontend/DESIGN.md`;新文案对照 `frontend/docs/VOICE.md`。
## 6. 开工前置
```bash
git fetch origin --prune
git worktree add -b codex/secondary-page-entry-20260918 \
.worktrees/secondary-page-entry-20260918 origin/staging
cd .worktrees/secondary-page-entry-20260918/frontend
npm test 2>&1 | grep -E "^# (tests|pass|fail)" # 开工基线:tests 3501 / pass 3455 / fail 31
```
收工:`docs/tasks/PROGRESS-secondary-page-entry-20260918.md`(附进入三页的改前改后录屏或逐帧截图)+ `docs/BUG_HISTORY.md`(BUG-965 转 resolved、BUG-966/967 新增)+ `frontend/DESIGN.md`,与代码同一批推 `staging`。