dsh-session-manager 0.5.4 → 0.6.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/lib/client.js CHANGED
@@ -21,7 +21,17 @@ window.__ModuleLoader__.load({
21
21
  const P = require("@deepseek-ai/dsh-client-ui-primitives");
22
22
  if (typeof window !== "undefined") {
23
23
  window.__smReact = React;
24
- window.__smReactDOM = React.createPortal ? React : null;
24
+ let rdomModule = null;
25
+ try { rdomModule = require("react-dom"); } catch (_) {}
26
+ if (!rdomModule) {
27
+ try { rdomModule = require("react-dom/client"); } catch (_) {}
28
+ }
29
+ if (!rdomModule && typeof window.ReactDOM !== "undefined") {
30
+ rdomModule = window.ReactDOM;
31
+ }
32
+ window.__smReactDOM = (rdomModule && typeof rdomModule.createPortal === "function")
33
+ ? rdomModule
34
+ : (typeof React.createPortal === "function" ? React : null);
25
35
  // Tiny imperative portal helper: takes a React element + a host
26
36
  // DOM node, creates a React root on the host (lazily), and re-renders
27
37
  // the element into it on each call. Used by ConfirmDialog to escape
@@ -41,16 +51,36 @@ window.__ModuleLoader__.load({
41
51
  }
42
52
  // Monotonically-increasing z-index for popup dialogs so the most-recently
43
53
  // opened popup always sits on top of any previously opened popup. The
44
- // CSS-defined z-index values (panel 9999 / confirm 10000 / annotation +
54
+ // CSS-defined z-index values (confirm 10000 / migrate 10001 / annotation +
45
55
  // bulk 10002) are static; without a counter the user can open an
46
56
  // annotation popup (10002) after a confirm popup (10000) and the older
47
57
  // confirm stays below. Each dialog component claims a fresh slot from
48
58
  // this counter on mount, writes it to inline style.zIndex, and never
49
59
  // recycles within a session, so the most-recently opened popup is
50
60
  // always the topmost one.
51
- let dialogZStackCounter = 11000;
61
+ // These values are all resolved INSIDE the #dsh-session-manager-overlay-root
62
+ // stacking context (see the layering contract in the stylesheet): the root
63
+ // itself sits at 1050, below DSH's 1100 floating tier, so the absolute size of
64
+ // the numbers here never competes with DSH's own tooltips and menus.
65
+ let dialogZStackCounter = 100000;
52
66
  function nextDialogZ() { return ++dialogZStackCounter; }
53
67
 
68
+ // Wrap an anchor in DSH's own Tooltip primitive (falls back to the bare
69
+ // node when the primitive is unavailable).
70
+ //
71
+ // `portal: true` renders the bubble under document.body, which escapes the
72
+ // plugin dialog's own overflow clipping -- but DSH's bubble paints at a
73
+ // hard-coded z-index 1100 and exposes no class hook, so it can only stay
74
+ // visible while every plugin surface stays BELOW 1100. Keep the panel
75
+ // (1020) and the overlay root (1050) inside that gap; see the layering
76
+ // contract in the stylesheet.
77
+ function renderTip(label, node, side = "bottom", portal = true) {
78
+ if (!label || !node) return node;
79
+ if (P && typeof P.Tooltip === "function") {
80
+ return h(P.Tooltip, { label, side, portal, align: "center" }, node);
81
+ }
82
+ return node;
83
+ }
54
84
 
55
85
  const NS = "session-manager";
56
86
  const API = "/session-manager/api";
@@ -119,6 +149,7 @@ function nextDialogZ() { return ++dialogZStackCounter; }
119
149
  "marks.tags.clear": "清空标签",
120
150
  "marks.note.clear": "清空备注",
121
151
  "header.aria": "会话操作",
152
+ "header.more": "更多操作",
122
153
  "menu.open": "打开",
123
154
  "menu.archive": "归档会话",
124
155
  "menu.unarchive": "移出归档",
@@ -129,6 +160,42 @@ function nextDialogZ() { return ++dialogZStackCounter; }
129
160
  "footer.label": "会话管理",
130
161
  "panel.title": "会话管理",
131
162
  "panel.github.title": "欢迎Star和提交Issue",
163
+ "panel.update.title": "检查更新",
164
+ "panel.update.badge": "有新版本可用",
165
+ "update.title": "检查更新",
166
+ "update.newVersion": "发现新版本",
167
+ "update.currentVersion": "当前版本",
168
+ "update.latestVersion": "最新版本",
169
+ "update.isLatest": "当前已是最新版本",
170
+ "update.checking": "正在检查更新…",
171
+ "update.installing": "正在通过插件管理器安装新版本…",
172
+ "update.btn.check": "检查更新",
173
+ "update.btn.update": "更新到 v{version}",
174
+ "update.btn.updating": "更新中…",
175
+ "update.btn.later": "稍后",
176
+ "update.btn.done": "好的",
177
+ "update.btn.retry": "重试",
178
+ "update.btn.close": "关闭",
179
+ "update.done.restart": "更新已安装(v{from} → v{to}),重启 DSH 后生效。",
180
+ "update.done.applied": "更新已应用!",
181
+ "update.error": "更新遇到问题",
182
+ "update.autoCheck": "自动检查更新",
183
+ "update.autoCheck.enable": "开启",
184
+ "settings.registry": "安装源",
185
+ "settings.registry.npm": "npm 官方源 registry.npmjs.org",
186
+ "settings.registry.china": "中国大陆镜像源 registry.npmmirror.com",
187
+ "settings.title": "会话管理",
188
+ "settings.pluginName": "dsh-session-manager",
189
+ "settings.versionSection": "插件版本",
190
+ "settings.prefSection": "偏好设置",
191
+ "settings.dialogOpacity": "弹窗透明度",
192
+ "settings.dialogOpacity.hint": "仅调整插件弹窗的背景透明度;文字、按钮、输入框和边框保持不透明。",
193
+ "settings.dialogOpacity.value": "{value}%",
194
+ "settings.dialogOpacity.reset": "恢复默认",
195
+ "settings.projectSection": "项目",
196
+ "settings.github": "GitHub 仓库",
197
+ "settings.star": "感谢Star",
198
+ "settings.feedback": "欢迎反馈",
132
199
  "panel.search": "搜索标题、ID、标签或备注",
133
200
  "panel.workspace": "工作区",
134
201
  "panel.workspace.all": "全部工作区",
@@ -303,6 +370,7 @@ function nextDialogZ() { return ++dialogZStackCounter; }
303
370
  "marks.tags.clear": "Clear tags",
304
371
  "marks.note.clear": "Clear note",
305
372
  "header.aria": "Session actions",
373
+ "header.more": "More actions",
306
374
  "menu.open": "Open",
307
375
  "menu.archive": "Archive session",
308
376
  "menu.unarchive": "Unarchive session",
@@ -313,6 +381,42 @@ function nextDialogZ() { return ++dialogZStackCounter; }
313
381
  "footer.label": "Sessions",
314
382
  "panel.title": "Session manager",
315
383
  "panel.github.title": "Star the project or open an issue",
384
+ "panel.update.title": "Check for updates",
385
+ "panel.update.badge": "New version available",
386
+ "update.title": "Check for Updates",
387
+ "update.newVersion": "New Version Available",
388
+ "update.currentVersion": "Current Version",
389
+ "update.latestVersion": "Latest Version",
390
+ "update.isLatest": "Up to date",
391
+ "update.checking": "Checking for updates…",
392
+ "update.installing": "Installing update via plugin manager…",
393
+ "update.btn.check": "Check for Updates",
394
+ "update.btn.update": "Update to v{version}",
395
+ "update.btn.updating": "Updating…",
396
+ "update.btn.later": "Later",
397
+ "update.btn.done": "OK",
398
+ "update.btn.retry": "Retry",
399
+ "update.btn.close": "Close",
400
+ "update.done.restart": "Update installed (v{from} → v{to}). Please restart DSH.",
401
+ "update.done.applied": "Update applied successfully!",
402
+ "update.error": "Update Error",
403
+ "update.autoCheck": "Automatically check for updates",
404
+ "update.autoCheck.enable": "Enable",
405
+ "settings.registry": "Install Source",
406
+ "settings.registry.npm": "npm official registry.npmjs.org",
407
+ "settings.registry.china": "China mainland mirror registry.npmmirror.com",
408
+ "settings.title": "Session Manager",
409
+ "settings.pluginName": "dsh-session-manager",
410
+ "settings.versionSection": "Plugin Version",
411
+ "settings.prefSection": "Preferences",
412
+ "settings.dialogOpacity": "Dialog opacity",
413
+ "settings.dialogOpacity.hint": "Adjusts the plugin dialog background only; text, buttons, inputs and borders stay opaque.",
414
+ "settings.dialogOpacity.value": "{value}%",
415
+ "settings.dialogOpacity.reset": "Reset to default",
416
+ "settings.projectSection": "Project",
417
+ "settings.github": "GitHub Repository",
418
+ "settings.star": "Star on GitHub",
419
+ "settings.feedback": "Feedback / Issues",
316
420
  "panel.search": "Search title, ID, tags or notes",
317
421
  "panel.workspace": "Workspace",
318
422
  "panel.workspace.all": "All workspaces",
@@ -440,23 +544,26 @@ function nextDialogZ() { return ++dialogZStackCounter; }
440
544
  ".sm-filter{display:flex;gap:4px;flex-wrap:wrap}",
441
545
  ".sm-filterBtn{cursor:pointer;display:inline-flex;align-items:center;color:var(--dsw-alias-label-secondary);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;height:28px;padding:3px 10px;font-size:12px;line-height:18px;white-space:nowrap;box-sizing:border-box}",
442
546
  ".sm-filterBtn:hover{background:var(--dsw-alias-interactive-bg-hover)}",
443
- ".sm-filterBtn.sm-on{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-fill-l2);border-color:transparent}",
547
+ ".sm-filterBtn.sm-on{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-module-platform);border-color:transparent}",
444
548
  ".sm-list{display:flex;flex-direction:column;gap:4px;min-height:0;overflow:auto;flex:1}",
445
549
  ".sm-row{box-sizing:border-box;display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px}",
446
550
  ".sm-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
447
- ".sm-rowCurrent{border-color:var(--dsw-alias-accent-primary,var(--dsw-alias-label-secondary))}",
551
+ ".sm-rowCurrent{border-color:var(--dsw-alias-state-business-primary,var(--dsw-alias-label-secondary))}",
448
552
  ".sm-rowMain{min-width:0;display:flex;flex-direction:column;gap:2px;flex:1 1 280px}",
449
553
  ".sm-rowTitle{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;display:flex;align-items:center;gap:6px;min-width:0}",
450
554
  ".sm-rowTitle>span:first-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}",
451
555
  ".sm-rowMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}",
452
556
  ".sm-rowActions{display:flex;gap:4px;flex:none;flex-wrap:wrap;justify-content:flex-end}",
453
557
  ".sm-badge{flex:none;border-radius:999px;padding:0 6px;font-size:10px;line-height:16px}",
454
- ".sm-badgeArchived{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-l2)}",
455
- ".sm-badgeRunning{color:var(--dsw-alias-state-warning-primary,#d97706);background:rgba(217,119,6,.12)}",
456
- ".sm-badgeCurrent{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-fill-l2)}",
457
- ".sm-danger{color:var(--dsw-alias-state-error-primary) !important;border-color:var(--dsw-alias-state-error-primary) !important}",
458
- ".sm-dangerText{color:var(--dsw-alias-state-error-primary) !important}",
459
- ".sm-warn{display:flex;align-items:flex-start;gap:8px;color:var(--dsw-alias-label-secondary);background:rgba(217,119,6,.08);border:1px solid rgba(217,119,6,.25);border-radius:8px;padding:8px 10px;margin-top:4px}",
558
+ ".sm-badgeArchived{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-markdown-tag)}",
559
+ ".sm-badgeRunning{color:var(--dsw-alias-state-warn-label,#dd8629);background:var(--dsw-alias-state-warn-tertiary,#fef5e7)}",
560
+ ".sm-badgeCurrent{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-tag)}",
561
+ // The former .sm-danger / .sm-dangerText rules were removed: no element
562
+ // ever carried those classes (dead CSS) and they were the only colour
563
+ // !important declarations that could have fought a theme. Danger-coloured
564
+ // affordances use .sm-headerBtnDanger / .sm-rowBtnDanger /
565
+ // .sm-nativeDialogDanger / .sm-bulkBtnDanger, all plain DSH tokens.
566
+ ".sm-warn{display:flex;align-items:flex-start;gap:8px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-state-warn-tertiary,#fef5e7);border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 30%,transparent);border-radius:8px;padding:8px 10px;margin-top:4px}",
460
567
  ".sm-warn svg{flex:none;margin-top:1px}",
461
568
  ".sm-error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;min-height:0;margin-top:4px}",
462
569
  ".sm-foot{display:flex;justify-content:flex-end;align-items:center;gap:8px;width:100%}",
@@ -467,56 +574,136 @@ function nextDialogZ() { return ++dialogZStackCounter; }
467
574
  ".sm-moveList{display:flex;flex-direction:column;gap:4px;max-height:200px;overflow:auto}",
468
575
  ".sm-moveItem{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;transition:all .15s}",
469
576
  ".sm-moveItem:hover{background:var(--dsw-alias-interactive-bg-hover)}",
470
- ".sm-moveItemSelected{border-color:var(--dsw-alias-accent-primary);background:var(--dsw-alias-accent-primary-bg)}",
577
+ ".sm-moveItemSelected{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}",
471
578
  ".sm-moveItemName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}",
472
- ".sm-moveItemMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.sm-panelModal{width:760px;max-width:92vw}.sm-panelModal .sm-rowActions{flex-wrap:wrap;justify-content:flex-end}.sm-headerMenu{position:absolute;right:0;top:100%;z-index:50;display:flex;flex-direction:column;min-width:160px;padding:4px;background:var(--dsw-alias-surface-l1);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.12)}.sm-headerMenuItem{display:flex;align-items:center;width:100%;text-align:left;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-primary);cursor:pointer;padding:6px 10px;font-size:13px;line-height:18px}.sm-headerMenuItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-headerMenuItemDanger{color:var(--dsw-alias-state-error-primary)}.sm-headerBtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;height:auto;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-l2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;padding:4px 12px;font-size:13px;line-height:18px;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}.sm-headerBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1)}.sm-headerBtn:focus-visible{outline:2px solid var(--dsw-alias-accent-primary);outline-offset:1px}.sm-headerBtn svg{width:16px;height:16px}.sm-migratePanel{display:flex;flex-direction:column;gap:14px;min-width:480px}.sm-migrateRow{display:flex;flex-direction:column;gap:8px}.sm-migrateRow>label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px;font-weight:500}.sm-migrateChips{display:flex;flex-wrap:wrap;gap:6px}.sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:transparent;cursor:pointer;font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary);white-space:nowrap}.sm-migrateChip:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-migrateChip:disabled{opacity:.5;cursor:not-allowed}.sm-migrateChipOn{background:var(--dsw-alias-accent-primary);color:#fff;border-color:var(--dsw-alias-accent-primary)}.sm-migrateEmpty{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.sm-migratePreview{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}.sm-migrateWarn{padding:10px 12px;border:1px solid rgba(217,119,6,.25);border-radius:8px;background:rgba(217,119,6,.08);color:var(--dsw-alias-label-secondary);font-size:13px;line-height:18px}.sm-migrateList{max-height:200px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:8px}.sm-headerBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1)}.sm-headerBtnDanger{background:var(--dsw-alias-state-error-tertiary);color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);font-weight:500}.sm-headerBtnDanger:hover:not(:disabled){background:#d92d20!important;color:#fff!important;border-color:#d92d20!important}.sm-header{display:inline-flex;align-items:center;gap:6px}.sm-annotationToggleOpen{position:relative;z-index:10004}.sm-headerBtnActive{background:var(--dsw-alias-fill-l3);color:var(--dsw-alias-label-primary)}",
473
- ".sm-panelDialog{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:600px;max-width:calc(100vw - 32px);max-height:560px;height:auto;box-sizing:border-box;display:flex;flex-direction:column;gap:0;background:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);overflow:hidden;z-index:9999;opacity:1}.sm-panelDialog .sm-nativeDialogHeader{display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none}.sm-panelDialog .sm-nativeDialogTitle{margin:0;font-size:14px;line-height:20px;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;flex:1 1 auto}.sm-panelDialog .sm-nativeDialogHeaderActions{display:flex;align-items:center;gap:6px;flex:none}.sm-panelDialog .sm-nativeDialogTitle{margin:0;font-size:14px;line-height:20px;font-weight:600;text-align:center;flex:1 1 auto}.sm-nativeDialogFooter,.sm-bulkDialog .sm-nativeDialogFooter,.sm-confirmDialog .sm-nativeDialogFooter,.sm-migrateDialog .sm-nativeDialogFooter{display:flex;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0}.sm-panelDialog .sm-panel{display:flex;flex-direction:column;gap:6px;min-height:0;flex:1 1 auto;overflow:auto;padding:8px 12px}.sm-panelDialog .sm-list{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-height:0;overflow:auto}.sm-panelDialog .sm-row{display:flex;align-items:center;gap:8px;padding:6px 10px;min-height:36px;border:1px solid transparent;border-radius:8px;background:transparent;flex-shrink:0}.sm-panelDialog .sm-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-panelDialog .sm-rowCurrent{background:var(--dsw-alias-fill-l2);border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-panelDialog .sm-rowMain{display:flex;align-items:stretch!important;gap:8px;min-width:0;flex:1 1 auto;overflow:visible;text-align:left!important}.sm-panelDialog .sm-rowTitle{min-width:0;flex:1 1 0%;width:100%;align-self:stretch;display:flex;align-items:center;justify-content:flex-start;gap:6px;font-size:12px;line-height:16px;overflow:hidden;text-align:left!important;direction:ltr!important}.sm-confirmDialog{z-index:10000}.sm-confirmDialog.sm-nativeDialogLayer{position:fixed;left:calc(50vw + 184px);top:calc(50vh - 90px);width:360px;max-width:calc(100vw - 32px);max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:auto;box-sizing:border-box;background:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);padding:0;overflow:hidden;animation:sm-confirmPop .14s ease-out}.sm-confirmDialog .sm-nativeDialogHeader{padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee)}.sm-confirmDialog .sm-nativeDialogTitle{margin:0;font-size:14px;line-height:20px;font-weight:600;text-align:center;width:100%}.sm-confirmDialog .sm-nativeDialogBody{padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}.sm-confirmDialog .sm-nativeDialogDescription{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap}.sm-confirmDialog .sm-warn{padding:6px 10px;font-size:12px;line-height:16px;margin-top:0}.sm-confirmDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0}.sm-confirmDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary);text-align:center}.sm-confirmDialog .sm-nativeDialogCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-confirmDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-confirmDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}@keyframes sm-confirmPop{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.sm-confirmDialog.sm-nativeDialog{padding:0;width:auto;max-width:none}.sm-panelDialog .sm-rowTitle>span:first-child{display:block;min-width:0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;flex:1 1 auto;width:auto;text-align:left!important;direction:ltr!important;unicode-bidi:plaintext}.sm-panelDialog .sm-rowMeta{display:none}.sm-panelDialog .sm-rowActions{display:flex;gap:2px;flex:none;justify-content:flex-end}.sm-panelDialog .sm-rowActions .sm-rowBtn{min-height:22px;padding:0 7px;font-size:11px;line-height:18px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap}.sm-rowBtn.sm-markIcon{padding:0 6px;font-size:13px;line-height:18px}.sm-panelDialog .sm-rowActions .sm-rowBtn:hover{background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger{color:var(--dsw-alias-state-error-primary)}.sm-panelDialog .sm-filter{flex:none;padding:6px 12px 4px;gap:4px;display:flex;flex-wrap:wrap}.sm-panelDialog .sm-filterBtn{min-height:24px;padding:2px 10px;font-size:11px;line-height:16px;border-radius:4px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}.sm-panelDialog .sm-filterBtn.sm-on{background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-badge{font-size:10px;line-height:14px;padding:0 5px;border-radius:6px;flex:none}.sm-panelDialog .sm-error{padding:6px 10px;margin:0;font-size:12px;line-height:16px}.sm-panelDialog .sm-nativeDialogButton{min-height:28px;padding:3px 12px;font-size:12px;line-height:18px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer}.sm-panelDialog .sm-nativeDialogCancel{background:transparent}.sm-panelDialog .sm-nativeDialogGhost{background:transparent;border-color:var(--dsw-alias-border-l2)}.sm-panelDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:#fff;border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-confirmDialog .sm-nativeDialogDanger,.sm-migrateDialog .sm-nativeDialogDanger,.sm-bulkDialog .sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:#fff;border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-confirmDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-migrateDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-bulkDialog .sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-panelDialog .sm-empty{padding:18px 0;font-size:12px;color:var(--dsw-alias-label-tertiary);text-align:center}.sm-panelDialog .sm-footError{font-size:11px;color:var(--dsw-alias-state-error-primary);margin-right:auto;line-height:20px}.sm-panelDialog .sm-moveLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.sm-migrateDialog .sm-nativeDialogFooter{flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0}.sm-migrateDialog .sm-migrateChip{cursor:pointer;pointer-events:auto;position:relative;z-index:1}.sm-migrateDialog .sm-migrateChip[disabled]{opacity:.5;cursor:not-allowed}.sm-migrateDialog.sm-nativeDialogLayer{position:fixed;left:50vw;top:auto;bottom:48px;transform:translateX(-50%);width:min(560px,calc(100vw - 32px));max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:auto;z-index:10001}.sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:360px;max-width:calc(100vw - 32px);max-height:min(70vh,520px);box-sizing:border-box;background-color:#ffffff;background-color:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.45);overflow:hidden;padding:0;display:flex;flex-direction:column;gap:0;opacity:1}.sm-migrateDialog{position:fixed;left:50vw;top:auto;bottom:48px;transform:translateX(-50%);width:min(560px,calc(100vw - 32px));max-height:min(70vh,520px);box-sizing:border-box;display:flex;flex-direction:column;gap:0;background-color:#ffffff;background-color:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.45);overflow:hidden;opacity:1;z-index:10001;backdrop-filter:none;-webkit-backdrop-filter:none}.sm-migrateDialog .sm-nativeDialogHeader{padding:12px 18px;border-bottom:1px solid #eee;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-nativeDialogTitle{margin:0;font-size:14px;line-height:20px;font-weight:600;color:#111;color:var(--dsw-alias-label-primary,#111);text-align:center;width:100%}.sm-migrateDialog .sm-migratePanel{padding:12px 18px;display:flex;flex-direction:column;gap:14px;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-nativeDialogBody{padding:0;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0;gap:8px;background-color:#fff;background-color:var(--dsw-alias-surface-l1,#fff)}.sm-migrateDialog .sm-migrateRow>label{color:#444;color:var(--dsw-alias-label-secondary,#444)}.sm-migrateDialog .sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);background-color:#f5f5f5;background-color:var(--dsw-alias-fill-l2,#f5f5f5);color:#111;color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px;cursor:pointer;pointer-events:auto;position:relative;z-index:1;white-space:nowrap}.sm-migrateDialog .sm-migrateChip:hover:not(:disabled){background-color:#e8e8e8;background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-migrateChipOn{background-color:#356ae6;background-color:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:#356ae6;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-migrateDialog .sm-migrateChip.sm-migrateChipOn:hover:not(:disabled){background-color:#4576f0;background-color:var(--dsw-alias-accent-primary-hover,#4576f0);color:#fff;border-color:#4576f0;border-color:var(--dsw-alias-accent-primary-hover,#4576f0);filter:none}.sm-migrateDialog .sm-migrateChipOn:hover:not(:disabled){filter:brightness(.92)}.sm-migrateDialog .sm-migrateChip[disabled]{opacity:.5;cursor:not-allowed}.sm-migrateDialog .sm-migrateEmpty{color:#888;color:var(--dsw-alias-label-tertiary,#888)}.sm-migrateDialog .sm-migratePreview{padding:10px 12px;border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background-color:#f5f5f5;background-color:var(--dsw-alias-fill-l2,#f5f5f5);color:#111;color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px}.sm-migrateDialog .sm-migrateWarn{padding:10px 12px;border:1px solid rgba(217,119,6,.25);border-radius:8px;background-color:rgba(217,119,6,.08);color:#444;color:var(--dsw-alias-label-secondary,#444);font-size:13px;line-height:18px}.sm-migrateDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid #d4d4d8;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background-color:#fff;background-color:var(--dsw-alias-fill-l2,#fff);color:#111;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-migrateDialog .sm-nativeDialogButton:hover:not(:disabled){background-color:#e8e8e8;background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-nativeDialogConfirm{background-color:#356ae6;background-color:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:#356ae6;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-migrateDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}",
474
- ".sm-panelDialog .sm-nativeDialogHeader{display:flex;align-items:center;gap:8px}.sm-panelHeaderClose{min-height:26px;padding:2px 9px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#555);cursor:pointer;font-size:12px;line-height:18px;flex:none}.sm-panelHeaderGithub{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:6px;color:var(--dsw-alias-label-secondary,#555);text-decoration:none;flex:none;transition:background .15s,color .15s;cursor:pointer}.sm-panelHeaderGithub:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderGithub svg{display:block;width:16px;height:16px;fill:currentColor}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose:disabled{opacity:.5;cursor:not-allowed}.sm-panelHeaderSelect{min-height:26px;padding:2px 9px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#555);cursor:pointer;font-size:12px;line-height:18px;display:inline-flex;align-items:center;gap:4px}.sm-panelHeaderSelect:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderSelect:disabled{opacity:.5;cursor:not-allowed}.sm-panelHeaderSelect.sm-on{background:var(--dsw-alias-accent-primary-bg,rgba(53,106,230,.12));border-color:var(--dsw-alias-accent-primary,#356ae6);color:var(--dsw-alias-accent-primary,#356ae6)}.sm-migrateCurrent{padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background:var(--dsw-alias-fill-l2,#f5f5f5);font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#111)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer{width:360px;inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer .sm-migrateDialog{position:relative;left:auto;top:auto;bottom:auto;transform:none;width:100%;max-width:none;max-height:min(70vh,520px);z-index:auto}.sm-migrateConfirmLayer .sm-migratePanel{min-width:0}.sm-migrateConfirmLayer .sm-nativeDialogButton.sm-nativeDialogConfirm:hover:not(:disabled){background-color:#4576f0;background-color:var(--dsw-alias-accent-primary-hover,#4576f0);color:#fff;border-color:#4576f0;border-color:var(--dsw-alias-accent-primary-hover,#4576f0);filter:none}",
475
-
476
- // Theme colors are scoped to the plugin dialogs. DSH exposes its
477
- // actual theme through `body[data-ds-dark-theme]` and color-scheme;
478
- // keeping these values local avoids changing DSH's global variables.
479
- "[data-sm-theme=light] .sm-panelDialog,[data-sm-theme=light] .sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialog,[data-sm-theme=light] .sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialog{--dsw-alias-surface-l1:#fff;--dsw-alias-label-primary:#1f1f1f;--dsw-alias-label-secondary:#555;--dsw-alias-label-tertiary:#888;--dsw-alias-border-l1:#d4d4d8;--dsw-alias-border-l2:#e5e5e5;--dsw-alias-fill-l2:#f5f5f5;--dsw-alias-interactive-bg-hover:#eee;--dsw-alias-state-error-primary:#d92d20;--dsw-alias-state-warning-primary:#d97706;--dsw-alias-accent-primary:#356ae6;--dsw-alias-accent-primary-bg:rgba(53,106,230,.12);background:#fff!important;background-color:#fff!important;color:#1f1f1f!important;border-color:#e5e5e5!important}",
480
- "[data-sm-theme=dark] .sm-panelDialog,[data-sm-theme=dark] .sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialog,[data-sm-theme=dark] .sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialog,[data-sm-theme=dark] .sm-bulkDialog.sm-nativeDialogLayer .sm-nativeDialog{--dsw-alias-surface-l1:#1f1f23;--dsw-alias-label-primary:#ececec;--dsw-alias-label-secondary:#b3b3b3;--dsw-alias-label-tertiary:#999;--dsw-alias-border-l1:#555;--dsw-alias-border-l2:#3a3a40;--dsw-alias-fill-l1:#1a1a1e;--dsw-alias-fill-l2:#2a2a30;--dsw-alias-interactive-bg-hover:rgba(255,255,255,.06);--dsw-alias-state-error-primary:#f47171;--dsw-alias-state-warning-primary:#f0a040;--dsw-alias-accent-primary:#5d8aff;--dsw-alias-accent-primary-bg:rgba(93,138,255,.18);background:#1f1f23!important;background-color:#1f1f23!important;color:#ececec!important;border-color:#3a3a40!important}","[data-sm-theme=dark] .sm-header{--dsw-alias-surface-l1:#1f1f23;--dsw-alias-label-primary:#ececec;--dsw-alias-label-secondary:#b3b3b3;--dsw-alias-label-tertiary:#999;--dsw-alias-border-l1:#555;--dsw-alias-border-l2:#3a3a40;--dsw-alias-fill-l1:#1a1a1e;--dsw-alias-fill-l2:#2a2a30;--dsw-alias-interactive-bg-hover:rgba(255,255,255,.06);--dsw-alias-accent-primary:#5d8aff;--dsw-alias-accent-primary-bg:rgba(93,138,255,.18)}",
481
- "[data-sm-theme=light] .sm-panelDialog .sm-nativeDialogHeader,[data-sm-theme=light] .sm-panelDialog .sm-nativeDialogFooter,[data-sm-theme=light] .sm-confirmDialog .sm-nativeDialogHeader,[data-sm-theme=light] .sm-confirmDialog .sm-nativeDialogFooter,[data-sm-theme=light] .sm-migrateDialog .sm-nativeDialogHeader,[data-sm-theme=light] .sm-migrateDialog .sm-nativeDialogBody,[data-sm-theme=light] .sm-migrateDialog .sm-nativeDialogFooter{background:#fff!important;background-color:#fff!important;border-color:#e5e5e5!important}",
482
- "[data-sm-theme=dark] .sm-panelDialog .sm-nativeDialogHeader,[data-sm-theme=dark] .sm-panelDialog .sm-nativeDialogFooter,[data-sm-theme=dark] .sm-confirmDialog .sm-nativeDialogHeader,[data-sm-theme=dark] .sm-confirmDialog .sm-nativeDialogFooter,[data-sm-theme=dark] .sm-migrateDialog .sm-nativeDialogHeader,[data-sm-theme=dark] .sm-migrateDialog .sm-nativeDialogBody,[data-sm-theme=dark] .sm-migrateDialog .sm-nativeDialogFooter,[data-sm-theme=dark] .sm-bulkDialog .sm-nativeDialogHeader,[data-sm-theme=dark] .sm-bulkDialog .sm-nativeDialogBody,[data-sm-theme=dark] .sm-bulkDialog .sm-nativeDialogFooter{background:#1f1f23!important;background-color:#1f1f23!important;border-color:#3a3a40!important}[data-sm-theme=dark] .sm-bulkDialog .sm-bulkSessionList,[data-sm-theme=dark] .sm-bulkDialog .sm-bulkResultList,[data-sm-theme=dark] .sm-bulkDialog .sm-bulkProgressBar{background:#1a1a1e!important;background-color:#1a1a1e!important;border-color:#3a3a40!important;color:var(--dsw-alias-label-primary,#ececec)}[data-sm-theme=dark] .sm-bulkDialog .sm-bulkSessionItem,[data-sm-theme=dark] .sm-bulkDialog .sm-bulkResultItemTitle,[data-sm-theme=dark] .sm-bulkDialog .sm-bulkResultItem{color:var(--dsw-alias-label-primary,#ececec)}",
579
+ ".sm-moveItemMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.sm-panelModal{width:760px;max-width:92vw}.sm-panelModal .sm-rowActions{flex-wrap:wrap;justify-content:flex-end}.sm-headerMenu{position:absolute;right:0;top:100%;margin-top:4px;z-index:50;display:flex;flex-direction:column;min-width:140px;padding:4px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.15)}.sm-headerMenuItem{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-primary,#111);cursor:pointer;padding:6px 10px;font-size:13px;line-height:18px;white-space:nowrap}.sm-headerMenuItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-headerMenuItem .sm-headerBtnIcon{width:20px;min-width:20px;text-align:center;flex-shrink:0}.sm-headerMenuItemDanger{color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-headerMenuItemDanger:hover{background:var(--dsw-alias-interactive-bg-hover-danger)}.sm-headerBtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;height:32px;box-sizing:border-box;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-button-floating-fill,#fff);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;padding:0 10px;font-size:13px;line-height:1;white-space:nowrap;transition:background .15s,color .15s,border-color .15s;flex-shrink:0}.sm-headerBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1)}.sm-headerBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.sm-headerBtn svg{width:16px;height:16px;display:block;margin:auto;flex-shrink:0}.sm-migratePanel{display:flex;flex-direction:column;gap:14px;min-width:480px}.sm-migrateRow{display:flex;flex-direction:column;gap:8px}.sm-migrateRow>label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px;font-weight:500}.sm-migrateChips{display:flex;flex-wrap:wrap;gap:6px}.sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:transparent;cursor:pointer;font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary);white-space:nowrap}.sm-migrateChip:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-migrateChip:disabled{opacity:.5;cursor:not-allowed}.sm-migrateChipOn{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary)}.sm-migrateEmpty{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.sm-migratePreview{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}.sm-migrateWarn{padding:10px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 30%,transparent);border-radius:8px;background:var(--dsw-alias-state-warn-tertiary,#fef5e7);color:var(--dsw-alias-label-secondary);font-size:13px;line-height:18px}.sm-migrateList{max-height:200px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:8px}.sm-headerBtnDanger{background:var(--dsw-alias-button-floating-fill,#fff);color:var(--dsw-alias-state-error-primary,#d92d20);border-color:var(--dsw-alias-state-error-primary,#d92d20);font-weight:500}.sm-headerBtnDanger:hover:not(:disabled){background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-header{display:inline-flex;align-items:center;gap:6px;flex-wrap:nowrap}.sm-headerSecondaryActions{display:inline-flex;align-items:center;gap:6px;flex-wrap:nowrap}.sm-headerCompactMenu{display:none;position:relative}.sm-headerCompactMenuBtn{width:32px;min-width:32px;height:32px;padding:0;justify-content:center;font-size:16px;line-height:1}.sm-headerBtnIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;font-size:14px;line-height:1;vertical-align:middle;flex-shrink:0}.sm-headerBtn.sm-markIcon{width:32px;min-width:32px;height:32px;padding:0;justify-content:center}.sm-headerBtn.sm-markIcon .sm-headerBtnIcon{width:16px;height:16px;font-size:15px;line-height:1}.sm-headerCompactMenuBtn .sm-headerBtnIcon{width:16px;height:16px;font-size:16px;line-height:1}.sm-annotationToggleOpen{position:relative;z-index:100004}.sm-headerBtnActive{background:var(--dsw-alias-button-ghost-active-fill);color:var(--dsw-alias-label-primary)}@container (max-width:720px){.sm-headerBtn{width:32px;min-width:32px;height:32px;padding:0;justify-content:center}.sm-headerBtnLabel,.sm-priorityText,.sm-priorityBadge{display:none}.sm-headerBtn.sm-headerBtn-withBadge{width:auto;min-width:32px;padding:0 6px}}@container (max-width:520px){.sm-headerSecondaryActions,.sm-headerSecondaryAction{display:none!important}.sm-headerCompactMenu{display:inline-flex}}",
580
+ ".sm-panelDialog{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:600px;max-width:calc(100vw - 32px);max-height:560px;height:auto;box-sizing:border-box;display:flex;flex-direction:column;gap:0;background:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);overflow:hidden;z-index:1020}.sm-panelDialog .sm-nativeDialogHeader{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;position:relative;min-height:46px;box-sizing:border-box}.sm-panelDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;min-width:0;max-width:calc(100% - 140px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;pointer-events:auto;z-index:0}.sm-panelDialog .sm-nativeDialogHeaderActions{display:flex;align-items:center;gap:6px;flex:none;position:relative;z-index:1;margin-left:auto}.sm-nativeDialogFooter,.sm-bulkDialog .sm-nativeDialogFooter,.sm-confirmDialog .sm-nativeDialogFooter,.sm-migrateDialog .sm-nativeDialogFooter{display:flex;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0}.sm-panelDialog .sm-panel{display:flex;flex-direction:column;gap:6px;min-height:0;flex:1 1 auto;overflow:auto;padding:8px 12px}.sm-panelDialog .sm-list{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-height:0;overflow:auto}.sm-panelDialog .sm-row{display:flex;align-items:center;gap:8px;padding:6px 10px;min-height:36px;border:1px solid transparent;border-radius:8px;background:transparent;flex-shrink:0}.sm-panelDialog .sm-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.sm-panelDialog .sm-rowCurrent{background:var(--dsw-alias-bg-module-platform);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-panelDialog .sm-rowMain{display:flex;align-items:stretch!important;gap:8px;min-width:0;flex:1 1 auto;overflow:visible;text-align:left!important}.sm-panelDialog .sm-rowTitle{min-width:0;flex:1 1 0%;width:100%;align-self:stretch;display:flex;align-items:center;justify-content:flex-start;gap:6px;font-size:12px;line-height:16px;overflow:hidden;text-align:left!important;direction:ltr!important}.sm-confirmDialog{z-index:10000}.sm-confirmDialog.sm-nativeDialogLayer{position:fixed;left:calc(50vw + 184px);top:calc(50vh - 90px);width:360px;max-width:calc(100vw - 32px);max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:auto;box-sizing:border-box;background:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);padding:0;overflow:hidden;animation:sm-confirmPop .14s ease-out}.sm-confirmDialog .sm-nativeDialogHeader{position:relative;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);display:flex;align-items:center;justify-content:center;min-height:44px;box-sizing:border-box}.sm-confirmDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;text-align:center;width:auto;max-width:calc(100% - 32px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-confirmDialog .sm-nativeDialogBody{padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}.sm-confirmDialog .sm-nativeDialogDescription{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap}.sm-confirmDialog .sm-warn{padding:6px 10px;font-size:12px;line-height:16px;margin-top:0}.sm-confirmDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0}.sm-confirmDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary);text-align:center}.sm-confirmDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-migrateDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-updateDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-bulkDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-panelDialog .sm-nativeDialogCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-confirmDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-confirmDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}@keyframes sm-confirmPop{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.sm-confirmDialog.sm-nativeDialog{padding:0;width:auto;max-width:none}.sm-panelDialog .sm-rowTitle>span:first-child{display:block;min-width:0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;flex:1 1 auto;width:auto;text-align:left!important;direction:ltr!important;unicode-bidi:plaintext}.sm-panelDialog .sm-rowMeta{display:none}.sm-panelDialog .sm-rowActions{display:flex;gap:2px;flex:none;justify-content:flex-end}.sm-panelDialog .sm-rowActions .sm-rowBtn{min-height:22px;padding:0 7px;font-size:11px;line-height:18px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap}.sm-rowBtn.sm-markIcon{padding:0 6px;font-size:13px;line-height:18px}.sm-panelDialog .sm-rowActions .sm-rowBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger{color:var(--dsw-alias-state-error-primary)}.sm-panelDialog .sm-rowActions .sm-rowBtnDanger:hover{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.sm-panelDialog .sm-filter{flex:none;padding:6px 12px 4px;gap:4px;display:flex;flex-wrap:wrap}.sm-panelDialog .sm-filterBtn{min-height:24px;padding:2px 10px;font-size:11px;line-height:16px;border-radius:4px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}.sm-panelDialog .sm-filterBtn.sm-on{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}.sm-panelDialog .sm-badge{font-size:10px;line-height:14px;padding:0 5px;border-radius:6px;flex:none}.sm-panelDialog .sm-error{padding:6px 10px;margin:0;font-size:12px;line-height:16px}.sm-panelDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:center}.sm-panelDialog .sm-nativeDialogCancel{background:transparent}.sm-panelDialog .sm-panelDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-confirmDialog .sm-nativeDialogDanger,.sm-migrateDialog .sm-nativeDialogDanger,.sm-bulkDialog .sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-confirmDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-migrateDialog .sm-nativeDialogDanger:hover:not(:disabled),.sm-bulkDialog .sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-panelDialog .sm-empty{padding:18px 0;font-size:12px;color:var(--dsw-alias-label-tertiary);text-align:center}.sm-panelDialog .sm-footError{font-size:11px;color:var(--dsw-alias-state-error-primary);margin-right:auto;line-height:20px}.sm-panelDialog .sm-moveLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.sm-migrateDialog .sm-nativeDialogFooter{flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0}.sm-migrateDialog .sm-migrateChip{cursor:pointer;pointer-events:auto;position:relative;z-index:1}.sm-migrateDialog .sm-migrateChip[disabled]{opacity:.5;cursor:not-allowed}.sm-migrateDialog.sm-nativeDialogLayer{position:fixed;left:50vw;top:auto;bottom:48px;transform:translateX(-50%);width:min(560px,calc(100vw - 32px));max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:auto;z-index:10001}.sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:360px;max-width:calc(100vw - 32px);max-height:min(70vh,520px);box-sizing:border-box;background-color:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.45);overflow:hidden;padding:0;display:flex;flex-direction:column;gap:0}.sm-migrateDialog{position:fixed;left:50vw;top:auto;bottom:48px;transform:translateX(-50%);width:min(560px,calc(100vw - 32px));max-height:min(70vh,520px);box-sizing:border-box;display:flex;flex-direction:column;gap:0;background-color:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.45);overflow:hidden;z-index:10001}.sm-migrateDialog .sm-nativeDialogHeader{padding:12px 18px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;position:relative;display:flex;align-items:center;justify-content:center;min-height:44px;box-sizing:border-box}.sm-migrateDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;color:var(--dsw-alias-label-primary,#111);text-align:center;width:auto;max-width:calc(100% - 36px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-migrateDialog .sm-migratePanel{padding:12px 18px;display:flex;flex-direction:column;gap:14px}.sm-migrateDialog .sm-nativeDialogBody{padding:0}.sm-migrateDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:6px;margin-top:0;gap:8px}.sm-migrateDialog .sm-migrateRow>label{color:var(--dsw-alias-label-secondary,#444)}.sm-migrateDialog .sm-migrateChip{padding:6px 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#ddd);background-color:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px;cursor:pointer;pointer-events:auto;position:relative;z-index:1;white-space:nowrap}.sm-migrateDialog .sm-migrateChip:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-migrateChipOn{background-color:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-migrateDialog .sm-migrateChip.sm-migrateChipOn:hover:not(:disabled){background-color:var(--dsw-alias-button-info-hover,#4576f0);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-button-info-hover,#4576f0);filter:none}.sm-migrateDialog .sm-migrateChipOn:hover:not(:disabled){filter:brightness(.92)}.sm-migrateDialog .sm-migrateChip[disabled]{opacity:.5;cursor:not-allowed}.sm-migrateDialog .sm-migrateEmpty{color:var(--dsw-alias-label-tertiary,#888)}.sm-migrateDialog .sm-migratePreview{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background-color:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:18px}.sm-migrateDialog .sm-migrateWarn{padding:10px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 30%,transparent);border-radius:8px;background-color:var(--dsw-alias-state-warn-tertiary,#fef5e7);color:var(--dsw-alias-label-secondary,#444);font-size:13px;line-height:18px}.sm-migrateDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background-color:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-migrateDialog .sm-nativeDialogButton:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover,#e8e8e8)}.sm-migrateDialog .sm-nativeDialogConfirm{background-color:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-migrateDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}",
581
+ // The .sm-tooltip rule below deliberately keeps !important: it targets
582
+ // DSH's Tooltip primitive (used through renderTip), which ships its own
583
+ // inline styles, so a plugin rule cannot win on specificity alone
584
+ // (Issue #21 regression guard). It overrides DSH's primitive rather than
585
+ // DSH's theme tokens -- and it only sets a token-based border plus
586
+ // z-index/shadow -- so it does not block theming.
587
+ // NOTE: DSH's bubble uses a hard-coded hashed class and forwards no
588
+ // className, so this rule matches nothing today (the class is never
589
+ // applied). Tooltip visibility is governed by the layering contract
590
+ // above, not by this rule; it is kept as the scoped replacement for the
591
+ // unscoped selector Issue #21 removed.
592
+ ".sm-panelHeaderClose{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;min-height:26px;padding:0;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#555);cursor:pointer;flex:none;box-sizing:border-box;outline:none}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose svg{display:block;width:14px;height:14px;margin:auto}.sm-panelHeaderGithub{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:6px;color:var(--dsw-alias-label-secondary,#555);text-decoration:none;flex:none;transition:background .15s,color .15s;cursor:pointer}.sm-panelHeaderGithub:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderGithub svg{display:block;width:16px;height:16px;fill:currentColor}.sm-panelHeaderLeft{display:flex;align-items:center;gap:4px;flex:none;position:relative;z-index:1}.sm-panelHeaderWhale{display:inline-flex;align-items:center;justify-content:center;position:relative;width:26px;height:26px;border-radius:6px;color:var(--dsw-alias-label-secondary,#555);text-decoration:none;flex:none;transition:background .15s,color .15s;cursor:pointer}.sm-panelHeaderWhale:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderWhale svg{display:block;width:16px;height:16px;margin:auto}.sm-panelHeaderWhale .sm-updateDot{position:absolute;top:2px;right:2px;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-error-primary,#e53e3e);border:1.5px solid var(--dsw-alias-bg-layer-1,#fff)}.sm-panelHeaderWhale .sm-updateSpinner{position:absolute;inset:2px;border:2px solid transparent;border-top-color:var(--dsw-alias-state-business-primary,#356ae6);border-radius:50%;animation:sm-spin .8s linear infinite}@keyframes sm-spin{to{transform:rotate(360deg)}}.sm-updateDialogLayer{display:flex!important;align-items:center!important;justify-content:center!important;position:fixed;inset:0;padding:16px;box-sizing:border-box;background:transparent;border:0;box-shadow:none;z-index:10010;pointer-events:none}.sm-updateDialogLayer .sm-nativeDialog{position:relative;width:380px;max-width:calc(100vw - 32px);box-sizing:border-box;background:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);overflow:hidden;animation:sm-confirmPop .14s ease-out;pointer-events:auto}.sm-updateDialog .sm-nativeDialogHeader{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;min-height:46px;box-sizing:border-box}.sm-updateDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:15px;line-height:20px;font-weight:600;text-align:center;width:auto;max-width:calc(100% - 80px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:0}.sm-updateDialog .sm-nativeDialogHeader .sm-panelHeaderClose{position:relative;z-index:1;margin-left:auto}.sm-updateDialog .sm-nativeDialogBody{padding:14px 16px;display:flex;flex-direction:column;gap:10px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}.sm-updateDialog .sm-nativeDialogFooter{display:flex;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:8px;margin-top:0}.sm-updateDialog .sm-nativeDialogFooter .sm-nativeDialogConfirm:only-child{margin-left:auto}.sm-updateDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-updateDialog .sm-nativeDialogButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-updateDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-updateDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}.sm-updateVersionGrid{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;padding:12px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px;font-size:13px;line-height:20px}.sm-updateVersionLabel{color:var(--dsw-alias-label-secondary,#666);font-weight:500}.sm-updateVersionValue{color:var(--dsw-alias-label-primary,#111);font-family:monospace;font-size:13px}.sm-updateStatusBox{margin-top:10px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}.sm-updateStatusBox.sm-warn{color:var(--dsw-alias-state-warn-label,#dd8629);background:var(--dsw-alias-state-warn-tertiary,#fef5e7);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 25%,transparent)}.sm-updateStatusBox.sm-error{color:var(--dsw-alias-state-error-primary,#ec1313);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 8%,transparent);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 22%,transparent)}.sm-updateStatusBox.sm-success{color:var(--dsw-alias-state-success-primary,#22c55e);background:var(--dsw-alias-state-success-tertiary,#e6faed);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 25%,transparent)}.sm-settingsCard{background:var(--sm-dialog-surface);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;padding:16px 20px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111);box-sizing:border-box;width:100%}.sm-settingsHeader{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee)}.sm-settingsTitle{margin:0;font-size:16px;line-height:22px;font-weight:600;display:flex;align-items:center;gap:8px}.sm-settingsTag{font-size:12px;padding:2px 8px;border-radius:999px;background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#666);font-family:monospace}.sm-settingsSection{margin-top:14px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}.sm-settingsSectionTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#666);margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px}.sm-settingsRow{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:32px}.sm-settingsActions{display:flex;align-items:center;gap:8px;margin-top:10px}.sm-settingsLink{color:var(--dsw-alias-state-business-primary,#356ae6);text-decoration:none}.sm-settingsLink:hover{text-decoration:underline}.sm-settingsCheckbox{display:inline-flex;align-items:center;gap:6px;cursor:pointer}.sm-settingsSelect{min-height:28px;padding:3px 8px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:6px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);cursor:pointer;outline:none}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose:disabled{opacity:.5;cursor:not-allowed}.sm-migrateCurrent{padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#111)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer{width:360px;inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer .sm-migrateDialog{position:relative;left:auto;top:auto;bottom:auto;transform:none;width:100%;max-width:none;max-height:min(70vh,520px);z-index:auto}.sm-migrateConfirmLayer .sm-migratePanel{min-width:0}.sm-migrateConfirmLayer .sm-nativeDialogButton.sm-nativeDialogConfirm:hover:not(:disabled){background-color:var(--dsw-alias-button-info-hover,#4576f0);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-button-info-hover,#4576f0);filter:none}.sm-settingsSectionHeader{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.sm-settingsSectionHeader .sm-settingsSectionTitle{margin-bottom:0}.sm-settingsVersionActions{display:flex;align-items:center;gap:8px}.sm-settingsCheckBtn{min-height:26px;padding:2px 12px;font-size:12px;line-height:18px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,#ddd);background:transparent;color:var(--dsw-alias-label-primary,#111);cursor:pointer;white-space:nowrap}.sm-settingsCheckBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-settingsRowInline{justify-content:flex-start;flex-wrap:wrap;gap:8px;align-items:center}.sm-settingsBadgeLink{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,#ddd);background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#555);font-size:12px;line-height:18px;text-decoration:none;cursor:pointer;transition:all .15s}.sm-settingsBadgeLink:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-state-business-primary,#356ae6);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-settingsBadgeLink svg{flex:none}.sm-tooltip{z-index:1100!important;box-shadow:none!important;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.15))!important;filter:none!important}",
593
+
594
+ // Issue #23 — dialog opacity.
595
+ //
596
+ // The fill is the DSH surface token itself, with the user's opacity used
597
+ // only as that token's weight inside color-mix(). At the default 100%
598
+ // the mix is `color-mix(in srgb, <token> 100%, transparent)`, which
599
+ // reproduces the token VERBATIM -- including whatever alpha the token (or
600
+ // a theme that republished it) already carries. The plugin therefore
601
+ // never forces an originally translucent DSH surface opaque, and only
602
+ // adds translucency when the user lowers the slider.
603
+ //
604
+ // Only the dialog *fill* is affected, so text, buttons, icons, borders
605
+ // and hover states stay fully opaque, and inside controls read their own
606
+ // DSH tokens rather than inheriting this setting. The static --sm-dialog-
607
+ // surface-fallback below is used only when the DSH token is missing; the
608
+ // plugin detects and names no theme plugin at all.
609
+ ":root{--sm-dialog-opacity:100%}",
610
+ "html[data-sm-theme=light]{--sm-dialog-surface-fallback:#fff}",
611
+ "html[data-sm-theme=dark]{--sm-dialog-surface-fallback:#1f1f23}",
612
+ // Native UA chrome (a <select> dropdown list, scrollbars, checkbox
613
+ // internals) is painted by the browser, not by DSH tokens. DSH owns the
614
+ // palette for it: ui-layout's ThemePresenter subscribes to the official
615
+ // `theme/change` event and writes `documentElement.style.colorScheme`
616
+ // ("html { color-scheme } for native UA chrome"), which every plugin
617
+ // surface inherits. The plugin must NOT override it -- an earlier revision
618
+ // forced `color-scheme` from this plugin's own theme mirror and could then
619
+ // pin the native chrome to the opposite scheme (dark dropdown lists in
620
+ // light mode) while the tokens had already moved on. DSH stays the single
621
+ // authority; the plugin only themes the list *items*, which is something
622
+ // color-scheme alone does not cover.
623
+ // The items use the *layer-3* surface, not the control's layer-2: a
624
+ // floating list must stay opaque, and a theme may give layer-2 an alpha,
625
+ // which composites over the UA's default popup backdrop and still reads as
626
+ // white. layer-3 is opaque in DSH's palette (#fff light / #353638 dark)
627
+ // and sits above the dialog (layer-1) and the control (layer-2). The
628
+ // checked row is marked with the accent text colour instead of a fill so
629
+ // no translucent token can wash it out either.
630
+ ".sm-panelDialog option,.sm-nativeDialog option,.sm-settingsCard option,.sm-headerMenu option{background-color:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-primary,#111)}",
631
+ ".sm-panelDialog option:checked,.sm-nativeDialog option:checked,.sm-settingsCard option:checked,.sm-headerMenu option:checked{background-color:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-state-business-primary,#356ae6)}",
632
+ ".sm-panelDialog option:disabled,.sm-nativeDialog option:disabled,.sm-settingsCard option:disabled,.sm-headerMenu option:disabled{color:var(--dsw-alias-label-tertiary,#888)}",
633
+ ".sm-nativeDialog,.sm-panelDialog,.sm-migrateDialog,.sm-settingsCard{--sm-dialog-surface:var(--dsw-alias-bg-layer-1,var(--sm-dialog-surface-fallback,#fff));--sm-dialog-fill:color-mix(in srgb,var(--sm-dialog-surface) var(--sm-dialog-opacity,100%),transparent)}",
634
+ ".sm-settingsSlider{display:flex;align-items:center;gap:8px;flex:1 1 auto;justify-content:flex-end;min-width:0}",
635
+ ".sm-settingsRange{flex:1 1 120px;min-width:96px;max-width:220px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-state-business-primary,#356ae6)}",
636
+ ".sm-settingsRange:disabled{cursor:not-allowed}",
637
+ ".sm-settingsRangeValue{min-width:38px;text-align:right;font-size:12px;line-height:18px;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary,#666)}",
638
+ ".sm-settingsResetBtn{min-height:26px;padding:2px 10px;font-size:12px;line-height:18px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,#ddd);background:transparent;color:var(--dsw-alias-label-secondary,#666);cursor:pointer;white-space:nowrap}",
639
+ ".sm-settingsResetBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary,#111)}",
640
+ ".sm-settingsResetBtn:disabled{opacity:.5;cursor:not-allowed}",
641
+ ".sm-settingsHint{margin-top:4px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#888)}",
642
+
643
+ // DSH's own theme is the single source of colour. Every rule above reads
644
+ // --dsw-alias-* tokens directly and none of them is redefined here: the
645
+ // plugin only *reads* DSH's theme, so a theme (or a third-party theme
646
+ // plugin) that republishes those tokens is picked up automatically.
647
+ // The only thing these two rules still provide is the token-less
648
+ // fallback surface colour required by the dialog-opacity fill. The
649
+ // light/dark state itself is mirrored onto <html data-sm-theme> by
650
+ // smApplyTheme(), which reads body[data-ds-dark-theme].
651
+ "[data-sm-theme=light] .sm-nativeDialog,[data-sm-theme=light] .sm-panelDialog,[data-sm-theme=light] .sm-migrateDialog,[data-sm-theme=light] .sm-settingsCard{--sm-dialog-surface-fallback:#fff}",
652
+ "[data-sm-theme=dark] .sm-nativeDialog,[data-sm-theme=dark] .sm-panelDialog,[data-sm-theme=dark] .sm-migrateDialog,[data-sm-theme=dark] .sm-settingsCard{--sm-dialog-surface-fallback:#1f1f23}",
483
653
  ".sm-panelDialog{max-height:min(640px,calc(100vh - 32px))}.sm-panelDialog .sm-panel{min-width:0;overflow:hidden}",
484
654
  ".sm-panelTools{display:flex;flex-direction:column;gap:8px;flex:none;padding:2px 0 6px}",
485
- ".sm-queryInput,.sm-viewSelect{box-sizing:border-box;width:100%;min-width:0;height:32px;padding:5px 9px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);font:inherit;font-size:12px;line-height:20px}",
486
- ".sm-queryInput::placeholder{color:var(--dsw-alias-label-tertiary,#888)}.sm-queryInput:focus-visible,.sm-viewSelect:focus-visible,.sm-resetFilters:focus-visible{outline:2px solid var(--dsw-alias-accent-primary,#356ae6);outline-offset:2px}.sm-viewSelect:disabled{opacity:.6;cursor:wait}",
655
+ ".sm-queryInput,.sm-viewSelect{box-sizing:border-box;width:100%;min-width:0;height:32px;padding:5px 9px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);font:inherit;font-size:12px;line-height:20px}",
656
+ ".sm-queryInput::placeholder{color:var(--dsw-alias-label-tertiary,#888)}.sm-queryInput:focus-visible,.sm-viewSelect:focus-visible,.sm-resetFilters:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#356ae6);outline-offset:2px}.sm-viewSelect:disabled{opacity:.6;cursor:wait}",
487
657
  ".sm-panelSelects{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.sm-panelSelects .sm-viewField>span{display:none}.sm-panelSelects .sm-viewField .sm-viewSelect{font-size:11px;padding:4px 7px;height:28px;line-height:18px}.sm-viewField{display:flex;flex-direction:column;gap:3px;min-width:0;color:var(--dsw-alias-label-secondary,#666);font-size:11px;line-height:16px}",
488
- ".sm-filterBar{display:flex;align-items:center;justify-content:space-between;gap:6px;flex-wrap:wrap}.sm-resultSummary{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary,#888)}.sm-resetFilters{padding:2px 0;border:0;background:none;color:var(--dsw-alias-accent-primary,#356ae6);font:inherit;cursor:pointer}.sm-resetFilters:hover{text-decoration:underline}.sm-workspaceNotice{display:flex;gap:8px;align-items:center;font-size:12px;color:var(--dsw-alias-label-secondary,#666)}",
658
+ ".sm-filterBar{display:flex;align-items:center;justify-content:space-between;gap:6px;flex-wrap:wrap}.sm-resultSummary{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary,#888)}.sm-resetFilters{padding:2px 0;border:0;background:none;color:var(--dsw-alias-state-business-primary,#356ae6);font:inherit;cursor:pointer}.sm-resetFilters:hover{text-decoration:underline}.sm-workspaceNotice{display:flex;gap:8px;align-items:center;font-size:12px;color:var(--dsw-alias-label-secondary,#666)}",
489
659
  "@media(max-width:540px){.sm-panelSelects{grid-template-columns:minmax(0,1fr)}.sm-panelDialog .sm-row{height:auto;min-height:36px;flex-shrink:0;flex-wrap:wrap;padding:6px 8px}.sm-panelDialog .sm-rowMain{flex-basis:100%}.sm-panelDialog .sm-rowActions{width:100%;min-width:0;flex:1 1 100%;justify-content:flex-start}}",
490
660
  ".sm-panelDialog:not(.sm-annotationSurface){width:680px}.sm-panelDialog .sm-row{flex-shrink:0;padding:5px 8px}.sm-panelDialog .sm-rowMain{flex:1 1 200px;flex-direction:column;gap:4px}.sm-panelDialog .sm-rowActions{max-width:100%}.sm-rowSecondary{display:flex;flex-wrap:nowrap;align-items:center;gap:6px;min-width:0;line-height:18px}.sm-rowBadges{display:flex;align-items:center;gap:5px;min-width:0;flex:1 1 auto;flex-wrap:wrap;font-size:11px;line-height:18px}.sm-rowMeta{color:var(--dsw-alias-label-tertiary,#888);font-size:11px;line-height:18px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;flex:none}.sm-rowAnnotations{display:flex;align-items:center;gap:5px;min-width:0;font-size:10px;line-height:16px}",
491
- ".sm-markIcon{min-width:25px;padding-left:5px;padding-right:5px;font-size:15px}.sm-markIcon.sm-markOn{color:var(--dsw-alias-accent-primary,#356ae6)!important;background:var(--dsw-alias-fill-l2,#eee)}.sm-priorityBadge{padding:0 5px;border-radius:4px;background:var(--dsw-alias-fill-l2,#eee);color:var(--dsw-alias-label-secondary,#666)}.sm-priority-1{color:#c2410c;background:rgba(234,88,12,.12)}.sm-priority-2{color:#a16207;background:rgba(202,138,4,.12)}",
661
+ // .sm-markIcon.sm-markOn keeps !important because the generic
662
+ // `.sm-panelDialog .sm-rowActions .sm-rowBtn` colour rule (3 classes)
663
+ // outranks this 2-class selector; it overrides the plugin's own rule,
664
+ // not DSH's theme. The .sm-priority-1/2 colours below are plugin
665
+ // semantics (priority levels have no DSH token); they and their dark
666
+ // branch are the only intentional data-driven theme branch left.
667
+ ".sm-markIcon{min-width:25px;padding-left:5px;padding-right:5px;font-size:15px}.sm-markIcon.sm-markOn{color:var(--dsw-alias-state-business-primary,#356ae6)!important;background:var(--dsw-alias-bg-module-platform,#f5f6f7)}.sm-priorityBadge{padding:0 5px;border-radius:4px;background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#666)}.sm-priority-1{color:#c2410c;background:rgba(234,88,12,.12)}.sm-priority-2{color:#a16207;background:rgba(202,138,4,.12)}",
492
668
  "[data-sm-theme=dark] .sm-priority-1{color:#fdba74}[data-sm-theme=dark] .sm-priority-2{color:#fde68a}",
493
- ".sm-tagChip,.sm-noteBadge{max-width:88px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:0;border-radius:4px;background:var(--dsw-alias-fill-l2,#eee);color:var(--dsw-alias-label-secondary,#666);font:inherit;line-height:16px;padding:0 5px;cursor:pointer}.sm-noteBadge{background:transparent}.sm-tagChip:hover,.sm-noteBadge:hover{color:var(--dsw-alias-accent-primary,#356ae6)}",
494
- ".sm-markFilters{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.sm-markFiltersGroup{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.sm-markFiltersSpacer{flex:1 1 auto;min-width:12px}.sm-markFilterToggle{height:28px;padding:3px 10px;white-space:nowrap}.sm-markFilterToggle:disabled{opacity:.5;cursor:not-allowed}.sm-bulkSelectBtn{height:28px;padding:3px 10px;white-space:nowrap}.sm-bulkSelectBtn:disabled{opacity:.5;cursor:not-allowed}.sm-header{flex-wrap:wrap}",
495
- ".sm-annotationDialog.sm-nativeDialogLayer{position:fixed;display:block;pointer-events:none;z-index:10002;background:transparent}.sm-annotationDialog .sm-nativeDialogBackdrop{display:none}.sm-annotationDialog.sm-annotationDialog-anchored.sm-nativeDialogLayer{inset:auto;background:transparent}.sm-annotationDialog.sm-confirmDialog.sm-nativeDialogLayer:not(.sm-annotationDialog-anchored){inset:50% auto auto calc(50vw + 308px);transform:translateY(-50%);max-height:calc(100vh - 32px)}.sm-panelDialog.sm-annotationSurface{position:absolute;width:360px;max-height:min(680px,calc(100vh - 32px));pointer-events:auto;z-index:10003;box-shadow:0 16px 48px rgba(0,0,0,.25)}.sm-panelDialog.sm-annotationSurface.sm-annotationSurface-anchored{position:relative;transform:none;left:auto;top:auto;margin:0}.sm-annotationCloseHint{margin:0 16px 8px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}",
669
+ ".sm-tagChip,.sm-noteBadge{max-width:88px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:0;border-radius:4px;background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#666);font:inherit;line-height:16px;padding:0 5px;cursor:pointer}.sm-noteBadge{background:transparent}.sm-tagChip:hover,.sm-noteBadge:hover{color:var(--dsw-alias-state-business-primary,#356ae6)}",
670
+ ".sm-markFilters{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.sm-markFiltersGroup{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.sm-markFiltersSpacer{flex:1 1 auto;min-width:12px}.sm-markFilterToggle{height:28px;padding:3px 10px;white-space:nowrap}.sm-markFilterToggle:disabled{opacity:.5;cursor:not-allowed}.sm-bulkSelectBtn{height:28px;padding:3px 10px;white-space:nowrap}.sm-bulkSelectBtn:disabled{opacity:.5;cursor:not-allowed}",
671
+ // Layering contract (do not raise these above 1100).
672
+ // DSH's own scale is: 1000 = modal backdrop / full-viewport overlay,
673
+ // 1100 = floating tier shared by menus, popovers, dialogs AND the
674
+ // Tooltip bubble this plugin renders through renderTip(). DSH's Tooltip
675
+ // bubble has a hard-coded z-index (100 inline, 1100 when portaled) and
676
+ // exposes no class hook, so a plugin surface stacked above 1100 always
677
+ // hides its own tooltips.
678
+ // The plugin therefore keeps its two document-level layers inside the
679
+ // 1000..1100 gap -- the panel at 1020 and this overlay root at 1050 --
680
+ // and everything portaled into this root is relative to its context.
681
+ "#dsh-session-manager-overlay-root{position:fixed!important;inset:0!important;width:100%!important;height:100%!important;z-index:1050!important;pointer-events:none!important;overflow:visible!important}.sm-annotationDialog.sm-nativeDialogLayer{position:fixed;display:block;pointer-events:none;z-index:100002;background:transparent}.sm-annotationDialog .sm-nativeDialogBackdrop{display:none}.sm-annotationDialog.sm-annotationDialog-anchored.sm-nativeDialogLayer,.sm-confirmDialog.sm-confirmDialog-anchored.sm-nativeDialogLayer{inset:auto;background:transparent}.sm-annotationDialog.sm-confirmDialog.sm-nativeDialogLayer:not(.sm-annotationDialog-anchored){inset:50% auto auto calc(50vw + 308px);transform:translateY(-50%);max-height:calc(100vh - 32px)}.sm-panelDialog.sm-annotationSurface{position:absolute;width:360px;max-height:min(680px,calc(100vh - 32px));pointer-events:auto;z-index:100003;box-shadow:0 16px 48px rgba(0,0,0,.25)}.sm-panelDialog.sm-annotationSurface.sm-annotationSurface-anchored{position:relative;transform:none;left:auto;top:auto;margin:0}.sm-annotationCloseHint{margin:0 16px 8px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}",
496
682
  ".sm-annotationBody{padding:14px 16px;display:flex;flex-direction:column;gap:12px;min-height:0;overflow:auto;flex:1 1 auto}.sm-annotationTarget{font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary,#666)}.sm-annotationChecks{display:grid;grid-template-columns:auto 1fr;column-gap:14px;row-gap:4px;align-items:center;font-size:13px}.sm-annotationChecks>label{display:flex;gap:6px;align-items:center;cursor:pointer}.sm-annotationChecks .sm-annotationPriority{display:inline-flex;align-items:center;gap:6px;font-size:13px;line-height:18px;color:inherit}.sm-annotationChecks .sm-prioritySelectInline{height:24px;padding:2px 6px;font-size:12px;line-height:18px;min-width:104px;width:auto;border-radius:6px;color:var(--dsw-alias-label-primary,#111)}.sm-annotationChecks>small{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#888);justify-self:start}.sm-annotationHint,.sm-annotationBody small{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#888)}",
497
- ".sm-noteInput{box-sizing:border-box;width:100%;min-height:60px;resize:vertical;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;padding:8px;background:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);font:inherit;font-size:13px;line-height:19px}.sm-noteInput:focus-visible{outline:2px solid var(--dsw-alias-accent-primary,#356ae6);outline-offset:2px}.sm-markConflict{display:flex;flex-direction:column;gap:5px;font-size:12px;padding:8px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:6px}.sm-fieldWithClear .sm-fieldRow{position:relative;display:flex;align-items:flex-start;gap:4px}.sm-fieldRow .sm-queryInput{flex:1 1 auto;min-width:0}.sm-fieldRowNote{align-items:stretch}.sm-fieldClear{position:absolute;top:5px;right:6px;width:22px;height:22px;border:0;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:4px;font-size:14px;line-height:18px;display:inline-flex;align-items:center;justify-content:center;padding:0}.sm-fieldClear:hover,.sm-fieldClear:focus-visible{color:var(--dsw-alias-accent-primary);background:var(--dsw-alias-interactive-bg-hover);outline:none}.sm-fieldClearNote{top:6px}.sm-importGroup{display:flex;flex-direction:column;gap:8px;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);background:var(--dsw-alias-fill-l1,#fafbfc)}.sm-importGroupButtons{display:flex;justify-content:space-between;align-items:center;gap:8px}.sm-importBtn{cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-surface-l1,#fff);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;padding:5px 12px;font-size:12px;line-height:18px}.sm-importBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.sm-importBtn:disabled{opacity:.5;cursor:not-allowed}.sm-importStatus{margin:0 16px;padding:8px 10px;font-size:12px;line-height:18px;background:var(--dsw-alias-accent-primary-bg,rgba(53,106,230,.08));color:var(--dsw-alias-label-secondary);border-radius:6px;white-space:pre-wrap;word-break:break-word}",
498
- ".sm-bulkBar{box-sizing:border-box;display:grid;grid-template-columns:repeat(6,minmax(0,1fr));align-items:stretch;gap:6px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:10px;background:var(--dsw-alias-fill-l1,#fafbfc)}.sm-bulkBar .sm-bulkSummary{grid-column:1/-1;color:var(--dsw-alias-label-primary,#111);font-size:12px;line-height:18px;font-weight:500;margin:0 0 2px}",
683
+ ".sm-noteInput{box-sizing:border-box;width:100%;min-height:60px;resize:vertical;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;padding:8px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);font:inherit;font-size:13px;line-height:19px}.sm-noteInput:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#356ae6);outline-offset:2px}.sm-markConflict{display:flex;flex-direction:column;gap:5px;font-size:12px;padding:8px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:6px}.sm-fieldWithClear .sm-fieldRow{position:relative;display:flex;align-items:flex-start;gap:4px}.sm-fieldRow .sm-queryInput{flex:1 1 auto;min-width:0}.sm-fieldRowNote{align-items:stretch}.sm-fieldClear{position:absolute;top:5px;right:6px;width:22px;height:22px;border:0;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:4px;font-size:14px;line-height:18px;display:inline-flex;align-items:center;justify-content:center;padding:0}.sm-fieldClear:hover,.sm-fieldClear:focus-visible{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover);outline:none}.sm-fieldClearNote{top:6px}.sm-importGroup{display:flex;flex-direction:column;gap:8px;padding:12px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}.sm-importGroupHeader{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:20px}.sm-importLabel{color:var(--dsw-alias-label-secondary,#666);font-size:11px;line-height:16px}.sm-importGroupButtons{display:flex;align-items:center;gap:6px;flex:none}.sm-importBtn{cursor:pointer;color:var(--dsw-alias-label-primary,#111);background:transparent;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;text-align:center}.sm-importBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.sm-importBtn:disabled{opacity:.5;cursor:not-allowed}.sm-importStatus{margin:0;padding:8px 10px;font-size:12px;line-height:18px;background:var(--dsw-alias-state-business-tertiary,rgba(53,106,230,.08));color:var(--dsw-alias-label-secondary);border-radius:6px;white-space:pre-wrap;word-break:break-word}",
684
+ ".sm-bulkBar{box-sizing:border-box;display:grid;grid-template-columns:repeat(6,minmax(0,1fr));align-items:stretch;gap:6px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff)}.sm-bulkBar .sm-bulkSummary{grid-column:1/-1;color:var(--dsw-alias-label-primary,#111);font-size:12px;line-height:18px;font-weight:500;margin:0 0 2px}",
499
685
  ".sm-bulkSummary{color:var(--dsw-alias-label-primary,#111);font-size:12px;line-height:18px;font-weight:500}",
500
686
  "",
501
- ".sm-bulkBar .sm-bulkBtn{cursor:pointer;color:var(--dsw-alias-label-secondary,#666);background:var(--dsw-alias-surface-l1,#fff);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;padding:4px 10px;font-size:12px;line-height:18px;display:inline-flex;align-items:center;justify-content:center;gap:4px;white-space:nowrap;text-align:center}",
687
+ ".sm-panelDialog button,.sm-panelDialog a,.sm-panelHeaderGithub,.sm-panelHeaderWhale,.sm-panelHeaderClose,.sm-headerBtn,.sm-headerMenuItem,.sm-updateDialog button,.sm-settingsCard button,.sm-settingsCard a,.sm-rowBtn,.sm-bulkBtn{outline:none;box-shadow:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.sm-panelDialog button:hover,.sm-panelDialog a:hover,.sm-panelHeaderGithub:hover,.sm-panelHeaderWhale:hover,.sm-panelHeaderClose:hover,.sm-headerBtn:hover,.sm-headerMenuItem:hover,.sm-updateDialog button:hover,.sm-settingsCard button:hover,.sm-rowBtn:hover,.sm-bulkBtn:hover{outline:none;box-shadow:none}",
688
+ ".sm-bulkBar .sm-bulkBtn{cursor:pointer;color:var(--dsw-alias-label-secondary,#666);background:transparent;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;padding:4px 10px;font-size:12px;line-height:18px;display:inline-flex;align-items:center;justify-content:center;gap:4px;white-space:nowrap;text-align:center}",
502
689
  ".sm-bulkBar .sm-bulkBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary,#111)}",
503
690
  ".sm-bulkBar .sm-bulkBtn:disabled{opacity:.5;cursor:not-allowed}",
504
- ".sm-bulkBar .sm-bulkBtnDanger{color:var(--dsw-alias-state-error-primary);border-color:rgba(217,80,80,.4)}",
505
- ".sm-bulkBar .sm-bulkBtnDanger:hover:not(:disabled){color:#fff;background:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}",
691
+ ".sm-bulkBar .sm-bulkBtnDanger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 45%,transparent)}",
692
+ ".sm-bulkBar .sm-bulkBtnDanger:hover:not(:disabled){color:var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}",
506
693
  "",
507
694
  ".sm-bulkRowCheckbox{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;flex:none}",
508
695
  ".sm-bulkRowCheckbox input{width:14px;height:14px;cursor:pointer;margin:0}",
509
696
  ".sm-bulkRowCheckbox input:disabled{cursor:not-allowed}",
510
- ".sm-rowBulkSelected{border-color:var(--dsw-alias-accent-primary,#356ae6);background:var(--dsw-alias-accent-primary-bg,rgba(53,106,230,.06))}",
511
- ".sm-bulkDialog.sm-nativeDialogLayer{position:fixed;left:calc(50vw + 184px);top:calc(50vh - 90px);width:360px;max-width:calc(100vw - 32px);max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:calc(50vh - 90px) auto auto calc(50vw + 308px);z-index:10002}.sm-bulkDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-bulkDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:auto;box-sizing:border-box;background:var(--dsw-alias-surface-l1,#fff);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);padding:0;overflow:hidden;animation:sm-confirmPop .14s ease-out}.sm-bulkDialog .sm-nativeDialogHeader{padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;display:flex;align-items:center;gap:8px}.sm-bulkDialog .sm-nativeDialogTitle{margin:0;font-size:14px;line-height:20px;font-weight:600;text-align:center;width:100%}.sm-bulkDialog .sm-nativeDialogBody{padding:12px 16px;display:flex;flex-direction:column;gap:10px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}.sm-bulkDialog .sm-nativeDialogFooter{padding:10px 16px;display:flex;flex-wrap:wrap;justify-content:space-between;gap:6px}.sm-bulkDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-bulkDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-accent-primary,#356ae6);color:#fff;border-color:var(--dsw-alias-accent-primary,#356ae6)}.sm-bulkDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}.sm-bulkDialog .sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:#fff;border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-bulkDialog .sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-bulkDialog .sm-nativeDialog select.sm-viewSelect{font-size:13px;line-height:20px;height:32px;padding:5px 9px}",
697
+ ".sm-rowBulkSelected{border-color:var(--dsw-alias-state-business-primary,#356ae6);background:var(--dsw-alias-state-business-tertiary,rgba(53,106,230,.06))}",
698
+ ".sm-bulkDialog.sm-nativeDialogLayer{position:fixed;left:calc(50vw + 184px);top:calc(50vh - 90px);width:360px;max-width:calc(100vw - 32px);max-height:none;display:block;align-items:initial;justify-content:initial;box-sizing:border-box;background:transparent;border:0;box-shadow:none;padding:0;inset:calc(50vh - 90px) auto auto calc(50vw + 308px);z-index:10002}.sm-bulkDialog.sm-nativeDialogLayer .sm-nativeDialogBackdrop{display:none}.sm-bulkDialog.sm-nativeDialogLayer .sm-nativeDialog{position:relative;left:auto;top:auto;transform:none;width:auto;box-sizing:border-box;background:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);padding:0;overflow:hidden;animation:sm-confirmPop .14s ease-out}.sm-bulkDialog .sm-nativeDialogHeader{padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;position:relative;display:flex;align-items:center;justify-content:center;min-height:44px;box-sizing:border-box}.sm-bulkDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:14px;line-height:20px;font-weight:600;text-align:center;width:auto;max-width:calc(100% - 32px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-bulkDialog .sm-nativeDialogBody{padding:12px 16px;display:flex;flex-direction:column;gap:10px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}.sm-bulkDialog .sm-nativeDialogFooter{padding:10px 16px;display:flex;flex-wrap:wrap;justify-content:space-between;gap:6px}.sm-bulkDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-bulkDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-bulkDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}.sm-bulkDialog .sm-nativeDialogDanger{background:var(--dsw-alias-state-error-primary,#d92d20);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-error-primary,#d92d20)}.sm-bulkDialog .sm-nativeDialogDanger:hover:not(:disabled){filter:brightness(.92)}.sm-bulkDialog .sm-nativeDialog select.sm-viewSelect{font-size:13px;line-height:20px;height:32px;padding:5px 9px}",
512
699
  ".sm-bulkDialog .sm-nativeDialog{width:100%;max-height:min(640px,calc(100vh - 32px))}",
513
700
  ".sm-bulkDialogBody{display:flex;flex-direction:column;gap:12px;min-height:0}",
514
701
  ".sm-bulkDialogSummary{font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}",
515
- ".sm-bulkDialogWarn{display:flex;align-items:flex-start;gap:8px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;background:rgba(217,80,80,.08);border:1px solid rgba(217,80,80,.25);border-radius:8px;padding:8px 10px}",
702
+ ".sm-bulkDialogWarn{display:flex;align-items:flex-start;gap:8px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 8%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 25%,transparent);border-radius:8px;padding:8px 10px}",
516
703
  ".sm-bulkSessionList{display:flex;flex-direction:column;gap:4px;max-height:200px;overflow:auto;border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px;padding:6px}",
517
704
  ".sm-bulkSessionItem{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#666);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
518
- ".sm-bulkProgressBar{position:relative;height:8px;background:var(--dsw-alias-fill-l2,#eee);border-radius:4px;overflow:hidden}",
519
- ".sm-bulkProgressFill{position:absolute;top:0;left:0;bottom:0;background:var(--dsw-alias-accent-primary,#356ae6);transition:width .2s ease}",
705
+ ".sm-bulkProgressBar{position:relative;height:8px;background:var(--dsw-alias-bg-skeleton,#0000000a);border-radius:4px;overflow:hidden}",
706
+ ".sm-bulkProgressFill{position:absolute;top:0;left:0;bottom:0;background:var(--dsw-alias-state-business-primary,#356ae6);transition:width .2s ease}",
520
707
  ".sm-bulkResultGroup{display:flex;flex-direction:column;gap:6px}",
521
708
  ".sm-bulkResultHeader{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary,#666)}",
522
709
  ".sm-bulkResultList{display:flex;flex-direction:column;gap:2px;max-height:160px;overflow:auto;border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px;padding:6px}",
@@ -526,22 +713,25 @@ function nextDialogZ() { return ++dialogZStackCounter; }
526
713
  ".sm-bulkResultItemReason{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;word-break:break-word}",
527
714
  ".sm-bulkResultItemReasonSkip{color:var(--dsw-alias-label-tertiary,#888)}",
528
715
  ".sm-bulkResultFooter{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}",
529
- ".sm-footerBtn-wide{flex:1 1 auto;min-width:0;min-height:36px;justify-content:flex-start;padding:0 10px;border-radius:12px;font-size:13px;line-height:20px;gap:8px}",
716
+ ".sm-footerBtn-wide{flex:none;display:flex;align-items:center;gap:8px;width:calc(100% + 8px);height:34px;margin:4px -4px 4px;padding:6px 2px 6px 10px;box-sizing:border-box;border:none;border-radius:12px;cursor:pointer;overflow:hidden;font-size:14px;line-height:22px}",
530
717
  ".sm-footerBtn-wide:hover,.sm-footerBtn-wide:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
531
- ".sm-footerBtn-rail{width:36px;height:36px;padding:0;justify-content:center;border-radius:12px;color:var(--dsw-alias-label-secondary)}",
718
+ ".sm-footerBtn-rail{width:36px;height:36px;padding:0;justify-content:center;border-radius:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;display:inline-flex;align-items:center}",
532
719
  ".sm-footerBtn-rail:hover,.sm-footerBtn-rail:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
533
720
  ".sm-footerBtn-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%}",
534
- ".sm-footer-wide{display:flex;width:100%}",
535
- ".sm-footer-rail{display:flex;width:100%;justify-content:center}",
721
+ ".sm-overlayLayer{position:fixed;inset:0;pointer-events:none}",
722
+ ".sm-overlayLayer>*{pointer-events:auto}",
723
+ ".sm-overlayBackdrop{position:fixed;inset:0;background:transparent;pointer-events:auto}",
536
724
  "@media(max-width:720px){.sm-panelSelects{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.sm-panelDialog .sm-row{flex-wrap:wrap}.sm-panelDialog .sm-rowMain{flex-basis:100%}.sm-panelDialog .sm-rowActions{width:100%;min-width:0;flex:1 1 100%;justify-content:flex-start}}@media(max-width:540px){.sm-panelSelects{grid-template-columns:minmax(0,1fr)}}",
537
725
  ].join("");
538
726
 
539
727
  // Auto-follow DSH's theme. We detect DSH's current theme from
540
- // body[data-ds-dark-theme] / color-scheme / data-theme / class tokens,
541
- // mirror it onto <html data-sm-theme>, and apply inline dialog colors.
542
- // The dialogs use that attribute via the scoped CSS rules above, and
543
- // inline styles guarantee an opaque background even when DSH's tokens
544
- // resolve to `unset` / `transparent`.
728
+ // body[data-ds-dark-theme] / color-scheme / data-theme / class tokens and
729
+ // mirror it onto <html data-sm-theme>. The scoped CSS rules above consume
730
+ // that attribute (light/dark fallback surface colour, local alias tokens).
731
+ // Deliberately no inline colour is written onto the dialogs any more:
732
+ // inline `!important` backgrounds/colors used to hard-code white / #1f1f23
733
+ // onto the dialog roots, which overrode any theme — including third-party
734
+ // themes that only publish --dsw-alias-* tokens.
545
735
  const SM_THEME_ATTR = "data-sm-theme";
546
736
  function smReadDshTheme() {
547
737
  try {
@@ -572,29 +762,10 @@ function nextDialogZ() { return ++dialogZStackCounter; }
572
762
  } catch (_) { /* ignore */ }
573
763
  return "light";
574
764
  }
575
- function smApplyInlineTheme(theme) {
576
- try {
577
- const values = theme === "dark"
578
- ? { background: "#1f1f23", color: "#ececec", border: "#3a3a40" }
579
- : { background: "#fff", color: "#1f1f1f", border: "#e5e5e5" };
580
- const roots = document.querySelectorAll(
581
- ".sm-panelDialog," +
582
- ".sm-confirmDialog.sm-nativeDialogLayer .sm-nativeDialog," +
583
- ".sm-migrateDialog.sm-nativeDialogLayer .sm-nativeDialog"
584
- );
585
- for (const el of roots) {
586
- el.style.setProperty("background", values.background, "important");
587
- el.style.setProperty("background-color", values.background, "important");
588
- el.style.setProperty("color", values.color, "important");
589
- el.style.setProperty("border-color", values.border, "important");
590
- }
591
- } catch (_) { /* ignore */ }
592
- }
593
765
  function smApplyTheme() {
594
766
  try {
595
767
  const theme = smReadDshTheme();
596
768
  document.documentElement.setAttribute(SM_THEME_ATTR, theme);
597
- smApplyInlineTheme(theme);
598
769
  } catch (_) { /* ignore */ }
599
770
  }
600
771
  smApplyTheme();
@@ -608,13 +779,66 @@ function nextDialogZ() { return ++dialogZStackCounter; }
608
779
  } catch (_) { /* ignore */ }
609
780
  }
610
781
 
782
+ // "Dialog opacity" preference (Issue #23).
783
+ //
784
+ // Generic, theme-agnostic escape hatch for third-party themes whose
785
+ // dialog surface does not match the plugin's own fill: the user tunes how
786
+ // much of *this plugin's* dialog background is painted, and whatever sits
787
+ // behind it (the theme's own surface) shows through. No theme plugin is
788
+ // detected or named, and no third-party CSS variable is read.
789
+ //
790
+ // The value is stored as a percentage in the plugin's own CSS variable on
791
+ // <html>; the CSS rules above turn it into the alpha channel of the dialog
792
+ // fill only, leaving text, buttons, inputs, borders and hover states
793
+ // opaque.
794
+ const SM_DIALOG_OPACITY = { min: 50, max: 100, step: 5, default: 100 };
795
+ const SM_DIALOG_OPACITY_KEY = "dsh-session-manager-dialog-opacity";
796
+ const SM_DIALOG_OPACITY_VAR = "--sm-dialog-opacity";
797
+ function smNormalizeDialogOpacity(value) {
798
+ const numeric = Number(value);
799
+ if (!Number.isFinite(numeric)) return SM_DIALOG_OPACITY.default;
800
+ const { min, max, step } = SM_DIALOG_OPACITY;
801
+ if (numeric <= min) return min;
802
+ if (numeric >= max) return max;
803
+ const snapped = min + Math.round((numeric - min) / step) * step;
804
+ return Math.min(max, Math.max(min, snapped));
805
+ }
806
+ function smReadDialogOpacity() {
807
+ try {
808
+ if (typeof localStorage !== "undefined") {
809
+ const raw = localStorage.getItem(SM_DIALOG_OPACITY_KEY);
810
+ if (raw !== null && raw !== "") return smNormalizeDialogOpacity(raw);
811
+ }
812
+ } catch (_) { /* storage unavailable: fall back to the default */ }
813
+ return SM_DIALOG_OPACITY.default;
814
+ }
815
+ function applyDialogOpacity(value) {
816
+ const percent = smNormalizeDialogOpacity(value);
817
+ try {
818
+ if (typeof document !== "undefined" && document.documentElement && document.documentElement.style) {
819
+ document.documentElement.style.setProperty(SM_DIALOG_OPACITY_VAR, percent + "%");
820
+ }
821
+ } catch (_) { /* ignore */ }
822
+ return percent;
823
+ }
824
+ applyDialogOpacity(smReadDialogOpacity());
825
+
611
826
  const CSS_ID = "dsh-session-manager/session-manager";
612
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(CSS_ID) + "]") === null) {
613
- const tag = document.createElement("style");
614
- tag.dataset.plugin = "dsh-session-manager";
615
- tag.dataset.pluginCss = CSS_ID;
616
- tag.textContent = css;
617
- document.head.appendChild(tag);
827
+ const SM_CSS_SELECTOR = "style[data-plugin-css=" + JSON.stringify(CSS_ID) + "]";
828
+ if (typeof document !== "undefined" && typeof document.createElement === "function") {
829
+ let styleTag = typeof document.querySelector === "function" ? document.querySelector(SM_CSS_SELECTOR) : null;
830
+ if (!styleTag) {
831
+ styleTag = document.createElement("style");
832
+ styleTag.dataset.plugin = "dsh-session-manager";
833
+ styleTag.dataset.pluginCss = CSS_ID;
834
+ if (document.head && typeof document.head.appendChild === "function") document.head.appendChild(styleTag);
835
+ }
836
+ // Always rewrite the contents. A client-plugin hot reload re-executes the
837
+ // bundle inside the same page but leaves the <style> tag it created
838
+ // earlier in place; skipping the write there would silently keep the
839
+ // previous version's rules while the new JS (e.g. a new setting) is
840
+ // already live -- exactly the "setting present but has no effect" bug.
841
+ styleTag.textContent = css;
618
842
  }
619
843
 
620
844
 
@@ -656,6 +880,275 @@ function nextDialogZ() { return ++dialogZStackCounter; }
656
880
  try { installAnchor(); } catch (_) {}
657
881
 
658
882
  const h = (type, props, ...children) => React.createElement(type, props, ...children);
883
+
884
+ let overlayRootElement = null;
885
+ function getOverlayRoot() {
886
+ if (typeof document === "undefined" || !document.body) return null;
887
+ if (typeof document.createElement !== "function") return null;
888
+ const contains = typeof document.body.contains === "function" ? (el) => document.body.contains(el) : () => true;
889
+ if (!overlayRootElement || !contains(overlayRootElement)) {
890
+ let el = typeof document.getElementById === "function"
891
+ ? document.getElementById("dsh-session-manager-overlay-root")
892
+ : (typeof document.querySelector === "function" ? document.querySelector("#dsh-session-manager-overlay-root") : null);
893
+ if (!el) {
894
+ el = document.createElement("div");
895
+ el.id = "dsh-session-manager-overlay-root";
896
+ el.style.position = "fixed";
897
+ el.style.top = "0";
898
+ el.style.left = "0";
899
+ el.style.width = "100%";
900
+ el.style.height = "100%";
901
+ el.style.zIndex = "1050";
902
+ el.style.overflow = "visible";
903
+ el.style.pointerEvents = "none";
904
+ if (typeof document.body.appendChild === "function") {
905
+ document.body.appendChild(el);
906
+ }
907
+ }
908
+ // Re-assert the z-index on an element created by an earlier version
909
+ // (a client-plugin hot reload re-executes this module in the same page
910
+ // and reuses the existing node, whose inline style would otherwise keep
911
+ // the old value). Must stay below DSH's 1100 floating/tooltip tier and
912
+ // above its 1000 backdrop tier -- see the layering contract in the
913
+ // stylesheet.
914
+ if (el.style && el.style.zIndex !== "1050") el.style.zIndex = "1050";
915
+ overlayRootElement = el;
916
+ }
917
+ try {
918
+ if (document.documentElement && typeof document.documentElement.getAttribute === "function") {
919
+ const currentTheme = document.documentElement.getAttribute(SM_THEME_ATTR);
920
+ if (currentTheme && overlayRootElement && typeof overlayRootElement.setAttribute === "function") {
921
+ if (overlayRootElement.getAttribute(SM_THEME_ATTR) !== currentTheme) {
922
+ overlayRootElement.setAttribute(SM_THEME_ATTR, currentTheme);
923
+ }
924
+ }
925
+ }
926
+ } catch (_) {}
927
+ return overlayRootElement;
928
+ }
929
+
930
+ function getPortalFn() {
931
+ if (typeof React.createPortal === "function") return React.createPortal;
932
+ if (typeof window !== "undefined") {
933
+ if (window.__smReactDOM && typeof window.__smReactDOM.createPortal === "function") return window.__smReactDOM.createPortal;
934
+ if (window.ReactDOM && typeof window.ReactDOM.createPortal === "function") return window.ReactDOM.createPortal;
935
+ if (window.React && typeof window.React.createPortal === "function") return window.React.createPortal;
936
+ }
937
+ try {
938
+ const RDOM = require("react-dom");
939
+ if (RDOM && typeof RDOM.createPortal === "function") return RDOM.createPortal;
940
+ } catch (_) {}
941
+ try {
942
+ const RDOMC = require("react-dom/client");
943
+ if (RDOMC && typeof RDOMC.createPortal === "function") return RDOMC.createPortal;
944
+ } catch (_) {}
945
+ return null;
946
+ }
947
+
948
+ function renderInOverlay(vnode) {
949
+ if (!vnode) return null;
950
+ const root = getOverlayRoot();
951
+ const portal = getPortalFn();
952
+ if (portal && root) {
953
+ return portal(vnode, root);
954
+ }
955
+ return vnode;
956
+ }
957
+
958
+ function OverlayLayer({ children, className, style, onBackdropClick, role = "presentation", onKeyDown }) {
959
+ const zRef = useRef(null);
960
+ if (zRef.current === null) {
961
+ zRef.current = nextDialogZ();
962
+ }
963
+ const root = getOverlayRoot();
964
+ const portal = getPortalFn();
965
+
966
+ const layerStyle = {
967
+ position: "fixed",
968
+ inset: "0",
969
+ zIndex: zRef.current,
970
+ pointerEvents: "none",
971
+ ...(style || {})
972
+ };
973
+
974
+ const content = h("div", {
975
+ className: "sm-overlayLayer " + (className || ""),
976
+ style: layerStyle,
977
+ role: role,
978
+ onKeyDown: onKeyDown
979
+ },
980
+ onBackdropClick ? h("div", {
981
+ className: "sm-nativeDialogBackdrop sm-overlayBackdrop",
982
+ style: {
983
+ position: "fixed",
984
+ inset: "0",
985
+ background: "transparent",
986
+ pointerEvents: "auto",
987
+ zIndex: -1
988
+ },
989
+ onClick: (e) => {
990
+ if (e.target === e.currentTarget && onBackdropClick) {
991
+ onBackdropClick(e);
992
+ }
993
+ }
994
+ }) : null,
995
+ children
996
+ );
997
+
998
+ if (portal && root) {
999
+ return portal(content, root);
1000
+ }
1001
+ return content;
1002
+ }
1003
+
1004
+ const raf = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 16);
1005
+ const caf = typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : (id) => clearTimeout(id);
1006
+
1007
+ function useFloatingPosition(anchor, open, options = {}) {
1008
+ const {
1009
+ placement = "bottom-end",
1010
+ dialogWidth = 360,
1011
+ dialogHeight = 220,
1012
+ gap = 8,
1013
+ margin = 16
1014
+ } = options;
1015
+
1016
+ const [coords, setCoords] = useState(() => {
1017
+ if (anchor && typeof anchor.top === "number" && typeof anchor.left === "number") {
1018
+ return { top: anchor.top, left: anchor.left };
1019
+ }
1020
+ return null;
1021
+ });
1022
+
1023
+ const anchorRef = useRef(anchor);
1024
+ anchorRef.current = anchor;
1025
+
1026
+ useEffect(() => {
1027
+ if (!open) return;
1028
+ const currentAnchor = anchorRef.current;
1029
+ if (!currentAnchor) return;
1030
+
1031
+ if (typeof currentAnchor.top === "number" && typeof currentAnchor.getBoundingClientRect !== "function") {
1032
+ setCoords({ top: currentAnchor.top, left: currentAnchor.left });
1033
+ return;
1034
+ }
1035
+
1036
+ const getEl = () => {
1037
+ const a = anchorRef.current;
1038
+ if (!a) return null;
1039
+ if (typeof a.getBoundingClientRect === "function") return a;
1040
+ if (a.current && typeof a.current.getBoundingClientRect === "function") return a.current;
1041
+ if (a.nodeType === 1) return a;
1042
+ return null;
1043
+ };
1044
+
1045
+ const targetEl = getEl();
1046
+ if (!targetEl) return;
1047
+
1048
+ let frameId = 0;
1049
+ const update = () => {
1050
+ caf(frameId);
1051
+ frameId = raf(() => {
1052
+ const el = getEl();
1053
+ if (!el) return;
1054
+ const r = el.getBoundingClientRect();
1055
+ if (!r || (r.width === 0 && r.height === 0 && r.top === 0 && r.bottom === 0)) {
1056
+ return;
1057
+ }
1058
+ const winW = typeof window !== "undefined" && window.innerWidth ? window.innerWidth : 1024;
1059
+ const winH = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : 768;
1060
+
1061
+ let left;
1062
+ if (placement.endsWith("-end")) {
1063
+ left = r.right - dialogWidth;
1064
+ } else if (placement.endsWith("-start")) {
1065
+ left = r.left;
1066
+ } else {
1067
+ left = r.left + (r.width - dialogWidth) / 2;
1068
+ }
1069
+ left = Math.max(margin, Math.min(left, winW - dialogWidth - margin));
1070
+
1071
+ let top;
1072
+ const fitsBelow = (r.bottom + gap + dialogHeight) <= (winH - margin);
1073
+ const fitsAbove = (r.top - gap - dialogHeight) >= margin;
1074
+
1075
+ if (placement.startsWith("top")) {
1076
+ if (fitsAbove || !fitsBelow) {
1077
+ top = r.top - dialogHeight - gap;
1078
+ } else {
1079
+ top = r.bottom + gap;
1080
+ }
1081
+ top = Math.max(margin, Math.min(top, winH - dialogHeight - margin));
1082
+ } else {
1083
+ // For anchors near viewport top (like title bar actions), default to below.
1084
+ // Never jump upward over the header when there is insufficient space above.
1085
+ if (!fitsBelow && fitsAbove && r.top > dialogHeight + gap + margin) {
1086
+ top = r.top - dialogHeight - gap;
1087
+ } else {
1088
+ top = r.bottom + gap;
1089
+ }
1090
+ top = Math.max(margin, top);
1091
+ }
1092
+
1093
+ setCoords({ top: Math.round(top), left: Math.round(left) });
1094
+ });
1095
+ };
1096
+
1097
+ update();
1098
+
1099
+ if (typeof window !== "undefined") {
1100
+ window.addEventListener("resize", update);
1101
+ window.addEventListener("scroll", update, true);
1102
+ if (window.visualViewport) {
1103
+ window.visualViewport.addEventListener("resize", update);
1104
+ window.visualViewport.addEventListener("scroll", update);
1105
+ }
1106
+ }
1107
+
1108
+ const pollTimer = setInterval(update, 100);
1109
+
1110
+ let ro = null;
1111
+ const getRealEl = (target) => {
1112
+ if (!target) return null;
1113
+ if (typeof Element !== "undefined" && target instanceof Element) return target;
1114
+ if (target.current && typeof Element !== "undefined" && target.current instanceof Element) return target.current;
1115
+ if (typeof target.resolveActiveEl === "function") {
1116
+ const resolved = target.resolveActiveEl();
1117
+ if (resolved && typeof Element !== "undefined" && resolved instanceof Element) return resolved;
1118
+ }
1119
+ if (target.nodeType === 1 && typeof target.addEventListener === "function") return target;
1120
+ return null;
1121
+ };
1122
+ const domEl = getRealEl(targetEl);
1123
+ if (typeof ResizeObserver === "function" && domEl) {
1124
+ try {
1125
+ ro = new ResizeObserver(update);
1126
+ ro.observe(domEl);
1127
+ if (domEl.parentElement) ro.observe(domEl.parentElement);
1128
+ const headerAncestor = typeof domEl.closest === "function" ? domEl.closest(".sm-header") : null;
1129
+ if (headerAncestor) ro.observe(headerAncestor);
1130
+ if (document.body) ro.observe(document.body);
1131
+ } catch (_) {}
1132
+ }
1133
+
1134
+ return () => {
1135
+ caf(frameId);
1136
+ clearInterval(pollTimer);
1137
+ if (typeof window !== "undefined") {
1138
+ window.removeEventListener("resize", update);
1139
+ window.removeEventListener("scroll", update, true);
1140
+ if (window.visualViewport) {
1141
+ window.visualViewport.removeEventListener("resize", update);
1142
+ window.visualViewport.removeEventListener("scroll", update);
1143
+ }
1144
+ }
1145
+ if (ro) ro.disconnect();
1146
+ };
1147
+ }, [open, placement, dialogWidth, dialogHeight, gap, margin]);
1148
+
1149
+ return coords;
1150
+ }
1151
+
659
1152
  const callApi = (path, payload) => fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload || {}) }).then((r) => r.json());
