dsh-rewind-plugin 0.13.0-alpha.2 → 0.14.0-beta.1

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
@@ -78,6 +78,32 @@ function isPreviewCommand(command) {
78
78
  function isCandidateCommand(command) {
79
79
  return (command.args ?? "").includes("__candidates");
80
80
  }
81
+ function coalesceCuts(spans) {
82
+ if (spans.length < 2) return spans;
83
+ const sorted = [...spans].sort((left, right) => left.start - right.start || left.end - right.end);
84
+ const merged = [];
85
+ for (const span of sorted) {
86
+ const last = merged[merged.length - 1];
87
+ if (last !== void 0 && span.start <= last.end) {
88
+ if (span.end > last.end) last.end = span.end;
89
+ continue;
90
+ }
91
+ merged.push({ start: span.start, end: span.end });
92
+ }
93
+ return merged;
94
+ }
95
+ function cutsContain(cuts, seq) {
96
+ let low = 0;
97
+ let high = cuts.length - 1;
98
+ while (low <= high) {
99
+ const middle = low + high >> 1;
100
+ const cut = cuts[middle];
101
+ if (seq < cut.start) high = middle - 1;
102
+ else if (seq > cut.end) low = middle + 1;
103
+ else return true;
104
+ }
105
+ return false;
106
+ }
81
107
  function hiddenSeqsOf(snap) {
82
108
  const hidden = /* @__PURE__ */ new Set();
83
109
  const spans = [];
@@ -99,13 +125,11 @@ function hiddenSeqsOf(snap) {
99
125
  spans.push({ start: target, end: marker });
100
126
  }
101
127
  }
128
+ const cuts = coalesceCuts(spans);
102
129
  for (const key of snap.order) {
103
130
  const node = snap.nodes.get(key);
104
131
  if (node === void 0) continue;
105
- const anchor = node.anchorSeq;
106
- if (spans.some((span) => anchor >= span.start && anchor <= span.end)) {
107
- hidden.add(anchor);
108
- }
132
+ if (cutsContain(cuts, node.anchorSeq)) hidden.add(node.anchorSeq);
109
133
  }
110
134
  return hidden;
111
135
  }
@@ -373,25 +397,10 @@ var STYLE = `
373
397
  pointer-events: none;
374
398
  }
375
399
 
376
- /* ---- Snapshot-cleanup configuration form (mirrors the harness plugin form) ---- */
377
- /* The Plugins page draws the bundle's title, icon, crumb, and description; this
378
- is the form body only. Every value mirrors ui-settings-plugins'
379
- PluginConfigForm.module.css / fields.module.css /
380
- SubagentModelSelectionFields.module.css: those components are package-internal
381
- (the package ships lib/** only) and cannot be imported across packages, so the
382
- numbers are copied rather than the components. */
383
- .dsh-rewind-cleanup-form {
384
- display: flex;
385
- flex-direction: column;
386
- }
387
- .dsh-rewind-cleanup-readonly,
388
- .dsh-rewind-cleanup-unavailable {
389
- margin: 0 0 12px;
390
- font-size: 12px;
391
- line-height: 1.5;
392
- color: var(--dsw-alias-label-tertiary);
393
- }
394
- /* Auto-cleanup permission block: the label + Switch row, then the hint. */
400
+ /* ---- Snapshot-cleanup configuration form ---- */
401
+ /* The Plugins page draws the bundle's title, icon, crumb, description, and the
402
+ shared settings form frame (labels, save, read-only and failure lines); this
403
+ styles only the one control the card owns: the auto-cleanup toggle row. */
395
404
  .dsh-rewind-cleanup-permission {
396
405
  display: grid;
397
406
  gap: 6px;
@@ -410,127 +419,12 @@ var STYLE = `
410
419
  flex: 1;
411
420
  min-width: 0;
412
421
  }
