dsh-diff-approval 0.10.0 → 0.12.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
@@ -643,15 +643,21 @@ window.__ModuleLoader__.load({
643
643
  * Compute the whole-file view between two contents. The context budget is the
644
644
  * side lengths, so every hunk covers the file and unchanged lines survive as
645
645
  * context rows; the `` patch marker is annotation
646
- * and never becomes a row.
646
+ * and never becomes a row. Both sides are line-ending normalized (`\r\n?` →
647
+ * `\n`) first, so a repo baseline stored with one EOL and a worktree with
648
+ * another never show as a whole-file delete+add — the diff is about approved
649
+ * content, not line-ending noise.
647
650
  * @param oldText - the file content before the pending change.
648
651
  * @param newText - the file content after the pending change.
649
652
  * @returns the complete row list with totals.
650
653
  */
651
654
  function computeWholeFileDiff(oldText, newText) {
655
+ oldText = oldText.replace(/\r\n?/g, "\n");
656
+ newText = newText.replace(/\r\n?/g, "\n");
652
657
  const oldLines = contentLines(oldText);
653
658
  const newLines = contentLines(newText);
654
- const patch = structuredPatch("", "", oldText, newText, void 0, void 0, { context: Math.max(1, oldLines.length, newLines.length) });
659
+ const context = Math.max(1, oldLines.length, newLines.length);
660
+ const patch = structuredPatch("", "", oldText, newText, void 0, void 0, { context });
655
661
  const rows = [];
656
662
  let removed = 0;
657
663
  let added = 0;
@@ -706,6 +712,111 @@ window.__ModuleLoader__.load({
706
712
  return (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n");
707
713
  }
708
714
  //#endregion
715
+ //#region lib/types/client/split-diff.js
716
+ /**
717
+ * Side-by-side (split) diff model: the unified whole-file diff rows are
718
+ * regrouped into line-aligned pairs for a two-column "before | current" view.
719
+ * Pure derivation; the view owns rendering. A context row becomes a pair with
720
+ * both sides, a deletion a left-only pair, an addition a right-only pair, and
721
+ * an adjacent deletion/addition run is paired line-by-line into a single
722
+ * replacement pair so the two columns line up.
723
+ * @module dsh-diff-approval/client/split-diff
724
+ */
725
+ /** One whole-file diff row → its visible left side text. */
726
+ function leftSideOf(row) {
727
+ if (row.kind === "add") return void 0;
728
+ return {
729
+ text: row.text,
730
+ line: row.oldLine
731
+ };
732
+ }
733
+ /** One whole-file diff row → its visible right side text. */
734
+ function rightSideOf(row) {
735
+ if (row.kind === "del") return void 0;
736
+ return {
737
+ text: row.text,
738
+ line: row.newLine
739
+ };
740
+ }
741
+ /**
742
+ * Regroup the whole-file rows into aligned split pairs, pairing a deletion run
743
+ * with a following addition run line-by-line (so a replaced line is one pair),
744
+ * and leaving a stray deletion or addition as a one-sided pair.
745
+ * @param rows - the whole-file diff rows.
746
+ * @returns the split pairs plus the row→pair index map.
747
+ */
748
+ function computeSideBySideDiff(rows) {
749
+ const pairs = [];
750
+ const pairOfRow = /* @__PURE__ */ new Map();
751
+ const pendingDel = [];
752
+ const push = (pair, rows) => {
753
+ const index = pairs.length;
754
+ pairs.push(pair);
755
+ for (const r of rows) pairOfRow.set(r, index);
756
+ };
757
+ const flushPending = () => {
758
+ for (const { index, row } of pendingDel) push({
759
+ kind: "del",
760
+ left: leftSideOf(row),
761
+ right: void 0
762
+ }, [index]);
763
+ pendingDel.length = 0;
764
+ };
765
+ for (let i = 0; i < rows.length; i++) {
766
+ const row = rows[i];
767
+ if (row.kind === "context") {
768
+ flushPending();
769
+ push({
770
+ kind: "context",
771
+ left: leftSideOf(row),
772
+ right: rightSideOf(row)
773
+ }, [i]);
774
+ } else if (row.kind === "del") pendingDel.push({
775
+ index: i,
776
+ row
777
+ });
778
+ else {
779
+ const del = pendingDel.shift();
780
+ if (del !== void 0) push({
781
+ kind: "replace",
782
+ left: leftSideOf(del.row),
783
+ right: rightSideOf(row)
784
+ }, [del.index, i]);
785
+ else push({
786
+ kind: "add",
787
+ left: void 0,
788
+ right: rightSideOf(row)
789
+ }, [i]);
790
+ }
791
+ }
792
+ flushPending();
793
+ return {
794
+ pairs,
795
+ pairOfRow
796
+ };
797
+ }
798
+ /**
799
+ * Pair indices whose left or right text contains the query (case-insensitive).
800
+ * A pair counts once however many times the query appears, so split search
801
+ * highlights the whole pair on both columns and a single "current" pair is
802
+ * stepped through — not each individual left/right occurrence.
803
+ * @param pairs - the split pairs.
804
+ * @param query - the query text; an empty query matches nothing.
805
+ * @returns matching pair indices, in file order.
806
+ */
807
+ function searchPairs(pairs, query) {
808
+ if (query === "") return [];
809
+ const lower = query.toLowerCase();
810
+ const matches = [];
811
+ for (let index = 0; index < pairs.length; index++) {
812
+ const p = pairs[index];
813
+ if (p === void 0) continue;
814
+ if (p.left !== void 0 && p.left.text.toLowerCase().includes(lower)) matches.push(index);
815
+ else if (p.right !== void 0 && p.right.text.toLowerCase().includes(lower)) matches.push(index);
816
+ }
817
+ return matches;
818
+ }
819
+ //#endregion
709
820
  //#region node_modules/.pnpm/@shikijs+types@4.4.3/node_modules/@shikijs/types/dist/index.mjs
710
821
  var ShikiError = class extends Error {
711
822
  constructor(message) {
@@ -11625,6 +11736,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11625
11736
  const PASTE_ON_COPY_KEY = "diff-approval:paste-on-copy";
11626
11737
  const IMPORT_UNTRACKED_KEY = "diff-approval:import-untracked";
11627
11738
  const TAB_WIDTH_KEY = "diff-approval:tab-size";
11739
+ const SPLIT_MODE_KEY = "diff-approval:split-mode";
11628
11740
  const WRAP_PREFIX = "diff-approval:wrap:";
11629
11741
  /**
11630
11742
  * Whether copying a reference should also paste it into the chat input and
@@ -11680,9 +11792,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11680
11792
  function setTabWidth(value) {
11681
11793
  localStorage.setItem(TAB_WIDTH_KEY, String(value));
11682
11794
  }
11795
+ /**
11796
+ * Whether the whole-file diff view uses the two-column (side-by-side) layout.
11797
+ * Default off (single column): the unified diff. Only an explicit `'1'` enables
11798
+ * split mode.
11799
+ * @returns whether the split (two-column) diff view is used.
11800
+ */
11801
+ function splitMode() {
11802
+ return localStorage.getItem(SPLIT_MODE_KEY) === "1";
11803
+ }
11804
+ /** Persist the split-view preference. */
11805
+ function setSplitMode(value) {
11806
+ localStorage.setItem(SPLIT_MODE_KEY, value ? "1" : "0");
11807
+ }
11683
11808
  //#endregion
11684
11809
  //#region \0dsh-css:/home/runner/work/dsh-diff-approval/dsh-diff-approval/src/client/PendingPanel.module.css.mjs
11685
- 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))}";
11810
+ 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))}.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)}";
11686
11811
  const tagId = "dsh-diff-approval/PendingPanel.module.css";
11687
11812
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
11688
11813
  const tag = document.createElement("style");
@@ -11692,99 +11817,111 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11692
11817
  document.head.appendChild(tag);
11693
11818
  }
