dsh-preset-studio 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +36 -0
  2. package/lib/client.js +71 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -85,6 +85,42 @@ dsh plugin --profile <profile> add dsh-preset-studio
85
85
  3. `settings.section` 是 `list` 类型(并列追加),没有「顶掉同位置条目」的
86
86
  选举语义,所以也无法用自己的条目替换官方条目。
87
87
 
88
+ ### 侧栏图标为什么是「画上去的」
89
+
90
+ `settings.section` 的注册参数只有 `id` / `order` / `label`,**没有图标字段**。
91
+ shell 用一张硬编码的映射表挑图标:
92
+
93
+ ```js
94
+ navIcon(id) // account / models / agent-presets / plugins / archived-sessions
95
+ // 其余 id 一律兜底成通用齿轮
96
+ ```
97
+
98
+ 所以第三方 id 永远拿到那只齿轮——它和「通用设置」长得一模一样,在一列入口里
99
+ 认不出来。
100
+
101
+ 契约里没有位置,就只能在渲染之后接管,做法与 `dsh-mcp-manager`、
102
+ `dsh-agnes-multimodal` 相同:
103
+
104
+ 1. `MutationObserver` 盯着 `document.body`,找到
105
+ `[data-shortcut-modal="settings"] nav` 里文字等于「预设工作室」的那个
106
+ `button`,给它打上 `data-ps-nav`;
107
+ 2. 一条 CSS 把 shell 的兜底齿轮 `display:none` 掉,再用 `::before` +
108
+ `-webkit-mask` 画自己的 mark;
109
+ 3. 图标是内联 SVG data URI,`background-color: currentColor` 配合 mask,
110
+ 于是亮/暗主题、悬停、选中态自动跟随,**不引入任何颜色值**。
111
+
112
+ 两个细节值得记下来:
113
+
114
+ - **只加 `data-*` 属性,不替换 shell 的节点。** 那一行的 `svg` 归 React 所有,
115
+ 把它换掉会让 React 在卸载时对着一个已不在树上的子节点调用 `removeChild` 而抛错。
116
+ React 不管理它没写过的属性,所以重渲染不会把标记抹掉。
117
+ - **mask 变量定义在 `[data-shortcut-modal="settings"]` 上,不是 `:root`**,
118
+ 这样它不会漏到 shell 的其它地方。
119
+
120
+ mark 本身是四个圆角方块:左上实心表示「开着」,其余空心表示「可以关」——
121
+ 正是这一页在做的事(一个方块=一个插件)。选它也是为了和邻居区分开:
122
+ 「MCP 服务器」是节点图,「多模态配置」是叠起来的方块加播放三角。
123
+
88
124
  ## 写入路径:为什么不能用官方的配置编辑器
89
125
 
90
126
  `@deepseek-ai/dsh-config-editor` 的 `entries()` 只认 `fiber.entry?.id === "include"`
package/lib/client.js CHANGED
@@ -514,6 +514,77 @@ button.ps-entry:hover{background:var(--ps-hover)}
514
514
  return function() { document.head.removeChild(style); };
515
515
  });
516
516
 
