dsh-diff-approval 0.11.0 → 0.13.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
@@ -18,6 +18,7 @@ window.__ModuleLoader__.load({
18
18
  //#endregion
19
19
  let react_jsx_runtime = require("react/jsx-runtime");
20
20
  let react = require("react");
21
+ let react_dom = require("react-dom");
21
22
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
22
23
  //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js
23
24
  var Diff = class {
@@ -654,6 +655,16 @@ window.__ModuleLoader__.load({
654
655
  function computeWholeFileDiff(oldText, newText) {
655
656
  oldText = oldText.replace(/\r\n?/g, "\n");
656
657
  newText = newText.replace(/\r\n?/g, "\n");
658
+ if (oldText === newText) return {
659
+ rows: contentLines(oldText).map((text, index) => ({
660
+ kind: "context",
661
+ text,
662
+ oldLine: index + 1,
663
+ newLine: index + 1
664
+ })),
665
+ removed: 0,
666
+ added: 0
667
+ };
657
668
  const oldLines = contentLines(oldText);
658
669
  const newLines = contentLines(newText);
659
670
  const context = Math.max(1, oldLines.length, newLines.length);
@@ -712,6 +723,111 @@ window.__ModuleLoader__.load({
712
723
  return (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n");
713
724
  }
714
725
  //#endregion
726
+ //#region lib/types/client/split-diff.js
727
+ /**
728
+ * Side-by-side (split) diff model: the unified whole-file diff rows are
729
+ * regrouped into line-aligned pairs for a two-column "before | current" view.
730
+ * Pure derivation; the view owns rendering. A context row becomes a pair with
731
+ * both sides, a deletion a left-only pair, an addition a right-only pair, and
732
+ * an adjacent deletion/addition run is paired line-by-line into a single
733
+ * replacement pair so the two columns line up.
734
+ * @module dsh-diff-approval/client/split-diff
735
+ */
736
+ /** One whole-file diff row → its visible left side text. */
737
+ function leftSideOf(row) {
738
+ if (row.kind === "add") return void 0;
739
+ return {
740
+ text: row.text,
741
+ line: row.oldLine
742
+ };
743
+ }
744
+ /** One whole-file diff row → its visible right side text. */
745
+ function rightSideOf(row) {
746
+ if (row.kind === "del") return void 0;
747
+ return {
748
+ text: row.text,
749
+ line: row.newLine
750
+ };
751
+ }
752
+ /**
753
+ * Regroup the whole-file rows into aligned split pairs, pairing a deletion run
754
+ * with a following addition run line-by-line (so a replaced line is one pair),
755
+ * and leaving a stray deletion or addition as a one-sided pair.
756
+ * @param rows - the whole-file diff rows.
757
+ * @returns the split pairs plus the row→pair index map.
758
+ */
759
+ function computeSideBySideDiff(rows) {
760
+ const pairs = [];
761
+ const pairOfRow = /* @__PURE__ */ new Map();
762
+ const pendingDel = [];
763
+ const push = (pair, rows) => {
764
+ const index = pairs.length;
765
+ pairs.push(pair);
766
+ for (const r of rows) pairOfRow.set(r, index);
767
+ };
768
+ const flushPending = () => {
769
+ for (const { index, row } of pendingDel) push({
770
+ kind: "del",
771
+ left: leftSideOf(row),
772
+ right: void 0
773
+ }, [index]);
774
+ pendingDel.length = 0;
775
+ };
776
+ for (let i = 0; i < rows.length; i++) {
777
+ const row = rows[i];
778
+ if (row.kind === "context") {
779
+ flushPending();
780
+ push({
781
+ kind: "context",
782
+ left: leftSideOf(row),
783
+ right: rightSideOf(row)
784
+ }, [i]);
785
+ } else if (row.kind === "del") pendingDel.push({
786
+ index: i,
787
+ row
788
+ });
789
+ else {
790
+ const del = pendingDel.shift();
791
+ if (del !== void 0) push({
792
+ kind: "replace",
793
+ left: leftSideOf(del.row),
794
+ right: rightSideOf(row)
795
+ }, [del.index, i]);
796
+ else push({
797
+ kind: "add",
798
+ left: void 0,
799
+ right: rightSideOf(row)
800
+ }, [i]);
801
+ }
802
+ }
803
+ flushPending();
804
+ return {
805
+ pairs,
806
+ pairOfRow
807
+ };
808
+ }
809
+ /**
810
+ * Pair indices whose left or right text contains the query (case-insensitive).
811
+ * A pair counts once however many times the query appears, so split search
812
+ * highlights the whole pair on both columns and a single "current" pair is
813
+ * stepped through — not each individual left/right occurrence.
814
+ * @param pairs - the split pairs.
815
+ * @param query - the query text; an empty query matches nothing.
816
+ * @returns matching pair indices, in file order.
817
+ */
818
+ function searchPairs(pairs, query) {
819
+ if (query === "") return [];
820
+ const lower = query.toLowerCase();
821
+ const matches = [];
822
+ for (let index = 0; index < pairs.length; index++) {
823
+ const p = pairs[index];
824
+ if (p === void 0) continue;
825
+ if (p.left !== void 0 && p.left.text.toLowerCase().includes(lower)) matches.push(index);
826
+ else if (p.right !== void 0 && p.right.text.toLowerCase().includes(lower)) matches.push(index);
827
+ }
828
+ return matches;
829
+ }
830
+ //#endregion
715
831
  //#region node_modules/.pnpm/@shikijs+types@4.4.3/node_modules/@shikijs/types/dist/index.mjs
716
832
  var ShikiError = class extends Error {
717
833
  constructor(message) {
@@ -11588,6 +11704,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11588
11704
  //#endregion
11589
11705
  //#region lib/types/client/reference.js
11590
11706
  /**
11707
+ * Reference labels for the selection toolbar: a workspace-relative path (or
11708
+ * the absolute path when the file is outside the workspace) plus a 1-based
11709
+ * line range. Pure derivation so the display rule is unit-testable without
11710
+ * the panel.
11711
+ * @module dsh-diff-approval/client/reference
11712
+ */
11713
+ /** The marker that replaces a reference's line number when its lines are gone. */
11714
+ const LINE_MISSING_LABEL = "LINE_MISSING";
11715
+ /**
11591
11716
  * The path embedded in a copied reference: workspace-relative (forward
11592
11717
  * slashes) when the file lives inside the current workspace, the absolute
11593
11718
  * path otherwise. A bare file name is never enough — a reference must resolve
@@ -11615,15 +11740,66 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11615
11740
  return start === end ? String(start) : `${start}-${end}`;
11616
11741
  }
11617
11742
  /**
11618
- * Build the clipboard text for a selected line range.
11743
+ * Build the clipboard text for a selected line range, wrapped in parentheses so
11744
+ * the reference reads as one unambiguous token (and can be matched precisely).
11619
11745
  * @param path - the selected file's path.
11620
11746
  * @param workspacePath - the current workspace root, or `undefined`.
11621
11747
  * @param start - first selected line number.
11622
11748
  * @param end - last selected line number.
11623
- * @returns the `path:range` reference text.
11749
+ * @returns the `(path:range)` reference text.
11624
11750
  */
11625
11751
  function referenceOf(path, workspacePath, start, end) {
11626
- return `${referencePathOf(path, workspacePath)}:${lineRangeLabel(start, end)}`;
11752
+ return `(${referencePathOf(path, workspacePath)}:${lineRangeLabel(start, end)})`;
11753
+ }
11754
+ /**
11755
+ * Map one referenced line range from `oldContent` coordinates to `newContent`
11756
+ * coordinates. Lines that survive (unchanged context) map to their new line
11757
+ * numbers, and the surviving lines' min/max span is returned. When every line
11758
+ * in the range was removed, returns `undefined` (the reference is expired).
11759
+ * @param oldContent - the file content the reference was made against.
11760
+ * @param newContent - the file content now.
11761
+ * @param start - first referenced line (1-based, inclusive).
11762
+ * @param end - last referenced line (1-based, inclusive).
11763
+ * @returns the surviving range, or `undefined` when nothing survives.
11764
+ */
11765
+ function remapReferenceRange(oldContent, newContent, start, end) {
11766
+ const diff = computeWholeFileDiff(oldContent, newContent);
11767
+ const oldToNew = /* @__PURE__ */ new Map();
11768
+ for (const row of diff.rows) if (row.oldLine !== void 0 && row.newLine !== void 0) oldToNew.set(row.oldLine, row.newLine);
11769
+ let min = Infinity;
11770
+ let max = -Infinity;
11771
+ for (let line = start; line <= end; line++) {
11772
+ const next = oldToNew.get(line);
11773
+ if (next === void 0) continue;
11774
+ min = Math.min(min, next);
11775
+ max = Math.max(max, next);
11776
+ }
11777
+ if (min === Infinity) return void 0;
11778
+ return {
11779
+ start: min,
11780
+ end: max
11781
+ };
11782
+ }
11783
+ /**
11784
+ * Rewrite every `(referencePath:line)` / `(referencePath:start-end)` occurrence
11785
+ * in `text`, remapping each range from `oldContent` to `newContent`. A range
11786
+ * whose lines all survived becomes the new range; one whose lines were all
11787
+ * removed becomes `(referencePath:LINE_MISSING)`.
11788
+ * @param text - the free text (composer draft, queued message) to rewrite.
11789
+ * @param referencePath - the file's reference path (workspace-relative or absolute).
11790
+ * @param oldContent - the file content the references were made against.
11791
+ * @param newContent - the file content now.
11792
+ * @returns the rewritten text.
11793
+ */
11794
+ function remapReferences(text, referencePath, oldContent, newContent) {
11795
+ const escaped = referencePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11796
+ const regex = new RegExp(`\\(${escaped}:(\\d+)(?:-(\\d+))?\\)`, "g");
11797
+ return text.replace(regex, (_whole, startText, endText) => {
11798
+ const start = Number(startText);
11799
+ const mapped = remapReferenceRange(oldContent, newContent, start, endText === void 0 ? start : Number(endText));
11800
+ if (mapped === void 0) return `(${referencePath}:${LINE_MISSING_LABEL})`;
11801
+ return `(${referencePath}:${lineRangeLabel(mapped.start, mapped.end)})`;
11802
+ });
11627
11803
  }
11628
11804
  //#endregion
11629
11805
  //#region lib/types/client/settings.js
@@ -11631,6 +11807,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11631
11807
  const PASTE_ON_COPY_KEY = "diff-approval:paste-on-copy";
11632
11808
  const IMPORT_UNTRACKED_KEY = "diff-approval:import-untracked";
11633
11809
  const TAB_WIDTH_KEY = "diff-approval:tab-size";
11810
+ const SPLIT_MODE_KEY = "diff-approval:split-mode";
11634
11811
  const WRAP_PREFIX = "diff-approval:wrap:";
11635
11812
  /**
11636
11813
  * Whether copying a reference should also paste it into the chat input and
@@ -11686,9 +11863,55 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11686
11863
  function setTabWidth(value) {
11687
11864
  localStorage.setItem(TAB_WIDTH_KEY, String(value));
11688
11865
  }
11866
+ /**
11867
+ * Whether the whole-file diff view uses the two-column (side-by-side) layout.
11868
+ * Default off (single column): the unified diff. Only an explicit `'1'` enables
11869
+ * split mode.
11870
+ * @returns whether the split (two-column) diff view is used.
11871
+ */
11872
+ function splitMode() {
11873
+ return localStorage.getItem(SPLIT_MODE_KEY) === "1";
11874
+ }
11875
+ /** Persist the split-view preference. */
11876
+ function setSplitMode(value) {
11877
+ localStorage.setItem(SPLIT_MODE_KEY, value ? "1" : "0");
11878
+ }
11879
+ const QUICK_SUMMON_KEY = "diff-approval:quick-summon-key";
11880
+ /**
11881
+ * The quick-summon chord. Stored as `Modifier+...+Key`; falls back to
11882
+ * {@link DEFAULT_QUICK_SUMMON}.
11883
+ * @returns the chord string.
11884
+ */
11885
+ function quickSummonKey() {
11886
+ return localStorage.getItem(QUICK_SUMMON_KEY) ?? "Ctrl+D";
11887
+ }
11888
+ /** Persist the quick-summon chord. */
11889
+ function setQuickSummonKey(value) {
11890
+ localStorage.setItem(QUICK_SUMMON_KEY, value);
11891
+ }
11892
+ /**
11893
+ * Whether a keyboard event matches a chord string like `Ctrl+D`. Modifier
11894
+ * names are matched case-insensitively (`Ctrl`/`Control`, `Alt`/`Option`,
11895
+ * `Shift`, `Meta`/`Cmd`/`Command`/`Win`); the final part is the key. Exact
11896
+ * modifier set is required (extra modifiers do not match).
11897
+ * @param event - the keydown event.
11898
+ * @param shortcut - the chord string.
11899
+ * @returns whether the event matches.
11900
+ */
11901
+ function matchesShortcut(event, shortcut) {
11902
+ const parts = shortcut.split("+").map((part) => part.trim().toLowerCase());
11903
+ const key = parts.pop();
11904
+ if (key === void 0 || key === "") return false;
11905
+ const mods = new Set(parts);
11906
+ const ctrl = mods.has("ctrl") || mods.has("control");
11907
+ const alt = mods.has("alt") || mods.has("option");
11908
+ const shift = mods.has("shift");
11909
+ const meta = mods.has("meta") || mods.has("cmd") || mods.has("command") || mods.has("win");
11910
+ return event.key.toLowerCase() === key && event.ctrlKey === ctrl && event.altKey === alt && event.shiftKey === shift && event.metaKey === meta;
11911
+ }
11689
11912
  //#endregion
11690
11913
  //#region \0dsh-css:/home/runner/work/dsh-diff-approval/dsh-diff-approval/src/client/PendingPanel.module.css.mjs
11691
- const css = ".F1KBNa_layer{box-sizing:border-box;flex:none;align-items:center;width:calc(100% + 4px);height:42px;margin:4px -2px;display:flex;position:relative}.F1KBNa_footerButtons{align-items:center;width:100%;display:flex}.F1KBNa_badge{box-sizing:border-box;width:100%;height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.F1KBNa_badge:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_badge[data-active]{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_badge:disabled{color:var(--dsw-alias-label-tertiary);cursor:default;background:0 0}.F1KBNa_badgeLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.F1KBNa_badgeCount{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;margin-left:auto;font-size:12px;line-height:16px}.F1KBNa_layer.F1KBNa_rail{width:36px;height:36px;margin:8px 0 10px}.F1KBNa_rail .F1KBNa_badge{border-radius:50%;justify-content:center;gap:0;width:36px;height:36px;padding:0}.F1KBNa_rail .F1KBNa_badgeLabel{display:none}.F1KBNa_rail .F1KBNa_badgeCount{box-sizing:border-box;background:var(--dsw-alias-state-business-primary);min-width:18px;height:18px;color:var(--dsw-alias-label-primary-foreground);font-variant-numeric:tabular-nums;border-radius:9px;justify-content:center;align-items:center;padding:0 4px;font-size:11px;line-height:18px;display:flex;position:absolute;top:-3px;right:-7px}.F1KBNa_fullscreenBackdrop{z-index:29;background:var(--dsw-specific-sidebar-fill);position:fixed;inset:0}.F1KBNa_panel{z-index:30;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:none;box-shadow:var(--dsw-shadow-lv2);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px;flex-direction:column;display:flex;position:fixed;inset:8px 8px 128px;overflow:hidden}.F1KBNa_header{box-sizing:border-box;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex:none;justify-content:space-between;align-items:center;min-height:44px;padding:10px 12px;display:flex}.F1KBNa_headerActions{align-items:center;gap:2px;display:flex}.F1KBNa_settingsPage{padding:8px 12px}.F1KBNa_settingsRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.F1KBNa_settingsRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.F1KBNa_settingsRowTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.F1KBNa_settingsRowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.F1KBNa_settingsSelector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.F1KBNa_settingsSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_settingsSelectorChevron{flex:none}.F1KBNa_states{flex-direction:column;flex:1;min-height:0;padding:4px 12px 12px;display:flex;overflow-y:auto}.F1KBNa_split{flex:1;align-items:stretch;min-height:0;display:flex;position:relative}.F1KBNa_fileListFloat{z-index:40;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:12px;flex-direction:column;padding:6px 8px 10px;display:flex;position:absolute;overflow:hidden}.F1KBNa_fileList{box-sizing:border-box;border-right:1px solid var(--dsw-alias-border-l2);flex-direction:column;flex:none;width:240px;min-height:0;padding:4px 8px 12px;display:flex}.F1KBNa_listScroll{flex:1;min-height:0;overflow-y:auto}.F1KBNa_bulkActions{flex:none;gap:6px;padding-top:8px;display:flex}.F1KBNa_bulkActions .F1KBNa_action{text-align:center;flex:1}.F1KBNa_resizeHandle{cursor:col-resize;background:0 0;flex:none;width:5px;margin:0 -2px}.F1KBNa_resizeHandle:hover{background:var(--dsw-alias-border-l2)}.F1KBNa_detail{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.F1KBNa_detailEmpty{color:var(--dsw-alias-label-tertiary);text-align:center;flex:1;justify-content:center;align-items:center;margin:0;padding:24px;font-size:12px;line-height:18px;display:flex}.F1KBNa_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}.F1KBNa_note,.F1KBNa_readError,.F1KBNa_hint{color:var(--dsw-alias-label-tertiary);margin:4px 0;font-size:12px;line-height:18px}.F1KBNa_noteCentered{text-align:center;margin:auto}.F1KBNa_emptyState{flex-direction:column;align-items:center;gap:10px;margin:auto;display:flex}.F1KBNa_importButton{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:5px 14px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_importButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_importButton:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_importNote{color:var(--dsw-alias-label-tertiary);text-align:center;margin:0;font-size:12px;line-height:18px}.F1KBNa_readError{color:var(--dsw-alias-state-error-primary)}.F1KBNa_group{color:var(--dsw-alias-label-tertiary);margin:8px 0 4px;font-size:12px;font-weight:500;line-height:16px}.F1KBNa_rows{margin:0;padding:0;list-style:none}.F1KBNa_row{border:1px solid #0000;border-radius:10px;margin:2px 0}.F1KBNa_rowHead{width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:baseline;gap:8px;padding:6px 8px;display:flex}.F1KBNa_rowHead:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_rowHead[data-selected]{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_rowPath{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:var(--dsw-font-markdown-code-block);overflow:hidden}.F1KBNa_kindTag{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_kindHint{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:16px}.F1KBNa_divergedHint,.F1KBNa_missingHint{border-bottom:1px solid var(--dsw-alias-border-l2);margin:0;padding:6px 8px;font-size:12px;line-height:18px}.F1KBNa_missing{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary));white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_rowFailed{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_actionError{border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;margin:0;padding:6px 8px;font-size:12px;line-height:18px}.F1KBNa_missingHint{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary))}.F1KBNa_rowMeta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;gap:6px;margin-left:auto;font-size:12px;line-height:16px;display:inline-flex}.F1KBNa_addCount{color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 60%, var(--dsw-alias-label-primary))}.F1KBNa_delCount{color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 60%, var(--dsw-alias-label-primary))}.F1KBNa_diff{background:var(--dsw-alias-markdown-code-block);flex-direction:column;flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.F1KBNa_diffHeader{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:6px 8px;display:flex}.F1KBNa_diffPath{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block);flex:1;font-size:12px;line-height:18px;overflow:hidden}.F1KBNa_diffActions{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:6px 8px;display:flex}.F1KBNa_diffStats{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px;line-height:16px}.F1KBNa_flexSpacer{flex:1}.F1KBNa_action{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_action:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_actionPrimary{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_actionPrimary:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}.F1KBNa_actionPrimary:disabled{background:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:.55}.F1KBNa_actionQuietDisabled:disabled{cursor:pointer;color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1);background:0 0}.F1KBNa_actionPrimary.F1KBNa_actionQuietDisabled:disabled{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:1}.F1KBNa_iconAction{justify-content:center;align-items:center;min-height:25px;padding:4px 6px;display:inline-flex}.F1KBNa_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expand{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_expand:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expandExpanded svg{transform:rotate(180deg)}.F1KBNa_diffBodyWrap{flex:1;min-height:0;display:flex;position:relative}.F1KBNa_diffBody{min-width:0;font:var(--dsw-font-markdown-code-block);cursor:text;outline:none;flex:1;padding:0;position:relative;overflow:auto}.F1KBNa_diffBody::-webkit-scrollbar,.F1KBNa_diffBody::-webkit-scrollbar-track,.F1KBNa_diffBody::-webkit-scrollbar-thumb{cursor:default}.F1KBNa_blockActions{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);z-index:2;border-radius:10px;align-items:center;gap:9px;padding:5px;display:flex;position:absolute;right:8px}.F1KBNa_blockPosition{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;margin-top:1px;margin-left:7px;font-size:12px;line-height:16px}.F1KBNa_searchBar{z-index:3;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);border-radius:10px;align-items:center;gap:4px;padding:4px;display:flex;position:absolute;top:8px;right:8px}.F1KBNa_searchInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:150px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:3px 8px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_searchInput:focus{border-color:var(--dsw-alias-state-business-primary)}.F1KBNa_searchCount{text-align:center;min-width:34px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px;line-height:16px}.F1KBNa_diffBody [data-diff-search=hit]{background-image:linear-gradient(#facc1529,#facc1529)}.F1KBNa_diffBody [data-diff-search=current]{background-image:linear-gradient(#facc1552,#facc1552)}.F1KBNa_blockFlash{z-index:1;box-sizing:border-box;border:2px solid var(--dsw-alias-state-business-primary);pointer-events:none;border-radius:4px;animation:1s ease-out forwards F1KBNa_diffFlash;position:absolute;left:0;right:0}@keyframes F1KBNa_diffFlash{0%{opacity:1}to{opacity:0}}.F1KBNa_overviewRuler{pointer-events:none;opacity:.5;width:4px;position:absolute;top:0;bottom:0;right:0}.F1KBNa_overviewMarker{border-radius:2px;width:100%;min-height:2px;position:absolute;right:0}.F1KBNa_markerDel{background-color:var(--dsw-alias-state-error-primary)}.F1KBNa_markerAdd{background-color:var(--dsw-alias-state-success-primary)}.F1KBNa_lines{border-spacing:0;width:max-content;min-width:100%;display:table}.F1KBNa_line{height:22px;line-height:22px;display:table-row}.F1KBNa_vSpacer{display:table-row}.F1KBNa_gutter{box-sizing:border-box;text-align:right;width:44px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;user-select:none;cursor:default;padding-right:10px;display:table-cell}.F1KBNa_code{white-space:pre;display:table-cell}.F1KBNa_context{color:var(--dsw-alias-label-primary)}.F1KBNa_del{background-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.F1KBNa_add{background-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent)}.F1KBNa_statusBar{box-sizing:border-box;border-top:1px solid var(--dsw-alias-border-l2);height:34px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-markdown-code-block);font-variant-numeric:tabular-nums;flex:none;align-items:center;gap:8px;padding:0 8px;font-size:12px;line-height:18px;display:flex}.F1KBNa_statusAction{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);white-space:nowrap;cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_statusAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langSelect{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;flex:none;align-items:center;gap:4px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px;display:inline-flex}.F1KBNa_langSelect:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langLabel{text-overflow:ellipsis;white-space:nowrap;max-width:140px;overflow:hidden}.F1KBNa_notice{z-index:60;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:360px;box-shadow:var(--dsw-shadow-lv3);border-radius:10px;align-items:flex-start;gap:12px;padding:12px 14px;display:flex;position:fixed;bottom:24px;right:24px}.F1KBNa_noticeText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);flex:1;margin:0;line-height:1.45}.F1KBNa_noticeButton{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font:var(--dsw-font-caption);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px}.F1KBNa_noticeButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_wrap .F1KBNa_line{height:auto;min-height:22px}.F1KBNa_subline{white-space:pre;height:22px;line-height:22px;display:block;overflow:hidden}.F1KBNa_wrapActive{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_wrapActive:hover{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}";
11914
+ const css = ".F1KBNa_layer{box-sizing:border-box;flex:none;align-items:center;width:calc(100% + 4px);height:42px;margin:4px -2px;display:flex;position:relative}.F1KBNa_footerButtons{align-items:center;width:100%;display:flex}.F1KBNa_badge{box-sizing:border-box;width:100%;height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.F1KBNa_badge:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_badge[data-active]{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_badge:disabled{color:var(--dsw-alias-label-tertiary);cursor:default;background:0 0}.F1KBNa_badgeLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.F1KBNa_badgeCount{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;margin-left:auto;font-size:12px;line-height:16px}.F1KBNa_layer.F1KBNa_rail{width:36px;height:36px;margin:8px 0 10px}.F1KBNa_rail .F1KBNa_badge{border-radius:50%;justify-content:center;gap:0;width:36px;height:36px;padding:0}.F1KBNa_rail .F1KBNa_badgeLabel{display:none}.F1KBNa_rail .F1KBNa_badgeCount{box-sizing:border-box;background:var(--dsw-alias-state-business-primary);min-width:18px;height:18px;color:var(--dsw-alias-label-primary-foreground);font-variant-numeric:tabular-nums;border-radius:9px;justify-content:center;align-items:center;padding:0 4px;font-size:11px;line-height:18px;display:flex;position:absolute;top:-3px;right:-7px}.F1KBNa_fullscreenBackdrop{z-index:29;background:var(--dsw-specific-sidebar-fill);position:fixed;inset:0}.F1KBNa_panel{z-index:30;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:none;box-shadow:var(--dsw-shadow-lv2);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px;flex-direction:column;display:flex;position:fixed;inset:8px 8px 128px;overflow:hidden}.F1KBNa_header{box-sizing:border-box;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex:none;justify-content:space-between;align-items:center;min-height:44px;padding:10px 12px;display:flex}.F1KBNa_headerActions{align-items:center;gap:2px;display:flex}.F1KBNa_settingsPage{padding:8px 12px}.F1KBNa_settingsRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.F1KBNa_settingsRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.F1KBNa_settingsRowTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.F1KBNa_settingsRowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.F1KBNa_settingsSelector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.F1KBNa_settingsSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_settingsSelectorChevron{flex:none}.F1KBNa_states{flex-direction:column;flex:1;min-height:0;padding:4px 12px 12px;display:flex;overflow-y:auto}.F1KBNa_split{flex:1;align-items:stretch;min-height:0;display:flex;position:relative}.F1KBNa_fileListFloat{z-index:40;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:12px;flex-direction:column;padding:6px 8px 10px;display:flex;position:absolute;overflow:hidden}.F1KBNa_fileList{box-sizing:border-box;border-right:1px solid var(--dsw-alias-border-l2);flex-direction:column;flex:none;width:240px;min-height:0;padding:4px 8px 12px;display:flex}.F1KBNa_listScroll{flex:1;min-height:0;overflow-y:auto}.F1KBNa_bulkActions{flex:none;gap:6px;padding-top:8px;display:flex}.F1KBNa_bulkActions .F1KBNa_action{text-align:center;flex:1}.F1KBNa_resizeHandle{cursor:col-resize;background:0 0;flex:none;width:5px;margin:0 -2px}.F1KBNa_resizeHandle:hover{background:var(--dsw-alias-border-l2)}.F1KBNa_detail{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.F1KBNa_detailEmpty{color:var(--dsw-alias-label-tertiary);text-align:center;flex:1;justify-content:center;align-items:center;margin:0;padding:24px;font-size:12px;line-height:18px;display:flex}.F1KBNa_confirmBackdrop{z-index:5;background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, transparent);justify-content:center;align-items:center;padding:24px;display:flex;position:absolute;inset:0}.F1KBNa_confirmCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:340px;box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:12px;padding:16px;display:flex}.F1KBNa_confirmText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0;line-height:1.5}.F1KBNa_confirmActions{justify-content:flex-end;gap:8px;display:flex}.F1KBNa_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}.F1KBNa_note,.F1KBNa_readError,.F1KBNa_hint{color:var(--dsw-alias-label-tertiary);margin:4px 0;font-size:12px;line-height:18px}.F1KBNa_noteCentered{text-align:center;margin:auto}.F1KBNa_emptyState{flex-direction:column;align-items:center;gap:10px;margin:auto;display:flex}.F1KBNa_importButton{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:5px 14px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_importButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_importButton:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_importNote{color:var(--dsw-alias-label-tertiary);text-align:center;margin:0;font-size:12px;line-height:18px}.F1KBNa_readError{color:var(--dsw-alias-state-error-primary)}.F1KBNa_group{color:var(--dsw-alias-label-tertiary);margin:8px 0 4px;font-size:12px;font-weight:500;line-height:16px}.F1KBNa_rows{margin:0;padding:0;list-style:none}.F1KBNa_row{border:1px solid #0000;border-radius:10px;margin:2px 0}.F1KBNa_rowHead{width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:baseline;gap:8px;padding:6px 8px;display:flex}.F1KBNa_rowHead:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_rowHead[data-selected]{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_rowPath{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:var(--dsw-font-markdown-code-block);overflow:hidden}.F1KBNa_kindTag{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_kindHint{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:16px}.F1KBNa_divergedHint,.F1KBNa_missingHint{border-bottom:1px solid var(--dsw-alias-border-l2);margin:0;padding:6px 8px;font-size:12px;line-height:18px}.F1KBNa_missing{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary));white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_rowFailed{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_actionError{border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;margin:0;padding:6px 8px;font-size:12px;line-height:18px}.F1KBNa_missingHint{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary))}.F1KBNa_rowMeta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;gap:6px;margin-left:auto;font-size:12px;line-height:16px;display:inline-flex}.F1KBNa_addCount{color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 60%, var(--dsw-alias-label-primary))}.F1KBNa_delCount{color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 60%, var(--dsw-alias-label-primary))}.F1KBNa_diff{background:var(--dsw-alias-markdown-code-block);flex-direction:column;flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.F1KBNa_diffHeader{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:6px 8px;display:flex}.F1KBNa_diffPath{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block);flex:1;font-size:12px;line-height:18px;overflow:hidden}.F1KBNa_diffActions{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:6px 8px;display:flex}.F1KBNa_diffStats{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px;line-height:16px}.F1KBNa_flexSpacer{flex:1}.F1KBNa_action{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_action:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_actionPrimary{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_actionPrimary:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}.F1KBNa_actionPrimary:disabled{background:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:.55}.F1KBNa_actionQuietDisabled:disabled{cursor:pointer;color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1);background:0 0}.F1KBNa_actionPrimary.F1KBNa_actionQuietDisabled:disabled{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:1}.F1KBNa_iconAction{justify-content:center;align-items:center;min-height:25px;padding:4px 6px;display:inline-flex}.F1KBNa_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expand{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_expand:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expandExpanded svg{transform:rotate(180deg)}.F1KBNa_diffBodyWrap{flex:1;min-height:0;display:flex;position:relative}.F1KBNa_diffBody{min-width:0;font:var(--dsw-font-markdown-code-block);-webkit-text-size-adjust:100%;text-size-adjust:100%;cursor:text;outline:none;flex:1;padding:0;position:relative;overflow:auto}.F1KBNa_diffBody::-webkit-scrollbar,.F1KBNa_diffBody::-webkit-scrollbar-track,.F1KBNa_diffBody::-webkit-scrollbar-thumb{cursor:default}.F1KBNa_blockActions{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);z-index:2;border-radius:10px;align-items:center;gap:9px;padding:5px;display:flex;position:absolute;right:8px}.F1KBNa_blockPosition{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;margin-top:1px;margin-left:7px;font-size:12px;line-height:16px}.F1KBNa_searchBar{z-index:3;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);border-radius:10px;align-items:center;gap:4px;padding:4px;display:flex;position:absolute;top:8px;right:8px}.F1KBNa_searchInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:150px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:3px 8px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_searchInput:focus{border-color:var(--dsw-alias-state-business-primary)}.F1KBNa_searchCount{text-align:center;min-width:34px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px;line-height:16px}.F1KBNa_diffBody [data-diff-search=hit]{background-image:linear-gradient(#facc1529,#facc1529)}.F1KBNa_diffBody [data-diff-search=current]{background-image:linear-gradient(#facc1552,#facc1552)}.F1KBNa_blockFlash{z-index:1;box-sizing:border-box;border:2px solid var(--dsw-alias-state-business-primary);pointer-events:none;border-radius:4px;animation:1s ease-out forwards F1KBNa_diffFlash;position:absolute;left:0;right:0}@keyframes F1KBNa_diffFlash{0%{opacity:1}to{opacity:0}}.F1KBNa_overviewRuler{pointer-events:none;opacity:.5;width:4px;position:absolute;top:0;bottom:0;right:0}.F1KBNa_overviewMarker{border-radius:2px;width:100%;min-height:2px;position:absolute;right:0}.F1KBNa_markerDel{background-color:var(--dsw-alias-state-error-primary)}.F1KBNa_markerAdd{background-color:var(--dsw-alias-state-success-primary)}.F1KBNa_lines{border-spacing:0;width:max-content;min-width:100%;display:table}.F1KBNa_line{height:22px;line-height:22px;display:table-row}.F1KBNa_vSpacer{display:table-row}.F1KBNa_gutter{box-sizing:border-box;text-align:right;width:44px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;user-select:none;cursor:default;padding-right:10px;display:table-cell}.F1KBNa_code{white-space:pre;display:table-cell}.F1KBNa_context{color:var(--dsw-alias-label-primary)}.F1KBNa_del{background-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.F1KBNa_add{background-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent)}.F1KBNa_statusBar{box-sizing:border-box;border-top:1px solid var(--dsw-alias-border-l2);height:34px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-markdown-code-block);font-variant-numeric:tabular-nums;flex:none;align-items:center;gap:8px;padding:0 8px;font-size:12px;line-height:18px;display:flex}.F1KBNa_statusAction{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);white-space:nowrap;cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_statusAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langSelect{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;flex:none;align-items:center;gap:4px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px;display:inline-flex}.F1KBNa_langSelect:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langLabel{text-overflow:ellipsis;white-space:nowrap;max-width:140px;overflow:hidden}.F1KBNa_notice{z-index:60;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:360px;box-shadow:var(--dsw-shadow-lv3);border-radius:10px;align-items:flex-start;gap:12px;padding:12px 14px;display:flex;position:fixed;bottom:24px;right:24px}.F1KBNa_noticeText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);flex:1;margin:0;line-height:1.45}.F1KBNa_noticeButton{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font:var(--dsw-font-caption);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px}.F1KBNa_noticeButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_wrap .F1KBNa_line{height:auto;min-height:22px}.F1KBNa_subline{white-space:pre;height:22px;line-height:22px;display:block;overflow:hidden}.F1KBNa_wrapActive{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_wrapActive:hover{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}.F1KBNa_splitRoot{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.F1KBNa_diffBodySplit{flex:1;min-height:0;overflow-x:hidden}.F1KBNa_splitCols{width:100%;min-width:0;display:flex}.F1KBNa_splitCol{box-sizing:border-box;flex:1 1 0;min-width:0;overflow:hidden}.F1KBNa_splitDivider{background:var(--dsw-alias-border-l2);flex:0 0 1px}.F1KBNa_splitCol .F1KBNa_gutter,.F1KBNa_splitCol .F1KBNa_code{vertical-align:top}.F1KBNa_splitHScrollRow{border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex:none;width:100%;min-width:0;display:flex}.F1KBNa_splitHScroll{flex:1 1 0;min-width:0;height:15px;overflow:auto hidden}.F1KBNa_splitHScroll::-webkit-scrollbar{height:15px}.F1KBNa_splitHScroll::-webkit-scrollbar-track{cursor:default}.F1KBNa_splitHScroll::-webkit-scrollbar-thumb{cursor:default}.F1KBNa_splitHScrollFill{height:1px}.F1KBNa_splitLdel{background-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.F1KBNa_splitLadd{background-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent)}.F1KBNa_splitRdel{background-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.F1KBNa_splitRadd{background-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent)}";
11692
11915
  const tagId = "dsh-diff-approval/PendingPanel.module.css";
11693
11916
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
11694
11917
  const tag = document.createElement("style");
@@ -11698,99 +11921,115 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11698
11921
  document.head.appendChild(tag);
11699
11922
  }
11700
11923
  var PendingPanel_module_css_default = {
11701
- "divergedHint": "F1KBNa_divergedHint",
11702
- "detail": "F1KBNa_detail",
11703
- "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
11704
- "diffBody": "F1KBNa_diffBody",
11705
- "langSelect": "F1KBNa_langSelect",
11706
- "note": "F1KBNa_note",
11707
- "code": "F1KBNa_code",
11708
- "diffHeader": "F1KBNa_diffHeader",
11709
- "overviewRuler": "F1KBNa_overviewRuler",
11710
- "readError": "F1KBNa_readError",
11711
- "gutter": "F1KBNa_gutter",
11712
- "markerAdd": "F1KBNa_markerAdd",
11713
- "del": "F1KBNa_del",
11714
- "fileList": "F1KBNa_fileList",
11715
- "delCount": "F1KBNa_delCount",
11716
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
11924
+ "kindHint": "F1KBNa_kindHint",
11925
+ "searchBar": "F1KBNa_searchBar",
11926
+ "diffPath": "F1KBNa_diffPath",
11927
+ "flexSpacer": "F1KBNa_flexSpacer",
11928
+ "splitRdel": "F1KBNa_splitRdel",
11929
+ "settingsRowTitle": "F1KBNa_settingsRowTitle",
11930
+ "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
11931
+ "group": "F1KBNa_group",
11932
+ "iconAction": "F1KBNa_iconAction",
11717
11933
  "blockFlash": "F1KBNa_blockFlash",
11718
- "context": "F1KBNa_context",
11719
- "fileListFloat": "F1KBNa_fileListFloat",
11720
- "diff": "F1KBNa_diff",
11721
- "rowMeta": "F1KBNa_rowMeta",
11722
- "panel": "F1KBNa_panel",
11723
- "title": "F1KBNa_title",
11724
- "expand": "F1KBNa_expand",
11725
- "lines": "F1KBNa_lines",
11726
- "missingHint": "F1KBNa_missingHint",
11727
- "headerActions": "F1KBNa_headerActions",
11728
- "settingsRowDesc": "F1KBNa_settingsRowDesc",
11934
+ "overviewRuler": "F1KBNa_overviewRuler",
11935
+ "row": "F1KBNa_row",
11729
11936
  "close": "F1KBNa_close",
11730
- "iconAction": "F1KBNa_iconAction",
11731
- "rail": "F1KBNa_rail",
11732
- "wrap": "F1KBNa_wrap",
11733
- "diffPath": "F1KBNa_diffPath",
11734
- "importNote": "F1KBNa_importNote",
11735
- "missing": "F1KBNa_missing",
11736
- "footerButtons": "F1KBNa_footerButtons",
11737
- "resizeHandle": "F1KBNa_resizeHandle",
11738
- "line": "F1KBNa_line",
11937
+ "settingsRowDesc": "F1KBNa_settingsRowDesc",
11938
+ "states": "F1KBNa_states",
11939
+ "code": "F1KBNa_code",
11739
11940
  "kindTag": "F1KBNa_kindTag",
11740
- "bulkActions": "F1KBNa_bulkActions",
11741
- "vSpacer": "F1KBNa_vSpacer",
11941
+ "badgeCount": "F1KBNa_badgeCount",
11942
+ "badge": "F1KBNa_badge",
11742
11943
  "emptyState": "F1KBNa_emptyState",
11743
- "badgeLabel": "F1KBNa_badgeLabel",
11744
- "rowFailed": "F1KBNa_rowFailed",
11944
+ "rowPath": "F1KBNa_rowPath",
11945
+ "notice": "F1KBNa_notice",
11946
+ "diffBodyWrap": "F1KBNa_diffBodyWrap",
11947
+ "missingHint": "F1KBNa_missingHint",
11948
+ "resizeHandle": "F1KBNa_resizeHandle",
11949
+ "splitRoot": "F1KBNa_splitRoot",
11950
+ "expandExpanded": "F1KBNa_expandExpanded",
11951
+ "splitLadd": "F1KBNa_splitLadd",
11952
+ "settingsPage": "F1KBNa_settingsPage",
11953
+ "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
11745
11954
  "layer": "F1KBNa_layer",
11746
- "badgeCount": "F1KBNa_badgeCount",
11747
- "noteCentered": "F1KBNa_noteCentered",
11748
- "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
11955
+ "settingsSelector": "F1KBNa_settingsSelector",
11956
+ "readError": "F1KBNa_readError",
11749
11957
  "importButton": "F1KBNa_importButton",
11958
+ "addCount": "F1KBNa_addCount",
11959
+ "context": "F1KBNa_context",
11750
11960
  "header": "F1KBNa_header",
11751
- "action": "F1KBNa_action",
11752
- "actionError": "F1KBNa_actionError",
11753
- "flexSpacer": "F1KBNa_flexSpacer",
11754
- "settingsRowText": "F1KBNa_settingsRowText",
11755
- "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
11961
+ "gutter": "F1KBNa_gutter",
11962
+ "confirmActions": "F1KBNa_confirmActions",
11963
+ "searchInput": "F1KBNa_searchInput",
11964
+ "importNote": "F1KBNa_importNote",
11965
+ "noticeText": "F1KBNa_noticeText",
11966
+ "wrap": "F1KBNa_wrap",
11967
+ "splitHScrollRow": "F1KBNa_splitHScrollRow",
11756
11968
  "blockActions": "F1KBNa_blockActions",
11757
- "listScroll": "F1KBNa_listScroll",
11758
- "hint": "F1KBNa_hint",
11759
- "kindHint": "F1KBNa_kindHint",
11969
+ "splitHScroll": "F1KBNa_splitHScroll",
11970
+ "splitHScrollFill": "F1KBNa_splitHScrollFill",
11971
+ "fileListFloat": "F1KBNa_fileListFloat",
11972
+ "noteCentered": "F1KBNa_noteCentered",
11760
11973
  "diffActions": "F1KBNa_diffActions",
11761
- "expandExpanded": "F1KBNa_expandExpanded",
11762
- "settingsRow": "F1KBNa_settingsRow",
11763
- "blockPosition": "F1KBNa_blockPosition",
11764
- "statusAction": "F1KBNa_statusAction",
11765
- "searchBar": "F1KBNa_searchBar",
11766
- "searchInput": "F1KBNa_searchInput",
11767
- "rows": "F1KBNa_rows",
11768
- "statusBar": "F1KBNa_statusBar",
11769
- "settingsPage": "F1KBNa_settingsPage",
11770
- "overviewMarker": "F1KBNa_overviewMarker",
11771
- "add": "F1KBNa_add",
11772
- "langLabel": "F1KBNa_langLabel",
11773
- "notice": "F1KBNa_notice",
11774
- "states": "F1KBNa_states",
11775
- "rowPath": "F1KBNa_rowPath",
11776
11974
  "searchCount": "F1KBNa_searchCount",
11975
+ "add": "F1KBNa_add",
11777
11976
  "diffStats": "F1KBNa_diffStats",
11778
- "badge": "F1KBNa_badge",
11977
+ "langLabel": "F1KBNa_langLabel",
11978
+ "detailEmpty": "F1KBNa_detailEmpty",
11979
+ "actionPrimary": "F1KBNa_actionPrimary",
11980
+ "diffBody": "F1KBNa_diffBody",
11981
+ "rowMeta": "F1KBNa_rowMeta",
11982
+ "diffHeader": "F1KBNa_diffHeader",
11983
+ "blockPosition": "F1KBNa_blockPosition",
11984
+ "subline": "F1KBNa_subline",
11985
+ "splitCol": "F1KBNa_splitCol",
11986
+ "missing": "F1KBNa_missing",
11987
+ "splitDivider": "F1KBNa_splitDivider",
11988
+ "rowFailed": "F1KBNa_rowFailed",
11989
+ "listScroll": "F1KBNa_listScroll",
11990
+ "actionError": "F1KBNa_actionError",
11991
+ "headerActions": "F1KBNa_headerActions",
11992
+ "confirmCard": "F1KBNa_confirmCard",
11993
+ "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
11779
11994
  "markerDel": "F1KBNa_markerDel",
11780
- "addCount": "F1KBNa_addCount",
11781
- "settingsSelector": "F1KBNa_settingsSelector",
11995
+ "bulkActions": "F1KBNa_bulkActions",
11996
+ "divergedHint": "F1KBNa_divergedHint",
11997
+ "lines": "F1KBNa_lines",
11998
+ "badgeLabel": "F1KBNa_badgeLabel",
11999
+ "detail": "F1KBNa_detail",
12000
+ "splitRadd": "F1KBNa_splitRadd",
12001
+ "footerButtons": "F1KBNa_footerButtons",
12002
+ "expand": "F1KBNa_expand",
12003
+ "overviewMarker": "F1KBNa_overviewMarker",
12004
+ "markerAdd": "F1KBNa_markerAdd",
12005
+ "hint": "F1KBNa_hint",
12006
+ "line": "F1KBNa_line",
12007
+ "wrapActive": "F1KBNa_wrapActive",
12008
+ "diffBodySplit": "F1KBNa_diffBodySplit",
12009
+ "delCount": "F1KBNa_delCount",
12010
+ "confirmText": "F1KBNa_confirmText",
12011
+ "note": "F1KBNa_note",
12012
+ "rows": "F1KBNa_rows",
12013
+ "settingsRow": "F1KBNa_settingsRow",
11782
12014
  "diffFlash": "F1KBNa_diffFlash",
11783
- "subline": "F1KBNa_subline",
12015
+ "del": "F1KBNa_del",
12016
+ "title": "F1KBNa_title",
12017
+ "statusAction": "F1KBNa_statusAction",
12018
+ "panel": "F1KBNa_panel",
12019
+ "fileList": "F1KBNa_fileList",
12020
+ "splitCols": "F1KBNa_splitCols",
12021
+ "settingsRowText": "F1KBNa_settingsRowText",
12022
+ "diff": "F1KBNa_diff",
12023
+ "confirmBackdrop": "F1KBNa_confirmBackdrop",
12024
+ "statusBar": "F1KBNa_statusBar",
12025
+ "splitLdel": "F1KBNa_splitLdel",
11784
12026
  "split": "F1KBNa_split",
11785
- "detailEmpty": "F1KBNa_detailEmpty",
11786
- "noticeButton": "F1KBNa_noticeButton",
11787
- "row": "F1KBNa_row",
11788
- "wrapActive": "F1KBNa_wrapActive",
11789
- "settingsRowTitle": "F1KBNa_settingsRowTitle",
11790
- "group": "F1KBNa_group",
12027
+ "langSelect": "F1KBNa_langSelect",
12028
+ "rail": "F1KBNa_rail",
11791
12029
  "rowHead": "F1KBNa_rowHead",
11792
- "actionPrimary": "F1KBNa_actionPrimary",
11793
- "noticeText": "F1KBNa_noticeText"
12030
+ "action": "F1KBNa_action",
12031
+ "vSpacer": "F1KBNa_vSpacer",
12032
+ "noticeButton": "F1KBNa_noticeButton"
11794
12033
  };
11795
12034
  //#endregion
11796
12035
  //#region lib/types/client/PendingPanel.js
@@ -11829,6 +12068,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11829
12068
  /** Total width of the two line-number gutters, subtracted from the code width
11830
12069
  * when measuring wrapped line heights. */
11831
12070
  const WRAP_GUTTERS_PX = 88;
12071
+ /** The dsh shell's sidebar auto-collapse breakpoint (ui-layout columns.ts):
12072
+ * below it the sidebar auto-collapses, and the file list floats on the same
12073
+ * breakpoint so the two stay consistent. */
12074
+ const SIDEBAR_AUTO_COLLAPSE_PX = 1024;
11832
12075
  /** A shared canvas for measuring wrapped line heights (CPU-only, no DOM reflow). */
11833
12076
  let measureCanvas;
11834
12077
  /**
@@ -12155,66 +12398,633 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12155
12398
  });
12156
12399
  return blocks;
12157
12400
  }
12158
- /** The diff-row index containing a node, or undefined. */
12159
- function rowIndexAt(node) {
12160
- if (node === null) return void 0;
12161
- const row = (node instanceof Element ? node : node.parentElement)?.closest("[data-diff-row]");
12162
- if (row === null || row === void 0) return void 0;
12163
- const index = Number(row.dataset.diffRow);
12164
- return Number.isFinite(index) ? index : void 0;
12165
- }
12166
- /** Character offset of a selection boundary within its line's code text. */
12167
- function lineOffsetAt(node, offset) {
12168
- const code = (node instanceof Element ? node : node.parentElement)?.closest("[data-diff-code]");
12169
- if (code === null || code === void 0) return 0;
12170
- let before = 0;
12171
- const walker = document.createTreeWalker(code, NodeFilter.SHOW_TEXT);
12172
- let current = walker.nextNode();
12173
- while (current !== null) {
12174
- if (current === node) return before + offset;
12175
- if (node instanceof Element && node.contains(current)) break;
12176
- before += current.length;
12177
- current = walker.nextNode();
12178
- }
12179
- if (node instanceof Element) {
12180
- const children = [...node.childNodes];
12181
- for (let i = 0; i < Math.min(offset, children.length); i++) {
12182
- const inner = document.createTreeWalker(children[i], NodeFilter.SHOW_TEXT);
12183
- let text = inner.nextNode();
12184
- while (text !== null) {
12185
- before += text.length;
12186
- text = inner.nextNode();
12187
- }
12188
- }
12189
- }
12190
- return before;
12191
- }
12192
- /** Length of the code text on the line holding a node. */
12193
- function lineLengthAt(node) {
12194
- return ((node instanceof Element ? node : node.parentElement)?.closest("[data-diff-code]"))?.textContent?.length ?? 0;
12401
+ /** One side's line-content for the split view: the highlighted runs or plain text. */
12402
+ function splitSideContent(side, wrapped, runs) {
12403
+ if (side === void 0) return "";
12404
+ const highlighted = runs !== void 0 && runs.length > 0;
12405
+ if (wrapped === void 0) return highlighted ? runs.map((span, i) => (0, react_jsx_runtime.jsx)("span", {
12406
+ style: span.style,
12407
+ children: span.text
12408
+ }, i)) : side.text === "" ? "\xA0" : side.text;
12409
+ let offset = 0;
12410
+ return wrapped.map((line, i) => {
12411
+ const start = offset;
12412
+ offset += line.length;
12413
+ const content = highlighted ? clipRuns(runs, start, offset) : line === "" ? "\xA0" : line;
12414
+ return (0, react_jsx_runtime.jsx)("div", {
12415
+ className: PendingPanel_module_css_default.subline,
12416
+ children: content
12417
+ }, i);
12418
+ });
12195
12419
  }
12196
12420
  /**
12197
- * Derive the selected diff-row range from a native text selection. A
12198
- * boundary sitting exactly at a line edge contributes no content: a start at
12199
- * the line's end skips to the next line, an end at the line's start falls
12200
- * back to the previous line.
12421
+ * One side of a split pair row, rendered inside its own column. The two columns
12422
+ * are drawn by two independent `.splitCol` scrollers (each with its own
12423
+ * horizontal scrollbar) that share one vertical scroller, and each row gets the
12424
+ * same fixed `height` (the pair's max of the two sides' wrapped sub-line
12425
+ * counts) so the left/right halves always align on the same Y — no jump when
12426
+ * one side is longer. The gutter and code are top-aligned so sub-lines line up
12427
+ * across the divider.
12201
12428
  */
12202
- function rowRangeOf(selection) {
12203
- if (selection === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
12204
- const range = selection.getRangeAt(0);
12205
- let start = rowIndexAt(range.startContainer);
12206
- let end = rowIndexAt(range.endContainer);
12207
- if (start === void 0 || end === void 0) return void 0;
12208
- if (lineOffsetAt(range.startContainer, range.startOffset) >= lineLengthAt(range.startContainer)) start += 1;
12209
- if (lineOffsetAt(range.endContainer, range.endOffset) === 0) end -= 1;
12210
- if (start > end) return void 0;
12211
- return {
12212
- start,
12213
- end
12214
- };
12215
- }
12216
- /** One row of the file list: the clickable head in the left pane. */
12217
- function PendingFileRow({ file, selected, failedMessage, t, onSelect }) {
12429
+ function SplitSideRow({ index, side, wrapped, runs, kind, isLeft, height, focused, searchHit, searchCurrent, onHover }) {
12430
+ const tint = isLeft ? kind === "del" || kind === "replace" ? PendingPanel_module_css_default.splitLdel : "" : kind === "add" || kind === "replace" ? PendingPanel_module_css_default.splitRadd : "";
12431
+ return (0, react_jsx_runtime.jsxs)("div", {
12432
+ className: PendingPanel_module_css_default.line,
12433
+ style: { height },
12434
+ "data-diff-split-row": true,
12435
+ "data-diff-split-index": index,
12436
+ "data-diff-split-side": isLeft ? "left" : "right",
12437
+ "data-diff-focused": focused ? "" : void 0,
12438
+ "data-diff-search": searchHit ? searchCurrent ? "current" : "hit" : void 0,
12439
+ onMouseEnter: onHover,
12440
+ children: [(0, react_jsx_runtime.jsx)("span", {
12441
+ className: PendingPanel_module_css_default.gutter,
12442
+ children: side?.line ?? ""
12443
+ }), (0, react_jsx_runtime.jsx)("span", {
12444
+ className: `${PendingPanel_module_css_default.code} ${tint}`,
12445
+ "data-diff-code": true,
12446
+ children: splitSideContent(side, wrapped, runs)
12447
+ })]
12448
+ });
12449
+ }
12450
+ /** The two-column (side-by-side) whole-file diff view. */
12451
+ const SplitDiff = (0, react.forwardRef)(function SplitDiff({ file, model, runs, langWrap, tabWidthSpaces, busy, t, onBlockKeep, onBlockRevert }, ref) {
12452
+ const { pairs, pairOfRow } = (0, react.useMemo)(() => computeSideBySideDiff(model.diff.rows), [model]);
12453
+ const pairCount = pairs.length;
12454
+ const bodyRef = (0, react.useRef)(null);
12455
+ const [scrollTop, setScrollTop] = (0, react.useState)(0);
12456
+ const [viewportH, setViewportH] = (0, react.useState)(0);
12457
+ const [bodyWidth, setBodyWidth] = (0, react.useState)(0);
12458
+ const [hoveredBlock, setHoveredBlock] = (0, react.useState)(void 0);
12459
+ const [focus, setFocus] = (0, react.useState)(0);
12460
+ const [flashKey, setFlashKey] = (0, react.useState)(0);
12461
+ const hoveredBlockRef = (0, react.useRef)(void 0);
12462
+ const leftColRef = (0, react.useRef)(null);
12463
+ const rightColRef = (0, react.useRef)(null);
12464
+ const leftHScrollRef = (0, react.useRef)(null);
12465
+ const rightHScrollRef = (0, react.useRef)(null);
12466
+ const [fillWidth, setFillWidth] = (0, react.useState)({
12467
+ left: 0,
12468
+ right: 0
12469
+ });
12470
+ const [searchOpen, setSearchOpen] = (0, react.useState)(false);
12471
+ const [searchQuery, setSearchQuery] = (0, react.useState)("");
12472
+ const [searchIndex, setSearchIndex] = (0, react.useState)(0);
12473
+ const searchInputRef = (0, react.useRef)(null);
12474
+ (0, react.useEffect)(() => {
12475
+ setFocus(0);
12476
+ bodyRef.current?.focus();
12477
+ setFlashKey((k) => k + 1);
12478
+ setHoveredBlock(void 0);
12479
+ setSearchOpen(false);
12480
+ setSearchQuery("");
12481
+ setSearchIndex(0);
12482
+ }, [file.id]);
12483
+ (0, react.useEffect)(() => {
12484
+ const body = bodyRef.current;
12485
+ if (body === null) return;
12486
+ const measure = () => {
12487
+ setViewportH(body.clientHeight);
12488
+ setBodyWidth(body.clientWidth);
12489
+ };
12490
+ measure();
12491
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
12492
+ observer?.observe(body);
12493
+ return () => {
12494
+ observer?.disconnect();
12495
+ };
12496
+ }, [file.id]);
12497
+ const blockOfPair = (0, react.useMemo)(() => model.blocks.map((block) => ({
12498
+ start: pairOfRow.get(block.start) ?? 0,
12499
+ end: pairOfRow.get(block.end) ?? 0
12500
+ })), [model, pairOfRow]);
12501
+ const blockIndexByPair = (0, react.useMemo)(() => {
12502
+ const map = /* @__PURE__ */ new Map();
12503
+ blockOfPair.forEach((block, bi) => {
12504
+ for (let k = block.start; k <= block.end; k++) map.set(k, bi);
12505
+ });
12506
+ return map;
12507
+ }, [blockOfPair]);
12508
+ const onPairHover = (0, react.useCallback)((k) => {
12509
+ const bi = blockIndexByPair.get(k);
12510
+ setHoveredBlock(bi);
12511
+ if (bi !== void 0) hoveredBlockRef.current = bi;
12512
+ }, [blockIndexByPair]);
12513
+ const colWidth = Math.max(0, (bodyWidth - 1) / 2);
12514
+ const pairWrapped = (0, react.useMemo)(() => {
12515
+ if (!langWrap || bodyWidth === 0) return null;
12516
+ const measure = makeMeasurer(codeFontOf());
12517
+ if (measure === void 0) return null;
12518
+ const charWidth = measure("0");
12519
+ const wrapW = colWidth - WRAP_GUTTERS_PX / 2 - charWidth;
12520
+ const tabPx = tabWidthSpaces * measure(" ");
12521
+ return pairs.map((p) => ({
12522
+ left: p.left === void 0 ? void 0 : wrapInto(p.left.text, wrapW, measure, tabPx),
12523
+ right: p.right === void 0 ? void 0 : wrapInto(p.right.text, wrapW, measure, tabPx)
12524
+ }));
12525
+ }, [
12526
+ pairs,
12527
+ langWrap,
12528
+ bodyWidth,
12529
+ colWidth,
12530
+ tabWidthSpaces
12531
+ ]);
12532
+ const pairHeights = (0, react.useMemo)(() => {
12533
+ if (pairWrapped === null) return null;
12534
+ return pairWrapped.map((w) => Math.max(w.left?.length ?? 1, w.right?.length ?? 1) * ROW_HEIGHT_PX);
12535
+ }, [pairWrapped]);
12536
+ const pairOffsets = (0, react.useMemo)(() => {
12537
+ if (pairHeights === null) return null;
12538
+ const offs = new Array(pairHeights.length + 1);
12539
+ offs[0] = 0;
12540
+ for (let i = 0; i < pairHeights.length; i++) offs[i + 1] = offs[i] + pairHeights[i];
12541
+ return offs;
12542
+ }, [pairHeights]);
12543
+ const totalHeight = pairOffsets === null ? pairCount * ROW_HEIGHT_PX : pairOffsets[pairCount] ?? 0;
12544
+ const off = (k) => pairOffsets === null ? k * ROW_HEIGHT_PX : pairOffsets[Math.max(0, Math.min(k, pairCount))] ?? 0;
12545
+ const pairHeightAt = (k) => pairHeights === null ? ROW_HEIGHT_PX : pairHeights[k] ?? ROW_HEIGHT_PX;
12546
+ const widestSide = (0, react.useMemo)(() => {
12547
+ let left = 0;
12548
+ let right = 0;
12549
+ for (const p of pairs) {
12550
+ if (p.left !== void 0) left = Math.max(left, p.left.text.length);
12551
+ if (p.right !== void 0) right = Math.max(right, p.right.text.length);
12552
+ }
12553
+ return {
12554
+ left,
12555
+ right
12556
+ };
12557
+ }, [pairs]);
12558
+ (0, react.useLayoutEffect)(() => {
12559
+ const sync = (side) => {
12560
+ const col = side === "left" ? leftColRef.current : rightColRef.current;
12561
+ const strip = side === "left" ? leftHScrollRef.current : rightHScrollRef.current;
12562
+ if (col === null || strip === null) return;
12563
+ const width = col.scrollWidth;
12564
+ setFillWidth((prev) => prev[side] === width ? prev : {
12565
+ ...prev,
12566
+ [side]: width
12567
+ });
12568
+ strip.scrollLeft = col.scrollLeft;
12569
+ };
12570
+ sync("left");
12571
+ sync("right");
12572
+ }, [
12573
+ pairs,
12574
+ langWrap,
12575
+ tabWidthSpaces,
12576
+ bodyWidth
12577
+ ]);
12578
+ const onHScroll = (0, react.useCallback)((side) => {
12579
+ const strip = side === "left" ? leftHScrollRef.current : rightHScrollRef.current;
12580
+ const col = side === "left" ? leftColRef.current : rightColRef.current;
12581
+ if (strip === null || col === null) return;
12582
+ col.scrollLeft = strip.scrollLeft;
12583
+ }, []);
12584
+ const searchMatches = (0, react.useMemo)(() => searchPairs(pairs, searchQuery), [pairs, searchQuery]);
12585
+ const searchHitSet = (0, react.useMemo)(() => new Set(searchMatches), [searchMatches]);
12586
+ const currentSearchPair = searchMatches.length === 0 ? void 0 : searchMatches[searchIndex % searchMatches.length];
12587
+ const closeSearch = () => {
12588
+ setSearchOpen(false);
12589
+ setSearchQuery("");
12590
+ setSearchIndex(0);
12591
+ bodyRef.current?.focus();
12592
+ };
12593
+ const openSearch = () => {
12594
+ setSearchOpen(true);
12595
+ requestAnimationFrame(() => {
12596
+ searchInputRef.current?.focus();
12597
+ searchInputRef.current?.select();
12598
+ });
12599
+ };
12600
+ const goSearch = (direction) => {
12601
+ const len = searchMatches.length;
12602
+ if (len === 0) return;
12603
+ const next = (searchIndex + direction + len) % len;
12604
+ setSearchIndex(next);
12605
+ const pairIndex = searchMatches[next];
12606
+ if (pairIndex === void 0) return;
12607
+ const body = bodyRef.current;
12608
+ if (body === null) return;
12609
+ const target = Math.max(0, off(pairIndex) - 44);
12610
+ const clamped = Math.max(0, Math.min(target, body.scrollHeight - body.clientHeight));
12611
+ if (body.scrollTop !== clamped) body.scrollTop = clamped;
12612
+ setScrollTop(clamped);
12613
+ };
12614
+ const handleBlockAction = async (action) => {
12615
+ const operated = hoveredBlock ?? hoveredBlockRef.current;
12616
+ if (operated === void 0) return;
12617
+ const range = blockRanges[operated];
12618
+ if (range === void 0) return;
12619
+ await (action === "keep" ? onBlockKeep(file.sessionId, file.id, range) : onBlockRevert(file.sessionId, file.id, range));
12620
+ const count = model.blocks.length;
12621
+ if (count === 0) return;
12622
+ const next = Math.max(0, Math.min(operated, count - 1));
12623
+ setFocus(next);
12624
+ setHoveredBlock(void 0);
12625
+ setFlashKey((key) => key + 1);
12626
+ };
12627
+ const pairAtY = (y) => {
12628
+ if (pairOffsets === null) return Math.floor(y / ROW_HEIGHT_PX);
12629
+ if (y <= 0) return 0;
12630
+ let lo = 0, hi = pairCount;
12631
+ while (lo < hi) {
12632
+ const mid = lo + hi + 1 >> 1;
12633
+ if ((pairOffsets[mid] ?? 0) <= y) lo = mid;
12634
+ else hi = mid - 1;
12635
+ }
12636
+ return lo;
12637
+ };
12638
+ const viewport = viewportH > 0 ? viewportH : totalHeight;
12639
+ const start = Math.max(0, pairAtY(scrollTop) - OVERSCAN_ROWS);
12640
+ const end = Math.min(pairCount, pairAtY(scrollTop + viewport) + OVERSCAN_ROWS);
12641
+ const visiblePairs = pairs.slice(start, end);
12642
+ const jump = (direction) => {
12643
+ if (blockOfPair.length === 0) return;
12644
+ setFocus((current) => {
12645
+ if (direction === -1) return (current - 1 + blockOfPair.length) % blockOfPair.length;
12646
+ const top = bodyRef.current?.scrollTop ?? 0;
12647
+ for (let index = current + 1; index < blockOfPair.length; index++) if (off(blockOfPair[index].start) >= top) return index;
12648
+ return 0;
12649
+ });
12650
+ setFlashKey((k) => k + 1);
12651
+ };
12652
+ (0, react.useImperativeHandle)(ref, () => ({
12653
+ jump,
12654
+ openSearch
12655
+ }), [jump, openSearch]);
12656
+ (0, react.useLayoutEffect)(() => {
12657
+ if (pairCount === 0) return;
12658
+ const block = blockOfPair[focus];
12659
+ if (block === void 0) return;
12660
+ const body = bodyRef.current;
12661
+ if (body === null) return;
12662
+ const target = off(block.start) - 44;
12663
+ const clamped = Math.max(0, Math.min(target, body.scrollHeight - body.clientHeight));
12664
+ if (body.scrollTop !== clamped) body.scrollTop = clamped;
12665
+ setScrollTop(clamped);
12666
+ }, [focus, flashKey]);
12667
+ const onScroll = () => {
12668
+ setScrollTop(bodyRef.current?.scrollTop ?? 0);
12669
+ };
12670
+ const inFocused = (k) => {
12671
+ const block = blockOfPair[focus];
12672
+ return block !== void 0 && k >= block.start && k <= block.end;
12673
+ };
12674
+ const blockRanges = (0, react.useMemo)(() => model.blocks.map((block) => blockRangesOf(model.diff.rows, block)), [model]);
12675
+ const focusedBlock = blockOfPair[focus];
12676
+ const flashTop = focusedBlock === void 0 ? 0 : Math.max(0, off(focusedBlock.start) - scrollTop);
12677
+ const flashBottom = focusedBlock === void 0 ? 0 : Math.min(viewportH > 0 ? viewportH : Number.POSITIVE_INFINITY, off(focusedBlock.end + 1) - scrollTop);
12678
+ const flashHeight = Math.max(0, flashBottom - flashTop);
12679
+ const blockActionsTop = hoveredBlock === void 0 || blockOfPair[hoveredBlock] === void 0 ? 0 : Math.max(0, Math.min(off(blockOfPair[hoveredBlock].end + 1) - scrollTop, Math.max(0, viewportH - BLOCK_ACTIONS_FRAME_PX)));
12680
+ return (0, react_jsx_runtime.jsxs)("div", {
12681
+ className: PendingPanel_module_css_default.splitRoot,
12682
+ onMouseLeave: () => setHoveredBlock(void 0),
12683
+ children: [
12684
+ (0, react_jsx_runtime.jsx)("div", {
12685
+ className: `${PendingPanel_module_css_default.diffBody} ${PendingPanel_module_css_default.diffBodySplit}`,
12686
+ ref: bodyRef,
12687
+ tabIndex: 0,
12688
+ onScroll,
12689
+ style: { tabSize: tabWidthSpaces },
12690
+ "data-diff-body": true,
12691
+ children: (0, react_jsx_runtime.jsxs)("div", {
12692
+ className: PendingPanel_module_css_default.splitCols,
12693
+ children: [
12694
+ (0, react_jsx_runtime.jsx)("div", {
12695
+ className: PendingPanel_module_css_default.splitCol,
12696
+ ref: leftColRef,
12697
+ "data-diff-split-side": "left",
12698
+ children: (0, react_jsx_runtime.jsxs)("div", {
12699
+ className: `${PendingPanel_module_css_default.lines}${langWrap ? " " + PendingPanel_module_css_default.wrap : ""}`,
12700
+ style: langWrap ? void 0 : { minWidth: `max(100%, ${widestSide.left}ch)` },
12701
+ children: [
12702
+ start > 0 && (0, react_jsx_runtime.jsx)("div", {
12703
+ className: PendingPanel_module_css_default.vSpacer,
12704
+ style: { height: off(start) },
12705
+ "aria-hidden": "true"
12706
+ }),
12707
+ visiblePairs.map((pair, offset) => {
12708
+ const index = start + offset;
12709
+ const leftRuns = pair.left === void 0 ? void 0 : runs?.oldRuns?.[(pair.left.line ?? 0) - 1];
12710
+ return (0, react_jsx_runtime.jsx)(SplitSideRow, {
12711
+ index,
12712
+ side: pair.left,
12713
+ wrapped: pairWrapped?.[index]?.left,
12714
+ runs: leftRuns,
12715
+ kind: pair.kind,
12716
+ isLeft: true,
12717
+ height: pairHeightAt(index),
12718
+ focused: inFocused(index),
12719
+ searchHit: searchHitSet.has(index),
12720
+ searchCurrent: index === currentSearchPair,
12721
+ onHover: () => onPairHover(index)
12722
+ }, index);
12723
+ }),
12724
+ end < pairCount && (0, react_jsx_runtime.jsx)("div", {
12725
+ className: PendingPanel_module_css_default.vSpacer,
12726
+ style: { height: totalHeight - off(end) },
12727
+ "aria-hidden": "true"
12728
+ })
12729
+ ]
12730
+ })
12731
+ }),
12732
+ (0, react_jsx_runtime.jsx)("div", { className: PendingPanel_module_css_default.splitDivider }),
12733
+ (0, react_jsx_runtime.jsx)("div", {
12734
+ className: PendingPanel_module_css_default.splitCol,
12735
+ ref: rightColRef,
12736
+ "data-diff-split-side": "right",
12737
+ children: (0, react_jsx_runtime.jsxs)("div", {
12738
+ className: `${PendingPanel_module_css_default.lines}${langWrap ? " " + PendingPanel_module_css_default.wrap : ""}`,
12739
+ style: langWrap ? void 0 : { minWidth: `max(100%, ${widestSide.right}ch)` },
12740
+ children: [
12741
+ start > 0 && (0, react_jsx_runtime.jsx)("div", {
12742
+ className: PendingPanel_module_css_default.vSpacer,
12743
+ style: { height: off(start) },
12744
+ "aria-hidden": "true"
12745
+ }),
12746
+ visiblePairs.map((pair, offset) => {
12747
+ const index = start + offset;
12748
+ const rightRuns = pair.right === void 0 ? void 0 : runs?.newRuns?.[(pair.right.line ?? 0) - 1];
12749
+ return (0, react_jsx_runtime.jsx)(SplitSideRow, {
12750
+ index,
12751
+ side: pair.right,
12752
+ wrapped: pairWrapped?.[index]?.right,
12753
+ runs: rightRuns,
12754
+ kind: pair.kind,
12755
+ isLeft: false,
12756
+ height: pairHeightAt(index),
12757
+ focused: inFocused(index),
12758
+ searchHit: searchHitSet.has(index),
12759
+ searchCurrent: index === currentSearchPair,
12760
+ onHover: () => onPairHover(index)
12761
+ }, index);
12762
+ }),
12763
+ end < pairCount && (0, react_jsx_runtime.jsx)("div", {
12764
+ className: PendingPanel_module_css_default.vSpacer,
12765
+ style: { height: totalHeight - off(end) },
12766
+ "aria-hidden": "true"
12767
+ })
12768
+ ]
12769
+ })
12770
+ })
12771
+ ]
12772
+ })
12773
+ }),
12774
+ (0, react_jsx_runtime.jsxs)("div", {
12775
+ className: PendingPanel_module_css_default.splitHScrollRow,
12776
+ "data-diff-hscroll-row": true,
12777
+ children: [
12778
+ (0, react_jsx_runtime.jsx)("div", {
12779
+ className: PendingPanel_module_css_default.splitHScroll,
12780
+ ref: leftHScrollRef,
12781
+ "data-diff-hscroll": "left",
12782
+ style: {
12783
+ width: colWidth,
12784
+ flex: "none"
12785
+ },
12786
+ onScroll: () => onHScroll("left"),
12787
+ children: (0, react_jsx_runtime.jsx)("div", {
12788
+ className: PendingPanel_module_css_default.splitHScrollFill,
12789
+ style: { width: fillWidth.left || void 0 }
12790
+ })
12791
+ }),
12792
+ (0, react_jsx_runtime.jsx)("div", { className: PendingPanel_module_css_default.splitDivider }),
12793
+ (0, react_jsx_runtime.jsx)("div", {
12794
+ className: PendingPanel_module_css_default.splitHScroll,
12795
+ ref: rightHScrollRef,
12796
+ "data-diff-hscroll": "right",
12797
+ style: {
12798
+ width: colWidth,
12799
+ flex: "none"
12800
+ },
12801
+ onScroll: () => onHScroll("right"),
12802
+ children: (0, react_jsx_runtime.jsx)("div", {
12803
+ className: PendingPanel_module_css_default.splitHScrollFill,
12804
+ style: { width: fillWidth.right || void 0 }
12805
+ })
12806
+ })
12807
+ ]
12808
+ }),
12809
+ searchOpen && (0, react_jsx_runtime.jsxs)("div", {
12810
+ className: PendingPanel_module_css_default.searchBar,
12811
+ "data-diff-searchbar": true,
12812
+ children: [
12813
+ (0, react_jsx_runtime.jsx)("input", {
12814
+ ref: searchInputRef,
12815
+ className: PendingPanel_module_css_default.searchInput,
12816
+ "data-diff-search-input": true,
12817
+ value: searchQuery,
12818
+ placeholder: t("panel.searchPlaceholder"),
12819
+ onChange: (event) => {
12820
+ setSearchQuery(event.target.value);
12821
+ setSearchIndex(0);
12822
+ },
12823
+ onKeyDown: (event) => {
12824
+ if (event.key === "Enter") {
12825
+ event.preventDefault();
12826
+ goSearch(event.shiftKey ? -1 : 1);
12827
+ } else if (event.key === "Escape") closeSearch();
12828
+ }
12829
+ }),
12830
+ (0, react_jsx_runtime.jsx)("span", {
12831
+ className: PendingPanel_module_css_default.searchCount,
12832
+ "data-diff-search-count": true,
12833
+ children: searchMatches.length === 0 ? "0/0" : `${searchIndex % searchMatches.length + 1}/${searchMatches.length}`
12834
+ }),
12835
+ (0, react_jsx_runtime.jsx)("button", {
12836
+ type: "button",
12837
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12838
+ "data-diff-search-prev": true,
12839
+ "aria-label": t("action.prevDiff"),
12840
+ disabled: searchMatches.length === 0,
12841
+ onClick: () => {
12842
+ goSearch(-1);
12843
+ },
12844
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12845
+ }),
12846
+ (0, react_jsx_runtime.jsx)("button", {
12847
+ type: "button",
12848
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12849
+ "data-diff-search-next": true,
12850
+ "aria-label": t("action.nextDiff"),
12851
+ disabled: searchMatches.length === 0,
12852
+ onClick: () => {
12853
+ goSearch(1);
12854
+ },
12855
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12856
+ }),
12857
+ (0, react_jsx_runtime.jsx)("button", {
12858
+ type: "button",
12859
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12860
+ "data-diff-search-close": true,
12861
+ "aria-label": t("action.close"),
12862
+ onClick: closeSearch,
12863
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
12864
+ })
12865
+ ]
12866
+ }),
12867
+ focusedBlock !== void 0 && flashKey > 0 && (0, react_jsx_runtime.jsx)("div", {
12868
+ className: PendingPanel_module_css_default.blockFlash,
12869
+ "data-diff-block-flash": true,
12870
+ style: {
12871
+ top: flashTop,
12872
+ height: flashHeight
12873
+ }
12874
+ }, flashKey),
12875
+ hoveredBlock !== void 0 && blockOfPair[hoveredBlock] !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
12876
+ className: PendingPanel_module_css_default.blockActions,
12877
+ "data-diff-block-actions": true,
12878
+ style: { top: blockActionsTop },
12879
+ children: [
12880
+ (0, react_jsx_runtime.jsx)("span", {
12881
+ className: PendingPanel_module_css_default.blockPosition,
12882
+ "data-diff-block-position": true,
12883
+ children: t("panel.blockPosition", {
12884
+ current: hoveredBlock + 1,
12885
+ total: blockOfPair.length
12886
+ })
12887
+ }),
12888
+ (0, react_jsx_runtime.jsx)("button", {
12889
+ type: "button",
12890
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12891
+ "data-diff-block-prev": true,
12892
+ "aria-label": t("action.prevDiff"),
12893
+ disabled: busy,
12894
+ onClick: () => jump(-1),
12895
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12896
+ }),
12897
+ (0, react_jsx_runtime.jsx)("button", {
12898
+ type: "button",
12899
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12900
+ "data-diff-block-next": true,
12901
+ "aria-label": t("action.nextDiff"),
12902
+ disabled: busy,
12903
+ onClick: () => jump(1),
12904
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12905
+ }),
12906
+ (0, react_jsx_runtime.jsx)("button", {
12907
+ type: "button",
12908
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
12909
+ "data-diff-block-keep": true,
12910
+ disabled: busy,
12911
+ onClick: () => {
12912
+ handleBlockAction("keep");
12913
+ },
12914
+ children: t("action.keep")
12915
+ }),
12916
+ (0, react_jsx_runtime.jsx)("button", {
12917
+ type: "button",
12918
+ className: `${PendingPanel_module_css_default.action}`,
12919
+ "data-diff-block-revert": true,
12920
+ disabled: busy,
12921
+ onClick: () => {
12922
+ handleBlockAction("revert");
12923
+ },
12924
+ children: t("action.revert")
12925
+ })
12926
+ ]
12927
+ })
12928
+ ]
12929
+ });
12930
+ });
12931
+ /** The diff-row index containing a node, or undefined. */
12932
+ function rowIndexAt(node) {
12933
+ if (node === null) return void 0;
12934
+ const row = (node instanceof Element ? node : node.parentElement)?.closest("[data-diff-row]");
12935
+ if (row === null || row === void 0) return void 0;
12936
+ const index = Number(row.dataset.diffRow);
12937
+ return Number.isFinite(index) ? index : void 0;
12938
+ }
12939
+ /** The split pair index and which side (left=old, right=new) a node sits in, or undefined. */
12940
+ function splitRowInfoAt(node) {
12941
+ if (node === null) return void 0;
12942
+ const row = (node instanceof Element ? node : node.parentElement)?.closest("[data-diff-split-row]");
12943
+ if (row === null || row === void 0) return void 0;
12944
+ const side = row.dataset.diffSplitSide;
12945
+ if (side !== "left" && side !== "right") return void 0;
12946
+ const index = Number(row.dataset.diffSplitIndex);
12947
+ if (!Number.isFinite(index)) return void 0;
12948
+ return {
12949
+ pairIndex: index,
12950
+ side: side === "left" ? "old" : "new"
12951
+ };
12952
+ }
12953
+ /**
12954
+ * Derive the selected split pair range per side (a left-column selection
12955
+ * references the old file, a right-column selection the new file). A selection
12956
+ * spanning the divider (both sides) references two files, so it is rejected.
12957
+ */
12958
+ function splitRowRangeOf(selection) {
12959
+ if (selection === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
12960
+ const range = selection.getRangeAt(0);
12961
+ const startInfo = splitRowInfoAt(range.startContainer);
12962
+ const endInfo = splitRowInfoAt(range.endContainer);
12963
+ if (startInfo === void 0 || endInfo === void 0) return void 0;
12964
+ if (startInfo.side !== endInfo.side) return void 0;
12965
+ let start = startInfo.pairIndex;
12966
+ let end = endInfo.pairIndex;
12967
+ if (lineOffsetAt(range.startContainer, range.startOffset) >= lineLengthAt(range.startContainer)) start += 1;
12968
+ if (lineOffsetAt(range.endContainer, range.endOffset) === 0) end -= 1;
12969
+ if (start > end) return void 0;
12970
+ return {
12971
+ start,
12972
+ end,
12973
+ side: startInfo.side
12974
+ };
12975
+ }
12976
+ /** Character offset of a selection boundary within its line's code text. */
12977
+ function lineOffsetAt(node, offset) {
12978
+ const code = (node instanceof Element ? node : node.parentElement)?.closest("[data-diff-code]");
12979
+ if (code === null || code === void 0) return 0;
12980
+ let before = 0;
12981
+ const walker = document.createTreeWalker(code, NodeFilter.SHOW_TEXT);
12982
+ let current = walker.nextNode();
12983
+ while (current !== null) {
12984
+ if (current === node) return before + offset;
12985
+ if (node instanceof Element && node.contains(current)) break;
12986
+ before += current.length;
12987
+ current = walker.nextNode();
12988
+ }
12989
+ if (node instanceof Element) {
12990
+ const children = [...node.childNodes];
12991
+ for (let i = 0; i < Math.min(offset, children.length); i++) {
12992
+ const inner = document.createTreeWalker(children[i], NodeFilter.SHOW_TEXT);
12993
+ let text = inner.nextNode();
12994
+ while (text !== null) {
12995
+ before += text.length;
12996
+ text = inner.nextNode();
12997
+ }
12998
+ }
12999
+ }
13000
+ return before;
13001
+ }
13002
+ /** Length of the code text on the line holding a node. */
13003
+ function lineLengthAt(node) {
13004
+ return ((node instanceof Element ? node : node.parentElement)?.closest("[data-diff-code]"))?.textContent?.length ?? 0;
13005
+ }
13006
+ /**
13007
+ * Derive the selected diff-row range from a native text selection. A
13008
+ * boundary sitting exactly at a line edge contributes no content: a start at
13009
+ * the line's end skips to the next line, an end at the line's start falls
13010
+ * back to the previous line.
13011
+ */
13012
+ function rowRangeOf(selection) {
13013
+ if (selection === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
13014
+ const range = selection.getRangeAt(0);
13015
+ let start = rowIndexAt(range.startContainer);
13016
+ let end = rowIndexAt(range.endContainer);
13017
+ if (start === void 0 || end === void 0) return void 0;
13018
+ if (lineOffsetAt(range.startContainer, range.startOffset) >= lineLengthAt(range.startContainer)) start += 1;
13019
+ if (lineOffsetAt(range.endContainer, range.endOffset) === 0) end -= 1;
13020
+ if (start > end) return void 0;
13021
+ return {
13022
+ start,
13023
+ end
13024
+ };
13025
+ }
13026
+ /** One row of the file list: the clickable head in the left pane. */
13027
+ function PendingFileRow({ file, selected, failedMessage, t, onSelect }) {
12218
13028
  const stats = (0, react.useMemo)(() => computeWholeFileDiff(file.oldText, file.newText), [file.oldText, file.newText]);
12219
13029
  return (0, react_jsx_runtime.jsx)("li", {
12220
13030
  className: PendingPanel_module_css_default.row,
@@ -12248,7 +13058,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12248
13058
  title: failedMessage,
12249
13059
  children: t("row.failed")
12250
13060
  }),
12251
- (0, react_jsx_runtime.jsxs)("span", {
13061
+ (stats.added !== 0 || stats.removed !== 0) && (0, react_jsx_runtime.jsxs)("span", {
12252
13062
  className: PendingPanel_module_css_default.rowMeta,
12253
13063
  children: [(0, react_jsx_runtime.jsx)("span", {
12254
13064
  className: PendingPanel_module_css_default.addCount,
@@ -12264,7 +13074,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12264
13074
  });
12265
13075
  }
12266
13076
  /** The selected file's diff, actions, jump controls, and copy toolbar. */
12267
- function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, failedMessage, onPasteReference, t, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, floatMode, floatOpen, onToggleFileList }) {
13077
+ function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, failedMessage, onPasteReference, onToast, t, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, floatMode, floatOpen, onToggleFileList }) {
12268
13078
  const [langOverride, setLangOverride] = (0, react.useState)(void 0);
12269
13079
  const [langMenuOpen, setLangMenuOpen] = (0, react.useState)(false);
12270
13080
  const langMenuItems = (0, react.useMemo)(() => [{
@@ -12288,6 +13098,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12288
13098
  setWrapEnabled(wrapKey, next);
12289
13099
  };
12290
13100
  const [tabWidthSpaces] = (0, react.useState)(() => tabWidth());
13101
+ const splitView = splitMode();
13102
+ const splitDiffRef = (0, react.useRef)(null);
12291
13103
  const model = (0, react.useMemo)(() => {
12292
13104
  const diff = computeWholeFileDiff(file.oldText, file.newText);
12293
13105
  return {
@@ -12295,6 +13107,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12295
13107
  blocks: changeBlocksOf(diff)
12296
13108
  };
12297
13109
  }, [file.oldText, file.newText]);
13110
+ const splitPairs = (0, react.useMemo)(() => splitView ? computeSideBySideDiff(model.diff.rows).pairs : null, [splitView, model]);
12298
13111
  const rulerMarkers = (0, react.useMemo)(() => {
12299
13112
  const rows = model.diff.rows;
12300
13113
  const total = rows.length;
@@ -12365,6 +13178,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12365
13178
  const [flashKey, setFlashKey] = (0, react.useState)(0);
12366
13179
  (0, react.useEffect)(() => {
12367
13180
  setFocus(0);
13181
+ setScrollTick((tick) => tick + 1);
12368
13182
  bodyRef.current?.focus();
12369
13183
  setFlashKey((key) => key + 1);
12370
13184
  setHoveredBlock(void 0);
@@ -12385,6 +13199,32 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12385
13199
  const blockRanges = (0, react.useMemo)(() => {
12386
13200
  return model.blocks.map((block) => blockRangesOf(model.diff.rows, block));
12387
13201
  }, [model]);
13202
+ const coveredBlockIndices = (0, react.useMemo)(() => {
13203
+ if (selection === void 0 || splitView) return [];
13204
+ const covered = [];
13205
+ for (let index = 0; index < model.blocks.length; index++) {
13206
+ const block = model.blocks[index];
13207
+ if (selection.start <= block.start && block.end <= selection.end) covered.push(index);
13208
+ }
13209
+ return covered;
13210
+ }, [
13211
+ selection,
13212
+ splitView,
13213
+ model
13214
+ ]);
13215
+ const selectionRange = (0, react.useMemo)(() => {
13216
+ if (coveredBlockIndices.length === 0) return void 0;
13217
+ const firstIndex = coveredBlockIndices[0];
13218
+ const lastIndex = coveredBlockIndices[coveredBlockIndices.length - 1];
13219
+ if (firstIndex === void 0 || lastIndex === void 0) return void 0;
13220
+ const first = model.blocks[firstIndex];
13221
+ const last = model.blocks[lastIndex];
13222
+ if (first === void 0 || last === void 0) return void 0;
13223
+ return blockRangesOf(model.diff.rows, {
13224
+ start: first.start,
13225
+ end: last.end
13226
+ });
13227
+ }, [coveredBlockIndices, model]);
12388
13228
  const searchMatches = (0, react.useMemo)(() => {
12389
13229
  if (searchQuery === "") return [];
12390
13230
  const lower = searchQuery.toLowerCase();
@@ -12490,6 +13330,13 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12490
13330
  const visibleRows = rows.slice(start, end);
12491
13331
  const blockEnd = hoveredBlock === void 0 ? void 0 : model.blocks[hoveredBlock]?.end;
12492
13332
  const blockActionsTop = blockEnd === void 0 ? 0 : Math.min(offsetOf(blockEnd + 1), Math.max(0, totalHeight - BLOCK_ACTIONS_FRAME_PX));
13333
+ const selectionBlockEnd = (() => {
13334
+ if (coveredBlockIndices.length === 0) return void 0;
13335
+ const lastIndex = coveredBlockIndices[coveredBlockIndices.length - 1];
13336
+ if (lastIndex === void 0) return void 0;
13337
+ return model.blocks[lastIndex]?.end;
13338
+ })();
13339
+ const selectionActionsTop = selectionBlockEnd === void 0 ? 0 : Math.min(offsetOf(selectionBlockEnd + 1), Math.max(0, totalHeight - BLOCK_ACTIONS_FRAME_PX));
12493
13340
  const widestLine = (0, react.useMemo)(() => {
12494
13341
  let widest = 0;
12495
13342
  for (const row of model.diff.rows) if (row.text.length > widest) widest = row.text.length;
@@ -12533,10 +13380,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12533
13380
  if (body.scrollTop !== clamped) body.scrollTop = clamped;
12534
13381
  setScrollTop(clamped);
12535
13382
  }, [
12536
- model,
12537
13383
  focus,
12538
13384
  scrollTick,
12539
- rowCount,
12540
13385
  rowOffsets === null
12541
13386
  ]);
12542
13387
  const jump = (direction) => {
@@ -12554,6 +13399,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12554
13399
  setScrollTick((tick) => tick + 1);
12555
13400
  setFlashKey((key) => key + 1);
12556
13401
  };
13402
+ const jumpBlock = (direction) => {
13403
+ if (splitView) {
13404
+ splitDiffRef.current?.jump(direction);
13405
+ return;
13406
+ }
13407
+ jump(direction);
13408
+ };
13409
+ const jumpBlockRef = (0, react.useRef)(jumpBlock);
13410
+ jumpBlockRef.current = jumpBlock;
12557
13411
  const stepBlock = (direction) => {
12558
13412
  const count = model.blocks.length;
12559
13413
  if (count === 0) return;
@@ -12563,9 +13417,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12563
13417
  setScrollTick((tick) => tick + 1);
12564
13418
  setFlashKey((key) => key + 1);
12565
13419
  };
13420
+ const runBlockAction = async (action, range, operated) => {
13421
+ await (action === "keep" ? onBlockKeep(file.sessionId, file.id, range) : onBlockRevert(file.sessionId, file.id, range));
13422
+ const count = model.blocks.length;
13423
+ if (count === 0) return;
13424
+ const next = Math.max(0, Math.min(operated, count - 1));
13425
+ setFocus(next);
13426
+ setHoveredBlock(void 0);
13427
+ setScrollTick((tick) => tick + 1);
13428
+ setFlashKey((key) => key + 1);
13429
+ };
13430
+ const handleBlockAction = async (action) => {
13431
+ if (busy || hoveredBlock === void 0) return;
13432
+ const operated = hoveredBlock;
13433
+ const range = blockRanges[operated];
13434
+ await runBlockAction(action, range, operated);
13435
+ };
13436
+ const handleSelectionAction = async (action) => {
13437
+ if (busy || selectionRange === void 0) return;
13438
+ const firstCovered = coveredBlockIndices[0];
13439
+ if (firstCovered === void 0) return;
13440
+ await runBlockAction(action, selectionRange, firstCovered);
13441
+ };
12566
13442
  (0, react.useEffect)(() => {
12567
13443
  if (jumpSignal === 0) return;
12568
- jump(1);
13444
+ jumpBlock(1);
12569
13445
  }, [jumpSignal]);
12570
13446
  const onScroll = () => {
12571
13447
  const body = bodyRef.current;
@@ -12574,31 +13450,49 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12574
13450
  setViewportHeight(body.clientHeight);
12575
13451
  };
12576
13452
  (0, react.useEffect)(() => {
12577
- const update = () => setSelection(rowRangeOf(window.getSelection()));
13453
+ const update = () => setSelection(splitView ? splitRowRangeOf(window.getSelection()) : rowRangeOf(window.getSelection()));
12578
13454
  document.addEventListener("selectionchange", update);
12579
13455
  update();
12580
13456
  return () => {
12581
13457
  document.removeEventListener("selectionchange", update);
12582
13458
  };
12583
- }, [file.id]);
13459
+ }, [file.id, splitView]);
12584
13460
  const selectionReference = (() => {
12585
13461
  if (selection === void 0) return void 0;
13462
+ if (splitView) {
13463
+ if (selection.side === void 0 || splitPairs === null) return void 0;
13464
+ const lineNumbers = [];
13465
+ for (let index = selection.start; index <= selection.end; index++) {
13466
+ const pair = splitPairs[index];
13467
+ if (pair === void 0) continue;
13468
+ const line = selection.side === "old" ? pair.left?.line : pair.right?.line;
13469
+ if (line !== void 0) lineNumbers.push(line);
13470
+ }
13471
+ if (lineNumbers.length === 0) return void 0;
13472
+ return referenceOf(file.path, workspacePath, Math.min(...lineNumbers), Math.max(...lineNumbers));
13473
+ }
12586
13474
  const lineNumbers = model.diff.rows.slice(selection.start, selection.end + 1).map((row) => row.newLine).filter((number) => number !== void 0);
12587
13475
  if (lineNumbers.length === 0) return void 0;
12588
13476
  return referenceOf(file.path, workspacePath, Math.min(...lineNumbers), Math.max(...lineNumbers));
12589
13477
  })();
12590
13478
  const copySelection = (0, react.useCallback)(async () => {
12591
13479
  if (selectionReference === void 0) return;
13480
+ if (pasteOnCopyEnabled()) {
13481
+ onPasteReference(file.sessionId, selectionReference);
13482
+ return;
13483
+ }
12592
13484
  if (!await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(selectionReference)) return;
12593
13485
  setCopied(true);
12594
- if (pasteOnCopyEnabled()) onPasteReference(file.sessionId, selectionReference);
13486
+ onToast(t("action.copied"));
12595
13487
  window.setTimeout(() => {
12596
13488
  setCopied(false);
12597
13489
  }, 1500);
12598
13490
  }, [
12599
13491
  file.sessionId,
12600
13492
  onPasteReference,
12601
- selectionReference
13493
+ onToast,
13494
+ selectionReference,
13495
+ t
12602
13496
  ]);
12603
13497
  (0, react.useEffect)(() => {
12604
13498
  const onKeyDown = (event) => {
@@ -12618,6 +13512,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12618
13512
  if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
12619
13513
  if (event.key.toLowerCase() !== "f") return;
12620
13514
  event.preventDefault();
13515
+ if (splitView) {
13516
+ splitDiffRef.current?.openSearch();
13517
+ return;
13518
+ }
12621
13519
  setSearchOpen(true);
12622
13520
  searchInputRef.current?.focus();
12623
13521
  searchInputRef.current?.select();
@@ -12627,8 +13525,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12627
13525
  window.removeEventListener("keydown", onKeyDown, true);
12628
13526
  };
12629
13527
  }, []);
12630
- const jumpRef = (0, react.useRef)(jump);
12631
- jumpRef.current = jump;
13528
+ const jumpRef = (0, react.useRef)(jumpBlock);
13529
+ jumpRef.current = jumpBlock;
12632
13530
  (0, react.useEffect)(() => {
12633
13531
  const onKeyDown = (event) => {
12634
13532
  if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
@@ -12714,7 +13612,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12714
13612
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: 14 })
12715
13613
  })
12716
13614
  }),
12717
- (0, react_jsx_runtime.jsx)("span", {
13615
+ (model.diff.added !== 0 || model.diff.removed !== 0) && (0, react_jsx_runtime.jsx)("span", {
12718
13616
  className: PendingPanel_module_css_default.diffStats,
12719
13617
  children: t("panel.stats", {
12720
13618
  added: model.diff.added,
@@ -12736,7 +13634,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12736
13634
  "aria-label": t("action.prevDiff"),
12737
13635
  disabled: busy,
12738
13636
  onClick: () => {
12739
- jump(-1);
13637
+ jumpBlock(-1);
12740
13638
  },
12741
13639
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12742
13640
  })
@@ -12751,7 +13649,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12751
13649
  "aria-label": t("action.nextDiff"),
12752
13650
  disabled: busy,
12753
13651
  onClick: () => {
12754
- jump(1);
13652
+ jumpBlock(1);
12755
13653
  },
12756
13654
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12757
13655
  })
@@ -12801,7 +13699,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12801
13699
  className: PendingPanel_module_css_default.missingHint,
12802
13700
  children: t("panel.missingHint")
12803
13701
  }),
12804
- (0, react_jsx_runtime.jsxs)("div", {
13702
+ splitView ? (0, react_jsx_runtime.jsx)(SplitDiff, {
13703
+ ref: splitDiffRef,
13704
+ file,
13705
+ model,
13706
+ runs,
13707
+ langWrap,
13708
+ tabWidthSpaces,
13709
+ busy,
13710
+ t,
13711
+ onBlockKeep,
13712
+ onBlockRevert
13713
+ }) : (0, react_jsx_runtime.jsxs)("div", {
12805
13714
  className: PendingPanel_module_css_default.diffBodyWrap,
12806
13715
  children: [
12807
13716
  (0, react_jsx_runtime.jsxs)("div", {
@@ -12845,7 +13754,30 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12845
13754
  "aria-hidden": "true"
12846
13755
  })
12847
13756
  ]
12848
- }), hoveredBlock !== void 0 && model.blocks[hoveredBlock] !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
13757
+ }), selectionRange !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
13758
+ className: PendingPanel_module_css_default.blockActions,
13759
+ "data-diff-selection-actions": true,
13760
+ style: { top: selectionActionsTop },
13761
+ children: [(0, react_jsx_runtime.jsx)("button", {
13762
+ type: "button",
13763
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
13764
+ "data-diff-selection-keep": true,
13765
+ disabled: busy,
13766
+ onClick: () => {
13767
+ handleSelectionAction("keep");
13768
+ },
13769
+ children: t("action.keep")
13770
+ }), (0, react_jsx_runtime.jsx)("button", {
13771
+ type: "button",
13772
+ className: PendingPanel_module_css_default.action,
13773
+ "data-diff-selection-revert": true,
13774
+ disabled: busy,
13775
+ onClick: () => {
13776
+ handleSelectionAction("revert");
13777
+ },
13778
+ children: t("action.revert")
13779
+ })]
13780
+ }) : hoveredBlock !== void 0 && model.blocks[hoveredBlock] !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
12849
13781
  className: PendingPanel_module_css_default.blockActions,
12850
13782
  "data-diff-block-actions": true,
12851
13783
  style: { top: blockActionsTop },
@@ -12886,7 +13818,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12886
13818
  "data-diff-block-keep": true,
12887
13819
  disabled: busy,
12888
13820
  onClick: () => {
12889
- onBlockKeep(file.sessionId, file.id, blockRanges[hoveredBlock]);
13821
+ handleBlockAction("keep");
12890
13822
  },
12891
13823
  children: t("action.keep")
12892
13824
  }),
@@ -12896,12 +13828,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12896
13828
  "data-diff-block-revert": true,
12897
13829
  disabled: busy,
12898
13830
  onClick: () => {
12899
- onBlockRevert(file.sessionId, file.id, blockRanges[hoveredBlock]);
13831
+ handleBlockAction("revert");
12900
13832
  },
12901
13833
  children: t("action.revert")
12902
13834
  })
12903
13835
  ]
12904
- })]
13836
+ }) : null]
12905
13837
  }),
12906
13838
  focusedBlock !== void 0 && flashKey > 0 && (0, react_jsx_runtime.jsx)("div", {
12907
13839
  className: PendingPanel_module_css_default.blockFlash,
@@ -13063,7 +13995,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13063
13995
  });
13064
13996
  }
13065
13997
  /** Render the pending-edit review panel and its unified footer action. */
13066
- function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPasteReference, onUndo, onRedo, onImportVcs, onAckRedoCleared, t }) {
13998
+ function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPasteReference, onUndo, onRedo, onImportVcs, onAckRedoCleared, onAckJustResolved, collapseSidebar, t }) {
13067
13999
  const current = useSessions((state) => state.current);
13068
14000
  const currentBlank = useSessions((state) => {
13069
14001
  const id = state.current;
@@ -13091,8 +14023,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13091
14023
  const [importToast, setImportToast] = (0, react.useState)(null);
13092
14024
  /** A transient banner for a keep/revert failure. */
13093
14025
  const [actionToast, setActionToast] = (0, react.useState)(null);
14026
+ /** A transient banner confirming a reference was copied to the clipboard. */
14027
+ const [copyToast, setCopyToast] = (0, react.useState)(null);
13094
14028
  /** Whether the redo-cleared notice is showing (bottom-right, OK to dismiss). */
13095
14029
  const [redoClearedNotice, setRedoClearedNotice] = (0, react.useState)(false);
14030
+ /** A file whose last block just resolved, pending a remove-or-keep choice. */
14031
+ const [confirmDismiss, setConfirmDismiss] = (0, react.useState)(null);
13096
14032
  /** Bottom offset tracking the chat composer's top edge so the input stays visible. */
13097
14033
  const [bottomPx, setBottomPx] = (0, react.useState)(FALLBACK_BOTTOM_PX);
13098
14034
  /** Fullscreen expanded: the panel bottom pins to the window edge, ignoring the composer offset. */
@@ -13105,6 +14041,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13105
14041
  /** The review panel's width, measured so the file list can collapse when it
13106
14042
  * would take more than a third of it (browser zoom / window resize). */
13107
14043
  const [panelWidth, setPanelWidth] = (0, react.useState)(0);
14044
+ const [viewportWidth, setViewportWidth] = (0, react.useState)(() => window.innerWidth);
13108
14045
  const panelRef = (0, react.useRef)(null);
13109
14046
  const splitRef = (0, react.useRef)(null);
13110
14047
  /** The code scroll box's bounds within the split, so the floating card is
@@ -13132,10 +14069,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13132
14069
  observer?.disconnect();
13133
14070
  };
13134
14071
  }, [open]);
13135
- const floatMode = panelWidth > 0 && listWidth > panelWidth / 3;
14072
+ const floatMode = viewportWidth < SIDEBAR_AUTO_COLLAPSE_PX;
13136
14073
  const toggleFileList = () => {
13137
14074
  setFloatOpen((value) => !value);
13138
14075
  };
14076
+ (0, react.useEffect)(() => {
14077
+ const onResize = () => {
14078
+ setViewportWidth(window.innerWidth);
14079
+ };
14080
+ window.addEventListener("resize", onResize);
14081
+ return () => {
14082
+ window.removeEventListener("resize", onResize);
14083
+ };
14084
+ }, []);
13139
14085
  (0, react.useEffect)(() => {
13140
14086
  if (!floatMode || !floatOpen) return;
13141
14087
  const el = panelRef.current;
@@ -13237,6 +14183,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13237
14183
  if (fresh.length > 0) setActionToast(fresh[0][1]);
13238
14184
  failedRef.current = current;
13239
14185
  }, [snapshot.failed]);
14186
+ (0, react.useEffect)(() => {
14187
+ if (snapshot.justResolved === void 0) return;
14188
+ setConfirmDismiss(snapshot.justResolved);
14189
+ onAckJustResolved();
14190
+ }, [snapshot.justResolved, onAckJustResolved]);
13240
14191
  (0, react.useEffect)(() => {
13241
14192
  if (!open) return;
13242
14193
  if (selected !== "" && files.some((file) => file.id === selected)) return;
@@ -13267,6 +14218,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13267
14218
  }
13268
14219
  };
13269
14220
  const toggleOpen = () => {
14221
+ if (!open) collapseSidebar();
13270
14222
  setOpen((value) => !value);
13271
14223
  };
13272
14224
  (0, react.useEffect)(() => {
@@ -13293,6 +14245,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13293
14245
  }
13294
14246
  }, entry.id);
13295
14247
  const selectedFile = files.find((file) => file.id === selected);
14248
+ /** The file whose removal is being confirmed, if any. */
14249
+ const confirmFile = confirmDismiss === null ? void 0 : files.find((file) => file.id === confirmDismiss);
13296
14250
  const fileListBody = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
13297
14251
  className: PendingPanel_module_css_default.listScroll,
13298
14252
  children: files.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
@@ -13360,6 +14314,58 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13360
14314
  handleUndo,
13361
14315
  handleRedo
13362
14316
  ]);
14317
+ (0, react.useEffect)(() => {
14318
+ if (!open || current === void 0) return;
14319
+ const onKeyDown = (event) => {
14320
+ if (!(event.ctrlKey || event.metaKey) || event.altKey) return;
14321
+ if (event.key.toLowerCase() !== "tab") return;
14322
+ const target = event.target;
14323
+ if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
14324
+ if (files.length === 0) return;
14325
+ event.preventDefault();
14326
+ const index = files.findIndex((file) => file.id === selected);
14327
+ const direction = event.shiftKey ? -1 : 1;
14328
+ const next = files[(index + direction + files.length) % files.length];
14329
+ if (next !== void 0) setSelected(next.id);
14330
+ };
14331
+ window.addEventListener("keydown", onKeyDown, true);
14332
+ return () => {
14333
+ window.removeEventListener("keydown", onKeyDown, true);
14334
+ };
14335
+ }, [
14336
+ open,
14337
+ current,
14338
+ files,
14339
+ selected
14340
+ ]);
14341
+ (0, react.useEffect)(() => {
14342
+ const onKeyDown = (event) => {
14343
+ if (!matchesShortcut(event, quickSummonKey())) return;
14344
+ const target = event.target;
14345
+ if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
14346
+ event.preventDefault();
14347
+ if (!open) collapseSidebar();
14348
+ setOpen((value) => !value);
14349
+ };
14350
+ window.addEventListener("keydown", onKeyDown, true);
14351
+ return () => {
14352
+ window.removeEventListener("keydown", onKeyDown, true);
14353
+ };
14354
+ }, [open, collapseSidebar]);
14355
+ (0, react.useEffect)(() => {
14356
+ if (!open) return;
14357
+ const onKeyDown = (event) => {
14358
+ if (event.key !== "Escape") return;
14359
+ const target = event.target;
14360
+ if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
14361
+ event.preventDefault();
14362
+ setOpen(false);
14363
+ };
14364
+ window.addEventListener("keydown", onKeyDown, true);
14365
+ return () => {
14366
+ window.removeEventListener("keydown", onKeyDown, true);
14367
+ };
14368
+ }, [open]);
13363
14369
  /** Drag the list/detail divider; width follows the pointer within its bounds. */
13364
14370
  const startResize = (event) => {
13365
14371
  if (event.button !== 0) return;
@@ -13401,162 +14407,207 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13401
14407
  setActionToast(null);
13402
14408
  }
13403
14409
  }),