11694
11819
  var PendingPanel_module_css_default = {
11695
- "badge": "F1KBNa_badge",
11696
- "panel": "F1KBNa_panel",
11697
- "settingsRow": "F1KBNa_settingsRow",
11698
- "searchBar": "F1KBNa_searchBar",
11699
- "detail": "F1KBNa_detail",
11700
- "diffActions": "F1KBNa_diffActions",
11701
- "markerAdd": "F1KBNa_markerAdd",
11702
- "diff": "F1KBNa_diff",
11703
- "gutter": "F1KBNa_gutter",
11704
- "missingHint": "F1KBNa_missingHint",
11705
- "wrap": "F1KBNa_wrap",
11706
- "diffHeader": "F1KBNa_diffHeader",
11707
- "context": "F1KBNa_context",
11708
- "importNote": "F1KBNa_importNote",
11709
- "divergedHint": "F1KBNa_divergedHint",
11710
- "listScroll": "F1KBNa_listScroll",
11711
- "statusBar": "F1KBNa_statusBar",
11712
- "readError": "F1KBNa_readError",
11713
- "settingsRowDesc": "F1KBNa_settingsRowDesc",
11714
- "settingsRowText": "F1KBNa_settingsRowText",
11715
- "emptyState": "F1KBNa_emptyState",
11716
- "langLabel": "F1KBNa_langLabel",
11717
- "expandExpanded": "F1KBNa_expandExpanded",
11718
- "noticeText": "F1KBNa_noticeText",
11719
- "addCount": "F1KBNa_addCount",
11720
- "subline": "F1KBNa_subline",
11721
- "badgeCount": "F1KBNa_badgeCount",
11722
- "line": "F1KBNa_line",
11820
+ "layer": "F1KBNa_layer",
11821
+ "rowHead": "F1KBNa_rowHead",
11723
11822
  "header": "F1KBNa_header",
11724
- "close": "F1KBNa_close",
11823
+ "searchCount": "F1KBNa_searchCount",
11824
+ "splitRdel": "F1KBNa_splitRdel",
11725
11825
  "blockActions": "F1KBNa_blockActions",
11726
- "actionError": "F1KBNa_actionError",
11826
+ "badge": "F1KBNa_badge",
11827
+ "fileList": "F1KBNa_fileList",
11828
+ "badgeLabel": "F1KBNa_badgeLabel",
11727
11829
  "rows": "F1KBNa_rows",
11728
- "actionPrimary": "F1KBNa_actionPrimary",
11729
- "searchCount": "F1KBNa_searchCount",
11730
- "wrapActive": "F1KBNa_wrapActive",
11830
+ "settingsRowTitle": "F1KBNa_settingsRowTitle",
11731
11831
  "rail": "F1KBNa_rail",
11732
- "overviewRuler": "F1KBNa_overviewRuler",
11733
- "diffBody": "F1KBNa_diffBody",
11734
- "vSpacer": "F1KBNa_vSpacer",
11832
+ "settingsPage": "F1KBNa_settingsPage",
11833
+ "add": "F1KBNa_add",
11834
+ "splitCols": "F1KBNa_splitCols",
11835
+ "splitHScrollRow": "F1KBNa_splitHScrollRow",
11836
+ "splitHScroll": "F1KBNa_splitHScroll",
11837
+ "diffPath": "F1KBNa_diffPath",
11838
+ "settingsRowText": "F1KBNa_settingsRowText",
11839
+ "detailEmpty": "F1KBNa_detailEmpty",
11840
+ "action": "F1KBNa_action",
11841
+ "notice": "F1KBNa_notice",
11842
+ "splitHScrollFill": "F1KBNa_splitHScrollFill",
11843
+ "splitLdel": "F1KBNa_splitLdel",
11844
+ "diffBodySplit": "F1KBNa_diffBodySplit",
11845
+ "wrap": "F1KBNa_wrap",
11846
+ "row": "F1KBNa_row",
11847
+ "expand": "F1KBNa_expand",
11848
+ "blockFlash": "F1KBNa_blockFlash",
11849
+ "importButton": "F1KBNa_importButton",
11850
+ "del": "F1KBNa_del",
11851
+ "splitRoot": "F1KBNa_splitRoot",
11852
+ "states": "F1KBNa_states",
11853
+ "note": "F1KBNa_note",
11854
+ "panel": "F1KBNa_panel",
11735
11855
  "title": "F1KBNa_title",
11736
- "rowMeta": "F1KBNa_rowMeta",
11856
+ "settingsSelector": "F1KBNa_settingsSelector",
11857
+ "diffBodyWrap": "F1KBNa_diffBodyWrap",
11858
+ "diffBody": "F1KBNa_diffBody",
11859
+ "readError": "F1KBNa_readError",
11860
+ "bulkActions": "F1KBNa_bulkActions",
11861
+ "delCount": "F1KBNa_delCount",
11862
+ "addCount": "F1KBNa_addCount",
11737
11863
  "diffStats": "F1KBNa_diffStats",
11864
+ "close": "F1KBNa_close",
11865
+ "kindHint": "F1KBNa_kindHint",
11738
11866
  "blockPosition": "F1KBNa_blockPosition",
11739
11867
  "searchInput": "F1KBNa_searchInput",
11740
- "bulkActions": "F1KBNa_bulkActions",
11741
- "noteCentered": "F1KBNa_noteCentered",
11742
- "blockFlash": "F1KBNa_blockFlash",
11743
- "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
11744
- "code": "F1KBNa_code",
11745
- "langSelect": "F1KBNa_langSelect",
11746
- "kindTag": "F1KBNa_kindTag",
11747
- "diffFlash": "F1KBNa_diffFlash",
11868
+ "overviewRuler": "F1KBNa_overviewRuler",
11869
+ "vSpacer": "F1KBNa_vSpacer",
11870
+ "context": "F1KBNa_context",
11871
+ "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
11872
+ "gutter": "F1KBNa_gutter",
11873
+ "iconAction": "F1KBNa_iconAction",
11874
+ "statusBar": "F1KBNa_statusBar",
11875
+ "noticeText": "F1KBNa_noticeText",
11876
+ "wrapActive": "F1KBNa_wrapActive",
11877
+ "splitRadd": "F1KBNa_splitRadd",
11878
+ "rowMeta": "F1KBNa_rowMeta",
11879
+ "diffHeader": "F1KBNa_diffHeader",
11748
11880
  "footerButtons": "F1KBNa_footerButtons",
11881
+ "headerActions": "F1KBNa_headerActions",
11882
+ "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
11883
+ "detail": "F1KBNa_detail",
11749
11884
  "missing": "F1KBNa_missing",
11750
- "layer": "F1KBNa_layer",
11751
- "noticeButton": "F1KBNa_noticeButton",
11752
- "fileList": "F1KBNa_fileList",
11753
- "add": "F1KBNa_add",
11754
- "split": "F1KBNa_split",
11755
- "overviewMarker": "F1KBNa_overviewMarker",
11885
+ "markerDel": "F1KBNa_markerDel",
11886
+ "settingsRowDesc": "F1KBNa_settingsRowDesc",
11887
+ "langSelect": "F1KBNa_langSelect",
11888
+ "importNote": "F1KBNa_importNote",
11889
+ "listScroll": "F1KBNa_listScroll",
11890
+ "group": "F1KBNa_group",
11891
+ "divergedHint": "F1KBNa_divergedHint",
11892
+ "splitCol": "F1KBNa_splitCol",
11756
11893
  "resizeHandle": "F1KBNa_resizeHandle",
11757
- "settingsSelector": "F1KBNa_settingsSelector",
11758
- "badgeLabel": "F1KBNa_badgeLabel",
11759
- "fileListFloat": "F1KBNa_fileListFloat",
11760
- "expand": "F1KBNa_expand",
11761
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
11762
- "lines": "F1KBNa_lines",
11763
- "action": "F1KBNa_action",
11894
+ "actionPrimary": "F1KBNa_actionPrimary",
11895
+ "diffFlash": "F1KBNa_diffFlash",
11896
+ "subline": "F1KBNa_subline",
11764
11897
  "statusAction": "F1KBNa_statusAction",
11765
- "notice": "F1KBNa_notice",
11766
- "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
11898
+ "noteCentered": "F1KBNa_noteCentered",
11899
+ "actionError": "F1KBNa_actionError",
11900
+ "lines": "F1KBNa_lines",
11901
+ "hint": "F1KBNa_hint",
11902
+ "diff": "F1KBNa_diff",
11903
+ "markerAdd": "F1KBNa_markerAdd",
11904
+ "code": "F1KBNa_code",
11905
+ "emptyState": "F1KBNa_emptyState",
11906
+ "expandExpanded": "F1KBNa_expandExpanded",
11907
+ "langLabel": "F1KBNa_langLabel",
11908
+ "searchBar": "F1KBNa_searchBar",
11909
+ "badgeCount": "F1KBNa_badgeCount",
11910
+ "diffActions": "F1KBNa_diffActions",
11911
+ "splitDivider": "F1KBNa_splitDivider",
11912
+ "settingsRow": "F1KBNa_settingsRow",
11913
+ "noticeButton": "F1KBNa_noticeButton",
11914
+ "splitLadd": "F1KBNa_splitLadd",
11767
11915
  "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
11768
- "delCount": "F1KBNa_delCount",
11769
- "flexSpacer": "F1KBNa_flexSpacer",
11770
- "markerDel": "F1KBNa_markerDel",
11771
- "note": "F1KBNa_note",
11772
- "states": "F1KBNa_states",
11773
- "rowFailed": "F1KBNa_rowFailed",
11774
- "settingsPage": "F1KBNa_settingsPage",
11775
- "rowHead": "F1KBNa_rowHead",
11776
- "headerActions": "F1KBNa_headerActions",
11777
- "group": "F1KBNa_group",
11778
- "settingsRowTitle": "F1KBNa_settingsRowTitle",
11779
11916
  "rowPath": "F1KBNa_rowPath",
11780
- "detailEmpty": "F1KBNa_detailEmpty",
11781
- "iconAction": "F1KBNa_iconAction",
11782
- "kindHint": "F1KBNa_kindHint",
11783
- "hint": "F1KBNa_hint",
11784
- "importButton": "F1KBNa_importButton",
11785
- "del": "F1KBNa_del",
11786
- "diffPath": "F1KBNa_diffPath",
11787
- "row": "F1KBNa_row"
11917
+ "fileListFloat": "F1KBNa_fileListFloat",
11918
+ "missingHint": "F1KBNa_missingHint",
11919
+ "rowFailed": "F1KBNa_rowFailed",
11920
+ "overviewMarker": "F1KBNa_overviewMarker",
11921
+ "kindTag": "F1KBNa_kindTag",
11922
+ "flexSpacer": "F1KBNa_flexSpacer",
11923
+ "split": "F1KBNa_split",
11924
+ "line": "F1KBNa_line"
11788
11925
  };
