feat(ui): 标题不再落宋体,强调色分阶换 coral,删输入框常驻底栏(BUG-737/738)
Independent Staging Quality Gate / validate (push) Failing after 9m26s
Independent Staging Quality Gate / publish (push) Skipped

BUG-737:--font-display 以两个从未加载的 Anthropic 授权字体开头
(Tiempos Headline / StyreneB:无 @font-face、public/ 无字体、layout.tsx
只 vendor 了 Inter),于是每个中文标题都掉到 Songti SC / SimSun——20 条
规则宽,含助手回答正文的 h2/h3。display 与 body 合并为同一条无衬线栈,
层级改由字重承担(33 条 display 规则 400→500)。

拉丁衬线方案实测否决:Newsreader 拉丁子集 132 KB(Inter 的 2.7 倍),
只为给一个品牌字上衬线,且会把「D10 事业盘怎么读」劈成两种字形。

BUG-738:亮色 #85432f 与暗色 #d78064 不同源;--color-ring 在
@theme inline 里硬编码不跟随 :root。产品拍板换 Claude coral #cc785c,
但实测它作文字色只有 3.14:1,而 62 个调用点里 53 个是 color:。
按角色拆两阶:--color-action #a9583e(文字 4.85:1)、
--color-action-strong #cc785c(填充/导轨/焦点环,3:1 非文字阈值)。
--report-accent 刻意保持 #85432f(报告是纸面,不跟应用强调色)。

T1.3/T1.4:模型选择器进 ChatComposer 的 toolbar 插槽,删掉常驻 44px 的
.composer-footer;顶栏 68→46px,「分析对象:」改为标题旁静默 chip。

防复发:新增 font-stack-loadable-contract,断言字体栈里每个 family 要么
vendored 要么是系统字体,且 --font-display 不得触达任何 CJK 衬线。