13404
- open && expanded && (0, react_jsx_runtime.jsx)("div", {
14410
+ copyToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
14411
+ text: copyToast,
14412
+ onDone: () => {
14413
+ setCopyToast(null);
14414
+ }
14415
+ }),
14416
+ open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [expanded && (0, react_jsx_runtime.jsx)("div", {
13405
14417
  className: PendingPanel_module_css_default.fullscreenBackdrop,
13406
14418
  "data-diff-fullscreen-backdrop": true
13407
- }),
13408
- open && (0, react_jsx_runtime.jsxs)("section", {
14419
+ }), (0, react_jsx_runtime.jsxs)("section", {
13409
14420
  className: PendingPanel_module_css_default.panel,
13410
14421
  ref: panelRef,
13411
14422
  style: { bottom: expanded ? PANEL_INSET_PX : bottomPx },
13412
14423
  "data-diff-approval-panel": true,
13413
14424
  "aria-label": t("panel.title"),
13414
- children: [(0, react_jsx_runtime.jsxs)("header", {
13415
- className: PendingPanel_module_css_default.header,
13416
- children: [(0, react_jsx_runtime.jsx)("span", {
13417
- className: PendingPanel_module_css_default.title,
13418
- children: t("panel.title")
13419
- }), (0, react_jsx_runtime.jsxs)("div", {
13420
- className: PendingPanel_module_css_default.headerActions,
13421
- children: [
13422
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13423
- label: t("action.settings"),
13424
- side: "bottom",
13425
- delayMs: 500,
13426
- children: (0, react_jsx_runtime.jsx)("button", {
13427
- type: "button",
13428
- className: PendingPanel_module_css_default.expand,
13429
- "data-diff-approval-settings": true,
13430
- "aria-label": t("action.settings"),
13431
- onClick: () => {
13432
- setOpen(false);
13433
- openSettingsSection(t("settings.tabLabel"));
13434
- },
13435
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 14 })
14425
+ children: [
14426
+ (0, react_jsx_runtime.jsxs)("header", {
14427
+ className: PendingPanel_module_css_default.header,
14428
+ children: [(0, react_jsx_runtime.jsx)("span", {
14429
+ className: PendingPanel_module_css_default.title,
14430
+ children: t("panel.title")
14431
+ }), (0, react_jsx_runtime.jsxs)("div", {
14432
+ className: PendingPanel_module_css_default.headerActions,
14433
+ children: [
14434
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
14435
+ label: t("action.settings"),
14436
+ side: "bottom",
14437
+ delayMs: 500,
14438
+ children: (0, react_jsx_runtime.jsx)("button", {
14439
+ type: "button",
14440
+ className: PendingPanel_module_css_default.expand,
14441
+ "data-diff-approval-settings": true,
14442
+ "aria-label": t("action.settings"),
14443
+ onClick: () => {
14444
+ setOpen(false);
14445
+ openSettingsSection(t("settings.tabLabel"));
14446
+ },
14447
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 14 })
14448
+ })
14449
+ }),
14450
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
14451
+ label: t(expanded ? "action.exitFullscreen" : "action.expand"),
14452
+ side: "bottom",
14453
+ delayMs: 500,
14454
+ children: (0, react_jsx_runtime.jsx)("button", {
14455
+ type: "button",
14456
+ className: expanded ? `${PendingPanel_module_css_default.expand} ${PendingPanel_module_css_default.expandExpanded}` : PendingPanel_module_css_default.expand,
14457
+ "data-diff-approval-expand": true,
14458
+ "aria-label": t(expanded ? "action.exitFullscreen" : "action.expand"),
14459
+ onClick: () => {
14460
+ if (!expanded) collapseSidebar();
14461
+ setExpanded((value) => !value);
14462
+ },
14463
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
14464
+ })
14465
+ }),
14466
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
14467
+ label: t("action.close"),
14468
+ side: "bottom",
14469
+ delayMs: 500,
14470
+ children: (0, react_jsx_runtime.jsx)("button", {
14471
+ type: "button",
14472
+ className: PendingPanel_module_css_default.close,
14473
+ "data-diff-approval-close": true,
14474
+ "aria-label": t("action.close"),
14475
+ onClick: () => {
14476
+ setOpen(false);
14477
+ },
14478
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
14479
+ })
13436
14480
  })
14481
+ ]
14482
+ })]
14483
+ }),
14484
+ snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
14485
+ className: PendingPanel_module_css_default.states,
14486
+ children: [
14487
+ snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
14488
+ className: PendingPanel_module_css_default.readError,
14489
+ role: "alert",
14490
+ children: t("panel.readFailed", { message: snapshot.error })
13437
14491
  }),
13438
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13439
- label: t(expanded ? "action.exitFullscreen" : "action.expand"),
13440
- side: "bottom",
13441
- delayMs: 500,
13442
- children: (0, react_jsx_runtime.jsx)("button", {
13443
- type: "button",
13444
- className: expanded ? `${PendingPanel_module_css_default.expand} ${PendingPanel_module_css_default.expandExpanded}` : PendingPanel_module_css_default.expand,
13445
- "data-diff-approval-expand": true,
13446
- "aria-label": t(expanded ? "action.exitFullscreen" : "action.expand"),
13447
- onClick: () => {
13448
- setExpanded((value) => !value);
14492
+ !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
14493
+ className: PendingPanel_module_css_default.note,
14494
+ children: t("panel.loading")
14495
+ }),
14496
+ snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsxs)("div", {
14497
+ className: PendingPanel_module_css_default.emptyState,
14498
+ children: [
14499
+ (0, react_jsx_runtime.jsx)("p", {
14500
+ className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
14501
+ children: t("panel.empty")
14502
+ }),
14503
+ (0, react_jsx_runtime.jsx)("button", {
14504
+ type: "button",
14505
+ className: PendingPanel_module_css_default.importButton,
14506
+ "data-diff-import-vcs": true,
14507
+ disabled: importBusy,
14508
+ onClick: () => {
14509
+ runImportVcs();
14510
+ },
14511
+ children: importBusy ? t("action.importVcsBusy") : t("action.importVcs")
14512
+ }),
14513
+ importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
14514
+ className: PendingPanel_module_css_default.importNote,
14515
+ role: importFailed ? "alert" : void 0,
14516
+ children: importNote
14517
+ })
14518
+ ]
14519
+ })
14520
+ ]
14521
+ }) : (0, react_jsx_runtime.jsxs)("div", {
14522
+ className: PendingPanel_module_css_default.split,
14523
+ ref: splitRef,
14524
+ children: [
14525
+ !floatMode && (0, react_jsx_runtime.jsx)("nav", {
14526
+ className: PendingPanel_module_css_default.fileList,
14527
+ style: { width: listWidth },
14528
+ "data-diff-approval-file-list": true,
14529
+ children: fileListBody
14530
+ }),
14531
+ !floatMode && (0, react_jsx_runtime.jsx)("div", {
14532
+ className: PendingPanel_module_css_default.resizeHandle,
14533
+ "data-diff-resize": true,
14534
+ onMouseDown: startResize
14535
+ }),
14536
+ (0, react_jsx_runtime.jsx)("div", {
14537
+ className: PendingPanel_module_css_default.detail,
14538
+ children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
14539
+ className: PendingPanel_module_css_default.detailEmpty,
14540
+ children: t("panel.selectHint")
14541
+ }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
14542
+ file: selectedFile,
14543
+ busy: snapshot.busy.has(selectedFile.id),
14544
+ workspacePath: snapshot.workspacePath,
14545
+ jumpSignal,
14546
+ undoFlash,
14547
+ failedMessage: failed.get(selectedFile.id),
14548
+ onPasteReference,
14549
+ onToast: (text) => {
14550
+ setCopyToast(text);
13449
14551
  },
13450
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
14552
+ t,
14553
+ onKeep,
14554
+ onRevert,
14555
+ onBlockKeep,
14556
+ onBlockRevert,
14557
+ onOpen,
14558
+ floatMode,
14559
+ floatOpen,
14560
+ onToggleFileList: toggleFileList
13451
14561
  })
13452
14562
  }),
