# 更新记录

本文件记录 **dsh-theme-palette** 每个版本改了什么；README 只讲当前版本的设计与用法。

## 版本一览

> **「（未发布）」标记**：本文件里带这个后缀的版本节**从未发布到 npm**（只在仓库里演进过）。
> 1.13.2 起逐节标注 —— 此前 27 个版本节看起来都能装，用户按 CHANGELOG 钉版本会撞 404
> （2026-10-29 审计 P2-9）。

### 1.13.5
用户回执：「自定义色派生好像有问题？并未覆盖所有啊，好多颜色仍然是默认色」。
这次不是猜：把宿主 `app.asar` 里的 token 表（`ui-theme` 的 `design-platform.css` + 应用 CSS）整份解出来，
和插件实际声明的 token 逐条对表，得到确切口径 ——

- 宿主定义 **412** 个 token 名，插件声明 151 个颜色 token（无一拼写失效、无一被宿主丢弃）；
- 应用 CSS 真正 `var()` 消费的只有 **66** 个颜色 token；
- 差集里 **58** 个颜色 token 插件从未声明：其中 19 个靠 `var()` 链落到已重建的色阶上（**会跟随**，
  例如 `state-business-primary` → deepseek-500、`menu-icon` → bluish-800），
  **39 个落在宿主字面色值上 —— 那是真正的"还是默认色"**。

根因：插件只重建四族色阶（bluish / neutral / deepseek / blue），
而宿主 `design-platform.css` 里还并列着**三族语义色阶 red / green / amber**，从来没有被碰过。
于是「步骤点（done/error/warning）、`data-tone` 标签、终端错误行、无效输入边框、
危险菜单项悬停、**文件 diff 的 +/− 行**」在自定义配色下永远是宿主的纯红/纯绿/纯琥珀。

- **修法一（色阶）**：`SEMANTIC_BASE` 补 red / green / amber 三族（含宿主给的两个带 alpha 的 diff 浅底），
  `buildRamps` 用新增的 `harmonizeSemantic` 做**温度调和**：色相往配色色相拉、**旋转封顶 8°**、
  色度压到 88–92%，**明度一律沿用宿主**。封顶是关键 —— 苔绿配色（hue 162）的"短路径"
  本来会把红（OKLCH h≈28°）往橙推 16°，报错就和警告撞色；封到 8° 后只是"同一件语义色换冷暖"。
  无彩（纯黑/纯白/正灰）的自定义配色**不做调和**，语义色一个像素都不动。
- **修法二（别名）**：`aliasOverrides` 显式落地 `state-{error,success,warn}-{primary,secondary,tertiary,label}`、
  `code-diff-added/deleted`、`interactive-bg-hover-danger`（用语义红重合成宿主那两个 alpha 字面量）。
  顺带补上宿主**没定义、但构建 CSS 在用**的两个洞：`--dsw-alias-label-error`（设置页"保存失败"那行，
  缺失时 `color` 整条失效、文字回落继承色）与 `--dsw-alias-bg-layer-4`（帮助按钮 hover 底）。
- **修法三（契约守卫，防复发）**：`tools/check-host-contract.mjs` 新增颜色覆盖守卫 ——
  从宿主构建 CSS 里解出所有被 `var()` 消费的颜色 token，逐条要求"被插件声明"或"`var()` 链
  落到插件声明的 token 上"。拿 1.13.4 的 `client.js` 跑同一条断言会挂 **30 处**（这就是这次漏检的形态），
  1.13.5 为 0。黑白档的 19 个 token、遮罩/透明分隔线/首次引导页等字面色值走显式白名单。
- **断言**：`check-audit` 第 29 组 —— 三族真的产出 / 色相偏移 ≤ 8° / 红绿琥珀两两可辨（≥ 25° 与 ≥ 40°）/
  别名映射与宿主明暗档位一致 / 补回的两个缺口 / diff 行对比度与宿主同量级 /
  黑白档不重建 / 无彩自定义不调和。
- **已知边界（不修）**：语义色沿用宿主明度，因此宿主本身的两个问题照旧 ——
  浅色下成功绿当文字只有 ~2.1:1（diff `+` 行、`data-tone=success` 标签），以及宿主为这一族
  设计了**明暗同值**的单一档位，任何"靠调明度补对比度"的做法都会在另一侧反噬。
  插件不替宿主改语义色的明暗节奏；这一条写进 README 的覆盖口径里。
- 复核：`check-audit` 29 组全过；`check-host-contract` 全过（含新增守卫）；两档报表 exit 0。
- **截图未重出**：设置页不渲染语义色，`screenshots.meta.json` 只同步了版本号。

#### 1.13.5 补丁 · 自定义配色的「底色」与「线条色」（同日第二次回执）

用户回执 #2：截图里 `v1.13.5` 已生效，但「很多地方底色、线条色都是按默认的」。核查确认两条：

- **底色只被当色相来源**。`customScheme` 只取色相与色度，深色画布固定 L≈0.226；
  用户把「底色」设成 `#070b11`（L≈0.148）时，屏幕上是 `#191c21` 一档的蓝灰 —— 他读成"底色没变"，
  而且这个读法是对的。
  **修法**：底色偏暗（L ≤ 0.35）→ 它就是**深色模式的画布明度**（夹到 0.14–0.30）；
  偏亮（L ≥ 0.75）→ **浅色模式的画布明度**（夹到 0.90–0.985）；中间灰（0.35–0.75）不表达明暗意图，
  两侧都沿用引擎值。只锚画布这一档，其余三层面按同一 delta 平移（层次间距不变），
  文字对比度由求解器在新承载面上重算 —— 该配色实测正文对画布 **11.33:1**、对 875 面 **10.16:1**。
  下限 0.14 是层次余量：再往下左栏「更深」档会掉进近纯黑。
- **线条色取的是文字色 alpha**。文字一旦近中性（`#e1eced`，C≈0.012），发丝线就是一片中性灰。
  **修法**：自定义配色下线条改用**配色色相 + 色度下限 0.02**（明度与 alpha 不变，对比关系不变）；
  预置仍是墨/纸原色，不受影响。
- UI 提示（四语）同步改成"底色偏暗/偏亮会当作对应模式的画布"。
- **断言**：`check-audit` 第 30 组（13 条）—— 深/浅/中间灰三类输入、四层面阶梯、左栏与画布 ΔE、
  正文对比度、线条色相与最小色度、预置不受影响、纯黑底色的层次余量。
- 复核：工具链回归 8/8、两档报表 exit 0；设置页截图按新文案重拍。

### 1.13.4
用户第二次回执：同一张悬停卡「还是深灰底色」。这次**明暗两套截图对比**把 1.13.3 的方向证伪了 ——
浅色模式下（画布 `#f8fcf6`）卡片仍是 `#2c2c2e` + 白字，颜色一个像素都没变：
它根本不读调色板，所以改多少条 token 都碰不到。

逐文件扫宿主 asar 后定位到根因：**`dsh-web-frontend` 把悬停卡底色写死在卡片元素上** ——
`._card_38jqx_9{--dsw-hovercard-bg:#2C2C2E;background:var(--dsw-hovercard-bg)}`。
这是全站唯一的大写手写 hex：既不读色阶、也不读别名，与主题完全无关（连浅色模式也是深灰）。

- **修法**：`buildTokens` 新增专用 token `--dsh-tp-hovercard-bg`（明暗两档都取色阶 **850 的暗面**），
  并注入第三条样式 `dsh-theme-palette/host-patch.css`：
  `*{--dsw-hovercard-bg:var(--dsh-tp-hovercard-bg,#2c2c2e)!important}`。
  宿主的声明在卡片元素**自身**，普通声明压不过它，必须 `!important`；只改这一个自定义属性，
  其它元素即使命中也没有副作用。卡片保持**暗底白字**（对比度不变），但从此带上配色色相。
  黑白档没有色阶可接，回落到宿主原色（行为不变）。
- **断言**：`check-audit` 第 28 组 —— 明暗同值 / = 850 暗面 / 补丁样式的形态 / 黑白档不产出。
- 复核：`npm run check` 8/8；两档报表 exit 0。

### 1.13.3
用户实测反馈：**左侧会话行的悬停卡（会话标题 / 时间 / 状态）底色不跟随主题色** —— 卡片背景停在
`#2c2c2e`、标题近白，而旁边的侧栏与画布都是染过色的。

核实（像素取样 + 宿主构建对账）：卡片背景正好是宿主 **原始**色阶 `--dsw-static-neutral-bluish-850`
（`#2c2c2e`）、标题是原始 `50`（`#f9fafb`）；而宿主把这些表面别名声明成
`var(--dsw-static-neutral-bluish-NN)` 的**引用**，插件过去只覆盖色阶、指望浏览器在 `body` 上完成替换 ——
这条链在浮层里没有生效，于是卡片拿到了宿主原色。

- **修法**：把宿主里**全部 47 个**「纯粹是色阶引用」的别名（`bg-base` / `bg-layer-1..3` / `tooltip-bg` /
  `label-primary` / `toast-bg` / `switch-thumb` / `specific-tip` / `specific-input-major` …）在覆盖层里
  **直接写成色阶值**，不再依赖任何中间引用。已经由求解器给值的键（`label-secondary` / `label-tertiary` /
  `link` / `brand` / `bubble` / `sidebar-fill` / `markdown-code-block` 等）一律跳过，保持求解结果。
  能正常替换的地方结果完全一致（同一档），链不生效的地方就是修复。
- **防回退**：`check-audit` 新增第 27 组 —— 每个别名都必须在覆盖表里、且值等于它声明的色阶档；
  `check-host-contract` 改为从**宿主真实构建**里解出这 47 条映射并与插件对表（宿主改映射会红，不会静默错色）。
- 覆盖的 token 数从 122 增至 **158**（黑白档 17 → 19），六语 README 同步。
- 复核：`npm run check` 8/8；两档报表 exit 0；`npm pack` 28 文件。

### 1.13.2
对 2026-10-29 全量审计（**34 条**，报告与反哺回执在仓库根目录的 `AUDIT-2026-10-29.md` /
`AUDIT-2026-10-29-FEEDBACK.md`）的修订。审计的核心判断成立：**引擎正确，缺的是"防回归"的工程能力** ——
把 `CONTRAST_FLOOR` 改成 1.0、把 `package.json` 改成 1.99.0，`npm run check` 全绿。这一版补的就是这些洞。

- **P0-1 版本漂移在 CI 里不可见**：`tools/check-pack.mjs` 新增四路对账 —— `package.json` / `PLUGIN_VERSION` /
  `screenshots.meta.json` / CHANGELOG 最新版本节必须同为 `1.13.2`。此前只有 `release.mjs` 断言其中一条边，
  而 `release.mjs` 不在 CI 里。
- **P0-2 包内 6 条死链**：`files` 补 `tools/README.md`（五语 README 末尾都指向它）与 `AUDIT-2026-10-27.md`
  （1.11.0 版本节引用）；`check-pack` 另加**通用死链扫描** —— 包内每份 markdown 的相对链接目标都必须在包里。
  这比"列出必需文件"更能防下一个死链。
- **P1-1 承诺下限没有锚点**：dev 钩子导出 `CONTRAST_FLOOR` / `MIN_*` / `FLOORS_HC*` / `RAMP_MIN_GAP`；
  `check-audit` 第 22 组用**字面量**断言它们（4.5 / 12 / 6.2 / 4.85 / 4.95 / 4.62 / 8.0 / 6.4 / HC 15·9·7 /
  9.6·7.6 / 0.006），第 23 组把 `physicalCeiling` / `limitedByPhysics` 改成**用渲染 token 独立复算**
  （216 格）—— 原先这两个断言拿被测字段互相验证，一起漂移时照样过。
- **P1-2 / P1-3 发版链的版本闸门**：`publish.yml` 在发布前断言 tag 版本 == `package.json` 版本
  （否则 npm 会发错版本、后续 Release 资产按 tag 版本 404）；`release.mjs` 的 `PLUGIN_VERSION` 正则换成与
  `check-ui` / `check-pack` 同一个宽松版，**找不到即 die**（原先是 warn 后继续发版）。
- **P1-4 宿主契约在 CI 上是 0 条断言**：`check-host-contract.mjs` 支持从 `node_modules` 里的宿主 peer 包取证
  （`@deepseek-ai/dsh-client-ui-theme` / `-ui-layout` / `dsh-web-frontend`，已进 devDependencies 并钉死
  `0.2.1-rc.2`）。CI 加 `npm install` + `npm run contract`（`--strict`：两个来源都没有就红）。16 条契约断言
  现在在 ubuntu 上也真的跑。
- **P1-5 脚本清单两处维护**：`release.mjs` 改为直接调用 `tools/check-all.mjs`，清单只有一份；
  SKIP 与否仍单独记账（SKIP 不会被打印成「通过」）。
- **P1-6 `insert-readme-sections.mjs` 休眠且损坏**：`SAMPLE` 改为运行时调 `tools/sample-code.mjs` 的
  `generateSampleCode()`（不再硬编码 1.9.10 之前那份带 `"r":"sidebar"` 的码），并接入 `npm run check`。
  此前它 `--check` 在干净检出上直接红，所以没人跑它。
- **P1-7 色阶 `900` 漏在顺序表外**：`RAMP_ORDER` 补 `'900'`（875 与 950 之间）。补完视觉无变化（钳制量
  小于一个 8-bit 量化步长），但 `check-audit` 第 25 组新增「凡是构建出来的档位都必须出现在 `RAMP_ORDER` 里」，
  下一档再漏会红。
