dsh-theme-palette 1.10.0 → 1.12.0

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,38 @@
4
4
 
5
5
  ## 版本一览
6
6
 
7
+ ### 1.12.0
8
+ 2026-10-28 一次外部审计(20 条问题,逐条核实后修订 14 条)。核心是**修好一个一直在发生的真 bug**:切换配色后颜色会跳回默认 —— 原因是自愈机制把自己刚装上的覆盖层撤掉了。此外把「失效不可见」「承诺与实现对不上」「发版门漏检」三类问题一并收口。
9
+
10
+ - **修复:切换配色后颜色跳回默认(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` **之前**(即使将来有别的重入路径,撤的也只是本次调用之前那一层)。
11
+ - **自愈停用不再静默(P0-B)**:连续失败 5 次后插件会放弃自动重放,但此前只有一条 `console.error` —— 界面看不出任何区别,用户改设置也不会复位它(需刷新页面)。现在 `healDisabled` / `healFails` 进入健康快照,设置页显示提示条并提供「重新启用自动恢复」按钮(`resetHeal()`),诊断 JSON 同样带上这两个字段。
12
+ - **新增可见性探针(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 背景色)钉住。
13
+ - **导入短码不再谎报成功(P2-I)**:隐私模式或存储满时,9 项写入各自静默失败,界面却显示「导入成功」,刷新后回到旧配置(部分写入还会得到混合配置)。现在写入结果如实汇总为 `persisted`,界面区分「已导入并保存」与「已生效但未能保存」。
14
+ - **热重载监听器泄漏(P2-L)**:`prefers-contrast` 的 `change` 监听从未移除,重载 N 次就留 N 个;系统切换对比度偏好时旧实例会依次重放覆盖层,把当前配色覆盖掉。现在回调提到外层并在 cleanup 里 `removeEventListener`。
15
+ - **样式标签热重载(P3-R)**:设置页/Markdown 样式命中已存在的标签就跳过,导致改过 CSS 后热重载看不到变化、卸载后标签残留。现在命中时覆盖写入内容并把标签交回 disposer。
16
+ - **百分比 alpha 解析(P3-Q)**:`rgba(0,0,0,50%)` 的 alpha 被当成 RGB 一样乘 2.55(算出 127.5,应为 0.5)。RGB 分量 ×2.55、alpha 分量 ÷100 分开处理。同条另一半**判定不修**:探针若改成「不分格式比较」,`var(...)`/`color-mix(...)` 会被与计算值字符串不等 → 把假阴性变成持续假失配。
17
+ - **文档口径对账(五语 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`)。
18
+ - **README 示例短码重生成(P1-f)**:示例短码里仍带着 1.9.10 已撤除的 `"r":"sidebar"`,解码能读、三色也对,所以旧断言查不出来 —— 只有「生产编码器往返自等」能发现它过期。五份 README 已用 `tools/sample-code.mjs` 重新生成。
19
+ - **测试与发版门**:`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 处违规)。
20
+ - **本地化与文案**:补 `locale/ja.json` 与 `locale/zh-TW.json`(此前只有 en/zh,而界面支持四语、README 五语);四语副标题里的 `localStorage` 改为「保存在本机 / this device / この端末」。
21
+ - **发版联动**:版本号 bump 至 1.12.0、四语 notice 换新、截图用 `npm run shots` 重拍(设置页新增提示条与按钮),`screenshots.meta.json` 的 `pluginVersion` 随之对齐。
22
+ - 复核:`npm run check` 六件套全绿;两档报表 exit 0,1080 组配置「未达标 0 / 低于 AA 4.5 = 0 / 硬门 0」;`npm pack --dry-run` 确认 22 个必需文件齐备。
23
+
24
+ ### 1.11.0
25
+ 2026-10-27 全量审计(评分 89/100,报告见 [`AUDIT-2026-10-27.md`](AUDIT-2026-10-27.md))的 12 条问题全部修订。两条 P1 都是门面级小修;审计里有一处**自我修正**也如实落地:取色拖动的性能问题经实测降级为防御性优化。
26
+
27
+ - **npm 包内 README 截图修复(P1-1)**:`files` 此前不含 `assets/`,npmjs.com 上 README 的 4 张截图全部 404。现已入包(包体 140KB → 436KB)。选「打进包」而非「绝对 GitHub URL」:自包含、不依赖仓库地址、npm 与镜像双兼容。
28
+ - **界面语言回退改 en(P1-2)**:宿主语言不在 zh/ja/en 里(fr/de/es/pt/…)时,设置页从整页中文改为回落英文。`pickLocale` 进 dev 钩子导出,`check-audit` 第 21 组断言 fr → en。
29
+ - **深色注释(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 装饰下限、有意不进承诺模型)。
30
+ - **取色拖动按帧合并(P2-4,防御性优化)**:取色器 `input` 在拖动中每帧多次触发,此前每次都全量重放覆盖层(宿主侧重写约 200 个 token 触发全页样式重算)。现在同帧多次取色合并到下一帧只重放一次、以最后一次为准;重置/派生/导入等离散动作保持同步。审计修正:插件侧构建实测仅约 0.05ms/次(60 次共 3ms),瓶颈在宿主侧重排——所以这是防御性优化而非实测缺陷修复。`check-selfheal` 新增第 6 场景(假宿主计数:帧内 +0、合并后 +1、末次生效),其沙箱的 localStorage 桩顺带从「恒返 null」改为 Map 实现(原桩让自定义色永远读回默认值,与真实宿主不符)。
31
+ - **CI(P2-5)**:新增 `.github/workflows/ci.yml`,push/PR 在 Node 22/24 跑 `npm run check` + `npm run report`;此前全部质量门只在本地/发版时跑。脚本只依赖 node 内建,无需 install;宿主契约在无 asar 的 CI 上自动 SKIP。
32
+ - **dev 钩子补导出(P2-6)**:`apcaContrast` / `raiseToLc` / `solveForeground` / `pickLocale` / `hostLocale` / `floorsDark` —— 外部工具复算 Lc 不必再自带一份 APCA 实现。
33
+ - **组合数口径统一为 1080(P3-10)**:五语 README 与 DESIGN.md 原写「2160 组(5 × 216 × 两档)」,公式把高对比算了两次(216 已含 hc 档),实际扫描 1080 组(1.9.10 撤右栏档前才是 2160)。
34
+ - **notice 文案版本同步断言(P3-11)**:`check-ui` 新增——四个语种的 `notice` 恰好 4 条且都含当前 `PLUGIN_VERSION`,升级漏改不会绿。
35
+ - **代码卫生(P3-7/8/9)**:短码编解码从废弃的 `escape`/`unescape` 换 `TextEncoder`/`TextDecoder`(产出逐字节相同,新旧短码互解码兼容,第 2 组旧码断言守着);`TEXT_TARGETS` 常量表从 `buildTokensRaw` 体内上提到模块级(紧随 `SURFACE_TARGETS`);`langPref` 声明先于引用它的 `getConfigCode` 闭包。
36
+ - **发版联动**:版本号 bump 至 1.11.0、四语 notice 换新、截图用 `npm run shots` 重拍(无头 Chrome/Edge + CDP 截 preview HTML,不需要 DSH 宿主——审计时「重拍需要宿主 GUI」的判断有误,发版时更正),`screenshots.meta.json` 的 `pluginVersion` 随之对齐。
37
+ - 复核:`npm run check` 六件套全绿(含新增断言);两档报表 exit 0,1080 组配置「未达标 0 / 低于 AA 4.5 = 0 / 硬失败 0」;`npm pack --dry-run` 确认 assets 入包(20 文件 / 435.6KB)。
38
+
7
39
  ### 1.10.0
8
40
  按审计报告的改进清单做了六件事:色彩数学单测、高对比跟随系统、运行时覆盖报告、色觉缺陷守门、单色派生,另外把 display-P3 的可行性量清楚后决定**本期不做**。其中「色觉缺陷守门」第一次跑就抓到一处真问题(浅色链接在模拟下低于 AA),下限因此微调。
9
41
 
package/README-es.md CHANGED
@@ -1,132 +1,132 @@
1
- # dsh-theme-palette
2
-
3
- [English](README.md) · [中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)
4
-
5
- Paleta de tema para DeepSeek Harness (web y escritorio): **4 paletas × claro/oscuro**, dos ejes independientes.
6
-
7
- ![Página de ajustes](assets/screenshot-settings-light.png)
8
-
9
- *Render estático con el ancho real del panel (564 px de contenido).*
10
-
11
- | Oscuro | Paleta personalizada | Alto contraste |
12
- |---|---|---|
13
- | ![Oscuro](assets/screenshot-settings-dark.png) | ![Paleta personalizada](assets/screenshot-settings-custom.png) | ![Alto contraste](assets/screenshot-settings-hc.png) |
14
-
15
- ## Compatibility
16
-
17
- | Superficie | Estado |
18
- |---|---|
19
- | Cliente DSH de escritorio / web | 0.2.x |
20
- | Node.js | 22.19.0+ (en la línea 22.x) o 24.0.0+ |
21
- | Cambios en el código del host | ninguno |
22
-
23
- El campo `engines` del paquete es `^22.19.0 || >=24.0.0`: la fila anterior es ese mismo requisito por extenso.
24
-
25
- El plugin solo apila tokens de color mediante `ctx.theme.overrideTokens`; no registra un tema, no parchea el host y no toca la preferencia de apariencia.
26
-
27
- ## What it does
28
-
29
- - **4 paletas** — Mono, Arena cálida, Índigo, Musgo — cada una adaptada de forma independiente a claro y oscuro;
30
- - la escala se **recalcula** completa: reconstrucción OKLCH → croma por rol → **ajuste al gamut sRGB** → contraste resuelto sobre superficies reales;
31
- - más de 27.000 referencias de token: texto, secundario, terciario, notas, enlaces, barras laterales, tarjetas, burbuja del usuario, botón primario, barras de desplazamiento, interruptores, títulos/negritas de Markdown, bloques de código;
32
- - ajustable: intensidad del texto (3), profundidad de la barra lateral (3), énfasis de Markdown, barras de desplazamiento, color de interruptores;
33
- - **autorreparable**: si el host descarta la capa de override, la paleta se vuelve a aplicar;
34
- - **promesas de contraste**: el cuerpo tiene la promesa de 12:1 en claro y 10:1 en oscuro (15:1 en alto contraste), el secundario ≥ 6.2 en claro y 8.0 en oscuro, y el terciario ≥ 4.85 y los enlaces ≥ 4.95 en claro, y 6.4 en oscuro (los mínimos claros llevan un margen para déficits de visión cromática; los enlaces además pasan una puerta APCA Lc ≥ 60 en oscuro / 75 en HC), medidos sobre las superficies donde el texto realmente cae (el lado oscuro necesita más: un cociente WCAG sobreestima la legibilidad sobre fondo oscuro);
35
- - las superficies prometidas son 00/60 en claro y 950/875 en oscuro; las oscuras 800 (superficie de módulo) y 850 (sub-superficie) **no** respaldan el texto del cuerpo: el informe las cuenta aparte como «fuera de política» (ni cuentan como aprobado ni como fallo);
36
- - interfaz en 简体中文 / 繁體中文 / English / 日本語 (sigue el idioma del host y se puede fijar).
37
-
38
- ## Install
39
-
40
- ```bash
41
- # desde npm
42
- cd ~/.dsh/profiles/desktop
43
- pnpm add dsh-theme-palette
44
- ```
45
-
46
- Para fijar una versión, usa `pnpm add dsh-theme-palette@<version>` — la versión actual está en [CHANGELOG.md](CHANGELOG.md).
47
- Desde el tarball de un release de GitHub: elige el asset en la [página de releases](https://github.com/zuoyunlai/dsh-theme-palette/releases) e instálalo con `pnpm add <ruta-del-tgz>`.
48
-
49
- Después **reinicia la interfaz**: un proceso en marcha conserva el módulo antiguo. Se abre en avatar → Ajustes → **Color theme**.
50
-
51
- ## Configuration
52
-
53
- | Elemento | Opciones |
54
- |---|---|
55
- | Paleta | Mono · Arena cálida · Índigo · Musgo (las miniaturas siguen claro/oscuro) |
56
- | Intensidad | Suave / Estándar / Fuerte — cuánto sigue el texto a la paleta |
57
- | Superficies | Barra lateral (Más clara / Igual / Más oscura). El panel derecho nunca se tiñe: es una capa transparente de altura completa, teñirlo taparía la conversación (ajuste retirado en 1.9.10) |
58
- | Más opciones | Idioma, énfasis de Markdown, barras de desplazamiento, interruptores |
59
-
60
- Cada elección se aplica al instante y se guarda en `localStorage`. Claro/oscuro sigue en el control «Apariencia» del host.
61
-
62
- ## Palette engine
63
-
64
- ```
65
- OKLCH(L, C, tono) ──► ajuste al gamut (búsqueda binaria en C) ──► hex sRGB
66
- ▲
67
- └── croma por rol × intensidad, luego contraste sobre {lienzo, tarjeta, barra, superposición}
68
- ```
69
-
70
- - los alias semánticos mantienen cadenas `var(--dsw-static-neutral-bluish-NN)`, así que todo lo que sigue a un token sigue a la paleta;
71
- - superficies claras usan los pasos 00–300, las oscuras 800–950;
72
- - los mínimos de texto se exigen en tiempo de construcción: cuerpo ≥ 12:1 en claro y ≥ 10:1 en oscuro (alto contraste: 15:1), secundario ≥ 6.2 claro / 8.0 oscuro (HC 9 / 9.6), terciario/enlace ≥ 4.85 / 4.95 claro / 6.4 oscuro (HC 7 / 7.6), enlace con APCA Lc ≥ 60 oscuro (HC 75), texto en barra lateral ≥ 7, texto en burbuja ≥ 7;
73
- - las superficies prometidas son 00/60 en claro y 950/875 en oscuro; las oscuras 800 (superficie de módulo) y 850 (sub-superficie) **no** respaldan el texto del cuerpo: el informe las cuenta aparte como «fuera de política» (ni cuentan como aprobado ni como fallo), y toda superficie sigue teniendo que superar el suelo AA de 4.5;
74
- - el informe separa tres cosas: el objetivo declarado, el techo físico (lo que el blanco o el negro alcanzan en esa superficie) y la exención de política; 0.93 es solo margen del solucionador, para decidir si un objetivo queda por encima del techo;
75
- - `node palette-dev.mjs` comprueba 2160 combinaciones (5 paletas × 216 ajustes × los dos niveles de contraste).
76
-
77
- Las notas de diseño, el criterio por componente y las tablas de contraste están en [docs/DESIGN.md](docs/DESIGN.md). Historial: [CHANGELOG.md](CHANGELOG.md).
78
-
79
- ## Development
80
-
81
- ```bash
82
- node palette-dev.mjs # informe de tokens de todas las paletas
83
- node tools/sheet.mjs # tablero de paletas → tools/sheet.html
84
- node tools/shoot-sheet.mjs # captura del tablero (Chrome headless)
85
- node tools/compare.mjs --dir tools/out/<run> # comparar con la línea base
86
- node tools/preview-settings.mjs --lang en # renderizar la página de ajustes
87
- node tools/release.mjs --dry # preflight de publicación
88
- node tools/release.mjs --tag # publicar en npm + tag
89
- ```
90
-
91
- La regresión visual tiene dos capas: el tablero de paletas (sin autenticación) y la ventana real del escritorio vía CDP (`tools/shoot-desktop.mjs`, requiere `--remote-debugging-port`). Ver [tools/README.md](tools/README.md).
92
-
93
-
94
- ## Pitfalls we hit
95
-
96
- Las cinco más sorprendentes de la auditoría de 1.7.0 — cada una la encontró un test, no mirando capturas.
97
-
98
- - **Un acento blanco generaba un enlace blanco** (1.13:1). El solucionador de contraste solo avanzaba en una dirección, alejándose del fondo, así que un acento más claro que el fondo seguía yendo hacia el blanco.
99
- - **Elegir un color cambiaba la página pero no el panel.** El componente se suscribía al id de paleta, que no cambia al cambiar un color; React comparó los snapshots, no vio diferencia y saltó el re-render.
100
- - **Compartir una configuración no compartía los colores.** El código corto llevaba los modos pero no los tres colores personalizados, así que importar en otra máquina informaba éxito y conservaba los valores locales.
101
- - **«No prometido» no es «imposible».** El informe antiguo etiquetaba las superficies modulares oscuras como «limitado por la superficie», porque comparaba el objetivo del cuerpo contra la más profunda (800). Pero 800 llega a unos 10.22:1 y 850 a unos 15.27:1 con un primer plano blanco, por encima del objetivo 10:1: nunca fue inalcanzable; sencillamente esas superficies no estaban prometidas. Ahora el informe separa el objetivo declarado, el techo físico y la exención de política, y cuenta 800/850 como «fuera de política» en lugar de «físicamente inalcanzable».
102
- - **Un selector que creíamos muerto estaba vivo.** La auditoría inicialmente lo reportó como roto; la prueba de contrato contra el CSS publicado demostró lo contrario. El hallazgo estaba equivocado, no el código.
103
-
104
- ## Sharing
105
-
106
- Toda la configuración — incluidos los tres colores personalizados — viaja en un código corto. Pégalo en el cuadro de importación y pulsa Vista previa para ver qué cambiará antes de aplicar:
107
-
108
- ```text
109
- dshs1.eyJ2IjoxLCJwIjoiY3VzdG9tIiwicyI6ImRlZXAiLCJ0IjoidGludGVkIiwiciI6InNpZGViYXIiLCJjIjoidGludGVkIiwiayI6ImFjY2VudCIsIm0iOiJvbiIsImgiOiJvZmYiLCJsIjoiemgtQ04iLCJ4Ijp7ImJnIjoiI2Y1ZWZlNiIsInRleHQiOiIjMWQxYTE3IiwiYWNjZW50IjoiIzNmNjZhMyJ9fQ
110
- ```
111
-
112
- Este ejemplo real decodifica a:
113
-
114
- - paleta: custom · barra lateral: deep · texto: tinted
115
- - scrollbar: tinted · controles: accent · Markdown: on · alto contraste: off · idioma: zh-CN
116
- - tres colores: `#f5efe6` (fondo) · `#1d1a17` (texto) · `#3f66a3` (acento)
117
-
118
- El código lo genera el codificador de producción y se auto-verifica ida y vuelta antes de entrar en este README — nunca se escribe a mano.
119
-
120
- ## Trade-offs
121
-
122
- Otros plugins de tema aquí ofrecen cristal, fondos y animación. Decidimos no hacerlo:
123
-
124
- - **Sin cristal ni fondos.** Esa carrera se gana con capturas; preferimos competir con pruebas.
125
- - **Sin mercado de temas ni generación de color con IA.** Ambos introducen colores que no podemos garantizar; el solucionador de contraste es el activo.
126
- - **Sin más paletas.** Cuatro preajustes más un generador de tres colores supera a diez elegidas a mano; el volumen no es la ventaja.
127
- - **Sin promesas de "protección ocular".** Reportamos ratios medidos en las superficies donde el texto realmente cae y decimos «fuera de política» cuando esa superficie no es una de las prometidas: es una medición, no una excusa.
128
-
129
-
130
- ## License
131
-
132
- MIT © 2026 Zuoyunlai
1
+ # dsh-theme-palette
2
+
3
+ [English](README.md) · [中文](README-zh.md) · [Español](README-es.md) · [Português](README-pt.md) · [हिन्दी](README-hi.md)
4
+
5
+ Paleta de tema para DeepSeek Harness (web y escritorio): **4 paletas preajustadas + personalizada × claro/oscuro**, dos ejes independientes.
6
+
7
+ ![Página de ajustes](assets/screenshot-settings-light.png)
8
+
9
+ *Render estático con el ancho real del panel (564 px de contenido).*
10
+
11
+ | Oscuro | Paleta personalizada | Alto contraste |
12
+ |---|---|---|
13
+ | ![Oscuro](assets/screenshot-settings-dark.png) | ![Paleta personalizada](assets/screenshot-settings-custom.png) | ![Alto contraste](assets/screenshot-settings-hc.png) |
14
+
15
+ ## Compatibility
16
+
17
+ | Superficie | Estado |
18
+ |---|---|
19
+ | Cliente DSH de escritorio / web | 0.2.x |
20
+ | Node.js | 22.19.0+ (en la línea 22.x) o 24.0.0+ |
21
+ | Cambios en el código del host | ninguno |
22
+
23
+ El campo `engines` del paquete es `^22.19.0 || >=24.0.0`: la fila anterior es ese mismo requisito por extenso.
24
+
25
+ El plugin solo apila tokens de color mediante `ctx.theme.overrideTokens`; no registra un tema, no parchea el host y no toca la preferencia de apariencia.
26
+
27
+ ## What it does
28
+
29
+ - **4 preajustadas + personalizada** — Mono, Arena cálida, Índigo, Musgo (cada una adaptada de forma independiente a claro y oscuro), más una paleta personalizada derivada de tres colores;
30
+ - la escala se **recalcula** completa: reconstrucción OKLCH → croma por rol → **ajuste al gamut sRGB** → contraste resuelto sobre superficies reales;
31
+ - se cubren todos los tokens de color del host (122 tokens en paletas con tinte, 17 en mono; 1080 combinaciones de ajuste escaneadas): texto, secundario, terciario, notas, enlaces, barras laterales, tarjetas, burbuja del usuario, botón primario, barras de desplazamiento, interruptores, títulos/negritas de Markdown, bloques de código;
32
+ - ajustable: intensidad del texto (3), profundidad de la barra lateral (3), énfasis de Markdown, barras de desplazamiento, color de interruptores;
33
+ - **autorreparable**: si el host descarta la capa de override, la paleta se vuelve a aplicar; tras fallos repetidos la autorreparación se pausa y la página de ajustes ofrece un botón para reactivarla;
34
+ - **promesas de contraste**: el cuerpo tiene la promesa de 12:1 en claro y 10:1 en oscuro (15:1 en alto contraste), el secundario ≥ 6.2 en claro y 8.0 en oscuro (HC 9 / 9.6), el terciario ≥ 4.85 en claro y 6.4 en oscuro (HC 7 / 7.6), los enlaces ≥ 4.95 en claro y 4.62 en oscuro (HC 7; los mínimos claros llevan un margen para déficits de visión cromática; los enlaces oscuros además pasan una puerta APCA Lc ≥ 60, 75 en HC), medidos sobre las superficies donde el texto realmente cae (el lado oscuro necesita más: un cociente WCAG sobreestima la legibilidad sobre fondo oscuro);
35
+ - las superficies prometidas son 00/60 en claro y 950/875 en oscuro; las oscuras 800 (superficie de módulo) y 850 (sub-superficie) **no** respaldan el texto del cuerpo: el informe las cuenta aparte como «fuera de política» (ni cuentan como aprobado ni como fallo);
36
+ - interfaz en 简体中文 / 繁體中文 / English / 日本語 (sigue el idioma del host y se puede fijar).
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ # desde npm
42
+ cd ~/.dsh/profiles/desktop
43
+ pnpm add dsh-theme-palette
44
+ ```
45
+
46
+ Para fijar una versión, usa `pnpm add dsh-theme-palette@<version>` — la versión actual está en [CHANGELOG.md](CHANGELOG.md).
47
+ Desde el tarball de un release de GitHub: elige el asset en la [página de releases](https://github.com/zuoyunlai/dsh-theme-palette/releases) e instálalo con `pnpm add <ruta-del-tgz>`.
48
+
49
+ Después **reinicia la interfaz**: un proceso en marcha conserva el módulo antiguo. Se abre en avatar → Ajustes → **Color theme**.
50
+
51
+ ## Configuration
52
+
53
+ | Elemento | Opciones |
54
+ |---|---|
55
+ | Paleta | Mono · Arena cálida · Índigo · Musgo (las miniaturas siguen claro/oscuro) |
56
+ | Intensidad | Suave / Estándar / Fuerte — cuánto sigue el texto a la paleta |
57
+ | Superficies | Barra lateral (Más clara / Igual / Más oscura). El panel derecho nunca se tiñe: es una capa transparente de altura completa, teñirlo taparía la conversación (ajuste retirado en 1.9.10) |
58
+ | Más opciones | Idioma, énfasis de Markdown, barras de desplazamiento, interruptores |
59
+
60
+ Cada elección se aplica al instante y se guarda en `localStorage`. Claro/oscuro sigue en el control «Apariencia» del host.
61
+
62
+ ## Palette engine
63
+
64
+ ```
65
+ OKLCH(L, C, tono) ──► ajuste al gamut (búsqueda binaria en C) ──► hex sRGB
66
+ ▲
67
+ └── croma por rol × intensidad, luego contraste sobre {lienzo, tarjeta, barra, superposición}
68
+ ```
69
+
70
+ - los alias semánticos mantienen cadenas `var(--dsw-static-neutral-bluish-NN)`, así que todo lo que sigue a un token sigue a la paleta;
71
+ - superficies claras usan los pasos 00–300, las oscuras 800–950;
72
+ - los mínimos de texto se exigen en tiempo de construcción: cuerpo ≥ 12:1 en claro y ≥ 10:1 en oscuro (alto contraste: 15:1), secundario ≥ 6.2 claro / 8.0 oscuro (HC 9 / 9.6), terciario ≥ 4.85 claro / 6.4 oscuro (HC 7 / 7.6), enlace ≥ 4.95 claro / 4.62 oscuro (HC 7), enlace con APCA Lc ≥ 60 oscuro (HC 75); el texto de la barra lateral y de la burbuja sigue la escala y mide ≥ 7 (sin puerta de contraste propia);
73
+ - las superficies prometidas son 00/60 en claro y 950/875 en oscuro; las oscuras 800 (superficie de módulo) y 850 (sub-superficie) **no** respaldan el texto del cuerpo: el informe las cuenta aparte como «fuera de política» (ni cuentan como aprobado ni como fallo), y toda superficie sigue teniendo que superar el suelo AA de 4.5;
74
+ - el informe separa tres cosas: el objetivo declarado, el techo físico (lo que el blanco o el negro alcanzan en esa superficie) y la exención de política; 0.93 es solo margen del solucionador, para decidir si un objetivo queda por encima del techo;
75
+ - `node palette-dev.mjs` comprueba 1080 combinaciones (5 paletas × 216 ajustes, ambos niveles de contraste incluidos).
76
+
77
+ Las notas de diseño, el criterio por componente y las tablas de contraste están en [docs/DESIGN.md](docs/DESIGN.md). Historial: [CHANGELOG.md](CHANGELOG.md).
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ node palette-dev.mjs # informe de tokens de todas las paletas
83
+ node tools/sheet.mjs # tablero de paletas → tools/sheet.html
84
+ node tools/shoot-sheet.mjs # captura del tablero (Chrome headless)
85
+ node tools/compare.mjs --dir tools/out/<run> # comparar con la línea base
86
+ node tools/preview-settings.mjs --lang en # renderizar la página de ajustes
87
+ node tools/release.mjs --dry # preflight de publicación
88
+ node tools/release.mjs --tag # publicar en npm + tag
89
+ ```
90
+
91
+ La regresión visual tiene dos capas: el tablero de paletas (sin autenticación) y la ventana real del escritorio vía CDP (`tools/shoot-desktop.mjs`, requiere `--remote-debugging-port`). Ver [tools/README.md](tools/README.md).
92
+
93
+
94
+ ## Pitfalls we hit
95
+
96
+ Las cinco más sorprendentes de la auditoría de 1.7.0 — cada una la encontró un test, no mirando capturas.
97
+
98
+ - **Un acento blanco generaba un enlace blanco** (1.13:1). El solucionador de contraste solo avanzaba en una dirección, alejándose del fondo, así que un acento más claro que el fondo seguía yendo hacia el blanco.
99
+ - **Elegir un color cambiaba la página pero no el panel.** El componente se suscribía al id de paleta, que no cambia al cambiar un color; React comparó los snapshots, no vio diferencia y saltó el re-render.
100
+ - **Compartir una configuración no compartía los colores.** El código corto llevaba los modos pero no los tres colores personalizados, así que importar en otra máquina informaba éxito y conservaba los valores locales.
101
+ - **«No prometido» no es «imposible».** El informe antiguo etiquetaba las superficies modulares oscuras como «limitado por la superficie», porque comparaba el objetivo del cuerpo contra la más profunda (800). Pero 800 llega a unos 10.22:1 y 850 a unos 15.27:1 con un primer plano blanco, por encima del objetivo 10:1: nunca fue inalcanzable; sencillamente esas superficies no estaban prometidas. Ahora el informe separa el objetivo declarado, el techo físico y la exención de política, y cuenta 800/850 como «fuera de política» en lugar de «físicamente inalcanzable».
102
+ - **Un selector que creíamos muerto estaba vivo.** La auditoría inicialmente lo reportó como roto; la prueba de contrato contra el CSS publicado demostró lo contrario. El hallazgo estaba equivocado, no el código.
103
+
104
+ ## Sharing
105
+
106
+ Toda la configuración — incluidos los tres colores personalizados — viaja en un código corto. Pégalo en el cuadro de importación y pulsa Vista previa para ver qué cambiará antes de aplicar:
107
+
108
+ ```text
109
+ dshs1.eyJ2IjoxLCJwIjoiY3VzdG9tIiwicyI6ImRlZXAiLCJ0IjoidGludGVkIiwiYyI6InRpbnRlZCIsImsiOiJhY2NlbnQiLCJtIjoib24iLCJoIjoib2ZmIiwibCI6InpoLUNOIiwieCI6eyJiZyI6IiNmNWVmZTYiLCJ0ZXh0IjoiIzFkMWExNyIsImFjY2VudCI6IiMzZjY2YTMifX0
110
+ ```
111
+
112
+ Este ejemplo real decodifica a:
113
+
114
+ - paleta: custom · barra lateral: deep · texto: tinted
115
+ - scrollbar: tinted · controles: accent · Markdown: on · alto contraste: off · idioma: zh-CN
116
+ - tres colores: `#f5efe6` (fondo) · `#1d1a17` (texto) · `#3f66a3` (acento)
117
+
118
+ El código lo genera el codificador de producción y se auto-verifica ida y vuelta antes de entrar en este README — nunca se escribe a mano.
119
+
120
+ ## Trade-offs
121
+
122
+ Otros plugins de tema aquí ofrecen cristal, fondos y animación. Decidimos no hacerlo:
123
+
124
+ - **Sin cristal ni fondos.** Esa carrera se gana con capturas; preferimos competir con pruebas.
125
+ - **Sin mercado de temas ni generación de color con IA.** Ambos introducen colores que no podemos garantizar; el solucionador de contraste es el activo.
126
+ - **Sin más paletas.** Cuatro preajustes más un generador de tres colores supera a diez elegidas a mano; el volumen no es la ventaja.
127
+ - **Sin promesas de "protección ocular".** Reportamos ratios medidos en las superficies donde el texto realmente cae y decimos «fuera de política» cuando esa superficie no es una de las prometidas: es una medición, no una excusa.
128
+
129
+
130
+ ## License
131
+
132
+ MIT © 2026 Zuoyunlai