11789
11926
  //#endregion
11790
11927
  //#region lib/types/client/PendingPanel.js
@@ -12149,6 +12286,541 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12149
12286
  });
12150
12287
  return blocks;
12151
12288
  }
12289
+ /** One side's line-content for the split view: the highlighted runs or plain text. */
12290
+ function splitSideContent(side, wrapped, runs) {
12291
+ if (side === void 0) return "";
12292
+ const highlighted = runs !== void 0 && runs.length > 0;
12293
+ if (wrapped === void 0) return highlighted ? runs.map((span, i) => (0, react_jsx_runtime.jsx)("span", {
12294
+ style: span.style,
12295
+ children: span.text
12296
+ }, i)) : side.text === "" ? "\xA0" : side.text;
12297
+ let offset = 0;
12298
+ return wrapped.map((line, i) => {
12299
+ const start = offset;
12300
+ offset += line.length;
12301
+ const content = highlighted ? clipRuns(runs, start, offset) : line === "" ? "\xA0" : line;
12302
+ return (0, react_jsx_runtime.jsx)("div", {
12303
+ className: PendingPanel_module_css_default.subline,
12304
+ children: content
12305
+ }, i);
12306
+ });
12307
+ }
12308
+ /**
12309
+ * One side of a split pair row, rendered inside its own column. The two columns
12310
+ * are drawn by two independent `.splitCol` scrollers (each with its own
12311
+ * horizontal scrollbar) that share one vertical scroller, and each row gets the
12312
+ * same fixed `height` (the pair's max of the two sides' wrapped sub-line
12313
+ * counts) so the left/right halves always align on the same Y — no jump when
12314
+ * one side is longer. The gutter and code are top-aligned so sub-lines line up
12315
+ * across the divider.
12316
+ */
12317
+ function SplitSideRow({ index, side, wrapped, runs, kind, isLeft, height, focused, searchHit, searchCurrent, onHover }) {
12318
+ const tint = isLeft ? kind === "del" || kind === "replace" ? PendingPanel_module_css_default.splitLdel : "" : kind === "add" || kind === "replace" ? PendingPanel_module_css_default.splitRadd : "";
12319
+ return (0, react_jsx_runtime.jsxs)("div", {
12320
+ className: PendingPanel_module_css_default.line,
12321
+ style: { height },
12322
+ "data-diff-split-row": true,
12323
+ "data-diff-split-index": index,
12324
+ "data-diff-split-side": isLeft ? "left" : "right",
12325
+ "data-diff-focused": focused ? "" : void 0,
12326
+ "data-diff-search": searchHit ? searchCurrent ? "current" : "hit" : void 0,
12327
+ onMouseEnter: onHover,
12328
+ children: [(0, react_jsx_runtime.jsx)("span", {
12329
+ className: PendingPanel_module_css_default.gutter,
12330
+ children: side?.line ?? ""
12331
+ }), (0, react_jsx_runtime.jsx)("span", {
12332
+ className: `${PendingPanel_module_css_default.code} ${tint}`,
12333
+ "data-diff-code": true,
12334
+ children: splitSideContent(side, wrapped, runs)
12335
+ })]
12336
+ });
12337
+ }
12338
+ /** The two-column (side-by-side) whole-file diff view. */
12339
+ const SplitDiff = (0, react.forwardRef)(function SplitDiff({ file, model, runs, langWrap, tabWidthSpaces, busy, t, onBlockKeep, onBlockRevert }, ref) {
12340
+ const { pairs, pairOfRow } = (0, react.useMemo)(() => computeSideBySideDiff(model.diff.rows), [model]);
12341
+ const pairCount = pairs.length;
12342
+ const bodyRef = (0, react.useRef)(null);
12343
+ const [scrollTop, setScrollTop] = (0, react.useState)(0);
12344
+ const [viewportH, setViewportH] = (0, react.useState)(0);
12345
+ const [bodyWidth, setBodyWidth] = (0, react.useState)(0);
12346
+ const [hoveredBlock, setHoveredBlock] = (0, react.useState)(void 0);
12347
+ const [focus, setFocus] = (0, react.useState)(0);
12348
+ const [flashKey, setFlashKey] = (0, react.useState)(0);
12349
+ const hoveredBlockRef = (0, react.useRef)(void 0);
12350
+ const leftColRef = (0, react.useRef)(null);
12351
+ const rightColRef = (0, react.useRef)(null);
12352
+ const leftHScrollRef = (0, react.useRef)(null);
12353
+ const rightHScrollRef = (0, react.useRef)(null);
12354
+ const [fillWidth, setFillWidth] = (0, react.useState)({
12355
+ left: 0,
12356
+ right: 0
12357
+ });
12358
+ const [searchOpen, setSearchOpen] = (0, react.useState)(false);
12359
+ const [searchQuery, setSearchQuery] = (0, react.useState)("");
12360
+ const [searchIndex, setSearchIndex] = (0, react.useState)(0);
12361
+ const searchInputRef = (0, react.useRef)(null);
12362
+ (0, react.useEffect)(() => {
12363
+ setFocus(0);
12364
+ bodyRef.current?.focus();
12365
+ setFlashKey((k) => k + 1);
12366
+ setHoveredBlock(void 0);
12367
+ setSearchOpen(false);
12368
+ setSearchQuery("");
12369
+ setSearchIndex(0);
12370
+ }, [file.id]);
12371
+ (0, react.useEffect)(() => {
12372
+ const body = bodyRef.current;
12373
+ if (body === null) return;
12374
+ const measure = () => {
12375
+ setViewportH(body.clientHeight);
12376
+ setBodyWidth(body.clientWidth);
12377
+ };
12378
+ measure();
12379
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
12380
+ observer?.observe(body);
12381
+ return () => {
12382
+ observer?.disconnect();
12383
+ };
12384
+ }, [file.id]);
12385
+ const blockOfPair = (0, react.useMemo)(() => model.blocks.map((block) => ({
12386
+ start: pairOfRow.get(block.start) ?? 0,
12387
+ end: pairOfRow.get(block.end) ?? 0
12388
+ })), [model, pairOfRow]);
12389
+ const blockIndexByPair = (0, react.useMemo)(() => {
12390
+ const map = /* @__PURE__ */ new Map();
12391
+ blockOfPair.forEach((block, bi) => {
12392
+ for (let k = block.start; k <= block.end; k++) map.set(k, bi);
12393
+ });
12394
+ return map;
12395
+ }, [blockOfPair]);
12396
+ const onPairHover = (0, react.useCallback)((k) => {
12397
+ const bi = blockIndexByPair.get(k);
12398
+ setHoveredBlock(bi);
12399
+ if (bi !== void 0) hoveredBlockRef.current = bi;
12400
+ }, [blockIndexByPair]);
12401
+ const colWidth = Math.max(0, (bodyWidth - 1) / 2);
12402
+ const pairWrapped = (0, react.useMemo)(() => {
12403
+ if (!langWrap || bodyWidth === 0) return null;
12404
+ const measure = makeMeasurer(codeFontOf());
12405
+ if (measure === void 0) return null;
12406
+ const charWidth = measure("0");
12407
+ const wrapW = colWidth - WRAP_GUTTERS_PX / 2 - charWidth;
12408
+ const tabPx = tabWidthSpaces * measure(" ");
12409
+ return pairs.map((p) => ({
12410
+ left: p.left === void 0 ? void 0 : wrapInto(p.left.text, wrapW, measure, tabPx),
12411
+ right: p.right === void 0 ? void 0 : wrapInto(p.right.text, wrapW, measure, tabPx)
12412
+ }));
12413
+ }, [
12414
+ pairs,
12415
+ langWrap,
12416
+ bodyWidth,
12417
+ colWidth,
12418
+ tabWidthSpaces
12419
+ ]);
12420
+ const pairHeights = (0, react.useMemo)(() => {
12421
+ if (pairWrapped === null) return null;
12422
+ return pairWrapped.map((w) => Math.max(w.left?.length ?? 1, w.right?.length ?? 1) * ROW_HEIGHT_PX);
12423
+ }, [pairWrapped]);
12424
+ const pairOffsets = (0, react.useMemo)(() => {
12425
+ if (pairHeights === null) return null;
12426
+ const offs = new Array(pairHeights.length + 1);
12427
+ offs[0] = 0;
12428
+ for (let i = 0; i < pairHeights.length; i++) offs[i + 1] = offs[i] + pairHeights[i];
12429
+ return offs;
12430
+ }, [pairHeights]);
12431
+ const totalHeight = pairOffsets === null ? pairCount * ROW_HEIGHT_PX : pairOffsets[pairCount] ?? 0;
12432
+ const off = (k) => pairOffsets === null ? k * ROW_HEIGHT_PX : pairOffsets[Math.max(0, Math.min(k, pairCount))] ?? 0;
12433
+ const pairHeightAt = (k) => pairHeights === null ? ROW_HEIGHT_PX : pairHeights[k] ?? ROW_HEIGHT_PX;
12434
+ const widestSide = (0, react.useMemo)(() => {
12435
+ let left = 0;
12436
+ let right = 0;
12437
+ for (const p of pairs) {
12438
+ if (p.left !== void 0) left = Math.max(left, p.left.text.length);
12439
+ if (p.right !== void 0) right = Math.max(right, p.right.text.length);
12440
+ }
12441
+ return {
12442
+ left,
12443
+ right
12444
+ };
12445
+ }, [pairs]);
12446
+ (0, react.useLayoutEffect)(() => {
12447
+ const sync = (side) => {
12448
+ const col = side === "left" ? leftColRef.current : rightColRef.current;
12449
+ const strip = side === "left" ? leftHScrollRef.current : rightHScrollRef.current;
12450
+ if (col === null || strip === null) return;
12451
+ const width = col.scrollWidth;
12452
+ setFillWidth((prev) => prev[side] === width ? prev : {
12453
+ ...prev,
12454
+ [side]: width
12455
+ });
12456
+ strip.scrollLeft = col.scrollLeft;
12457
+ };
12458
+ sync("left");
12459
+ sync("right");
12460
+ }, [
12461
+ pairs,
12462
+ langWrap,
12463
+ tabWidthSpaces,
12464
+ bodyWidth
12465
+ ]);
12466
+ const onHScroll = (0, react.useCallback)((side) => {
12467
+ const strip = side === "left" ? leftHScrollRef.current : rightHScrollRef.current;
12468
+ const col = side === "left" ? leftColRef.current : rightColRef.current;
12469
+ if (strip === null || col === null) return;
12470
+ col.scrollLeft = strip.scrollLeft;
12471
+ }, []);
12472
+ const searchMatches = (0, react.useMemo)(() => searchPairs(pairs, searchQuery), [pairs, searchQuery]);
12473
+ const searchHitSet = (0, react.useMemo)(() => new Set(searchMatches), [searchMatches]);
12474
+ const currentSearchPair = searchMatches.length === 0 ? void 0 : searchMatches[searchIndex % searchMatches.length];
12475
+ const closeSearch = () => {
12476
+ setSearchOpen(false);
12477
+ setSearchQuery("");
12478
+ setSearchIndex(0);
12479
+ bodyRef.current?.focus();
12480
+ };
12481
+ const openSearch = () => {
12482
+ setSearchOpen(true);
12483
+ requestAnimationFrame(() => {
12484
+ searchInputRef.current?.focus();
12485
+ searchInputRef.current?.select();
12486
+ });
12487
+ };
12488
+ const goSearch = (direction) => {
12489
+ const len = searchMatches.length;
12490
+ if (len === 0) return;
12491
+ const next = (searchIndex + direction + len) % len;
12492
+ setSearchIndex(next);
12493
+ const pairIndex = searchMatches[next];
12494
+ if (pairIndex === void 0) return;
12495
+ const body = bodyRef.current;
12496
+ if (body === null) return;
12497
+ const target = Math.max(0, off(pairIndex) - 44);
12498
+ const clamped = Math.max(0, Math.min(target, body.scrollHeight - body.clientHeight));
12499
+ if (body.scrollTop !== clamped) body.scrollTop = clamped;
12500
+ setScrollTop(clamped);
12501
+ };
12502
+ const handleBlockAction = async (action) => {
12503
+ const operated = hoveredBlock ?? hoveredBlockRef.current;
12504
+ if (operated === void 0) return;
12505
+ const range = blockRanges[operated];
12506
+ if (range === void 0) return;
12507
+ await (action === "keep" ? onBlockKeep(file.sessionId, file.id, range) : onBlockRevert(file.sessionId, file.id, range));
12508
+ const count = model.blocks.length;
12509
+ if (count === 0) return;
12510
+ const next = Math.max(0, Math.min(operated, count - 1));
12511
+ setFocus(next);
12512
+ setHoveredBlock(void 0);
12513
+ setFlashKey((key) => key + 1);
12514
+ };
12515
+ const pairAtY = (y) => {
12516
+ if (pairOffsets === null) return Math.floor(y / ROW_HEIGHT_PX);
12517
+ if (y <= 0) return 0;
12518
+ let lo = 0, hi = pairCount;
12519
+ while (lo < hi) {
12520
+ const mid = lo + hi + 1 >> 1;
12521
+ if ((pairOffsets[mid] ?? 0) <= y) lo = mid;
12522
+ else hi = mid - 1;
12523
+ }
12524
+ return lo;
12525
+ };
12526
+ const viewport = viewportH > 0 ? viewportH : totalHeight;
12527
+ const start = Math.max(0, pairAtY(scrollTop) - OVERSCAN_ROWS);
12528
+ const end = Math.min(pairCount, pairAtY(scrollTop + viewport) + OVERSCAN_ROWS);
12529
+ const visiblePairs = pairs.slice(start, end);
12530
+ const jump = (direction) => {
12531
+ if (blockOfPair.length === 0) return;
12532
+ setFocus((current) => {
12533
+ if (direction === -1) return (current - 1 + blockOfPair.length) % blockOfPair.length;
12534
+ const top = bodyRef.current?.scrollTop ?? 0;
12535
+ for (let index = current + 1; index < blockOfPair.length; index++) if (off(blockOfPair[index].start) >= top) return index;
12536
+ return 0;
12537
+ });
12538
+ setFlashKey((k) => k + 1);
12539
+ };
12540
+ (0, react.useImperativeHandle)(ref, () => ({
12541
+ jump,
12542
+ openSearch
12543
+ }), [jump, openSearch]);
12544
+ (0, react.useLayoutEffect)(() => {
12545
+ if (pairCount === 0) return;
12546
+ const block = blockOfPair[focus];
12547
+ if (block === void 0) return;
12548
+ const body = bodyRef.current;
12549
+ if (body === null) return;
12550
+ const target = off(block.start) - 44;
12551
+ const clamped = Math.max(0, Math.min(target, body.scrollHeight - body.clientHeight));
12552
+ if (body.scrollTop !== clamped) body.scrollTop = clamped;
12553
+ setScrollTop(clamped);
12554
+ }, [
12555
+ model,
12556
+ focus,
12557
+ flashKey,
12558
+ pairCount
12559
+ ]);
12560
+ const onScroll = () => {
12561
+ setScrollTop(bodyRef.current?.scrollTop ?? 0);
12562
+ };
12563
+ const inFocused = (k) => {
12564
+ const block = blockOfPair[focus];
12565
+ return block !== void 0 && k >= block.start && k <= block.end;
12566
+ };
12567
+ const blockRanges = (0, react.useMemo)(() => model.blocks.map((block) => blockRangesOf(model.diff.rows, block)), [model]);
12568
+ const focusedBlock = blockOfPair[focus];
12569
+ const flashTop = focusedBlock === void 0 ? 0 : Math.max(0, off(focusedBlock.start) - scrollTop);
12570
+ const flashBottom = focusedBlock === void 0 ? 0 : Math.min(viewportH > 0 ? viewportH : Number.POSITIVE_INFINITY, off(focusedBlock.end + 1) - scrollTop);
12571
+ const flashHeight = Math.max(0, flashBottom - flashTop);
12572
+ 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)));
12573
+ return (0, react_jsx_runtime.jsxs)("div", {
12574
+ className: PendingPanel_module_css_default.splitRoot,
12575
+ onMouseLeave: () => setHoveredBlock(void 0),
12576
+ children: [
12577
+ (0, react_jsx_runtime.jsx)("div", {
12578
+ className: `${PendingPanel_module_css_default.diffBody} ${PendingPanel_module_css_default.diffBodySplit}`,
12579
+ ref: bodyRef,
12580
+ tabIndex: 0,
12581
+ onScroll,
12582
+ style: { tabSize: tabWidthSpaces },
12583
+ "data-diff-body": true,
12584
+ children: (0, react_jsx_runtime.jsxs)("div", {
12585
+ className: PendingPanel_module_css_default.splitCols,
12586
+ children: [
12587
+ (0, react_jsx_runtime.jsx)("div", {
12588
+ className: PendingPanel_module_css_default.splitCol,
12589
+ ref: leftColRef,
12590
+ "data-diff-split-side": "left",
12591
+ children: (0, react_jsx_runtime.jsxs)("div", {
12592
+ className: `${PendingPanel_module_css_default.lines}${langWrap ? " " + PendingPanel_module_css_default.wrap : ""}`,
12593
+ style: langWrap ? void 0 : { minWidth: `max(100%, ${widestSide.left}ch)` },
12594
+ children: [
12595
+ start > 0 && (0, react_jsx_runtime.jsx)("div", {
12596
+ className: PendingPanel_module_css_default.vSpacer,
12597
+ style: { height: off(start) },
12598
+ "aria-hidden": "true"
12599
+ }),
12600
+ visiblePairs.map((pair, offset) => {
12601
+ const index = start + offset;
12602
+ const leftRuns = pair.left === void 0 ? void 0 : runs?.oldRuns?.[(pair.left.line ?? 0) - 1];
12603
+ return (0, react_jsx_runtime.jsx)(SplitSideRow, {
12604
+ index,
12605
+ side: pair.left,
12606
+ wrapped: pairWrapped?.[index]?.left,
12607
+ runs: leftRuns,
12608
+ kind: pair.kind,
12609
+ isLeft: true,
12610
+ height: pairHeightAt(index),
12611
+ focused: inFocused(index),
12612
+ searchHit: searchHitSet.has(index),
12613
+ searchCurrent: index === currentSearchPair,
12614
+ onHover: () => onPairHover(index)
12615
+ }, index);
12616
+ }),
12617
+ end < pairCount && (0, react_jsx_runtime.jsx)("div", {
12618
+ className: PendingPanel_module_css_default.vSpacer,
12619
+ style: { height: totalHeight - off(end) },
12620
+ "aria-hidden": "true"
12621
+ })
12622
+ ]
12623
+ })
12624
+ }),
12625
+ (0, react_jsx_runtime.jsx)("div", { className: PendingPanel_module_css_default.splitDivider }),
12626
+ (0, react_jsx_runtime.jsx)("div", {
12627
+ className: PendingPanel_module_css_default.splitCol,
12628
+ ref: rightColRef,
12629
+ "data-diff-split-side": "right",
12630
+ children: (0, react_jsx_runtime.jsxs)("div", {
12631
+ className: `${PendingPanel_module_css_default.lines}${langWrap ? " " + PendingPanel_module_css_default.wrap : ""}`,
12632
+ style: langWrap ? void 0 : { minWidth: `max(100%, ${widestSide.right}ch)` },
12633
+ children: [
12634
+ start > 0 && (0, react_jsx_runtime.jsx)("div", {
12635
+ className: PendingPanel_module_css_default.vSpacer,
12636
+ style: { height: off(start) },
12637
+ "aria-hidden": "true"
12638
+ }),
12639
+ visiblePairs.map((pair, offset) => {
12640
+ const index = start + offset;
12641
+ const rightRuns = pair.right === void 0 ? void 0 : runs?.newRuns?.[(pair.right.line ?? 0) - 1];
12642
+ return (0, react_jsx_runtime.jsx)(SplitSideRow, {
12643
+ index,
12644
+ side: pair.right,
12645
+ wrapped: pairWrapped?.[index]?.right,
12646
+ runs: rightRuns,
12647
+ kind: pair.kind,
12648
+ isLeft: false,
12649
+ height: pairHeightAt(index),
12650
+ focused: inFocused(index),
12651
+ searchHit: searchHitSet.has(index),
12652
+ searchCurrent: index === currentSearchPair,
12653
+ onHover: () => onPairHover(index)
12654
+ }, index);
12655
+ }),
12656
+ end < pairCount && (0, react_jsx_runtime.jsx)("div", {
12657
+ className: PendingPanel_module_css_default.vSpacer,
12658
+ style: { height: totalHeight - off(end) },
12659
+ "aria-hidden": "true"
12660
+ })
12661
+ ]
12662
+ })
12663
+ })
12664
+ ]
12665
+ })
12666
+ }),
12667
+ (0, react_jsx_runtime.jsxs)("div", {
12668
+ className: PendingPanel_module_css_default.splitHScrollRow,
12669
+ "data-diff-hscroll-row": true,
12670
+ children: [
12671
+ (0, react_jsx_runtime.jsx)("div", {
12672
+ className: PendingPanel_module_css_default.splitHScroll,
12673
+ ref: leftHScrollRef,
12674
+ "data-diff-hscroll": "left",
12675
+ style: {
12676
+ width: colWidth,
12677
+ flex: "none"
12678
+ },
12679
+ onScroll: () => onHScroll("left"),
12680
+ children: (0, react_jsx_runtime.jsx)("div", {
12681
+ className: PendingPanel_module_css_default.splitHScrollFill,
12682
+ style: { width: fillWidth.left || void 0 }
12683
+ })
12684
+ }),
12685
+ (0, react_jsx_runtime.jsx)("div", { className: PendingPanel_module_css_default.splitDivider }),
12686
+ (0, react_jsx_runtime.jsx)("div", {
12687
+ className: PendingPanel_module_css_default.splitHScroll,
12688
+ ref: rightHScrollRef,
12689
+ "data-diff-hscroll": "right",
12690
+ style: {
12691
+ width: colWidth,
12692
+ flex: "none"
12693
+ },
12694
+ onScroll: () => onHScroll("right"),
12695
+ children: (0, react_jsx_runtime.jsx)("div", {
12696
+ className: PendingPanel_module_css_default.splitHScrollFill,
12697
+ style: { width: fillWidth.right || void 0 }
12698
+ })
12699
+ })
12700
+ ]
12701
+ }),
12702
+ searchOpen && (0, react_jsx_runtime.jsxs)("div", {
12703
+ className: PendingPanel_module_css_default.searchBar,
12704
+ "data-diff-searchbar": true,
12705
+ children: [
12706
+ (0, react_jsx_runtime.jsx)("input", {
12707
+ ref: searchInputRef,
12708
+ className: PendingPanel_module_css_default.searchInput,
12709
+ "data-diff-search-input": true,
12710
+ value: searchQuery,
12711
+ placeholder: t("panel.searchPlaceholder"),
12712
+ onChange: (event) => {
12713
+ setSearchQuery(event.target.value);
12714
+ setSearchIndex(0);
12715
+ },
12716
+ onKeyDown: (event) => {
12717
+ if (event.key === "Enter") {
12718
+ event.preventDefault();
12719
+ goSearch(event.shiftKey ? -1 : 1);
12720
+ } else if (event.key === "Escape") closeSearch();
12721
+ }
12722
+ }),
12723
+ (0, react_jsx_runtime.jsx)("span", {
12724
+ className: PendingPanel_module_css_default.searchCount,
12725
+ "data-diff-search-count": true,
12726
+ children: searchMatches.length === 0 ? "0/0" : `${searchIndex % searchMatches.length + 1}/${searchMatches.length}`
12727
+ }),
12728
+ (0, react_jsx_runtime.jsx)("button", {
12729
+ type: "button",
12730
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12731
+ "data-diff-search-prev": true,
12732
+ "aria-label": t("action.prevDiff"),
12733
+ disabled: searchMatches.length === 0,
12734
+ onClick: () => {
12735
+ goSearch(-1);
12736
+ },
12737
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12738
+ }),
12739
+ (0, react_jsx_runtime.jsx)("button", {
12740
+ type: "button",
12741
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12742
+ "data-diff-search-next": true,
12743
+ "aria-label": t("action.nextDiff"),
12744
+ disabled: searchMatches.length === 0,
12745
+ onClick: () => {
12746
+ goSearch(1);
12747
+ },
12748
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12749
+ }),
12750
+ (0, react_jsx_runtime.jsx)("button", {
12751
+ type: "button",
12752
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12753
+ "data-diff-search-close": true,
12754
+ "aria-label": t("action.close"),
12755
+ onClick: closeSearch,
12756
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
12757
+ })
12758
+ ]
12759
+ }),
12760
+ focusedBlock !== void 0 && flashKey > 0 && (0, react_jsx_runtime.jsx)("div", {
12761
+ className: PendingPanel_module_css_default.blockFlash,
12762
+ "data-diff-block-flash": true,
12763
+ style: {
12764
+ top: flashTop,
12765
+ height: flashHeight
12766
+ }
12767
+ }, flashKey),
12768
+ hoveredBlock !== void 0 && blockOfPair[hoveredBlock] !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
12769
+ className: PendingPanel_module_css_default.blockActions,
12770
+ "data-diff-block-actions": true,
12771
+ style: { top: blockActionsTop },
12772
+ children: [
12773
+ (0, react_jsx_runtime.jsx)("span", {
12774
+ className: PendingPanel_module_css_default.blockPosition,
12775
+ "data-diff-block-position": true,
12776
+ children: t("panel.blockPosition", {
12777
+ current: hoveredBlock + 1,
12778
+ total: blockOfPair.length
12779
+ })
12780
+ }),
12781
+ (0, react_jsx_runtime.jsx)("button", {
12782
+ type: "button",
12783
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12784
+ "data-diff-block-prev": true,
12785
+ "aria-label": t("action.prevDiff"),
12786
+ disabled: busy,
12787
+ onClick: () => jump(-1),
12788
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12789
+ }),
12790
+ (0, react_jsx_runtime.jsx)("button", {
12791
+ type: "button",
12792
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12793
+ "data-diff-block-next": true,
12794
+ "aria-label": t("action.nextDiff"),
12795
+ disabled: busy,
12796
+ onClick: () => jump(1),
12797
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12798
+ }),
12799
+ (0, react_jsx_runtime.jsx)("button", {
12800
+ type: "button",
12801
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
12802
+ "data-diff-block-keep": true,
12803
+ disabled: busy,
12804
+ onClick: () => {
12805
+ handleBlockAction("keep");
12806
+ },
12807
+ children: t("action.keep")
12808
+ }),
12809
+ (0, react_jsx_runtime.jsx)("button", {
12810
+ type: "button",
12811
+ className: `${PendingPanel_module_css_default.action}`,
12812
+ "data-diff-block-revert": true,
12813
+ disabled: busy,
12814
+ onClick: () => {
12815
+ handleBlockAction("revert");
12816
+ },
12817
+ children: t("action.revert")
12818
+ })
12819
+ ]
12820
+ })
12821
+ ]
12822
+ });
12823
+ });
12152
12824
  /** The diff-row index containing a node, or undefined. */
