Files
Jyotisha/docs/tasks/PROGRESS-cend-ui-r1-20260916.md
T
Jesse_ChenandClaude Opus 5 66d9f59d84
Independent Staging Quality Gate / validate (push) Failing after 9m26s
Independent Staging Quality Gate / publish (push) Skipped
feat(ui): 标题不再落宋体,强调色分阶换 coral,删输入框常驻底栏(BUG-737/738)
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
2026-09-16 04:53:14 +00:00

181 lines
11 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 端界面对齐 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`