13453
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13454
- label: t("action.close"),
13455
- side: "bottom",
13456
- delayMs: 500,
13457
- children: (0, react_jsx_runtime.jsx)("button", {
14563
+ floatMode && floatOpen && files.length > 0 && floatBox !== null && (0, react_jsx_runtime.jsx)("div", {
14564
+ className: PendingPanel_module_css_default.fileListFloat,
14565
+ style: {
14566
+ left: floatBox.left + FLOAT_LIST_MARGIN_PX,
14567
+ top: floatBox.top + FLOAT_LIST_MARGIN_PX,
14568
+ width: Math.min(listWidth, Math.max(0, floatBox.width - 24)),
14569
+ height: Math.max(0, floatBox.height - 24)
14570
+ },
14571
+ "data-diff-floating-file-list": true,
14572
+ children: fileListBody
14573
+ })
14574
+ ]
14575
+ }),
14576
+ confirmFile !== void 0 && (0, react_jsx_runtime.jsx)("div", {
14577
+ className: PendingPanel_module_css_default.confirmBackdrop,
14578
+ "data-diff-confirm": true,
14579
+ children: (0, react_jsx_runtime.jsxs)("div", {
14580
+ className: PendingPanel_module_css_default.confirmCard,
14581
+ role: "dialog",
14582
+ "aria-modal": "true",
14583
+ children: [(0, react_jsx_runtime.jsx)("p", {
14584
+ className: PendingPanel_module_css_default.confirmText,
14585
+ children: t("panel.resolvedAsk", { file: basenameOf(confirmFile.path) })
14586
+ }), (0, react_jsx_runtime.jsxs)("div", {
14587
+ className: PendingPanel_module_css_default.confirmActions,
14588
+ children: [(0, react_jsx_runtime.jsx)("button", {
13458
14589
  type: "button",
13459
- className: PendingPanel_module_css_default.close,
13460
- "data-diff-approval-close": true,
13461
- "aria-label": t("action.close"),
14590
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
14591
+ "data-diff-confirm-remove": true,
13462
14592
  onClick: () => {
13463
- setOpen(false);
14593
+ setConfirmDismiss(null);
14594
+ onKeep(confirmFile.sessionId, confirmFile.id);
13464
14595
  },
13465
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
13466
- })
13467
- })
13468
- ]
13469
- })]
13470
- }), snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
13471
- className: PendingPanel_module_css_default.states,
13472
- children: [
13473
- snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
13474
- className: PendingPanel_module_css_default.readError,
13475
- role: "alert",
13476
- children: t("panel.readFailed", { message: snapshot.error })
13477
- }),
13478
- !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
13479
- className: PendingPanel_module_css_default.note,
13480
- children: t("panel.loading")
13481
- }),
13482
- snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsxs)("div", {
13483
- className: PendingPanel_module_css_default.emptyState,
13484
- children: [
13485
- (0, react_jsx_runtime.jsx)("p", {
13486
- className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
13487
- children: t("panel.empty")
13488
- }),
13489
- (0, react_jsx_runtime.jsx)("button", {
14596
+ children: t("row.dismiss")
14597
+ }), (0, react_jsx_runtime.jsx)("button", {
13490
14598
  type: "button",
13491
- className: PendingPanel_module_css_default.importButton,
13492
- "data-diff-import-vcs": true,
13493
- disabled: importBusy,
14599
+ className: PendingPanel_module_css_default.action,
14600
+ "data-diff-confirm-keep": true,
13494
14601
  onClick: () => {
13495
- runImportVcs();
14602
+ setConfirmDismiss(null);
13496
14603
  },
13497
- children: importBusy ? t("action.importVcsBusy") : t("action.importVcs")
13498
- }),
13499
- importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
13500
- className: PendingPanel_module_css_default.importNote,
13501
- role: importFailed ? "alert" : void 0,
13502
- children: importNote
13503
- })
13504
- ]
14604
+ children: t("panel.keepInList")
14605
+ })]
14606
+ })]
13505
14607
  })
