dsh-rewind-plugin 0.18.0 → 0.19.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
@@ -9,6 +9,7 @@ var __defProp = Object.defineProperty;
9
9
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
11
11
  var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
13
  var __export = (target, all) => {
13
14
  for (var name2 in all)
14
15
  __defProp(target, name2, { get: all[name2], enumerable: true });
@@ -22,22 +23,72 @@ var __copyProps = (to, from, except, desc) => {
22
23
  return to;
23
24
  };
24
25
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
26
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
25
27
 
26
28
  // src/client/index.ts
27
29
  var index_exports = {};
28
30
  __export(index_exports, {
29
31
  apply: () => apply,
30
- hiddenSeqsOf: () => hiddenSeqsOf,
31
32
  inject: () => inject,
32
- name: () => name,
33
- targetSeqOfArgs: () => targetSeqOfArgs
33
+ name: () => name
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
+ // src/client/candidates.ts
38
+ var PREVIEW_CHARS = 80;
39
+ var DEFAULT_CANDIDATE_LIMIT = 100;
40
+ function messagePreviewOf(message) {
41
+ const text = message.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("").replace(/\s+/g, " ").trim();
42
+ return text.length <= PREVIEW_CHARS ? text : `${text.slice(0, PREVIEW_CHARS - 1)}\u2026`;
43
+ }
44
+ function formatCandidateTime(time) {
45
+ const d = new Date(time);
46
+ const hh = String(d.getHours()).padStart(2, "0");
47
+ const mm = String(d.getMinutes()).padStart(2, "0");
48
+ return `${hh}:${mm}`;
49
+ }
50
+ function rewindCandidatesOf(snap, limit = DEFAULT_CANDIDATE_LIMIT) {
51
+ const candidates = [];
52
+ for (let i = snap.order.length - 1; i >= 0 && candidates.length < limit; i--) {
53
+ const key = snap.order[i];
54
+ if (key === void 0) continue;
55
+ const node = snap.nodes.get(key);
56
+ if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
57
+ candidates.push({
58
+ seq: node.data.seq,
59
+ time: node.data.time,
60
+ preview: messagePreviewOf(node.data)
61
+ });
62
+ }
63
+ return candidates;
64
+ }
65
+ function rewindCandidatesOfChat(snap) {
66
+ return rewindCandidatesOf(snap);
67
+ }
68
+ function rewindOptionsFromCandidates(candidates, t) {
69
+ return candidates.map((candidate) => ({
70
+ id: String(candidate.seq),
71
+ label: candidate.preview || t("popover.noText"),
72
+ detail: formatCandidateTime(candidate.time)
73
+ }));
74
+ }
75
+
37
76
  // src/client/hidden.ts
38
77
  var ENGINE_COMMAND = "rewind-plugin";
39
78
  var LEGACY_ENGINE_COMMAND = "rewind";
40
79
  var ENGINE_COMMANDS = /* @__PURE__ */ new Set([ENGINE_COMMAND, LEGACY_ENGINE_COMMAND]);
80
+ function chatNodesOf(chat) {
81
+ if (chat === void 0) return EMPTY_NODES;
82
+ const values = chat.nodes.values;
83
+ if (typeof values === "function") return [...values.call(chat.nodes)];
84
+ const nodes = [];
85
+ for (const key of chat.order) {
86
+ const node = chat.nodes.get(key);
87
+ if (node !== void 0) nodes.push(node);
88
+ }
89
+ return nodes;
90
+ }
91
+ var EMPTY_NODES = [];
41
92
  function resolveChatWatch(resolveView, sessionId, cb) {
42
93
  const view = resolveView(sessionId);
43
94
  return view?.subscribe?.(cb) ?? (() => {
@@ -48,9 +99,8 @@ function chatSnapshotOf(chatView) {
48
99
  }
49
100
  function messageTextAt(chat, seq) {
50
101
  if (chat === void 0) return void 0;
51
- for (const key of chat.order) {
52
- const node = chat.nodes.get(key);
53
- if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
102
+ for (const node of chatNodesOf(chat)) {
103
+ if (node.kind !== "user" && node.kind !== "steering") continue;
54
104
  const data = node.data;
55
105
  if (data.seq === seq) {
56
106
  return data.content?.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
@@ -60,9 +110,8 @@ function messageTextAt(chat, seq) {
60
110
  }
61
111
  function messageAttachmentsAt(chat, seq) {
62
112
  if (chat === void 0) return void 0;
63
- for (const key of chat.order) {
64
- const node = chat.nodes.get(key);
65
- if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
113
+ for (const node of chatNodesOf(chat)) {
114
+ if (node.kind !== "user" && node.kind !== "steering") continue;
66
115
  const data = node.data;
67
116
  if (data.seq !== seq) continue;
68
117
  const images = [];
@@ -100,141 +149,21 @@ function isPreviewCommand(command) {
100
149
  function isCandidateCommand(command) {
101
150
  return (command.args ?? "").includes("__candidates");
102
151
  }
103
- function coalesceCuts(spans) {
104
- if (spans.length < 2) return spans;
105
- const sorted = [...spans].sort((left, right) => left.start - right.start || left.end - right.end);
106
- const merged = [];
107
- for (const span of sorted) {
108
- const last = merged[merged.length - 1];
109
- if (last !== void 0 && span.start <= last.end) {
110
- if (span.end > last.end) last.end = span.end;
111
- continue;
112
- }
113
- merged.push({ start: span.start, end: span.end });
114
- }
115
- return merged;
116
- }
117
- function cutsContain(cuts, seq) {
118
- let low = 0;
119
- let high = cuts.length - 1;
120
- while (low <= high) {
121
- const middle = low + high >> 1;
122
- const cut = cuts[middle];
123
- if (seq < cut.start) high = middle - 1;
124
- else if (seq > cut.end) low = middle + 1;
125
- else return true;
126
- }
127
- return false;
128
- }
129
- function liveAnchorKeys(snap, cuts) {
130
- const live = /* @__PURE__ */ new Map();
131
- for (const key of snap.order) {
132
- const node = snap.nodes.get(key);
133
- if (node === void 0 || cutsContain(cuts, node.anchorSeq)) continue;
134
- const keys = live.get(node.anchorSeq);
135
- if (keys === void 0) live.set(node.anchorSeq, [key]);
136
- else keys.push(key);
137
- }
138
- return live;
139
- }
140
- var EMPTY_KEYS = [];
141
- function addWithdrawnTurnChrome(snap, cuts, hidden) {
142
- const locations = snap.locations;
143
- if (locations === void 0 || cuts.length === 0) return;
144
- let live;
145
- for (const key of snap.order) {
146
- const chrome = snap.nodes.get(key);
147
- if (chrome === void 0 || chrome.kind !== "turn-process") continue;
148
- const located = chrome.location?.turn;
149
- if (located?.start === void 0) continue;
150
- const turn = located.turn;
151
- if (typeof turn !== "number") continue;
152
- const keys = locations.getTurn(turn);
153
- if (keys.length === 0) continue;
154
- const rows = keys.map((member) => snap.nodes.get(member)).filter((member) => member !== void 0);
155
- if (rows.length !== keys.length) continue;
156
- if (!rows.every((member) => member.key === chrome.key || cutsContain(cuts, member.anchorSeq))) continue;
157
- live ?? (live = liveAnchorKeys(snap, cuts));
158
- if ((live.get(chrome.anchorSeq) ?? EMPTY_KEYS).some((holder) => holder !== chrome.key)) continue;
159
- hidden.add(chrome.anchorSeq);
160
- }
161
- }
162
- var hiddenSeqsMemo = /* @__PURE__ */ new WeakMap();
163
- function hiddenSeqsOf(snap) {
164
- const memo = hiddenSeqsMemo.get(snap);
165
- if (memo !== void 0) return new Set(memo);
166
- const hidden = /* @__PURE__ */ new Set();
167
- const spans = [];
168
- for (const key of snap.order) {
169
- const node = snap.nodes.get(key);
170
- if (node === void 0 || node.kind !== "command") continue;
152
+ function rewindCutsOf(nodes) {
153
+ const cuts = [];
154
+ for (const node of nodes) {
155
+ if (node.kind !== "command") continue;
171
156
  const command = node.data;
172
157
  if (command.name === null || !ENGINE_COMMANDS.has(command.name)) continue;
173
- if (isPreviewCommand(command) || isCandidateCommand(command)) {
174
- hidden.add(command.seq);
175
- continue;
176
- }
158
+ if (isPreviewCommand(command) || isCandidateCommand(command)) continue;
177
159
  if (command.outcome?.kind !== "success") continue;
178
160
  const marker = command.outcome.sourceEventSeq;
179
161
  if (marker === void 0) continue;
180
- hidden.add(command.seq);
181
162
  const target = targetSeqOfArgs(command.args);
182
- if (target !== void 0) {
183
- spans.push({ start: target, end: marker });
184
- }
163
+ if (target === void 0 || target > marker) continue;
164
+ cuts.push({ target, marker });
185
165
  }
186
- const cuts = coalesceCuts(spans);
187
- for (const key of snap.order) {
188
- const node = snap.nodes.get(key);
189
- if (node === void 0) continue;
190
- if (cutsContain(cuts, node.anchorSeq)) hidden.add(node.anchorSeq);
191
- }
192
- try {
193
- addWithdrawnTurnChrome(snap, cuts, hidden);
194
- } catch {
195
- }
196
- hiddenSeqsMemo.set(snap, new Set(hidden));
197
- return hidden;
198
- }
199
-
200
- // src/client/candidates.ts
201
- var PREVIEW_CHARS = 80;
202
- var DEFAULT_CANDIDATE_LIMIT = 100;
203
- function messagePreviewOf(message) {
204
- const text = message.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("").replace(/\s+/g, " ").trim();
205
- return text.length <= PREVIEW_CHARS ? text : `${text.slice(0, PREVIEW_CHARS - 1)}\u2026`;
206
- }
207
- function formatCandidateTime(time) {
208
- const d = new Date(time);
209
- const hh = String(d.getHours()).padStart(2, "0");
210
- const mm = String(d.getMinutes()).padStart(2, "0");
211
- return `${hh}:${mm}`;
212
- }
213
- function rewindCandidatesOf(snap, hidden, limit = DEFAULT_CANDIDATE_LIMIT) {
214
- const candidates = [];
215
- for (let i = snap.order.length - 1; i >= 0 && candidates.length < limit; i--) {
216
- const key = snap.order[i];
217
- if (key === void 0) continue;
218
- const node = snap.nodes.get(key);
219
- if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
220
- if (hidden.has(node.anchorSeq ?? node.data.seq)) continue;
221
- candidates.push({
222
- seq: node.data.seq,
223
- time: node.data.time,
224
- preview: messagePreviewOf(node.data)
225
- });
226
- }
227
- return candidates;
228
- }
229
- function rewindCandidatesOfChat(snap) {
230
- return rewindCandidatesOf(snap, hiddenSeqsOf(snap));
231
- }
232
- function rewindOptionsFromCandidates(candidates, t) {
233
- return candidates.map((candidate) => ({
234
- id: String(candidate.seq),
235
- label: candidate.preview || t("popover.noText"),
236
- detail: formatCandidateTime(candidate.time)
237
- }));
166
+ return cuts;
238
167
  }
239
168
 
240
169
  // src/client/styles.ts
@@ -278,47 +207,58 @@ var STYLE = `
278
207
  width: calc(15px + var(--dsh-content-font-delta, 0px));
279
208
  height: calc(15px + var(--dsh-content-font-delta, 0px));
280
209
  }
281
- .dsh-rewind-btn:hover {
282
- background: var(--dsw-alias-interactive-bg-hover);
283
- color: var(--dsw-alias-label-secondary);
210
+ @media (hover: hover) {
211
+ .dsh-rewind-btn:hover {
212
+ background: var(--dsw-alias-interactive-bg-hover);
213
+ color: var(--dsw-alias-label-secondary);
214
+ }
284
215
  }
285
216
 
286
217
  .dsh-rewind-popover {
287
218
  position: fixed;
288
- z-index: 1000;
289
- width: 288px;
290
- padding: 12px;
219
+ z-index: 1100;
220
+ box-sizing: border-box;
221
+ width: min(300px, calc(100vw - 24px));
222
+ padding: 16px;
291
223
  border: 0;
292
- border-radius: var(--dsw-radius-md);
224
+ border-radius: var(--dsw-radius-lg);
293
225
  background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));
294
226
  backdrop-filter: var(--dsw-menu-backdrop-filter);
295
227
  isolation: isolate;
296
228
  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
229
+ /* Elevated surface: match the menu cards' l2 scrollbar tokens. */
230
+ --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
231
+ --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
297
232
  box-shadow: var(--dsw-elevation-prominent);
298
- font-size: 14px;
299
- line-height: 20px;
233
+ font-size: 12px;
234
+ line-height: 18px;
300
235
  color: var(--dsw-alias-label-primary);
301
236
  }
302
237
  .dsh-rewind-popover-title {
303
- font-size: 14px;
304
- font-weight: 600;
305
- line-height: 20px;
238
+ font-size: 12px;
239
+ font-weight: 500;
240
+ line-height: 18px;
306
241
  }
307
242
  .dsh-rewind-popover-target {
308
- margin: 4px 0 10px;
243
+ margin: 2px 0 10px;
309
244
  font-size: 12px;
310
- line-height: 16px;
245
+ line-height: 18px;
311
246
  color: var(--dsw-alias-label-tertiary);
312
247
  word-break: break-all;
248
+ /* The full message sits above the popover; two lines are enough context. */
249
+ display: -webkit-box;
250
+ -webkit-box-orient: vertical;
251
+ -webkit-line-clamp: 2;
252
+ overflow: hidden;
313
253
  }
314
254
  .dsh-rewind-popover-option {
315
255
  display: flex;
316
256
  flex-direction: column;
317
257
  gap: 2px;
318
258
  width: 100%;
319
- margin: 0 0 6px;
320
- padding: 8px 10px;
321
- border: 1px solid transparent;
259
+ margin: 0 0 2px;
260
+ padding: 6px 8px;
261
+ border: 0;
322
262
  border-radius: 8px;
323
263
  background: transparent;
324
264
  color: inherit;
@@ -338,7 +278,7 @@ var STYLE = `
338
278
  outline: none;
339
279
  }
340
280
  .dsh-rewind-popover-option:disabled {
341
- opacity: 0.5;
281
+ opacity: 0.4;
342
282
  cursor: default;
343
283
  }
344
284
  .dsh-rewind-popover-option-label {
@@ -371,15 +311,15 @@ var STYLE = `
371
311
  display: flex;
372
312
  justify-content: flex-end;
373
313
  gap: 8px;
314
+ margin-top: 8px;
374
315
  }
375
316
  .dsh-rewind-popover-primary,
376
317
  .dsh-rewind-popover-ghost {
377
- padding: 5px 12px;
318
+ height: 28px;
319
+ padding: 0 10px;
378
320
  border: none;
379
321
  border-radius: 8px;
380
322
  font: inherit;
381
- font-size: 13px;
382
- line-height: 18px;
383
323
  cursor: pointer;
384
324
  }
385
325
  .dsh-rewind-popover-primary {
@@ -390,7 +330,7 @@ var STYLE = `
390
330
  background: var(--dsw-alias-button-primary-hover);
391
331
  }
392
332
  .dsh-rewind-popover-primary:disabled {
393
- opacity: 0.5;
333
+ opacity: 0.4;
394
334
  cursor: default;
395
335
  }
396
336
  .dsh-rewind-popover-ghost {
@@ -400,6 +340,9 @@ var STYLE = `
400
340
  .dsh-rewind-popover-ghost:hover {
401
341
  background: var(--dsw-alias-interactive-bg-hover);
402
342
  }
343
+ .dsh-rewind-popover-ghost:active {
344
+ background: var(--dsw-alias-interactive-bg-active);
345
+ }
403
346
 
404
347
  .dsh-rewind-guard-hint {
405
348
  position: fixed;
@@ -452,12 +395,21 @@ var STYLE = `
452
395
 
453
396
  // src/client/popover.ts
454
397
  var popoverEl = null;
398
+ var popoverAnchor = null;
455
399
  var disposeOutside = null;
456
400
  var liveSessionId = null;
457
401
  var composerFocuser = null;
458
402
  function registerComposerFocuser(focus) {
459
403
  composerFocuser = focus;
460
404
  }
405
+ function markOpen(anchor) {
406
+ popoverAnchor = anchor;
407
+ anchor.setAttribute("aria-expanded", "true");
408
+ }
409
+ function markClosed() {
410
+ popoverAnchor?.setAttribute("aria-expanded", "false");
411
+ popoverAnchor = null;
412
+ }
461
413
  function closePopover() {
462
414
  if (popoverEl !== null) {
463
415
  popoverEl.remove();
@@ -467,6 +419,7 @@ function closePopover() {
467
419
  disposeOutside();
468
420
  disposeOutside = null;
469
421
  }
422
+ markClosed();
470
423
  const sessionId = liveSessionId;
471
424
  liveSessionId = null;
472
425
  if (sessionId !== null) {
@@ -476,22 +429,14 @@ function closePopover() {
476
429
  }
477
430
  }
478
431
  }
479
- function formatTarget(t, seq, time, preview) {
480
- const d = new Date(time);
481
- const hh = String(d.getHours()).padStart(2, "0");
482
- const mm = String(d.getMinutes()).padStart(2, "0");
483
- const previewText = preview.length > 0 ? preview : t("popover.noText");
484
- return `seq ${seq} \xB7 ${hh}:${mm} \xB7 ${previewText}`;
485
- }
486
432
  function findCommand(chat, match) {
487
433
  if (chat === void 0) return void 0;
488
434
  let found;
489
- for (const key of chat.order) {
490
- const node = chat.nodes.get(key);
491
- if (node !== void 0 && node.kind === "command") {
492
- const command = node.data;
493
- if (match(command)) found = command;
494
- }
435
+ for (const node of chatNodesOf(chat)) {
436
+ if (node.kind !== "command") continue;
437
+ const command = node.data;
438
+ if (!match(command)) continue;
439
+ if (found === void 0 || command.seq >= found.seq) found = command;
495
440
  }
496
441
  return found;
497
442
  }
@@ -499,12 +444,10 @@ function knownCommandSeqs(session, chatOf, match) {
499
444
  const known = /* @__PURE__ */ new Set();
500
445
  const chat = chatOf(session);
501
446
  if (chat === void 0) return known;
502
- for (const key of chat.order) {
503
- const node = chat.nodes.get(key);
504
- if (node !== void 0 && node.kind === "command") {
505
- const command = node.data;
506
- if (match(command)) known.add(command.seq);
507
- }
447
+ for (const node of chatNodesOf(chat)) {
448
+ if (node.kind !== "command") continue;
449
+ const command = node.data;
450
+ if (match(command)) known.add(command.seq);
508
451
  }
509
452
  return known;
510
453
  }
@@ -566,7 +509,7 @@ function moveFocus(root, dir) {
566
509
  const next = index === -1 ? dir === 1 ? 0 : buttons.length - 1 : (index + dir + buttons.length) % buttons.length;
567
510
  buttons[next]?.focus();
568
511
  }
569
- function renderImpactStep(root, opts, back, cached) {
512
+ function renderImpactStep(root, opts, back, reposition, cached) {
570
513
  const { session, seq, t } = opts;
571
514
  const impact = el("div", CLASS.popoverImpact, t("popover.impact.loading"));
572
515
  const actions = el("div", CLASS.popoverActions);
@@ -584,6 +527,7 @@ function renderImpactStep(root, opts, back, cached) {
584
527
  actions.append(confirm);
585
528
  root.replaceChildren(impact, actions);
586
529
  focusFirst(root);
530
+ reposition();
587
531
  void (async () => {
588
532
  const outcome = cached ?? await previewImpact(session.sessionId, seq);
589
533
  if (outcome === null) {
@@ -619,16 +563,44 @@ function renderImpactStep(root, opts, back, cached) {
619
563
  });
620
564
  })().catch(() => {
621
565
  impact.textContent = t("popover.impact.failed", { message: "unexpected error" });
566
+ }).finally(() => {
567
+ reposition();
622
568
  });
623
569
  }
624
570
  function mountShell(root, anchor, onKeyDown) {
571
+ const margin = 12;
572
+ const gap = 4;
573
+ let flippedAbove = null;
625
574
  const position = () => {
575
+ if (!root.isConnected) return;
576
+ if (!anchor.isConnected) {
577
+ if (popoverEl === root) closePopover();
578
+ return;
579
+ }
626
580
  const rect = anchor.getBoundingClientRect();
627
- const gap = 4;
628
581
  const height = root.offsetHeight;
629
- const top = rect.bottom + gap + height <= window.innerHeight - 8 ? rect.bottom + gap : Math.max(8, rect.top - gap - height);
582
+ if (flippedAbove === null) {
583
+ flippedAbove = rect.bottom + gap + height > window.innerHeight - margin;
584
+ }
585
+ const top = flippedAbove ? Math.max(margin, rect.top - gap - height) : Math.min(Math.max(margin, rect.bottom + gap), Math.max(margin, window.innerHeight - margin - height));
630
586
  root.style.top = `${Math.round(top)}px`;
631
- root.style.left = `${Math.round(Math.min(rect.right, window.innerWidth - 8 - root.offsetWidth))}px`;
587
+ root.style.left = `${Math.round(Math.max(margin, Math.min(rect.right, window.innerWidth - margin - root.offsetWidth)))}px`;
588
+ };
589
+ let frame = 0;
590
+ const schedule = () => {
591
+ if (typeof requestAnimationFrame !== "function") {
592
+ position();
593
+ return;
594
+ }
595
+ if (frame !== 0) return;
596
+ frame = requestAnimationFrame(() => {
597
+ frame = 0;
598
+ position();
599
+ });
600
+ };
601
+ const onScroll = (event) => {
602
+ if (event.target instanceof Node && root.contains(event.target)) return;
603
+ schedule();
632
604
  };
633
605
  const onPointerDown = (event) => {
634
606
  const target = event.target;
@@ -639,19 +611,30 @@ function mountShell(root, anchor, onKeyDown) {
639
611
  document.addEventListener("pointerdown", onPointerDown);
640
612
  document.addEventListener("keydown", onKeyDown, true);
641
613
  }, 0);
614
+ document.body.append(root);
615
+ position();
616
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
617
+ schedule();
618
+ });
619
+ observer?.observe(root);
620
+ window.addEventListener("scroll", onScroll, true);
621
+ window.addEventListener("resize", schedule);
642
622
  const dispose = () => {
643
623
  clearTimeout(deferred);
644
624
  document.removeEventListener("pointerdown", onPointerDown);
645
625
  document.removeEventListener("keydown", onKeyDown, true);
626
+ window.removeEventListener("scroll", onScroll, true);
627
+ window.removeEventListener("resize", schedule);
628
+ observer?.disconnect();
629
+ if (frame !== 0) cancelAnimationFrame(frame);
646
630
  };
647
- document.body.append(root);
648
- position();
649
631
  return { position, dispose };
650
632
  }
651
633
  function openRetractPopover(opts) {
652
634
  closePopover();
653
635
  const { preview, anchor, t, retract, onRetract } = opts;
654
636
  if (retract === void 0 || onRetract === void 0) return;
637
+ markOpen(anchor);
655
638
  const root = el("div", CLASS.popover);
656
639
  root.setAttribute("role", "dialog");
657
640
  root.setAttribute("aria-label", t("popover.retract.title"));
@@ -711,6 +694,7 @@ function openPopover(opts) {
711
694
  const { session, seq, time, preview, anchor, t } = opts;
712
695
  const onRewind = opts.onRewind;
713
696
  if (seq === void 0 || time === void 0 || onRewind === void 0) return;
697
+ markOpen(anchor);
714
698
  const durableOpts = { session, seq, time, preview, anchor, t, onRewind };
715
699
  const root = el("div", CLASS.popover);
716
700
  root.setAttribute("role", "dialog");
@@ -718,11 +702,14 @@ function openPopover(opts) {
718
702
  let bothState = { state: "loading" };
719
703
  let impactOutcome = null;
720
704
  let step = "modes";
705
+ let reposition = () => {
706
+ };
721
707
  const renderModes = () => {
722
708
  step = "modes";
709
+ const previewText = preview.length > 0 ? preview : t("popover.noText");
723
710
  const children = [
724
711
  el("div", CLASS.popoverTitle, t("popover.title")),
725
- el("div", CLASS.popoverTarget, formatTarget(t, seq, time, preview)),
712
+ el("div", CLASS.popoverTarget, previewText),
726
713
  modeOption(t("popover.chat"), t("popover.chat.hint"), () => {
727
714
  closePopover();
728
715
  durableOpts.onRewind("chat");
@@ -741,20 +728,13 @@ function openPopover(opts) {
741
728
  if (bothState.state === "loading") option.disabled = true;
742
729
  children.push(option);
743
730
  }
744
- const actions = el("div", CLASS.popoverActions);
745
- const cancel = document.createElement("button");
746
- cancel.type = "button";
747
- cancel.className = CLASS.popoverGhost;
748
- cancel.textContent = t("popover.cancel");
749
- cancel.addEventListener("click", closePopover);
750
- actions.append(cancel);
751
- children.push(actions);
752
731
  root.replaceChildren(...children);
753
732
  focusFirst(root);
733
+ reposition();
754
734
  };
755
735
  const renderImpact = () => {
756
736
  step = "impact";
757
- renderImpactStep(root, durableOpts, renderModes, impactOutcome);
737
+ renderImpactStep(root, durableOpts, renderModes, reposition, impactOutcome);
758
738
  };
759
739
  const onKeyDown = (event) => {
760
740
  if (event.key === "ArrowDown") {
@@ -778,6 +758,7 @@ function openPopover(opts) {
778
758
  };
779
759
  renderModes();
780
760
  const shell = mountShell(root, anchor, onKeyDown);
761
+ reposition = shell.position;
781
762
  popoverEl = root;
782
763
  disposeOutside = shell.dispose;
783
764
  void (async () => {
@@ -789,17 +770,303 @@ function openPopover(opts) {
789
770
  bothState = { state: "error", message: outcome?.kind === "error" ? outcome.text : "preview request timed out" };
790
771
  }
791
772
  renderModes();
792
- shell.position();
793
773
  })().catch(() => {
794
774
  bothState = { state: "error", message: "unexpected error" };
795
775
  renderModes();
796
- shell.position();
797
776
  });
798
777
  }
799
778
 
779
+ // src/client/log.ts
780
+ var NS = "dsh-rewind";
781
+ var DEBUG_KEY = "dsh-rewind.debug";
782
+ var ALWAYS_ON = /* @__PURE__ */ new Set(["error", "warn"]);
783
+ function switchValue() {
784
+ try {
785
+ return window.localStorage.getItem(DEBUG_KEY) ?? "";
786
+ } catch {
787
+ return "";
788
+ }
789
+ }
790
+ function matches(value, ns) {
791
+ for (const entry of value.split(",")) {
792
+ const part = entry.trim();
793
+ if (part === "") continue;
794
+ if (part === "*" || part === `${NS}*`) return true;
795
+ if (part.endsWith("*")) {
796
+ if (ns.startsWith(part.slice(0, -1))) return true;
797
+ } else if (ns === part) {
798
+ return true;
799
+ }
800
+ }
801
+ return false;
802
+ }
803
+ function tag(scope) {
804
+ return `[${NS}:${scope}]`;
805
+ }
806
+ function log(level, scope, message, data) {
807
+ if (ALWAYS_ON.has(level)) {
808
+ console[level](tag(scope), message, data);
809
+ return;
810
+ }
811
+ if (!matches(switchValue(), `${NS}:${scope}`)) return;
812
+ console.info(tag(scope), message, data);
813
+ }
814
+ var rewindLog = {
815
+ error: (scope, message, data) => log("error", scope, message, data),
816
+ warn: (scope, message, data) => log("warn", scope, message, data),
817
+ info: (scope, message, data) => log("info", scope, message, data),
818
+ debug: (scope, message, data) => log("debug", scope, message, data)
819
+ };
820
+
821
+ // src/client/visibility.ts
822
+ var patches = /* @__PURE__ */ new Map();
823
+ var currentCleanup;
824
+ function installChatViewVisibility(ctx) {
825
+ let stopped = false;
826
+ const start = () => {
827
+ const scope = ctx.inject?.(["uiConversation"], (child) => {
828
+ if (!stopped) installOn(child.get("uiConversation"), ctx.sessions);
829
+ });
830
+ if (ctx.inject === void 0) {
831
+ installOn(ctx.get("uiConversation"), ctx.sessions);
832
+ }
833
+ return () => {
834
+ stopped = true;
835
+ currentCleanup?.();
836
+ currentCleanup = void 0;
837
+ if (typeof scope === "function") scope();
838
+ };
839
+ };
840
+ if (ctx.effect === void 0) {
841
+ start();
842
+ return;
843
+ }
844
+ ctx.effect(start, "dsh-rewind chat visibility");
845
+ }
846
+ function installOn(ui, sessions) {
847
+ currentCleanup?.();
848
+ currentCleanup = void 0;
849
+ if (ui?.views === void 0) return;
850
+ const views = ui.views;
851
+ const ensure = () => {
852
+ try {
853
+ const definition = views.entries().find((candidate) => candidate.target === "chat");
854
+ if (definition === void 0 || typeof definition.create !== "function") return;
855
+ const current = patches.get(definition);
856
+ const create = definition.create;
857
+ if (current !== void 0 && create === current.wrapper) return;
858
+ const wrapper = function wrappedChatBuilder() {
859
+ return new RewindVisibilityBuilder(create.call(definition));
860
+ };
861
+ const mutable = definition;
862
+ mutable.create = wrapper;
863
+ patches.set(definition, { original: create, wrapper });
864
+ rewindLog.info("visibility", "installed Chat snapshot visibility wrapper");
865
+ rebuildOpenSessions(ui, sessions);
866
+ } catch (error) {
867
+ rewindLog.error("visibility", "Chat visibility install probe failed", error);
868
+ }
869
+ };
870
+ let unsubscribe;
871
+ try {
872
+ unsubscribe = views.subscribe?.(ensure);
873
+ } catch (error) {
874
+ rewindLog.warn("visibility", "Chat view subscription failed", error);
875
+ }
876
+ ensure();
877
+ currentCleanup = () => {
878
+ unsubscribe?.();
879
+ restorePatches();
880
+ rebuildOpenSessions(ui, sessions);
881
+ };
882
+ }
883
+ function restorePatches() {
884
+ for (const [definition, record] of patches) {
885
+ const mutable = definition;
886
+ if (mutable.create === record.wrapper) mutable.create = record.original;
887
+ }
888
+ patches = /* @__PURE__ */ new Map();
889
+ }
890
+ function rebuildOpenSessions(ui, sessions) {
891
+ const state = sessions?.list?.getSnapshot();
892
+ if (state === void 0) return;
893
+ const ids = /* @__PURE__ */ new Set([...state.ids ?? [], ...Object.keys(state.byId ?? {})]);
894
+ for (const id of ids) {
895
+ const row = state.byId?.[id];
896
+ const retained = row?.retainedBy;
897
+ if (retained === void 0 || !Object.values(retained).some((count) => (count ?? 0) > 0)) continue;
898
+ try {
899
+ ui.binding?.(id)?.rebuild?.();
900
+ } catch (error) {
901
+ rewindLog.warn("visibility", `chat rebuild failed for ${id}`, error);
902
+ }
903
+ }
904
+ }
905
+ var RewindVisibilityBuilder = class {
906
+ /** @param inner - the real Chat builder. */
907
+ constructor(inner) {
908
+ __publicField(this, "inner", inner);
909
+ __publicField(this, "originals", /* @__PURE__ */ new Map());
910
+ __publicField(this, "commands", /* @__PURE__ */ new Map());
911
+ __publicField(this, "byTurn", /* @__PURE__ */ new Map());
912
+ __publicField(this, "turnProcess", /* @__PURE__ */ new Map());
913
+ __publicField(this, "derived", /* @__PURE__ */ new Map());
914
+ __publicField(this, "cuts", []);
915
+ __publicField(this, "cutSignature", "[]");
916
+ __publicField(this, "lastSignature", "[]");
917
+ }
918
+ get empty() {
919
+ return this.inner.empty;
920
+ }
921
+ replace(input) {
922
+ this.setAll(input.nodes);
923
+ this.lastSignature = this.cutSignature;
924
+ return this.inner.replace({ nodes: this.deriveAll(), timeline: input.timeline, ...changed(input) });
925
+ }
926
+ apply(input) {
927
+ const affected = /* @__PURE__ */ new Set();
928
+ let commandsChanged = false;
929
+ for (const node of input.upserts) {
930
+ const previous = this.originals.get(node.key);
931
+ const previousTurn = previous === void 0 ? void 0 : turnOf(previous);
932
+ const nextTurn = turnOf(node);
933
+ if (previousTurn !== void 0) affected.add(previousTurn);
934
+ if (nextTurn !== void 0) affected.add(nextTurn);
935
+ if (previous?.kind === "command" || node.kind === "command") commandsChanged = true;
936
+ this.setOriginal(node);
937
+ }
938
+ if (commandsChanged) this.refreshCuts();
939
+ if (this.cutSignature !== this.lastSignature) {
940
+ this.lastSignature = this.cutSignature;
941
+ return this.inner.replace({ nodes: this.deriveAll(), timeline: input.timeline, ...changed(input) });
942
+ }
943
+ const cuts = this.cuts;
944
+ const upserts = /* @__PURE__ */ new Map();
945
+ for (const node of input.upserts) upserts.set(node.key, this.deriveOne(node, cuts));
946
+ for (const turn of affected) {
947
+ const processKey = this.turnProcess.get(turn);
948
+ const base = processKey === void 0 ? void 0 : this.originals.get(processKey);
949
+ if (base === void 0) continue;
950
+ const next = this.deriveTurnProcess(base, cuts, turn, this.deriveOne(base, cuts));
951
+ if (this.derived.get(processKey) !== next) upserts.set(processKey, next);
952
+ }
953
+ const list = [...upserts.values()];
954
+ for (const node of list) this.derived.set(node.key, node);
955
+ return this.inner.apply({ upserts: list, timeline: input.timeline, ...changed(input) });
956
+ }
957
+ groupInput() {
958
+ return this.inner.groupInput?.();
959
+ }
960
+ publish() {
961
+ this.inner.publish?.();
962
+ }
963
+ setAll(nodes) {
964
+ this.originals.clear();
965
+ this.commands.clear();
966
+ this.byTurn.clear();
967
+ this.turnProcess.clear();
968
+ this.derived.clear();
969
+ for (const node of nodes) this.setOriginal(node);
970
+ this.refreshCuts();
971
+ }
972
+ setOriginal(node) {
973
+ const previous = this.originals.get(node.key);
974
+ if (previous !== void 0) this.unindex(previous);
975
+ this.originals.set(node.key, node);
976
+ if (node.kind === "command") this.commands.set(node.key, node);
977
+ const turn = turnOf(node);
978
+ if (turn === void 0) return;
979
+ const members = this.byTurn.get(turn) ?? /* @__PURE__ */ new Map();
980
+ members.set(node.key, node);
981
+ this.byTurn.set(turn, members);
982
+ if (node.kind === "turn-process") this.turnProcess.set(turn, node.key);
983
+ }
984
+ unindex(node) {
985
+ if (node.kind === "command") this.commands.delete(node.key);
986
+ const turn = turnOf(node);
987
+ if (turn === void 0) return;
988
+ const members = this.byTurn.get(turn);
989
+ if (members === void 0) return;
990
+ members.delete(node.key);
991
+ if (members.size === 0) this.byTurn.delete(turn);
992
+ if (node.kind === "turn-process" && this.turnProcess.get(turn) === node.key) {
993
+ this.turnProcess.delete(turn);
994
+ for (const member of members.values()) {
995
+ if (member.kind === "turn-process") {
996
+ this.turnProcess.set(turn, member.key);
997
+ break;
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ refreshCuts() {
1003
+ this.cuts = rewindCutsOf(this.commands.values());
1004
+ this.cutSignature = JSON.stringify(
1005
+ this.cuts.map((cut) => `${cut.target}:${cut.marker}`).sort()
1006
+ );
1007
+ }
1008
+ deriveAll() {
1009
+ const cuts = this.cuts;
1010
+ const derived = /* @__PURE__ */ new Map();
1011
+ for (const node of this.originals.values()) derived.set(node.key, this.deriveOne(node, cuts));
1012
+ for (const [turn, key] of this.turnProcess) {
1013
+ const base = this.originals.get(key);
1014
+ if (base === void 0) continue;
1015
+ const current = derived.get(key) ?? this.deriveOne(base, cuts);
1016
+ derived.set(key, this.deriveTurnProcess(base, cuts, turn, current));
1017
+ }
1018
+ const result = [...derived.values()];
1019
+ this.derived = derived;
1020
+ return result;
1021
+ }
1022
+ deriveOne(node, cuts) {
1023
+ const base = this.originals.get(node.key) ?? node;
1024
+ if (base.kind === "command") {
1025
+ const command = base.data;
1026
+ if (command.name !== void 0 && command.name !== null && ENGINE_COMMANDS.has(command.name)) {
1027
+ const args = command.args ?? "";
1028
+ const probe = args.includes("preview") || args.includes("__candidates");
1029
+ const executed = command.outcome?.kind === "success" && command.outcome.sourceEventSeq !== void 0;
1030
+ if (probe || executed) return hidden(base);
1031
+ }
1032
+ return base;
1033
+ }
1034
+ const contained = cuts.some((cut) => cut.target <= base.anchorSeq && base.anchorSeq <= cut.marker);
1035
+ return contained ? hidden(base) : base;
1036
+ }
1037
+ deriveTurnProcess(base, cuts, turn = turnOf(base), current = this.deriveOne(base, cuts)) {
1038
+ if (turn === void 0 || current.visibility === "hidden") return current;
1039
+ const members = this.byTurn.get(turn);
1040
+ if (members === void 0) return current;
1041
+ let count = 0;
1042
+ let allHidden = true;
1043
+ for (const member of members.values()) {
1044
+ if (member.key === base.key) continue;
1045
+ count += 1;
1046
+ if (this.deriveOne(member, cuts).visibility !== "hidden") {
1047
+ allHidden = false;
1048
+ break;
1049
+ }
1050
+ }
1051
+ if (count === 0) return current;
1052
+ return allHidden ? hidden(base) : current;
1053
+ }
1054
+ };
1055
+ function hidden(node) {
1056
+ return node.visibility === "hidden" ? node : { ...node, visibility: "hidden" };
1057
+ }
1058
+ function turnOf(node) {
1059
+ const turn = node.location?.turn?.turn;
1060
+ return typeof turn === "number" ? turn : void 0;
1061
+ }
1062
+ function changed(input) {
1063
+ return input.changedTurns === void 0 ? {} : { changedTurns: input.changedTurns };
1064
+ }
1065
+
800
1066
  // src/client/portals.tsx
801
1067
  var import_react = require("react");
802
1068
  var import_react_dom = require("react-dom");
1069
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
803
1070
 
804
1071
  // src/client/pending.ts
805
1072
  var QUEUE_PREVIEW_CHARS = 200;
@@ -831,11 +1098,11 @@ function matchPendingRows(rows, steering) {
831
1098
  for (let i = 0; i < rows.length; i++) {
832
1099
  const row = rows[i];
833
1100
  const item = steering[i];
834
- matched.push(item !== void 0 && matches(item, row) ? item.id : null);
1101
+ matched.push(item !== void 0 && matches2(item, row) ? item.id : null);
835
1102
  }
836
1103
  return matched;
837
1104
  }
838
- function matches(item, row) {
1105
+ function matches2(item, row) {
839
1106
  if (item.attachments > 0) return row.attachments === item.attachments;
840
1107
  return row.text === (item.text ?? "");
841
1108
  }
@@ -845,48 +1112,6 @@ function retractSpan(steering, targetId) {
845
1112
  return steering.slice(index).map((item) => item.id);
846
1113
  }
847
1114
 
848
- // src/client/log.ts
849
- var NS = "dsh-rewind";
850
- var DEBUG_KEY = "dsh-rewind.debug";
851
- var ALWAYS_ON = /* @__PURE__ */ new Set(["error", "warn"]);
852
- function switchValue() {
853
- try {
854
- return window.localStorage.getItem(DEBUG_KEY) ?? "";
855
- } catch {
856
- return "";
857
- }
858
- }
859
- function matches2(value, ns) {
860
- for (const entry of value.split(",")) {
861
- const part = entry.trim();
862
- if (part === "") continue;
863
- if (part === "*" || part === `${NS}*`) return true;
864
- if (part.endsWith("*")) {
865
- if (ns.startsWith(part.slice(0, -1))) return true;
866
- } else if (ns === part) {
867
- return true;
868
- }
869
- }
870
- return false;
871
- }
872
- function tag(scope) {
873
- return `[${NS}:${scope}]`;
874
- }
875
- function log(level, scope, message, data) {
876
- if (ALWAYS_ON.has(level)) {
877
- console[level](tag(scope), message, data);
878
- return;
879
- }
880
- if (!matches2(switchValue(), `${NS}:${scope}`)) return;
881
- console.info(tag(scope), message, data);
882
- }
883
- var rewindLog = {
884
- error: (scope, message, data) => log("error", scope, message, data),
885
- warn: (scope, message, data) => log("warn", scope, message, data),
886
- info: (scope, message, data) => log("info", scope, message, data),
887
- debug: (scope, message, data) => log("debug", scope, message, data)
888
- };
889
-
890
1115
  // src/client/portals.tsx
891
1116
  var import_jsx_runtime = require("react/jsx-runtime");
892
1117
  function fillComposerEditable(text) {
@@ -1018,7 +1243,6 @@ function showHint(text) {
1018
1243
  }
1019
1244
  var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
1020
1245
  var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
1021
- var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
1022
1246
  var IGNORED_PORTAL_SCOPE = [
1023
1247
  "[data-composer-seat]",
1024
1248
  "[data-composer-input]",
@@ -1026,14 +1250,6 @@ var IGNORED_PORTAL_SCOPE = [
1026
1250
  "[data-sidebar-chat]",
1027
1251
  "[data-plugin-panel]"
1028
1252
  ].join(", ");
1029
- var GROUP_SHELL_SELECTOR = '[data-chat-anchor-key^="group:"]';
1030
- function resolveSeatAnchorSeq(element, chat) {
1031
- if (chat === void 0) return void 0;
1032
- const flowKey = element.dataset.chatAnchorKey;
1033
- const nodeKey = element.dataset.chatNodeKey;
1034
- const node = (flowKey !== void 0 ? chat.nodes.get(flowKey) : void 0) ?? (nodeKey !== void 0 ? chat.nodes.get(nodeKey) : void 0);
1035
- return node?.anchorSeq;
1036
- }
1037
1253
  var PENDING_SEAT_SELECTOR = "[data-pending-steering]";
1038
1254
  var MESSAGE_ATTACHMENT_SELECTOR = "[data-message-attachments]";
1039
1255
  function actionsContainerOf(row) {
@@ -1043,47 +1259,7 @@ function actionsContainerOf(row) {
1043
1259
  if (structural instanceof HTMLElement && structural.querySelector("button") !== null) return structural;
1044
1260
  return void 0;
1045
1261
  }
1046
- function hideWithdrawnSeats(chat, seats, hiddenSeqs, hidden) {
1047
- if (chat === void 0) return;
1048
- const shells = /* @__PURE__ */ new Map();
1049
- for (const seat of seats) {
1050
- if (seat.matches(GROUP_SHELL_SELECTOR)) continue;
1051
- const anchor = resolveSeatAnchorSeq(seat, chat);
1052
- const withdrawn = anchor !== void 0 && hiddenSeqs.has(anchor);
1053
- if (withdrawn) {
1054
- seat.style.display = "none";
1055
- seat.dataset.dshRewindHidden = "true";
1056
- hidden.add(seat);
1057
- } else if (hidden.has(seat)) {
1058
- seat.style.display = "";
1059
- delete seat.dataset.dshRewindHidden;
1060
- hidden.delete(seat);
1061
- }
1062
- if (seat.dataset.chatNodeKey === void 0) continue;
1063
- const shell = seat.parentElement?.closest(GROUP_SHELL_SELECTOR);
1064
- if (shell === null || shell === void 0) continue;
1065
- shells.set(shell, (shells.get(shell) ?? true) && withdrawn);
1066
- }
1067
- for (const [shell, allWithdrawn] of shells) {
1068
- if (allWithdrawn) {
1069
- shell.style.display = "none";
1070
- shell.dataset.dshRewindHidden = "true";
1071
- hidden.add(shell);
1072
- } else if (hidden.has(shell)) {
1073
- shell.style.display = "";
1074
- delete shell.dataset.dshRewindHidden;
1075
- hidden.delete(shell);
1076
- }
1077
- }
1078
- }
1079
- function showHiddenSeats(hidden) {
1080
- for (const element of hidden) {
1081
- element.style.display = "";
1082
- delete element.dataset.dshRewindHidden;
1083
- }
1084
- hidden.clear();
1085
- }
1086
- function collectTargets(chat, hiddenSeqs) {
1262
+ function collectTargets(chat) {
1087
1263
  const rows = /* @__PURE__ */ new Map();
1088
1264
  for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
1089
1265
  const key = element.dataset.chatAnchorKey;
@@ -1094,7 +1270,6 @@ function collectTargets(chat, hiddenSeqs) {
1094
1270
  const node = chat.nodes.get(key);
1095
1271
  if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
1096
1272
  const user = node.data;
1097
- if (hiddenSeqs.has(node.anchorSeq ?? user.seq)) continue;
1098
1273
  const row = rows.get(key);
1099
1274
  const actions = actionsContainerOf(row);
1100
1275
  if (actions === void 0) continue;
@@ -1105,9 +1280,9 @@ function collectTargets(chat, hiddenSeqs) {
1105
1280
  function isRewindInertSession(snapshot) {
1106
1281
  return snapshot.subagent !== null && snapshot.subagent !== void 0;
1107
1282
  }
1108
- function collectDurableTargets(snapshot, chat, hiddenSeqs) {
1283
+ function collectDurableTargets(snapshot, chat) {
1109
1284
  if (chat === void 0 || isRewindInertSession(snapshot)) return [];
1110
- return collectTargets(chat, hiddenSeqs);
1285
+ return collectTargets(chat);
1111
1286
  }
1112
1287
  function inboxOf(session) {
1113
1288
  return session.projections.faceOf(INBOX_PROJECTION).getSnapshot();
@@ -1156,7 +1331,6 @@ function sameTargets(left, right) {
1156
1331
  }
1157
1332
  function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchChat, t, subscribeLocale, setComposerText, restoreImages }) {
1158
1333
  const [targets, setTargets] = (0, import_react.useState)([]);
1159
- const hidden = (0, import_react.useRef)(/* @__PURE__ */ new Set());
1160
1334
  const [, forceRender] = (0, import_react.useReducer)((count) => count + 1, 0);
1161
1335
  (0, import_react.useEffect)(() => subscribeLocale(() => {
1162
1336
  forceRender();
@@ -1176,17 +1350,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchC
1176
1350
  }
1177
1351
  const snapshot = session.getSnapshot();
1178
1352
  const chat = chatOf(session);
1179
- const hiddenSeqs = chat === void 0 ? /* @__PURE__ */ new Set() : hiddenSeqsOf(chat);
1180
- for (const element of hidden.current) if (!element.isConnected) hidden.current.delete(element);
1181
- if (hiddenSeqs.size > 0 || hidden.current.size > 0) {
1182
- hideWithdrawnSeats(
1183
- chat,
1184
- chat === void 0 ? [] : document.querySelectorAll(CHAT_SEAT_SELECTOR),
1185
- hiddenSeqs,
1186
- hidden.current
1187
- );
1188
- }
1189
- const durable = collectDurableTargets(snapshot, chat, hiddenSeqs);
1353
+ const durable = collectDurableTargets(snapshot, chat);
1190
1354
  const next = [...durable, ...collectPendingTargets(snapshot, steeringItemsOf(inboxOf(session)?.["next-step"]))];
1191
1355
  setTargets((current) => sameTargets(current, next) ? current : next);
1192
1356
  };
@@ -1214,26 +1378,17 @@ function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchC
1214
1378
  const unsubscribeChat = watchChat(sessionId, queueRefresh);
1215
1379
  const observer = new MutationObserver((records) => {
1216
1380
  for (const record of records) {
1217
- if (record.type === "attributes") {
1218
- const target = record.target;
1219
- if (target instanceof HTMLElement && hidden.current.has(target) && target.style.display !== "none") {
1220
- queueRefresh();
1221
- return;
1222
- }
1223
- continue;
1224
- }
1225
1381
  const node = record.target instanceof Element ? record.target : record.target.parentElement;
1226
1382
  if (node !== null && node.closest(IGNORED_PORTAL_SCOPE) !== null) continue;
1227
1383
  queueRefresh();
1228
1384
  return;
1229
1385
  }
1230
1386
  });
1231
- observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["style"] });
1387
+ observer.observe(document.body, { childList: true, subtree: true });
1232
1388
  return () => {
1233
1389
  active = false;
1234
1390
  observer.disconnect();
1235
1391
  unsubscribeChat();
1236
- showHiddenSeats(hidden.current);
1237
1392
  unsubscribeInbox?.();
1238
1393
  unsubscribeInbox = void 0;
1239
1394
  inboxOwner = void 0;
@@ -1291,17 +1446,17 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, isMainV
1291
1446
  }
1292
1447
  });
1293
1448
  };
1294
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tooltip, { label: t("button.title"), side: "bottom", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1295
1450
  "button",
1296
1451
  {
1297
1452
  type: "button",
1298
1453
  className: CLASS.button,
1299
1454
  "aria-label": t("button.aria"),
1300
- title: t("button.title"),
1455
+ "aria-haspopup": "dialog",
1301
1456
  onClick,
1302
1457
  dangerouslySetInnerHTML: { __html: REWIND_ICON_SVG }
1303
1458
  }
1304
- );
1459
+ ) });
1305
1460
  }
1306
1461
  function composerText() {
1307
1462
  const surface = composerSurface();
@@ -1343,17 +1498,17 @@ function RetractButton({ target, sessionId, sessionOf, setComposerText, t }) {
1343
1498
  }
1344
1499
  });
1345
1500
  };
1346
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1501
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tooltip, { label: t("button.retract.title"), side: "bottom", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1347
1502
  "button",
1348
1503
  {
1349
1504
  type: "button",
1350
1505
  className: CLASS.button,
1351
1506
  "aria-label": t("button.retract.aria"),
1352
- title: t("button.retract.title"),
1507
+ "aria-haspopup": "dialog",
1353
1508
  onClick,
1354
1509
  dangerouslySetInnerHTML: { __html: REWIND_ICON_SVG }
1355
1510
  }
1356
- );
1511
+ ) });
1357
1512
  }
1358
1513
  function createRewindBridge(deps) {
1359
1514
  return function RewindBridge({ sessionId }) {
@@ -1421,9 +1576,9 @@ async function candidatesOverRpc(connection, sessionId) {
1421
1576
  }
1422
1577
 
1423
1578
  // src/client/build-info.ts
1424
- var PLUGIN_VERSION = true ? "0.18.0" : "dev";
1579
+ var PLUGIN_VERSION = true ? "0.19.0" : "dev";
1425
1580
  var PLUGIN_PACKAGE = "dsh-rewind-plugin";
1426
- var BUILD_HASH = true ? "18b65dfa" : "dev";
1581
+ var BUILD_HASH = true ? "bccc8c9b" : "dev";
1427
1582
 
1428
1583
  // src/client/locales.ts
1429
1584
  var zh = {
@@ -1438,11 +1593,11 @@ var zh = {
1438
1593
  "popover.retract.hint": "\u64A4\u56DE\u8FD9\u6761\u53CA\u4E4B\u540E\u7684\u63D2\u8BDD\u6D88\u606F\uFF0C\u4E0D\u6253\u65AD\u5F53\u524D\u751F\u6210",
1439
1594
  "popover.retract.confirm": "\u786E\u8BA4\u56DE\u9000",
1440
1595
  "popover.chat": "\u4EC5\u56DE\u9000\u5BF9\u8BDD",
1441
- "popover.chat.hint": "\u53EA\u56DE\u9000\u6A21\u578B\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u52A8\u5DE5\u4F5C\u533A\u6587\u4EF6",
1442
- "popover.both": "\u56DE\u9000\u5BF9\u8BDD\u548C\u4EE3\u7801",
1443
- "popover.both.hint": "\u5BF9\u8BDD\u56DE\u9000\u5E76\u8FD8\u539F\u5DE5\u4F5C\u533A\u6587\u4EF6",
1596
+ "popover.chat.hint": "\u4EC5\u56DE\u9000\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u52A8\u5DE5\u4F5C\u533A\u6587\u4EF6",
1597
+ "popover.both": "\u56DE\u9000\u5BF9\u8BDD\u548C\u6587\u4EF6",
1598
+ "popover.both.hint": "\u56DE\u9000\u4E0A\u4E0B\u6587\u5E76\u8FD8\u539F\u5DE5\u4F5C\u533A\u6587\u4EF6",
1444
1599
  "popover.checking": "\u6B63\u5728\u68C0\u67E5\u6587\u4EF6\u53D8\u66F4\u2026",
1445
- "popover.noChanges": "\u6B64\u6D88\u606F\u4E4B\u540E\u6CA1\u6709\u53EF\u8FD8\u539F\u7684\u6587\u4EF6\u53D8\u66F4\uFF0C\u4EC5\u53EF\u56DE\u9000\u5BF9\u8BDD",
1600
+ "popover.noChanges": "\u65E0\u53EF\u8FD8\u539F\u7684\u6587\u4EF6\u53D8\u66F4",
1446
1601
  "popover.backupDisabled": "\u5FEB\u7167\u5907\u4EFD\u5DF2\u5173\u95ED\uFF0C\u8FD8\u539F\u53EF\u80FD\u4E0D\u5145\u5206",
1447
1602
  "popover.cancel": "\u53D6\u6D88",
1448
1603
  "popover.impact.loading": "\u6B63\u5728\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\u2026",
@@ -1481,11 +1636,11 @@ var en = {
1481
1636
  "popover.retract.hint": "Retracts this pending message and the ones after it, without interrupting the run",
1482
1637
  "popover.retract.confirm": "Confirm rewind",
1483
1638
  "popover.chat": "Rewind conversation only",
1484
- "popover.chat.hint": "Cut the model context only; workspace files stay untouched",
1485
- "popover.both": "Rewind conversation and code",
1486
- "popover.both.hint": "Cut the context and restore workspace files",
1639
+ "popover.chat.hint": "Context only; workspace untouched",
1640
+ "popover.both": "Rewind conversation and files",
1641
+ "popover.both.hint": "Context and restore workspace files",
1487
1642
  "popover.checking": "Checking for file changes\u2026",
1488
- "popover.noChanges": "No tracked file changes after this message; conversation-only rewind",
1643
+ "popover.noChanges": "No restorable file changes",
1489
1644
  "popover.backupDisabled": "Snapshots off; the restore may be incomplete",
1490
1645
  "popover.cancel": "Cancel",
1491
1646
  "popover.impact.loading": "Fetching impact list\u2026",
@@ -1514,7 +1669,7 @@ var en = {
1514
1669
  };
1515
1670
 
1516
1671
  // src/client/settings-card.tsx
1517
- var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1672
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
1518
1673
  var import_jsx_runtime2 = require("react/jsx-runtime");
1519
1674
  var SETTINGS_ENTRY_ID = "dsh-rewind-plugin";
1520
1675
  var DEFAULT_MAX_AGE_DAYS = 30;
@@ -1542,10 +1697,10 @@ function formLabels(t) {
1542
1697
  };
1543
1698
  }
1544
1699
  function settingsForm(scope) {
1545
- const form = new import_dsh_client_ui_primitives.SettingsFormModel(scope, [
1700
+ const form = new import_dsh_client_ui_primitives2.SettingsFormModel(scope, [
1546
1701
  fileCheckpointingEnabledField(),
1547
1702
  autoCleanupEnabledField(),
1548
- (0, import_dsh_client_ui_primitives.settingsNumberField)("autoCleanupMaxAgeDays")
1703
+ (0, import_dsh_client_ui_primitives2.settingsNumberField)("autoCleanupMaxAgeDays")
1549
1704
  ]);
1550
1705
  return {
1551
1706
  form,
@@ -1565,7 +1720,7 @@ function SettingsCard(props) {
1565
1720
  const checkpointing = state.checkpointingEnabled.text === "true";
1566
1721
  const cleanupEnabled = state.enabled.text === "true";
1567
1722
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1568
- import_dsh_client_ui_primitives.SettingsForm,
1723
+ import_dsh_client_ui_primitives2.SettingsForm,
1569
1724
  {
1570
1725
  labels: formLabels(t),
1571
1726
  state,
@@ -1576,7 +1731,7 @@ function SettingsCard(props) {
1576
1731
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-toggle-row", children: [
1577
1732
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-settings-toggle-label", children: t("backup.title") }),
1578
1733
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1579
- import_dsh_client_ui_primitives.Switch,
1734
+ import_dsh_client_ui_primitives2.Switch,
1580
1735
  {
1581
1736
  checked: checkpointing,
1582
1737
  label: t("backup.title"),
@@ -1594,7 +1749,7 @@ function SettingsCard(props) {
1594
1749
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-settings-toggle-row", children: [
1595
1750
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-settings-toggle-label", children: t("cleanup.auto") }),
1596
1751
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1597
- import_dsh_client_ui_primitives.Switch,
1752
+ import_dsh_client_ui_primitives2.Switch,
1598
1753
  {
1599
1754
  checked: cleanupEnabled,
1600
1755
  label: t("cleanup.auto"),
@@ -1608,7 +1763,7 @@ function SettingsCard(props) {
1608
1763
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-settings-hint", children: t(cleanupEnabled ? "cleanup.auto.on" : "cleanup.auto.off") })
1609
1764
  ] }),
1610
1765
  cleanupEnabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1611
- import_dsh_client_ui_primitives.SettingsValueField,
1766
+ import_dsh_client_ui_primitives2.SettingsValueField,
1612
1767
  {
1613
1768
  id: "dsh-rewind-cleanup-maxage",
1614
1769
  label: t("cleanup.maxAge"),
@@ -1653,6 +1808,7 @@ function apply(ctx) {
1653
1808
  style.dataset.plugin = PLUGIN_PACKAGE;
1654
1809
  style.textContent = STYLE;
1655
1810
  document.head.appendChild(style);
1811
+ installChatViewVisibility(ctx);
1656
1812
  const sessionOf = (sessionId) => ctx.sessions.binding(sessionId)?.session;
1657
1813
  const isMainViewSession = (sessionId) => (ctx.sessions.list.getSnapshot().byId[sessionId]?.retainedBy.mainView ?? 0) > 0;
1658
1814
  const subscribeLocale = (cb) => ctx.locale.subscribe(cb);