660
1153
  const fetchApi = (path, options) => fetch(API + path, options).then((r) => r.json());
661
1154
  // Run a bulk action across many sessionIds. /batch already does per-id
@@ -743,12 +1236,30 @@ function nextDialogZ() { return ++dialogZStackCounter; }
743
1236
  const annotationButtons = (id, title, t, state, className, onToggle, onEdit, disabled = false, editorOpen = false) => {
744
1237
  const value = annotationOf(state, id);
745
1238
  const busy = disabled || !state.loaded || !!state.error || state.pending.has(id);
746
- const favoriteLabel = t(value.favorite ? "marks.favorite.remove" : "marks.favorite.add") + ": " + title;
747
- const reviewLabel = t(value.reviewLater ? "marks.review.remove" : "marks.review.add") + ": " + title;
1239
+ const favoriteDesc = t(value.favorite ? "marks.favorite.remove" : "marks.favorite.add");
1240
+ const reviewDesc = t(value.reviewLater ? "marks.review.remove" : "marks.review.add");
1241
+ const favoriteLabel = favoriteDesc + ": " + title;
1242
+ const reviewLabel = reviewDesc + ": " + title;
1243
+ const isHeader = className === "sm-headerBtn";
748
1244
  return h(React.Fragment, null,
749
- h("button", { type: "button", className: className + " sm-markIcon" + (value.favorite ? " sm-markOn" : ""), disabled: busy, "aria-label": favoriteLabel, title: favoriteLabel, "aria-pressed": value.favorite, onClick: () => onToggle({ favorite: !value.favorite }) }, value.favorite ? "★" : "☆"),
750
- h("button", { type: "button", className: className + " sm-markIcon" + (value.reviewLater ? " sm-markOn" : ""), disabled: busy, "aria-label": reviewLabel, title: reviewLabel, "aria-pressed": value.reviewLater, onClick: () => onToggle({ reviewLater: !value.reviewLater }) }, "◷"),
751
- h("button", { type: "button", className: className + (editorOpen ? " sm-annotationToggleOpen sm-headerBtnActive" : ""), disabled: busy, "aria-label": t("marks.edit.aria") + ": " + title, title: t("marks.edit.aria"), ...(className === "sm-headerBtn" ? { "aria-expanded": editorOpen } : {}), onClick: e => onEdit(e) }, t(className === "sm-headerBtn" ? "marks.header.edit" : "marks.edit"), className === "sm-headerBtn" ? h("span", { className: "sm-priorityBadge sm-priority-" + (value.priority ?? 3), title: t("marks.priority." + (value.priority ?? 3)) }, "P" + (value.priority ?? 3)) : null)
1245
+ renderTip(favoriteDesc, h("button", { type: "button", className: className + " sm-markIcon" + (value.favorite ? " sm-markOn" : ""), disabled: busy, "aria-label": favoriteLabel, "aria-pressed": value.favorite, onClick: () => onToggle({ favorite: !value.favorite }) },
1246
+ h("span", { className: "sm-headerBtnIcon" }, value.favorite ? "★" : "☆")
1247
+ )),
1248
+ renderTip(reviewDesc, h("button", { type: "button", className: className + " sm-markIcon" + (value.reviewLater ? " sm-markOn" : ""), disabled: busy, "aria-label": reviewLabel, "aria-pressed": value.reviewLater, onClick: () => onToggle({ reviewLater: !value.reviewLater }) },
1249
+ h("span", { className: "sm-headerBtnIcon" }, "◷")
1250
+ )),
1251
+ renderTip(t("marks.edit.aria"), h("button", {
1252
+ type: "button",
1253
+ className: className + (isHeader ? " sm-headerSecondaryAction" : "") + (editorOpen ? " sm-annotationToggleOpen sm-headerBtnActive" : ""),
1254
+ disabled: busy,
1255
+ "aria-label": t("marks.edit.aria") + ": " + title,
1256
+ ...(isHeader ? { "aria-expanded": editorOpen } : {}),
1257
+ onClick: e => onEdit(e)
1258
+ },
1259
+ isHeader ? h("span", { className: "sm-headerBtnIcon" }, "🏷") : null,
1260
+ isHeader ? h("span", { className: "sm-headerBtnLabel" }, t("marks.header.edit")) : t("marks.edit"),
1261
+ isHeader ? h("span", { className: "sm-priorityBadge sm-priority-" + (value.priority ?? 3) }, "P" + (value.priority ?? 3)) : null
1262
+ ))
752
1263
  );
753
1264
  };
