Files
Jyotisha/docs/tasks/TASK-frontend-interaction-20260830.md
T
Jesse_Chen 8db71aaf81 docs: product-level README, AGENTS.md split into code/reading parts, add CLAUDE.md, move task briefs to docs/tasks
- README.md is now the product/repo front door (architecture, repo map,
  local dev, test tiers, delivery flow, doc map). Engine positioning,
  VedAstro/Codex setup and the oracle/benchmark command reference move
  verbatim to docs/engine/README.md, docs/engine/vedastro-gateway.md and
  docs/benchmark/README.md. Capability badges realigned with the registry
  (91/78/8/0); tests/test_readme_badges.py was red on staging.
- AGENTS.md: Part A (environment truth, delivery, worktrees, record
  placement, bug workflow, growth freeze, frontend red lines, privacy,
  pre-work check, test tiers) and Part B (reading-rigor constraints).
  GitHub issue-tracker/triage boilerplate removed: GitHub is a read-only
  mirror. All strings locked by tests/ are preserved.
- CLAUDE.md added: roles, three working modes, task-brief sections,
  acceptance criteria, session discipline; imports AGENTS.md.
- 50 tracked TASK-*/PROGRESS-* files and 3 never-committed briefs move to
  docs/tasks/ with an index; REPO_LAYOUT.md merged into README.

Docs-only change (no gated path touched).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
2026-09-03 06:56:06 +00:00