- **P1-8 APCA 的 Lc 被取整到整数**：`apcaContrast` 精度改 0.01 Lc，公开参考值随之回到
  **106.04 / −107.88 / 63.06**（此前发出的是 106 / −108 / 63）。`raiseToLc` 不再"量化后首次过闸就停"。
  ⚠️ 审计报告给的修法 `Math.round(out * 10000) / 100` 是错的（`out` 本身是 Lc/100，除以 100 会返回 Lc，
  差 100 倍 —— 实装验证过 `worst()` 算出 −4708）；正确分母是 **10000**。
- **P2 十六条**：`mixHex` 注释改回真相（gamma 编码 sRGB 插值，不是线性光）；`hexToRgb` 加校验、畸形输入返回
  `null`（`toOklch` / `luminance` / `apcaY` / `flatten` / `mixHex` / `simulateCvd` 全部有定义的回退，
  不再产出 `#NaNNaNNaN`、也不再对 `null` 抛 TypeError）；UI 文案改称「OKLab ΔE」；组件级回退从 `zh-CN`
  改成 `en`（与 `pickLocale` 同口径）；`<section lang>` 改用解析后的 `locale`；es/pt/hi 补「自定义配色 +
  短码 + 高对比 + 4 语界面」一条并为六语 README 的要点条数加断言；`DESIGN.md` 补 **i18n 章节**、
  自愈章节补 1.12.0 的四件事、修掉 `paperC` 的自相矛盾与 `+0.022/+0.014`、`0.008` 两处旧值；
  CHANGELOG 逐节标注未发布版本；`locale/*.json` 定性为**市场页元数据**并加键集/覆盖断言；
  补 `README-ja.md` 与 `locale/es|pt|hi.json`；`release.mjs` 区分「registry 网络失败」与「版本不存在」
  （前者 die）、`dist-tag add` 失败改 die；对比度矩阵的面名跟随语言；5 个工具脚本补 `npm run` 别名、
  `npm run packs` 进 CI；`tools/baseline/*.png` 移出版本控制（`compare.mjs` 不在 CI，基线会烂）；
  清掉 6 个遗留 `.tgz` 并加 `npm run clean`。
- **P3 八条**：空数组恒真断言加长度守卫；删掉色相检查里的恒真子句；`fail()` 的 printf 风格改模板字符串；
  「六件套」等硬编码件数改为动态；删 `CONTRAST_ROLES[].step` 死数据；删 `enforceRampOrder` 的死返回值；
  `check-audit` 分组 18/19 调成升序；APCA 锚点注释随 P1-8 自动变正确（106.04 / −107.88）。
- **复核**：`npm run check` 8 项全绿（新增 `insert-readme-sections --check`）；两档报表 exit 0；
  `npm pack` **28 文件 / 455.9 KB**；宿主契约从 peer 包取证 16 条断言全过；CI 新增 install + `npm run contract` + `npm run packs`；
  `node tools/release.mjs --dry` 在临时 git 仓里全流程通过（含版本闸门、8/8 回归、两档 1080 组、registry 探针）。
- **顺手修掉一个审计没列的洞**：`tools/export-theme-pack.mjs` 拿 `floors(false).main`（浅色 12:1）去卡**深色**包，
  而深色正文的承诺是 10:1 —— 三套深色包一直被静默丢弃（`npm run packs` 退出 1，但它从不在 CI 里，所以没人发现）。
  改为按 `CONTRAST_TARGETS.main[mode].normal` 取目标后，8 个包全部导出（深色 10.91 / 11.08 / 11.20 / 12.05）。
- **未做（留给发版决定）**：本次不发 npm。真正发版仍走 `node tools/release.mjs --ci` 打 tag → Actions 用
  Trusted Publishing 发布。

### 1.13.1
发版链路改造：改用 **Trusted Publishing（OIDC）** 从 GitHub Actions 直接发布，不再依赖长期 token。**插件运行时没有任何变化** —— 这一版主要是让 README 与包内容跟上门禁与流程的真实状态。

- **为什么改**：npm 正在收紧「绕过 2FA 的 token」用于直接发布的权限（registry 会打印 `npm tokens that bypass 2FA are being restricted for account changes and direct publishing`）。本地发布要么给一次性验证码，要么换机制 —— 而本机账号从未在验证器 app 里开过 2FA，验证码这条路走不通。Trusted Publishing 用 OIDC 换一次性凭据，仓库里不存任何 token。
- **新增 `.github/workflows/publish.yml`**：tag 触发 → 门禁 → `npm publish`（OIDC + provenance）→ 等 registry 放出 → **自动建 GitHub Release**（资产取 registry 官方 tarball，sha1 与 `dist.shasum` 逐字节核对；notes 从本文件对应版本节抽出）。顺带补上这个仓库的老缺口：此前没有 publish 用 CI，GitHub Release 每次都得手工建。
- **`tools/release.mjs` 新增 `--ci` 模式**：本地只打签名 tag 推上去，不 publish。顺序不能反 —— 本地先 publish 会先占坑，CI 再 publish 必然 E409；`--ci` 与 `--skip-publish` 显式互斥。
- **包内容守卫抽成 `tools/check-pack.mjs`**：清单原先写死在 `release.mjs` 里，只有本地发版路径会跑到，CI 直接推 tag 时会漏。现在本地 `npm run check`、CI、`release.mjs` 三条路径共用同一份 required（进了标准回归第 7 项）。
- **踩到并修掉的一个坑（值得记下）**：workflow 最初用 node 22 —— 它自带 npm 10.x，**不支持 OIDC**，于是 `npm publish` 静默退化成「无凭据发布」，症状只是一个含糊的 `E404 Not Found - PUT`，看起来像仓库或权限问题。npm 官方要求 **npm CLI ≥ 11.5.1 + Node ≥ 22.14**，现固定 node 24 并加了断言（npm 版本不够直接红）。
- **另一条 npm 的隐形前提**：2026-09-03 之后创建的 Trusted Publisher 配置**默认只允许 `npm stage publish`**，必须在 **Allowed actions** 里勾上「npm publish」才会允许直接发布；且首次成功发布要在配置后 2 天内完成，否则配置过期。
- 五语 README 的 Development 段补 `--ci` 用法；`docs/WORKFLOW-NOTES.md` 新增第 11 节（事故出处、npm 政策、配置步骤、三个坑）。
- 复核：`npm run check` 七件套全绿（新增包内容守卫）；两档报表 exit 0，1080 组硬门 0。

### 1.13.0
新增斜杠命令 `/theme-palette`：切换配色从 4 步（头像 → 设置 → 主题色 → 点卡片）变成 1 步 —— 输入 `/theme-palette` 弹出 5 套配色（黑白 / 暖砂 / 靛蓝 / 苔绿 / 自定义），当前配色带 active 标记，选中即切换并保存。

- **命令注册走可选依赖**：`ctx.inject(['commandUi'], …)`，宿主没有该服务时静默不挂，插件本体不受影响（不写进模块级 `inject`，否则缺服务会连插件一起拖住）。
- **两条来自生产先例的坑注记**（照 `dsh-free-search` 的注册骨架抄，`label`/`description` **必须传函数** —— 传字符串会让 ui-commands 读回时抛 TypeError，整份 `/` 候选列表一起失败、菜单空白、别的命令也点不到；命令名带前缀，避免与宿主命令目录撞名 —— `candidates()` 对撞名是直接 throw，同样炸整个菜单）。
- **回归覆盖**：`check-selfheal` 新增第 8 场景 —— 命令注册成功、`label`/`description` 均为函数、`ui.kind = popupSelect`、`options` 返回 5 项且恰有一个 active、`onSelect` 切换恰好 +1 次 `overrideTokens`、卸载时注册一并摘除。

### 1.12.0
2026-10-28 一次外部审计（20 条问题，逐条核实后修订 14 条）。核心是**修好一个一直在发生的真 bug**：切换配色后颜色会跳回默认 —— 原因是自愈机制把自己刚装上的覆盖层撤掉了。此外把「失效不可见」「承诺与实现对不上」「发版门漏检」三类问题一并收口。

- **修复：切换配色后颜色跳回默认（P0，真 bug）**：宿主 `overrideTokens` 会**同步** emit `theme/change`（`dsh-client-ui-theme/lib/client.js:1835-1847`；cordis `emit` 同步执行，`cordis/lib/index.js:283-285`），监听器按注册序触发（`:338-348`）；宿主 presenter（核心插件，boot 期注册）先收到事件并把新 token 同步写进 `body`（`dsh-client-ui-layout/lib/client.js:700-710`、`:548-569`），插件的自愈监听器（动态包，后注册）随后拿到**上一次**的 `lastTokens` 去比 body 上的**新**值 → 必然 `color-mismatch` → 重入 `applyAll` → 重入装上的新层被外层 `prev()` 撤掉（`client.js:3276` 的取值时机在 `overrideTokens` 之后）。复现：换一次配色 `overrideTokens` 被调 3 次（期望 2）、`--dshtp-layer` 消失、9 个探针 token 全部回落宿主基色。**修法**：`applyAll` 期间置 `applying` 标志，`ensureApplied` 期间一律不判定「层丢了」；并把 `prev = disposeLayer` 的取值提到 `overrideTokens` **之前**（即使将来有别的重入路径，撤的也只是本次调用之前那一层）。
- **自愈停用不再静默（P0-B）**：连续失败 5 次后插件会放弃自动重放，但此前只有一条 `console.error` —— 界面看不出任何区别，用户改设置也不会复位它（需刷新页面）。现在 `healDisabled` / `healFails` 进入健康快照，设置页显示提示条并提供「重新启用自动恢复」按钮（`resetHeal()`），诊断 JSON 同样带上这两个字段。
- **新增可见性探针（P0-C）**：此前的健康判据只证明「我们写的变量在 body 上、值正确」—— 宿主一旦改 token 名，变量照样在、值照样对，但页面已无规则引用它们，探针恒绿、界面却毫无异样。现在额外比对 **body 实际背景色**与本层画布色（`--dsw-static-neutral-bluish-00` 浅 / `950` 深；黑白档不覆盖这两步，期望值回落宿主原值；`forced-colors` 强制配色下跳过比对）。配套在 `check-host-contract.mjs` 增加 3 条宿主契约断言，把这条判据依赖的链路（`--dsw-alias-bg-base` → 色阶 00/950 → body 背景色）钉住。
- **导入短码不再谎报成功（P2-I）**：隐私模式或存储满时，9 项写入各自静默失败，界面却显示「导入成功」，刷新后回到旧配置（部分写入还会得到混合配置）。现在写入结果如实汇总为 `persisted`，界面区分「已导入并保存」与「已生效但未能保存」。
- **热重载监听器泄漏（P2-L）**：`prefers-contrast` 的 `change` 监听从未移除，重载 N 次就留 N 个；系统切换对比度偏好时旧实例会依次重放覆盖层，把当前配色覆盖掉。现在回调提到外层并在 cleanup 里 `removeEventListener`。
- **样式标签热重载（P3-R）**：设置页/Markdown 样式命中已存在的标签就跳过，导致改过 CSS 后热重载看不到变化、卸载后标签残留。现在命中时覆盖写入内容并把标签交回 disposer。
- **百分比 alpha 解析（P3-Q）**：`rgba(0,0,0,50%)` 的 alpha 被当成 RGB 一样乘 2.55（算出 127.5，应为 0.5）。RGB 分量 ×2.55、alpha 分量 ÷100 分开处理。同条另一半**判定不修**：探针若改成「不分格式比较」，`var(...)`/`color-mix(...)` 会被与计算值字符串不等 → 把假阴性变成持续假失配。
- **文档口径对账（五语 README + DESIGN.md）**：深色链接承诺此前写「6.4」，代码真源是 `MIN_LINK_DARK = 4.62` + APCA Lc ≥ 60 —— 文档高估了承诺，已按真源改写并把三级与链接的 HC 下限拆开；「27,000+ 处 token 引用」全仓无脚本能算出、且随 1.9.10 撤右栏已过期，改为可复现口径「122 个颜色 token（黑白档 17 个）· 1080 组设置全扫描」；「4 套配色」统一为「4 套预设 + 自定义」；「侧栏/气泡 ≥ 7」原本像承诺但无对应门，改为「随色阶派生、实测均 ≥ 7（不设独立对比度门）」；DESIGN.md 的深色参数表（`darkLift` / `darkC` / `paperC`）与「高对比默认关」也已对齐代码真源（默认 `auto`）。
- **README 示例短码重生成（P1-f）**：示例短码里仍带着 1.9.10 已撤除的 `"r":"sidebar"`，解码能读、三色也对，所以旧断言查不出来 —— 只有「生产编码器往返自等」能发现它过期。五份 README 已用 `tools/sample-code.mjs` 重新生成。
- **测试与发版门**：`check-selfheal` 的假宿主此前**从不 emit** `theme/change`，整类重入 bug 零覆盖 —— 现在同步 emit，并新增「换一次配色 → `overrideTokens` 恰好 +1、标记仍在、无假警报」的场景（负向验证：临时拆掉守卫即变红，确认断言有牙齿）；原第 7 场景「卸载后不再重放」排在「停用」之后，`healDisabled` 让它恒真，已重写为「先 `resetHeal()` 证明自动恢复能救活，再验卸载」。`check-ui` 增加示例码往返自等断言与文档口径对账门。`check-host-contract` 的 SKIP 改用独立退出码 78（支持 `DSH_ASAR` 环境变量与 `--strict`），发版门不再把 SKIP 打印成「✓ 通过」，新增 `tools/check-all.mjs` 把「SKIP（未执行）」与「通过」分开记账。发版门新增 1080 组全扫描两道门与 **npm 包内容守卫**（22 个必需文件逐个核对 —— 1.11.0 刚修过「README 截图 404」，当时没有回归守卫）。`palette-dev.mjs` 的「低于 AA 4.5 底线」由「只打印」改为**真门**（当前实测 0 处违规）。
- **本地化与文案**：补 `locale/ja.json` 与 `locale/zh-TW.json`（此前只有 en/zh，而界面支持四语、README 五语）；四语副标题里的 `localStorage` 改为「保存在本机 / this device / この端末」。
- **发版联动**：版本号 bump 至 1.12.0、四语 notice 换新、截图用 `npm run shots` 重拍（设置页新增提示条与按钮），`screenshots.meta.json` 的 `pluginVersion` 随之对齐。
- 复核：`npm run check` 六件套全绿；两档报表 exit 0，1080 组配置「未达标 0 / 低于 AA 4.5 = 0 / 硬门 0」；`npm pack --dry-run` 确认 22 个必需文件齐备。

