Dark muted surfaces missed WCAG AA; action and tertiary tokens plus a 32-pair contract close that. Hit targets, remaining-count, and Enter-to-send follow the interaction audit without changing visual sizes. Co-authored-by: Cursor <cursoragent@cursor.com>
6.5 KiB
PROGRESS · 客户端交互与 UI 收尾(2026-08-30)
工作树:.worktrees/interaction-20260830
分支:codex/interaction-20260830
基线:任务书写 origin/staging @ ea0fbd44;开工时远端已快进到 95f58512。推送前再 fetch,远端又多了 b43808b0(占用 BUG-444),本轮条目改为 BUG-445–448。按红线始终跟 origin/staging,不是本地 ref。
不要写成 PROGRESS.md。
未改 .gitea/workflows/**。未提升 main。未手写 useCallback / useMemo。未改既有测试断言(composer-isolation-contract 的 import { ChatComposer } 通过把计数组件拆到独立文件保住)。
任务 0 · 深色对比度
两个深色块(prefers-color-scheme 内的 :root:not([data-theme="light"]) 与 :root[data-theme="dark"]):
| token | 旧 | 新 |
|---|---|---|
--color-ink-tertiary |
#928e84 |
#9c988e |
--color-action / --color-focus / --report-accent |
#d4785a |
#d78064 |
#d4785a 在 frontend/src + DESIGN.md 已清零(含四个根边界页)。浅色 #85432f 未动。
32 组(8 字色 × 4 底色)全部 ≥ 4.5:1。最紧两对:tertiary on muted 4.60:1,action on muted 4.51:1。
反向:把 tertiary 改回 #928e84 后合同变红,失败信息为 --color-ink-tertiary (#928e84) on --color-canvas-muted (#30302d) is 4.05:1。已还原。
BUG-445。
任务 1 · 触达尺寸
视觉尺寸未改。浅色夹具 before/after:actions / chips / auth 0 像素差(标题字符串不同,2689 px 只在 h1)。深色夹具除标题外,actions 313 px(tertiary 提亮)、auth 1148 px(action 提亮),chip 字形 0 差。
| 选择器 | 视觉 | 命中(elementFromPoint) | 相邻 |
|---|---|---|---|
.message-actions button ×4 |
26×26,gap 1px | 27×35(CSS 伪元素 27×34;末颗 28×35 为整数探针取整) | gapX 0,不重叠。中心距 27px,44×44 会水平重叠 17px;8px 底边距下 44 高会吃 follow-up 1px |
.chart-nav-chip |
32 高,gap 8px | 约 58×39(inset: -4px → 40 高) |
gapX 2,不重叠。距 44 差 4px |
.auth-links button |
32 高 | 86×41 / 60×41 | 登录页当前无密码通道,节点未挂;夹具 space-between gapX 142。注入探针:浅色 rgb(133,67,47),深色 rgb(215,128,100) = #d78064 |
.select-item |
min-height 44 | 列表 max-height 仍在,条目多 4px,时钟菜单本来就滚 |
— |
.birth-time-skip-button |
min-height 44 | 独占一行 | — |
.report-center-section-heading button |
min-height 44 | 与 h2 间距 24px | — |
止损见 BLOCKED.md。BUG-446。
任务 2 · 字数上限
四处都做了,没有因高度跳动减范围。
| 位置 | 上限 | 阈值(剩余 ≤) | 计数放哪 |
|---|---|---|---|
主对话 ChatComposer |
500(入门称呼 80) | 50 / 8 | 既有 .composer-footer 右侧,<span> 不是 <p>,移动端 > p { display: none } 不误伤 |
ProfileFields 姓名 |
80 | 8 | 标签行 character-remaining-row |
birth-time-guide-turn |
500 | 50 | 标签行 |
BirthTimeUnmatchedClarification |
240 | 24 | 标签行 |
平时不挂载。aria-live="polite" + aria-atomic="true" 只在阈值内。可见数字 aria-hidden,live 文案只有「接近字数上限」和「已达 N 字上限」,避免每字播报。到顶 data-limit → --color-danger。
未登录会话,产品主输入未逐字录屏;行为由 character-remaining-contract 锁死。footer min-height: 44px 已在,计数 white-space: nowrap,出现时不新造一行。
BUG-447。
任务 3 · 发送键
选择:统一 Enter 发送、Shift+Enter 换行,不是保留 ⌘+Enter 只补提示。
理由:主对话和生时校正对话已经是这套;用户把 Enter 习惯带到引导框只会换行。引导框仍有「整理为经历草稿」,不会卡死。漏 isComposing 会让中文选词误发送,比现状严重。rows={3} 的换行改走 Shift+Enter,并在既有 #birth-time-guide-hint 写明。
| 输入框 | 发送 | 换行 | isComposing |
|---|---|---|---|
主对话 handleComposerKeyDown |
Enter | Shift+Enter | 先 return |
生时校正 rectification-agentic-chat |
Enter | Shift+Enter | 同一条件 |
生时引导 birth-time-guide-turn |
Enter(本轮从 ⌘/Ctrl+Enter 改来) | Shift+Enter | 先 return |
headless 无输入法,未做产品里的拼音→Enter 选词实机。合同锁三处源码都带 event.nativeEvent.isComposing。
BUG-448。
命令输出
./node_modules/.bin/tsc --noEmit → 0(约 235s,无诊断)
./node_modules/.bin/eslint 触及文件 → 0 error。既有 warning:page.tsx 两处 react-hooks/exhaustive-deps(restoreConsultationRecovery / profile),globals.css 无 eslint 配置被忽略。不是本轮引入。
./node_modules/.bin/tsx --test tests/*.test.ts → 2307 / 2298 pass / 9 fail / skipped 0 / todo 0。
9 条失败全是本机无 Docker 的数据库类(与任务书 23+10 的环境缺口同类,本环境实际是 9 条 fail、0 skip)。标题:
- admin code functions reject immutable codes, revoked redemption, and roll back on audit failure
- Owner recovery grants only one currently loginable synced identity admin
- local PostgreSQL applies the reviewed business schema and serves authenticated business calls
- redeem security: case-sensitive hashing, rate limiting, idempotency and order ownership
- Better Auth supports shared user OTP/password sessions for admins
- database drops secret refs, generates immutable codes, and invalidates evidence after key rotation
- ingest P0: education kinds, batch confirm, opening focus reuse, precision lock
- PR-4 candidate decisions use server UUIDs, receipt-derived gates and separate acceptance/confirmation
- v9 migration applies on a fresh database and re-applies idempotently
本轮新增合同(dark 32 组、touch-target、character-remaining、composer-ime)在聚焦套件 121/121 中全绿。无产品逻辑新增失败。
./node_modules/.bin/next build → 0。第一次因 worktree 里 node_modules 外链被 Turbopack 拒;npm ci 后:
✓ Compiled successfully in 3.3s
Finished TypeScript in 6.1s
✓ Generating static pages using 7 workers (60/60) in 330ms
5 条既有 skill-package-registry.ts filesystem-tracing warning,未失败。
证据路径(未提交)
.omo/evidence/interaction-20260830/:before/after 浅色深色 PNG、hit-rects.json、login-light、not-found-light。