@takboo/dsh-session-bin 0.1.1 → 0.2.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/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  window.__ModuleLoader__.load({ id: "@takboo/dsh-session-bin", factory(require) {
2
2
  var module = { exports: {} }; var exports = module.exports;
3
- const __SESSION_BIN_CSS__ = "/* src/client/panel.module.css */\n.panel_panel {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: calc(28px + var(--dsh-frame-top-clearance, 0px)) 32px 24px;\n box-sizing: border-box;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n}\n.panel_heading {\n display: flex;\n gap: 16px;\n justify-content: space-between;\n align-items: flex-start;\n}\n.panel_headingActions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n.panel_title {\n margin: 0;\n font-size: 22px;\n font-weight: 600;\n letter-spacing: 0;\n}\n.panel_description {\n margin: 8px 0 0;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.6;\n}\n.panel_count {\n margin: 0 0 6px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n.panel_toolbar {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n align-items: center;\n}\n.panel_search {\n flex: 1;\n min-width: 180px;\n box-sizing: border-box;\n}\n.panel_filter {\n height: 36px;\n max-width: 250px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-1);\n font: inherit;\n}\n.panel_filter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.panel_alert {\n display: flex;\n gap: 12px;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 12px 16px;\n border-radius: var(--dsw-radius-md, 12px);\n border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n line-height: 1.6;\n}\n.panel_content {\n flex: 1;\n min-height: 0;\n overflow: auto;\n}\n.panel_list {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.panel_row {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px 4px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n.panel_rowCheck {\n position: relative;\n flex-shrink: 0;\n}\n.panel_rowCheck > span:last-child {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n.panel_rowBody {\n flex: 1;\n min-width: 0;\n}\n.panel_rowTitle {\n font-size: 14px;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.panel_meta {\n margin-top: 6px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px 12px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 1.5;\n}\n.panel_empty {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n min-height: 220px;\n gap: 12px;\n text-align: center;\n padding: 28px;\n}\n.panel_emptyIcon {\n color: var(--dsw-alias-label-secondary);\n opacity: .65;\n}\n.panel_emptyTitle {\n margin: 0;\n font-size: 16px;\n font-weight: 500;\n}\n.panel_emptyHint {\n margin: 0;\n max-width: 440px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.7;\n}\n.panel_selection {\n display: flex;\n gap: 12px;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 12px 16px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n}\n.panel_selectionActions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n.panel_results {\n list-style: none;\n padding: 0;\n margin: 0;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n max-height: 110px;\n overflow: auto;\n}\n.panel_result {\n padding: 5px 0;\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n justify-content: space-between;\n}\n.panel_batchModalBody {\n max-height: min(56vh, 560px);\n overflow-y: auto;\n padding-right: 4px;\n}\n.panel_batchProgress {\n margin: 0 0 8px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.5;\n}\n.panel_batchSummary {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n line-height: 1.5;\n}\n.panel_resourceCounts {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px 16px;\n margin: 16px 0;\n}\n.panel_resourceCounts div {\n display: flex;\n min-width: 0;\n gap: 8px;\n justify-content: space-between;\n}\n.panel_resourceCounts dt {\n color: var(--dsw-alias-label-secondary);\n overflow-wrap: anywhere;\n}\n.panel_resourceCounts dd {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-variant-numeric: tabular-nums;\n}\n.panel_batchItems {\n list-style: none;\n padding: 0;\n margin: 16px 0;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n.panel_batchItems li {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(120px, .65fr);\n gap: 12px;\n padding: 10px 0;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n line-height: 1.5;\n}\n.panel_batchItemTitle {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--dsw-alias-label-primary);\n}\n.panel_batchItemStatus {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--dsw-alias-label-secondary);\n text-align: right;\n}\n.panel_batchStatus {\n padding: 16px 0;\n border-top: 1px solid var(--dsw-alias-border-l1);\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n.panel_batchStatus h2 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n letter-spacing: 0;\n}\n.panel_batchOutcome {\n margin: 12px 0 0;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.5;\n}\n.panel_batchActions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n flex-wrap: wrap;\n margin-top: 12px;\n}\n@media (max-width: 640px) {\n .panel_panel {\n padding: calc(20px + var(--dsh-frame-top-clearance, 0px)) 16px 16px;\n gap: 16px;\n }\n .panel_title {\n font-size: 20px;\n }\n .panel_description {\n font-size: 13px;\n }\n .panel_toolbar {\n align-items: stretch;\n }\n .panel_search {\n min-width: 100%;\n }\n .panel_filter {\n max-width: none;\n width: 100%;\n }\n .panel_heading {\n gap: 10px;\n flex-wrap: wrap;\n }\n .panel_headingActions {\n width: 100%;\n justify-content: flex-start;\n }\n .panel_row {\n gap: 10px;\n flex-wrap: wrap;\n }\n .panel_rowBody {\n min-width: min(180px, 60%);\n }\n .panel_meta {\n gap: 4px 10px;\n }\n .panel_selectionActions {\n width: 100%;\n justify-content: flex-end;\n }\n .panel_resourceCounts {\n grid-template-columns: 1fr;\n }\n .panel_batchItems li {\n grid-template-columns: 1fr;\n gap: 4px;\n }\n .panel_batchItemStatus {\n text-align: left;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .panel_panel *,\n .panel_panel *::before,\n .panel_panel *::after {\n scroll-behavior: auto;\n }\n}\n";
3
+ const __SESSION_BIN_CSS__ = "/* src/client/panel.module.css */\n.panel_panel {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: calc(28px + var(--dsh-frame-top-clearance, 0px)) 32px 24px;\n box-sizing: border-box;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n}\n.panel_heading {\n display: flex;\n gap: 16px;\n justify-content: space-between;\n align-items: flex-start;\n}\n.panel_headingActions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n.panel_title {\n margin: 0;\n font-size: 22px;\n font-weight: 600;\n letter-spacing: 0;\n}\n.panel_description {\n margin: 8px 0 0;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.6;\n}\n.panel_count {\n margin: 0 0 6px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n.panel_toolbar {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n align-items: center;\n}\n.panel_search {\n flex: 1;\n min-width: 180px;\n box-sizing: border-box;\n}\n.panel_filter {\n height: 36px;\n max-width: 250px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-1);\n font: inherit;\n}\n.panel_filter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.panel_alert {\n display: flex;\n gap: 12px;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 12px 16px;\n border-radius: var(--dsw-radius-md, 12px);\n border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n line-height: 1.6;\n}\n.panel_content {\n flex: 1;\n min-height: 0;\n overflow: auto;\n}\n.panel_list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.panel_row {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px 18px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-lg, 18px);\n}\n.panel_rowCheck {\n position: relative;\n flex-shrink: 0;\n}\n.panel_rowCheck > span:last-child {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n.panel_rowBody {\n flex: 1;\n min-width: 0;\n}\n.panel_rowTitle {\n font-size: 14px;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.panel_empty {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n min-height: 220px;\n gap: 12px;\n text-align: center;\n padding: 28px;\n}\n.panel_emptyIcon {\n color: var(--dsw-alias-label-secondary);\n opacity: .65;\n}\n.panel_emptyTitle {\n margin: 0;\n font-size: 16px;\n font-weight: 500;\n}\n.panel_emptyHint {\n margin: 0;\n max-width: 440px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.7;\n}\n.panel_selection {\n display: flex;\n gap: 12px;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 12px 16px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-md, 12px);\n background: var(--dsw-alias-bg-layer-1);\n}\n.panel_selectionActions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n.panel_results {\n list-style: none;\n padding: 0;\n margin: 0;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n max-height: 110px;\n overflow: auto;\n}\n.panel_result {\n padding: 5px 0;\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n justify-content: space-between;\n}\n.panel_batchModalBody {\n max-height: min(56vh, 560px);\n overflow-y: auto;\n padding-right: 4px;\n}\n.panel_batchProgress {\n margin: 0 0 8px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.5;\n}\n.panel_batchSummary {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n line-height: 1.5;\n}\n.panel_batchItems {\n list-style: none;\n padding: 0;\n margin: 16px 0;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n.panel_batchItems li {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(120px, .65fr);\n gap: 12px;\n padding: 10px 0;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n line-height: 1.5;\n}\n.panel_batchItemTitle {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--dsw-alias-label-primary);\n}\n.panel_batchItemStatus {\n min-width: 0;\n overflow-wrap: anywhere;\n color: var(--dsw-alias-label-secondary);\n text-align: right;\n}\n.panel_batchStatus h2 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n letter-spacing: 0;\n}\n.panel_batchOutcome {\n margin: 12px 0 0;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.5;\n}\n.panel_batchActions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n flex-wrap: wrap;\n margin-top: 12px;\n}\n.panel_workspaceGroup + .panel_workspaceGroup {\n margin-top: 24px;\n}\n.panel_workspaceHeading {\n display: flex;\n gap: 10px;\n align-items: center;\n margin: 0 0 12px;\n font-size: 14px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n overflow-wrap: anywhere;\n}\n.panel_workspaceHeading span {\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n}\n.panel_danger:not(:disabled) {\n color: var(--dsw-alias-state-error-primary);\n}\n.panel_danger:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n}\n.panel_settings {\n padding: 14px 18px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-lg, 18px);\n}\n.panel_batchProgress {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.panel_batchProgress progress {\n width: 100%;\n accent-color: var(--dsw-alias-brand-primary);\n}\n.panel_batchStatus {\n flex-shrink: 0;\n padding: 12px 16px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-lg, 18px);\n}\n.panel_batchStatus .panel_batchModalBody {\n max-height: min(24vh, 220px);\n}\n.panel_batchStatus .panel_batchItems {\n margin: 8px 0;\n}\n@media (max-width: 640px) {\n .panel_panel {\n padding: calc(20px + var(--dsh-frame-top-clearance, 0px)) 16px 16px;\n gap: 16px;\n }\n .panel_title {\n font-size: 20px;\n }\n .panel_description {\n font-size: 13px;\n }\n .panel_toolbar {\n align-items: stretch;\n }\n .panel_search {\n min-width: 100%;\n }\n .panel_filter {\n max-width: none;\n width: 100%;\n }\n .panel_heading {\n gap: 10px;\n flex-wrap: wrap;\n }\n .panel_headingActions {\n width: 100%;\n justify-content: flex-start;\n }\n .panel_row {\n gap: 10px;\n flex-wrap: wrap;\n }\n .panel_rowBody {\n min-width: min(180px, 60%);\n }\n .panel_selectionActions {\n width: 100%;\n justify-content: flex-end;\n }\n .panel_batchItems li {\n grid-template-columns: 1fr;\n gap: 4px;\n }\n .panel_batchItemStatus {\n text-align: left;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .panel_panel *,\n .panel_panel *::before,\n .panel_panel *::after {\n scroll-behavior: auto;\n }\n}\n";
4
4
  "use strict";
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -15177,10 +15177,11 @@ function frozenPurgeKey(plan) {
15177
15177
  ]);
15178
15178
  }
15179
15179
  var SessionBinClientModel = class {
15180
- constructor(api, createStream, cache) {
15180
+ constructor(api, createStream, cache, preferences) {
15181
15181
  this.api = api;
15182
15182
  this.createStream = createStream;
15183
15183
  this.cache = cache;
15184
+ this.preferences = preferences;
15184
15185
  try {
15185
15186
  this.state = { ...this.state, pending: pendingPlans(cache?.load()) };
15186
15187
  } catch {
@@ -15236,7 +15237,12 @@ var SessionBinClientModel = class {
15236
15237
  }
15237
15238
  const purgeCacheBlocked = Boolean(cache?.isPurgeCacheBlocked?.()) || purgeCacheOverflow(rawPurgePlans, rawPurgeGrants, rawPurgeObservations, rawPurgeBatchOperations);
15238
15239
  this.state = { ...this.state, purgeCacheBlocked };
15240
+ try {
15241
+ this.state.confirmDeletion = preferences?.load() !== false;
15242
+ } catch {
15243
+ }
15239
15244
  this.snapshot = structuredClone(this.state);
15245
+ this.stopPreferences = preferences?.subscribe?.((confirmDeletion) => this.publish({ confirmDeletion }));
15240
15246
  }
15241
15247
  state = {
15242
15248
  phase: "loading",
@@ -15251,7 +15257,8 @@ var SessionBinClientModel = class {
15251
15257
  purgeResults: [],
15252
15258
  purgeBatch: null,
15253
15259
  purgeBatchOperationIds: [],
15254
- purgeCacheBlocked: false
15260
+ purgeCacheBlocked: false,
15261
+ confirmDeletion: true
15255
15262
  };
15256
15263
  snapshot = structuredClone(this.state);
15257
15264
  listeners = /* @__PURE__ */ new Set();
@@ -15273,6 +15280,14 @@ var SessionBinClientModel = class {
15273
15280
  purgeBatch = null;
15274
15281
  purgeBatchRun = null;
15275
15282
  purgePreparationEpoch = 0;
15283
+ stopPreferences;
15284
+ setConfirmDeletion(confirmDeletion) {
15285
+ try {
15286
+ this.preferences?.save(confirmDeletion);
15287
+ } catch {
15288
+ }
15289
+ this.publish({ confirmDeletion });
15290
+ }
15276
15291
  getSnapshot = () => this.snapshot;
15277
15292
  subscribe = (listener) => {
15278
15293
  this.listeners.add(listener);
@@ -15321,6 +15336,7 @@ var SessionBinClientModel = class {
15321
15336
  })();
15322
15337
  }
15323
15338
  async dispose() {
15339
+ this.stopPreferences?.();
15324
15340
  this.lifetime.abort();
15325
15341
  ++this.epoch;
15326
15342
  await this.stream?.dispose();
@@ -15469,6 +15485,25 @@ var SessionBinClientModel = class {
15469
15485
  return { plan: null, reason: "connection-failed" };
15470
15486
  }
15471
15487
  }
15488
+ /** Only a fresh user click can opt out of the presentation confirmation. */
15489
+ async requestPurge(entry, title) {
15490
+ const skip = !this.state.confirmDeletion;
15491
+ const epoch = this.epoch;
15492
+ const plan = await this.preparePurge(entry, title);
15493
+ if (skip && !this.state.confirmDeletion && epoch === this.epoch && plan && this.confirmation?.plan.operationId === plan.operationId) {
15494
+ this.acknowledgePurge(true);
15495
+ await this.confirmPurge();
15496
+ }
15497
+ }
15498
+ async requestPurgeBatch(scope, titles = {}) {
15499
+ const skip = !this.state.confirmDeletion;
15500
+ const epoch = this.epoch;
15501
+ const batch = await this.preparePurgeBatch(scope, titles);
15502
+ if (skip && !this.state.confirmDeletion && epoch === this.epoch && batch?.phase === "confirming" && this.purgeBatch?.batchId === batch.batchId && batch.items.some((item) => item.state === "ready")) {
15503
+ this.acknowledgePurgeBatch(true);
15504
+ await this.runPurgeBatch();
15505
+ }
15506
+ }
15472
15507
  async preparePurge(entry, title) {
15473
15508
  const target = { sessionId: entry.sessionId, entryId: entry.entryId, title };
15474
15509
  if (this.rejectPurgeCacheCapacity() || this.state.phase !== "ready" || this.state.busy.includes(target.sessionId) || this.confirmation || this.hasActivePurgeBatch() || this.state.purgePending.length > 0 || this.state.pending.some((plan) => plan.schemaVersion === 2 && plan.sessionId === target.sessionId) || this.lifetime.signal.aborted) return null;
@@ -16206,6 +16241,29 @@ function browserPendingCache(storage) {
16206
16241
  }
16207
16242
  };
16208
16243
  }
16244
+ function browserDeletionPreferences(storage, events) {
16245
+ const key2 = "dsh-session-bin.preferences.v1";
16246
+ const parse3 = (raw) => {
16247
+ try {
16248
+ const value2 = JSON.parse(raw ?? "null");
16249
+ return !(value2?.schemaVersion === 1 && value2.confirmDeletion === false);
16250
+ } catch {
16251
+ return true;
16252
+ }
16253
+ };
16254
+ return {
16255
+ load: () => parse3(storage.getItem(key2)),
16256
+ save: (confirmDeletion) => storage.setItem(key2, JSON.stringify({ schemaVersion: 1, confirmDeletion })),
16257
+ subscribe: (listener) => {
16258
+ const changed = (event) => {
16259
+ const update = event;
16260
+ if (update.storageArea === storage && (update.key === key2 || update.key === null)) listener(parse3(update.newValue));
16261
+ };
16262
+ events?.addEventListener("storage", changed);
16263
+ return () => events?.removeEventListener("storage", changed);
16264
+ }
16265
+ };
16266
+ }
16209
16267
 
16210
16268
  // src/client/components.tsx
16211
16269
  var import_react = require("react");
@@ -16214,6 +16272,13 @@ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primit
16214
16272
  // src/client/locales.ts
16215
16273
  var NS = "dshSessionBin";
16216
16274
  var en = {
16275
+ settings: "Settings",
16276
+ confirmDeletionSetting: "Confirm before permanently deleting conversations",
16277
+ confirmDeletionHint: "Applies to single deletion, selected conversations, and clear all in this browser profile for this Host. When off, clicking Delete starts permanent deletion immediately.",
16278
+ batchPreparingTitle: "Checking conversations",
16279
+ batchRunningTitle: "Deleting conversations",
16280
+ batchExecutionProgress: "Processed {completed} of {total}",
16281
+ batchProgressLabel: "Deletion progress",
16217
16282
  title: "Session Bin",
16218
16283
  count: "{count} conversations",
16219
16284
  countOne: "{count} conversation",
@@ -16261,7 +16326,7 @@ var en = {
16261
16326
  deleteConversation: "Permanently delete {title}",
16262
16327
  deleteTitle: "Permanently delete \u201C{title}\u201D?",
16263
16328
  deleteDescription: "Delete this one conversation\u2019s logs and its session metadata and indexes. This cannot be undone.",
16264
- deleteScope: "Shared attachments, independent forks, and copies in external tools are kept. Loaded conversations, retained readers, unknown files, and active writers can block deletion.",
16329
+ deleteScope: "Shared attachments, independent forks, and copies in external tools are kept.",
16265
16330
  deletionAcknowledge: "I understand that this conversation cannot be restored.",
16266
16331
  cancelDeletion: "Cancel",
16267
16332
  confirmDeletion: "Delete permanently",
@@ -16289,14 +16354,14 @@ var en = {
16289
16354
  batchSelectionTitle: "Permanently delete {count} selected conversations?",
16290
16355
  batchAllTitle: "Clear all {count} archived conversations?",
16291
16356
  batchPreparingDescription: "Preparing and checking each fixed target. Closing stops targets that have not started preparation.",
16292
- batchConfirmDescription: "Review the fixed targets and resource scope before starting this irreversible batch.",
16357
+ batchConfirmDescription: "These conversations will be permanently deleted and cannot be restored.",
16293
16358
  batchRunningDescription: "Deletion runs one conversation at a time. Stop prevents unsent conversations; the current operation still finishes and remains protected.",
16294
16359
  closeBatchDeletion: "Close batch deletion",
16295
16360
  confirmBatchDeletion: "Delete executable conversations",
16296
16361
  stopBatchDeletion: "Stop remaining deletions",
16297
16362
  batchSummary: "{total} fixed \xB7 {executable} executable \xB7 {blocked} blocked",
16298
16363
  batchPreparationProgress: "Checked {prepared} of {total}",
16299
- batchScope: "Executable conversations lose their logs, session metadata, and indexes permanently. Blocked conversations are not sent for deletion.",
16364
+ batchScope: "Blocked conversations will be skipped.",
16300
16365
  batchResources: "Combined resource scope",
16301
16366
  resourceErase: "Erase",
16302
16367
  resourceReleaseReference: "Release references",
@@ -16316,11 +16381,18 @@ var en = {
16316
16381
  batchPausedDescription: "Review or handle the current operation first, then continue the remaining conversations.",
16317
16382
  batchDoneTitle: "Batch deletion complete",
16318
16383
  batchCancelledTitle: "Batch deletion stopped",
16319
- batchOutcome: "{success} deleted \xB7 {unresolved} still needs attention",
16384
+ batchOutcome: "{success} deleted \xB7 {unresolved} not deleted",
16320
16385
  continueBatchDeletion: "Continue remaining",
16321
16386
  dismissBatchDeletion: "Dismiss batch results"
16322
16387
  };
16323
16388
  var zh = {
16389
+ settings: "\u8BBE\u7F6E",
16390
+ confirmDeletionSetting: "\u6C38\u4E45\u5220\u9664\u4F1A\u8BDD\u524D\u8FDB\u884C\u786E\u8BA4",
16391
+ confirmDeletionHint: "\u9002\u7528\u4E8E\u5F53\u524D\u6D4F\u89C8\u5668\u914D\u7F6E\u4E2D\u6B64\u5BBF\u4E3B\u7684\u5355\u9879\u5220\u9664\u3001\u5220\u9664\u6240\u9009\u548C\u6E05\u7A7A\u5168\u90E8\u3002\u5173\u95ED\u540E\uFF0C\u70B9\u51FB\u5220\u9664\u5C06\u7ACB\u5373\u5F00\u59CB\u6C38\u4E45\u5220\u9664\u3002",
16392
+ batchPreparingTitle: "\u6B63\u5728\u6838\u5BF9\u4F1A\u8BDD",
16393
+ batchRunningTitle: "\u6B63\u5728\u5220\u9664\u4F1A\u8BDD",
16394
+ batchExecutionProgress: "\u5DF2\u5904\u7406 {completed}/{total} \u9879",
16395
+ batchProgressLabel: "\u5220\u9664\u8FDB\u5EA6",
16324
16396
  title: "\u4F1A\u8BDD\u56DE\u6536\u7AD9",
16325
16397
  count: "{count} \u4E2A\u4F1A\u8BDD",
16326
16398
  countOne: "{count} \u4E2A\u4F1A\u8BDD",
@@ -16368,7 +16440,7 @@ var zh = {
16368
16440
  deleteConversation: "\u6C38\u4E45\u5220\u9664{title}",
16369
16441
  deleteTitle: "\u6C38\u4E45\u5220\u9664\u201C{title}\u201D\uFF1F",
16370
16442
  deleteDescription: "\u5220\u9664\u8FD9\u4E00\u4E2A\u4F1A\u8BDD\u7684\u65E5\u5FD7\u53CA\u5176\u4F1A\u8BDD\u5143\u6570\u636E\u548C\u7D22\u5F15\u3002\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002",
16371
- deleteScope: "\u5171\u4EAB\u9644\u4EF6\u3001\u72EC\u7ACB fork \u4F1A\u8BDD\u53CA\u5916\u90E8\u5DE5\u5177\u4E2D\u7684\u526F\u672C\u4F1A\u4FDD\u7559\u3002\u5BBF\u4E3B\u4ECD\u52A0\u8F7D\u7684\u4F1A\u8BDD\u3001\u672A\u91CA\u653E\u7684\u8BFB\u53D6\u5F15\u7528\u3001\u672A\u77E5\u6587\u4EF6\u548C\u6D3B\u52A8\u5199\u5165\u8005\u53EF\u80FD\u963B\u6B62\u5220\u9664\u3002",
16443
+ deleteScope: "\u5171\u4EAB\u9644\u4EF6\u3001\u72EC\u7ACB fork \u4F1A\u8BDD\u53CA\u5916\u90E8\u5DE5\u5177\u4E2D\u7684\u526F\u672C\u4F1A\u4FDD\u7559\u3002",
16372
16444
  deletionAcknowledge: "\u6211\u7406\u89E3\u5220\u9664\u540E\u65E0\u6CD5\u6062\u590D\u8FD9\u4E2A\u4F1A\u8BDD\u3002",
16373
16445
  cancelDeletion: "\u53D6\u6D88",
16374
16446
  confirmDeletion: "\u786E\u8BA4\u6C38\u4E45\u5220\u9664",
@@ -16396,14 +16468,14 @@ var zh = {
16396
16468
  batchSelectionTitle: "\u6C38\u4E45\u5220\u9664\u6240\u9009\u7684 {count} \u4E2A\u4F1A\u8BDD\uFF1F",
16397
16469
  batchAllTitle: "\u6E05\u7A7A\u5168\u90E8 {count} \u4E2A\u5F52\u6863\u4F1A\u8BDD\uFF1F",
16398
16470
  batchPreparingDescription: "\u6B63\u5728\u51C6\u5907\u5E76\u9010\u9879\u6838\u5BF9\u56FA\u5B9A\u5BF9\u8C61\u3002\u5173\u95ED\u540E\u4F1A\u505C\u6B62\u5C1A\u672A\u5F00\u59CB\u51C6\u5907\u7684\u5BF9\u8C61\u3002",
16399
- batchConfirmDescription: "\u5F00\u59CB\u8FD9\u4E2A\u4E0D\u53EF\u9006\u6279\u6B21\u524D\uFF0C\u8BF7\u6838\u5BF9\u56FA\u5B9A\u5BF9\u8C61\u4E0E\u8D44\u6E90\u8303\u56F4\u3002",
16471
+ batchConfirmDescription: "\u4EE5\u4E0B\u4F1A\u8BDD\u5C06\u88AB\u6C38\u4E45\u5220\u9664\uFF0C\u65E0\u6CD5\u6062\u590D\u3002",
16400
16472
  batchRunningDescription: "\u5220\u9664\u4F1A\u9010\u4E2A\u4E32\u884C\u6267\u884C\u3002\u505C\u6B62\u53EA\u963B\u6B62\u5C1A\u672A\u53D1\u9001\u7684\u4F1A\u8BDD\uFF1B\u5F53\u524D\u64CD\u4F5C\u4ECD\u4F1A\u5B8C\u6210\u5E76\u7EE7\u7EED\u53D7\u4FDD\u62A4\u3002",
16401
16473
  closeBatchDeletion: "\u5173\u95ED\u6279\u91CF\u5220\u9664",
16402
16474
  confirmBatchDeletion: "\u5220\u9664\u53EF\u6267\u884C\u4F1A\u8BDD",
16403
16475
  stopBatchDeletion: "\u505C\u6B62\u5269\u4F59\u5220\u9664",
16404
16476
  batchSummary: "\u56FA\u5B9A {total} \u9879 \xB7 \u53EF\u6267\u884C {executable} \u9879 \xB7 \u963B\u6B62 {blocked} \u9879",
16405
16477
  batchPreparationProgress: "\u5DF2\u6838\u5BF9 {prepared}/{total} \u9879",
16406
- batchScope: "\u53EF\u6267\u884C\u4F1A\u8BDD\u7684\u65E5\u5FD7\u3001\u4F1A\u8BDD\u5143\u6570\u636E\u548C\u7D22\u5F15\u5C06\u88AB\u6C38\u4E45\u5220\u9664\u3002\u88AB\u963B\u6B62\u7684\u4F1A\u8BDD\u4E0D\u4F1A\u53D1\u9001\u5220\u9664\u8BF7\u6C42\u3002",
16478
+ batchScope: "\u88AB\u963B\u6B62\u7684\u4F1A\u8BDD\u4F1A\u8DF3\u8FC7\uFF0C\u4E0D\u4F1A\u5220\u9664\u3002",
16407
16479
  batchResources: "\u5408\u5E76\u8D44\u6E90\u8303\u56F4",
16408
16480
  resourceErase: "\u6E05\u9664",
16409
16481
  resourceReleaseReference: "\u89E3\u9664\u5F15\u7528",
@@ -16423,7 +16495,7 @@ var zh = {
16423
16495
  batchPausedDescription: "\u8BF7\u5148\u6838\u5BF9\u6216\u5904\u7406\u5F53\u524D\u9879\uFF0C\u518D\u7EE7\u7EED\u5269\u4F59\u4F1A\u8BDD\u3002",
16424
16496
  batchDoneTitle: "\u6279\u91CF\u5220\u9664\u5DF2\u5B8C\u6210",
16425
16497
  batchCancelledTitle: "\u6279\u91CF\u5220\u9664\u5DF2\u505C\u6B62",
16426
- batchOutcome: "\u5DF2\u5220\u9664 {success} \u9879 \xB7 \u4ECD\u9700\u5904\u7406 {unresolved} \u9879",
16498
+ batchOutcome: "\u5DF2\u5220\u9664 {success} \u9879 \xB7 \u672A\u5220\u9664 {unresolved} \u9879",
16427
16499
  continueBatchDeletion: "\u7EE7\u7EED\u5269\u4F59\u9879",
16428
16500
  dismissBatchDeletion: "\u5173\u95ED\u6279\u6B21\u7ED3\u679C"
16429
16501
  };
@@ -16446,7 +16518,6 @@ var panel_default = {
16446
16518
  rowCheck: "panel_rowCheck",
16447
16519
  rowBody: "panel_rowBody",
16448
16520
  rowTitle: "panel_rowTitle",
16449
- meta: "panel_meta",
16450
16521
  empty: "panel_empty",
16451
16522
  emptyIcon: "panel_emptyIcon",
16452
16523
  emptyTitle: "panel_emptyTitle",
@@ -16458,13 +16529,16 @@ var panel_default = {
16458
16529
  batchModalBody: "panel_batchModalBody",
16459
16530
  batchProgress: "panel_batchProgress",
16460
16531
  batchSummary: "panel_batchSummary",
16461
- resourceCounts: "panel_resourceCounts",
16462
16532
  batchItems: "panel_batchItems",
16463
16533
  batchItemTitle: "panel_batchItemTitle",
16464
16534
  batchItemStatus: "panel_batchItemStatus",
16465
16535
  batchStatus: "panel_batchStatus",
16466
16536
  batchOutcome: "panel_batchOutcome",
16467
- batchActions: "panel_batchActions"
16537
+ batchActions: "panel_batchActions",
16538
+ workspaceGroup: "panel_workspaceGroup",
16539
+ workspaceHeading: "panel_workspaceHeading",
16540
+ danger: "panel_danger",
16541
+ settings: "panel_settings"
16468
16542
  };
16469
16543
 
16470
16544
  // src/client/components.tsx
@@ -16548,39 +16622,32 @@ function batchItemText(item, t) {
16548
16622
  if (item.outcome?.status === "success") return t("batchItemDeleted");
16549
16623
  return reasonText(item.outcome?.reason ?? item.reason, t);
16550
16624
  }
16551
- function PurgeBatchDetails({ batch, t }) {
16625
+ function PurgeBatchDetails({ batch, t, failuresOnly = false }) {
16552
16626
  const counts = batchCounts(batch);
16627
+ const processed = batch.items.filter((item) => ["settled", "blocked", "cancelled"].includes(item.state)).length;
16628
+ const items = failuresOnly ? batch.items.filter((item) => item.state !== "ready" && item.outcome?.status !== "success" && item.state !== "cancelled") : batch.items;
16553
16629
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
16554
- batch.phase === "preparing" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.batchProgress, role: "status", children: t("batchPreparationProgress", {
16555
- prepared: counts.prepared,
16556
- total: batch.frozenCount
16557
- }) }),
16558
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.batchSummary, children: t("batchSummary", {
16559
- total: batch.frozenCount,
16560
- executable: counts.executable,
16561
- blocked: counts.blocked
16562
- }) }),
16563
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.description, children: t("batchScope") }),
16564
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: panel_default.resourceCounts, "aria-label": t("batchResources"), children: [
16565
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
16566
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("resourceErase") }),
16567
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: batch.resourceCounts.erase })
16568
- ] }),
16569
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
16570
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("resourceReleaseReference") }),
16571
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: batch.resourceCounts.releaseReference })
16572
- ] }),
16573
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
16574
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("resourceRetainShared") }),
16575
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: batch.resourceCounts.retainShared })
16576
- ] }),
16577
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
16578
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: t("resourceRetainCoordination") }),
16579
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: batch.resourceCounts.retainCoordination })
16580
- ] })
16630
+ ["preparing", "running"].includes(batch.phase) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: panel_default.batchProgress, role: "status", children: [
16631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: batch.phase === "preparing" ? t("batchPreparationProgress", { prepared: counts.prepared, total: batch.frozenCount }) : t("batchExecutionProgress", { completed: processed, total: batch.frozenCount }) }),
16632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16633
+ "progress",
16634
+ {
16635
+ "aria-label": t("batchProgressLabel"),
16636
+ max: batch.frozenCount,
16637
+ value: batch.phase === "preparing" ? counts.prepared : processed
16638
+ }
16639
+ )
16581
16640
  ] }),
