dsh-session-manager 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -39,6 +39,18 @@ window.__ModuleLoader__.load({
39
39
  }
40
40
  } catch (_) { /* react-dom/client not available */ }
41
41
  }
42
+ // Monotonically-increasing z-index for popup dialogs so the most-recently
43
+ // opened popup always sits on top of any previously opened popup. The
44
+ // CSS-defined z-index values (panel 9999 / confirm 10000 / annotation +
45
+ // bulk 10002) are static; without a counter the user can open an
46
+ // annotation popup (10002) after a confirm popup (10000) and the older
47
+ // confirm stays below. Each dialog component claims a fresh slot from
48
+ // this counter on mount, writes it to inline style.zIndex, and never
49
+ // recycles within a session, so the most-recently opened popup is
50
+ // always the topmost one.
51
+ let dialogZStackCounter = 11000;
52
+ function nextDialogZ() { return ++dialogZStackCounter; }
53
+
42
54
 
43
55
  const NS = "session-manager";
44
56
  const API = "/session-manager/api";
@@ -116,6 +128,7 @@ window.__ModuleLoader__.load({
116
128
  "footer.aria": "会话管理",
117
129
  "footer.label": "会话管理",
118
130
  "panel.title": "会话管理",
131
+ "panel.github.title": "欢迎Star和Issue",
119
132
  "panel.search": "搜索标题、ID、标签或备注",
120
133
  "panel.workspace": "工作区",
121
134
  "panel.workspace.all": "全部工作区",
@@ -170,7 +183,62 @@ window.__ModuleLoader__.load({
170
183
  "busy.processing": "处理中…",
171
184
  "error.operation": "操作失败:{message}",
172
185
  "workspace.default": "默认工作区",
173
- "workspace.sessions": "{count} 个会话"
186
+ "workspace.sessions": "{count} 个会话",
187
+ "bulk.title": "批量管理",
188
+ "bulk.selectedFiltered": "已选 {selected} / {filtered} 项",
189
+ "bulk.selectAllFiltered": "全选当前筛选",
190
+ "bulk.clear": "清空选择",
191
+ "bulk.archive": "归档",
192
+ "bulk.unarchive": "取消归档",
193
+ "bulk.delete": "删除会话",
194
+ "bulk.move": "移动至工作区",
195
+ "bulk.moveBtn": "移动至工作区",
196
+ "bulk.presetMigrate": "迁移预设",
197
+ "bulk.favorite": "收藏",
198
+ "bulk.unfavorite": "取消收藏",
199
+ "bulk.review": "待回看",
200
+ "bulk.unreview": "取消待回看",
201
+ "bulk.tagsAdd": "添加标签",
202
+ "bulk.tagsRemove": "清空标签",
203
+ "bulk.priority": "设置优先级",
204
+ "bulk.tags.placeholder": "用逗号分隔,例如:排障, 方案",
205
+ "bulk.priority.aria": "选择优先级",
206
+ "bulk.workspace.aria": "选择目标工作区",
207
+ "bulk.preset.aria": "选择目标 preset",
208
+ "bulk.dialog.title": "批量操作:{action}",
209
+ "bulk.dialog.summary": "将会对 {count} 个会话执行「{action}」。",
210
+ "bulk.dialog.warning.delete": "删除操作不可撤销,请确认后再继续。",
211
+ "bulk.confirm.archive": "确认归档",
212
+ "bulk.confirm.unarchive": "确认还原",
213
+ "bulk.confirm.favorite": "确认收藏",
214
+ "bulk.confirm.unfavorite": "确认移除",
215
+ "bulk.confirm.review": "确认标记",
216
+ "bulk.confirm.unreview": "确认清除",
217
+ "bulk.confirm.tagsAdd": "确认添加",
218
+ "bulk.confirm.tagsRemove": "确认清空",
219
+ "bulk.confirm.priority": "确认设置",
220
+ "bulk.confirm.move": "确认移动",
221
+ "bulk.confirm.presetMigrate": "确认迁移",
222
+ "bulk.confirm.delete": "确认删除",
223
+ "bulk.dialog.execute": "执行",
224
+ "bulk.dialog.cancel": "取消",
225
+ "bulk.progress.title": "正在执行:{action}",
226
+ "bulk.progress.current": "处理 {done} / {total}",
227
+ "bulk.progress.cancel": "取消",
228
+ "bulk.progress.completed": "已完成 {done} / {total}",
229
+ "bulk.result.title": "批量操作完成:{action}",
230
+ "bulk.result.successGroup": "成功 ({count})",
231
+ "bulk.result.failedGroup": "失败 ({count})",
232
+ "bulk.result.skippedGroup": "跳过 ({count})",
233
+ "bulk.result.retry": "重试失败项",
234
+ "bulk.result.close": "关闭",
235
+ "bulk.error.tooMany": "批量操作最多支持 200 项,请缩小筛选范围后重试",
236
+ "bulk.checkbox.aria": "选择 {title}",
237
+ "bulk.heading": "批量操作",
238
+ "panel.selectMode": "批量处理",
239
+ "panel.selectMode.exit": "退出批量处理",
240
+ "panel.selectMode.aria": "进入批量管理模式",
241
+ "panel.selectMode.active.aria": "退出批量管理模式",
174
242
  };
175
243
  const en = {
176
244
  "marks.filter.favorite": "Favorites",
@@ -245,6 +313,7 @@ window.__ModuleLoader__.load({
245
313
  "footer.aria": "Session manager",
246
314
  "footer.label": "Sessions",
247
315
  "panel.title": "Session manager",
316
+ "panel.github.title": "Star or open an issue",
248
317
  "panel.search": "Search title, ID, tags or notes",
249
318
  "panel.workspace": "Workspace",
250
319
  "panel.workspace.all": "All workspaces",
@@ -261,6 +330,61 @@ window.__ModuleLoader__.load({
261
330
  "panel.count": "Showing {shown} / {total} sessions",
262
331
  "panel.reset": "Reset filters",
263
332
  "panel.empty.filtered": "No matching sessions. Adjust your search or filters.",
333
+ "bulk.title": "Bulk management",
334
+ "bulk.selectedFiltered": "Selected {selected} / {filtered}",
335
+ "bulk.selectAllFiltered": "Select all in filter",
336
+ "bulk.clear": "Clear selection",
337
+ "bulk.archive": "Archive",
338
+ "bulk.unarchive": "Unarchive",
339
+ "bulk.delete": "Delete session",
340
+ "bulk.move": "Move to workspace",
341
+ "bulk.moveBtn": "Move",
342
+ "bulk.presetMigrate": "Migrate preset",
343
+ "bulk.favorite": "Favorite",
344
+ "bulk.unfavorite": "Unfavorite",
345
+ "bulk.review": "Mark for review",
346
+ "bulk.unreview": "Clear review flag",
347
+ "bulk.tagsAdd": "Add tags",
348
+ "bulk.tagsRemove": "Clear tags",
349
+ "bulk.priority": "Set priority",
350
+ "bulk.tags.placeholder": "Comma-separated, e.g. bug, design",
351
+ "bulk.priority.aria": "Choose priority",
352
+ "bulk.workspace.aria": "Choose target workspace",
353
+ "bulk.preset.aria": "Choose target preset",
354
+ "bulk.dialog.title": "Bulk action: {action}",
355
+ "bulk.dialog.summary": "Apply \"{action}\" to {count} sessions.",
356
+ "bulk.dialog.warning.delete": "Deletion is permanent and cannot be undone.",
357
+ "bulk.confirm.archive": "Archive",
358
+ "bulk.confirm.unarchive": "Unarchive",
359
+ "bulk.confirm.favorite": "Favorite",
360
+ "bulk.confirm.unfavorite": "Unfavorite",
361
+ "bulk.confirm.review": "Mark review",
362
+ "bulk.confirm.unreview": "Clear review",
363
+ "bulk.confirm.tagsAdd": "Add tags",
364
+ "bulk.confirm.tagsRemove": "Clear tags",
365
+ "bulk.confirm.priority": "Set priority",
366
+ "bulk.confirm.move": "Move",
367
+ "bulk.confirm.presetMigrate": "Migrate",
368
+ "bulk.confirm.delete": "Delete",
369
+ "bulk.dialog.execute": "Execute",
370
+ "bulk.dialog.cancel": "Cancel",
371
+ "bulk.progress.title": "Running: {action}",
372
+ "bulk.progress.current": "Processing {done} / {total}",
373
+ "bulk.progress.cancel": "Cancel",
374
+ "bulk.progress.completed": "Finished {done} / {total}",
375
+ "bulk.result.title": "Bulk action finished: {action}",
376
+ "bulk.result.successGroup": "Success ({count})",
377
+ "bulk.result.failedGroup": "Failed ({count})",
378
+ "bulk.result.skippedGroup": "Skipped ({count})",
379
+ "bulk.result.retry": "Retry failed",
380
+ "bulk.result.close": "Close",
381
+ "bulk.error.tooMany": "Batch actions support at most 200 sessions; please narrow the filter.",
382
+ "bulk.checkbox.aria": "Select {title}",
383
+ "bulk.heading": "Bulk actions",
384
+ "panel.selectMode": "Batch process",
385
+ "panel.selectMode.exit": "Exit batch process",
386
+ "panel.selectMode.aria": "Enter bulk selection mode",
387
+ "panel.selectMode.active.aria": "Exit bulk selection mode",
264
388
  "panel.filter.all": "All",
265
389
  "panel.filter.active": "Active",
266
390
  "panel.filter.archived": "Archived",
@@ -315,7 +439,7 @@ window.__ModuleLoader__.load({
315
439
  ".sm-footerBtn svg{flex:none}",
316
440
  ".sm-panel{display:flex;flex-direction:column;gap:10px;min-height:280px;max-height:min(70vh,640px);min-width:480px}",
317
441
  ".sm-filter{display:flex;gap:4px;flex-wrap:wrap}",
318
- ".sm-filterBtn{cursor:pointer;color:var(--dsw-alias-label-secondary);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 12px;font-size:12px;line-height:18px}",
442
+ ".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}",
319
443
  ".sm-filterBtn:hover{background:var(--dsw-alias-interactive-bg-hover)}",
320
444
  ".sm-filterBtn.sm-on{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-fill-l2);border-color:transparent}",
321
445
  ".sm-list{display:flex;flex-direction:column;gap:4px;min-height:0;overflow:auto;flex:1}",
@@ -323,7 +447,7 @@ window.__ModuleLoader__.load({
323
447
  ".sm-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
324
448
  ".sm-rowCurrent{border-color:var(--dsw-alias-accent-primary,var(--dsw-alias-label-secondary))}",
325
449
  ".sm-rowMain{min-width:0;display:flex;flex-direction:column;gap:2px;flex:1 1 280px}",
326
- ".sm-rowTitle{color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px;display:flex;align-items:center;gap:6px;min-width:0}",
450
+ ".sm-rowTitle{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;display:flex;align-items:center;gap:6px;min-width:0}",
327
451
  ".sm-rowTitle>span:first-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}",
328
452
  ".sm-rowMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}",
329
453
  ".sm-rowActions{display:flex;gap:4px;flex:none;flex-wrap:wrap;justify-content:flex-end}",
@@ -347,32 +471,70 @@ window.__ModuleLoader__.load({
347
471
  ".sm-moveItemSelected{border-color:var(--dsw-alias-accent-primary);background:var(--dsw-alias-accent-primary-bg)}",
348
472
  ".sm-moveItemName{color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}",
349
473
  ".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)}",
350
- ".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{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}.sm-panelDialog .sm-nativeDialogFooter{padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:flex-end;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:0 10px;height:36px;min-height:36px;border:1px solid transparent;border-radius:8px;background:transparent}.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:hidden;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:300px;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}.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:flex-end;gap:8px;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)}.sm-confirmDialog .sm-nativeDialogCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-confirmDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-error-primary);color:#fff;border-color:var(--dsw-alias-state-error-primary)}.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;height:22px;padding:0 8px;font-size:11px;line-height:14px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}.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-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:flex-end;gap:8px;margin-top:14px}.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:auto;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:15px;line-height:22px;font-weight:600;color:#111;color:var(--dsw-alias-label-primary,#111)}.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:12px 18px;border-top:1px solid #eee;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:flex-end;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:32px;padding:4px 14px;font-size:13px;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)}.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)}",
351
- ".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}.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-fill-l2,#f5f5f5);font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#111)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer{width:300px;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}",
474
+ ".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)}",
475
+ ".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}",
352
476
 
353
477
  // Theme colors are scoped to the plugin dialogs. DSH exposes its
354
478
  // actual theme through `body[data-ds-dark-theme]` and color-scheme;
355
479
  // keeping these values local avoids changing DSH's global variables.
356
480
  "[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}",
357
- "[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{--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=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)}",
358
482
  "[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}",
359
- "[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{background:#1f1f23!important;background-color:#1f1f23!important;border-color:#3a3a40!important}",
483
+ "[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)}",
360
484
  ".sm-panelDialog{max-height:min(640px,calc(100vh - 32px))}.sm-panelDialog .sm-panel{min-width:0;overflow:hidden}",
361
485
  ".sm-panelTools{display:flex;flex-direction:column;gap:8px;flex:none;padding:2px 0 6px}",
362
486
  ".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}",
363
487
  ".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}",
364
- ".sm-panelSelects{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px}.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-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}",
365
489
  ".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)}",
366
490
  "@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}}",
367
- ".sm-panelDialog:not(.sm-annotationSurface){width:680px}.sm-panelDialog .sm-row{height:auto;min-height:36px;flex-shrink:0;flex-wrap:wrap;padding:5px 8px}.sm-panelDialog .sm-rowMain{flex:1 1 200px;flex-direction:column;gap:3px}.sm-panelDialog .sm-rowActions{margin-left:auto;max-width:100%}.sm-rowAnnotations{display:flex;align-items:center;gap:5px;min-width:0;font-size:10px;line-height:16px}",
491
+ ".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}",
368
492
  ".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)}",
369
493
  "[data-sm-theme=dark] .sm-priority-1{color:#fdba74}[data-sm-theme=dark] .sm-priority-2{color:#fde68a}",
370
494
  ".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)}",
371
- ".sm-markFilters{display:grid;grid-template-columns:auto auto minmax(100px,1fr) minmax(100px,1fr);align-items:end;gap:6px}.sm-markFilterToggle{height:32px;white-space:nowrap}.sm-markFilterToggle:disabled{opacity:.5;cursor:not-allowed}.sm-header{flex-wrap:wrap}",
372
- ".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:460px;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)}",
495
+ ".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}",
496
+ ".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)}",
373
497
  ".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)}",
374
498
  ".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}",
375
- "@media(max-width:600px){.sm-markFilters{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}}",
499
+ ".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}",
500
+ ".sm-bulkSummary{color:var(--dsw-alias-label-primary,#111);font-size:12px;line-height:18px;font-weight:500}",
501
+ "",
502
+ ".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}",
503
+ ".sm-bulkBar .sm-bulkBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary,#111)}",
504
+ ".sm-bulkBar .sm-bulkBtn:disabled{opacity:.5;cursor:not-allowed}",
505
+ ".sm-bulkBar .sm-bulkBtnDanger{color:var(--dsw-alias-state-error-primary);border-color:rgba(217,80,80,.4)}",
506
+ ".sm-bulkBar .sm-bulkBtnDanger:hover:not(:disabled){color:#fff;background:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}",
507
+ "",
508
+ ".sm-bulkRowCheckbox{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;flex:none}",
509
+ ".sm-bulkRowCheckbox input{width:14px;height:14px;cursor:pointer;margin:0}",
510
+ ".sm-bulkRowCheckbox input:disabled{cursor:not-allowed}",
511
+ ".sm-rowBulkSelected{border-color:var(--dsw-alias-accent-primary,#356ae6);background:var(--dsw-alias-accent-primary-bg,rgba(53,106,230,.06))}",
512
+ ".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}",
513
+ ".sm-bulkDialog .sm-nativeDialog{width:100%;max-height:min(640px,calc(100vh - 32px))}",
514
+ ".sm-bulkDialogBody{display:flex;flex-direction:column;gap:12px;min-height:0}",
515
+ ".sm-bulkDialogSummary{font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}",
516
+ ".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}",
517
+ ".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}",
518
+ ".sm-bulkSessionItem{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#666);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
519
+ ".sm-bulkProgressBar{position:relative;height:8px;background:var(--dsw-alias-fill-l2,#eee);border-radius:4px;overflow:hidden}",
520
+ ".sm-bulkProgressFill{position:absolute;top:0;left:0;bottom:0;background:var(--dsw-alias-accent-primary,#356ae6);transition:width .2s ease}",
521
+ ".sm-bulkResultGroup{display:flex;flex-direction:column;gap:6px}",
522
+ ".sm-bulkResultHeader{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary,#666)}",
523
+ ".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}",
524
+ ".sm-bulkResultItem{display:flex;flex-direction:column;gap:2px;padding:4px 6px;font-size:12px;line-height:18px;border-radius:6px;cursor:default}",
525
+ ".sm-bulkResultItem:hover{background:var(--dsw-alias-interactive-bg-hover)}",
526
+ ".sm-bulkResultItemTitle{color:var(--dsw-alias-label-primary,#111);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
527
+ ".sm-bulkResultItemReason{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;word-break:break-word}",
528
+ ".sm-bulkResultItemReasonSkip{color:var(--dsw-alias-label-tertiary,#888)}",
529
+ ".sm-bulkResultFooter{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}",
530
+ ".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}",
531
+ ".sm-footerBtn-wide:hover,.sm-footerBtn-wide:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
532
+ ".sm-footerBtn-rail{width:36px;height:36px;padding:0;justify-content:center;border-radius:12px;color:var(--dsw-alias-label-secondary)}",
533
+ ".sm-footerBtn-rail:hover,.sm-footerBtn-rail:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
534
+ ".sm-footerBtn-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%}",
535
+ ".sm-footer-wide{display:flex;width:100%}",
536
+ ".sm-footer-rail{display:flex;width:100%;justify-content:center}",
537
+ "@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)}}",
376
538
  ].join("");
377
539
 
378
540
  // Auto-follow DSH's theme. We detect DSH's current theme from
@@ -497,6 +659,35 @@ window.__ModuleLoader__.load({
497
659
  const h = (type, props, ...children) => React.createElement(type, props, ...children);
498
660
  const callApi = (path, payload) => fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload || {}) }).then((r) => r.json());
499
661
  const fetchApi = (path, options) => fetch(API + path, options).then((r) => r.json());
662
+ // Run a bulk action across many sessionIds. /batch already does per-id
663
+ // error reporting, so partial failures come back as {summary, items}
664
+ // with per-id success/failed/skipped status. Cancellation is best-effort
665
+ // because the v0.1.7 host does not reliably support req.on("close").
666
+ async function runBatch(args, onProgress) {
667
+ const { action, sessionIds, payload } = args || {};
668
+ const controller = new AbortController();
669
+ try {
670
+ const response = await fetch(API + "/batch", {
671
+ method: "POST",
672
+ headers: { "content-type": "application/json" },
673
+ body: JSON.stringify({ action, sessionIds: sessionIds || [], payload: payload || {} }),
674
+ signal: controller.signal
675
+ });
676
+ const data = await response.json();
677
+ if (data && data.ok) {
678
+ const result = data.result || {};
679
+ const items = Array.isArray(result.items) ? result.items : [];
680
+ if (typeof onProgress === "function") onProgress({ done: items.length, total: (sessionIds || []).length, lastItem: items[items.length - 1] || null });
681
+ return { summary: result.summary || { total: items.length, success: 0, failed: 0, skipped: 0 }, items, aborted: !!result.aborted };
682
+ }
683
+ const error = new Error((data && data.error) || "batch failed");
684
+ error.code = (data && data.code) || "bad-request";
685
+ throw error;
686
+ } catch (error) {
687
+ if (error && error.name === "AbortError") return { summary: { total: 0, success: 0, failed: 0, skipped: 0 }, items: [], aborted: true };
688
+ throw error;
689
+ }
690
+ }
500
691
 
501
692
  const EMPTY_ANNOTATION = Object.freeze({ favorite: false, reviewLater: false, tags: Object.freeze([]), note: "", priority: 3, revision: 0 });
502
693
  const validAnnotation = value => value && typeof value.favorite === "boolean" && typeof value.reviewLater === "boolean" && Array.isArray(value.tags) && value.tags.every(tag => typeof tag === "string") && typeof value.note === "string" && (value.priority === null || Number.isInteger(value.priority) && value.priority >= 1 && value.priority <= 5) && Number.isSafeInteger(value.revision) && value.revision >= 0;
@@ -710,7 +901,239 @@ window.__ModuleLoader__.load({
710
901
  ].join(String.fromCharCode(10))
711
902
  };
712
903
  const promptFor = lang => CLIPBOARD_PROMPT_TEXT[lang] || CLIPBOARD_PROMPT_TEXT.zh;
904
+ function BatchPreviewDialog(props) {
905
+ const t = typeof props.t === "function" ? props.t : (key) => key;
906
+ const open = props.open === true;
907
+ const onCancel = typeof props.onCancel === "function" ? props.onCancel : () => {};
908
+ const onExecute = typeof props.onExecute === "function" ? props.onExecute : () => {};
909
+ const actionLabel = props.actionLabel || "";
910
+ const sessions = Array.isArray(props.sessions) ? props.sessions : [];
911
+ const warning = typeof props.warning === "string" ? props.warning : "";
912
+ const confirmLabel = props.confirmLabel || t("bulk.dialog.execute");
913
+ const cancelLabel = props.cancelLabel || t("bulk.dialog.cancel");
914
+ const danger = props.danger === true;
915
+ const [expanded, setExpanded] = useState(false);
916
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
917
+ const escStateRef = useRef({});
918
+ escStateRef.current = { onCancel };
919
+ useEffect(() => {
920
+ if (!open) return;
921
+ const handler = (e) => { if (e.key !== "Escape" || e.defaultPrevented) return; e.preventDefault(); e.stopPropagation(); escStateRef.current.onCancel(); };
922
+ window.addEventListener("keydown", handler);
923
+ return () => window.removeEventListener("keydown", handler);
924
+ }, [open]);
925
+ if (!open) return null;
926
+ const showExpand = sessions.length > 12;
927
+ const visible = expanded ? sessions : sessions.slice(0, 12);
928
+ return h("div", { className: "sm-nativeDialogLayer sm-bulkDialog", role: "presentation", style: { zIndex: zIndex || undefined } },
929
+ h("section", { className: "sm-nativeDialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "sm-batch-preview-title" },
930
+ h("div", { className: "sm-nativeDialogHeader" }, h("h2", { id: "sm-batch-preview-title", className: "sm-nativeDialogTitle" }, t("bulk.dialog.title", { action: actionLabel }))),
931
+ h("div", { className: "sm-nativeDialogBody" },
932
+ h("div", { className: "sm-bulkDialogSummary" }, t("bulk.dialog.summary", { count: sessions.length, action: actionLabel })),
933
+ warning ? h("div", { className: "sm-bulkDialogWarn" }, warning) : null,
934
+ h("div", { className: "sm-bulkSessionList" },
935
+ visible.map((s, i) => h("div", { key: (s.id || i) + "-" + i, className: "sm-bulkSessionItem", title: s.id || "" }, s.displayTitle || s.id || "—"))
936
+ ),
937
+ showExpand ? h("button", { type: "button", className: "sm-resetFilters", onClick: () => setExpanded(value => !value) }, expanded ? t("panel.reset") : "…(+" + (sessions.length - 12) + ")") : null
938
+ ),
939
+ h("div", { className: "sm-nativeDialogFooter" },
940
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogCancel", onClick: onCancel }, cancelLabel),
941
+ h("button", { type: "button", className: "sm-nativeDialogButton" + (danger ? " sm-nativeDialogDanger" : " sm-nativeDialogConfirm"), onClick: onExecute }, confirmLabel)
942
+ )
943
+ )
944
+ );
945
+ }
946
+
947
+ function BatchProgressDialog(props) {
948
+ const t = typeof props.t === "function" ? props.t : (key) => key;
949
+ const open = props.open === true;
950
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
951
+ const actionLabel = props.actionLabel || "";
952
+ const done = Number.isInteger(props.done) ? props.done : 0;
953
+ const total = Number.isInteger(props.total) ? props.total : 0;
954
+ const completed = props.completed === true;
955
+ const onCancel = typeof props.onCancel === "function" ? props.onCancel : () => {};
956
+ const cancelLabel = props.cancelLabel || t("bulk.progress.cancel");
957
+ if (!open) return null;
958
+ const pct = total > 0 ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0;
959
+ return h("div", { className: "sm-nativeDialogLayer sm-bulkDialog", role: "presentation", style: { zIndex: zIndex || undefined } },
960
+ h("section", { className: "sm-nativeDialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "sm-batch-progress-title" },
961
+ h("div", { className: "sm-nativeDialogHeader" }, h("h2", { id: "sm-batch-progress-title", className: "sm-nativeDialogTitle" }, t("bulk.progress.title", { action: actionLabel }))),
962
+ h("div", { className: "sm-nativeDialogBody" },
963
+ h("div", { className: "sm-bulkDialogSummary" }, completed ? t("bulk.progress.completed", { done, total }) : t("bulk.progress.current", { done, total })),
964
+ h("div", { className: "sm-bulkProgressBar", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": total, "aria-valuenow": done },
965
+ h("div", { className: "sm-bulkProgressFill", style: { width: pct + "%" } })
966
+ )
967
+ ),
968
+ h("div", { className: "sm-nativeDialogFooter" },
969
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogCancel", disabled: completed, onClick: onCancel }, cancelLabel)
970
+ )
971
+ )
972
+ );
973
+ }
974
+
975
+ function BatchResultDialog(props) {
976
+ const t = typeof props.t === "function" ? props.t : (key) => key;
977
+ const open = props.open === true;
978
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
979
+ const actionLabel = props.actionLabel || "";
980
+ const items = Array.isArray(props.items) ? props.items : [];
981
+ const onClose = typeof props.onClose === "function" ? props.onClose : () => {};
982
+ const onRetry = typeof props.onRetry === "function" ? props.onRetry : () => {};
983
+ const closeLabel = props.closeLabel || t("bulk.result.close");
984
+ const retryLabel = props.retryLabel || t("bulk.result.retry");
985
+ const escStateRef = useRef({});
986
+ escStateRef.current = { onClose };
987
+ useEffect(() => {
988
+ if (!open) return;
989
+ const handler = (e) => { if (e.key !== "Escape" || e.defaultPrevented) return; e.preventDefault(); e.stopPropagation(); escStateRef.current.onClose(); };
990
+ window.addEventListener("keydown", handler);
991
+ return () => window.removeEventListener("keydown", handler);
992
+ }, [open]);
993
+ if (!open) return null;
994
+ const groups = { success: [], failed: [], skipped: [] };
995
+ for (const item of items) {
996
+ const status = item && item.status;
997
+ if (status === "success") groups.success.push(item);
998
+ else if (status === "skipped") groups.skipped.push(item);
999
+ else groups.failed.push(item);
1000
+ }
1001
+ const renderGroup = (labelKey, group, reasonClass) => {
1002
+ if (group.length === 0) return null;
1003
+ return h("div", { key: labelKey, className: "sm-bulkResultGroup" },
1004
+ h("div", { className: "sm-bulkResultHeader" }, t(labelKey, { count: group.length })),
1005
+ h("div", { className: "sm-bulkResultList" },
1006
+ group.map((item, i) => h("div", { key: (item.sessionId || i) + "-" + i, className: "sm-bulkResultItem", title: item.sessionId || "" },
1007
+ h("div", { className: "sm-bulkResultItemTitle" }, item.displayTitle || item.sessionId || "—"),
1008
+ reasonClass && (item.reason || item.error) ? h("div", { className: reasonClass }, item.reason || item.error) : null
1009
+ ))
1010
+ )
1011
+ );
1012
+ };
1013
+ return h("div", { className: "sm-nativeDialogLayer sm-bulkDialog", role: "presentation", style: { zIndex: zIndex || undefined } },
1014
+ h("section", { className: "sm-nativeDialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "sm-batch-result-title" },
1015
+ h("div", { className: "sm-nativeDialogHeader" }, h("h2", { id: "sm-batch-result-title", className: "sm-nativeDialogTitle" }, t("bulk.result.title", { action: actionLabel }))),
1016
+ h("div", { className: "sm-nativeDialogBody" },
1017
+ renderGroup("bulk.result.successGroup", groups.success),
1018
+ renderGroup("bulk.result.failedGroup", groups.failed, "sm-bulkResultItemReason"),
1019
+ renderGroup("bulk.result.skippedGroup", groups.skipped, "sm-bulkResultItemReasonSkip")
1020
+ ),
1021
+ h("div", { className: "sm-nativeDialogFooter" },
1022
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogCancel", onClick: onClose }, closeLabel),
1023
+ groups.failed.length > 0 ? h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogConfirm", onClick: () => onRetry(groups.failed.map(item => item.sessionId)) }, retryLabel) : null
1024
+ )
1025
+ )
1026
+ );
1027
+ }
1028
+
1029
+ function BulkActionBar(props) {
1030
+ const t = props.t;
1031
+ const selectedCount = props.selectedCount || 0;
1032
+ const filteredCount = props.filteredCount || 0;
1033
+ const busy = props.busy === true;
1034
+ const onAction = props.onAction;
1035
+ const onOpenTagDialog = props.onOpenTagDialog;
1036
+ const onOpenPriorityDialog = props.onOpenPriorityDialog;
1037
+ const onOpenMoveDialog = props.onOpenMoveDialog;
1038
+ const onOpenPresetDialog = props.onOpenPresetDialog;
1039
+ const disableNonDestructive = selectedCount === 0 || busy;
1040
+ const destructiveBusy = selectedCount === 0 || busy;
1041
+ return h("div", { className: "sm-bulkBar", role: "toolbar", "aria-label": t("bulk.heading") },
1042
+ h("span", { className: "sm-bulkSummary" }, t("bulk.selectedFiltered", { selected: selectedCount, filtered: filteredCount })),
1043
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: () => onAction("archive") }, t("bulk.archive")),
1044
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: () => onAction("unarchive") }, t("bulk.unarchive")),
1045
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: () => onAction("favorite") }, t("bulk.favorite")),
1046
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: () => onAction("unfavorite") }, t("bulk.unfavorite")),
1047
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: () => onAction("review") }, t("bulk.review")),
1048
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: () => onAction("unreview") }, t("bulk.unreview")),
1049
+ // Row 2 of the 3x6 grid (添加标签 / 清空标签 / 设置优先级 / 移动至工作区 / 迁移预设 / 删除会话).
1050
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: onOpenTagDialog }, t("bulk.tagsAdd")),
1051
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: () => onAction("remove-tags", { tags: [] }) }, t("bulk.tagsRemove")),
1052
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: onOpenPriorityDialog }, t("bulk.priority")),
1053
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: onOpenMoveDialog }, t("bulk.moveBtn")),
1054
+ h("button", { type: "button", className: "sm-bulkBtn", disabled: disableNonDestructive, onClick: onOpenPresetDialog }, t("bulk.presetMigrate")),
1055
+ h("button", { type: "button", className: "sm-bulkBtn sm-bulkBtnDanger", disabled: destructiveBusy, onClick: () => onAction("delete", null, true) }, t("bulk.delete"))
1056
+ );
1057
+ }
713
1058
 
