docs(tasks): site-wide solid button contrast and disabled state (BUG-1079), with contrast harness
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b3b5f0e184
commit
7db58230ad
@@ -247,6 +247,7 @@
|
||||
| `TASK-consult-answer-the-question-20260927.md` | `PROGRESS-consult-answer-the-question-20260927.md` | **普通对话回答问的那句话**:反差只写盘上、删「按我的理解答」、禁写动机句;追问轮 ≤ 200 字直接回答不重开四标题骨架(服务端按「本会话已有解读」切指令,不用正则);清单是核对项不是段落大纲;步骤栏「先抓住你真正在问的事」改「先回答你问的这件事」 | 已实现待验收(Claude 子代理直接执行) | 分支 `codex/consult-answer-the-question-20260927`(未推送);基线 `2ce3c216`;BUG-1070~1073;Skill 不 bump;真机清单 `docs/testing/consult-answer-the-question-20260927.md` |
|
||||
| `TASK-consult-first-frame-and-pacing-20260928.md` | `PROGRESS-consult-first-frame-and-pacing-20260928.md` | **普通对话发出即有反馈、正文按打字节奏放出**:客户端分类前静默(BUG-976 副作用)+ 服务端分类后才开流 → 发出后一两秒空白;服务端 160 字截留 + 客户端 settle 一帧全放 → 追问轮正文一下全出。D1 首帧「收到,正在看你的问题…」、D2 先建流、D3 每帧上限 + 限时收尾、D4 160 字阈值不动 | 已验收并部署 staging(真机欠) | 分支 `codex/consult-first-frame-and-pacing-20260928`(未推送);进度 `PROGRESS-consult-first-frame-and-pacing-20260928.md`;BUG-1074 / BUG-1075 |
|
||||
| `TASK-chart-types-and-report-buttons-20260928.md` | `PROGRESS-chart-types-and-report-buttons-20260928.md`、`PROGRESS-ayanamsa-report-buttons-20260928.md` | **星盘盘型扩充 + 表格随盘换 + 七政隐藏 + 岁差 6 项 + 我的报告按钮配色**:分盘表一直显示 D1 数据(BUG-1076);盘型条 + 「星盘类型」面板共 23 种(加 月亮盘 / Bhava / 行运 / D40 / D45 / D60);Bhava 端点缺 MC 静默等宫(BUG-1077);报告深色主按钮黑字深红、禁用半透明(BUG-1078,只改报告范围) | 已验收、已部署 staging `6a4e312c`(真机欠) | `af27709d`…`4189750c`(子代理实现 + Claude 验收补丁);BUG-1076~1078;含迁移 `20260928010000_profile_ayanamsa_six` |
|
||||
| `TASK-site-button-contrast-20260928.md` | `PROGRESS-site-button-contrast-20260928.md` | **全站实心按钮配色与禁用态统一**:层外 `button { color: inherit }` 压掉 Tailwind 文字色(61 个 `<Button>` 浅 3.33 / 深 2.57),`.button-primary` 30 个浅色 3.14,全部禁用态 45% 透明;产品选 A:全站用报告页配色(浅 4.85 / 深 5.77),推翻 DESIGN「已知缺口」;禁用改灰;删 BUG-1078 局部覆盖(BUG-1079) | 待领取 | — |
|
||||
| `TASK-cend-ui-claude-alignment-20260916.md` | `PROGRESS-cend-ui-r1/r2/r3-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 段 737–738 | 待领取 | — |
|
||||
| `TASK-cend-surfaces-claude-alignment-20260916.md` | `PROGRESS-cend-shell-20260916.md`、`PROGRESS-cend-report-20260916.md`、`PROGRESS-cend-rectification-20260916.md`、`PROGRESS-cend-chart-eph-20260916.md` | **次级页面对齐(上一单的续篇,R4→R5/R6,R7、R8 可并行)**:星盘 `/chart`、星历 `/ephemeris`、报告 `/reports` **各是脱离 app 外壳的独立全屏页**,顶部只有一个「返回对话」链接、侧栏整个消失,且三家各写了一套一模一样的 `*-shell`/`*-topbar`/`*-hero` 骨架——与上一单 E5 同根因(营销站 band 结构被套到产品界面)。**R4** 抽只读导航外壳 `AppNavRail`(只用现成的 `GET /api/sessions` + `GET /api/account`,会话行走 `sessionHref` 跳 `/?c=<uuid>`;**刻意不带**重命名/删除/收藏/归档——那套连着 `Home()` 的乐观更新与回滚,搬过来会撞 useState 增长门禁)。**R5** 星盘五 tab 下划线化 + 参数合表 + 行星表横向滚动;星历日期导航改 `‹ 日期 ›`。**R6** 报告中心卡片网格改行式列表;阅读页加常驻目录。**R7** 生时校正把可信区间从盘面板标题行提成常驻条(窄屏 `.is-compact` 下盘面板是 overlay,现在默认看不到区间),五个 `technique-audit` 折叠块收成两段。**R8** 设置内容区收窄(880px 弹窗里表单铺了 690px)、套餐卡三修饰符收敛成两态。**已解锁**:原挡路的设置单已于 `111b4a84`(BUG-698)合入。**两条不得回退**:BUG-698 的 `@supports (height: 1dvh)` 写法(重复声明回退会被 Lightning CSS 折叠)、BUG-616/617 的报告盘面 grid 实现。默认不占 BUG 号 | **R4–R8 全部已实现并验收合入** | R4:抽出 `AppNavRail`(只读,两个 GET,零写操作)+ `SecondaryShell`,三个次级页并入 app 外壳并删掉各自的 shell/topbar/hero;四个路由渲染标记**完全不变**(`/` `/chart` `/ephemeris` 仍 Static);CSS gzip −0.25%。`/reports/[reportId]` 留给 R6 与目录一起做。两处自身健壮性问题被测试抓到:`usePathname()` 可为 null、`fetch` 可能不存在。差点弄丢 BUG-717 的 eyebrow 文案(已放回)。R8:表单分区收窄到 440px(列表分区不变)、套餐卡三修饰符收敛成互斥的 `is-current` / `is-recommended`,`--highlighted` 删除改为滚动定位;手机端 `order:-1` 改挂 `[data-plan-alias]`(版位不是状态)。测试 3350→3354(净增 4),失败清单与基线逐条一致;`/` 仍 Static;我的干净构建实测 CSS gzip −3 字节。**遗留待产品拍板**:`?plan=` 深链现在完全没有视觉指向,只有滚动位置。R6:报告中心卡片网格改行式列表、阅读页并入外壳并把目录挪到右侧常驻。**任务书 E10 过期**——目录在 `cfcd369d` 就已存在,本轮是挪位置定稿而非从零加。挂外壳带出一个真实打印风险已处理:`.chat-app`/`.chat-panel` 是 `height:100%;overflow:hidden`,裸 `window.print()` 会把九节报告裁成一页,阅读页因此多挂一条只在挂载期生效的 print 样式解锁外壳。「生成中的分节进度」做不了——`REPORT_LIST_COLUMNS` 不返回节数,按 VOICE.md 不许前端编。R7:区间常驻条与盘面折叠收敛。**任务书 E9 也不准确**——对话区顶部早有常驻条 `RectificationTimeline` 且窄屏可见,真正只在盘面标题行的是**代表分钟**;因此没另造第二条,在既有条上补齐代表分钟与已答题数(与盘面同一次 `workingRectificationTime()` 调用)。折叠块实际是 **8 个**不是 5 个。**触发让步顺序第 5 条**:收窄进度未做——服务端无该字段,且 `candidate_range` 会放宽(BUG-572),前端相减会把一次放宽报成收窄,已写进 `BLOCKED.md`。顺带修掉一个**静默失效的旧断言**(`slice(indexOf(A), indexOf(B))` 在 B 改名后变成几乎整份文件,四条 `doesNotMatch` 假通过)|
|
||||
| — (产品口头拍板,无任务书) | `PROGRESS-chart-glyphs-20260918.md` | **星盘改用行星符号,度数进行星表(非 Bug,无 BUG 号)**:北印星盘(星盘页 / 校正右栏 / 我的报告三处共用 `VedicChartSvg`)宫内不再画「水 19°」「罗逆 29°」这类文本——一宫三颗星就叠三行、字号压到 8。改成九个彩色符号(☉☽☿♀♂♃♄☊☋),每行最多 3 个、逆行改符号下同色横线、第 1 宫加 `As`、盘下两行图例。颜色只认星不表吉凶,罗计共用中性灰,`--color-planet-*` 九个 token 在 `:root` 与两个深色块各一份;颜色挂 `is-*` 修饰类而非行内 `style`(报告标记合同禁 `style=`)。符号字体是 vendored 的 9 字形 Noto Sans Symbols 子集(OFL,1.5 KB),`--font-planet-glyphs`,♀♂ 带 U+FE0E 防 iOS 画成表情。星盘页行星表四列扩到八列(行星 / 黄道经度 / 速度 / 宫位 / 星宿 / 宿主 / pada / 状态),度数精确到分,首行是上升(`vedic.ascendant` 新字段)。数据来源:`toNorthIndianChart()` 加结构化 `occupantGlyphs`;校正与报告两条路线的模型只有引擎/golden 显示文本,按首字解析并有测试锁住。**星宿未译中文**——仓库无对照表,印度 27 宿与中国二十八宿不同源,不自造 | 已实现,待验收 | `codex/chart-glyphs-20260918`:tsc 0 / lint 0 error(118 warning 不变)/ npm test 3471→3484 条、36 红与基线 `84b293fb` 逐条相同、0 新红 / `○ /` Static / 首屏 JS gzip 1,450,774→1,455,017(+0.29%),CSS gzip +2.23%(构成见 PROGRESS §2)。真机走查欠(iOS 符号呈现、深浅两主题配色、375px 横滚、打印) |
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# TASK · 全站实心按钮配色与禁用态统一(2026-09-28)
|
||||
|
||||
## 基线
|
||||
|
||||
- `origin/staging` = `b3b5f0e1`(2026-09-28)。开工时 `git fetch origin --prune`,以最新 `origin/staging` 为基线,实际 SHA 写进 PROGRESS。
|
||||
- 分支 `codex/site-button-contrast-20260928`,工作树 `.worktrees/site-button-contrast-20260928`。
|
||||
- 进度记录 `docs/tasks/PROGRESS-site-button-contrast-20260928.md`。
|
||||
- 前置:`TASK-chart-types-and-report-buttons-20260928`(BUG-1078,报告页局部修复)已部署 staging `6a4e312c`。本单**取代**它的局部覆盖,改成全站一处定义。同日其它任务书与本单无文件交集;`frontend/src/app/globals.css` 只动本单列出的规则。
|
||||
|
||||
## 事故实证(Claude 2026-09-28 全站扫描)
|
||||
|
||||
方法:TypeScript AST 列出 `frontend/src` 里全部 213 个 `<Button>` / `<button>`,再把 staging 线上实际下发的 CSS(`/_next/static/chunks/*.css`)装进无头 Chrome,深浅两种主题逐一读计算样式、按叠加后的颜色算对比度。脚本已随本单提交:`docs/testing/button-contrast-harness.cjs`。
|
||||
|
||||
| 样本 | 浅色 文字 / 底 / 对比 | 深色 文字 / 底 / 对比 |
|
||||
| --- | --- | --- |
|
||||
| `<Button>` 默认实心 | `#1d1d1f` / `#a9583e` / **3.33** | `#f2f0ea` / `#d78064` / **2.57** |
|
||||
| `<Button>` 默认 · 禁用 | 45% 透明 / **1.49** | 45% 透明 / **1.83** |
|
||||
| `.button-primary` | `#fbfaf7` / `#cc785c` / **3.14** | `#241f1c` / `#d78064` / 5.56 |
|
||||
| `.button-primary` · 禁用 | 45% 透明 / **1.61** | 45% 透明 / **2.20** |
|
||||
| `.danger-primary` | `#fbfaf7` / `#9a2f2f` / 7.13 | `#241f1c` / `#e08573` / 6.04 |
|
||||
| 输入框发送键 | `#fbfaf7` / `#cc785c` / 3.14 | `#241f1c` / `#d78064` / 5.56 |
|
||||
| 报告页 `<Button>`(BUG-1078 已修) | `#fbfaf7` / `#a9583e` / 4.85 | `#f2f0ea` / `#8f4a33` / 5.77 |
|
||||
| 描边 / 次要按钮 | 16.12 | 13.30 |
|
||||
|
||||
受影响范围:
|
||||
|
||||
- **实心 `<Button>`,渲染为真 `<button>` 的共 63 个**(渲染为链接 `<a>` 的不受影响),其中报告页 2 个已由 BUG-1078 修好,**未修 61 个**:后台管理 52、点数 / 订阅面板 3、星历 2、生时校正对话 2、输入框发送 / 停止键 2(这两个另有 `.composer` 层外规则上色,见下)。日期选择器选中日(`calendar.tsx` 的 `bg-primary text-primary-foreground`,`birth-date-picker.tsx` 用 `!` 强行救回了一处)同病。
|
||||
- **`.button-primary` 共 30 个**:登录 7、首次引导 3、人物档案 3(含 2 个 `.danger-primary`)、首页 2、生时校正各卡 11、兑换码付费墙 2、星盘资料表单 1、首页错误屏 1。
|
||||
- **禁用态**:全部 213 个按钮都被 `button:disabled { opacity: .45 }` 整体淡化。
|
||||
- 其它自定义按钮类(`.button-secondary`、会话列表、Tab、chip 等)逐条算过,均 ≥ 4.5。
|
||||
|
||||
## 根因(按符号定位,基线 `b3b5f0e1`)
|
||||
|
||||
1. `frontend/src/app/globals.css` 顶部元素规则 `button { color: inherit; }`、`button:disabled { cursor: default; opacity: .45; }` **不在任何 `@layer` 里**。Tailwind v4 的工具类(`text-primary-foreground`、`disabled:opacity-50`)都在 `@layer utilities`;层外规则一律压过层内规则,与选择器权重无关。所以 `<Button>` 的文字色永远是继承来的正文色:浅色是近黑,深色是近白。Tailwind 预检(preflight)在 `@layer base` 里本来就给 `button` 设了 `color: inherit`,这条层外规则是重复的。
|
||||
2. `.button-primary` 与发送键用 `--color-action-strong`(`#cc785c`)作底,文字用 `--color-on-dark`。深色主题把 `--color-on-dark` 翻成 `#241f1c`,于是深色下是深字配珊瑚底,浅色下是白字配浅珊瑚(3.14)。`frontend/DESIGN.md`「Where the action color appears」写明这是产品曾接受的已知缺口("looks like Claude")。
|
||||
3. 两套实心按钮各自取色:`<Button>` 走 `--color-primary`(= `--color-action`),`.button-primary` 走 `--color-action-strong`。同一个"主操作"在不同页面是两种颜色。
|
||||
4. BUG-1078 只在报告页作用域内覆盖了颜色(`globals.css` 里 `:is(.report-center-message, …) [data-slot="button"]…` 那一段 + `.report-center-generate`),根因 1 没动。
|
||||
|
||||
## 决策记录(产品 2026-09-28 拍板)
|
||||
|
||||
- **D1 全站一种实心主按钮(产品选 A)**,与报告页 BUG-1078 已上线的配色相同:
|
||||
- 浅色:底 `#a9583e`(`--color-action`)+ 字 `#fbfaf7`,4.85:1;悬停 `--color-action-hover`(`#8f4630`)。
|
||||
- 深色:底 `#8f4a33`(`--color-action-on-dark`)+ 字 `#f2f0ea`(`--color-ink`),5.77:1;悬停与报告页同法(向 ink 混 6%)。
|
||||
- 适用:`<Button>` 默认变体、`.button-primary`、输入框发送 / 停止键、日期选择器选中日。
|
||||
- **D2 推翻 DESIGN.md 的"已知缺口"**:`.button-primary` 不再用 `#cc785c` 珊瑚底。代价是登录、首次引导按钮深一档,没那么像 Claude,产品接受。`#cc785c` 在**非按钮**处照旧:会话选中竖条、焦点环、打字光标、单选圆点、两处头像圆。
|
||||
- **D3 实心按钮禁用态 = 中性灰**:底 `--color-canvas-strong`、字 `--color-ink-tertiary`、不透明(opacity 1)。适用于 D1 的全部实心按钮与 `.danger-primary`。**描边、幽灵、次要、图标、chip 等非实心按钮的禁用淡化不在本单范围**,保持现状。
|
||||
- **D4 删掉 BUG-1078 的报告页局部覆盖**:一处定义、全站生效。报告页观感不变(数值与 D1 相同)。
|
||||
- **D5 `.danger-primary` 启用态保持现在的对比度**(浅 7.13、深 6.04):`.button-primary` 改色后它不得被连带改成浅字配浅红,需要时给它单独写文字色。
|
||||
- **D6 不重映射 `--color-primary`**:它还被 `text-primary` 用在报告页的加载圈和图标上,深色下改成 `#8f4a33` 会让这些图标对比度掉到约 2.3。新增一对只给按钮用的 token(建议名 `--color-action-fill` / `--color-on-action-fill`,深浅各一组),`<Button>` 默认变体、`.button-primary`、发送键、日历选中日都引用它。
|
||||
|
||||
## 硬红线
|
||||
|
||||
1. 修根因:`button { color: inherit; }` 删掉(preflight 已有),`button:disabled` 那条移进 `@layer base`,或改成不压工具类的写法;不得在各页面再写局部覆盖来绕开。
|
||||
2. 不改 `--color-primary` / `--color-primary-foreground` 的映射(D6);不改 `#cc785c` 的非按钮用途(D2)。
|
||||
3. 非实心按钮的外观与禁用态不变:用 harness 前后对比描边、次要、幽灵的数值,必须完全相同。
|
||||
4. `.danger-primary` 两种主题 ≥ 4.5:1。
|
||||
5. 每个 D1 实心按钮两种主题 ≥ 4.5:1;禁用态 opacity = 1。
|
||||
6. `components/ui/button.tsx` 可以改(上一单的红线只针对报告单),但变体名和 props 不变,调用方零改动。
|
||||
7. `frontend/src/app/page.tsx` 不碰;`/` 保持 `○ Static`;首屏 gzip ±2%。
|
||||
8. 改既有断言写「原值 / 新值 / 原因」(`report-buttons-scope-20260928.test.ts` 必然要改);测试总数不低于基线。
|
||||
9. 不升级依赖;harness 不进 `package.json`,只用本机全局 playwright-core + Chrome。
|
||||
|
||||
## 任务分解
|
||||
|
||||
### T1 根因:层外元素规则
|
||||
|
||||
- 处理 `globals.css` 顶部的 `button { color: inherit; }`、`button:disabled { … }`(见红线 1)。
|
||||
- 源码合同测试:`globals.css` 里不存在层外的 `button { color` 与层外的 `button:disabled`。
|
||||
- **验收**:harness 显示 `<Button>` 默认变体文字色 = `text-primary-foreground` 解析值;描边 / 次要 / 幽灵数值与基线逐项相同。
|
||||
|
||||
### T2 按钮专用 token + 两套实心按钮合一
|
||||
|
||||
- 新增 D6 的 token 对(`:root`、`prefers-color-scheme: dark` 且非 `data-theme=light`、`[data-theme=dark]` 三处,照现有主题写法)。
|
||||
- `button.tsx` 默认变体改用新 token(含 hover),禁用态按 D3。
|
||||
- `.button-primary`、`.composer .composer-row-end > button`、`.composer .composer-stop` 改用新 token,禁用态按 D3。
|
||||
- `.danger-primary` 按 D5 保持对比度,禁用态按 D3。
|
||||
- 日期选择器选中日走新 token,删掉 `birth-date-picker.tsx` 里的 `!` 强制补丁(根因修了就不需要)。
|
||||
- **验收**:harness 全表 D1 项两种主题 ≥ 4.5,禁用态 opacity 1、灰底;数值写进 PROGRESS。
|
||||
|
||||
### T3 删 BUG-1078 局部覆盖
|
||||
|
||||
- 删掉报告页作用域的颜色与禁用规则、`.report-center-generate` 类(`generate-personal-report-button.tsx` 里的 className 一并去掉)。报告导出套餐选中态(`.report-export-presets [aria-pressed="true"]`)是另一回事,保留。
|
||||
- `report-buttons-scope-20260928.test.ts` 改为全站合同,三栏说明。
|
||||
- **验收**:报告页 harness 数值与改前相同(4.85 / 5.77,禁用灰)。
|
||||
|
||||
### T4 文档
|
||||
|
||||
- `frontend/DESIGN.md`:「Where the action color appears」删掉"已知缺口"段,改写两级规则(按钮底 = `--color-action-fill`;`#cc785c` 只留非按钮用途);「Button」一节写 D1–D3。
|
||||
- `docs/BUG_HISTORY.md`:**BUG-1079**(全站实心按钮文字被层外 `button { color: inherit }` 压掉 + `.button-primary` 低对比 + 禁用半透明)。关联 BUG-1078,写明 1078 只修了报告作用域、根因未动,所以同病在其它 60 多处未拦住。
|
||||
- `CHANGELOG.md` 一条(Skill 不 bump)。
|
||||
- `docs/testing/site-button-contrast-20260928.md`:真机清单,浅 / 深各走一遍:登录、首次引导、点数与订阅面板、兑换码、人物档案(含删除确认)、星盘资料保存、星历、生时校正卡片、输入框发送 / 停止 / 排队、日期选择器、我的报告导出;每处看"文字清楚、禁用是灰不是淡红"。
|
||||
|
||||
## 让步顺序
|
||||
|
||||
1. 后台管理页(`components/admin/*`)的真机走查可以只抽 3 页,其余靠 harness 与合同测试。
|
||||
2. 日期选择器选中日若牵动 `calendar.tsx` 太多,保留 `birth-date-picker.tsx` 的补丁,只在 PROGRESS 说明。
|
||||
|
||||
T1、T2 的 `<Button>` / `.button-primary` / 禁用灰不让。
|
||||
|
||||
## 开工前置命令
|
||||
|
||||
```bash
|
||||
cd /workspace/Jyotisha && git status -sb | head -1
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/site-button-contrast-20260928 .worktrees/site-button-contrast-20260928 origin/staging
|
||||
cd .worktrees/site-button-contrast-20260928
|
||||
grep -oE "^## BUG-[0-9]+" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 BUG-1078
|
||||
grep -n "BUG-1078\|button-primary\|opacity: .45" docs/BUG_HISTORY.md | head
|
||||
cd frontend && export PATH=/exec-daemon:$PATH && npm ci && ./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -6 # 记基线与失败名单
|
||||
npm run build && mkdir -p /tmp/btn-base && cp .next/static/chunks/*.css /tmp/btn-base/ && node ../docs/testing/button-contrast-harness.cjs /tmp/btn-base # 基线数值
|
||||
```
|
||||
|
||||
harness 只读**刚 build 出来**的 CSS。09-28 扫描时一份过期的 `.next` 曾让已上线的修复看起来不存在。
|
||||
|
||||
## BUG 编号
|
||||
|
||||
起点 **BUG-1079**(开工时核对 `docs/BUG_HISTORY.md` 最大号为 1078)。
|
||||
|
||||
## 交付
|
||||
|
||||
- 代码 + 文档同批一次快进推 `git push origin HEAD:staging`,核对远端 SHA;门禁绿后核对 `/api/health` 的 `deployment.gitCommit`,再用 harness 对 staging 线上 CSS 复测一遍,数值贴进 PROGRESS。
|
||||
- 不提升 `main`、不改 workflow。
|
||||
@@ -0,0 +1,59 @@
|
||||
// Button contrast harness (TASK-site-button-contrast-20260928).
|
||||
// Renders sample buttons against the real built CSS in headless Chrome, light
|
||||
// and dark, and prints text / background / opacity / contrast per sample.
|
||||
// Not a repo dependency: uses the machine's global playwright-core + Chrome.
|
||||
//
|
||||
// Usage:
|
||||
// mkdir -p /tmp/btn && cp frontend/.next/static/chunks/*.css /tmp/btn/ # or download staging's CSS:
|
||||
// for c in $(curl -s https://staging.jyotisha.chat/login | grep -o '/_next/static/chunks/[^"]*\.css' | sort -u); do curl -s "https://staging.jyotisha.chat$c" -o /tmp/btn/$(basename $c); done
|
||||
// node docs/testing/button-contrast-harness.cjs /tmp/btn
|
||||
//
|
||||
// Only copy a *fresh* build's CSS: a stale .next once hid a live fix during
|
||||
// the 2026-09-28 scan. Update `base` / `V` below if button.tsx classes change.
|
||||
const { chromium } = require(process.env.PLAYWRIGHT_CORE || "/usr/local/lib/node_modules/playwright-core");
|
||||
const fs = require("fs"), path = require("path");
|
||||
const dir = process.argv[2];
|
||||
const cssFiles = fs.readdirSync(dir).filter(f => f.endsWith(".css"));
|
||||
const base = "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-[background-color,border-color,color,box-shadow,transform] duration-150 ease-out outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:scale-[0.96] disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 h-11 gap-2 px-4";
|
||||
const V = { default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: "border-border bg-background hover:bg-muted hover:text-foreground", ghost: "hover:bg-muted hover:text-foreground" };
|
||||
const samples = [
|
||||
["Button 默认(实心)", `<button data-slot="button" class="${base} ${V.default}">导出</button>`],
|
||||
["Button 默认 · 禁用", `<button data-slot="button" disabled class="${base} ${V.default}">导出</button>`],
|
||||
["Button 描边", `<button data-slot="button" class="${base} ${V.outline}">取消</button>`],
|
||||
["Button 描边 · 禁用", `<button data-slot="button" disabled class="${base} ${V.outline}">取消</button>`],
|
||||
[".button-primary", `<button class="button-primary">保存</button>`],
|
||||
[".button-primary · 禁用", `<button class="button-primary" disabled>保存</button>`],
|
||||
[".danger-primary", `<button class="button-primary danger-primary">删除</button>`],
|
||||
[".danger-primary · 禁用", `<button class="button-primary danger-primary" disabled>删除</button>`],
|
||||
[".button-secondary", `<button class="button-secondary">返回</button>`],
|
||||
["输入框发送键", `<div class="composer"><div class="composer-row-end"><button>↑</button></div></div>`],
|
||||
["输入框发送键 · 禁用", `<div class="composer"><div class="composer-row-end"><button disabled>↑</button></div></div>`],
|
||||
["报告页 Button 默认(BUG-1078 后)", `<div class="personal-report-reader"><button data-slot="button" class="${base} ${V.default}">导出选中</button></div>`],
|
||||
["报告页 Button 默认 · 禁用", `<div class="personal-report-reader"><button data-slot="button" disabled class="${base} ${V.default}">导出选中</button></div>`],
|
||||
];
|
||||
function lum([r,g,b]) { const f = c => { c/=255; return c<=0.03928?c/12.92:((c+0.055)/1.055)**2.4; }; return 0.2126*f(r)+0.7152*f(g)+0.0722*f(b); }
|
||||
function parse(c) { const m = c.match(/[\d.]+/g).map(Number); return { rgb: m.slice(0,3), a: m.length>3?m[3]:1 }; }
|
||||
function over(fg, bg, a) { return fg.map((v,i)=>v*a+bg[i]*(1-a)); }
|
||||
function ratio(a,b){ const [x,y]=[lum(a),lum(b)].sort((p,q)=>q-p); return (x+0.05)/(y+0.05); }
|
||||
const hex = c => "#" + c.map(v=>Math.round(v).toString(16).padStart(2,"0")).join("");
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ executablePath: process.env.CHROME || "/usr/bin/google-chrome", args: ["--no-sandbox"] });
|
||||
const page = await browser.newPage();
|
||||
const rows = [];
|
||||
for (const theme of ["light", "dark"]) {
|
||||
const html = `<!doctype html><html data-theme="${theme}"><head>${cssFiles.map(f=>`<link rel="stylesheet" href="file://${path.join(dir,f)}">`).join("")}</head><body>${samples.map(([n,h],i)=>`<div id="s${i}" style="padding:8px">${h}</div>`).join("")}</body></html>`;
|
||||
fs.writeFileSync(path.join(dir, `h-${theme}.html`), html);
|
||||
await page.goto("file://" + path.join(dir, `h-${theme}.html`));
|
||||
for (let i=0;i<samples.length;i++) {
|
||||
const r = await page.evaluate((i) => { const b = document.querySelector(`#s${i} button`); const s = getComputedStyle(b);
|
||||
const bodyBg = getComputedStyle(document.body).backgroundColor; return { color: s.color, bg: s.backgroundColor, op: s.opacity, bodyBg }; }, i);
|
||||
const page0 = parse(r.bodyBg).rgb; const bgc = parse(r.bg); const fgc = parse(r.color); const op = Number(r.op);
|
||||
const bgSolid = over(bgc.rgb, page0, bgc.a); const fgSolid = over(fgc.rgb, bgSolid, fgc.a);
|
||||
const bgFinal = over(bgSolid, page0, op), fgFinal = over(fgSolid, page0, op);
|
||||
rows.push({ theme, name: samples[i][0], text: hex(fgFinal), bg: hex(bgFinal), opacity: op, contrast: ratio(fgFinal,bgFinal).toFixed(2) });
|
||||
}
|
||||
}
|
||||
await browser.close();
|
||||
console.table(rows);
|
||||
fs.writeFileSync(path.join(dir,"contrast.json"), JSON.stringify(rows,null,1));
|
||||
})();
|
||||
Reference in New Issue
Block a user