16582
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.description, children: t("batchRetainedCopies") }),
16583
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: panel_default.batchItems, "aria-label": t("batchItems"), children: batch.items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
16641
+ batch.phase === "confirming" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
16642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.batchSummary, children: t("batchSummary", {
16643
+ total: batch.frozenCount,
16644
+ executable: counts.executable,
16645
+ blocked: counts.blocked
16646
+ }) }),
16647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.description, children: t("batchScope") }),
16648
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.description, children: t("batchRetainedCopies") })
16649
+ ] }),
16650
+ items.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: panel_default.batchItems, "aria-label": t("batchItems"), children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
16584
16651
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: panel_default.batchItemTitle, children: item.target.title }),
16585
16652
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: panel_default.batchItemStatus, children: batchItemText(item, t) })
16586
16653
  ] }, `${item.target.entryId}-${index}`)) })
@@ -16591,7 +16658,7 @@ function BinNotice({ useBin, model, t }) {
16591
16658
  const notice = state.notice;
16592
16659
  const confirmation = state.purgeConfirmation;
16593
16660
  const batch = state.purgeBatch ?? null;
16594
- const batchModal = !confirmation && batch && ["preparing", "confirming", "running"].includes(batch.phase) ? batch : null;
16661
+ const batchModal = !confirmation && batch && batch.phase === "confirming" ? batch : null;
16595
16662
  const blocked = confirmation && (confirmation.plan.blockers.length > 0 || !confirmation.plan.binding || !confirmation.plan.manifest);
16596
16663
  const text = notice?.kind === "unarchived" ? t("unarchived") : notice?.kind === "deleted" ? t("deleted") : reasonText(notice?.reason ?? null, t);
16597
16664
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -16620,8 +16687,9 @@ function BinNotice({ useBin, model, t }) {
16620
16687
  {
16621
16688
  variant: "outline",
16622
16689
  style: { color: "var(--dsw-alias-state-error-primary)" },
16623
- disabled: Boolean(blocked) || !confirmation.acknowledged || state.phase !== "ready" || Boolean(state.purgeCacheBlocked),
16690
+ disabled: Boolean(blocked) || state.phase !== "ready" || Boolean(state.purgeCacheBlocked),
16624
16691
  onClick: () => {
16692
+ model.acknowledgePurge(true);
16625
16693
  void model.confirmPurge();
16626
16694
  },
16627
16695
  children: t("confirmDeletion")
@@ -16630,16 +16698,7 @@ function BinNotice({ useBin, model, t }) {
16630
16698
  ] }),
16631
16699
  children: [
16632
16700
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.description, children: t("deleteScope") }),
16633
- confirmation.plan.blockers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { "aria-label": t("deletionBlockers"), role: "alert", children: confirmation.plan.blockers.map((blocker, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: reasonText(blocker.code, t) }, `${blocker.code}-${index}`)) }),
16634
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16635
- import_dsh_client_ui_primitives.Checkbox,
16636
- {
16637
- label: t("deletionAcknowledge"),
16638
- checked: confirmation.acknowledged,
16639
- disabled: Boolean(blocked) || Boolean(state.purgeCacheBlocked),
16640
- onChange: (checked) => model.acknowledgePurge(checked)
16641
- }
16642
- )
16701
+ confirmation.plan.blockers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { "aria-label": t("deletionBlockers"), role: "alert", children: confirmation.plan.blockers.map((blocker, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: reasonText(blocker.code, t) }, `${blocker.code}-${index}`)) })
16643
16702
  ]