12153
12825
  function rowIndexAt(node) {
12154
12826
  if (node === null) return void 0;
@@ -12157,6 +12829,43 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12157
12829
  const index = Number(row.dataset.diffRow);
12158
12830
  return Number.isFinite(index) ? index : void 0;
12159
12831
  }
12832
+ /** The split pair index and which side (left=old, right=new) a node sits in, or undefined. */
12833
+ function splitRowInfoAt(node) {
12834
+ if (node === null) return void 0;
12835
+ const row = (node instanceof Element ? node : node.parentElement)?.closest("[data-diff-split-row]");
12836
+ if (row === null || row === void 0) return void 0;
12837
+ const side = row.dataset.diffSplitSide;
12838
+ if (side !== "left" && side !== "right") return void 0;
12839
+ const index = Number(row.dataset.diffSplitIndex);
12840
+ if (!Number.isFinite(index)) return void 0;
12841
+ return {
12842
+ pairIndex: index,
12843
+ side: side === "left" ? "old" : "new"
12844
+ };
12845
+ }
12846
+ /**
12847
+ * Derive the selected split pair range per side (a left-column selection
12848
+ * references the old file, a right-column selection the new file). A selection
12849
+ * spanning the divider (both sides) references two files, so it is rejected.
12850
+ */
12851
+ function splitRowRangeOf(selection) {
12852
+ if (selection === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
12853
+ const range = selection.getRangeAt(0);
12854
+ const startInfo = splitRowInfoAt(range.startContainer);
12855
+ const endInfo = splitRowInfoAt(range.endContainer);
12856
+ if (startInfo === void 0 || endInfo === void 0) return void 0;
12857
+ if (startInfo.side !== endInfo.side) return void 0;
12858
+ let start = startInfo.pairIndex;
12859
+ let end = endInfo.pairIndex;
12860
+ if (lineOffsetAt(range.startContainer, range.startOffset) >= lineLengthAt(range.startContainer)) start += 1;
12861
+ if (lineOffsetAt(range.endContainer, range.endOffset) === 0) end -= 1;
12862
+ if (start > end) return void 0;
12863
+ return {
12864
+ start,
12865
+ end,
12866
+ side: startInfo.side
12867
+ };
12868
+ }
12160
12869
  /** Character offset of a selection boundary within its line's code text. */
12161
12870
  function lineOffsetAt(node, offset) {
12162
12871
  const code = (node instanceof Element ? node : node.parentElement)?.closest("[data-diff-code]");
@@ -12282,6 +12991,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12282
12991
  setWrapEnabled(wrapKey, next);
12283
12992
  };
12284
12993
  const [tabWidthSpaces] = (0, react.useState)(() => tabWidth());
12994
+ const splitView = splitMode();
12995
+ const splitDiffRef = (0, react.useRef)(null);
12285
12996
  const model = (0, react.useMemo)(() => {
12286
12997
  const diff = computeWholeFileDiff(file.oldText, file.newText);
12287
12998
  return {
@@ -12289,6 +13000,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12289
13000
  blocks: changeBlocksOf(diff)
12290
13001
  };
12291
13002
  }, [file.oldText, file.newText]);
13003
+ const splitPairs = (0, react.useMemo)(() => splitView ? computeSideBySideDiff(model.diff.rows).pairs : null, [splitView, model]);
12292
13004
  const rulerMarkers = (0, react.useMemo)(() => {
12293
13005
  const rows = model.diff.rows;
12294
13006
  const total = rows.length;
@@ -12548,6 +13260,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12548
13260
  setScrollTick((tick) => tick + 1);
12549
13261
  setFlashKey((key) => key + 1);
12550
13262
  };
13263
+ const jumpBlock = (direction) => {
13264
+ if (splitView) {
13265
+ splitDiffRef.current?.jump(direction);
13266
+ return;
13267
+ }
13268
+ jump(direction);
13269
+ };
13270
+ const jumpBlockRef = (0, react.useRef)(jumpBlock);
13271
+ jumpBlockRef.current = jumpBlock;
12551
13272
  const stepBlock = (direction) => {
12552
13273
  const count = model.blocks.length;
12553
13274
  if (count === 0) return;
@@ -12557,9 +13278,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12557
13278
  setScrollTick((tick) => tick + 1);
12558
13279
  setFlashKey((key) => key + 1);
12559
13280
  };
13281
+ const handleBlockAction = async (action) => {
13282
+ if (busy || hoveredBlock === void 0) return;
13283
+ const operated = hoveredBlock;
13284
+ const range = blockRanges[operated];
13285
+ await (action === "keep" ? onBlockKeep(file.sessionId, file.id, range) : onBlockRevert(file.sessionId, file.id, range));
13286
+ const count = model.blocks.length;
13287
+ if (count === 0) return;
13288
+ const next = Math.max(0, Math.min(operated, count - 1));
13289
+ setFocus(next);
13290
+ setHoveredBlock(void 0);
13291
+ setScrollTick((tick) => tick + 1);
13292
+ setFlashKey((key) => key + 1);
13293
+ };
12560
13294
  (0, react.useEffect)(() => {
12561
13295
  if (jumpSignal === 0) return;
12562
- jump(1);
13296
+ jumpBlock(1);
12563
13297
  }, [jumpSignal]);
12564
13298
  const onScroll = () => {
12565
13299
  const body = bodyRef.current;
@@ -12568,15 +13302,27 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12568
13302
  setViewportHeight(body.clientHeight);
12569
13303
  };
12570
13304
  (0, react.useEffect)(() => {
12571
- const update = () => setSelection(rowRangeOf(window.getSelection()));
13305
+ const update = () => setSelection(splitView ? splitRowRangeOf(window.getSelection()) : rowRangeOf(window.getSelection()));
12572
13306
  document.addEventListener("selectionchange", update);
12573
13307
  update();
12574
13308
  return () => {
12575
13309
  document.removeEventListener("selectionchange", update);
12576
13310
  };
12577
- }, [file.id]);
13311
+ }, [file.id, splitView]);
12578
13312
  const selectionReference = (() => {
12579
13313
  if (selection === void 0) return void 0;
13314
+ if (splitView) {
13315
+ if (selection.side === void 0 || splitPairs === null) return void 0;
13316
+ const lineNumbers = [];
13317
+ for (let index = selection.start; index <= selection.end; index++) {
13318
+ const pair = splitPairs[index];
13319
+ if (pair === void 0) continue;
13320
+ const line = selection.side === "old" ? pair.left?.line : pair.right?.line;
13321
+ if (line !== void 0) lineNumbers.push(line);
13322
+ }
13323
+ if (lineNumbers.length === 0) return void 0;
13324
+ return referenceOf(file.path, workspacePath, Math.min(...lineNumbers), Math.max(...lineNumbers));
13325
+ }
12580
13326
  const lineNumbers = model.diff.rows.slice(selection.start, selection.end + 1).map((row) => row.newLine).filter((number) => number !== void 0);
12581
13327
  if (lineNumbers.length === 0) return void 0;
12582
13328
  return referenceOf(file.path, workspacePath, Math.min(...lineNumbers), Math.max(...lineNumbers));
@@ -12612,6 +13358,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12612
13358
  if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
12613
13359
  if (event.key.toLowerCase() !== "f") return;
12614
13360
  event.preventDefault();
13361
+ if (splitView) {
13362
+ splitDiffRef.current?.openSearch();
13363
+ return;
13364
+ }
12615
13365
  setSearchOpen(true);
12616
13366
  searchInputRef.current?.focus();
12617
13367
  searchInputRef.current?.select();
@@ -12621,8 +13371,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12621
13371
  window.removeEventListener("keydown", onKeyDown, true);
12622
13372
  };
12623
13373
  }, []);
