dsh-theme-palette 1.4.2 → 1.4.3

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,19 @@
4
4
 
5
5
  ## 版本一览
6
6
 
7
+ ### 1.4.3
8
+ 安全审计后的整改(`@应用安全工程师` 审计,逐条复核后落地):
9
+
10
+ - **文档**:`tools/README.md` 删掉「把 `--remote-debugging-port` 长期写进快捷方式」的建议,改为「只在截图那一次开、抓完即关」,并说明该端口无鉴权等价于渲染进程任意代码执行,同时给出 `--remote-debugging-pipe` 替代
11
+ - **`tools/release.mjs` 去 shell 拼接**:name/version/tag 先过严格白名单,命令一律 `execFileSync(程序, 参数数组)`,npm 走 `node <npm-cli.js>`;registry 探测改用 `fetch`(不再 curl);`--dry` 无副作用
12
+ - **发布前校验**:`PLUGIN_VERSION` 必须等于 `package.json.version`;CI 下自动加 `--provenance`;配了 `user.signingkey` 时 tag 自动用 `-s`
13
+ - **dev 钩子收口**:默认不暴露,必须 URL 带 `?dshThemeDev=1`;`attach` 返回的清理函数在插件卸载时调用;`setTheme` 补 `light/dark/system` 白名单(此前是唯一不校验输入的 setter)
14
+ - **自愈定时器**:4s → 15s,且 `visibilityState === 'hidden'` 直接早退(不再后台空转样式回读)
15
+ - **token 前缀**:`--dsh-*` → `--dshtp-*`(与宿主 `--dsh-content-*` 等命名解耦,避免跨插件碰撞)
16
+ - **`tools/shoot-desktop.mjs`**:修收尾处引用未定义变量 `original` 的崩溃;还原动作移进 `try/finally`,中途失败也不会把用户明暗偏好留在被改写状态
17
+ - **声明补齐**:peer 补 `dsh-client-ui-theme` / `dsh-client-ui-settings`(optional),`engines.dsh >= 0.2.1-rc.1`;`files` 移出 `palette-dev.mjs` 与 `tools/*`(发布面从 24 个文件降到 14 个)
18
+ - **README 五语版本钉** 1.4.0 → 1.4.3
19
+
7
20
  ### 1.4.2
8
21
  - **修复启动崩溃**:1.4.1 为了展示元数据加了 `exports`,但漏了 `"./client"` —— 宿主 `client-modules` 找不到 client bundle 时会让 **required 插件 `modules` 启动失败**,整个 profile 的插件全部不加载(重启后界面报错、插件消失)
9
22
  - `exports` 现在显式列出 `./client`、`./client.js`、`./index.js`、`./cordis.patch.yml`、`./package.json`
package/README-es.md CHANGED
@@ -31,13 +31,13 @@ cd ~/.dsh/profiles/desktop
31
31
  pnpm add dsh-theme-palette
32
32
 
33
33
  # o una versión concreta
34
- pnpm add dsh-theme-palette@1.4.0
34
+ pnpm add dsh-theme-palette@1.4.3
35
35
  ```
36
36
 
37
37
  Desde el tarball del release de GitHub:
38
38
 
39
39
  ```bash
40
- pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
40
+ pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.3/dsh-theme-palette-1.4.3.tgz"
41
41
  ```
42
42
 
43
43
  Después **reinicia la interfaz**: un proceso en marcha conserva el módulo antiguo. Se abre en avatar → Ajustes → **Color theme**.
package/README-hi.md CHANGED
@@ -31,13 +31,13 @@ cd ~/.dsh/profiles/desktop
31
31
  pnpm add dsh-theme-palette
32
32
 
33
33
  # या कोई विशेष संस्करण
34
- pnpm add dsh-theme-palette@1.4.0
34
+ pnpm add dsh-theme-palette@1.4.3
35
35
  ```
36
36
 
37
37
  GitHub रिलीज़ के tarball से:
38
38
 
39
39
  ```bash
40
- pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
40
+ pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.3/dsh-theme-palette-1.4.3.tgz"
41
41
  ```
42
42
 
43
43
  इसके बाद **GUI को फिर से शुरू करें** — चल रहा प्रोसेस पुराना मॉड्यूल रखता है। खोलें: अवतार → सेटिंग्स → **Color theme**।
package/README-pt.md CHANGED
@@ -31,13 +31,13 @@ cd ~/.dsh/profiles/desktop
31
31
  pnpm add dsh-theme-palette
32
32
 
33
33
  # ou uma versão específica
34
- pnpm add dsh-theme-palette@1.4.0
34
+ pnpm add dsh-theme-palette@1.4.3
35
35
  ```
36
36
 
37
37
  A partir do tarball do release do GitHub:
38
38
 
39
39
  ```bash
40
- pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
40
+ pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.3/dsh-theme-palette-1.4.3.tgz"
41
41
  ```
42
42
 
43
43
  Depois **reinicie a interface**: um processo em execução mantém o módulo antigo. Abra em avatar → Configurações → **Color theme**.
package/README-zh.md CHANGED
@@ -31,13 +31,13 @@ cd ~/.dsh/profiles/desktop
31
31
  pnpm add dsh-theme-palette
32
32
 
33
33
  # 或指定版本
34
- pnpm add dsh-theme-palette@1.4.0
34
+ pnpm add dsh-theme-palette@1.4.3
35
35
  ```