754
1265
 
@@ -1239,17 +1750,19 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1239
1750
  return "zh";
1240
1751
  };
1241
1752
  const activeLanguage = detectLanguage(t);
1242
- const hasAnchor = anchor && typeof anchor.top === "number" && typeof anchor.left === "number";
1243
- const layerClass = "sm-nativeDialogLayer sm-annotationDialog sm-confirmDialog" + (hasAnchor ? " sm-annotationDialog-anchored" : "");
1244
- const surfaceClass = "sm-nativeDialog sm-panelDialog sm-annotationSurface" + (hasAnchor ? " sm-annotationSurface-anchored" : "");
1245
- // Default position matches ConfirmDialog / MoveDialog so all inner
1246
- // dialogs line up at top calc(50vh - 90px), left calc(50vw + 308px).
1753
+ const floatingCoords = useFloatingPosition(anchor, true, { placement: "bottom-end", dialogWidth: 360, dialogHeight: 460 });
1754
+ const hasCoords = floatingCoords !== null || (anchor && typeof anchor.top === "number" && typeof anchor.left === "number");
1755
+ const computedTop = floatingCoords ? floatingCoords.top : (anchor && typeof anchor.top === "number" ? anchor.top : null);
1756
+ const computedLeft = floatingCoords ? floatingCoords.left : (anchor && typeof anchor.left === "number" ? anchor.left : null);
1757
+ const layerClass = "sm-nativeDialogLayer sm-annotationDialog sm-confirmDialog" + (hasCoords ? " sm-annotationDialog-anchored" : "");
1758
+ const surfaceClass = "sm-nativeDialog sm-panelDialog sm-annotationSurface" + (hasCoords ? " sm-annotationSurface-anchored" : "");
1247
1759
  const layerStyle = {
1248
1760
  zIndex: zIndex || undefined,
1249
- ...(hasAnchor ? { position: "fixed", top: anchor.top + "px", left: anchor.left + "px" } : {})
1761
+ ...(hasCoords ? { position: "fixed", top: computedTop + "px", left: computedLeft + "px", right: "auto", bottom: "auto", inset: `${computedTop}px auto auto ${computedLeft}px` } : {})
1250
1762
  };
