dsh-rewind-plugin 0.6.3 → 0.7.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
@@ -319,6 +319,249 @@ var STYLE = `
319
319
  color: var(--dsw-alias-label-primary);
320
320
  pointer-events: none;
321
321
  }
322
+
323
+ /* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */
324
+ .dsh-rewind-cleanup-card {
325
+ list-style: none;
326
+ border: 1px solid var(--dsw-alias-border-l2);
327
+ border-radius: 12px;
328
+ background: var(--dsw-alias-bg-layer-3);
329
+ transition: border-color .16s, background .16s;
330
+ }
331
+ .dsh-rewind-cleanup-card:hover {
332
+ border-color: var(--dsw-alias-label-dimmed);
333
+ }
334
+ .dsh-rewind-cleanup-card-open {
335
+ background: var(--dsw-alias-bg-layer-2);
336
+ border-color: var(--dsw-alias-label-dimmed);
337
+ }
338
+ .dsh-rewind-cleanup-header {
339
+ width: 100%;
340
+ appearance: none;
341
+ border: 0;
342
+ background: none;
343
+ font: inherit;
344
+ color: inherit;
345
+ text-align: left;
346
+ cursor: pointer;
347
+ display: flex;
348
+ align-items: center;
349
+ gap: 12px;
350
+ padding: 14px 16px;
351
+ border-radius: 12px;
352
+ }
353
+ .dsh-rewind-cleanup-header:focus-visible {
354
+ outline: 2px solid var(--dsw-alias-brand-primary);
355
+ outline-offset: -2px;
356
+ }
357
+ .dsh-rewind-cleanup-head-text {
358
+ flex: 1;
359
+ min-width: 0;
360
+ display: flex;
361
+ flex-direction: column;
362
+ gap: 4px;
363
+ }
364
+ .dsh-rewind-cleanup-name {
365
+ font-size: 15px;
366
+ font-weight: 600;
367
+ line-height: 1.4;
368
+ color: var(--dsw-alias-label-primary);
369
+ }
370
+ .dsh-rewind-cleanup-desc {
371
+ font-size: 13px;
372
+ line-height: 1.5;
373
+ color: var(--dsw-alias-label-tertiary);
374
+ }
375
+ .dsh-rewind-cleanup-chevron {
376
+ flex: none;
377
+ color: var(--dsw-alias-label-tertiary);
378
+ transition: transform .16s;
379
+ }
380
+ .dsh-rewind-cleanup-chevron-open {
381
+ transform: rotate(180deg);
382
+ }
383
+ .dsh-rewind-cleanup-pending {
384
+ flex: none;
385
+ border-radius: 999px;
386
+ padding: 1px 8px;
387
+ font-size: 11px;
388
+ line-height: 17px;
389
+ font-weight: 500;
390
+ white-space: nowrap;
391
+ background: var(--dsw-alias-bg-module-platform);
392
+ color: var(--dsw-alias-label-secondary);
393
+ }
394
+ .dsh-rewind-cleanup-body {
395
+ border-top: 1px solid var(--dsw-alias-border-l2);
396
+ margin: 0 16px;
397
+ padding: 4px 0 8px;
398
+ }
399
+ .dsh-rewind-cleanup-readonly {
400
+ margin: 12px 0 0;
401
+ font-size: 12px;
402
+ line-height: 1.5;
403
+ color: var(--dsw-alias-label-tertiary);
404
+ }
405
+ .dsh-rewind-cleanup-permission {
406
+ display: grid;
407
+ gap: 6px;
408
+ padding: 12px 0;
409
+ }
410
+ .dsh-rewind-cleanup-field {
411
+ display: flex;
412
+ flex-direction: column;
413
+ gap: 6px;
414
+ padding: 12px 0;
415
+ }
416
+ .dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {
417
+ border-top: 1px solid var(--dsw-alias-border-l2);
418
+ }
419
+ .dsh-rewind-cleanup-head {
420
+ display: flex;
421
+ align-items: center;
422
+ gap: 8px;
423
+ }
424
+ .dsh-rewind-cleanup-label {
425
+ flex: 1;
426
+ min-width: 0;
427
+ font-size: 13px;
428
+ font-weight: 500;
429
+ line-height: 1.5;
430
+ color: var(--dsw-alias-label-primary);
431
+ }
432
+ .dsh-rewind-cleanup-hint {
433
+ margin: 0;
434
+ font-size: 12px;
435
+ line-height: 1.5;
436
+ color: var(--dsw-alias-label-tertiary);
437
+ }
438
+ .dsh-rewind-cleanup-error {
439
+ margin: 0;
440
+ font-size: 12px;
441
+ line-height: 1.5;
442
+ color: var(--dsw-alias-label-error);
443
+ }
444
+ /* Switch row: label left, role=switch button right, hint below (Subagent module). */
445
+ .dsh-rewind-cleanup-toggle-row {
446
+ display: flex;
447
+ align-items: flex-start;
448
+ justify-content: space-between;
449
+ gap: 16px;
450
+ font-size: 13px;
451
+ line-height: 1.5;
452
+ color: var(--dsw-alias-label-primary);
453
+ }
454
+ .dsh-rewind-cleanup-toggle-label {
455
+ flex: 1;
456
+ min-width: 0;
457
+ }
458
+ .dsh-rewind-cleanup-switch {
459
+ box-sizing: border-box;
460
+ position: relative;
461
+ flex: 0 0 auto;
462
+ width: 36px;
463
+ height: 20px;
464
+ padding: 2px;
465
+ border: 0;
466
+ border-radius: 10px;
467
+ background: var(--dsw-alias-border-l3);
468
+ cursor: pointer;
469
+ }
470
+ .dsh-rewind-cleanup-switch-on {
471
+ background: var(--dsw-alias-brand-primary);
472
+ }
473
+ .dsh-rewind-cleanup-switch:disabled {
474
+ cursor: default;
475
+ opacity: 0.5;
476
+ }
477
+ .dsh-rewind-cleanup-switch:focus-visible {
478
+ outline: 2px solid var(--dsw-alias-brand-primary);
479
+ outline-offset: 2px;
480
+ }
481
+ .dsh-rewind-cleanup-thumb {
482
+ display: block;
483
+ width: 16px;
484
+ height: 16px;
485
+ border-radius: 50%;
486
+ background: var(--dsw-alias-label-primary-foreground);
487
+ transition: transform 120ms ease;
488
+ }
489
+ .dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {
490
+ transform: translateX(16px);
491
+ }
492
+ .dsh-rewind-cleanup-input {
493
+ box-sizing: border-box;
494
+ height: 34px;
495
+ padding: 0 12px;
496
+ border: 1px solid var(--dsw-alias-border-l2);
497
+ border-radius: 8px;
498
+ background: var(--dsw-alias-bg-layer-3);
499
+ font: inherit;
500
+ font-size: 13px;
501
+ line-height: 1.5;
502
+ color: var(--dsw-alias-label-primary);
503
+ }
504
+ .dsh-rewind-cleanup-input:focus-visible {
505
+ outline: none;
506
+ border-color: var(--dsw-alias-brand-primary);
507
+ }
508
+ .dsh-rewind-cleanup-input:disabled {
509
+ color: var(--dsw-alias-label-tertiary);
510
+ cursor: default;
511
+ }
512
+ .dsh-rewind-cleanup-input-invalid {
513
+ border-color: var(--dsw-alias-label-error);
514
+ }
515
+ .dsh-rewind-cleanup-footer {
516
+ display: flex;
517
+ align-items: center;
518
+ justify-content: flex-end;
519
+ gap: 8px;
520
+ padding: 12px 0 4px;
521
+ border-top: 1px solid var(--dsw-alias-border-l2);
522
+ }
523
+ .dsh-rewind-cleanup-failed {
524
+ flex: 1;
525
+ min-width: 0;
526
+ margin: 0;
527
+ font-size: 12px;
528
+ line-height: 1.5;
529
+ color: var(--dsw-alias-label-error);
530
+ }
531
+ .dsh-rewind-cleanup-discard,
532
+ .dsh-rewind-cleanup-save {
533
+ appearance: none;
534
+ border: 1px solid transparent;
535
+ border-radius: 8px;
536
+ padding: 5px 14px;
537
+ font: inherit;
538
+ font-size: 13px;
539
+ line-height: 1.5;
540
+ cursor: pointer;
541
+ }
542
+ .dsh-rewind-cleanup-discard {
543
+ border-color: var(--dsw-alias-border-l2);
544
+ background: none;
545
+ color: var(--dsw-alias-label-secondary);
546
+ }
547
+ .dsh-rewind-cleanup-discard:hover:not(:disabled) {
548
+ color: var(--dsw-alias-label-primary);
549
+ border-color: var(--dsw-alias-label-dimmed);
550
+ }
551
+ .dsh-rewind-cleanup-save {
552
+ background: var(--dsw-alias-label-primary);
553
+ color: var(--dsw-alias-bg-layer-3);
554
+ }
555
+ .dsh-rewind-cleanup-discard:disabled,
556
+ .dsh-rewind-cleanup-save:disabled {
557
+ opacity: 0.4;
558
+ cursor: default;
559
+ }
560
+ .dsh-rewind-cleanup-discard:focus-visible,
561
+ .dsh-rewind-cleanup-save:focus-visible {
562
+ outline: 2px solid var(--dsw-alias-brand-primary);
563
+ outline-offset: 1px;
564
+ }
322
565
  `;
