Files
Jyotisha/docs/tasks/PROGRESS-cend-shell-20260916.md
T
Jesse_ChenandClaude Opus 5 50ce02c837
Independent Staging Quality Gate / validate (push) Failing after 9m57s
Independent Staging Quality Gate / publish (push) Skipped
feat(ui): 星盘/星历/报告中心并入 app 外壳,侧栏不再消失
三个页面此前各是脱离外壳的独立全屏路由,各写了一套一样的
*-shell / *-topbar(只有一个「返回对话」链接)/ *-hero 骨架,
侧栏在打开它们的瞬间整个消失。

新增 AppNavRail(只读:两个 GET,零写操作)与 SecondaryShell。
会话行走 sessionHref → /?c=<uuid>,跳转复用现有侧栏的
persistLoginSessionReturn + location.assign,行为完全一致。

刻意不带重命名/收藏/归档/删除与账户菜单:那套连着 Home() 的乐观更新与
回滚层,为四个路由把它整体上提远超需要,且会撞 useState 增长门禁。

四个路由渲染标记完全不变:/ ○、/chart ○、/ephemeris ○、/reports ƒ。
CSS gzip 39,825→39,727(−0.25%);per-route JS 体积构建不输出,
已作为口径缺口记录。

两处自身问题被测试抓到并修复:usePathname() 在 app-router 上下文外
返回 null(类型说是 string)、fetch 在 jsdom 里可能不存在。
另差点随 hero 一起丢掉 BUG-717 的 eyebrow 文案(成本与速度承诺,
会印在失败页上),已放回 tab 行下方。

/reports/[reportId] 留给 R6 与目录一起做;根边界页保留「返回对话」,
它们不得 import globals.css,挂不了外壳。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
2026-09-16 06:03:30 +00:00