1251
- return h("div", { className: layerClass, style: layerStyle, role: "presentation" },
1252
- h("section", { className: surfaceClass, role: "dialog", "aria-labelledby": "sm-annotation-title", ref: dialogRef, onKeyDown },
1763
+ return renderInOverlay(
1764
+ h("div", { className: layerClass, style: layerStyle, role: "presentation" },
1765
+ h("section", { className: surfaceClass, style: { pointerEvents: "auto" }, role: "dialog", "aria-labelledby": "sm-annotation-title", ref: dialogRef, onKeyDown },
1253
1766
  h("div", { className: "sm-nativeDialogHeader" }, h("h2", { id: "sm-annotation-title", className: "sm-nativeDialogTitle" }, t("marks.title"))),
1254
1767
  h("div", { className: "sm-annotationBody" },
1255
1768
  h("div", { className: "sm-annotationTarget", title: displayTitle || sessionId }, displayTitle || sessionId),
@@ -1271,20 +1784,22 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1271
1784
  error ? h("div", { className: "sm-error", role: "alert" }, error) : null
1272
1785
  ),
1273
1786
  h("div", { className: "sm-importGroup" },
1274
- h("div", { className: "sm-importGroupButtons" },
1275
- h("button", { type: "button", className: "sm-importBtn", disabled: busy || !importText.trim(), onClick: pasteFromInput }, t("marks.import.paste")),
1276
- h("button", { type: "button", className: "sm-importBtn", disabled: busy, onClick: copyPrompt }, t("marks.import.copy"))),
1787
+ h("div", { className: "sm-importGroupHeader" },
1788
+ h("span", { className: "sm-importLabel" }, t("marks.import.paste.label")),
1789
+ h("div", { className: "sm-importGroupButtons" },
1790
+ h("button", { type: "button", className: "sm-importBtn", disabled: busy || !importText.trim(), onClick: pasteFromInput }, t("marks.import.paste")),
1791
+ h("button", { type: "button", className: "sm-importBtn", disabled: busy, onClick: copyPrompt }, t("marks.import.copy")))),
1277
1792
  h("div", { className: "sm-fieldWithClear" },
1278
1793
  h("div", { className: "sm-fieldRow sm-fieldRowNote" },
1279
1794
  h("textarea", { className: "sm-noteInput", "aria-label": t("marks.import.paste.aria"), value: importText, placeholder: t("marks.import.paste.placeholder"), disabled: busy, rows: 3, onChange: e => setImportText(e.target.value) }),
1280
1795
  importText && !busy ? h("button", { type: "button", className: "sm-fieldClear sm-fieldClearNote", "aria-label": t("marks.import.paste.clear"), title: t("marks.import.paste.clear"), onClick: () => setImportText("") }, String.fromCharCode(10005)) : null))),
1281
1796
  importStatus ? h("div", { className: "sm-importStatus", role: "status" }, importStatus) : null,
1282
1797
  h("div", { className: "sm-nativeDialogFooter" },
1283
- h("button", { type: "button", className: "sm-nativeDialogButton", disabled: busy, onClick: cancel }, t("marks.cancel")),
1798
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogCancel", disabled: busy, onClick: cancel }, t("marks.cancel")),
1284
1799
  h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogConfirm", disabled: busy || !annotations.loaded || !!annotations.error || latest.revision !== base.revision, onClick: save }, t(busy ? "marks.saving" : "marks.save"))
1285
1800
  )
1286
1801
  )
1287
- );
1802
+ ));
1288
1803
  }