16644
16703
  }
16645
16704
  ),
@@ -16649,43 +16708,25 @@ function BinNotice({ useBin, model, t }) {
16649
16708
  open: true,
16650
16709
  title: t(batchModal.scope === "selection" ? "batchSelectionTitle" : "batchAllTitle", { count: batchModal.frozenCount }),
16651
16710
  closeLabel: t("closeBatchDeletion"),
16652
- description: t(batchModal.phase === "preparing" ? "batchPreparingDescription" : batchModal.phase === "running" ? "batchRunningDescription" : "batchConfirmDescription"),
16711
+ description: t("batchConfirmDescription"),
16653
16712
  onClose: () => model.stopPurgeBatch(),
16654
- footer: batchModal.phase === "confirming" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
16713
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
16655
16714
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", "data-modal-autofocus": true, onClick: () => model.stopPurgeBatch(), children: t("cancelDeletion") }),
16656
16715
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16657
16716
  import_dsh_client_ui_primitives.Button,
16658
16717
  {
16659
16718
  variant: "outline",
16660
16719
  style: { color: "var(--dsw-alias-state-error-primary)" },
16661
- disabled: !batchModal.acknowledged || batchCounts(batchModal).executable === 0 || state.phase !== "ready" || Boolean(state.purgeCacheBlocked),
16720
+ disabled: batchCounts(batchModal).executable === 0 || state.phase !== "ready" || Boolean(state.purgeCacheBlocked),
16662
16721
  onClick: () => {
16722
+ model.acknowledgePurgeBatch(true);
16663
16723
  void model.runPurgeBatch();
16664
16724
  },
16665
16725
  children: t("confirmBatchDeletion")
16666
16726
  }
16667
16727
  )
