星盘/星历/报告三个页面各是脱离 app 外壳的独立全屏页,侧栏整个消失,
且三家各写了一套一样的 shell/topbar/hero 骨架——与上一单空状态同根因。
R4 抽只读导航外壳(前置)→ R5 星盘+星历、R6 报告
R7 生时校正、R8 设置+充值 可并行
设置单已于 111b4a84 合入,R8 阻塞解除。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
22 KiB
TASK · C 端次级页面向 claude.ai 产品界面对齐(2026-09-16)
基线
- 基线 commit:
origin/staging=111b4a8455b5e3d6ae7b4ea759ba35492fc806cc(2026-09-16,BUG-698 设置弹窗 vh 基线之后) - 原型图(十套画面,本单覆盖后七套): https://claude.ai/code/artifact/da275da6-2954-4f50-99aa-32bb8694d38b
- 本单是
TASK-cend-ui-claude-alignment-20260916.md的续篇。 那一份改对话主链路(R1 字体与强调色、R2 空状态、R3 侧栏),本份改五个次级面。 - BUG 编号起点:BUG-739(当前最大 BUG-738;本单四轮均为产品改造,默认不占 BUG 号,只有实现中发现真实缺陷才从 739 起顺延)
轮次依赖
brief#1 R1(字体 + coral token) ← 软前置,先落地可避免两次视觉返工
│
├─ R4 次级页外壳 ──┬─ R5 星盘 + 星历
│ └─ R6 报告中心 + 报告阅读
├─ R7 生时校正 (文件不重叠,可与 R4/R5/R6 并行)
└─ R8 设置 + 充值 (文件不重叠,可并行;基线已解锁,见下)
R8 原本被 codex/settings-dialog-size-and-nav-20260916 挡着,该单已于 111b4a84 合入,阻塞解除。
globals.css 四轮都要动,但落在互不重叠的区段:R4/R5 在 .chart-page-* / .ephemeris-*;R6 在 .report-center-* / .personal-report-*;R7 在 2614–2800 的 .rectification-workspace*;R8 在 1757–1960 的 .settings-* / .membership-*。同一轮内不得越界改别轮的区段,越界即产生冲突。
事故实证
行号按 origin/staging 的 111b4a84。符号名在括号里,行号漂移时按符号定位。
E8 · 三个次级页各自是脱离 app 外壳的独立全屏页
| 路由 | 入口文件 | 外壳写法 |
|---|---|---|
/chart |
frontend/src/app/chart/page.tsx |
<ChartPageRoute />,仅 import "../site-styles" |
/ephemeris |
frontend/src/app/ephemeris/page.tsx |
<EphemerisPage />,同上 |
/reports |
frontend/src/app/reports/page.tsx |
<PersonalReportCenter />,另加 export const dynamic = "force-dynamic" |
三个页面的根节点分别是 chart-page-view.tsx:70(<main className="chart-page-shell">)、ephemeris-page.tsx:116(.ephemeris-shell)、personal-report-center.tsx:179(.report-center-shell),彼此各写了一套一模一样的骨架:
*-shell
└ *-topbar → 只有一个「返回对话」链接
└ *-hero → h1 大标题 + 一行副文案
└ *-section → h2 + 内容
侧栏(AppSidebar)只在 page.tsx:1560 的 Home() 里渲染,挂在 SidebarProvider(:1557)下。所以用户从对话点进星盘页,侧栏整个消失,唯一的回路是那个「返回对话」链接。
这与 brief#1 的 E5(空状态是营销落地页)是同一个根因的三次复制:*-hero 加 *-section 就是 claude.com 营销页的 band 结构。
E9 · 生时校正的盘面板有五个折叠块,最该看的数字埋在里面
frontend/src/components/rectification-board.tsx 内 <details className="technique-audit"> 出现在 :117、:132、:160、:182、:198 共五处,分别装:盘面技法审计、换升时刻、次级候选分钟、口径披露、双轨一致性。
当前可信区间与代表分钟是整个流程里用户唯一持续关心的数字,现在它只以 .rectification-board__clock(:81)的形式出现在右侧面板的标题行,在窄屏(.rectification-workspace.is-compact,globals.css:2630)下该面板整体变成 overlay,默认不可见。
E10 · 报告阅读页是一整篇 Markdown,没有任何导航
frontend/src/components/personal-report/personal-report-page.tsx:421 起:
<main className="personal-report-reader">
<ReportActions … />
<div className="personal-report-reader-body">
<PersonalReportMarkdownView markdown={state.markdown} />
</div>
</main>
一份完整个人报告是 9 节、20 余张盘。整篇直下,没有目录、没有章节定位,只能靠滚动。
已修、不要重做:BUG-616(22 张盘叠在同一位置)与 BUG-617(滚动整篇重建卡顿)在 docs/BUG_HISTORY.md 里都是 resolved,修法是 frontend/src/lib/report-chart-grid-rehype.ts 把 D\d+ / Moon Chart 标题收进 grid 容器。本轮不得回退该实现,只在其上加导航。
E11 · 报告中心是卡片网格,状态信息靠底色区分
personal-report-center.tsx:218 起 .report-center-list → .report-center-card(globals.css:3608)。状态 .report-center-status 三个变体(:3632-3635)分别用 --color-success-muted / --color-action-soft / --color-danger-muted 三块底色区分。卡片网格在报告数量超过五六份后扫读成本高于行式列表。
E12 · 套餐卡三个修饰符叠加三层描边与底色
frontend/src/components/billing-panel.tsx:135:
className={`membership-plan-card${recommended ? " membership-plan-card--recommended" : ""}${isCurrent ? " membership-plan-card--current" : ""}${highlighted ? " membership-plan-card--highlighted" : ""}`}
三个修饰符可同时命中(推荐 + 当前 + 从 URL 高亮),叠出的边框与底色组合没有设计定义。
E13 · 设置弹窗内容区铺满,与导航宽度失衡
globals.css:1759(.account-settings-shell):grid-template-columns: 176px minmax(0, 1fr),弹窗总宽 880px,内容区因此有约 690px。其中「个人资料」是一列表单,铺到 690px 后每行左右大量留白,字段与标签的关联被拉断。
(.settings-dialog-nav-item[aria-current="page"] 的左侧强调条已在 BUG-698 里改成填充块,本单不重复改。)
根因
-
E8/E11 的共同根因:与 brief#1 的根因同源——
frontend/CLAUDE_DESIGN.md是 claude.com 营销官网的设计系统,它自己在## Known Gaps写明 claude.ai 产品界面不在范围内。营销站的页面是「独立着陆页 + hero + 特性卡网格」,产品界面是「常驻外壳 + 主区域换内容」。三个次级页照抄了前者。 -
E8 的直接根因:
AppSidebar的数据与回调(sessions/charts/account/sessionControls共约 15 个)全部生长在Home()的 hook 里,没有一个可独立复用的导航外壳。于是新页面只能选择「不要侧栏」。 -
E9/E10 的根因:把"可核查的依据"与"用户要一直看着的结论"放进了同一种容器(折叠块 / 长正文流)。依据可以折叠,结论不能。
决策记录
产品负责人 2026-09-16 在会话中审阅原型图后确认「现在已经很完美了,请你开始吧」,即授权按原型图实施。逐条:
- D8|三个次级页收进同一个外壳,侧栏常驻。 删掉各页的「返回对话」链接与 hero 大标题;页名进顶栏。
- D9|次级页的侧栏是只读导航,不带会话操作。 重命名 / 删除 / 收藏 / 归档 / 分享只在
/上提供。理由见 T4.1 的工作量论证;这是刻意的功能差,不是遗漏。 - D10|生时校正的可信区间提成常驻条(含收窄进度),盘面板五个折叠块收敛成「候选分钟」与「参数」两段。窄屏下常驻条必须仍然可见。
- D11|报告阅读页加常驻目录。 纸面调色板(
--report-paper/--report-rule/--report-accent)保持不动,延续 brief#1 决策 D3:报告是文档表面,不跟应用强调色走。 - D12|报告中心卡片网格改行式列表,状态用带色点的 chip。
- D13|套餐卡的三个修饰符收敛成两种状态:「当前套餐」与「推荐」。URL 高亮(
highlighted)不再改变卡片外观,改为滚动定位。 - D14|会员充值不恢复独立路由。 线上已无
/membership页面(frontend/src/app/membership/不存在,membershipHref只用于设置弹窗内的 pane 定位),维持现状。
硬红线
./node_modules/.bin/tsc --noEmit0 错;npm run lint0 error。- 测试总数不得低于开工时在
origin/staging的实测值。改任何既有断言必须在进度记录里写「原值 / 新值 / 原因」三栏。 next build后/保持○ Static。R4 给次级页加外壳不得把/变成动态渲染;/chart与/ephemeris当前的渲染标记也必须保持不变(/reports本来就是force-dynamic,维持)。四个路由的渲染标记改前改后逐个列进进度记录。- 首屏 gzip:
/的变化在 ±2% 内。次级页各自的首屏体积改前改后都要列出;R4 因为新增了侧栏,次级页体积预期上升,上升幅度要给出数字并说明。 frontend/src/app/page.tsx按AGENTS.md§6 现行门禁:Home()的useState/useRef数不得增长(frontend/tests/home-shell-growth-contract.test.ts执行),行数是粗护栏(开工实测基线 + 150)。- 一律复用
ChatComposer;一律复用useConversationScrollAnchor+JumpToLatestButton。不得手写第二套。 - 揭幕后不得出现 spinner / 骨架 /「正在加载」(流式生成中除外)。次级页外壳在会话列表到达前显示静态空态,不是骨架屏。
- 不得回退 BUG-698 的
@supports (height: 1dvh)写法(globals.css:5110起)。经典的height: 100vh; height: 100dvh;重复声明回退会被 Lightning CSS 折叠掉,frontend/tests/viewport-unit-fallback-contract.test.ts会拦。新写的全高容器照同一模式办。 - 不得回退 BUG-616/617 的报告盘面 grid 实现(
report-chart-grid-rehype.ts)。 - 改 UI 的同一提交内更新
frontend/DESIGN.md;新文案先对照frontend/docs/VOICE.md。 - 不改数据库结构,不得顺带动迁移。不得顺手升级依赖、不得顺手修不在本单里的 warning。
- 次级页的只读侧栏不得发起任何写操作接口。会话重命名 / 删除 / 归档的 RPC 在这些页面上必须不可达(D9)。
任务分解
R4 · 次级页外壳(前置轮)
分支 codex/cend-shell-20260916。R5 与 R6 依赖本轮。
T4.1 · 抽出只读导航外壳
新建 frontend/src/components/app-nav-rail.tsx(名字可改,不要叫 AppShell 以免与 Home() 的 SidebarProvider 混淆)。它渲染:品牌字、三个导航项(新建对话 → /、我的报告 → /reports、星盘资料 → /?settings=chart-library 或等价入口)、一条「最近」列表、账户页脚。
数据只需两个现成的只读接口:
| 需要 | 来源 | 现状 |
|---|---|---|
| 会话列表 | GET /api/sessions(frontend/src/app/api/sessions/route.ts) |
已存在 |
| 账户与余额 | GET /api/account(frontend/src/app/api/account/route.ts) |
已存在 |
会话行用 sessionHref(search, sessionId)(frontend/src/lib/chat-session-url.ts:32)生成 /?c=<uuid> 链接,直接跳回 / 并选中该会话——不要另造一套跳转。
为什么是只读(D9 的工作量论证):AppSidebar 当前需要 sessionControls 这一整组回调(重命名、删除、收藏、归档、分享、菜单开合),它们连着 Home() 里的乐观更新、确认弹窗与错误回滚。把这套搬到四个路由共享,等于把 Home() 的会话管理层整体上提,远超本单范围且会撞红线 5。只读版本用一次 GET 就够,且次级页上并不需要这些操作。
验收标准
AppNavRail不 importpage.tsx的任何 hook,不持有会话写状态。- 四个路由(
/chart、/ephemeris、/reports、/reports/[reportId])都挂上外壳,侧栏在桌面常驻、在移动端是抽屉。 - 点任一「最近」行跳到
/?c=<uuid>并正确选中该会话(parseSessionUrlQuery已有测试,补一条链接生成的断言即可)。 - 会话列表未到达时显示静态空态文案,无骨架屏、无 spinner(红线 7)。
- 未登录时外壳降级为只有品牌字与登录入口,不报错、不空白。
- 四个路由的渲染标记与
/的○ Static逐个列进进度记录(红线 3)。
T4.2 · 删掉三套重复骨架
chart-page-view.tsx:删.chart-page-topbar与其中的「返回对话」<Link>;.chart-page-hero的<h1>星盘</h1>删除,出生行改为顶栏里的一枚 chip。ephemeris-page.tsx:同样删.ephemeris-topbar/.ephemeris-back,<h1>删除;底部.ephemeris-footer里的「去问」按钮提到顶栏右侧。personal-report-center.tsx:删.report-center-topbar/.report-center-back,<h1>个人报告</h1>删除;生成按钮提到顶栏右侧。globals.css里对应的.chart-page-topbar/.chart-page-back/.ephemeris-topbar/.ephemeris-back/.report-center-topbar/.report-center-back/ 三个*-hero规则一并清理。
验收标准
git grep -n "返回对话" frontend/src无命中。- 三个页面的错误态与空态(未登录、读取失败、无数据)仍然各自可达且文案不变——这些分支现在挂在
*-shell *-message上(例如ephemeris-page.tsx:61、personal-report-center.tsx:169),改外壳时不要连它们一起删。 - 页名在顶栏可见且窄屏下省略号截断。
R5 · 星盘 + 星历
分支 codex/cend-chart-eph-20260916,基线是 R4 合入后的 staging。
T5.1 · 星盘页
- 五个体系 tab(
CHART_VIEW_TABS)改成顶栏下方一条下划线式 tab 行。 - 分盘 chips(
.chart-page-varga-chips)保留,视觉降一级。 - 参数从
.chart-page-center-card与.chart-page-params-below收进一张统一的键值表;view.vedic.boundary的边界句放在表下方,文案不得改动(它是准确性边界句)。 - 行星表加横向滚动容器。
验收标准
- 五个 tab 的按需 layer 拉取(
onNeedLayer("chara"/"western"/"qizheng"/"varga"))行为不变,等待文案仍用CHART_VIEW_COPY.waitingLayer,不得换成 spinner。 - 行星表在 375px 宽下横向滚动,页面本身不横向滚动。
chart-view-contract相关测试全绿。
T5.2 · 星历页
- 日期导航从三个并排按钮(前一天 / 今天 / 后一天)改成居中的
‹ 日期 ›形式,「今天」在非今日时以文字按钮出现在日期下方。 - 三个 section 统一成同一套卡片;
.ephemeris-panchanga的dl网格保留。 - 「就今天问一句」提到顶栏(T4.2 已做),底部
.ephemeris-footer删除。
验收标准
isToday的禁用逻辑仍然正确:在今日时不出现「今天」按钮,而不是出现一个禁用按钮。natalMissing/panchangaUnavailable/transitsUnavailable/eventsUnavailable四个降级分支文案与可达性不变。
R6 · 报告中心 + 报告阅读
分支 codex/cend-report-20260916,基线是 R4 合入后的 staging。
T6.1 · 报告中心改行式列表(D12)
.report-center-list 从卡片网格改成单列行式列表,每行:状态 chip(带色点)+ 标题 + 元信息(日期 / 节数 / 盘数)+ 右侧操作。
验收标准
- 四种状态(
ready/generating/failed以及生成中的分节进度)都有对应行样式;role="status"在generating行上保留。 - 失败行仍显示
failureSummary ?? failureCode,导出错误report-center-export-error的role="alert"保留。 - 空态与读取失败态(
personal-report-center.tsx:204-216)不变。
T6.2 · 报告阅读页加目录(D11)
在 .personal-report-reader 右侧加一条常驻目录,从 Markdown 的 ## 标题生成,滚动时高亮当前节。窄屏(< 860px)隐藏目录。
验收标准
- 目录项点击滚动到对应节;当前节高亮。
- 打印样式不受影响:
@page规则(personal-report-page.tsx:422)与@media print下目录必须隐藏。 - 纸面 token 一个不改(D11)。
- 不触碰
report-chart-grid-rehype.ts(红线 9);改后仍要实测一份 20 张盘以上的报告不重叠、滚动不卡——这一条无真机就写成环境缺口,不得写「通过」。
R7 · 生时校正(可与 R4/R5/R6 并行)
分支 codex/cend-rectification-20260916。只动 rectification-* 文件与 globals.css 的 2614–2800 区段。
T7.1 · 可信区间提成常驻条(D10)
在对话区顶部加一条常驻条:当前区间、宽度、收窄进度、已答题数。数字只能来自服务端投影,不得前端口算(frontend/docs/VOICE.md 第 2 条)。
验收标准
- 窄屏(
.is-compact)下常驻条仍然可见——这是本轮的核心收益,盘面板 overlay 关闭时也必须看得到区间。 - 边界语义不弱化:「代表分钟只是代表性候选,不是已确认的唯一出生分钟」这句在交付/采用轮仍完整出现一次(
AGENTS.mdPart B、VOICE.md 第 4 条)。 - 区间数字与盘面板标题行
.rectification-board__clock的取值同源,不得出现两处不一致。
T7.2 · 盘面板折叠块收敛
五个 <details className="technique-audit">(rectification-board.tsx:117/132/160/182/198)收敛成两段:「候选分钟」(含次级候选与换升时刻)与「参数与口径」(含技法审计、披露、双轨一致性)。
验收标准
- 五块内容一条不丢,只是换了归属;
engineMeaningToDisplayCopy()的调用点全部保留。 - 候选分钟列表的
is-changed/当前时间标记行为不变。 - 键盘可达性不弱于改前。
R8 · 设置弹窗 + 会员充值(可并行;阻塞已解除)
分支 codex/cend-settings-billing-20260916。只动 account-dialog-overlay.tsx、billing-panel.tsx 与 globals.css 的 1757–1960 区段。
T8.1 · 设置内容区收窄(E13)
.settings-dialog-content 内的表单类面板加一个最大宽度(建议 420–460px),左对齐。列表类面板(星盘资料、订单)保持铺满。
验收标准
- 「个人资料」与「通用设置」的表单收窄;「星盘资料」列表与「订单」表格不受影响。
- 四个分区切换时弹窗尺寸不跳变——这是 BUG-554/BUG-698 的现象,改完必须复验,并在进度记录里说明复验方式。
- 不碰
.settings-dialog-nav-item[aria-current="page"](BUG-698 刚改过)。
T8.2 · 套餐卡状态收敛(D13)
billing-panel.tsx:135 的三修饰符拼接改成两种互斥状态:is-current(当前套餐)与 is-recommended(推荐)。highlighted 不再改变外观,改为挂载后滚动定位到该卡。
验收标准
- 三种历史组合(推荐 / 当前 / 推荐且当前)各有明确且不叠加的外观。
- 从
membershipHref("credits")一类带参数的入口进入时,对应 tab 与卡片仍被定位到(改成滚动定位后要真跑一次)。 - 支付关闭态(
paymentEnabled === false)、套餐为空态、packagesError三个分支文案与可达性不变。
让步顺序
- T4.1 的只读侧栏:若
GET /api/sessions在未登录或超时的情况下让次级页首屏变慢 —— 退到「侧栏先渲染品牌字与三个导航项,最近列表到达后再插入」,但不得用骨架屏(红线 7)。再退一步:次级页只给品牌字 + 三个导航项,不要最近列表。 - T4.2 的页名进顶栏:若某页顶栏放不下页名加动作 —— 优先保页名,动作收进一个「更多」菜单。
- T5.1 的参数统一表:若合并后信息密度过高 —— 退到保留
.chart-page-center-card,只做视觉对齐,不做结构合并。 - T6.2 的目录:若从 Markdown 生成标题锚点与现有 rehype 链冲突 —— 退到只做"回到顶部",目录另立单。不得为了做目录去改
report-chart-grid-rehype.ts。 - T7.1 的收窄进度:若服务端投影里没有现成的"已从 N 分钟收到 M 分钟"字段 —— 只显示当前区间与宽度,不要前端口算进度。缺的字段写进
BLOCKED.md并另立单。 - T7.2 的两段收敛:若某块内容在两段里都不自然 —— 保留三段,但不得回到五段。
- 任何一轮若被门禁或环境卡住,先把已完成的子任务单独提交并推 staging,未完成项写进
BLOCKED.md,不要整轮压着不交。
开工前置命令
git fetch origin --prune
git worktree add -b codex/cend-shell-20260916 .worktrees/cend-shell-20260916 origin/staging
cd .worktrees/cend-shell-20260916/frontend
npm ci
./node_modules/.bin/tsc --noEmit
npm run lint
npm test 2>&1 | tail -30 # 记下测试总数,红线 2 的基线
npm run build 2>&1 | tail -40 # 记下 / /chart /ephemeris /reports 四个渲染标记与各自首屏体积,红线 3、4 的基线
R5–R8 各自重复上述流程:R5、R6 的基线是 R4 合入后的 origin/staging;R7、R8 可直接以 111b4a84 之后的 origin/staging 为基线。
开工时核对 docs/BUG_HISTORY.md 的当前最大编号(现为 BUG-738)。
环境缺口
本单诊断来自读代码与设计文档,没有真机截图:本会话无登录态、无 Chrome。以下必须落到 docs/testing/ 的人工清单,不得在进度记录里写成「通过」:
- 次级页侧栏在移动端抽屉的手势与焦点行为(R4)。
- 星盘行星表在 375px 宽下的横向滚动(R5)。
- 20 张盘以上的报告在加目录后仍不重叠、滚动不卡(R6,红线 9 关联)。
- 生时校正常驻条在窄屏
.is-compact下的实际可见性(R7,本轮核心收益)。 - 设置弹窗四个分区切换不跳尺寸(R8,BUG-554/698 的复验)。
- 支付流程(
membershipHref入口定位、下单)——无受控账号,整条留给产品负责人实测。