dsh-rewind-plugin 0.12.2 → 0.13.0-alpha.2

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
@@ -292,6 +292,14 @@ var STYLE = `
292
292
  .dsh-rewind-popover-option:hover {
293
293
  background: var(--dsw-alias-interactive-bg-hover);
294
294
  }
295
+ /* Arrow navigation moves real focus, so the focused row carries the pointer's
296
+ fill and the browser ring is suppressed: in dark mode the UA two-tone ring
297
+ draws its dark half against the filled row and reads as a residue edge (the
298
+ harness Menu suppresses it the same way). */
299
+ .dsh-rewind-popover-option:focus-visible:not(:disabled) {
300
+ background: var(--dsw-alias-interactive-bg-hover);
301
+ outline: none;
302
+ }
295
303
  .dsh-rewind-popover-option:disabled {
296
304
  opacity: 0.5;
297
305
  cursor: default;
@@ -365,95 +373,43 @@ var STYLE = `
365
373
  pointer-events: none;
366
374
  }
367
375
 
368
- /* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */
369
- /* Standardized on the 0.1.2 harness card look (border-radius 16px + 0.5px
370
- border-l4, 0.5px internal separators). */
371
- .dsh-rewind-cleanup-card {
372
- list-style: none;
373
- border: 0.5px solid var(--dsw-alias-border-l4);
374
- border-radius: 16px;
375
- background: var(--dsw-alias-bg-layer-3);
376
- transition: border-color .16s, background .16s;
377
- }
378
- .dsh-rewind-cleanup-card:hover {
379
- border-color: var(--dsw-alias-label-dimmed);
380
- }
381
- .dsh-rewind-cleanup-card-open {
382
- background: var(--dsw-alias-bg-layer-2);
383
- border-color: var(--dsw-alias-label-dimmed);
384
- }
385
- .dsh-rewind-cleanup-header {
386
- width: 100%;
387
- appearance: none;
388
- border: 0;
389
- background: none;
390
- font: inherit;
391
- color: inherit;
392
- text-align: left;
393
- cursor: pointer;
394
- display: flex;
395
- align-items: center;
396
- gap: 12px;
397
- padding: 14px 16px;
398
- border-radius: 12px;
399
- }
400
- .dsh-rewind-cleanup-header:focus-visible {
401
- outline: 2px solid var(--dsw-alias-brand-primary);
402
- outline-offset: -2px;
403
- }
404
- .dsh-rewind-cleanup-head-text {
405
- flex: 1;
406
- min-width: 0;
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 {
407
384
  display: flex;
408
385
  flex-direction: column;
409
- gap: 4px;
410
- }
411
- .dsh-rewind-cleanup-name {
412
- font-size: 15px;
413
- font-weight: 600;
414
- line-height: 1.4;
415
- color: var(--dsw-alias-label-primary);
416
- }
417
- .dsh-rewind-cleanup-desc {
418
- font-size: 13px;
419
- line-height: 1.5;
420
- color: var(--dsw-alias-label-tertiary);
421
- }
422
- .dsh-rewind-cleanup-chevron {
423
- flex: none;
424
- color: var(--dsw-alias-label-tertiary);
425
- transition: transform .16s;
426
386
  }
427
- .dsh-rewind-cleanup-chevron-open {
428
- transform: rotate(180deg);
429
- }
430
- .dsh-rewind-cleanup-pending {
431
- flex: none;
432
- border-radius: 999px;
433
- padding: 1px 8px;
434
- font-size: 11px;
435
- line-height: 17px;
436
- font-weight: 500;
437
- white-space: nowrap;
438
- background: var(--dsw-alias-bg-module-platform);
439
- color: var(--dsw-alias-label-secondary);
440
- }
441
- .dsh-rewind-cleanup-body {
442
- border-top: 0.5px solid var(--dsw-alias-border-l2);
443
- margin: 0 16px;
444
- padding: 0 0 8px;
445
- }
446
- .dsh-rewind-cleanup-readonly {
447
- margin: 12px 0 0;
387
+ .dsh-rewind-cleanup-readonly,
388
+ .dsh-rewind-cleanup-unavailable {
389
+ margin: 0 0 12px;
448
390
  font-size: 12px;
449
391
  line-height: 1.5;
450
392
  color: var(--dsw-alias-label-tertiary);
451
393
  }
394
+ /* Auto-cleanup permission block: the label + Switch row, then the hint. */
452
395
  .dsh-rewind-cleanup-permission {
453
396
  display: grid;
454
397
  gap: 6px;
455
398
  padding: 12px 0;
456
399
  }
400
+ .dsh-rewind-cleanup-toggle-row {
401
+ display: flex;
402
+ align-items: flex-start;
403
+ justify-content: space-between;
404
+ gap: 16px;
405
+ font-size: 13px;
406
+ line-height: 1.5;
407
+ color: var(--dsw-alias-label-primary);
408
+ }
409
+ .dsh-rewind-cleanup-toggle-label {
410
+ flex: 1;
411
+ min-width: 0;
412
+ }
457
413
  .dsh-rewind-cleanup-field {
458
414
  display: flex;
459
415
  flex-direction: column;
@@ -463,17 +419,18 @@ var STYLE = `
463
419
  .dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {
464
420
  border-top: 0.5px solid var(--dsw-alias-border-l2);
465
421
  }
466
- /* Auto-cleanup (permission) block and the max-age field are consecutive control
467
- * sections of the open card \u2014 give them the same 0.5px divider the harness gives
468
- * every pair of config fields (fields.module.css field + field). */
469
- .dsh-rewind-cleanup-permission + .dsh-rewind-cleanup-field {
470
- border-top: 0.5px solid var(--dsw-alias-border-l2);
471
- }
472
422
  .dsh-rewind-cleanup-head {
473
423
  display: flex;
474
424
  align-items: center;
475
425
  gap: 8px;
476
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
+ }
477
434
  .dsh-rewind-cleanup-label {
478
435
  flex: 1;
479
436
  min-width: 0;
@@ -482,6 +439,30 @@ var STYLE = `
482
439
  line-height: 1.5;
483
440
  color: var(--dsw-alias-label-primary);
484
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
+ }
485
466
  .dsh-rewind-cleanup-hint {
486
467
  margin: 0;
487
468
  font-size: 12px;
@@ -492,65 +473,9 @@ var STYLE = `
492
473
  margin: 0;
493
474
  font-size: 12px;
494
475
  line-height: 1.5;
495
- color: var(--dsw-alias-label-error);
496
- }
497
- /* Switch row: label left, role=switch button right, hint below (Subagent module).
498
- * The track mirrors the harness ui-primitives Switch (0.1.3-alpha.2 line): the
499
- * on/off appearance keys off aria-checked rather than a parallel class, so the
500
- * visual state cannot disagree with the state assistive technology reads, and
501
- * corner-shape: round opts the capsule track out of the global superellipse
502
- * (which would square the capsule ends off against the round thumb inside). */
503
- .dsh-rewind-cleanup-toggle-row {
504
- display: flex;
505
- align-items: flex-start;
506
- justify-content: space-between;
507
- gap: 16px;
508
- font-size: 13px;
509
- line-height: 1.5;
510
- color: var(--dsw-alias-label-primary);
511
- }
512
- .dsh-rewind-cleanup-toggle-label {
513
- flex: 1;
514
- min-width: 0;
515
- }
516
- .dsh-rewind-cleanup-switch {
517
- box-sizing: border-box;
518
- position: relative;
519
- flex: 0 0 auto;
520
- width: 36px;
521
- height: 20px;
522
- padding: 2px;
523
- border: 0;
524
- border-radius: 10px;
525
- corner-shape: round;
526
- background: var(--dsw-alias-border-l3);
527
- cursor: pointer;
528
- }
529
- .dsh-rewind-cleanup-switch[aria-checked='true'] {
530
- background: var(--dsw-alias-brand-primary);
531
- }
532
- .dsh-rewind-cleanup-switch:disabled {
533
- cursor: default;
534
- opacity: 0.5;
535
- }
536
- .dsh-rewind-cleanup-switch:focus-visible {
537
- outline: 2px solid var(--dsw-alias-brand-primary);
538
- outline-offset: 2px;
539
- }
540
- .dsh-rewind-cleanup-thumb {
541
- display: block;
542
- width: 16px;
543
- height: 16px;
544
- border-radius: 50%;
545
- corner-shape: round;
546
- background: var(--dsw-alias-label-primary-foreground);
547
- transition: transform 120ms ease;
548
- }
549
- .dsh-rewind-cleanup-switch[aria-checked='true'] .dsh-rewind-cleanup-thumb {
550
- transform: translateX(16px);
476
+ color: var(--dsw-alias-state-error-primary);
551
477
  }
552
478
  .dsh-rewind-cleanup-input {
553
- box-sizing: border-box;
554
479
  height: 34px;
555
480
  padding: 0 12px;
556
481
  border: 0.5px solid var(--dsw-alias-border-l4);
@@ -569,16 +494,14 @@ var STYLE = `
569
494
  color: var(--dsw-alias-label-tertiary);
570
495
  cursor: default;
571
496
  }
572
- .dsh-rewind-cleanup-input-invalid {
573
- border-color: var(--dsw-alias-label-error);
497
+ .dsh-rewind-cleanup-input[aria-invalid='true'] {
498
+ border-color: var(--dsw-alias-state-error-primary);
574
499
  }
575
500
  .dsh-rewind-cleanup-footer {
576
501
  display: flex;
577
502
  align-items: center;
578
- justify-content: flex-end;
579
503
  gap: 8px;
580
- padding: 12px 0 4px;
581
- border-top: 0.5px solid var(--dsw-alias-border-l2);
504
+ padding-top: 16px;
582
505
  }
583
506
  .dsh-rewind-cleanup-failed {
584
507
  flex: 1;
@@ -588,7 +511,6 @@ var STYLE = `
588
511
  line-height: 1.5;
589
512
  color: var(--dsw-alias-label-error);
590
513
  }
591
- .dsh-rewind-cleanup-discard,
592
514
  .dsh-rewind-cleanup-save {
593
515
  appearance: none;
594
516
  border: 1px solid transparent;
@@ -598,26 +520,13 @@ var STYLE = `
598
520
  font-size: 13px;
599
521
  line-height: 1.5;
600
522
  cursor: pointer;
601
- }
602
- .dsh-rewind-cleanup-discard {
603
- border-color: var(--dsw-alias-border-l2);
604
- background: none;
605
- color: var(--dsw-alias-label-secondary);
606
- }
607
- .dsh-rewind-cleanup-discard:hover:not(:disabled) {
608
- color: var(--dsw-alias-label-primary);
609
- border-color: var(--dsw-alias-label-dimmed);
610
- }
611
- .dsh-rewind-cleanup-save {
612
523
  background: var(--dsw-alias-label-primary);
613
524
  color: var(--dsw-alias-bg-layer-3);
614
525
  }
615
- .dsh-rewind-cleanup-discard:disabled,
616
526
  .dsh-rewind-cleanup-save:disabled {
617
527
  opacity: 0.4;
618
528
  cursor: default;
619
529
  }
620
- .dsh-rewind-cleanup-discard:focus-visible,
621
530
  .dsh-rewind-cleanup-save:focus-visible {
622
531
  outline: 2px solid var(--dsw-alias-brand-primary);
623
532
  outline-offset: 1px;
@@ -984,6 +893,23 @@ var import_react = require("react");
984
893
  var import_react_dom = require("react-dom");
985
894
 
986
895
  // src/client/pending.ts
896
+ var QUEUE_PREVIEW_CHARS = 200;
897
+ function textOf(content) {
898
+ if (!content.every((block) => block.type === "text")) return null;
899
+ return content.map((block) => block.text ?? "").join("");
900
+ }
901
+ function previewOf(content) {
902
+ const flat = content.filter((block) => block.type !== "image" && block.type !== "file").map((block) => block.type === "text" ? block.text ?? "" : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
903
+ const chars = Array.from(flat);
904
+ return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join("")}\u2026` : flat;
905
+ }
906
+ function steeringItemsOf(nextStep) {
907
+ return (nextStep ?? []).filter((item) => item.source?.kind === "user").map((item) => ({
908
+ id: item.id,
909
+ text: textOf(item.content),
910
+ preview: previewOf(item.content)
911
+ }));
912
+ }
987
913
  function matchPendingRows(rows, steering) {
988
914
  const matched = [];
989
915
  for (let i = 0; i < rows.length; i++) {
@@ -1045,7 +971,7 @@ function userNodeOf(chat, key) {
1045
971
  if (node === void 0 || node.kind !== "user" && node.kind !== "steering") return void 0;
1046
972
  return node.data;
1047
973
  }
1048
- async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, watchChat, setComposerText) {
974
+ async function runRewindAndFill(session, seq, mode, isMainViewSession, chatOf, watchChat, setComposerText) {
1049
975
  const known = knownCommandSeqs(session, chatOf, (node) => isExecutedRewindCommand(node, seq));
1050
976
  let result;
1051
977
  try {
@@ -1077,7 +1003,7 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, wa
1077
1003
  showHint(outcome.text ?? "rewind failed");
1078
1004
  return;
1079
1005
  }
1080
- if (currentSessionId() !== session.sessionId) {
1006
+ if (!isMainViewSession(session.sessionId)) {
1081
1007
  return;
1082
1008
  }
1083
1009
  let text;
@@ -1155,19 +1081,21 @@ function collectDurableTargets(snapshot, chat, hiddenSeqs) {
1155
1081
  if (chat === void 0 || isRewindInertSession(snapshot)) return [];
1156
1082
  return collectTargets(chat, hiddenSeqs);
1157
1083
  }
1084
+ function inboxOf(session) {
1085
+ return session.projections.faceOf("inbox").getSnapshot();
1086
+ }
1158
1087
  function bubbleTextOf(row) {
1159
1088
  const clone = row.cloneNode(true);
1160
1089
  clone.lastElementChild?.remove();
1161
1090
  return clone.textContent ?? "";
1162
1091
  }
1163
- function collectPendingTargets(snapshot) {
1092
+ function collectPendingTargets(snapshot, steering) {
1164
1093
  if (snapshot.subagent !== null) return [];
1165
- const steering = snapshot.queue.filter((item) => item.placement === "steering");
1166
1094
  if (steering.length === 0) return [];
1167
1095
  const rows = Array.from(document.querySelectorAll(PENDING_SEAT_SELECTOR));
1168
1096
  const matched = matchPendingRows(
1169
1097
  rows.map((row) => ({ text: bubbleTextOf(row) })),
1170
- steering.map((item) => ({ id: item.id, text: item.text }))
1098
+ steering
1171
1099
  );
1172
1100
  const targets = [];
1173
1101
  for (let i = 0; i < matched.length; i++) {
@@ -1197,7 +1125,7 @@ function sameTargets(left, right) {
1197
1125
  return other.kind === "pending" && target.itemId === other.itemId;
1198
1126
  });
1199
1127
  }
1200
- function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchChat, t, subscribeLocale, setComposerText }) {
1128
+ function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchChat, t, subscribeLocale, setComposerText }) {
1201
1129
  const [targets, setTargets] = (0, import_react.useState)([]);
1202
1130
  const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
1203
1131
  const [, forceRender] = (0, import_react.useReducer)((count) => count + 1, 0);
@@ -1231,7 +1159,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchCh
1231
1159
  }
1232
1160
  }
1233
1161
  const durable = collectDurableTargets(snapshot, chat, hiddenSeqs);
1234
- const next = [...durable, ...collectPendingTargets(snapshot)];
1162
+ const next = [...durable, ...collectPendingTargets(snapshot, steeringItemsOf(inboxOf(session)?.["next-step"]))];
1235
1163
  setTargets((current) => sameTargets(current, next) ? current : next);
1236
1164
  };
1237
1165
  const queueRefresh = () => {
@@ -1271,7 +1199,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchCh
1271
1199
  sessionOf,
1272
1200
  chatOf,
1273
1201
  watchChat,
1274
- currentSessionId,
1202
+ isMainViewSession,
1275
1203
  setComposerText,
1276
1204
  t
1277
1205
  },
@@ -1281,7 +1209,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchCh
1281
1209
  target.key
1282
1210
  ));
1283
1211
  }
1284
- function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, currentSessionId, setComposerText, t }) {
1212
+ function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, isMainViewSession, setComposerText, t }) {
1285
1213
  const onClick = (event) => {
1286
1214
  event.stopPropagation();
1287
1215
  const session = sessionOf(sessionId);
@@ -1301,7 +1229,7 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, current
1301
1229
  anchor: event.currentTarget,
1302
1230
  t,
1303
1231
  onRewind: (mode) => {
1304
- void runRewindAndFill(session, node.seq, mode, currentSessionId, chatOf, watchChat, setComposerText);
1232
+ void runRewindAndFill(session, node.seq, mode, isMainViewSession, chatOf, watchChat, setComposerText);
1305
1233
  }
1306
1234
  });
1307
1235
  };
@@ -1323,8 +1251,7 @@ function composerText() {
1323
1251
  return surface.textContent ?? "";
1324
1252
  }
1325
1253
  async function retractPending(session, itemId, text, setComposerText) {
1326
- const queue = session.getSnapshot().queue;
1327
- const steering = queue.filter((item) => item.placement === "steering");
1254
+ const steering = steeringItemsOf(inboxOf(session)?.["next-step"]);
1328
1255
  let removedTarget = false;
1329
1256
  for (const id of retractSpan(steering, itemId)) {
1330
1257
  let accepted = false;
@@ -1379,8 +1306,9 @@ function createRewindBridge(deps) {
1379
1306
  }
1380
1307
 
1381
1308
  // src/client/build-info.ts
1382
- var PLUGIN_VERSION = true ? "0.12.2" : "dev";
1383
- var BUILD_HASH = true ? "c9f4bb5f" : "dev";
1309
+ var PLUGIN_VERSION = true ? "0.13.0-alpha.2" : "dev";
1310
+ var PLUGIN_PACKAGE = "dsh-rewind-plugin";
1311
+ var BUILD_HASH = true ? "d7022b63" : "dev";
1384
1312
 
1385
1313
  // src/client/locales.ts
1386
1314
  var zh = {
@@ -1408,23 +1336,19 @@ var zh = {
1408
1336
  "popover.impact.delete": "\u5220\u9664 {path}",
1409
1337
  "popover.confirm": "\u786E\u8BA4\u56DE\u9000",
1410
1338
  "popover.back": "\u8FD4\u56DE",
1411
- "cleanup.title": "\u5FEB\u7167\u6E05\u7406",
1412
- "cleanup.desc": "dsh-rewind \xB7 \u7BA1\u7406\u4F1A\u8BDD\u5FEB\u7167\u5907\u4EFD\u7684\u81EA\u52A8\u6E05\u7406\u7B56\u7565",
1413
- "cleanup.expand": "\u5C55\u5F00",
1414
- "cleanup.collapse": "\u6536\u8D77",
1415
- "cleanup.unsaved": "\u672A\u4FDD\u5B58\u4FEE\u6539",
1416
1339
  "cleanup.auto": "\u81EA\u52A8\u6E05\u7406",
1417
1340
  "cleanup.auto.on": "\u5F00\u542F\u540E\u6309\u4E0D\u6D3B\u8DC3\u5929\u6570\u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\u5FEB\u7167",
1418
1341
  "cleanup.auto.off": "\u5173\u95ED\u540E\u4FDD\u7559\u5168\u90E8\u5FEB\u7167\uFF0C\u4E0D\u81EA\u52A8\u6E05\u7406",
1419
1342
  "cleanup.maxAge": "\u4E0D\u6D3B\u8DC3\u65F6\u95F4\uFF08\u5929\uFF09",
1420
1343
  "cleanup.maxAge.hint": "\u8D85\u8FC7\u8BE5\u5929\u6570\u672A\u6D3B\u52A8\u7684\u4F1A\u8BDD\u5FEB\u7167\u4F1A\u88AB\u6E05\u7406",
1421
- "cleanup.invalid": "\u4EC5\u63A5\u53D7\u6B63\u6574\u6570",
1422
- "cleanup.discard": "\u653E\u5F03\u4FEE\u6539",
1344
+ "cleanup.invalid": "\u8BF7\u586B\u6B63\u6574\u6570\uFF1B\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u9ED8\u8BA4\u503C",
1345
+ "cleanup.overridden": "\u5DF2\u8986\u76D6",
1346
+ "cleanup.reset": "\u6062\u590D\u9ED8\u8BA4",
1423
1347
  "cleanup.save": "\u4FDD\u5B58",
1424
1348
  "cleanup.saving": "\u4FDD\u5B58\u4E2D\u2026",
1425
- "cleanup.saved": "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548",
1426
- "cleanup.saveFailed": "\u4FDD\u5B58\u5931\u8D25\uFF1A{message}",
1427
- "cleanup.readonly": "\u8BBE\u7F6E\u6E90\u53EA\u8BFB"
1349
+ "cleanup.saveFailed": "\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539",
1350
+ "cleanup.readonly": "\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB",
1351
+ "cleanup.unavailable": "\u8BE5\u63D2\u4EF6\u5F53\u524D\u672A\u52A0\u8F7D\uFF0C\u6682\u65F6\u65E0\u6CD5\u914D\u7F6E"
1428
1352
  };
1429
1353
  var en = {
1430
1354
  "button.aria": "Rewind to this message",
@@ -1451,185 +1375,211 @@ var en = {
1451
1375
  "popover.impact.delete": "Delete {path}",
1452
1376
  "popover.confirm": "Confirm rewind",
1453
1377
  "popover.back": "Back",
1454
- "cleanup.title": "Snapshot cleanup",
1455
- "cleanup.desc": "dsh-rewind \xB7 Manage the auto-cleanup policy of session snapshot backups",
1456
- "cleanup.expand": "Expand",
1457
- "cleanup.collapse": "Collapse",
1458
- "cleanup.unsaved": "Unsaved changes",
1459
1378
  "cleanup.auto": "Auto cleanup",
1460
1379
  "cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically",
1461
1380
  "cleanup.auto.off": "When disabled, all snapshot backups are kept; nothing is cleaned automatically.",
1462
1381
  "cleanup.maxAge": "Idle time (days)",
1463
1382
  "cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned",
1464
- "cleanup.invalid": "A positive integer only",
1465
- "cleanup.discard": "Discard changes",
1383
+ "cleanup.invalid": "Enter a positive integer, or leave blank to use the default",
1384
+ "cleanup.overridden": "Overridden",
1385
+ "cleanup.reset": "Reset to default",
1466
1386
  "cleanup.save": "Save",
1467
1387
  "cleanup.saving": "Saving\u2026",
1468
- "cleanup.saved": "Saved and applied",
1469
- "cleanup.saveFailed": "Save failed: {message}",
1470
- "cleanup.readonly": "Read-only settings source"
1388
+ "cleanup.saveFailed": "The deployment did not accept these values; they were left for you to correct",
1389
+ "cleanup.readonly": "This deployment stores settings read-only",
1390
+ "cleanup.unavailable": "This plugin is not loaded, so it cannot be configured right now"
1471
1391
  };
1472
1392
 
1473
1393
  // src/client/settings-card.tsx
1474
1394
  var import_react2 = require("react");
1395
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1475
1396
  var import_jsx_runtime2 = require("react/jsx-runtime");
1476
1397
  var CLEANUP_SETTINGS_NAMESPACE = "dsh-rewind-snapshot-cleanup";
1477
1398
  var DEFAULT_MAX_AGE_DAYS = 30;
1478
- function draftFrom(policy) {
1479
- return { enabled: policy?.enabled ?? false, maxAgeDays: String(policy?.maxAgeDays ?? "") };
1480
- }
1481
- function maxAgeOf(text) {
1399
+ function parseMaxAge(text) {
1482
1400
  const trimmed = text.trim();
1483
- if (!/^\d+$/.test(trimmed)) return null;
1401
+ if (trimmed === "") return { kind: "clear" };
1402
+ if (!/^\d+$/.test(trimmed)) return void 0;
1484
1403
  const days = Number(trimmed);
1485
- return Number.isSafeInteger(days) && days > 0 ? days : null;
1486
- }
1487
- function configOf(draft) {
1488
- const days = maxAgeOf(draft.maxAgeDays);
1489
- if (days === null) return null;
1490
- return { enabled: draft.enabled, maxAgeDays: days };
1491
- }
1492
- function dirtyOf(base, draft) {
1493
- return base.enabled !== draft.enabled || base.maxAgeDays !== draft.maxAgeDays;
1494
- }
1495
- function SettingsCleanupCard({ api, t }) {
1496
- const [open, setOpen] = (0, import_react2.useState)(false);
1497
- const [baseline, setBaseline] = (0, import_react2.useState)(() => draftFrom(api.read()));
1498
- const [draft, setDraft] = (0, import_react2.useState)(() => draftFrom(api.read()));
1499
- const [busy, setBusy] = (0, import_react2.useState)(false);
1500
- const [error, setError] = (0, import_react2.useState)(null);
1501
- const writable = api.writable();
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) {
1456
+ return {
1457
+ dirty: plan.length > 0,
1458
+ invalid: plan.some((entry) => entry.kind === "invalid"),
1459
+ saving: flags.saving,
1460
+ failed: flags.failed
1461
+ };
1462
+ }
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);
1502
1468
  (0, import_react2.useEffect)(() => api.subscribe(() => {
1503
- const next = draftFrom(api.read());
1504
- setBaseline((base) => {
1505
- setDraft((cur) => dirtyOf(base, cur) ? cur : next);
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);
1506
1480
  return next;
1507
1481
  });
1508
- }), [api]);
1509
- const dirty = dirtyOf(baseline, draft);
1510
- const days = maxAgeOf(draft.maxAgeDays);
1511
- const invalid = days === null;
1512
- const disabled = busy || !writable;
1513
- const edit = (patch) => {
1514
- setDraft((cur) => ({ ...cur, ...patch }));
1515
- setError(null);
1482
+ setFailed(false);
1516
1483
  };
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;
1517
1489
  const save = async () => {
1518
- if (busy || !writable || !dirty) return;
1519
- const next = configOf(draft);
1520
- if (next === null) {
1521
- setError(t("cleanup.invalid"));
1522
- return;
1523
- }
1524
- setBusy(true);
1525
- setError(null);
1526
- try {
1527
- await api.save(next);
1528
- setBaseline(draft);
1529
- setError(null);
1530
- } catch (e) {
1531
- setError(t("cleanup.saveFailed", { message: e instanceof Error ? e.message : String(e) }));
1532
- } finally {
1533
- setBusy(false);
1534
- }
1535
- };
1536
- const discard = () => {
1537
- if (busy) return;
1538
- setDraft(baseline);
1539
- setError(null);
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);
1540
1503
  };
1541
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: `dsh-rewind-cleanup-card${open ? " dsh-rewind-cleanup-card-open" : ""}`, children: [
1542
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1543
- "button",
1544
- {
1545
- type: "button",
1546
- className: "dsh-rewind-cleanup-header",
1547
- "aria-expanded": open,
1548
- "aria-label": `${t(open ? "cleanup.collapse" : "cleanup.expand")}: ${t("cleanup.title")}`,
1549
- onClick: () => setOpen(!open),
1550
- children: [
1551
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-rewind-cleanup-head-text", children: [
1552
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-name", children: t("cleanup.title") }),
1553
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-desc", children: t("cleanup.desc") })
1554
- ] }),
1555
- dirty ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-pending", children: t("cleanup.unsaved") }) : null,
1556
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1557
- "svg",
1558
- {
1559
- className: `dsh-rewind-cleanup-chevron${open ? " dsh-rewind-cleanup-chevron-open" : ""}`,
1560
- width: "14",
1561
- height: "14",
1562
- viewBox: "0 0 16 16",
1563
- "aria-hidden": "true",
1564
- 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" })
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,
1520
+ {
1521
+ checked: enabled,
1522
+ label: t("cleanup.auto"),
1523
+ disabled,
1524
+ onChange: (next) => {
1525
+ stage("enabled", { text: formatEnabled(next), clear: false });
1565
1526
  }
1566
- )
1567
- ]
1568
- }
1569
- ),
1570
- open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-body", children: [
1571
- !writable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-readonly", role: "status", children: t("cleanup.readonly") }) : null,
1572
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
1573
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
1574
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", id: "dsh-rewind-cleanup-enabled-label", children: t("cleanup.auto") }),
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") }),
1575
1537
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1576
1538
  "button",
1577
1539
  {
1578
1540
  type: "button",
1579
- role: "switch",
1580
- className: "dsh-rewind-cleanup-switch",
1581
- "aria-checked": draft.enabled,
1582
- "aria-labelledby": "dsh-rewind-cleanup-enabled-label",
1541
+ className: "dsh-rewind-cleanup-reset",
1583
1542
  disabled,
1584
- onClick: () => edit({ enabled: !draft.enabled }),
1585
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-thumb" })
1543
+ onClick: () => {
1544
+ stage("maxAgeDays", { text: formatMaxAge(api.base("maxAgeDays")), clear: true });
1545
+ },
1546
+ children: t("cleanup.reset")
1586
1547
  }
1587
1548
  )
1588
- ] }),
1589
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(draft.enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
1549
+ ] }) : null
1590
1550
  ] }),
1591
- draft.enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-field", children: [
1592
- /* @__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") }) }),
1593
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1594
- "input",
1595
- {
1596
- className: `dsh-rewind-cleanup-input${invalid ? " dsh-rewind-cleanup-input-invalid" : ""}`,
1597
- type: "text",
1598
- inputMode: "numeric",
1599
- id: "dsh-rewind-cleanup-maxage",
1600
- value: draft.maxAgeDays,
1601
- disabled,
1602
- "aria-invalid": invalid || void 0,
1603
- placeholder: String(DEFAULT_MAX_AGE_DAYS),
1604
- onChange: (e) => edit({ maxAgeDays: e.target.value })
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 });
1605
1565
  }
1606
- ),
1607
- /* @__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") })
1608
- ] }) : null,
1609
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-footer", children: [
1610
- error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-failed", role: "status", children: error }) : null,
1611
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1612
- "button",
1613
- {
1614
- type: "button",
1615
- className: "dsh-rewind-cleanup-discard",
1616
- disabled: !dirty || busy || !writable,
1617
- onClick: discard,
1618
- children: t("cleanup.discard")
1619
- }
1620
- ),
1621
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1622
- "button",
1623
- {
1624
- type: "button",
1625
- className: "dsh-rewind-cleanup-save",
1626
- disabled: !dirty || busy || !writable || invalid,
1627
- onClick: save,
1628
- children: busy ? t("cleanup.saving") : t("cleanup.save")
1629
- }
1630
- )
1631
- ] })
1632
- ] }) : null
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
+ ] })
1633
1583
  ] });
1634
1584
  }
1635
1585
 
@@ -1645,11 +1595,11 @@ function apply(ctx) {
1645
1595
  yield ctx.locale.register(NS2, { zh, en });
1646
1596
  const t = ctx.locale.bind(NS2);
1647
1597
  const style = document.createElement("style");
1648
- style.dataset.plugin = "dsh-rewind";
1598
+ style.dataset.plugin = PLUGIN_PACKAGE;
1649
1599
  style.textContent = STYLE;
1650
1600
  document.head.appendChild(style);
1651
1601
  const sessionOf = (sessionId) => ctx.sessions.binding(sessionId)?.session;
1652
- const currentSessionId = () => ctx.sessions.list.getSnapshot().current;
1602
+ const isMainViewSession = (sessionId) => (ctx.sessions.list.getSnapshot().byId[sessionId]?.retainedBy.mainView ?? 0) > 0;
1653
1603
  const subscribeLocale = (cb) => ctx.locale.subscribe(cb);
1654
1604
  const uiConversation = () => ctx.get("uiConversation");
1655
1605
  const CHAT_VIEW = "chat";
@@ -1700,33 +1650,51 @@ function apply(ctx) {
1700
1650
  id: "dsh-rewind-portals",
1701
1651
  order: 1e3
1702
1652
  },
1703
- createRewindBridge({ sessionOf, chatOf, currentSessionId, watchChat, setComposerText, t, subscribeLocale })
1653
+ createRewindBridge({ sessionOf, chatOf, isMainViewSession, watchChat, setComposerText, t, subscribeLocale })
1704
1654
  ));
1705
1655
  const clientCtx = ctx;
1706
1656
  clientCtx.inject(["settingsScope"], (scoped) => {
1707
1657
  try {
1708
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
+ };
1709
1663
  const cardApi = {
1710
- read: () => {
1711
- const value = scope.getSnapshot().value;
1712
- return value === void 0 ? void 0 : { enabled: value.enabled, maxAgeDays: value.maxAgeDays };
1713
- },
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",
1714
1667
  writable: () => {
1715
1668
  return scope.getSnapshot().writable === true;
1716
1669
  },
1717
- save: async (next) => {
1718
- await scope.set("enabled", next.enabled);
1719
- await scope.set("maxAgeDays", next.maxAgeDays);
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);
1720
1686
  },
1721
1687
  subscribe: (cb) => scope.subscribe(cb)
1722
1688
  };
1723
- scoped.slots.inject("settings.plugin.item", () => scoped.slots.register(
1689
+ scoped.slots.inject("plugins.bundle.config", () => scoped.slots.register(
1724
1690
  {
1725
- name: "settings.plugin.item",
1726
- key: CLEANUP_SETTINGS_NAMESPACE,
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,
1727
1695
  // Match the official cards / dsh-market: locale + inject provide
1728
- // the card its props through the slot renderer (the keyed card owns
1729
- // its internals, but the page feeds it locale + the bound api).
1696
+ // the card its props through the slot renderer (the page feeds it
1697
+ // the `view` it asks for plus the bound api).
1730
1698
  locale: NS2,
1731
1699
  inject: () => ({ t, api: cardApi })
1732
1700
  },
@@ -1789,7 +1757,7 @@ function apply(ctx) {
1789
1757
  anchor: composerAnchor(),
1790
1758
  t,
1791
1759
  onRewind: (mode) => {
1792
- void runRewindAndFill(face, candidate.seq, mode, currentSessionId, chatOf, watchChat, setComposerText);
1760
+ void runRewindAndFill(face, candidate.seq, mode, isMainViewSession, chatOf, watchChat, setComposerText);
1793
1761
  }
1794
1762
  });
1795
1763
  }
@@ -1800,6 +1768,7 @@ function apply(ctx) {
1800
1768
  }
1801
1769
  const composerSurface2 = () => document.querySelector(COMPOSER_EDITABLE_SELECTOR2);
1802
1770
  yield () => {
1771
+ closePopover();
1803
1772
  style.remove();
1804
1773
  };
1805
1774
  }, "dsh-rewind client lifecycle");