Files
Jyotisha/docs/tasks/TASK-cend-ui-claude-alignment-20260916.md
T
Jesse_ChenandClaude Opus 5 a2c1f1fbda docs(tasks): C 端界面向 claude.ai 产品界面对齐任务书(三轮串行)
根因:CLAUDE_DESIGN.md 扒的是 claude.com 营销官网,它自己在 Known Gaps
里写明 claude.ai 产品界面不在范围内,而 DESIGN.md 把它当成了产品界面的
实现契约。于是营销页语汇(hero band、3-up feature card)进了聊天空状态,
拉丁 display face 的衬线规则套到中文界面变成了宋体。

R1 字体 + 强调色 + chrome 瘦身(BUG-737/738)
R2 空状态重做
R3 侧栏拍平 + 入口收敛 + 去头像

三轮都动 globals.css,不得并行。附原型图链接。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
2026-09-16 03:42:28 +00:00

22 KiB
Raw Blame History

TASK · C 端界面向 claude.ai 产品界面对齐(2026-09-16

基线

  • 基线 commitorigin/staging = 5094fd26207e587037fa366dbe186dd3139042362026-09-16
  • 原型图(改后形态,含空状态 / 会话中 / 移动端三套画面与明暗两套): https://claude.ai/code/artifact/da275da6-2954-4f50-99aa-32bb8694d38b
  • 本单分三轮串行:R1 → R2 → R3。三轮都动 frontend/src/app/globals.css不得并行
  • BUG 编号起点:BUG-737(开工时以 docs/BUG_HISTORY.md 实际最大号为准,当前最大为 BUG-736)

事故实证

行号按 origin/staging5094fd26。符号名在括号里,行号漂移时按符号定位。

E1 · --font-display 里没有一个字体是能加载的(→ 中文标题全站落到宋体)

frontend/src/app/globals.css:100:root--font-display):

--font-display: "Tiempos Headline", "Songti SC", STSong, "Noto Serif CJK SC", Georgia, serif;

同文件 :101--font-body)以 StyreneB 开头。

实证三条:

  1. git grep "@font-face" origin/staging -- frontend/src 无命中。
  2. frontend/public/ 下只有 data/jyotish-logo.png,没有字体文件。
  3. frontend/src/app/layout.tsx:2-13 只通过 next/font/local 加载了一个 InterVariable-latin.woff2,变量名 --font-inter。注释写明「publish image cannot download fonts at build time」。

也就是说 Tiempos Headline 与 StyreneB 这两个 Anthropic 授权字体从来没有被加载过,它们在栈里只起占位作用。真实用户拿到的是回退结果:

用户环境 --font-display 实际命中
macOS / iOS Songti SC(宋体)
Windows Georgia 覆盖拉丁,中文掉到浏览器默认 serif,即 SimSun
Android 多数机型无 Noto Serif CJK SC,同样落到系统默认 serif

--font-display 的使用面(git grep "var(--font-display)" -- frontend/src/app/globals.css 共 20 处)覆盖:品牌字 .brand-row:686、顶栏会话标题 .chat-header strong:846、首页大标题 .starter-hero h1、入口卡标题、主题卡正文 .starter-content span助手回答正文里的 h2/h3 .message-markdown h2, h3:1372、所有弹窗标题 .account-modal h2:1699、模型选择器标题 .model-selector-title:1604、登录页 .auth-story h2:1766

--font-body 里的 StyreneB 同样从不命中,但它后面紧跟 var(--font-inter, Inter),回退无害,属于同一类死配置。

E2 · 亮色与暗色的强调色不同源

位置 亮色 暗色
globals.css:65:root --color-action #85432f 深棕
globals.css:175@media (prefers-color-scheme: dark) #d78064
globals.css:246:root[data-theme="dark"] #d78064

暗色块自带注释(:173:244):「#85432f is too dark to read as an action on」——暗色已经自己走到 coral 了,亮色还停在深棕。