12624
- const jumpRef = (0, react.useRef)(jump);
12625
- jumpRef.current = jump;
13374
+ const jumpRef = (0, react.useRef)(jumpBlock);
13375
+ jumpRef.current = jumpBlock;
12626
13376
  (0, react.useEffect)(() => {
12627
13377
  const onKeyDown = (event) => {
12628
13378
  if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
@@ -12730,7 +13480,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12730
13480
  "aria-label": t("action.prevDiff"),
12731
13481
  disabled: busy,
12732
13482
  onClick: () => {
12733
- jump(-1);
13483
+ jumpBlock(-1);
12734
13484
  },
12735
13485
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12736
13486
  })
@@ -12745,7 +13495,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12745
13495
  "aria-label": t("action.nextDiff"),
12746
13496
  disabled: busy,
12747
13497
  onClick: () => {
12748
- jump(1);
13498
+ jumpBlock(1);
12749
13499
  },
12750
13500
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12751
13501
  })
@@ -12795,7 +13545,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12795
13545
  className: PendingPanel_module_css_default.missingHint,
12796
13546
  children: t("panel.missingHint")
12797
13547
  }),
12798
- (0, react_jsx_runtime.jsxs)("div", {
13548
+ splitView ? (0, react_jsx_runtime.jsx)(SplitDiff, {
13549
+ ref: splitDiffRef,
13550
+ file,
13551
+ model,
13552
+ runs,
13553
+ langWrap,
13554
+ tabWidthSpaces,
13555
+ busy,
13556
+ t,
13557
+ onBlockKeep,
13558
+ onBlockRevert
13559
+ }) : (0, react_jsx_runtime.jsxs)("div", {
12799
13560
  className: PendingPanel_module_css_default.diffBodyWrap,
12800
13561
  children: [
12801
13562
  (0, react_jsx_runtime.jsxs)("div", {
@@ -12880,7 +13641,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12880
13641
  "data-diff-block-keep": true,
12881
13642
  disabled: busy,
12882
13643
  onClick: () => {
12883
- onBlockKeep(file.sessionId, file.id, blockRanges[hoveredBlock]);
13644
+ handleBlockAction("keep");
12884
13645
  },
12885
13646
  children: t("action.keep")
12886
13647
  }),
@@ -12890,7 +13651,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12890
13651
  "data-diff-block-revert": true,
12891
13652
  disabled: busy,
12892
13653
  onClick: () => {
12893
- onBlockRevert(file.sessionId, file.id, blockRanges[hoveredBlock]);
13654
+ handleBlockAction("revert");
12894
13655
  },
12895
13656
  children: t("action.revert")
12896
13657
  })
@@ -13727,6 +14488,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13727
14488
  const [includeUntrackedOpen, setIncludeUntrackedOpen] = (0, react.useState)(false);
13728
14489
  const [tab, setTabState] = (0, react.useState)(tabWidth);
13729
14490
  const [tabOpen, setTabOpen] = (0, react.useState)(false);
14491
+ const [split, setSplitState] = (0, react.useState)(splitMode);
14492
+ const [splitOpen, setSplitOpen] = (0, react.useState)(false);
13730
14493
  const setPasteOnCopy = (value) => {
13731
14494
  setPasteOnCopyState(value);
13732
14495
  setPasteOnCopyEnabled(value);
@@ -13739,6 +14502,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13739
14502
  setTabState(value);
13740
14503
  setTabWidth(value);
13741
14504
  };
14505
+ const setSplit = (value) => {
14506
+ setSplitState(value);
14507
+ setSplitMode(value);
14508
+ };
13742
14509
  return (0, react_jsx_runtime.jsxs)("div", {
13743
14510
  className: PendingPanel_module_css_default.settingsPage,
13744
14511
  "data-diff-settings": true,
@@ -13771,6 +14538,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13771
14538
  onOpenChange: setTabOpen,
13772
14539
  onSelect: setTab,
13773
14540
  dataAttribute: "data-diff-tab-width-select"
14541
+ }),
14542
+ (0, react_jsx_runtime.jsx)(PreferenceRow, {
14543
+ title: t("panel.splitMode"),
14544
+ description: t("panel.splitModeDesc"),
14545
+ value: split,
14546
+ open: splitOpen,
14547
+ onOpenChange: setSplitOpen,
14548
+ onSelect: setSplit,
14549
+ dataAttribute: "data-diff-split-mode-select",
14550
+ t
13774
14551
  })
13775
14552
  ]
13776
14553
  });