16668
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16669
- import_dsh_client_ui_primitives.Button,
16670
- {
16671
- variant: "outline",
16672
- "data-modal-autofocus": true,
16673
- onClick: () => model.stopPurgeBatch(),
16674
- children: t(batchModal.phase === "running" ? "stopBatchDeletion" : "cancelDeletion")
16675
- }
16676
- ),
16677
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: panel_default.batchModalBody, children: [
16678
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PurgeBatchDetails, { batch: batchModal, t }),
16679
- batchModal.phase === "confirming" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16680
- import_dsh_client_ui_primitives.Checkbox,
16681
- {
16682
- label: t("batchAcknowledge"),
16683
- checked: batchModal.acknowledged,
16684
- disabled: batchCounts(batchModal).executable === 0 || Boolean(state.purgeCacheBlocked),
16685
- onChange: (checked) => model.acknowledgePurgeBatch(checked)
16686
- }
16687
- )
16688
- ] })
16728
+ ] }),
16729
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.batchModalBody, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PurgeBatchDetails, { batch: batchModal, t }) })
16689
16730
  }
16690
16731
  )
16691
16732
  ] });
@@ -16700,6 +16741,7 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16700
16741
  const [workspaceFilter, setWorkspaceFilter] = (0, import_react.useState)("all");