### 1.11.0
2026-10-27 全量审计（评分 89/100，报告见 [`AUDIT-2026-10-27.md`](AUDIT-2026-10-27.md)）的 12 条问题全部修订。两条 P1 都是门面级小修；审计里有一处**自我修正**也如实落地：取色拖动的性能问题经实测降级为防御性优化。

- **npm 包内 README 截图修复（P1-1）**：`files` 此前不含 `assets/`，npmjs.com 上 README 的 4 张截图全部 404。现已入包（包体 140KB → 436KB）。选「打进包」而非「绝对 GitHub URL」：自包含、不依赖仓库地址、npm 与镜像双兼容。
- **界面语言回退改 en（P1-2）**：宿主语言不在 zh/ja/en 里（fr/de/es/pt/…）时，设置页从整页中文改为回落英文。`pickLocale` 进 dev 钩子导出，`check-audit` 第 21 组断言 fr → en。
- **深色注释（label-caption）过 Lc 闸（P2-3）**：原先深色侧只按浅色下限求解，实测 Lc 47.9–51.7，低于本插件给文字定的 Lc 60 线（三级同面 62–66、链接有 Lc 闸，唯独注释没有）。现标准档 Lc 60 / HC 档 70——HC 定 70 而非照抄链接的 75：HC 三级 Lc 落在 72–77，套 75 会反超三级破坏层级（实测暖砂 HC 原值 71.7 已达标故不变，靛蓝 HC 66.6 → 72.0）。抬完标准档注释 WCAG 5.73–6.19，仍低于三级的 6.4（层级断言钉住）。`check-audit` 第 4 组扩为 WCAG + Lc + 层级三重断言；DESIGN.md 口径表补「注释层」与「装饰层」两行（后者即 P3-12：label-dimmed/document-preview 按 3.0 装饰下限、有意不进承诺模型）。
- **取色拖动按帧合并（P2-4，防御性优化）**：取色器 `input` 在拖动中每帧多次触发，此前每次都全量重放覆盖层（宿主侧重写约 200 个 token 触发全页样式重算）。现在同帧多次取色合并到下一帧只重放一次、以最后一次为准；重置/派生/导入等离散动作保持同步。审计修正：插件侧构建实测仅约 0.05ms/次（60 次共 3ms），瓶颈在宿主侧重排——所以这是防御性优化而非实测缺陷修复。`check-selfheal` 新增第 6 场景（假宿主计数：帧内 +0、合并后 +1、末次生效），其沙箱的 localStorage 桩顺带从「恒返 null」改为 Map 实现（原桩让自定义色永远读回默认值，与真实宿主不符）。
- **CI（P2-5）**：新增 `.github/workflows/ci.yml`，push/PR 在 Node 22/24 跑 `npm run check` + `npm run report`；此前全部质量门只在本地/发版时跑。脚本只依赖 node 内建，无需 install；宿主契约在无 asar 的 CI 上自动 SKIP。
- **dev 钩子补导出（P2-6）**：`apcaContrast` / `raiseToLc` / `solveForeground` / `pickLocale` / `hostLocale` / `floorsDark` —— 外部工具复算 Lc 不必再自带一份 APCA 实现。
- **组合数口径统一为 1080（P3-10）**：五语 README 与 DESIGN.md 原写「2160 组（5 × 216 × 两档）」，公式把高对比算了两次（216 已含 hc 档），实际扫描 1080 组（1.9.10 撤右栏档前才是 2160）。
- **notice 文案版本同步断言（P3-11）**：`check-ui` 新增——四个语种的 `notice` 恰好 4 条且都含当前 `PLUGIN_VERSION`，升级漏改不会绿。
- **代码卫生（P3-7/8/9）**：短码编解码从废弃的 `escape`/`unescape` 换 `TextEncoder`/`TextDecoder`（产出逐字节相同，新旧短码互解码兼容，第 2 组旧码断言守着）；`TEXT_TARGETS` 常量表从 `buildTokensRaw` 体内上提到模块级（紧随 `SURFACE_TARGETS`）；`langPref` 声明先于引用它的 `getConfigCode` 闭包。
- **发版联动**：版本号 bump 至 1.11.0、四语 notice 换新、截图用 `npm run shots` 重拍（无头 Chrome/Edge + CDP 截 preview HTML，不需要 DSH 宿主——审计时「重拍需要宿主 GUI」的判断有误，发版时更正），`screenshots.meta.json` 的 `pluginVersion` 随之对齐。
- 复核：`npm run check` 六件套全绿（含新增断言）；两档报表 exit 0，1080 组配置「未达标 0 / 低于 AA 4.5 = 0 / 硬失败 0」；`npm pack --dry-run` 确认 assets 入包（20 文件 / 435.6KB）。

### 1.10.0
按审计报告的改进清单做了六件事：色彩数学单测、高对比跟随系统、运行时覆盖报告、色觉缺陷守门、单色派生，另外把 display-P3 的可行性量清楚后决定**本期不做**。其中「色觉缺陷守门」第一次跑就抓到一处真问题（浅色链接在模拟下低于 AA），下限因此微调。

- **色彩数学单测**（新增 `tools/check-colormath.mjs`，约 40 条断言，并入 `npm run check` 与发版回归清单）：锚点（OKLab 里的白 / 黑 / 中灰）、OKLCH↔hex 往返、色域裁切「保明度保色相」、WCAG 样例值（`#777` on `#fff` = 4.48、黑白 21）、`flatten()` 的 α 合成交换性、`harmonize()` 的三条不变量（保 L、色度按比例、色相朝目标按 `pull` 走）。以前这些函数只靠「界面上看着对」，现在挪一个常数就会红。
  写单测时踩了一次：`harmonize` 的色相断言最初写成「移动量 = pull」，实际是「按剩下的角度比例移动」（`dh1 ≈ pull × dh0`）—— 是断言错了，不是代码错。
- **高对比档新增「跟随系统」并设为默认**：系统开「提高对比度」（`prefers-contrast: more`）就自动用高对比档，关掉回标准档；监听 `change` 事件即时重算。手动选过「关 / 开」的用户读回自己的选择，升级不会被改成别的档位。
- **色觉缺陷守门**（`tools/check-audit.mjs` 第 20 组）：四套配色 × 明暗 × 正文/次级/三级/链接 × 三个承载面 × deutan / protan / tritan（Machado 2009），断言最低对比度 ≥ 4.5。**这条断言一开始没过**：三级与链接的浅色下限原本压在 4.62（刚过 AA），模拟把比值推动 5–9% 后链接跌到 **4.22–4.34**。修法是把浅色下限抬到三级 **4.85**、链接 **4.95**，抬完三种模拟下最低 **4.61**。代价：浅色链接与三级文字比 1.9.x 略深一档。
- **自定义配色新增「由主色派生」**：只给一个主色，按它的色相与色度推出纸面与墨色（明度按当前明暗模式取引擎已验证的区间），派生结果直接过全部下限（实测正文 14.6–15.0 / 次级 6.8 / 三级 5.3 / 链接 5.4）。主色无彩时派生中性纸面。**它是一次性的填写动作，不是一种模式**：写进去的就是普通的三色，之后照常手改、导出分享码也只带这三个值；但正因为它会填这两格，面板里加了一行可见说明 ——「会覆盖你手动设置的底色与文字，派生之后仍可逐项手改」（四语种，`tools/check-custom.mjs` 断言这句必须是可见文本，不能只挂在 tooltip 上）。
- **自定义配色新增色觉提示**：把三个色按 deutan / protan / tritan 模拟后，若强调色与文字或底色的 ΔE 塌到 **12** 以下（典型是红 / 绿搭配），面板里给一行说明 —— 只提示，不阻止保存，也不改用户的颜色。
- **诊断导出新增运行时覆盖报告** `coverage`：枚举宿主样式表里所有 `--dsw-*`，分成「本插件接管 / var 引用 / 字面彩色 / 字面灰」四类，回答「宿主还有多少 token 没跟上配色」。只读、拿不到样式表就返回 `ok: false`。
- **display-P3：量完决定不做**。同一 L/h 下 P3 比 sRGB 能多给的色度：纸面层（00/60）只多 ΔC 0.0008–0.0053（ΔE 0.08–0.53，JND 以下，看不出来）；能看出差别的是强调色（indigo ΔE 5.29、moss 2.96）。但强调色同时是链接 / info 填充 / 勾选支点的来源，按 P3 抬色度就要为 sRGB 回退再标定一套。数据与取舍写在 `docs/DESIGN.md`，将来若做，只值得给强调色加一层 `color(display-p3 …)`。
- 复核：`npm run check` 全绿（含新增单测与第 20 组）；`palette-dev.mjs` 两档 exit 0，1080 组配置「未达标 0 / 低于 AA 4.5 = 0 / 硬失败 0」。

### 1.9.10
撤除「右侧栏底色」这一档，并把它的技术债一起清干净（用户拍板）。**这不是修 bug，是承认做不到**：宿主右栏没有可染的落点，留着这个开关只会一直看起来像坏的。

- **两条路都走不通**。只染网格列（`data-rightbar-col`，0.9.0 起的做法）：宿主真正的右栏面板是 `position:absolute; right:0; top:0; bottom:0` 的**整高透明覆盖层**，它盖在列上、自己不上底色（透明才露出主栏画布）—— 染列几乎看不出差别，这就是用户说的「设了像没设」。连面板本体一起染（本版第一版试过）：那个覆盖层的覆盖范围大于右栏列宽，一上色**直接盖住主栏一大块**，右栏开关按钮也被压在它下面（用户实测「按钮根本看不到，主栏被遮盖了一半」）。给那条 1px 分隔线上色也不成立 —— 用户明确不要对右栏做任何设置，该尝试已回退。
- **整条链路一并删除，不留半截**：设置页那一行、`TEXT` 里 4 个语种的文案（`rightbarTitle/rightbarSub/rightbarSame/rightbar`）、`rightbarMode` 状态与 `getRightbarMode` / `setRightbarMode` / `subscribeRightbar` / `emitRightbar` / `rightbarListeners`、`readRightbarStored` / `writeRightbarStored` 与那个 `localStorage` 键、分享码的 `r` 字段（encode + decode）、`DEFAULT_RIGHTBAR`、`RIGHTBAR_MODES` 表、四条右栏染色规则、`--dshtp-rightbar-fill` / `--dshtp-canvas-fill` / `--dshtp-rightbar-divider` 三个 token、`buildTokens` 的第九个参数、`unknown xxx id` 告警里的 rightbar 项、palette-dev 的右栏循环（扫描从 2160 组降到 **1080 组**）、dev 钩子导出的 `RIGHTBAR_MODES` / `setRightbarMode`、`tools/check-host-contract.mjs` 里针对 `data-rightbar-col` / `data-sidebar-right-panel` 的契约断言。改名留下的壳：`RIGHTBAR_CSS` → `MARKDOWN_CSS`（这张表从 1.9.10 起只装 Markdown 与代码块样式）。
- **唯一保留的是兼容读取**：旧分享码里的 `r` 字段、旧 `localStorage` 里那个键，读到只是忽略、不报错（`decodeConfig` 不认识它就跳过）。`tools/check-audit.mjs` 第 18 组改成反向断言：样式表不碰右栏任何元素、不产出右栏 token、**旧码仍可解析且不复活该字段**、新码不再带 `r`。
- **高对比边框不再比浅色侧还硬**（同版本内的调整）。深色有四层面（`950` 侧栏 / `875` / `850` / `800` 模块面），对比度在暗面上单调 —— 面越暗读数越高，所以 1.9.9 按最亮的 `800` 定标后，同一根线在 `950` 上是 **5.3 / 6.1 / 7.1:1**，用户实测反馈「一开高对比边框全变了、扎眼」。改为对中间两层 `875` / `850` 定标、目标抬到 **3.2 / 3.6 / 4.1**：`950` 落到 **3.81 / 4.27 / 4.88**，`800` 从 3.0 降到 2.2–2.8（仍看得见分界，但不再满足 1.4.11 的 3:1 —— 取舍是「用户盯着看的是侧栏与画布，不是模块面」）。浅色侧不动。`tools/check-audit.mjs` 新增第 19 组钉住这条（浅色对 00/60、深色对 875/850、`950 ≤ 5.0`、`800 ≥ 2.0`，以及「标准档必须是墨色 alpha、`l1` 两档都不变」）。
- 「左侧栏底色」三档不受影响：它有自己的宿主 token（`--dsw-specific-sidebar-fill`），一直可靠。
- 复核：`npm run check` 全绿（结构 / 契约 / 自愈 / 审计 19 组）；`palette-dev.mjs` 两档均 exit 0，1080 组配置 × 承诺面「低于 AA 4.5 = 0、硬失败 = 0」。