1059
+ function BulkTagInputDialog(props) {
1060
+ const t = typeof props.t === "function" ? props.t : (key) => key;
1061
+ const open = props.open === true;
1062
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
1063
+ const title = props.title || t("bulk.tagsAdd");
1064
+ const placeholder = props.placeholder || t("bulk.tags.placeholder");
1065
+ const onCancel = typeof props.onCancel === "function" ? props.onCancel : () => {};
1066
+ const onApply = typeof props.onApply === "function" ? props.onApply : () => {};
1067
+ const confirmLabel = props.confirmLabel || t("bulk.confirm.tagsAdd");
1068
+ const [value, setValue] = useState("");
1069
+ useEffect(() => { if (open) setValue(""); }, [open]);
1070
+ const escStateRef = useRef({});
1071
+ escStateRef.current = { onCancel };
1072
+ useEffect(() => {
1073
+ if (!open) return;
1074
+ const handler = (e) => { if (e.key !== "Escape" || e.defaultPrevented) return; e.preventDefault(); e.stopPropagation(); escStateRef.current.onCancel(); };
1075
+ window.addEventListener("keydown", handler);
1076
+ return () => window.removeEventListener("keydown", handler);
1077
+ }, [open]);
1078
+ if (!open) return null;
1079
+ const submit = () => {
1080
+ const tags = (value || "").split(/[,,\n]/).map(s => s.trim()).filter(Boolean);
1081
+ onApply(tags);
1082
+ };
1083
+ return h("div", { className: "sm-nativeDialogLayer sm-bulkDialog", role: "presentation", style: { zIndex: zIndex || undefined } },
1084
+ h("section", { className: "sm-nativeDialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "sm-bulk-tags-title" },
1085
+ h("div", { className: "sm-nativeDialogHeader" }, h("h2", { id: "sm-bulk-tags-title", className: "sm-nativeDialogTitle" }, title)),
1086
+ h("div", { className: "sm-nativeDialogBody" },
1087
+ h("textarea", { className: "sm-noteInput", "aria-label": placeholder, value, placeholder, rows: 3, onChange: (e) => setValue(e.target.value), autoFocus: true })
1088
+ ),
1089
+ h("div", { className: "sm-nativeDialogFooter" },
1090
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogCancel", onClick: onCancel }, t("bulk.dialog.cancel")),
1091
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogConfirm", onClick: submit }, confirmLabel)
1092
+ )
1093
+ )
1094
+ );
1095
+ }
1096
+
1097
+ function BulkChoiceDialog(props) {
1098
+ const t = typeof props.t === "function" ? props.t : (key) => key;
1099
+ const open = props.open === true;
1100
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
1101
+ const title = props.title || "";
1102
+ const description = props.description || "";
1103
+ const options = Array.isArray(props.options) ? props.options : [];
1104
+ const onCancel = typeof props.onCancel === "function" ? props.onCancel : () => {};
1105
+ const onApply = typeof props.onApply === "function" ? props.onApply : () => {};
1106
+ const confirmLabel = props.confirmLabel || t("bulk.dialog.execute");
1107
+ const [value, setValue] = useState("");
1108
+ useEffect(() => { if (open) setValue(""); }, [open]);
1109
+ const escStateRef = useRef({});
1110
+ escStateRef.current = { onCancel };
1111
+ useEffect(() => {
1112
+ if (!open) return;
1113
+ const handler = (e) => { if (e.key !== "Escape" || e.defaultPrevented) return; e.preventDefault(); e.stopPropagation(); escStateRef.current.onCancel(); };
1114
+ window.addEventListener("keydown", handler);
1115
+ return () => window.removeEventListener("keydown", handler);
1116
+ }, [open]);
1117
+ if (!open) return null;
1118
+ return h("div", { className: "sm-nativeDialogLayer sm-bulkDialog", role: "presentation", style: { zIndex: zIndex || undefined } },
1119
+ h("section", { className: "sm-nativeDialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "sm-bulk-choice-title" },
1120
+ h("div", { className: "sm-nativeDialogHeader" }, h("h2", { id: "sm-bulk-choice-title", className: "sm-nativeDialogTitle" }, title)),
1121
+ h("div", { className: "sm-nativeDialogBody" },
1122
+ description ? h("div", { className: "sm-bulkDialogSummary" }, description) : null,
1123
+ options.length === 0
1124
+ ? h("div", { className: "sm-empty" }, t("migrate.noPresets"))
1125
+ : h("select", { className: "sm-viewSelect", value, onChange: (e) => setValue(e.target.value), "aria-label": title, autoFocus: true },
1126
+ h("option", { value: "" }, "—"),
1127
+ options.map((opt) => h("option", { key: opt.value, value: opt.value }, opt.label))
1128
+ )
1129
+ ),
1130
+ h("div", { className: "sm-nativeDialogFooter" },
1131
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogCancel", onClick: onCancel }, t("bulk.dialog.cancel")),
1132
+ h("button", { type: "button", className: "sm-nativeDialogButton sm-nativeDialogConfirm", disabled: value === "", onClick: () => onApply(value) }, confirmLabel)
1133
+ )
1134
+ )
1135
+ );
1136
+ }
714
1137
  function AnnotationDialog({ sessionId, displayTitle, t, onClose, anchor }) {
715
1138
  const annotations = useAnnotations();
716
1139
  const latest = annotationOf(annotations, sessionId);
@@ -723,6 +1146,11 @@ window.__ModuleLoader__.load({
723
1146
  const [busy, setBusy] = useState(false);
724
1147
  const [error, setError] = useState(null);
725
1148
  const dialogRef = useRef(null);
1149
+ // Claim a fresh z-index so this popup always sits above any earlier
1150
+ // popup the user may have opened (annotation popup typically opens on
1151
+ // top of a confirm/move/migrate dialog, so we need a guaranteed-
1152
+ // higher z-index than those static CSS values).
1153
+ const [zIndex] = useState(() => nextDialogZ());
726
1154
  const dirty = favorite !== base.favorite || reviewLater !== base.reviewLater || tagsText !== base.tags.join(", ") || note !== base.note || priority !== String(base.priority ?? 3);
727
1155
  const cancel = () => { if (busy || dirty && !window.confirm(t("marks.discard"))) return false; onClose(); return true; };
728
1156
  const reload = () => {
@@ -817,9 +1245,10 @@ window.__ModuleLoader__.load({
817
1245
  const surfaceClass = "sm-nativeDialog sm-panelDialog sm-annotationSurface" + (hasAnchor ? " sm-annotationSurface-anchored" : "");
818
1246
  // Default position matches ConfirmDialog / MoveDialog so all inner
819
1247
  // dialogs line up at top calc(50vh - 90px), left calc(50vw + 308px).
820
- const layerStyle = hasAnchor
821
- ? { position: "fixed", top: anchor.top + "px", left: anchor.left + "px", zIndex: 10002 }
822
- : null;
1248
+ const layerStyle = {
1249
+ zIndex: zIndex || undefined,
1250
+ ...(hasAnchor ? { position: "fixed", top: anchor.top + "px", left: anchor.left + "px" } : {})
1251
+ };
823
1252
  return h("div", { className: layerClass, style: layerStyle, role: "presentation" },
824
1253
  h("section", { className: surfaceClass, role: "dialog", "aria-labelledby": "sm-annotation-title", ref: dialogRef, onKeyDown },
825
1254
  h("div", { className: "sm-nativeDialogHeader" }, h("h2", { id: "sm-annotation-title", className: "sm-nativeDialogTitle" }, t("marks.title"))),
@@ -888,6 +1317,9 @@ window.__ModuleLoader__.load({
888
1317
  const onCancel = typeof props.onCancel === "function" ? props.onCancel : () => {};
889
1318
  const onConfirm = typeof props.onConfirm === "function" ? props.onConfirm : () => {};
890
1319
  const busy = props.busy === true;
1320
+ const danger = props.danger === true;
1321
+ // Claim a fresh z-index so this popup sits above any earlier popup.
1322
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
891
1323
  const title = props.title || "";
892
1324
  const description = props.description || "";
893
1325
  const warning = props.warning || "";
@@ -927,9 +1359,12 @@ window.__ModuleLoader__.load({
927
1359
 
928
1360
  return h("div", {
929
1361
  className: "sm-nativeDialogLayer sm-confirmDialog",
930
- style: props.anchor && typeof props.anchor.top === "number"
931
- ? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px", zIndex: 10001 }
932
- : null,
1362
+ style: {
1363
+ zIndex: zIndex || undefined,
1364
+ ...(props.anchor && typeof props.anchor.top === "number"
1365
+ ? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px" }
1366
+ : {})
1367
+ },
933
1368
  role: "presentation"
934
1369
  },
935
1370
 
@@ -955,7 +1390,7 @@ window.__ModuleLoader__.load({
955
1390
  }, cancelLabel),
956
1391
  h("button", {
957
1392
  type: "button",
958
- className: "sm-nativeDialogButton sm-nativeDialogConfirm",
1393
+ className: "sm-nativeDialogButton" + (danger ? " sm-nativeDialogDanger" : " sm-nativeDialogConfirm"),
959
1394
  disabled: busy,
960
1395
  onClick: () => { if (!busy) onConfirm(); }
961
1396
  }, confirmLabel)
@@ -976,6 +1411,8 @@ window.__ModuleLoader__.load({
976
1411
  const confirmLabel = props.confirmLabel || t("confirm.move.confirm");
977
1412
  const cancelLabel = props.cancelLabel || t("confirm.cancel");
978
1413
  const error = props.error || "";
1414
+ // Claim a fresh z-index so this popup sits above any earlier popup.
1415
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
979
1416
  const [selectedWorkspaceId, setSelectedWorkspaceId] = useState("");
980
1417
  // Esc handled at window level (issue #7) -- Enter stays on the
981
1418
  // layer because (a) it gates on selectedWorkspaceId which is set
@@ -1042,9 +1479,12 @@ window.__ModuleLoader__.load({
1042
1479
  };
1043
1480
  return h("div", {
1044
1481
  className: "sm-nativeDialogLayer sm-confirmDialog",
1045
- style: props.anchor && typeof props.anchor.top === "number"
1046
- ? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px", zIndex: 10001 }
1047
- : null,
1482
+ style: {
1483
+ zIndex: zIndex || undefined,
1484
+ ...(props.anchor && typeof props.anchor.top === "number"
1485
+ ? { position: "fixed", top: props.anchor.top + "px", left: props.anchor.left + "px" }
1486
+ : {})
1487
+ },
1048
1488
  role: "presentation",
1049
1489
  onKeyDown
1050
1490
  },
@@ -1087,6 +1527,8 @@ window.__ModuleLoader__.load({
1087
1527
  const fallbackPreset = props.currentPreset || "";
1088
1528
  const [scan, setScan] = useState(null);
1089
1529
  const [target, setTarget] = useState("");
1530
+ // Claim a fresh z-index so this popup sits above any earlier popup.
1531
+ const [zIndex] = useState(() => open ? nextDialogZ() : 0);
1090
1532
  useEffect(() => {
1091
1533
  if (!open || !sessionId) return;
1092
1534
  let alive = true;
@@ -1171,7 +1613,8 @@ window.__ModuleLoader__.load({
1171
1613
  );
1172
1614
  return h("div", {
1173
1615
  className: "sm-nativeDialogLayer sm-confirmDialog sm-migrateConfirmLayer",
1174
- role: "presentation"
1616
+ role: "presentation",
1617
+ style: { zIndex: zIndex || undefined }
1175
1618
  },
1176
1619
 
1177
1620
  h("section", {
@@ -1300,6 +1743,193 @@ window.__ModuleLoader__.load({
1300
1743
  const [migrating, setMigrating] = useState(false);
1301
1744
  const [migrateFor, setMigrateFor] = useState(null);
1302
1745
  const sessionMembershipKey = useMemo(() => [...list.ids].sort().join("\0"), [list.ids]);
1746
+ // ----- Bulk-action selection state -----
1747
+ const [selectedIds, setSelectedIds] = useState(() => new Set());
1748
+ const [lastClickedRank, setLastClickedRank] = useState(null);
1749
+ const [bulkPreview, setBulkPreview] = useState(null);
1750
+ const [bulkProgress, setBulkProgress] = useState(null);
1751
+ const [bulkResult, setBulkResult] = useState(null);
1752
+ const [bulkBusy, setBulkBusy] = useState(false);
1753
+ const [bulkTagDraft, setBulkTagDraft] = useState(null);
1754
+ // Select-mode is the user-controlled switch that reveals the row
1755
+ // checkboxes and the bulk action bar. It lives outside selectedIds
1756
+ // so the row checkboxes can be visible even before anything is
1757
+ // ticked (otherwise the BulkActionBar's own "select all" button
1758
+ // is unreachable).
1759
+ const [selectModeEnabled, setSelectModeEnabled] = useState(false);
1760
+ // Closing the panel resets everything bulk-related.
1761
+ useEffect(() => {
1762
+ if (!open) {
1763
+ setSelectedIds(new Set());
1764
+ setLastClickedRank(null);
1765
+ setBulkPreview(null);
1766
+ setBulkProgress(null);
1767
+ setBulkResult(null);
1768
+ setBulkTagDraft(null);
1769
+ setBulkPresetDraft(null);
1770
+ setBulkPresetOptions([]);
1771
+ setBulkBusy(false);
1772
+ setSelectModeEnabled(false);
1773
+ }
1774
+ }, [open]);
1775
+ const toggleSelectMode = () => {
1776
+ setSelectModeEnabled(value => {
1777
+ if (value) clearSelection();
1778
+ return !value;
1779
+ });
1780
+ };
1781
+ const toggleSelect = (id, shift, currentIndex) => {
1782
+ setSelectedIds(prev => {
1783
+ const next = new Set(prev);
1784
+ if (shift && lastClickedRank !== null && Number.isInteger(currentIndex)) {
1785
+ const lo = Math.min(lastClickedRank, currentIndex);
1786
+ const hi = Math.max(lastClickedRank, currentIndex);
1787
+ const allInRange = rows.slice(lo, hi + 1).every(r => next.has(r.id));
1788
+ for (let i = lo; i <= hi; i++) {
1789
+ const rowId = rows[i] && rows[i].id;
1790
+ if (!rowId) continue;
1791
+ if (allInRange) next.delete(rowId); else next.add(rowId);
1792
+ }
1793
+ } else {
1794
+ if (next.has(id)) next.delete(id); else next.add(id);
1795
+ }
1796
+ return next;
1797
+ });
1798
+ if (!shift) setLastClickedRank(Number.isInteger(currentIndex) ? currentIndex : null);
1799
+ };
1800
+ // Clear the selected-id set without touching selectModeEnabled.
1801
+ // Earlier this also reset selectModeEnabled to false, which made
1802
+ // clicking the "清空选择" toolbar button silently exit bulk
1803
+ // mode. Bulk mode is now exited exclusively by toggleSelectMode()
1804
+ // (the "退出批量处理" toggle button in the toolbar).
1805
+ const clearSelection = () => { setSelectedIds(new Set()); setLastClickedRank(null); };
1806
+ const selectAllFiltered = () => {
1807
+ if (rows.length > 200) { setError(t("bulk.error.tooMany")); return; }
1808
+ setSelectedIds(new Set(rows.map(r => r.id)));
1809
+ };
1810
+ const selectedSessions = useMemo(() => {
1811
+ const out = [];
1812
+ for (const id of selectedIds) {
1813
+ const s = list.byId[id];
1814
+ if (!s) continue;
1815
+ out.push({ id, displayTitle: s.blank ? t("panel.session.new") : (s.displayTitle || id) });
1816
+ }
1817
+ return out;
1818
+ }, [selectedIds, list.byId, t]);
1819
+ const executeBulk = async ({ action, payload, actionLabel, ids, sessions }) => {
1820
+ if (ids.length === 0) return;
1821
+ if (ids.length > 200) { setError(t("bulk.error.tooMany")); return; }
1822
+ const cancelController = { abort: () => {} };
1823
+ setBulkProgress({ action, actionLabel, done: 0, total: ids.length, completed: false, cancel: () => cancelController.abort() });
1824
+ setBulkBusy(true);
1825
+ let result;
1826
+ try {
1827
+ result = await runBatch({ action, sessionIds: ids, payload }, ({ done }) => {
1828
+ setBulkProgress(prev => prev ? { ...prev, done, completed: false } : prev);
1829
+ });
1830
+ } catch (error) {
1831
+ setBulkProgress(null);
1832
+ setBulkBusy(false);
1833
+ setError(error instanceof Error ? error.message : String(error));
1834
+ return;
1835
+ }
1836
+ const items = (result.items || []).map(item => {
1837
+ const session = sessions.find(s => s.id === item.sessionId);
1838
+ return { ...item, displayTitle: session ? session.displayTitle : (item.sessionId || "—") };
1839
+ });
1840
+ setBulkProgress(null);
1841
+ setBulkResult({ action, actionLabel, items });
1842
+ setBulkBusy(false);
1843
+ try { await annotationStore.load(true); } catch (_) { /* ignore */ }
1844
+ if (typeof props.onRefreshAfterBulk === "function") {
1845
+ try { await props.onRefreshAfterBulk(action); } catch (_) { /* ignore */ }
1846
+ }
1847
+ };
1848
+ const runBulk = ({ action, payload, actionLabel, needsConfirm }) => {
1849
+ if (bulkBusy) return;
1850
+ const ids = [...selectedIds];
1851
+ if (ids.length === 0) return;
1852
+ if (ids.length > 200) { setError(t("bulk.error.tooMany")); return; }
1853
+ const sessions = selectedSessions;
1854
+ const resolvedLabel = actionLabel || ({
1855
+ archive: t("bulk.archive"), unarchive: t("bulk.unarchive"), delete: t("bulk.delete"),
1856
+ move: t("bulk.move"), "preset-migrate": t("bulk.presetMigrate"),
1857
+ favorite: t("bulk.favorite"), unfavorite: t("bulk.unfavorite"),
1858
+ review: t("bulk.review"), unreview: t("bulk.unreview"),
1859
+ "add-tags": t("bulk.tagsAdd"), "remove-tags": t("bulk.tagsRemove"),
1860
+ "set-priority": t("bulk.priority")
1861
+ })[action] || action;
1862
+ if (needsConfirm) {
1863
+ setBulkPreview({ action, payload, actionLabel: resolvedLabel, sessions, warning: action === "delete" ? t("bulk.dialog.warning.delete") : "" });
1864
+ return;
1865
+ }
1866
+ void executeBulk({ action, payload, actionLabel: resolvedLabel, ids, sessions });
1867
+ };
1868
+ const retryFailedBulk = (failedIds, action, payload, actionLabel) => {
1869
+ setBulkResult(null);
1870
+ const filtered = (failedIds || []).filter(id => typeof id === "string" && id !== "");
1871
+ setSelectedIds(new Set(filtered));
1872
+ if (filtered.length === 0) return;
1873
+ const needsConfirm = action === "delete" || action === "move" || action === "preset-migrate" || action === "add-tags" || action === "remove-tags" || action === "set-priority";
1874
+ runBulk({ action, payload, actionLabel, needsConfirm });
1875
+ };
1876
+ const needsConfirmFor = (action) => action === "delete";
1877
+ const onBulkAction = (action, payload, forceNeedsConfirm) => {
1878
+ // Toggle: if a preview or result dialog is already open for this
1879
+ // same action, click the button again to close it.
1880
+ if (bulkPreview && bulkPreview.action === action) { setBulkPreview(null); return; }
1881
+ if (bulkResult && bulkResult.action === action) { setBulkResult(null); return; }
1882
+ const needsConfirm = forceNeedsConfirm === true || needsConfirmFor(action);
1883
+ runBulk({ action, payload, needsConfirm });
1884
+ };
1885
+ const onBulkSelectAll = () => { setSelectModeEnabled(true); selectAllFiltered(); };
1886
+ const onBulkClear = () => clearSelection();
1887
+ const onBulkTagsAdd = () => { if (bulkTagDraft && bulkTagDraft.mode === "add") { setBulkTagDraft(null); return; } setBulkTagDraft({ mode: "add" }); };
1888
+ const onBulkRemoveAllTags = () => onBulkAction("remove-tags", { tags: [] });
1889
+ const onBulkPriority = () => { if (bulkTagDraft && bulkTagDraft.mode === "priority") { setBulkTagDraft(null); return; } setBulkTagDraft({ mode: "priority" }); };
1890
+ const onBulkMove = () => { if (bulkTagDraft && bulkTagDraft.mode === "move") { setBulkTagDraft(null); return; } setBulkTagDraft({ mode: "move" }); };
1891
+ const onBulkTagApply = (tags) => {
1892
+ const mode = bulkTagDraft && bulkTagDraft.mode;
1893
+ setBulkTagDraft(null);
1894
+ if (mode === "add") onBulkAction("add-tags", { tags }, true);
1895
+ };
1896
+ const onBulkPriorityApply = (value) => {
1897
+ const priority = Number(value);
1898
+ setBulkTagDraft(null);
1899
+ if (Number.isInteger(priority) && priority >= 1 && priority <= 5) onBulkAction("set-priority", { priority }, true);
1900
+ };
1901
+ const onBulkMoveApply = (value) => {
1902
+ setBulkTagDraft(null);
1903
+ if (value) onBulkAction("move", { targetWorkspaceId: value }, true);
1904
+ };
1905
+ const [bulkPresetDraft, setBulkPresetDraft] = useState(null);
1906
+ const [bulkPresetOptions, setBulkPresetOptions] = useState([]);
1907
+ const onBulkPreset = () => {
1908
+ if (selectedIds.size === 0) return;
1909
+ // Toggle: close the dialog if it is already open.
1910
+ if (bulkPresetDraft) { setBulkPresetDraft(null); return; }
1911
+
1912
+ // Scan using the first selected session; /preset-scan returns
1913
+ // the global available preset list regardless of session, so any
1914
+ // selected id works.
1915
+ const firstId = [...selectedIds][0];
1916
+ setBulkPresetDraft({ mode: "preset" });
1917
+ setBulkPresetOptions([]);
1918
+ fetchApi("/preset-scan?sessionId=" + encodeURIComponent(firstId)).then((data) => {
1919
+ if (!data || !data.ok) return;
1920
+ const result = data.result || {};
1921
+ const presets = Array.isArray(result.availablePresets)
1922
+ ? result.availablePresets
1923
+ : (result.rows && Array.isArray(result.rows.availablePresets) ? result.rows.availablePresets : []);
1924
+ setBulkPresetOptions(presets.filter(p => typeof p === "string" && p.length > 0).map(p => ({ value: p, label: p })));
1925
+ }).catch(() => { /* surface empty list */ });
1926
+ };
1927
+ const onBulkPresetApply = (value) => {
1928
+ setBulkPresetDraft(null);
1929
+ if (value) onBulkAction("preset-migrate", { toPreset: value }, true);
1930
+ };
1931
+ const bulkMoveOptions = useMemo(() => workspaces.map(ws => ({ value: ws.id, label: ws.title || ws.name || ws.id })), [workspaces]);
1932
+ const bulkPriorityOptions = useMemo(() => [1, 2, 3, 4, 5].map(value => ({ value: String(value), label: t("marks.priority." + value) })), [t]);
1303
1933
  useEffect(() => {
1304
1934
  const timer = setInterval(() => setNow(Date.now()), 30000);
1305
1935
  return () => clearInterval(timer);
@@ -1382,6 +2012,25 @@ window.__ModuleLoader__.load({
1382
2012
  .sort((a, b) => a.time === b.time ? a.rank - b.rank : a.time === null ? 1 : b.time === null ? -1 : direction * (a.time - b.time))
1383
2013
  .map(item => item.session);
1384
2014
  }, [allRows, archived, filter, query, workspaceFilter, workspaceIndex, sortOrder, createdTimes, annotations.entries, onlyFavorite, onlyReview, tagFilter, priorityFilter, t]);
2015
+ // The set of ids that survive the current filter; bulk-selection
2016
+ // bookkeeping reads from this so it stays in sync with row visibility.
2017
+ const currentFilterIds = useMemo(() => rows.map(r => r.id), [rows]);
2018
+ const currentFilterRank = useMemo(() => {
2019
+ const m = new Map();
2020
+ rows.forEach((r, i) => m.set(r.id, i));
2021
+ return m;
2022
+ }, [rows]);
2023
+ // Drop ids from the selection that no longer match the current filter
2024
+ // (e.g. user narrowed the workspace filter or hid archived rows).
2025
+ useEffect(() => {
2026
+ const allowed = new Set(currentFilterIds);
2027
+ setSelectedIds(prev => {
2028
+ let changed = false;
2029
+ const next = new Set();
2030
+ for (const id of prev) if (allowed.has(id)) next.add(id); else changed = true;
2031
+ return changed ? next : prev;
2032
+ });
2033
+ }, [currentFilterIds]);
1385
2034
  const creationTimeUnavailable = sortOrder.startsWith("created-") && allRows.length > 0
1386
2035
  && !allRows.some(session => (sessionTime(session.createdAt) ?? sessionTime(createdTimes[session.id])) !== null);
1387
2036
  const hasViewChanges = onlyFavorite || onlyReview || tagFilter !== "all" || priorityFilter !== "all" || query !== "" || filter !== "all" || workspaceFilter !== "all" || sortOrder !== DEFAULT_SORT;
@@ -1405,16 +2054,29 @@ window.__ModuleLoader__.load({
1405
2054
  "aria-pressed": filter === id,
1406
2055
  onClick: () => setFilter(id)
1407
2056
  }, label);
1408
- const renderRow = (s) => {
2057
+ const renderRow = (s, rank) => {
1409
2058
  const isArchived = archived.has(s.id);
1410
2059
  const isCurrent = s.id === list.current;
1411
2060
  const busy = busyId === s.id;
1412
2061
  const mark = annotationOf(annotations, s.id);
2062
+ const isSelected = selectedIds.has(s.id);
2063
+ const checkbox = (selectedIds.size === 0 && !selectModeEnabled)
2064
+ ? null
2065
+ : h("label", { className: "sm-bulkRowCheckbox", onClick: (e) => e.stopPropagation() },
2066
+ h("input", {
2067
+ type: "checkbox",
2068
+ checked: isSelected,
2069
+ disabled: busy || bulkBusy,
2070
+ "aria-label": t("bulk.checkbox.aria", { title: s.displayTitle || s.id }),
2071
+ onChange: (e) => toggleSelect(s.id, e.shiftKey === true, rank)
2072
+ })
2073
+ );
1413
2074
  return h("div", {
1414
2075
  key: s.id,
1415
2076
  "data-session-id": s.id,
1416
- className: "sm-row" + (isCurrent ? " sm-rowCurrent" : "")
2077
+ className: "sm-row" + (isCurrent ? " sm-rowCurrent" : "") + (isSelected ? " sm-rowBulkSelected" : "")
1417
2078
  },
2079
+ h("div", { className: "sm-rowCheckboxSlot" + ((selectedIds.size > 0 || selectModeEnabled) ? " sm-rowCheckboxSlot-visible" : "") }, checkbox),
1418
2080
  h("div", { className: "sm-rowMain" },
1419
2081
  h("div", { className: "sm-rowTitle" },
1420
2082
  h("span", null, s.blank ? t("panel.session.new") : (s.displayTitle || s.id)),
@@ -1422,27 +2084,29 @@ window.__ModuleLoader__.load({
1422
2084
  s.running ? h("span", { className: "sm-badge sm-badgeRunning" }, t("panel.running")) : null,
1423
2085
  isCurrent ? h("span", { className: "sm-badge sm-badgeCurrent" }, t("panel.current")) : null
1424
2086
  ),
1425
- mark.priority !== null || mark.tags.length || mark.note ? h("div", { className: "sm-rowAnnotations" },
1426
- h("span", { className: "sm-priorityBadge sm-priority-" + (mark.priority ?? 3), title: t("marks.priority." + (mark.priority ?? 3)) }, "P" + (mark.priority ?? 3)),
1427
- mark.tags.slice(0, 2).map(tag => h("button", { key: tag, type: "button", className: "sm-tagChip", title: tag, onClick: () => setTagFilter("tag:" + tag.toLowerCase()) }, tag)),
1428
- mark.tags.length > 2 ? h("span", { title: mark.tags.join(", ") }, "+" + (mark.tags.length - 2)) : null,
1429
- mark.note ? h("button", { type: "button", className: "sm-noteBadge", title: t("marks.note.exists"), onClick: () => { setAnnotationAnchor(null); setAnnotationFor(current => current?.id === s.id ? null : s); } }, "✎") : null
1430
- ) : null,
1431
- h("div", { className: "sm-rowMeta" }, shortPath(s.cwd) + (s.cwd ? " · " : "") + formatRelative(s.updatedAt, now))
1432
- ),
1433
- h("div", { className: "sm-rowActions" },
1434
- h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => onOpen(s.id) }, t("row.open")),
1435
- h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => runRow(s.id, () => isArchived ? onUnarchive(s.id) : onArchive(s.id)) }, t(isArchived ? "row.unarchive" : "row.archive")),
1436
- annotationButtons(s.id, s.displayTitle || s.id, t, annotations, "sm-rowBtn", patch => runRow(s.id, () => annotationStore.save(s.id, patch)), () => { setAnnotationAnchor(null); setAnnotationFor(current => current?.id === s.id ? null : s); }, busy),
1437
- h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (moveFor && moveFor.id === s.id) setMoveFor(null); else setMoveFor(s); } }, t("row.move")),
1438
- h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (migrateFor && migrateFor.id === s.id) setMigrateFor(null); else setMigrateFor(s); } }, t("row.migrate")),
1439
- h("button", { type: "button", className: "sm-rowBtn sm-rowBtnDanger", disabled: busy, onClick: () => { if (confirmFor && confirmFor.id === s.id) setConfirmFor(null); else setConfirmFor(s); } }, t("row.delete"))
2087
+ h("div", { className: "sm-rowSecondary" },
2088
+ h("div", { className: "sm-rowBadges" },
2089
+ h("span", { className: "sm-priorityBadge sm-priority-" + (mark.priority ?? 3), title: t("marks.priority." + (mark.priority ?? 3)) }, "P" + (mark.priority ?? 3)),
2090
+ mark.tags.slice(0, 2).map(tag => h("button", { key: tag, type: "button", className: "sm-tagChip", title: tag, onClick: () => setTagFilter("tag:" + tag.toLowerCase()) }, tag)),
2091
+ mark.tags.length > 2 ? h("span", { title: mark.tags.join(", ") }, "+" + (mark.tags.length - 2)) : null,
2092
+ mark.note ? h("button", { type: "button", className: "sm-noteBadge", title: t("marks.note.exists"), onClick: () => { setAnnotationAnchor(null); setAnnotationFor(current => current?.id === s.id ? null : s); } }, "✎") : null
2093
+ ),
2094
+ h("div", { className: "sm-rowMeta" }, shortPath(s.cwd) + (s.cwd ? " · " : "") + formatRelative(s.updatedAt, now)),
2095
+ h("div", { className: "sm-rowActions" },
2096
+ h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (isArchived) { /* DSH refuses to surface an archived session as the current view: clicking "Open" on an archived row flashes an empty chat panel and bounces back to "no session selected". Unarchive first, then open. (issue #17.4) */ runRow(s.id, async () => { await onUnarchive(s.id); await onOpen(s.id); }); } else { onOpen(s.id); } } }, t("row.open")),
2097
+ h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => runRow(s.id, () => isArchived ? onUnarchive(s.id) : onArchive(s.id)) }, t(isArchived ? "row.unarchive" : "row.archive")),
2098
+ annotationButtons(s.id, s.displayTitle || s.id, t, annotations, "sm-rowBtn", patch => runRow(s.id, () => annotationStore.save(s.id, patch)), () => { setAnnotationAnchor(null); setAnnotationFor(current => current?.id === s.id ? null : s); }, busy),
2099
+ h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (moveFor && moveFor.id === s.id) setMoveFor(null); else setMoveFor(s); } }, t("row.move")),
2100
+ h("button", { type: "button", className: "sm-rowBtn", disabled: busy, onClick: () => { if (migrateFor && migrateFor.id === s.id) setMigrateFor(null); else setMigrateFor(s); } }, t("row.migrate")),
2101
+ h("button", { type: "button", className: "sm-rowBtn sm-rowBtnDanger", disabled: busy, onClick: () => { if (confirmFor && confirmFor.id === s.id) setConfirmFor(null); else setConfirmFor(s); } }, t("row.delete"))
2102
+ )
2103
+ )
1440
2104
  )
1441
2105
  );
1442
2106
  };
1443
2107
  const rowsList = rows.length === 0
1444
2108
  ? h("div", { className: "sm-empty" }, allRows.length > 0 ? t("panel.empty.filtered") : filter === "archived" ? t("panel.empty.archived") : t("panel.empty"))
1445
- : h("div", { className: "sm-list" }, rows.map(renderRow));
2109
+ : h("div", { className: "sm-list" }, rows.map((s, i) => renderRow(s, i)));
1446
2110
  const onPanelClose = () => {
1447
2111
  if (busyId !== null) return;
1448
2112
  setConfirmFor(null);
@@ -1495,13 +2159,30 @@ window.__ModuleLoader__.load({
1495
2159
  "aria-labelledby": "sm-panel-dialog-title"
1496
2160
  },
1497
2161
  h("div", { className: "sm-nativeDialogHeader" },
2162
+ h("a", {
2163
+ href: "https://github.com/hkkz9522/dsh-session-manager",
2164
+ target: "_blank",
2165
+ rel: "noopener noreferrer",
2166
+ className: "sm-panelHeaderGithub",
2167
+ title: t("panel.github.title"),
2168
+ "aria-label": t("panel.github.title")
2169
+ },
2170
+ h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": "true" },
2171
+ h("path", {
2172
+ fill: "currentColor",
2173
+ 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"
2174
+ })
2175
+ )
2176
+ ),
1498
2177
  h("h2", { id: "sm-panel-dialog-title", className: "sm-nativeDialogTitle" }, t("panel.title")),
1499
- h("button", {
1500
- type: "button",
1501
- className: "sm-panelHeaderClose",
1502
- disabled: busyId !== null || migrating,
1503
- onClick: onPanelClose
1504
- }, t("panel.close"))
2178
+ h("div", { className: "sm-nativeDialogHeaderActions" },
2179
+ h("button", {
2180
+ type: "button",
2181
+ className: "sm-panelHeaderClose",
2182
+ disabled: busyId !== null || migrating,
2183
+ onClick: onPanelClose
2184
+ }, t("panel.close"))
2185
+ )
1505
2186
  ),
1506
2187
  h("div", { className: "sm-panel" },
1507
2188
  h("div", { className: "sm-panelTools" },
@@ -1529,32 +2210,49 @@ window.__ModuleLoader__.load({
1529
2210
  className: "sm-viewSelect", "aria-label": t("panel.sort"), value: sortOrder,
1530
2211
  onChange: event => setSortOrder(event.target.value)
1531
2212
  }, SORT_ORDERS.map(value => h("option", { key: value, value, disabled: value === "priority" && !marksReady }, t(value === "priority" ? "marks.sort" : "panel.sort." + value))))
2213
+ ),
2214
+ h("label", { className: "sm-viewField" },
2215
+ h("span", null, t("marks.tags")),
2216
+ h("select", { className: "sm-viewSelect", "aria-label": t("marks.filter.tags"), value: tagFilter, disabled: !marksReady, onChange: e => setTagFilter(e.target.value) },
2217
+ h("option", { value: "all" }, t("marks.tags.all")), h("option", { value: "none" }, t("marks.tags.none")),
2218
+ availableTags.map(([key, tag]) => h("option", { key, value: "tag:" + key }, tag)),
2219
+ tagFilter.startsWith("tag:") && !availableTags.some(([key]) => "tag:" + key === tagFilter) ? h("option", { value: tagFilter }, t("marks.tags.missing", { tag: tagFilter.slice(4) })) : null
2220
+ )
2221
+ ),
2222
+ h("label", { className: "sm-viewField" },
2223
+ h("span", null, t("marks.priority")),
2224
+ h("select", { className: "sm-viewSelect", "aria-label": t("marks.filter.priority"), value: priorityFilter, disabled: !marksReady, onChange: e => setPriorityFilter(e.target.value) },
2225
+ h("option", { value: "all" }, t("marks.priority.all")),
2226
+ [1,2,3,4,5].map(value => h("option", { key: value, value: String(value) }, t("marks.priority." + value)))
2227
+ )
1532
2228
  )
1533
2229
  ),
1534
2230
  h("div", { className: "sm-markFilters" },
1535
- h("button", { type: "button", className: "sm-filterBtn sm-markFilterToggle" + (onlyFavorite ? " sm-on" : ""), "aria-label": t("marks.filter.favorite.aria"), "aria-pressed": onlyFavorite, disabled: !marksReady, onClick: () => setOnlyFavorite(value => !value) }, "★ " + t("marks.filter.favorite")),
1536
- h("button", { type: "button", className: "sm-filterBtn sm-markFilterToggle" + (onlyReview ? " sm-on" : ""), "aria-label": t("marks.filter.review.aria"), "aria-pressed": onlyReview, disabled: !marksReady, onClick: () => setOnlyReview(value => !value) }, "◷ " + t("marks.filter.review")),
1537
- h("label", { className: "sm-viewField" }, h("span", null, t("marks.tags")), h("select", { className: "sm-viewSelect", "aria-label": t("marks.filter.tags"), value: tagFilter, disabled: !marksReady, onChange: e => setTagFilter(e.target.value) },
1538
- h("option", { value: "all" }, t("marks.tags.all")), h("option", { value: "none" }, t("marks.tags.none")),
1539
- availableTags.map(([key, tag]) => h("option", { key, value: "tag:" + key }, tag)),
1540
- tagFilter.startsWith("tag:") && !availableTags.some(([key]) => "tag:" + key === tagFilter) ? h("option", { value: tagFilter }, t("marks.tags.missing", { tag: tagFilter.slice(4) })) : null
1541
- )),
1542
- h("label", { className: "sm-viewField" }, h("span", null, t("marks.priority")), h("select", { className: "sm-viewSelect", "aria-label": t("marks.filter.priority"), value: priorityFilter, disabled: !marksReady, onChange: e => setPriorityFilter(e.target.value) },
1543
- h("option", { value: "all" }, t("marks.priority.all")),
1544
- [1,2,3,4,5].map(value => h("option", { key: value, value: String(value) }, t("marks.priority." + value)))
1545
- ))
1546
- ),
1547
- h("div", { className: "sm-filterBar" },
1548
- h("div", { className: "sm-filter" },
2231
+ h("div", { className: "sm-markFiltersGroup" },
1549
2232
  filterBtn("all", t("panel.filter.all")),
1550
2233
  filterBtn("active", t("panel.filter.active")),
1551
- filterBtn("archived", t("panel.filter.archived"))
2234
+ filterBtn("archived", t("panel.filter.archived")),
2235
+ h("button", { type: "button", className: "sm-filterBtn sm-markFilterToggle" + (onlyFavorite ? " sm-on" : ""), "aria-label": t("marks.filter.favorite.aria"), "aria-pressed": onlyFavorite, disabled: !marksReady, onClick: () => setOnlyFavorite(value => !value) }, "★ " + t("marks.filter.favorite")),
2236
+ h("button", { type: "button", className: "sm-filterBtn sm-markFilterToggle" + (onlyReview ? " sm-on" : ""), "aria-label": t("marks.filter.review.aria"), "aria-pressed": onlyReview, disabled: !marksReady, onClick: () => setOnlyReview(value => !value) }, "◷ " + t("marks.filter.review"))
1552
2237
  ),
1553
- h("div", { className: "sm-resultSummary" },
1554
- h("span", { role: "status", "aria-live": "polite", "aria-atomic": "true" }, t("panel.count", { shown: rows.length, total: allRows.length })),
1555
- hasViewChanges ? h("button", { type: "button", className: "sm-resetFilters", onClick: resetView }, t("panel.reset")) : null
2238
+ h("div", { className: "sm-markFiltersSpacer" }),
2239
+ h("div", { className: "sm-markFiltersGroup" },
2240
+ h("button", {
2241
+ type: "button",
2242
+ className: "sm-filterBtn" + (selectModeEnabled ? " sm-on" : ""),
2243
+ disabled: busyId !== null || migrating,
2244
+ "aria-pressed": selectModeEnabled,
2245
+ "aria-label": t(selectModeEnabled ? "panel.selectMode.active.aria" : "panel.selectMode.aria"),
2246
+ onClick: toggleSelectMode
2247
+ }, selectModeEnabled ? t("panel.selectMode.exit") : t("panel.selectMode")),
2248
+ h("button", { type: "button", className: "sm-filterBtn sm-bulkSelectBtn", disabled: rows.length === 0 || rows.length === selectedIds.size || bulkBusy, onClick: onBulkSelectAll, "aria-label": t("bulk.selectAllFiltered") }, t("bulk.selectAllFiltered")),
2249
+ h("button", { type: "button", className: "sm-filterBtn sm-bulkSelectBtn", disabled: selectedIds.size === 0 || bulkBusy, onClick: onBulkClear, "aria-label": t("bulk.clear") }, t("bulk.clear"))
1556
2250
  )
1557
2251
  ),
2252
+ h("div", { className: "sm-resultSummary" },
2253
+ h("span", { role: "status", "aria-live": "polite", "aria-atomic": "true" }, t("panel.count", { shown: rows.length, total: allRows.length })),
2254
+ hasViewChanges ? h("button", { type: "button", className: "sm-resetFilters", onClick: resetView }, t("panel.reset")) : null
2255
+ ),
1558
2256
  annotations.loading && !annotations.loaded ? h("div", { className: "sm-workspaceNotice", role: "status" }, t("marks.loading")) : null,
1559
2257
  annotations.error ? h("div", { className: "sm-workspaceNotice", role: "alert" }, t("marks.error", { message: annotations.error }), h("button", { type: "button", className: "sm-resetFilters", onClick: () => annotationStore.load(true) }, t("marks.retry"))) : null,
1560
2258
  creationTimeUnavailable && !workspacesLoading ? h("div", { className: "sm-workspaceNotice", role: "status" }, t("panel.sort.unavailable")) : null,
@@ -1565,6 +2263,15 @@ window.__ModuleLoader__.load({
1565
2263
  ) : null
1566
2264
  ),
1567
2265
  error ? h("div", { className: "sm-error", role: "alert" }, t("error.operation", { message: error })) : null,
2266
+ (selectedIds.size > 0 || selectModeEnabled) ? h(BulkActionBar, {
2267
+ t, selectedCount: selectedIds.size, filteredCount: rows.length, busy: bulkBusy,
2268
+ onSelectAll: onBulkSelectAll, onClear: onBulkClear,
2269
+ onAction: onBulkAction,
2270
+ onOpenTagDialog: onBulkTagsAdd,
2271
+ onOpenPriorityDialog: onBulkPriority,
2272
+ onOpenMoveDialog: onBulkMove,
2273
+ onOpenPresetDialog: onBulkPreset
2274
+ }) : null,
1568
2275
  rowsList
1569
2276
  )
1570
2277
  )
@@ -1574,11 +2281,12 @@ window.__ModuleLoader__.load({
1574
2281
  open: true,
1575
2282
  onClose: () => { if (busyId === null) setConfirmFor(null); },
1576
2283
  title: t("confirm.delete.title"),
1577
- description: t("confirm.delete.desc", { title: confirmFor.displayTitle || confirmFor.id }),
2284
+ description: (confirmFor.displayTitle && t("confirm.delete.desc", { title: confirmFor.displayTitle })) || "",
1578
2285
  warning: confirmFor.running ? t("confirm.delete.running") : null,
1579
2286
  confirmLabel: t("confirm.delete.confirm"),
1580
2287
  cancelLabel: t("confirm.cancel"),
1581
2288
  busy: busyId !== null,
2289
+ danger: true,
1582
2290
  onCancel: () => { if (busyId === null) setConfirmFor(null); },
1583
2291
  onConfirm: () => {
1584
2292
  const target = confirmFor;
@@ -1589,9 +2297,12 @@ window.__ModuleLoader__.load({
1589
2297
  const moveDialog = moveFor === null ? null : h(MoveDialog, {
1590
2298
  open: true,
1591
2299
  onClose: () => { if (busyId === null) setMoveFor(null); },
2300
+ /* Forward the locale function so the inner labels (e.g. confirm.move.select, confirm.move.empty) get translated; without this they render as raw keys. (issue #17.2) */
2301
+ t: t,
1592
2302
  title: t("confirm.move.title"),
1593
- description: t("confirm.move.desc", { title: moveFor.displayTitle || moveFor.id }),
1594
- sessionTitle: moveFor.displayTitle || moveFor.id,
2303
+ /* Show the real session display title only (no raw id); falls back to empty so the description
2304
+ stays blank when the name is unknown instead of leaking the id into the dialog body. (issue #17.2 follow-up) */
2305
+ description: (moveFor.displayTitle && t("confirm.move.desc", { title: moveFor.displayTitle })) || "",
1595
2306
  currentWorkspaceId: moveFor.workspaceId,
1596
2307
  workspaces: workspaces,
1597
2308
  confirmLabel: t("confirm.move.confirm"),
@@ -1639,13 +2350,115 @@ window.__ModuleLoader__.load({
1639
2350
  // panel area entirely (the inner modal is centered).
1640
2351
  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;
1641
2352
  // Render panel + ALL inner dialogs together; z-index ordering puts
1642
- // whichever dialog is open above the panel.
1643
- return h(React.Fragment, null, panelModal, annotationDialog, moveDialog, deleteDialog, migrateDialog);
2353
+ // whichever dialog is open above the panel. The bulk dialogs are
2354
+ // appended here so destructive actions get a preview, in-flight
2355
+ // operations get a progress bar, and finished runs show per-id
2356
+ // success / failed / skipped counts.
2357
+ // Map bulk action -> 4-char confirm label ("确认<verb>"). Delete is
2358
+ // the only destructive action routed through BatchPreviewDialog, so it
2359
+ // also gets danger: true so the confirm button paints red.
2360
+ const bulkConfirmLabelFor = (action) => ({
2361
+ archive: t("bulk.confirm.archive"),
2362
+ unarchive: t("bulk.confirm.unarchive"),
2363
+ favorite: t("bulk.confirm.favorite"),
2364
+ unfavorite: t("bulk.confirm.unfavorite"),
2365
+ review: t("bulk.confirm.review"),
2366
+ unreview: t("bulk.confirm.unreview"),
2367
+ "add-tags": t("bulk.confirm.tagsAdd"),
2368
+ "remove-tags": t("bulk.confirm.tagsRemove"),
2369
+ "set-priority": t("bulk.confirm.priority"),
2370
+ move: t("bulk.confirm.move"),
2371
+ "preset-migrate": t("bulk.confirm.presetMigrate"),
2372
+ delete: t("bulk.confirm.delete")
2373
+ })[action] || t("bulk.dialog.execute");
2374
+ const bulkPreviewDialog = bulkPreview ? h(BatchPreviewDialog, {
2375
+ open: true, t, actionLabel: bulkPreview.actionLabel,
2376
+ sessions: bulkPreview.sessions, warning: bulkPreview.warning,
2377
+ confirmLabel: bulkConfirmLabelFor(bulkPreview.action),
2378
+ danger: bulkPreview.action === "delete",
2379
+ onCancel: () => setBulkPreview(null),
2380
+ onExecute: () => {
2381
+ const preview = bulkPreview;
2382
+ setBulkPreview(null);
2383
+ void executeBulk({
2384
+ action: preview.action, payload: preview.payload,
2385
+ actionLabel: preview.actionLabel,
2386
+ ids: preview.sessions.map(s => s.id), sessions: preview.sessions
2387
+ });
2388
+ }
2389
+ }) : null;
2390
+ const bulkProgressDialog = bulkProgress ? h(BatchProgressDialog, {
2391
+ open: true, t, actionLabel: bulkProgress.actionLabel,
2392
+ done: bulkProgress.done, total: bulkProgress.total, completed: false,
2393
+ onCancel: () => { try { bulkProgress.cancel(); } catch (_) {} }
2394
+ }) : null;
2395
+ const bulkResultDialog = bulkResult ? h(BatchResultDialog, {
2396
+ open: true, t, actionLabel: bulkResult.actionLabel,
2397
+ items: bulkResult.items,
2398
+ onClose: () => { setBulkResult(null); clearSelection(); },
2399
+ onRetry: (failedIds) => {
2400
+ const last = bulkResult;
2401
+ setBulkResult(null);
2402
+ retryFailedBulk(failedIds, last.action, last.payload, last.actionLabel);
2403
+ }
2404
+ }) : null;
2405
+ const bulkTagDialog = bulkTagDraft && bulkTagDraft.mode === "add" ? h(BulkTagInputDialog, {
2406
+ open: true, t, title: t("bulk.tagsAdd"),
2407
+ confirmLabel: t("bulk.confirm.tagsAdd"),
2408
+ onCancel: () => setBulkTagDraft(null),
2409
+ onApply: onBulkTagApply
2410
+ }) : null;
2411
+ const bulkPriorityDialog = bulkTagDraft && bulkTagDraft.mode === "priority" ? h(BulkChoiceDialog, {
2412
+ open: true, t, title: t("bulk.priority"),
2413
+ description: t("bulk.dialog.summary", { count: selectedIds.size, action: t("bulk.priority") }),
2414
+ options: bulkPriorityOptions,
2415
+ confirmLabel: t("bulk.confirm.priority"),
2416
+ onCancel: () => setBulkTagDraft(null),
2417
+ onApply: onBulkPriorityApply
2418
+ }) : null;
2419
+ const bulkMoveDialog = bulkTagDraft && bulkTagDraft.mode === "move" ? h(BulkChoiceDialog, {
2420
+ open: true, t, title: t("bulk.move"),
2421
+ description: t("bulk.dialog.summary", { count: selectedIds.size, action: t("bulk.move") }),
2422
+ options: bulkMoveOptions,
2423
+ confirmLabel: t("bulk.confirm.move"),
2424
+ onCancel: () => setBulkTagDraft(null),
2425
+ onApply: onBulkMoveApply
2426
+ }) : null;
2427
+ const bulkPresetDialog = bulkPresetDraft ? h(BulkChoiceDialog, {
2428
+ open: true, t, title: t("bulk.presetMigrate"),
2429
+ description: t("bulk.dialog.summary", { count: selectedIds.size, action: t("bulk.presetMigrate") }),
2430
+ options: bulkPresetOptions,
2431
+ confirmLabel: t("bulk.confirm.presetMigrate"),
2432
+ onCancel: () => setBulkPresetDraft(null),
2433
+ onApply: onBulkPresetApply
2434
+ }) : null;
2435
+ // Bulk dialogs sit at z-index 10002 (set in CSS) so they overlap the
2436
+ return h(React.Fragment, null, panelModal, annotationDialog, moveDialog, deleteDialog, migrateDialog, bulkPreviewDialog, bulkProgressDialog, bulkResultDialog, bulkTagDialog, bulkPriorityDialog, bulkMoveDialog, bulkPresetDialog);
1644
2437
  }
1645
2438
 
1646
2439
  function HeaderAction(props) {
1647
2440
  const t = props.t;
1648
2441
  const sessionId = props.sessionId;
2442
+ const useSessions = props.useSessions;
2443
+ // Resolve the friendly display title once per render. Earlier this
2444
+ // lived inside a realDisplayTitle() helper that was called from the
2445
+ // setMoveFor / setConfirmFor / setAnnotationFor onClick handlers,
2446
+ // which invoked useSessions() outside React's render phase. The
2447
+ // slot framework's useSyncExternalStore subscriber reads .current off
2448
+ // the active fiber and that handle goes null during an event
2449
+ // dispatch, so the hook threw "Cannot read properties of null
2450
+ // (reading 'current')". The surrounding SlotErrorBoundary then
2451
+ // replaced the whole conversation.session.header.actions slot with
2452
+ // <div data-slot-error="…" />, taking every title-bar button down
2453
+ // and leaving 移动 / 删除 with no click target. Compute the value
2454
+ // here once per render, then reuse the captured const in handlers.
2455
+ const displayTitle = (() => {
2456
+ const fallback = props.displayTitle;
2457
+ if (fallback && fallback !== sessionId) return fallback;
2458
+ const list = useSessions ? useSessions((s) => s) : null;
2459
+ const found = list ? list.byId[sessionId] : null;
2460
+ return (found && found.displayTitle) || "";
2461
+ })();
1649
2462
  const annotations = useAnnotations();
1650
2463
  const [annotationFor, setAnnotationFor] = useState(null);
1651
2464
  const [annotationAnchor, setAnnotationAnchor] = useState(null);
@@ -1682,12 +2495,15 @@ window.__ModuleLoader__.load({
1682
2495
  });
1683
2496
  return () => { alive = false; };
1684
2497
  }, [moveFor ? (moveFor.id || "x") : null]);
2498
+ // All three HeaderAction dialogs (annotation / move / delete)
2499
+ // share a 360px width. Anchor the dialog's right edge to the
2500
+ // trigger button's right edge, with 8px screen-edge padding.
1685
2501
  const anchorFor = (el) => {
1686
2502
  if (!el || typeof el.getBoundingClientRect !== "function") return null;
1687
2503
  const r = el.getBoundingClientRect();
1688
2504
  return {
1689
2505
  top: Math.round(r.top + r.height + 6),
1690
- left: Math.max(8, Math.round(r.right - 320))
2506
+ left: Math.max(8, Math.round(r.right - 360))
1691
2507
  };
1692
2508
  };
1693
2509
  const archiveLabelKey = isArchived ? "row.unarchive" : "row.archive";
@@ -1705,49 +2521,56 @@ window.__ModuleLoader__.load({
1705
2521
 
1706
2522
  annotationButtons(sessionId, props.displayTitle || sessionId, t, annotations, "sm-headerBtn", patch => runWithAlert("annotations", () => annotationStore.save(sessionId, patch)), (e) => {
1707
2523
  const nextOpen = !(annotationFor && annotationFor.id === sessionId);
1708
- setMoveFor(null); setConfirmFor(null);
2524
+ // Do NOT clear moveFor / confirmFor here: the z-index counter
2525
+ // (see nextDialogZ) decides stacking, so opening the annotation
2526
+ // popup must not silently dismiss a previously opened move or
2527
+ // delete popup on the same session.
1709
2528
  if (nextOpen && e && e.currentTarget) {
1710
2529
  const r = e.currentTarget.getBoundingClientRect();
1711
- setAnnotationAnchor({ top: Math.round(r.top + r.height + 6), left: Math.max(8, Math.round(r.right - 460)) });
2530
+ setAnnotationAnchor({ top: Math.round(r.top + r.height + 6), left: Math.max(8, Math.round(r.right - 360)) });
1712
2531
  } else setAnnotationAnchor(null);
1713
- setAnnotationFor(current => current?.id === sessionId ? null : { id: sessionId, displayTitle: props.displayTitle || sessionId });
2532
+ setAnnotationFor(current => current?.id === sessionId ? null : { id: sessionId, displayTitle });
1714
2533
  }, false, annotationFor?.id === sessionId),
1715
2534
  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,
1716
2535
 
1717
2536
  h("button", {
1718
2537
  type: "button",
1719
2538
  className: "sm-headerBtn" + (moveFor ? " sm-headerBtnActive" : ""),
1720
- "aria-label": t ? t("row.move") : "移动到工作区",
2539
+ "aria-label": t ? t("row.move") : "移动至工作区",
1721
2540
  onClick: (e) => {
1722
2541
  e.stopPropagation();
1723
- setConfirmFor(null);
1724
- setConfirmAnchor(null);
2542
+ // Do NOT clear confirmFor / confirmAnchor here: opening the
2543
+ // move popup must not silently dismiss a previously opened
2544
+ // delete popup on the same session. The z-index counter
2545
+ // (see nextDialogZ) handles stacking.
1725
2546
  if (moveFor && moveFor.id === sessionId) {
1726
2547
  setMoveFor(null);
1727
2548
  setMoveAnchor(null);
1728
2549
  } else {
1729
2550
  setMoveAnchor(anchorFor(e.currentTarget));
1730
- setMoveFor({ id: sessionId, displayTitle: sessionId, workspaceId: "" });
2551
+ setMoveFor({ id: sessionId, displayTitle, workspaceId: "" });
1731
2552
  }
1732
2553
  }
1733
- }, t ? t("row.move") : "移动"),
2554
+ }, t ? t("row.move") : "移动至工作区"),
1734
2555
  h("button", {
1735
2556
  type: "button",
1736
2557
  className: "sm-headerBtn sm-headerBtnDanger" + (confirmFor ? " sm-headerBtnActive" : ""),
1737
2558
  "aria-label": t ? t("row.delete") : "删除会话",
1738
2559
  onClick: (e) => {
1739
2560
  e.stopPropagation();
1740
- setMoveFor(null);
1741
- setMoveAnchor(null);
2561
+ // Do NOT clear moveFor / moveAnchor here: opening the delete
2562
+ // popup must not silently dismiss a previously opened move
2563
+ // popup on the same session. The z-index counter (see
2564
+ // nextDialogZ) handles stacking.
1742
2565
  if (confirmFor && confirmFor.id === sessionId) {
1743
2566
  setConfirmFor(null);
1744
2567
  setConfirmAnchor(null);
1745
2568
  } else {
1746
2569
  setConfirmAnchor(anchorFor(e.currentTarget));
1747
- setConfirmFor({ id: sessionId, displayTitle: sessionId });
2570
+ setConfirmFor({ id: sessionId, displayTitle });
1748
2571
  }
1749
2572
  }
1750
- }, t ? t("row.delete") : "删除")
2573
+ }, t ? t("row.delete") : "删除会话")
1751
2574
  ),
1752
2575
  confirmFor === null ? null : h(ConfirmDialog, {
1753
2576
  open: true,
@@ -1760,10 +2583,11 @@ window.__ModuleLoader__.load({
1760
2583
  runWithAlert("delete", () => onRemove(target.id));
1761
2584
  },
1762
2585
  title: t ? t("confirm.delete.title") : "删除会话",
1763
- description: (confirmFor && (t ? t("confirm.delete.desc", { title: confirmFor.displayTitle || confirmFor.id }) : ("将删除 " + (confirmFor.displayTitle || confirmFor.id)))) || "",
2586
+ description: (confirmFor && confirmFor.displayTitle && (t ? t("confirm.delete.desc", { title: confirmFor.displayTitle }) : ("将删除 " + confirmFor.displayTitle))) || "",
1764
2587
  confirmLabel: t ? t("confirm.delete.confirm") : "确认删除",
1765
2588
  cancelLabel: t ? t("confirm.cancel") : "取消",
1766
- busy: false
2589
+ busy: false,
2590
+ danger: true
1767
2591
  }),
1768
2592
  moveFor === null ? null : h(MoveDialog, {
1769
2593
  open: true,
@@ -1774,8 +2598,7 @@ window.__ModuleLoader__.load({
1774
2598
  onClose: () => { setMoveFor(null); setMoveAnchor(null); },
1775
2599
  onCancel: () => { setMoveFor(null); setMoveAnchor(null); },
1776
2600
  title: t ? t("confirm.move.title") : "移动会话到工作区",
1777
- description: (moveFor && (t ? t("confirm.move.desc", { title: moveFor.displayTitle || moveFor.id }) : "")) || "",
1778
- sessionTitle: moveFor.displayTitle || moveFor.id,
2601
+ description: (moveFor && moveFor.displayTitle && (t ? t("confirm.move.desc", { title: moveFor.displayTitle }) : "")) || "",
1779
2602
  confirmLabel: t ? t("confirm.move.confirm") : "确认移动",
1780
2603
  cancelLabel: t ? t("confirm.cancel") : "取消",
1781
2604
  busy: false,
@@ -1801,6 +2624,12 @@ window.__ModuleLoader__.load({
1801
2624
  useSessions: this.props.useSessions,
1802
2625
  useWorkspaces: this.props.useWorkspaces,
1803
2626
  t: this.props.t,
2627
+ // Bulk operations need to refresh ctx.sessions.list and the
2628
+ // workspace projection after /batch mutates disk state so the
2629
+ // panel reflects the new layout.
2630
+ onRefreshAfterBulk: async () => {
2631
+ try { await this.props.props.onBulkRefresh(); } catch (_) { /* ignore */ }
2632
+ },
1804
2633
  onOpen: (id) => { try { this.props.panelStore.set(false); this.props.props.onOpen(id); } catch (_) {} },
1805
2634
  onArchive: this.props.props.onArchive,
1806
2635
  onUnarchive: this.props.props.onUnarchive,
@@ -1814,19 +2643,31 @@ window.__ModuleLoader__.load({
1814
2643
  function FooterAction(props) {
1815
2644
  const t = props.t;
1816
2645
  const onOpenPanel = props.onOpenPanel;
2646
+ // props.wide comes from SidebarRoot via SidebarFooterActionOwnerProps.
2647
+ // When the sidebar is collapsed into the 56px rail, render a 36x36
2648
+ // icon-only button so the Chinese label does not word-wrap vertically
2649
+ // (issue #16); when expanded, render a full-width row with icon +
2650
+ // label, left-aligned to match neighbouring footer entries.
2651
+ const wide = props.wide === true;
1817
2652
  const open = useSyncExternalStore(
1818
2653
  (cb) => panelStore.subscribe(cb),
1819
2654
  () => panelStore.getSnapshot(),
1820
2655
  () => panelStore.getSnapshot()
1821
2656
  );
1822
- return h("div", { className: "sm-footer" },
1823
- h("button", { type: "button", className: "sm-footerBtn", "aria-label": t("footer.aria"), onClick: () => onOpenPanel() },
1824
- h(P.IconArchiveOutline20, { size: 14 }),
1825
- h("span", null, t("footer.label"))
2657
+ const footerClass = "sm-footer" + (wide ? " sm-footer-wide" : " sm-footer-rail");
2658
+ const btnClass = "sm-footerBtn" + (wide ? " sm-footerBtn-wide" : " sm-footerBtn-rail");
2659
+ const btnLabel = t("footer.aria");
2660
+ return h("div", { className: footerClass },
2661
+ h("button", {
2662
+ type: "button",
2663
+ className: btnClass,
2664
+ "aria-label": btnLabel,
2665
+ title: wide ? undefined : btnLabel,
2666
+ onClick: () => onOpenPanel()
2667
+ },
2668
+ h(P.IconArchiveOutlineRegular, { size: wide ? 14 : 18 }),
2669
+ wide ? h("span", { className: "sm-footerBtn-label" }, t("footer.label")) : null
1826
2670
  ),
1827
- // The panel is mounted ONLY while the user has the modal open. When
1828
- // The wrap guards against an in-panel crash by returning null on its
1829
- // own error boundary rather than letting it bubble up to the slot.
1830
2671
  open ? h(SafePanel, {
1831
2672
  panelStore: panelStore,
1832
2673
  props: props,
@@ -1914,9 +2755,8 @@ window.__ModuleLoader__.load({
1914
2755
  if (!response.ok) throw new Error(response.error || "preset migration failed");
1915
2756
  // The current DSH client session store derives agentPreset from
1916
2757
  // the refreshed projection; older builds exposed noteAgentPreset,
1917
- // but calling it unconditionally breaks migration on newer builds.
2758
+ onOpenPanel: () => panelStore.toggle(),
1918
2759
  await ctx.sessions.refresh();
1919
- if (wasCurrent) ctx.sessions.open(sessionId);
1920
2760
  // The migrate rewrite happens in-process on the live session, so
1921
2761
  // we just need a delayed baseline to let the api-gateway's projection
1922
2762
  // settle before re-activating the in-memory session.
@@ -1926,7 +2766,8 @@ window.__ModuleLoader__.load({
1926
2766
  }).catch(() => {});
1927
2767
  }, 250);
1928
2768
  },
1929
- onOpenPanel: () => panelStore.toggle()
2769
+ onOpenPanel: () => panelStore.toggle(),
2770
+ onBulkRefresh: async () => { try { await ctx.sessions.refresh(); } catch (_) { /* ignore */ } try { await ctx.workspaces.refresh(); } catch (_) { /* ignore */ } },
1930
2771
  });
1931
2772
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
1932
2773
  name: "conversation.session.header.actions",
@@ -1950,4 +2791,4 @@ window.__ModuleLoader__.load({
1950
2791
  exports.inject = inject;
1951
2792
  return module.exports;
1952
2793
  }
1953
- });
2794
+ });