16701
16742
  const [selected, setSelected] = (0, import_react.useState)(() => /* @__PURE__ */ new Set());
16702
16743
  const [batchBusy, setBatchBusy] = (0, import_react.useState)(false);
16744
+ const [settingsOpen, setSettingsOpen] = (0, import_react.useState)(false);
16703
16745
  const ready = state.phase === "ready";
16704
16746
  const unconfirmed = state.pending.filter((plan) => !state.busy.includes(plan.sessionId));
16705
16747
  const legacyPending = unconfirmed.some((plan) => plan.schemaVersion === 1);
@@ -16718,6 +16760,11 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16718
16760
  return { entry, title, workspaceId: workspace?.workspaceId ?? "ungrouped", workspaceTitle: workspace ? workspace.title || unnamedWorkspace : ungrouped };
16719
16761
  }), [state.entries, sessions, workspaces, unnamed, ungrouped, unnamedWorkspace]);
16720
16762
  const visible = rows.filter((row) => (workspaceFilter === "all" || row.workspaceId === workspaceFilter) && `${row.title} ${row.workspaceTitle}`.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
16763
+ const groups = [...new Set(visible.map((row) => row.workspaceId))].map((id) => ({
16764
+ id,
16765
+ title: visible.find((row) => row.workspaceId === id).workspaceTitle,
16766
+ rows: visible.filter((row) => row.workspaceId === id)
16767
+ }));
16721
16768
  const targets = rows.filter((row) => selected.has(row.entry.entryId));
16722
16769
  const allSelected = visible.length > 0 && visible.every((row) => selected.has(row.entry.entryId));
16723
16770
  (0, import_react.useEffect)(() => {
@@ -16757,11 +16804,11 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16757
16804
  }
16758
16805
  };
16759
16806
  const titlesBySessionId = () => Object.fromEntries(rows.map((row) => [row.entry.sessionId, row.title]));
16760
- const prepareSelectedPurge = () => model.preparePurgeBatch(
16807
+ const prepareSelectedPurge = () => model.requestPurgeBatch(
16761
16808
  { kind: "selection", entryIds: targets.map((row) => row.entry.entryId) },
16762
16809
  titlesBySessionId()
16763
16810
  );
16764
- const prepareAllPurge = () => model.preparePurgeBatch({ kind: "all-archived" }, titlesBySessionId());
16811
+ const prepareAllPurge = () => model.requestPurgeBatch({ kind: "all-archived" }, titlesBySessionId());
16765
16812
  const newPurgeDisabled = !ready || activePurgeBatch || purgeCacheBlocked || Boolean(state.purgeConfirmation) || purgePending.length > 0 || state.busy.length > 0;
16766
16813
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: panel_default.panel, "aria-label": t("title"), children: [
16767
16814
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: panel_default.heading, children: [
@@ -16774,7 +16821,18 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16774
16821
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16775
16822
  import_dsh_client_ui_primitives.Button,
16776
16823
  {
16777
- variant: "outline",
16824
+ variant: "ghost",
16825
+ size: "sm",
16826
+ "aria-expanded": settingsOpen,
16827
+ onClick: () => setSettingsOpen((open) => !open),
16828
+ children: t("settings")
16829
+ }
16830
+ ),
16831
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16832
+ import_dsh_client_ui_primitives.Button,
16833
+ {
16834
+ variant: "ghost",
16835
+ className: panel_default.danger,
16778
16836
  size: "sm",
16779
16837
  disabled: newPurgeDisabled || state.entries.length === 0,
16780
16838
  onClick: () => {
@@ -16798,6 +16856,17 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16798
16856
  )
16799
16857
  ] })
16800
16858
  ] }),
