# One Dark Pro for DSH — 实施计划 > 状态:**已实现、已安装,自动检查全部通过;等待你的目检反馈(见第 11 节)** > 日期:2026-10-18 > 工作目录:本仓库根目录 > 目标:给当前 DSH Web GUI 做一个可切换的 **One Dark Pro 经典版** 主题插件,并安装进 `desktop` profile --- ## 0. 你的选择(已确认) | 问题 | 你的决定 | | --- | --- | | 配色基准 | **One Dark Pro 经典版**(`#282c34` 底 + `#abb2bf` 字 + 蓝/紫/绿语法色) | | 交付形态 | **做成可切换主题插件**(设置里可切换,随时切回原生,不改 DSH 本体) | | 亮色模式 | **只做暗色**,亮色保持 DSH 现有默认配色不变 | | 覆盖范围 | **全套复刻**:UI 表面 / 文字 / 边框 / 品牌色 / 状态色 / 滚动条 / Markdown 代码块 / Shiki 语法高亮 | | 主按钮 / 品牌色 | **按扩展原版用灰 `#404754`**(`button.background`),不改蓝 | | 流程 | 计划已确认,按此文档实施 | --- ## 1. 目标与非目标 **目标** - 在 **设置 → 通用** 新增一行「主题」,提供两个方块:**原生** / **One Dark Pro**。 - 选中 One Dark Pro 后,当前 DSH 界面切换成 One Dark Pro 经典版配色;选择「原生」立即恢复 DSH 默认。 - 选择被记住(刷新、重启 GUI 后仍生效);多窗口之间同步。 - 覆盖 DSH 设计系统登记的全部可覆写令牌(约 150 个:`--dsw-alias-*`、`--dsw-specific-*`、`--dsw-menu-surface-fill`、`--dsw-linear-*`、`--shiki-*`),包括滚动条和代码高亮。 - 以 bundle 插件形式安装进 `desktop` profile,可一键卸载,**不改动 DSH 安装目录的任何文件**。 **非目标** - 不做亮色变体(不设计 One Light);亮色外观保持 DSH 默认。 - 不做 One Dark Pro 的其它变体(Darker / Flat / Night Flat / Mix)——后续想加时只需再补一份 token 表。 - 不改 DSH 源码、不替换 `ui-theme` 插件、不动 profile 里已有的 patch 条目。 - 无法覆盖第三方插件里**写死**的颜色(这是主题扩展点的固有边界)。 --- ## 2. 参考色板(不是凭记忆,取自本机原版主题文件) 本机 VS Code 装有 One Dark Pro 扩展,已直接读取其主题源文件: - VS Code 扩展 `zhuangtongfa.material-theme 3.20.3` 的 `themes/OneDark-Pro.json` - 该文件定义 **223 个 UI 颜色键 + 275 条语法 tokenColors**,本计划按它为准。 关键颜色(表格均取自该文件): | 用途 | 色值 | | --- | --- | | 编辑器 / 主背景 | `#282c34` | | 侧栏 / 面板 / 弹层背景 | `#21252b` | | 列表悬停 / 选中底 | `#2c313a` | | 行内高亮 | `#2c313c` | | 输入框底 | `#1d1f23` | | 聚焦边框 / 面板边框 | `#3e4452` | | 标签栏、分组分隔线 | `#181a1f` | | 选区 | `#67769660` | | 光标 / 强调蓝 | `#528bff` | | 正文前景 | `#abb2bf` | | 状态栏文字 / 弱化文字 | `#9da5b4` | | 注释 / 最弱文字 | `#5c6370` | | 行号槽 / 滚动条 | `#4b5263`、`#4e566660`、`#5a637580`、`#747d9180` | | 语法:关键字 / 函数 | `#c678dd` / `#61afef` | | 语法:字符串 / 数字常量 | `#98c379` / `#d19a66` | | 语法:变量、标签、正则 | `#e06c75` | | 语法:类型、类 | `#e5c07b` | | 语法:转义、枚举成员、模板表达式 | `#56b6c2` | | 链接 | `#61afef` | > 许可说明:One Dark Pro 采用 MIT 许可(© 2016 Zhuang TongFa / Binaryify 等,见扩展 LICENSE)。色值本身不受版权保护;README 中会标注参考来源,不复制其代码。 --- ## 3. 交互设计 ### 3.1 设置行 位置:**设置 → 通用**,排在 DSH 自带的「外观」「字号大小」之后(`settings.general.item`,order = 13)。 ``` 主题 [ 原生(虚线色块) ] [ One Dark Pro(四色渐变色块) ] 深色外观下生效;选择 One Dark Pro 会自动切换到深色外观 ``` - 两个方块复用 DSH 内置「外观」行的视觉规则(相同边框、圆角、选中态),全部用主题令牌上色。 - 选中态:`aria-pressed` + 高亮边框(用 `--dsw-alias-brand-primary` 而不是硬编码灰色)。 - 文案跟随界面语言(中文 / English),通过 Client locale 服务注册、由 slot 描述符 `locale` 驱动重渲染。 ### 3.2 行为 | 操作 | 结果 | | --- | --- | | 点「One Dark Pro」 | 记住选择;把 One Dark Pro 图层叠加到当前主题;如果当前外观是浅色,自动调用 `ctx.theme.setTheme('dark')` 切到深色,让主题立刻可见 | | 点「原生」 | 撤掉图层,恢复 DSH 默认(外观深浅仍由「外观」那行决定) | | 刷新 / 重启 | 读取 localStorage 中记住的选择并恢复;不强行改变外观偏好 | | 另一个窗口切换 | 监听 `storage` 事件自动同步 | | 切换到浅色外观 | One Dark Pro 图层自动"透明"(浅色面 = DSH 默认浅色值),界面与原生完全一致 | > 设计说明:One Dark Pro 本身是深色主题,所以「选中即切深色」只发生**点击那一刻**;用户之后在「外观」里手动选浅色,不会被强行拉回深色。 --- ## 4. 技术方案 ### 4.1 为什么不做成「注册一个一等主题」 DSH 的 `ctx.theme` 支持 `register({ id, colorScheme, tokens })`,但实测源码后确认: 1. 设置里的「外观」行只有 `light / dark / system` 三个硬编码方块,第三方注册的 theme id 不会出现在 UI 里; 2. 设置 schema 的 `preference` 字段只接受 `light / dark / system`,第三方 theme id **不持久化**,设置在回传后会被 `adopt()` 重置。 因此采用 DSH 官方文档认可的第二种扩展点:**`ctx.theme.overrideTokens(source, tokens)` 叠加一层别名令牌覆盖 + 自己贡献一行设置 UI**。这正是本 profile 里 `@xiaotuchu/dsh-theme-eye-care` 使用并被验证过的模式(该插件已安装、可作对照)。 ### 4.2 DSH 主题令牌机制(已从源码确认) - `ctx.theme.overrideTokens(source, tokens)`:`tokens` 是 `{ 令牌名: { light, dark } }`;图层按注册顺序叠加、后者逐令牌获胜;同一 `source` 再调用会整体替换并置顶。 - `ui-layout` 的 presenter 把最终合成后的 `active.tokens` 以 **body 内联 CSS 变量** 写入,切换主题时先移除旧变量再写新变量。 - 内置 `light/dark` 主题的 `tokens` 是空对象——两套调色板在 CSS 样式表里定义;覆盖层就是给这些变量注入具体值。 - 因此:**暗色面写 One Dark Pro,浅色面写 DSH 当前默认浅色值**,即可做到「深色变、浅色不变」。浅色值已从 DSH 0.2.1-rc.2 的 `design-platform.css` 等 8 张样式表自动解析提取(429 个令牌的双面值,快照存档,可在 DSH 升级后重新生成)。 ### 4.3 插件结构(bundle 包) ``` 仓库根目录\ ├── PLAN.md # 本文件 ├── README.md # 安装/切换/卸载/覆盖范围/已知边界 ├── package.json # dsh.bundle.patch + dsh.client(platform: web, inject: ui-theme) ├── cordis.patch.yml # insert 本插件 row ├── index.js # Host 半边:空 apply()(只为让 loader 发现 dsh.client 清单) ├── client.js # 构建产物:ModuleLoader bundle(令牌表 + 设置行 + 覆盖逻辑) ├── locale/ │ ├── en.json # meta.title / meta.description(插件页展示用) │ └── zh.json ├── icon.svg # 原创图标:One Dark 四色块 ├── themes/ │ └── one-dark-pro.json # 设计期真源:令牌 → 暗色值 + 说明(不含浅色值) ├── tools/ │ ├── build.mjs # 由 themes/*.json + 浅色默认快照生成 client.js │ ├── contrast.mjs # 对比度自检 │ └── reference/ │ ├── dsh-default-light.json # 从 DSH 0.2.1-rc.2 样式表提取的浅色/暗色默认值快照 │ └── onedark-colors.json # 从 One Dark Pro 主题 JSON 提取的色值参考 ``` 安装方式:`plugin_manager` 的 `install_bundle`,target 为本目录绝对路径。安装后写入 profile 的 bundle 列表,**不手改** profile 的 `package.json` / `cordis.patch.yml`。 ### 4.4 client 模块要点 - 包格式:`window.__ModuleLoader__.load({ id: 'dsh-theme-one-dark-pro', factory(require) { ... } })`。 - factory 返回 `{ inject: ['locale', 'theme', 'slots'], apply(ctx) }`。 - `apply` 内: - `ctx.effect` 注册 locale 词典、读取 localStorage、挂 `storage` 监听、必要时叠加图层,并返回清理函数; - `ctx.slots.inject('settings.general.item', () => ctx.slots.register({ name: 'settings.general.item', id: 'one-dark-pro', order: 13, locale: LOCALE_NS }, ThemeRow))`。 - 样式:组件内注入一行 CSS(`style[data-plugin-css]`,模块卸载时自动移除),类名加 `dshOneDarkPro_` 前缀,避免与其他插件冲突。 - 资源清理:图层 disposer、事件监听、locale 词典都在 effect 的返回函数里释放。 --- ## 5. 令牌映射(暗色面) 完整 156 个令牌的映射写在 `themes/one-dark-pro.json`;下表是分组策略与最终取值: | 分组 | 令牌示例 | One Dark Pro 值 | | --- | --- | --- | | 基础背景 | `bg-base` | `#282c34` | | 抬升表面 | `bg-layer-1` / `-2` / `-3` | `#21252b` / `#2c313a` / `#323842` | | 浮层/平台底 | `bg-overlay`、`bg-module-platform`、`bg-multi-select`、`bg-document-preview` | `#21252b` / `#21252b` / `#3a3f4b` / `#21252b` | | 遮罩 | `bg-mask-1/2/3/photo/drop` | `rgba(0,0,0,.5)` / `.2` / `.55` / `.88` / `rgba(40,44,52,.72)` | | 骨架屏/选区 | `bg-skeleton`、`bg-document-selection` | `rgba(171,178,191,.08)`、`#67769660` | | 边框 | `border-l1` → `l4`、`border-inverted*` | `rgba(171,178,191,.08/.14/.20/.28)`、透明 | | 文字 | `label-primary` / `secondary` / `tertiary` / `caption` / `dimmed` | `#abb2bf` / `#9da5b4` / `#7f848e` / `#6b717d` / `#5c6370` | | 反色文字 | `label-primary-inverted` / `-foreground` / `-dimmed` / `-bluish` | `#282c34` / `#1e2127` / `#9da5b4` / `#61afef` | | 品牌 | `brand-primary`、`brand-text`、`brand-primary-invert`、新色键 | `#404754`(VS Code `button.background`)、`#d7dae0`、`#d7dae0`、`#61afef` | | 按钮 | `button-primary-fill/hover/dimmed`、`elevated/floating/ghost/info`、`tool-bar-*` | 主按钮 `#404754` / 悬停 `#4b5263` / 文字 `#ffffff`;其余用 `#2c313a`/`#21252b`/`#4d78cc` | | 交互底 | `interactive-bg-hover/active/*` | `rgba(255,255,255,.08/.14)`、accent `rgba(97,175,239,.24)`、danger `rgba(224,108,117,.16)` | | 状态 | `state-success/error/warn/business/idle` 的 primary/secondary/tertiary | `#98c379` / `#ff616e` / `#d19a66` / `#61afef` / `#5c6370`,次级用同色 12~18% 透明底 | | 滚动条 | `scrollbar-bg-l1/l2`、`hover-l1/l2` | `#4e566666` / `#5a637580` / `#5a637580` / `#747d9180` | | 代码/Markdown | `markdown-code-block`、`inline-code`、`code-block-banner`、`code-segment-*`、`placeholder`、`tag`、`citation` | `#21252b` / `#2c313a` / `#21252b` / `#2c313a` / `#21252b` / `#3e4452` / `#2c313a` / `#2c313a` | | 文件 diff | `file-diff-*`、`code-diff-*` | 绿 `#21301f`~`#98c379`、红 `#3a2325`~`#e06c75`,alpha 12% | | 菜单/浮层 | `menu-surface-fill`、`menu-group-header-fill`、`menu-icon`、`specific-menu`、`tooltip-*`、`toast-*` | `#21252b`、`rgba(33,37,43,.94)`、`#9da5b4`、`#21252b`、`#1d1f23` / `#2c313a`、`#21252b` / `#abb2bf` | | 侧栏 | `specific-sidebar-fill`、`nav-item-active/hover/accent`、`sidebar` 系列 | `#21252b`、`#2c313a`、`rgba(255,255,255,.06)`、`#61afef` | | 其它 specific | `bubble`、`bubble-highlight`、`input-major`、`login-input`、`selector`、`tip`、`switch-thumb` | `#2c313a`、`#3a3f4b`、`#21252b`、`#1d1f23`、`#2c313a`、`#3a3f4b`、`#abb2bf` | | 卡片/引导 | `settings-card-fill/stroke`、`onboarding-*` | `#2c313a` / `#3e4452`、`#61afef` 等 | | 动效渐变 | `linear-gradient-think`、`linear-think-select` | 由 `#21252b` / `#2c313a` 派生 | | Shiki 代码高亮 | `shiki-foreground/background`、`token-comment/constant/function/keyword/link/parameter/punctuation/string/string-expression` | `#abb2bf` / `#21252b`、`#7f848e` / `#d19a66` / `#61afef` / `#c678dd` / `#61afef` / `#e06c75` / `#abb2bf` / `#98c379` / `#56b6c2` | **静态色阶**:16 个被 DSH 组件直接引用的 `--dsw-static-*` 步进一起映射(`neutral-850 → #323842` 作卡片底、`neutral-800 → #3e4452` 作卡片悬停、`neutral-700 → #4b5263`、`blue-* → One Dark 蓝`,白/黑/浅色步进保持中性),否则它们会被第三方主题插件染成暖色;其余未被引用的静态步进、`--dsw-shadow-*`、字号/字体/圆角令牌不动。 ### 5.1 对比度自检 `tools/contrast.mjs` 会对关键组合计算 WCAG 对比度并列成表,目标: - 主要文字 / 主底 ≥ 4.5(One Dark Pro 原值约 7); - 次要文字 / 主底 ≥ 4.5;三级文字 ≥ 3.5;最弱提示 ≥ 2.5; - 链接、状态色 / 主底 ≥ 4.5; - 反色文字 / 品牌色 ≥ 4.5。 若有组合不达标,优先保留 One Dark Pro 观感、仅对派生的弱化灰阶做最小调整,并在 README 里记录实测值。 --- ## 6. 交付物 1. 仓库根目录下的完整插件工程(见 4.3 目录树)。 2. 安装进 `desktop` profile 并生效(若 DSH 提示需重启,则请你重启一次)。 3. `README.md`:安装/切换/卸载方式、覆盖范围表、对比度实测、已知边界、如何重新生成。 4. 本计划文档(确认后保留为项目记录)。 5. 给出一份验收清单,你在界面上逐条目检。 --- ## 7. 验证计划 | # | 检查项 | 方法 | | --- | --- | --- | | 1 | 语法/清单 | `node --check` 所有 JS;JSON 全部 parse;`package.json` exports/files 与文件一致 | | 2 | 构建可复现 | 连续构建两次,`client.js` 字节一致 | | 3 | 令牌覆盖率 | 与 `tools/reference/overridable-tokens.json`(DSH 当前登记的约 150 个可覆写令牌)比对,覆盖率 100%,缺一个就报错 | | 4 | 对比度 | 跑 `tools/contrast.mjs`,输出实测表,无低于阈值的项 | | 5 | 安装 | `plugin_manager install_bundle`;按返回的 `application` / `warnings` 判断是否 live,若 `restart-required` 则告知你重启 | | 6 | 设置行真的挂载 | `cordis_inspect_query`(Client `Slots.listSubTree`,root = `settings.general.item`)确认 occupant 中出现本行 id;`Theme.listTokens` 无异常 | | 7 | 目检 | **需要你配合**:打开 设置 → 通用 → 主题,点「One Dark Pro」,检查主界面/侧栏/代码块/滚动条;再点「原生」确认回退。我这边无法截屏,视觉确认以你的反馈为准 | | 8 | 持久化/同步 | 刷新页面后选择仍在;打开第二个窗口切换,第一窗跟随变化 | | 9 | 回滚 | 点「原生」;或卸载 bundle 后刷新,界面回到 DSH 默认 | > 关于「无法自动截图」:本会话没有浏览器控制工具,按插件开发规范(verification.md),视觉部分只能以实时 slot 注册 + 你的目检为准,我不会用假截图冒充验证。 --- ## 8. 风险与边界 | 风险 | 说明 / 应对 | | --- | --- | | 首次安装需重启 | 插件名册在 DSH 启动时组装;装完若提示 `restart-required` 就重启一次,之后切换主题无需重启 | | 与护眼配色插件叠加 | profile 里已启用 `@xiaotuchu/dsh-theme-eye-care`,它也叠一层令牌层;两层同时存在时后叠的逐令牌获胜。建议在「背景色」行选「原生」或禁用该插件,避免两套配色混搭 | | 浅色值被钉住 | 浅色面写的是 DSH 0.2.1-rc.2 的默认值;DSH 大版本若改浅色调色板,重跑 `tools/build.mjs` 即可刷新 | | 第三方硬编码颜色 | 少数第三方组件/行内 style 写死颜色时主题盖不到(DSH 自带组件没有此问题) | | One Dark Pro 变体 | 本版只做经典版;要 Darker/Flat 只需再加一份 token JSON | | 旧配置残留 | profile patch 里有 `@hmkk/dsh-appearance`(onedark 预设)的 **disabled** 条目,已装包不在 node_modules,属惰性残留;本计划**不动它**,避免手改 profile 配置 | --- ## 9. 实施步骤(确认后执行) 1. 建工程骨架:`package.json` / `cordis.patch.yml` / `index.js` / locale / 原创 icon。 2. 整理参考数据:`tools/reference/onedark-colors.json`(从你本机 VS Code 扩展解析)、`dsh-default-light.json`(已从 DSH 样式表提取)。 3. 编写 `themes/one-dark-pro.json`(约 150 令牌的暗色映射 + 派生色),跑覆盖率与对比度自检。 4. 写 `tools/build.mjs` 与 `client.js` 运行时(设置行 UI、选择持久化、多窗口同步、令牌图层、locale),构建并校验。 5. `install_bundle` 安装,检查安装结果与实时 slot 注册。 6. 请你目检;根据你的反馈做一轮颜色微调(只改 `themes/*.json` 后重新构建、重装)。 7. 补 README、验收清单与最终报告。 **预计**:实现 + 安装约 30–60 分钟;目检后的微调按你的反馈进行。 --- ## 10. 已确定的小点 1. **主按钮颜色**:✅ 已按你的要求改用 VS Code 扩展原版灰 `#404754`(`button.background`),按钮文字 `#ffffff`。 2. **点击自动切深色**:保持「点 One Dark Pro 即自动切到深色外观」,只在点击那一次写入深色偏好。 3. **插件标识**:包名 `dsh-theme-one-dark-pro`(本地工作目录 `D:\aclo\OneDarkPro\dsh-theme`),设置行标题「主题 / Theme」,选项「原生 / Native」与「One Dark Pro」。 4. **颜色微调记录**:错误色从语法红 `#e06c75`(4.38:1)换成同主题的 `terminal.ansiBrightRed #ff616e`(4.79:1),以满足正文对比度;其余颜色严格取扩展原值。 --- ## 11. 实施结果 - 插件位于本仓库根目录,已通过 `plugin_manager install_bundle` 安装进 `desktop` profile(bundle 列表已含 `dsh-theme-one-dark-pro`,本地目录 junction)。 - 安装返回 `application: applied`、无 warnings;无需重启即生效。 - 实时校验:Client `Slots.listSubTree` 显示 `settings.general.item` 的 occupants 中已有 `one-dark-pro`(order 13,active),说明 Client 模块已加载、设置行已挂载。 - 静态校验:`node --check` 全通过;JSON 全部可解析;构建两次字节一致;令牌覆盖 132/132 + 16 个静态步进 + 8 个组件专用令牌 = 156;对比度硬性项全通过。 - 目检反馈修复(2026-10-18):文件卡片呈土黄色——根因是护眼配色插件把 `--dsw-static-neutral-850/800` 等静态令牌改成暖棕,而 DSH 文件卡片直接引用这些令牌。已把 16 个静态步进纳入本主题映射,卡片改为灰色并保持与背景的层级差。 - 目检反馈修复(2026-10-18):Markdown 行内代码(`button.background` 这种小方块)文字改为 One Dark Pro 关键字紫 `#c678dd`。前端把行内代码渲染成裸 `:not(pre)>code`,文字颜色继承正文、没有对应 `--dsw-*` 令牌,因此由主题图层激活时注入一条 `[class*="markdown"] :not(pre)>code` 规则,浅色或选「原生」时移除。 - 目检反馈修复(2026-10-18):行内代码字号从前端固定的 `0.875em` 改为 `1em!important`(与正文同字号,跟随 Settings 字号设置缩放),并加 `font-weight:600` 半粗;Markdown 加粗文字颜色改用链接蓝 `var(--dsw-alias-link)`;两者仍只在该主题激活时生效。 - 运行时行为测试(无浏览器):图层在深色下叠加 / 浅色下撤销、默认选择、持久化、多窗口 storage 同步、清理释放,全部通过。 - 目检反馈修复(2026-10-19):启动后先显示 DSH 原生深色、要手动点「原生 → One Dark Pro」才套上主题。原因是主题服务在启动时异步恢复偏好,图层可能在恢复前就尝试叠加、之后又没收到/错过主题事件。已改为启动后多次延迟重试 + 每秒轻量校准(`sync()` 幂等),首屏会自动补上图层;同时把选择写入 `127.0.0.1` 的 cookie,桌面版重启换端口后仍能记住「原生 / One Dark Pro」的选择。 - 目检反馈修复(2026-10-19,v1.0.2):加载页阶段 One Dark Pro 闪一下、进入界面后仍退回原生深色。判断为启动竞态:旧的 ThemePresenter 晚于新的销毁时会按名字清掉 body 上的内联主题变量(不区分实例)。已加三道路径修复:监听 `body` 的 `style`/`data-ds-dark-theme` 变化(MutationObserver)立即重叠;每秒对账 body 上实际的 `--dsw-alias-bg-base` 是否等于主题值,不一致就重新叠层;重解析 `ctx.get("theme")` 服务实例并在快照 revision 变化时重叠。旧实例的清理也用 materialization token 保护,避免误删新实例的图层。 - **视觉确认待你完成**:我这边没有浏览器截图能力,请在界面上按第 7 节第 7 条目检。