DSH Plugins Marketplace

DSH Plugins

Plugins

/

dsh-custom-css

F

dsh-custom-css

Manifest valid★ 1

DSH Web GUI Extension: Adds a custom CSS editor row below the "Appearance" section in Settings → General.

UI (client)hasBundlePatch

dsh-custom-css

dsh-custom-css 图标:深色渐变圆角方块 + 白色花括号与三条声明行

设置行里的 CSS 编辑器:写完即生效,样式表就是磁盘上的 .css 文件

CI release npm version npm unpacked size GitHub stars License: MIT 插件生态:GitHub topic dsh-plugin 收录于 dshfind

支持的 DSH 版本:0.1.2 与 0.1.5 已真机验证,接口契约断言覆盖到 0.0.1-rc.5

即时生效 DevTools 编辑器 键入补全 规则面板 格式校验 声明模板 元素拾取器 多文件管理 明暗自适应 属性字典 历史版本 查找替换 撤销重做 规则大纲

拾取元素 · 查找 · 文件下拉 · 更多操作,把 ~/.dsh/custom-css/ 下的样式表注入界面 ——
编辑器照 DevTools 的 Styles 标签页做:行号 gutter、语法高亮、键入补全、规则面板、格式校验、撤销重做、查找替换;
拾取器在界面里点选一个元素,直接给出跨版本尽量稳的选择器,并把它实际生效的令牌写成声明。