1289
1804
 
1290
1805
 
@@ -1351,24 +1866,31 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1351
1866
  window.addEventListener("keydown", handler);
1352
1867
  return () => window.removeEventListener("keydown", handler);
1353
1868
  }, [open]);
1354
- // Render inline as a sibling of the panel; createPortal to document.body broke under some hosts. Migrate and AnnotationDialog render inline; do the same here.
1355
1869
 
1870
+ const floatingCoords = useFloatingPosition(props.anchor, open, { placement: "bottom-end", dialogWidth: 360, dialogHeight: 180 });
1356
1871
 
1357
1872
  if (!open) return null;
1358
1873
 
1359
- return h("div", {
1360
- className: "sm-nativeDialogLayer sm-confirmDialog",
1361
- style: {
1362
- zIndex: zIndex || undefined,
1363
- ...(props.anchor && typeof props.anchor.top === "number"
1364
- ? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px" }
1365
- : {})
1366
- },
1367
- role: "presentation"
1368
- },
1874
+ const topPx = floatingCoords ? floatingCoords.top : (props.anchor && typeof props.anchor.top === "number" ? props.anchor.top : null);
1875
+ const leftPx = floatingCoords ? floatingCoords.left : (props.anchor && typeof props.anchor.left === "number" ? props.anchor.left : null);
1876
+ const hasCoords = topPx !== null && leftPx !== null;
1369
1877
 
1370
- h("section", {
1371
- className: "sm-nativeDialog",
1878
+ const layerStyle = {
1879
+ zIndex: zIndex || undefined,
1880
+ ...(hasCoords
1881
+ ? { position: "fixed", top: topPx + "px", left: leftPx + "px", right: "auto", bottom: "auto", inset: `${topPx}px auto auto ${leftPx}px` }
1882
+ : {})
1883
+ };
1884
+
1885
+ return renderInOverlay(
1886
+ h("div", {
1887
+ className: "sm-nativeDialogLayer sm-confirmDialog" + (hasCoords ? " sm-confirmDialog-anchored" : ""),
1888
+ style: layerStyle,
1889
+ role: "presentation"
1890
+ },
1891
+ h("section", {
1892
+ className: "sm-nativeDialog",
1893
+ style: { pointerEvents: "auto" },
1372
1894
  role: "dialog",
1373
1895
  "aria-modal": "true",
1374
1896
  "aria-labelledby": "sm-confirm-dialog-title"
@@ -1395,7 +1917,7 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1395
1917
  }, confirmLabel)
1396
1918
  )
1397
1919
  )
1398
- );
1920
+ ));
1399
1921
  }
1400
1922
 
1401
1923
  function MoveDialog(props) {
@@ -1476,20 +1998,27 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1476
1998
  onConfirm(selectedWorkspaceId);
1477
1999
  }
1478
2000
  };
1479
- return h("div", {
1480
- className: "sm-nativeDialogLayer sm-confirmDialog",
1481
- style: {
1482
- zIndex: zIndex || undefined,
1483
- ...(props.anchor && typeof props.anchor.top === "number"
1484
- ? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px" }
1485
- : {})
1486
- },
1487
- role: "presentation",
1488
- onKeyDown
1489
- },
2001
+ const floatingCoords = useFloatingPosition(props.anchor, open, { placement: "bottom-end", dialogWidth: 360, dialogHeight: 320 });
2002
+ const topPx = floatingCoords ? floatingCoords.top : (props.anchor && typeof props.anchor.top === "number" ? props.anchor.top : null);
2003
+ const leftPx = floatingCoords ? floatingCoords.left : (props.anchor && typeof props.anchor.left === "number" ? props.anchor.left : null);
2004
+ const hasCoords = topPx !== null && leftPx !== null;
1490
2005
 
1491
- h("section", {
1492
- className: "sm-nativeDialog",
2006
+ const layerStyle = {
2007
+ zIndex: zIndex || undefined,
2008
+ ...(hasCoords
2009
+ ? { position: "fixed", top: topPx + "px", left: leftPx + "px", right: "auto", bottom: "auto", inset: `${topPx}px auto auto ${leftPx}px` }
2010
+ : {})
2011
+ };
2012
+ return renderInOverlay(
2013
+ h("div", {
2014
+ className: "sm-nativeDialogLayer sm-confirmDialog" + (hasCoords ? " sm-confirmDialog-anchored" : ""),
2015
+ style: layerStyle,
2016
+ role: "presentation",
2017
+ onKeyDown
2018
+ },
2019
+ h("section", {
2020
+ className: "sm-nativeDialog",
2021
+ style: { pointerEvents: "auto" },
1493
2022
  role: "dialog",
1494
2023
  "aria-modal": "true",
1495
2024
  "aria-labelledby": "sm-move-dialog-title"
@@ -1513,7 +2042,7 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1513
2042
  }, confirmLabel)
1514
2043
  )
1515
2044
  )
1516
- );
2045
+ ));
1517
2046
  }
1518
2047
 
1519
2048
  function MigratePresetDialog(props) {
@@ -1610,14 +2139,15 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1610
2139
  target === currentPreset ? h("div", { className: "sm-migrateWarn" }, t("migrate.same")) : null
1611
2140
  )
1612
2141
  );
