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
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-custom-skin from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.