323
566
 
324
567
  // src/client/popover.ts
@@ -841,6 +1084,40 @@ function sameTargets(left, right) {
841
1084
  return other.kind === "pending" && target.itemId === other.itemId;
842
1085
  });
843
1086
  }
1087
+ function hasExecutedRewindCommand(chat) {
1088
+ for (const key of chat.order) {
1089
+ const node = chat.nodes.get(key);
1090
+ if (node === void 0 || node.kind !== "command") continue;
1091
+ const command = node.data;
1092
+ if (command.name !== "rewind") continue;
1093
+ const args = command.args ?? "";
1094
+ if (args.includes("preview") || args.includes("__candidates")) continue;
1095
+ return true;
1096
+ }
1097
+ return false;
1098
+ }
1099
+ function describeHiding(chat, hiddenSeqs) {
1100
+ const commands = [];
1101
+ for (const key of chat.order) {
1102
+ const node = chat.nodes.get(key);
1103
+ if (node === void 0 || node.kind !== "command") continue;
1104
+ const c = node.data;
1105
+ commands.push(`#${c.seq} name=${c.name} outcome=${c.outcome?.kind ?? "-"} marker=${c.outcome?.sourceEventSeq ?? "-"} args=${JSON.stringify(c.args)}`);
1106
+ }
1107
+ const scoped = hiddenSeqs.size > 0 ? `hidden=[${[...hiddenSeqs].slice(0, 20).join(",")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]` : "";
1108
+ let seats = 0;
1109
+ let resolved = 0;
1110
+ let inHidden = 0;
1111
+ for (const seat of document.querySelectorAll(CHAT_SEAT_SELECTOR)) {
1112
+ seats += 1;
1113
+ const key = seat.dataset.chatAnchorKey;
1114
+ const node = key === void 0 ? void 0 : chat.nodes.get(key);
1115
+ if (node === void 0) continue;
1116
+ resolved += 1;
1117
+ if (hiddenSeqs.has(node.anchorSeq)) inHidden += 1;
1118
+ }
1119
+ return `commands=[${commands.join(" | ")}] ${scoped} seats=${seats} resolved=${resolved} inHidden=${inHidden}`;
1120
+ }
844
1121
  function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }) {
845
1122
  const [targets, setTargets] = (0, import_react.useState)([]);
846
1123
  const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
@@ -881,6 +1158,9 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
881
1158
  `[dsh-rewind] hiding: ${hiddenCount} rows, seqs [${[...hiddenSeqs].slice(0, 20).join(", ")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]`
882
1159
  );
