此前空态是 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
138 lines
10 KiB
Markdown
138 lines
10 KiB
Markdown
# 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`。
|