### 1.9.9
按一次独立审计（`_audit-theme-palette-20261025/`，针对 1.9.8，11 条问题）逐条核实后修订。**核实结论是：8 条成立并已修；3.5 成立但属于权衡（只补控件边界档，不把全部边框提到 3:1）；1 条不成立（报告本身的口径错，已更正）。另有两项在三套预置的「对齐契约」上做了让步（浅画布分离、链接 Lc 门），由用户拍板后修改。**

- **深色侧单独一套文字下限**（审计 3.1）。WCAG 2.x 的相对亮度比在暗底上系统性高估可读性：同一个 4.62 在浅色模块面 `#f5eee3` 上相当于 APCA Lc≈71，在深色模块面 `#4a3f36` 上只有 Lc≈48 —— 所以旧下限在深色上是「纸面达标」。现在深色次级 **8.0**、三级 **6.4**（高对比 9.6 / 7.6），浅色侧不动（它的 6.2 / 4.62 换算成 Lc 是 79 / 71，本来够）。
  实测：深色模块面对 APCA Lc，三级从 **44.7–47.7 → 61.9–68.5**；承诺面 48 组（4 套配色 × 正文/次级/三级/链接 × 深浅各两面）里
  `Lc < 60` 的从 **11 组降到 2 组**，剩下的 2 组是**自定义配色的深色链接**（Lc 52.7 / 55.0）—— 链接下限当时仍是 6.4。
  这两组已随下面的「链接按 APCA 收口」一并抹平：现在承诺面 **48 组全部 ≥60**。
- **链接按 APCA 收口**：链接的 WCAG 下限不动（4.62 / 高对比 7），另加一道 APCA 门 —— 深色侧 Lc **≥60**（高对比 ≥75），浅色侧靠比值已够。求解器新增 `raiseToLc()`：比值达标而 Lc 不够时，沿「远离背景」的方向按 Lc 目标反推、二分回色域，最多 12 步。
  实测链接 Lc（深色 950/875/800）：暖砂 73/72/64、靛蓝 66/65/60、苔绿 71/70/64、自定义 67/65/60；浅色 00/60 落在 61–81。自定义深色链接由 Lc 52.7 抬进同一档。
  走过一段弯路必须记下：先把下限从 4.62 直接抬到 8.0，自定义深色链接被求解到 **`#ffffff`** —— 纯白链接等于丢掉品牌色。所以门开在 Lc 上，WCAG 下限保持原值。
- **对比度报表新增 APCA 列**：每个格子除 WCAG 比值外给出 Lc 与「APCA 参考档」（正文 ≥75 / 次级 ≥60 / 三级 ≥45）。这一列**不设门**，只把「过了 AA 但读起来费劲」变成看得见的数字。`palette-dev.mjs` 的表格新增 `Lc` 列；设置页矩阵的每格悬停提示也给出 Lc（表格本身仍以比值为主，Lc 不设门）。
- **色阶单调性**（审计 3.2）。`compressL` 原来是 `L ≥ 0.92 → L-0.044` 的硬阈值，跨越阈值的相邻两档会撞成同色（浅色 150/200 实测 ΔE 0.36）；`tuneRamps` 逐个反解明度却从不排序，于是 `950` 被表面目标钉住、`1000` 为了过 12:1 被拉亮 —— 出现「编号越大反而越亮」。现在压缩是连续的（误差 <0.001），并在 `tuneRamps` 收尾加一次 `enforceRampOrder`：按档位序号排序后强制 `L[i+1] ≤ L[i] - 0.008`，越界时按比例回压而非硬截；深浅侧各自一套「墨色档」（浅 700/1000、深 50/300/400）不参与阶梯 —— 它们在深色里就是字色，天生要比面亮。实测 4 套配色 × 两档，**12 个面档全部单调**。
- **「文字着色」三档对预置配色恢复有效**（审计 3.3）。1.9.0 为了「参考实现精确对齐」给三套预置写了固定参考墨色，并在 `fit()` 里把色阶整体覆写 —— 三档算出的色度差在覆写那一刻就没了（实测静↔浓 ΔE 0）。现在参考墨色降级为**起点**：明度与色相仍对齐参考值，色度改由档位驱动（`TEXT_MODES` 新增 `inkC` / `inkDarkC`，静 −0.026 / 标准 0 / 浓 +0.022），`fit()` / `boost()` / `vivid()` 三条旧路合并成一个 `solveOn()`。实测静→浓：浅色正文 ΔE **3.2 / 4.7 / 4.1**、屏幕上渲染的次级 **5.1 / 5.6 / 6.4**、深色正文 0.5–1.6（近白处色度台阶被 sRGB 色域压住，暖砂/靛蓝/苔绿）。代价必须说清：**逐 token 的「精确对齐」只保住了正文**（1000 档 ΔE 0.0–2.1），次级与三级现在比 1.9.0 的参考墨色偏 4.6–11.4 —— 档位若要真有效，这两个 token 就不能继续钉死在参考墨色上。
- **高对比档的边框与滚动条真的提上去了**（审计 3.4）。旧实现里 HC 只改文字：`border-l*` 的 alpha 一个字没动（实测与普通档完全相同，1.23–1.39:1），滚动条压根不在 HC 分支里。现在 HC 下 `border-l2 / l3 / l4` 按 SC 1.4.11 对承载面**定量**求解到 **3.0 / 3.4 / 4.0:1**（实测 3.26–3.39），滚动手柄 **3.01–3.07:1**。
  这里的「定量」是必须的：先按老路用 `solveForeground` 求解，从墨色起步时它一步都不用挪就「已达标」，直接返回墨色 —— 实测 16.9:1，发丝线变黑框，比不改还难看。新增 `mixToContrast()`：在 `面→墨` 的直线上二分混合比，命中目标值本身。
- **新增 `--dshtp-border-control`（组件边界 3:1）**（审计 3.5）。宿主的 `border-l1..l4` 是四层递进的「发丝线」，没有「装饰性分隔线」与「组件边界」的区分，而 SC 1.4.11 只管后者。插件自己的设置页输入框改用这一档实色。宿主控件边框仍由宿主负责 —— 插件不替它做这个决定，也不打算把全部边框一律提到 3:1（那会把界面弄成表格）。
- **三套预置的浅色画布：先拉开、再回归参考实现**（审计 3.7）。旧浅画布 `#fdfbf7 / #f8fafc / #f9fbf7` 两两 ΔE 只有 0.8–1.4 —— 1.9.0「surface token 对齐参考实现」的直接结果。1.9.9 期间先按审计建议拉开到 `#fff8ec / #eef7fd / #eff4e4`（ΔE 3.11 / 3.23 / 4.35），随后**用户对照参考实现否决了这版**：苔绿的纸面被染成黄绿，而参考实现的苔绿纸面是近中性白 `#f9fbf7`。
  现在回到参考值：00 取参考 `surface`、60 取 `surface-subtle`、150 取 `surface-strong`（暖砂 `#fdfbf7 / #faf6ef / #e9dfd0`、靛蓝 `#f8fafc / #f3f6fb / #e0e7f0`、苔绿 `#f9fbf7 / #f4f8f0 / #dee8d8`）。**同套内部**层次照旧（00↔150 有 6–8 ΔE），**跨套的区分**挪到次层与强调色：00 两两 0.52–1.00（与参考实现一致），150 两两 **2.46 / 3.42 / 4.13**。深色侧不取参考的 `surface-dark(#0d1310)`：本插件侧栏在画布下方，画布压到那里「更深」档会掉到近黑 `#050a07`，四层仍按 subtle / muted / raised / strong 落位。
- **「更浅」档不再反向压暗，也不会「点了没反应」**（用户 1.9.9 预览实测）。参考实现的暖砂纸面 L 已经是 **0.9888**，而 `lift()` 的提亮上限是 0.985 —— clamp 一夹，「更浅」反而比画布更暗（实测 `#fdfbf8 → #fafaf9`）。现在上限抬到 0.995，并给 `lift()` 加单调保护（提亮不许把亮度压下去）；近白纸面余量不足时改为「削色度 + 顶到上限」，读作更白的一张纸。悬停/选中另给 0.012 的步进地板，此前在靛蓝/苔绿的「更浅」档二者与底色**完全相同**（ΔE 0.00）。
  地板与「不许穿回画布」在近白纸面上是相互冲突的（可用空间只有 0.005–0.012），口径定为**最多下穿画布 0.008**（实测 0.0026–0.0057，ΔE ≤ 1.4，读作「被按下去一点」）。实测三档：更浅 0.73–1.12（悬停 1.19 / 选中 2.39）、同色 0.00、更深 2.02–2.13。
- **「同色」侧栏现在真的与主栏同色**（用户 1.9.9 预览实测）。旧实现里三档侧栏底色都走一遍 `lift()`，随后再补一次色度，而补偿固定用 `scheme.hue` —— 苔绿的 `scheme.hue` 是 **162（青绿）**、画布 00 的色相却是 **121（橄榄）**，于是「同色」被染成薄荷绿 `#defaeb`（与画布 ΔE **2.49**，左栏和主栏明显不是一个色）；靛蓝差 0.50、暖砂差 0.40。
  现在同色档直接取画布本身（**ΔE 0.00**，逐字节相等），另两档的补偿只补「不足的」色度并**沿用画布自身色相**。
  `tools/check-audit.mjs` 新增第 17 组断言（3 套 × 2 档 × 3 项 = 24 条）把这条锁死 —— 同色必须逐字节等于画布、另两档色相差 ≤12°。
- **新增「色觉无障碍」文档节**（审计 3.8）：预置之间的差异主要靠强调色与深色档（强调色 vs 画布在四种视觉下 ΔE ≥ 37.8，是安全通道）；暖砂与苔绿在 deutan 下几乎同色（深画布 ΔE 0.84），红/绿语义色在 protan 下只剩 5.7 —— 语义色由宿主提供、插件有意不染色，用图标或文字区分。
- **未知模式 id 不再静默**（审计 3.9）。`buildTokens` 入口对未知的 `sidebarMode / textMode / rightbarMode / scrollMode / controlMode / mdMode` 打一行 `console.warn`（带合法值清单，同值只报一次）；`tools/check-audit.mjs` 新增第 16 组断言把这条锁死。
- **自定义配色的无彩色底不再派生错色相**（审计 3.10）。纯黑 / 纯白 / 正灰的画布色度≈0，色相是数值噪声（实测出现过粉色偏色）。`customScheme()` 现在按「底色 → 文字色 → 强调色」的顺序取第一个有色度（C ≥ 0.008）的色相；三者都无彩色时判定 `achromatic`，把整套色度归零并回落到中性。
- **工程与维护**（审计 3.11）：`rampCache` 改成 96 条上限的 LRU（原来是无界 Map，长会话里配色/档位组合多起来只增不减）；`buildTokens` 结果按设置签名 memo；自愈探针加上 `--dsw-specific-menu` 与 `--dsw-alias-label-tertiary`（菜单是唯一被宿主用更高特异性规则声明过的 token，它若失效，菜单会悄悄回到宿主的冷灰）。
- **更正审计报告里的一处错**：报告说「三处 `:focus-visible` 只设 `outline-offset`、轮廓颜色吃浏览器默认」——不成立。设置页 CSS 用一条组选择器统一给了 `outline:2px solid var(--dsw-alias-label-primary)`，那三处只是覆盖 offset；`prefers-contrast: more` 下还有 `ButtonBorder` 兜底。
- README 五种语言同步：文字下限改成「浅 / 深」两套口径，并说明深色为什么需要更高的比值。

### 1.9.8（未发布）
把「承诺什么」「能不能做到」「健不健康」三件事分开说清，并修掉几个会骗自己的判据：

- **自愈判据改为「覆盖层标记 + 颜色比对」**：旧判据只看关键 token 的计算值是否非空 —— 覆盖层被宿主撤销后，宿主的基础 token 仍然有值，判据永远说「健康」，配色却早已跳回默认（假健康）。现在用两件事判断：本插件自己写入的标记属性 `--dshtp-layer` 是否还在，以及关键 token 的计算值是否等于本层期望值（明暗两色任一相符即可）。判据不成立才重放，退避与停用逻辑不变。
- **对比度报告改成单一模型：声明目标 / 物理上限 / 政策豁免 三者分开**。设置页 UI、报表 CLI（`palette-dev.mjs`）与审计断言（`tools/check-audit.mjs`）共用同一份数据，不再各算各的。
  - 声明目标：正文浅色 **12:1**、深色 **10:1**（高对比档 15:1）；次级 **6.2**（HC 9）；三级 **4.62**（HC 7）；链接 **4.62**（HC 7）。
  - 承诺面：浅色 **00/60**，深色 **950/875**。
  - 政策豁免：深色 **800**（模块面）与 **850**（次级深面）**不为正文兜底**（1.9.7 的产品策略保留），报表单列为「政策外」——单独统计，既不算达标也不算失败，也不再标成「物理上限不足」。
  - 物理上限**只由计算得出**：该承载面上黑或白前景能达到的对比度。求解器里的 0.93 只是**求解余量**，仅用于判断「目标是否高于上限」，不再混进上限本身。
  - 底线不变：AA **4.5**。政策外的面仍须过 4.5（暖砂 800 实测 6.20、850 实测 9.27，均过线）。