13506
- ]
13507
- }) : (0, react_jsx_runtime.jsxs)("div", {
13508
- className: PendingPanel_module_css_default.split,
13509
- ref: splitRef,
13510
- children: [
13511
- !floatMode && (0, react_jsx_runtime.jsx)("nav", {
13512
- className: PendingPanel_module_css_default.fileList,
13513
- style: { width: listWidth },
13514
- "data-diff-approval-file-list": true,
13515
- children: fileListBody
13516
- }),
13517
- !floatMode && (0, react_jsx_runtime.jsx)("div", {
13518
- className: PendingPanel_module_css_default.resizeHandle,
13519
- "data-diff-resize": true,
13520
- onMouseDown: startResize
13521
- }),
13522
- (0, react_jsx_runtime.jsx)("div", {
13523
- className: PendingPanel_module_css_default.detail,
13524
- children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
13525
- className: PendingPanel_module_css_default.detailEmpty,
13526
- children: t("panel.selectHint")
13527
- }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
13528
- file: selectedFile,
13529
- busy: snapshot.busy.has(selectedFile.id),
13530
- workspacePath: snapshot.workspacePath,
13531
- jumpSignal,
13532
- undoFlash,
13533
- failedMessage: failed.get(selectedFile.id),
13534
- onPasteReference,
13535
- t,
13536
- onKeep,
13537
- onRevert,
13538
- onBlockKeep,
13539
- onBlockRevert,
13540
- onOpen,
13541
- floatMode,
13542
- floatOpen,
13543
- onToggleFileList: toggleFileList
13544
- })
13545
- }),
13546
- floatMode && floatOpen && files.length > 0 && floatBox !== null && (0, react_jsx_runtime.jsx)("div", {
13547
- className: PendingPanel_module_css_default.fileListFloat,
13548
- style: {
13549
- left: floatBox.left + FLOAT_LIST_MARGIN_PX,
13550
- top: floatBox.top + FLOAT_LIST_MARGIN_PX,
13551
- width: Math.min(listWidth, Math.max(0, floatBox.width - 24)),
13552
- height: Math.max(0, floatBox.height - 24)
13553
- },
13554
- "data-diff-floating-file-list": true,
13555
- children: fileListBody
13556
- })
13557
- ]
13558
- })]
13559
- }),
14608
+ })
14609
+ ]
14610
+ })] }), document.body),
13560
14611
  (0, react_jsx_runtime.jsx)("div", {
13561
14612
  className: PendingPanel_module_css_default.footerButtons,
13562
14613
  children: (0, react_jsx_runtime.jsxs)("button", {
@@ -13719,6 +14770,69 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13719
14770
  })]
13720
14771
  });