1613
- return h("div", {
1614
- className: "sm-nativeDialogLayer sm-confirmDialog sm-migrateConfirmLayer",
1615
- role: "presentation",
1616
- style: { zIndex: zIndex || undefined }
1617
- },
1618
-
1619
- h("section", {
1620
- className: "sm-nativeDialog sm-migrateDialog",
2142
+ return renderInOverlay(
2143
+ h("div", {
2144
+ className: "sm-nativeDialogLayer sm-confirmDialog sm-migrateConfirmLayer",
2145
+ role: "presentation",
2146
+ style: { zIndex: zIndex || undefined }
2147
+ },
2148
+ h("section", {
2149
+ className: "sm-nativeDialog sm-migrateDialog",
2150
+ style: { pointerEvents: "auto" },
1621
2151
  role: "dialog",
1622
2152
  "aria-modal": "true",
1623
2153
  "aria-labelledby": "sm-migrate-dialog-title"
@@ -1641,9 +2171,295 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1641
2171
  }, busy ? t("busy.processing") : t("migrate.confirm"))
1642
2172
  )
1643
2173
  )
1644
- );
2174
+ ));
2175
+ }
2176
+
2177
+ const CURRENT_VERSION = "0.6.3";
2178
+
2179
+ function compareSemver(v1, v2) {
2180
+ if (typeof v1 !== "string" || typeof v2 !== "string") return 0;
2181
+ const parse = (v) => {
2182
+ // Strip build metadata (+...)
2183
+ const noBuild = v.trim().replace(/^[vV]/, "").split("+")[0];
2184
+ const hyphenIdx = noBuild.indexOf("-");
2185
+ const core = hyphenIdx === -1 ? noBuild : noBuild.slice(0, hyphenIdx);
2186
+ const prerelease = hyphenIdx === -1 ? undefined : noBuild.slice(hyphenIdx + 1);
2187
+ const parts = (core || "").split(".").map(n => parseInt(n, 10) || 0);
2188
+ while (parts.length < 3) parts.push(0);
2189
+ return { parts, prerelease: prerelease !== undefined ? prerelease.trim() : undefined };
2190
+ };
2191
+ const p1 = parse(v1);
2192
+ const p2 = parse(v2);
2193
+ for (let i = 0; i < 3; i++) {
2194
+ if (p1.parts[i] > p2.parts[i]) return 1;
2195
+ if (p1.parts[i] < p2.parts[i]) return -1;
2196
+ }
2197
+ // When major, minor, patch match: version without prerelease is higher
2198
+ if (p1.prerelease && !p2.prerelease) return -1;
2199
+ if (!p1.prerelease && p2.prerelease) return 1;
2200
+ if (p1.prerelease && p2.prerelease) {
2201
+ if (p1.prerelease === p2.prerelease) return 0;
2202
+ const id1 = p1.prerelease.split(".");
2203
+ const id2 = p2.prerelease.split(".");
2204
+ const maxLen = Math.max(id1.length, id2.length);
2205
+ for (let i = 0; i < maxLen; i++) {
2206
+ if (i >= id1.length) return -1;
2207
+ if (i >= id2.length) return 1;
2208
+ const part1 = id1[i];
2209
+ const part2 = id2[i];
2210
+ if (part1 === part2) continue;
2211
+ const num1 = /^\d+$/.test(part1) ? parseInt(part1, 10) : null;
2212
+ const num2 = /^\d+$/.test(part2) ? parseInt(part2, 10) : null;
2213
+ if (num1 !== null && num2 !== null) {
2214
+ if (num1 > num2) return 1;
2215
+ if (num1 < num2) return -1;
2216
+ } else if (num1 !== null && num2 === null) {
2217
+ return -1;
2218
+ } else if (num1 === null && num2 !== null) {
2219
+ return 1;
2220
+ } else {
2221
+ if (part1 > part2) return 1;
2222
+ if (part1 < part2) return -1;
2223
+ }
2224
+ }
2225
+ }
2226
+ return 0;
1645
2227
  }
1646
2228
 
2229
+ let activeClientCtx = null;
2230
+
2231
+ const updateStore = {
2232
+ state: {
2233
+ status: "idle",
2234
+ currentVersion: CURRENT_VERSION,
2235
+ latestVersion: null,
2236
+ error: null,
2237
+ application: null,
2238
+ dialogOpen: false,
2239
+ checkedAt: null,
2240
+ },
2241
+ listeners: new Set(),
2242
+ getSnapshot() { return this.state; },
2243
+ subscribe(cb) {
2244
+ this.listeners.add(cb);
2245
+ return () => this.listeners.delete(cb);
2246
+ },
2247
+ _notify() {
2248
+ this.state = { ...this.state };
2249
+ for (const fn of [...this.listeners]) fn();
2250
+ },
2251
+ openDialog() {
2252
+ this.state.dialogOpen = true;
2253
+ this._notify();
2254
+ },
2255
+ closeDialog() {
2256
+ this.state.dialogOpen = false;
2257
+ this._notify();
2258
+ },
2259
+ getAutoCheck() {
2260
+ try {
2261
+ if (typeof localStorage !== "undefined") {
2262
+ return localStorage.getItem("dsh-session-manager-auto-check") !== "false";
2263
+ }
2264
+ } catch (_) {}
2265
+ return true;
2266
+ },
2267
+ setAutoCheck(enabled) {
2268
+ try {
2269
+ if (typeof localStorage !== "undefined") {
2270
+ localStorage.setItem("dsh-session-manager-auto-check", enabled ? "true" : "false");
2271
+ }
2272
+ } catch (_) {}
2273
+ this._notify();
2274
+ },
2275
+ // Dialog opacity preference (Issue #23). Reuses the same localStorage
2276
+ // backed preference store as autoCheck / registry instead of adding a
2277
+ // second storage mechanism.
2278
+ DIALOG_OPACITY: SM_DIALOG_OPACITY,
2279
+ DIALOG_OPACITY_KEY: SM_DIALOG_OPACITY_KEY,
2280
+ getDialogOpacity() {
2281
+ return smReadDialogOpacity();
2282
+ },
2283
+ setDialogOpacity(value) {
2284
+ const next = applyDialogOpacity(value);
2285
+ try {
2286
+ if (typeof localStorage !== "undefined") {
2287
+ localStorage.setItem(SM_DIALOG_OPACITY_KEY, String(next));
2288
+ }
2289
+ } catch (_) {}
2290
+ this._notify();
2291
+ return next;
2292
+ },
2293
+ resetDialogOpacity() {
2294
+ return this.setDialogOpacity(SM_DIALOG_OPACITY.default);
2295
+ },
2296
+ REGISTRY_OPTIONS: {
2297
+ "registry.npmjs.org": "https://registry.npmjs.org",
2298
+ "registry.npmmirror.com": "https://registry.npmmirror.com",
2299
+ },
2300
+ DEFAULT_REGISTRY: "registry.npmjs.org",
2301
+ getRegistry() {
2302
+ try {
2303
+ if (typeof localStorage !== "undefined") {
2304
+ const v = localStorage.getItem("dsh-session-manager-registry");
2305
+ if (v && this.REGISTRY_OPTIONS[v]) return v;
2306
+ }
2307
+ } catch (_) {}
2308
+ return this.DEFAULT_REGISTRY;
2309
+ },
2310
+ setRegistry(key) {
2311
+ try {
2312
+ if (typeof localStorage !== "undefined") {
2313
+ if (this.REGISTRY_OPTIONS[key]) {
2314
+ localStorage.setItem("dsh-session-manager-registry", key);
2315
+ } else {
2316
+ localStorage.setItem("dsh-session-manager-registry", this.DEFAULT_REGISTRY);
2317
+ }
2318
+ }
2319
+ } catch (_) {}
2320
+ this._notify();
2321
+ },
2322
+ getRegistryUrl() {
2323
+ return this.REGISTRY_OPTIONS[this.getRegistry()] || this.REGISTRY_OPTIONS[this.DEFAULT_REGISTRY];
2324
+ },
2325
+ async checkForUpdate(options = {}) {
2326
+ const { silent = false, openOnFound = true, manual = false } = options;
2327
+ const shouldOpenDialog = options.openDialog ?? (manual && !silent && options.openOnFound !== false);
2328
+ if (this.state.status === "checking" || this.state.status === "updating") return this.state;
2329
+
2330
+ this.state.status = "checking";
2331
+ this.state.error = null;
2332
+ if (shouldOpenDialog) {
2333
+ this.state.dialogOpen = true;
2334
+ }
2335
+ this._notify();
2336
+
2337
+ try {
2338
+ let res = null;
2339
+ if (typeof window !== "undefined" && window.__DSH_SM_TEST_UPDATE__) {
2340
+ const testOverride = window.__DSH_SM_TEST_UPDATE__;
2341
+ if (testOverride.error) {
2342
+ throw new Error(testOverride.error);
2343
+ }
2344
+ const cur = testOverride.currentVersion || this.state.currentVersion;
2345
+ const lat = testOverride.latestVersion || "9.9.9";
2346
+ const hasUpdate = compareSemver(lat, cur) > 0;
2347
+ res = { ok: true, result: { currentVersion: cur, latestVersion: lat, hasUpdate } };
2348
+ } else {
2349
+ const registryUrl = this.getRegistryUrl();
2350
+ const registryParam = encodeURIComponent(registryUrl);
2351
+ try {
2352
+ res = await fetchApi("/update/check?registry=" + registryParam);
2353
+ } catch (fetchErr) {
2354
+ try {
2355
+ const base = registryUrl.endsWith("/") ? registryUrl : registryUrl + "/";
2356
+ const npmRes = await fetch(base + "dsh-session-manager/latest");
2357
+ if (npmRes.ok) {
2358
+ const npmData = await npmRes.json();
2359
+ if (npmData && npmData.version) {
2360
+ const cur = this.state.currentVersion;
2361
+ const lat = npmData.version;
2362
+ const hasUpdate = compareSemver(lat, cur) > 0;
2363
+ res = { ok: true, result: { currentVersion: cur, latestVersion: lat, hasUpdate } };
2364
+ }
2365
+ }
2366
+ } catch (_) {}
2367
+ if (!res) throw fetchErr;
2368
+ }
2369
+ }
2370
+
2371
+ if (!res || !res.ok) {
2372
+ throw new Error(res?.error || "检查更新失败");
2373
+ }
2374
+
2375
+ const result = res.result || {};
2376
+ const currentVersion = result.currentVersion || this.state.currentVersion;
2377
+ const latestVersion = result.latestVersion || currentVersion;
2378
+ const hasUpdate = !!result.hasUpdate || compareSemver(latestVersion, currentVersion) > 0;
2379
+
2380
+ this.state.currentVersion = currentVersion;
2381
+ this.state.latestVersion = latestVersion;
2382
+ this.state.checkedAt = Date.now();
2383
+
2384
+ if (hasUpdate) {
2385
+ this.state.status = "available";
2386
+ if (openOnFound && !silent) {
2387
+ this.state.dialogOpen = true;
2388
+ }
2389
+ } else {
2390
+ this.state.status = "idle";
2391
+ if (shouldOpenDialog) {
2392
+ this.state.dialogOpen = true;
2393
+ }
2394
+ }
2395
+ this._notify();
2396
+ return this.state;
2397
+ } catch (err) {
2398
+ this.state.status = "error";
2399
+ this.state.error = err.message || "检查更新失败";
2400
+ if (shouldOpenDialog) {
2401
+ this.state.dialogOpen = true;
2402
+ }
2403
+ this._notify();
2404
+ return this.state;
2405
+ }
2406
+ },
2407
+ async startUpdate(options = {}) {
2408
+ const { openDialog = false } = options;
2409
+ if (this.state.status === "updating") return this.state;
2410
+ this.state.status = "updating";
2411
+ this.state.error = null;
2412
+ if (openDialog) {
2413
+ this.state.dialogOpen = true;
2414
+ }
2415
+ this._notify();
2416
+
2417
+ const targetVersion = this.state.latestVersion || "latest";
2418
+ const spec = "dsh-session-manager@" + targetVersion;
2419
+
2420
+ try {
2421
+ let outcome = null;
2422
+ if (typeof window !== "undefined" && window.__DSH_SM_TEST_UPDATE__) {
2423
+ const testOverride = window.__DSH_SM_TEST_UPDATE__;
2424
+ if (testOverride.error) {
2425
+ throw new Error(testOverride.error);
2426
+ }
2427
+ if (typeof testOverride.installBundle === "function") {
2428
+ outcome = await testOverride.installBundle(spec);
2429
+ } else {
2430
+ await new Promise(r => setTimeout(r, 100));
2431
+ outcome = { application: testOverride.application || "restart-required" };
2432
+ }
2433
+ } else {
2434
+ const remotePm = activeClientCtx?.remote?.pluginManager || activeClientCtx?.pluginManager;
2435
+ if (remotePm && typeof remotePm.installBundle === "function") {
2436
+ outcome = await remotePm.installBundle(spec);
2437
+ } else {
2438
+ const res = await callApi("/update/install", { version: targetVersion, registry: this.getRegistryUrl() });
2439
+ if (!res || !res.ok) {
2440
+ throw new Error(res?.error || "安装更新失败");
2441
+ }
2442
+ outcome = res.result;
2443
+ }
2444
+ }
2445
+
2446
+ const application = outcome?.application || "restart-required";
2447
+ this.state.status = "done";
2448
+ this.state.application = application;
2449
+ this._notify();
2450
+ return outcome;
2451
+ } catch (err) {
2452
+ this.state.status = "error";
2453
+ this.state.error = err.message || "更新安装失败";
2454
+ if (openDialog) {
2455
+ this.state.dialogOpen = true;
2456
+ }
2457
+ this._notify();
2458
+ return { ok: false, error: this.state.error };
2459
+ }
2460
+ }
2461
+ };
2462
+
1647
2463
  const panelStore = {
1648
2464
  open: false,
1649
2465
  listeners: new Set(),
@@ -1661,6 +2477,7 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1661
2477
  setPendingMove(v) { this.pendingMove = v; this._emitPending(); },
1662
2478
  consumePendingMove() { const v = this.pendingMove; this.pendingMove = null; return v; }
1663
2479
  };
2480
+ const lastBatchContext = { action: null, ids: [] };
1664
2481
 
1665
2482
  const DEFAULT_SORT = "updated-desc";
1666
2483
  const SORT_ORDERS = ["updated-desc", "updated-asc", "created-desc", "created-asc", "priority"];
@@ -1716,6 +2533,11 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1716
2533
  const onMigrate = props.onMigrate;
1717
2534
  const annotations = useAnnotations(true);
1718
2535
  const marksReady = annotations.loaded && !annotations.error;
2536
+ const updateState = useSyncExternalStore(
2537
+ (cb) => updateStore.subscribe(cb),
2538
+ () => updateStore.getSnapshot(),
2539
+ () => updateStore.getSnapshot()
2540
+ );
1719
2541
  const [onlyFavorite, setOnlyFavorite] = useState(false);
1720
2542
  const [onlyReview, setOnlyReview] = useState(false);
1721
2543
  const [tagFilter, setTagFilter] = useState("all");
@@ -1839,6 +2661,8 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1839
2661
  setBulkProgress(null);
1840
2662
  setBulkResult({ action, actionLabel, items });
1841
2663
  setBulkBusy(false);
2664
+ lastBatchContext.action = action;
2665
+ lastBatchContext.ids = ids;
1842
2666
  try { await annotationStore.load(true); } catch (_) { /* ignore */ }
1843
2667
  if (typeof props.onRefreshAfterBulk === "function") {
1844
2668
  try { await props.onRefreshAfterBulk(action); } catch (_) { /* ignore */ }
@@ -1890,16 +2714,16 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1890
2714
  const onBulkTagApply = (tags) => {
1891
2715
  const mode = bulkTagDraft && bulkTagDraft.mode;
1892
2716
  setBulkTagDraft(null);
1893
- if (mode === "add") onBulkAction("add-tags", { tags }, true);
2717
+ if (mode === "add") onBulkAction("add-tags", { tags }, false);
1894
2718
  };
1895
2719
  const onBulkPriorityApply = (value) => {
1896
2720
  const priority = Number(value);
1897
2721
  setBulkTagDraft(null);
1898
- if (Number.isInteger(priority) && priority >= 1 && priority <= 5) onBulkAction("set-priority", { priority }, true);
2722
+ if (Number.isInteger(priority) && priority >= 1 && priority <= 5) onBulkAction("set-priority", { priority }, false);
1899
2723
  };
1900
2724
  const onBulkMoveApply = (value) => {
1901
2725
  setBulkTagDraft(null);
1902
- if (value) onBulkAction("move", { targetWorkspaceId: value }, true);
2726
+ if (value) onBulkAction("move", { targetWorkspaceId: value }, false);
1903
2727
  };
1904
2728
  const [bulkPresetDraft, setBulkPresetDraft] = useState(null);
1905
2729
  const [bulkPresetOptions, setBulkPresetOptions] = useState([]);
@@ -1925,7 +2749,7 @@ function nextDialogZ() { return ++dialogZStackCounter; }
1925
2749
  };
1926
2750
  const onBulkPresetApply = (value) => {
1927
2751
  setBulkPresetDraft(null);
1928
- if (value) onBulkAction("preset-migrate", { toPreset: value }, true);
2752
+ if (value) onBulkAction("preset-migrate", { toPreset: value }, false);
1929
2753
  };
1930
2754
  const bulkMoveOptions = useMemo(() => workspaces.map(ws => ({ value: ws.id, label: ws.title || ws.name || ws.id })), [workspaces]);
1931
2755
  const bulkPriorityOptions = useMemo(() => [1, 2, 3, 4, 5].map(value => ({ value: String(value), label: t("marks.priority." + value) })), [t]);
@@ -2158,29 +2982,74 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2158
2982
  "aria-labelledby": "sm-panel-dialog-title"
2159
2983
  },
2160
2984
  h("div", { className: "sm-nativeDialogHeader" },
2161
- h("a", {
2162
- href: "https://github.com/hkkz9522/dsh-session-manager",
2163
- target: "_blank",
2164
- rel: "noopener noreferrer",
2165
- className: "sm-panelHeaderGithub",
2166
- title: t("panel.github.title"),
2167
- "aria-label": t("panel.github.title")
2168
- },
2169
- h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": "true" },
2170
- h("path", {
2171
- fill: "currentColor",
2172
- d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
2173
- })
2174
- )
2985
+ h("div", { className: "sm-panelHeaderLeft" },
2986
+ renderTip(t("panel.github.title"), h("a", {
2987
+ href: "https://github.com/hkkz9522/dsh-session-manager",
2988
+ target: "_blank",
2989
+ rel: "noopener noreferrer",
2990
+ className: "sm-panelHeaderGithub",
2991
+ "aria-label": t("panel.github.title")
2992
+ },
2993
+ h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": "true" },
2994
+ h("path", {
2995
+ fill: "currentColor",
2996
+ d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
2997
+ })
2998
+ )
2999
+ )),
3000
+ renderTip(updateState.status === "available"
3001
+ ? t("panel.update.badge")
3002
+ : updateState.status === "checking"
3003
+ ? t("update.checking")
3004
+ : updateState.status === "done"
3005
+ ? t("update.done.applied")
3006
+ : updateState.status === "error"
3007
+ ? (updateState.error || t("update.error"))
3008
+ : t("panel.update.title"),
3009
+ h("a", {
3010
+ role: "button",
3011
+ tabIndex: 0,
3012
+ className: "sm-panelHeaderGithub sm-panelHeaderWhale" + (updateState.status === "available" ? " sm-hasUpdate" : ""),
3013
+ "aria-label": updateState.status === "available"
3014
+ ? t("panel.update.badge")
3015
+ : updateState.status === "checking"
3016
+ ? t("update.checking")
3017
+ : t("panel.update.title"),
3018
+ onClick: (e) => {
3019
+ if (e && typeof e.preventDefault === "function") e.preventDefault();
3020
+ if (updateState.status === "available" || updateState.status === "done" || updateState.status === "updating") {
3021
+ updateStore.openDialog();
3022
+ } else {
3023
+ updateStore.openDialog();
3024
+ updateStore.checkForUpdate({ manual: true, silent: false, openDialog: true });
3025
+ }
3026
+ },
3027
+ onKeyDown: (e) => {
3028
+ if (e.key === "Enter" || e.key === " ") {
3029
+ if (e && typeof e.preventDefault === "function") e.preventDefault();
3030
+ if (updateState.status === "available" || updateState.status === "done" || updateState.status === "updating") {
3031
+ updateStore.openDialog();
3032
+ } else {
3033
+ updateStore.openDialog();
3034
+ updateStore.checkForUpdate({ manual: true, silent: false, openDialog: true });
3035
+ }
3036
+ }
3037
+ }
3038
+ },
3039
+ h(NpmIcon, { size: 16 }),
3040
+ updateState.status === "available" ? h("span", { className: "sm-updateDot", "aria-hidden": "true" }) : null,
3041
+ updateState.status === "checking" ? h("span", { className: "sm-updateSpinner", "aria-hidden": "true" }) : null
3042
+ )),
2175
3043
  ),
2176
3044
  h("h2", { id: "sm-panel-dialog-title", className: "sm-nativeDialogTitle" }, t("panel.title")),
2177
3045
  h("div", { className: "sm-nativeDialogHeaderActions" },
2178
- h("button", {
3046
+ renderTip(t("panel.close") || "关闭", h("button", {
2179
3047
  type: "button",
2180
3048
  className: "sm-panelHeaderClose",
2181
3049
  disabled: busyId !== null || migrating,
2182
- onClick: onPanelClose
2183
- }, t("panel.close"))
3050
+ onClick: onPanelClose,
3051
+ "aria-label": t("panel.close")
3052
+ }, h(CloseIcon, { size: 14 })))
2184
3053
  )
2185
3054
  ),
2186
3055
  h("div", { className: "sm-panel" },
@@ -2343,8 +3212,9 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2343
3212
  // Render the panel AND the inner dialog as siblings so React keeps
2344
3213
  // both mounted. CSS z-index keeps the right ordering: the inner
2345
3214
  // dialog (.sm-confirmDialog / .sm-moveDialog / .sm-migrateDialog)
2346
- // carries z-index 10000 and therefore sits ON TOP OF the panel
2347
- // (z-index 9999). Clicking inside the panel that the inner dialog
3215
+ // lives in the #dsh-session-manager-overlay-root context (1050) and
3216
+ // therefore sits ON TOP OF the panel (1020, in DSH's own document
3217
+ // context). Clicking inside the panel that the inner dialog
2348
3218
  // blocks is not possible because the inner backdrop covers the
2349
3219
  // panel area entirely (the inner modal is centered).
2350
3220
  const annotationDialog = annotationFor ? h(AnnotationDialog, { key: annotationFor.id, sessionId: annotationFor.id, displayTitle: annotationFor.displayTitle || annotationFor.id, t, anchor: annotationAnchor, onClose: () => { setAnnotationFor(null); setAnnotationAnchor(null); } }) : null;
@@ -2431,7 +3301,9 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2431
3301
  onCancel: () => setBulkPresetDraft(null),
2432
3302
  onApply: onBulkPresetApply
2433
3303
  }) : null;
2434
- // Bulk dialogs sit at z-index 10002 (set in CSS) so they overlap the
3304
+ // Bulk dialogs are portaled into the overlay root like every other
3305
+ // dialog, so their static z-index (10002) only orders them inside that
3306
+ // context; the root's own 1050 keeps them below DSH's 1100 tooltip tier.
2435
3307
  return h(React.Fragment, null, panelModal, annotationDialog, moveDialog, deleteDialog, migrateDialog, bulkPreviewDialog, bulkProgressDialog, bulkResultDialog, bulkTagDialog, bulkPriorityDialog, bulkMoveDialog, bulkPresetDialog);
2436
3308
  }
2437
3309
 
@@ -2494,30 +3366,119 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2494
3366
  });
2495
3367
  return () => { alive = false; };
2496
3368
  }, [moveFor ? (moveFor.id || "x") : null]);