- **健康反馈不再含糊**：同色重放后「已恢复 N 次」会跟着刷新（判重时把重放次数算进去）；「配置已保存」与「主题已应用」分开报，应用失败有明确提示（`配置已保存，但主题没有应用成功`）。
- **界面可用性**：对比度表放进**有名可聚焦的横向滚动区**（窄栏 / 移动端 / 200% 缩放都能用）；HEX 草稿与外部改动冲突时不再覆盖新值，保留最新值并提示。
- **对比度报告量的是屏幕上真正渲染的 token**：次级 / 三级文字按插件覆盖后的 alias 值量，不再拿色阶步进代替。这一处口径错位修掉了两件事 —— 自定义配色下三级文字被报成 **4.21**（低于 AA 4.5 的假警报，实际渲染的覆盖值已被求解器兜到 ≥ 4.62），以及深色 800/850 上真实字色低于下限（暖砂三级 **3.79**、高对比档 **4.29**，均低于 AA 4.5）。
- **深色次级 / 三级文字的压暗面从 950/875 扩到 950/875/850/800**：在更亮的模块面（800）上同时守住下限，代价是深色次级 / 三级字色比 1.9.7 亮一档。
- 修正 1.9.7 记录里的自相矛盾：那一版既写「以 950/875/850 定标」，又写「800/850 标为政策外」。1.9.8 统一为「**850 政策外**」——深色 850 不为正文兜底，承诺面是 950/875。旧记法把深色模块面说成「物理不可达」同样不成立：800 的物理上限约 10.22:1、850 约 15.27:1，都高于 10:1 目标 —— 它们不是「做不到」，只是不在承诺范围内。
- README 五种语言与 [docs/DESIGN.md](docs/DESIGN.md) 同步到同一口径（Node 要求改为 `^22.19.0 || >=24.0.0`）。

### 1.9.7
暗色正文按策略 A 定标（用户反馈「白色文字太亮」）：

- 非高对比模式正文目标从 12:1 降到约 **10:1**（950 上实测 10.1–11.5）
- 以侧栏/卡片面（950/875/850）定标；**模块面 800 不再为正文兜底**（约 5.6–6.5:1，仍过 AA）
- 报表口径同步：深色正文承诺 12→10，800/850 标为「政策外」而非违例
- README 的 12:1 表述待同步（发版前完成）


### 1.9.6（未发布）
深色菜单/说明文字压暗（用户反馈「左侧菜单明度太高」）：

- 深色 `label-secondary` / `label-tertiary` 按导航实际承载面（950/875）压到 L0.78 / L0.70
- 暖砂 `#cdb295`/`#b7987a`、靛蓝 `#a3b9d7`/`#88a0c1`、苔绿 `#a0c1a9`/`#84a990`
- 侧栏对比度 8.9:1 / 6.7:1，仍高于下限；四套（含自定义）同步


### 1.9.5（未发布）
自定义配色对齐新的标签规则（不再只有三套预置享受）：

- 二级/三级/更弱文字（`label-secondary/tertiary/dimmed`）在自定义下也按主题色相给足色度
- `midC` 0.010→0.020；侧栏补色度设上限 0.010，不会过度染色
- **画布不强制推白**：自定义的页面色仍由用户选的底色派生（保留选择权）
- 对比度仍按真实承载面求解


### 1.9.4（未发布）
侧栏色度显式补足（用户对比参考实现发现侧栏偏中性）：

- 画布收敛到近中性后，侧栏继承不到暖色；现按方案 `lightC×0.75` 显式补色度（保持明度）
- 暖砂 `#f8f4ef`（C0.008，参考 `#faf6ef` C0.009）、靛蓝 `#f0f3f8`、苔绿 `#eff5f2`
- 只改浅色侧栏底色，悬停/选中随之推导；深色侧不变


### 1.9.3（未发布）
二级/三级/更弱文字**明暗两套**都提高鲜艳度（用户反馈「只是希望字色鲜艳一点」）：

- 显式覆盖 `label-secondary` / `label-tertiary`：色度 0.050–0.055（原来是 0.01–0.03 的灰），明暗各一套
- 暖砂 `#785c40`/`#c9a98b`、靛蓝 `#526685`/`#8ea6c7`、苔绿 `#4a6c54`/`#8bb097`
- `label-dimmed` / `document-preview` 同步提到 C0.030–0.040
- 仍对真实承载面求解对比度（二级 6.2、三级 4.62），不牺牲可读性


### 1.9.2（未发布）
更弱的注释层也跟随主题（用量统计 / 时间戳这类）：

- 显式覆盖 `label-dimmed`（200/750）与 `label-document-preview`（300/700），色度提到 0.018–0.023
- 对比度按 3:1（次要信息下限）对真实承载面求解：暖砂 `#887e72`/`#958a7e`、靛蓝 `#7b8592`、苔绿 `#7d8577`
- 说明：若界面里该处仍发灰，可能是宿主直接用了 Tailwind 字面色而非 token，需要具体元素定位


### 1.9.1（未发布）
注释/时间戳类文字跟随主题（用户反馈「颜色发灰」）：

- `midC` 0.010→0.020：label-caption / label-tertiary / 边框等中间层色度提高约一倍
- `label-caption` 浅色不再直接用 400 号色（只有 ~2.4:1），改为对 00/60 求解到 AA；
  暖砂 `#776855`（C0.034，画布 5.22 / 卡片 4.68）、靛蓝 `#636d81`、苔绿 `#597165`
- 深色 caption 求解保持在 800/850 ≥4.6（暖砂 `#bbad9c` 4.66）


### 1.9.0（未发布）
按参考实现（web.yily.top）的设计 token 做**精确重标定**，不再凭截图猜：

- 取得其四套配色的 surface / ink / link / accent 全表；三套预置的表面层逐字对齐：
  暖砂 、靛蓝 、苔绿 - 深色层对齐 （暖砂）等，四层与参考一致
- 正文/次级/三级/链接改用参考 ink 目标；**仍过对比度下限**——参考本身不达 AA 的三级文字会被最小幅度调深（唯一允许的偏离）
- 新增断言：三套 ink 与参考值 ΔE ≤4；深正文色相仍贴近 darkHue
- 删除旧的「三套正文色度比」断言：参考本身各套 ink 色度不同，等色度不是正确目标
- 中间层（75/100/200）按 60→150 的 OKLCH 插值，边框与悬浮态不跳变


### 1.8.4（未发布）
三套预置统一「变轻」（依据参考实现的实际取色，不是凭感觉）：

- 参考图实测：暖砂 页面 `#fcfbf7` / 卡片 `#f5efe3` / 控件 `#e8dfd0`；
  靛蓝 页面 `#ffffff` / 侧栏 `#f2f6fc` / 控件 `#e1e8f1`——共同点是**页面近白、染色集中在侧栏与控件**
- 原文三套都把色度均匀铺在所有表面上：暖砂 `#feeedb`（C0.030）、靛蓝 `#eaf1ff`（C0.020）、
  苔绿 `#e8f4ed`（C0.016），所以整体像「米黄/浅蓝/浅绿纸」
- 现在：`lightC` 暖砂 0.030→0.010、靛蓝 0.020→0.010、苔绿 0.016→0.009；
  三套画布统一到 `L0.955 / C0.008–0.010`（`#f5efe9` / `#edf0f7` / `#ebf2ef`），卡片与侧栏保留层次
- **刻意没有把画布推到纯白**：那会让「左侧栏：更浅」失去空间（ΔE 掉到 JND 以下）。
  三套实测三档 ΔE 均 ≥3.09 / 0 / 2.07，全部可辨
- 深色同步收敛：参考暗色页面 暖砂 `#1a1613` / 靛蓝 `#131723` / 苔绿 `#111b15`，
  侧栏与卡片几乎同色（扁平），只有控件提亮；
  原 `darkLift` 0.026/0.032/0.042 把画布抬到 L0.223–0.238 —— 现统一 `darkLift 0.006`，
  画布降到 `#1a1612` / `#13171f` / `#131815`（L0.203），`darkC` 0.010 / 0.019 / 0.010
- 副作用是好的：深色模块面(800)变暗后可达上限提高，「表面受限」从 8 处降到 5 处
- 正文/次级/链接的对比度全部复验（暖砂正文/画布 14.69 ≥ 12）
- 四张 README 截图与字体对照图同步重建

### 1.8.3
补齐 token 覆盖缺口（按宿主 118 个 alias/specific 做传递闭包统计：76% 跟随；余下 28 个逐项判断该不该跟）：

- **深色菜单**：`--dsw-specific-menu` 宿主带字面量兜底（深 `#303136f0`），只改 `menu-surface-fill` 不够，
  深色菜单仍是冷灰 → 现在按配色给（浅 50/深 850，alpha 0.94）
- **品牌新色**：`--dsw-alias-brand-primary-new-…` 浅色写死 `#4176e6` 宿主蓝 → 两模式都归到强调色
- **文件 diff 温度调和**：6 个 `file-diff-*` 保留绿/红语义，色相往配色拉 16–22°、压低饱和；
  断言「新增仍是绿色系、删除仍是红色系」+ 正文对 diff 底 ≥4.5
- **对比度矩阵加菜单面**：半透明面按画布合成后给出参考值，标 `※`（不计硬门），
  UI 与 `palette-dev` 共用同一份 `CONTRAST_SURFACES`（含 token 面）
- **诊断导出**：矩阵区新增「复制诊断」，输出 settings + 对比度矩阵 JSON，便于提 issue
- **forced-colors**：核实我们改的都是 UA 强制色属性，系统高对比模式会自动接管，
  因此**不加** `forced-color-adjust: none` 对抗（代码内已注明理由）
- 契约测试补 `--dsw-specific-menu` / `--dsw-alias-brand-primary-new-…` / `--dsw-alias-file-diff-added-bg`
- 明确不跟：`bg-mask-*`（遮罩）、`border-inverted*`（反色边框）、onboarding（一次性引导）——刻意保留宿主原值

### 1.8.2
**暖砂字体体系修正**（用户反馈“和其他两套不一样，尤其字体颜色”）：

- **根因 1：深色正文字色度没有按色域归一**。`paperC` 是绝对值，×2.4 后暖砂请求 130%、靛蓝 166% 色域上限，
  实际色度被裁成 0.0244 / 0.0115 / 0.0303——暖砂深正文几乎是靛蓝的两倍黄。现在三套统一 `paperC=0.0053`
  （标准档实际 ≈0.0127），浅/深正文色度比降到 1.21 / 1.10
- **根因 2：暖砂是唯一「深色面 60°、正文 84°」的双色相预置**。深色面橙棕、正文黄绿，色相分裂 24°；
  现在 `paper` 角色跟随 `darkHue`，并把暖砂 `hue` 84→72、`darkHue` 60→66，全套色相收敛到 12° 内
- 新增断言：深正文色相偏离 `darkHue` ≤12°、三套浅色正文色度比 ≤1.25、深色 ≤1.5
- 新增 `tools/preview-typography.mjs`：三套 × 明暗并排渲染同一段文字，发版前目检

语法高亮跟随配色（1.8.1 里“未做”的那条，前提已实测）：

- **实测依据**：宿主渲染器用 Shiki 的 `css-variables` 模式（`class="shiki css-variables"`，
  `style="background-color:var(--shiki-background)"`），token 颜色走 CSS 变量，
  所以覆盖 `--shiki-token-*` 生效，不需要 `!important`
- **色相策略**：从强调色相取 3 个色族（强调 / +150° 冷族 / -60° 暖族），
  同族靠明度分常/表达；每个 token 对代码块底求解到 **≥4.6:1**，四族最小 ΔE **≥10**
- 覆盖 9 类 token（keyword / function / constant / parameter / string / string-expression /
  comment / punctuation / link）+ `--shiki-background` / `--shiki-foreground`
- **md=off 时不输出**任何 shiki token，交回宿主原色
- 新增 `tools/preview-code.mjs`：用生产 token 渲染一段真实代码，发版前肉眼确认
- 断言：9 类 token 对比度 ≥4.6、四族 ΔE ≥10、shiki 背景与代码块一致、md=off 无输出

### 1.8.1
Markdown 配色丰富化（非加色相，而是加层级 + 修代码块底）：

- **标题三档**：h1/h2 → h3/h4 → h5/h6 由强到弱（`--dshtp-md-h1/h2/h3`），不再六级同色；
  层级按**着色强度**区分，字号仍由宿主决定
- **引用 / 表格 / 分割线 / 列表**：引用淡底 + 左侧强调边、表头淡底、表格边框与分割线跟随语义 token、列表标记用三级文字色
- **修代码块双层底**：宿主 `<pre>` 背景来自 `--shiki-background → --dsw-alias-markdown-code-block`（bluish-50/900），
  此前只染 `.md-code-block` 外层，内层仍是宿主中性色；现在直接覆盖该 token（含 banner），两层同色