16859
+ settingsOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: panel_default.settings, "aria-label": t("settings"), children: [
16860
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16861
+ import_dsh_client_ui_primitives.Checkbox,
16862
+ {
16863
+ label: t("confirmDeletionSetting"),
16864
+ checked: state.confirmDeletion !== false,
16865
+ onChange: (checked) => model.setConfirmDeletion(checked)
16866
+ }
16867
+ ),
16868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.description, children: t("confirmDeletionHint") })
16869
+ ] }),
16801
16870
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: panel_default.toolbar, children: [
16802
16871
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16803
16872
  import_dsh_client_ui_primitives.Input,
@@ -16843,7 +16912,7 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16843
16912
  void model.checkPending();
16844
16913
  }, children: t(pendingLabel) })
16845
16914
  ] }),
16846
- purgePending.map((plan) => {
16915
+ purgePending.filter((plan) => !purgeBatch?.items.some((item) => item.state === "running" && item.plan?.operationId === plan.operationId)).map((plan) => {
16847
16916
  const title = sessions.byId[plan.sessionId]?.title || unnamed;
16848
16917
  const result = purgeResults.find((row) => row.operationId === plan.operationId);
16849
16918
  const missing = result?.reason === "deletion-result-missing";
@@ -16877,73 +16946,84 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16877
16946
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: panel_default.emptyIcon, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 32 }) }),
16878
16947
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.emptyTitle, children: t(rows.length === 0 ? "empty" : "noMatches") }),
16879
16948
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.emptyHint, children: t(rows.length === 0 ? "emptyHint" : "noMatchesHint") })
16880
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: panel_default.list, "aria-label": t("entries"), children: visible.map(({ entry, title, workspaceTitle }) => {
16881
- const disabled = !ready || batchBusy || activePurgeBatch || state.busy.includes(entry.sessionId) || Boolean(state.purgeConfirmation) || purgePending.some((plan) => plan.sessionId === entry.sessionId) || state.pending.some((plan) => plan.schemaVersion === 2 && plan.sessionId === entry.sessionId);
16882
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: panel_default.row, children: [
16883
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16884
- import_dsh_client_ui_primitives.Checkbox,
16885
- {
16886
- className: panel_default.rowCheck,
16887
- label: t("select", { title }),
16888
- checked: selected.has(entry.entryId),
16889
- disabled,
16890
- onChange: (checked) => toggle(entry.entryId, checked)
16891
- }
16892
- ),
16893
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: panel_default.rowBody, children: [
16894
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.rowTitle, title, children: title }),
16895
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.meta, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: workspaceTitle }) })
16896
- ] }),
16897
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16898
- import_dsh_client_ui_primitives.Button,
16899
- {
16900
- variant: "outline",
16901
- size: "sm",
16902
- disabled,
16903
- onClick: () => {
16904
- void model.unarchive(entry);
16905
- },
16906
- children: t("unarchive")
16907
- }
16908
- ),
16909
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16910
- import_dsh_client_ui_primitives.Button,
16911
- {
16912
- variant: "ghost",
16913
- size: "sm",
16914
- disabled: disabled || purgeCacheBlocked,
16915
- "aria-label": t("deleteConversation", { title }),
16916
- onClick: () => {
16917
- void model.preparePurge(entry, title);
16918
- },
16919
- children: t("permanentDelete")
16920
- }
16921
- )
16922
- ] }, entry.entryId);
16923
- }) }) }),
16949
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-label": t("entries"), children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: panel_default.workspaceGroup, "aria-label": group.title, children: [
16950
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { className: panel_default.workspaceHeading, children: [
16951
+ group.title,
16952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: group.rows.length })
16953
+ ] }),
16954
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: panel_default.list, "aria-label": t("entries"), children: group.rows.map(({ entry, title }) => {
16955
+ const disabled = !ready || batchBusy || activePurgeBatch || state.busy.includes(entry.sessionId) || Boolean(state.purgeConfirmation) || purgePending.some((plan) => plan.sessionId === entry.sessionId) || state.pending.some((plan) => plan.schemaVersion === 2 && plan.sessionId === entry.sessionId);
16956
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: panel_default.row, children: [
16957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16958
+ import_dsh_client_ui_primitives.Checkbox,
16959
+ {
16960
+ className: panel_default.rowCheck,
16961
+ label: t("select", { title }),
16962
+ checked: selected.has(entry.entryId),
16963
+ disabled,
16964
+ onChange: (checked) => toggle(entry.entryId, checked)
16965
+ }
16966
+ ),
16967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.rowBody, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.rowTitle, title, children: title }) }),
16968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16969
+ import_dsh_client_ui_primitives.Button,
16970
+ {
16971
+ variant: "outline",
16972
+ size: "sm",
16973
+ disabled,
16974
+ onClick: () => {
16975
+ void model.unarchive(entry);
16976
+ },
16977
+ children: t("unarchive")
16978
+ }
16979
+ ),
16980
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16981
+ import_dsh_client_ui_primitives.Button,
16982
+ {
16983
+ variant: "ghost",
16984
+ className: panel_default.danger,
16985
+ size: "sm",
16986
+ disabled: disabled || purgeCacheBlocked,
16987
+ "aria-label": t("deleteConversation", { title }),
16988
+ onClick: () => {
16989
+ void model.requestPurge(entry, title);
16990
+ },
16991
+ children: t("permanentDelete")
16992
+ }
16993
+ )
16994
+ ] }, entry.entryId);
16995
+ }) })
16996
+ ] }, group.id)) }) }),
16924
16997
  state.results.length > 0 && state.results.some((result) => result.status !== "success") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: panel_default.results, "aria-label": t("results"), "aria-live": "polite", children: state.results.map((result) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: panel_default.result, children: [
16925
16998
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: sessions.byId[result.sessionId]?.title || t("unnamed") }),
16926
16999
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: result.status === "success" ? t("resultSuccess") : reasonText(result.reason, t) })
16927
17000
  ] }, result.sessionId)) }),
