dsh-session-curator 0.2.0 → 0.3.0

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
@@ -41,16 +41,12 @@ __export(index_exports, {
41
41
  GroupHeader: () => GroupHeader,
42
42
  NS: () => NS,
43
43
  SessionRow: () => SessionRow,
44
- TRASH_DAYS_MAX: () => TRASH_DAYS_MAX,
45
- TRASH_DAYS_MIN: () => TRASH_DAYS_MIN,
46
- TRASH_RETENTION_DAYS: () => TRASH_RETENTION_DAYS,
47
44
  UsagePanel: () => UsagePanel,
48
45
  apply: () => apply,
49
46
  cachedSnapshot: () => cachedSnapshot,
50
47
  collapseKey: () => collapseKey,
51
48
  describeSession: () => describeSession,
52
49
  dialogStore: () => dialogStore,
53
- effectiveTrashDays: () => effectiveTrashDays,
54
50
  filterRows: () => filterRows,
55
51
  forgetDetails: () => forgetDetails,
56
52
  forgetSnapshot: () => forgetSnapshot,
@@ -61,14 +57,12 @@ __export(index_exports, {
61
57
  isGroupCollapsed: () => isGroupCollapsed,
62
58
  matchesFilter: () => matchesFilter,
63
59
  matchesSearch: () => matchesSearch,
64
- parseCustomDays: () => parseCustomDays,
60
+ mutationStore: () => mutationStore,
65
61
  sectionIdFor: () => sectionIdFor,
66
62
  showNotice: () => showNotice,
67
- showTrashUndo: () => showTrashUndo,
68
63
  sortRows: () => sortRows,
69
64
  toastStore: () => toastStore,
70
65
  toggledCollapse: () => toggledCollapse,
71
- undoTrash: () => undoTrash,
72
66
  workspaceSummaries: () => workspaceSummaries
73
67
  });
74
68
  module.exports = __toCommonJS(index_exports);
@@ -190,79 +184,38 @@ function useStore(store) {
190
184
  // src/client/dialog.ts
191
185
  var dialogStore = createStore(null);
192
186
  var mutationStore = createStore(0);
193
- function requestTrash(ids, title) {
194
- dialogStore.set({ kind: "trash", ids, title });
195
- }
196
- function requestPurge(ids, title) {
197
- dialogStore.set({ kind: "purge", ids, title });
198
- }
199
- function requestPurgeOld(days, count, bytes) {
200
- dialogStore.set({ kind: "purge-old", ids: [], title: "", days, count, bytes });
201
- }
202
-
203
- // src/client/format.ts
204
- function formatBytes(bytes) {
205
- if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes <= 0) return "0 B";
206
- const units = ["B", "KB", "MB", "GB"];
207
- let value = bytes;
208
- let unit = 0;
209
- while (value >= 1024 && unit < units.length - 1) {
210
- value /= 1024;
211
- unit += 1;
212
- }
213
- return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
214
- }
215
- function formatTime(ms) {
216
- if (typeof ms !== "number" || ms <= 0) return "";
217
- try {
218
- return new Date(ms).toLocaleString();
219
- } catch {
220
- return "";
221
- }
222
- }
223
- function baseName(path) {
224
- if (typeof path !== "string" || path === "") return "";
225
- const parts = path.split(/[\\/]/).filter((part) => part !== "");
226
- return parts.length > 0 ? parts[parts.length - 1] : path;
187
+ function requestDelete(ids, title) {
188
+ dialogStore.set({ kind: "delete", ids, title });
227
189
  }
228
190
 
229
191
  // src/client/i18n.ts
230
192
  var NS = "session-curator";
231
193
  var zh = {
232
194
  sectionLabel: "会话管理",
233
- menuDelete: "删除会话",
195
+ deleteSession: "删除会话",
234
196
  titleArchived: "已归档",
235
197
  titlePinned: "已置顶",
236
- titleTrash: "回收站",
237
198
  refresh: "刷新",
238
199
  loading: "读取中…",
239
200
  retry: "重试",
240
201
  emptyArchived: "还没有归档的会话。在会话行的「⋯」菜单里归档后会出现在这里。",
241
202
  emptyPinned: "没有置顶的会话。",
242
- emptyTrash: "回收站是空的。",
243
203
  unarchive: "取消归档",
244
204
  pin: "置顶",
245
205
  unpin: "取消置顶",
246
- restore: "恢复",
247
- restoreUnavailable: "无法恢复(缺元数据)",
248
- purge: "永久删除",
249
- moveToTrash: "移入回收站",
250
206
  rowActions: "更多操作",
251
207
  selectAll: "全选",
252
208
  selectNone: "取消选择",
253
209
  selected: "已选 {n} 项",
254
- emptyTrashAll: "清空回收站",
255
210
  total: "合计 {n} 个会话 · {size}",
256
211
  working: "处理中…",
257
212
  sessionRunning: "会话正在运行,先停止它再删除。",
258
- confirmTrashTitle: "删除会话",
259
- confirmTrashBody: "「{title}」会被移到回收站,之后可以在「设置 → 会话管理」里恢复。",
260
- confirmTrashAction: "移到回收站",
261
- confirmPurgeTitle: "永久删除",
262
- confirmPurgeBody: "「{title}」将从回收站永久删除,日志文件会被彻底移除,无法恢复。",
263
- confirmPurgeManyBody: "选中的 {n} 个会话将从回收站永久删除,无法恢复。",
264
- confirmPurgeAction: "永久删除",
265
- ackPurge: "我明白这无法撤销",
213
+ // 物理删除的确认弹窗(唯一一种弹窗):正文必须写清"不可恢复"并指路归档。
214
+ confirmDeleteTitle: "永久删除",
215
+ confirmDeleteBody: "「{title}」将被永久删除:日志文件会从磁盘上彻底移除,无法恢复。如果想以后还能找回,请改用「归档」。",
216
+ confirmDeleteManyBody: "选中的 {n} 个会话将被永久删除:日志文件会从磁盘上彻底移除,无法恢复。如果想以后还能找回,请改用「归档」。",
217
+ confirmDeleteAction: "永久删除",
218
+ ackDelete: "我明白这无法撤销",
266
219
  cancel: "取消",
267
220
  close: "关闭",
268
221
  noProject: "(未知目录)",
@@ -282,7 +235,7 @@ var zh = {
282
235
  workspaceActions: "整个工作区",
283
236
  workspaceMenu: "工作区操作",
284
237
  archiveWorkspace: "整个工作区归档",
285
- trashWorkspace: "整个工作区移入回收站",
238
+ deleteWorkspace: "整个工作区删除",
286
239
  workspaceRunningHint: "这个工作区里有会话正在运行,危险动作已禁用",
287
240
  // 运行中徽标(宿主用官方的 workspace/session-activity 判定)。
288
241
  running: "运行中",
@@ -296,14 +249,13 @@ var zh = {
296
249
  // 磁盘占用视图。
297
250
  titleUsage: "磁盘占用",
298
251
  usageTitle: "磁盘占用",
299
- usageTotal: "共 {n} 个会话 · {size}(磁盘上 {disk} 个)",
252
+ usageTotal: "共 {n} 个会话 · {size}",
300
253
  usageByWorkspace: "按工作区",
301
254
  usageLargest: "最占地方的会话",
302
255
  usageOldest: "最久没打开的会话",
303
256
  usageLoading: "正在统计…",
304
257
  usageArchived: "已归档",
305
258
  usagePinned: "已置顶",
306
- usageTrashed: "在回收站",
307
259
  usageSuggestArchive: "建议归档",
308
260
  usageArchive: "归档",
309
261
  usageUnarchive: "取消归档",
@@ -325,12 +277,11 @@ var zh = {
325
277
  configMeta: "行内显示体积与文件数",
326
278
  configMetaHint: "关掉后每行只留标题(平铺时仍会显示文件夹)",
327
279
  configOrder: "排序",
328
- configOrderHint: "原顺序就是宿主给的顺序(回收站里是最近删除在前)",
280
+ configOrderHint: "原顺序就是宿主给的顺序",
329
281
  orderHost: "原顺序",
330
282
  orderTitle: "标题",
331
283
  orderBytes: "体积",
332
284
  orderCreatedAt: "创建时间",
333
- orderDeletedAt: "删除时间",
334
285
  // 搜索与过滤(只影响显示;批量操作与计数仍按整个页签算)。
335
286
  searchLabel: "搜索会话",
336
287
  searchPlaceholder: "搜索标题或目录…",
@@ -340,22 +291,6 @@ var zh = {
340
291
  timeRecent: "最近 7 天",
341
292
  filtered: "已过滤出 {n} 个会话(共 {total} 个)",
342
293
  emptyFiltered: "没有符合搜索条件的会话。",
343
- // 回收站保留策略(永久删除:永远先预览、再确认,绝不自动后台清理)。
344
- configTrashDays: "回收站保留天数",
345
- configTrashDaysHint: "「清理 N 天前」按这个天数算;不会自动执行,每次都要先看预览再确认",
346
- retentionDays: "{n} 天",
347
- retentionCustom: "自定义",
348
- customDaysPlaceholder: "天数",
349
- customDaysLabel: "自定义保留天数",
350
- customDaysHint: "填 {min}–{max} 之间的整数;留空就用上面的档位",
351
- customDaysInvalid: "天数要在 {min}–{max} 之间",
352
- trashCleanup: "删除 {n} 天前的会话",
353
- trashPreview: "将删除 {n} 个会话 · {size}",
354
- trashPreviewNone: "没有超过 {days} 天的会话",
355
- trashPreviewLoading: "正在统计…",
356
- confirmPurgeOldTitle: "清理旧会话",
357
- confirmPurgeOldBody: "将永久删除 {n} 个在 {days} 天前移入回收站的会话({size}),无法恢复。",
358
- ackPurgeOld: "我明白这无法撤销",
359
294
  configAutoUpdate: "自动检查更新",
360
295
  configAutoUpdateHint: "打开这个页面时问一次 npm registry(10 分钟内有缓存)",
361
296
  configReset: "恢复默认",
@@ -377,50 +312,34 @@ var zh = {
377
312
  hoverStats: "{size} · {n} 个文件",
378
313
  hoverCwd: "原目录",
379
314
  hoverArchived: "已归档",
380
- hoverPinned: "已置顶",
381
- hoverTrashed: "在回收站",
382
- hoverDeleted: "删除于",
383
- // 删除成功后的撤销提示(`Toast`)。
384
- toastTrashed: "「{title}」已移入回收站",
385
- toastTrashedMany: "{n} 个会话已移入回收站",
386
- toastUndo: "撤销",
387
- toastUndoFailed: "撤销失败:{reason}"
315
+ hoverPinned: "已置顶"
388
316
  };
389
317
  var en = {
390
318
  sectionLabel: "Session manager",
391
- menuDelete: "Delete session",
319
+ deleteSession: "Delete session",
392
320
  titleArchived: "Archived",
393
321
  titlePinned: "Pinned",
394
- titleTrash: "Trash",
395
322
  refresh: "Refresh",
396
323
  loading: "Loading…",
397
324
  retry: "Retry",
398
325
  emptyArchived: "No archived sessions yet. Archive one from its “⋯” menu and it shows up here.",
399
326
  emptyPinned: "No pinned sessions.",
400
- emptyTrash: "The trash is empty.",
401
327
  unarchive: "Unarchive",
402
328
  pin: "Pin",
403
329
  unpin: "Unpin",
404
- restore: "Restore",
405
- restoreUnavailable: "Cannot restore (no metadata)",
406
- purge: "Delete permanently",
407
- moveToTrash: "Move to trash",
408
330
  rowActions: "More actions",
409
331
  selectAll: "Select all",
410
332
  selectNone: "Clear selection",
411
333
  selected: "{n} selected",
412
- emptyTrashAll: "Empty trash",
413
334
  total: "{n} sessions · {size}",
414
335
  working: "Working…",
415
336
  sessionRunning: "This session is running; stop it before deleting.",
416
- confirmTrashTitle: "Delete session",
417
- confirmTrashBody: "“{title}” moves to the trash. You can restore it later under Settings → Session manager.",
418
- confirmTrashAction: "Move to trash",
419
- confirmPurgeTitle: "Delete permanently",
420
- confirmPurgeBody: "“{title}” is removed from the trash for good; its log files are gone and cannot be recovered.",
421
- confirmPurgeManyBody: "{n} selected sessions are removed from the trash for good and cannot be recovered.",
422
- confirmPurgeAction: "Delete permanently",
423
- ackPurge: "I understand this cannot be undone",
337
+ // The one confirmation dialog: it must spell out "permanent" and point at archiving instead.
338
+ confirmDeleteTitle: "Delete permanently",
339
+ confirmDeleteBody: "“{title}” is deleted permanently: its log files are removed from disk for good and cannot be recovered. If you want to be able to bring it back later, use Archive instead.",
340
+ confirmDeleteManyBody: "{n} selected sessions are deleted permanently: their log files are removed from disk for good and cannot be recovered. If you want to be able to bring them back later, use Archive instead.",
341
+ confirmDeleteAction: "Delete permanently",
342
+ ackDelete: "I understand this cannot be undone",
424
343
  cancel: "Cancel",
425
344
  close: "Close",
426
345
  noProject: "(unknown directory)",
@@ -440,7 +359,7 @@ var en = {
440
359
  workspaceActions: "Whole workspace",
441
360
  workspaceMenu: "Workspace actions",
442
361
  archiveWorkspace: "Archive this whole workspace",
443
- trashWorkspace: "Move this whole workspace to the trash",
362
+ deleteWorkspace: "Delete this whole workspace",
444
363
  workspaceRunningHint: "A session in this workspace is running, so the destructive actions are off",
445
364
  // Running badge (the host asks the official workspace/session-activity hook).
446
365
  running: "Running",
@@ -454,14 +373,13 @@ var en = {
454
373
  // Disk usage view.
455
374
  titleUsage: "Disk usage",
456
375
  usageTitle: "Disk usage",
457
- usageTotal: "{n} sessions · {size} ({disk} on disk)",
376
+ usageTotal: "{n} sessions · {size}",
458
377
  usageByWorkspace: "By workspace",
459
378
  usageLargest: "Largest sessions",
460
379
  usageOldest: "Least recently opened",
461
380
  usageLoading: "Measuring…",
462
381
  usageArchived: "Archived",
463
382
  usagePinned: "Pinned",
464
- usageTrashed: "In trash",
465
383
  usageSuggestArchive: "Archive suggested",
466
384
  usageArchive: "Archive",
467
385
  usageUnarchive: "Unarchive",
@@ -483,12 +401,11 @@ var en = {
483
401
  configMeta: "Show size and file count per row",
484
402
  configMetaHint: "Off leaves only the title (the folder still shows in the flat list)",
485
403
  configOrder: "Order",
486
- configOrderHint: "Host order is what the host sent (the trash is most-recently-deleted first)",
404
+ configOrderHint: "Host order is what the host sent",
487
405
  orderHost: "Host",
488
406
  orderTitle: "Title",
489
407
  orderBytes: "Size",
490
408
  orderCreatedAt: "Created",
491
- orderDeletedAt: "Deleted",
492
409
  // Search and filtering (display only; batch actions and counts still cover the whole tab).
493
410
  searchLabel: "Search sessions",
494
411
  searchPlaceholder: "Search title or folder…",
@@ -498,22 +415,6 @@ var en = {
498
415
  timeRecent: "Last 7 days",
499
416
  filtered: "{n} of {total} sessions match",
500
417
  emptyFiltered: "No session matches the search.",
501
- // Trash retention (permanent deletion: always preview, then confirm; never automatic).
502
- configTrashDays: "Trash retention",
503
- configTrashDaysHint: "How “clean up N days ago” counts the days; it never runs on its own — every cleanup needs a preview and a confirmation",
504
- retentionDays: "{n} days",
505
- retentionCustom: "Custom",
506
- customDaysPlaceholder: "Days",
507
- customDaysLabel: "Custom retention days",
508
- customDaysHint: "An integer between {min} and {max}; leave it empty to use the preset above",
509
- customDaysInvalid: "Days must be between {min} and {max}",
510
- trashCleanup: "Delete sessions older than {n} days",
511
- trashPreview: "{n} sessions · {size} will be deleted",
512
- trashPreviewNone: "Nothing is older than {days} days",
513
- trashPreviewLoading: "Counting…",
514
- confirmPurgeOldTitle: "Clean up old sessions",
515
- confirmPurgeOldBody: "{n} sessions moved to the trash more than {days} days ago ({size}) are deleted for good and cannot be recovered.",
516
- ackPurgeOld: "I understand this cannot be undone",
517
418
  configAutoUpdate: "Check for updates automatically",
518
419
  configAutoUpdateHint: "Asks the npm registry when this page opens (cached for 10 minutes)",
519
420
  configReset: "Reset to defaults",
@@ -535,14 +436,7 @@ var en = {
535
436
  hoverStats: "{size} · {n} files",
536
437
  hoverCwd: "Folder",
537
438
  hoverArchived: "Archived",
538
- hoverPinned: "Pinned",
539
- hoverTrashed: "In trash",
540
- hoverDeleted: "Deleted",
541
- // Undo notice after a successful delete (`Toast`).
542
- toastTrashed: "“{title}” moved to the trash",
543
- toastTrashedMany: "{n} sessions moved to the trash",
544
- toastUndo: "Undo",
545
- toastUndoFailed: "Undo failed: {reason}"
439
+ hoverPinned: "Pinned"
546
440
  };
547
441
  var dictionaries = { zh, en };
548
442
  function text(t, key, params) {
@@ -554,8 +448,7 @@ function text(t, key, params) {
554
448
  }
555
449
  var TAB_LABEL = {
556
450
  archived: "titleArchived",
557
- pinned: "titlePinned",
558
- trash: "titleTrash"
451
+ pinned: "titlePinned"
559
452
  };
560
453
 
561
454
  // src/client/snapshot.ts
@@ -635,15 +528,6 @@ var S = {
635
528
  background: "var(--dsw-alias-interactive-bg-active)",
636
529
  flex: "0 0 auto"
637
530
  },
638
- badgeDanger: {
639
- color: "var(--dsw-alias-state-error-primary)",
640
- fontSize: "10px",
641
- lineHeight: "16px",
642
- padding: "0 6px",
643
- borderRadius: "8px",
644
- background: "var(--dsw-alias-interactive-bg-active)",
645
- flex: "0 0 auto"
646
- },
647
531
  /** 「运行中」:同款胶囊,但用业务蓝表示"它是活的",不是危险。 */
648
532
  badgeRunning: {
649
533
  color: "var(--dsw-alias-state-business-primary)",
@@ -789,11 +673,6 @@ var S = {
789
673
  /** 下拉锚点:文字 + 双向箭头图标,别被挤窄(长标签在按钮里折行是这个仓库踩过的坑)。 */
790
674
  dropdownAnchor: { flex: "0 0 auto" },
791
675
  dropdownButton: { flex: "0 0 auto", whiteSpace: "nowrap" },
792
- /** 保留策略那一格:档位下拉 + 自定义天数输入框。 */
793
- retentionDays: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap", justifyContent: "flex-end" },
794
- customDaysInput: { width: "88px", flex: "0 0 auto" },
795
- /** 天数非法时只把边框标红,不动尺寸(否则整行会跳一下)。 */
796
- customDaysInvalid: { borderColor: "var(--dsw-alias-state-error-primary)" },
797
676
  configFoot: {
798
677
  display: "flex",
799
678
  alignItems: "center",
@@ -860,18 +739,9 @@ var S = {
860
739
  background: "var(--dsw-alias-interactive-bg-active)",
861
740
  flex: "0 0 auto"
862
741
  },
863
- hoverBadgeDanger: {
864
- color: "var(--dsw-alias-state-error-primary)",
865
- fontSize: "10px",
866
- lineHeight: "16px",
867
- padding: "0 6px",
868
- borderRadius: "8px",
869
- background: "var(--dsw-alias-interactive-bg-active)",
870
- flex: "0 0 auto"
871
- },
872
742
  /**
873
- * 侧边栏会话行 hover 时那个「移入回收站」图标按钮(见
874
- * `components/trash-row-button.ts`):16×16 的朴素按钮,**两种状态都没有底色**,
743
+ * 侧边栏会话行 hover 时那个「删除会话」图标按钮(见
744
+ * `components/delete-row-button.ts`):16×16 的朴素按钮,**两种状态都没有底色**,
875
745
  * 只有前景色变深一档。
876
746
  *
877
747
  * `color-mix` 是 DSH 自己的产物里就在用的写法(例如 ui-deliverables 的
@@ -883,45 +753,6 @@ var S = {
883
753
  rowIconButtonHover: { ...ROW_ICON_BUTTON, color: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 78%, #000)" }
884
754
  };
885
755
 
886
- // src/client/toast.ts
887
- var toastStore = createStore(null);
888
- var sequence = 0;
889
- function showTrashUndo(ids, title) {
890
- if (ids.length === 0) return;
891
- sequence += 1;
892
- toastStore.set({ seq: sequence, ids: [...ids], title, error: null });
893
- }
894
- function showNotice(message) {
895
- if (message === "") return;
896
- sequence += 1;
897
- toastStore.set({ seq: sequence, ids: [], title: message, error: null });
898
- }
899
- function dismissToast() {
900
- toastStore.set(null);
901
- }
902
- function failToast(reason) {
903
- const current2 = toastStore.get();
904
- if (current2 === null) return;
905
- toastStore.set({ ...current2, error: reason });
906
- }
907
- async function undoTrash() {
908
- const current2 = toastStore.get();
909
- if (current2 === null || current2.error !== null) return;
910
- try {
911
- const value = await call("restore", { ids: current2.ids });
912
- const failure = firstFailure(value.results);
913
- if (failure !== null) {
914
- failToast(failure);
915
- return;
916
- }
917
- mutationStore.set(mutationStore.get() + 1);
918
- rememberSnapshot(value);
919
- dismissToast();
920
- } catch (thrown) {
921
- failToast(reportError(thrown));
922
- }
923
- }
924
-
925
756
  // src/client/components/dialog-host.ts
926
757
  function DialogHost(props) {
927
758
  const t = props.t;
@@ -929,18 +760,15 @@ function DialogHost(props) {
929
760
  const [busy, setBusy] = React2.useState(false);
930
761
  const [ack, setAck] = React2.useState(false);
931
762
  const [error, setError] = React2.useState(null);
932
- const requestKey = request === null ? "" : `${request.kind}:${request.ids.join(",")}:${request.title}:${request.days ?? ""}`;
763
+ const requestKey = request === null ? "" : `${request.kind}:${request.ids.join(",")}:${request.title}`;
933
764
  React2.useEffect(() => {
934
765
  setAck(false);
935
766
  setError(null);
936
767
  }, [requestKey]);
937
768
  if (request === null) return null;
938
- const purgeOld = request.kind === "purge-old";
939
- const purge = request.kind === "purge" || purgeOld;
940
769
  const many = request.ids.length > 1;
941
- const days = request.days ?? 0;
942
- const title = purgeOld ? t("confirmPurgeOldTitle") : purge ? t("confirmPurgeTitle") : t("confirmTrashTitle");
943
- const body = purgeOld ? text(t, "confirmPurgeOldBody", { n: request.count ?? 0, days, size: formatBytes(request.bytes ?? 0) }) : purge ? many ? text(t, "confirmPurgeManyBody", { n: request.ids.length }) : text(t, "confirmPurgeBody", { title: request.title }) : text(t, "confirmTrashBody", { title: request.title });
770
+ const title = t("confirmDeleteTitle");
771
+ const body = many ? text(t, "confirmDeleteManyBody", { n: request.ids.length }) : text(t, "confirmDeleteBody", { title: request.title });
944
772
  const close = () => {
945
773
  if (busy) return;
946
774
  dialogStore.set(null);
@@ -948,14 +776,12 @@ function DialogHost(props) {
948
776
  const confirm = async () => {
949
777
  setBusy(true);
950
778
  try {
951
- const endpoint = purgeOld ? "purge-older-than" : purge ? "purge" : "trash";
952
- const value = await call(endpoint, purgeOld ? { days } : { ids: request.ids });
779
+ const value = await call("delete", { ids: request.ids });
953
780
  const failure = firstFailure(value.results);
954
781
  if (failure === null) {
955
782
  dialogStore.set(null);
956
783
  rememberSnapshot(value);
957
784
  mutationStore.set(mutationStore.get() + 1);
958
- if (!purge) showTrashUndo(request.ids, request.title);
959
785
  } else {
960
786
  setError(failure);
961
787
  }
@@ -982,14 +808,14 @@ function DialogHost(props) {
982
808
  {
983
809
  size: "md",
984
810
  variant: "primary",
985
- disabled: busy || purge && !ack,
811
+ disabled: busy || !ack,
986
812
  onClick: () => void confirm()
987
813
  },
988
- purge ? t("confirmPurgeAction") : t("confirmTrashAction")
814
+ t("confirmDeleteAction")
989
815
  )
990
816
  )
991
817
  },
992
- purge ? React2.createElement(
818
+ React2.createElement(
993
819
  "div",
994
820
  { style: S.column },
995
821
  React2.createElement(
@@ -1001,10 +827,10 @@ function DialogHost(props) {
1001
827
  disabled: busy,
1002
828
  onChange: (event) => setAck(Boolean(event.target.checked))
1003
829
  }),
1004
- purgeOld ? t("ackPurgeOld") : t("ackPurge")
830
+ t("ackDelete")
1005
831
  ),
1006
832
  error ? React2.createElement("div", { style: S.error }, error) : null
1007
- ) : error ? React2.createElement("div", { style: S.error }, error) : null
833
+ )
1008
834
  );
1009
835
  }
1010
836
 
@@ -1040,6 +866,32 @@ async function describeSession(sessionId) {
1040
866
  return request;
1041
867
  }
1042
868
 
869
+ // src/client/format.ts
870
+ function formatBytes(bytes) {
871
+ if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes <= 0) return "0 B";
872
+ const units = ["B", "KB", "MB", "GB"];
873
+ let value = bytes;
874
+ let unit = 0;
875
+ while (value >= 1024 && unit < units.length - 1) {
876
+ value /= 1024;
877
+ unit += 1;
878
+ }
879
+ return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
880
+ }
881
+ function formatTime(ms) {
882
+ if (typeof ms !== "number" || ms <= 0) return "";
883
+ try {
884
+ return new Date(ms).toLocaleString();
885
+ } catch {
886
+ return "";
887
+ }
888
+ }
889
+ function baseName(path) {
890
+ if (typeof path !== "string" || path === "") return "";
891
+ const parts = path.split(/[\\/]/).filter((part) => part !== "");
892
+ return parts.length > 0 ? parts[parts.length - 1] : path;
893
+ }
894
+
1043
895
  // src/client/components/session-hover.ts
1044
896
  function SessionHover(props) {
1045
897
  const t = props.t;
@@ -1059,9 +911,8 @@ function SessionHover(props) {
1059
911
  }, [props.sessionId]);
1060
912
  if (detail === null) return null;
1061
913
  const badges = [];
1062
- if (detail.trashed) badges.push({ key: "trashed", label: t("hoverTrashed"), danger: true });
1063
- if (detail.archived) badges.push({ key: "archived", label: t("hoverArchived"), danger: false });
1064
- if (detail.pinned) badges.push({ key: "pinned", label: t("hoverPinned"), danger: false });
914
+ if (detail.archived) badges.push({ key: "archived", label: t("hoverArchived") });
915
+ if (detail.pinned) badges.push({ key: "pinned", label: t("hoverPinned") });
1065
916
  const lines = [
1066
917
  React3.createElement(
1067
918
  "div",
@@ -1079,16 +930,6 @@ function SessionHover(props) {
1079
930
  )
1080
931
  );
1081
932
  }
1082
- if (detail.trashed && detail.deletedAt) {
1083
- lines.push(
1084
- React3.createElement(
1085
- "div",
1086
- { key: "deletedAt", style: S.hoverLine },
1087
- React3.createElement("span", { style: S.hoverLabel }, t("hoverDeleted")),
1088
- React3.createElement("span", { style: S.hoverMeta }, formatTime(detail.deletedAt))
1089
- )
1090
- );
1091
- }
1092
933
  if (badges.length > 0) {
1093
934
  lines.push(
1094
935
  React3.createElement(
@@ -1099,7 +940,7 @@ function SessionHover(props) {
1099
940
  "span",
1100
941
  {
1101
942
  key: badge.key,
1102
- style: badge.danger ? S.hoverBadgeDanger : S.hoverBadge
943
+ style: S.hoverBadge
1103
944
  },
1104
945
  badge.label
1105
946
  )
@@ -1146,12 +987,12 @@ async function copyText(value) {
1146
987
  }
1147
988
 
1148
989
  // src/client/groups.ts
1149
- function workspaceSummaries(archived, pinned, trashed) {
990
+ function workspaceSummaries(archived, pinned) {
1150
991
  const summaries = /* @__PURE__ */ new Map();
1151
992
  const bucketOf = (key) => {
1152
993
  let summary = summaries.get(key);
1153
994
  if (summary === void 0) {
1154
- summary = { key, archivableIds: [], trashableIds: [], activeIds: [] };
995
+ summary = { key, archivableIds: [], deletableIds: [], activeIds: [] };
1155
996
  summaries.set(key, summary);
1156
997
  }
1157
998
  return summary;
@@ -1160,11 +1001,10 @@ function workspaceSummaries(archived, pinned, trashed) {
1160
1001
  for (const row of rows) {
1161
1002
  const summary = bucketOf(groupKeyOf(row));
1162
1003
  summary.archivableIds.push(row.sessionId);
1163
- summary.trashableIds.push(row.sessionId);
1004
+ summary.deletableIds.push(row.sessionId);
1164
1005
  if (row.active === true) summary.activeIds.push(row.sessionId);
1165
1006
  }
1166
1007
  }
1167
- for (const row of trashed) bucketOf(groupKeyOf(row));
1168
1008
  return summaries;
1169
1009
  }
1170
1010
  function groupKeyOf(row) {
@@ -1215,13 +1055,9 @@ function sortRows(rows, order) {
1215
1055
  return [...rows].sort((left, right) => (right.bytes ?? 0) - (left.bytes ?? 0));
1216
1056
  }
1217
1057
  if (order === "createdAt") return [...rows].sort(descending((row) => row.createdAt));
1218
- if (order === "deletedAt") return [...rows].sort(descending((row) => row.deletedAt));
1219
1058
  return rows;
1220
1059
  }
1221
1060
  var RECENT_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
1222
- function isTrashRow(row) {
1223
- return typeof row.deletedAt === "number";
1224
- }
1225
1061
  function matchesSearch(row, search) {
1226
1062
  const needle = search.trim().toLowerCase();
1227
1063
  if (needle === "") return true;
@@ -1229,7 +1065,6 @@ function matchesSearch(row, search) {
1229
1065
  return haystacks.some((value) => typeof value === "string" && value.toLowerCase().includes(needle));
1230
1066
  }
1231
1067
  function timeStampOf(row) {
1232
- if (isTrashRow(row)) return typeof row.deletedAt === "number" && row.deletedAt > 0 ? row.deletedAt : null;
1233
1068
  if (typeof row.createdAt === "number" && row.createdAt > 0) return row.createdAt;
1234
1069
  return null;
1235
1070
  }
@@ -1263,21 +1098,6 @@ function toggledCollapse(key, overrides, fallback) {
1263
1098
  }
1264
1099
 
1265
1100
  // src/client/settings.ts
1266
- var TRASH_RETENTION_DAYS = [7, 30, 90];
1267
- var DEFAULT_TRASH_DAYS = 30;
1268
- var TRASH_DAYS_MIN = 1;
1269
- var TRASH_DAYS_MAX = 3650;
1270
- function parseCustomDays(raw) {
1271
- const trimmed = raw.trim();
1272
- if (trimmed === "") return null;
1273
- if (!/^\d+$/.test(trimmed)) return null;
1274
- const value = Number(trimmed);
1275
- if (!Number.isSafeInteger(value) || value < TRASH_DAYS_MIN || value > TRASH_DAYS_MAX) return null;
1276
- return value;
1277
- }
1278
- function effectiveTrashDays(settings) {
1279
- return parseCustomDays(settings.trashDaysCustom) ?? settings.trashDays;
1280
- }
1281
1101
  var DEFAULT_SETTINGS = {
1282
1102
  tab: "archived",
1283
1103
  grouping: true,
@@ -1287,9 +1107,7 @@ var DEFAULT_SETTINGS = {
1287
1107
  order: "host",
1288
1108
  autoUpdate: true,
1289
1109
  search: "",
1290
- timeFilter: "all",
1291
- trashDays: DEFAULT_TRASH_DAYS,
1292
- trashDaysCustom: ""
1110
+ timeFilter: "all"
1293
1111
  };
1294
1112
  var SEARCH_MAX_LENGTH = 200;
1295
1113
  var SETTINGS_KEY = "dsh-session-curator.settings";
@@ -1304,19 +1122,14 @@ function readSettings() {
1304
1122
  const value = validate(parsed[key]);
1305
1123
  if (value !== void 0) next[key] = value;
1306
1124
  };
1307
- pick("tab", (value) => value === "archived" || value === "pinned" || value === "trash" ? value : void 0);
1125
+ pick("tab", (value) => value === "archived" || value === "pinned" ? value : void 0);
1308
1126
  pick("grouping", (value) => typeof value === "boolean" ? value : void 0);
1309
1127
  pick("groupDefault", (value) => value === "expanded" || value === "collapsed" ? value : void 0);
1310
1128
  pick("showMeta", (value) => typeof value === "boolean" ? value : void 0);
1311
- pick(
1312
- "order",
1313
- (value) => value === "host" || value === "title" || value === "bytes" || value === "createdAt" || value === "deletedAt" ? value : void 0
1314
- );
1129
+ pick("order", (value) => value === "host" || value === "title" || value === "bytes" || value === "createdAt" ? value : void 0);
1315
1130
  pick("autoUpdate", (value) => typeof value === "boolean" ? value : void 0);
1316
1131
  pick("search", (value) => typeof value === "string" ? value.slice(0, SEARCH_MAX_LENGTH) : void 0);
1317
1132
  pick("timeFilter", (value) => value === "all" || value === "recent" ? value : void 0);
1318
- pick("trashDays", (value) => typeof value === "number" && TRASH_RETENTION_DAYS.includes(value) ? value : void 0);
1319
- pick("trashDaysCustom", (value) => typeof value === "string" ? value.slice(0, 8) : void 0);
1320
1133
  return next;
1321
1134
  } catch {
1322
1135
  return { ...DEFAULT_SETTINGS };
@@ -1360,6 +1173,18 @@ function writeSidebarArchived(show) {
1360
1173
  }
1361
1174
  }
1362
1175
 
1176
+ // src/client/toast.ts
1177
+ var toastStore = createStore(null);
1178
+ var sequence = 0;
1179
+ function showNotice(message) {
1180
+ if (message === "") return;
1181
+ sequence += 1;
1182
+ toastStore.set({ seq: sequence, message });
1183
+ }
1184
+ function dismissToast() {
1185
+ toastStore.set(null);
1186
+ }
1187
+
1363
1188
  // src/client/update.ts
1364
1189
  var updateStore = createStore({ state: "idle", value: null, error: null });
1365
1190
  async function checkUpdate(force) {
@@ -1497,11 +1322,11 @@ function workspaceActionsOf(t, summary) {
1497
1322
  disabled: summary.archivableIds.length === 0 || summary.activeIds.length > 0
1498
1323
  },
1499
1324
  {
1500
- id: "trash",
1501
- label: t("trashWorkspace"),
1325
+ id: "delete",
1326
+ label: t("deleteWorkspace"),
1502
1327
  icon: React5.createElement(import_dsh_client_ui_primitives3.IconTrashOutlineRegular, { size: 14 }),
1503
1328
  danger: true,
1504
- disabled: summary.trashableIds.length === 0 || summary.activeIds.length > 0
1329
+ disabled: summary.deletableIds.length === 0 || summary.activeIds.length > 0
1505
1330
  }
1506
1331
  ];
1507
1332
  }
@@ -1518,7 +1343,7 @@ function SessionRow(props) {
1518
1343
  if (row.bytes) meta.push(formatBytes(row.bytes));
1519
1344
  if (row.files) meta.push(text(t, "files", { n: row.files }));
1520
1345
  }
1521
- const time = props.showDeletedAt ? formatTime(row.deletedAt) : formatTime(row.createdAt);
1346
+ const time = formatTime(row.createdAt);
1522
1347
  if (time !== "") meta.push(time);
1523
1348
  const menu = props.menu;
1524
1349
  const running = row.active === true;
@@ -1578,12 +1403,11 @@ var React7 = __toESM(require("react"), 1);
1578
1403
  var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
1579
1404
  function usageRow(t, row, busy, onArchive, onUnarchive) {
1580
1405
  const badges = [];
1581
- if (row.trashed) badges.push({ key: "trashed", label: t("usageTrashed"), style: S.badgeDanger });
1582
- else if (row.archived) badges.push({ key: "archived", label: t("usageArchived"), style: S.badge });
1406
+ if (row.archived) badges.push({ key: "archived", label: t("usageArchived"), style: S.badge });
1583
1407
  else if (row.pinned) badges.push({ key: "pinned", label: t("usagePinned"), style: S.badge });
1584
1408
  if (row.active) badges.push({ key: "running", label: t("running"), style: S.badgeRunning });
1585
- const action = row.trashed ? null : row.archived ? { label: t("usageUnarchive"), run: () => onUnarchive(row.sessionId) } : row.active ? null : { label: t("usageArchive"), run: () => onArchive(row.sessionId) };
1586
- const when = row.deletedAt !== null ? formatTime(row.deletedAt) : formatTime(row.createdAt);
1409
+ const action = row.archived ? { label: t("usageUnarchive"), run: () => onUnarchive(row.sessionId) } : row.active ? null : { label: t("usageArchive"), run: () => onArchive(row.sessionId) };
1410
+ const when = formatTime(row.createdAt);
1587
1411
  return React7.createElement(
1588
1412
  "div",
1589
1413
  { key: row.sessionId, style: S.usageRow },
@@ -1637,8 +1461,7 @@ function UsagePanel(props) {
1637
1461
  { style: S.configHint },
1638
1462
  props.loading ? t("usageLoading") : usage === null ? props.error ?? "" : text(t, "usageTotal", {
1639
1463
  n: usage.totalSessions,
1640
- size: formatBytes(usage.totalBytes),
1641
- disk: usage.onDiskSessions
1464
+ size: formatBytes(usage.totalBytes)
1642
1465
  })
1643
1466
  )
1644
1467
  )
@@ -1696,9 +1519,6 @@ function SessionManagerPanel(props) {
1696
1519
  const [collapsedOverrides, setCollapsedOverrides] = React8.useState({});
1697
1520
  const [menuFor, setMenuFor] = React8.useState(null);
1698
1521
  const [workspaceMenuFor, setWorkspaceMenuFor] = React8.useState(null);
1699
- const [trashStats, setTrashStats] = React8.useState(null);
1700
- const [trashStatsError, setTrashStatsError] = React8.useState(null);
1701
- const [trashStatsBusy, setTrashStatsBusy] = React8.useState(false);
1702
1522
  const [usage, setUsage] = React8.useState(null);
1703
1523
  const [usageError, setUsageError] = React8.useState(null);
1704
1524
  const [usageBusy, setUsageBusy] = React8.useState(false);
@@ -1738,26 +1558,6 @@ function SessionManagerPanel(props) {
1738
1558
  setMenuFor(null);
1739
1559
  setWorkspaceMenuFor(null);
1740
1560
  }, [tab]);
1741
- const retentionDays = effectiveTrashDays(settings);
1742
- React8.useEffect(() => {
1743
- if (tab !== "config") return;
1744
- let cancelled = false;
1745
- setTrashStatsBusy(true);
1746
- setTrashStatsError(null);
1747
- void call("trash-stats", { days: retentionDays }).then((value) => {
1748
- if (!cancelled) setTrashStats(value);
1749
- }).catch((failure) => {
1750
- if (!cancelled) {
1751
- setTrashStats(null);
1752
- setTrashStatsError(reportError(failure));
1753
- }
1754
- }).finally(() => {
1755
- if (!cancelled) setTrashStatsBusy(false);
1756
- });
1757
- return () => {
1758
- cancelled = true;
1759
- };
1760
- }, [tab, retentionDays]);
1761
1561
  React8.useEffect(() => {
1762
1562
  if (tab !== "usage") return;
1763
1563
  let cancelled = false;
@@ -1808,8 +1608,7 @@ function SessionManagerPanel(props) {
1808
1608
  };
1809
1609
  const archived = snapshot ? snapshot.archived : [];
1810
1610
  const pinned = snapshot ? snapshot.pinned : [];
1811
- const trashed = snapshot ? snapshot.trashed : [];
1812
- const rows = sortRows(tab === "archived" ? archived : tab === "pinned" ? pinned : tab === "trash" ? trashed : [], settings.order);
1611
+ const rows = sortRows(tab === "archived" ? archived : pinned, settings.order);
1813
1612
  const filter = React8.useMemo(
1814
1613
  () => ({ search: settings.search, timeFilter: settings.timeFilter }),
1815
1614
  [settings.search, settings.timeFilter]
@@ -1826,7 +1625,6 @@ function SessionManagerPanel(props) {
1826
1625
  const tabs = [
1827
1626
  { id: "archived", label: t(TAB_LABEL.archived), count: archived.length },
1828
1627
  { id: "pinned", label: t(TAB_LABEL.pinned), count: pinned.length },
1829
- { id: "trash", label: t(TAB_LABEL.trash), count: trashed.length },
1830
1628
  { id: "usage", label: t("titleUsage"), count: 0 },
1831
1629
  { id: "config", label: t("titleConfig"), count: 0 }
1832
1630
  ];
@@ -1834,13 +1632,9 @@ function SessionManagerPanel(props) {
1834
1632
  { value: "host", label: t("orderHost") },
1835
1633
  { value: "title", label: t("orderTitle") },
1836
1634
  { value: "bytes", label: t("orderBytes") },
1837
- { value: "createdAt", label: t("orderCreatedAt") },
1838
- { value: "deletedAt", label: t("orderDeletedAt") }
1635
+ { value: "createdAt", label: t("orderCreatedAt") }
1839
1636
  ];
1840
- const customDays = settings.trashDaysCustom;
1841
- const customDaysValid = customDays.trim() === "" || parseCustomDays(customDays) !== null;
1842
- const retentionOptions = TRASH_RETENTION_DAYS.map((days) => ({ value: days, label: text(t, "retentionDays", { n: days }) }));
1843
- const summaries = React8.useMemo(() => workspaceSummaries(archived, pinned, trashed), [archived, pinned, trashed]);
1637
+ const summaries = React8.useMemo(() => workspaceSummaries(archived, pinned), [archived, pinned]);
1844
1638
  const copyRowPath = async (sessionId) => {
1845
1639
  try {
1846
1640
  const value = await openSessionPath(sessionId);
@@ -1863,9 +1657,9 @@ function SessionManagerPanel(props) {
1863
1657
  };
1864
1658
  const rowMenu = (row) => {
1865
1659
  const running = row.active === true;
1866
- const moveToTrash = {
1867
- id: "trash",
1868
- label: t("moveToTrash"),
1660
+ const deleteSession = {
1661
+ id: "delete",
1662
+ label: t("deleteSession"),
1869
1663
  icon: React8.createElement(import_dsh_client_ui_primitives6.IconTrashOutlineRegular, { size: 14 }),
1870
1664
  danger: true,
1871
1665
  disabled: running
@@ -1890,49 +1684,27 @@ function SessionManagerPanel(props) {
1890
1684
  items: [
1891
1685
  { id: "unarchive", label: t("unarchive"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconUnarchiveOutlineRegular, { size: 14 }) },
1892
1686
  { id: "pin", label: t("pin"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconPinOutlineRegular, { size: 14 }) },
1893
- moveToTrash,
1894
- ...locate
1895
- ],
1896
- run: (id) => {
1897
- setMenuFor(null);
1898
- if (handleLocate(id)) return;
1899
- if (id === "trash") requestTrash([row.sessionId], row.title);
1900
- else void run(id, [row.sessionId]);
1901
- }
1902
- };
1903
- }
1904
- if (tab === "pinned") {
1905
- return {
1906
- items: [
1907
- { id: "unpin", label: t("unpin"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconPinOutlineRegular, { size: 14 }) },
1908
- moveToTrash,
1687
+ deleteSession,
1909
1688
  ...locate
1910
1689
  ],
1911
1690
  run: (id) => {
1912
1691
  setMenuFor(null);
1913
1692
  if (handleLocate(id)) return;
1914
- if (id === "trash") requestTrash([row.sessionId], row.title);
1693
+ if (id === "delete") requestDelete([row.sessionId], row.title);
1915
1694
  else void run(id, [row.sessionId]);
1916
1695
  }
1917
1696
  };
1918
1697
  }
1919
- const restorable = row.restorable !== false;
1920
1698
  return {
1921
1699
  items: [
1922
- {
1923
- id: "restore",
1924
- label: t(restorable ? "restore" : "restoreUnavailable"),
1925
- icon: React8.createElement(import_dsh_client_ui_primitives6.IconRefreshOutlineRegular, { size: 14 }),
1926
- disabled: !restorable
1927
- },
1928
- { id: "purge", label: t("purge"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconTrashOutlineRegular, { size: 14 }), danger: true },
1700
+ { id: "unpin", label: t("unpin"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconPinOutlineRegular, { size: 14 }) },
1701
+ deleteSession,
1929
1702
  ...locate
1930
1703
  ],
1931
1704
  run: (id) => {
1932
1705
  setMenuFor(null);
1933
1706
  if (handleLocate(id)) return;
1934
- if (id === "restore" && !restorable) return;
1935
- if (id === "purge") requestPurge([row.sessionId], row.title);
1707
+ if (id === "delete") requestDelete([row.sessionId], row.title);
1936
1708
  else void run(id, [row.sessionId]);
1937
1709
  }
1938
1710
  };
@@ -1942,36 +1714,27 @@ function SessionManagerPanel(props) {
1942
1714
  const summary = summaries.get(group.key);
1943
1715
  if (summary === void 0) return;
1944
1716
  if (id === "archive") void run("archive", summary.archivableIds);
1945
- if (id === "trash") requestTrash(summary.trashableIds, group.title);
1717
+ if (id === "delete") requestDelete(summary.deletableIds, group.title);
1946
1718
  };
1947
1719
  const batch = [];
1948
1720
  if (tab === "archived" && selected.length > 0) {
1949
1721
  batch.push({ label: t("unarchive"), run: () => void run("unarchive", selected) });
1950
1722
  batch.push({
1951
- label: t("moveToTrash"),
1723
+ label: t("deleteSession"),
1952
1724
  variant: "outline",
1953
- run: () => requestTrash(selected, selectedRows[0] ? selectedRows[0].title : "")
1725
+ run: () => requestDelete(selected, selectedRows[0] ? selectedRows[0].title : "")
1954
1726
  });
1955
1727
  }
1956
1728
  if (tab === "pinned" && selected.length > 0) {
1957
1729
  batch.push({ label: t("unpin"), run: () => void run("unpin", selected) });
1958
1730
  batch.push({
1959
- label: t("moveToTrash"),
1731
+ label: t("deleteSession"),
1960
1732
  variant: "outline",
1961
- run: () => requestTrash(selected, selectedRows[0] ? selectedRows[0].title : "")
1962
- });
1963
- }
1964
- if (tab === "trash" && selected.length > 0) {
1965
- const canRestore = selectedRows.some((row) => row.restorable !== false);
1966
- batch.push({ label: t("restore"), disabled: !canRestore, run: () => void run("restore", selected) });
1967
- batch.push({
1968
- label: t("purge"),
1969
- variant: "outline",
1970
- run: () => requestPurge(selected, selectedRows[0] ? selectedRows[0].title : "")
1733
+ run: () => requestDelete(selected, selectedRows[0] ? selectedRows[0].title : "")
1971
1734
  });
1972
1735
  }
1973
1736
  const totals = snapshot ? snapshot.totals : null;
1974
- const totalBytes = tab === "archived" ? totals ? totals.archivedBytes : 0 : tab === "trash" ? totals ? totals.trashBytes : 0 : 0;
1737
+ const totalBytes = tab === "archived" && totals ? totals.archivedBytes : 0;
1975
1738
  const nodes = [];
1976
1739
  let first = true;
1977
1740
  for (const block of blocks) {
@@ -1991,8 +1754,8 @@ function SessionManagerPanel(props) {
1991
1754
  onToggleGroup: () => toggleGroup(group),
1992
1755
  onToggleCollapse: () => toggleCollapse(key),
1993
1756
  // 「整个工作区」:跨页签的目标集合由 `workspaceSummaries()` 算好。
1994
- // "未知目录"那一组也有 key,但它的目标集合可能只含回收站条目 ——
1995
- // `workspaceActionsOf` 会把两项都置 `disabled`,菜单仍然画得出来(不是坏形状)。
1757
+ // 每个渲染出来的分组都至少有一行,所以 `summary` 一定有值;`workspaceActionsOf`
1758
+ // 再按"有没有在跑的会话"把危险项置 `disabled`。
1996
1759
  workspaceActions: summary === void 0 ? [] : workspaceActionsOf(t, summary),
1997
1760
  workspaceMenuOpen: workspaceMenuFor === group.key,
1998
1761
  onToggleWorkspaceMenu: () => setWorkspaceMenuFor(workspaceMenuFor === group.key ? null : group.key),
@@ -2012,7 +1775,6 @@ function SessionManagerPanel(props) {
2012
1775
  busy,
2013
1776
  showCwd: block.group === null,
2014
1777
  showMeta: settings.showMeta,
2015
- showDeletedAt: tab === "trash",
2016
1778
  checked: selected.includes(row.sessionId),
2017
1779
  onToggle: () => toggle(row.sessionId),
2018
1780
  menu: rowMenu(row),
@@ -2038,29 +1800,6 @@ function SessionManagerPanel(props) {
2038
1800
  ),
2039
1801
  React8.createElement("div", { style: S.configControl }, control)
2040
1802
  );
2041
- const retentionCount = trashStats === null ? 0 : trashStats.count;
2042
- const retentionBytes = trashStats === null ? 0 : trashStats.bytes;
2043
- const retentionFoot = React8.createElement(
2044
- "div",
2045
- { key: "config:trash-preview", style: S.configFoot },
2046
- trashStatsBusy ? React8.createElement("span", { style: S.configHint }, t("trashPreviewLoading")) : trashStatsError !== null ? React8.createElement("span", { style: S.error }, trashStatsError) : React8.createElement(
2047
- "span",
2048
- { style: S.configHint },
2049
- retentionCount > 0 ? text(t, "trashPreview", { n: retentionCount, size: formatBytes(retentionBytes) }) : text(t, "trashPreviewNone", { days: retentionDays })
2050
- ),
2051
- React8.createElement(
2052
- import_dsh_client_ui_primitives6.Button,
2053
- {
2054
- size: "sm",
2055
- variant: "outline",
2056
- // 自定义天数非法时不给点:这时候 `retentionDays` 回落到档位,点下去删的和看到的
2057
- // 那行数字对不上(虽然那行也是按档位算的,但用户以为自己填的数生效了)。
2058
- disabled: busy || trashStatsBusy || retentionCount === 0 || !customDaysValid,
2059
- onClick: () => requestPurgeOld(retentionDays, retentionCount, retentionBytes)
2060
- },
2061
- text(t, "trashCleanup", { n: retentionDays })
2062
- )
2063
- );
2064
1803
  const filterBar = React8.createElement(
2065
1804
  "div",
2066
1805
  { style: S.filterBar },
@@ -2115,8 +1854,7 @@ function SessionManagerPanel(props) {
2115
1854
  value: settings.tab,
2116
1855
  options: [
2117
1856
  { value: "archived", label: t(TAB_LABEL.archived) },
2118
- { value: "pinned", label: t(TAB_LABEL.pinned) },
2119
- { value: "trash", label: t(TAB_LABEL.trash) }
1857
+ { value: "pinned", label: t(TAB_LABEL.pinned) }
2120
1858
  ],
2121
1859
  label: t("configTab"),
2122
1860
  onChange: (next) => void updateSettings({ tab: next })
@@ -2209,39 +1947,6 @@ function SessionManagerPanel(props) {
2209
1947
  onChange: (next) => void updateSettings({ order: next })
2210
1948
  })
2211
1949
  ),
2212
- // ── 回收站保留策略 ──────────────────────────────────────────────
2213
- // 这条**故意不做成自动清理**:它是本插件唯一不可撤销的动作。这里只让用户选一个
2214
- // 天数(档位或自定义)并看到预览,真正的删除必须再点一次、再过一道确认弹窗(`purge-old`)。
2215
- configRow(
2216
- "config:trash-days",
2217
- t("configTrashDays"),
2218
- t("configTrashDaysHint"),
2219
- React8.createElement(
2220
- "div",
2221
- { style: S.retentionDays },
2222
- React8.createElement(Dropdown, {
2223
- id: "session-curator-trash-days",
2224
- value: settings.trashDays,
2225
- options: retentionOptions,
2226
- label: t("configTrashDays"),
2227
- // 填了自定义天数时锚点显示"自定义",否则显示档位 —— 让"现在到底按哪个算"一眼可见。
2228
- display: customDaysValid && customDays.trim() !== "" ? t("retentionCustom") : void 0,
2229
- open: openDropdown === "trash-days",
2230
- onToggle: (next) => setOpenDropdown(next ? "trash-days" : null),
2231
- onChange: (next) => void updateSettings({ trashDays: next, trashDaysCustom: "" })
2232
- }),
2233
- React8.createElement(import_dsh_client_ui_primitives6.Input, {
2234
- value: customDays,
2235
- placeholder: t("customDaysPlaceholder"),
2236
- "aria-label": t("customDaysLabel"),
2237
- "aria-invalid": !customDaysValid,
2238
- title: text(t, "customDaysHint", { min: TRASH_DAYS_MIN, max: TRASH_DAYS_MAX }),
2239
- style: customDaysValid ? S.customDaysInput : { ...S.customDaysInput, ...S.customDaysInvalid },
2240
- onChange: (event) => void updateSettings({ trashDaysCustom: event.target.value })
2241
- })
2242
- )
2243
- ),
2244
- retentionFoot,
2245
1950
  configRow(
2246
1951
  "config:auto",
2247
1952
  t("configAutoUpdate"),
@@ -2416,7 +2121,7 @@ function SessionManagerPanel(props) {
2416
2121
  loading && !snapshot ? React8.createElement("div", { style: S.empty }, t("loading")) : filtered.length === 0 ? React8.createElement(
2417
2122
  "div",
2418
2123
  { style: S.empty },
2419
- filtering ? t("emptyFiltered") : tab === "archived" ? t("emptyArchived") : tab === "pinned" ? t("emptyPinned") : t("emptyTrash")
2124
+ filtering ? t("emptyFiltered") : tab === "archived" ? t("emptyArchived") : t("emptyPinned")
2420
2125
  ) : React8.createElement("div", { style: S.list }, nodes)
2421
2126
  ) : null,
2422
2127
  isDataTab ? React8.createElement(
@@ -2445,19 +2150,6 @@ function SessionManagerPanel(props) {
2445
2150
  action.label
2446
2151
  )
2447
2152
  ),
2448
- tab === "trash" && trashed.length > 0 ? React8.createElement(
2449
- import_dsh_client_ui_primitives6.Button,
2450
- {
2451
- size: "sm",
2452
- variant: "outline",
2453
- disabled: busy,
2454
- onClick: () => requestPurge(
2455
- trashed.map((row) => row.sessionId),
2456
- ""
2457
- )
2458
- },
2459
- t("emptyTrashAll")
2460
- ) : null,
2461
2153
  busy ? React8.createElement("span", { style: S.footer }, t("working")) : null
2462
2154
  ) : null,
2463
2155
  isUsage ? usagePage : null,
@@ -2470,7 +2162,7 @@ function SessionManagerPanel(props) {
2470
2162
  filtering ? React8.createElement("span", null, text(t, "filtered", { n: filtered.length, total: rows.length })) : null,
2471
2163
  grouping && groups.length > 0 ? React8.createElement("span", null, text(t, "workspaces", { n: groups.length })) : null,
2472
2164
  selected.length > 0 ? React8.createElement("span", null, text(t, "selected", { n: selected.length })) : null,
2473
- snapshot ? React8.createElement("span", { style: S.mono, title: snapshot.trashDir }, snapshot.trashDir) : null
2165
+ snapshot ? React8.createElement("span", { style: S.mono, title: snapshot.sessionsDir }, snapshot.sessionsDir) : null
2474
2166
  ) : null
2475
2167
  );
2476
2168
  }
@@ -2480,52 +2172,26 @@ var React9 = __toESM(require("react"), 1);
2480
2172
  var import_dsh_client_ui_primitives7 = require("@deepseek-ai/dsh-client-ui-primitives");
2481
2173
  var HOLD_MS = 5e3;
2482
2174
  function ToastHost(props) {
2483
- const t = props.t;
2484
2175
  const notice = useStore(toastStore);
2485
2176
  if (notice === null) return null;
2486
- if (notice.error !== null) {
2487
- return React9.createElement(
2488
- import_dsh_client_ui_primitives7.Toast,
2489
- {
2490
- text: text(t, "toastUndoFailed", { reason: notice.error }),
2491
- icon: React9.createElement(import_dsh_client_ui_primitives7.IconWarningOutlineRegular, {}),
2492
- onDone: dismissToast
2493
- },
2494
- `session-curator-toast-${notice.seq}`
2495
- );
2496
- }
2497
- if (notice.ids.length === 0) {
2498
- return React9.createElement(
2499
- import_dsh_client_ui_primitives7.Toast,
2500
- {
2501
- text: notice.title,
2502
- holdMs: HOLD_MS,
2503
- onDone: dismissToast
2504
- },
2505
- `session-curator-toast-${notice.seq}`
2506
- );
2507
- }
2508
- const many = notice.ids.length > 1;
2509
2177
  return React9.createElement(
2510
2178
  import_dsh_client_ui_primitives7.Toast,
2511
2179
  {
2512
- text: many ? text(t, "toastTrashedMany", { n: notice.ids.length }) : text(t, "toastTrashed", { title: notice.title }),
2513
- tone: "success",
2180
+ text: notice.message,
2514
2181
  holdMs: HOLD_MS,
2515
- actions: [{ label: t("toastUndo"), onClick: () => void undoTrash() }],
2516
2182
  onDone: dismissToast
2517
2183
  },
2518
2184
  `session-curator-toast-${notice.seq}`
2519
2185
  );
2520
2186
  }
2521
2187
 
2522
- // src/client/components/trash-row-button.ts
2188
+ // src/client/components/delete-row-button.ts
2523
2189
  var React10 = __toESM(require("react"), 1);
2524
2190
  var import_dsh_client_ui_primitives8 = require("@deepseek-ai/dsh-client-ui-primitives");
2525
- function TrashRowButton(props) {
2191
+ function DeleteRowButton(props) {
2526
2192
  const [hovered, setHovered] = React10.useState(false);
2527
2193
  const title = props.displayTitle || props.sessionId;
2528
- const label = props.t("moveToTrash");
2194
+ const label = props.t("deleteSession");
2529
2195
  return React10.createElement(import_dsh_client_ui_primitives8.Tooltip, {
2530
2196
  label,
2531
2197
  side: "bottom",
@@ -2539,7 +2205,7 @@ function TrashRowButton(props) {
2539
2205
  "aria-label": `${title} — ${label}`,
2540
2206
  onPointerEnter: () => setHovered(true),
2541
2207
  onPointerLeave: () => setHovered(false),
2542
- onClick: () => requestTrash([props.sessionId], title)
2208
+ onClick: () => requestDelete([props.sessionId], title)
2543
2209
  },
2544
2210
  React10.createElement(import_dsh_client_ui_primitives8.IconTrashOutlineRegular, { size: 14 })
2545
2211
  )
@@ -2551,7 +2217,7 @@ var MENU_ID = "session-curator.delete";
2551
2217
  var OVERLAY_ID = "session-curator.dialog";
2552
2218
  var TOAST_ID = "session-curator.toast";
2553
2219
  var HOVER_ID = "session-curator.hover";
2554
- var ROW_ACTION_ID = "session-curator.trash";
2220
+ var ROW_ACTION_ID = "session-curator.delete-row";
2555
2221
  var SECTION_ID = "session-curator";
2556
2222
  var NAV_SECTION_ID = "archived-sessions";
2557
2223
  function sectionIdFor(ctx) {
@@ -2581,7 +2247,7 @@ function apply(ctx) {
2581
2247
  name: "sidebar.workspaces.session.menu.item",
2582
2248
  id: MENU_ID,
2583
2249
  order: 500,
2584
- label: () => t("menuDelete"),
2250
+ label: () => t("deleteSession"),
2585
2251
  locale: NS
2586
2252
  },
2587
2253
  (props) => {
@@ -2593,10 +2259,10 @@ function apply(ctx) {
2593
2259
  separatorBefore: true,
2594
2260
  onSelect: () => {
2595
2261
  setMenuOpen(false);
2596
- requestTrash([props.sessionId], props.displayTitle || props.sessionId);
2262
+ requestDelete([props.sessionId], props.displayTitle || props.sessionId);
2597
2263
  }
2598
2264
  },
2599
- t("menuDelete")
2265
+ t("deleteSession")
2600
2266
  );
2601
2267
  },
2602
2268
  "menu item"
@@ -2644,7 +2310,7 @@ function apply(ctx) {
2644
2310
  order: 500,
2645
2311
  locale: NS
2646
2312
  },
2647
- TrashRowButton,
2313
+ DeleteRowButton,
2648
2314
  "row action"
2649
2315
  );
2650
2316
  contribute(