dsh-rewind-plugin 0.6.2 → 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
@@ -40,6 +40,18 @@ function chatSnapshotOf(face, chatView) {
40
40
  if (legacy !== void 0) return legacy;
41
41
  return chatView?.getSnapshot() ?? void 0;
42
42
  }
43
+ function messageTextAt(chat, seq) {
44
+ if (chat === void 0) return void 0;
45
+ for (const key of chat.order) {
46
+ const node = chat.nodes.get(key);
47
+ if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
48
+ const data = node.data;
49
+ if (data.seq === seq) {
50
+ return data.content?.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
51
+ }
52
+ }
53
+ return void 0;
54
+ }
43
55
  function targetSeqOfArgs(args) {
44
56
  if (args === void 0 || args === null) return void 0;
45
57
  const match = args.match(/@(\d+)/);
@@ -307,6 +319,249 @@ var STYLE = `
307
319
  color: var(--dsw-alias-label-primary);
308
320
  pointer-events: none;
309
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
+ }
310
565
  `;
311
566
 
312
567
  // src/client/popover.ts
@@ -677,18 +932,6 @@ function retractSpan(steering, targetId) {
677
932
 
678
933
  // src/client/portals.tsx
679
934
  var import_jsx_runtime = require("react/jsx-runtime");
680
- function userTextAt(chat, seq) {
681
- if (chat === void 0) return void 0;
682
- for (const key of chat.order) {
683
- const node = chat.nodes.get(key);
684
- if (node === void 0 || node.kind !== "user") continue;
685
- const user = node.data;
686
- if (user.seq === seq) {
687
- return user.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
688
- }
689
- }
690
- return void 0;
691
- }
692
935
  function fillComposerTextarea(text) {
693
936
  const textarea = document.querySelector(COMPOSER_TEXTAREA_SELECTOR);
694
937
  if (textarea === null) return false;
@@ -750,7 +993,7 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, se
750
993
  return;
751
994
  }
752
995
  if (currentSessionId() !== session.sessionId) return;
753
- const text = userTextAt(chatOf(session), seq);
996
+ const text = messageTextAt(chatOf(session), seq);
754
997
  if (text === void 0 || text === "") return;
755
998
  setComposerText(session.sessionId, text);
756
999
  }
@@ -776,8 +1019,8 @@ var COMPOSER_TEXTAREA_SELECTOR = "[data-input-scroll] textarea, textarea[data-ph
776
1019
  var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
777
1020
  var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
778
1021
  var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
779
- var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root]";
780
- var PENDING_SEAT_SELECTOR = "[data-pending-steering][data-time-hover-root]";
1022
+ var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root], [data-actions-reveal]";
1023
+ var PENDING_SEAT_SELECTOR = "[data-pending-steering][data-time-hover-root], [data-pending-steering][data-actions-reveal]";
781
1024
  function collectTargets(chat, hiddenSeqs) {
782
1025
  const rows = /* @__PURE__ */ new Map();
783
1026
  for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
@@ -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);