517
+ // ── 侧栏图标 ───────────────────────────────────────────────────
518
+ // shell 用 `navIcon(sectionId)` 选设置侧栏每行的图标,那是一张**只认
519
+ // 官方 id 的硬编码映射表**(account / models / agent-presets /
520
+ // plugins / archived-sessions),而 `settings.section` 的注册参数
521
+ // 只有 id / order / label 三个字段,没有图标位。于是任何第三方 id
522
+ // 都落进兜底的那只通用齿轮——本页原本就是齿轮,和「通用设置」长得
523
+ // 一模一样,在一列入口里认不出来。
524
+ //
525
+ // 契约没有位置,就只能在渲染之后接管:给这一行的按钮打一个 data-*
526
+ // 标记,用 CSS 把兜底齿轮藏掉,再用 currentColor 的 mask 画自己的
527
+ // mark。用 mask 而不是直接插 svg,有两个原因:
528
+ // 1. 颜色走 currentColor,亮/暗主题、悬停、选中态自动跟随,
529
+ // 不必自己判断主题,也不引入任何颜色值;
530
+ // 2. **不碰 React 拥有的节点**。这一行的 svg 是 shell 渲染的,
531
+ // 把它换掉会让 React 在卸载时对着一个已经不在树上的子节点调用
532
+ // removeChild 而抛错。只加一个 data-* 属性则是安全的:React
533
+ // 不管理它没写过的属性,重渲染不会把它抹掉。
534
+ // 观察器跟着 body 走,因为设置弹层是开时挂载、关时卸载的,这一行
535
+ // 只在弹层打开期间存在。
536
+ var NAV_ATTR = "data-ps-nav";
537
+ // 16×16 / 1.4px 描边,与 shell 自带图标的规格一致(medium 档)。
538
+ // 四个圆角方块=四个插件行,左上实心表示「开着」,其余空心表示
539
+ // 「可以关」——正是这个页面在做的事。
540
+ var NAV_MARK = "url(\"data:image/svg+xml,"
541
+ + "%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"
542
+ + "%3Crect x='1.7' y='1.7' width='5.4' height='5.4' rx='1.5' fill='%23000'/%3E"
543
+ + "%3Crect x='8.9' y='1.7' width='5.4' height='5.4' rx='1.5' fill='none' stroke='%23000' stroke-width='1.4'/%3E"
544
+ + "%3Crect x='1.7' y='8.9' width='5.4' height='5.4' rx='1.5' fill='none' stroke='%23000' stroke-width='1.4'/%3E"
545
+ + "%3Crect x='8.9' y='8.9' width='5.4' height='5.4' rx='1.5' fill='none' stroke='%23000' stroke-width='1.4'/%3E"
546
+ + "%3C/svg%3E\")";
547
+ // 这个 url() 通过自定义属性交给 mask:变量定义在弹层范围内,不会
548
+ // 漏到 shell 的其它地方。mask 只看 alpha 通道,所以图案本身用不透明
549
+ // 黑(%23000)——它不是一个「颜色」,最终显示的颜色由上面的
550
+ // currentColor 决定。
551
+ var NAV_CSS = `
552
+ [data-shortcut-modal="settings"]{--ps-nav-mark:${NAV_MARK}}
553
+ [data-shortcut-modal="settings"] nav button[${NAV_ATTR}]>svg:first-child{display:none}
554
+ [data-shortcut-modal="settings"] nav button[${NAV_ATTR}]::before{
555
+ content:"";flex:none;width:16px;height:16px;background-color:currentColor;
556
+ -webkit-mask:var(--ps-nav-mark) center/16px 16px no-repeat;
557
+ mask:var(--ps-nav-mark) center/16px 16px no-repeat}
558
+ `;
559
+ /** 找到本页在设置侧栏的那一行,打上标记。 */
560
+ function tagNavCell() {
561
+ var nav = document.querySelector('[data-shortcut-modal="settings"] nav');
562
+ if (nav === null) return;
563
+ var cells = nav.querySelectorAll("button");
564
+ for (var i = 0; i < cells.length; i++) {
565
+ var text = cells[i].textContent;
566
+ if (text === null || text.trim() !== NAV_LABEL) continue;
567
+ if (cells[i].getAttribute(NAV_ATTR) !== "") cells[i].setAttribute(NAV_ATTR, "");
568
+ }
569
+ }
570
+ ctx.effect(function() {
571
+ // 测试桩里没有 MutationObserver,也没有真实 DOM;没有就跳过,
572
+ // 图标是装饰,不该让整页挂掉。
573
+ if (typeof MutationObserver !== "function" || typeof document.querySelector !== "function") {
574
+ return function() {};
575
+ }
576
+ var style = document.createElement("style");
577
+ style.textContent = NAV_CSS;
578
+ document.head.appendChild(style);
579
+ var observer = new MutationObserver(tagNavCell);
580
+ observer.observe(document.body, { childList: true, subtree: true });
581
+ tagNavCell();
582
+ return function() {
583
+ observer.disconnect();
584
+ document.head.removeChild(style);
585
+ };
586
+ });
587
+
517
588
  // ── data ───────────────────────────────────────────────────────
518
589
 
519
590
  /** POST one action to the fenced host route. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-preset-studio",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "DSH 预设工作室:在设置里用一个整页新建 / 编辑 / 复制 / 删除本 profile 的 Agent 预设——组成树、系统提示词(含 {{变量}} 校验)、装配诊断、来源行定位、与 standard 的差异对比、快照回滚,以及导出为可分享的 bundle。",
5
5
  "license": "MIT",
6
6
  "type": "module",