13721
14772
  }
14773
+ /** Build the `Modifier+...+Key` chord label from a keydown event; a bare
14774
+ * modifier key alone returns undefined (wait for the full combo). */
14775
+ function chordLabel(event) {
14776
+ const key = event.key;
14777
+ if (key === "Control" || key === "Shift" || key === "Alt" || key === "Meta") return void 0;
14778
+ const parts = [];
14779
+ if (event.ctrlKey) parts.push("Ctrl");
14780
+ if (event.altKey) parts.push("Alt");
14781
+ if (event.shiftKey) parts.push("Shift");
14782
+ if (event.metaKey) parts.push("Meta");
14783
+ parts.push(key.length === 1 ? key.toUpperCase() : key);
14784
+ return parts.join("+");
14785
+ }
14786
+ /** A button that records the next key chord (modifiers + key) as the shortcut. */
14787
+ function ShortcutRecorder({ value, onChange, dataAttribute, placeholder }) {
14788
+ const [recording, setRecording] = (0, react.useState)(false);
14789
+ return (0, react_jsx_runtime.jsxs)("button", {
14790
+ type: "button",
14791
+ className: PendingPanel_module_css_default.settingsSelector,
14792
+ onClick: () => {
14793
+ setRecording(true);
14794
+ },
14795
+ onBlur: () => {
14796
+ setRecording(false);
14797
+ },
14798
+ onKeyDown: recording ? (event) => {
14799
+ event.preventDefault();
14800
+ event.stopPropagation();
14801
+ if (event.key === "Escape") {
14802
+ setRecording(false);
14803
+ return;
14804
+ }
14805
+ const chord = chordLabel(event);
14806
+ if (chord !== void 0) {
14807
+ onChange(chord);
14808
+ setRecording(false);
14809
+ }
14810
+ } : void 0,
14811
+ [dataAttribute]: true,
14812
+ children: [recording ? placeholder : value, (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: PendingPanel_module_css_default.settingsSelectorChevron })]
14813
+ });
14814
+ }
14815
+ /** One shortcut row: title + description, chord recorder right. */
14816
+ function ShortcutRow({ title, description, value, onChange, dataAttribute, placeholder }) {
14817
+ return (0, react_jsx_runtime.jsxs)("div", {
14818
+ className: PendingPanel_module_css_default.settingsRow,
14819
+ children: [(0, react_jsx_runtime.jsxs)("div", {
14820
+ className: PendingPanel_module_css_default.settingsRowText,
14821
+ children: [(0, react_jsx_runtime.jsx)("div", {
14822
+ className: PendingPanel_module_css_default.settingsRowTitle,
14823
+ children: title
14824
+ }), (0, react_jsx_runtime.jsx)("div", {
14825
+ className: PendingPanel_module_css_default.settingsRowDesc,
14826
+ children: description
14827
+ })]
14828
+ }), (0, react_jsx_runtime.jsx)(ShortcutRecorder, {
14829
+ value,
14830
+ onChange,
14831
+ dataAttribute,
14832
+ placeholder
14833
+ })]
14834
+ });
14835
+ }
13722
14836
  /**
13723
14837
  * The plugin's preferences: auto-paste a copied reference into the input,
13724
14838
  * whether importing workspace VCS changes includes untracked files, and the
@@ -13733,6 +14847,13 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13733
14847
  const [includeUntrackedOpen, setIncludeUntrackedOpen] = (0, react.useState)(false);
13734
14848
  const [tab, setTabState] = (0, react.useState)(tabWidth);
13735
14849
  const [tabOpen, setTabOpen] = (0, react.useState)(false);
14850
+ const [split, setSplitState] = (0, react.useState)(splitMode);
14851
+ const [splitOpen, setSplitOpen] = (0, react.useState)(false);
14852
+ const [summon, setSummonState] = (0, react.useState)(quickSummonKey);
14853
+ const setSummon = (value) => {
14854
+ setSummonState(value);
14855
+ setQuickSummonKey(value);
14856
+ };
13736
14857
  const setPasteOnCopy = (value) => {
13737
14858
  setPasteOnCopyState(value);
13738
14859
  setPasteOnCopyEnabled(value);
@@ -13745,6 +14866,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13745
14866
  setTabState(value);
13746
14867
  setTabWidth(value);
13747
14868
  };
14869
+ const setSplit = (value) => {
14870
+ setSplitState(value);
14871
+ setSplitMode(value);
14872
+ };
13748
14873
  return (0, react_jsx_runtime.jsxs)("div", {
13749
14874
  className: PendingPanel_module_css_default.settingsPage,
13750
14875
  "data-diff-settings": true,
@@ -13777,6 +14902,24 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13777
14902
  onOpenChange: setTabOpen,
13778
14903
  onSelect: setTab,
13779
14904
  dataAttribute: "data-diff-tab-width-select"
14905
+ }),
14906
+ (0, react_jsx_runtime.jsx)(PreferenceRow, {
14907
+ title: t("panel.splitMode"),
14908
+ description: t("panel.splitModeDesc"),
14909
+ value: split,
14910
+ open: splitOpen,
14911
+ onOpenChange: setSplitOpen,
14912
+ onSelect: setSplit,
14913
+ dataAttribute: "data-diff-split-mode-select",
14914
+ t
14915
+ }),
14916
+ (0, react_jsx_runtime.jsx)(ShortcutRow, {
14917
+ title: t("panel.quickSummon"),
14918
+ description: t("panel.quickSummonDesc"),
14919
+ value: summon,
14920
+ onChange: setSummon,
14921
+ dataAttribute: "data-diff-quick-summon-key",
14922
+ placeholder: t("panel.recordShortcut")
13780
14923
  })
13781
14924
  ]
13782
14925
  });
@@ -13900,9 +15043,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13900
15043
  const outcome = value.outcome;
13901
15044
  if (outcome !== "kept" && outcome !== "reverted" && outcome !== "missing" && outcome !== "undone" && outcome !== "redone" && outcome !== "nothing") throw new Error("the action returned a malformed outcome");
13902
15045
  const id = value.id;
13903
- return {
15046
+ const entryId = typeof id === "string" && id.length > 0 ? id : void 0;
15047
+ return value.resolved === true ? {
13904
15048
  outcome,
13905
- id: typeof id === "string" && id.length > 0 ? id : void 0
15049
+ id: entryId,
15050
+ resolved: true
15051
+ } : {
15052
+ outcome,
15053
+ id: entryId
13906
15054
  };
13907
15055
  }
13908
15056
  /** Narrow the vcs-import endpoint's value; a malformed wire value is a failure. */