改色时必须同步的四处亮色声明,漏一处就会出现「按钮变了、焦点环没变」:

声明 说明
globals.css:16 --color-ring: #85432f; @theme inline 块里硬编码,不跟随 :rootTailwind 的 ring-* 工具类走这里
globals.css:65-69 --color-action / -soft / -hover / --color-focus / --color-action-on-dark 主声明
globals.css:133 --report-accent: #85432f; 报告纸面调色板,见决策记录 D3
globals.css:4358-4375 第四个 :root 亮色块(--color-action / -hover / --color-focus / --report-accent :65 同值,必须同步

@theme inline 的语义::7:25--color-action: var(--color-action) 一类声明是 Tailwind v4 的 re-export 写法,不是自引用 bug,不要「修」它。但 :16--color-ring 是写死的十六进制,必须手动跟改。

E3 · 输入框下方常驻一条 44px 的横栏

globals.css:1667.composer-footer):width: min(760px, 100%); min-height: 44px; frontend/src/app/page.tsx:1815-1816.composer-footer 里放 ModelSelector + ComposerCharacterRemaining

字数统计本身已经有 characterRemainingVisible() 做条件显示,但容器高度是常驻的,所以没到字数上限时这 44px 是空的。

E4 · 顶栏 68px,常驻内部术语

globals.css:852.chat-panel):grid-template-rows: 68px minmax(0, 1fr) auto; page.tsx:1627<span className="chat-header-subtitle">分析对象:{...}</span>

顶栏固定 68px + --composer-reserve: 148pxglobals.css:129),每屏固定被吃掉 216px。「分析对象」是内部术语。

E5 · 空状态是一张营销落地页

frontend/src/components/starter-home.tsx:47 起,.starter-list.starter-workbench 内依次是:

选择器 尺寸
hero 卡 .starter-heroglobals.css:1910 起) padding: 40pxh1 clamp(34px, 5vw, 52px)
两张产品入口大卡 .product-entrypoints:1997 起) 每张 min-height: 132px
「从一个主题开始」标题 + 主题卡网格 .starter-themes / .starter-theme-card 3 列,每张 min-height: 156px

.welcome:1890 被重设为 width: min(1040px, 100%)。输入框在 page.tsx:1803composer-wrap-starter)位于这些之后,即首屏 800px 以上内容压在输入框之上,且输入框宽 1040px 而上方内容也是 1040px。

对照 claude.ai 的产品空状态:一行问候 + 紧随其下的输入框(垂直居中)+ 一排小 chip,总高约 300px。主动作是「打字」,它应该是视觉焦点。

E6 · 侧栏比 claude.ai 多一层折叠,星盘有两个入口

frontend/src/components/app-sidebar.tsx

内容
:266-297 SidebarGroup.chart-nav「星盘列表」+ 加号,chip 列表
:299-314 SidebarGroup.session-nav 内嵌 <details>「收藏对话」
:316-340 SidebarGroup.session-nav 内嵌 <details>「历史对话」
:389 账户菜单项「星盘资料」

page.tsx:1598-1601onSelectChart / onAddChart / onOpenChartLibrary 三个回调打开的是同一个弹窗 openAccountDialog("chart-library")。即侧栏「星盘列表」与账户菜单「星盘资料」是同一目的地的两个入口。

E7 · 每条助手消息带头像

globals.css:1074.agent-avatar):32px 圆形 url("/jyotish-logo.png"):2411 在会话态收到 30px。claude.ai 的产品界面助手消息没有逐条头像,靠留白与行宽区分发言人。