113 lines
8.5 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.
# PROGRESS · 次级页外壳 R4
任务书:`docs/tasks/TASK-cend-surfaces-claude-alignment-20260916.md` 的 R4
执行方式:**直接执行模式**
分支:`codex/cend-shell-20260916`
基线:`origin/staging` = `b3ea8c33`(第一份任务书 R1R3 合入后)
BUG 段:不占用(产品改造)
R5(星盘+星历)与 R6(报告)依赖本轮。
---
## T4.1 · 只读导航外壳
新增三个文件:
| 文件 | 职责 |
|---|---|
| `src/hooks/use-nav-rail.ts` | 两个只读 GET`/api/sessions?limit=40``/api/account`),`Promise.allSettled`**不含任何写操作** |
| `src/components/app-nav-rail.tsx` | 品牌 + 新建对话 + 星盘/星历/我的报告 + 一条「最近」平铺列表 + 身份页脚 |
| `src/components/secondary-shell.tsx` | `SidebarProvider``AppNavRail``SidebarInset.chat-panel.secondary-panel` + 46px 顶栏 |
会话行走 `sessionHref("", id)``/?c=<uuid>`,跳转用 `persistLoginSessionReturn()` + `window.location.assign()`**与现有侧栏 `leaveChat()` 完全一致的行为**,没有另造一套。
侧栏原语(`SidebarProvider` / `Sidebar` / `SidebarTrigger` / `SidebarRail`)是通用的,抽屉与折叠态直接复用,没有重写。
### 验收对照
| 任务书验收标准 | 结果 |
|---|---|
| `AppNavRail` 不 import page.tsx 的 hook,不持有会话写状态 | ✅ 依赖只有 ui/sidebar、`chat-session-url``session-groups`、自己的 hook |
| 四个路由都挂上外壳,桌面常驻、移动端抽屉 | ⚠️ **三个**`/chart``/ephemeris``/reports``/reports/[reportId]`(阅读页)留给 R6,见下 |
| 点「最近」行跳 `/?c=<uuid>` 并选中 | ✅ 复用 `sessionHref``parseSessionUrlQuery` 的既有测试未动 |
| 列表未到达时静态空态,无骨架无 spinner | ✅ 三档文案:未登录 / 已 settled 无数据 / 读取中,全是静态文字 |
| 未登录时降级为品牌 + 登录入口,不报错不空白 | ✅ 401 → `signedOut`,列表位显示「登录后可以看到你的对话」,页脚变「去登录」 |
| 四个路由渲染标记逐个入记录 | ✅ 见下 |
**`/reports/[reportId]` 本轮没挂**:阅读页是纸面文档表面,外壳会改变它的滚动所有权与打印行为,而 R6 正要重做它(加目录)。同一轮里既挂外壳又改阅读结构,出问题难以定位。留给 R6,已在任务书依赖里写明。
### 两处我自己的健壮性问题,被测试抓到
1. **`usePathname()` 会返回 `null`。** 类型标注是 `string`,但在 app-router 上下文之外(包括直接挂载组件的单测里)返回 null,我的 `pathname.startsWith(...)` 直接抛 `Cannot read properties of null`,打红了 `chart-page-view``ephemeris-page` 两个渲染测试共 5 条。已加 `?? ""`。既有的 `app-sidebar.tsx` 有同样的写法但没被覆盖到,本轮不动它(红线 11)。
2. **`fetch` 可能不存在**jsdom / 纯 node 环境)。原写法会在 `load()` 里同步抛出,变成未捕获的 promise 拒绝。已加 `typeof fetch !== "function"` 短路 + `.catch()` 兜底。
## T4.2 · 删掉三套重复骨架
| 页面 | 删除 |
|---|---|
| `chart-page-view.tsx` | `.chart-page-shell` / `.chart-page-topbar` / `.chart-page-back`(返回对话)/ `.chart-page-hero` + `<h1>星盘</h1>` |
| `ephemeris-page.tsx` | `.ephemeris-shell` / `.ephemeris-topbar` / `.ephemeris-back` / hero 的 `<h1>`;底部「去问」提到顶栏 |
| `personal-report-center.tsx` | `.report-center-shell` / `.report-center-topbar` / `.report-center-back` / hero 的 `<h1>`;「生成完整报告」提到顶栏 |
CSS 侧删掉 22 条规则、裁剪 6 条选择器列表。**这次用了 R2 教训后的切分规则**(只在注释之后找选择器),改完复核:注释配平 73/73、含花括号的注释 0 条。
### 一处内容差点被我弄丢
`CHART_VIEW_COPY.eyebrow`(「主盘直接算 · 分盘按需 · 不消耗点数」)原本在 hero 里,随 hero 一起被删。这是 **BUG-717 专门定的文案**——它是成本与速度承诺,而且会印在失败页上。测试打红才发现,已放回 tab 行下方作为一行静默说明。
### 错误态与空态
三个页面的降级分支(未登录、读取失败、无数据、`natalMissing``panchangaUnavailable` 等)全部保留,只是从 `*-shell *-message` 的根节点改成外壳内的一个 `section``h1` 降为 `h2`(页名已在顶栏,一页不该有两个 h1)。
### 「返回对话」残留说明
`git grep 返回对话 src/` 还有 4 处命中,都在**根边界页**(`error.tsx``not-found.tsx``global-error.tsx``reports/[reportId]/not-found.tsx`)。按 `DESIGN.md`,这些页在根 layout 段里、**不得 import `globals.css`**,因此不能挂外壳,那个链接是它们唯一的回家路径。本轮不动。
---
## 收尾验收
| 红线 | 结果 |
|---|---|
| 1 · `tsc` 0 错 / `lint` 0 error | ✅ 118 warning,与基线同 |
| 2 · 测试总数 | ✅ tests 3350pass 3303fail 31,与基线 `diff` **逐条一致** |
| 3 · **四个路由渲染标记不变** | ✅ `/` `○` · `/chart` `○` · `/ephemeris` `○` · `/reports` `ƒ`,改前改后完全相同 |
| 4 · 次级页首屏体积 | ⚠️ **口径缺口**CSS 侧 39,825 → **39,727 字节(−0.25%**,三套骨架并成一套是净减。但当前 Turbopack 的路由表**不输出 per-route JS 体积**,导航栏与 sidebar 原语给三个次级页增加的 JS 我量不到,不能给数字。任务书预期的「次级页体积上升」无法证实也无法证伪 |
| 6 · 一律 `ChatComposer` / 一套滚动跟随 | ✅ 本轮未涉及 |
| 7 · 无 spinner / 骨架 | ✅ 列表在途是静态文案 |
| 8 · 不回退 BUG-698 的 `@supports (height: 1dvh)` | ✅ 未触碰 |
| 9 · 不回退 BUG-616/617 的报告盘面 grid | ✅ 未触碰 `report-chart-grid-rehype.ts` |
| 10 · 同提交更新 `DESIGN.md` | ✅ 新增「Secondary page shell」一节;「Personal report centre」的 Structure 与 Width 两条重写 |
| 11 · 不升依赖、不留新 warning | ✅ |
| 12 · 只读侧栏不发起写操作 | ✅ `use-nav-rail.ts` 只有两个 GET |
Python 快速门(BUG-736):本轮**未改** `page.tsx`;新增的 `use-nav-rail.ts` 不是 Home 级 hook`Home()` 不引用它),按规则不需要登记进 `OPTIONAL_HOME_HOOKS`。仍跑了 `pytest tests/test_supabase_user_data_contract.py`8 条全绿。
### 按红线 3 更新的既有断言(原值 / 新值 / 原因)
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
| `chart-page-view` · 移动端 | 三条以 `@media (max-width: 767px) { .chart-page-hero h1` 起锚;另断 `.chart-page-back` 44px | 直接断该断点块里的三个开关;删 `.chart-page-back` 一条 | hero 与返回按钮已删;**被断言的三个开关一条没动** |
| `chart-page-view` · 空态 | `assert.match(markup, /返回对话/)` | `assert.match(markup, /新建对话/)` + `doesNotMatch(/返回对话/)` | 返回路径从一次性链接变成常驻侧栏。这条**更强**:要求整条导航在场 |
| `personal-report-entry` | `.report-center-shell` 的 height/overflow | `.secondary-panel > :not(.chat-header)` 的 overflow + `.chat-panel` 的 grid 行 | 滚动所有权归外壳;「在高度锁内部滚动、不自己撑高」没变 |
| `personal-report-theme-contract` ×2 | `.report-center-back` 44px;标题三处含 `.report-center-hero h1` | 前者删除;后者收为封面 + 章节两处 | 返回按钮与页标题都移进了外壳顶栏,由 chat-header 规则统管 |
| `viewport-breakpoint-contract` ×2 | `.report-center-shell` / `.ephemeris-shell` 在 767 块内 | `.report-center-body` / `.ephemeris-body` | 页面根换了;「用全局 767 断点、不自立断点」由同测试的两条 `doesNotMatch` 继续守 |
一条实际收紧(空态从「有返回链接」变成「整条导航在场 + 不得再有返回链接」),其余是形态同步。
---
## 环境缺口(不得记为通过)
1. **次级页侧栏在移动端抽屉的手势与焦点行为**(本轮新挂,优先级最高)。
2. **未登录时三个次级页的降级表现**——无登录态,只能证明代码分支存在。
3. **从次级页点「最近」行回到对话并正确选中**——需要真实会话数据。
4. **per-route JS 体积**:构建不输出,需要另外的测量手段(见红线 4)。
---
## 下一轮
R5(星盘 + 星历)与 R6(报告中心 + 报告阅读,含把 `/reports/[reportId]` 挂进外壳)基线取本轮合入后的 `origin/staging`。R7、R8 与本轮文件不重叠,可并行。