测试 3346→3350,fail 仍 31 且清单与基线逐条一致;/ 仍 Static;
CSS gzip 41,095→41,096(+0.002%);快速门 pytest 段 798 passed / 0 failed。

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 04:53:14 +00:00
co-authored by Claude Opus 5
parent 3fd74daf34
commit 66d9f59d84
22 changed files with 580 additions and 159 deletions
+180
View File
@@ -0,0 +1,180 @@
# PROGRESS · C 端界面对齐 R1(字体、强调色、chrome 瘦身)
任务书:`docs/tasks/TASK-cend-ui-claude-alignment-20260916.md` 的 R1
执行方式:**直接执行模式**(产品 2026-09-16 授权「你直接执行任务书就可以」)
分支:`codex/cend-ui-r1-20260916`
基线:`origin/staging` = `3fd74daf`
BUG 段:BUG-737、BUG-738
---
## 开工基线实测
| 项 | 基线值 |
|---|---|
| `tsc --noEmit` | 0 错 |
| `npm run lint` | 0 error / 118 warning |
| `npm test` | tests 3346 · pass 3300 · **fail 31** · skipped 15 |
| 路由渲染标记 | `/` `○` · `/chart` `○` · `/ephemeris` `○` · `/reports` `ƒ` · `/reports/[reportId]` `ƒ` |
| 产物 CSS gzip | 41,095 字节(四个 css chunk 合计) |
31 条失败全部是无 Docker 的既有环境缺口,清单存档比对用。
**首屏 gzip 口径说明**:本仓没有 gzip 测量脚本,当前 Turbopack 的路由表也不再输出 Size 列。R1 的改动几乎全在 CSS,因此口径取 **`.next/static` 下全部 css chunk 的 gzip 字节合计**,改前改后同机同命令测量。
---
## T1.1 · 字体栈(BUG-737
### 做了什么
`--font-display``--font-body` 合并为同一条无衬线栈,display 层级改由**字重**承担(33 条 display 规则 `font-weight: 400``500`)。`StyreneB``--font-body``admin.css``admin-app.tsx` 一并删除。
### 让步顺序 1 被触发:拉丁衬线方案实测后否决
任务书首选「vendor 一个 OFL 拉丁衬线」。实测后退到让步项「拉丁也走 Inter」,依据两条:
| 证据 | 数字 |
|---|---|
| Newsreader 拉丁可变子集(OFL,取自 Google Fonts v26 | **132,000 字节** |
| 对比:本仓已 vendor 的 `InterVariable-latin.woff2` | 48,256 字节 |
| 倍数 | **2.7×** |
第二条理由比体积更重要:`--font-display` 的使用面(品牌字、会话标题、首页大标题、入口卡标题、**回答正文的 h2/h3**、弹窗标题、登录页)**压倒性是中文**。拉丁衬线在里面只会命中品牌字和零星拉丁标签,却会把「D10 事业盘怎么读」这类混排标题劈成两种字形——读起来像字体加载失败,比统一无衬线更糟。
任务书 D4「中文永远不用衬线」不可退让的部分完整保留。
### 验收对照
| 任务书验收标准 | 结果 |
|---|---|
| `git grep -nE "Tiempos\|StyreneB\|Songti\|STSong" frontend/src` 无命中 | ✅ 除解释性注释外无命中 |
| 新增字体文件带 OFL 许可证、体积入记录 | ➖ 不适用(未 vendor 新字体,见上) |
| 新增 token 合同测试 | ✅ `frontend/tests/font-stack-loadable-contract.test.ts`4 条全绿 |
| `docs/BUG_HISTORY.md` 新增 BUG-737 `resolved` | ✅ |
| `/``○ Static`gzip 入记录 | ✅ 见下 |
新测试断言四件事:两条栈里每个带引号 family 必须是 vendored 或系统字体;两条栈必须经 `var(--font-inter, Inter)``--font-display` 不得触达任何 CJK 衬线或泛型 `serif`;不得有 display 规则停在字重 400。
---
## T1.2 · 强调色换 coralBUG-738
### 与任务书 D2 的偏离,及依据
产品拍板亮色换 Claude coral `#cc785c`。**实测发现它不能直接当文字色**——按 WCAG 相对亮度公式手算:
| 前景 / 背景 | 对比度 | 正文 AA4.5:1 |
|---|---:|---|
| `#cc785c` 文字 on `#fbfaf7` 画布 | **3.14** | ✗ |
| `#85432f`(原值)on 同背景 | 7.08 | ✓ AAA |
| `#a9583e` on 同背景 | **4.85** | ✓ |
| 白字 on `#cc785c` 填充 | **3.28** | ✗(≥3:1,满足非文字 UI 阈值) |
`--color-action` 的 62 个调用点里 **53 个是 `color:`**`background` 9 个、`border` 37 个),包括**助手回答正文里的 markdown 链接**。直接把它设成 `#cc785c` 会让 53 处读起来的文字全部掉到 3.14:1。
因此按**角色分两阶**
| token | 值 | 承担 |
|---|---|---|
| `--color-action` | `#a9583e` | 文字、图标、链接(4.85:1) |
| `--color-action-strong` | `#cc785c` | 填充、导轨、焦点环(3:1 非文字阈值) |
| `--color-action-hover` | `#8f4630` | 按下态 |
| `--color-action-soft` | `#f7ece5` | 淡底 |
| `--color-focus` / `--color-ring` | `#cc785c` | 焦点环 |
9 处 `background: var(--color-action)``.button-primary` 的 border 全部改指 `-strong`37 处 border 保持 `--color-action`(多为低透明度 `color-mix`,肉眼无差)。
**需要产品确认的一条**:白字落在 `#cc785c` 填充上是 3.28:1,低于正文 AA,涉及 `.button-primary` 与两个头像圆。claude.ai 本身就是这个值,而本轮目标正是「像 Claude」,故保留并在 `DESIGN.md` 与 BUG-738 里写明为**已知缺口**。若产品要求达 AA,把这几处填充改回 `--color-action`(白字 5.06:1)即可,视觉上仍是 coral。
### 四处亮色声明同步情况
| 位置 | 处理 |
|---|---|
| `@theme inline``--color-ring`(原硬编码 `#85432f`,**任务书点名的易漏点**) | ✅ 改为 `var(--color-focus)`,并新增断言锁住 |
| `:root` 主声明 | ✅ |
| `@media print` 里的第四个 `:root` | ✅ |
| `--report-accent``:root` 与 print 两处) | ✅ **刻意保持 `#85432f`**D3 |
| 暗色两块 | ✅ 补 `--color-action-strong: #d78064`,两块 token 集合仍完全一致 |
---
## T1.3 · 模型选择器进输入框,删常驻底栏
`ChatComposer` 从横向 flex 改为纵向:文本域在上,自己的底排(`.composer-row`)在下,左侧 `toolbar` 插槽放模型选择器,右侧是字数与发送/停止键。`.composer-footer` 的三条 CSS 规则与两个调用方(`page.tsx``rectification-agentic-chat.tsx`)的 footer div 全部删除;`composer-character-remaining.tsx` 整个文件删除(计数改由 composer 用自己已有的 `draft` 内联渲染,对自带 `value` 的校正面反而更准)。
`--composer-reserve` 148 → 124px(窄屏 116 → 100px)。
| 验收标准 | 结果 |
|---|---|
| 输入框为空且未近上限时下方无常驻元素 | ✅ `.composer-row` 只在有 toolbar 或计数时有内容,`.composer-footer` 已不存在 |
| 键盘可达性与 `aria-label` 不弱于改前 | ✅ `aria-describedby` 链路未变(`remainingId` 仍由调用方可覆盖,校正面仍传自己的 `useId()` |
| composer / model-selector 既有测试全绿 | ✅ |
| `ChatComposer` 仍是唯一输入框实现 | ✅ 校正面走同一组件的 `toolbar` 插槽,未新开第二个 |
## T1.4 · 顶栏瘦身
`.chat-panel` 首行 68 → 46px`is-rectification` 变体同步)。「分析对象:{盘名}」副标题删除。
**触发让步顺序 3**:任务书原方案是把分析对象移进输入框的对象选择器,但那需要 chart-library 状态与会话写接口,会撑大 `page.tsx` 并撞增长门禁(红线 5)。按让步项退到「标题旁一枚静默 chip」(`.chat-header-chart`),768px 以下隐藏,顶栏保持单行。会话与星盘资料的绑定关系本身没动。
常驻 chrome 合计:68 + 148 → **46 + 124 px**
---
## 收尾验收
| 红线 | 结果 |
|---|---|
| 1 · `tsc --noEmit` 0 错 | ✅ |
| 1 · `npm run lint` 0 error | ✅ 0 error / 118 warning(与基线同) |
| 2 · 测试总数不低于基线 | ✅ tests **3346 → 3350**(+4 为新增的字体栈合同),pass 3300 → 3304fail 仍 31,失败清单与基线 `diff` **逐条一致** |
| 3 · `/` 保持 `○ Static` | ✅ `/chart` `○``/ephemeris` `○``/reports` `ƒ` 也全部不变 |
| 4 · 首屏 gzip ±2% | ✅ **41,095 → 41,096 字节(+0.002%** |
| 5 · `page.tsx` 增长门禁 | ✅ 本轮净删(删了 footer div 与一个 import),`home-shell-growth-contract` 绿 |
| 6 · 一律 `ChatComposer` | ✅ |
| 7 · 无 spinner / 骨架 | ✅ 未新增等待态 |
| 8 · 不回退 BUG-698 的 `@supports (height: 1dvh)` | ✅ 未触碰该块 |
| 10 · 同提交更新 `DESIGN.md` | ✅ §2 调色板与「Where the action color appears」重写、§3 字体栈重写、新增「Chat header」段、composer 段更新 |
| 11 · 不升依赖、不顺手修 warning | ✅ warning 数与基线同为 118 |
### Python 快速门(BUG-736 防复发要求)
BUG-736 的防复发写明:**改动 `page.tsx` 的轮次,验收必须另跑 `run_quality_gate.py --profile quick`**,因为有 Python 测试拿前端源码当断言对象。本轮改了 `page.tsx`,已跑:
- `pytest tests/test_supabase_user_data_contract.py` → 8 条全绿
- `run_quality_gate.py --profile quick` → pytest 段 **798 passed / 1 skipped / 0 failed**;整体退出 1,唯一原因是 `npm test` 段的 31 条失败与无 `rsync`——把门禁的 31 条失败清单与本轮基线 `comm` 比对,**零新增**。
### 按红线 3 更新的既有断言(原值 / 新值 / 原因)
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
| `design-token-contract` | `--color-action: #85432f` | `#a9583e` + `--color-action-strong: #cc785c`;另加 `--color-ring: var(--color-focus)` | 强调色分阶(BUG-738);ring 那条是**新增**约束,不是放宽 |
| `personal-report-theme-contract` | 标题 `font-weight: 400`,测试名称含「serif display stack」 | `500`,名称改为「display stack at the display weight」 | 那个「衬线栈」从未加载,实际是宋体;无衬线 400 在大字号发虚 |
| `consultation-entrypoint` | hero / 入口卡 `font-weight: 400` | `500` | 同上 |
| `site-style-isolation-contract` | `--font-body: StyreneB, var(--font-inter, Inter)` | 去掉 `StyreneB` | 死配置,从未加载 |
| `character-remaining-contract` | `<ComposerCharacterRemaining>` + `className="composer-footer"` + `.composer-footer > p { display: none; }` | 计数在 ChatComposer 内联;`doesNotMatch(page, /composer-footer/)``.composer-row` | 常驻 44px 横栏删除 |
| `session-conversation-layout` | `.composer-footer``.composer` 同宽 | `doesNotMatch(/\.composer-footer\s*\{/)` | 同上,**收紧**而非放宽 |
| `session-conversation-layout` | `.composer-stop` 背景 `--color-action` | `--color-action-strong` | 停止键是填充面,走填充阶 |
| `chat-stream-layout` | `grid-template-rows: 68px …` | `46px …` | 顶栏瘦身 |
| `rectification-agentic-entry` | `<CharacterRemaining` | `toolbar={(` + `doesNotMatch(/composer-footer/)` | 计数与模型选择器都进了 composer 插槽 |
| `chat-session-write` | `分析对象:` | `className="chat-header-chart"` + `sessionChartLabel(activeSession, chartLibrary)` | 标签是内部术语;**绑定关系仍被两条断言锁住**,未放宽 |
八条是同步形态,两条(design-token 的 ring、session-conversation-layout 的 `doesNotMatch`)实际**收紧**了约束。
---
## 环境缺口(不得记为通过)
以下四项本会话做不了(无登录态、无 Chrome),已在任务书里登记,需真机验收:
1. **中文标题在 macOS / Windows / Android 三端的实际字形** —— 本轮的核心收益,必须真机对照改前改后。
2. **coral 在真实屏幕上的观感,以及明暗切换**;特别是 `.button-primary` 上白字 3.28:1 的实际可读性,产品需据此决定是否接受该缺口。
3. **输入框底排在窄屏的换行行为**(模型选择器标签较长时)。
4. **顶栏 46px 下会话标题 + chart chip 的截断表现**
---
## 下一轮
R2(空状态重做)基线取本轮合入后的 `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` | | **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` | **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 审查决定)