根因

  1. E1/E2 的共同根因frontend/CLAUDE_DESIGN.md 扒的是 claude.com 营销官网,该文件自己在 ## Known Gaps 末尾写明「The actual Claude product surface (claude.ai chat interface) … adds many product-specific components (chat bubbles, message tools, file upload chips, conversation history sidebar) that are out of scope for this marketing-surface document」。frontend/DESIGN.md:3 把这份营销站文档当成了产品界面的实现契约,于是:

    • 照抄了 Copernicus/Tiempos 的衬线 display 规则,但那是拉丁 display face 的规则,套到中文界面就变成宋体;
    • 照抄了 hero band、3-up feature card、ArrowUpRight 链接箭头等营销页语汇,放进了一个聊天产品的空状态(E5)。
  2. E1 的直接根因:字体栈里写了两个永远不会加载的授权字体名,没有任何测试或构建检查拦住「声明了一个加载不到的 family」。DESIGN.md §3「Font stacks」把它当成既定事实记录,所以后续轮次也没人质疑。

  3. E3/E4 的根因:把「随时可调」的开发者控件(模型选择器、字数)与「用户身份上下文」(分析对象)放进了常驻 chrome,而不是放进用得到它们的那一刻。


决策记录

产品负责人 2026-09-16 在会话中的授权:

  • D1|三轮全做。 不是只修字体就收工。R1 → R2 → R3 串行,每轮独立可验收、可单独上 staging。
  • D2|亮色强调色换成 Claude coral #cc785c 产品明确选了「换成 Claude coral」而不是「保留深棕」或「取中间值」。配套的 hover/soft/on-dark/ring/focus 由执行方按对比度要求推导,见 T1.2。
    • 本条推翻 frontend/DESIGN.md §2 调色板表里 --color-action 一行的既定值,以及 ## Where the action color appears 一节对深棕的描述。DESIGN.md 必须在同一提交内改掉,不得留旧值。
  • D3|报告纸面的 --report-accent 不跟随,保持 #85432f 理由:报告是打印文档表面,coral 在米色纸上读起来发飘;DESIGN.md §2 已经允许报告走独立的窄调色板。
    • 本条推翻 DESIGN.md §2 里「Report accent … same hue as the action color」这句话,必须改写成「报告强调色与应用强调色不同源,刻意保留深棕」。
  • D4|中文永远不用衬线。 --font-display 改造后,任何 CJK 字形都不得落到 serif。拉丁字形是否保留衬线见 T1.1 的让步顺序。
  • D5|两个产品入口(今日星语 / 生时校正)降级为输入框下方的 pill,不删除。 入口本身保留,只降权。
  • D6|星盘入口收敛到侧栏一处,账户菜单里的「星盘资料」项删除。依据产品既有口径「多余入口宁可删除也不修」。
  • D7|助手消息头像删除。 /jyotish-logo.png 资源保留(登录页与 favicon 仍在用),只是不再逐条渲染。

硬红线

  1. ./node_modules/.bin/tsc --noEmit 0 错;npm run lint 0 error
  2. 测试总数不得低于开工时在 origin/staging 的实测值。改任何既有断言必须在进度记录里写「原值 / 新值 / 原因」三栏。
  3. next build/ 保持 ○ Static
  4. 首屏 gzipR1 允许 ±2%R2 预期是负增长(删掉 .starter-hero / .product-entrypoints / .starter-theme-* 一大片 CSS 与 JSX),若反而增长必须在进度记录里给出原因。
  5. frontend/src/app/page.tsxAGENTS.md §6 的现行门禁:Home()useState / useRef不得增长frontend/tests/home-shell-growth-contract.test.ts 执行),行数是粗护栏(开工实测基线 + 150)。R2 会从 page.tsx 删掉空状态相关的 props 传递,三项都应下降;新逻辑一律进 frontend/src/hooks/ / frontend/src/lib/ / 组件。
  6. 一律复用 ChatComposer不得手写第二个输入框;模型选择器移进输入框是改 ChatComposer 的内部结构,不是新开一个 composer。
  7. 揭幕后不得出现 spinner / 骨架 /「正在加载」(流式生成中除外)。
  8. 改 UI 的同一提交内更新 frontend/DESIGN.md;新文案先对照 frontend/docs/VOICE.md
  9. 不得顺手升级依赖、不得顺手修不在本单里的 warning;发现了写进 BLOCKED.md 或进度记录。
  10. 本轮不改数据库结构,不得顺带动迁移。
  11. 不得新增任何 next/font/google 调用。layout.tsx:8-9 的注释写明发布镜像在构建时下载不到字体,必须 vendor 到 frontend/src/app/fonts/