413
- .dsh-rewind-cleanup-field {
414
- display: flex;
415
- flex-direction: column;
416
- gap: 6px;
417
- padding: 12px 0;
418
- }
419
- .dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {
420
- border-top: 0.5px solid var(--dsw-alias-border-l2);
421
- }
422
- .dsh-rewind-cleanup-head {
423
- display: flex;
424
- align-items: center;
425
- gap: 8px;
426
- }
427
- .dsh-rewind-cleanup-label-group {
428
- display: flex;
429
- align-items: center;
430
- gap: 4px;
431
- flex: 1;
432
- min-width: 0;
433
- }
434
- .dsh-rewind-cleanup-label {
435
- flex: 1;
436
- min-width: 0;
437
- font-size: 13px;
438
- font-weight: 500;
439
- line-height: 1.5;
440
- color: var(--dsw-alias-label-primary);
441
- }
442
- .dsh-rewind-cleanup-label-group > .dsh-rewind-cleanup-label {
443
- flex: 0 1 auto;
444
- }
445
- .dsh-rewind-cleanup-badges {
446
- display: inline-flex;
447
- align-items: center;
448
- gap: 8px;
449
- }
450
- .dsh-rewind-cleanup-reset {
451
- border: none;
452
- background: none;
453
- padding: 0;
454
- font: inherit;
455
- font-size: 12px;
456
- line-height: 1.5;
457
- color: var(--dsw-alias-label-secondary);
458
- cursor: pointer;
459
- }
460
- .dsh-rewind-cleanup-reset:hover:not(:disabled) {
461
- color: var(--dsw-alias-label-primary);
462
- }
463
- .dsh-rewind-cleanup-reset:disabled {
464
- cursor: default;
465
- }
466
422
  .dsh-rewind-cleanup-hint {
467
423
  margin: 0;
468
424
  font-size: 12px;
469
425
  line-height: 1.5;
470
426
  color: var(--dsw-alias-label-tertiary);
471
427
  }