883
1160
  }
1161
+ if (chat !== void 0 && hiddenCount === 0 && hasExecutedRewindCommand(chat)) {
1162
+ console.warn(`[dsh-rewind] rewind not hidden: ${describeHiding(chat, hiddenSeqs)}`);
1163
+ }
884
1164
  const durable = chat === void 0 ? [] : collectTargets(chat, hiddenSeqs);
885
1165
  const next = [...durable, ...collectPendingTargets(snapshot)];
886
1166
  setTargets((current) => sameTargets(current, next) ? current : next);
@@ -1042,7 +1322,24 @@ var zh = {
1042
1322
  "popover.impact.restore": "\u8FD8\u539F {path}",
1043
1323
  "popover.impact.delete": "\u5220\u9664 {path}",
1044
1324
  "popover.confirm": "\u786E\u8BA4\u56DE\u9000",
1045
- "popover.back": "\u8FD4\u56DE"
1325
+ "popover.back": "\u8FD4\u56DE",
1326
+ "cleanup.title": "\u5FEB\u7167\u6E05\u7406",
1327
+ "cleanup.desc": "dsh-rewind \xB7 \u7BA1\u7406\u4F1A\u8BDD\u5FEB\u7167\u5907\u4EFD\u7684\u81EA\u52A8\u6E05\u7406\u7B56\u7565",
1328
+ "cleanup.expand": "\u5C55\u5F00",
1329
+ "cleanup.collapse": "\u6536\u8D77",
1330
+ "cleanup.unsaved": "\u672A\u4FDD\u5B58\u4FEE\u6539",
1331
+ "cleanup.auto": "\u81EA\u52A8\u6E05\u7406",
1332
+ "cleanup.auto.on": "\u5F00\u542F\u540E\u6309\u4E0D\u6D3B\u8DC3\u5929\u6570\u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\u5FEB\u7167",
1333
+ "cleanup.auto.off": "\u5173\u95ED\u540E\u4FDD\u7559\u5168\u90E8\u5FEB\u7167\uFF0C\u4E0D\u81EA\u52A8\u6E05\u7406",
1334
+ "cleanup.maxAge": "\u4E0D\u6D3B\u8DC3\u65F6\u95F4\uFF08\u5929\uFF09",
1335
+ "cleanup.maxAge.hint": "\u8D85\u8FC7\u8BE5\u5929\u6570\u672A\u6D3B\u52A8\u7684\u4F1A\u8BDD\u5FEB\u7167\u4F1A\u88AB\u6E05\u7406",
1336
+ "cleanup.invalid": "\u4EC5\u63A5\u53D7\u6B63\u6574\u6570",
1337
+ "cleanup.discard": "\u653E\u5F03\u4FEE\u6539",
1338
+ "cleanup.save": "\u4FDD\u5B58",
1339
+ "cleanup.saving": "\u4FDD\u5B58\u4E2D\u2026",
1340
+ "cleanup.saved": "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548",
1341
+ "cleanup.saveFailed": "\u4FDD\u5B58\u5931\u8D25\uFF1A{message}",
1342
+ "cleanup.readonly": "\u8BBE\u7F6E\u6E90\u53EA\u8BFB"
1046
1343
  };