任务分解

R1 · 字体、强调色、chrome 瘦身

分支 codex/cend-ui-r1-20260916。这一轮改动集中在 globals.css 的 token 区与 ChatComposer,风险最低,先上。

注意codex/settings-dialog-size-and-nav-20260916 的 worktree 里有未提交的 globals.css 改动,落在 1756–1953 与 5108 附近的弹窗区,与本轮目标行不重叠。开工前 git fetch 确认它是否已合入,若已合入则以新 staging 为基线重新定位行号。

T1.1 · 修掉字体栈(BUG-737

首选做法:vendor 一个 OFL 许可的拉丁衬线 woff2(建议 Newsreader 或 EB Garamond 的 latin subset),放 frontend/src/app/fonts/,按 layout.tsx 里 Inter 的同一套 next/font/local 写法加载,然后:

--font-display: var(--font-<新衬线>), "PingFang SC", "Microsoft YaHei", var(--font-inter, Inter), sans-serif;
--font-body: var(--font-inter, Inter), -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;

关键点是拉丁衬线放第一位、CJK 无衬线紧随其后:衬线 face 不含 CJK 字形,汉字会自动落到后面的无衬线,拉丁字母则拿到衬线。原型图 https://claude.ai/code/artifact/da275da6-2954-4f50-99aa-32bb8694d38b 的标题就是这套栈的实际渲染结果,可直接对照。

同时从 --font-body 里删掉 StyreneB(死配置)。

验收标准

  • git grep -n "Tiempos\|StyreneB\|Songti\|STSong" frontend/src 无命中(frontend/CLAUDE_DESIGN.md 是上游参照,不在此列)。
  • 新增字体文件有 OFL/SIL 许可证文件同目录存放,体积写进进度记录。
  • 新增一条 token 合同测试:断言 --font-display--font-body 里出现的每一个带引号 family,要么在 layout.tsx 里有对应的 next/font/local 声明,要么在系统字体白名单里(-apple-system / PingFang SC / Microsoft YaHei / 通用族名)。这条是 BUG-737 的防复发措施。
  • docs/BUG_HISTORY.md 新增 BUG-737,状态 resolved,记录现象、触发条件、根因、修复、验证、防复发。
  • next build/○ Static;首屏 gzip 变化写进进度记录。

T1.2 · 亮色强调色换 coralBUG-738

把 E2 表里列的四处亮色声明一并改到以 #cc785c 为基准的一组值:

token 新值 约束
--color-action #cc785c D2 指定
--color-action-hover 推导(建议 #a9583e 按下态必须明显深于默认态
--color-action-soft 推导(建议 #f7ece5 作为底色时,其上的 --color-ink 对比度 ≥ 7:1
--color-focus --color-action 同值
--color-action-on-dark 保持或按需微调
globals.css:16 --color-ring 跟改 易漏,在 @theme inline 里硬编码
globals.css:133 / :4375 --report-accent 保持 #85432f D3

验收标准

  • git grep -n "#85432f" frontend/src/app/globals.css 只剩 --report-accent 的两处(:133 与第四个 :root 块内)。
  • 亮色下,coral 作为文字色放在 --color-canvas#fbfaf7)上的对比度 ≥ 4.5:1;作为 --color-surface-dark 上的按钮底色时,其上 --color-on-dark 文字对比度 ≥ 4.5:1。把实测数值列进进度记录。
  • 暗色两块(:175:246)保持 #d78064 不动,两块 token 集合仍然完全一致(class-name-definition-contract 的同胞测试会查)。
  • frontend/DESIGN.md §2 调色板表与 ## Where the action color appears 一节改到新值;## Dark theme 一节补一句两套强调色现在同源。
  • docs/BUG_HISTORY.md 新增 BUG-738(亮暗强调色不同源 + --color-ring 硬编码不跟随)。
  • ChatComposerfrontend/src/components/chat-composer.tsx)内部加一条底排:左侧放模型选择器与「分析对象」选择器,右侧是发送 / 停止按钮。
  • page.tsx:1815-1816.composer-footer 整块删除;globals.css:1667.composer-footer 规则与 :571-572:2436 等相关覆盖一并清理。
  • 字数提示改成只在 characterRemainingVisible() 为真时插入 DOM,不再占常驻高度。
  • --composer-reserveglobals.css:129:1825)按新高度重算。

验收标准

  • 输入框为空且未接近字数上限时,输入框下方没有任何常驻元素
  • 模型选择器与对象选择器的键盘可达性与 aria-label 不弱于改前;npm test 里既有的 composer / model-selector 测试全绿。
  • ChatComposer 仍是唯一的聊天输入框实现,校正面(自带 value 的那条路径)不受影响。

T1.4 · 顶栏瘦身

  • .chat-panelgrid-template-rows 首行 68px → 46pxglobals.css:852:853)。
  • 删掉 page.tsx:1627 的「分析对象:xxx」副标题,该信息由 T1.3 的对象选择器承载。
  • 顶栏只留:侧栏折叠按钮、会话标题、点数。空状态下不显示会话标题。