@@ -13955,11 +15103,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13955
15103
  };
13956
15104
  const listeners = /* @__PURE__ */ new Set();
13957
15105
  let redoCleared = false;
15106
+ let justResolved;
13958
15107
  const publish = (next) => {
13959
- snapshot = redoCleared ? {
13960
- ...next,
15108
+ let result = next;
15109
+ if (redoCleared) result = {
15110
+ ...result,
13961
15111
  redoCleared: true
13962
- } : next;
15112
+ };
15113
+ if (justResolved !== void 0) result = {
15114
+ ...result,
15115
+ justResolved
15116
+ };
15117
+ snapshot = result;
13963
15118
  for (const listener of [...listeners]) listener();
13964
15119
  };
13965
15120
  const failedOf = (value) => value.failed ?? EMPTY_FAILED;
@@ -14068,8 +15223,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14068
15223
  ...base,
14069
15224
  busy: /* @__PURE__ */ new Set([...snapshot.busy, id])
14070
15225
  });
15226
+ let value;
14071
15227
  try {
14072
- await port.blockKeep(sessionId, id, block);
15228
+ value = await port.blockKeep(sessionId, id, block);
14073
15229
  } catch (error) {
14074
15230
  markFailed(id, error instanceof Error ? error.message : String(error));
14075
15231
  publish({
@@ -14079,6 +15235,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14079
15235
  return;
14080
15236
  }
14081
15237
  clearFailed(id);
15238
+ if (value.resolved === true) justResolved = id;
14082
15239
  await this.refresh(sessionId);
14083
15240
  },
14084
15241
  async blockRevert(sessionId, id, block) {
@@ -14087,8 +15244,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14087
15244
  ...base,
14088
15245
  busy: /* @__PURE__ */ new Set([...snapshot.busy, id])
14089
15246
  });
