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

11 KiB
Raw Blame History

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: 400500)。StyreneB--font-bodyadmin.cssadmin-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.ts4 条全绿
docs/BUG_HISTORY.md 新增 BUG-737 resolved
/○ Staticgzip 入记录 见下

新测试断言四件事:两条栈里每个带引号 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 全部改指 -strong37 处 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 两处) 刻意保持 #85432fD3
暗色两块 --color-action-strong: #d78064,两块 token 集合仍完全一致

T1.3 · 模型选择器进输入框,删常驻底栏

ChatComposer 从横向 flex 改为纵向:文本域在上,自己的底排(.composer-row)在下,左侧 toolbar 插槽放模型选择器,右侧是字数与发送/停止键。.composer-footer 的三条 CSS 规则与两个调用方(page.tsxrectification-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 → 46pxis-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