472
- .dsh-rewind-cleanup-error {
473
- margin: 0;
474
- font-size: 12px;
475
- line-height: 1.5;
476
- color: var(--dsw-alias-state-error-primary);
477
- }
478
- .dsh-rewind-cleanup-input {
479
- height: 34px;
480
- padding: 0 12px;
481
- border: 0.5px solid var(--dsw-alias-border-l4);
482
- border-radius: 8px;
483
- background: var(--dsw-alias-bg-layer-3);
484
- font: inherit;
485
- font-size: 13px;
486
- line-height: 1.5;
487
- color: var(--dsw-alias-label-primary);
488
- }
489
- .dsh-rewind-cleanup-input:focus-visible {
490
- outline: none;
491
- border-color: var(--dsw-alias-brand-primary);
492
- }
493
- .dsh-rewind-cleanup-input:disabled {
494
- color: var(--dsw-alias-label-tertiary);
495
- cursor: default;
496
- }
497
- .dsh-rewind-cleanup-input[aria-invalid='true'] {
498
- border-color: var(--dsw-alias-state-error-primary);
499
- }
500
- .dsh-rewind-cleanup-footer {
501
- display: flex;
502
- align-items: center;
503
- gap: 8px;
504
- padding-top: 16px;
505
- }
506
- .dsh-rewind-cleanup-failed {
507
- flex: 1;
508
- min-width: 0;
509
- margin: 0;
510
- font-size: 12px;
511
- line-height: 1.5;
512
- color: var(--dsw-alias-label-error);
513
- }
514
- .dsh-rewind-cleanup-save {
515
- appearance: none;
516
- border: 1px solid transparent;
517
- border-radius: 8px;
518
- padding: 5px 14px;
519
- font: inherit;
520
- font-size: 13px;
521
- line-height: 1.5;
522
- cursor: pointer;
523
- background: var(--dsw-alias-label-primary);
524
- color: var(--dsw-alias-bg-layer-3);
525
- }
526
- .dsh-rewind-cleanup-save:disabled {
527
- opacity: 0.4;
528
- cursor: default;
529
- }
530
- .dsh-rewind-cleanup-save:focus-visible {
531
- outline: 2px solid var(--dsw-alias-brand-primary);
532
- outline-offset: 1px;
533
- }
534
428
  `;
535
429
 
536
430
  // src/client/popover.ts
@@ -1047,6 +941,14 @@ function showHint(text) {
1047
941
  var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
1048
942
  var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
1049
943
  var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
944
+ var GROUP_SHELL_SELECTOR = '[data-chat-anchor-key^="group:"]';
945
+ function resolveSeatAnchorSeq(element, chat) {
946
+ if (chat === void 0) return void 0;
947
+ const flowKey = element.dataset.chatAnchorKey;
948
+ const nodeKey = element.dataset.chatNodeKey;
949
+ const node = (flowKey !== void 0 ? chat.nodes.get(flowKey) : void 0) ?? (nodeKey !== void 0 ? chat.nodes.get(nodeKey) : void 0);
950
+ return node?.anchorSeq;
951
+ }
1050
952
  var PENDING_SEAT_SELECTOR = "[data-pending-steering]";
1051
953
  function actionsContainerOf(row) {
1052
954
  const buttons = Array.from(row?.querySelectorAll("button") ?? []);
@@ -1055,6 +957,38 @@ function actionsContainerOf(row) {
1055
957
  if (structural instanceof HTMLElement && structural.querySelector("button") !== null) return structural;
1056
958
  return void 0;
1057
959
  }
960
+ function hideWithdrawnSeats(chat, seats, hiddenSeqs, hidden) {
961
+ if (chat === void 0) return;
962
+ const shells = /* @__PURE__ */ new Map();
963
+ for (const seat of seats) {
964
+ if (seat.matches(GROUP_SHELL_SELECTOR)) continue;
965
+ const anchor = resolveSeatAnchorSeq(seat, chat);
966
+ const withdrawn = anchor !== void 0 && hiddenSeqs.has(anchor);
967
+ if (withdrawn) {
968
+ seat.style.display = "none";
969
+ seat.dataset.dshRewindHidden = "true";
970
+ hidden.add(seat);
971
+ } else if (hidden.has(seat)) {
972
+ seat.style.display = "";
973
+ delete seat.dataset.dshRewindHidden;
974
+ hidden.delete(seat);
975
+ }
976
+ const shell = seat.parentElement?.closest(GROUP_SHELL_SELECTOR);
977
+ if (shell === null || shell === void 0) continue;
978
+ shells.set(shell, (shells.get(shell) ?? true) && withdrawn);
979
+ }
980
+ for (const [shell, allWithdrawn] of shells) {
981
+ if (allWithdrawn) {
982
+ shell.style.display = "none";
983
+ shell.dataset.dshRewindHidden = "true";
984
+ hidden.add(shell);
985
+ } else if (hidden.has(shell)) {
986
+ shell.style.display = "";
987
+ delete shell.dataset.dshRewindHidden;
988
+ hidden.delete(shell);
989
+ }
990
+ }
991
+ }
1058
992
  function collectTargets(chat, hiddenSeqs) {
1059
993
  const rows = /* @__PURE__ */ new Map();
1060
994
  for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
@@ -1145,19 +1079,12 @@ function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchC
1145
1079
  const snapshot = session.getSnapshot();
1146
1080
  const chat = chatOf(session);
1147
1081
  const hiddenSeqs = chat === void 0 ? /* @__PURE__ */ new Set() : hiddenSeqsOf(chat);
1148
- for (const seat of chat === void 0 ? [] : document.querySelectorAll(CHAT_SEAT_SELECTOR)) {
1149
- const key = seat.dataset.chatAnchorKey;
1150
- const anchor = key !== void 0 ? chat?.nodes.get(key)?.anchorSeq : void 0;
1151
- if (anchor !== void 0 && hiddenSeqs.has(anchor)) {
1152
- seat.style.display = "none";
1153
- seat.dataset.dshRewindHidden = "true";
1154
- hidden.current.add(seat);
1155
- } else if (hidden.current.has(seat)) {
1156
- seat.style.display = "";
1157
- delete seat.dataset.dshRewindHidden;
1158
- hidden.current.delete(seat);
1159
- }
1160
- }
1082
+ hideWithdrawnSeats(
1083
+ chat,
1084
+ chat === void 0 ? [] : document.querySelectorAll(CHAT_SEAT_SELECTOR),
1085
+ hiddenSeqs,
1086
+ hidden.current
1087
+ );
1161
1088
  const durable = collectDurableTargets(snapshot, chat, hiddenSeqs);
1162
1089
  const next = [...durable, ...collectPendingTargets(snapshot, steeringItemsOf(inboxOf(session)?.["next-step"]))];
1163
1090
  setTargets((current) => sameTargets(current, next) ? current : next);
@@ -1306,9 +1233,9 @@ function createRewindBridge(deps) {
1306
1233
  }
1307
1234
 
1308
1235
  // src/client/build-info.ts
1309
- var PLUGIN_VERSION = true ? "0.13.0-alpha.2" : "dev";
1236
+ var PLUGIN_VERSION = true ? "0.14.0-beta.1" : "dev";
1310
1237
  var PLUGIN_PACKAGE = "dsh-rewind-plugin";
1311
- var BUILD_HASH = true ? "d7022b63" : "dev";
1238
+ var BUILD_HASH = true ? "190a881a" : "dev";
1312
1239
 
1313
1240
  // src/client/locales.ts
1314
1241
  var zh = {
@@ -1391,201 +1318,100 @@ var en = {
1391
1318
  };
1392
1319
 
1393
1320
  // src/client/settings-card.tsx
1394
- var import_react2 = require("react");
1395
1321
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1396
1322
  var import_jsx_runtime2 = require("react/jsx-runtime");
1397
- var CLEANUP_SETTINGS_NAMESPACE = "dsh-rewind-snapshot-cleanup";
1323
+ var CLEANUP_ENTRY_ID = "dsh-rewind-plugin";
1398
1324
  var DEFAULT_MAX_AGE_DAYS = 30;
1399
- function parseMaxAge(text) {
1400
- const trimmed = text.trim();
1401
- if (trimmed === "") return { kind: "clear" };
1402
- if (!/^\d+$/.test(trimmed)) return void 0;
1403
- const days = Number(trimmed);
1404
- return Number.isSafeInteger(days) && days > 0 ? { kind: "set", value: days } : void 0;
1405
- }
1406
- function formatMaxAge(value) {
1407
- return typeof value === "number" ? String(value) : "";
1408
- }
1409
- function formatEnabled(value) {
1410
- return String(value === true);
1411
- }
1412
- function parseEnabled(text) {
1413
- return { kind: "set", value: text === "true" };
1414
- }
1415
- function formatOf(field, value) {
1416
- return field === "enabled" ? formatEnabled(value) : formatMaxAge(value);
1417
- }
1418
- function parseOf(field, text) {
1419
- return field === "enabled" ? parseEnabled(text) : parseMaxAge(text);
1420
- }
1421
- function textOf2(field, staged, reads) {
1422
- const edit = staged.get(field);
1423
- return edit !== void 0 ? edit.text : formatOf(field, reads.value);
1424
- }
1425
- function overriddenOf(field, staged, reads) {
1426
- const edit = staged.get(field);
1427
- if (edit === void 0) return reads.stored;
1428
- if (edit.clear) return false;
1429
- return parseOf(field, edit.text)?.kind === "set";
1430
- }
1431
- function invalidOf(field, staged) {
1432
- const edit = staged.get(field);
1433
- if (edit === void 0 || edit.clear) return false;
1434
- return parseOf(field, edit.text) === void 0;
1435
- }
1436
- function planOf(staged, reads) {
1437
- const plan = [];
1438
- for (const [field, edit] of staged) {
1439
- const read = reads(field);
1440
- if (edit.clear) {
1441
- if (read.stored) plan.push({ field, kind: "clear" });
1442
- continue;
1443
- }
1444
- if (edit.text === formatOf(field, read.value)) continue;
1445
- const write = parseOf(field, edit.text);
1446
- if (write === void 0) plan.push({ field, kind: "invalid" });
1447
- else if (write.kind === "clear") {
1448
- if (read.stored) plan.push({ field, kind: "clear" });
1449
- } else {
1450
- plan.push({ field, kind: "set", value: write.value });
1451
- }
1452
- }
1453
- return plan;
1454
- }
1455
- function shellOf(plan, flags) {
1325
+ function autoCleanupEnabledField() {
1456
1326
  return {
1457
- dirty: plan.length > 0,
1458
- invalid: plan.some((entry) => entry.kind === "invalid"),
1459
- saving: flags.saving,
1460
- failed: flags.failed
1327
+ field: "autoCleanupEnabled",
1328
+ format: (value) => String(value === true),
1329
+ parse: (text) => ({ kind: "set", value: text === "true" })
1461
1330
  };
1462
1331
  }
1463
- function SettingsCleanupCard({ view, api, t }) {
1464
- const [staged, setStaged] = (0, import_react2.useState)(() => /* @__PURE__ */ new Map());
1465
- const [saving, setSaving] = (0, import_react2.useState)(false);
1466
- const [failed, setFailed] = (0, import_react2.useState)(false);
1467
- const [, setRevision] = (0, import_react2.useState)(0);
1468
- (0, import_react2.useEffect)(() => api.subscribe(() => {
1469
- setRevision((revision) => revision + 1);
1470
- }), [api]);
1471
- if (view === "summary") return null;
1472
- const readField = (field) => ({
1473
- value: api.read(field),
1474
- stored: api.stored(field)
1475
- });
1476
- const stage = (field, edit) => {
1477
- setStaged((current) => {
1478
- const next = new Map(current);
1479
- next.set(field, edit);
1480
- return next;
1481
- });
1482
- setFailed(false);
1332
+ function formLabels(t) {
1333
+ return {
1334
+ unavailable: t("cleanup.unavailable"),
1335
+ readOnly: t("cleanup.readonly"),
1336
+ saveFailed: t("cleanup.saveFailed"),
1337
+ save: t("cleanup.save"),
1338
+ saving: t("cleanup.saving")
1483
1339
  };
1484
- const plan = planOf(staged, readField);
1485
- const shell = shellOf(plan, { saving, failed });
1486
- const writable = api.writable();
1487
- const disabled = !writable || saving;
1488
- const blocked = !shell.dirty || shell.invalid || shell.saving;
1489
- const save = async () => {
1490
- const writes = plan.map((entry) => async () => {
1491
- if (entry.kind === "clear") return api.unset(entry.field);
1492
- if (entry.kind === "set") return api.set(entry.field, entry.value);
1493
- return false;
1494
- });
1495
- if (plan.length === 0 || saving || plan.some((entry) => entry.kind === "invalid")) return;
1496
- setSaving(true);
1497
- setFailed(false);
1498
- let landed = true;
1499
- for (const write of writes) landed = await write() && landed;
1500
- if (landed) setStaged(/* @__PURE__ */ new Map());
1501
- setSaving(false);
1502
- setFailed(!landed);
1340
+ }
1341
+ function cleanupForm(scope) {
1342
+ const form = new import_dsh_client_ui_primitives.SettingsFormModel(scope, [
1343
+ autoCleanupEnabledField(),
1344
+ (0, import_dsh_client_ui_primitives.settingsNumberField)("autoCleanupMaxAgeDays")
1345
+ ]);
1346
+ return {
1347
+ form,
1348
+ store: form.bind(() => ({
1349
+ ...form.shell(),
1350
+ enabled: form.field("autoCleanupEnabled"),
1351
+ maxAgeDays: form.field("autoCleanupMaxAgeDays")
1352
+ }))
1503
1353
  };
1504
- const enabledReads = readField("enabled");
1505
- const enabledText = textOf2("enabled", staged, enabledReads);
1506
- const enabled = enabledText === "true";
1507
- const maxAgeReads = readField("maxAgeDays");
1508
- const maxAgeText = textOf2("maxAgeDays", staged, maxAgeReads);
1509
- const maxAgeInvalid = invalidOf("maxAgeDays", staged);
1510
- if (!api.available()) {
1511
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-unavailable", role: "status", children: t("cleanup.unavailable") });
1512
- }
1513
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-form", children: [
1514
- !writable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-readonly", role: "status", children: t("cleanup.readonly") }) : null,
1515
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
1516
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
1517
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", children: t("cleanup.auto") }),
1518
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1519
- import_dsh_client_ui_primitives.Switch,
1354
+ }
1355
+ function SettingsCleanupCard(props) {
1356
+ const { t, useCleanupCard } = props;
1357
+ const state = useCleanupCard((snapshot) => snapshot);
1358
+ if (props.view === "summary") return null;
1359
+ const disabled = !state.writable || state.saving;
1360
+ const enabled = state.enabled.text === "true";
1361
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1362
+ import_dsh_client_ui_primitives.SettingsForm,
1363
+ {
1364
+ labels: formLabels(t),
1365
+ state,
1366
+ onSave: props.save,
1367
+ onDiscard: props.discard,
1368
+ children: [
1369
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
1370
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", children: t("cleanup.auto") }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1373
+ import_dsh_client_ui_primitives.Switch,
1374
+ {
1375
+ checked: enabled,
1376
+ label: t("cleanup.auto"),
1377
+ disabled,
1378
+ onChange: (next) => {
1379
+ props.edit("autoCleanupEnabled", String(next));
1380
+ }
1381
+ }
1382
+ )
1383
+ ] }),
1384
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
1385
+ ] }),
1386
+ enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1387
+ import_dsh_client_ui_primitives.SettingsValueField,
1520
1388
  {
1521
- checked: enabled,
1522
- label: t("cleanup.auto"),
1389
+ id: "dsh-rewind-cleanup-maxage",
1390
+ label: t("cleanup.maxAge"),
1391
+ hint: t("cleanup.maxAge.hint"),
1392
+ overriddenLabel: t("cleanup.overridden"),
1393
+ resetLabel: t("cleanup.reset"),
1394
+ invalidLabel: t("cleanup.invalid"),
1395
+ placeholder: String(DEFAULT_MAX_AGE_DAYS),
1396
+ numeric: true,
1523
1397
  disabled,
1524
- onChange: (next) => {
1525
- stage("enabled", { text: formatEnabled(next), clear: false });
1526
- }
1527
- }
1528
- )
1529
- ] }),
1530
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
1531
- ] }),
1532
- enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-field", children: [
1533
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-head", children: [
1534
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-rewind-cleanup-label-group", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "dsh-rewind-cleanup-label", htmlFor: "dsh-rewind-cleanup-maxage", children: t("cleanup.maxAge") }) }),
1535
- overriddenOf("maxAgeDays", staged, maxAgeReads) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-rewind-cleanup-badges", children: [
1536
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "neutral", children: t("cleanup.overridden") }),
1537
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1538
- "button",
1539
- {
1540
- type: "button",
1541
- className: "dsh-rewind-cleanup-reset",
1542
- disabled,
1543
- onClick: () => {
1544
- stage("maxAgeDays", { text: formatMaxAge(api.base("maxAgeDays")), clear: true });
1545
- },
1546
- children: t("cleanup.reset")
1398
+ ...state.maxAgeDays,
1399
+ onEdit: (text) => {
1400
+ props.edit("autoCleanupMaxAgeDays", text);
1401
+ },
1402
+ onReset: () => {
1403
+ props.resetField("autoCleanupMaxAgeDays");
1547
1404
  }
1548
- )
1549
- ] }) : null
1550
- ] }),
1551
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1552
- "input",
1553
- {
1554
- id: "dsh-rewind-cleanup-maxage",
1555
- className: "dsh-rewind-cleanup-input",
1556
- type: "text",
1557
- inputMode: "numeric",
1558
- ...maxAgeInvalid ? { "aria-invalid": true } : {},
1559
- "aria-describedby": "dsh-rewind-cleanup-maxage-message",
1560
- value: maxAgeText,
1561
- placeholder: String(DEFAULT_MAX_AGE_DAYS),
1562
- disabled,
1563
- onChange: (event) => {
1564
- stage("maxAgeDays", { text: event.target.value, clear: false });
1565
1405
  }
1566
- }
1567
- ),
1568
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1569
- "p",
1570
- {
1571
- id: "dsh-rewind-cleanup-maxage-message",
1572
- className: maxAgeInvalid ? "dsh-rewind-cleanup-error" : "dsh-rewind-cleanup-hint",
1573
- children: maxAgeInvalid ? t("cleanup.invalid") : t("cleanup.maxAge.hint")
1574
- }
1575
- )
1576
- ] }) : null,
1577
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-footer", children: [
1578
- shell.failed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-failed", role: "status", children: t("cleanup.saveFailed") }) : null,
1579
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-rewind-cleanup-save", disabled: blocked, onClick: () => {
1580
- void save();
1581
- }, children: shell.saving ? t("cleanup.saving") : t("cleanup.save") })
1582
- ] })
1583
- ] });
1406
+ ) : null
1407
+ ]
1408
+ }
1409
+ );
1584
1410
  }
1585
1411
 
1586
1412
  // src/client/index.ts
1587
1413
  var name = "dsh-rewind";
1588
- var inject = ["slots", "sessions", "locale", "commandUi"];
1414
+ var inject = ["slots", "sessions", "locale", "commandUi", "configForms"];
1589
1415
  var NS2 = "rewind";
1590
1416
  var HEADER_ACTIONS_SLOT = "conversation.session.header.actions";
1591
1417
  var COMPOSER_EDITABLE_SELECTOR2 = "[data-composer-input]";
@@ -1652,58 +1478,25 @@ function apply(ctx) {
1652
1478
  },
1653
1479
  createRewindBridge({ sessionOf, chatOf, isMainViewSession, watchChat, setComposerText, t, subscribeLocale })
1654
1480
  ));
1655
- const clientCtx = ctx;
1656
- clientCtx.inject(["settingsScope"], (scoped) => {
1657
- try {
1658
- const scope = scoped.settingsScope.bind({ namespace: CLEANUP_SETTINGS_NAMESPACE });
1659
- const userLayer = () => {
1660
- const user = scope.getSnapshot().user;
1661
- return typeof user === "object" && user !== null ? user : void 0;
1662
- };
1663
- const cardApi = {
1664
- // The official shell reads `status === 'ready'`; a loading namespace
1665
- // is not available, so the form offers no fields yet.
1666
- available: () => scope.getSnapshot().status === "ready",
1667
- writable: () => {
1668
- return scope.getSnapshot().writable === true;
1669
- },
1670
- read: (field) => scope.getSnapshot().value?.[field],
1671
- base: (field) => scope.getSnapshot().base?.[field],
1672
- stored: (field) => {
1673
- const user = userLayer();
1674
- return user !== void 0 && Object.hasOwn(user, field);
1675
- },
1676
- // The Host decides whether a write landed: report the read-back the
1677
- // official `CardForm.store` performs.
1678
- set: async (field, value) => {
1679
- await scope.set(field, value);
1680
- return userLayer()?.[field] === value;
1681
- },
1682
- unset: async (field) => {
1683
- await scope.unset(field);
1684
- const user = userLayer();
1685
- return user === void 0 || !Object.hasOwn(user, field);
1686
- },
1687
- subscribe: (cb) => scope.subscribe(cb)
1688
- };
1689
- scoped.slots.inject("plugins.bundle.config", () => scoped.slots.register(
1690
- {
1691
- name: "plugins.bundle.config",
1692
- // Keyed by the bundle's package name: the page renders this form on
1693
- // the bundle's own page, between its description and its rows.
1694
- key: PLUGIN_PACKAGE,
1695
- // Match the official cards / dsh-market: locale + inject provide
1696
- // the card its props through the slot renderer (the page feeds it
1697
- // the `view` it asks for plus the bound api).
1698
- locale: NS2,
1699
- inject: () => ({ t, api: cardApi })
1700
- },
1701
- SettingsCleanupCard
1702
- ));
1703
- } catch (error) {
1704
- rewindLog.error("settings", "settings card register failed", error);
1705
- }
1706
- });
1481
+ try {
1482
+ const { store, form } = cleanupForm(
1483
+ ctx.configForms.get(CLEANUP_ENTRY_ID)
1484
+ );
1485
+ ctx.effect(() => () => {
1486
+ form.dispose();
1487
+ }, "dsh-rewind cleanup form");
1488
+ yield slots.inject("plugins.bundle.config", () => slots.register(
1489
+ {
1490
+ name: "plugins.bundle.config",
1491
+ key: PLUGIN_PACKAGE,
1492
+ locale: NS2,
1493
+ inject: () => ({ hooks: { cleanupCard: store }, ...form.actions() })
1494
+ },
1495
+ SettingsCleanupCard
1496
+ ));
1497
+ } catch (error) {
1498
+ rewindLog.error("settings", "settings card register failed", error);
1499
+ }
1707
1500
  const commandUi = ctx.get("commandUi");
1708
1501
  const hasCandidates = (sessionId) => {
1709
1502
  const face = sessionId === void 0 ? void 0 : sessionOf(sessionId);