16928
- purgeResults.length > 0 && purgeResults.some((result) => result.status !== "success") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: panel_default.results, "aria-label": t("deletionResults"), "aria-live": "polite", children: purgeResults.map((result) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: panel_default.result, children: [
17001
+ purgeResults.length > 0 && purgeResults.some((result) => result.status !== "success") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: panel_default.results, "aria-label": t("deletionResults"), "aria-live": "polite", children: purgeResults.filter((result) => result.status !== "success" && !purgeBatch?.items.some((item) => item.outcome?.operationId === result.operationId) && !purgePending.some((plan) => plan.operationId === result.operationId)).map((result) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: panel_default.result, children: [
16929
17002
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: sessions.byId[result.sessionId]?.title || unnamed }),
16930
17003
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: result.status === "success" ? t("deleted") : result.status === "partial-failure" ? t("deletionPartial") : reasonText(result.reason, t) })
16931
17004
  ] }, result.operationId)) }),
16932
- purgeBatch && ["paused", "done", "cancelled"].includes(purgeBatch.phase) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
17005
+ purgeBatch && purgeBatch.phase !== "confirming" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
16933
17006
  "section",
16934
17007
  {
16935
17008
  className: panel_default.batchStatus,
16936
17009
  "aria-label": t("batchStatus"),
16937
17010
  "aria-live": "polite",
16938
17011
  children: [
16939
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t(purgeBatch.phase === "paused" ? "batchPausedTitle" : purgeBatch.phase === "done" ? "batchDoneTitle" : "batchCancelledTitle") }),
17012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t(purgeBatch.phase === "preparing" ? "batchPreparingTitle" : purgeBatch.phase === "running" ? "batchRunningTitle" : purgeBatch.phase === "paused" ? "batchPausedTitle" : purgeBatch.phase === "done" ? "batchDoneTitle" : "batchCancelledTitle") }),
16940
17013
  purgeBatch.phase === "paused" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.description, children: t("batchPausedDescription") }),
