Files
Jyotisha/docs/tasks/PROGRESS-cend-ui-r2-20260916.md
T
Jesse_ChenandClaude Opus 5 62903b7a12
Independent Staging Quality Gate / validate (push) Failing after 9m13s
Independent Staging Quality Gate / publish (push) Skipped
feat(ui): 空状态改成「问候 + 输入框 + 两枚入口 pill」
此前空态是 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
2026-09-16 05:31:15 +00:00

138 lines
10 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 · 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`