界面精简(dsh-ui-fit)
一个插件,管五件事:左栏导航折叠、输入条精简、手机端密度压缩、对话宽度挡位、 手机端隐藏 WorkBuddy 积分读数(桌面照常显示)。 五块可以分别开关,关掉哪一块,那一块就完全回到宿主原样——不是把按钮藏起来,是那一段 样式真的失效。
前三块合并前分散在三个独立插件里,各管一处界面,却各自往 <html> 上挂自己的开关属性、
还互相借 CSS 变量,卸载其中一个另一个就会退化。第四块原先依赖第三方插件
dsh-width-tiers(作者 aaronlei),本插件只做了一颗转发按钮;1.2.0 起宽度轴由本插件
自己驱动,不再需要装那个插件。现在只有一份样式表、一个根属性、一套变量。
安装
要求 DSH >= 0.2.1-rc.5、Node ^22.19.0 || >=24.0.0。
方式一:Web 界面(最简单)
左栏「插件」→ 右上角安装框 → 粘贴 github:HotAntOne/dsh-ui-fit → 安装 → 打开开关。
方式二:命令行
dsh plugin 是把参数转发给 profile 目录里的 pnpm,所以用 add:
dsh plugin --profile web add github:HotAntOne/dsh-ui-fit
装到别的 profile 就换 --profile 的名字(desktop / tui / …)。装完刷新页面(Ctrl+R),
不需要重启 DSH。卸载:
dsh plugin --profile web remove dsh-ui-fit
方式三:本地源码
git clone https://github.com/HotAntOne/dsh-ui-fit
dsh plugin --profile web add /绝对路径/dsh-ui-fit
本仓库同时是一个可发布的 npm 包,
npm publish之后也能dsh plugin --profile web add dsh-ui-fit。 发布前请先把package.json里的author改成你自己的名字。
装完发现插件"消失"了?先跑一次自检
本插件用 link: 方式装(方式三)时,Node 会先把入口解析成真实路径(源码目录),再从源码
目录向上找 node_modules。如果源码目录那条链上没有 node_modules,index.js 里
import '@deepseek-ai/schemastery' 就会失败,Loader 把这一行标成 inactive——表现是
插件整个不见(页脚按钮、设置页都没了),但 package.json、bundles、cordis.patch.yml
全都是好的,从界面上完全看不出是缺依赖。
cd /绝对路径/dsh-ui-fit && npm run setup
它做三件事:用 Node 的规则解析一次;解析不到就找本机已装好的 profile 链接过去(开发机 自愈);都不行退出码 1 并打印该跑的命令。不会静默通过。
用 dsh plugin add(方式一、二)安装时 pnpm 会按 dependencies 正常装依赖,不需要这一步。
装完之后在哪
设置(左下角账号菜单 → 设置)→ 左列「界面精简」,五个开关:
| 开关 | 默认 | 管什么 |
|---|---|---|
| 左栏导航 | 开 | 把「插件、插件市场、上下文洞察、任务看板、SSH」这些全局行收进「更多」,或压矮行高 |
| 输入条 | 开 | 藏掉语音按钮,模式 / 计划 / PUA 三个控件改纯图标;输入条变窄时自动收紧 |
| 手机端密度 | 开 | 只在窗口宽度 ≤ 899 像素时生效,压缩手机上的各类留白 |
| 对话宽度 | 开 | 左栏底部加一颗宽度按钮,六档内容宽度;拖动对话区两侧手柄也会记成自定义 |
| 手机端隐藏积分 | 开 | 只在手机端藏掉输入框旁 WorkBuddy 常驻的「WB CN · 积分」读数,桌面照常显示;明细面板仍可点开(见第五节) |
默认全开,与合并前完全一致。
为什么设置页要自己写:宿主只按
Configschema 生成表单定义(Config.listConfigs里status: 'schema'),不会把它变成设置面板里的一节——@deepseek-ai/dsh-settings的 README 明写「no shipped client does so yet」。所以本插件自己注册了settings.section(iddsh-ui-fit、order 18),页面里的开关直接写本插件的配置命名空间,写成功后宿主广播, 样式与页面同时刷新。这一节不受任何开关管辖,否则关掉之后就再也开不回来。
一、左栏导航
左栏有六行全局面板(插件、插件市场、上下文洞察、任务看板、SSH、更多),工作区列表被挤到 很下面。这个功能把竖直空间还回来,有三种状态,点左栏最底部那颗三横线图标循环切换:
| 状态 | 外观 | 颜色 | 说明 |
|---|---|---|---|
| 标准 | 三行等距 | 三级文字色 | 宿主原样 |
| 紧凑 | 三行更近 | 二级文字色 | 每行矮 6px、去掉多余行距,约还回一行高度 |
| 收纳 | 一行 + 尖角 | 品牌色 | 除「更多」外的面板行全部隐藏,都进「更多」菜单 |
收纳态下「更多」菜单里会列出插件管理 + 每一个被收起的面板,所以没有面板会变得不可达。
三态存在哪:localStorage 的 dsh-ui-fit:nav,每浏览器各存一份。理由是这个选择
是「这一屏我临时想怎么摆」,属于设备级偏好,不该跟着账号同步到别的设备。
页脚那颗柱状图图标是「对话宽度挡位」的入口,见第四节。
二、输入条
四条纯样式规则:
藏掉语音按钮。
dsh-better-input那个麦克风按钮在这个环境里一直失败,直接不显示。 它的尺寸写在行内样式里,所以这条必须!important。徽章改纯图标,并统一到宿主的尺寸节奏。PUA、权限模式、计划三个控件原来是「图标 + 文字」, 改成只有图标;同时按钮一律 28px、图标一律 14px——宿主自己的「+」和右侧两颗就是 28px 按钮 + 14px 图标,原来 PUA / 权限模式是 36px(PUA 图标 18px),并排看明显大一截。 两个数抽成
--dsh-fit-btn/--dsh-fit-icon,挂在[data-composer-card]上,改一处即齐。:has()守卫保证:宿主若没给某个权限值渲染图标,那个控件就保留文字,不会变成空圆圈。窄行收紧。输入条自身实测宽度 < 560px 时:间距从 8px 收到 4px、内边距减半、 输入卡铺满宽度、并解掉
dsh-mobile在 720px 以下强加的 44px 触控下限。 只收间距,不收按钮尺寸——桌面和窄屏都用同一套 28px / 14px,否则同一排里会出现 34px 与 28px 混排。为什么用实测行宽而不是媒体查询:媒体查询问的是「窗口多宽」,折行只关心「这一行多宽」。 手机壳里有 56px 轨道、外层内边距、抽屉,这两个数并不相等。地址后加
#fit-debug可以 看到真实测量值。
三、手机端密度
只在窗口宽度 ≤ 899px 生效,桌面窗口下这一段完全不打标记。九组规则:
| # | 位置 | 做了什么 |
|---|---|---|
| ① | 每轮费用行 | 压成一行可省略的 11px 小字 |
| ② | 底部状态条 | 只留「上下文占用」与「缓存命中」,去掉计时、费用明细入口、本会话合计 |
| ③ | 顶部工具条 | 解掉宿主 76px 的固定下限,按钮收到 32px |
| ④ | 消息操作行 | 按钮收到 22px,图标 13px;第三颗之后(格式、分享)收起 |
| ⑤ | 助手状态行 | 「已完成,用时 X」与「深度求索中」压到 24px |
| ⑥ | 对话标签栏 | 去掉 10px 上边距,标签改横向滚动,不再逐字换行 |
| ⑦ | 顶栏溢出 | 标题行横向滚动、收掉宿主的推挤边距,三块徽标不再互相压住 |
| ⑧ | 上下文百分比 | 单独下移 1px(宿主那颗是「14px 圆环 + 13px 文字」,与 12px 的胶囊基线不同源) |
| ⑨ | 「专家」按钮 | 从 74px 的「图标 + 专家 + 箭头」压成 28px 纯图标,文字与箭头隐藏,图标 14px |
调那 1px:改 --dsh-fit-context-shift,默认 1px。
四、对话宽度挡位
左栏页脚最右边那颗柱状图图标。点开是六档:
| 挡位 | 目标宽度 | 说明 |
|---|---|---|
| 标准 | clamp(680, 列宽 × 0.64, 920) |
宿主原样,交还给宿主自己算 |
| 中 | 1120px | 名义值,超过 列宽 − 176 时按上限夹 |
| 宽 | 1280px | 同上 |
| 超宽 | 1400px | 同上 |
| 满宽 | max(640, 列宽 − 176) |
两侧留出拖拽手柄的位置 |
| 自定义 | 上次拖出来的宽度 | 直接拖对话区两侧手柄即可记录 |
菜单里显示的是实际落地宽度;够不着名义值时写成 1280px → 696px,不假装能到。
它怎么生效(这部分是本插件自己实现的,不再依赖 dsh-width-tiers):
宿主把内容宽度发布在 [data-conversation-content] 上:
--dsh-chat-content-width: var(--dsh-chat-user-width, clamp(680px, calc(列宽 * .64), 920px))
而 --dsh-chat-user-width 的落点是它的父元素(宿主的 ConversationWidthControls 里写的是
container.parentElement ?? container)。本插件做的就两件事:
- 往那个父元素写
--dsh-chat-user-width: <px>,立即生效; - 往
localStorage的dsh.conversation.contentWidth写同一个值,这样窗口 resize 时宿主的ResizeObserver用resolveContentWidth(列宽, 偏好)重算,不会跳回默认。
所以这是「接管」而不是「覆盖」——宿主自己的拖拽手柄照常能用,拖完还会自动记成自定义档。
拖拽读数:拖动 [data-width-handle] 期间跟一个浮标显示当前内容宽度,松手 800ms 后消失。
读数优先取宿主发布的 --dsh-chat-content-width;拿到的若是 clamp(...) 这种没被求值的 token 串,
就按宿主默认公式 max(680, min(列宽 × 0.64, 920)) 回算,保证第一下拖动就有数字。
松手时如果落点和当前挡位的目标宽度差超过 2px,就切到「自定义」并记住这个宽度。标准挡位没有 目标值,拿宿主默认宽度当基准——不然原地碰一下手柄就会被误记成自定义。
存哪:挡位在 localStorage 的 dsh-ui-fit:width,自定义像素在 dsh-ui-fit:widthCustom,
都是设备级。宿主的偏好键只在本插件写过时才回收(appPrefOwned 标记),绝不误删你自己
拖出来的宽度。
和第三方插件共存:如果还装着 dsh-width-tiers,它的浮动按钮会被压成 left:-9999px
不可见不可点,避免两个按钮打同一个宽度轴。卸载它之后这几条规则自然失效。
五、仅在手机端隐藏 WorkBuddy 积分读数
dsh-connect-workbuddy(WorkBuddy)会在输入框右侧常驻一颗「WB CN · 8,992」积分余额。
按口径只在手机端把它藏掉,桌面(> 899px)照常显示:
- 开关
credits(默认开 = 手机端隐藏)写入门控属性data-dsh-fit-credits。 - 一条规则被
@media (max-width: 899px)框住:html[data-dsh-fit-credits="on"] .dsm-workbuddy-composer-credits{display:none !important}同时覆盖两处座位:conversation.input.right(默认)与conversation.composer.dock(设置里把读数挪到「输入框下面」时)共用同一个类。桌面不命中媒体查询,读数照常显示。 用媒体查询而不是挂data-dsh-fit-mobile,是为了让 credits 开关独立——关掉「手机端密度」 不会让手机上的读数冒出来;899px 与 JS 里MOBILE_QUERY阈值一致,改要两处一起改。 - 只藏常驻读数,不藏查看明细的能力:点击弹出的
.dsm-workbuddy-composer-panel是 portal 到document.body的,不在这条规则里,明细面板始终能打开。 - 没装 WorkBuddy 时选择器自然不匹配,静默失效、无副作用。
桌面窗口读数一直都在;只有把窗口缩到 899px 以下(或手机上)且开关打开时才隐藏。关掉开关, 手机端读数立刻回来。
六、验证
node --check client.js # 语法
node test-client.mjs # 离线冒烟:五段规则、三态循环、宽度六档、开关失效、清理
node check-hooks.mjs # 宿主升级后体检:CSS 钩子还在不在
check-hooks.mjs 会扫 asar 里和 profile 里所有 .js,逐个确认本插件依赖的宿主钩子
(data-composer-dock、data-conversation-tabs、_titleRow 之类)是否还存在。宿主升级
后跑一次,退出码 1 就说明有钩子改名了,对应规则会静默失效。
三类结果分别看:
| 前缀 | 含义 | 要不要管 |
|---|---|---|
✓ |
钩子在 | 不用 |
– 跳过(未装某插件) |
那台机器没装该第三方插件,规则本就不生效 | 不用 |
– 跳过(旧兜底判据) |
有契约锚点接管了,旧判据可以没有 | 不用 |
✗ |
宿主真的改版了 | 要修选择器 |
--dir <路径> 可指定别的 profile 的 node_modules(默认当前 desktop),
--asar <路径> 可指定 asar 位置(默认按常见安装路径探测)。
人工验证清单:
- 左栏底部有两颗小图标;点三横线那颗,面板行依次变成紧凑、收纳、标准。
- 输入条右侧的麦克风图标不在;PUA / 权限 / 计划是纯图标。
- 手机(或窗口缩到 899px 以下)上:每轮费用行变矮、底部只剩两个读数、消息操作行只剩三颗。
- 点柱状图那颗 → 菜单弹出、六档可见、当前档高亮;选「宽」→ 对话区立刻变宽。
- 拖动对话区两侧手柄 → 拖动中出现宽度读数;松手后再点菜单,高亮变成「自定义」。
- 把「左栏导航」关掉 → 面板行立刻回来、三横线图标消失、柱状图图标还在。
- 把「输入条」关掉 → 麦克风回来、徽章恢复文字。
- 把「手机端密度」关掉 → 手机上的一切恢复原样。
- 把「对话宽度」关掉 → 柱状图图标消失、对话区回到宿主默认宽度。
- 桌面窗口下输入框旁「WB CN · 积分」读数照常显示;把窗口缩到 899px 以下(或手机)→ 读数消失,点开的明细面板始终可用;把「手机端隐藏积分」关掉 → 手机端读数恢复。
- 关掉任意一个开关后按 Ctrl+R 刷新 → 状态保持(开关已落到 profile 的
cordis.patch.yml)。 - 关掉导航后仍能从设置页把开关打开 → 设置页自身不受开关影响。
七、实现要点
- 一个文件、五段样式。
client.js里CSS数组按「变量 → 导航+宽度 → 输入条 → 手机密度 → 积分隐藏 → 设置页」排列。手机密度在输入条之后,所以窄屏规则天然压过前两段;积分隐藏 自带 899px 媒体查询(仅手机端生效),在设置页之前,设置页自身不归任何开关管。 - 门控属性:
data-dsh-fit-nav/data-dsh-fit-composer/data-dsh-fit-mobile/data-dsh-fit-width/data-dsh-fit-credits,外加data-dsh-fit-navmode(三态)和data-dsh-fit-tight(实测窄行)。关开关就是摘属性,规则随之失效。 - 认元素优先用宿主契约:
[data-slot="sidebar.panellist"]、[data-slot="sidebar.footer.action"]、[data-conversation-content]、[data-sidebar-chat]都是渲染器写死的槽位名/属性, 改版不动;wtg9ga_panelList这类哈希类名只作兜底。 - 宽度轴是接管不是覆盖:写宿主自己的
--dsh-chat-user-width与localStorage['dsh.conversation.contentWidth'],宿主的拖拽手柄与ResizeObserver照常工作。 详见第四节。 - 变量统一:
--dsh-fit-ctl(控件直径,clamp(28px, 1.5vw + 20px, 36px))定义在样式表 第一行,导航图标和输入条按钮都读它。 - 不 import 任何
@deepseek-ai/*。primitives 只在模块表真的提供时才用,拿不到就走 朴素元素兜底。 - 颜色一律
--dsw-alias-*,不写死色值。 - 所有监听、观察器、槽位注册都在
apply内创建并返回清理函数。 - 四个槽位:
sidebar.footer.action两颗按钮(order 50 / 51)、sidebar.panellist.actions的「更多」菜单段(order 10)、settings.section的设置页(order 18)。 - 设置页自绘开关:不用 primitives 的
Switch(宿主禁止requireprimitives 包), 是button[role=switch][aria-checked],状态靠data-on给样式钩子。 - 模板字符串的注释里不能出现反引号——会截断模板,运行时
ReferenceError,而node --check查不出来(它只管语法)。
八、兼容性
| 端 | 合并前 | 合并后 |
|---|---|---|
| 桌面 | 导航生效;输入条部分生效;手机密度不生效 | 完全一致 |
| 手机 | 三者全生效 | 完全一致(默认全开) |
宽度挡位在两种端上都生效(它是内容宽度,不是触控布局)。
没装其它插件会怎样
本插件不 import 任何第三方插件的代码,只 require('react'),primitives 拿不到就走朴素元素兜底。
所以:
| 情况 | 结果 |
|---|---|
没装 dsh-mobile |
正常。页脚只对齐自己的按钮;dsh-mobile 那条 44px 反制规则静默失效 |
没装 dsh-better-input / @michengai/dsh-pua / dsh-cost-meter |
正常。对应的对齐规则静默失效,不报错、不影响软件 |
没装 dsh-connect-workbuddy |
正常。积分隐藏规则(仅手机端)选择器自然不匹配,静默失效;新钩子带 owner 会跳过 |
| 没装任何第三方插件 | 正常。npm test 也通过(这 13 个钩子带 owner 标记,owner 没装就跳过并单独计数) |
换一台没有 asar 的机器跑 npm test |
check-hooks.mjs 按常见安装位置探测 asar;真找不到时用 --asar <路径> 指定 |
依赖第三方的样式规则占比:输入条 9/28、手机密度 21/40;积分隐藏段(仅手机端)1/1 全依赖 WorkBuddy(但没装就静默失效);导航与宽度段零依赖。
选择器用的是契约还是哈希类名
宿主渲染器会给每个槽位包一层 <div data-slot="<slotKey>" style="display:contents">,
data-slot 的槽位名是公开契约,宿主改版不动;wtg9ga_panelList 这类是构建哈希类名,换版本就变。
本插件因此优先走契约:
| 用途 | 首选(契约) | 兜底(哈希类名) |
|---|---|---|
面板导航 <nav> |
[data-slot="sidebar.panellist"] 向上找 nav |
类名含 panelList 且有「更多」行 |
| 页脚动作排 | [data-slot="sidebar.footer.action"] |
自己按钮的父元素 |
开关的存储方式:
| 项 | 存储 | 范围 | 默认 |
|---|---|---|---|
| 五个模块开关 | profile 的 cordis.patch.yml 对应行 config: |
账号级,多设备一致 | 全开 |
| 导航三态 | localStorage 的 dsh-ui-fit:nav |
设备级,每浏览器一份 | 标准 |
| 宽度挡位 | localStorage 的 dsh-ui-fit:width |
设备级,每浏览器一份 | 标准 |
| 自定义宽度 | localStorage 的 dsh-ui-fit:widthCustom |
设备级,每浏览器一份 | 未设置 |
九、许可
Apache-2.0,见 LICENSE。
No comments yet. Be the first to write one.