@@ -14169,8 +14946,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14169
14946
  "panel.pasteOnCopyDesc": "开启后,复制的引用会自动填入消息输入框,输入框会获得焦点。",
14170
14947
  "panel.importUntracked": "导入未跟踪的文件改动",
14171
14948
  "panel.importUntrackedDesc": "开启后,导入工作区改动会包含未跟踪/未版本化的新增文件(Git 未跟踪、SVN 未版本化、Perforce 未版本化)。收集改动需要扫描整个工作区,文件数量大时可能较慢。",
14172
- "panel.tabWidth": "Tab 宽度",
14173
- "panel.tabWidthDesc": "diff 中制表符(Tab)的缩进宽度,可选 2 / 4 / 8 个空格(默认 4),同时作用于行宽折行测量。",
14949
+ "panel.tabWidth": "制表符宽度",
14950
+ "panel.tabWidthDesc": "差异中制表符的缩进宽度,可选 2 / 4 / 8 个空格(默认 4),同时作用于行宽折行测量。",
14951
+ "panel.splitMode": "双栏对比",
14952
+ "panel.splitModeDesc": "开启后整文件差异用左「改前」|右「当前」双栏、逐行对齐的视图展示,默认关闭使用单栏(合并)视图。",
14174
14953
  "settings.tabLabel": "改动审批",