36
36
 
37
37
  从 GitHub Release 的 tarball:
38
38
 
39
39
  ```bash
40
- pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
40
+ pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.3/dsh-theme-palette-1.4.3.tgz"
41
41
  ```
42
42
 
43
43
  装完**必须重启 GUI**(运行中的进程仍持旧模块)。入口:左下角头像 → **设置** → 「**主题色**」。
package/README.md CHANGED
@@ -31,13 +31,13 @@ cd ~/.dsh/profiles/desktop
31
31
  pnpm add dsh-theme-palette
32
32
 
33
33
  # or a specific version
34
- pnpm add dsh-theme-palette@1.4.0
34
+ pnpm add dsh-theme-palette@1.4.3
35
35
  ```
36
36
 
37
37
  From the GitHub Release tarball:
38
38
 
39
39
  ```bash
40
- pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.0/dsh-theme-palette-1.4.0.tgz"
40
+ pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.4.3/dsh-theme-palette-1.4.3.tgz"
41
41
  ```
42
42
 
43
43
  Then **restart the GUI** — a running process keeps the old module. Open it from avatar → Settings → **Color theme**.
package/client.js CHANGED
@@ -27,7 +27,7 @@ window.__ModuleLoader__.load({
27
27
  const React = require('react')
28
28
  const h = React.createElement
29
29
 
30
- const PLUGIN_VERSION = '1.4.2'
30
+ const PLUGIN_VERSION = '1.4.3'
31
31
  const STORE_KEY = 'dsh-theme-palette.v1'
32
32
  const OVERRIDE_SOURCE = 'dsh-theme-palette'
33
33
 
@@ -171,7 +171,7 @@ window.__ModuleLoader__.load({
171
171
  * 右侧栏底色。宿主的右栏(ui-layout 的 RightbarColumn)**没有自己的背景**,
172
172
  * 露出的是 frame 的 --dsw-alias-bg-base(也就是会话区画布色),所以看着和中间一样。
173
173
  * 元素上有一个稳定钩子 `data-rightbar-col`,用一条 CSS 规则就能单独上色,
174
- * 具体颜色由 token 层给的 --dsh-rightbar-fill 决定(默认直接引用左栏 token)。
174
+ * 具体颜色由 token 层给的 --dshtp-rightbar-fill 决定(默认直接引用左栏 token)。
175
175
  */
176
176
  const RIGHTBAR_MODES = [
177
177
  { id: 'sidebar', label: '同左栏' },
@@ -673,20 +673,20 @@ window.__ModuleLoader__.load({
673
673
  // 会得到一张近白卡片,而卡片文字是 label-primary(深色下近白)→ 白底白字。
674
674
  const accentHex = ramps.deepseek['500']
675
675
  const accentDark = ramps.deepseek['400']
676
- pairs['--dsh-card-picked'] = {
676
+ pairs['--dshtp-card-picked'] = {
677
677
  light: mixHex(accentHex, ramps.bluish['00'], 0.10),
678
678
  dark: mixHex(accentDark, ramps.bluish['950'], 0.16),
679
679
  }
680
680
  const rightbarFill = rightbarMode === 'canvas'
681
681
  ? 'var(--dsw-alias-bg-base)'
682
682
  : 'var(--dsw-specific-sidebar-fill)'
683
- pairs['--dsh-rightbar-fill'] = { light: rightbarFill, dark: rightbarFill }
683
+ pairs['--dshtp-rightbar-fill'] = { light: rightbarFill, dark: rightbarFill }
684
684
  // Markdown 强调色:标题 = 正文色混 40% 强调色;加粗 = 混 20%(比标题弱一档)
685
685
  if (scheme.tinted) {
686
686
  if (mdMode === 'off') {
687
687
  const inherit = { light: 'inherit', dark: 'inherit' }
688
- pairs['--dsh-md-heading'] = inherit
689
- pairs['--dsh-md-strong'] = inherit
688
+ pairs['--dshtp-md-heading'] = inherit
689
+ pairs['--dshtp-md-strong'] = inherit
690
690
  } else {
691
691
  // 加粗:只加色度、不动明度 —— 混色会把正文调亮(浅色下反而比正文更弱)。
692
692
  // 标题:再往中间推一点明度(浅色提亮 / 深色压暗),配合更大的字号拉开层级。
@@ -695,11 +695,11 @@ window.__ModuleLoader__.load({
695
695
  const acc = toOklch(accentHex)
696
696
  return fromOklch(clamp(L + lift, 0.05, 0.97), C + acc[1] * extraChroma, acc[2])
697
697
  }
698
- pairs['--dsh-md-heading'] = {
698
+ pairs['--dshtp-md-heading'] = {
699
699
  light: tintText(ramps.bluish['1000'], ramps.deepseek['500'], 0.45, 0.065),
700
700
  dark: tintText(ramps.bluish['50'], ramps.deepseek['400'], 0.40, -0.030),
701
701
  }
702
- pairs['--dsh-md-strong'] = {
702
+ pairs['--dshtp-md-strong'] = {
703
703
  light: tintText(ramps.bluish['1000'], ramps.deepseek['500'], 0.32, 0.012),
704
704
  dark: tintText(ramps.bluish['50'], ramps.deepseek['400'], 0.32, -0.010),
705
705
  }
@@ -708,11 +708,11 @@ window.__ModuleLoader__.load({
708
708
  // 代码块底色:宿主用 bg-layer-1,浅色下与画布同色(都指 00)→ 看不出边界。
709
709
  // 这里给它一档独立的"内嵌面板"色:卡层色 + 少量强调色相,保证与画布有可辨差。
710
710
  // mixHex(a, b, w) = a*w + b*(1-w):a 是要掺进去的颜色,b 是底色。
711
- pairs['--dsh-code-fill'] = {
711
+ pairs['--dshtp-code-fill'] = {
712
712
  light: mixHex(ramps.deepseek['500'], ramps.bluish['60'], 0.08),
713
713
  dark: mixHex(ramps.deepseek['400'], ramps.bluish['875'], 0.10),
714
714
  }
715
- pairs['--dsh-code-inline'] = {
715
+ pairs['--dshtp-code-inline'] = {
716
716
  light: mixHex(ramps.deepseek['500'], ramps.bluish['50'], 0.05),
717
717
  dark: mixHex(ramps.deepseek['400'], ramps.bluish['900'], 0.07),
718
718
  }
@@ -771,16 +771,16 @@ window.__ModuleLoader__.load({
771
771
  * 全屏右栏时退回画布色 —— 那时右栏就是整块内容区,染成侧栏色会像只剩一条侧栏。
772
772
  */
773
773
  const RIGHTBAR_CSS = [
774
- '[data-rightbar-col]{background:var(--dsh-rightbar-fill,var(--dsw-alias-bg-base))}',
774
+ '[data-rightbar-col]{background:var(--dshtp-rightbar-fill,var(--dsw-alias-bg-base))}',
775
775
  '[data-rightbar-fullscreen] [data-rightbar-col]{background:var(--dsw-alias-bg-base)}',
776
776
  // Markdown 标题/加粗:容器类是 CSS Modules 的 `_markdown_<hash>_<n>`,
777
777
  // 用「类名里含 _markdown_」做钩子(局部名稳定,比整串哈希耐版本)。
778
- '[class*="_markdown_"] :where(h1,h2,h3,h4,h5,h6){color:var(--dsh-md-heading,inherit)}',
779
- '[class*="_markdown_"] strong{color:var(--dsh-md-strong,inherit)}',
778
+ '[class*="_markdown_"] :where(h1,h2,h3,h4,h5,h6){color:var(--dshtp-md-heading,inherit)}',
779
+ '[class*="_markdown_"] strong{color:var(--dshtp-md-strong,inherit)}',
780
780
  // 代码块:宿主把底色写在 CSS Modules 的 .block 上(浅色与画布同为 00,看不出边界)。
781
781
  // 这里以 `.md-code-block` 提高一级特异性覆盖,不需要 !important。
782
- '[class*="_markdown_"] .md-code-block{background:var(--dsh-code-fill,inherit)}',
783
- '[class*="_markdown_"] :not(pre) > code{background:var(--dsh-code-inline,inherit)}',
782
+ '[class*="_markdown_"] .md-code-block{background:var(--dshtp-code-fill,inherit)}',
783
+ '[class*="_markdown_"] :not(pre) > code{background:var(--dshtp-code-inline,inherit)}',
784
784
  ].join('')
785
785
 
786
786
  /* ================= 设置页样式(head 里第二条 CSS) ================= */
@@ -795,7 +795,7 @@ window.__ModuleLoader__.load({
795
795
  @media (max-width:620px){.dsh-tp-grid{grid-template-columns:1fr}}
796
796
  .dsh-tp-card{position:relative;display:flex;flex-direction:column;gap:8px;text-align:left;font:inherit;cursor:pointer;padding:9px;border-radius:14px;border:.5px solid var(--dsw-alias-border-l4);background:transparent;transition:background .12s ease,border-color .12s ease}
797
797
  .dsh-tp-card:hover{background:var(--dsw-alias-interactive-bg-hover)}
798
- .dsh-tp-card.is-on{background:var(--dsh-card-picked,var(--dsw-alias-bg-module-platform));border-color:var(--t-accent-l)}
798
+ .dsh-tp-card.is-on{background:var(--dshtp-card-picked,var(--dsw-alias-bg-module-platform));border-color:var(--t-accent-l)}
799
799
  body[data-ds-dark-theme] .dsh-tp-card.is-on{border-color:var(--t-accent-d)}
800
800
  .dsh-tp-thumb{display:flex;height:56px;border-radius:9px;overflow:hidden;border:.5px solid rgba(128,128,128,.28)}
801
801
  .dsh-tp-thumb-side{width:34%;background:var(--t-side-l)}
@@ -1302,8 +1302,20 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1302
1302
  }
1303
1303
 
1304
1304
  /* ================= 插件 ================= */
1305
+ /**
1306
+ * 开发期接口默认**关闭**:只有在页面 URL 上显式带 `?dshThemeDev=1` 时才暴露。
1307
+ * 这不是安全边界(同上下文脚本本就有同等权力),而是防止"谁先定义谁拿到全部 setter"
1308
+ * 的默认开放;一旦宿主将来把插件放进受限上下文,它就是跨权限入口。
1309
+ */
1310
+ const DEV_OPT_IN = (() => {
1311
+ try {
1312
+ const search = typeof location !== 'undefined' && location ? String(location.search || '') : ''
1313
+ return /(?:^|[?&])dshThemeDev=1(?:&|$)/.test(search)
1314
+ } catch (err) { return false }
1315
+ })()
1316
+
1305
1317
  // 开发期接口:palette-dev.mjs 通过它拿到同一套引擎(避免「报表好看、上线不一样」)
1306
- if (typeof window !== 'undefined' && typeof window.__DSH_THEME_PALETTE_DEV__ === 'function') {
1318
+ if (DEV_OPT_IN && typeof window !== 'undefined' && typeof window.__DSH_THEME_PALETTE_DEV__ === 'function') {
1307
1319
  window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, RIGHTBAR_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, contrast, toOklch, rampsFor, settingsCss: SETTINGS_CSS, cardColors })
1308
1320
  }
1309
1321
 
@@ -1543,11 +1555,13 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1543
1555
  } catch (err) { /* 没有 MutationObserver 就只在打开设置页时读一次 */ }
1544
1556
 
1545
1557
  // 开发期自动化接口:截图回归要从外部驱动「换配色 / 换明暗」,而这两件事
1546
- // 平时只能点界面。暴露一组 setter,让 CDP 的 Runtime.evaluate 能直接调用。
1558
+ // 平时只能点界面。默认不暴露,必须 URL 带 ?dshThemeDev=1 才挂(见 DEV_OPT_IN)。
1559
+ let detachDev = null
1547
1560
  try {
1548
- const dev = typeof window !== 'undefined' ? window.__DSH_THEME_PALETTE_DEV__ : undefined
1561
+ const dev = DEV_OPT_IN && typeof window !== 'undefined' ? window.__DSH_THEME_PALETTE_DEV__ : undefined
1549
1562
  if (dev && typeof dev.attach === 'function') {
1550
- dev.attach({
1563
+ const THEME_IDS = ['light', 'dark', 'system']
1564
+ const attached = dev.attach({
1551
1565
  setPalette,
1552
1566
  setSidebarMode,
1553
1567
  setTextMode,
@@ -1557,9 +1571,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1557
1571
  getPreference: () => theme.getTheme().preference,
1558
1572
  setLangPref,
1559
1573
  locales: LOCALES,
1560
- setTheme: (id) => theme.setTheme(id),
1574
+ // 和其它 setter 一样过白名单:任意字符串会原样进宿主主题状态并被持久化
1575
+ setTheme: (id) => { if (THEME_IDS.indexOf(id) !== -1) theme.setTheme(id) },
1561
1576
  schemes: SCHEMES.map((s) => s.id),
1562
1577
  })
1578
+ if (typeof attached === 'function') detachDev = attached
1563
1579
  }
1564
1580
  } catch (err) {
1565
1581
  console.warn('[dsh-theme-palette] dev hook attach 失败:', err)
@@ -1579,7 +1595,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1579
1595
  } catch (err) { /* 无 DOM 时跳过 */ }
1580
1596
  try { ctx.on('theme/change', healOnTheme) } catch (err) { /* 事件名变了就只靠定时器 */ }
1581
1597
  try {
1582
- healTimer = setInterval(() => ensureApplied('interval'), 4000)
1598
+ healTimer = setInterval(() => {
1599
+ // 页面不可见时不做样式回读:容器里长时间后台驻留时,4 秒一次 getComputedStyle 是纯浪费
1600
+ try { if (document.visibilityState === 'hidden') return } catch (err) { /* 无该 API */ }
1601
+ ensureApplied('interval')
1602
+ }, 15000)
1583
1603
  if (healTimer && typeof healTimer.unref === 'function') healTimer.unref()
1584
1604
  } catch (err) { /* 无定时器环境 */ }
1585
1605
 
@@ -1597,6 +1617,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1597
1617
  window.removeEventListener('pageshow', healOnShow)
1598
1618
  } catch (err) { /* 同上 */ }
1599
1619
  if (healTimer !== null) { clearInterval(healTimer); healTimer = null }
1620
+ if (typeof detachDev === 'function') { try { detachDev() } catch (err) { /* 忽略 */ } }
1600
1621
  if (langObserver !== null) { langObserver.disconnect(); langObserver = null }
1601
1622
  }
1602
1623
  }, 'dsh-theme-palette: token layer + 右栏样式')
package/docs/DESIGN.md CHANGED
@@ -105,13 +105,13 @@ MIN_MAIN 从 15.5 放宽到 12:给“带色正文”留出明度空间,仍
105
105
 
106
106
  ```css
