Skip to content
dsh.fish
Bundle

dsh-custom-skin

DSH Web GUI plugin: custom UI/code fonts plus theme selection (palettes ported from dsh-refined: Border / Nord / Twilight / GitHub / Atom One), live settings page, localStorage persistence.

Source
v587d
stars
1 stars
License
MIT
Updated
Updated yesterday

Readme

# dsh-custom-skin

DeepSeek Harness(DSH)Web GUI 插件:自定义 **界面字体 / 代码字体**,以及**主题选择**(调色板移植自 [dsh-refined](https://github.com/djh2203/dsh-refined):Border / Nord / Twilight / GitHub / Atom One)。

- 改字体:覆盖 `--dsw-font-family`、`--ds-font-family-code`
- 选主题:`ctx.theme.overrideTokens` 覆盖 `--dsw-alias-*` 令牌(light/dark 成对)+ Markdown 美化 CSS(标题色条、代码/引用样式,选择器与当前 DSH 构建一致)
- 偏好存在浏览器 `localStorage`(键 `dsh-custom-skin.v1`),刷新不丢
- 设置页「Custom Skin」里可即时编辑(若当前 DSH 版本支持 `settings.section` 注入)

## 安装

从 GitHub(推荐):

```bash
dsh plugin --profile web add github:v587d/dsh-custom-skin
```

本地开发(改代码即时映射,不用重新 add):

```bash
dsh plugin --profile web add link:/home/shawn/projects/dsh-custom-skin
```

安装完成后重启:

```bash
dsh web
```

> 安装命令会把插件自动加入 `dsh.profile.bundles`(reconcile 检测到
> `package.json` 里的 `dsh.bundle.patch` 声明),无需手动改配置。
> 卸载:`dsh plugin --profile web remove dsh-custom-skin`。
> 验证:重启后 设置 → Custom Skin 出现,或 `grep dsh-custom-skin ~/.dsh/profiles/web/package.json`。

> 兼容性:`0.1.1-rc.2` 开发,`0.1.2-rc.1` 实测通过(client bundle 形态、
> `slots`/`theme` 服务、`settings.section` 注册协议、
> `[class*="_markdown_"]`、`body[data-ds-dark-theme]`、`.md-table-wide`
> 在 0.1.2 均未变)。主题令牌与 Markdown 选择器随 DSH 构建版本变化,
> 升级 DSH 后如失效,按文末「开发注意事项」复查。

## 使用

1. 打开 Web UI → **设置 → Custom Skin**(若有该分节)
2. **Color theme**:选 `Default (DSH theme)` 或 Border / Nord / Twilight / GitHub / Atom One(默认 GitHub)
3. 填写本机已安装的字体名,例如:
   - UI:`"OPPO Sans 4.0", "Microsoft YaHei", sans-serif`
   - Code:`"Geist Mono", "Fira Code", Consolas, monospace`

没有设置页时,也可在控制台执行:

```js
localStorage.setItem('dsh-custom-skin.v1', JSON.stringify({
  uiFont: '"OPPO Sans 4.0", "Microsoft YaHei", sans-serif',
  codeFont: '"Geist Mono", "Fira Code", Consolas, monospace',
  theme: 'github'   // 'default' | 'border' | 'nord' | 'twilight' | 'github' | 'atom-one'
}))
location.reload()
```

## 注意

- **字体必须已安装在操作系统里**,插件不打包字体文件
- 其他主题/皮肤插件若也写同一批 CSS 变量,后加载的会盖掉先加载的
- 卸载插件后 `localStorage` 里的配置仍会留着,可手动清:`localStorage.removeItem('dsh-custom-skin.v1')`

## 开发注意事项(重要)

- **`lib/client.js` 必须是「经典脚本」,不能含顶层 `export`/`import`**。
  DSH 用 `<script src>` 加载 client bundle(见 `dsh-client-modules` 的
  `loadBundle`),文件里出现 `export` 会直接 `SyntaxError: Unexpected token 'export'`,
  整个插件加载失败,Web 启动页报 "Failed to load plugins"。
  正确形态:`window.__ModuleLoader__.load({ id: 'dsh-custom-skin', factory: (require) => { ... return module.exports } })`,
  与官方编译产物一致。
- 主题走 `ctx.theme.overrideTokens('custom-skin', { '--dsw-alias-xxx': { light, dark } })`,
  不要手写 `:root` 覆盖颜色令牌(令牌要求 light/dark 成对)。
  `buildTokens` 只输出 **Theme inspect provider 当前列出的可覆盖令牌**
  (0.1.2-rc.1 为 13 个),而不是「设计令牌表里出现过的所有名字」——
  `--dsw-alias-bg-layer-3`、`--dsw-alias-label-tertiary`、
  `--dsw-alias-markdown-*` 等 0.1.1-rc.2 里没有、0.1.2-rc.1 的设计令牌表里
  已经有了,但仍不在可覆盖清单内,写了也不生效(想扩展先查
  Theme inspect provider 的最新清单);Markdown 美化 CSS 依赖
  `[class*="_markdown_"]` 与 `body[data-ds-dark-theme]` 两个选择器,
  升级 DSH 后需复查。
- **宽表格抖动修复(主题无关,总是生效)**:DSH 对 ≥4 列的宽表格
  (`.md-table-wide`)平时 `overflow-x:hidden` + `padding-bottom:
  var(--dsh-scrollbar-width, 8px)`,hover 时切到 `overflow-x:auto` 并清零
  padding。DSH 构建从未定义 `--dsh-scrollbar-width`(恒为 8px 兜底),且
  hover 的高度补偿只在表格**实际溢出**且滚动条占用空间时成立;表格装得下
  时 hover 会直接塌缩 8px(在真实 GUI 实测 146px→138px),鼠标在表格底边
  反复进出 hover → 页面抖动。插件在 `applySkin` 中**无条件**输出两条修复:
  ① 发布 `:root { --dsh-scrollbar-width: <实测值>px }`(探针实测水平滚动条
  高度,悬浮滚动条系统为 0,供 DSH 及 dsh-context 等消费);
  ② 用 `!important` 把 `.md-table-wide` 三态(常态/hover/focus-visible)
  固定为 `overflow-x: auto` + `padding-bottom: 0`——滚动条常驻、无 hover
  依赖的 padding,任何溢出与否、任何滚动条尺寸下高度都恒定(与 DSH 对小
  表格 `.tableFill` 的做法一致)。代价:宽表格的横向滚动条不再"hover 才
  出现",而是常驻可见。类名 `md-table-wide` 与令牌名 `--dsh-scrollbar-width`
  与 DSH 构建耦合,升级 DSH 后需复查(0.1.2-rc.1 已复查:`.md-table-wide`
  的 hover 规则与 `--dsh-scrollbar-width` 的 8px 兜底都没变)。
- 设置页用 `ctx.slots.inject('settings.section', () => ctx.slots.register({...}, Panel))`。
- **inject 有两处、写法不同**:
  - bundle 内插件对象的 `inject` 用**服务 key**:`['slots', 'theme']`
    (fiber 靠它在 ctx 注册表里等服务;写包名会永远 pending,
    启动页报 "waiting for services: @deepseek-ai/...");
  - `package.json` 的 `dsh.client.inject` 用**包名**:
    `['@deepseek-ai/dsh-client-ui-renderer', '@deepseek-ai/dsh-client-ui-theme']`
    (仅用于 boot 清单预取/排序)。两者各司其职,别混。
    包名会随 DSH 版本搬家:0.1.1 里 slots 的包名是
    `@deepseek-ai/dsh-client-ui-slots`,**0.1.2 起已合并进
    `@deepseek-ai/dsh-client-ui-renderer`**。写旧包名不会报错——构图时
    host 只看 `external`、浏览器端对不在启动图里的 inject 包名直接跳过——
    代价是丢掉「slots 提供方先于本插件加载」的排序保证。
- 改完 `lib/client.js` 后无需重装,重启 `dsh web` 即可(bundle 内容 hash 变化会自动换 rev)。

## 目录

```
dsh-custom-skin/
├── package.json
├── cordis.patch.yml
├── lib/
│   ├── index.js    # host 半边(占位)
│   └── client.js   # 浏览器半边(应用样式 + 设置 UI)
└── README.md
```

## License

MIT

Install

dsh plugin --profile web add github:v587d/dsh-custom-skin

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source