dsh-panel-control 0.7.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -229
- package/client.js +540 -8
- package/docs/bridged-settings-pages.md +85 -0
- package/package.json +2 -2
- package/scripts/bridge-smoke.mjs +324 -0
- package/scripts/layout-smoke.mjs +16 -0
package/README.md
CHANGED
|
@@ -1,232 +1,112 @@
|
|
|
1
|
-
# dsh-panel-control · 侧栏面板管理
|
|
2
|
-
|
|
3
|
-
管理
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-

|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
装好后**不需要重启 DSH**:新增 bundle 会随下一次页面重载生效(`Ctrl+R`,或界面里的刷新)。
|
|
63
|
-
|
|
64
|
-
## 实现要点
|
|
65
|
-
|
|
66
|
-
- 两半:`client.js` 管布局(排序 / 隐藏 / 分组分隔线),`index.js`(Host)只做一件事——提供「让模型分类」的唯一一条路由。
|
|
67
|
-
- 客户端拿不到模型:`ctx.remote.llm.*` 只有 `listProviders` / `listConfigurableProviders` / `discoverModels` 三个只读方法,没有任何补全入口,所以模型调用必须落在 Host 侧。
|
|
68
|
-
- Host 路由:`ctx.connection.fetch.register({ path: '/api/dsh-panel-control/classify', methods: ['POST'], requestBody: 'buffered' })`,信封与 `/api` 网关一致(`{type:'client-request', rpcId, method, payload}` → `{type:'server-response', rpcId, result}`);只接受 loopback 的 Host/Origin、只接受 `application/json`、请求体上限 64 KB、45 s 超时,一次请求最多 1 次模型调用。
|
|
69
|
-
- 模型调用:`ctx.agentDefaultModel.currentSelection()` 取默认 provider/model,`ctx.llm.prepareCall(...)` 后 `prepared.stream(prepared.config)` 收流并拼 `text-delta`。分类不需要思考预算,因此该次调用固定 `reasoningEffort: 'off'`(否则思考会吃掉输出额度、拿不到正文);返回的 JSON 在 Host 侧就做白名单清洗(未知 id、重复 id、超过 4 组、超长组名都会被丢弃)。
|
|
70
|
-
- 设置读写也走同一条路由:`{method:'load'}` / `{method:'save'}`(原子写 `prefs.json`,上限 256 KB),另有 `{method:'report'}` 把客户端的异常与启动信息写进 `~/.dsh/panel-control/client.log`。Host 侧日志写在同目录的 `host.log`,出问题先看这两个文件。
|
|
71
|
-
- 干净降级:`connection` 或 `llm` 不可用时路由不注册 / 返回明确错误,客户端在开关下方显示原因,不影响布局功能。
|
|
72
|
-
- 启动稳定性(v0.3.0 修的三个真问题):①`apply()` 里调用的 `applyInner` 曾是对象方法——方法名不在词法作用域里,`apply()` 直接 `ReferenceError`,于是"插件装了但没运行",现已改成真正的函数;②`locale` 不再是硬依赖,`inject` 只留 `slots`,可选服务统一走 `optionalService(ctx, name)`(`ctx.get(name)` + `try`),缺服务只会降级不会让 `apply` 抛错;③宿主渲染侧栏可能晚于插件 apply,引擎启动改成 `[0,120,400,1000,2500,5000,9000,14000] ms` 重试 + 每 3 s 轮询直到找到导航区容器。
|
|
73
|
-
- 定位导航区:优先 `[class*="panelList"]`,失败时用已知面板的 `button[aria-label]` 反查公共父节点。
|
|
74
|
-
- 行身份:以 `sidebar.panellist` 的 `entriesOfSlot()` 顺序(按 `order` 稳定排序)为位置主键,用 DOM 的 `aria-label` 做二次校验,宿主重渲染后自动重新对齐。
|
|
75
|
-
- 应用方式只写 `style.order` / `style.display` / `data-dpc-group`,不移动 DOM 节点,避免与 React 的 reconciliation 打架。
|
|
76
|
-
- 变更来源:`MutationObserver`(childList + class/style 属性)、`window.resize`、`ctx.slots.subscribe('sidebar.panellist')`、`ctx.locale.subscribe` 都汇入一次 `requestAnimationFrame` 去抖的 `schedule()`;应用期间暂停 observer 防止自激,签名不变时短路。
|
|
77
|
-
- 语言:注册 `locale` 命名空间 `dsh-panel-control`,中英双语,跟随界面语言切换。
|
|
78
|
-
|
|
79
|
-
## 自测
|
|
80
|
-
|
|
81
|
-
`.scratch/harness.html` 是引擎测试页:伪造 `panelList` DOM、假 `react`、假 `ctx`,直接驱动 `testables.createEngine()`,覆盖排序 / 隐藏 / 分组分隔线 / 分组顺序不漂移 / 两套列表共用的拖拽重排规则 / 折叠态 / 新注册项 / 脏数据清洗,当前 **27 项全部通过**。`.scratch/apply-test.html` 另外用伪 Cordis 上下文调用真实的 `apply(ctx)`,断言它注册了 `locale` / `main` / `settings.section` / `sidebar.panellist` 四样东西——上一版的 `ReferenceError` 就是它抓出来的。
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
python -m http.server 8787 --bind 127.0.0.1 # 在仓库根目录
|
|
85
|
-
# 打开 http://127.0.0.1:8787/.scratch/harness.html
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## 重启后「插件不见了」怎么办
|
|
89
|
-
|
|
90
|
-
桌面端窗口加载插件时用的是**应用启动那一刻冻结的一份插件清单**。如果在这份清单生成之后又改过插件代码(开发时改一次 `client.js` 就算),新开的页面仍会按清单里的旧版本号去要脚本,Host 现在服务的是新版本,那次请求就答 404 —— 结果就是**这个插件静默地没有加载**,屏幕上什么都不说,看起来像「重启后插件不会自己运行」。手动刷新一次页面(桌面端右上角的刷新按钮,或 `Ctrl+R`)就会按当前清单重新加载。
|
|
91
|
-
|
|
92
|
-
### 那条「清单过期」的提示什么时候才算数
|
|
93
|
-
|
|
94
|
-
提示条只在**真有入口没加载**时出现:拿 Host 当前清单里的 id 和这个页面启动时拿到的 id 逐一对,缺了才说。早先它还比较两边清单的版本号,于是每次改插件文件(版本号必然变)都会弹一次「刷新一次即可同步」——页面其实什么都不缺,提示却赶不走,还叠一句「刷新后仍然缺」。现在版本号只写进「诊断」里给人看,不参与判断。
|
|
95
|
-
|
|
96
|
-
另外两处防呆:刷新提示只在**这次页面自己发起的刷新**之后 10 分钟内、且缺的还是同一批 id 时才追加「关一次再打开」;提示条右侧有一个 ✕,点掉后对同一批 id 不再出现(只作用于当前页面)。
|
|
97
|
-
|
|
98
|
-
### 关于"救自己"这一段:已删除
|
|
99
|
-
|
|
100
|
-
早前版本做过一次 `recovery nudge`:Host 半等不到客户端上报就翻转 `client.js` 的末尾换行,逼加载器把插件热加载进已打开的页面。它在 0.4.0 里被**整段删掉**了,原因有三条,都很实在:
|
|
101
|
-
|
|
102
|
-
- 热加载会重跑整个插件的注册流程,邻居插件只要注册不是幂等的(`dsh-workspace-control` 就不是)就会抛 `already has an entry` 并丢掉自己的入口——修自己一次,打坏别人一次;
|
|
103
|
-
- 它让行为变得依赖时钟("等 12 秒看看"),而这既不是用户要的,也无法解释为什么别的插件不需要等;
|
|
104
|
-
- 真正的毛病在安装方式与版本号上(见下一节与 [0.4.0 那一节](#040不再有任何等一会儿的机制)),改那两处才是根治。
|
|
105
|
-
|
|
106
|
-
现在 Host 半在挂载的同一瞬间只做三件事:装路由、记一行插件图结论、自检一次"我这个包的脚本取得到吗"。没有定时器,没有重试,也不会写自己的文件。
|
|
107
|
-
|
|
108
|
-
## 提示条只该出现一次(0.3.12)
|
|
109
|
-
|
|
110
|
-
「这个页面用的插件清单,已经不是最新的了」这条提示会因为一个更麻烦的原因反复出现:
|
|
111
|
-
应用给每个页面的是**它启动时冻结的那份清单**,所以安装完新插件后**刷新页面并不改变清单**——
|
|
112
|
-
页面照样拿不到那个插件,提示条也就照样出现。之前它每次加载都弹,还把"刷一次就好"当结论,
|
|
113
|
-
等于把用户往一个不会成功的操作上引。
|
|
114
|
-
|
|
115
|
-
现在:
|
|
116
|
-
|
|
117
|
-
- 同一组缺失**只提示一次**(记在 `localStorage` 的 `dsh.panel-control.notice-nagged`),
|
|
118
|
-
刷新后还在就保持安静,诊断里仍能看到 `本页缺失` / `已提示过`。
|
|
119
|
-
- 文案改成「刷新后仍然缺:重启 DSH 才会应用新的插件清单」——这才是真正有效的动作。
|
|
120
|
-
- 缺口修复后自动清除记忆,下次真出现新缺口还会提示。
|
|
121
|
-
|
|
122
|
-
## 0.4.0:不再有任何"等一会儿"的机制
|
|
123
|
-
|
|
124
|
-
前面几版为了对付"启动后入口不见了",加过看门狗、自愈改写、延时判断。这些都是**事后补偿**,代价是行为不可预测(改写自己的脚本会让加载器热替换,顺手把邻居插件的重复注册打挂)。0.4.0 把它们全部删掉了:
|
|
125
|
-
|
|
126
|
-
- 没有定时器、没有自查重试、不会改写任何文件。Host 半只做三件事:装路由、读一次插件图并记一行结论、做一次"我这个包的脚本取得到吗"的自检(都是**启动瞬间**完成的同步调用)。
|
|
127
|
-
- Client 半和别的插件一样:`dsh.client.immediately = true`,页面启动时随清单一起加载,立刻把入口挂到侧栏。
|
|
128
|
-
- 安装方式也建议和别的插件一致:用 **npm 安装**(`npm i dsh-panel-control`),而不是 `link:` 本地目录。`link:` 指向的是一个我随时在改的开发目录,任何一次编辑都会让页面手里的版本号作废;装成包之后文件是静止的,不存在这个窗口。
|
|
129
|
-
|
|
130
|
-
## 为什么重启后偶尔看不到入口(0.3.16)
|
|
131
|
-
|
|
132
|
-
DSH 给每个插件的脚本算一个**版本号**,来源是文件的 `mtime / ctime / 大小`,注册表只认它自己在组装那一刻读到的那个版本。页面拿到的是**启动时那份清单**,只要 `client.js` 在清单发出去之后被重写过(编辑器保存、`git` 换行归一化、或者早先那个"自愈"改写),页面手里的版本号就成了作废的号,请求回 404,而页面**不会报错**——入口就这么没了。
|
|
133
|
-
|
|
134
|
-
0.3.16 的处理:
|
|
135
|
-
|
|
136
|
-
- Host 半只对本插件包装注册表的资源查询:页面若拿旧版本号来要,先用**本进程发过的同一版本字节**回答(缓存最近 5 个版本),再退到"用当前版本回答",其余请求原样放行,卸载时撤掉包装。别家插件的行为一点不改。
|
|
137
|
-
- 早先的"自愈"(改写 `client.js` 强制热加载)在 0.4.0 里已**整段删除**:它会重跑所有插件的注册,邻居不幂等就会丢入口——`dsh-workspace-control` 就是这么被弄掉的。
|
|
138
|
-
- `host.log` 每次 apply 打两行结论:`graph: lists|does not list <包名>` 和 `graph: own bundle at rev … answers 200|404`,一次就能分清是"没进清单"还是"清单有但取不到"。
|
|
139
|
-
|
|
140
|
-
## 代码审查后的加固(0.3.11)
|
|
141
|
-
|
|
142
|
-
一轮完整审查(正确性 / 安全 / 可维护性 / 性能)之后改掉的东西,按主题记在这里,免得以后又踩回去。
|
|
143
|
-
|
|
144
|
-
**安全**
|
|
145
|
-
|
|
146
|
-
- 路由只认自己这一页:以前只校验「Host 和 Origin 都是 loopback」,于是本机任何一个 dev server 的页面(`http://127.0.0.1:3000`)都能调 `save` / `classify` / `report`——改布局、烧模型额度。现在要求 **端口也一致**,`Sec-Fetch-Site` 不是 `same-origin`/`none` 的一律 403,`dsh-app:`/`file:` 两种桌面端协议放行。
|
|
147
|
-
- 体积上限移到解析之前:先 `request.text()` 量长度再看,超限直接 413(以前是先 `json()` 整个解析完再比,等于没有上限)。
|
|
148
|
-
|
|
149
|
-
**正确性**
|
|
150
|
-
|
|
151
|
-
- `mapButtons`:入口与行按钮的映射先按 `aria-label` 认,只有「剩余数量相等且标签逐一相符(或按钮根本没有标签)」才按位置补。以前无条件按位置兜底,一旦头部插入一行就整体错位,隐藏会隐藏错行。
|
|
152
|
-
- `hydratePrefs`:冷启动读 Host 期间用户改过的设置不再被回来的旧快照覆盖(`commitRevision` 比对)。
|
|
153
|
-
- `classifyNow`:并发闸(`classifyToken`),连点「重新分类」时只有最新一次的结果会落地。
|
|
154
|
-
- `autoFiled`:只记这次真正归了组的条目,模型漏掉的下次还能补。
|
|
155
|
-
- 写盘串行化(`writeChain`):两个窗口同时保存不再抢同一个 `.tmp`。
|
|
156
|
-
- 热替换后重置 `clientModules` 缓存,避免拿到上一代的图。
|
|
157
|
-
|
|
158
|
-
**资源与性能**
|
|
159
|
-
|
|
160
|
-
- `sanitize` 给所有数组/字典设上限并去重(order/hidden 200、autoFiled 400、分组 24、assign 400、名称 40 字):256KB 的恶意文件不再能变出几千个分隔节点。
|
|
161
|
-
- `MutationObserver` 只在找到列表之后盯那个列表,并给 `apply` 加 100ms 最小间隔——聊天流式输出不再每帧触发全量重排。
|
|
162
|
-
|
|
163
|
-
**可维护性**
|
|
164
|
-
|
|
165
|
-
- 常量具名(`SAVE_DEBOUNCE_MS` / `AUTO_DELAY_MS` / `AUTO_RETRY_MS` / `ORDER_UNMANAGED_TAIL`)。
|
|
166
|
-
- 入口行与分组行的拖拽逻辑合并成 `dragProps()`,两边只传提交函数。
|
|
167
|
-
- `flatten()` 缺语言时回退到另一种语言,不再渲染 `undefined`。
|
|
168
|
-
- Host 的失败按 `code` 本地化(超时 / 模型失败 / 回答不可解析),不再把英文原文拼进中文界面。
|
|
169
|
-
- `checkInventory` 5 秒节流(窗口每次聚焦都会触发)。
|
|
170
|
-
- 页面列改成 `max-width:1056px; margin-inline:auto`:窄窗口下不再横向溢出(以前 `50% - 480px` 的算法在窄容器里会把列推到屏幕外,右侧按钮点不到)。
|
|
171
|
-
|
|
172
|
-
回归测试:`npm run check`(Host 半 17 项,含同机异端口 / `same-site` / 自定义协议 / 超大 body 四条安全用例),`.scratch/harness.html` 37 项(新增 `mapButtons` 四条与 `sanitize` 上限四条)。
|
|
173
|
-
|
|
174
|
-
## 让 DSH 崩掉的两件事
|
|
175
|
-
|
|
176
|
-
这两条都是真崩过的,不是推测。
|
|
177
|
-
|
|
178
|
-
**一、半装的插件是致命的。** `dsh.profile.bundles` 里写了一个名字,但 `dependencies` 里没有、`node_modules` 里也没有可解析的包,Host 启动时就会去加载这个不存在的包;这个失败发生在 Cordis 的逐个插件错误边界之外,于是整个 Host 进程直接退出——不是「这个插件没加载」,是「应用打不开」。手改 `bundles` 最容易造成这个状态。
|
|
179
|
-
|
|
180
|
-
所以安装拆成顺序固定、每步都校验的三步(依赖 → 链接 → 清单),任何一步失败就整体回滚:
|
|
181
|
-
|
|
182
|
-
```bash
|
|
183
|
-
npm run enable # 装进 desktop profile,最后校验整份清单
|
|
184
|
-
npm run enable -- --disable # 三步一起撤掉
|
|
185
|
-
npm run doctor # 只检查:每个 bundle 是否都能解析
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
(这三个脚本随包发布,装成 npm 包后也能直接跑;`.scratch/harness.html` 只在仓库里。)
|
|
189
|
-
|
|
190
|
-
`doctor` 也会揪出别的断链插件(指向已删除目录的 `link:` 依赖、名字与 `package.json` 不符的包),它只读真实 profile,不改任何东西。
|
|
191
|
-
|
|
192
|
-
**二、Node 定时器里的回调没有错误边界。** `setTimeout` / `Promise` 回调不在 Cordis 的 `apply` 调用栈里,那里抛出的异常没人接,就是进程级 fatal。第一个版本正是在 20 秒看门狗里调了一个已被删掉的函数,启动 20 秒后必崩,日志里只留一行「客户端半没上报」。
|
|
193
|
-
|
|
194
|
-
现在每个延迟回调都自己包 `try/catch`,调用点再加一层 `.catch(...)`;`npm run check` 里的 `scripts/host-smoke.mjs` 会劫持 `setTimeout` 并把回调**真的执行一遍**,再驱动 `inventory` / `report` / 403 三条路由。对着 0.3.6 那份代码跑,它会清清楚楚地报:
|
|
195
|
-
|
|
196
|
-
```
|
|
197
|
-
FAIL timer(20000ms) survived: watchFirstClientBoot is not defined
|
|
198
|
-
FAIL inventory answers: handle is not defined
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
npm run smoke # 当前代码:全绿
|
|
203
|
-
npm run smoke -- <文件> # 跑别的副本,用来复现回归
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## 边界
|
|
207
|
-
|
|
208
|
-
- 自动分类会把**入口名称**(如「插件市场」「工作区管理」,不含任何会话内容、路径或凭据)发给当前 profile 配置的模型服务商;不想发就别开这个开关,它的结果也可以在面板里随手改。
|
|
209
|
-
- 分组是"建议":模型只提出组名与归属,你可以在每行右侧的下拉里改成别的组,或直接改组名。
|
|
210
|
-
- 宿主若改动 `panelList` 的 class 命名或行结构,需要同步更新选择器;已做多级回退,但仍是 DOM 适配层。
|
|
211
|
-
- 隐藏/分组只影响本机这份侧栏渲染,不改动任何插件自身配置。
|
|
212
|
-
- 插件销毁时移除注入的 `style` 节点、observer 与轮询定时器;布局存在 `~/.dsh/panel-control/prefs.json`,重装或重启后接着用(想清空就在面板里点「恢复默认」,或直接删掉这个文件)。
|
|
213
|
-
|
|
214
|
-
## 侧栏导航溢出兜底(0.7.0,对应讨论 #12083)
|
|
215
|
-
|
|
216
|
-
官方 `dsh-client-ui-sidebar` 把导航列表设成 `flex:none`、把下方的会话/工作区设成
|
|
217
|
-
`flex:1;overflow:hidden`。导航项一多,列表按内容高度吃满,下面的区域被压成 0 高且没有可滚动的容器
|
|
218
|
-
([讨论 #12083](https://github.com/deepseek-ai/deepseek-harness/discussions/12083))。本插件不改官方包,
|
|
219
|
-
只在页面上给两个节点加上自己的属性钩子,再用一段 CSS 兜底:
|
|
220
|
-
|
|
221
|
-
| 钩子 | 写在哪 | 作用 |
|
|
1
|
+
# dsh-panel-control · 侧栏面板管理
|
|
2
|
+
|
|
3
|
+
管理 DeepSeek Harness 侧栏顶部那一列插件入口(插件、插件市场、自动化任务、灵枢、工作区管理……):排序、隐藏、分组、折叠;导航项变多时,还会托住下方的会话与工作区列表。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **排序** — 拖动行首把手任意换位;一键按名称 A→Z;一键恢复注册时的默认顺序。
|
|
8
|
+
- **隐藏** — 行右侧的眼睛图标。隐藏项仍留在列表里,随时放回;不影响插件是否加载,也不写插件自己的配置。
|
|
9
|
+
- **分组** — 新建分组(如「创作」「系统」)并把入口分进去,侧栏会在组与组之间插入带标题的分隔线。组的先后由设置里的分组列表拖动决定,与注册顺序、分类结果无关。
|
|
10
|
+
- **分组折叠** — 点分组标题收起该组,只留标题与计数;`Enter` / 空格同样可切换,状态写进配置。侧栏收成图标条时不显示标题,也不折叠。
|
|
11
|
+
- **侧栏溢出兜底** — 导航项变多时,官方侧栏会把下方的会话/工作区列表压到 0 高且无法滚动([讨论 #12083](https://github.com/deepseek-ai/deepseek-harness/discussions/12083))。本插件给导航列表加上限高与滚动,给下方区域留最小高度。
|
|
12
|
+
- **自动分类** — 用当前 profile 的默认模型读入口名称,自动分成 2–4 组。只处理没见过的入口,不会改动你排好的顺序;随时可手动「重新分类」。
|
|
13
|
+
- **设置页补入口** — 有的第三方插件只注册了设置页(`settings.section`),却没给面板入口,设置页只能从设置弹窗里翻到。本插件为这类插件自动补一行侧栏入口:点开就是宿主渲染的完整设置页,顶部带「返回对话」;不依赖插件白名单,整体可在「侧栏布局」里关掉。
|
|
14
|
+
- **面板管理自己不可隐藏** — 避免把自己锁在门外;未接管的节点(如「更多」)始终排在最后。
|
|
15
|
+
|
|
16
|
+
## 界面
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
侧栏分组后的效果:
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
两个入口:侧栏导航里的「面板管理」行,以及 **设置 → 面板管理**(同一套管理界面整块嵌在设置页)。
|
|
25
|
+
|
|
26
|
+
补出来的设置页入口也出现在同一个侧栏列表里,排在普通入口之后、本插件自己之前;标题沿用设置页自己的名称,重复时附带设置页 id 以区分。
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
从 npm 安装(推荐):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd C:/Users/<you>/.dsh/profiles/desktop
|
|
34
|
+
npm install dsh-panel-control@0.8.3
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
把包名加进同一目录 `package.json` 的 `dsh.profile.bundles`:
|
|
38
|
+
|
|
39
|
+
```jsonc
|
|
40
|
+
"dsh": { "profile": { "bundles": [ ..., "dsh-panel-control" ] } }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`npm install` 会把包(含 `client.js` 与 `cordis.patch.yml`)放进 profile 的 `node_modules`,不需要再建目录链接。
|
|
44
|
+
|
|
45
|
+
本地开发用 `link:` 指向工作副本,再建目录链接(免管理员):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd C:/Users/<you>/.dsh/profiles/desktop
|
|
49
|
+
cmd //c mklink /J node_modules\dsh-panel-control D:\Github\dsh-panel-control
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
装好后需要一次页面重载才会加载新的 bundle(界面里的刷新按钮,或重启 DSH)。
|
|
53
|
+
|
|
54
|
+
## 侧栏布局与折叠
|
|
55
|
+
|
|
56
|
+
官方 `dsh-client-ui-sidebar` 把导航列表设为 `flex: none`,把下方区域设为 `flex: 1; overflow: hidden`。导航项一多,列表独占高度,下方被压成 0 高且没有可滚动容器——滚轮看起来"失灵",其实是没东西可滚。
|
|
57
|
+
|
|
58
|
+
本插件不改宿主代码,只从三个属性出发写 CSS:
|
|
59
|
+
|
|
60
|
+
| 属性 | 加在哪 | 作用 |
|
|
222
61
|
| --- | --- | --- |
|
|
223
|
-
| `data-dpc-list` |
|
|
224
|
-
| `data-dpc-unbounded` |
|
|
225
|
-
| `data-dpc-region` |
|
|
62
|
+
| `data-dpc-list` | 导航列表容器 | `overflow-y: auto` + 高度上限 `max(120px, var(--dpc-panellist-max, 45vh))` |
|
|
63
|
+
| `data-dpc-unbounded` | 同上(选择「不限」时) | 取消高度上限,列表按内容撑开 |
|
|
64
|
+
| `data-dpc-region` | 列表下方的会话/工作区区域 | 保留最小高度 `min(120px, 18vh)` |
|
|
65
|
+
|
|
66
|
+
设置页「侧栏布局」卡片里可以调整:
|
|
67
|
+
|
|
68
|
+
- 高度兜底开关,以及 35 / 45 / 55 / 不限 四档上限;
|
|
69
|
+
- 分组折叠开关,以及「全部收起 / 全部展开」;
|
|
70
|
+
- 设置页入口开关(默认开)。关掉后补出来的行会连同注册一起撤掉,不只是藏起来;
|
|
71
|
+
- 折叠状态按分组 id 记在配置里,重启后保留。
|
|
72
|
+
|
|
73
|
+
## 配置与存储
|
|
74
|
+
|
|
75
|
+
布局写在 Host 侧:`~/.dsh/panel-control/prefs.json`(`~` 是这个 profile 的用户目录)。包含顺序、隐藏项、分组、折叠状态、布局开关、设置页入口开关与分类结果。
|
|
76
|
+
|
|
77
|
+
页面 origin 是 Host 的 loopback 端口,每次启动都会换端口,`localStorage` 会因此"看起来每次清空"——所以真实存储放在 Host 侧,`localStorage` 只留镜像;读不到 Host 时才回退到镜像。配置按安装维度生效,不跟随工作区。
|
|
78
|
+
|
|
79
|
+
## 排错
|
|
80
|
+
|
|
81
|
+
- **侧栏没有「面板管理」**:先重载一次页面。仍没有,看 `~/.dsh/panel-control/host.log` 与 `client.log`。
|
|
82
|
+
- **设置页入口没补出来**:`host.log` 里先搜 `bridge-scan`,启动后的前几次扫描会列出当时可见的设置页(`id:registrant`)和侧栏条目;再搜 `kind=bridge` 看最终补出的 id。目标插件出现在 `sections` 里但 `ids` 没它,说明它的 section id 或 label 与某个已有面板重合,被归属规则当成了已有入口;始终不出现,是它的设置页注册得比扫描更晚(见 `docs/bridged-settings-pages.md`)。
|
|
83
|
+
- **插件管理显示运行,界面却没变化**:页面多半还在用旧的 bundle revision(刚改过 `client.js` 时常见),重载一次即可。
|
|
84
|
+
- **改了代码没反应**:确认 `BUILD_STAMP` 与 `package.json` 的版本一致,并跑一遍 `npm run check`。
|
|
85
|
+
|
|
86
|
+
## 开发与测试
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm run check # 语法检查 + 宿主路由 smoke + 布局/桥接静态与行为断言
|
|
90
|
+
npm run smoke # 只跑宿主路由用例
|
|
91
|
+
npm run doctor # 体检 profile 安装状态
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `.scratch/harness.html`:引擎测试页(伪造 DOM 与 ctx),覆盖排序、隐藏、分组、折叠、脏数据清洗。
|
|
95
|
+
- `scripts/host-smoke.mjs`:驱动宿主侧路由,覆盖设置读写、分类请求与 bundle 兜底。
|
|
96
|
+
- `scripts/layout-smoke.mjs`:断言侧栏布局的 CSS 钩子与关键数值,并校验 `BUILD_STAMP` 与版本号一致。
|
|
97
|
+
- `scripts/bridge-smoke.mjs`:在假 ctx 上跑 `client.js`,断言只给「有设置页、没有侧栏入口」的第三方插件补行,且成对注册 main、开关关闭后整体撤回、桥接页经自有子槽交给宿主渲染并能返回对话。
|
|
98
|
+
|
|
99
|
+
进一步材料:[docs/sidebar-layout.md](docs/sidebar-layout.md)(布局与折叠实现)、[docs/bridged-settings-pages.md](docs/bridged-settings-pages.md)(设置页补入口的识别与渲染)、[docs/ui-design-notes.md](docs/ui-design-notes.md)(界面规范与踩坑)、[docs/discussion-bundle-evaluated-twice.md](docs/discussion-bundle-evaluated-twice.md)(bundle 被求值两次的事故复盘)。
|
|
100
|
+
|
|
101
|
+
## 边界
|
|
102
|
+
|
|
103
|
+
- 自动分类会把入口名称发给当前 profile 配置的模型服务商;不想要就关掉开关,分组仍可手动编辑。
|
|
104
|
+
- 分类结果只是建议,可以整体重排或改名。
|
|
105
|
+
- 依赖宿主侧栏的 DOM 结构(`[class*="panelList"]` 与面板按钮的 `aria-label`),官方大改版时需要跟进。
|
|
106
|
+
- 只写 `style.order`、`style.display` 与 `data-dpc-*` 属性,不移动 React 节点,不改宿主代码。
|
|
107
|
+
- 排序、隐藏、分组都只影响本机这份安装。
|
|
108
|
+
- 设置页入口按「section id / label 是否已被某个面板占用」识别归属,不依赖插件声明的注册者(client runner 会把多个插件报成同一个名字);代价是启发式:与面板同名的第三方设置页会被视为已有入口,新增内置设置页需要更新 `CORE_SECTION_IDS`。第三方设置页按宿主渲染器原位渲染,若它自己依赖设置弹窗的私有上下文,可能出现布局差异。
|
|
226
109
|
|
|
227
|
-
|
|
228
|
-
关掉兜底后两个属性都会被移除,侧栏回到官方原本的样子;`destroy()` 也会清理它们,不会留在一个别人的 DOM 上。
|
|
110
|
+
## 许可
|
|
229
111
|
|
|
230
|
-
|
|
231
|
-
标题和数量仍保留(否则折叠后就找不到回来的入口)。折叠状态存在 `prefs.collapsed`,和排序、隐藏一样跟着
|
|
232
|
-
设置文件走。窄侧栏(<64px)不渲染标题,也不应用折叠。
|
|
112
|
+
MIT
|