验收标准

  • 会话标题在窄屏下仍然省略号截断、不换行、不溢出。
  • 空状态顶栏只有折叠按钮与点数。
  • 点数按钮的 aria-label 保持原样(含余额数字与「会员与充值」)。

R2 · 空状态重做

分支 codex/cend-ui-r2-20260916,基线是 R1 合入后的 staging。

T2.1 · 按原型重排空状态

starter-home.tsx 重写为:问候一行 → 输入框(垂直居中)→ 两枚入口 pill(今日星语 / 生时校正,D5)→ 一排建议 chip → 一行边界说明。删掉 .starter-hero / .product-entrypoints / .starter-themes / .starter-theme-card / .starter-workbench 的 JSX 与 CSS。

验收标准

  • 在 1440×900 与 1280×720 两个视口下,问候与输入框同时在首屏内可见,无需滚动。
  • 空状态下输入框宽度与会话态一致(都走 --session-column-width),不再是 1040px 对 760px 两套。
  • 现有的空状态相关测试(onboarding 分步、productEntrypointsDisabled 禁用态、rectificationEntrySummary 三种文案分支)全部保留并通过;文案按 VOICE.md 重写的,在进度记录里列「原值 / 新值 / 原因」。
  • 首屏 gzip 相对 R1 后的基线应下降,数值写进进度记录。

T2.2 · 文案去内部术语

对照 frontend/docs/VOICE.md 复核空状态全部可见文案。已知要处理的:「分析对象」(R1 已删)、入口 pill 的说明文案、chip 文案。

验收标准:新文案逐条在进度记录里对照 VOICE.md 的五条原则给出依据。


R3 · 侧栏拍平、入口收敛、去头像

分支 codex/cend-ui-r3-20260916,基线是 R2 合入后的 staging。

T3.1 · 侧栏拍平

app-sidebar.tsx

  • 顶部三个平铺导航项:新建对话(coral 文字,保持 DESIGN.md 既定的「不做实心块」)、我的报告、星盘资料。
  • 删掉 :266-297 的「星盘列表」分组与 :299-340 的两个 <details>
  • 一条平铺的「最近」列表,收藏的会话用星标置顶(session.pinned 仍是排序依据,不再是独立分组)。