2497
- // All three HeaderAction dialogs (annotation / move / delete)
2498
- // share a 360px width. Anchor the dialog's right edge to the
2499
- // trigger button's right edge, with 8px screen-edge padding.
2500
- const anchorFor = (el) => {
2501
- if (!el || typeof el.getBoundingClientRect !== "function") return null;
2502
- const r = el.getBoundingClientRect();
3369
+ const [moreOpen, setMoreOpen] = useState(false);
3370
+ const headerRef = useRef(null);
3371
+ const moreBtnRef = useRef(null);
3372
+ const moreBtnElRef = useRef(null);
3373
+ useEffect(() => {
3374
+ if (!moreOpen) return;
3375
+ const onDocClick = (e) => {
3376
+ if (moreBtnRef.current && moreBtnRef.current.contains(e.target)) return;
3377
+ setMoreOpen(false);
3378
+ };
3379
+ const onKey = (e) => {
3380
+ if (e.key === "Escape") setMoreOpen(false);
3381
+ };
3382
+ window.addEventListener("click", onDocClick, true);
3383
+ window.addEventListener("keydown", onKey, true);
3384
+ return () => {
3385
+ window.removeEventListener("click", onDocClick, true);
3386
+ window.removeEventListener("keydown", onKey, true);
3387
+ };
3388
+ }, [moreOpen]);
3389
+
3390
+ const getMoreBtn = () => {
3391
+ if (moreBtnElRef.current && moreBtnElRef.current.nodeType === 1) return moreBtnElRef.current;
3392
+ if (moreBtnRef.current) {
3393
+ const btn = moreBtnRef.current.querySelector(".sm-headerCompactMenuBtn");
3394
+ if (btn) return btn;
3395
+ }
3396
+ if (headerRef.current) {
3397
+ const btn = headerRef.current.querySelector(".sm-headerCompactMenuBtn");
3398
+ if (btn) return btn;
3399
+ }
3400
+ return null;
3401
+ };
3402
+
3403
+ const isVisible = (element) => {
3404
+ if (!element || typeof element.getBoundingClientRect !== "function") return false;
3405
+ if (typeof element.isConnected === "boolean" && !element.isConnected) return false;
3406
+ const rect = element.getBoundingClientRect();
3407
+ if (rect.width === 0 && rect.height === 0) return false;
3408
+ if (typeof element.offsetParent !== "undefined" && element.offsetParent === null && element.style?.position !== "fixed") return false;
3409
+ return true;
3410
+ };
3411
+
3412
+ const resolveActiveAnchorEl = (targetEl, actionType) => {
3413
+ if (isVisible(targetEl)) return targetEl;
3414
+ const moreBtn = getMoreBtn();
3415
+ if (isVisible(moreBtn)) return moreBtn;
3416
+ if (headerRef.current) {
3417
+ if (actionType === "annotation") {
3418
+ const btn = headerRef.current.querySelector(".sm-headerSecondaryAction")
3419
+ || headerRef.current.querySelector(".sm-annotationToggleOpen")
3420
+ || headerRef.current.querySelector("[aria-label*='marks']")
3421
+ || headerRef.current.querySelector("[aria-label*='标签']");
3422
+ if (isVisible(btn)) return btn;
3423
+ } else if (actionType === "move") {
3424
+ const btn = headerRef.current.querySelector(".sm-headerSecondaryActions button:first-child")
3425
+ || headerRef.current.querySelector("[aria-label*='move']")
3426
+ || headerRef.current.querySelector("[aria-label*='移动']");
3427
+ if (isVisible(btn)) return btn;
3428
+ } else if (actionType === "delete") {
3429
+ const btn = headerRef.current.querySelector(".sm-headerSecondaryActions button:last-child")
3430
+ || headerRef.current.querySelector(".sm-headerBtnDanger");
3431
+ if (isVisible(btn)) return btn;
3432
+ }
3433
+ if (isVisible(headerRef.current)) return headerRef.current;
3434
+ }
3435
+ return moreBtn || targetEl || null;
3436
+ };
3437
+
3438
+ // Anchor dialogs to the trigger button with dynamic tracking (issue #19 / #20).
3439
+ const anchorFor = (el, actionType) => {
3440
+ const domEl = el?.nodeType === 1 ? el : (el?.currentTarget || el?.target || el?.current || el);
3441
+ if (!domEl || typeof domEl.getBoundingClientRect !== "function") return domEl || null;
3442
+
3443
+ const activeEl = resolveActiveAnchorEl(domEl, actionType);
3444
+ const r = (activeEl && isVisible(activeEl) ? activeEl : domEl).getBoundingClientRect();
3445
+
2503
3446
  return {
2504
3447
  top: Math.round(r.top + r.height + 6),
2505
- left: Math.max(8, Math.round(r.right - 360))
3448
+ left: Math.max(8, Math.round(r.right - 360)),
3449
+ current: domEl,
3450
+ nodeType: domEl.nodeType,
3451
+ actionType: actionType,
3452
+ resolveActiveEl: () => resolveActiveAnchorEl(domEl, actionType),
3453
+ getBoundingClientRect: () => {
3454
+ const currentEl = resolveActiveAnchorEl(domEl, actionType);
3455
+ if (currentEl && typeof currentEl.getBoundingClientRect === "function") {
3456
+ const curR = currentEl.getBoundingClientRect();
3457
+ if (curR.width > 0 || curR.height > 0) return curR;
3458
+ }
3459
+ if (headerRef.current && typeof headerRef.current.getBoundingClientRect === "function") {
3460
+ const hr = headerRef.current.getBoundingClientRect();
3461
+ if (hr.width > 0 || hr.height > 0) return hr;
3462
+ }
3463
+ return r;
3464
+ }
2506
3465
  };
2507
3466
  };
2508
3467
  const archiveLabelKey = isArchived ? "row.unarchive" : "row.archive";
2509
3468
  return h(React.Fragment, null,
2510
3469
  annotationFor ? h(AnnotationDialog, { key: annotationFor.id, sessionId: annotationFor.id, displayTitle: annotationFor.displayTitle, t, anchor: annotationAnchor, onClose: () => { setAnnotationFor(null); setAnnotationAnchor(null); } }) : null,
2511
- h("div", { className: "sm-header" },
2512
- h("button", {
3470
+ h("div", { className: "sm-header", ref: headerRef },
3471
+ (onArchive || onUnarchive) ? renderTip(t ? t(archiveLabelKey) : (isArchived ? "移出归档" : "归档会话"), h("button", {
2513
3472
  type: "button",
2514
3473
  className: "sm-headerBtn" + (isArchived ? " sm-headerBtnActive" : ""),
2515
3474
  "aria-label": t ? t(archiveLabelKey) : (isArchived ? "移出归档" : "归档会话"),
2516
3475
  onClick: () => runWithAlert("archive", () =>
2517
3476
  isArchived ? onUnarchive(sessionId) : onArchive(sessionId)
2518
3477
  )
2519
- }, isArchived ? (t ? t("row.unarchive") : "移出归档") : (t ? t("row.archive") : "归档")),
2520
-
3478
+ },
3479
+ h("span", { className: "sm-headerBtnIcon" }, isArchived ? "📤" : "📥"),
3480
+ h("span", { className: "sm-headerBtnLabel" }, isArchived ? (t ? t("row.unarchive") : "移出归档") : (t ? t("row.archive") : "归档"))
3481
+ )) : null,
2521
3482
  annotationButtons(sessionId, props.displayTitle || sessionId, t, annotations, "sm-headerBtn", patch => runWithAlert("annotations", () => annotationStore.save(sessionId, patch)), (e) => {
2522
3483
  const nextOpen = !(annotationFor && annotationFor.id === sessionId);
2523
3484
  // Do NOT clear moveFor / confirmFor here: the z-index counter
@@ -2525,51 +3486,143 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2525
3486
  // popup must not silently dismiss a previously opened move or
2526
3487
  // delete popup on the same session.
2527
3488
  if (nextOpen && e && e.currentTarget) {
2528
- const r = e.currentTarget.getBoundingClientRect();
2529
- setAnnotationAnchor({ top: Math.round(r.top + r.height + 6), left: Math.max(8, Math.round(r.right - 360)) });
3489
+ setAnnotationAnchor(anchorFor(e.currentTarget, "annotation"));
2530
3490
  } else setAnnotationAnchor(null);
2531
3491
  setAnnotationFor(current => current?.id === sessionId ? null : { id: sessionId, displayTitle });
2532
3492
  }, false, annotationFor?.id === sessionId),
2533
- annotations.error ? h("button", { type: "button", className: "sm-headerBtn", title: t("marks.error", { message: annotations.error }), "aria-label": t("marks.retry"), onClick: () => annotationStore.load(true) }, "!") : null,
3493
+ annotations.error ? renderTip(t("marks.error", { message: annotations.error }), h("button", { type: "button", className: "sm-headerBtn", "aria-label": t("marks.retry"), onClick: () => annotationStore.load(true) }, "!")) : null,
2534
3494
 
2535
- h("button", {
2536
- type: "button",
2537
- className: "sm-headerBtn" + (moveFor ? " sm-headerBtnActive" : ""),
2538
- "aria-label": t ? t("row.move") : "移动至工作区",
2539
- onClick: (e) => {
2540
- e.stopPropagation();
2541
- // Do NOT clear confirmFor / confirmAnchor here: opening the
2542
- // move popup must not silently dismiss a previously opened
2543
- // delete popup on the same session. The z-index counter
2544
- // (see nextDialogZ) handles stacking.
2545
- if (moveFor && moveFor.id === sessionId) {
2546
- setMoveFor(null);
2547
- setMoveAnchor(null);
2548
- } else {
2549
- setMoveAnchor(anchorFor(e.currentTarget));
2550
- setMoveFor({ id: sessionId, displayTitle, workspaceId: "" });
3495
+ h("div", { className: "sm-headerSecondaryActions" },
3496
+ renderTip(t ? t("row.move") : "移动至工作区", h("button", {
3497
+ type: "button",
3498
+ className: "sm-headerBtn" + (moveFor ? " sm-headerBtnActive" : ""),
3499
+ "aria-label": t ? t("row.move") : "移动至工作区",
3500
+ onClick: (e) => {
3501
+ if (e && typeof e.stopPropagation === "function") e.stopPropagation();
3502
+ // Do NOT clear confirmFor / confirmAnchor here: opening the
3503
+ // move popup must not silently dismiss a previously opened
3504
+ // delete popup on the same session. The z-index counter
3505
+ // (see nextDialogZ) handles stacking.
3506
+ if (moveFor && moveFor.id === sessionId) {
3507
+ setMoveFor(null);
3508
+ setMoveAnchor(null);
3509
+ } else {
3510
+ setMoveAnchor(anchorFor(e?.currentTarget, "move"));
3511
+ setMoveFor({ id: sessionId, displayTitle, workspaceId: "" });
3512
+ }
2551
3513
  }
2552
- }
2553
- }, t ? t("row.move") : "移动至工作区"),
2554
- h("button", {
2555
- type: "button",
2556
- className: "sm-headerBtn sm-headerBtnDanger" + (confirmFor ? " sm-headerBtnActive" : ""),
2557
- "aria-label": t ? t("row.delete") : "删除会话",
2558
- onClick: (e) => {
2559
- e.stopPropagation();
2560
- // Do NOT clear moveFor / moveAnchor here: opening the delete
2561
- // popup must not silently dismiss a previously opened move
2562
- // popup on the same session. The z-index counter (see
2563
- // nextDialogZ) handles stacking.
2564
- if (confirmFor && confirmFor.id === sessionId) {
2565
- setConfirmFor(null);
2566
- setConfirmAnchor(null);
2567
- } else {
2568
- setConfirmAnchor(anchorFor(e.currentTarget));
2569
- setConfirmFor({ id: sessionId, displayTitle });
3514
+ },
3515
+ h("span", { className: "sm-headerBtnIcon" }, "↔"),
3516
+ h("span", { className: "sm-headerBtnLabel" }, t ? t("row.move") : "移动至工作区")
3517
+ )),
3518
+ renderTip(t ? t("row.delete") : "删除会话", h("button", {
3519
+ type: "button",
3520
+ className: "sm-headerBtn sm-headerBtnDanger" + (confirmFor ? " sm-headerBtnActive" : ""),
3521
+ "aria-label": t ? t("row.delete") : "删除会话",
3522
+ onClick: (e) => {
3523
+ if (e && typeof e.stopPropagation === "function") e.stopPropagation();
3524
+ // Do NOT clear moveFor / moveAnchor here: opening the delete
3525
+ // popup must not silently dismiss a previously opened move
3526
+ // popup on the same session. The z-index counter (see
3527
+ // nextDialogZ) handles stacking.
3528
+ if (confirmFor && confirmFor.id === sessionId) {
3529
+ setConfirmFor(null);
3530
+ setConfirmAnchor(null);
3531
+ } else {
3532
+ setConfirmAnchor(anchorFor(e?.currentTarget, "delete"));
3533
+ setConfirmFor({ id: sessionId, displayTitle });
3534
+ }
2570
3535
  }
2571
- }
2572
- }, t ? t("row.delete") : "删除会话")
3536
+ },
3537
+ h("span", { className: "sm-headerBtnIcon" }, "🗑"),
3538
+ h("span", { className: "sm-headerBtnLabel" }, t ? t("row.delete") : "删除会话")
3539
+ ))
3540
+ ),
3541
+ h("div", { className: "sm-headerCompactMenu", ref: moreBtnRef },
3542
+ renderTip((t && t("header.more")) || "更多操作", h("button", {
3543
+ ref: moreBtnElRef,
3544
+ type: "button",
3545
+ className: "sm-headerBtn sm-headerCompactMenuBtn" + (moreOpen ? " sm-headerBtnActive" : ""),
3546
+ "aria-label": (t && t("header.more")) || "更多操作",
3547
+ "aria-haspopup": "menu",
3548
+ "aria-expanded": moreOpen,
3549
+ onClick: (e) => {
3550
+ if (e && typeof e.stopPropagation === "function") e.stopPropagation();
3551
+ setMoreOpen(v => !v);
3552
+ }
3553
+ },
3554
+ h("span", { className: "sm-headerBtnIcon" }, "⋯")
3555
+ )),
3556
+ moreOpen ? h("div", { className: "sm-headerMenu", role: "menu" },
3557
+ h("button", {
3558
+ type: "button",
3559
+ className: "sm-headerMenuItem",
3560
+ role: "menuitem",
3561
+ onClick: () => {
3562
+ const anchorEl = getMoreBtn() || moreBtnRef.current;
3563
+ setMoreOpen(false);
3564
+ const nextOpen = !(annotationFor && annotationFor.id === sessionId);
3565
+ if (nextOpen && anchorEl) {
3566
+ setAnnotationAnchor(anchorFor(anchorEl, "annotation"));
3567
+ } else setAnnotationAnchor(null);
3568
+ setAnnotationFor(curr => (curr?.id === sessionId ? null : { id: sessionId, displayTitle }));
3569
+ }
3570
+ },
3571
+ h("span", { className: "sm-headerBtnIcon" }, "🏷"),
3572
+ h("span", null, t ? t("marks.header.edit") : "标签/备注")
3573
+ ),
3574
+ h("button", {
3575
+ type: "button",
3576
+ className: "sm-headerMenuItem",
3577
+ role: "menuitem",
3578
+ onClick: () => {
3579
+ const anchorEl = getMoreBtn() || moreBtnRef.current;
3580
+ setMoreOpen(false);
3581
+ if (moveFor && moveFor.id === sessionId) {
3582
+ setMoveFor(null);
3583
+ setMoveAnchor(null);
3584
+ } else {
3585
+ setMoveAnchor(anchorFor(anchorEl, "move"));
3586
+ setMoveFor({ id: sessionId, displayTitle, workspaceId: "" });
3587
+ }
3588
+ }
3589
+ },
3590
+ h("span", { className: "sm-headerBtnIcon" }, "↔"),
3591
+ h("span", null, t ? t("row.move") : "移动至工作区")
3592
+ ),
3593
+ (onArchive || onUnarchive) ? h("button", {
3594
+ type: "button",
3595
+ className: "sm-headerMenuItem",
3596
+ role: "menuitem",
3597
+ onClick: () => {
3598
+ setMoreOpen(false);
3599
+ runWithAlert("archive", () => isArchived ? onUnarchive(sessionId) : onArchive(sessionId));
3600
+ }
3601
+ },
3602
+ h("span", { className: "sm-headerBtnIcon" }, isArchived ? "📤" : "📥"),
3603
+ h("span", null, isArchived ? (t ? t("row.unarchive") : "移出归档") : (t ? t("row.archive") : "归档"))
3604
+ ) : null,
3605
+ h("button", {
3606
+ type: "button",
3607
+ className: "sm-headerMenuItem sm-headerMenuItemDanger",
3608
+ role: "menuitem",
3609
+ onClick: () => {
3610
+ const anchorEl = getMoreBtn() || moreBtnRef.current;
3611
+ setMoreOpen(false);
3612
+ if (confirmFor && confirmFor.id === sessionId) {
3613
+ setConfirmFor(null);
3614
+ setConfirmAnchor(null);
3615
+ } else {
3616
+ setConfirmAnchor(anchorFor(anchorEl, "delete"));
3617
+ setConfirmFor({ id: sessionId, displayTitle });
3618
+ }
3619
+ }
3620
+ },
3621
+ h("span", { className: "sm-headerBtnIcon" }, "🗑"),
3622
+ h("span", null, t ? t("row.delete") : "删除会话")
3623
+ )
3624
+ ) : null
3625
+ )
2573
3626
  ),
2574
3627
  confirmFor === null ? null : h(ConfirmDialog, {
2575
3628
  open: true,
@@ -2626,8 +3679,8 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2626
3679
  // Bulk operations need to refresh ctx.sessions.list and the
2627
3680
  // workspace projection after /batch mutates disk state so the
2628
3681
  // panel reflects the new layout.
2629
- onRefreshAfterBulk: async () => {
2630
- try { await this.props.props.onBulkRefresh(); } catch (_) { /* ignore */ }
3682
+ onRefreshAfterBulk: async (...args) => {
3683
+ try { await this.props.props.onBulkRefresh?.(...args); } catch (_) { /* ignore */ }
2631
3684
  },
2632
3685
  onOpen: (id) => { try { this.props.panelStore.set(false); this.props.props.onOpen(id); } catch (_) {} },
2633
3686
  onArchive: this.props.props.onArchive,
@@ -2653,10 +3706,14 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2653
3706
  () => panelStore.getSnapshot(),
2654
3707
  () => panelStore.getSnapshot()
2655
3708
  );
2656
- const footerClass = "sm-footer" + (wide ? " sm-footer-wide" : " sm-footer-rail");
3709
+ const updateState = useSyncExternalStore(
3710
+ (cb) => updateStore.subscribe(cb),
3711
+ () => updateStore.getSnapshot(),
3712
+ () => updateStore.getSnapshot()
3713
+ );
2657
3714
  const btnClass = "sm-footerBtn" + (wide ? " sm-footerBtn-wide" : " sm-footerBtn-rail");
2658
3715
  const btnLabel = t("footer.aria");
2659
- return h("div", { className: footerClass },
3716
+ return h(React.Fragment, null,
2660
3717
  h("button", {
2661
3718
  type: "button",
2662
3719
  className: btnClass,
@@ -2673,11 +3730,377 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2673
3730
  t: t,
2674
3731
  useSessions: props.useSessions,
2675
3732
  useWorkspaces: props.useWorkspaces,
2676
- }) : null
3733
+ }) : null,
3734
+ updateState.dialogOpen ? h(UpdateDialog, { t: t }) : null
3735
+ );
3736
+ }
3737
+
3738
+ function NpmIcon(props) {
3739
+ const size = props.size || 16;
3740
+ return h("svg", {
3741
+ viewBox: "0 0 16 16",
3742
+ width: size,
3743
+ height: size,
3744
+ fill: "currentColor",
3745
+ "aria-hidden": "true",
3746
+ style: { display: "block" }
3747
+ },
3748
+ h("path", {
3749
+ fillRule: "evenodd",
3750
+ d: "M0 0v16h16V0H0zm13 13h-2V5H8v8H3V3h10v10z"
3751
+ })
3752
+ );
3753
+ }
3754
+ const WhaleIcon = NpmIcon;
3755
+
3756
+ function CloseIcon(props) {
3757
+ const size = props.size || 14;
3758
+ return h("svg", {
3759
+ viewBox: "0 0 16 16",
3760
+ width: size,
3761
+ height: size,
3762
+ fill: "none",
3763
+ stroke: "currentColor",
3764
+ strokeWidth: "1.8",
3765
+ strokeLinecap: "round",
3766
+ strokeLinejoin: "round",
3767
+ "aria-hidden": "true",
3768
+ style: { display: "block" }
3769
+ },
3770
+ h("path", { d: "M3.5 3.5l9 9M12.5 3.5l-9 9" })
3771
+ );
3772
+ }
3773
+
3774
+ function StarIcon(props) {
3775
+ const size = props.size || 14;
3776
+ return h("svg", {
3777
+ viewBox: "0 0 16 16",
3778
+ width: size,
3779
+ height: size,
3780
+ fill: "#eac54f",
3781
+ "aria-hidden": "true",
3782
+ style: { display: "block" }
3783
+ },
3784
+ h("path", {
3785
+ d: "M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.75.75 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25z"
3786
+ })
3787
+ );
3788
+ }
3789
+
3790
+ function IssueIcon(props) {
3791
+ const size = props.size || 14;
3792
+ return h("svg", {
3793
+ viewBox: "0 0 16 16",
3794
+ width: size,
3795
+ height: size,
3796
+ fill: "currentColor",
3797
+ "aria-hidden": "true",
3798
+ style: { display: "block" }
3799
+ },
3800
+ h("path", {
3801
+ fillRule: "evenodd",
3802
+ d: "M8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zM0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm9 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-.25-6.25a.75.75 0 0 0-1.5 0v3.5a.75.75 0 0 0 1.5 0v-3.5z"
3803
+ })
3804
+ );
3805
+ }
3806
+
3807
+ function UpdateDialog(props) {
3808
+ const t = props.t || ((k) => k);
3809
+ const updateState = useSyncExternalStore(
3810
+ (cb) => updateStore.subscribe(cb),
3811
+ () => updateStore.getSnapshot(),
3812
+ () => updateStore.getSnapshot()
3813
+ );
3814
+
3815
+ const status = updateState.status;
3816
+ const isUpdating = status === "updating";
3817
+ const isDone = status === "done";
3818
+ const isError = status === "error";
3819
+ const isAvailable = status === "available";
3820
+ const isChecking = status === "checking";
3821
+ const isIdle = status === "idle";
3822
+
3823
+ let dialogTitle = t("update.title");
3824
+ if (isAvailable) dialogTitle = t("update.newVersion");
3825
+ else if (isUpdating) dialogTitle = t("update.installing");
3826
+ else if (isDone) dialogTitle = t("update.title");
3827
+ else if (isError) dialogTitle = t("update.error");
3828
+
3829
+ const handleClose = () => {
3830
+ if (!isUpdating) {
3831
+ updateStore.closeDialog();
3832
+ }
3833
+ };
3834
+
3835
+ return h(OverlayLayer, {
3836
+ className: "sm-updateDialogLayer",
3837
+ onBackdropClick: handleClose,
3838
+ onKeyDown: (e) => {
3839
+ if (e.key === "Escape" && !isUpdating) handleClose();
3840
+ }
3841
+ },
3842
+ h("section", {
3843
+ className: "sm-nativeDialog sm-updateDialog",
3844
+ style: { pointerEvents: "auto" },
3845
+ role: "dialog",
3846
+ "aria-modal": "true",
3847
+ "aria-labelledby": "sm-update-dialog-title"
3848
+ },
3849
+ h("div", { className: "sm-nativeDialogHeader" },
3850
+ h("h3", { id: "sm-update-dialog-title", className: "sm-nativeDialogTitle" }, dialogTitle),
3851
+ !isUpdating ? renderTip(t("update.btn.close"), h("button", {
3852
+ type: "button",
3853
+ className: "sm-panelHeaderClose",
3854
+ "aria-label": t("update.btn.close"),
3855
+ onClick: handleClose
3856
+ }, h(CloseIcon, { size: 14 }))) : null
3857
+ ),
3858
+ h("div", { className: "sm-nativeDialogBody" },
3859
+ h("div", { className: "sm-updateVersionGrid" },
3860
+ h("span", { className: "sm-updateVersionLabel" }, t("update.currentVersion")),
3861
+ h("span", { className: "sm-updateVersionValue" }, "v" + updateState.currentVersion),
3862
+ h("span", { className: "sm-updateVersionLabel" }, t("update.latestVersion")),
3863
+ h("span", { className: "sm-updateVersionValue" }, updateState.latestVersion ? "v" + updateState.latestVersion : (isChecking ? t("update.checking") : "-"))
3864
+ ),
3865
+ isChecking ? h("div", { className: "sm-updateStatusBox" }, t("update.checking")) : null,
3866
+ isAvailable ? h("div", { className: "sm-updateStatusBox sm-warn" }, t("update.newVersion") + " (v" + updateState.latestVersion + ")") : null,
3867
+ isUpdating ? h("div", { className: "sm-updateStatusBox sm-warn" }, t("update.installing")) : null,
3868
+ isDone ? h("div", { className: "sm-updateStatusBox sm-success" },
3869
+ updateState.application === "applied"
3870
+ ? t("update.done.applied")
3871
+ : t("update.done.restart", { from: updateState.currentVersion, to: updateState.latestVersion || updateState.currentVersion })
3872
+ ) : null,
3873
+ isError ? h("div", { className: "sm-updateStatusBox sm-error" }, (updateState.error || t("update.error"))) : null,
3874
+ (isIdle && updateState.latestVersion && !isAvailable) ? h("div", { className: "sm-updateStatusBox sm-success" }, t("update.isLatest")) : null
3875
+ ),
3876
+ h("div", { className: "sm-nativeDialogFooter" },
3877
+ isAvailable ? h(React.Fragment, null,
3878
+ h("button", {
3879
+ type: "button",
3880
+ className: "sm-nativeDialogButton sm-nativeDialogCancel",
3881
+ onClick: handleClose
3882
+ }, t("update.btn.later")),
3883
+ h("button", {
3884
+ type: "button",
3885
+ className: "sm-nativeDialogButton sm-nativeDialogConfirm",
3886
+ onClick: () => updateStore.startUpdate()
3887
+ }, t("update.btn.update", { version: updateState.latestVersion }))
3888
+ ) : null,
3889
+ isUpdating ? h("button", {
3890
+ type: "button",
3891
+ className: "sm-nativeDialogButton sm-nativeDialogConfirm",
3892
+ disabled: true
3893
+ }, t("update.btn.updating")) : null,
3894
+ isDone ? h("button", {
3895
+ type: "button",
3896
+ className: "sm-nativeDialogButton sm-nativeDialogConfirm",
3897
+ onClick: handleClose
3898
+ }, t("update.btn.done")) : null,
3899
+ isError ? h(React.Fragment, null,
3900
+ h("button", {
3901
+ type: "button",
3902
+ className: "sm-nativeDialogButton sm-nativeDialogCancel",
3903
+ onClick: handleClose
3904
+ }, t("update.btn.close")),
3905
+ h("button", {
3906
+ type: "button",
3907
+ className: "sm-nativeDialogButton sm-nativeDialogConfirm",
3908
+ onClick: () => {
3909
+ if (updateState.latestVersion && compareSemver(updateState.latestVersion, updateState.currentVersion) > 0) {
3910
+ updateStore.startUpdate();
3911
+ } else {
3912
+ updateStore.checkForUpdate({ manual: true, openDialog: true });
3913
+ }
3914
+ }
3915
+ }, t("update.btn.retry"))
3916
+ ) : null,
3917
+ (isIdle || isChecking) ? h(React.Fragment, null,
3918
+ h("button", {
3919
+ type: "button",
3920
+ className: "sm-nativeDialogButton sm-nativeDialogCancel",
3921
+ onClick: handleClose
3922
+ }, t("update.btn.close")),
3923
+ !isChecking ? h("button", {
3924
+ type: "button",
3925
+ className: "sm-nativeDialogButton",
3926
+ onClick: () => updateStore.checkForUpdate({ manual: true, openDialog: true })
3927
+ }, t("update.btn.check")) : null
3928
+ ) : null
3929
+ )
3930
+ )
3931
+ );
3932
+ }
3933
+
3934
+ function SessionManagerSettingsCard(props) {
3935
+ const t = props.t || ((k, v) => {
3936
+ const dict = zh[k] || en[k] || k;
3937
+ return dict.replace(/\{(\w+)\}/g, (_, name) => String(v?.[name] ?? ""));
3938
+ });
3939
+ const updateState = useSyncExternalStore(
3940
+ (cb) => updateStore.subscribe(cb),
3941
+ () => updateStore.getSnapshot(),
3942
+ () => updateStore.getSnapshot()
3943
+ );
3944
+ const autoCheck = updateStore.getAutoCheck();
3945
+
3946
+ const isChecking = updateState.status === "checking";
3947
+ const isUpdating = updateState.status === "updating";
3948
+ const isAvailable = updateState.status === "available";
3949
+
3950
+ const onToggleAutoCheck = (e) => {
3951
+ updateStore.setAutoCheck(e.target.checked);
3952
+ };
3953
+ const currentRegistry = updateStore.getRegistry();
3954
+ const onRegistryChange = (e) => {
3955
+ updateStore.setRegistry(e.target.value);
3956
+ };
3957
+ // Dialog opacity reads straight from the shared preference store, so
3958
+ // moving the slider updates every plugin dialog immediately.
3959
+ const dialogOpacity = updateStore.getDialogOpacity();
3960
+ const onDialogOpacityChange = (e) => {
3961
+ updateStore.setDialogOpacity(e.target.value);
3962
+ };
3963
+
3964
+ return h("div", { className: "sm-settingsCard" },
3965
+ h("div", { className: "sm-settingsHeader" },
3966
+ h("h3", { className: "sm-settingsTitle" },
3967
+ h(NpmIcon, { size: 18 }),
3968
+ h("span", null, t("settings.title")),
3969
+ h("span", { className: "sm-settingsTag" }, "v" + updateState.currentVersion)
3970
+ )
3971
+ ),
3972
+ h("div", { className: "sm-settingsSection" },
3973
+ h("div", { className: "sm-settingsSectionHeader" },
3974
+ h("div", { className: "sm-settingsSectionTitle" }, t("settings.versionSection")),
3975
+ h("div", { className: "sm-settingsVersionActions" },
3976
+ h("button", {
3977
+ type: "button",
3978
+ className: "sm-nativeDialogButton sm-settingsCheckBtn",
3979
+ disabled: isChecking || isUpdating,
3980
+ onClick: () => updateStore.checkForUpdate({ manual: true, openOnFound: false, openDialog: false })
3981
+ }, isChecking ? t("update.checking") : t("update.btn.check")),
3982
+ isAvailable ? h("button", {
3983
+ type: "button",
3984
+ className: "sm-nativeDialogButton sm-nativeDialogConfirm sm-settingsCheckBtn",
3985
+ disabled: isUpdating,
3986
+ onClick: () => updateStore.startUpdate({ openDialog: false })
3987
+ }, isUpdating ? t("update.btn.updating") : t("update.btn.update", { version: updateState.latestVersion })) : null
3988
+ )
3989
+ ),
3990
+ h("div", { className: "sm-settingsRow" },
3991
+ h("span", null, t("update.currentVersion")),
3992
+ h("span", { style: { fontFamily: "monospace" } }, "v" + updateState.currentVersion)
3993
+ ),
3994
+ h("div", { className: "sm-settingsRow" },
3995
+ h("span", null, t("update.latestVersion")),
3996
+ h("span", { style: { fontFamily: "monospace" } },
3997
+ updateState.latestVersion ? "v" + updateState.latestVersion : (isChecking ? t("update.checking") : "-")
3998
+ )
3999
+ ),
4000
+ updateState.status === "available" ? h("div", { className: "sm-updateStatusBox sm-warn" },
4001
+ t("update.newVersion") + " (v" + updateState.latestVersion + ")"
4002
+ ) : null,
4003
+ updateState.status === "updating" ? h("div", { className: "sm-updateStatusBox sm-warn" },
4004
+ t("update.installing")
4005
+ ) : null,
4006
+ updateState.status === "done" ? h("div", { className: "sm-updateStatusBox sm-success" },
4007
+ updateState.application === "applied" ? t("update.done.applied") : t("update.done.restart", { from: updateState.currentVersion, to: updateState.latestVersion || updateState.currentVersion })
4008
+ ) : null,
4009
+ updateState.status === "error" ? h("div", { className: "sm-updateStatusBox sm-error" },
4010
+ updateState.error || t("update.error")
4011
+ ) : null
4012
+ ),
4013
+ h("div", { className: "sm-settingsSection" },
4014
+ h("div", { className: "sm-settingsSectionTitle" }, t("settings.prefSection")),
4015
+ h("div", { className: "sm-settingsRow" },
4016
+ h("span", null, t("settings.registry")),
4017
+ h("select", {
4018
+ className: "sm-settingsSelect",
4019
+ value: currentRegistry,
4020
+ onChange: onRegistryChange,
4021
+ "aria-label": t("settings.registry")
4022
+ },
4023
+ h("option", { value: "registry.npmjs.org" }, t("settings.registry.npm")),
4024
+ h("option", { value: "registry.npmmirror.com" }, t("settings.registry.china"))
4025
+ )
4026
+ ),
4027
+ h("div", { className: "sm-settingsRow" },
4028
+ h("span", null, t("update.autoCheck")),
4029
+ h("label", { className: "sm-settingsCheckbox" },
4030
+ h("input", {
4031
+ type: "checkbox",
4032
+ checked: autoCheck,
4033
+ onChange: onToggleAutoCheck,
4034
+ "aria-label": t("update.autoCheck")
4035
+ }),
4036
+ h("span", null, t("update.autoCheck.enable"))
4037
+ )
4038
+ ),
4039
+ h("div", { className: "sm-settingsRow" },
4040
+ h("span", null, t("settings.dialogOpacity")),
4041
+ h("div", { className: "sm-settingsSlider" },
4042
+ h("input", {
4043
+ type: "range",
4044
+ className: "sm-settingsRange",
4045
+ min: updateStore.DIALOG_OPACITY.min,
4046
+ max: updateStore.DIALOG_OPACITY.max,
4047
+ step: updateStore.DIALOG_OPACITY.step,
4048
+ value: dialogOpacity,
4049
+ "aria-label": t("settings.dialogOpacity"),
4050
+ "aria-valuemin": updateStore.DIALOG_OPACITY.min,
4051
+ "aria-valuemax": updateStore.DIALOG_OPACITY.max,
4052
+ "aria-valuenow": dialogOpacity,
4053
+ "aria-valuetext": t("settings.dialogOpacity.value", { value: dialogOpacity }),
4054
+ onChange: onDialogOpacityChange
4055
+ }),
4056
+ h("span", { className: "sm-settingsRangeValue" }, t("settings.dialogOpacity.value", { value: dialogOpacity })),
4057
+ h("button", {
4058
+ type: "button",
4059
+ className: "sm-settingsResetBtn",
4060
+ disabled: dialogOpacity === updateStore.DIALOG_OPACITY.default,
4061
+ onClick: () => updateStore.resetDialogOpacity()
4062
+ }, t("settings.dialogOpacity.reset"))
4063
+ )
4064
+ ),
4065
+ h("div", { className: "sm-settingsHint" }, t("settings.dialogOpacity.hint"))
4066
+ ),
4067
+ h("div", { className: "sm-settingsSection" },
4068
+ h("div", { className: "sm-settingsSectionTitle" }, t("settings.projectSection")),
4069
+ h("div", { className: "sm-settingsRow sm-settingsRowInline" },
4070
+ h("span", null, t("settings.github")),
4071
+ h("a", {
4072
+ href: "https://github.com/hkkz9522/dsh-session-manager",
4073
+ target: "_blank",
4074
+ rel: "noopener noreferrer",
4075
+ className: "sm-settingsLink"
4076
+ }, "hkkz9522/dsh-session-manager"),
4077
+ h("a", {
4078
+ href: "https://github.com/hkkz9522/dsh-session-manager",
4079
+ target: "_blank",
4080
+ rel: "noopener noreferrer",
4081
+ className: "sm-settingsBadgeLink",
4082
+ title: "Star on GitHub"
4083
+ },
4084
+ h(StarIcon, { size: 14 }),
4085
+ h("span", null, t("settings.star"))
4086
+ ),
4087
+ h("a", {
4088
+ href: "https://github.com/hkkz9522/dsh-session-manager/issues",
4089
+ target: "_blank",
4090
+ rel: "noopener noreferrer",
4091
+ className: "sm-settingsBadgeLink",
4092
+ title: "GitHub Issues"
4093
+ },
4094
+ h(IssueIcon, { size: 14 }),
4095
+ h("span", null, t("settings.feedback"))
4096
+ )
4097
+ )
4098
+ )
2677
4099
  );
2678
4100
  }
2679
4101
 
2680
4102
  function apply(ctx) {
4103
+ activeClientCtx = ctx;
2681
4104
  ctx.effect(() => {
2682
4105
  const refresh = () => { if (annotationStore.hasSubscribers()) void annotationStore.load(true); };
2683
4106
  window.addEventListener("focus", refresh);
@@ -2692,6 +4115,34 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2692
4115
  return () => { window.removeEventListener("focus", refresh); channel?.close(); annotationStore.notify = null; };
2693
4116
  }, "session-manager: annotation synchronization");
2694
4117
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "session-manager: dictionaries");
4118
+ const getCurrentSessionId = () => {
4119
+ try {
4120
+ const id = ctx.uiWorkspace?.mainReference?.sessionId
4121
+ || ctx.uiWorkspace?.selection?.getSnapshot?.()?.sessionId
4122
+ || ctx.sessions?.list?.getSnapshot?.()?.current;
4123
+ return typeof id === "string" && id ? id : null;
4124
+ } catch (_) {
4125
+ return null;
4126
+ }
4127
+ };
4128
+
4129
+ const isCurrentSession = (id) => {
4130
+ if (!id) return false;
4131
+ return getCurrentSessionId() === id;
4132
+ };
4133
+
4134
+ const clearCurrentSession = () => {
4135
+ const uiWorkspace = ctx.uiWorkspace;
4136
+ if (uiWorkspace && typeof uiWorkspace.clearMain === "function") {
4137
+ try { uiWorkspace.clearMain(); } catch (_) {}
4138
+ } else if (uiWorkspace && typeof uiWorkspace.startSession === "function") {
4139
+ try { uiWorkspace.startSession(); } catch (_) {}
4140
+ }
4141
+ if (ctx.sessions && typeof ctx.sessions.clear === "function") {
4142
+ try { ctx.sessions.clear(); } catch (_) {}
4143
+ }
4144
+ };
4145
+
2695
4146
  const actions = () => ({
2696
4147
  // DSH 0.1.6+ removed ctx.sessions.open(); the navigation owner is
2697
4148
  // `ctx.uiWorkspace.openSession()`, and the bare SessionController only
@@ -2713,18 +4164,28 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2713
4164
  }
2714
4165
  throw new Error("session-manager: no Session open API on this DSH host");
2715
4166
  },
2716
- onArchive: async (sessionId) => { await ctx.workspaces.archiveSession(sessionId); },
4167
+ onArchive: async (sessionId) => {
4168
+ if (ctx.uiWorkspace && typeof ctx.uiWorkspace.archiveSession === "function") {
4169
+ await ctx.uiWorkspace.archiveSession(sessionId);
4170
+ } else {
4171
+ await ctx.workspaces.archiveSession(sessionId);
4172
+ }
4173
+ },
2717
4174
  onUnarchive: async (sessionId) => {
2718
4175
  const response = await callApi("/unarchive", { sessionId });
2719
4176
  if (!response.ok) throw new Error(response.error || "unarchive failed");
2720
4177
  },
2721
4178
  onRemove: async (sessionId) => {
4179
+ const wasCurrent = isCurrentSession(sessionId);
2722
4180
  const response = await callApi("/delete", { sessionId });
2723
4181
  if (!response.ok) throw new Error(response.error || "delete failed");
2724
4182
  await annotationStore.load(true);
2725
4183
  if (response.result?.warning) window.alert(response.result.warning);
2726
- const current = ctx.sessions.list.getSnapshot().current;
2727
- if (current === sessionId) ctx.sessions.clear();
4184
+ if (wasCurrent || isCurrentSession(sessionId)) {
4185
+ clearCurrentSession();
4186
+ }
4187
+ try { await ctx.sessions?.refresh?.(); } catch (_) {}
4188
+ try { await ctx.workspaces?.refresh?.(); } catch (_) {}
2728
4189
  },
2729
4190
  onMove: async (sessionId, targetWorkspaceId) => {
2730
4191
  const wasCurrent = ctx.sessions.list.getSnapshot().current === sessionId;
@@ -2754,7 +4215,6 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2754
4215
  if (!response.ok) throw new Error(response.error || "preset migration failed");
2755
4216
  // The current DSH client session store derives agentPreset from
2756
4217
  // the refreshed projection; older builds exposed noteAgentPreset,
2757
- onOpenPanel: () => panelStore.toggle(),
2758
4218
  await ctx.sessions.refresh();
2759
4219
  // The migrate rewrite happens in-process on the live session, so
2760
4220
  // we just need a delayed baseline to let the api-gateway's projection
@@ -2766,7 +4226,31 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2766
4226
  }, 250);
2767
4227
  },
2768
4228
  onOpenPanel: () => panelStore.toggle(),
2769
- onBulkRefresh: async () => { try { await ctx.sessions.refresh(); } catch (_) { /* ignore */ } try { await ctx.workspaces.refresh(); } catch (_) { /* ignore */ } },
4229
+ onBulkRefresh: async (action, meta) => {
4230
+ const currentBefore = getCurrentSessionId();
4231
+ if (action === "delete" || action === "archive") {
4232
+ const items = meta?.items || meta?.result?.items || [];
4233
+ const successIds = items
4234
+ .filter(item => item && (item.status === "success" || item.ok === true))
4235
+ .map(item => item.sessionId);
4236
+ const batchIds = (lastBatchContext.action === action && Array.isArray(lastBatchContext.ids)) ? lastBatchContext.ids : [];
4237
+ const candidateIds = successIds.length > 0 ? successIds : (meta?.ids || batchIds);
4238
+ if (candidateIds.some(isCurrentSession)) {
4239
+ clearCurrentSession();
4240
+ }
4241
+ }
4242
+ try { await ctx.sessions?.refresh?.(); } catch (_) { /* ignore */ }
4243
+ try { await ctx.workspaces?.refresh?.(); } catch (_) { /* ignore */ }
4244
+ if (currentBefore && (action === "delete" || action === "archive")) {
4245
+ const sessionsSnapshot = ctx.sessions?.list?.getSnapshot?.();
4246
+ const workspacesSnapshot = ctx.workspaces?.list?.getSnapshot?.();
4247
+ const deleted = sessionsSnapshot?.byId && sessionsSnapshot.byId[currentBefore] === undefined;
4248
+ const archived = Array.isArray(workspacesSnapshot?.archivedSessionIds) && workspacesSnapshot.archivedSessionIds.includes(currentBefore);
4249
+ if (deleted || archived) {
4250
+ clearCurrentSession();
4251
+ }
4252
+ }
4253
+ },
2770
4254
  });
2771
4255
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
2772
4256
  name: "conversation.session.header.actions",
@@ -2782,12 +4266,83 @@ function nextDialogZ() { return ++dialogZStackCounter; }
2782
4266
  locale: NS,
2783
4267
  inject: () => actions()
2784
4268
  }, FooterAction));