250 lines
13 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.
# 任务书 · 客户端交互与 UI 收尾(2026-08-30
基线:`origin/staging` @ `ea0fbd44`
本轮四条任务全部来自一次对着代码实测的审计,每条都附了实测数据。**先读完「硬红线」再动手。**
---
## 硬红线
1. **不得改变任何元素的视觉尺寸。** 任务 1 是扩大热区,不是把按钮画大。改完截图对比,图标和留白必须和改前一模一样。
2. **不得手写 `useCallback` / `useMemo`。**
3. **不得修改既有测试断言** —— 除非该断言锁住的正是本轮要修的缺陷本身;那种情况下必须在断言上方写注释说明「原值是什么、为什么它是错的」,并在 PROGRESS 里单列。
4. 推 staging 前必须 `./node_modules/.bin/tsc --noEmit` 通过。**不要用 `npx tsc`**,本仓库环境下会装到空包 `tsc@2.0.4`
5. 测试数不得低于基线,且 `fail=0``skipped=0`(在有 Docker 的环境里跑)。本机没有 Docker 时会有 23 条数据库/部署类失败 + 10 条 skipped,那是既有环境缺口,不是你引入的 —— 但**必须逐条比对失败清单,确认没有新增**。
6. **浅色和深色两套都要验。** 本仓库已有深色主题(`prefers-color-scheme` + `data-theme`),任何配色改动必须同时在两套下检查对比度。
7. 不得改 `.gitea/workflows/**`。不得在有未提交改动的工作树上切分支。不得自行把 staging 提升到 main。
让步顺序:功能与测试不回归 > 可验证的修复 > 视觉一致 > 代码整洁。
## 开工前置
```bash
git fetch origin --prune
git worktree add -b codex/interaction-20260830 \
../.worktrees/interaction-20260830 origin/staging
```
基线必须是 `origin/staging`,不是任何本地 ref。读 `pre_work_error_ledger.md`,跑 `scripts/pre_work_check.py`,读 `frontend/AGENTS.md`(Next.js 版本与训练数据不同,写代码前先看 `node_modules/next/dist/docs/`)。改前先在 `docs/BUG_HISTORY.md` 检索同类记录。
**下面所有行号只是线索,请按选择器定位**`origin/staging` 上的行号可能有偏移。
---
## 任务 0(P0)· 深色下两处对比度不达标
### 事实
深色调色板的字色逐个对底色实测,两组低于 WCAG AA 4.5:1
| 组合 | 实测 |
| --- | ---: |
| `--color-ink-tertiary` `#928e84` on `--color-canvas-muted` `#30302d` | **4.05:1** ✗ |
| `--color-action` `#d4785a` on `--color-canvas-muted` `#30302d` | **4.18:1** ✗ |
`frontend/tests/dark-theme-contract.test.ts` 现有的对比度断言**只验了「字色 vs `--color-canvas`」**,漏掉了卡片底 `--color-canvas-muted`,所以这两组没被拦住。代码块的语言标签(`.markdown-code-language`)正是 `ink-tertiary` 落在 `.markdown-code` 的暖卡片底上,是已在线上的实例。
### 做法
两个深色块(`@media (prefers-color-scheme: dark)` 内的 `:root:not([data-theme="light"])`,以及 `:root[data-theme="dark"]`**各改一处**,共两处:
```
--color-ink-tertiary: #928e84 → #9c988e
--color-action: #d4785a → #d78064
```
`#d78064` 与原值**色相 15°、饱和度均不变**,只提亮 2.5% 明度 —— 观感仍是同一个黏土橙,不要顺手换色相。
`#d4785a` 在仓库里共 **15 处**,四个根边界页(`error.tsx` / `not-found.tsx` / `forbidden.tsx` / `global-error.tsx`,它们各自内联了一份深色 token)和 `DESIGN.md` 的深色对照表里都有,**必须一起换**,否则边界页的行动色会和主站不一致:
```bash
grep -rn '#d4785a' frontend/src frontend/DESIGN.md
```
`--color-focus` 在深色块里跟随 `--color-action`,一并确认。
### 测试
`dark-theme-contract.test.ts` 里那条对比度断言从「1 种底色」扩成 **8 种字色 × 4 种底色**
- 字色:`color-ink``color-ink-strong``color-ink-secondary``color-ink-tertiary``color-action``color-danger``color-success``color-warning`
- 底色:`color-canvas``color-canvas-soft``color-canvas-muted``color-sidebar-solid`
全部 32 组必须 ≥ 4.5:1,失败信息里要打印出具体是哪一对、实际多少。**不要放宽阈值来让测试通过。**
### 验收
```bash
./node_modules/.bin/tsx --test tests/dark-theme-contract.test.ts
grep -rc '#d4785a' frontend/src frontend/DESIGN.md # 期望全 0
```
反向验证:把 `--color-ink-tertiary` 改回 `#928e84`,测试必须变红。
### 建档
`docs/BUG_HISTORY.md` 新增一条(用户可见的可读性缺陷)。**编号前先确认远端最大号**,别抢号:
```bash
git fetch origin --prune && git show origin/staging:docs/BUG_HISTORY.md \
| grep -o '^## BUG-[0-9]*' | sort -t- -k2 -n | tail -3
```
防复发条写清楚:新增字色/底色 token 时必须同时进这张 32 组矩阵,不得只验阅读面。
---
## 任务 1(P0)· 触达尺寸普遍低于文档承诺的 44px
### 事实
`frontend/DESIGN.md:165` 写着 *"Touch targets are at least 44px"*。实测违反:
| 选择器 | 实际 | 位置 |
| --- | ---: | --- |
| **`.message-actions button`**globals.css:1253 附近) | **26×26px`padding: 0`** | 每条回答下方的赞/踩/复制/重新生成 |
| `.chart-nav-chip`:732 | 32px | 侧栏星盘 chip |
| `.auth-links button`:1657 | 32px`padding: 0` | 登录页链接按钮 |
| `.select-item`:2507 | 40px | 模型选择器每一项 |
| `.birth-time-window-details .birth-time-skip-button`:2450 | 40px | |
| `.report-center-section-heading button` | 40px | |
**26px 那条优先级最高** —— 它是全产品点得最勤的控件,出现在每一条回答下方,且完全没有 padding 撑开热区。
### 做法
**扩大热区,不改视觉尺寸。** 用透明的伪元素把可点区域撑到 44px:
```css
.message-actions button { position: relative; }
.message-actions button::after {
position: absolute;
content: "";
inset: 50% 50% 50% 50%;
translate: -50% -50%;
width: 44px;
height: 44px;
}
```
(上面是示意,具体写法自己定,但必须满足:视觉尺寸不变、热区 ≥44px、不影响相邻元素布局、不遮挡兄弟元素的点击。)
对 40px 那几个,直接把 `min-height` 提到 44px 更简单 —— **但前提是不撑破所在容器**`.select-item` 在下拉里有 `max-height` 限制,改高之后一屏能显示的条目变少,需要目视确认下拉不会变得难用;如果变差,改用伪元素方案。
`.chart-nav-chip` 是 inline-flex 的胶囊,直接提高会改变视觉;用伪元素。
### 止损
- 如果某一处用伪元素会造成热区互相重叠(相邻按钮间距小于 44px 时必然发生),**不要硬撑到 44px**:把该处的实际间距和重叠范围写进 `BLOCKED.md`,只做到不重叠的最大值,并说明还差多少。宁可 36px 不重叠,也不要 44px 互相吃点击。
- `.message-actions` 一行有 4 个按钮,请先量一下它们的间距再决定。
### 验收
- 改前改后各截一张图(浅色 + 深色),确认视觉零差异
- 用浏览器 devtools 量出每个按钮的实际命中矩形,列表给出
- 相邻按钮热区不得重叠 —— 给出实测间距
- 新增合同测试:断言这些选择器要么 `min-height ≥ 44px`,要么带扩大热区的伪元素规则
### 建档
同任务 0,另建一条 BUG_HISTORY。防复发:新增可点元素必须满足 44px 命中区,视觉尺寸小于 44px 时用伪元素补。
---
## 任务 2(P1)· 字数上限是静默的
### 事实
| 位置 | 上限 | 反馈 |
| --- | ---: | --- |
| 主对话输入框(`page.tsx` 传给 `chat-composer.tsx` | 500 | **无** |
| `birth-time-guide-turn.tsx:34` | 500 | **无** |
| `birth-time-choice-question.tsx:113` | 240 | **无** |
| 姓名输入(`page.tsx:909` | 80 | **无** |
只有 `maxLength` 属性。到顶之后继续打字,浏览器直接不接收,界面**毫无反应** —— 这是最难自我诊断的一类问题,用户只会觉得"键盘坏了"。
### 做法
给这几处加接近上限时才出现的计数提示:
- **平时不显示**,剩余字数低于某个阈值(建议 50,短输入按比例调)才出现,避免常态噪声
-`--color-ink-tertiary`;到达上限时才转 `--color-danger`
- 计数区必须 `aria-live="polite"``aria-atomic="true"`,但**只在阈值内挂载**,不要让读屏用户每敲一个字都被播报
- 输入框加 `aria-describedby` 指向计数区
主输入框的计数放哪需要判断:`.composer-footer` 已经存在,优先复用,不要新造一层容器把输入框顶高。
### 止损
如果加计数会让移动端输入区高度变化导致布局跳动,**先只做主对话输入框**,其余三处登记 `BLOCKED.md`。布局跳动比没有计数更糟。
### 验收
- 四处(或已做的那几处)各录一段:从阈值外打到上限,说明何时出现、何时变红
- 读屏验证:阈值外不播报,进入阈值后播报一次,不是每字一次
- 移动端确认输入区高度不跳
---
## 任务 3(P1)· 三个输入框的发送键不一致,且无任何提示
### 事实
| 位置 | 发送方式 |
| --- | --- |
| 主对话输入框(`page.tsx:3701` | **Enter**Shift+Enter 换行),已正确处理 `isComposing` |
| 生时校正对话(`rectification-agentic-chat.tsx:1233` | **Enter**,已处理 `isComposing` |
| `birth-time-guide-turn.tsx:39` | **⌘/Ctrl+Enter** |
第三处不但与前两处相反,界面上**没有一个字**告诉用户。它有可见的「整理为经历草稿」按钮所以不会卡死,但用户在主对话养成的 Enter 习惯到这里只会换行。
### 做法
**统一为 Enter 发送、Shift+Enter 换行**,与前两处一致。改 `birth-time-guide-turn.tsx`
- 必须同时加 `!event.nativeEvent.isComposing` 判断 —— 这是中文输入法的必要条件,前两处都有,第三处因为原本需要修饰键所以没加。**漏了这条会导致中文用户选词时误发送**,比现状严重得多。
- 保留「整理为经历草稿」按钮不动。
那是一个 `rows={3}` 的多行框,改成 Enter 发送后换行只能靠 Shift+Enter。如果你判断这个框的使用场景更需要自由换行(用户在描述一段经历),**可以反过来选择保留 ⌘+Enter 但补上可见提示**(放在既有的 `#birth-time-guide-hint` 里)。两种都可接受,但必须二选一并在 PROGRESS 里写明理由 —— 不允许维持"既不一致又无提示"的现状。
### 验收
- 中文输入法下逐字验证:输入拼音 → 按 Enter 选词 → **不得发送**;选完词再按 Enter → 发送
- 三个输入框的行为写成一张对照表放进 PROGRESS
- 新增合同测试:三处的 `onKeyDown` 必须都带 `isComposing` 判断
---
## 不在本轮范围
- **会话列表虚拟化 / `page.tsx:1212``visibleSessions` 每次 render 重排**:与已修好的消息列表同类,但会话数通常只有几十。**没有渲染基准之前不得动手** —— 需要先按 `frontend/scripts/home-streaming-render-benchmark.mts` 的模式出一份会话列表基准,证明它是热点。红线也禁止手写 `useMemo`
- **代码块语法高亮**:产品已明确不做。
- **会话搜索、编辑自己发出的消息**:不在本轮。
- **15 个无 CSS 规则的死类名**:见 `frontend/tests/class-name-definition-contract.test.ts``knownUnstyled` 白名单,都是有意的空修饰符,不要顺手清理。
---
## 收尾
- PROGRESS 文件名写 `PROGRESS-frontend-interaction-20260830.md`。**不要写成 `PROGRESS.md`** —— 根目录已有受版本控制的 `progress.md`,大小写不敏感文件系统上会互相覆盖。
- 四条任务合成**一次** staging 推送(staging push 触发全量构建+部署,没有路径过滤,不要分多次推)。
- 推送后核对 `https://staging.jyotisha.chat/api/health``.deployment.gitCommit` 等于新 SHA。**流水线是 `validate``publish` → 才派发 `deploy`,全链路约 20 分钟**,不要在十几分钟内就断定部署失败。
- 不自行提升 main。
## 交付物清单
1. 两个深色块各改两处色值,`#d4785a` 全仓 15 处清零
2. `dark-theme-contract.test.ts` 扩成 32 组对比度断言 + 反向验证证据
3. 触达尺寸改动,附改前改后截图(浅色+深色)证明视觉零差异
4. 每个可点元素的实测命中矩形与相邻间距
5. 字数计数提示,附阈值行为录制与读屏播报验证
6. 发送键统一(或补提示)+ 中文输入法选词不误发的逐字验证
7. 三个输入框行为对照表
8. 新增合同测试(触达尺寸、`isComposing`
9. `docs/BUG_HISTORY.md` 两条新条目(编号已对远端确认)
10. `BLOCKED.md`:任何触发止损的项
11. `PROGRESS-frontend-interaction-20260830.md`
12. `tsc --noEmit` / `eslint`0 error/ `tsx --test`(失败清单与基线逐条比对)/ `next build` 四条命令的实际输出