16941
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PurgeBatchDetails, { batch: purgeBatch, t }),
16942
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.batchOutcome, children: t("batchOutcome", {
17014
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.batchModalBody, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
17015
+ PurgeBatchDetails,
17016
+ {
17017
+ batch: purgeBatch,
17018
+ t,
17019
+ failuresOnly: ["paused", "done", "cancelled"].includes(purgeBatch.phase)
17020
+ }
17021
+ ) }),
17022
+ !["preparing", "running"].includes(purgeBatch.phase) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: panel_default.batchOutcome, children: t("batchOutcome", {
16943
17023
  success: batchCounts(purgeBatch).completed,
16944
17024
  unresolved: batchCounts(purgeBatch).unresolved
16945
17025
  }) }),
16946
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.batchActions, children: purgeBatch.phase === "paused" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
17026
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: panel_default.batchActions, children: ["preparing", "running"].includes(purgeBatch.phase) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", size: "sm", onClick: () => model.stopPurgeBatch(), children: t("stopBatchDeletion") }) : purgeBatch.phase === "paused" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
16947
17027
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16948
17028
  import_dsh_client_ui_primitives.Button,
16949
17029
  {
@@ -16960,14 +17040,15 @@ function BinPanel({ useBin, useSessions, useWorkspaces, model, t }) {
16960
17040
  ]
16961
17041
  }
16962
17042
  ),
16963
- targets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: panel_default.selection, children: [
17043
+ targets.length > 0 && !["preparing", "running"].includes(purgeBatch?.phase ?? "") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: panel_default.selection, children: [
16964
17044
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("selected", { count: targets.length }) }),
16965
17045
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: panel_default.selectionActions, children: [
16966
17046
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: batchBusy || activePurgeBatch, onClick: () => setSelected(/* @__PURE__ */ new Set()), children: t("clearSelection") }),
16967
17047
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
16968
17048
  import_dsh_client_ui_primitives.Button,
16969
17049
  {
16970
- variant: "outline",
17050
+ variant: "ghost",
17051
+ className: panel_default.danger,
16971
17052
  size: "sm",
16972
17053
  disabled: newPurgeDisabled,
16973
17054
  onClick: () => {
@@ -17002,11 +17083,16 @@ async function initialize(ctx) {
17002
17083
  cache = browserPendingCache(window.sessionStorage);
17003
17084
  } catch {
17004
17085
  }
17086
+ let preferences;
17087
+ try {
17088
+ preferences = browserDeletionPreferences(window.localStorage, window);
17089
+ } catch {
17090
+ }
17005
17091
  const model = new SessionBinClientModel(ctx.remote.sessionBin, () => ctx.remote.$stream({
17006
17092
  name: "sessionBin.follow",
17007
17093
  open: (signal) => ctx.remote.sessionBin.follow(signal),
17008
17094
  ended: () => new Error("Session Bin snapshot stream ended.")
17009
- }), cache);
17095
+ }), cache, preferences);
17010
17096
  ctx.effect(() => () => model.dispose(), "session-bin.model");
17011
17097
  const face = () => ({
17012
17098
  hooks: { bin: model },