feat(ui): 空状态改成「问候 + 输入框 + 两枚入口 pill」
Independent Staging Quality Gate / validate (push) Failing after 9m13s
Independent Staging Quality Gate / publish (push) Skipped

此前空态是 hero 卡(h1 clamp 到 52px)加两张 min-height 228px 的产品卡,
输入框被压在约 800px 内容之下,主动作不是视觉焦点。

改法:问候拆成 StarterGreeting 留在对话区并底对齐,两个产品入口降级成
StarterEntries 渲染在 composer 之后。composer 本身一行未动——它在空态与
会话态是同一个 DOM 位置,首条消息落地时不重挂载,草稿/焦点/排队卡都不受影响。

顺带修掉:空态 composer 1040px、会话态 760px,发首条消息时输入框会当场
变窄一截。两态统一到 --session-column-width,并新增断言锁住。

校正入口保留三个服务端驱动的文案分支(我第一版收成了两个,是
rectification-agentic-entry 打红才发现)。

DESIGN.md:Product entrypoint card 整节重写为 Starter home;删掉
Suggestion card 死文档(它描述的主题卡样式正是本轮删除的 CSS)。

测试 3350 不变,fail 仍 31 且与基线双向零差异;/ 仍 Static;
CSS gzip 41,096→41,044(−0.13%)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
Jesse_Chen
2026-09-16 05:31:15 +00:00
co-authored by Claude Opus 5
parent 66d9f59d84
commit 62903b7a12
13 changed files with 486 additions and 493 deletions
+137
View File
@@ -0,0 +1,137 @@
# PROGRESS · C 端界面对齐 R2(空状态重做)
任务书:`docs/tasks/TASK-cend-ui-claude-alignment-20260916.md` 的 R2
执行方式:**直接执行模式**
分支:`codex/cend-ui-r2-20260916`
基线:`origin/staging` = `66d9f59d`R1 合入后)
BUG 段:不占用(产品改造,非缺陷)
---
## 一处与任务书不符的现状,先说明
任务书 E5 写的是「hero 卡 + 两张入口大卡 + 『从一个主题开始』标题 + 3 列主题卡」。实际在 `66d9f59d` 上,**主题卡网格早已下线**`starter-home.tsx` 只剩 hero 卡 + 两张入口卡 + 错误行,`globals.css``.starter-theme-*` 一条不剩。任务书写于旧观察,此处以代码为准,工作量相应变小。
「输入框被压在约 800px 内容之下」这条仍然成立(hero 卡 padding 40px + `h1` clamp 到 52px,加两张 `min-height: 228px` 的卡)。
---
## T2.1 · 按原型重排空状态
### 结构
拆成两个组件,分别落在两个插槽:
| 组件 | 位置 | 内容 |
|---|---|---|
| `StarterGreeting` | 对话区内,`align-content: end` 底对齐 | 招呼语一行 + 问句 `clamp(26px, 3.2vw, 34px)` |
| (不动)`ChatComposer` | `.composer-wrap`,**与会话态同一个 DOM 位置** | — |
| `StarterEntries` | `.composer-wrap` 内、composer **之后** | 两枚入口 pill + 合并提示行 + 边界句 |
**为什么不把 composer 搬进空态区**:那会让它在空态与会话态之间挂载到两个不同位置,首条消息落地时 React 会卸载重建——正在输入的草稿、焦点、排队卡都会受影响。现在 composer 一行没动,只是它上下的东西换了。
### 与会话态同宽
`.welcome``.composer-wrap-starter .composer` 都改为 `min(var(--session-column-width), 100%)`。此前空态 composer 是 1040px、会话态 760px,**发出第一条消息的瞬间输入框会当场变窄一截**。这是原断言没覆盖到的问题,新断言把它锁住了。
### 验收对照
| 任务书验收标准 | 结果 |
|---|---|
| 1440×900 / 1280×720 下问候与输入框同屏可见 | ⚠️ **环境缺口**,无 Chrome 无法实测。静态推算:问候块约 90px + composer 约 96px + pill 区约 84px + 内边距,合计约 330px,远小于两个视口的可用高度,但必须真机确认 |
| 空态输入框宽度与会话态一致 | ✅ 两处都是 `--session-column-width`,并新增断言 |
| onboarding 分步 / `productEntrypointsDisabled` / `rectificationEntrySummary` 三分支保留 | ✅ 见下 |
| 首屏 gzip 相对 R1 应下降 | ✅ **41,096 → 41,044 字节(−0.13%** |
**三分支这条我第一版做错了**:我把校正入口的文案从三个分支收成了两个,漏掉了 `rectificationCardAction === "restart"`。是 `rectification-agentic-entry` 的断言打红才发现的,已补回三分支(入口标签本来就由服务端 entry summary 驱动,不读会话列表)。
### 一处内容取舍
`dailyStarlanguage.card.action`(今日星语卡片上的「行动建议」行)在 pill 形态下没有位置,对应的 `dailyStarlanguageAction` 变量已删除。卡片降级为 pill 是任务书 D5 授权的;这条行动建议在点进去之后的正文里仍然完整。若产品认为它必须留在首页,需要另立单——现在的 pill 只能承载一个词。
---
## T2.2 · 文案
对照 `frontend/docs/VOICE.md`
| 位置 | 文案 | 依据 |
|---|---|---|
| 入口 pill | 「今日星语」/「每日运势」、「生时校正」 | 原样保留,未改 |
| 合并提示行 | `{今日趋势} · {校正提示}` | 原则 3「解释为什么问,一句就够」;两张卡各自的描述段合成一行 |
| 校正提示(首次) | 「不确定出生时间时,用记得住的经历一步步缩小范围」 | 原值「不确定准确出生时间时,可通过已经发生的人生事件逐步缩小范围。」→ 去掉公文腔,对照 VOICE 好/坏表第 1 行 |
| 校正提示(可重做) | 「上次已经校正完,可以拿最新资料再来一次」 | 原值「上一次校正已经完成,可以基于最新资料再次校正。」→ 同上 |
| 校正提示(有未完成) | 「未完成的那次可以从左侧继续」 | 原值「新建一段独立校正;未完成的记录仍可从左侧历史会话继续。」→ 缩短,语义不变 |
| 边界句 | 「出生时间不足以支持的判断我会说明,不会补造具体分钟。」 | 原值「不支持的个人判断会明确说明,不会补造出生时间。」→ 准确性边界语义完全保留,改为第一人称 |
---
## 收尾验收
| 红线 | 结果 |
|---|---|
| 1 · `tsc` 0 错 / `lint` 0 error | ✅ 118 warning,与基线同 |
| 2 · 测试总数 | ✅ tests **3350**(与 R1 后持平),pass 3304,fail 31,失败清单与基线 `comm` 双向比对**零差异** |
| 3 · `/` 保持 `○ Static` | ✅ `/chart` `○``/ephemeris` `○``/reports` `ƒ` 均不变 |
| 4 · gzip 预期负增长 | ✅ −52 字节(−0.13%)。删掉约 108 行 starter CSS,同时新增了 pill 与问候的规则,净减不大 |
| 5 · `page.tsx` 增长门禁 | ✅ 净删(删掉一个派生变量与十几行 props),`home-shell-growth-contract` 绿 |
| 6 · 一律 `ChatComposer` | ✅ composer 一行未动 |
| 7 · 无 spinner / 骨架 | ✅ `data-opening` 仍只换 `cursor: progress` |
| 10 · 同提交更新 `DESIGN.md` | ✅ 「Product entrypoint card」整节重写为「Starter home」;顺带删掉「Suggestion card」死文档(它描述的主题卡样式正是本轮删除的 CSS) |
| 11 · 不升依赖、不留新 warning | ✅ |
### 两处我自己造成的问题,记在这里
**一、CSS 规则删除器切坏了一条注释。** 我用脚本按类名批量删 starter 规则时,对逗号分隔的选择器列表做了裁剪。有一条规则前面带注释:
```
/* The card is opening a Case: static copy and a progress cursor, no spinner (...). */
.product-entrypoint-card[data-opening="true"],
.product-entrypoint-card[data-opening="true"] .product-entrypoint-hitarea { cursor: progress; }
```
注释里那个逗号被当成了选择器分隔符,裁剪时把 `*/` 一起丢掉了。结果一个未闭合注释吞掉了后面 **2762 个字符**,连带 `.session-messages-loading``.rectification-entry-error``.message-thinking` 等四条无关规则在 `class-name-definition-contract` 眼里「不存在」。已删除该残片,并复核全文注释配平(69 / 69)、扫描所有含花括号的注释(0 条)、确认 `git diff` 里被删的非 starter 选择器只有一条(是我有意改的 `.conversation.is-empty`)。
**二、第一版差点误删共享的按压动效规则。** 那条 `:active` 规则的选择器列表里同时有 `.starter-list > button`(死)和 `.new-chat` / `.button-primary` / `.composer button` 等十几个活选择器。第一版脚本按「选择器命中死类名就删整条」,会把整个按压动效干掉。已改成「逐个选择器判定,全死才删整条,否则只摘掉死的那个」,并复核该规则现在仍有 13 个活选择器。
### 一条性能问题,顺手记下
`consultation-entrypoint` 里有一条断言写成:
```js
const start = source.indexOf('<div className="starter-list starter-workbench"');
const home = source.slice(start); // start === -1 时 slice(-1)
assert.match(styles, /\/\* Starter workbench \*\/[\s\S]*?\.starter-list \{.../);
```
切片起点消失后 `indexOf` 返回 1,随后那条 `[\s\S]*?` 正则在 250KB 的 CSS 上灾难性回溯,**单条测试跑了 532 秒**,整轮 `npm test` 从 37 秒涨到 541 秒。重写后该文件 0.3 秒跑完。新断言直接对文件断,不再依赖 `indexOf` 切片。
### 按红线 3 更新的既有断言(原值 / 新值 / 原因)
共 14 条,分布在 6 个文件。
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
| `chat-reading-load-home` ×2 | `starter-hero` + `product-entrypoints`;「空态 = 问候卡 + 两张产品卡 + composer」 | `starter-greeting-block` + `starter-entry`;「问候 + composer + 两枚 pill」,并**新增**一条断言要求 `StarterEntries` 渲染在 `ChatComposer` 之后 | 形态变更;新增那条锁住「输入框不能再被压到下面」 |
| `consultation-entrypoint` ×6 | 两个 `hitarea` 盖满卡片 / 卡片 hover 不改底色 / 卡片无 `<small>` 细则 / 手机断点卡片高度与对齐 / hero 与卡标题字重 400 / `indexOf` 切片查技术参数 | 对应意图全部迁到 `.starter-entry``.starter-greeting-block` | 见上;其中「hover 只动描边、按下才改底色」这条意图我专门补了 CSS 规则去承接,没有丢 |
| `onboarding-presentation` | `className="starter-greeting"` | `className="starter-salutation"` | 旧类名既指招呼语行、又被读成整块问候区;拆成 `-block` / `-salutation` 消歧。「两行」意图不变 |
| `rectification-agentic-entry` | — | (未改断言,是它打红让我发现少了 restart 分支) | — |
| `rectification-surface-contract` | `.product-entrypoint-card[data-opening]``cursor: progress` | `.starter-entry[data-opening]` | 选择器跟随形态;「打开 Case 只换指针不出 spinner」不变 |
| `session-conversation-layout` | `.welcome``.starter-list` 都是 1040px | `.welcome``--session-column-width`**新增**锁住 composer 两态同宽,并 `doesNotMatch(/\.starter-list/)` | 比原断言更强:顺带锁住「输入框不跳宽度」 |
| `class-name-definition-contract` | `knownUnstyled``starter-workbench` | 移除该项 | 类名已不存在,白名单里留着会掩盖真问题 |
其中 3 条(两处 `doesNotMatch`、一条新增的渲染顺序断言)**收紧**了约束,无一条放宽。
---
## 环境缺口(不得记为通过)
1. **1440×900 与 1280×720 下问候与输入框同屏可见** —— 本轮的核心验收项,静态推算通过但必须真机确认。
2. **入口 pill 在窄屏的换行与触摸目标**(36px 高,低于 44px 推荐值,需确认实际可点性)。
3. **空态 → 首条消息的过渡**:确认输入框不跳宽度、草稿不丢、焦点不丢。
---
## 下一轮
R3(侧栏拍平 + 星盘入口收敛 + 去头像)基线取本轮合入后的 `origin/staging`
+1 -1
View File
@@ -156,7 +156,7 @@
| — | `PROGRESS-react-compiler-20260817.md` | React Compiler 接管首页 | 已回滚 | 结论见 `BLOCKED.md` |
| `TASK-engine-runtime-hygiene-20260901.md` | `PROGRESS-engine-runtime-hygiene-20260901.md` | Python 引擎运行时治理 | 已验收 | `124d3990`(staging 实机三条待部署窗口) |
| `TASK-billing-pricing-20260830.md` | `PROGRESS-billing-pricing-20260830.md` | 计费闭环与功能级定价 | 已合入 | `aaaa5fb7``a499c344` |
| `TASK-cend-ui-claude-alignment-20260916.md` | `PROGRESS-cend-ui-r1-20260916.md` | **C 端界面向 claude.ai 产品界面对齐(三轮串行 R1→R2→R3,都动 `globals.css`,不得并行)**:根因是 `frontend/CLAUDE_DESIGN.md` 扒的是 **claude.com 营销官网**,它自己在 Known Gaps 里写明 claude.ai 产品界面不在范围内,而 `DESIGN.md:3` 把它当成了产品界面的实现契约。**R1**:`--font-display` 里 Tiempos Headline / StyreneB **从未加载**(无 `@font-face`、`public/` 无字体、`layout.tsx` 只 vendor 了 Inter),中文标题全站落到 **宋体 / SimSun**,波及 20 处含助手回答的 h2/h3(BUG-737);亮色强调色 `#85432f` 与暗色 `#d78064` 不同源,产品拍板亮色换 **Claude coral `#cc785c`****易漏点**是 `globals.css:16` 的 `--color-ring` 硬编码在 `@theme inline` 里不跟随 `:root`,另有第四个 `:root` 亮色块(`:4358`)必须同步(BUG-738);`.composer-footer` 常驻 44px + 顶栏 68px + `--composer-reserve` 148px,每屏固定吃掉 216px,模型选择器移进输入框内部、删掉底栏、顶栏收到 46px 并删「分析对象」副标题。**R2**:空状态是营销落地页(hero 卡 + 两张 132px 入口大卡 + 3 列 156px 主题卡),输入框被压在 **800px 以上**内容之下,重排成「问候 + 居中输入框 + 两枚入口 pill + 一排 chip」。**R3**:侧栏两个 `<details>` 拍平成一条「最近」、星盘的两个入口(侧栏分组 + 账户菜单)收敛到一处、删掉逐条助手头像。**决策记录 D3 推翻 DESIGN.md「报告强调色与应用同源」一句**(报告刻意保留深棕)。原型图 https://claude.ai/code/artifact/da275da6-2954-4f50-99aa-32bb8694d38b(三套画面 + 明暗,页面标题就是建议字体栈的实际渲染)。环境缺口:无登录态无 Chrome,四项真机观感留 `docs/testing/`。BUG 段 737738 | 待领取 | — |
| `TASK-cend-ui-claude-alignment-20260916.md` | `PROGRESS-cend-ui-r1-20260916.md`、`PROGRESS-cend-ui-r2-20260916.md` | **C 端界面向 claude.ai 产品界面对齐(三轮串行 R1→R2→R3,都动 `globals.css`,不得并行)**:根因是 `frontend/CLAUDE_DESIGN.md` 扒的是 **claude.com 营销官网**,它自己在 Known Gaps 里写明 claude.ai 产品界面不在范围内,而 `DESIGN.md:3` 把它当成了产品界面的实现契约。**R1**:`--font-display` 里 Tiempos Headline / StyreneB **从未加载**(无 `@font-face`、`public/` 无字体、`layout.tsx` 只 vendor 了 Inter),中文标题全站落到 **宋体 / SimSun**,波及 20 处含助手回答的 h2/h3(BUG-737);亮色强调色 `#85432f` 与暗色 `#d78064` 不同源,产品拍板亮色换 **Claude coral `#cc785c`****易漏点**是 `globals.css:16` 的 `--color-ring` 硬编码在 `@theme inline` 里不跟随 `:root`,另有第四个 `:root` 亮色块(`:4358`)必须同步(BUG-738);`.composer-footer` 常驻 44px + 顶栏 68px + `--composer-reserve` 148px,每屏固定吃掉 216px,模型选择器移进输入框内部、删掉底栏、顶栏收到 46px 并删「分析对象」副标题。**R2**:空状态是营销落地页(hero 卡 + 两张 132px 入口大卡 + 3 列 156px 主题卡),输入框被压在 **800px 以上**内容之下,重排成「问候 + 居中输入框 + 两枚入口 pill + 一排 chip」。**R3**:侧栏两个 `<details>` 拍平成一条「最近」、星盘的两个入口(侧栏分组 + 账户菜单)收敛到一处、删掉逐条助手头像。**决策记录 D3 推翻 DESIGN.md「报告强调色与应用同源」一句**(报告刻意保留深棕)。原型图 https://claude.ai/code/artifact/da275da6-2954-4f50-99aa-32bb8694d38b(三套画面 + 明暗,页面标题就是建议字体栈的实际渲染)。环境缺口:无登录态无 Chrome,四项真机观感留 `docs/testing/`。BUG 段 737738 | 待领取 | — |
| `TASK-cend-surfaces-claude-alignment-20260916.md` | — | **次级页面对齐(上一单的续篇,R4→R5/R6,R7、R8 可并行)**:星盘 `/chart`、星历 `/ephemeris`、报告 `/reports` **各是脱离 app 外壳的独立全屏页**,顶部只有一个「返回对话」链接、侧栏整个消失,且三家各写了一套一模一样的 `*-shell`/`*-topbar`/`*-hero` 骨架——与上一单 E5 同根因(营销站 band 结构被套到产品界面)。**R4** 抽只读导航外壳 `AppNavRail`(只用现成的 `GET /api/sessions` + `GET /api/account`,会话行走 `sessionHref` 跳 `/?c=<uuid>`;**刻意不带**重命名/删除/收藏/归档——那套连着 `Home()` 的乐观更新与回滚,搬过来会撞 useState 增长门禁)。**R5** 星盘五 tab 下划线化 + 参数合表 + 行星表横向滚动;星历日期导航改 ` 日期 `。**R6** 报告中心卡片网格改行式列表;阅读页加常驻目录。**R7** 生时校正把可信区间从盘面板标题行提成常驻条(窄屏 `.is-compact` 下盘面板是 overlay,现在默认看不到区间),五个 `technique-audit` 折叠块收成两段。**R8** 设置内容区收窄(880px 弹窗里表单铺了 690px)、套餐卡三修饰符收敛成两态。**已解锁**:原挡路的设置单已于 `111b4a84`(BUG-698)合入。**两条不得回退**BUG-698 的 `@supports (height: 1dvh)` 写法(重复声明回退会被 Lightning CSS 折叠)、BUG-616/617 的报告盘面 grid 实现。默认不占 BUG 号 | 执行中(Claude 直接执行) | — |
### 待开的任务书(2026-09-03 审查决定)