@morehao/dsh-skills-console 0.5.1 → 0.5.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/client.js CHANGED
@@ -304,18 +304,18 @@ var DICT = {
304
304
  "viewer.info.mode": "状态",
305
305
  "viewer.info.status": "更新",
306
306
  "viewer.info.none": "无",
307
- "intro.button": "介绍",
308
- "intro.open": "AI 介绍:这个技能能做什么",
309
- "intro.title": "AI 介绍",
307
+ "intro.button": "简介",
308
+ "intro.open": "AI 简介:这个技能能做什么",
309
+ "intro.title": "AI 简介",
310
310
  "intro.loading": "读取中…",
311
- "intro.empty": "还没有生成介绍",
312
- "intro.generate": "生成介绍",
311
+ "intro.empty": "还没有生成简介",
312
+ "intro.generate": "生成简介",
313
313
  "intro.regenerate": "重新生成",
314
314
  "intro.generating": "生成中…",
315
315
  "intro.hint": "会把 SKILL.md 正文发送给当前配置的模型提供方",
316
316
  "intro.hintRemote": "会把该技能在 {source}@{commit} 的 SKILL.md 发送给当前配置的模型提供方",
317
317
  "intro.meta": "{model} · {time} · AI 生成,可能不准确",
318
- "intro.stale": "技能内容已更新,介绍可能过期",
318
+ "intro.stale": "技能内容已更新,简介可能过期",
319
319
  "intro.view": "查看技能",
320
320
  "intro.unavailable": "当前宿主没有可用的模型服务",
321
321
  "intro.error.noModel": "还没有配置可用的模型",
@@ -1040,7 +1040,7 @@ function FolderIcon({ size = 12 }) {
1040
1040
  }
1041
1041
 
1042
1042
  // client/section.js
1043
- var import_react10 = __toESM(require("react"), 1);
1043
+ var import_react9 = __toESM(require("react"), 1);
1044
1044
 
1045
1045
  // client/scroll.js
1046
1046
  function isScroller(node, tolerance = 4) {
@@ -1134,6 +1134,19 @@ var CSS = `
1134
1134
  .dshsc-pop-label em { font-style: normal; font-size: 11px; color: var(--dsw-alias-label-tertiary); }
1135
1135
  .dshsc-pop-name { display: inline-flex; align-items: baseline; gap: 6px; }
1136
1136
  .dshsc-pop-hint { font-size: 11px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }
1137
+ /* 与 DSH 自带开关同尺寸/同 token:关是 border-l3 轨道,开是 brand-primary 轨道。
1138
+ 0.5.0 换三档 chip 时这组规则被一起删掉,但「侧边栏入口」还在用 .dshsc-switch,补回来。 */
1139
+ .dshsc-switch { box-sizing: border-box; position: relative; flex: none; width: 36px; height: 20px; padding: 2px;
1140
+ border: 0; border-radius: 999px; background: var(--dsw-alias-border-l3); cursor: pointer;
1141
+ transition: background .14s ease; }
1142
+ .dshsc-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
1143
+ .dshsc-switch:disabled { cursor: not-allowed; opacity: .5; }
1144
+ .dshsc-switch[aria-checked="true"] { background: var(--dsw-alias-brand-primary); }
1145
+ .dshsc-knob { display: block; width: 16px; height: 16px; border-radius: 50%;
1146
+ background: var(--dsw-alias-switch-thumb, #fff); box-shadow: 0 1px 2px rgba(0, 0, 0, .28);
1147
+ transition: transform .12s ease, background .12s ease; }
1148
+ .dshsc-switch[aria-checked="true"] .dshsc-knob { transform: translateX(16px);
1149
+ background: var(--dsw-alias-label-primary-foreground, #fff); }
1137
1150
 
1138
1151
  .dshsc-btn { font: inherit; font-size: 12px; line-height: 1; padding: 7px 11px; border-radius: 8px; cursor: pointer;
1139
1152
  white-space: nowrap;
@@ -1357,10 +1370,15 @@ var CSS = `
1357
1370
  .dshsc-sub-list { display: flex; flex-direction: column; padding: 0 12px 7px 46px;
1358
1371
  background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 22%, transparent);
1359
1372
  border-top: 1px solid var(--dsw-alias-border-l1); }
1373
+ /* 条目操作列 92px 是照两枚按钮预留的,眼下每行只有一枚「安装」(两字 ≈ 43px:0 9px padding + 边框)。
1374
+ 表头「操作」和已装行的破折号都按按钮宽度做一个右对齐的居中盒子:三者落在同一条中轴上,
1375
+ 既不贴着列右缘,也不用改列宽 / 挪按钮。换语言或字号时盒子只是不再严格等于按钮宽度。 */
1376
+ .dshsc-sub-list { --dshsc-entry-act: 44px; }
1360
1377
  .dshsc-entryhead { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
1361
1378
  align-items: center; height: 26px; border-bottom: 1px solid var(--dsw-alias-border-l1);
1362
1379
  color: var(--dsw-alias-label-tertiary); font-size: 10px; font-weight: 500; letter-spacing: .02em; }
1363
- .dshsc-entryhead .right { text-align: right; padding-right: 2px; }
1380
+ .dshsc-entryhead .right { justify-self: end; box-sizing: border-box; width: var(--dshsc-entry-act);
1381
+ text-align: center; padding-right: 0; }
1364
1382
  .dshsc-sub-entry { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
1365
1383
  align-items: center; font-size: 12px; padding: 6px 0; border-top: 1px solid var(--dsw-alias-border-l1); }
1366
1384
  .dshsc-entryhead + .dshsc-sub-entry { border-top: 0; }
@@ -1374,8 +1392,9 @@ var CSS = `
1374
1392
  .dshsc-state.ok svg { color: var(--dsw-alias-state-success-primary); }
1375
1393
  .dshsc-sub-entry .dshsc-actions { justify-self: end; display: flex; justify-content: flex-end; }
1376
1394
  .dshsc-sub-entry .dshsc-btn { height: 24px; padding: 0 9px; font-size: 11.5px; }
1377
- /* 已装条目的操作列没有动作:破折号占位,保持右对齐与按钮基线一致 */
1378
- .dshsc-sub-entry .dshsc-noop { color: var(--dsw-alias-label-tertiary); font-size: 11.5px; line-height: 24px; padding-right: 9px; }
1395
+ /* 已装条目的操作列没有动作:破折号占位,居中在按钮宽度里,和表头、安装按钮同一条中轴 */
1396
+ .dshsc-sub-entry .dshsc-noop { box-sizing: border-box; width: var(--dshsc-entry-act); padding: 0;
1397
+ text-align: center; color: var(--dsw-alias-label-tertiary); font-size: 11.5px; line-height: 24px; }
1379
1398
  /* 设置页的真实宽度常常远小于窗口(侧栏 + 页面缩放),视口 media query 不可靠;
1380
1399
  以 .dshsc-root 自身宽度为准,窄面板时切两列,避免固定列把 Agent 标签挤成竖排。
1381
1400
  断点取 860 而不是贴着面板常见的 ~900:四列网格需要 280 + 150 + 操作列 + 3×14 gap + 中间列,
@@ -1404,7 +1423,7 @@ var CSS = `
1404
1423
  }