15247
+ let value;
14090
15248
  try {
14091
- await port.blockRevert(sessionId, id, block);
15249
+ value = await port.blockRevert(sessionId, id, block);
14092
15250
  } catch (error) {
14093
15251
  markFailed(id, error instanceof Error ? error.message : String(error));
14094
15252
  publish({
@@ -14098,6 +15256,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14098
15256
  return;
14099
15257
  }
14100
15258
  clearFailed(id);
15259
+ if (value.resolved === true) justResolved = id;
14101
15260
  await this.refresh(sessionId);
14102
15261
  },
14103
15262
  async undo(sessionId) {
@@ -14136,6 +15295,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14136
15295
  }
14137
15296
  },
14138
15297
  reset() {
15298
+ justResolved = void 0;
14139
15299
  publish({
14140
15300
  read: false,
14141
15301
  files: [],
@@ -14146,6 +15306,131 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14146
15306
  redoCleared = false;
14147
15307
  const { redoCleared: _omit, ...rest } = snapshot;
14148
15308
  publish(rest);
15309
+ },
15310
+ clearJustResolved() {
15311
+ justResolved = void 0;
15312
+ const { justResolved: _omit, ...rest } = snapshot;
15313
+ publish(rest);
15314
+ }
15315
+ };
15316
+ }
15317
+ //#endregion
15318
+ //#region lib/types/client/remap-sync.js
15319
+ /**
15320
+ * Reference remapping sync: track each pending file's last-seen content and,
15321
+ * when it changes (an agent edit, a block revert, or an external adoption),
15322
+ * rewrite stale references in the current composer draft and the pending queue.
15323
+ * Pure orchestration over the pending store; the actual line remapping lives in
15324
+ * `reference.ts`.
15325
+ * @module dsh-diff-approval/client/remap-sync
15326
+ */
15327
+ /**
15328
+ * Subscribe to the pending store and remap references as each file's content
15329
+ * changes. The first observation of a file only seeds the baseline; a later
15330
+ * `newText` change remaps (draft + queue) and advances the baseline. Files that
15331
+ * leave the list drop their baseline so a future re-appearance re-baselines.
15332
+ * @param opts - the sync inputs.
15333
+ * @returns an unsubscribe function plus a direct remap verb for whole-file reverts.
15334
+ */
15335
+ function attachReferenceRemap(opts) {
15336
+ const { store, readDraft, writeDraft, readQueue, writeQueue } = opts;
15337
+ const lastContent = /* @__PURE__ */ new Map();
15338
+ const remap = (path, oldText, newText, workspacePath) => {
15339
+ const referencePath = referencePathOf(path, workspacePath);
15340
+ const draft = readDraft();
15341
+ if (draft !== void 0 && draft !== "") {
15342
+ const rewritten = remapReferences(draft, referencePath, oldText, newText);
15343
+ if (rewritten !== draft) writeDraft(rewritten);
15344
+ }
15345
+ for (const message of readQueue()) {
15346
+ let changed = false;
15347
+ const content = message.content.map((block) => {
15348
+ if (block.type !== "text" || block.text === void 0) return block;
15349
+ const rewritten = remapReferences(block.text, referencePath, oldText, newText);
15350
+ if (rewritten !== block.text) {
15351
+ changed = true;
15352
+ return {
15353
+ ...block,
15354
+ text: rewritten
15355
+ };
15356
+ }
15357
+ return block;
15358
+ });
15359
+ if (changed) writeQueue(message.id, content);
15360
+ }
15361
+ };
15362
+ const observe = () => {
15363
+ const snapshot = store.getSnapshot();
15364
+ for (const file of snapshot.files) {
15365
+ const previous = lastContent.get(file.path);
15366
+ if (previous === void 0) lastContent.set(file.path, file.newText);
15367
+ else if (previous !== file.newText) {
15368
+ remap(file.path, previous, file.newText, snapshot.workspacePath);
15369
+ lastContent.set(file.path, file.newText);
15370
+ }
15371
+ }
15372
+ for (const path of [...lastContent.keys()]) if (!snapshot.files.some((file) => file.path === path)) lastContent.delete(path);
15373
+ };
15374
+ const unsubscribe = store.subscribe(observe);
15375
+ observe();
15376
+ return {
15377
+ unsubscribe,
15378
+ remapFile: (path, oldText, newText) => {
15379
+ remap(path, oldText, newText, store.getSnapshot().workspacePath);
15380
+ }
15381
+ };
15382
+ }
15383
+ //#endregion
15384
+ //#region lib/types/client/conversation-access.js
15385
+ /**
15386
+ * Scope-addressed access to the session's composer draft and pending queue.
15387
+ * Extracted from the plugin entry so the wiring (not just the remap math) is
15388
+ * unit-testable against a faithful fake of the DSH conversation service.
15389
+ * @module dsh-diff-approval/client/conversation-access
15390
+ */
15391
+ /**
15392
+ * Resolve the current session's scoped conversation face. The scoped context
15393
+ * (`sessions.scope(id)`) carries no `conversation` inject of its own, so reading
15394
+ * it as a property (`actx.conversation`) trips Cordis's inject check and throws
15395
+ * `cannot get property "conversation" without inject`. `actx.get('conversation')`
15396
+ * bypasses that check yet still returns the traceable bound to `actx`, so its
15397
+ * verbs (e.g. `updateQueue`) resolve the right session scope.
15398
+ */
15399
+ function resolveConversation(ctx, sessionId) {
15400
+ if (sessionId === void 0) return void 0;
15401
+ const actx = ctx.get("sessions")?.scope(sessionId);
15402
+ if (actx === void 0) return void 0;
15403
+ const conversation = actx.get("conversation");
15404
+ if (conversation?.input === void 0) return void 0;
15405
+ return {
15406
+ actx,
15407
+ conversation
15408
+ };
15409
+ }
15410
+ /**
15411
+ * Build the per-call-resolved draft/queue accessor. `sessionId` is a function
15412
+ * so the accessor always addresses the *current* session, even though it is
15413
+ * handed to the remap sync once.
15414
+ */
15415
+ function conversationAccess(ctx, sessionId) {
15416
+ return {
15417
+ writeDraft: (text) => {
15418
+ const scoped = resolveConversation(ctx, sessionId());
15419
+ if (scoped === void 0) return;
15420
+ scoped.conversation.input.for(scoped.actx).setDraft(text);
15421
+ },
15422
+ readQueue: () => {
15423
+ const scoped = resolveConversation(ctx, sessionId());
15424
+ if (scoped === void 0) return [];
15425
+ return scoped.conversation.input.for(scoped.actx).state.getSnapshot().queue.filter((item) => item.placement === "queued");
15426
+ },
15427
+ writeQueue: (itemId, content) => {
15428
+ const scoped = resolveConversation(ctx, sessionId());
15429
+ if (scoped === void 0) return;
15430
+ scoped.conversation.updateQueue(itemId, {
15431
+ kind: "edit",
15432
+ content
15433
+ });
14149
15434
  }
14150
15435
  };
14151
15436
  }
