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

10 KiB
Raw Blame History

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 里有一条断言写成:

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 knownUnstyledstarter-workbench 移除该项 类名已不存在,白名单里留着会掩盖真问题

其中 3 条(两处 doesNotMatch、一条新增的渲染顺序断言)收紧了约束,无一条放宽。


环境缺口(不得记为通过)

  1. 1440×900 与 1280×720 下问候与输入框同屏可见 —— 本轮的核心验收项,静态推算通过但必须真机确认。
  2. 入口 pill 在窄屏的换行与触摸目标(36px 高,低于 44px 推荐值,需确认实际可点性)。
  3. 空态 → 首条消息的过渡:确认输入框不跳宽度、草稿不丢、焦点不丢。

下一轮

R3(侧栏拍平 + 星盘入口收敛 + 去头像)基线取本轮合入后的 origin/staging