14175
14954
  "row.create": "新增文件",
14176
14955
  "row.failed": "失败",
@@ -14233,7 +15012,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14233
15012
  "panel.importUntracked": "Import changes to untracked files",
14234
15013
  "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.",
14235
15014
  "panel.tabWidth": "Tab width",
14236
- "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.",
15015
+ "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.",
15016
+ "panel.splitMode": "Side-by-side view",
15017
+ "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.",
14237
15018
  "settings.tabLabel": "Diff Approval",
14238
15019
  "row.create": "New file",
14239
15020
  "row.failed": "Failed",
@@ -14284,10 +15065,64 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
14284
15065
  "sessions"
14285
15066
  ];
14286
15067
  /**
15068
+ * The dsh web-react renderer gives `div[data-slot="sidebar.footer.action"]` an
15069
+ * inline `display: contents`, so every plugin's footer entry root participates
15070
+ * directly in the `.footerActions` flex row — several plugins (e.g. this one
15071
+ * plus a file-browser) get crammed into one row and a full-width badge
15072
+ * overflows. Stack the slot entries vertically instead (the same fix the
15073
+ * dsh-footer-order plugin injects), so each plugin gets its own row.
15074
+ *
15075
+ * We defer to the dedicated dsh-footer-order plugin when it is present, so the
15076
+ * two never fight.
15077
+ *
15078
+ * Why we detect it by its injected stylesheet's `--dsh-footer-order-gap` custom
15079
+ * property rather than by `ctx.slots.entriesOfSlot('settings.plugin.item')`
15080
+ * (where footer-order registers its settings card, id `footer-order`): that slot
15081
+ * key is not in our SlotMap type (would need a cast) and, more importantly, it
15082
+ * may not be an active slot in this dsh version at all — footer-order targets a
15083
+ * newer dsh settings API, so the detection could miss footer-order and we would
15084
+ * then both inject, fighting over the same `!important` rule. The CSS marker is
15085
+ * footer-order's own, and its presence is exactly "footer-order's rule is really
15086
+ * applied", which is what decides the layout. So only when the marker is absent
15087
+ * do we inject our own rule.
15088
+ */
15089
+ function injectFooterStackStyle() {
15090
+ if (typeof document === "undefined") return;
15091
+ if (document.querySelector("style[data-diff-approval-footer-stack]") !== null) return;
15092
+ const CSS = "div[data-slot=\"sidebar.footer.action\"]{display:flex!important;flex-direction:column!important;flex:1 1 auto!important;align-items:stretch!important;}";
15093
+ const footerOrderInstalled = () => {
15094
+ for (const style of document.querySelectorAll("style")) if ((style.textContent ?? "").includes("--dsh-footer-order-gap")) return true;
15095
+ return false;
15096
+ };
15097
+ const inject = () => {
15098
+ if (document.querySelector("style[data-diff-approval-footer-stack]") !== null) return;
15099
+ if (footerOrderInstalled()) return;
15100
+ const style = document.createElement("style");
15101
+ style.setAttribute("data-diff-approval-footer-stack", "");
15102
+ style.textContent = CSS;
15103
+ document.head.appendChild(style);
15104
+ };
15105
+ if (document.querySelector("div[data-slot=\"sidebar.footer.action\"]") !== null) {
15106
+ inject();
15107
+ return;
15108
+ }
15109
+ const observer = new MutationObserver(() => {
15110
+ if (document.querySelector("div[data-slot=\"sidebar.footer.action\"]") !== null) {
15111
+ observer.disconnect();
15112
+ inject();
15113
+ }
15114
+ });
15115
+ observer.observe(document.documentElement, {
15116
+ childList: true,
15117
+ subtree: true
15118
+ });
15119
+ }
15120
+ /**
14287
15121
  * Mount the pending-edit review panel.
14288
15122
  * @param ctx - Client Cordis context carrying the wire and slot services.
14289
15123
  */
14290
15124
  function apply(ctx) {
15125
+ injectFooterStackStyle();
14291
15126
  ctx.effect(() => ctx.locale.register(NS, {
14292
15127
  zh,
14293
15128
  en