1405
1424
  .dshsc-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
1406
1425
 
1407
- /* 调用策略 chip:一个入口显示当前模式;点开三档弹层。窄列里允许省略,不撑破 102px */
1426
+ /* 调用策略 chip:一个入口显示当前模式;点开三档弹窗。窄列里允许省略,不撑破 102px */
1408
1427
  .dshsc-chip { box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
1409
1428
  height: 24px; padding: 0 9px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
1410
1429
  background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); font-family: inherit;
@@ -1428,40 +1447,27 @@ var CSS = `
1428
1447
  .dshsc-chip.legacy { color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-bg-layer-2); }
1429
1448
  .dshsc-chip.muted { color: var(--dsw-alias-label-tertiary); cursor: default; opacity: .8; }
1430
1449
 
1431
- .dshsc-policy-pop { position: fixed; z-index: 70; box-sizing: border-box; width: min(380px, calc(100vw - 16px));
1432
- max-height: min(600px, calc(100vh - 16px)); overflow: auto; padding: 14px;
1433
- background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: 14px;
1434
- box-shadow: 0 18px 48px rgba(0,0,0,.32), 0 2px 8px rgba(0,0,0,.16);
1435
- display: flex; flex-direction: column; gap: 10px; }
1436
- .dshsc-policy-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
1437
- .dshsc-policy-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
1438
- .dshsc-policy-sub { margin: 2px 0 0; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary); }
1439
- .dshsc-policy-close { flex: none; width: 24px; height: 24px; margin: -4px -4px 0 0; border: 0; border-radius: 6px;
1440
- background: transparent; color: var(--dsw-alias-label-secondary); font-size: 18px; line-height: 1; cursor: pointer; }
1441
- .dshsc-policy-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
1442
- .dshsc-policy-options { display: flex; flex-direction: column; gap: 6px; }
1443
- .dshsc-policyopt { box-sizing: border-box; display: flex; align-items: flex-start; gap: 10px; width: 100%;
1444
- padding: 9px 10px; text-align: left; border: 1px solid var(--dsw-alias-border-l1); border-radius: 9px;
1445
- background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-family: inherit; cursor: pointer; }
1446
- .dshsc-policyopt:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-border-l2); }
1447
- .dshsc-policyopt.active { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, var(--dsw-alias-border-l1));
1448
- background: color-mix(in srgb, var(--dsw-alias-brand-primary) 7%, transparent); }
1450
+ /* 调用策略:标准居中弹窗(Overlay)里的单选列表;行距 / 圆点尺寸对齐共享设置的 .dshsc-check */
1451
+ .dshsc-policy-options { max-height: none; overflow: visible; }
1452
+ .dshsc-policyopt { box-sizing: border-box; display: flex; align-items: flex-start; gap: 9px; width: 100%;
1453
+ padding: 5px 8px; text-align: left; border: 0; border-radius: 8px;
1454
+ background: transparent; color: var(--dsw-alias-label-primary); font-family: inherit; cursor: pointer; }
1455
+ .dshsc-policyopt:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2); }
1456
+ .dshsc-policyopt:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
1449
1457
  .dshsc-policyopt:disabled { cursor: not-allowed; opacity: .55; }
1450
1458
  .dshsc-policyopt.static { cursor: default; }
1451
- .dshsc-policyopt-dot { flex: none; width: 14px; height: 14px; margin-top: 1px; border-radius: 50%;
1452
- border: 1.5px solid var(--dsw-alias-border-l3); }
1453
- .dshsc-policyopt.active .dshsc-policyopt-dot { border-color: var(--dsw-alias-brand-primary);
1454
- background: radial-gradient(circle, var(--dsw-alias-brand-primary) 0 40%, transparent 47%); }
1459
+ .dshsc-policyopt.static:hover { background: transparent; }
1460
+ .dshsc-policyopt-dot { box-sizing: border-box; flex: none; width: 15px; height: 15px; margin-top: 2px;
1461
+ border-radius: 50%; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); }
1462
+ .dshsc-policyopt.active .dshsc-policyopt-dot { border: 5px solid var(--dsw-alias-brand-primary); }
1455
1463
  .dshsc-policyopt-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1456
- .dshsc-policyopt-body b { font-size: 12px; font-weight: 600; }
1464
+ .dshsc-policyopt-body b { font-size: 13px; font-weight: 600; }
1457
1465
  .dshsc-policyopt-body small { font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
1458
1466
  .dshsc-policy-note { padding: 7px 9px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2);
1459
1467
  color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 15px; }
1460
1468
  .dshsc-policy-note.warn, .dshsc-policy-note.author { color: var(--dsw-alias-state-warn-primary);
1461
1469
  background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 9%, transparent); }
1462
1470
  .dshsc-policy-hidden { display: flex; flex-direction: column; gap: 8px; }
1463
- .dshsc-policy-restore { align-self: flex-start; }
1464
- .dshsc-policy-foot { display: flex; justify-content: flex-end; gap: 6px; }
1465
1471
  .dshsc-policy-confirm { display: flex; flex-direction: column; gap: 6px; padding: 10px; border-radius: 9px;
1466
1472
  border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 34%, var(--dsw-alias-border-l1));
1467
1473
  background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 7%, transparent); }
@@ -1647,7 +1653,7 @@ var CSS = `
1647
1653
  .dshsc-info-sec dd { margin: 0; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
1648
1654
  .dshsc-info-sec dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
1649
1655
 
1650
- /* 列表行的 ✨ 介绍入口:只占一个小胶囊,不改变行高与列宽 */
1656
+ /* 列表行的 ✨ 简介入口:只占一个小胶囊,不改变行高与列宽 */
1651
1657
  .dshsc-intro-btn { flex: none; display: inline-flex; align-items: center; gap: 3px;
1652
1658
  height: 19px; padding: 0 7px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
1653
1659
  background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; font-size: 11px;
@@ -1657,15 +1663,15 @@ var CSS = `
1657
1663
  .dshsc-intro-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
1658
1664
  .dshsc-intro-btn .dshsc-spark { font-size: 10px; color: var(--dsw-alias-brand-primary); }
1659
1665
 
1660
- /* 介绍浮层:复用 policy 浮层的外壳(fixed + portal),只调内容排版 */
1661
- .dshsc-intro-pop { width: min(360px, calc(100vw - 16px)); gap: 7px; }
1666
+ /* 简介弹窗:和调用策略一样走标准居中弹窗(Overlay),这里只调内容排版 */
1667
+ .dshsc-intro-sub { margin: 0; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary);
1668
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1662
1669
  .dshsc-intro-text { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--dsw-alias-label-primary);
1663
1670
  white-space: pre-wrap; overflow-wrap: anywhere; }
1664
1671
  .dshsc-intro-note { margin: 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary); }
1665
1672
  .dshsc-intro-meta { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
1666
1673
  .dshsc-intro-stale { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-state-warn-primary); }
1667
1674
  .dshsc-intro-error { margin: 0; font-size: 11.5px; line-height: 16px; color: var(--dsw-alias-state-error-primary); }
1668
- .dshsc-intro-foot { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 3px; }
1669
1675
 
1670
1676
  /* 正文排版:只用 token,不写死深浅色 */
1671
1677
  .dshsc-md { font-size: 13.5px; line-height: 1.72; color: var(--dsw-alias-label-primary); }
@@ -2441,10 +2447,35 @@ function Toolbar({
2441
2447
  }
2442
2448
 
2443
2449
  // client/views/installed.js
2444
- var import_react5 = __toESM(require("react"), 1);
2450
+ var import_react4 = __toESM(require("react"), 1);
2445
2451
 
2446
- // client/views/intro-popover.js
2447
- var import_react3 = __toESM(require("react"), 1);
2452
+ // client/views/overlay.js
2453
+ function Overlay({ t, title, children, footer, onCancel }) {
2454
+ useOverlayLayer(true, onCancel);
2455
+ return portalize(h(
2456
+ "div",
2457
+ { className: "dshsc-overlay", onClick: (event) => {
2458
+ if (event.target === event.currentTarget) onCancel?.();
2459
+ } },
2460
+ h(
2461
+ "div",
2462
+ { className: "dshsc-modal", role: "dialog", "aria-modal": "true" },
2463
+ h("button", {
2464
+ className: "dshsc-modal-close",
2465
+ "aria-label": t("action.cancel"),
2466
+ onClick: (event) => {
2467
+ event.stopPropagation();
2468
+ onCancel?.();
2469
+ }
2470
+ }, "×"),
2471
+ h("h3", null, title),
2472
+ children,
2473
+ h("div", { className: "dshsc-modal-foot" }, footer)
2474
+ )
2475
+ ), overlayHost());
2476
+ }
2477
+
2478
+ // client/views/intro-modal.js
2448
2479
  var ERROR_KEY = {
2449
2480
  "error.skills.intro-no-model": "intro.error.noModel",
2450
2481
  "error.skills.intro-rate-limit": "intro.error.rateLimit",
@@ -2470,22 +2501,18 @@ function modelLabel(intro) {
2470
2501
  if (!intro?.model) return "—";
2471
2502
  return intro.provider ? `${intro.provider}/${intro.model}` : intro.model;
2472
2503
  }
2473
- function IntroPopover({
2504
+ function IntroModal({
2474
2505
  t,
2475
2506
  name,
2476
2507
  stamp = "",
2477
2508
  subtitle = null,
2478
2509
  endpoint = null,
2479
2510
  capability,
2480
- anchor,
2481
- anchorEl,
2482
2511
  hint = null,
2483
2512
  viewLabel = null,
2484
2513
  onClose,
2485
2514
  onView = null
2486
2515
  }) {
2487
- const ref = import_react3.default.useRef(null);
2488
- const [pos, setPos] = import_react3.default.useState({ left: 0, top: 0, visibility: "hidden" });
2489
2516
  const enabled = capability?.enabled !== false;
2490
2517
  const available = capability?.available !== false;
2491
2518
  const intro = useSkillIntro({
@@ -2495,32 +2522,6 @@ function IntroPopover({
2495
2522
  enabled,
2496
2523
  endpoint
2497
2524
  });
2498
- useOverlayLayer(true, onClose);
2499
- import_react3.default.useLayoutEffect(() => {
2500
- const node = ref.current;
2501
- if (!node || !anchor || typeof window === "undefined") return;
2502
- const rect = node.getBoundingClientRect();
2503
- const width = rect.width || 360;
2504
- const height = rect.height || 220;
2505
- let left = anchor.left;
2506
- left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
2507
- let top = anchor.bottom + 8;
2508
- if (top + height > window.innerHeight - 8) {
2509
- top = Math.max(8, anchor.top - height - 8);
2510
- }
2511
- setPos({ left, top, visibility: "visible" });
2512
- }, [anchor, intro.loading, intro.intro?.text, intro.generating, intro.genError]);
2513
- import_react3.default.useEffect(() => {
2514
- if (typeof document === "undefined") return void 0;
2515
- const onPointerDown = (event) => {
2516
- const target = event?.target;
2517
- if (ref.current?.contains?.(target)) return;
2518
- if (anchorEl?.contains?.(target)) return;
2519
- onClose();
2520
- };
2521
- document.addEventListener("pointerdown", onPointerDown, true);
2522
- return () => document.removeEventListener("pointerdown", onPointerDown, true);
2523
- }, [anchorEl, onClose]);
2524
2525
  const failure = intro.genError || intro.error;
2525
2526
  const failureKey = failure?.code ? ERROR_KEY[failure.code] || "intro.error.generic" : null;
2526
2527
  const meta = intro.intro ? t("intro.meta").replace("{model}", modelLabel(intro.intro)).replace("{time}", formatWhen(intro.intro.generatedAt) || "—") : "";
@@ -2554,75 +2555,30 @@ function IntroPopover({
2554
2555
  }
2555
2556
  }, intro.generating ? t("intro.generating") : t("intro.generate")));
2556
2557
  }
2557
- return portalize(h(
2558
- "div",
2558
+ return h(
2559
+ Overlay,
2559
2560
  {
2560
- ref,
2561
- className: "dshsc-policy-pop dshsc-intro-pop",
2562
- role: "dialog",
2563
- "aria-label": `${t("intro.title")} · ${name}`,
2564
- style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
2561
+ t,
2562
+ title: `✨ ${t("intro.title")}`,
2563
+ onCancel: onClose,
2564
+ footer: actions
2565
2565
  },
2566
- h(
2567
- "div",
2568
- { className: "dshsc-policy-head" },
2569
- h(
2570
- "div",
2571
- null,
2572
- h("div", { className: "dshsc-policy-title" }, `✨ ${t("intro.title")}`),
2573
- h("div", { className: "dshsc-policy-sub", title: subtitle || name }, subtitle || name)
2574
- ),
2575
- h("button", {
2576
- type: "button",
2577
- className: "dshsc-policy-close",
2578
- "aria-label": t("action.close"),
2579
- onClick: onClose
2580
- }, "×")
2581
- ),
2582
- intro.loading ? h("p", { className: "dshsc-intro-note" }, t("intro.loading")) : intro.intro ? h("p", { className: "dshsc-intro-text" }, intro.intro.text) : h("p", { className: "dshsc-intro-note" }, t("intro.empty")),
2583
- intro.intro?.stale ? h("p", { className: "dshsc-intro-stale" }, t("intro.stale")) : null,
2584
- failureKey ? h("p", { className: "dshsc-intro-error" }, t(failureKey)) : null,
2585
- meta ? h("p", { className: "dshsc-intro-meta" }, meta) : null,
2586
- available && !intro.intro && !intro.loading ? h("p", { className: "dshsc-intro-meta" }, hint || t("intro.hint")) : null,
2587
- !available ? h("p", { className: "dshsc-intro-error" }, t("intro.unavailable")) : null,
2588
- h("div", { className: "dshsc-intro-foot" }, actions)
2589
- ));
2566
+ h("p", { className: "dshsc-intro-sub", key: "sub", title: subtitle || name }, subtitle || name),
2567
+ intro.loading ? h("p", { className: "dshsc-intro-note", key: "loading" }, t("intro.loading")) : intro.intro ? h("p", { className: "dshsc-intro-text", key: "text" }, intro.intro.text) : h("p", { className: "dshsc-intro-note", key: "empty" }, t("intro.empty")),
2568
+ intro.intro?.stale ? h("p", { className: "dshsc-intro-stale", key: "stale" }, t("intro.stale")) : null,
2569
+ failureKey ? h("p", { className: "dshsc-intro-error", key: "error" }, t(failureKey)) : null,
2570
+ meta ? h("p", { className: "dshsc-intro-meta", key: "meta" }, meta) : null,
2571
+ available && !intro.intro && !intro.loading ? h("p", { className: "dshsc-intro-meta", key: "hint" }, hint || t("intro.hint")) : null,
2572
+ !available ? h("p", { className: "dshsc-intro-error", key: "unavailable" }, t("intro.unavailable")) : null
2573
+ );
2590
2574
  }
2591
2575
 
2592
- // client/views/policy-popover.js
2593
- var import_react4 = __toESM(require("react"), 1);
2594
- function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpark }) {
2576
+ // client/views/policy-modal.js
2577
+ var import_react3 = __toESM(require("react"), 1);
2578
+ function PolicyModal({ t, row, busy, onClose, onSave, onUnpark }) {
2595
2579
  const initial = row.mode === "hidden" ? "auto" : row.mode;
2596
- const [mode, setMode] = import_react4.default.useState(initial);
2597
- const [confirming, setConfirming] = import_react4.default.useState(false);
2598
- const ref = import_react4.default.useRef(null);
2599
- const [pos, setPos] = import_react4.default.useState({ left: 0, top: 0, visibility: "hidden" });
2600
- useOverlayLayer(true, onClose);
2601
- import_react4.default.useLayoutEffect(() => {
2602
- const node = ref.current;
2603
- if (!node || !anchor || typeof window === "undefined") return;
2604
- const rect = node.getBoundingClientRect();
2605
- const width = rect.width || 360;
2606
- const height = rect.height || 320;
2607
- let left = anchor.left + anchor.width / 2 - width / 2;
2608
- left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
2609
- let top = anchor.bottom + 8;
2610
- if (top + height > window.innerHeight - 8) {
2611
- top = Math.max(8, anchor.top - height - 8);
2612
- }
2613
- setPos({ left, top, visibility: "visible" });
2614
- }, [anchor, confirming, mode]);
2615
- import_react4.default.useEffect(() => {
2616
- if (typeof document === "undefined") return void 0;
2617
- const onPointerDown = (event) => {
2618
- const target = event?.target;
2619
- if (ref.current?.contains?.(target)) return;
2620
- if (anchorEl?.contains?.(target)) return;
2621
- onClose();
2622
- };
2623
- document.addEventListener("pointerdown", onPointerDown, true);
2624
- return () => document.removeEventListener("pointerdown", onPointerDown, true);
2625
- }, [anchorEl, onClose]);
2580
+ const [mode, setMode] = import_react3.default.useState(initial);
2581
+ const [confirming, setConfirming] = import_react3.default.useState(false);
2626
2582
  const hidden = row.mode === "hidden";
2627
2583
  const author = row.origin === "author" && row.mode === "manual";
2628
2584
  const flat = row.kind === "file";
@@ -2644,7 +2600,8 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
2644
2600
  type: "button",
2645
2601
  key: `policy-${id}`,
2646
2602
  className: `dshsc-policyopt${mode === id ? " active" : ""}`,
2647
- "aria-pressed": String(mode === id),
2603
+ role: "radio",
2604
+ "aria-checked": String(mode === id),
2648
2605
  disabled: busy || flat,
2649
2606
  onClick: () => choose(id)
2650
2607
  },
@@ -2656,95 +2613,85 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
2656
2613
  h("small", null, desc)
2657
2614
  )
2658
2615
  );
2659
- return portalize(h(
2660
- "div",
2616
+ const footer = confirming ? [
2617
+ h("button", {
2618
+ type: "button",
2619
+ key: "cancel",
2620
+ className: "dshsc-btn ghost",
2621
+ disabled: busy,
2622
+ onClick: () => setConfirming(false)
2623
+ }, t("action.cancel")),
2624
+ h("button", {
2625
+ type: "button",
2626
+ key: "confirm",
2627
+ className: "dshsc-btn danger",
2628
+ disabled: busy,
2629
+ onClick: () => onSave("hidden")
2630
+ }, busy ? t("policy.saving") : t("policy.hiddenConfirm"))
2631
+ ] : hidden ? [
2632
+ h("button", { type: "button", key: "cancel", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
2633
+ h("button", {
2634
+ type: "button",
2635
+ key: "restore",
2636
+ className: "dshsc-btn primary",
2637
+ disabled: busy,
2638
+ onClick: onUnpark
2639
+ }, t("policy.unpark"))
2640
+ ] : [
2641
+ h("button", { type: "button", key: "cancel", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
2642
+ h("button", {
2643
+ type: "button",
2644
+ key: "save",
2645
+ className: "dshsc-btn primary",
2646
+ disabled: busy || !dirty || flat,
2647
+ onClick: submit
2648
+ }, busy ? t("policy.saving") : t("action.save"))
2649
+ ];
2650
+ return h(
2651
+ Overlay,
2661
2652
  {
2662
- ref,
2663
- className: "dshsc-policy-pop",
2664
- role: "dialog",
2665
- "aria-label": t("policy.title"),
2666
- style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
2653
+ t,
2654
+ title: t("policy.title"),
2655
+ onCancel: onClose,
2656
+ footer
2667
2657
  },
2668
- h(
2669
- "div",
2670
- { className: "dshsc-policy-head" },
2671
- h(
2672
- "div",
2673
- null,
2674
- h("div", { className: "dshsc-policy-title" }, t("policy.title")),
2675
- h("div", { className: "dshsc-policy-sub" }, t("policy.subtitle"))
2676
- ),
2677
- h("button", {
2678
- type: "button",
2679
- className: "dshsc-policy-close",
2680
- "aria-label": t("action.close"),
2681
- onClick: onClose
2682
- }, "×")
2683
- ),
2684
- author ? h("div", { className: "dshsc-policy-note author" }, t("policy.authorHint")) : null,
2685
- row.legacy ? h("div", { className: "dshsc-policy-note" }, t("policy.legacyHint")) : null,
2686
- row.drift ? h("div", { className: "dshsc-policy-note warn" }, t("policy.driftHint")) : null,
2658
+ h("p", { className: "dshsc-sub", key: "sub" }, t("policy.subtitle")),
2659
+ author ? h("div", { className: "dshsc-policy-note author", key: "author" }, t("policy.authorHint")) : null,
2660
+ row.legacy ? h("div", { className: "dshsc-policy-note", key: "legacy" }, t("policy.legacyHint")) : null,
2661
+ row.drift ? h("div", { className: "dshsc-policy-note warn", key: "drift" }, t("policy.driftHint")) : null,
2687
2662
  hidden ? h(
2688
2663
  "div",
2689
- { className: "dshsc-policy-hidden" },
2664
+ { className: "dshsc-policy-hidden", key: "hidden" },
2690
2665
  h(
2691
2666
  "div",
2692
- { className: "dshsc-policyopt active static" },
2693
- h("span", { className: "dshsc-policyopt-dot" }),
2667
+ { className: "dshsc-panel" },
2694
2668
  h(
2695
- "span",
2696
- { className: "dshsc-policyopt-body" },
2697
- h("b", null, t("policy.hidden")),
2698
- h("small", null, t("policy.hiddenDesc"))
2669
+ "div",
2670
+ { className: "dshsc-policyopt active static", role: "radio", "aria-checked": "true" },
2671
+ h("span", { className: "dshsc-policyopt-dot", "aria-hidden": "true" }),
2672
+ h(
2673
+ "span",
2674
+ { className: "dshsc-policyopt-body" },
2675
+ h("b", null, t("policy.hidden")),
2676
+ h("small", null, t("policy.hiddenDesc"))
2677
+ )
2699
2678
  )
2700
2679
  ),
2701
- h("button", {
2702
- type: "button",
2703
- className: "dshsc-btn primary dshsc-policy-restore",
2704
- disabled: busy,
2705
- onClick: onUnpark
2706
- }, t("policy.unpark")),
2707
- h("p", { className: "dshsc-policy-sub" }, t("policy.hiddenHint"))
2680
+ h("p", { className: "dshsc-sub" }, t("policy.hiddenHint"))
2708
2681
  ) : h(
2709
2682
  "div",
2710
- { className: "dshsc-policy-options" },
2683
+ { className: "dshsc-panel dshsc-policy-options", key: "options", role: "radiogroup", "aria-label": t("policy.title") },
2711
2684
  option("auto", t("policy.auto"), t("policy.autoDesc")),
2712
2685
  option("manual", t("policy.manual"), t("policy.manualDesc")),
2713
2686
  option("hidden", t("policy.hidden"), t("policy.hiddenDesc"))
2714
2687
  ),
2715
2688
  confirming ? h(
2716
2689
  "div",
2717
- { className: "dshsc-policy-confirm" },
2690
+ { className: "dshsc-policy-confirm", key: "confirm" },
2718
2691
  h("b", null, t("policy.hiddenConfirmTitle")),
2719
- h("p", null, t("policy.hiddenConfirmBody")),
2720
- h(
2721
- "div",
2722
- { className: "dshsc-policy-foot" },
2723
- h("button", {
2724
- type: "button",
2725
- className: "dshsc-btn ghost",
2726
- disabled: busy,
2727
- onClick: () => setConfirming(false)
2728
- }, t("action.cancel")),
2729
- h("button", {
2730
- type: "button",
2731
- className: "dshsc-btn danger",
2732
- disabled: busy,
2733
- onClick: () => onSave("hidden")
2734
- }, busy ? t("policy.saving") : t("policy.hiddenConfirm"))
2735
- )
2736
- ) : h(
2737
- "div",
2738
- { className: "dshsc-policy-foot" },
2739
- h("button", { type: "button", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
2740
- hidden ? null : h("button", {
2741
- type: "button",
2742
- className: "dshsc-btn primary",
2743
- disabled: busy || !dirty || flat,
2744
- onClick: submit
2745
- }, busy ? t("policy.saving") : t("action.save"))
2746
- )
2747
- ), overlayHost());
2692
+ h("p", null, t("policy.hiddenConfirmBody"))
2693
+ ) : null
2694
+ );
2748
2695
  }
2749
2696
 
2750
2697
  // client/views/installed.js
@@ -2766,38 +2713,26 @@ function InstalledTab({
2766
2713
  actions
2767
2714
  }) {
2768
2715
  const updatable = (row) => UPDATABLE.has(row.status);
2769
- const [policy, setPolicy] = import_react5.default.useState(null);
2770
- const [intro, setIntro] = import_react5.default.useState(null);
2716
+ const [policy, setPolicy] = import_react4.default.useState(null);
2717
+ const [intro, setIntro] = import_react4.default.useState(null);
2771
2718
  const sharedTargets = new Set((data?.agents?.detected || []).filter((agent) => agent.kind === "agent" && agent.sharedHome === "yes").map((agent) => agent.target));
2772
- import_react5.default.useEffect(() => {
2719
+ import_react4.default.useEffect(() => {
2773
2720
  if (!policy) return;
2774
2721
  if (rows.some((row) => row.name === policy.row.name)) return;
2775
2722
  setPolicy(null);
2776
2723
  }, [policy, rows]);
2777
- import_react5.default.useEffect(() => {
2724
+ import_react4.default.useEffect(() => {
2778
2725
  if (!intro) return;
2779
2726
  if (rows.some((row) => row.name === intro.row.name)) return;
2780
2727
  setIntro(null);
2781
2728
  }, [intro, rows]);
2782
- function openIntro(event, row) {
2783
- const el = event.currentTarget;
2784
- const rect = el.getBoundingClientRect();
2785
- setIntro({
2786
- row,
2787
- anchorEl: el,
2788
- anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
2789
- });
2729
+ function openIntro(row) {
2730
+ setIntro({ row });
2790
2731
  }
2791
- function openPolicy(event, row) {
2732
+ function openPolicy(row) {
2792
2733
  if (busy !== null || row.kind === "file") return;
2793
2734
  if (row.mode === "manual" && row.origin === "author") return;
2794
- const el = event.currentTarget;
2795
- const rect = el.getBoundingClientRect();
2796
- setPolicy({
2797
- row,
2798
- anchorEl: el,
2799
- anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
2800
- });
2735
+ setPolicy({ row });
2801
2736
  }
2802
2737
  function policyChip(row) {
2803
2738
  if (row.status === "unmanaged") {
@@ -2814,7 +2749,7 @@ function InstalledTab({
2814
2749
  disabled: busy !== null,
2815
2750
  title: t("policy.hiddenHint"),
2816
2751
  "aria-haspopup": "dialog",
2817
- onClick: (event) => openPolicy(event, row)
2752
+ onClick: () => openPolicy(row)
2818
2753
  }, t("policy.hidden"));
2819
2754
  }
2820
2755
  if (mode === "manual" && row.origin === "author") {
@@ -2830,7 +2765,7 @@ function InstalledTab({
2830
2765
  disabled: busy !== null,
2831
2766
  title: t("policy.driftHint"),
2832
2767
  "aria-haspopup": "dialog",
2833
- onClick: (event) => openPolicy(event, row)
2768
+ onClick: () => openPolicy(row)
2834
2769
  }, t("policy.drift"));
2835
2770
  }
2836
2771
  if (row.legacy === "dsh-off") {
@@ -2840,7 +2775,7 @@ function InstalledTab({
2840
2775
  disabled: busy !== null,
2841
2776
  title: t("policy.legacyHint"),
2842
2777
  "aria-haspopup": "dialog",
2843
- onClick: (event) => openPolicy(event, row)
2778
+ onClick: () => openPolicy(row)
2844
2779
  }, t("policy.legacy"));
2845
2780
  }
2846
2781
  if (mode === "manual") {
@@ -2850,7 +2785,7 @@ function InstalledTab({
2850
2785
  disabled: busy !== null,
2851
2786
  title: t("policy.manualOnlyHint"),
2852
2787
  "aria-haspopup": "dialog",
2853
- onClick: (event) => openPolicy(event, row)
2788
+ onClick: () => openPolicy(row)
2854
2789
  }, t("policy.manual"));
2855
2790
  }
2856
2791
  return h("button", {
@@ -2859,7 +2794,7 @@ function InstalledTab({
2859
2794
  disabled: busy !== null,
2860
2795
  title: t("policy.open"),
2861
2796
  "aria-haspopup": "dialog",
2862
- onClick: (event) => openPolicy(event, row)
2797
+ onClick: () => openPolicy(row)
2863
2798
  }, t("policy.auto"));
2864
2799
  }
2865
2800
  function statusTag(row) {
@@ -2988,7 +2923,7 @@ function InstalledTab({
2988
2923
  className: "dshsc-intro-btn",
2989
2924
  title: t("intro.open"),
2990
2925
  "aria-haspopup": "dialog",
2991
- onClick: (event) => openIntro(event, row)
2926
+ onClick: () => openIntro(row)
2992
2927
  },
2993
2928
  h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
2994
2929
  t("intro.button")
@@ -3242,12 +3177,10 @@ function InstalledTab({
3242
3177
  )
3243
3178
  );
3244
3179
  }
3245
- const introPopover = intro && data?.intro ? h(IntroPopover, {
3180
+ const introModal = intro && data?.intro ? h(IntroModal, {
3246
3181
  t,
3247
3182
  name: intro.row.name,
3248
3183
  stamp: [intro.row.commit || "", intro.row.currentHash || "", intro.row.updatedAt || "", intro.row.status || ""].join("|"),
3249
- anchor: intro.anchor,
3250
- anchorEl: intro.anchorEl,
3251
3184
  capability: data.intro,
3252
3185
  onClose: () => setIntro(null),
3253
3186
  onView: () => {
@@ -3256,11 +3189,9 @@ function InstalledTab({
3256
3189
  actions.onView(target);
3257
3190
  }
3258
3191
  }) : null;
3259
- const popover = policy ? h(PolicyPopover, {
3192
+ const policyModal = policy ? h(PolicyModal, {
3260
3193
  t,
3261
3194
  row: policy.row,
3262
- anchor: policy.anchor,
3263
- anchorEl: policy.anchorEl,
3264
3195
  busy: busy !== null,
3265
3196
  onClose: () => setPolicy(null),
3266
3197
  onSave: (mode) => {
@@ -3274,7 +3205,7 @@ function InstalledTab({
3274
3205
  actions.onRequestUnpark(target);
3275
3206
  }
3276
3207
  }) : null;
3277
- return h(import_react5.default.Fragment, null, content(), popover, introPopover);
3208
+ return h(import_react4.default.Fragment, null, content(), policyModal, introModal);
3278
3209
  }
3279
3210
 
3280
3211
  // client/views/agent-tip.js
@@ -3309,7 +3240,7 @@ function AgentTip({ t, agentTip, agentTipPos, nodeRef }) {
3309
3240
  }
3310
3241
 
3311
3242
  // client/views/subscriptions.js
3312
- var import_react6 = __toESM(require("react"), 1);
3243
+ var import_react5 = __toESM(require("react"), 1);
3313
3244
  function repoOf(source) {
3314
3245
  const value = String(source || "").trim().toLowerCase().replace(/\.git$/, "");
3315
3246
  return value || null;
@@ -3331,22 +3262,18 @@ function SubscriptionsTab({
3331
3262
  onInstall,
3332
3263
  onAdd
3333
3264
  }) {
3334
- const [intro, setIntro] = import_react6.default.useState(null);
3335
- import_react6.default.useEffect(() => {
3265
+ const [intro, setIntro] = import_react5.default.useState(null);
3266
+ import_react5.default.useEffect(() => {
3336
3267
  if (!intro) return;
3337
3268
  const subscription = subscriptions.find((item) => item.id === intro.subscriptionId);
3338
3269
  const entries = subscription?.snapshot?.entries || [];
3339
3270
  if (entries.some((entry) => entry.name === intro.name)) return;
3340
3271
  setIntro(null);
3341
3272
  }, [intro, subscriptions]);
3342
- function openIntro(event, subscription, entry) {
3343
- const el = event.currentTarget;
3344
- const rect = el.getBoundingClientRect();
3273
+ function openIntro(subscription, entry) {
3345
3274
  setIntro({
3346
3275
  subscriptionId: subscription.id,
3347
- name: entry.name,
3348
- anchorEl: el,
3349
- anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
3276
+ name: entry.name
3350
3277
  });
3351
3278
  }
3352
3279
  function introButton(subscription, entry) {
@@ -3357,7 +3284,7 @@ function SubscriptionsTab({
3357
3284
  className: "dshsc-intro-btn",
3358
3285
  title: t("intro.open"),
3359
3286
  "aria-haspopup": "dialog",
3360
- onClick: (clickEvent) => openIntro(clickEvent, subscription, entry)
3287
+ onClick: () => openIntro(subscription, entry)
3361
3288
  },
3362
3289
  h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
3363
3290
  t("intro.button")
@@ -3365,22 +3292,20 @@ function SubscriptionsTab({
3365
3292
  }
3366
3293
  const introSubscription = intro ? subscriptions.find((item) => item.id === intro.subscriptionId) || null : null;
3367
3294
  const introEntry = introSubscription ? (introSubscription.snapshot?.entries || []).find((entry) => entry.name === intro.name) || null : null;
3368
- const introPopover = intro && introEntry && capability ? (() => {
3295
+ const introModal = intro && introEntry && capability ? (() => {
3369
3296
  const commit = introSubscription.snapshot?.commit || "";
3370
3297
  const installedAlready = rows.some((row) => {
3371
3298
  if (row.name !== introEntry.name) return false;
3372
3299
  if (row.subscriptionId === introSubscription.id) return true;
3373
3300
  return repoOf(row.source) === repoOf(introSubscription.source);
3374
3301
  });
3375
- return h(IntroPopover, {
3302
+ return h(IntroModal, {
3376
3303
  t,
3377
3304
  name: introEntry.name,
3378
3305
  stamp: commit,
3379
3306
  subtitle: `${introSubscription.source}@${shortCommit(commit)}`,
3380
3307
  endpoint: `/subscriptions/${encodeURIComponent(introSubscription.id)}/entries/${encodeURIComponent(introEntry.name)}/intro`,
3381
3308
  capability,
3382
- anchor: intro.anchor,
3383
- anchorEl: intro.anchorEl,
3384
3309
  hint: t("intro.hintRemote").replace("{source}", introSubscription.source).replace("{commit}", shortCommit(commit) || "—"),
3385
3310
  viewLabel: t("action.install"),
3386
3311
  onView: installedAlready ? null : () => {
@@ -3412,7 +3337,7 @@ function SubscriptionsTab({
3412
3337
  );
3413
3338
  }
3414
3339
  return h(
3415
- import_react6.default.Fragment,
3340
+ import_react5.default.Fragment,
3416
3341
  null,
3417
3342
  h(
3418
3343
  "div",
@@ -3511,7 +3436,6 @@ function SubscriptionsTab({
3511
3436
  h(
3512
3437
  "span",
3513
3438
  { className: `dshsc-state${installedAlready ? " ok" : ""}` },
3514
- installedAlready ? "✓ " : "",
3515
3439
  installedAlready ? t("installed") : t("notInstalled")
3516
3440
  ),
3517
3441
  h(
@@ -3530,51 +3454,23 @@ function SubscriptionsTab({
3530
3454
  })
3531
3455
  )
3532
3456
  ),
3533
- introPopover
3457
+ introModal
3534
3458
  );
3535
3459
  }
3536
3460
 
3537
3461
  // client/views/modals.js
3538
- var import_react8 = __toESM(require("react"), 1);
3539
-
3540
- // client/views/add-skill.js
3541
3462
  var import_react7 = __toESM(require("react"), 1);
3542
3463
 
3543
- // client/views/overlay.js
3544
- function Overlay({ t, title, children, footer, onCancel }) {
3545
- useOverlayLayer(true, onCancel);
3546
- return portalize(h(
3547
- "div",
3548
- { className: "dshsc-overlay", onClick: (event) => {
3549
- if (event.target === event.currentTarget) onCancel?.();
3550
- } },
3551
- h(
3552
- "div",
3553
- { className: "dshsc-modal", role: "dialog", "aria-modal": "true" },
3554
- h("button", {
3555
- className: "dshsc-modal-close",
3556
- "aria-label": t("action.cancel"),
3557
- onClick: (event) => {
3558
- event.stopPropagation();
3559
- onCancel?.();
3560
- }
3561
- }, "×"),
3562
- h("h3", null, title),
3563
- children,
3564
- h("div", { className: "dshsc-modal-foot" }, footer)
3565
- )
3566
- ), overlayHost());
3567
- }
3568
-
3569
3464
  // client/views/add-skill.js
3465
+ var import_react6 = __toESM(require("react"), 1);
3570
3466
  function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
3571
- const [source, setSource] = import_react7.default.useState("");
3572
- const [phase, setPhase] = import_react7.default.useState("idle");
3573
- const [options, setOptions] = import_react7.default.useState([]);
3574
- const [selected, setSelected] = import_react7.default.useState(() => /* @__PURE__ */ new Set());
3575
- const [manual, setManual] = import_react7.default.useState("");
3576
- const [skipped, setSkipped] = import_react7.default.useState(0);
3577
- const [error, setError] = import_react7.default.useState(null);
3467
+ const [source, setSource] = import_react6.default.useState("");
3468
+ const [phase, setPhase] = import_react6.default.useState("idle");
3469
+ const [options, setOptions] = import_react6.default.useState([]);
3470
+ const [selected, setSelected] = import_react6.default.useState(() => /* @__PURE__ */ new Set());
3471
+ const [manual, setManual] = import_react6.default.useState("");
3472
+ const [skipped, setSkipped] = import_react6.default.useState(0);
3473
+ const [error, setError] = import_react6.default.useState(null);
3578
3474
  const reset = (value) => {
3579
3475
  setSource(value);
3580
3476
  setPhase("idle");
@@ -3710,7 +3606,7 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
3710
3606
 
3711
3607
  // client/views/modals.js
3712
3608
  function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
3713
- const [form, setForm] = import_react8.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
3609
+ const [form, setForm] = import_react7.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
3714
3610
  const incomplete = fields.some(
3715
3611
  (field) => field.required !== false && !String(form[field.key] || "").trim()
3716
3612
  );
@@ -3758,7 +3654,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
3758
3654
  const candidates = agents.filter(
3759
3655
  (agent) => agent.kind === "agent" && agent.exists && agent.exportable
3760
3656
  );
3761
- const [targets, setTargets] = import_react8.default.useState(() => {
3657
+ const [targets, setTargets] = import_react7.default.useState(() => {
3762
3658
  const available = new Set(candidates.map((agent) => agent.target));
3763
3659
  return new Set((row.exports || []).map((item) => item.target).filter((target) => available.has(target)));
3764
3660
  });
@@ -3800,7 +3696,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
3800
3696
  );
3801
3697
  }
3802
3698
  function DefaultExportModal({ t, settings, agents, onCancel, onSave }) {
3803
- const [targets, setTargets] = import_react8.default.useState(() => new Set(
3699
+ const [targets, setTargets] = import_react7.default.useState(() => new Set(
3804
3700
  Array.isArray(settings.exportTargets) ? settings.exportTargets : []
3805
3701
  ));
3806
3702
  const toggle = (target) => setTargets((prev) => {
@@ -3929,7 +3825,7 @@ ${conflicts.map((item) => `${item.label}: ${item.linkPath}`).join("\n")}`,
3929
3825
  }
3930
3826
 
3931
3827
  // client/views/skill-reader.js
3932
- var import_react9 = __toESM(require("react"), 1);
3828
+ var import_react8 = __toESM(require("react"), 1);
3933
3829
 
3934
3830
  // client/markdown.js
3935
3831
  function classifyHref(href) {
@@ -4426,17 +4322,17 @@ function SkillReader({
4426
4322
  }) {
4427
4323
  const { state, file, loadFile, retry } = useSkillReader(row);
4428
4324
  const name = row?.name || "";
4429
- const [tab, setTab] = import_react9.default.useState(initialTab);
4430
- const [format, setFormat] = import_react9.default.useState("rendered");
4431
- const [docOverride, setDocOverride] = import_react9.default.useState(null);
4432
- const [fileFormat, setFileFormat] = import_react9.default.useState("rendered");
4433
- const [tocActive, setTocActive] = import_react9.default.useState(null);
4434
- const [descOpen, setDescOpen] = import_react9.default.useState(false);
4435
- const [descClamped, setDescClamped] = import_react9.default.useState(false);
4436
- const bodyRef = import_react9.default.useRef(null);
4437
- const dialogRef = import_react9.default.useRef(null);
4438
- const descRef = import_react9.default.useRef(null);
4439
- const scrollRef = import_react9.default.useRef(0);
4325
+ const [tab, setTab] = import_react8.default.useState(initialTab);
4326
+ const [format, setFormat] = import_react8.default.useState("rendered");
4327
+ const [docOverride, setDocOverride] = import_react8.default.useState(null);
4328
+ const [fileFormat, setFileFormat] = import_react8.default.useState("rendered");
4329
+ const [tocActive, setTocActive] = import_react8.default.useState(null);
4330
+ const [descOpen, setDescOpen] = import_react8.default.useState(false);
4331
+ const [descClamped, setDescClamped] = import_react8.default.useState(false);
4332
+ const bodyRef = import_react8.default.useRef(null);
4333
+ const dialogRef = import_react8.default.useRef(null);
4334
+ const descRef = import_react8.default.useRef(null);
4335
+ const scrollRef = import_react8.default.useRef(0);
4440
4336
  useOverlayLayer(variant === "drawer" || variant === "page", onClose);
4441
4337
  const tree = state.tree;
4442
4338
  const files = tree?.files || [];
@@ -4444,18 +4340,18 @@ function SkillReader({
4444
4340
  const bodyText = docOverride ? docOverride.content : state.body?.content || "";
4445
4341
  const description = row?.description || state.body?.meta?.description || "";
4446
4342
  const headings = tab === "doc" && bodyText ? parseCached(bodyText).headings : [];
4447
- import_react9.default.useEffect(() => {
4343
+ import_react8.default.useEffect(() => {
4448
4344
  setDocOverride(null);
4449
4345
  scrollRef.current = 0;
4450
4346
  }, [name]);
4451
- const descOwnerRef = import_react9.default.useRef(name);
4452
- import_react9.default.useEffect(() => {
4347
+ const descOwnerRef = import_react8.default.useRef(name);
4348
+ import_react8.default.useEffect(() => {
4453
4349
  if (descOwnerRef.current === name) return;
4454
4350
  descOwnerRef.current = name;
4455
4351
  setDescOpen(false);
4456
4352
  setDescClamped(false);
4457
4353
  }, [name]);
4458
- import_react9.default.useLayoutEffect(() => {
4354
+ import_react8.default.useLayoutEffect(() => {
4459
4355
  const node = descRef.current;
4460
4356
  if (!node) return void 0;
4461
4357
  const measure = () => {
@@ -4477,16 +4373,16 @@ function SkillReader({
4477
4373
  window.removeEventListener("resize", measure);
4478
4374
  };
4479
4375
  }, [description, descOpen]);
4480
- import_react9.default.useEffect(() => {
4376
+ import_react8.default.useEffect(() => {
4481
4377
  if (!initialFile) return;
4482
4378
  setTab("files");
4483
4379
  void loadFile(initialFile);
4484
4380
  }, [initialFile, loadFile]);
4485
- import_react9.default.useEffect(() => {
4381
+ import_react8.default.useEffect(() => {
4486
4382
  const node = bodyRef.current;
4487
4383
  if (node && scrollRef.current) node.scrollTop = scrollRef.current;
4488
4384
  });
4489
- import_react9.default.useEffect(() => {
4385
+ import_react8.default.useEffect(() => {
4490
4386
  if (variant !== "drawer" || typeof document === "undefined") return void 0;
4491
4387
  const previous = document.activeElement;
4492
4388
  const node = dialogRef.current;
@@ -4513,12 +4409,12 @@ function SkillReader({
4513
4409
  previous?.focus?.();
4514
4410
  };
4515
4411
  }, [variant, onClose]);
4516
- import_react9.default.useEffect(() => {
4412
+ import_react8.default.useEffect(() => {
4517
4413
  if (variant !== "page" || typeof document === "undefined") return;
4518
4414
  const node = document.getElementById("dshsc-reader-name");
4519
4415
  node?.focus?.();
4520
4416
  }, [variant, name]);
4521
- import_react9.default.useEffect(() => {
4417
+ import_react8.default.useEffect(() => {
4522
4418
  if (variant !== "page" || typeof window === "undefined") return void 0;
4523
4419
  const node = bodyRef.current;
4524
4420
  if (!node?.querySelectorAll) return void 0;
@@ -4956,19 +4852,19 @@ function SkillReader({
4956
4852
  var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
4957
4853
  function Section({ t, page = false, pickDirectory, onPlacementChange }) {
4958
4854
  const { data, error, busy, toast, setToast, setBusy, reload, run } = useSkillsData();
4959
- const [tab, setTab] = import_react10.default.useState("installed");
4960
- const [view, setView] = import_react10.default.useState("flat");
4961
- const [modal, setModal] = import_react10.default.useState(null);
4962
- const [expanded, setExpanded] = import_react10.default.useState({});
4963
- const [placementBusy, setPlacementBusy] = import_react10.default.useState(null);
4964
- const [placementOverride, setPlacementOverride] = import_react10.default.useState(null);
4965
- const [query, setQuery] = import_react10.default.useState("");
4966
- const [viewer, setViewer] = import_react10.default.useState(null);
4967
- const rootRef = import_react10.default.useRef(null);
4968
- const scrollPos = import_react10.default.useRef(null);
4969
- const restoreScroll = import_react10.default.useRef(null);
4970
- const [filter, setFilter] = import_react10.default.useState("all");
4971
- const [builtinOpen, setBuiltinOpen] = import_react10.default.useState(false);
4855
+ const [tab, setTab] = import_react9.default.useState("installed");
4856
+ const [view, setView] = import_react9.default.useState("flat");
4857
+ const [modal, setModal] = import_react9.default.useState(null);
4858
+ const [expanded, setExpanded] = import_react9.default.useState({});
4859
+ const [placementBusy, setPlacementBusy] = import_react9.default.useState(null);
4860
+ const [placementOverride, setPlacementOverride] = import_react9.default.useState(null);
4861
+ const [query, setQuery] = import_react9.default.useState("");
4862
+ const [viewer, setViewer] = import_react9.default.useState(null);
4863
+ const rootRef = import_react9.default.useRef(null);
4864
+ const scrollPos = import_react9.default.useRef(null);
4865
+ const restoreScroll = import_react9.default.useRef(null);
4866
+ const [filter, setFilter] = import_react9.default.useState("all");
4867
+ const [builtinOpen, setBuiltinOpen] = import_react9.default.useState(false);
4972
4868
  const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
4973
4869
  const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
4974
4870
  const rows = data?.installs || [];
@@ -4991,7 +4887,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
4991
4887
  const agentLabels = row.exports.map((item) => item.label);
4992
4888
  return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
4993
4889
  });
4994
- import_react10.default.useEffect(() => {
4890
+ import_react9.default.useEffect(() => {
4995
4891
  if (!viewer || !data) return;
4996
4892
  if (rows.some((row) => row.name === viewer.name)) return;
4997
4893
  setViewer(null);
@@ -5025,7 +4921,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
5025
4921
  if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
5026
4922
  setViewer(null);
5027
4923
  }
5028
- import_react10.default.useLayoutEffect(() => {
4924
+ import_react9.default.useLayoutEffect(() => {
5029
4925
  if (restoreScroll.current === null) return;
5030
4926
  const target = restoreScroll.current;
5031
4927
  restoreScroll.current = null;
@@ -5094,7 +4990,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
5094
4990
  onToast: (next) => setToast(next)
5095
4991
  }) : null;
5096
4992
  const listNode = h(
5097
- import_react10.default.Fragment,
4993
+ import_react9.default.Fragment,
5098
4994
  null,
5099
4995
  h(Header, {
5100
4996
  t,