- **新增断言**：标题三档互异、正文/代码块底 ≥4.5、正文/引用底 ≥4.5、次级/表头底 ≥5.5、
  `<pre>` 底色与代码块一致、`md=off` 时全部继承；契约测试补 `--dsw-alias-markdown-code-block`
- **未做**：语法高亮（`--shiki-token-*`）跟随配色。宿主构建里这 9 个变量只定义、无消费者，
  需先在 GUI 实测渲染器是吃变量还是内联色，再决定方案——不在未验证的情况下强改

### 1.8.0
依据竞品调研（`_audit-theme-palette-20261023/竞品调研-2026-10-23.md`，不在本仓库内）的差距反哺：把工程严谨变成可见、可分享、可验证的东西（不改求解器）。

- **对比度矩阵**：设置页新增可折叠「对比度」区，4 个前景角色 × 7 个承载面的实测/目标/表面受限三态；
  `CONTRAST_SURFACES` / `CONTRAST_ROLES` 成为唯一定义，`palette-dev.mjs` 改为消费同一份（消除两套口径）
- **「试试示例」**：导入区一键填入演示短码并触发预览；码由生产 `encodeConfig` 运行时生成，schema 演进自动跟随
- **五语 README 叙事**：新增 `## Pitfalls we hit`（1.7.0 的 5 条审计事故）与 `## Trade-offs`
  （明确不做玻璃/壁纸/市场/AI 生色），标题逐字一致由脚本断言
- **五语 `## Sharing`**：放一条真实示例短码（生产编码器生成 + `decodeConfig` 往返自检），说明它包含三色
- **截图资产重建**：4 张（浅/深/自定义面板含解释性校正/高对比），`screenshots.json` 3→4
- **主题包导出（单向）**：`npm run packs` 导出 `dsh-dream-skin/pack` 格式 8 个包（4 配色 × 明暗，每包 90 tokens），
  全部通过正文/画布对比度自检；**不实现导入**——导入会绕过对比度求解器
- 工具：`preview-settings` 支持 `--notice/--hc/--out`，透传 `id/scope/title/data-*`；
  新增 `sample-code` / `insert-readme-sections` / `insert-readme-gallery` / `export-theme-pack`；
  `check-ui` 扩到 69+ 断言（矩阵、示例码可解码、五语标题一致、单一 SURFACES 定义）
- 无存储键变更；无求解器行为变更

### 1.7.0
依据 2026-10-23 审计反哺（对 DSH 0.2.1-rc.2 实际 token 与承载面核实）：

- **A01 极端强调色**：`accentL` 钳位到 0.30–0.64；链接求解器改为双向搜索并对 00/60/150 同时达标；
  新增 `--dsw-alias-button-info-fill` 覆盖（与主按钮一样按白字 4.5 压暗），不再出现白底白链/白勾
- **A02 自定义色订阅**：`readCustom` 改为按存储内容缓存的冻结快照，新增 `subscribeCustom`；
  取色/重置/导入会更换引用并触发重渲染，空闲时快照稳定（`useSyncExternalStore` 语义）
- **A03 分享完整**：`dshs1.` 短码新增 `x`（三色），旧码仍可导入并提示未含自定义色；
  `applyConfigCode` 返回 `{ok,hasCustom}`，非法码不部分写入
- **A04 承载面求解**：深色正文（50）、次级（300）、caption（500）补上求解；目标按承载面可达上限
  钳位（0.93 余量），`palette-dev` 打印「实际/目标/可达」并标注「表面上限」，不再虚报 12/15；
  caption 注释里的 6.5:1 更正为实测值
- **A05 文字色相**：自定义文字色相进入 700/750/1000/50 档（此前只取色度）
- **A06 HEX 校验**：改为 draft + 提交，非法值显示 `aria-invalid` 与行内错误，Enter 提交、Esc 回滚
- **A07 键盘模型**：分段控件 roving tabindex + 方向键/Home/End
- **A08 标签结构**：自定义色改为 `role=group` + 色盘/HEX 两个独立 label
- **A09 健康检测**：探针比对期望值（支持 `#rgb/#rrggbb/rgb()/rgba()`），报 `mismatched`；mono 覆盖更多 token
- **A10 测试门**：`palette-dev.mjs` 改用生产 `floors()`，支持 `--hc`，硬失败退出码 1
- **A11 边界**：grid/swatches 改 `minmax(min(100%,N),1fr)`；说明 `_markdown_`/`.md-code-block`
  在当前宿主构建 CSS 中仍存在（属契约风险而非失效）；ramp 缓存上限 48
- **建议落地**：自定义面板新增**解释性校正**（强调色归一化 / 链接压暗，带实际对比度）；
  导入改为**先预览后确认**（预览不写存储）；新增按版本一次的**升级提示**（`notice.v1`，不碰配色键；
  仅升级前已有配置的用户可见）；高对比从「更多选项」移到阅读区；新增 `tools/check-host-contract.mjs`
  契约测试与 `tools/check-ui.mjs` 结构回归，并把它们接入 `npm run check` 与发版预检
- 新增 `tools/check-audit.mjs`：覆盖极端色、分享往返、文字色相、caption、深色正文可达上限、HC 卡层

### 未发版 · 发版预检补强
- `tools/release.mjs` 新增 **engines.dsh 兼容性预检**：按宿主 app-boot 的同一口径
  （`semver.satisfies(v, range, { includePrerelease: true })`）计算，守住两条底线——
  不得放行 0.1.x/0.2.0（那一代不是 `exports["./client"]` 加载器），也不得误拒
  0.2.2-rc.x / 0.3.x-rc.x（预发布分支缺失的经典坑）；`--host <版本>` 可断言具体宿主持有版本
- 起因：我一度用 node-semver 默认语义判断「0.3.0-rc.1 会被拒」，据此差点发一个给假 bug 打补丁的 1.4.4；
  复核宿主源码发现它带 `includePrerelease`，结论相反。现在这条判断固化进脚本，不再靠手算
- 该改动只影响本地发版工具，**不进 npm 包**（`files` 不含 `tools/`），故不升版本号

### 1.6.0
视觉与可访问性整改（依据 2026-10-22 第三方 UI 视觉验收审计，60/100）：

- **A2 配置块重做**：改用 `.dsh-tp-field` 容器（与普通行同节奏），导出/导入各占一行，
  反馈文案就地显示（复制→复制按钮旁，导入→导入按钮旁），导出框加 `text-overflow:ellipsis`
- **A3 复制不再谎报**：`await navigator.clipboard.writeText()`，失败或无 API 时显示
  「复制失败，请手选后 Ctrl+C」，不再无条件报成功
- **A4 布局从视口断点改容器驱动**：卡片 `repeat(auto-fit,minmax(196px,1fr))`、色块 `minmax(86px,1fr)`、
  行与分段控件 `flex-wrap`；日语长标签改短（右栏「サイドバー / キャンバス」、滚动条「無彩 / 追従 / 強調」）。
  实测 4 语 × 内容宽 564/336/216：**越界元素 0**（改前日语 216px 溢出 61px）
- **A5 语言属性**：`<section lang>` 跟随手选语言，`auto` 时回落宿主 `documentElement.lang`
- **A6 分段控件有名字**：`role="radiogroup"` + `aria-labelledby` 指向行标题，选项改
  `role="radio" aria-checked`（8 组 22 个按钮）
- **A7 反馈可播报**：复制/导入/自愈状态挂 `role="status" aria-live="polite"`，错误用 `alert`；
  导入框补 `aria-label`
- **A8 自定义面板层级**：面板底改 `bg-layer-1`（按钮不再与面板同底 1.00:1）、说明文字提到
  `label-secondary` 12px；色值从只读 `<code>` 改为**可输入 hex**（回车/失焦生效，非法值忽略）
- **A9 缩略图跟随侧栏档位**：`cardColors()` 接收 `sidebarMode`，不再写死默认档
- **A11** 黑白配色下「滚动条」「开关」置灰禁用（点了也不会有变化）
- **A12** 卡片与分段按钮 `:focus-visible{outline-offset:2px}`，焦点环不再与选中边框贴死
- **A13** 加 `prefers-reduced-motion` 降级
- **A14** `docs/DESIGN.md` 补「当前标定值」表（旧推演内容保留，注明以 `client.js` 为准）
- **A15** 删掉 4 语共 36 条从未渲染的文案键与未被调用的 `swatches()`
- **A10 证据链**：README 首图换成当前版本（5 张卡）的**静态渲染**，按真机内容宽 564 px 出图
  （浅色 / 深色 / 自定义面板三张，`--scale 2` 即 1240 px 宽），并在五语 README 里注明「静态渲染」；
  `screenshots.json` 同步为这三张
- 工具：`tools/preview-settings.mjs` 新增 `--width`（默认按真机 564px 内容宽）与 `--adv`（默认折叠），
  token 跟随 `--palette`；新增 `tools/measure-preview.mjs`（量越界/行高/关键计算色，非 0 越界退出 1）

### 1.5.3
修：选中「自定义」后滚动条不跟随取色。

- **根因**：`scrollbarTokens()` 收到的是 `SCHEMES` 里的**占位** scheme 对象
  （`accentHue` 恒为 60），而真正按三色派生出的参数只在 `rampsFor()` 内部解引用。
  于是画布/强调色都对，唯独滚动条永远停在暖橙。
- **修法**：`buildTokens()` 开头统一解引用一次（`params = scheme.custom ? customScheme(readCustom()) : scheme`），
  之后所有读 `scheme.*` 的地方改用 `params`；`scrollbarTokens(ramps, params, mode)` 也改传解析后的对象。
- **防复发**：新增 `tools/check-custom.mjs` —— 断言三种强调色必须产出三种滚动条颜色，
  且三组自选色的正文/次文/三级对比度仍在限内（现在输出
  `#2f6fd0→#bfd6f9  #a9683e→#f2cbb3  #4f7a42→#c2ddbb`）。
- 顺带：`tools/preview-settings.mjs` 支持 `PREVIEW_ACCENT`，可预览任意强调色的自定义面板。

### 1.5.2
设置页交互重排（用户反馈：自定义不该埋在「更多选项」里、取色看不到即时效果）：

- **自定义配色提为一级区块**：选中「自定义」卡片后，面板直接出现在卡片网格下方，
  不再需要展开「更多选项」
- **取色实时生效**：同时接 `onInput`（拖动过程中）与 `onChange`（确认后），
  一次 `buildTokens` 实测 **0.15 ms**，整页配色随取色实时变化，不需要"确认"动作
- **色块放大**：`input[type=color]` 变成 44px 高的圆角色块（隐藏原生 swatch 边框），
  下方显示字段名与十六进制值；三个色块一排，窄屏自动堆叠
- **恢复默认**：面板右上角一键回到出厂三色
- 修：预览渲染器不输出 `value`/`placeholder`/`readonly`，导致静态预览里色块是黑的、
  导出码框是空的（真实应用不受影响）

### 1.5.1
- `dsh.compatibility.profiles` 补 `web`：在 web profile 上实测 5 张卡片（含「自定义」）正常显示，
  与 desktop 一致；`dshOperations` 的 install/start 保持 `passed`，uninstall/rollback 仍如实 `unknown`
- 只改声明，无运行时改动

### 1.5.0
反哺 A-2 / A-3 / A-4 / A-5 —— 把"能调"扩成"可迁移、可自定义、可无障碍、可回归"：

- **A-3 自定义配色**：新增第 5 张卡片「自定义」，给 底色 / 文字 / 强调 三个颜色，
  引擎只取它们的**色相与色度**，明度仍按明暗模式由引擎决定 —— 因此对比度下限照旧成立
  （实测三组自选色：正文 14.6–15.0、三级 4.62–4.71）
- **A-2 配置导入/导出**：`dshs1.` 短码（base64url），导入逐项校验、任一不符整体拒绝；
  「更多选项 → 配置」可复制 / 粘贴导入，覆盖全部 8 项
- **A-4 高对比档**：新开关把文字下限抬到 AAA（正文 ≥15 / 次文 ≥9 / 三级 ≥7 / 链接 ≥7），
  只调求解目标，不换配色；实测 三级 4.65→7.06、次文 6.2→9.05、链接 4.62→7.03
- **A-5 真机回归可用**：dev 接口新增 `window.__DSH_THEME_PALETTE_DEV_REQUEST__` 入口，
  `tools/shoot-desktop.mjs` 改用 CDP `Page.addScriptToEvaluateOnNewDocument` 注入 + 重载，
  B 层截图回归不再依赖"无人创建的全局"
- 顺带修：求解器位移上限从写死的 0.13 改为可传参（高对比需要更大位移）；迭代次数跟随上限
- `tools/preview-settings.mjs` 支持 `--palette <id>`，可预览任意配色（含自定义）的设置页

### 1.4.4
- 补 `dsh.compatibility` 兼容性履历（反哺 A-1）：

  ```json
  "compatibility": {
    "dsh": ">=0.2.1-rc.1",
    "dshReleases": { "0.2.1-rc.2": "compatible" },
    "dshOperations": { "0.2.1-rc.2": { "install": "passed", "start": "passed", "uninstall": "unknown", "rollback": "unknown" } },
    "profiles": ["desktop"]
  }
  ```

  说明：**宿主不读这个字段**（`client-modules`/`app-boot` 只读 `dsh.bundle`、`dsh.client`、`engines.dsh`），
  它是给目录站/市场展示"哪些版本真的装过、启过、卸过"的。写入的都是**实测过的**事实：
  0.2.1-rc.2 上装过、启动过；卸载/回滚没测，如实写 `unknown`；web profile 已安装但未重启验证，暂不列入。