@@ -14175,11 +15460,17 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14175
15460
  "panel.pasteOnCopyDesc": "开启后,复制的引用会自动填入消息输入框,输入框会获得焦点。",
14176
15461
  "panel.importUntracked": "导入未跟踪的文件改动",
14177
15462
  "panel.importUntrackedDesc": "开启后,导入工作区改动会包含未跟踪/未版本化的新增文件(Git 未跟踪、SVN 未版本化、Perforce 未版本化)。收集改动需要扫描整个工作区,文件数量大时可能较慢。",
14178
- "panel.tabWidth": "Tab 宽度",
14179
- "panel.tabWidthDesc": "diff 中制表符(Tab)的缩进宽度,可选 2 / 4 / 8 个空格(默认 4),同时作用于行宽折行测量。",
15463
+ "panel.tabWidth": "制表符宽度",
15464
+ "panel.tabWidthDesc": "差异中制表符的缩进宽度,可选 2 / 4 / 8 个空格(默认 4),同时作用于行宽折行测量。",
15465
+ "panel.splitMode": "双栏对比",
15466
+ "panel.splitModeDesc": "开启后整文件差异用左「改前」|右「当前」双栏、逐行对齐的视图展示,默认关闭使用单栏(合并)视图。",
15467
+ "panel.quickSummon": "快速呼出",
15468
+ "panel.quickSummonDesc": "用键盘快捷键打开/关闭差异面板(默认 Ctrl+D)。点击右侧按钮后按下新的按键组合即可修改,Esc 取消。",
15469
+ "panel.recordShortcut": "按下快捷键…",
14180
15470
  "settings.tabLabel": "改动审批",
14181
15471
  "row.create": "新增文件",
14182
15472
  "row.failed": "失败",
15473
+ "row.dismiss": "移除",
14183
15474
  "row.added": "+{added}",
14184
15475
  "row.removed": "-{removed}",
14185
15476
  "action.keep": "保留",
@@ -14214,7 +15505,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14214
15505
  "action.close": "关闭",
14215
15506
  "status.kept": "已保留",
14216
15507
  "status.reverted": "已回退",
14217
- "status.missing": "该改动已不存在"
15508
+ "status.missing": "该改动已不存在",
15509
+ "panel.resolvedAsk": "「{file}」的所有改动都已处理,是否从列表移除?",
15510
+ "panel.keepInList": "保留在列表"
14218
15511
  };
14219
15512
  /** English pending-edit review messages. */
14220
15513
  const en = {
@@ -14239,10 +15532,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14239
15532
  "panel.importUntracked": "Import changes to untracked files",
14240
15533
  "panel.importUntrackedDesc": "When enabled, importing workspace changes includes new/untracked files (Git-untracked, SVN-unversioned, Perforce-unversioned). Collecting changes scans the whole workspace, which can be slow on large trees.",
14241
15534
  "panel.tabWidth": "Tab width",
14242
- "panel.tabWidthDesc": "The indent width of a tab character in the diff, as 2 / 4 / 8 spaces (default 4). Also drives the wrapped-line measurement.",
15535
+ "panel.tabWidthDesc": "The indent width of a tab character in the differences, as 2 / 4 / 8 spaces (default 4). Also drives the wrapped-line measurement.",
15536
+ "panel.splitMode": "Side-by-side view",
15537
+ "panel.splitModeDesc": "When on, the whole-file differences render as a side-by-side (left \"before\" | right \"current\") line-aligned view. Off (default) uses the single-column unified view.",
15538
+ "panel.quickSummon": "Quick summon",
15539
+ "panel.quickSummonDesc": "Open or close the diff panel with a keyboard shortcut (default Ctrl+D). Click the button and press a new chord to change it; Esc cancels.",
15540
+ "panel.recordShortcut": "Press keys…",
14243
15541
  "settings.tabLabel": "Diff Approval",
14244
15542
  "row.create": "New file",
14245
15543
  "row.failed": "Failed",
15544
+ "row.dismiss": "Remove",
14246
15545
  "row.added": "+{added}",
14247
15546
  "row.removed": "-{removed}",
14248
15547
  "action.keep": "Keep",
@@ -14277,17 +15576,21 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14277
15576
  "action.close": "Close",
14278
15577
  "status.kept": "Kept",
14279
15578
  "status.reverted": "Reverted",
14280
- "status.missing": "This change no longer exists"
15579
+ "status.missing": "This change no longer exists",
15580
+ "panel.resolvedAsk": "All changes in \"{file}\" are resolved. Remove it from the list?",
15581
+ "panel.keepInList": "Keep in list"
14281
15582
  };
14282
15583
  //#endregion
14283
15584
  //#region lib/types/client/index.js
14284
15585
  /** Pending-edit review panel, browser half: footer action, pending list, and whole-file diff viewer. */
14285
- /** Required services: locale, slots, the wire channel, and the current session. */
15586
+ /** Required services: locale, slots, the wire channel, the current session, and
15587
+ * the layout controller (this plugin collapses the sidebar before its modal opens). */
14286
15588
  const inject = [
14287
15589
  "slots",
14288
15590
  "locale",
14289
15591
  "connection",
14290
- "sessions"
15592
+ "sessions",
15593
+ "layout"
14291
15594
  ];
14292
15595
  /**
14293
15596
  * The dsh web-react renderer gives `div[data-slot="sidebar.footer.action"]` an
@@ -14354,6 +15657,29 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14354
15657
  }), "ui-diff-approval: dictionaries");
14355
15658
  const t = ctx.locale.bind(NS);
14356
15659
  const store = createPendingDiffStore(createDiffApprovalPort(ctx.get("connection").rpc));
15660
+ let currentSessionId;
15661
+ let remapFile = () => {};
15662
+ const access = conversationAccess(ctx, () => currentSessionId);
15663
+ ctx.effect(() => {
15664
+ const attached = attachReferenceRemap({
15665
+ store,
15666
+ readDraft: () => document.querySelector("[data-composer-card] textarea")?.value,
15667
+ writeDraft: access.writeDraft,
15668
+ readQueue: access.readQueue,
15669
+ writeQueue: access.writeQueue
15670
+ });
15671
+ remapFile = attached.remapFile;
15672
+ return attached.unsubscribe;
15673
+ }, "diff-approval: reference remap");
15674
+ const collapseSidebar = () => {
15675
+ if (window.innerWidth >= 1024) return;
15676
+ const ctxLayout = ctx.layout;
15677
+ if (ctxLayout === void 0) return;
15678
+ if (document.querySelector("[data-sidebar-collapsed]") !== null) return;
15679
+ try {
15680
+ ctxLayout.toggleSidebar();
15681
+ } catch {}
15682
+ };
14357
15683
  ctx.on("connection/reset", () => {
14358
15684
  store.reset();
14359
15685
  });
@@ -14364,10 +15690,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14364
15690
  inject: () => ({
14365
15691
  hooks: { pending: store },
14366
15692
  onRefresh: (sessionId) => {
15693
+ currentSessionId = sessionId;
14367
15694
  store.refresh(sessionId);
14368
15695
  },
14369
15696
  onKeep: (sessionId, path) => store.keep(sessionId, path),
14370
- onRevert: (sessionId, path) => store.revert(sessionId, path),
15697
+ onRevert: (sessionId, path) => {
15698
+ const entry = store.getSnapshot().files.find((file) => file.id === path);
15699
+ const before = entry?.newText;
15700
+ const after = entry?.oldText;
15701
+ const filePath = entry?.path;
15702
+ return store.revert(sessionId, path).then(() => {
15703
+ if (filePath !== void 0 && before !== void 0 && after !== void 0) remapFile(filePath, before, after);
15704
+ });
15705
+ },
14371
15706
  onBlockKeep: (sessionId, id, block) => store.blockKeep(sessionId, id, block),
14372
15707
  onBlockRevert: (sessionId, id, block) => store.blockRevert(sessionId, id, block),
14373
15708
  onOpen: (sessionId, id, action) => store.open(sessionId, id, action),
@@ -14375,6 +15710,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14375
15710
  onRedo: (sessionId) => store.redo(sessionId),
14376
15711
  onImportVcs: (sessionId, includeUntracked) => store.importVcs(sessionId, includeUntracked),
14377
15712
  onAckRedoCleared: () => store.clearRedoCleared(),
15713
+ onAckJustResolved: () => store.clearJustResolved(),
14378
15714
  onPasteReference: (sessionId, reference) => {
14379
15715
  const actx = ctx.get("sessions")?.scope(sessionId);
14380
15716
  if (actx === void 0) return;
@@ -14384,7 +15720,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14384
15720
  const base = textarea?.value ?? "";
14385
15721
  conversation.input.for(actx).setDraft(base === "" ? reference : `${base} ${reference}`);
14386
15722
  textarea?.focus();
14387
- }
15723
+ },
15724
+ collapseSidebar
14388
15725
  })
14389
15726
  }, PendingPanel));
14390
15727
  ctx.slots.inject("settings.section", () => ctx.slots.register({