4269
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
4270
+ name: "plugins.bundle.config",
4271
+ id: "dsh-session-manager-bundle-config",
4272
+ key: "dsh-session-manager",
4273
+ locale: NS,
4274
+ inject: () => actions()
4275
+ }, (ownerProps = {}) => (ownerProps?.view === "summary" ? null : h(SessionManagerSettingsCard, ownerProps))));
4276
+
4277
+ try {
4278
+ if (typeof ctx.inject === "function") {
4279
+ ctx.inject(["settingsScope"], (scoped) => {
4280
+ if (scoped && scoped.slots) {
4281
+ scoped.slots.inject("settings.plugin.item", () => scoped.slots.register({
4282
+ name: "settings.plugin.item",
4283
+ id: "session-manager-settings-scoped",
4284
+ key: "dsh-session-manager",
4285
+ locale: NS,
4286
+ inject: () => actions()
4287
+ }, SessionManagerSettingsCard));
4288
+ }
4289
+ });
4290
+ }
4291
+ } catch (_) {}
4292
+
4293
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
4294
+ name: "settings.plugin.item",
4295
+ id: "session-manager-settings",
4296
+ key: "dsh-session-manager",
4297
+ order: 50,
4298
+ locale: NS,
4299
+ inject: () => actions()
4300
+ }, SessionManagerSettingsCard));
4301
+
4302
+ ctx.effect(() => {
4303
+ if (!updateStore.getAutoCheck()) return;
4304
+ const timer = setTimeout(() => {
4305
+ updateStore.checkForUpdate({ silent: true, openOnFound: false, openDialog: false }).catch(() => {});
4306
+ }, 1500);
4307
+ return () => {
4308
+ try {
4309
+ if (typeof clearTimeout === "function") clearTimeout(timer);
4310
+ else if (typeof window !== "undefined" && typeof window.clearTimeout === "function") window.clearTimeout(timer);
4311
+ } catch (_) {}
4312
+ };
4313
+ }, "session-manager: auto update check");
4314
+
4315
+ // Keep <html data-sm-theme> in step with DSH through the official theme
4316
+ // event. ui-layout's ThemePresenter subscribes to the same event and
4317
+ // writes documentElement.colorScheme plus body[data-ds-dark-theme], so
4318
+ // this is the authoritative change signal; the DOM observer above still
4319
+ // covers plain attribute flips, and a theme whose tokens change without
4320
+ // touching an observed attribute would otherwise leave the mirror stale.
4321
+ // The mirror only feeds two cosmetic branches (the token-less opacity
4322
+ // fallback and the priority colours) -- native UA chrome is DSH's job.
4323
+ try {
4324
+ if (typeof ctx.on === "function") {
4325
+ ctx.effect(() => {
4326
+ const off = ctx.on("theme/change", () => smApplyTheme());
4327
+ return () => { try { if (typeof off === "function") off(); } catch (_) {} };
4328
+ }, "session-manager: theme mirror");
4329
+ }
4330
+ } catch (_) { /* theme service absent: the DOM observer is enough */ }
2785
4331
  }
2786
4332
 
2787
4333
  const inject = ["slots", "sessions", "workspaces", "locale", "uiWorkspace"];
2788
4334
 
2789
4335
  exports.apply = apply;
2790
4336
  exports.inject = inject;
4337
+ exports.updateStore = updateStore;
4338
+ exports.compareSemver = compareSemver;
4339
+ exports.UpdateDialog = UpdateDialog;
4340
+ exports.SessionManagerSettingsCard = SessionManagerSettingsCard;
4341
+ exports.WhaleIcon = WhaleIcon;
4342
+ exports.NpmIcon = NpmIcon;
4343
+ exports.CloseIcon = CloseIcon;
4344
+ exports.StarIcon = StarIcon;
4345
+ exports.IssueIcon = IssueIcon;
2791
4346
  return module.exports;
2792
4347
  }
2793
4348
  });