### 1.4.3
安全审计后的整改（`@应用安全工程师` 审计，逐条复核后落地）：

- **文档**：`tools/README.md` 删掉「把 `--remote-debugging-port` 长期写进快捷方式」的建议，改为「只在截图那一次开、抓完即关」，并说明该端口无鉴权等价于渲染进程任意代码执行，同时给出 `--remote-debugging-pipe` 替代
- **`tools/release.mjs` 去 shell 拼接**：name/version/tag 先过严格白名单，命令一律 `execFileSync(程序, 参数数组)`，npm 走 `node <npm-cli.js>`；registry 探测改用 `fetch`（不再 curl）；`--dry` 无副作用
- **发布前校验**：`PLUGIN_VERSION` 必须等于 `package.json.version`；CI 下自动加 `--provenance`；配了 `user.signingkey` 时 tag 自动用 `-s`
- **dev 钩子收口**：默认不暴露，必须 URL 带 `?dshThemeDev=1`；`attach` 返回的清理函数在插件卸载时调用；`setTheme` 补 `light/dark/system` 白名单（此前是唯一不校验输入的 setter）
- **自愈定时器**：4s → 15s，且 `visibilityState === 'hidden'` 直接早退（不再后台空转样式回读）
- **token 前缀**：`--dsh-*` → `--dshtp-*`（与宿主 `--dsh-content-*` 等命名解耦，避免跨插件碰撞）
- **`tools/shoot-desktop.mjs`**：修收尾处引用未定义变量 `original` 的崩溃；还原动作移进 `try/finally`，中途失败也不会把用户明暗偏好留在被改写状态
- **声明补齐**：peer 补 `dsh-client-ui-theme` / `dsh-client-ui-settings`（optional），`engines.dsh >= 0.2.1-rc.1`；`files` 移出 `palette-dev.mjs` 与 `tools/*`（发布面从 24 个文件降到 14 个）
- **README 五语版本钉** 1.4.0 → 1.4.3

### 1.4.2
- **修复启动崩溃**：1.4.1 为了展示元数据加了 `exports`，但漏了 `"./client"` —— 宿主 `client-modules` 找不到 client bundle 时会让 **required 插件 `modules` 启动失败**，整个 profile 的插件全部不加载（重启后界面报错、插件消失）
- `exports` 现在显式列出 `./client`、`./client.js`、`./index.js`、`./cordis.patch.yml`、`./package.json`
- `tools/release.mjs` 增加守卫：声明了 `dsh.client` 就必须能解析 `exports["./client"]`，否则发版前直接失败

### 1.4.1
- README 按 DSH 插件标准拆成五语：`README.md`（英文源）/ `README-zh.md` / `README-es.md` / `README-pt.md` / `README-hi.md`，章节结构完全一致
- 补展示元数据：`locale/{en,zh}.json` 的 `meta.title`/`meta.description` + `exports` 暴露 `./locale/*.json` 与 `./icon`
- 对齐官方工程门禁：`engines.node` → `^22.19.0 || >=24.0.0`、`packageManager` 钉 `pnpm@11.7.0`
- `dsh-plugin-dev check`：**11 passed / 0 failed / 0 warned**

### 1.4.0
- 多语言：简体中文 / 繁體中文 / English / 日本語；「自动」跟随宿主的 `documentElement.lang`
- 配色名（黑白/暖砂/靛蓝/苔绿）、各档位文案、状态行都本地化，不是只翻标题
- 宿主切换界面语言时用 MutationObserver 立刻重渲染
- 自愈加重试退避（4s → 15s → 1m → 3m → 10m）并封顶：token 真被改名时不再每 4 秒重放
- `tools/preview-settings.mjs` 支持 `--lang`

### 1.3.3
- 文档拆分：README 只留用法与当前设计（759 → 210 行），版本记录移到 `CHANGELOG.md`，逐组件设计说明移到 `docs/DESIGN.md`
- 修正 README 里过期的「当前版本 0.8.2」

### 1.3.2
- README 安装说明改为 npm / Release tarball / 源码三条路径，去掉本机绝对路径
- 新增 `tools/release.mjs`：一键发版（检查 → 发布 → 等 registry → 校验 latest → 可选打 tag）

### 1.3.1
- 自愈：override 层被宿主丢掉时（插件 fiber 重载等）自动重放，设置页显示恢复次数
- 触发点：visibilitychange / focus / pageshow / theme/change / 4s 定时；健康时零写入

### 1.3.0
- 设置页重排：2×2 缩略图卡片（随明暗切换）+ 段落式设置行 + 分段控件 + 版本徽标
- 样式从内联改为独立注入的 `settings.css`（支持 hover / 窄屏 / `body[data-ds-dark-theme]`）
- 新增 `tools/preview-settings.mjs` + `tools/shoot-html.mjs`：不打开 App 也能截设置页

### 1.2.0（未发布）
- 代码块底色独立成 `--dsh-code-fill`：与画布 ΔE 5.5（浅）/ 11.0（深），原来浅色下同色
- 行内代码用更浅的 `--dsh-code-inline`；两者都不需要 `!important`

### 1.1.0（未发布）
- Markdown 标题 / 加粗用「正文色 + 强调色相」染色（标题 12.3、加粗 14.4，均远高于 AA）
- 「更多选项」加一档 Markdown 强调开关；黑白配色不染

### 1.0.0（未发布）
- `fromOklch` 加色域钳位（二分收色度），修「通道顶满 255 → 色相被压歪」
- 浅色侧重标定：近白端 −0.044、`lightC` 上调、暖砂/苔绿色相收敛，套间最弱一对 ΔE 2.15 → 3.07
- 黑白配色接入引擎：三级 4.67、链接 4.63、主按钮 4.54（原 3.42 / 3.91 / 4.34）
- 主按钮 `button-primary-fill` 从宿主红色改为强调色（并压到按钮文字 ≥4.5）
- 设置页改为「配色 / 强度 / 面板表面 / 更多选项」，运行时 token 自检

### 0.18.0（未发布）
- 主人气泡 `--dsw-specific-bubble` 改由「强调色 11%（深 13%）混画布」派生：暖砂 `#ffede0` → `#f2e5d1`，不再发粉
- 气泡与画布 ΔE 4.2–7.9、气泡内文字对比 9.99–13.53

### 0.17.0（未发布）
- 文字着色第二版标定：跟随 3.0/+0.055（ΔE 2.77 → 5.5）、浓一些 4.6/+0.085（ΔE 8.5）
- 会话条目标题与正文共用 token，一并变深变暖

### 0.16.0（未发布）
- 注释文字跟随文字着色：`mid` 角色乘 `1+(textScale-1)×2.0`，`midC` 0.006 → 0.010（注释 ΔE 1.76 / 3.61）
- 深色注释 `label-caption` 改用色阶 500：对比 3.0 → 6.0–6.3

### 0.15.0（未发布）
- 修：深色模式下选中卡片是白底白字（内联色混错了浅色画布）→ 改由 `--dsh-card-picked` 按模式给值
- 设置页细项收进「更多选项」，默认折叠，只留配色卡片

### 0.14.0（未发布）
- 新增 `darkHue`：暖砂深色层色相 88° → 60°（黄 → 橙棕），修掉暗部「发绿 / 橄榄」
- 深端 neutral（深色滚动条）跟同一夜色相；浅色侧与另两套未变

### 0.13.0（未发布）
- 深色侧栏改用独立 `darkLift`（0.048 / 0 / −0.042）：与画布 ΔE 从 1.9–2.3 提到 4.03–5.05
- 侧栏内正文对比 12.48–16.40（仍 AAA）

### 0.12.0（未发布）
- 文字着色重新标定：加 `textLift`（正文抬 0.030 / 0.065），三档 ΔE 从 0.35–1.66 提到 2.77–6.79
- 左栏会话条目与正文共用 `label-*`，一并生效
- MIN_MAIN 15.5 → 12（给带色正文留空间，仍 ≥ 1.7 倍 AAA）

### 0.11.0（未发布）
- 开关 / 复选框 / 单选 / 插件页主按钮改跟强调色（覆盖 `--dsw-alias-brand-primary`，13 处引用一个支点）
- 设置页现为六行：配色 · 文字着色 · 左侧栏 · 右侧栏 · 滚动条 · 开关勾选

### 0.10.0（未发布）
- 滚动条三档着色（中性 / 跟随 / 强调），只抬色度不改明度；黑白配色下该行说明会自述无效
- 设置页现为五行：配色 · 文字着色 · 左侧栏 · 右侧栏 · 滚动条

### 0.9.0（未发布）
- 右侧栏可单独上色（默认与左栏同色）；注入一条命中 `data-rightbar-col` 的 CSS，颜色走 `--dsh-rightbar-fill`
- 全屏右栏退回画布色

### 0.8.2（未发布） （强调色改为设计稿标准值）
- 强调色新增 `accentL`：色阶中段 `500` 的明度定死成设计值，不再乘 `accentScale`
  —— 乘性缩放会把选中态软底 `deepseek-50/100/200` 一起压暗 13%
- 暖砂 `#a9683e`（h 68→52.4，C 0.105→0.101）、靛蓝 `#3f66a3`（C 0.135→0.107，L 0.589→0.511）、
  苔绿 `#4f7a42`（h 165→139），三档 8-bit 精确复现
- 链接与基准色**解耦**：`--dsw-alias-link` 从基准色派生（浅 `#915227 / #3c629f / #436d36`，
  深 `#c8a28b / #95acd0 / #9cb695`）—— 基准色当正文只有 3.40–4.41，过不了 AA
- `tuneRamps` 不再改写 `deepseek-500`（品牌面保持标准值），只兜深色链接 `400`
- `palette-dev.mjs`：链接列改读 `--dsw-alias-link`；每套新增「品牌基准色 / 链接」一行

### 0.8.1（未发布） （深色专项审计后）
- `ROLE` 深色权重拉平（`800/850` 0.6/0.75 → 1.0，`875` 0.85 → 0.95）—— 卡片/模块不再比画布灰 30–45%，三套在深色层上的区分度从 ΔE 1.02 回到 2.05
- `darkC` 按色相归一化：暖砂 0.016→0.014、靛蓝 0.017→0.028、苔绿不动；三套占可用色域 30/19/24%（原 35/11/24%，暖砂发脏、靛蓝发灰）
- `darkLift` 三套错开 0.026 / 0.032 / 0.042（原统一 0.034）—— 暖砂↔苔绿 850 层 ΔE 1.15 → 2.05
- `paperC` 下调 0.014 / 0.009 / 0.0125 —— 靛蓝不再撞 8-bit 天花板（原「浓一些」请求 0.033 实得 0.0241）
- 深色交互 alpha 0.08/0.14/0.24 → 0.05/0.09/0.14 —— 深色悬停强度从浅色的 1.8–2.1 倍回到 1.2–1.3 倍
- 侧栏深色放开下沉阻尼 —— 深色导轨边界 ΔE 0.49 → 1.86–2.10，与浅色同档对齐
- 苔绿强调 `155°→165°` —— 与底色（h≈152）从 2° 拉开到 13°，链接不再只靠明度识别
- 深色对比度：正文 14.40–14.98、三级 4.79 起、链接 4.65 起（0 处低于下限）

### 0.8.0（未发布） （- 新增「文字着色」三档（中性 / 跟随 / 浓一些），默认「跟随」；只放大文字色度，不影响对比度）
- 配色重建缓存按（配色, 文字档）分键；设置页新增第三行控件

### 0.7.0（未发布） （UI 设计审计后全面修订）
- 求解靶点从画布改为真实承载面（`60` / `800` / `150`），上限 0.09 → 0.13 —— 修掉 12 处真实的 AA 违规
- `accentScale` 三套全部 1.0 —— 强调色浅端恢复四档层次
- 阈值 15.5 / 6.2 / 4.62；三级与次级重新拉开明度
- 近白端压缩改等量下移 —— 画布↔卡层 ΔL 从 0.0147 回到 0.026
- 深色：`darkC` 下调、`darkLift` 上调 —— 底色不再读成深咖，卡片不再脱色
- 暖砂强调 `55°→68°`（离橙棕脏区）、苔绿 `146°→155°`（绿字可读性）、靛蓝 `276°→262°`（回到品牌蓝）
- 暖砂/苔绿拉开色相与色度 —— 唯一低于 JND 的一对从 ΔE 1.30 提到 2.15
- 侧栏步进 0.012 → 0.024/0.030，默认档改「更深」，并给「更浅」加穿层护栏
- `palette-dev.mjs` 改为加载生产引擎

## 版本详解

### 为什么配色会自己跳回默认（v1.3.1）

现象：设了暖砂，用了一阵（跑模型操作、切窗口、长时间放着），**突然变成黑白**，但设置页里
「暖砂」还是选中的。原因在宿主的 override 生命周期：

```js
// ThemeRuntime（宿主）
overrideTokens(source, tokens) {
  const layer = { seq: this.overrideSeq++, tokens: validateOverrides(source, tokens) }
  this.overrides.set(source, layer)
  this.publish()
  return () => { if (this.overrides.get(source) !== layer) return; this.overrides.delete(source); this.publish() }
}
```