1047
1344
  var en = {
1048
1345
  "button.aria": "Rewind to this message",
@@ -1068,9 +1365,189 @@ var en = {
1068
1365
  "popover.impact.restore": "Restore {path}",
1069
1366
  "popover.impact.delete": "Delete {path}",
1070
1367
  "popover.confirm": "Confirm rewind",
1071
- "popover.back": "Back"
1368
+ "popover.back": "Back",
1369
+ "cleanup.title": "Snapshot cleanup",
1370
+ "cleanup.desc": "dsh-rewind \xB7 Manage the auto-cleanup policy of session snapshot backups",
1371
+ "cleanup.expand": "Expand",
1372
+ "cleanup.collapse": "Collapse",
1373
+ "cleanup.unsaved": "Unsaved changes",
1374
+ "cleanup.auto": "Auto cleanup",
1375
+ "cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically",
1376
+ "cleanup.auto.off": "When disabled, all snapshot backups are kept; nothing is cleaned automatically.",
1377
+ "cleanup.maxAge": "Idle time (days)",
1378
+ "cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned",
1379
+ "cleanup.invalid": "A positive integer only",
1380
+ "cleanup.discard": "Discard changes",
1381
+ "cleanup.save": "Save",
1382
+ "cleanup.saving": "Saving\u2026",
1383
+ "cleanup.saved": "Saved and applied",
1384
+ "cleanup.saveFailed": "Save failed: {message}",
1385
+ "cleanup.readonly": "Read-only settings source"
1072
1386
  };
1073
1387
 
1388
+ // src/client/settings-card.tsx
1389
+ var import_react2 = require("react");
1390
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1391
+ var CLEANUP_SETTINGS_NAMESPACE = "dsh-rewind-snapshot-cleanup";
1392
+ var DEFAULT_MAX_AGE_DAYS = 30;
1393
+ function draftFrom(policy) {
1394
+ return { enabled: policy?.enabled ?? false, maxAgeDays: String(policy?.maxAgeDays ?? "") };
1395
+ }
1396
+ function maxAgeOf(text) {
1397
+ const trimmed = text.trim();
1398
+ if (!/^\d+$/.test(trimmed)) return null;
1399
+ const days = Number(trimmed);
1400
+ return Number.isSafeInteger(days) && days > 0 ? days : null;
1401
+ }
1402
+ function configOf(draft) {
1403
+ const days = maxAgeOf(draft.maxAgeDays);
1404
+ if (days === null) return null;
1405
+ return { enabled: draft.enabled, maxAgeDays: days };
1406
+ }
1407
+ function dirtyOf(base, draft) {
1408
+ return base.enabled !== draft.enabled || base.maxAgeDays !== draft.maxAgeDays;
1409
+ }
1410
+ function SettingsCleanupCard({ api, t }) {
1411
+ const [open, setOpen] = (0, import_react2.useState)(false);
1412
+ const [baseline, setBaseline] = (0, import_react2.useState)(() => draftFrom(api.read()));
1413
+ const [draft, setDraft] = (0, import_react2.useState)(() => draftFrom(api.read()));
1414
+ const [busy, setBusy] = (0, import_react2.useState)(false);
1415
+ const [error, setError] = (0, import_react2.useState)(null);
1416
+ const writable = api.writable();
1417
+ (0, import_react2.useEffect)(() => api.subscribe(() => {
1418
+ const next = draftFrom(api.read());
1419
+ setBaseline((base) => {
1420
+ setDraft((cur) => dirtyOf(base, cur) ? cur : next);
1421
+ return next;
1422
+ });
1423
+ }), [api]);
1424
+ const dirty = dirtyOf(baseline, draft);
1425
+ const days = maxAgeOf(draft.maxAgeDays);
1426
+ const invalid = days === null;
1427
+ const disabled = busy || !writable;
1428
+ const edit = (patch) => {
1429
+ setDraft((cur) => ({ ...cur, ...patch }));
1430
+ setError(null);
1431
+ };
1432
+ const save = async () => {
1433
+ if (busy || !writable || !dirty) return;
1434
+ const next = configOf(draft);
1435
+ if (next === null) {
1436
+ setError(t("cleanup.invalid"));
1437
+ return;
1438
+ }
1439
+ setBusy(true);
1440
+ setError(null);
1441
+ try {
1442
+ await api.save(next);
1443
+ setBaseline(draft);
1444
+ setError(null);
1445
+ } catch (e) {
1446
+ setError(t("cleanup.saveFailed", { message: e instanceof Error ? e.message : String(e) }));
1447
+ } finally {
1448
+ setBusy(false);
1449
+ }
1450
+ };
1451
+ const discard = () => {
1452
+ if (busy) return;
1453
+ setDraft(baseline);
1454
+ setError(null);
1455
+ };
1456
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: `dsh-rewind-cleanup-card${open ? " dsh-rewind-cleanup-card-open" : ""}`, children: [
1457
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1458
+ "button",
1459
+ {
1460
+ type: "button",
1461
+ className: "dsh-rewind-cleanup-header",
1462
+ "aria-expanded": open,
1463
+ "aria-label": `${t(open ? "cleanup.collapse" : "cleanup.expand")}: ${t("cleanup.title")}`,
1464
+ onClick: () => setOpen(!open),
1465
+ children: [
1466
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-rewind-cleanup-head-text", children: [
1467
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-name", children: t("cleanup.title") }),
1468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-desc", children: t("cleanup.desc") })
1469
+ ] }),
1470
+ dirty ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-pending", children: t("cleanup.unsaved") }) : null,
1471
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1472
+ "svg",
1473
+ {
1474
+ className: `dsh-rewind-cleanup-chevron${open ? " dsh-rewind-cleanup-chevron-open" : ""}`,
1475
+ width: "14",
1476
+ height: "14",
1477
+ viewBox: "0 0 16 16",
1478
+ "aria-hidden": "true",
1479
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
1480
+ }
1481
+ )
1482
+ ]
1483
+ }
1484
+ ),
1485
+ open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-body", children: [
1486
+ !writable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-readonly", role: "status", children: t("cleanup.readonly") }) : null,
1487
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
1488
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
1489
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", id: "dsh-rewind-cleanup-enabled-label", children: t("cleanup.auto") }),
1490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1491
+ "button",
1492
+ {
1493
+ type: "button",
1494
+ role: "switch",
1495
+ className: `dsh-rewind-cleanup-switch${draft.enabled ? " dsh-rewind-cleanup-switch-on" : ""}`,
1496
+ "aria-checked": draft.enabled,
1497
+ "aria-labelledby": "dsh-rewind-cleanup-enabled-label",
1498
+ disabled,
1499
+ onClick: () => edit({ enabled: !draft.enabled }),
1500
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-thumb" })
1501
+ }
1502
+ )
1503
+ ] }),
1504
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(draft.enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
1505
+ ] }),
1506
+ draft.enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-field", children: [
1507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-rewind-cleanup-head", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "dsh-rewind-cleanup-label", htmlFor: "dsh-rewind-cleanup-maxage", children: t("cleanup.maxAge") }) }),
1508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1509
+ "input",
1510
+ {
1511
+ className: `dsh-rewind-cleanup-input${invalid ? " dsh-rewind-cleanup-input-invalid" : ""}`,
1512
+ type: "text",
1513
+ inputMode: "numeric",
1514
+ id: "dsh-rewind-cleanup-maxage",
1515
+ value: draft.maxAgeDays,
1516
+ disabled,
1517
+ "aria-invalid": invalid || void 0,
1518
+ placeholder: String(DEFAULT_MAX_AGE_DAYS),
1519
+ onChange: (e) => edit({ maxAgeDays: e.target.value })
1520
+ }
1521
+ ),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: invalid ? "dsh-rewind-cleanup-error" : "dsh-rewind-cleanup-hint", children: invalid ? t("cleanup.invalid") : t("cleanup.maxAge.hint") })
1523
+ ] }) : null,
1524
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-footer", children: [
1525
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-failed", role: "status", children: error }) : null,
1526
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1527
+ "button",
1528
+ {
1529
+ type: "button",
1530
+ className: "dsh-rewind-cleanup-discard",
1531
+ disabled: !dirty || busy || !writable,
1532
+ onClick: discard,
1533
+ children: t("cleanup.discard")
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1537
+ "button",
1538
+ {
1539
+ type: "button",
1540
+ className: "dsh-rewind-cleanup-save",
1541
+ disabled: !dirty || busy || !writable || invalid,
1542
+ onClick: save,
1543
+ children: busy ? t("cleanup.saving") : t("cleanup.save")
1544
+ }
1545
+ )
1546
+ ] })
1547
+ ] }) : null
1548
+ ] });
1549
+ }
1550
+
1074
1551
  // src/client/index.ts