DSH Web GUI 扩展:在 设置 → 通用 的「外观」下方增加一行 自定义 CSS —— 样式表以普通 .css 文件保存在宿主磁盘上,写进去即时应用到整个界面。

  • npm:dsh-custom-css(2026-09-13 首发,当前版本见上方徽章)
  • 收录:dshfind 插件超市 → 皮肤主题(徽章与展示卡由 dshfind 提供,/api/badge 与 /api/card)
  • 兼容:DSH 0.1.2-rc.1 ~ 0.1.5-rc.1 已真机验证,接口契约断言覆盖到 0.0.1-rc.5;npm run compat 可复跑(见「兼容性」)
  • 许可:MIT
  • 形态:DSH profile 插件(host 半 + 浏览器半),无构建步骤,lib/*.js 即产物
  • 测试:node tests/loader-smoke.cjs / node tests/host-api-smoke.mjs
dshfind 展示卡(440×200)

dshfind

位置与行为

  • 注册在 settings.general.item 槽,order: 12。DSH 自带的 ui-theme 在这个槽上占了 appearance(order 10) 和 font-size(order 11),所以这一行正好落在它们下面。
  • 样式表由 host 侧读写,存放在 ~/.dsh/custom-css/($DSH_HOME/custom-css,$DSH_HOME 未设时为 ~/.dsh)。是普通文件:可用任意编辑器改、可备份、可放进版本库。
  • 当前活动文件名与各文件的开关记录在同目录的 active.json(如 {"active":"custom.css","disabled":["dark-tweak.css"]})。
  • 编辑内容即时生效(防抖 400ms 后写回文件并重绘页面)。编辑器自带撤销/重做(每次连续输入的停顿算一步)、查找 / 替换(Ctrl+F)、Tab / Shift+Tab 缩进、Ctrl/Cmd+S 立即写盘;补全列表打开时 Tab 是「采用建议」。
  • 查找 / 替换:「查找」按钮或 Ctrl+F 打开表头的查找栏 —— 查询取编辑器里选中的那段文字,Aa 控制大小写,命中在彩色层里被圈出来(当前那条更重),Enter / Shift+Enter 走命中,替换 / 全部替换都是一次编辑(一次 Ctrl+Z 全撤回)。查询是纯文本,不是正则。窄窗口下命中计数、按钮文字会依次让位(见「已知坑」)。
  • 格式化在「更多操作」里:一条规则一行、每层缩进两空格、去掉空行。它只重排行,不改一行里写的东西(缺分号留给校验器报);括号不配对的表原样返回。
  • 在文本编辑器里保存,插件跟着变(自动同步):每 1.5 秒(只在页面可见时)问一次宿主「这张表动过没有」,判据是宿主报的修订号(size/mtime),不是文本内容 —— 编辑器把同一份内容重写一遍(改行尾、重新保存)不会被误判成冲突,而「文件被 touch 了一下」也不会被漏掉。编辑器干净时直接采用磁盘那份;有没保存的改动时不覆盖,改成页脚问「重新载入 / 覆盖它」。宿主另外用 fs.watch 盯着样式表目录(不是单个文件:VS Code 默认写临时文件再改名覆盖,盯着旧文件的监听从此看不到任何东西),那只是让同步快一点 —— 起不来的文件系统上功能照旧,走轮询。
  • 写盘前会先读一次 同一套判据:文件在别处被改过且编辑器里有没保存的内容,就不写,页脚问你「重新载入 / 覆盖它」。页面从后台切回前台时也会查一次,但那条路径只发现、不采用 —— 你可能正读到一半。
  • 每次覆盖写盘前留一份快照:被替换掉的内容存进同目录的 .history/<文件名>/<毫秒戳>.css,每张表最多 20 份(按时间从旧的开始剪)。内容与最新一份相同时不重复留档 —— 否则自动保存的防抖写盘会把有用的版本挤出去。整张表的任何一次覆盖都留档(编辑器的自动保存、导入、以及恢复本身),所以恢复是可逆的。从「更多操作 → 历史版本」可以查和回退(见下表)。
  • 编辑器是 DevTools Styles 标签页的样式:左侧行号 gutter、语法高亮(注释 / 选择器 / 属性 / 值 / 标点)、输入时弹出补全列表。
    • 高亮用 DSH 自己的 shiki 配色 token(--shiki-token-keyword/constant/string/comment/punctuation),所以与 Markdown 代码块同色并随浅深主题切换;渲染前逐段 HTML 转义,样式表永远不可能变成标记。
    • 补全数据来自浏览器本身:属性名是从 CSSStyleDeclaration.prototype 枚举出来的(即该引擎真正认识的全部属性,含厂商前缀与新增属性),不再依赖手写列表 —— 手写列表只在没有 DOM 的调用方(如测试)里兜底。属性值的枚举集合仍由插件维护,但每个候选都先过 CSS.supports(prop, value),引擎不认的直接不显示。
    • ↑/↓ 选择、Enter 或 Tab 采用、Esc 关闭,鼠标按下即采用;补全只在规则块内提示属性名(选择器区域不打扰)。
    • 只在真正键入时出现:靠 InputEvent.inputType 判断,删除、粘贴、移动光标都不会弹出列表(老引擎缺这个字段时回退为"文本变长了才算输入")。
    • 一行里的多条声明只看当前那条:color: red; 之后(分号紧邻,还没开始写新属性)不再弹任何补全 —— 否则同一行的冒号会把光标所在位置误判成"值"。
  • 规则面板:编辑器里的选择器本身就是按钮(.cm-peak-strip 这样的选择器带悬停底色与指针光标),点它在编辑器右侧展开面板:
    • 顶部是可重命名的选择器字段(铺满面板宽度,关闭按钮 × 在字段内部、靠右)—— 改名会原地改写样式表里的选择器,注释与缩进不受影响;整个字段获得焦点时统一显示 brand-primary 聚焦环;
    • 中间是这条规则已有属性的摘要(不是一张空白表单):
      • 解析规则块里真实存在的声明并逐条列出。枚举型属性显示中文名 + 中文值下拉(flex-direction: row → 「主轴方向:水平排列」);非枚举型(gap、margin-top、min-width…)显示属性名 + 值输入框,可以直接改。
      • 下拉里可选「(删除此项)」移除该声明;手工写的、不在选项里的值会被保留为当前选项,不会被静默改掉。
      • 未设置的属性不占行 —— 它们收在属性列表下方的「+ 添加属性…」菜单里(只列当前还没设的;控件铺满面板宽度、文案靠左箭头靠右),菜单按用途分成 布局 / 弹性与对齐 / 网格 / 尺寸 / 间距 / 文本 / 背景 / 描边与圆角 / 效果与动效 / 交互 十组(整组都被设完时该组自动隐藏)。
      • 面板里所有下拉都是插件自绘的菜单(DSH 原生菜单的底色 + 阴影 + 20px 圆角,与文件下拉同一套):原生 <select> 的弹出层不受 CSS 控制(方角、跟随系统配色),所以这里不用它。菜单以 position: fixed 按触发按钮的位置定位,不会被容器或面板自身的滚动裁掉,视口下方不够高时自动向上展开。
      • 控件规格对齐 DSH 自己的设置行:值输入框与下拉触发按钮都是 36px 高、18px 圆角胶囊、14px/22px 字号、bg-module-platform 底 + border-l2 描边、聚焦时用 brand-primary 环;菜单项 36px 高。"+ 添加属性…" 用 hover 底色区分它是动作而不是字段。
      • 属性行自适应多列:最小列宽 200px,面板宽时一行能放两个属性(上外边距 4px、最小宽度 0 并排),窄面板自动回到一列。
      • 属性字典 112 条,三种形态:枚举型(display、position、justify-content…)给中文值下拉,写入的是 CSS 值本身;长度 / 颜色 / 阴影 / 函数型(gap、margin-top、font-size、box-shadow、grid-template-columns…)给中文标签 + 自由输入框,选中即以一个可用的种子值写入(如 gap: 8px),输入框的占位文本就是该属性的取值提示(如 0 / 8px / auto)。字典里没有的属性也不会丢:仍然按原属性名给一个自由输入框。
      • 分量形态(简写属性):flex → 放大 / 收缩 / 基准尺寸三个框;margin / padding / inset / border-radius → 上 / 右 / 下 / 左四个框(占满整行);gap → 行间距 / 列间距;aspect-ratio → 宽 / 高。每个分量是 名字 [值] 的一对、排在同一行,其中分量名渲染为浅底小标签(--dsw-alias-markdown-tag 底 + --dsw-alias-label-secondary 文字 + 6px 圆角),与行标题、字段值在视觉上分开(放大 [1] 收缩 [0] 基准尺寸 [auto]、上 [8px] 右 [12px] 下 [8px] 左 [12px]),框内占位只留取值提示(如 auto / 0 / 240px),改动任意一个框会与其余分量重新拼成一条声明。读取时按 CSS 的简写展开规则拆分(margin: 8px 12px → 8px / 12px / 8px / 12px),写回时收敛成最短等价形式(四个都是 8px 就写回 margin: 8px),所以编辑一个角不会把你的样式表改得啰嗦。border-radius 的斜杠形式(8px / 12px)表达的是两个轴向,一个角一个框表达不了,因此这种写法保留为自由输入框。
      • 规则里一条声明都没有时,面板显示「这条规则还没有声明」。
    • 面板排在代码下方(占满容器宽度),声明与模板都按自适应网格铺开:声明 repeat(auto-fill, minmax(240px, 1fr))、模板 minmax(104px, 1fr);一条规则声明很多时面板自身限高 260px 内滚动,不会把整个设置行撑长。
    • 最下面是声明模板:容器 / 横向排列 / 居中 / 网格 / 文本 / 背景 / 描边 / 阴影 / 尺寸 / 间距 / 截断 / 滚动 / 吸顶 / 隐藏 —— 点一下按 2 空格缩进追加到块内(已有内容保留)。
    • 改属性是替换,不是叠加:同一个属性反复改只会有一条,绝不堆成 display: flex; display: grid;。写入走的是"逐条声明"的扫描而不是正则替换 —— 声明之间是换行分隔、最后一条常常没有分号,任何假设 ; 分隔的做法都会失手并追加重复。
    • 块在写入前会规范化:若某条声明缺了结尾的 ;(老版本模板插入留下的、或手写的),自动补上再做编辑 —— 所以那种被粘连的老文件也能被逐次修正,不需要手工清理。
    • 面板贴着编辑器的下沿伸缩:拖编辑器右下角的 resize 手柄,两个框一起变高,不会一大一小。
    • 选择器按钮浮在高亮层上(该层其余部分仍然点击穿透到文本框),所以点选择器不会把光标打乱。
  • 格式校验:每次渲染都扫一遍 —— 括号与注释必须闭合、每条声明必须可解析、每个值必须被引擎接受(CSS.supports)。有问题时状态行变红并给出行首问题 + 总数(如 第 3 行 color 的值无效:notacolor、第 73 行 syntax 必须是带引号的字符串,例如 '<color>' 等 3 处),最多看 5 处;正常时状态行保持中性色。
    • 变量体检(停顿 600ms 后跑一次,需要读文档所以是异步的):自定义属性是继承的,所以「这个变量有没有值」是个关于元素的问题 —— 编辑器把每条规则的选择器在页面里匹配一遍,再从命中的元素上读该属性,读不到就在同一份问题列表里报出来(变量 --dsl-g-shadow-card 在这条规则命中的元素上读不到)。别的插件在自己卡片上定义的令牌,在卡片外面用就是这么静默失效的。两种情况故意不报:规则没命中任何元素(没得问),以及 var(--x, 备用值)(有备用值就没有可丢的)。
    • 同一次巡检也产出规则大纲里那个「命中 N 个」的数字(见下一节)。
    • 描述符块走自己的规则:@property --x { syntax: '<color>'; inherits: true; initial-value: … } 里的 syntax / inherits / initial-value 是 descriptor 而不是 CSS 属性,CSS.supports() 一律不认 —— 早期版本就是把它们当声明判,于是一个完全合法的 @property 被报成 3 处错误。现在按块类型分流:@property 用描述符规则校(syntax 必须带引号、inherits 只能 true/false),@font-face / @page / @counter-style / @viewport / @font-palette-values / @font-feature-values 这些描述符块整块交给引擎,其余规则照旧逐条过 CSS.supports。
  • 注入的 <style id="dsh-custom-css-user-style"> 追加到 <head> 末尾,同特异性下优先于内置样式表;需要压过内置规则时用 !important。
  • 启动时由 apply() 直接读取并应用活动样式表 —— 不依赖设置面板打开(只在面板挂载的组件里应用的话,普通对话页永远拿不到样式)。

这一行上的控件

表头只留两个常驻入口 + 一个菜单:[拾取元素] [查找] [文件下拉] [更多操作](菜单里依次是 打开文件 / 导入 / 导出 / 格式化 / 历史版本 / 重置 —— 重置放最后,因为它是唯一会毁掉工作的入口);编辑器整块(表头 + 正文 + 状态行)在同一个容器里 —— 表头右侧另有一个开关胶囊。原来的六个控件一行放不下,所以除了留在手边的拾取器,其余折进了菜单(菜单用的就是文件下拉那套 chrome)。「历史版本」是菜单里的第二页(不关菜单,就地翻页),左上角有「返回」。

控件行为
拾取元素在界面里点选一个元素,把它写成一条规则(见下一节)。武装期间按钮变成「取消拾取」,Esc 等效
查找打开表头的查找栏(Ctrl+F 等效):查找 / 替换为 / 命中计数 / 区分大小写 / 上一个 / 下一个 / 替换 / 全部替换 / 关闭。栏打开时按钮变成「关闭查找」。命中在彩色层里圈出,当前那条更重;Aa 按下去有明显样式(品牌色文字 + 按下态底色),状态一眼可辨。窄窗口下计数与按钮文字依次让位,控件不会消失
大纲(文件栏右侧)整张表的顶层规则按顺序列出,每条后面跟着它在当前页面命中几个元素(无命中 就是那条规则什么都没做)。点一下就跳到那条规则并打开它的面板
文件选择器DSH 原生样式的下拉(触发按钮 + 弹出菜单,不是原生 <select>):列出目录里全部 .css,当前项带勾选;切换即写回 active.json 并立刻换用该文件。最下面一项是「+ 新建…」
+ 新建…选中后行内出现文件名输入框(Enter 创建 / Esc 取消),创建空文件并切换过去;重名返回「已存在」
打开文件用系统默认程序打开当前选中的那个文件(浏览器的文件对话框只能选文件、不能"打开",所以由 host 侧 POST /open 调起系统关联程序),适合在真正的编辑器里改
导入用文件对话框从磁盘选一个 .css,其内容复制为目录里的新文件并切换过去;同名自动加 -2、-3 后缀,绝不静默覆盖
导出把当前样式表另存为文件:优先用 File System Access 的「另存为」选择器指定位置,不支持时回退为浏览器下载
重置清空当前文件内容(移除注入的样式;文件本身保留)。语义是破坏性的,用错误色与其他控件区分(--dsw-alias-state-error-primary)
格式化把整张表重排:一条规则一行、每层缩进两空格、去掉空行。只动行的排布,不改一行里写的东西(缺分号、缺空格留给校验器报),括号不配对的表原样返回;已经排好的表上这一项置灰
历史版本「更多操作」菜单里的最后一页:把当前这张表留在磁盘上的版本按时间倒序列出来(本地时间 + 字节数),点一条就把那一版恢复进编辑器并落盘。恢复前会先把被替换的内容也留一份快照,所以恢复本身也可以反悔;恢复会清空编辑器的撤销栈 —— Ctrl+Z 不该把你刚决定离开的那一版又变回来。一次都没被覆盖过的表显示「还没有历史版本」
开关胶囊(容器表头)编辑器容器表头的右侧:临时停用当前样式表 —— 样式不再注入页面,而文件内容、当前选择、校验状态全都不动。状态写进 active.json 的 disabled 列表,因此多个浏览器与重启后一致。左侧是「CSS 图标 + 文件名 + CSS 徽章」,与状态点一起构成这一行的身份区

「导入」是复制而不是链接原文件:浏览器出于安全不会把所选文件的完整路径交给页面。原文件不受影响;要反复编辑同一份文件,请用「打开文件」。

元素拾取器

不想对着 DevTools 抄类名时用它:点选界面里的任意元素,得到一条尽量活得久的选择器,外加它实际解析到的 DSH 令牌。

选择器是「挑」出来的,不是「算」出来的

设计系统用 CSS Modules:_card_1fywu_26 这类类名里的哈希随构建变化,写进样式表下次升级就失效。所以候选按「什么能活下来」排序:

优先级类型例为什么
1[data-*] 钩子[data-dockkit-strip]DSH 自己也在用它查询,跨版本最稳
2[aria-label]div[aria-label="QQ 音乐"]语义层,不会随便改
3手写语义类名div.dsh-music-qq-head插件作者自己维护
4哈希容错div[class*="_card_"]不接哈希,但依赖命名习惯
5结构路径div:nth-child(2) > div兜底,界面改版会失效

命中多个元素不是隐藏而是标注(「N 个命中」):一次性给一族元素写样式是正当需求,而结构路径注定会在升级时断。唯一性只决定默认选项,证据层级用来打破平局。探针查不出来时写「命中数未知」,并且按非唯一处理 —— 「没查出来」不等于「只有一个」。

交互

  • 悬停只预览,点击才锁定:高亮框一直跟着指针(上面标着 宽×高),点击把它固定成实心框,面板同时换成这个元素的候选与令牌。Enter 或面板上的「插入规则」才提交,Esc 或「取消拾取」退出。
  • 不接管页面:不遮罩、不拦点击、不改属性 —— 拾取期间设置页照常能用(可以关掉它去别的页面拾取,会话挂在模块级而不是这一行上)。
  • 层级:↑/W 上一级、↓/S 下一级、←/A 上一个同级、→/D 下一个同级,也有滑杆(0 一定是「点到的那个元素」)。移动鼠标只改悬停预览,不会悄悄换掉已锁定的元素 —— 要换选就再点一下。
  • 面板是浮动的(挂在 body 上),带候选选择器、令牌 chips(可关掉不想写的)、层级滑杆与快捷键提示。

「插入规则」做什么

表里已有同名规则就打开它(绝不重复追加),否则在表尾追加一条空规则、把勾选的令牌写成声明,并把光标停在新块内、编辑器滚到新规则。复用已有规则时只开面板、不动光标 —— 那条规则不是这次手势创建的,把光标丢进去是惊吓而不是帮忙。

令牌反查

令牌表从 CSSOM 现读(文档里各样式表的 --* 定义),所以新增令牌、主题改值自动跟上:插入的是 var(--dsw-alias-bg-layer-1),不是它解析出的 #101010。两条约束是刻意的:

  • 作用域:只有定义规则能匹配该元素或其祖先的令牌才算数 —— 别的组件卡片内部的定义,对它外头的元素不成立。
  • 种类:只有名字读起来像该属性的令牌才会被建议(bg / label·text·fg / border / corner·radius)。宁可一个都不给,也不给一个解析不出来的(否则一个 22px 的圆角会被建议成 line-height 的令牌)。

host 侧文件接口

host 侧在 /dsh-custom-css 前缀上挂了一组 JSON 端点,并且必须通过 Connection 服务的请求围栏(未认证 / 非信任来源直接 401/403;拿不到围栏时 503 fail-closed,绝不裸奔):

方法路径作用
GET/list{ files, active, disabled, dir }
GET/read?name=<name>{ name, css }
GET/history?name=<name>{ name, entries: [{ stamp, bytes, mtime }] },按时间倒序(快照目录 .history/<name>/,.history 这一层不会被 /list 当成表列出来)
POST/write写入指定文件(写之前把被替换的内容留一份快照)
POST/create新建空文件(已存在 → 409)
POST/import导入内容为文件(按设计覆盖同名)
POST/active记录当前文件(文件不存在 → 404 not-found,与 /toggle 一致)
POST/toggle{ name, enabled } → 打开 / 关闭某张样式表(文件保留),状态记进 active.json 的 disabled
POST/open用系统默认程序打开指定文件(仅限目录内已存在的 .css;不存在 → 404)
POST/restore{ name, stamp } → 把某一版快照写回该文件(被替换的内容先留一份快照,所以恢复可逆;stamp 不是时间戳 → 400,快照不存在 → 404)

文件名限定为单个路径段且以 .css 结尾(^[A-Za-z0-9._一-龥-]+\.css$,≤64 字符);..、分隔符、无扩展名、Windows 设备名(nul.css、con.css…)一律 400。目录外的路径在拼接后还会二次校验,符号链接也拒绝:目录内的软链会让写入落到目录之外,而字面量校验看不出来。

active.json 用临时文件 + rename 写入,改动串行执行:直接写入会先截断,中途断电留下的半截文件会被读成"没有当前文件、没有关闭项",下一次改动就把这个空状态固化下来;两个标签页同时开关两张表也会各写一份、后写覆盖先写。目录簿记里指向已删除文件的关闭项在每次写入时清理,因此删掉再重建同名文件不会一出生就是关闭状态。

/list 最多返回 200 个文件,但当前文件一定在列表里(哪怕它排在 200 名之外):否则行会退回到列表第一个文件,每次打开页面都悄悄换掉用户选的样式表。

设计一致性

行内样式全部来自 DSH 自身的设计变量与行规格,因此会跟随当前主题(浅色/深色)自动变色,无需为配色方案写分支:

元素规格来源
行容器0.5px solid --dsw-alias-border-l2 分隔线,上下 16px 内边距,gap: 8pxGeneral 章节各行的统一规格
标题14px/22px,--dsw-alias-label-primary同 AppearanceRow / FontSizeRow
说明 / 状态12px/18px,--dsw-alias-label-tertiary;错误用 --dsw-alias-state-error-primary同上
按钮 / 下拉 / 输入圆角 8px、13px、描边 --dsw-alias-border-l2、聚焦环 --dsw-alias-brand-primary插件卡片控件规格
按钮语义色中性按钮文字 --dsw-alias-label-secondary,hover 底色 --dsw-alias-interactive-bg-hover;重置用 --dsw-alias-state-error-primary(浅色 #ec1313 / 深色 #f25a5a)+ hover 底色 --dsw-alias-interactive-bg-hover-danger破坏性操作与普通操作用色区分
下拉与菜单触发按钮:--dsw-alias-bg-module-platform、36px 高、圆角 18px、内边距 0 14px、14px/22px;菜单:--dsw-specific-menu 底 + --dsw-elevation-prominent 阴影、圆角 20px、4px 内边距;菜单项:圆角 10px、min-height 40px、hover --dsw-alias-interactive-bg-hover逐值照抄 DSH 设置行 selector(oY77xG_selector / T1PP_q_selector / lats3W_selector 三者字节级相同)与共享下拉 _root_1nxmc_1
编辑器行号 gutter 与文本区共用一个等宽行高(--dshCc-line: 19px);获得焦点时边框让位给 --dsw-alias-brand-primary 聚焦环;补全列表用共享菜单的紧凑规格(圆角 7px、项 min-height 26px、12px/18px)DevTools 风格的代码编辑外观 + 共享菜单层级
编辑器容器圆角 12px + --dsw-alias-border-l2 描边、--dsw-alias-bg-layer-1 底:表头(文件名 + CSS 徽章 + 开关)/正文(代码在上、属性面板在下,均占满宽度)/状态行 三段同框;表头与状态行用 --dsw-alias-bg-module-platform 与 0.5px 分隔线区分设置面板里成块控件的统一做法

安装

方式一:从 npm 安装(推荐)

前置:DSH 0.1.2-rc.1 ~ 0.1.5-rc.1(已真机验证;接口契约断言覆盖到 0.0.1-rc.5,见「兼容性」),Node.js ≥ 20、pnpm ≥ 10。

# 1) 把插件装进 profile(dsh plugin 会把参数转发给 profile 目录里的 pnpm)
dsh plugin --profile web add dsh-custom-css

# 2) 让它真的被加载:把包名加进 profiles/web/package.json 的 dsh.profile.bundles
#    "dsh": { "profile": { "bundles": [ ..., "dsh-custom-css" ] } }

# 3) 重启 GUI(重开会话不够,进程内模块缓存是旧的)

方式二:从 GitHub 安装(跟 main 分支)

dsh plugin --profile web add github:FOX4096/dsh-custom-css

方式一里第 2、3 步照旧(bundles 里写同一个包名、重启 GUI)。

第 1 步和第 2 步缺一不可:只装依赖不列进 bundles,包根本不会被 loader 载入;只写 bundles 没装依赖,启动时直接失败。反过来,列进 bundles 的包必须自带 dsh.bundle.patch —— dsh-app-boot 会为每个包读 package.json 的 dsh.bundle.patch,缺失即启动失败:

profile bundle "…" declares no dsh.bundle in its package.json

本仓库的 cordis.patch.yml 用 - insert: 声明自己的 loader 条目;改写它(或 dsh.bundle 字段)会直接让 DSH 起不来。改完用 dsh web --dump-default-config 复查:该命令只解析不启动服务器,输出里应出现 # == dsh-custom-css 段。

方式三:本机开发安装(link:,改完即见)

  1. package.json 的 dependencies: "dsh-custom-css": "link:<本仓库绝对路径>"
  2. package.json 的 dsh.profile.bundles 加入 "dsh-custom-css"
  3. profiles/web/node_modules/dsh-custom-css 建 junction 指向本目录
  4. 重启 GUI

pnpm install 会重建 node_modules,可能抹掉该 junction;重建一次即可。

兼容性

插件与 DSH 的耦合面是刻意做窄的,所以它跨版本比多数插件活得久:浏览器半在运行时一个 @deepseek-ai/* 模块都不 require(只通过 dsh.client.inject 声明可用 id、并从 Context 取 slots 服务),host 半只挂 HTTP 路由。真正依赖的平台契约只有四条,全部记在 compat.json,由 npm run compat 逐个版本断言:

契约点内容
槽位settings.general.item(@deepseek-ai/dsh-client-ui-settings-general 里 GeneralSection 渲染的那个 seat)
声明的模块 id@deepseek-ai/dsh-client-ui-settings、@deepseek-ai/dsh-client-ui-settings-general
需要的服务仅 slots
运行时 require零

验证矩阵

DSH 版本验证方式结论
0.1.5-rc.1真机运行(本仓库开发环境)通过
0.1.2-rc.1真机运行:独立 DSH_HOME 起实例,/dsh-custom-css/list、/write、/read 全部 200 且文件确实落盘通过
0.1.1-rc.2 / 0.1.0-rc.7 / 0.0.1-rc.5接口契约断言(npm run compat 解包该版本的 settings 包,核对槽位与 id)通过

npm run compat 需要网络(要拉各版本的包),所以本地 npm test 保持离线可跑;CI 里单跑一档。

两种失效模式的判别:槽位若被 DSH 改名,行内控件会静默不出现(没有报错)—— 这正是 npm run compat 要提前挡住的事;而 bundle 里若硬 require 了平台包(如 @deepseek-ai/dsh-client-runtime/client),加载时会抛 client-modules: require("…") missed the module table(见下文「已知坑」)。

已知坑(都是实测踩出来的)

坑现象结论
注入顺序不定自定义 CSS 与 DSH 内置样式表同为 (0,1,0) 特异性时,谁后插入谁赢,而两者的插入时机都不受你控制覆盖 DSH 自己的类名时一律加 !important,让结果与顺序无关
类名是构建哈希hHd-Xa_footArea、VOzbGW_triggerRow 这类名字会随 DSH 版本变用它做选择器的样式在 DSH 升级后要复测;从 DOM 里抓类名的正则必须包含 -,否则会截掉 hHd- 前缀、选择器永不命中
:has() 不能嵌套写成 :has(div:has(> .foo)) 时 Chrome 抛 is not a valid selector,整条规则被静默丢弃(不报错、不生效):has() 只写一层,把「直接父级」的约束放进同一条相对选择器里(> div:not(...) > .foo)
描述符不是属性@property 块被校验器报成「syntax 的值无效」等 3 处校验按块类型分流(见上文「格式校验」);CSS.supports 判不了 descriptor
host 半不热更新改 lib/index.js 后刷新页面毫无变化host 半必须重启 dsh;只有浏览器半(lib/client.js)走 dsh-client-hmr 热更新
于是新功能可以先安静地躺在磁盘上「外部改动已被拦下」的提示、以及后来的自动同步,都写在 host 半:一个从昨天就开着、没重启过的 dsh 会继续跑旧代码 —— 用户会以为「这功能从来没生效过」(本次实测:dsh 启动于 22:29,host 改动写于次日的 00:24)改完 host 半立刻重启再谈验证;报告缺陷前先对一下 dsh 进程的启动时间和 lib/index.js 的修改时间
一行放不下就换行,而换行不报错查找栏的两个输入框加六个控件是固定 610px,而行宽只有 600px(侧栏打开时 440px):字段被压到最小值之后,最后两个按钮掉到第二行,栏变两倍高,页面看起来只是有点挤用实测定位(%TEMP%\css-probe\findbar-fit.mjs,视口宽 = 行宽),加两级让步:640px 以下隐去命中计数、540px 以下 替换 / 全部替换 换成 ↦ / ⇉。阈值取在仍有富余处。任何装进固定宽度行的 flex 行都该这样量一遍
自定义属性动画--my-color 在关键帧之间硬跳,不插值这是规范行为:先用 @property --my-color { syntax: '<color>'; inherits: true; initial-value: … } 声明类型,浏览器才肯对它做插值
跨代插件插件加载失败:client-modules: require("…") missed the module table那是给旧一代 DSH 编译的 bundle:它把平台包硬写进了产物(如 @deepseek-ai/dsh-client-runtime/client、dsh-client-ui-primitives),而当前代已删掉这些包。只能等插件作者更新;本插件运行时 require 数为 0,不受这类漂移影响

仓库结构

dsh-custom-css/
├── package.json        # 插件清单:dsh.bundle / dsh.client 两个约定块
├── cordis.patch.yml    # loader 补丁(- insert: 自己的条目)
├── lib/
│   ├── index.js        # host 半:样式表目录 + 围栏内的 JSON 接口
│   └── client.js       # 浏览器半:设置行、编辑器、补全、校验、规则面板
├── tests/
│   ├── loader-smoke.cjs    # 假 host 里真跑 client.js(含校验、补全、规则面板、开关、分量)
│   ├── host-api-smoke.mjs  # 假 webServer 驱动 host 路由(含穿越拒绝、fail-closed、/toggle)
│   └── compat-matrix.mjs   # 跨版本兼容断言(槽位 + inject id),npm run compat
├── .github/
│   ├── workflows/ci.yml        # CI:Node 20/22 × 语法自检 + 冒烟测试 + 兼容断言
│   ├── workflows/publish.yml   # 推 v* tag → OIDC 受信发布(带 provenance)+ 自动建 Release
│   ├── ISSUE_TEMPLATE/         # Bug / 功能建议模板
│   └── pull_request_template.md
├── examples/
│   ├── showcase.css        # 可直接导入的示例样式表(只用设计变量,不选哈希类名)
│   └── README.md           # 用它 + 写自定义样式的三条经验
├── docs/
│   ├── architecture.html   # 架构图(自包含 HTML:明暗主题 / 平移缩放 / 焦点 / 导出)
│   ├── architecture.archify.json  # 图的源码规格(Archify)
│   └── architecture.visual-check.*  # 桌面containment 证据:截图 + JSON 收据
├── assets/
│   ├── icon.svg            # 图标(矢量,README 顶部用的就是它)
│   ├── icon-512.png        # 同一图标 512px,带透明通道,可用于仓库头像
│   ├── social-preview.svg  # 社交预览卡(1280×640)源文件
│   └── social-preview.png  # 同上,位图版
├── compat.json             # 跨版本兼容契约(槽位 / inject id / 已验证版本)
├── .editorconfig
├── CONTRIBUTING.md         # 本地跑起来、硬约束、发版流程
├── SECURITY.md             # 攻击面与私密报告渠道
├── LICENSE
├── CHANGELOG.md
└── README.md

已知限制

  • 只能追加 CSS,不能修改内置样式表本身。
  • 跨浏览器会话跟随的是文件而非 origin,因此端口变化(如 DSH Desktop 每次分配端口)不影响已保存的样式表。
  • host 文件接口不可达时(例如跑在非 Web composition 里)自动降级:编辑器仍在、样式仍生效,但内容退回浏览器 localStorage,且此行隐藏下拉与「打开文件」并给出提示。
  • 规则面板只认顶层样式规则:写在 @media / @supports 里的规则不在可点范围内(它们照常生效,只是面板编辑不到),@font-face、@property 这类块也按各自规矩单独校验而不进面板。
  • 拾取器只能拾取当前文档里的元素:侧边栏浏览器面板那种跨域 iframe 里拾取不到。
  • 拾取器给出的候选是「尽量活得久」,不是「绝对唯一」——命中多个元素的候选仍会被列出(标注为「N 个命中」),只是不会默认选中它。
  • 令牌建议可能一个都不给:只有作用域内、且名字读起来像该属性的令牌才会被建议(见「元素拾取器 · 令牌反查」)。这是有意的取舍,不是没读到。
  • 未注册多语言字典,界面文案为中文。

架构图

四张自包含的可交互架构图(双击即可打开,无依赖)。先看总图,再按下钻到三条链:

图讲什么文件
总图浏览器半 + 宿主半:设置行、注入的样式、宿主 API、文件监听、样式表目录、文本编辑器 —— 一张图看完整条主干docs/architecture.html
写盘链设置行 → store → 宿主 API → 磁盘:写前比对修订号、写前留快照、写完之后的目录信号docs/architecture-write-chain.html
读回链磁盘 → 界面:外部保存 → 目录监听 → 轮询修订号 → 干净则采用、有改动则问你docs/architecture-read-chain.html
编辑器栈一个 textarea 与围着它的四个视图:彩色层 / 校验 / 格式化 / 补全,以及状态怎么流回 storedocs/architecture-editor-stack.html
  • 图上的节点名就是代码里的东西:CustomCssRow、applySheet、handle()、watchSheets、stylesDir()、validateCss、searchHits、restoreVersion… —— 照着图能直接翻到 lib/client.js / lib/index.js 对应位置。
  • 每张图都自带明暗主题、平移缩放、搜索、聚焦、按关系追踪,以及几个 guided view(例如总图的「写盘路径 / 应用路径 / 外部同步」)。
  • 规格是同名的 .archify.json(由 Archify 生成)。改动图形请改规格再重新 validate --quality showcase 与 deliver,不要手改 HTML —— 交付会把规格冻结成一个私有快照,工件与规格的 sha256 一起写进回执。
  • 四张图的 validate 都是 9/9 通过、0 error 0 warning,桌面 containment(1440×900 / 1600×1000 / 1920×1080 / 2048×1320)全部 pass;证据在各自的 .visual-check.json 与同名 PNG 里。

开发

  • lib/client.js 是交给 DSH 浏览器模块加载器的产物(window.__ModuleLoader__.load({ id, factory }) 工厂),不是普通 ES 模块 —— 与 DSH 自身 @deepseek-ai/dsh-client-ui-* 的产物同构。
  • lib/index.js 是 host 侧:拥有样式表目录、挂载围栏内的 JSON 接口。路由挂载方式与 dsh-smooth-stream 一致(本代内核的 connection.rpc.handle() 会因服务内部 Context 未注入 webServer 而失败,直挂 Web 服务器是受支持的兜底)。
  • node --check lib/client.js:语法自检(浏览器半是 .js 但按模块工厂包了一层,不要当 ESM 引入)。
  • node tests/loader-smoke.cjs:用假 host 真实执行 lib/client.js,校验模块形状、槽注册参数、host 支撑的启动应用、离线降级、首次运行从 localStorage 播种默认文件,以及组件真能渲染。其中包含校验器的两类回归:合法的 @property 块必须零报错,非法描述符(syntax: <color>、inherits: maybe)必须报出来。补全、规则面板、属性下拉、字符串/url()/嵌套块的不透明性、同名规则的面板绑定、补全与光标的一致性、保存指示只在写入成功后说"已保存",也各有断言。每条修复都用「先把补丁反向打回去、确认测试会红」验证过,测试确实在守行为而不是守实现。
    • 拾取器端到端也在这里跑:点按钮 → 武装 → 悬停报告尺寸 → 点击锁定 → 候选排序 → 插入规则(含复用已有规则、光标落点与滚动到新规则)。测试床自带一个真的做选择器匹配与按矩形命中的 DOM 桩 —— 以前这两处忽略入参、等于常量,断言其实只守了"桩被配置成了什么"。
    • 另外两条样式审计守着那些"看不出来"的错:规则若用 attr() 取一个元素没有的属性就点名(曾有锁定框因此画出一条 12×2 的横线);样式表里出现、源码里没人命中的类也点名(它同时也是类名拼错的探测器 —— 审计要认得出 'dshCc_tok' + kindOf() 这类拼接出来的名字)。
  • node tests/host-api-smoke.mjs:用假 webServer 驱动 host 路由,校验文件 API、目录簿记(含 active.json 损坏后的恢复与幽灵关闭项清理)、/active、重名冲突、路径穿越 / 非法文件名 / Windows 设备名 / 符号链接拒绝、请求体上限、/open 的启动器注入、列表上限不会吞掉当前文件、以及无围栏时的 fail-closed(读写都被围栏拦住)。
  • CI(.github/workflows/ci.yml):Node 20 / 22 两档,跑上面四条 node --check 加两套冒烟测试。插件没有构建步骤也没有运行时依赖,所以 CI 里不装任何东西,几秒结束 —— 顶部那枚 CI 徽章就是它。

许可

MIT,见 LICENSE。改动记录见 CHANGELOG.md。

Versions

Latest versionPublishedSize
0.1.0——
0.1.1——
0.1.2——
0.1.3——
0.1.4——
0.1.5——
0.1.6——
0.1.7——
0.1.8——

Comments

Loading…

From the same category

deepseek-harness

by deepseek-ai

DeepSeek Harness: Everything is a Plugin.

Development & InfrastructureWorkflow & Automation

★ 236.5k

MIT

TypeScript

Sep 24, 2026

Index only — not installable

by nexu-io

🎨 Best DeepSeek Harness Design Plugin. The open-source Claude Design alternative. 🖥️ Local-first desktop app. 🖼️ Your coding agent becomes the design engine: prototypes, landing pages, dashboards,

Development & InfrastructureTerminal & ClientsUI & ExperienceManifest valid

★ 98.1k

Apache-2.0

TypeScript

Sep 26, 2026

Index only — not installable

by tt-a1i

Agent skill for beautiful, verifiable architecture, workflow, sequence, data-flow, and lifecycle diagrams—self-contained HTML with motion and crisp export.

Workflow & AutomationTools & CapabilitiesDevelopment & InfrastructureManifest valid

★ 72k

↓ 3.5k/wk

MIT

JavaScript

Sep 26, 2026

dsh plugin --profile agent add @tt-a1i/archify-dsh

by Tencent

Open-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.

Development & InfrastructureTools & CapabilitiesManifest valid

★ 30.2k

↓ 838/wk

NOASSERTION

Go

Sep 26, 2026

dsh plugin --profile web add @wxg-prc-cpg/dsh-weknora

by awesome-dsh-plugin

A curated list of plugins for DeepSeek Harness (dsh) · DeepSeek Harness 插件精选列表

Development & Infrastructure

★ 17k

CC0-1.0

Python

Sep 26, 2026

Index only — not installable

by zhu1090093659

DeepSeek Harness (DSH) Web Plugin Aggregation Ecosystem · Everything is a plugin, distributed via the Creative Workshop

Tools & CapabilitiesTerminal & ClientsDevelopment & InfrastructureModels & ProvidersUI & ExperienceManifest valid

★ 8k

Apache-2.0

TypeScript

Sep 26, 2026

dsh plugin --profile web add dsh-web