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
This commit is contained in:
Jesse_Chen
2026-09-16 03:42:28 +00:00
co-authored by Claude Opus 5
parent 5094fd2620
commit a2c1f1fbda
2 changed files with 324 additions and 0 deletions
+1
View File
@@ -156,6 +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 | 待领取 | — |
### 待开的任务书(2026-09-03 审查决定)
@@ -0,0 +1,323 @@
# TASK · C 端界面向 claude.ai 产品界面对齐(2026-09-16
## 基线
- 基线 commit`origin/staging` = `5094fd26207e587037fa366dbe186dd313904236`2026-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/staging``5094fd26`。符号名在括号里,行号漂移时按符号定位。
### E1 · `--font-display` 里没有一个字体是能加载的(→ 中文标题全站落到宋体)
`frontend/src/app/globals.css:100``:root``--font-display`):
```css
--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` 块里**硬编码**,不跟随 `:root`Tailwind 的 `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: 148px``globals.css:129`),每屏固定被吃掉 216px。「分析对象」是内部术语。
### E5 · 空状态是一张营销落地页
`frontend/src/components/starter-home.tsx:47` 起,`.starter-list.starter-workbench` 内依次是:
| 段 | 选择器 | 尺寸 |
|---|---|---|
| hero 卡 | `.starter-hero``globals.css:1910` 起) | `padding: 40px``h1` `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:1803``composer-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-1601``onSelectChart` / `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.tsx``AGENTS.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` 写法加载,然后:
```css
--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` 硬编码不跟随)。
#### T1.3 · 模型选择器与对象选择移进输入框,删掉 `.composer-footer`
- `ChatComposer``frontend/src/components/chat-composer.tsx`)内部加一条底排:左侧放模型选择器与「分析对象」选择器,右侧是发送 / 停止按钮。
- `page.tsx:1815-1816``.composer-footer` 整块删除;`globals.css:1667``.composer-footer` 规则与 `:571-572``:2436` 等相关覆盖一并清理。
- 字数提示改成只在 `characterRemainingVisible()` 为真时插入 DOM,不再占常驻高度。
- `--composer-reserve``globals.css:129``:1825`)按新高度重算。
**验收标准**
- 输入框为空且未接近字数上限时,输入框下方**没有任何常驻元素**。
- 模型选择器与对象选择器的键盘可达性与 `aria-label` 不弱于改前;`npm test` 里既有的 composer / model-selector 测试全绿。
- `ChatComposer` 仍是唯一的聊天输入框实现,校正面(自带 `value` 的那条路径)不受影响。
#### T1.4 · 顶栏瘦身
- `.chat-panel``grid-template-rows` 首行 68px → 46px`globals.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.tsx``openAccountDialog("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`,不要整轮压着不交。
---
## 开工前置命令
```bash
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)。