1075
1552
  var name = "dsh-rewind";
1076
1553
  var inject = ["slots", "sessions", "locale", "commandUi"];
@@ -1122,6 +1599,40 @@ function apply(ctx) {
1122
1599
  },
1123
1600
  createRewindBridge({ sessionOf, chatOf, currentSessionId, setComposerText, t, subscribeLocale })
1124
1601
  ));
1602
+ const clientCtx = ctx;
1603
+ clientCtx.inject(["settingsScope"], (scoped) => {
1604
+ try {
1605
+ const scope = scoped.settingsScope.bind({ namespace: CLEANUP_SETTINGS_NAMESPACE });
1606
+ const cardApi = {
1607
+ read: () => {
1608
+ const value = scope.getSnapshot().value;
1609
+ return value === void 0 ? void 0 : { enabled: value.enabled, maxAgeDays: value.maxAgeDays };
1610
+ },
1611
+ writable: () => {
1612
+ return scope.getSnapshot().writable === true;
1613
+ },
1614
+ save: async (next) => {
1615
+ await scope.set("enabled", next.enabled);
1616
+ await scope.set("maxAgeDays", next.maxAgeDays);
1617
+ },
1618
+ subscribe: (cb) => scope.subscribe(cb)
1619
+ };
1620
+ scoped.slots.inject("settings.plugin.item", () => scoped.slots.register(
1621
+ {
1622
+ name: "settings.plugin.item",
1623
+ key: CLEANUP_SETTINGS_NAMESPACE,
1624
+ // Match the official cards / dsh-market: locale + inject provide
1625
+ // the card its props through the slot renderer (the keyed card owns
1626
+ // its internals, but the page feeds it locale + the bound api).
1627
+ locale: NS,
1628
+ inject: () => ({ t, api: cardApi })
1629
+ },
1630
+ SettingsCleanupCard
1631
+ ));
1632
+ } catch (error) {
1633
+ console.error("[dsh-rewind] settings card register failed:", error);
1634
+ }
1635
+ });
1125
1636
  const commandUi = ctx.get("commandUi");
1126
1637
  const hasCandidates = (sessionId) => {
1127
1638
  const face = sessionId === void 0 ? void 0 : sessionOf(sessionId);