验收标准

  • 收藏与归档功能不丢:会话行的「更多操作」菜单里收藏 / 重命名 / 分享 / 归档 / 删除五项全在。
  • 归档视图(sessionControls.showingArchived)仍可进入。
  • 侧栏折叠态(isCollapsedDesktop)与移动端抽屉行为不变;viewport-breakpoint-contract.test.ts 全绿。
  • 键盘可达性不弱于改前:handleExpandHistory 的焦点转移逻辑要么保留、要么有等价替代。

T3.2 · 星盘入口收敛(D6

删掉 app-sidebar.tsx:389 账户菜单里的「星盘资料」项,只留侧栏一处。

验收标准git grep -n "chart-library" frontend/src/app/page.tsxopenAccountDialog("chart-library") 的调用点从三处减到一处(或两处,若加号与列表项仍分开);账户菜单项数减一。

T3.3 · 删掉助手消息头像(D7

globals.css:1074.agent-avatar:2408-2415 的会话态覆盖,以及渲染它的组件节点。行间距按原型调整,靠留白区分发言人。

验收标准/jyotish-logo.png 仍被登录页与 favicon 引用(不要删文件);助手与用户消息在视觉上仍能一眼区分(用户是 coral 淡底气泡,助手是纯文本)。


让步顺序

按此顺序退让,每退一步都要在进度记录里写明退到了哪一级、为什么:

  1. T1.1 的拉丁衬线:若 vendor 字体导致首屏 gzip 超出预算,或找不到合规的 latin subset —— 退到「拉丁也走 Inter」,即 --font-display: var(--font-inter, Inter), "PingFang SC", …,靠字重、字号、字距(letter-spacing: -.02em)做层级。中文不用衬线这条(D4)不可退让。
  2. T1.2 的配套色:若推导出的 --color-action-hover--color-action-soft 过不了对比度要求 —— 调这两个派生值,--color-action 本身的 #cc785c 不可退让(D2)。
  3. T1.3 的对象选择器:若「分析对象」选择器在输入框内实现成本过高 —— 退到只把模型选择器移进去,分析对象信息改由会话标题旁的一枚静默 chip 承载;.composer-footer 这条常驻横栏必须删掉
  4. T2.1 的垂直居中:若在小高度视口(max-height: 640px)下居中导致内容被裁 —— 退到顶部对齐 + 上方留白,首屏可见性要求不变。
  5. T3.1 的收藏置顶:若星标置顶与现有排序逻辑冲突 —— 退到保留一个「收藏」分组,但必须去掉 <details> 折叠,改成常驻小标题。
  6. 任何一轮若被门禁或环境卡住,先把已完成的子任务单独提交并推 staging,未完成项写进 BLOCKED.md,不要整轮压着不交。

开工前置命令

git fetch origin --prune
git worktree add -b codex/cend-ui-r1-20260916 .worktrees/cend-ui-r1-20260916 origin/staging
cd .worktrees/cend-ui-r1-20260916/frontend
npm ci
./node_modules/.bin/tsc --noEmit
npm run lint
npm test 2>&1 | tail -30     # 记下测试总数,这是红线 2 的基线
npm run build                # 记下 / 的渲染标记与首屏 gzip,这是红线 3、4 的基线

开工时核对 docs/BUG_HISTORY.md 的当前最大编号,若已超过 BUG-736 则顺延。

R2、R3 各自重复上述流程,基线换成前一轮合入后的 origin/staging


环境缺口

本单的诊断全部来自读代码与设计文档,没有真机截图:本会话无登录态、无 Chrome。以下验收项必须落到 docs/testing/ 的人工清单,不得在进度记录里写成「通过」:

  • 中文标题在 macOS / Windows / Android 三端的实际字形(T1.1 的核心收益)。
  • coral 在真实屏幕上的观感与明暗切换(T1.2)。
  • 空状态在真机小屏上的首屏可见性(T2.1)。
  • 侧栏抽屉在移动端的手势与焦点行为(T3.1)。