107
107
  /* 插件注入到 <head>,用官方同一套 data-plugin-css 标记 */
108
- [data-rightbar-col]{background:var(--dsh-rightbar-fill,var(--dsw-alias-bg-base))}
108
+ [data-rightbar-col]{background:var(--dshtp-rightbar-fill,var(--dsw-alias-bg-base))}
109
109
  [data-rightbar-fullscreen] [data-rightbar-col]{background:var(--dsw-alias-bg-base)}
110
110
  ```
111
111
 
112
112
  - 钩子是 `data-rightbar-col` —— ui-layout 里 `RightbarColumn` 上的**语义属性**,不是哈希类名,
113
113
  宿主升级时不会跟着变;
114
- - 颜色不经硬编码:token 层只给一个 `--dsh-rightbar-fill`,值是 `var(--dsw-specific-sidebar-fill)`
114
+ - 颜色不经硬编码:token 层只给一个 `--dshtp-rightbar-fill`,值是 `var(--dsw-specific-sidebar-fill)`
115
115
  或 `var(--dsw-alias-bg-base)`,所以它会自动跟着「左侧栏底色」三档一起变;
116
116
  - 全屏右栏时退回画布色 —— 那时右栏就是整块内容区,染成侧栏色会像只剩一条侧栏。
117
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theme-palette",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -21,14 +21,6 @@
21
21
  "icon.svg",
22
22
  "README.md",
23
23
  "LICENSE",
24
- "palette-dev.mjs",
25
- "tools/sheet.mjs",
26
- "tools/shoot-sheet.mjs",
27
- "tools/shoot-html.mjs",
28
- "tools/preview-settings.mjs",
29
- "tools/shoot-desktop.mjs",
30
- "tools/compare.mjs",
31
- "tools/release.mjs",
32
24
  "CHANGELOG.md",
33
25
  "docs/DESIGN.md",
34
26
  "README-zh.md",
@@ -38,10 +30,13 @@
38
30
  "locale"
39
31
  ],
40
32
  "peerDependencies": {
41
- "@deepseek-ai/cordis": "^4.0.0"
33
+ "@deepseek-ai/cordis": "^4.0.0",
34
+ "@deepseek-ai/dsh-client-ui-theme": "^0.2.1-rc.1",
35
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.1-rc.1"
42
36
  },
43
37
  "engines": {
44
- "node": "^22.19.0 || >=24.0.0"
38
+ "node": "^22.19.0 || >=24.0.0",
39
+ "dsh": ">=0.2.1-rc.1"
45
40
  },
46
41
  "dsh": {
47
42
  "bundle": {
@@ -78,5 +73,16 @@
78
73
  "access": "public",
79
74
  "registry": "https://registry.npmjs.org"
80
75
  },
81
- "packageManager": "pnpm@11.7.0"
76
+ "packageManager": "pnpm@11.7.0",
77
+ "peerDependenciesMeta": {
78
+ "@deepseek-ai/cordis": {
79
+ "optional": true
80
+ },
81
+ "@deepseek-ai/dsh-client-ui-theme": {
82
+ "optional": true
83
+ },
84
+ "@deepseek-ai/dsh-client-ui-settings": {
85
+ "optional": true
86
+ }
87
+ }
82
88
  }
package/palette-dev.mjs DELETED
@@ -1,140 +0,0 @@
1
- /**
2
- * 配色报表(开发期)。
3
- *
4
- * 直接加载 client.js 的**生产引擎**(经 window.__DSH_THEME_PALETTE_DEV__ 这个开发期钩子),
5
- * 不保留第二份配色实现 —— 早先这份文件自带旧引擎,报表和上线不是同一套。
6
- *
7
- * 用法:node palette-dev.mjs
8
- */
9
- import fs from 'node:fs'
10
- import path from 'node:path'
11
- import { fileURLToPath } from 'node:url'
12
-
13
- const here = path.dirname(fileURLToPath(import.meta.url))
14
- const source = fs.readFileSync(path.join(here, 'client.js'), 'utf8')
15
-
16
- let engine = null
17
- const sandbox = {
18
- window: {
19
- __ModuleLoader__: { load: (def) => { sandbox.__def = def } },
20
- __DSH_THEME_PALETTE_DEV__: (api) => { engine = api },
21
- },
22
- localStorage: { getItem: () => null, setItem: () => {} },
23
- document: { documentElement: { lang: 'zh' } },
24
- navigator: { language: 'zh' },
25
- }
26
- new Function('window', 'localStorage', 'document', 'navigator', source)(
27
- sandbox.window, sandbox.localStorage, sandbox.document, sandbox.navigator,
28
- )
29
- sandbox.__def.factory(() => ({
30
- createElement: () => null,
31
- useState: () => [null, () => {}],
32
- useSyncExternalStore: () => 'mono',
33
- }))
34
- if (!engine) throw new Error('生产引擎没有暴露 dev 钩子')
35
-
36
- const { buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, contrast, toOklch } = engine
37
-
38
- /** 真实承载面 → 落在该面上的前景。取自宿主 ui-theme 的别名映射。 */
39
- const SURFACES = [
40
- { name: '画布 00', step: 'neutral-bluish-00', fg: ['1000', '700', '600', 'link500'] },
41
- { name: '卡层 60', step: 'neutral-bluish-60', fg: ['1000', '700', '600', 'link500'] },
42
- { name: '悬浮 150*', step: 'neutral-bluish-150', fg: ['600', 'link500'], scope: false },
43
- { name: '深画布 950', step: 'neutral-bluish-950', fg: ['50', '300', '400', 'link400'] },
44
- // 875 = bg-layer-1(弹层/抽屉),850 = bg-layer-2,800 = bg-layer-3 兼 bg-module-platform。
45
- // 三层都要报:文末的「承诺面」是这三个面里最亮的 800,不是画布。
46
- { name: '深层1 875', step: 'neutral-bluish-875', fg: ['50', '300', '400', 'link400'] },
47
- { name: '深卡 850', step: 'neutral-bluish-850', fg: ['50', '300', '400', 'link400'] },
48
- { name: '深模块 800', step: 'neutral-bluish-800', fg: ['50', '300', '400', 'link400'] },
49
- ]
50
- // 合规下限:低于它才是违规(正文 AAA 7 / 次级与三级 AA 4.62)。
51
- // '1000' 的 15.5 与 '700' 的 6.2 是设计目标,一并当硬线放在这里,因为浅色侧从来没破过。
52
- const FLOOR = { '1000': 15.5, '700': 6.2, '600': 4.62, link500: 4.62, '50': 7, '300': 6, '400': 4.62, link400: 4.62 }
53
- // 设计目标(高于 WCAG):深色正文希望 ≥10。缺口只记 △,不算违规 ——
54
- // 800 是最亮的深色面(layer-3 / module),正文在那里天然最低(8.95–9.56,仍是 AAA 7 的 1.3 倍)。
55
- const TARGET = { '50': 10 }
56
-
57
- // 链接报**实际渲染用的那条**(`--dsw-alias-link`),不是色阶中段:
58
- // 中段 500 是设计稿给的标准色(品牌基准色),它在浅色 150 面上本来就过不了 AA,
59
- // 插件已把「当文字用」的链接单独派生。若改用 static-500 报,会报出一个界面里不存在的数值。
60
- const getter = (tokens) => (key) => {
61
- if (key === 'link500') return tokens['--dsw-alias-link'].light
62
- if (key === 'link400') return tokens['--dsw-alias-link'].dark
63
- const name = /^[0-9]+$/.test(key) ? `neutral-bluish-${key}` : key
64
- return tokens[`--dsw-static-${name}`].light
65
- }
66
-
67
- const lab = (hex) => {
68
- const [L, C, h] = toOklch(hex)
69
- return [L, C * Math.cos((h * Math.PI) / 180), C * Math.sin((h * Math.PI) / 180)]
70
- }
71
- const dE = (x, y) => {
72
- const [L1, a1, b1] = lab(x), [L2, a2, b2] = lab(y)
73
- return Math.hypot(L1 - L2, a1 - a2, b1 - b2) * 100
74
- }
75
-
76
- let violations = 0
77
- for (const scheme of SCHEMES) {
78
- if (!scheme.tinted) { console.log(`\n=== ${scheme.label} ===(未覆盖,走宿主原值)`); continue }
79
- const tokens = buildTokens(scheme, 'deep')
80
- const get = getter(tokens)
81
- console.log(`\n=== ${scheme.label} ===`)
82
- for (const surface of SURFACES) {
83
- const bg = get(surface.step)
84
- const parts = surface.fg.map((fg) => {
85
- const ratio = contrast(get(fg), bg)
86
- const ok = surface.scope === false ? true : ratio >= FLOOR[fg]
87
- if (!ok) violations++
88
- const short = ok && TARGET[fg] !== undefined && ratio < TARGET[fg]
89
- return `${fg} ${ratio.toFixed(2)}${ok ? (short ? ' △' : '') : ' ✗'}`
90
- })
91
- console.log(` ${surface.name.padEnd(12)} ${bg} ${parts.join(' ')}`)
92
- }
93
-
94
- console.log(' 品牌基准色 / 链接')
95
- {
96
- const anchor = tokens['--dsw-static-deepseek-500'].light
97
- const rgb = 'rgb(' + [1, 3, 5].map((i) => parseInt(anchor.slice(i, i + 2), 16)).join(', ') + ')'
98
- const lk = tokens['--dsw-alias-link']
99
- console.log(` 基准色 500 ${anchor} ${rgb}(卡片预览 / info 填充 / 焦点环)`
100
- + ` 白字对比 ${contrast('#ffffff', anchor).toFixed(2)}`)
101
- console.log(` 链接 浅 ${lk.light} 对 150 ${contrast(lk.light, get('neutral-bluish-150')).toFixed(2)}`
102
- + ` / 卡层 ${contrast(lk.light, get('neutral-bluish-60')).toFixed(2)}`
103
- + ` | 深 ${lk.dark} 对 800 ${contrast(lk.dark, get('neutral-bluish-800')).toFixed(2)}`)
104
- }
105
-
106
- console.log(' 侧栏三档(ΔE:画布↔侧栏 / 悬停 / 选中)')
107
- for (const mode of SIDEBAR_MODES) {
108
- const tk = buildTokens(scheme, mode.id)
109
- const fill = tk['--dsw-specific-sidebar-fill'].light
110
- const hov = tk['--dsw-specific-sidebar-nav-item-hover'].light
111
- const act = tk['--dsw-specific-sidebar-nav-item-active'].light
112
- const dFill = tk['--dsw-specific-sidebar-fill'].dark
113
- const dHov = tk['--dsw-specific-sidebar-nav-item-hover'].dark
114
- const dAct = tk['--dsw-specific-sidebar-nav-item-active'].dark
115
- const canvas = get('neutral-bluish-00')
116
- const dCanvas = get('neutral-bluish-950')
117
- const mark = mode.id === 'deep' ? ' ←默认' : ''
118
- console.log(` ${mode.label} 浅 ${fill} / ${hov} / ${act} ΔE ${dE(canvas, fill).toFixed(2)}/${dE(fill, hov).toFixed(2)}/${dE(fill, act).toFixed(2)}${mark}`)
119
- console.log(` ${''.padEnd(mode.label.length)} 深 ${dFill} / ${dHov} / ${dAct} ΔE ${dE(dCanvas, dFill).toFixed(2)}/${dE(dFill, dHov).toFixed(2)}/${dE(dFill, dAct).toFixed(2)}`)
120
- }
121
- }
122
-
123
- console.log(`\n低于下限的配对:${violations} 处`)
124
- console.log(`侧栏档位:${SIDEBAR_MODES.map((m) => m.label).join(' / ')}`)
125
-
126
- console.log('\n文字着色三档')
127
- for (const scheme of SCHEMES) {
128
- if (!scheme.tinted) continue
129
- console.log(` ${scheme.label}`)
130
- for (const mode of TEXT_MODES) {
131
- const t = buildTokens(scheme, 'deep', mode.id)
132
- const g = (k) => t[`--dsw-static-${k}`].light
133
- const cL = (fg, bg) => contrast(g(fg), g(bg)).toFixed(2)
134
- console.log(` ${mode.label.padEnd(4)} 正文 ${g('neutral-bluish-1000')} 次文 ${g('neutral-bluish-700')}`
135
- + ` 深正文 ${g('neutral-bluish-50')}`
136
- + ` | 正文 ${cL('neutral-bluish-1000', 'neutral-bluish-00')}`
137
- + ` 三级 ${cL('neutral-bluish-600', 'neutral-bluish-60')}`
138
- + ` 深正文 ${cL('neutral-bluish-50', 'neutral-bluish-950')}`)
139
- }
140
- }
package/tools/README.md DELETED
@@ -1,66 +0,0 @@
1
- # 截图回归
2
-
3
- 配色插件的回归分两层,**能跑哪层取决于你想要多接近真实界面**。
4
-
5
- | 层 | 截的是什么 | 需要什么 | 能发现什么 |
6
- |---|---|---|---|
7
- | **A. 配色看板**(已就绪) | `tools/sheet.html`:每套配色一屏,浅色 + 深色两个面板,内含侧栏条目、气泡、卡片、代码块、按钮、开关、滚动条、正文四档文字 | 只要 Chrome/Edge | 配色系统本身的变化:色相、层次、对比度、气泡/按钮的落色 |
8
- | **B. 真机**(需一次重启) | 桌面版**真实窗口**(Electron 渲染进程) | 桌面版以 `--remote-debugging-port=9333` 启动一次 | 真实内容下的观感、真实间距/组件、宿主改版导致的偏移 |
9
-
10
- 两层的产物都进 `tools/out/<run>/`,与 `tools/baseline/` 做 sha256 对比。
11
-
12
- ## A. 配色看板
13
-
14
- ```bash
15
- node tools/sheet.mjs # 生成 tools/sheet.html(颜色来自生产引擎)
16
- node tools/shoot-sheet.mjs # 4 张 → tools/out/<时间戳>/
17
- node tools/compare.mjs --dir tools/out/<run> # 与基线比
18
- node tools/compare.mjs --dir tools/out/<run> --update # 认这次为新基线
19
- ```
20
-
21
- 同一台机器上渲染是确定的:连跑两次哈希一致(已实测 4/4 未变),所以**字节变化 = 像素变化**,
22
- 不需要容忍阈值。变了就并排看两张 PNG。
23
-
24
- ## B. 桌面版真机
25
-
26
- 桌面版是 Electron,Web GUI 跑在渲染进程里、鉴权由 App 内部完成,所以外部拿不到 token ——
27
- 但 Electron 支持标准 CDP,只要**带调试端口启动一次**:
28
-
29
- ```powershell
30
- # 关掉当前 DSH,然后:
31
- & "D:\Programs\DeepSeek Harness\DeepSeek Harness.exe" --remote-debugging-port=9333
32
- ```
33
-
34
- (想长期这样用:右键桌面快捷方式 → 属性 → 目标末尾加 ` --remote-debugging-port=9333`。)
35
-
36
- 然后:
37
-
38
- ```bash
39
- node tools/shoot-desktop.mjs --out tools/out/desk1 # 4 套 × 浅/深 = 8 张
40
- node tools/compare.mjs --dir tools/out/desk1 --baseline tools/baseline-desktop
41
- ```
42
-
43
- `shoot-desktop.mjs` 的驱动方式不是"点界面",而是走插件自己的开发期接口:
44
-
45
- ```js
46
- window.__DSH_THEME_PALETTE_DEV__.setPalette('warm')
47
- window.__DSH_THEME_PALETTE_DEV__.setTheme('dark')
48
- ```
49
-
50
- 这样不依赖按钮文字、不依赖设置页结构,宿主改版也不会把脚本搞坏。**抓完会把原来的配色与
51
- 明暗偏好还原**(`setTheme` 会写进宿主设置,不还会改用户配置)。
52
-
53
- > 为什么不用 `--profile desktop --dump-config` 之类:那验的是配置层,不是渲染层,
54
- > 而配色的问题全都长在渲染层(色域、层次、对比度)。
55
-
56
- ## 什么时候跑
57
-
58
- - 改 `SCHEMES` / `TEXT_MODES` / `SIDEBAR_MODES` 等参数之后;
59
- - 改 `aliasOverrides`(气泡、按钮、边框、滚动条、注释)之后;
60
- - 升宿版本之后(token 名或步号映射可能变)。
61
-
62
- ## 已知边界
63
-
64
- - A 层是**忠实投影**但不是真机:不含宿主的真实排版、字体回退、外部插件注入的样式;
65
- - B 层依赖调试端口,属于开发期手段,不要常开;
66
- - 两层都只做"变了没有",不做"变得对不对" —— 判断仍要人看。
package/tools/compare.mjs DELETED
@@ -1,62 +0,0 @@
1
- /**
2
- * 截图回归比对:把一批截图与基线逐张比 sha256,报「新增 / 未变 / 变化」。
3
- *
4
- * 为什么用哈希而不是逐像素差分:同一台机器、同一 Chrome、同一视口下渲染是确定的
5
- * (`--force-color-profile=srgb` + `deviceScaleFactor` 固定),字节相同就真的没变;
6
- * 变了就是像素变了,不需要容忍阈值。要「变在哪」就看两张 PNG 并排。
7
- *
8
- * 用法:
9
- * node tools/compare.mjs --dir tools/out/<run> # 与基线比
10
- * node tools/compare.mjs --dir tools/out/<run> --update # 把这次设为新基线
11
- */
12
- import fs from 'node:fs'
13
- import path from 'node:path'
14
- import { createHash } from 'node:crypto'
15
- import { fileURLToPath } from 'node:url'
16
-
17
- const here = path.dirname(fileURLToPath(import.meta.url))
18
- const argv = process.argv.slice(2)
19
- const arg = (k, d) => { const i = argv.indexOf('--' + k); return i >= 0 ? argv[i + 1] : d }
20
- const has = (k) => argv.includes('--' + k)
21
-
22
- const dir = path.resolve(arg('dir', path.join(here, 'out')))
23
- const base = path.resolve(arg('baseline', path.join(here, 'baseline')))
24
- if (!fs.existsSync(dir)) { console.error('找不到截图目录:' + dir); process.exit(2) }
25
-
26
- const hash = (p) => createHash('sha256').update(fs.readFileSync(p)).digest('hex').slice(0, 16)
27
- const pngs = (d) => fs.existsSync(d) ? fs.readdirSync(d).filter((f) => f.endsWith('.png')).sort() : []
28
-
29
- if (has('update')) {
30
- fs.mkdirSync(base, { recursive: true })
31
- for (const f of fs.readdirSync(base)) if (f.endsWith('.png')) fs.unlinkSync(path.join(base, f))
32
- for (const f of pngs(dir)) fs.copyFileSync(path.join(dir, f), path.join(base, f))
33
- console.log(`✓ 基线已更新:${base}(${pngs(dir).length} 张)`)
34
- process.exit(0)
35
- }
36
-
37
- const current = pngs(dir)
38
- const baseline = pngs(base)
39
- if (baseline.length === 0) {
40
- console.log('还没有基线。第一次请跑:node tools/compare.mjs --dir ' + path.relative(process.cwd(), dir) + ' --update')
41
- process.exit(2)
42
- }
43
-
44
- const rows = []
45
- let changed = 0
46
- let missing = 0
47
- for (const name of current) {
48
- const inBase = baseline.includes(name)
49
- const same = inBase && hash(path.join(dir, name)) === hash(path.join(base, name))
50
- if (!inBase) missing++
51
- else if (!same) changed++
52
- rows.push({ name, status: !inBase ? '新增(基线里没有)' : same ? '未变' : '变化', size: fs.statSync(path.join(dir, name)).size })
53
- }
54
- const gone = baseline.filter((n) => !current.includes(n))
55
-
56
- const pad = (s, n) => String(s).padEnd(n)
57
- console.log(pad('截图', 26) + pad('状态', 18) + '大小')
58
- for (const r of rows) console.log(pad(r.name, 26) + pad(r.status, 18) + (r.size / 1024).toFixed(0) + ' KB')
59
- if (gone.length) console.log('\n基线里有、这次没抓:' + gone.join(', '))
60
-
61
- console.log(`\n合计 ${current.length} 张:未变 ${current.length - changed - missing},变化 ${changed},新增 ${missing}`)
62
- process.exit(changed + missing > 0 ? 1 : 0)