而插件侧拿到的是被 runner 包装过的版本 —— **每一层的 disposer 都被挂在调用方的 fiber 上**：

```js
const dispose = Reflect.apply(method, target, [id, tokens])
ctx.effect(() => dispose, 'cordis-client-runner: dynamic theme override layer')
```

也就是说：**这一层的生命周期 = 插件 fiber 的生命周期**。插件被卸载/重载、或 fiber 的
effect 被清理时，层就没了；`composeActive` 在 `overrides.size === 0` 时直接返回内置主题，
于是页面**静默**变回默认色 —— localStorage 里的选择还在，设置页自然还显示暖砂。

会触发 fiber 重载的典型情况：动态插件被宿主重载（改过插件文件/profile、宿主自我更新）、
渲染进程恢复、以及某些"模型操作"引起的插件重载。

**v1.3.1 的处理**：加了一个自愈环。窗口重新可见 / 获焦 / `pageshow` / `theme/change` /
每 4 秒，回读 `body` 上的 5 个关键 token；不在了就把最后一层重放：

```
[dsh-theme-palette] 配色层不见了（visibility），已重放第 1 次
```

设置页顶部会显示"配色层被宿主丢过一次，已自动恢复 N 次"；健康时不产生任何写入。

**另一种可能**（值得先分辨）：如果是 **localStorage 被清**（换 profile、清缓存），设置页会回到
「黑白」选中态 —— 那种情况自愈救不回来，因为连"你想用哪套"都丢了。判据就是**设置页里选中的
是不是你原来那套**。

### 设置页重排（v1.3.0）

原来的设置页是六行"工程参数"堆在一起、每行都是同一种小药丸。v1.3.0 重做成四段：

```
主题色                                            v1.3.0
配色与明暗模式独立；切换后立即生效并保存在 localStorage。
┌────────────┬────────────┐
│ 缩略图      │ 缩略图   ✓  │   2×2 卡片：每张是「侧栏 + 画布 + 两行文字 + 强调色」的微缩图
│ 黑白 无色调 │ 暖砂 米白+棕 │
└────────────┴────────────┘
强度      正文与强调的着色程度        [静][标准][浓]
左侧栏底色 相对会话区的层次           [更浅][同色][更深]
右侧栏底色 面板底色                   [同左栏][跟随画布]
▸ 更多选项
```

四个变化：

1. **卡片有缩略图**，而且**跟随明暗模式**（深色下缩略图自动用深色画布/侧栏/强调色 —— 靠
   `body[data-ds-dark-theme]` 切换两组 CSS 变量，不是靠 JS 猜模式）；
2. **段落式行**：左边标题 + 副标题，右边分段控件，中间一条 0.5px 分隔线；
3. **分段控件**（segmented）替代散落的药丸，选中项是"抬起"的白色/深色片；
4. 标题右侧有版本徽标；样式表是**独立注入的一条 CSS**（`data-plugin-css=.../settings.css`），
   不再全是内联样式 —— 于是能写 `:hover`、媒体查询（窄屏单列）和 `body[data-ds-dark-theme]` 作用域。

### 怎么"看见"它

设置页在 App 里，外部浏览器进不去；但组件树可以脱离 React 渲染成静态 HTML：

```bash
node tools/preview-settings.mjs     # → tools/preview-light.html / preview-dark.html
node tools/shoot-html.mjs --file preview-light.html --out tools/out/settings
node tools/shoot-html.mjs --file preview-dark.html  --out tools/out/settings
```

`preview-settings.mjs` 用生产引擎算 token、用插件真正注入的 `SETTINGS_CSS`，
再用一个 20 行的树→HTML 渲染器把组件铺开 —— 所以截图就是"设置页长什么样"，
不是另画一版。

### 代码块底色（v1.2.0）

宿主的代码块底色是 `--dsw-alias-bg-layer-1`，而**浅色模式下 layer-1 就是画布色（都指色阶 00）** ——
所以代码框和正文底是同一块，只剩一条发丝线。v1.2.0 给它一档独立的"内嵌面板"色：

| token | 暖砂 | 与画布 ΔE | 代码文字对比 |
|---|---|---|---|
| `--dsh-code-fill`（围栏代码块） | `#eadcc7` / `#3e342d` | **5.52 / 11.05** | 5.75 / 8.04 |
| `--dsh-code-inline`（行内 `code`） | 更浅一档（色阶 50 打底） | ~5.9 | — |

规则同样注入到 `<head>`：

```css
[class*="_markdown_"] .md-code-block{background:var(--dsh-code-fill,inherit)}
[class*="_markdown_"] :not(pre) > code{background:var(--dsh-code-inline,inherit)}
```

`.md-code-block` 是宿主自己的类（不是 CSS Modules 哈希），特异性 0,2,0 高于宿主的 `.block`（0,1,0），
**不需要 `!important`**。`:not(pre) > code` 把围栏块里的 `code` 排除掉，只染行内代码。

### Markdown 标题与加粗（v1.1.0）

宿主的 Markdown 里，标题和正文**同色**（都继承 `label-primary`），只靠字号/字重区分。
v1.1.0 起把「标题」和「加粗」各染一档配色：

| token | 暖砂 | 对比度（画布 / 卡层） |
|---|---|---|
| `--dsh-md-heading` | `#42250b`（浅）/ `#fed9bd`（深） | 12.3 / 11.3 |
| `--dsh-md-strong` | `#2f1b08` / `#ffe2cc` | 14.4 |

**加粗只加色度、不动明度** —— 一开始用"混到强调色"，结果加粗比正文更亮（浅色下反而更弱）；
改成只叠加色相后，加粗与正文同深、只是"更有色"。标题则再往中间推一点明度
（浅色提亮 0.065 / 深色压暗 0.030），配合更大的字号拉开层级。

规则注入到 `<head>`，用 CSS Modules 的局部名做钩子（`[class*="_markdown_"]`，比整串哈希耐版本）：

```css
[class*="_markdown_"] :where(h1,h2,h3,h4,h5,h6){color:var(--dsh-md-heading,inherit)}
[class*="_markdown_"] strong{color:var(--dsh-md-strong,inherit)}
```

「更多选项 → Markdown 强调 > 同正文」可以关掉（token 置 `inherit`，回到宿主原样）。
黑白配色不染（它没有色相可跟）。

### 主人气泡（v0.18.0）

用户消息的底色是 `--dsw-specific-bubble`，宿主指向 `deepseek-50` —— 也就是「品牌色的极浅底」。
我的强调色色度比宿主的品牌蓝高得多，浅端直接算出 **`#ffede0`（R 顶满 255）**，在暖砂下看着发粉发红。

v0.18.0 改成「强调色按比例混进画布」，气泡是**有色味的浅底**而不是一块彩色：

| | 气泡 | 与画布 ΔE | 文字对比 |
|---|---|---|---|
| 暖砂 | `#f2e5d1`（浅）/ `#362c24`（深） | 4.2 / 7.9 | 13.53 / 11.89 |
| 靛蓝 | `#dce5f5` / `#272f40` | 4.9 / 7.8 | 13.29 / 11.62 |
| 苔绿 | `#dde9dd` / `#2b342b` | 4.6 / 7.5 | 13.39 / 11.37 |

混合比例：浅色 11%、深色 13%（`mix(强调色, 画布)`）。ΔE 4–8 足以让气泡读成一块面，
又不会盖过正文。

### 文字着色第二版标定（v0.17.0）

v0.12 把三档从“不可见”提到“可感知”，但**默认档 ΔE 只有 2.77** —— 在 14px 的会话标题上仍然读作黑字。
v0.17.0 再标定一次，让默认档真正看得出配色：

| 档位 | scale / lift | 暖砂标题 | ΔE | 正文对比 |
|---|---|---|---|---|
| 中性 | 1 / 0 | `#12110f` | 0 | 17.2 |
| **跟随（默认）** | 3.0 / +0.055 | **`#201d17`** | **5.5** | 15.3 |
| 浓一些 | 4.6 / +0.085 | `#28241a` | 8.5 | 14.1 |

靛蓝跟随是 `#1b1d23`（ΔE 5.4）、苔绿 `#191f1b`（ΔE 5.6）。
深色正文同步加深：暖砂 `#f3efe5` → `#f9efd6` → `#feeeca`（ΔE 2.1 / 3.6）。

会话条目标题与正文共用 `label-primary`，所以左栏列表跟着一起变 —— 侧栏内对比 14.41（跟随）/ 13.27（浓一些），
仍远高于 AAA 的 7。**如果想要纯黑**，切「中性」即可。

### 注释 / 说明文字（v0.16.0）

DSH 里“注释类”文字走的是 `--dsw-alias-label-caption`：宿主浅色指色阶 **400**、深色指 **600**。
它落在 `mid` 角色上，原来只给 0.006 的色度 —— 等于灰字。v0.16.0 两件事：

**① 跟着「文字着色」走（部分强度）**。`mid` 角色（注释 400/600、三级、`label-dimmed` 200）
乘 `1 + (textScale-1) × 2.0`，并且 `midC` 从 0.006 提到 0.010：

| 档位 | 暖砂注释 | ΔE | 靛蓝 / 苔绿注释 |
|---|---|---|---|
| 中性 | `#b3b1ad` | 0 | `#afb1b6` / `#aeb3af` |
| 跟随 | `#b8b1a1` | 1.76 | `#abb2c1` / `#a7b6aa` |
| 浓一些 | `#bdb194` | 3.61 | `#a5b2cd` / `#9ebaa5` |

注释刻意比正文弱一档（正文 跟随 ≈2.8、浓一些 ≈6.4），否则整页都会“彩色化”。

**② 修掉深色注释的对比度**。宿主深色注释用 600，而这个步为了「浅色三级文字达标」被求解压暗过，
在深底上只剩 **3.0:1**。深色这一侧改用 500 → **6.0–6.3:1**；浅色保持宿主原值
（宿主自己的浅色注释就是 1.95，那是它的低对比设计，要提亮我可以换 500）。

### 设置页的两条约定（v0.15.0）

**① 选中卡片用 token，不用内联色。** 之前选中项的淡底是「强调色 8% 混**浅色**画布」写成内联样式的，
深色模式下就变成一张近白卡片，而卡片文字是 `label-primary`（深色下近白）—— 白底白字。
现在由 token 层给 `--dsh-card-picked` 一对值：

| | 选中底色 | 卡片文字对比 |
|---|---|---|
| 暖砂 浅 / 深 | `#f3e6d3` / `#3b3028` | 14.55 / 11.17 |
| 靛蓝 浅 / 深 | `#dde7f6` / `#2a3345` | 14.25 / 11.00 |
| 苔绿 浅 / 深 | `#deebdf` / `#2f392f` | 14.42 / 10.55 |

**② 细项默认折叠。** 配色卡片下面是「▸ 更多选项」；文字着色 / 左侧栏 / 右侧栏 / 滚动条 /
开关勾选这五行只在展开后出现。所有档位都保留，默认值就是我推荐的那套，不展开也能正常用。

### 1.0.0（未发布）：按设计审计重做的四件事

**① 色域安全钳位。** `fromOklch` 现在先判断是否超出 sRGB（线性通道必须落在 [0,1]），
超了就二分收色度，而不是把通道硬钳到 0/255。此前「暖砂气泡算出 `#ffede0`、R 恒为 255」
就是这个缺口的症状 —— 颜色会被压成另一个色相。

> 顺带更正审计里的一个数字：我第一次测「占色域 %」只判断了负通道（漏了超 1 的情况），
> 所以报成 3–12%。真实值是 暖砂 62% / 靛蓝 **99%** / 苔绿 26% —— 靛蓝不是"没上色"，
> 而是**顶到高明度的蓝色上限还超了一点**（14.8‰ 请求 vs 14.1‰ 上限）。

**② 浅色侧重标定。** 近白端整段下移 0.044（画布 0.968 → 0.955），高明度处色域上限因此抬高
（蓝色在 L 0.97 只有 14‰、L 0.95 有 24‰）；`lightC` 上调到 0.030 / 0.020 / 0.016；
暖砂浅底色相 88→84、强调 52→60（收敛到 ±20° 内）；苔绿 152→162 与暖砂拉开。

套间区分度（浅画布 ΔE）：暖砂↔苔绿 **2.15 → 3.07**、暖砂↔靛蓝 3.84 → 5.04、靛蓝↔苔绿 2.28 → 2.85。

**③ 黑白配色也过引擎。** 它色度全 0（保持宿主原貌），但走 `tuneRamps` 求解：
三级文字 3.42 → **4.67**、链接 3.91 → **4.63**（都到 AA 了），主按钮顺带压暗到 4.54。

**④ 信息架构从 6 行参数改成 3 段。**
配色卡片 → **强度**（静 / 标准 / 浓）→ **面板表面**（侧栏三档 + 右栏两档）→ **▸ 更多选项**（滚动条、控件）。
「文字着色」改名为「强度」并升为主控件；旧的「跟随 / 浓一些」是相对量，现在是一档风格。

**⑤ 运行时自检。** 应用后回读 `body` 上的 5 个关键 token，写不进去就在设置页显示
「⚠ 部分 token 未生效」并在控制台告警 —— 宿主改 token 名时不再静默失效。
