Files
Jyotisha/docs/tasks/PROGRESS-site-button-contrast-20260928.md
T

124 lines
16 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.
# PROGRESS · 全站实心按钮配色与禁用态统一(BUG-1079,2026-09-28)
任务书:`docs/tasks/TASK-site-button-contrast-20260928.md`。执行:Claude 子代理(产品 2026-09-28「请你修复」= 直接执行)。
## 基线
- `origin/staging` = `7db58230`;分支 `codex/site-button-contrast-20260928`,工作树 `.worktrees/site-button-contrast-20260928`。Node 22.14(`/exec-daemon`)。
- 基线实测:`tsc` 0;lint 0 error / 127 warning;`npm test` 4248 / pass 4193 / fail 24 / skip 31 / cancelled 0;`next build` `/` ○ Static;首屏 gzip(rootMainFiles,level 9)130,915 B。
- harness 基线:`rm -rf .next && npm run build` 后只拷这次 build 的 CSS;样本类名取自基线源码(`button.tsx` / `calendar.tsx` / `birth-date-picker.tsx` 的 `HEAD` 版本)。
## 做了什么
- **T1 根因**:删层外 `button { color: inherit; }`(Tailwind preflight 在 `@layer base` 已有)。`button:disabled { cursor: default; opacity: .45; }` 改为 `button:disabled:where(:not([data-slot="button"]))`:权重仍是 (0,1,1),原生 `<button>` 的禁用观感逐字不变;`<Button>` 不再被它压,改由自身工具类决定,基础淡化 `disabled:opacity-50` → `disabled:opacity-45`(与旧全局值一致,保证红线 3)。没有采用「移进 `@layer base`」:那样层外类规则里的 `cursor: pointer` / `opacity` 会反过来赢过禁用规则,改变原生按钮禁用观感。
- **T2 token + 合一**:`:root` 新增 `--color-action-fill` #a9583e / `--color-action-fill-hover` #8f4630 / `--color-on-action-fill` #fbfaf7 / `--color-action-fill-disabled` = `var(--color-canvas-strong)` / `--color-on-action-fill-disabled` = `var(--color-ink-tertiary)`;两处深色块 #8f4a33 / `color-mix(in srgb, #8f4a33 94%, #f2f0ea)` / #f2f0ea + 两个 disabled(`dark-theme-contract` 要求每个 token 有深色值);打印块补浅色值;`@theme inline` 登记五个 token。`button.tsx` 默认变体 = `bg-action-fill text-on-action-fill hover:bg-action-fill-hover disabled:bg-action-fill-disabled disabled:text-on-action-fill-disabled disabled:opacity-100`(twMerge 实测保留 `text-sm`、丢掉基础 `disabled:opacity-45`)。`.button-primary` 底 / 边 / 字改新 token,新增 `.button-primary:disabled`(灰底、opacity 1)。发送 / 停止键 `.composer .composer-row-end > button`、`.composer-stop` 改新 token,新增 `:disabled` 灰;悬停规则拆开:`.composer button:hover` 其它按钮仍 `--color-action-hover`,`.button-primary` 与发送键改 `--color-action-fill-hover`。`.danger-primary` 显式 `color: var(--color-on-dark)`(D5)。`calendar.tsx` 选中 / 区间起止日改 `bg-action-fill text-on-action-fill`;删 `birth-date-picker.tsx` 的 `[&_button[data-selected-single=true]]:text-primary-foreground!`。`--color-primary` 映射未动(D6)。
- **T3**:删 BUG-1078 报告作用域整段与 `.report-center-generate`(CSS + `generate-personal-report-button.tsx`)。`.report-export-presets [aria-pressed="true"]` 保留。
- **T4**:`frontend/DESIGN.md`(token 表加一行、撤回"已知缺口"段、Button 一节写 D1–D3、报告阅读器一节改写)、`docs/BUG_HISTORY.md` BUG-1079、`CHANGELOG.md`、`docs/testing/site-button-contrast-20260928.md`、`BLOCKED.md`、本文件、`docs/tasks/README.md` 状态。
- **harness**(`docs/testing/button-contrast-harness.cjs`):样本改为从源码读类名;增描边 / 幽灵 / 次要 / 危险 / 发送 / 停止 / 日历选中日 / 日期占位 / 报告页 / 悬停样本;支持 `oklab()` 与 `color(srgb …)` 计算值;**默认跳过 admin 专用的 antd reset**(见偏离 2)。
## harness 前后对照(用户页 CSS,无头 Chrome)
| 主题 | 样本 | 改前 文字 / 底 / 不透明度 / 对比 | 改后 文字 / 底 / 不透明度 / 对比 | 变化 |
| --- | --- | --- | --- | --- |
| light | Button 默认(实心) | `#1d1d1f` / `#a9583e` / 1 / 3.33 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | **变** |
| light | Button 默认 · 禁用 | `#979796` / `#d6b1a4` / 0.45 / 1.49 | `#6a6963` / `#e1ded6` / 1 / 4.10 | **变** |
| light | Button 描边 | `#1d1d1f` / `#fbfaf7` / 1 / 16.12 | `#1d1d1f` / `#fbfaf7` / 1 / 16.12 | 相同 |
| light | Button 描边 · 禁用 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | 相同 |
| light | Button 幽灵 | `#1d1d1f` / `#fbfaf7` / 1 / 16.12 | `#1d1d1f` / `#fbfaf7` / 1 / 16.12 | 相同 |
| light | Button 幽灵 · 禁用 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | 相同 |
| light | .button-primary | `#fbfaf7` / `#cc785c` / 1 / 3.14 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | **变** |
| light | .button-primary · 禁用 | `#fbfaf7` / `#e6c0b1` / 0.45 / 1.61 | `#6a6963` / `#e1ded6` / 1 / 4.10 | **变** |
| light | .danger-primary | `#fbfaf7` / `#9a2f2f` / 1 / 7.13 | `#fbfaf7` / `#9a2f2f` / 1 / 7.13 | 相同 |
| light | .danger-primary · 禁用 | `#fbfaf7` / `#cf9f9d` / 0.45 / 2.21 | `#6a6963` / `#e1ded6` / 1 / 4.10 | **变** |
| light | .button-secondary | `#1d1d1f` / `#fbfaf7` / 1 / 16.12 | `#1d1d1f` / `#fbfaf7` / 1 / 16.12 | 相同 |
| light | .button-secondary · 禁用 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | 相同 |
| light | 输入框发送键 | `#fbfaf7` / `#cc785c` / 1 / 3.14 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | **变** |
| light | 输入框发送键 · 禁用 | `#fbfaf7` / `#e6c0b1` / 0.45 / 1.61 | `#6a6963` / `#e1ded6` / 1 / 4.10 | **变** |
| light | 输入框停止键 | `#fbfaf7` / `#cc785c` / 1 / 3.14 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | **变** |
| light | 日历选中日 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | 相同 |
| light | 日期选择器(未选,占位) | `#1d1d1f` / `#fbfaf7` / 1 / 16.12 | `#5f5f59` / `#fbfaf7` / 1 / 6.16 | **变** |
| light | 报告页 Button 默认 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | 相同 |
| light | 报告页 Button 默认 · 禁用 | `#6a6963` / `#e1ded6` / 1 / 4.10 | `#6a6963` / `#e1ded6` / 1 / 4.10 | 相同 |
| light | 报告页 描边 · 禁用 | `#6a6963` / `#e1ded6` / 1 / 4.10 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | **变** |
| light | 报告页页眉「生成报告」· 禁用 | `#6a6963` / `#e1ded6` / 1 / 4.10 | `#979796` / `#fbfaf7` / 0.45 / 2.81 | **变** |
| light | Button 默认 · 悬停 | `#1d1d1f` / `#b97863` / 1 / 4.78 | `#fbfaf7` / `#8f4630` / 1 / 6.52 | **变** |
| light | Button 描边 · 悬停 | `#1d1d1f` / `#ebe9e3` / 1 / 13.86 | `#1d1d1f` / `#ebe9e3` / 1 / 13.86 | 相同 |
| light | .button-primary · 悬停 | `#fbfaf7` / `#8f4630` / 1 / 6.52 | `#fbfaf7` / `#8f4630` / 1 / 6.52 | 相同 |
| light | .danger-primary · 悬停 | `#fbfaf7` / `#8b2d2d` / 1 / 8.02 | `#fbfaf7` / `#8b2d2d` / 1 / 8.02 | 相同 |
| light | 输入框发送键 · 悬停 | `#fbfaf7` / `#8f4630` / 1 / 6.52 | `#fbfaf7` / `#8f4630` / 1 / 6.52 | 相同 |
| light | 日历选中日 · 悬停 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | `#fbfaf7` / `#a9583e` / 1 / 4.85 | 相同 |
| dark | Button 默认(实心) | `#f2f0ea` / `#d78064` / 1 / 2.57 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | **变** |
| dark | Button 默认 · 禁用 | `#82817d` / `#764f41` / 0.45 / 1.83 | `#9c988e` / `#3b3a37` / 1 / 3.95 | **变** |
| dark | Button 描边 | `#f2f0ea` / `#262624` / 1 / 13.30 | `#f2f0ea` / `#262624` / 1 / 13.30 | 相同 |
| dark | Button 描边 · 禁用 | `#82817d` / `#262624` / 0.45 / 3.88 | `#82817d` / `#262624` / 0.45 / 3.88 | 相同 |
| dark | Button 幽灵 | `#f2f0ea` / `#262624` / 1 / 13.30 | `#f2f0ea` / `#262624` / 1 / 13.30 | 相同 |
| dark | Button 幽灵 · 禁用 | `#82817d` / `#262624` / 0.45 / 3.88 | `#82817d` / `#262624` / 0.45 / 3.88 | 相同 |
| dark | .button-primary | `#241f1c` / `#d78064` / 1 / 5.56 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | **变** |
| dark | .button-primary · 禁用 | `#252320` / `#764f41` / 0.45 / 2.20 | `#9c988e` / `#3b3a37` / 1 / 3.95 | **变** |
| dark | .danger-primary | `#241f1c` / `#e08573` / 1 / 6.04 | `#241f1c` / `#e08573` / 1 / 6.04 | 相同 |
| dark | .danger-primary · 禁用 | `#252320` / `#7a5148` / 0.45 / 2.30 | `#9c988e` / `#3b3a37` / 1 / 3.95 | **变** |
| dark | .button-secondary | `#f2f0ea` / `#262624` / 1 / 13.30 | `#f2f0ea` / `#262624` / 1 / 13.30 | 相同 |
| dark | .button-secondary · 禁用 | `#82817d` / `#262624` / 0.45 / 3.88 | `#82817d` / `#262624` / 0.45 / 3.88 | 相同 |
| dark | 输入框发送键 | `#241f1c` / `#d78064` / 1 / 5.56 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | **变** |
| dark | 输入框发送键 · 禁用 | `#252320` / `#764f41` / 0.45 / 2.20 | `#9c988e` / `#3b3a37` / 1 / 3.95 | **变** |
| dark | 输入框停止键 | `#241f1c` / `#d78064` / 1 / 5.56 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | **变** |
| dark | 日历选中日 | `#241f1c` / `#d78064` / 1 / 5.56 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | **变** |
| dark | 日期选择器(未选,占位) | `#f2f0ea` / `#262624` / 1 / 13.30 | `#b3afa4` / `#262624` / 1 / 6.92 | **变** |
| dark | 报告页 Button 默认 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | 相同 |
| dark | 报告页 Button 默认 · 禁用 | `#9c988e` / `#3b3a37` / 1 / 3.95 | `#9c988e` / `#3b3a37` / 1 / 3.95 | 相同 |
| dark | 报告页 描边 · 禁用 | `#9c988e` / `#3b3a37` / 1 / 3.95 | `#82817d` / `#262624` / 0.45 / 3.88 | **变** |
| dark | 报告页页眉「生成报告」· 禁用 | `#9c988e` / `#3b3a37` / 1 / 3.95 | `#82817d` / `#262624` / 0.45 / 3.88 | **变** |
| dark | Button 默认 · 悬停 | `#f2f0ea` / `#b46e57` / 1 / 3.48 | `#f2f0ea` / `#95543e` / 1 / 5.10 | **变** |
| dark | Button 描边 · 悬停 | `#f2f0ea` / `#30302d` / 1 / 11.62 | `#f2f0ea` / `#30302d` / 1 / 11.62 | 相同 |
| dark | .button-primary · 悬停 | `#241f1c` / `#e59273` / 1 / 6.75 | `#f2f0ea` / `#95543e` / 1 / 5.10 | **变** |
| dark | .danger-primary · 悬停 | `#241f1c` / `#e29281` / 1 / 6.72 | `#241f1c` / `#e29281` / 1 / 6.72 | 相同 |
| dark | 输入框发送键 · 悬停 | `#241f1c` / `#e59273` / 1 / 6.75 | `#f2f0ea` / `#95543e` / 1 / 5.10 | **变** |
| dark | 日历选中日 · 悬停 | `#241f1c` / `#d78064` / 1 / 5.56 | `#f2f0ea` / `#8f4a33` / 1 / 5.77 | **变** |
结论:实心按钮(默认 `<Button>`、`.button-primary`、发送 / 停止键、日历选中日)两主题全部 ≥ 4.5(浅 4.85、深 5.77;悬停浅 6.52、深 5.10);实心禁用 opacity 1 灰底;`.danger-primary` 7.13 / 6.04 不变;描边、幽灵、次要及其禁用、悬停**逐项相同**(红线 3)。
## 外观会变的非实心按钮 / 原生按钮
- 日期选择器未选时的占位文字:`data-[empty=true]:text-muted-foreground` 以前被层外 `color: inherit` 压成正文色,现在生效:浅 #1d1d1f → #5f5f59(6.16),深 #f2f0ea → #b3afa4(6.92)。这是组件本来的设计。
- `<Button>` 描边 / 幽灵的 `hover:text-foreground` 现在生效:只在按钮所在容器文字色不是 `--color-ink` 时才看得出(harness 标准样本前后相同)。
- 报告页的描边按钮禁用态(页眉「生成报告」提交中、原始附录下载忙时):BUG-1078 期间是灰底,现在回到与全站描边按钮一致的 45% 淡化(浅 2.81、深 3.88)。按任务书 D3(非实心禁用不在范围)+ D4(删局部覆盖)执行;真机清单第 12 条已写明。
- 原生 `<button>` 带 Tailwind 文字色工具类的:AST 扫描 + `grep` 除上面的日期占位外没有(其余 `text-primary` / `text-danger` 都在图标上,不是按钮)。其它 `!` 补丁:TSX 里只有日期选择器那一处,已删;`globals.css` 的 `!important` 均与按钮文字色无关(toast、候选徽章等)。
## 改动的既有断言(原值 / 新值 / 原因)
| 文件 · 断言 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `session-conversation-layout.test.ts` 停止键底色 | `.composer .composer-stop` `background: var(--color-action-strong)` | `var(--color-action-fill)` | BUG-1079 D1:实心按钮一种底色 |
| 同上 停止键悬停 | `var(--color-action-hover)` | `var(--color-action-fill-hover)` | 同上 |
| `report-buttons-scope-20260928.test.ts` 「the shared button keeps its variants, disabled fade and data-slot hook」 | 默认变体 `bg-primary text-primary-foreground hover:bg-primary/80`、`disabled:opacity-50` | 并入「the shared Button, .button-primary, the composer controls and the calendar read the same fill」:默认变体为新 token、`disabled:opacity-45` | 上一单红线「button.tsx 不改」只针对报告单;本单 D1 / 红线 6 允许改 |
| 同文件 「the theme tokens the fix reads are unchanged」 | 断言报告修复读的旧 token 原值 | 「one solid-button token pair per theme, readable at 4.5:1 or better」:断言新 token 对三处主题块的值与对比度,并锁 `--color-primary` 映射与 `#cc785c` | 报告局部修复已删,锁的对象换成全站 token |
| 同文件 「button overrides exist only inside the report scope」 | 所有 `[data-slot="button"]` 覆盖必须在报告作用域 | 「the BUG-1078 report-only override is gone; …」:不得再有 `--report-button-*`、`.report-center-generate`、`[class~="bg-primary"]` | D4 删局部覆盖 |
| 同文件 「solid report buttons read at least 4.5:1 …」 | 断言 `\nbutton { color: inherit; }` **仍存在** | 「the unlayered element rules no longer override button utilities」:断言层外 `button { color` 与 `button:disabled {` **不存在**,原生按钮淡化规则存在 | 根因本单修掉 |
| 同文件 「the selected export preset no longer uses …」 | 套餐选中态不用 action-soft | 合并进上面「… gone; the selected export preset stays neutral」,断言不变 | 保留 |
| 同文件 「the header 「生成报告」 button carries the report hook …」 | 必须带 `report-center-generate` | 必须**不**带(同上条) | D4 |
测试名:该文件 6 条旧名全部移除,换成 6 条新名(上表列出的 4 条,外加 `solid disabled buttons are grey at full opacity, never a faded clay`、`.danger-primary keeps its own label colour instead of taking the new fill ink`),文件内条数与全量总数都不变。
## 验证
| 项 | 基线 `7db58230` | 本分支 | 结论 |
| --- | --- | --- | --- |
| `tsc --noEmit` | 0 | 0 | 通过 |
| `npm run lint` | 0 error / 127 warning | 0 error / 127 warning | 通过 |
| `npm test` | 4248 / 4193 / 24 / 31 / 0 | 4248 / 4193 / 24 / 31 / 0 | 通过:24 条失败 `diff` 为空,均为 DB / 部署 / staging 套件(无 Docker) |
| 读 DESIGN.md 的 7 个测试 + 本单相关(改文档后重跑) | — | 97 / 97 | 通过 |
| `next build` 路由表 | — | 与基线逐行相同,`/` ○ Static | 通过 |
| 首屏 gzip | 130,915 B | 130,915 B | 0.00% |
## 偏离与发现
1. 禁用规则用 `:where(:not([data-slot="button"]))` 而非移进 `@layer base`(理由见 T1),任务书红线 1 的「或改成不压工具类的写法」允许。
2. **扫描误计**:后台管理路由(`src/app/admin/layout.tsx`)只加载 `antd/dist/reset.css` + `admin.css` + 字体,**不加载 `globals.css`**。所以任务书里「后台管理 52 个黑字深红」不成立:它们没有任何 Tailwind 样式,是 antd reset 下的朴素按钮,本单修复对它们无影响。harness 起初把 antd reset 一起加载,才测出"改后仍 3.33"的假象;现默认跳过(`HARNESS_INCLUDE_ADMIN=1` 可带上)。写入 `BLOCKED.md` 待产品决定。
3. 同理的另一条层外规则 `button, input, textarea, select { font: inherit; }` 压掉 `<Button>` 的 `text-sm`(harness `fontSize` 列:`<Button>` 16px、`.button-primary` 14px)。不在任务书范围,未改,写入 `BLOCKED.md`。
## 环境缺口
- 无 Docker:24 条 DB / 部署套件同基线。
- 无登录态真机:页面级走查按 `docs/testing/site-button-contrast-20260928.md`。
- 未推送、未部署。