dsh-auto-collapse 0.1.3 → 0.1.4

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
@@ -5,6 +5,12 @@ var __dshcfBundle = (() => {
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
8
14
  var __export = (target, all) => {
9
15
  for (var name2 in all)
10
16
  __defProp(target, name2, { get: all[name2], enumerable: true });
@@ -29,6 +35,9 @@ var __dshcfBundle = (() => {
29
35
 
30
36
  // src/fold.ts
31
37
  var STYLE_ID = "dshcf-style";
38
+ var DEFAULT_STATUS_TEXT = "Deep sleeping...";
39
+ var ANIM_DURATION_MS = 180;
40
+ var ANIM_EASING = "ease-out";
32
41
  var TOOL_LABELS = {
33
42
  bash: "Bash",
34
43
  pwsh: "Pwsh",
@@ -68,6 +77,12 @@ var __dshcfBundle = (() => {
68
77
  text-align: left;
69
78
  cursor: pointer;
70
79
  user-select: none;
80
+ /* \u5C55\u5F00\u6001\u8865\u7684 margin-bottom 16px \u7531 aria-expanded/has-body \u7FFB\u8F6C\u9A71\u52A8\uFF0C
81
+ \u4E00\u5E27\u77AC\u5F00\uFF08\u4E0E\u4E09\u7EA7\u884C display \u7FFB\u8F6C\u540C pass \u540C\u5E27\uFF0C\u65E0\u4E0B\u63A8\uFF09\uFF1B\u6536\u8D77\u65B9\u5411
82
+ \u7531 JS \u4FA7\u9489\u4F4F\u95F4\u8DDD\uFF08\u6536\u8D77 fade \u671F\u95F4\u5185\u8054 16px\uFF0C\u6700\u540E\u4E00\u6761\u5728\u9014\u6E10\u9690 settle
83
+ \u540E\u5F52\u96F6\uFF0C\u89C1 reconcileBlock / hasPendingCollapse\uFF09\u3002\u4E0D\u8BBE CSS transition
84
+ \u2014\u2014v13 \u7684\u8FC7\u6E21\u4E0E chip \u5143\u7D20\u751F\u547D\u5468\u671F\u968F\u673A\u4EA4\u4E92\uFF0C\u4EA7\u751F\u5C55\u5F00\u65B9\u5411\u53CC\u91CD\u4EBA\u683C
85
+ \uFF08\u590D\u7528\u5143\u7D20\u7F13\u52A8\u4E0B\u63A8\u4E09\u7EA7\u884C vs \u65B0\u5EFA\u5143\u7D20\u77AC\u5F00\uFF09\uFF0C\u540C\u7C7B\u578B\u5757\u4E0D\u4E00\u81F4\u3002 */
71
86
  }
72
87
  .dshcf-chip[aria-expanded="true"],
73
88
  .dshcf-chip.dshcf-has-body {
@@ -328,7 +343,7 @@ var __dshcfBundle = (() => {
328
343
  }
329
344
  `;
330
345
  var FoldController = class {
331
- constructor() {
346
+ constructor(statusTextProvider2) {
332
347
  this.observer = null;
333
348
  this.raf = 0;
334
349
  this.timer = 0;
@@ -351,18 +366,45 @@ var __dshcfBundle = (() => {
351
366
  this.segmentStates = /* @__PURE__ */ new Map();
352
367
  /** segment 首次观察到 running 的时间,用于没有官方时长的实时回合。 */
353
368
  this.runningSince = /* @__PURE__ */ new Map();
369
+ /** 曾完成过的 segment key:段恢复运行时据此重开本地计时,防止重新结算
370
+ * 的本地时长吞掉完成间隙。 */
371
+ this.completedOnce = /* @__PURE__ */ new Set();
354
372
  /** 插件改写 display 前的精确原值;受控集合用于分类漂移和 stop() 恢复。 */
355
373
  this.originalDisplay = /* @__PURE__ */ new WeakMap();
356
374
  this.controlledDisplay = /* @__PURE__ */ new Set();
357
- /** 被改写为 Deep sleeping 的原生状态文本,卸载时按节点恢复。 */
375
+ /** 被改写为状态提示词的原生状态文本,卸载时按节点恢复。 */
376
+ /** 被改写为状态提示词的原生状态文本:original = 宿主原文(卸载还原用),
377
+ * written = 插件最后一次写入的值(仅当节点仍等于它时才还原,避免覆盖
378
+ * 宿主在插件写入之后的状态更新)。 */
358
379
  this.turnStatusTexts = /* @__PURE__ */ new Map();
380
+ /** 正文判定缓存(消息元素 → 有无正文):流式期间只有被 mutation 命中的
381
+ * 消息失效重算,历史消息跨 pass 复用,避免每帧全量 TreeWalker。 */
382
+ this.bodyTextCache = /* @__PURE__ */ new WeakMap();
383
+ /** 自上次 pass 以来子树发生变化的 flow 顶层消息;pass 开头统一失效。 */
384
+ this.dirtyMessages = /* @__PURE__ */ new Set();
385
+ /** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
386
+ * 用 Map 不用 WeakMap——switchFlow/stop 需要遍历全量 cancel。 */
387
+ this.pendingAnims = /* @__PURE__ */ new Map();
388
+ /** 手势点击的一次性可动画 block key;segment 级点击另保留中间正文的门控。 */
389
+ this.animatableKeys = /* @__PURE__ */ new Set();
390
+ /** segment 点击时只让点击前已存在的 block 播放 reveal;流式中新出现的
391
+ * 临时分裂块直接显示,避免分类收敛时留下半透明 stale chip。 */
392
+ this.animatableSegmentBlocks = /* @__PURE__ */ new Map();
393
+ this.statusTextProvider = statusTextProvider2 ?? (() => DEFAULT_STATUS_TEXT);
394
+ }
395
+ /** 设置变更后重跑一轮,让状态提示词立即生效。 */
396
+ refresh() {
397
+ this.schedule();
359
398
  }
360
399
  start() {
361
400
  if (this.disposed) return;
362
401
  injectStyle();
363
402
  try {
364
403
  this.observer = new MutationObserver((records) => {
365
- if (this.shouldSchedule(records)) this.schedule();
404
+ if (this.shouldSchedule(records)) {
405
+ this.markDirty(records);
406
+ this.schedule();
407
+ }
366
408
  });
367
409
  this.observer.observe(document.body, {
368
410
  childList: true,
@@ -394,6 +436,29 @@ var __dshcfBundle = (() => {
394
436
  if (records.length === 0 || this.flow === null || !this.flow.isConnected) return true;
395
437
  return records.some((record) => nodeWithin(record.target, this.flow) || nodeWithin(this.flow, record.target));
396
438
  }
439
+ /** 记录本批 mutation 命中的 flow 顶层消息,供正文判定缓存定向失效。
440
+ * 从 record.target 沿 parentNode 走到 flow 的直接子级即所属消息;
441
+ * 归属不到单一顶层消息(flow 直挂层结构变化、flow 外节点、文本直接
442
+ * 子节点)时全量失效——保守正确且罕见。 */
443
+ markDirty(records) {
444
+ const flow = this.flow;
445
+ if (flow === null || !flow.isConnected) return;
446
+ if (records.length === 0) {
447
+ this.bodyTextCache = /* @__PURE__ */ new WeakMap();
448
+ this.dirtyMessages.clear();
449
+ return;
450
+ }
451
+ for (const record of records) {
452
+ let current = record.target;
453
+ while (current !== null && current.parentNode !== flow) current = current.parentNode;
454
+ if (!(current instanceof HTMLElement)) {
455
+ this.bodyTextCache = /* @__PURE__ */ new WeakMap();
456
+ this.dirtyMessages.clear();
457
+ return;
458
+ }
459
+ this.dirtyMessages.add(current);
460
+ }
461
+ }
397
462
  schedule() {
398
463
  if (this.disposed || this.raf !== 0) return;
399
464
  this.raf = requestAnimationFrame(() => {
@@ -424,6 +489,9 @@ var __dshcfBundle = (() => {
424
489
  style?.removeAttribute("data-dshcf-error");
425
490
  } catch (error) {
426
491
  this.reportError(error);
492
+ } finally {
493
+ this.animatableKeys.clear();
494
+ this.animatableSegmentBlocks.clear();
427
495
  }
428
496
  }
429
497
  reportError(error) {
@@ -442,12 +510,19 @@ var __dshcfBundle = (() => {
442
510
  if (nextFlow !== this.flow) this.switchFlow(nextFlow);
443
511
  const flow = this.flow;
444
512
  if (flow === null) return;
445
- const blocks = findBlocks(flow);
513
+ for (const el of this.dirtyMessages) this.bodyTextCache.delete(el);
514
+ this.dirtyMessages.clear();
515
+ const blocks = findBlocks(flow, (el) => this.hasBodyCached(el));
446
516
  this.currentBlocks = new Map(blocks.map((block) => [block.key, block]));
447
- const segments = buildSegments(flow, blocks);
517
+ const segments = buildSegments(flow, blocks, (el) => this.hasBodyCached(el));
448
518
  const liveSegmentKeys = new Set(segments.map((segment) => segment.key));
449
519
  for (const segment of segments) {
450
- if (segment.running && !this.runningSince.has(segment.key)) {
520
+ if (!segment.running) continue;
521
+ if (this.completedOnce.has(segment.key)) {
522
+ this.completedOnce.delete(segment.key);
523
+ this.runningSince.delete(segment.key);
524
+ }
525
+ if (!this.runningSince.has(segment.key)) {
451
526
  this.runningSince.set(segment.key, Date.now());
452
527
  }
453
528
  }
@@ -455,6 +530,7 @@ var __dshcfBundle = (() => {
455
530
  for (const snapshot of segments) {
456
531
  if (!snapshot.closed || snapshot.running || !snapshot.hasWork) continue;
457
532
  completedKeys.add(snapshot.key);
533
+ this.completedOnce.add(snapshot.key);
458
534
  let state = this.segmentStates.get(snapshot.key);
459
535
  if (state === void 0) {
460
536
  state = { key: snapshot.key, row: null, expanded: false, snapshot };
@@ -465,7 +541,7 @@ var __dshcfBundle = (() => {
465
541
  const started = this.runningSince.get(snapshot.key);
466
542
  const parsed = snapshot.boundary === null ? void 0 : parseTurnDuration(snapshot.boundary);
467
543
  if (parsed !== void 0) state.duration = parsed;
468
- else if (started !== void 0) state.duration = Date.now() - started;
544
+ else if (state.duration === void 0 && started !== void 0) state.duration = Date.now() - started;
469
545
  if (state.row === null || !state.row.isConnected) state.row = this.createProcessedRow(state);
470
546
  this.syncProcessedRow(state);
471
547
  }
@@ -487,9 +563,10 @@ var __dshcfBundle = (() => {
487
563
  for (const segment of segments) {
488
564
  const state = this.segmentStates.get(segment.key);
489
565
  const collapse = state !== void 0 && !state.expanded;
566
+ const animate = this.animatableKeys.has(segment.key);
490
567
  for (const middle of segment.middleSteps) {
491
- if (collapse) this.hideElement(middle, desiredHidden);
492
- else this.restoreElement(middle);
568
+ if (collapse) this.hideElement(middle, desiredHidden, animate);
569
+ else this.restoreElement(middle, animate);
493
570
  }
494
571
  if (segment.finalStep !== null) this.restoreElement(segment.finalStep);
495
572
  }
@@ -510,14 +587,32 @@ var __dshcfBundle = (() => {
510
587
  for (const key of [...this.runningSince.keys()]) {
511
588
  if (!liveSegmentKeys.has(key)) this.runningSince.delete(key);
512
589
  }
590
+ for (const key of [...this.completedOnce]) {
591
+ if (!liveSegmentKeys.has(key)) this.completedOnce.delete(key);
592
+ }
513
593
  for (const [node] of [...this.turnStatusTexts]) {
514
594
  if (!node.isConnected) this.turnStatusTexts.delete(node);
515
595
  }
516
- replaceTurnStatus(flow, this.turnStatusTexts);
596
+ for (const [el] of [...this.pendingAnims]) {
597
+ if (!el.isConnected) this.pendingAnims.delete(el);
598
+ }
599
+ const statusText = this.statusTextProvider();
600
+ if (statusText === void 0 || statusText === "") {
601
+ restoreTurnStatus(this.turnStatusTexts);
602
+ } else {
603
+ replaceTurnStatus(flow, this.turnStatusTexts, statusText);
604
+ }
517
605
  }
518
606
  /** flow 元素变化即视为会话切换:完整恢复旧 flow,再从新 DOM 重建。 */
519
607
  switchFlow(next) {
520
608
  if (next === this.flow) return;
609
+ for (const [el, record] of this.pendingAnims) {
610
+ record.anim.cancel();
611
+ if (record.kind === "height") this.clearCollapseLock(el);
612
+ }
613
+ this.pendingAnims.clear();
614
+ this.animatableKeys.clear();
615
+ this.animatableSegmentBlocks.clear();
521
616
  for (const record of this.chips.values()) record.chip.remove();
522
617
  this.chips.clear();
523
618
  for (const host of [...this.mergedThinks.keys()]) this.removeMergedThink(host);
@@ -526,6 +621,9 @@ var __dshcfBundle = (() => {
526
621
  this.currentBlocks.clear();
527
622
  this.blockExpanded.clear();
528
623
  this.runningSince.clear();
624
+ this.completedOnce.clear();
625
+ this.bodyTextCache = /* @__PURE__ */ new WeakMap();
626
+ this.dirtyMessages.clear();
529
627
  this.restoreAllDisplays();
530
628
  restoreTurnStatus(this.turnStatusTexts);
531
629
  this.flow = next;
@@ -534,6 +632,8 @@ var __dshcfBundle = (() => {
534
632
  const row = createProcessedRowElement(state.duration);
535
633
  row.addEventListener("click", () => {
536
634
  state.expanded = !state.expanded;
635
+ this.animatableKeys.add(state.key);
636
+ this.animatableSegmentBlocks.set(state.key, new Set(state.snapshot.blocks.map((block) => block.key)));
537
637
  if (state.expanded) {
538
638
  for (const block of state.snapshot.blocks) {
539
639
  this.blockExpanded.set(block.key, false);
@@ -564,7 +664,11 @@ var __dshcfBundle = (() => {
564
664
  return;
565
665
  }
566
666
  let target = state.snapshot.firstWork ?? state.snapshot.finalStep ?? state.snapshot.boundary;
567
- if (target === null || target.parentElement !== flow) return;
667
+ if (target === null || target.parentElement !== flow) {
668
+ row.remove();
669
+ state.row = null;
670
+ return;
671
+ }
568
672
  while (target.previousElementSibling?.classList.contains("dshcf-flow-chip") === true) {
569
673
  target = target.previousElementSibling;
570
674
  }
@@ -572,40 +676,68 @@ var __dshcfBundle = (() => {
572
676
  }
573
677
  reconcileBlock(block, segment, desiredHidden) {
574
678
  const state = segment === null ? void 0 : this.segmentStates.get(segment.key);
679
+ const segmentAnimatableBlocks = segment === null ? void 0 : this.animatableSegmentBlocks.get(segment.key);
680
+ const animate = this.animatableKeys.has(block.key) || segment !== null && this.animatableKeys.has(segment.key) && (segmentAnimatableBlocks === void 0 || segmentAnimatableBlocks.has(block.key));
575
681
  const levelCollapsed = state !== void 0 && !state.expanded;
576
682
  if (levelCollapsed) {
577
- const existing = this.chips.get(block.key)?.chip;
578
- if (existing !== void 0 && existing.style.display !== "none") existing.style.display = "none";
579
- for (const row of block.rows) this.hideElement(row, desiredHidden);
580
- for (const container of block.containers) this.hideElement(container, desiredHidden);
581
- const keepHost = segment?.finalStep === block.host && hasBodyContent(block.host);
683
+ const keepHost = segment?.finalStep === block.host && this.hasBodyCached(block.host);
684
+ let hostFade = false;
582
685
  if (keepHost) this.restoreElement(block.host);
583
- else this.hideElement(block.host, desiredHidden);
584
- this.removeMergedThink(block.host);
686
+ else hostFade = this.hideElement(block.host, desiredHidden, animate);
687
+ for (const container of block.containers) this.hideElement(container, desiredHidden, animate);
688
+ for (const row of block.rows) this.hideElement(row, desiredHidden, animate);
689
+ const existing = this.chips.get(block.key)?.chip;
690
+ if (existing !== void 0 && existing.style.display !== "none") {
691
+ existing.style.marginBottom = "";
692
+ if (block.mount === "before" || keepHost) {
693
+ if (animate && this.canAnimate(existing)) this.startFadeCollapse(existing);
694
+ else existing.style.display = "none";
695
+ } else if (!hostFade) {
696
+ existing.style.display = "none";
697
+ }
698
+ }
699
+ this.releaseMergedThink(block.host, animate);
585
700
  return;
586
701
  }
587
- const chip = this.ensureChip(block);
588
- this.restoreElement(block.host);
589
- if (chip.style.display !== "") chip.style.display = "";
590
702
  let expanded = this.blockExpanded.get(block.key) ?? false;
591
703
  if (!expanded && block.rows.some((row) => row.hasAttribute("data-selected"))) {
592
704
  expanded = true;
593
705
  this.blockExpanded.set(block.key, true);
594
706
  }
595
- for (const row of block.rows) {
596
- if (expanded) this.restoreElement(row);
597
- else this.hideElement(row, desiredHidden);
598
- }
707
+ const chip = this.ensureChip(block);
708
+ const hostIsCollapsedRow = !expanded && block.rows.includes(block.host);
709
+ const hostWasHidden = block.host.style.display === "none";
710
+ const hostAnimate = !hostIsCollapsedRow && hostWasHidden && animate;
711
+ if (!hostIsCollapsedRow) this.restoreElement(block.host, hostAnimate);
712
+ const pendingChip = this.pendingAnims.get(chip);
713
+ if (pendingChip?.target === "hidden") this.cancelPendingSync(chip);
714
+ const chipWasHidden = chip.style.display === "none";
715
+ if (chip.style.display !== "") chip.style.display = "";
716
+ if (chipWasHidden && animate && !(hostAnimate && block.mount === "inside")) this.revealVisual(chip);
717
+ if (expanded || !this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
718
+ const chipSettle = () => {
719
+ if (!this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
720
+ };
599
721
  for (const container of block.containers) {
600
- if (expanded) this.restoreElement(container);
601
- else this.hideElement(container, desiredHidden);
722
+ if (expanded) this.restoreElement(container, animate);
723
+ else {
724
+ const started = this.hideElement(container, desiredHidden, animate, chipSettle);
725
+ if (started) this.pinChipMargin(chip);
726
+ }
727
+ }
728
+ for (const row of block.rows) {
729
+ if (expanded) this.restoreElement(row, animate);
730
+ else {
731
+ const started = this.hideElement(row, desiredHidden, animate, chipSettle);
732
+ if (started) this.pinChipMargin(chip);
733
+ }
602
734
  }
603
735
  if (expanded && block.rows.length > 1 && block.rows.every((row) => isThinkRow(row))) {
604
- this.syncMergedThink(block.host, block.rows, desiredHidden);
736
+ this.syncMergedThink(block.host, block.rows, desiredHidden, animate);
605
737
  } else {
606
- this.removeMergedThink(block.host);
738
+ if (this.releaseMergedThink(block.host, animate, chipSettle)) this.pinChipMargin(chip);
607
739
  }
608
- chip.classList.toggle("dshcf-has-body", block.mount === "inside" && hasBodyContent(block.host));
740
+ chip.classList.toggle("dshcf-has-body", block.mount === "inside" && this.hasBodyCached(block.host));
609
741
  updateChip(chip, block.rows, expanded);
610
742
  }
611
743
  ensureChip(block) {
@@ -623,14 +755,16 @@ var __dshcfBundle = (() => {
623
755
  chip2.setAttribute("data-dshcf-block-key", block.key);
624
756
  const leading = document.createElement("span");
625
757
  leading.className = "dshcf-leading";
626
- leading.appendChild(createTerminalIcon());
758
+ leading.appendChild(createCommandIcon());
627
759
  chip2.appendChild(leading);
628
760
  chip2.appendChild(createSpan("dshcf-chip-title"));
629
761
  chip2.appendChild(createSpan("dshcf-chip-sep"));
630
762
  chip2.appendChild(createSpan("dshcf-chip-summary"));
631
763
  chip2.appendChild(createChevronIcon("dshcf-chevron"));
764
+ chip2.style.display = "none";
632
765
  chip2.addEventListener("click", () => {
633
766
  this.blockExpanded.set(block.key, !(this.blockExpanded.get(block.key) ?? false));
767
+ this.animatableKeys.add(block.key);
634
768
  this.schedule();
635
769
  });
636
770
  record = { host: block.host, chip: chip2 };
@@ -648,7 +782,10 @@ var __dshcfBundle = (() => {
648
782
  }
649
783
  suppressBlock(block, desiredHidden) {
650
784
  const existing = this.chips.get(block.key)?.chip;
651
- if (existing !== void 0 && existing.style.display !== "none") existing.style.display = "none";
785
+ if (existing !== void 0 && existing.style.display !== "none") {
786
+ existing.style.marginBottom = "";
787
+ existing.style.display = "none";
788
+ }
652
789
  this.removeMergedThink(block.host);
653
790
  this.retainDisplayControl(block.host, desiredHidden);
654
791
  for (const row of block.rows) this.retainDisplayControl(row, desiredHidden);
@@ -668,7 +805,7 @@ var __dshcfBundle = (() => {
668
805
  }
669
806
  /** 连续思考合并行:插在第一个思考行前,标题用第一行思考内容;
670
807
  * 点击切换显示/隐藏全部原始思考行。 */
671
- syncMergedThink(host, rows, desiredHidden) {
808
+ syncMergedThink(host, rows, desiredHidden, animate = false) {
672
809
  let row = this.mergedThinks.get(host);
673
810
  if (row === void 0 || !row.isConnected) {
674
811
  row = document.createElement("button");
@@ -682,14 +819,21 @@ var __dshcfBundle = (() => {
682
819
  title.className = "dshcf-merged-title";
683
820
  const chevron = createChevronIcon("dshcf-chevron");
684
821
  row.append(leading, title, chevron);
822
+ row.style.display = "none";
685
823
  const btn = row;
686
824
  btn.addEventListener("click", () => {
825
+ if (this.mergedThinks.get(host) !== btn) return;
687
826
  const next = !this.mergedExpanded.has(host);
688
- if (next) this.mergedExpanded.add(host);
689
- else this.mergedExpanded.delete(host);
690
- btn.setAttribute("aria-expanded", String(next));
691
- if (next) this.expandMergedBody(host, btn);
692
- else this.collapseMergedBody(host);
827
+ if (next) {
828
+ if (this.expandMergedBody(host, btn)) {
829
+ this.mergedExpanded.add(host);
830
+ btn.setAttribute("aria-expanded", "true");
831
+ }
832
+ } else {
833
+ this.mergedExpanded.delete(host);
834
+ btn.setAttribute("aria-expanded", "false");
835
+ this.collapseMergedBody(host);
836
+ }
693
837
  });
694
838
  rows[0].before(row);
695
839
  this.mergedThinks.set(host, row);
@@ -711,42 +855,155 @@ var __dshcfBundle = (() => {
711
855
  }
712
856
  const expanded = this.mergedExpanded.has(host);
713
857
  if (row.getAttribute("aria-expanded") !== String(expanded)) row.setAttribute("aria-expanded", String(expanded));
858
+ const rowWasHidden = row.style.display === "none";
714
859
  if (row.style.display !== "") row.style.display = "";
860
+ if (rowWasHidden && animate) this.revealVisual(row);
715
861
  for (const r of rows) this.hideElement(r, desiredHidden);
716
- if (expanded) this.ensureMergedBody(host, row, false);
862
+ if (expanded) {
863
+ const result = this.ensureMergedBody(host, row, false);
864
+ if (result !== null && result.created && animate) this.revealMergedBody(result.body);
865
+ }
717
866
  }
718
867
  /** 展开合并行:直接读各思考行文本合成内容块(不依赖原生行展开:
719
- * 程序化 click 不触发 React 展开,且后台 tab 的 rAF 不执行)。 */
868
+ * 程序化 click 不触发 React 展开,且后台 tab 的 rAF 不执行)。
869
+ * 返回是否成功——思考行已不可读(parts 为空)时返回 false,调用方
870
+ * 据此保持收起态,避免展开状态与内容块脱节。 */
720
871
  expandMergedBody(host, btn) {
721
872
  const cached = this.mergedBodyTexts.get(host);
722
- if (cached !== void 0) {
723
- this.ensureMergedBody(host, btn, true);
724
- return;
873
+ if (cached === void 0) {
874
+ const parts = this.currentThinkRows(host).map((r) => r.textContent.replace(/^Think\s*/, "").trim()).filter(Boolean);
875
+ if (parts.length === 0) return false;
876
+ this.mergedBodyTexts.set(host, parts.join("\n\n"));
877
+ }
878
+ const result = this.ensureMergedBody(host, btn, true);
879
+ if (result === null) return false;
880
+ if (result.created) {
881
+ this.revealMergedBody(result.body);
882
+ } else {
883
+ this.cancelPendingSync(result.body);
725
884
  }
726
- const parts = this.currentThinkRows(host).map((r) => r.textContent.replace(/^Think\s*/, "").trim()).filter(Boolean);
727
- if (parts.length === 0) return;
728
- this.mergedBodyTexts.set(host, parts.join("\n\n"));
729
- this.ensureMergedBody(host, btn, true);
885
+ return true;
730
886
  }
731
- /** 创建/更新合并内容块(缓存优先,不重新展开原生行)。 */
887
+ /** 创建/更新合并内容块(缓存优先,不重新展开原生行)。
888
+ * 返回内容块与其是否为本次新建(新建才走展开动画)。 */
732
889
  ensureMergedBody(host, btn, force) {
733
890
  const cached = this.mergedBodyTexts.get(host);
734
- if (cached === void 0) return;
891
+ if (cached === void 0) return null;
735
892
  let body = btn.nextElementSibling;
893
+ let created = false;
736
894
  if (body === null || !body.classList.contains("dshcf-merged-body")) {
737
- body = document.createElement("div");
738
- body.className = "dshcf-merged-body";
739
- btn.after(body);
895
+ const next = document.createElement("div");
896
+ next.className = "dshcf-merged-body";
897
+ btn.after(next);
898
+ body = next;
899
+ created = true;
740
900
  }
741
901
  if (force || body.textContent !== cached) body.textContent = cached;
902
+ return { body, created };
903
+ }
904
+ /** 清理合并 think 行(v12):状态 map 立即清除;DOM 在手势动画路径下
905
+ * 渐隐后移除(settle 回调),其余路径瞬删。渐隐中途被反向取消时元素
906
+ * 保留,由后续 pass 的 syncMergedThink 重建/复用。
907
+ * settle 透传给每个渐隐目标的移除回调之后(chip 间距钉住的结算探测点,
908
+ * AI 评审 P0:merged 行渐隐不走 block.rows,必须纳入同一钉住体系)。 */
909
+ releaseMergedThink(host, animate = false, settle) {
910
+ const row = this.mergedThinks.get(host);
911
+ this.mergedExpanded.delete(host);
912
+ this.mergedBodyTexts.delete(host);
913
+ if (row === void 0) return false;
914
+ this.mergedThinks.delete(host);
915
+ const body = row.nextElementSibling;
916
+ const targets = body !== null && body.classList.contains("dshcf-merged-body") ? [row, body] : [row];
917
+ if (animate && this.canAnimate(row)) {
918
+ for (const t of targets) this.startFadeCollapse(t, () => {
919
+ t.remove();
920
+ settle?.();
921
+ });
922
+ return true;
923
+ } else {
924
+ for (const t of targets) t.remove();
925
+ return false;
926
+ }
742
927
  }
743
- /** 收起合并行:移除内容块(原生行保持隐藏)。 */
928
+ /** merged-body 展开高度动画(机制样板:插件全资 DOM)。
929
+ * 关键帧含 marginBottom 0→16px——其 CSS 有常量 margin-bottom:16px,
930
+ * 高度从 0 起步时这 16px 会先占位产生小跳变。fill:'forwards' 托住终态,
931
+ * onfinish 清内联后 cancel 释放,无闪烁窗口。收起由 collapseMergedBody
932
+ * 做镜像高度卷下(同款账本与身份守卫),开合对称。 */
933
+ revealMergedBody(body) {
934
+ if (!this.canAnimate(body)) return;
935
+ this.cancelPendingSync(body);
936
+ const targetHeight = body.getBoundingClientRect().height;
937
+ if (!(targetHeight > 0)) return;
938
+ body.style.height = "0px";
939
+ body.style.overflow = "hidden";
940
+ body.style.marginBottom = "0px";
941
+ const anim = body.animate(
942
+ [
943
+ { height: "0px", marginBottom: "0px" },
944
+ { height: `${targetHeight}px`, marginBottom: "16px" }
945
+ ],
946
+ { duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
947
+ );
948
+ const record = { anim, target: "visible", kind: "height" };
949
+ this.pendingAnims.set(body, record);
950
+ anim.onfinish = () => {
951
+ if (this.pendingAnims.get(body) !== record) return;
952
+ this.pendingAnims.delete(body);
953
+ body.style.height = "";
954
+ body.style.overflow = "";
955
+ body.style.marginBottom = "";
956
+ anim.cancel();
957
+ this.schedule();
958
+ };
959
+ anim.oncancel = () => {
960
+ if (this.pendingAnims.get(body) !== record) return;
961
+ this.pendingAnims.delete(body);
962
+ };
963
+ }
964
+ /** 收起合并行:内容块高度卷下后移除——镜像 revealMergedBody 的唯一几何动画,
965
+ * 开合对称。插件全资静态文本 DOM、无 React 协调竞争,可安全做几何收起
966
+ * (与 seat 级拒绝盲卷的场景不同:那里是 React 混杂多卡片)。
967
+ * reduced-motion / 无 WAAPI / 零高度降级为同步 remove()。 */
744
968
  collapseMergedBody(host) {
745
969
  const btn = this.mergedThinks.get(host);
746
- if (btn !== void 0) {
747
- const body = btn.nextElementSibling;
748
- if (body !== null && body.classList.contains("dshcf-merged-body")) body.remove();
970
+ if (btn === void 0) return;
971
+ const body = btn.nextElementSibling;
972
+ if (body === null || !body.classList.contains("dshcf-merged-body")) return;
973
+ const el = body;
974
+ if (!this.canAnimate(el)) {
975
+ el.remove();
976
+ return;
977
+ }
978
+ this.cancelPendingSync(el);
979
+ const current = el.getBoundingClientRect().height;
980
+ if (!(current > 0)) {
981
+ el.remove();
982
+ return;
749
983
  }
984
+ el.style.height = `${current}px`;
985
+ el.style.overflow = "hidden";
986
+ const anim = el.animate(
987
+ [
988
+ { height: `${current}px`, marginBottom: "16px" },
989
+ { height: "0px", marginBottom: "0px" }
990
+ ],
991
+ { duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
992
+ );
993
+ const record = { anim, target: "hidden", kind: "height" };
994
+ this.pendingAnims.set(el, record);
995
+ anim.onfinish = () => {
996
+ if (this.pendingAnims.get(el) !== record) return;
997
+ this.pendingAnims.delete(el);
998
+ el.remove();
999
+ anim.cancel();
1000
+ this.schedule();
1001
+ };
1002
+ anim.oncancel = () => {
1003
+ if (this.pendingAnims.get(el) !== record) return;
1004
+ this.pendingAnims.delete(el);
1005
+ this.clearCollapseLock(el);
1006
+ };
750
1007
  }
751
1008
  /** 当前宿主内的思考行(现取,React 重渲染后引用仍然有效)。 */
752
1009
  currentThinkRows(host) {
@@ -767,19 +1024,180 @@ var __dshcfBundle = (() => {
767
1024
  this.mergedExpanded.delete(host);
768
1025
  this.mergedBodyTexts.delete(host);
769
1026
  }
770
- hideElement(el, desired) {
771
- if (!this.originalDisplay.has(el) && !isDisplayed(el)) return;
1027
+ /** 正文判定(带缓存):同一消息子树未变时直接复用上次结果。失效由
1028
+ * markDirty(mutation 定向)与 switchFlow(整体重置)驱动;缓存的是
1029
+ * 纯文本/媒体存在性判定,与 display 状态无关,插件自身的显隐切换
1030
+ * 不会产生脏数据。 */
1031
+ hasBodyCached(el) {
1032
+ const cached = this.bodyTextCache.get(el);
1033
+ if (cached !== void 0) return cached;
1034
+ const value = hasBodyContent(el);
1035
+ this.bodyTextCache.set(el, value);
1036
+ return value;
1037
+ }
1038
+ /** 本块是否有在途收起渐隐(rows/containers/merged 行/body 任一)。
1039
+ * 基于 pendingAnims 账本无状态判定:onfinish/oncancel 都会即时清账,
1040
+ * 取消路径天然解锁(计数器/最后注册者会卡死)。merged 行渐隐时已被
1041
+ * releaseMergedThink 摘出 mergedThinks,按 DOM 类名现查。 */
1042
+ hasPendingCollapse(block) {
1043
+ const check = (el) => el !== null && el !== void 0 && this.pendingAnims.get(el)?.target === "hidden";
1044
+ if (block.containers.some(check)) return true;
1045
+ if (block.rows.some(check)) return true;
1046
+ const mergedRow = block.host.querySelector(".dshcf-merged-think");
1047
+ if (check(mergedRow)) return true;
1048
+ const mergedBody = mergedRow?.nextElementSibling;
1049
+ if (mergedBody instanceof HTMLElement && check(mergedBody)) return true;
1050
+ return false;
1051
+ }
1052
+ /** 钉住 chip 与首行的 16px 间距(收起 fade 期间;内联优先于 aria=false 的 0)。
1053
+ * flow-chip(context 等 before-mounted)豁免:其间距由宿主 row-gap 16px
1054
+ * 提供、自身 CSS 恒 0,钉住 16px 会叠加成 32px(真机实测:收起上下文
1055
+ * 注入时二级与三级间距瞬间扩大)。
1056
+ */
1057
+ pinChipMargin(chip) {
1058
+ if (chip.classList.contains("dshcf-flow-chip")) return;
1059
+ if (chip.style.marginBottom !== "16px") chip.style.marginBottom = "16px";
1060
+ }
1061
+ /** 解除钉住(aria=true 的 16px 或 aria=false 的 0 由 CSS 接管)。 */
1062
+ unpinChipMargin(chip) {
1063
+ if (chip.style.marginBottom !== "") chip.style.marginBottom = "";
1064
+ }
1065
+ /** 返回 true 表示启动了渐隐动画(调用方可据此决定内部元素的处置)。
1066
+ * settle 在渐隐自然结束时调用(onfinish 链;反向取消不触发)。 */
1067
+ hideElement(el, desired, animate = false, settle) {
1068
+ desired.add(el);
1069
+ const pending = this.pendingAnims.get(el);
1070
+ if (pending !== void 0) {
1071
+ if (pending.target === "hidden") return false;
1072
+ this.cancelPendingSync(el);
1073
+ }
1074
+ if (!this.originalDisplay.has(el) && !isDisplayed(el)) return false;
1075
+ if (this.hasAnimatingAncestor(el)) return false;
772
1076
  if (!this.originalDisplay.has(el)) this.originalDisplay.set(el, el.style.display);
773
1077
  this.controlledDisplay.add(el);
774
- desired.add(el);
775
- if (el.style.display !== "none") el.style.display = "none";
1078
+ if (el.style.display === "none") return false;
1079
+ if (animate && this.canAnimate(el)) {
1080
+ this.startFadeCollapse(el, settle);
1081
+ return true;
1082
+ }
1083
+ el.style.display = "none";
1084
+ return false;
776
1085
  }
777
- restoreElement(el) {
1086
+ restoreElement(el, animate = false) {
1087
+ const pending = this.pendingAnims.get(el);
1088
+ if (pending !== void 0) {
1089
+ if (pending.target === "visible") return;
1090
+ this.cancelPendingSync(el);
1091
+ }
778
1092
  if (!this.originalDisplay.has(el)) return;
779
1093
  const original = this.originalDisplay.get(el);
1094
+ if (!animate || !this.canAnimate(el) || this.hasAnimatingAncestor(el)) {
1095
+ if (el.style.display !== original) el.style.display = original;
1096
+ this.originalDisplay.delete(el);
1097
+ this.controlledDisplay.delete(el);
1098
+ return;
1099
+ }
780
1100
  if (el.style.display !== original) el.style.display = original;
781
- this.originalDisplay.delete(el);
782
- this.controlledDisplay.delete(el);
1101
+ this.startReveal(el);
1102
+ }
1103
+ /** 是否可动画:WAAPI 特性检测 + reduced-motion 门控(均做 typeof 防桩缺失)。 */
1104
+ canAnimate(el) {
1105
+ if (typeof el.animate !== "function") return false;
1106
+ if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
1107
+ return true;
1108
+ }
1109
+ /** 展开方向淡入(opacity + 4px 微位移):无高度分量、零布局读取。
1110
+ * onfinish 按终态可见对齐账本(双删除)并 schedule() 幂等重同步;
1111
+ * oncancel 只做身份守卫删除——取消方的终态写入自己负责。 */
1112
+ startReveal(el) {
1113
+ const anim = el.animate(
1114
+ [
1115
+ { opacity: "0", transform: "translateY(4px)" },
1116
+ { opacity: "1", transform: "translateY(0)" }
1117
+ ],
1118
+ { duration: ANIM_DURATION_MS, easing: ANIM_EASING }
1119
+ );
1120
+ const record = { anim, target: "visible", kind: "fade" };
1121
+ this.pendingAnims.set(el, record);
1122
+ anim.onfinish = () => {
1123
+ if (this.pendingAnims.get(el) !== record) return;
1124
+ this.pendingAnims.delete(el);
1125
+ this.originalDisplay.delete(el);
1126
+ this.controlledDisplay.delete(el);
1127
+ this.schedule();
1128
+ };
1129
+ anim.oncancel = () => {
1130
+ if (this.pendingAnims.get(el) !== record) return;
1131
+ this.pendingAnims.delete(el);
1132
+ };
1133
+ }
1134
+ /** 同步取消在途动画并清账:收起动画需同时清锁高内联(height/overflow/
1135
+ * marginBottom),否则取消方写完终态后元素仍被锁高裁剪一帧以上。 */
1136
+ cancelPendingSync(el) {
1137
+ const pending = this.pendingAnims.get(el);
1138
+ if (pending === void 0) return;
1139
+ pending.anim.cancel();
1140
+ this.pendingAnims.delete(el);
1141
+ if (pending.kind === "height") this.clearCollapseLock(el);
1142
+ }
1143
+ clearCollapseLock(el) {
1144
+ el.style.height = "";
1145
+ el.style.overflow = "";
1146
+ el.style.marginBottom = "";
1147
+ el.style.boxSizing = "";
1148
+ }
1149
+ /** 祖先 seat 在途动画检测:沿 parentNode 走到 flow,任一祖先在 pendingAnims
1150
+ * 即视为在途。分层规则——同一视觉变化只动画一层。 */
1151
+ hasAnimatingAncestor(el) {
1152
+ const flow = this.flow;
1153
+ if (flow === null) return false;
1154
+ let node = el.parentElement;
1155
+ while (node !== null && node !== flow) {
1156
+ if (this.pendingAnims.has(node)) return true;
1157
+ node = node.parentElement;
1158
+ }
1159
+ return false;
1160
+ }
1161
+ /** 收起方向渐隐动画(v11 定稿):镜像 reveal 的 opacity + 4px 微位移,
1162
+ * 淡完 onfinish 写 display:none 并保持双条目(镜像 hideElement 终态契约)。
1163
+ * fill:'forwards' 占位到终态写入后释放;无几何锁、无 gap 补偿。 */
1164
+ startFadeCollapse(el, settle) {
1165
+ const anim = el.animate(
1166
+ [
1167
+ { opacity: "1", transform: "translateY(0)" },
1168
+ { opacity: "0", transform: "translateY(4px)" }
1169
+ ],
1170
+ { duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
1171
+ );
1172
+ const record = { anim, target: "hidden", kind: "fade" };
1173
+ this.pendingAnims.set(el, record);
1174
+ anim.onfinish = () => {
1175
+ if (this.pendingAnims.get(el) !== record) return;
1176
+ this.pendingAnims.delete(el);
1177
+ if (el.style.display !== "none") el.style.display = "none";
1178
+ settle?.();
1179
+ anim.cancel();
1180
+ this.schedule();
1181
+ };
1182
+ anim.oncancel = () => {
1183
+ if (this.pendingAnims.get(el) !== record) return;
1184
+ this.pendingAnims.delete(el);
1185
+ };
1186
+ }
1187
+ /** 轻量视觉 reveal(opacity + 4px 微位移):用于插件全资元素的即时显示
1188
+ * 路径——chip(一级展开时出现)与 merged-think 行(二级展开时出现)。
1189
+ * 这些元素的 display 完全由插件直写、无 React 协调竞争,因此不入
1190
+ * pendingAnims 账本、无仲裁;收起同为直写 display:none,无 fill 的在途
1191
+ * 动画残留在隐藏元素上自然失效。门控沿用 animate 布尔(手势路径才调)。 */
1192
+ revealVisual(el) {
1193
+ if (!this.canAnimate(el)) return;
1194
+ el.animate(
1195
+ [
1196
+ { opacity: "0", transform: "translateY(4px)" },
1197
+ { opacity: "1", transform: "translateY(0)" }
1198
+ ],
1199
+ { duration: ANIM_DURATION_MS, easing: ANIM_EASING }
1200
+ );
783
1201
  }
784
1202
  restoreUnusedDisplays(desired) {
785
1203
  for (const el of [...this.controlledDisplay]) {
@@ -797,28 +1215,20 @@ var __dshcfBundle = (() => {
797
1215
  span.className = cls;
798
1216
  return span;
799
1217
  }
800
- function createTerminalIcon() {
801
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
802
- svg.setAttribute("viewBox", "0 0 12 10");
803
- svg.setAttribute("width", "12");
804
- svg.setAttribute("height", "10");
805
- const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
806
- rect.setAttribute("x", "0.5");
807
- rect.setAttribute("y", "0.5");
808
- rect.setAttribute("width", "11");
809
- rect.setAttribute("height", "9");
810
- rect.setAttribute("rx", "1.5");
811
- rect.setAttribute("fill", "none");
812
- rect.setAttribute("stroke", "currentColor");
813
- const prompt = document.createElementNS("http://www.w3.org/2000/svg", "text");
814
- prompt.setAttribute("x", "2");
815
- prompt.setAttribute("y", "7.5");
816
- prompt.setAttribute("font-size", "7");
817
- prompt.setAttribute("fill", "currentColor");
818
- prompt.textContent = ">_";
819
- svg.append(rect, prompt);
820
- return svg;
821
- }
1218
+ var COMMAND_ICON_PATHS = [
1219
+ {
1220
+ transform: "translate(0.6689 1.073)",
1221
+ d: "M11.4818 5.57813C11.4818 4.45301 11.4807 3.66237 11.4075 3.05908C11.3359 2.46953 11.2024 2.13852 10.9939 1.89441C10.9247 1.81341 10.8493 1.73801 10.7683 1.66882C10.5242 1.46033 10.1932 1.32686 9.60364 1.25525C9.00034 1.18198 8.20974 1.18091 7.0846 1.18091L5.57813 1.18091C4.45301 1.18091 3.66238 1.18198 3.05908 1.25525C2.46953 1.32686 2.13852 1.46033 1.89441 1.66882C1.81341 1.73801 1.73801 1.81341 1.66882 1.89441C1.46033 2.13852 1.32686 2.46953 1.25525 3.05908C1.18198 3.66238 1.18091 4.45301 1.18091 5.57813L1.18091 6.2771C1.18091 7.40218 1.18197 8.19288 1.25525 8.79614C1.32687 9.38553 1.46036 9.71674 1.66882 9.96082C1.73797 10.0417 1.81347 10.1173 1.89441 10.1864C2.13851 10.3948 2.13965 10.5275 3.05908 10.5991C3.66238 10.6724 4.45298 10.6735 5.57813 10.6735L7.0846 10.6735C8.20977 10.6735 9.00033 10.6724 9.60364 10.5991C10.1931 10.5275 10.5242 10.3948 10.7683 10.1864C10.8493 10.1173 10.9247 10.0417 10.9939 9.96082C11.2024 9.71674 11.3358 9.38553 11.4075 8.79614C11.4808 8.19288 11.4818 7.40218 11.4818 6.2771L11.4818 5.57813ZM12.6627 6.2771C12.6627 7.37222 12.6637 8.247 12.5798 8.93799C12.4942 9.64284 12.3133 10.2359 11.8928 10.7282C11.7834 10.8562 11.6637 10.9751 11.5356 11.0845C11.0434 11.5049 10.4511 11.6867 9.74634 11.7723C9.05525 11.8563 8.17999 11.8552 7.0846 11.8552L5.57813 11.8552C4.48273 11.8552 3.60747 11.8563 2.91638 11.7723C2.21157 11.6867 1.61933 11.5049 1.12708 11.0845C0.99901 10.9751 0.879281 10.8562 0.769898 10.7282C0.349454 10.2359 0.168506 9.64284 0.0828864 8.93799C-0.00101964 8.247 4.88512e-07 7.37222 6.47206e-07 6.2771L6.47206e-07 5.57813C6.47206e-07 4.48273 -0.00106163 3.60747 0.0828864 2.91638C0.168502 2.21168 0.349594 1.61928 0.769898 1.12708C0.879302 0.998981 0.998981 0.879302 1.12708 0.769898C1.61928 0.349594 2.21168 0.168502 2.91638 0.0828864C3.60747 -0.00106163 4.48273 6.47206e-07 5.57813 6.47206e-07L7.0846 6.47206e-07C8.17999 6.47206e-07 9.05525 -0.00106163 9.74634 0.0828864C10.451 0.168505 11.0434 0.349587 11.5356 0.769898C11.6637 0.879302 11.7834 0.998981 11.8928 1.12708C12.3131 1.61928 12.4942 2.21169 12.5798 2.91638C12.6638 3.60747 12.6627 4.48273 12.6627 5.57813L12.6627 6.2771Z"
1222
+ },
1223
+ {
1224
+ transform: "translate(0.6689 1.073)",
1225
+ d: "M6.02607 5.50955L6.44306 5.9274L3.84284 8.52762L3.425 8.11063L3.00715 7.69278L4.77253 5.9274L3.00715 4.16202L3.84284 3.32633L6.02607 5.50955Z"
1226
+ },
1227
+ {
1228
+ transform: "translate(0.6689 1.073)",
1229
+ d: "M9.23789 7.35397L9.23789 8.53488L6.96238 8.53488L6.96238 7.35397L9.23789 7.35397Z"
1230
+ }
1231
+ ];
822
1232
  var NATIVE_CHEVRON_DOWN_PATH = "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z";
823
1233
  function createChevronIcon(className) {
824
1234
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
@@ -886,10 +1296,31 @@ var __dshcfBundle = (() => {
886
1296
  const vb = (svg.getAttribute("viewBox") ?? "").trim().split(/\s+/);
887
1297
  return vb.length === 4 && Number(vb[2]) === 14 && Number(vb[3]) === 14;
888
1298
  }
1299
+ var cachedNativeCommandSvg = null;
889
1300
  function createCommandIcon() {
1301
+ if (cachedNativeCommandSvg !== null) return cachedNativeCommandSvg.cloneNode(true);
890
1302
  const native = findNativeCommandSvg();
891
- if (native !== null) return native.cloneNode(true);
892
- return createTerminalIcon();
1303
+ if (native !== null) {
1304
+ cachedNativeCommandSvg = native;
1305
+ return native.cloneNode(true);
1306
+ }
1307
+ return createCommandIconFallback();
1308
+ }
1309
+ function createCommandIconFallback() {
1310
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1311
+ svg.setAttribute("viewBox", "0 0 14 14");
1312
+ svg.setAttribute("width", "14");
1313
+ svg.setAttribute("height", "14");
1314
+ svg.setAttribute("fill", "none");
1315
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
1316
+ for (const p of COMMAND_ICON_PATHS) {
1317
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
1318
+ path.setAttribute("transform", p.transform);
1319
+ path.setAttribute("d", p.d);
1320
+ path.setAttribute("fill", "currentColor");
1321
+ svg.appendChild(path);
1322
+ }
1323
+ return svg;
893
1324
  }
894
1325
  var CONTEXT_ICON_PATHS = [
895
1326
  {
@@ -928,13 +1359,41 @@ var __dshcfBundle = (() => {
928
1359
  }
929
1360
  return svg;
930
1361
  }
1362
+ var WRITE_ICON_PATH = "M9.94076 1.34942C10.7047 0.90231 11.6503 0.902415 12.4143 1.34942C12.7061 1.52015 12.9688 1.79118 13.3104 2.13284C13.6521 2.47448 13.9231 2.73721 14.0939 3.02894C14.5408 3.79294 14.5409 4.73856 14.0939 5.50251C13.9231 5.79415 13.652 6.05704 13.3104 6.39861L6.65932 13.0497C6.28068 13.4284 6.00695 13.7108 5.66543 13.9097C5.32391 14.1085 4.94315 14.2074 4.42705 14.3498L3.24394 14.6761C2.77527 14.8054 2.34538 14.9262 2.00131 14.9684C1.65196 15.0112 1.17964 15.0013 0.810764 14.6325C0.441921 14.2637 0.432107 13.7913 0.47486 13.442C0.517035 13.0979 0.6379 12.668 0.767181 12.1993L1.09352 11.0162C1.23588 10.5001 1.33481 10.1193 1.5336 9.77784C1.7325 9.43632 2.0149 9.1626 2.39355 8.78395L9.04466 2.13284C9.38625 1.79126 9.64911 1.52016 9.94076 1.34942ZM15.5427 14.8398H7.55223L8.96707 13.425H15.5427V14.8398ZM3.39382 9.78422C2.965 10.213 2.84244 10.3436 2.75709 10.49C2.67183 10.6366 2.61862 10.8079 2.45733 11.3925L2.13099 12.5756C2.00183 13.0439 1.92194 13.3419 1.88863 13.5536C2.10041 13.5204 2.39872 13.4416 2.86764 13.3123L4.05075 12.9859C4.63544 12.8246 4.80669 12.7715 4.95323 12.6862C5.09968 12.6008 5.23022 12.4783 5.65905 12.0494L10.721 6.98644L8.45577 4.72121L3.39382 9.78422ZM11.7 2.57079C11.3774 2.38198 10.9777 2.38198 10.6551 2.57079C10.5602 2.62647 10.4487 2.72931 10.0449 3.13311L9.45604 3.72094L11.7213 5.98617L12.3102 5.39833C12.7139 4.99457 12.8168 4.88307 12.8725 4.78818C13.0613 4.46561 13.0612 4.06585 12.8725 3.74326C12.8169 3.64827 12.7146 3.53752 12.3102 3.13311C11.9057 2.72863 11.795 2.6264 11.7 2.57079Z";
1363
+ function findNativeWriteSvg() {
1364
+ const selector = '[data-tool="write"] [data-disclosure-row], [data-tool="edit"] [data-disclosure-row]';
1365
+ for (const drow of document.querySelectorAll(selector)) {
1366
+ for (const svg of drow.querySelectorAll("svg")) {
1367
+ if (svg.querySelectorAll("path").length === 1 && isIcon16(svg)) return svg;
1368
+ }
1369
+ }
1370
+ return null;
1371
+ }
1372
+ function isIcon16(svg) {
1373
+ const vb = (svg.getAttribute("viewBox") ?? "").trim().split(/\s+/);
1374
+ return vb.length === 4 && Number(vb[2]) === 16 && Number(vb[3]) === 16;
1375
+ }
1376
+ function createWriteIcon() {
1377
+ const native = findNativeWriteSvg();
1378
+ if (native !== null) return native.cloneNode(true);
1379
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1380
+ svg.setAttribute("viewBox", "0 0 16 16");
1381
+ svg.setAttribute("width", "14");
1382
+ svg.setAttribute("height", "14");
1383
+ svg.setAttribute("fill", "none");
1384
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
1385
+ path.setAttribute("d", WRITE_ICON_PATH);
1386
+ path.setAttribute("fill", "currentColor");
1387
+ svg.appendChild(path);
1388
+ return svg;
1389
+ }
931
1390
  function syncLeadingIcon(chip, kind) {
932
1391
  const leading = chip.querySelector(".dshcf-leading");
933
1392
  if (leading === null) return;
934
1393
  const existing = leading.querySelector("svg");
935
1394
  if (existing !== null && existing.getAttribute("data-dshcf-icon") === kind) return;
936
1395
  for (const child of [...leading.childNodes]) child.remove();
937
- const svg = kind === "think" ? createThinkIcon() : kind === "context" ? createContextIcon() : createCommandIcon();
1396
+ const svg = kind === "think" ? createThinkIcon() : kind === "context" ? createContextIcon() : kind === "write" ? createWriteIcon() : createCommandIcon();
938
1397
  svg.setAttribute("data-dshcf-icon", kind);
939
1398
  leading.appendChild(svg);
940
1399
  }
@@ -969,7 +1428,7 @@ var __dshcfBundle = (() => {
969
1428
  return kind === "assistant-step" || kind === "assistant" || kind === "tool-call" || kind === "command" || kind === "manual-compaction";
970
1429
  });
971
1430
  }
972
- function buildSegments(flow, blocks) {
1431
+ function buildSegments(flow, blocks, hasBody) {
973
1432
  const items = flowItems(flow);
974
1433
  const itemIndex = new Map(items.map((el, index) => [el, index]));
975
1434
  const snapshots = [];
@@ -982,7 +1441,7 @@ var __dshcfBundle = (() => {
982
1441
  const segmentBlocks = blocks.filter((block) => inRange.has(block.host));
983
1442
  const bodySteps = range.filter((el) => {
984
1443
  const kind = el.getAttribute("data-chat-flow-kind");
985
- return (kind === "assistant-step" || kind === "assistant") && hasBodyContent(el);
1444
+ return (kind === "assistant-step" || kind === "assistant") && hasBody(el);
986
1445
  });
987
1446
  const finalStep = bodySteps.length > 0 ? bodySteps[bodySteps.length - 1] : null;
988
1447
  const middleSteps = new Set(bodySteps.slice(0, -1));
@@ -1043,7 +1502,7 @@ var __dshcfBundle = (() => {
1043
1502
  if (segment.finalStep !== null) workHosts.add(segment.finalStep);
1044
1503
  return [...workHosts].some(isDisplayed);
1045
1504
  }
1046
- function findBlocks(flow) {
1505
+ function findBlocks(flow, hasBody) {
1047
1506
  const blocks = [];
1048
1507
  const children = flowItems(flow);
1049
1508
  let run = null;
@@ -1080,7 +1539,7 @@ var __dshcfBundle = (() => {
1080
1539
  const workRows = [...callRowsIn(el), ...commandRowsIn(el)];
1081
1540
  const isToolPile = workRows.length > 0;
1082
1541
  const isContext = kind === "context";
1083
- const hasBody = !isToolPile ? hasBodyContent(el) : false;
1542
+ const msgHasBody = !isToolPile ? hasBody(el) : false;
1084
1543
  if (isContext) {
1085
1544
  flushCarry();
1086
1545
  if (run === null || run.category !== "context") run = makeBlock(el, "context");
@@ -1088,7 +1547,7 @@ var __dshcfBundle = (() => {
1088
1547
  run.mount = "before";
1089
1548
  continue;
1090
1549
  }
1091
- if (isToolPile || thinkRows.length > 0 && !hasBody) {
1550
+ if (isToolPile || thinkRows.length > 0 && !msgHasBody) {
1092
1551
  if (run === null || run.category !== "work") run = makeBlock(el, "work");
1093
1552
  if (carry.length > 0) {
1094
1553
  run.rows.push(...carry);
@@ -1100,7 +1559,7 @@ var __dshcfBundle = (() => {
1100
1559
  run.containers.push(el);
1101
1560
  }
1102
1561
  if (workRows.includes(el)) run.mount = "before";
1103
- } else if (el.hasAttribute("data-chat-anchor-key") && (thinkRows.length > 0 || hasBody) || hasBody && kind !== null) {
1562
+ } else if (el.hasAttribute("data-chat-anchor-key") && (thinkRows.length > 0 || msgHasBody) || msgHasBody && kind !== null) {
1104
1563
  flushCarry();
1105
1564
  if (thinkRows.length > 0) {
1106
1565
  const segments = splitThinkByBody(el, thinkRows);
@@ -1128,11 +1587,29 @@ var __dshcfBundle = (() => {
1128
1587
  return blocks;
1129
1588
  }
1130
1589
  function splitThinkByBody(el, rows) {
1590
+ const texts = [];
1591
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
1592
+ let node;
1593
+ while ((node = walker.nextNode()) !== null) {
1594
+ if (node.data.trim() === "") continue;
1595
+ const parent = node.parentElement;
1596
+ if (parent !== null && parent.closest('[data-variant="think"], [data-chat-call-id], .dshcf-chip, .dshcf-merged-think, .dshcf-merged-body') !== null) continue;
1597
+ texts.push(node);
1598
+ }
1599
+ const hasBetween = (a, b) => {
1600
+ for (const t of texts) {
1601
+ const posA = a.compareDocumentPosition(t);
1602
+ if ((posA & Node.DOCUMENT_POSITION_FOLLOWING) === 0) continue;
1603
+ const posB = b.compareDocumentPosition(t);
1604
+ return (posB & Node.DOCUMENT_POSITION_PRECEDING) !== 0;
1605
+ }
1606
+ return false;
1607
+ };
1131
1608
  const segments = [];
1132
1609
  let current = [];
1133
1610
  for (let i = 0; i < rows.length; i++) {
1134
1611
  current.push(rows[i]);
1135
- if (i + 1 < rows.length && hasBodyBetween(el, rows[i], rows[i + 1])) {
1612
+ if (i + 1 < rows.length && hasBetween(rows[i], rows[i + 1])) {
1136
1613
  segments.push(current);
1137
1614
  current = [];
1138
1615
  }
@@ -1140,21 +1617,6 @@ var __dshcfBundle = (() => {
1140
1617
  if (current.length > 0) segments.push(current);
1141
1618
  return segments.length > 0 ? segments : [rows];
1142
1619
  }
1143
- function hasBodyBetween(el, a, b) {
1144
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
1145
- let node;
1146
- while ((node = walker.nextNode()) !== null) {
1147
- if (node.data.trim() === "") continue;
1148
- const parent = node.parentElement;
1149
- if (parent !== null && parent.closest('[data-variant="think"], [data-chat-call-id], .dshcf-chip, .dshcf-merged-think, .dshcf-merged-body') !== null) continue;
1150
- const posA = a.compareDocumentPosition(node);
1151
- const posB = b.compareDocumentPosition(node);
1152
- if ((posA & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 && (posB & Node.DOCUMENT_POSITION_PRECEDING) !== 0) {
1153
- return true;
1154
- }
1155
- }
1156
- return false;
1157
- }
1158
1620
  function hasBodyText(el) {
1159
1621
  const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
1160
1622
  let node;
@@ -1269,7 +1731,6 @@ var __dshcfBundle = (() => {
1269
1731
  runningTool,
1270
1732
  runningThink,
1271
1733
  tools,
1272
- count: rows.length,
1273
1734
  hasError: infos.some((i) => i.state === "error"),
1274
1735
  hasStopped: infos.some((i) => i.state === "stopped"),
1275
1736
  allContext: infos.length > 0 && infos.every((i) => i.label === "\u4E0A\u4E0B\u6587\u6CE8\u5165")
@@ -1298,14 +1759,19 @@ var __dshcfBundle = (() => {
1298
1759
  titleText = "\u5DF2\u601D\u8003";
1299
1760
  summaryText = "";
1300
1761
  }
1301
- const kind = running !== null ? running.kind : info.allContext ? "context" : info.tools.length > 0 ? "tool" : "think";
1762
+ let kind = running !== null ? running.kind : info.allContext ? "context" : info.tools.length > 0 ? "tool" : "think";
1763
+ if (running === null && info.tools.some((tool) => tool === "Edit" || tool === "Write")) kind = "write";
1302
1764
  if (title.textContent !== titleText) title.textContent = titleText;
1303
1765
  if (summary.textContent !== summaryText) summary.textContent = summaryText;
1304
1766
  if (sep !== null) {
1305
1767
  const sepDisplay = summaryText === "" ? "none" : "";
1306
1768
  if (sep.style.display !== sepDisplay) sep.style.display = sepDisplay;
1307
1769
  }
1308
- summary.scrollLeft = running !== null ? summary.scrollWidth - summary.clientWidth : 0;
1770
+ if (running !== null) {
1771
+ summary.scrollLeft = summary.scrollWidth - summary.clientWidth;
1772
+ } else if (chip.classList.contains("running") && summary.scrollLeft !== 0) {
1773
+ summary.scrollLeft = 0;
1774
+ }
1309
1775
  const expandedAttr = String(expanded);
1310
1776
  if (chip.getAttribute("aria-expanded") !== expandedAttr) {
1311
1777
  chip.setAttribute("aria-expanded", expandedAttr);
@@ -1403,21 +1869,29 @@ var __dshcfBundle = (() => {
1403
1869
  style.textContent = CHIP_CSS;
1404
1870
  document.head.appendChild(style);
1405
1871
  }
1406
- function replaceTurnStatus(flow, originals) {
1872
+ function replaceTurnStatus(flow, originals, statusText) {
1407
1873
  const statuses = flow.matches('[role="status"]') ? [flow, ...flow.querySelectorAll('[role="status"]')] : [...flow.querySelectorAll('[role="status"]')];
1408
1874
  for (const status of statuses) {
1409
1875
  for (const node of status.childNodes) {
1410
1876
  if (node instanceof Text && node.data.includes("Deep diving")) {
1411
- if (!originals.has(node)) originals.set(node, node.data);
1412
- const next = node.data.replace("Deep diving", "Deep sleeping");
1413
- if (node.data !== next) node.data = next;
1877
+ let record = originals.get(node);
1878
+ if (record === void 0) {
1879
+ record = { original: node.data, written: "" };
1880
+ originals.set(node, record);
1881
+ }
1882
+ if (node.data !== record.written) record.original = node.data;
1883
+ const next = node.data.replace(/Deep diving[.…]*/, statusText);
1884
+ if (node.data !== next) {
1885
+ node.data = next;
1886
+ record.written = next;
1887
+ }
1414
1888
  }
1415
1889
  }
1416
1890
  }
1417
1891
  }
1418
1892
  function restoreTurnStatus(originals) {
1419
- for (const [node, original] of originals) {
1420
- if (node.isConnected && node.data.includes("Deep sleeping")) node.data = original;
1893
+ for (const [node, record] of originals) {
1894
+ if (node.isConnected && node.data === record.written && node.data !== record.original) node.data = record.original;
1421
1895
  }
1422
1896
  originals.clear();
1423
1897
  }
@@ -1425,15 +1899,253 @@ var __dshcfBundle = (() => {
1425
1899
  document.getElementById(STYLE_ID)?.remove();
1426
1900
  }
1427
1901
 
1902
+ // src/settings.ts
1903
+ var AUTO_COLLAPSE_NS = "dsh-auto-collapse";
1904
+ var DEFAULT_STATUS_TEXT2 = "Deep sleeping...";
1905
+ function statusTextProvider(scope) {
1906
+ return () => {
1907
+ if (scope === void 0) return DEFAULT_STATUS_TEXT2;
1908
+ const snapshot = scope.getSnapshot();
1909
+ const value = snapshot.value;
1910
+ return value?.statusText ?? DEFAULT_STATUS_TEXT2;
1911
+ };
1912
+ }
1913
+ var CARD_CSS = `
1914
+ .dshcf-settings-card {
1915
+ border: 1px solid var(--dsw-alias-border-l2);
1916
+ background: var(--dsw-alias-bg-layer-3);
1917
+ border-radius: 12px;
1918
+ list-style: none;
1919
+ transition: border-color .16s, background .16s;
1920
+ }
1921
+ .dshcf-settings-card:hover { border-color: var(--dsw-alias-label-dimmed); }
1922
+ .dshcf-settings-cardOpen {
1923
+ background: var(--dsw-alias-bg-layer-2);
1924
+ border-color: var(--dsw-alias-label-dimmed);
1925
+ }
1926
+ .dshcf-settings-header {
1927
+ appearance: none;
1928
+ width: 100%;
1929
+ font: inherit;
1930
+ color: inherit;
1931
+ text-align: left;
1932
+ cursor: pointer;
1933
+ background: 0 0;
1934
+ border: 0;
1935
+ border-radius: 12px;
1936
+ align-items: center;
1937
+ gap: 12px;
1938
+ padding: 14px 16px;
1939
+ display: flex;
1940
+ }
1941
+ .dshcf-settings-header:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
1942
+ .dshcf-settings-headText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; display: flex; }
1943
+ .dshcf-settings-name { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; }
1944
+ .dshcf-settings-description { color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.5; }
1945
+ .dshcf-settings-chevron { color: var(--dsw-alias-label-tertiary); flex: none; transition: transform .16s; }
1946
+ .dshcf-settings-chevronOpen { transform: rotate(180deg); }
1947
+ .dshcf-settings-pending {
1948
+ white-space: nowrap;
1949
+ background: var(--dsw-alias-bg-module-platform);
1950
+ color: var(--dsw-alias-label-secondary);
1951
+ border-radius: 999px;
1952
+ flex: none;
1953
+ padding: 1px 8px;
1954
+ font-size: 11px;
1955
+ font-weight: 500;
1956
+ line-height: 17px;
1957
+ }
1958
+ .dshcf-settings-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding-bottom: 8px; }
1959
+ .dshcf-settings-readOnly { color: var(--dsw-alias-label-tertiary); margin: 12px 0 0; font-size: 12px; line-height: 1.5; }
1960
+ .dshcf-settings-field { flex-direction: column; gap: 6px; padding: 12px 0; display: flex; }
1961
+ .dshcf-settings-fieldHead { align-items: center; gap: 8px; display: flex; }
1962
+ .dshcf-settings-fieldLabel { min-width: 0; color: var(--dsw-alias-label-primary); flex: 1; font-size: 13px; font-weight: 500; line-height: 1.5; }
1963
+ .dshcf-settings-badges { align-items: center; gap: 8px; display: inline-flex; }
1964
+ .dshcf-settings-badge {
1965
+ white-space: nowrap;
1966
+ background: var(--dsw-alias-bg-module-platform);
1967
+ color: var(--dsw-alias-label-secondary);
1968
+ border-radius: 999px;
1969
+ padding: 1px 8px;
1970
+ font-size: 11px;
1971
+ font-weight: 500;
1972
+ line-height: 17px;
1973
+ }
1974
+ .dshcf-settings-reset { font: inherit; color: var(--dsw-alias-label-secondary); cursor: pointer; background: 0 0; border: none; padding: 0; font-size: 12px; line-height: 1.5; }
1975
+ .dshcf-settings-reset:hover:not(:disabled) { color: var(--dsw-alias-label-primary); }
1976
+ .dshcf-settings-reset:disabled { cursor: default; }
1977
+ .dshcf-settings-input {
1978
+ border: 1px solid var(--dsw-alias-border-l2);
1979
+ background: var(--dsw-alias-bg-layer-3);
1980
+ height: 34px;
1981
+ font: inherit;
1982
+ color: var(--dsw-alias-label-primary);
1983
+ border-radius: 8px;
1984
+ padding: 0 12px;
1985
+ font-size: 13px;
1986
+ line-height: 1.5;
1987
+ box-sizing: border-box;
1988
+ width: 100%;
1989
+ }
1990
+ .dshcf-settings-input:focus-visible { border-color: var(--dsw-alias-brand-primary); outline: none; }
1991
+ .dshcf-settings-input:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
1992
+ .dshcf-settings-hint { color: var(--dsw-alias-label-tertiary); margin: 0; font-size: 12px; line-height: 1.5; }
1993
+ .dshcf-settings-footer { border-top: 1px solid var(--dsw-alias-border-l2); justify-content: flex-end; align-items: center; gap: 8px; padding: 12px 0 4px; display: flex; }
1994
+ .dshcf-settings-failed { min-width: 0; color: var(--dsw-alias-label-error); flex: 1; margin: 0; font-size: 12px; line-height: 1.5; }
1995
+ .dshcf-settings-discard,
1996
+ .dshcf-settings-save { appearance: none; font: inherit; cursor: pointer; border: 1px solid #0000; border-radius: 8px; padding: 5px 14px; font-size: 13px; line-height: 1.5; }
1997
+ .dshcf-settings-discard { border-color: var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); background: 0 0; }
1998
+ .dshcf-settings-discard:hover:not(:disabled) { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-dimmed); }
1999
+ .dshcf-settings-save { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3); }
2000
+ .dshcf-settings-discard:disabled,
2001
+ .dshcf-settings-save:disabled { opacity: .4; cursor: default; }
2002
+ .dshcf-settings-discard:focus-visible,
2003
+ .dshcf-settings-save:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
2004
+ `;
2005
+ var STYLE_ID2 = "dshcf-settings-style";
2006
+ function injectCardStyle() {
2007
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID2) !== null) return;
2008
+ const style = document.createElement("style");
2009
+ style.id = STYLE_ID2;
2010
+ style.textContent = CARD_CSS;
2011
+ document.head.appendChild(style);
2012
+ }
2013
+ function ChevronIcon(open) {
2014
+ const React = __require("react");
2015
+ const className = open ? "dshcf-settings-chevron dshcf-settings-chevronOpen" : "dshcf-settings-chevron";
2016
+ return React.createElement(
2017
+ "svg",
2018
+ { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, className },
2019
+ React.createElement("path", {
2020
+ d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2021
+ fill: "currentColor"
2022
+ })
2023
+ );
2024
+ }
2025
+ function StatusTextCard(props) {
2026
+ const React = __require("react");
2027
+ const scope = props.scope;
2028
+ const [open, setOpen] = React.useState(false);
2029
+ const [snapshot, setSnapshot] = React.useState(scope.getSnapshot());
2030
+ const [pending, setPending] = React.useState(null);
2031
+ const [saving, setSaving] = React.useState(false);
2032
+ const [failed, setFailed] = React.useState(false);
2033
+ React.useEffect(() => scope.subscribe(() => setSnapshot(scope.getSnapshot())), [scope]);
2034
+ if (snapshot.status !== "ready") return null;
2035
+ const value = snapshot.value;
2036
+ const base = snapshot.base;
2037
+ const user = snapshot.user;
2038
+ const currentText = value?.statusText ?? "";
2039
+ const defaultText = base?.statusText ?? DEFAULT_STATUS_TEXT2;
2040
+ const text = pending ? pending.text : currentText;
2041
+ const userHas = user !== void 0 && Object.prototype.hasOwnProperty.call(user, "statusText");
2042
+ const overridden = pending ? !pending.reset : userHas;
2043
+ const dirty = pending !== null && (pending.reset ? userHas : pending.text.trim() !== currentText);
2044
+ const writable = snapshot.writable;
2045
+ const discard = () => {
2046
+ setPending(null);
2047
+ setFailed(false);
2048
+ };
2049
+ const resetField = () => {
2050
+ setPending({ text: defaultText, reset: true });
2051
+ setFailed(false);
2052
+ };
2053
+ const edit = (next) => {
2054
+ setPending({ text: next, reset: false });
2055
+ setFailed(false);
2056
+ };
2057
+ const save = async () => {
2058
+ if (pending === null) return;
2059
+ setSaving(true);
2060
+ setFailed(false);
2061
+ try {
2062
+ if (pending.reset) await scope.unset("statusText");
2063
+ else await scope.set("statusText", pending.text.trim());
2064
+ setPending(null);
2065
+ } catch {
2066
+ setFailed(true);
2067
+ } finally {
2068
+ setSaving(false);
2069
+ }
2070
+ };
2071
+ const blocked = !dirty || saving;
2072
+ const cardClass = `dshcf-settings-card${open ? " dshcf-settings-cardOpen" : ""}`;
2073
+ return React.createElement("li", { className: cardClass }, [
2074
+ React.createElement(
2075
+ "button",
2076
+ {
2077
+ type: "button",
2078
+ className: "dshcf-settings-header",
2079
+ "aria-expanded": open,
2080
+ "aria-label": `${open ? "\u6536\u8D77\u8BBE\u7F6E" : "\u5C55\u5F00\u8BBE\u7F6E"}: \u72B6\u6001\u63D0\u793A\u8BCD`,
2081
+ onClick: () => setOpen(!open)
2082
+ },
2083
+ [
2084
+ React.createElement("span", { className: "dshcf-settings-headText" }, [
2085
+ React.createElement("span", { className: "dshcf-settings-name" }, "\u72B6\u6001\u63D0\u793A\u8BCD"),
2086
+ React.createElement("span", { className: "dshcf-settings-description" }, "\u81EA\u5B9A\u4E49\u72B6\u6001\u63D0\u793A\u8BCD\uFF0C\u53EF\u4EE5\u66FF\u6362\u539F\u6709\u7684Deep diving...\u4E00\u884C\uFF0C\u7531\u63D2\u4EF6dsh-auto-collapse\u63D0\u4F9B")
2087
+ ]),
2088
+ dirty ? React.createElement("span", { className: "dshcf-settings-pending" }, "\u672A\u4FDD\u5B58") : null,
2089
+ ChevronIcon(open)
2090
+ ]
2091
+ ),
2092
+ open ? React.createElement("div", { className: "dshcf-settings-body" }, [
2093
+ !writable ? React.createElement("p", { className: "dshcf-settings-readOnly", role: "status" }, "\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002") : null,
2094
+ React.createElement("div", { className: "dshcf-settings-field" }, [
2095
+ React.createElement("div", { className: "dshcf-settings-fieldHead" }, [
2096
+ React.createElement("label", { className: "dshcf-settings-fieldLabel", htmlFor: "dshcf-status-text" }, "\u81EA\u5B9A\u4E49\u72B6\u6001\u63D0\u793A\u8BCD"),
2097
+ overridden ? React.createElement("span", { className: "dshcf-settings-badges" }, [
2098
+ React.createElement("span", { className: "dshcf-settings-badge" }, "\u5DF2\u8986\u76D6"),
2099
+ React.createElement("button", { type: "button", className: "dshcf-settings-reset", disabled: !writable, onClick: resetField }, "\u6062\u590D\u9ED8\u8BA4")
2100
+ ]) : null
2101
+ ]),
2102
+ React.createElement("input", {
2103
+ id: "dshcf-status-text",
2104
+ className: "dshcf-settings-input",
2105
+ type: "text",
2106
+ value: text,
2107
+ placeholder: "Deep diving...",
2108
+ disabled: !writable,
2109
+ onChange: (event) => edit(event.target.value)
2110
+ }),
2111
+ React.createElement("p", { className: "dshcf-settings-hint" }, "\u4E3A\u7A7A\u65F6\u6062\u590D\u9ED8\u8BA4Deep diving...\u63D0\u793A\u8BCD\u72B6\u6001")
2112
+ ]),
2113
+ React.createElement("div", { className: "dshcf-settings-footer" }, [
2114
+ failed ? React.createElement("p", { className: "dshcf-settings-failed", role: "status" }, "\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002") : null,
2115
+ React.createElement("button", { type: "button", className: "dshcf-settings-discard", disabled: !dirty || saving, onClick: discard }, "\u653E\u5F03\u4FEE\u6539"),
2116
+ React.createElement("button", { type: "button", className: "dshcf-settings-save", disabled: blocked, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58")
2117
+ ])
2118
+ ]) : null
2119
+ ]);
2120
+ }
2121
+ function setupSettingsCard(ctx, scope) {
2122
+ injectCardStyle();
2123
+ return ctx.slots.inject("settings.plugin.item", () => ctx.slots.register(
2124
+ {
2125
+ name: "settings.plugin.item",
2126
+ key: AUTO_COLLAPSE_NS,
2127
+ inject: () => ({ scope })
2128
+ },
2129
+ StatusTextCard
2130
+ ));
2131
+ }
2132
+
1428
2133
  // src/client.ts
1429
2134
  var name = "dsh-auto-collapse";
1430
- var inject = [];
2135
+ var inject = ["slots", "settingsScope"];
1431
2136
  function apply(ctx) {
1432
2137
  ctx.effect(() => {
1433
- const controller = new FoldController();
2138
+ const scope = ctx.settingsScope?.bind({ namespace: AUTO_COLLAPSE_NS });
2139
+ const controller = new FoldController(statusTextProvider(scope));
1434
2140
  controller.start();
1435
- return () => controller.stop();
1436
- }, "dsh-auto-collapse: fold observer");
2141
+ const offScope = scope?.subscribe(() => controller.refresh());
2142
+ const offSettings = ctx.slots === void 0 || scope === void 0 ? void 0 : setupSettingsCard(ctx, scope);
2143
+ return () => {
2144
+ offScope?.();
2145
+ offSettings?.();
2146
+ controller.stop();
2147
+ };
2148
+ }, "dsh-auto-collapse: fold observer + settings card");
1437
2149
  }
1438
2150
  return __toCommonJS(client_exports);
1439
2151
  })();