dsh-theme-palette 1.13.0 → 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,153 @@
1
+ # dsh-theme-palette 全量审计与反哺(v1.10.0 → 1.11.0 批次)
2
+
3
+ > **状态:本文件先于修订落盘**(2026-10-27)。各条的「证据」列均为修订前实测;
4
+ > 「落地与复核」一节在修订完成后回填。分数与结论以修订前的 1.10.0 为对象。
5
+
6
+ 审计对象:`client.js`(3749 行,生产引擎)、`index.js`、打包与发版链、`tools/` 全部校验脚本、五语 README 与 docs。
7
+ 方法:通读全部源码 + 本地复跑 `npm run check` 六件套与 `palette-dev.mjs` 两档报表 + 对可疑点写独立脚本实测(caption 的 APCA Lc、npm 包内容、取色开销、组合数口径)。
8
+
9
+ ## 0. 总评
10
+
11
+ **89 / 100。** 复核结论:`npm run check` 全绿,两档报表 0 硬失败、低于 AA 底线 0 处,README 声明的承诺与实测一致。
12
+ 验证体系(1080 组合全扫描、20 组回归断言共 371 条、自愈假宿主测试、宿主 asar 契约测试、发版安全脚本)是本插件最强的资产。
13
+ 本次发现的 12 条问题里,两条 P1 都是半小时级小修;其余为口径一致性与防御性优化。
14
+
15
+ | 维度 | 得分 | 一句话 |
16
+ |---|---|---|
17
+ | 架构与宿主集成 | 9.5 | 只走 overrideTokens 一条正道;自愈带退避与停用;卸载清理完整 |
18
+ | 色彩引擎正确性 | 9.0 | OKLCH 重建 + 色域钳制 + 真实承载面求解;色彩数学有 40 条单测锚定 |
19
+ | 无障碍 | 8.5 | 承诺面全绿 + CVD 守门 + APCA 双口径;caption 深色口径不一致(P2-3) |
20
+ | 测试基础设施 | 9.0 | 断言密度极高;缺 CI 与真实浏览器端到端(P2-5) |
21
+ | 代码质量 | 8.0 | 注释即决策记录;3749 行单文件与若干组织问题(P3-8/9) |
22
+ | 性能 | 8.0 | 双层缓存 + 后台不回读;取色拖动的宿主侧重写无合并(P2-4) |
23
+ | 国际化 | 7.0 | 四语完整;回退语言选错(P1-2) |
24
+ | 安全 | 9.5 | 无 eval、纯文本渲染、短码全字段白名单、发版无 shell 拼接 |
25
+ | 打包与发布 | 8.0 | 发版脚本 9.5 分;npm 包内 README 截图全部 404(P1-1) |
26
+ | 文档 | 9.5 | DESIGN.md 逐组件推演;CHANGELOG 66KB 可追溯 |
27
+
28
+ ## 1. 问题清单(核实证据 → 修订方案)
29
+
30
+ ### P1-1 npm 包内 README 的 4 张截图全部失效
31
+
32
+ **证据**:`package.json` 的 `files` 不含 `assets/`;解包 `dsh-theme-palette-1.10.0.tgz` 确认无 assets 目录(`tar -tzf | grep -c assets` = 0)。
33
+ npmjs.com 渲染 README 时 `assets/screenshot-*.png` 四处相对链接 404,而 README 的卖点正是这些截图。四图合计约 305KB。
34
+ **方案**:`files` 加入 `"assets"`。选「打进包」而不是「改绝对 GitHub URL」:自包含、不依赖仓库地址不变、npm 与镜像双兼容;包体积增量约 305KB(现包 140KB),可接受。
35
+
36
+ ### P1-2 i18n 回退语言不合理:非 zh/ja/en 宿主看到中文界面
37
+
38
+ **证据**:`client.js` `hostLocale()` 只识别 zh/ja/en,其余(fr/de/es/pt/…)返回 null;`pickLocale` 兜底 `|| 'zh-CN'`。
39
+ 德语用户的宿主语言得不到识别时,设置页整页中文。项目维护 es/pt/hi 三份 README,明显有国际受众。
40
+ **方案**:兜底改为 `|| 'en'`;并把 `pickLocale` 加入 dev 钩子导出(P2-6),在 `check-audit` 加断言:宿主语言 fr/ko/de → `en`,zh-TW → zh-TW,ja → ja。
41
+
42
+ ### P2-3 深色注释文字(label-caption)没跟上自家「深色侧下限」哲学
43
+
44
+ **证据(修订前实测,最深面 800)**:
45
+
46
+ | 配色 | 标准 caption WCAG / Lc | 同面三级文字 WCAG / Lc | HC caption Lc | HC 三级 Lc |
47
+ |---|---|---|---|---|
48
+ | 暖砂 | 4.93 / **51.7** | 6.43 / 66.3 | 71.7 | 76.8 |
49
+ | 靛蓝 | 4.89 / **47.9** | 6.44 / 61.9 | **66.6** | 72.0 |
50
+ | 苔绿 | 4.86 / **48.5** | 6.47 / 63.3 | **68.0** | 73.1 |
51
+
52
+ 插件在 `MIN_SECONDARY_DARK` 的注释里论证了「WCAG 比值在暗底高估可读性」,据此给三级定了 6.4、给链接加了 Lc 60 闸——
53
+ 但 caption 深色侧仍按**浅色**下限(4.85)求解,标准档 Lc 只有 48–52,低于自己给文字定的 Lc 60 舒适线;caption 也不在
54
+ `CONTRAST_ROLES` 报表模型里,设置页矩阵看不到它。`check-audit` 第 4 组同样只用浅色 floor 校验。
55
+ **方案**:caption 深色在现有 WCAG 求解后追加 `raiseToLc` 闸——标准档 Lc 60;**HC 档定 70 而不是照抄链接的 75**:
56
+ HC 三级文字 Lc 落在 72.0–76.8,caption 套 75 会反超三级(靛蓝 75 > 72.0),破坏「注释弱于三级」的层级。
57
+ 同步:`check-audit` 第 4 组加 Lc 断言与层级断言(caption 在 800 上 WCAG ≤ 三级),DESIGN.md 口径表补一行。
58
+
59
+ ### P2-4 取色拖动期间每个 input 事件都全量重放覆盖层(降级为防御性优化)
60
+
61
+ **证据(含对初版审计的自我修正)**:初版审计引 `client.js` 内注释「buildTokens 实测 2.4ms/次」推断拖动开销大。
62
+ 本次实测:60 个连续中间色 × 未命中缓存的全量构建共 **3ms(约 0.05ms/次)**——插件侧构建不是瓶颈。
63
+ 真正的每事件开销在宿主侧:`overrideTokens` 重写约 200 个 token 触发全页样式重算,外加 rAF 后 `getComputedStyle` 强制回读。
64
+ **结论**:不是实测到的性能缺陷,是「把宿主侧昂贵操作按帧合并」的防御性优化——拖动中每个 input 事件都打一次全页重排,在低端机/大文档下仍会掉帧。
65
+ **方案**:`setCustomColor` 的 `applyAll + emitCustom` 改为「标脏 + requestAnimationFrame 合并」,同帧多次取色只重放一次;
66
+ 离散动作(重置/派生/导入)保持同步。`check-selfheal` 加第 7 场景:同一帧内 3 次改色 → overrideTokens 恰好 +1。
67
+
68
+ ### P2-5 没有 CI
69
+
70
+ **证据**:`.github/` 不存在;`release.mjs` 预留了 `CI === 'true'` 的 provenance 分支但永远走不到。全部质量门只在本地/发版时跑。
71
+ **方案**:新增 `.github/workflows/ci.yml`:push/PR 触发,Node 22/24 矩阵,跑 `npm run check` + `npm run report`。
72
+ check 脚本只依赖 node 内建模块(semver 仅 release 用),无需 install;宿主契约脚本在无 asar 的 CI 上自动 SKIP,不阻断。
73
+
74
+ ### P2-6 dev 钩子未导出 APCA 与求解函数
75
+
76
+ **证据**:`client.js` dev 导出清单缺 `apcaContrast` / `raiseToLc` / `solveForeground` / `pickLocale` / `floorsDark`——
77
+ 外部工具想复算某个 token 的 Lc(如本审计对 caption 的取证)只能自带一份 APCA 实现,口径可能漂移。
78
+ **方案**:补进导出对象;`check-audit` 的新断言直接用导出的 `apcaContrast`。
79
+
80
+ ### P3-7 `encodeConfig`/`decodeConfig` 用已废弃的 `escape`/`unescape`
81
+
82
+ **证据**:`client.js` `btoa(unescape(encodeURIComponent(...)))` / `decodeURIComponent(escape(atob(...)))`。
83
+ **方案**:换 `TextEncoder`/`TextDecoder`。两条路径产出逐字节相同的 UTF-8(escape 链的语义就是 UTF-8 字节串),
84
+ 新旧版本互解码完全兼容;`check-audit` 第 2 组的旧码解码断言继续守着这一点。
85
+
86
+ ### P3-8 `TEXT_TARGETS` 大常量定义在 `buildTokensRaw` 函数体内
87
+
88
+ **证据**:每次未命中缓存构建都重新分配三套 × 明暗 × 四角色的对象字面量,且与 `SURFACE_TARGETS` 的模块级组织不一致。
89
+ **方案**:上提到模块级(紧随 `SURFACE_TARGETS`)。
90
+
91
+ ### P3-9 `getConfigCode` 闭包引用了下一行才声明的 `langPref`
92
+
93
+ **证据**:`const getConfigCode = () => encodeConfig({ …, lang: langPref })` 在 `let langPref = readLangPref()` 之前。
94
+ 运行时无害(调用发生在初始化后),但阅读时易误判为 TDZ 错误。
95
+ **方案**:把 `langPref` 声明上移。
96
+
97
+ ### P3-10 组合数口径混用:README 说 2160,实际 1080
98
+
99
+ **证据**:`palette-dev.mjs` 全组合扫描实际循环 5 配色 × 3 侧栏 × 3 文字 × 2 Markdown × 3 滚动 × 2 控件 × 2 高对比 = **1080**
100
+ (输出也打印 1080);CHANGELOG 1.9.10 已改口径「从 2160 降到 1080」。但五份 README 仍写「2160 组(5 × 216 × 两档)」——
101
+ 公式还把高对比算了两次(216 已含 hc 档)。`docs/DESIGN.md:326` 同样残留 2160。
102
+ **方案**:五语 README 与 DESIGN.md 统一改为「1080 组(5 套 × 216 组设置,已含标准/高对比两档)」。
103
+
104
+ ### P3-11 升级提示文案与版本号无同步断言
105
+
106
+ **证据**:每版要手改 4 语种的 `notice` 字符串;`check-ui` 只断言「可渲染」,不校验文案含当前版本号——漏改不会红。
107
+ **方案**:`check-ui` 加断言:client.js 源内 `notice: '…'` 恰好 4 处,且每处都包含当前 `PLUGIN_VERSION`。
108
+
109
+ ### P3-12 `label-dimmed`/`document-preview` 的 3.0 下限不在口径表里
110
+
111
+ **证据**:这两个 token 按「装饰性」解到 3.0,不在 `CONTRAST_ROLES` 承诺模型内;DESIGN.md 未说明,未来审计会重复疑问。
112
+ **方案**:DESIGN.md 承诺口径一节补一行说明其定位与底线。
113
+
114
+ ## 2. 修订顺序
115
+
116
+ P1-1 → P1-2 → P2-6(先导出,后续断言要用)→ P2-3 → P2-4 → P2-5 → P3-7 → P3-8 → P3-9 → P3-10 → P3-11 → P3-12。
117
+ 每批修改后跑 `npm run check`;全部完成后跑两档报表 + `npm pack --dry-run` 验包。
118
+ 版本策略:本批不 bump `PLUGIN_VERSION`(截图元数据与版本绑定,重拍需要宿主环境);
119
+ CHANGELOG 落「1.11.0(未发布)」一节,发版时随版本/notice/截图一起走。
120
+
121
+ ## 3. 落地与复核(2026-10-27 修订完成)
122
+
123
+ 12 条全部修订,按第 2 节顺序执行。逐条结果:
124
+
125
+ | # | 修订 | 落点 | 复核证据 |
126
+ |---|---|---|---|
127
+ | P1-1 | `files` 加 `assets` | `package.json` | `npm pack --dry-run`:20 文件 / 435.6KB,4 张截图 + meta 入包 |
128
+ | P1-2 | 回退 `en` + `pickLocale` 进 dev 钩子 | `client.js` `pickLocale` | `check-audit` 第 21 组:fr → **en**;zh → zh-CN;手选原样 |
129
+ | P2-3 | caption 深色 Lc 闸 60/70 | `client.js` `aliasOverrides` | 第 4 组新断言全过:标准 Lc 60.0(WCAG 5.73–6.19,仍 ≤ 三级 6.4x);HC 暖砂原值 71.7 未动、靛蓝 66.6→72.0 ≤ 三级 7.67 |
130
+ | P2-4 | `scheduleCustomApply` 帧合并 | `client.js` `setCustomColor` | `check-selfheal` 场景 6:帧内 2 次取色 overrideTokens **+0**,flush 后 **+1**,末次生效(#1e2f41 → #73889d) |
131
+ | P2-5 | CI 工作流 | `.github/workflows/ci.yml` | push/PR × Node 22/24 跑 check + report;无外部依赖 |
132
+ | P2-6 | dev 钩子补 6 个导出 | `client.js` dev 导出 | `check-audit` 直接用导出的 `apcaContrast`/`pickLocale` |
133
+ | P3-7 | TextEncoder/TextDecoder | `encodeConfig`/`decodeConfig` | 第 2 组旧码(Buffer base64url 构造)仍可解码 |
134
+ | P3-8 | `TEXT_TARGETS` 上提模块级 | `client.js`(紧随 `SURFACE_TARGETS`) | 六件套全绿 |
135
+ | P3-9 | `langPref` 声明上移 | `client.js` `apply()` | 六件套全绿 |
136
+ | P3-10 | 组合数统一 1080 | 五语 README + `docs/DESIGN.md` | 与 `palette-dev.mjs` 输出的「全组合扫描(1080 组配置)」一致 |
137
+ | P3-11 | notice 版本断言 | `tools/check-ui.mjs` | 断言恰好 4 条且都含 `PLUGIN_VERSION`,当前 1.10.0 全过 |
138
+ | P3-12 | 口径表补「注释层」「装饰层」两行 | `docs/DESIGN.md` | 与 P2-3 的闸值(60/70)互相引用 |
139
+
140
+ **修订中额外发现并一并修掉的一处测试基建问题**:`check-selfheal` 的沙箱 localStorage 桩是「恒返 null」,
141
+ 导致 `readCustom` 每次都回落默认三色——场景 6 第一次跑就暴露了它(取色永远不生效)。
142
+ 换成 Map 实现后与真实宿主行为一致。这正是「先写测试再写实现」的价值:坏桩自己先红了。
143
+
144
+ **全量复核结果**:
145
+ - `npm run check` 六件套全绿(含本轮新增的全部断言);
146
+ - `node palette-dev.mjs` 与 `--hc` 均 exit 0:**未达标 0 · 低于 AA 4.5 底线 0 · 硬失败 0**;
147
+ - `check-ui` 的预览页 sha256 校验未受 caption 修改影响(设置页结构不变,截图无需重拍);
148
+ - `npm pack --dry-run` 确认 assets 入包。
149
+
150
+ **版本策略(发版时更正)**:审计阶段曾判断「重拍截图需要宿主 GUI,故本批不 bump 版本」——发版时核实
151
+ `tools/shoot-settings.mjs` 实为无头 Chrome/Edge + CDP 截 `preview-settings.mjs` 生成的 HTML,**不依赖 DSH 宿主**,
152
+ 该判断有误。1.11.0 发版时已按既定流程联动:版本号 bump、四语 notice 换新、`npm run shots` 重拍截图
153
+ (`screenshots.meta.json` 的 `pluginVersion` 对齐)、CHANGELOG 转正。