dsh-diff-approval 0.6.0 → 0.8.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
@@ -11620,8 +11620,39 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11620
11620
  return `${referencePathOf(path, workspacePath)}:${lineRangeLabel(start, end)}`;
11621
11621
  }
11622
11622
  //#endregion
11623
+ //#region lib/types/client/settings.js
11624
+ /** Client preferences for the review panel, persisted in localStorage. */
11625
+ const PASTE_ON_COPY_KEY = "diff-approval:paste-on-copy";
11626
+ const IMPORT_UNTRACKED_KEY = "diff-approval:import-untracked";
11627
+ /**
11628
+ * Whether copying a reference should also paste it into the chat input and
11629
+ * focus it. Defaults to on; only an explicit `'0'` disables it.
11630
+ * @returns whether auto-paste is enabled.
11631
+ */
11632
+ function pasteOnCopyEnabled() {
11633
+ return localStorage.getItem(PASTE_ON_COPY_KEY) !== "0";
11634
+ }
11635
+ /** Persist the auto-paste preference. */
11636
+ function setPasteOnCopyEnabled(value) {
11637
+ localStorage.setItem(PASTE_ON_COPY_KEY, value ? "1" : "0");
11638
+ }
11639
+ /**
11640
+ * Whether importing workspace VCS changes includes new/untracked files (git
11641
+ * `??`, svn `?`, p4 unversioned). Defaults to off: collecting them scans the
11642
+ * whole workspace, which can be slow on large trees, so the preference is
11643
+ * opt-in. Only an explicit `'1'` enables it.
11644
+ * @returns whether untracked files are imported.
11645
+ */
11646
+ function includeUntrackedEnabled() {
11647
+ return localStorage.getItem(IMPORT_UNTRACKED_KEY) === "1";
11648
+ }
11649
+ /** Persist the import-untracked preference. */
11650
+ function setIncludeUntrackedEnabled(value) {
11651
+ localStorage.setItem(IMPORT_UNTRACKED_KEY, value ? "1" : "0");
11652
+ }
11653
+ //#endregion
11623
11654
  //#region \0dsh-css:/home/runner/work/dsh-diff-approval/dsh-diff-approval/src/client/PendingPanel.module.css.mjs
11624
- const css = ".F1KBNa_layer{box-sizing:border-box;flex:none;align-items:center;width:calc(100% + 8px);height:42px;margin:4px -4px;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_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_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_states{flex:1;min-height:0;padding:4px 12px 12px;overflow-y:auto}.F1KBNa_split{flex:1;align-items:stretch;min-height:0;display:flex}.F1KBNa_fileList{box-sizing:border-box;border-right:1px solid var(--dsw-alias-border-l2);flex:none;width:240px;min-height:0;padding:4px 8px 12px;overflow-y:auto}.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_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_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_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;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:4px;padding:4px;display:flex;position:absolute;right:6px}.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;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}";
11655
+ 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}.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;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}";
11625
11656
  const tagId = "dsh-diff-approval/PendingPanel.module.css";
11626
11657
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
11627
11658
  const tag = document.createElement("style");
@@ -11631,69 +11662,92 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11631
11662
  document.head.appendChild(tag);
11632
11663
  }
11633
11664
  var PendingPanel_module_css_default = {
11634
- "diffHeader": "F1KBNa_diffHeader",
11635
- "missing": "F1KBNa_missing",
11636
- "resizeHandle": "F1KBNa_resizeHandle",
11637
- "diffBody": "F1KBNa_diffBody",
11638
- "rowPath": "F1KBNa_rowPath",
11639
11665
  "expandExpanded": "F1KBNa_expandExpanded",
11640
- "line": "F1KBNa_line",
11641
- "headerActions": "F1KBNa_headerActions",
11642
- "states": "F1KBNa_states",
11666
+ "panel": "F1KBNa_panel",
11667
+ "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
11668
+ "settingsPage": "F1KBNa_settingsPage",
11669
+ "hint": "F1KBNa_hint",
11670
+ "row": "F1KBNa_row",
11671
+ "title": "F1KBNa_title",
11672
+ "blockActions": "F1KBNa_blockActions",
11673
+ "langLabel": "F1KBNa_langLabel",
11643
11674
  "detailEmpty": "F1KBNa_detailEmpty",
11644
- "markerDel": "F1KBNa_markerDel",
11675
+ "del": "F1KBNa_del",
11676
+ "listScroll": "F1KBNa_listScroll",
11645
11677
  "close": "F1KBNa_close",
11646
- "markerAdd": "F1KBNa_markerAdd",
11647
- "statusBar": "F1KBNa_statusBar",
11648
- "diffStats": "F1KBNa_diffStats",
11649
- "footerButtons": "F1KBNa_footerButtons",
11650
- "title": "F1KBNa_title",
11651
- "row": "F1KBNa_row",
11652
- "delCount": "F1KBNa_delCount",
11653
- "expand": "F1KBNa_expand",
11654
- "layer": "F1KBNa_layer",
11678
+ "group": "F1KBNa_group",
11655
11679
  "diffActions": "F1KBNa_diffActions",
11656
- "context": "F1KBNa_context",
11657
- "langSelect": "F1KBNa_langSelect",
11658
- "kindTag": "F1KBNa_kindTag",
11659
- "langLabel": "F1KBNa_langLabel",
11660
- "flexSpacer": "F1KBNa_flexSpacer",
11680
+ "diffBodyWrap": "F1KBNa_diffBodyWrap",
11681
+ "states": "F1KBNa_states",
11682
+ "searchInput": "F1KBNa_searchInput",
11683
+ "settingsRowText": "F1KBNa_settingsRowText",
11684
+ "note": "F1KBNa_note",
11685
+ "addCount": "F1KBNa_addCount",
11686
+ "layer": "F1KBNa_layer",
11687
+ "diff": "F1KBNa_diff",
11661
11688
  "add": "F1KBNa_add",
11662
- "badgeLabel": "F1KBNa_badgeLabel",
11663
- "diffPath": "F1KBNa_diffPath",
11689
+ "overviewMarker": "F1KBNa_overviewMarker",
11690
+ "settingsRowDesc": "F1KBNa_settingsRowDesc",
11691
+ "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
11692
+ "searchCount": "F1KBNa_searchCount",
11664
11693
  "badgeCount": "F1KBNa_badgeCount",
11665
- "rows": "F1KBNa_rows",
11694
+ "context": "F1KBNa_context",
11695
+ "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
11696
+ "kindHint": "F1KBNa_kindHint",
11697
+ "footerButtons": "F1KBNa_footerButtons",
11698
+ "diffHeader": "F1KBNa_diffHeader",
11699
+ "resizeHandle": "F1KBNa_resizeHandle",
11700
+ "statusBar": "F1KBNa_statusBar",
11701
+ "rowPath": "F1KBNa_rowPath",
11702
+ "settingsSelector": "F1KBNa_settingsSelector",
11703
+ "importButton": "F1KBNa_importButton",
11704
+ "markerAdd": "F1KBNa_markerAdd",
11705
+ "header": "F1KBNa_header",
11706
+ "settingsRowTitle": "F1KBNa_settingsRowTitle",
11707
+ "emptyState": "F1KBNa_emptyState",
11666
11708
  "rail": "F1KBNa_rail",
11667
- "lines": "F1KBNa_lines",
11668
- "hint": "F1KBNa_hint",
11669
- "code": "F1KBNa_code",
11670
- "detail": "F1KBNa_detail",
11671
- "readError": "F1KBNa_readError",
11709
+ "diffFlash": "F1KBNa_diffFlash",
11672
11710
  "action": "F1KBNa_action",
11673
- "header": "F1KBNa_header",
11711
+ "detail": "F1KBNa_detail",
11712
+ "rowMeta": "F1KBNa_rowMeta",
11713
+ "actionError": "F1KBNa_actionError",
11714
+ "diffStats": "F1KBNa_diffStats",
11715
+ "flexSpacer": "F1KBNa_flexSpacer",
11716
+ "diffBody": "F1KBNa_diffBody",
11674
11717
  "statusAction": "F1KBNa_statusAction",
11675
- "panel": "F1KBNa_panel",
11676
- "overviewMarker": "F1KBNa_overviewMarker",
11677
- "blockActions": "F1KBNa_blockActions",
11678
- "iconAction": "F1KBNa_iconAction",
11679
- "rowHead": "F1KBNa_rowHead",
11718
+ "readError": "F1KBNa_readError",
11719
+ "kindTag": "F1KBNa_kindTag",
11720
+ "delCount": "F1KBNa_delCount",
11721
+ "blockPosition": "F1KBNa_blockPosition",
11722
+ "overviewRuler": "F1KBNa_overviewRuler",
11723
+ "rowFailed": "F1KBNa_rowFailed",
11724
+ "headerActions": "F1KBNa_headerActions",
11725
+ "divergedHint": "F1KBNa_divergedHint",
11726
+ "code": "F1KBNa_code",
11727
+ "noteCentered": "F1KBNa_noteCentered",
11728
+ "expand": "F1KBNa_expand",
11680
11729
  "vSpacer": "F1KBNa_vSpacer",
11681
- "missingHint": "F1KBNa_missingHint",
11682
- "note": "F1KBNa_note",
11683
- "gutter": "F1KBNa_gutter",
11730
+ "importNote": "F1KBNa_importNote",
11731
+ "settingsRow": "F1KBNa_settingsRow",
11684
11732
  "split": "F1KBNa_split",
11685
- "divergedHint": "F1KBNa_divergedHint",
11686
- "overviewRuler": "F1KBNa_overviewRuler",
11687
- "rowMeta": "F1KBNa_rowMeta",
11688
- "addCount": "F1KBNa_addCount",
11689
- "diff": "F1KBNa_diff",
11690
- "fileList": "F1KBNa_fileList",
11733
+ "missing": "F1KBNa_missing",
11691
11734
  "actionPrimary": "F1KBNa_actionPrimary",
11692
- "kindHint": "F1KBNa_kindHint",
11693
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
11694
- "del": "F1KBNa_del",
11735
+ "blockFlash": "F1KBNa_blockFlash",
11736
+ "langSelect": "F1KBNa_langSelect",
11695
11737
  "badge": "F1KBNa_badge",
11696
- "group": "F1KBNa_group"
11738
+ "diffPath": "F1KBNa_diffPath",
11739
+ "iconAction": "F1KBNa_iconAction",
11740
+ "markerDel": "F1KBNa_markerDel",
11741
+ "bulkActions": "F1KBNa_bulkActions",
11742
+ "gutter": "F1KBNa_gutter",
11743
+ "rowHead": "F1KBNa_rowHead",
11744
+ "searchBar": "F1KBNa_searchBar",
11745
+ "fileList": "F1KBNa_fileList",
11746
+ "lines": "F1KBNa_lines",
11747
+ "rows": "F1KBNa_rows",
11748
+ "missingHint": "F1KBNa_missingHint",
11749
+ "line": "F1KBNa_line",
11750
+ "badgeLabel": "F1KBNa_badgeLabel"
11697
11751
  };
11698
11752
  //#endregion
11699
11753
  //#region lib/types/client/PendingPanel.js
@@ -11727,25 +11781,55 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11727
11781
  const MAX_LIST_WIDTH_PX = 560;
11728
11782
  /** Fixed diff-row height in px; the virtual window and jump math are built on it. */
11729
11783
  const ROW_HEIGHT_PX = 22;
11784
+ /** The overview ruler's width in px (mirrors `.overviewRuler`). The flash is
11785
+ * kept off it even when the scroller has no vertical scrollbar. */
11786
+ const OVERVIEW_RULER_WIDTH_PX = 4;
11730
11787
  /** Rows rendered beyond the visible window in each direction. */
11731
11788
  const OVERSCAN_ROWS = 8;
11789
+ /** Height of the floating per-block Keep/Revert frame in px: 26px actions +
11790
+ * 5px frame padding on each side + 1px border on each side, plus a little
11791
+ * breathing room so the bottom padding never sits flush against it. */
11792
+ const BLOCK_ACTIONS_FRAME_PX = 40;
11793
+ /** How long a fully-processed list stays open before the panel folds, so the
11794
+ * last action (a Keep-all/Revert-all especially) can still be undone with
11795
+ * Ctrl+Z. An undo that restores an entry cancels the pending close. */
11796
+ const EMPTY_CLOSE_GRACE_MS = 3e3;
11732
11797
  /** The trailing file-name segment of a path, used for row display. */
11733
11798
  function basenameOf(path) {
11734
11799
  const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
11735
11800
  return index < 0 ? path : path.slice(index + 1);
11736
11801
  }
11802
+ /**
11803
+ * Open the DSH settings dialog and switch to this plugin's section. The
11804
+ * settings shell keeps its open state and the active section id as
11805
+ * component-local viewing state with no cross-plugin service, so the dialog
11806
+ * is opened by clicking the sidebar's settings trigger and then the nav cell
11807
+ * for this section. `element.click()` still fires React's delegated click
11808
+ * handlers, reaching the same state transitions a user gesture would.
11809
+ * @param sectionLabel - the nav label of this plugin's settings section.
11810
+ */
11811
+ function openSettingsSection(sectionLabel) {
11812
+ const trigger = document.querySelector("button[aria-haspopup=\"dialog\"]");
11813
+ if (trigger === null) return;
11814
+ trigger.click();
11815
+ requestAnimationFrame(() => {
11816
+ Array.from(document.querySelectorAll("button")).find((button) => button.textContent?.trim() === sectionLabel)?.click();
11817
+ });
11818
+ }
11737
11819
  /** The diff body's row class per line kind. */
11738
11820
  const ROW_CLASS = {
11739
11821
  context: PendingPanel_module_css_default.context,
11740
11822
  del: PendingPanel_module_css_default.del,
11741
11823
  add: PendingPanel_module_css_default.add
11742
11824
  };
11825
+ /** Empty failure map reused as the snapshot's canonical absent value. */
11826
+ const EMPTY_FAILED_MAP = /* @__PURE__ */ new Map();
11743
11827
  /**
11744
11828
  * One rendered diff row, memoized so a poll or an unrelated state change
11745
11829
  * does not re-render rows whose content, highlight, and focus are unchanged.
11746
11830
  */
11747
11831
  const DiffRow = (0, react.memo)(function DiffRow(props) {
11748
- const { index, row, runs, focused, onRowHover } = props;
11832
+ const { index, row, runs, focused, searchHit, searchCurrent, onRowHover } = props;
11749
11833
  const lineNumber = row.kind === "del" ? row.oldLine : row.newLine;
11750
11834
  const sideRuns = row.kind === "del" ? runs?.oldRuns : runs?.newRuns;
11751
11835
  const lineRuns = lineNumber === void 0 ? void 0 : sideRuns?.[lineNumber - 1];
@@ -11758,6 +11842,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11758
11842
  "data-diff-line": row.kind,
11759
11843
  "data-diff-row": index,
11760
11844
  "data-diff-focused": focused ? "" : void 0,
11845
+ "data-diff-search": searchHit ? searchCurrent ? "current" : "hit" : void 0,
11761
11846
  onMouseEnter: () => {
11762
11847
  onRowHover(index);
11763
11848
  },
@@ -11900,7 +11985,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11900
11985
  };
11901
11986
  }
11902
11987
  /** One row of the file list: the clickable head in the left pane. */
11903
- function PendingFileRow({ file, selected, t, onSelect }) {
11988
+ function PendingFileRow({ file, selected, failedMessage, t, onSelect }) {
11904
11989
  const stats = (0, react.useMemo)(() => computeWholeFileDiff(file.oldText, file.newText), [file.oldText, file.newText]);
11905
11990
  return (0, react_jsx_runtime.jsx)("li", {
11906
11991
  className: PendingPanel_module_css_default.row,
@@ -11929,6 +12014,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11929
12014
  title: t("panel.missingHint"),
11930
12015
  children: t("panel.missing")
11931
12016
  }),
12017
+ failedMessage !== void 0 && (0, react_jsx_runtime.jsx)("span", {
12018
+ className: PendingPanel_module_css_default.rowFailed,
12019
+ title: failedMessage,
12020
+ children: t("row.failed")
12021
+ }),
11932
12022
  (0, react_jsx_runtime.jsxs)("span", {
11933
12023
  className: PendingPanel_module_css_default.rowMeta,
11934
12024
  children: [(0, react_jsx_runtime.jsx)("span", {
@@ -11945,7 +12035,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11945
12035
  });
11946
12036
  }
11947
12037
  /** The selected file's diff, actions, jump controls, and copy toolbar. */
11948
- function PendingDiff({ file, busy, workspacePath, t, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen }) {
12038
+ function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, failedMessage, onPasteReference, t, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen }) {
11949
12039
  const [langOverride, setLangOverride] = (0, react.useState)(void 0);
11950
12040
  const [langMenuOpen, setLangMenuOpen] = (0, react.useState)(false);
11951
12041
  const langMenuItems = (0, react.useMemo)(() => [{
@@ -12026,19 +12116,76 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12026
12116
  const [hoveredBlock, setHoveredBlock] = (0, react.useState)(void 0);
12027
12117
  const [selection, setSelection] = (0, react.useState)(void 0);
12028
12118
  const [copied, setCopied] = (0, react.useState)(false);
12119
+ const [searchOpen, setSearchOpen] = (0, react.useState)(false);
12120
+ const [searchQuery, setSearchQuery] = (0, react.useState)("");
12121
+ const [searchIndex, setSearchIndex] = (0, react.useState)(0);
12122
+ const searchInputRef = (0, react.useRef)(null);
12123
+ const [flashKey, setFlashKey] = (0, react.useState)(0);
12029
12124
  (0, react.useEffect)(() => {
12030
12125
  setFocus(0);
12031
12126
  setScrollTop(0);
12032
12127
  if (bodyRef.current !== null) bodyRef.current.scrollTop = 0;
12128
+ bodyRef.current?.focus();
12129
+ setFlashKey((key) => key + 1);
12033
12130
  setHoveredBlock(void 0);
12034
12131
  setSelection(void 0);
12035
12132
  setLangOverride(void 0);
12036
12133
  setLangMenuOpen(false);
12037
12134
  setCopied(false);
12135
+ setSearchOpen(false);
12136
+ setSearchQuery("");
12137
+ setSearchIndex(0);
12038
12138
  }, [file.id]);
12139
+ (0, react.useEffect)(() => {
12140
+ if (undoFlash === 0) return;
12141
+ setFocus(0);
12142
+ setScrollTick((tick) => tick + 1);
12143
+ setFlashKey((key) => key + 1);
12144
+ }, [undoFlash]);
12039
12145
  const blockRanges = (0, react.useMemo)(() => {
12040
12146
  return model.blocks.map((block) => blockRangesOf(model.diff.rows, block));
12041
12147
  }, [model]);
12148
+ const searchMatches = (0, react.useMemo)(() => {
12149
+ if (searchQuery === "") return [];
12150
+ const lower = searchQuery.toLowerCase();
12151
+ const matches = [];
12152
+ for (let index = 0; index < model.diff.rows.length; index++) if (model.diff.rows[index].text.toLowerCase().includes(lower)) matches.push(index);
12153
+ return matches;
12154
+ }, [model, searchQuery]);
12155
+ const searchHitSet = (0, react.useMemo)(() => new Set(searchMatches), [searchMatches]);
12156
+ const currentSearchRow = searchMatches.length === 0 ? void 0 : searchMatches[searchIndex % searchMatches.length];
12157
+ const goSearch = (direction) => {
12158
+ if (searchMatches.length === 0) return;
12159
+ setSearchIndex((current) => (current + direction + searchMatches.length) % searchMatches.length);
12160
+ };
12161
+ const toggleSearch = () => {
12162
+ if (searchOpen) {
12163
+ setSearchOpen(false);
12164
+ setSearchQuery("");
12165
+ setSearchIndex(0);
12166
+ } else setSearchOpen(true);
12167
+ };
12168
+ const closeSearch = () => {
12169
+ setSearchOpen(false);
12170
+ setSearchQuery("");
12171
+ setSearchIndex(0);
12172
+ };
12173
+ (0, react.useEffect)(() => {
12174
+ if (searchOpen) searchInputRef.current?.focus();
12175
+ }, [searchOpen]);
12176
+ (0, react.useEffect)(() => {
12177
+ setSearchIndex(0);
12178
+ }, [searchQuery, file.id]);
12179
+ (0, react.useLayoutEffect)(() => {
12180
+ const row = currentSearchRow;
12181
+ if (row === void 0) return;
12182
+ const body = bodyRef.current;
12183
+ if (body === null) return;
12184
+ const target = row * ROW_HEIGHT_PX + ROW_HEIGHT_PX / 2 - body.clientHeight / 2;
12185
+ const clamped = Math.max(0, Math.min(target, body.scrollHeight - body.clientHeight));
12186
+ if (body.scrollTop !== clamped) body.scrollTop = clamped;
12187
+ setScrollTop(clamped);
12188
+ }, [currentSearchRow, searchMatches]);
12042
12189
  const blockIndexByRow = (0, react.useMemo)(() => {
12043
12190
  const map = /* @__PURE__ */ new Map();
12044
12191
  model.blocks.forEach((block, blockIndex) => {
@@ -12055,6 +12202,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12055
12202
  const start = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT_PX) - OVERSCAN_ROWS);
12056
12203
  const end = Math.min(rowCount, Math.ceil((scrollTop + viewport) / ROW_HEIGHT_PX) + OVERSCAN_ROWS);
12057
12204
  const visibleRows = rows.slice(start, end);
12205
+ const hoveredBlockEnd = hoveredBlock !== void 0 ? model.blocks[hoveredBlock]?.end : void 0;
12206
+ const blockActionsPadPx = hoveredBlockEnd === void 0 ? 0 : Math.max(0, (hoveredBlockEnd + 1) * ROW_HEIGHT_PX + BLOCK_ACTIONS_FRAME_PX - rowCount * ROW_HEIGHT_PX);
12058
12207
  const widestLine = (0, react.useMemo)(() => {
12059
12208
  let widest = 0;
12060
12209
  for (const row of model.diff.rows) if (row.text.length > widest) widest = row.text.length;
@@ -12073,7 +12222,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12073
12222
  observer?.disconnect();
12074
12223
  };
12075
12224
  }, [file.id]);
12076
- (0, react.useEffect)(() => {
12225
+ (0, react.useLayoutEffect)(() => {
12077
12226
  if (rowCount === 0) return;
12078
12227
  const block = model.blocks[focus];
12079
12228
  if (block === void 0) return;
@@ -12102,7 +12251,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12102
12251
  return 0;
12103
12252
  });
12104
12253
  setScrollTick((tick) => tick + 1);
12254
+ setFlashKey((key) => key + 1);
12105
12255
  };
12256
+ (0, react.useEffect)(() => {
12257
+ if (jumpSignal === 0) return;
12258
+ jump(1);
12259
+ }, [jumpSignal]);
12106
12260
  const onScroll = () => {
12107
12261
  const body = bodyRef.current;
12108
12262
  if (body === null) return;
@@ -12127,10 +12281,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12127
12281
  if (selectionReference === void 0) return;
12128
12282
  if (!await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(selectionReference)) return;
12129
12283
  setCopied(true);
12284
+ if (pasteOnCopyEnabled()) onPasteReference(file.sessionId, selectionReference);
12130
12285
  window.setTimeout(() => {
12131
12286
  setCopied(false);
12132
12287
  }, 1500);
12133
- }, [selectionReference]);
12288
+ }, [
12289
+ file.sessionId,
12290
+ onPasteReference,
12291
+ selectionReference
12292
+ ]);
12134
12293
  (0, react.useEffect)(() => {
12135
12294
  const onKeyDown = (event) => {
12136
12295
  if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
@@ -12139,13 +12298,50 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12139
12298
  event.preventDefault();
12140
12299
  copySelection();
12141
12300
  };
12142
- document.addEventListener("keydown", onKeyDown);
12301
+ window.addEventListener("keydown", onKeyDown, true);
12143
12302
  return () => {
12144
- document.removeEventListener("keydown", onKeyDown);
12303
+ window.removeEventListener("keydown", onKeyDown, true);
12145
12304
  };
12146
12305
  }, [copySelection]);
12306
+ (0, react.useEffect)(() => {
12307
+ const onKeyDown = (event) => {
12308
+ if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
12309
+ if (event.key.toLowerCase() !== "f") return;
12310
+ event.preventDefault();
12311
+ setSearchOpen(true);
12312
+ searchInputRef.current?.focus();
12313
+ searchInputRef.current?.select();
12314
+ };
12315
+ window.addEventListener("keydown", onKeyDown, true);
12316
+ return () => {
12317
+ window.removeEventListener("keydown", onKeyDown, true);
12318
+ };
12319
+ }, []);
12320
+ const jumpRef = (0, react.useRef)(jump);
12321
+ jumpRef.current = jump;
12322
+ (0, react.useEffect)(() => {
12323
+ const onKeyDown = (event) => {
12324
+ if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
12325
+ const key = event.key.toLowerCase();
12326
+ if (key !== "arrowup" && key !== "arrowdown") return;
12327
+ const target = event.target;
12328
+ if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
12329
+ event.preventDefault();
12330
+ jumpRef.current(key === "arrowup" ? -1 : 1);
12331
+ };
12332
+ window.addEventListener("keydown", onKeyDown, true);
12333
+ return () => {
12334
+ window.removeEventListener("keydown", onKeyDown, true);
12335
+ };
12336
+ }, []);
12147
12337
  const focusedBlock = model.blocks.length > 0 ? model.blocks[focus] : void 0;
12148
12338
  const inFocusedBlock = (index) => focusedBlock !== void 0 && index >= focusedBlock.start && index <= focusedBlock.end;
12339
+ const flashWidth = (() => {
12340
+ const scroller = bodyRef.current;
12341
+ if (scroller === null) return 0;
12342
+ const scrollbarWidth = scroller.offsetWidth - scroller.clientWidth;
12343
+ return Math.max(0, scroller.clientWidth - (scrollbarWidth > 0 ? 0 : OVERVIEW_RULER_WIDTH_PX));
12344
+ })();
12149
12345
  return (0, react_jsx_runtime.jsxs)("div", {
12150
12346
  className: PendingPanel_module_css_default.diff,
12151
12347
  "data-diff-approval-diff": true,
@@ -12234,105 +12430,211 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12234
12430
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12235
12431
  })
12236
12432
  })] }),
12433
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12434
+ label: t("action.search"),
12435
+ side: "bottom",
12436
+ delayMs: 500,
12437
+ children: (0, react_jsx_runtime.jsx)("button", {
12438
+ type: "button",
12439
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12440
+ "data-diff-search-toggle": true,
12441
+ "aria-label": t("action.search"),
12442
+ onClick: toggleSearch,
12443
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 })
12444
+ })
12445
+ }),
12237
12446
  (0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
12238
12447
  (0, react_jsx_runtime.jsx)("button", {
12239
12448
  type: "button",
12240
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
12449
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary} ${PendingPanel_module_css_default.actionQuietDisabled}`,
12241
12450
  "data-diff-keep": true,
12242
12451
  disabled: busy,
12243
12452
  onClick: () => {
12244
12453
  onKeep(file.sessionId, file.id);
12245
12454
  },
12246
- children: busy ? t("action.busy") : t("action.keep")
12455
+ children: t("action.keep")
12247
12456
  }),
12248
12457
  (0, react_jsx_runtime.jsx)("button", {
12249
12458
  type: "button",
12250
- className: PendingPanel_module_css_default.action,
12459
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionQuietDisabled}`,
12251
12460
  "data-diff-revert": true,
12252
12461
  disabled: busy,
12253
12462
  onClick: () => {
12254
12463
  onRevert(file.sessionId, file.id);
12255
12464
  },
12256
- children: busy ? t("action.busy") : t("action.revert")
12465
+ children: t("action.revert")
12257
12466
  })
12258
12467
  ]
12259
12468
  }),
12469
+ failedMessage !== void 0 && (0, react_jsx_runtime.jsx)("p", {
12470
+ className: PendingPanel_module_css_default.actionError,
12471
+ "data-diff-action-error": true,
12472
+ children: failedMessage
12473
+ }),
12260
12474
  file.missing && (0, react_jsx_runtime.jsx)("p", {
12261
12475
  className: PendingPanel_module_css_default.missingHint,
12262
12476
  children: t("panel.missingHint")
12263
12477
  }),
12264
12478
  (0, react_jsx_runtime.jsxs)("div", {
12265
12479
  className: PendingPanel_module_css_default.diffBodyWrap,
12266
- children: [(0, react_jsx_runtime.jsxs)("div", {
12267
- className: PendingPanel_module_css_default.diffBody,
12268
- ref: bodyRef,
12269
- onScroll,
12270
- onMouseLeave: () => {
12271
- setHoveredBlock(void 0);
12272
- },
12273
- "data-diff-body": true,
12274
- children: [(0, react_jsx_runtime.jsxs)("div", {
12275
- className: PendingPanel_module_css_default.lines,
12276
- style: { minWidth: `max(100%, ${widestLine}ch)` },
12480
+ children: [
12481
+ (0, react_jsx_runtime.jsxs)("div", {
12482
+ className: PendingPanel_module_css_default.diffBody,
12483
+ ref: bodyRef,
12484
+ tabIndex: 0,
12485
+ onScroll,
12486
+ onMouseLeave: () => {
12487
+ setHoveredBlock(void 0);
12488
+ },
12489
+ "data-diff-body": true,
12490
+ children: [(0, react_jsx_runtime.jsxs)("div", {
12491
+ className: PendingPanel_module_css_default.lines,
12492
+ style: { minWidth: `max(100%, ${widestLine}ch)` },
12493
+ children: [
12494
+ start > 0 && (0, react_jsx_runtime.jsx)("div", {
12495
+ className: PendingPanel_module_css_default.vSpacer,
12496
+ style: { height: start * ROW_HEIGHT_PX },
12497
+ "aria-hidden": "true"
12498
+ }),
12499
+ visibleRows.map((row, offset) => {
12500
+ const index = start + offset;
12501
+ return (0, react_jsx_runtime.jsx)(DiffRow, {
12502
+ index,
12503
+ row,
12504
+ runs,
12505
+ focused: inFocusedBlock(index),
12506
+ searchHit: searchHitSet.has(index),
12507
+ searchCurrent: index === currentSearchRow,
12508
+ onRowHover
12509
+ }, index);
12510
+ }),
12511
+ end < rowCount && (0, react_jsx_runtime.jsx)("div", {
12512
+ className: PendingPanel_module_css_default.vSpacer,
12513
+ style: { height: (rowCount - end) * ROW_HEIGHT_PX },
12514
+ "aria-hidden": "true"
12515
+ }),
12516
+ blockActionsPadPx > 0 && (0, react_jsx_runtime.jsx)("div", {
12517
+ className: PendingPanel_module_css_default.vSpacer,
12518
+ style: { height: blockActionsPadPx },
12519
+ "aria-hidden": "true"
12520
+ })
12521
+ ]
12522
+ }), hoveredBlock !== void 0 && model.blocks[hoveredBlock] !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
12523
+ className: PendingPanel_module_css_default.blockActions,
12524
+ "data-diff-block-actions": true,
12525
+ style: { top: (model.blocks[hoveredBlock].end + 1) * ROW_HEIGHT_PX },
12526
+ children: [
12527
+ (0, react_jsx_runtime.jsx)("span", {
12528
+ className: PendingPanel_module_css_default.blockPosition,
12529
+ "data-diff-block-position": true,
12530
+ children: t("panel.blockPosition", {
12531
+ current: hoveredBlock + 1,
12532
+ total: model.blocks.length
12533
+ })
12534
+ }),
12535
+ (0, react_jsx_runtime.jsx)("button", {
12536
+ type: "button",
12537
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
12538
+ "data-diff-block-keep": true,
12539
+ disabled: busy,
12540
+ onClick: () => {
12541
+ onBlockKeep(file.sessionId, file.id, blockRanges[hoveredBlock]);
12542
+ },
12543
+ children: t("action.keep")
12544
+ }),
12545
+ (0, react_jsx_runtime.jsx)("button", {
12546
+ type: "button",
12547
+ className: PendingPanel_module_css_default.action,
12548
+ "data-diff-block-revert": true,
12549
+ disabled: busy,
12550
+ onClick: () => {
12551
+ onBlockRevert(file.sessionId, file.id, blockRanges[hoveredBlock]);
12552
+ },
12553
+ children: t("action.revert")
12554
+ })
12555
+ ]
12556
+ })]
12557
+ }),
12558
+ focusedBlock !== void 0 && flashKey > 0 && (0, react_jsx_runtime.jsx)("div", {
12559
+ className: PendingPanel_module_css_default.blockFlash,
12560
+ "data-diff-block-flash": true,
12561
+ style: {
12562
+ top: focusedBlock.start * ROW_HEIGHT_PX - scrollTop,
12563
+ height: Math.min((focusedBlock.end - focusedBlock.start + 1) * ROW_HEIGHT_PX, viewportHeight > 0 ? viewportHeight : Number.POSITIVE_INFINITY),
12564
+ width: flashWidth
12565
+ }
12566
+ }, flashKey),
12567
+ searchOpen && (0, react_jsx_runtime.jsxs)("div", {
12568
+ className: PendingPanel_module_css_default.searchBar,
12569
+ "data-diff-searchbar": true,
12277
12570
  children: [
12278
- start > 0 && (0, react_jsx_runtime.jsx)("div", {
12279
- className: PendingPanel_module_css_default.vSpacer,
12280
- style: { height: start * ROW_HEIGHT_PX },
12281
- "aria-hidden": "true"
12571
+ (0, react_jsx_runtime.jsx)("input", {
12572
+ ref: searchInputRef,
12573
+ className: PendingPanel_module_css_default.searchInput,
12574
+ "data-diff-search-input": true,
12575
+ value: searchQuery,
12576
+ placeholder: t("panel.searchPlaceholder"),
12577
+ onChange: (event) => {
12578
+ setSearchQuery(event.target.value);
12579
+ },
12580
+ onKeyDown: (event) => {
12581
+ if (event.key === "Enter") {
12582
+ event.preventDefault();
12583
+ goSearch(event.shiftKey ? -1 : 1);
12584
+ } else if (event.key === "Escape") closeSearch();
12585
+ }
12586
+ }),
12587
+ (0, react_jsx_runtime.jsx)("span", {
12588
+ className: PendingPanel_module_css_default.searchCount,
12589
+ "data-diff-search-count": true,
12590
+ children: searchMatches.length === 0 ? "0/0" : `${searchIndex % searchMatches.length + 1}/${searchMatches.length}`
12591
+ }),
12592
+ (0, react_jsx_runtime.jsx)("button", {
12593
+ type: "button",
12594
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12595
+ "data-diff-search-prev": true,
12596
+ "aria-label": t("action.prevDiff"),
12597
+ disabled: searchMatches.length === 0,
12598
+ onClick: () => {
12599
+ goSearch(-1);
12600
+ },
12601
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12282
12602
  }),
12283
- visibleRows.map((row, offset) => {
12284
- const index = start + offset;
12285
- return (0, react_jsx_runtime.jsx)(DiffRow, {
12286
- index,
12287
- row,
12288
- runs,
12289
- focused: inFocusedBlock(index),
12290
- onRowHover
12291
- }, index);
12603
+ (0, react_jsx_runtime.jsx)("button", {
12604
+ type: "button",
12605
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12606
+ "data-diff-search-next": true,
12607
+ "aria-label": t("action.nextDiff"),
12608
+ disabled: searchMatches.length === 0,
12609
+ onClick: () => {
12610
+ goSearch(1);
12611
+ },
12612
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
12292
12613
  }),
12293
- end < rowCount && (0, react_jsx_runtime.jsx)("div", {
12294
- className: PendingPanel_module_css_default.vSpacer,
12295
- style: { height: (rowCount - end) * ROW_HEIGHT_PX },
12296
- "aria-hidden": "true"
12614
+ (0, react_jsx_runtime.jsx)("button", {
12615
+ type: "button",
12616
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
12617
+ "data-diff-search-close": true,
12618
+ "aria-label": t("action.close"),
12619
+ onClick: closeSearch,
12620
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
12297
12621
  })
12298
12622
  ]
12299
- }), hoveredBlock !== void 0 && model.blocks[hoveredBlock] !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
12300
- className: PendingPanel_module_css_default.blockActions,
12301
- "data-diff-block-actions": true,
12302
- style: { top: model.blocks[hoveredBlock].start * ROW_HEIGHT_PX },
12303
- children: [(0, react_jsx_runtime.jsx)("button", {
12304
- type: "button",
12305
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
12306
- "data-diff-block-keep": true,
12307
- disabled: busy,
12308
- onClick: () => {
12309
- onBlockKeep(file.sessionId, file.id, blockRanges[hoveredBlock]);
12310
- },
12311
- children: t("action.keep")
12312
- }), (0, react_jsx_runtime.jsx)("button", {
12313
- type: "button",
12314
- className: PendingPanel_module_css_default.action,
12315
- "data-diff-block-revert": true,
12316
- disabled: busy,
12317
- onClick: () => {
12318
- onBlockRevert(file.sessionId, file.id, blockRanges[hoveredBlock]);
12319
- },
12320
- children: t("action.revert")
12321
- })]
12322
- })]
12323
- }), rulerMarkers.length > 0 && (0, react_jsx_runtime.jsx)("div", {
12324
- className: PendingPanel_module_css_default.overviewRuler,
12325
- "data-diff-approval-ruler": true,
12326
- "aria-hidden": "true",
12327
- children: rulerMarkers.map((marker, index) => (0, react_jsx_runtime.jsx)("div", {
12328
- className: `${PendingPanel_module_css_default.overviewMarker} ${marker.kind === "del" ? PendingPanel_module_css_default.markerDel : PendingPanel_module_css_default.markerAdd}`,
12329
- "data-diff-ruler-marker": marker.kind,
12330
- style: {
12331
- top: `${marker.top}%`,
12332
- height: `${marker.height}%`
12333
- }
12334
- }, index))
12335
- })]
12623
+ }),
12624
+ rulerMarkers.length > 0 && (0, react_jsx_runtime.jsx)("div", {
12625
+ className: PendingPanel_module_css_default.overviewRuler,
12626
+ "data-diff-approval-ruler": true,
12627
+ "aria-hidden": "true",
12628
+ children: rulerMarkers.map((marker, index) => (0, react_jsx_runtime.jsx)("div", {
12629
+ className: `${PendingPanel_module_css_default.overviewMarker} ${marker.kind === "del" ? PendingPanel_module_css_default.markerDel : PendingPanel_module_css_default.markerAdd}`,
12630
+ "data-diff-ruler-marker": marker.kind,
12631
+ style: {
12632
+ top: `${marker.top}%`,
12633
+ height: `${marker.height}%`
12634
+ }
12635
+ }, index))
12636
+ })
12637
+ ]
12336
12638
  }),
12337
12639
  (0, react_jsx_runtime.jsxs)("div", {
12338
12640
  className: PendingPanel_module_css_default.statusBar,
@@ -12395,11 +12697,34 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12395
12697
  });
12396
12698
  }
12397
12699
  /** Render the pending-edit review panel and its unified footer action. */
12398
- function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, t }) {
12700
+ function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPasteReference, onUndo, onRedo, onImportVcs, t }) {
12399
12701
  const current = useSessions((state) => state.current);
12702
+ const currentBlank = useSessions((state) => {
12703
+ const id = state.current;
12704
+ return id === void 0 ? false : state.byId[id]?.blank ?? false;
12705
+ });
12706
+ const noSession = current === void 0 || currentBlank;
12400
12707
  const snapshot = usePending((snapshot) => snapshot);
12401
12708
  const [open, setOpen] = (0, react.useState)(false);
12402
12709
  const [selected, setSelected] = (0, react.useState)("");
12710
+ /** Bumped when the already-open file is clicked again, to jump to the next
12711
+ * diff block in the open file's detail pane. */
12712
+ const [jumpSignal, setJumpSignal] = (0, react.useState)(0);
12713
+ /** Bumped when an undo/redo affected the already-open file, so its detail
12714
+ * pane re-selects the undone diff (flash). */
12715
+ const [undoFlash, setUndoFlash] = (0, react.useState)(0);
12716
+ /** Which bulk decision (keep-all / revert-all) is running; null when idle. */
12717
+ const [bulkBusy, setBulkBusy] = (0, react.useState)(null);
12718
+ /** True while a workspace-changes import is running. */
12719
+ const [importBusy, setImportBusy] = (0, react.useState)(false);
12720
+ /** Feedback under the empty note after an import (no VCS, or a failure). */
12721
+ const [importNote, setImportNote] = (0, react.useState)(void 0);
12722
+ /** Whether the last import note is a failure (drives the alert role). */
12723
+ const [importFailed, setImportFailed] = (0, react.useState)(false);
12724
+ /** A transient banner for an import that found nothing to bring in. */
12725
+ const [importToast, setImportToast] = (0, react.useState)(null);
12726
+ /** A transient banner for a keep/revert failure. */
12727
+ const [actionToast, setActionToast] = (0, react.useState)(null);
12403
12728
  /** Pending count captured at open time; auto-close needs a list that emptied. */
12404
12729
  const [openedCount, setOpenedCount] = (0, react.useState)(0);
12405
12730
  /** Bottom offset tracking the chat composer's top edge so the input stays visible. */
@@ -12461,13 +12786,27 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12461
12786
  document.removeEventListener("pointerdown", onPointerDown, true);
12462
12787
  };
12463
12788
  }, [open]);
12464
- const files = snapshot.files;
12465
- const mine = files.filter((file) => file.sessionId === current);
12466
- const theirs = files.filter((file) => file.sessionId !== current);
12789
+ const files = snapshot.files.filter((file) => file.sessionId === current);
12790
+ /** Per-file keep/revert failures, surfaced inline on the row and detail. */
12791
+ const failed = snapshot.failed ?? EMPTY_FAILED_MAP;
12792
+ const failedInitialized = (0, react.useRef)(false);
12793
+ const failedRef = (0, react.useRef)(void 0);
12794
+ (0, react.useEffect)(() => {
12795
+ const current = snapshot.failed;
12796
+ if (!failedInitialized.current) {
12797
+ failedInitialized.current = true;
12798
+ failedRef.current = current;
12799
+ return;
12800
+ }
12801
+ const previous = failedRef.current;
12802
+ const fresh = [...(current ?? EMPTY_FAILED_MAP).entries()].filter(([id]) => previous === void 0 || !previous.has(id));
12803
+ if (fresh.length > 0) setActionToast(fresh[0][1]);
12804
+ failedRef.current = current;
12805
+ }, [snapshot.failed]);
12467
12806
  (0, react.useEffect)(() => {
12468
12807
  if (!open) return;
12469
12808
  if (selected !== "" && files.some((file) => file.id === selected)) return;
12470
- const next = [...mine, ...theirs][0];
12809
+ const next = files[0];
12471
12810
  if (next !== void 0 && next.id !== selected) setSelected(next.id);
12472
12811
  }, [
12473
12812
  open,
@@ -12476,10 +12815,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12476
12815
  selected
12477
12816
  ]);
12478
12817
  (0, react.useEffect)(() => {
12479
- if (open && openedCount > 0 && snapshot.read && snapshot.error === void 0 && files.length === 0) {
12818
+ if (!open || openedCount === 0) return;
12819
+ if (!(snapshot.read && snapshot.error === void 0 && files.length === 0)) return;
12820
+ const timer = window.setTimeout(() => {
12480
12821
  setOpenedCount(0);
12481
12822
  setOpen(false);
12482
- }
12823
+ }, EMPTY_CLOSE_GRACE_MS);
12824
+ return () => {
12825
+ window.clearTimeout(timer);
12826
+ };
12483
12827
  }, [
12484
12828
  open,
12485
12829
  openedCount,
@@ -12487,17 +12831,88 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12487
12831
  snapshot.error,
12488
12832
  files.length
12489
12833
  ]);
12834
+ const runImportVcs = async () => {
12835
+ if (current === void 0 || importBusy) return;
12836
+ setImportBusy(true);
12837
+ setImportNote(void 0);
12838
+ setImportFailed(false);
12839
+ setImportToast(null);
12840
+ try {
12841
+ const value = await onImportVcs(current, includeUntrackedEnabled());
12842
+ await onRefresh(current);
12843
+ if (!value.detected) setImportNote(t("panel.importNoVcs"));
12844
+ else if (value.imported === 0) setImportToast(t("panel.importNone"));
12845
+ } catch (error) {
12846
+ setImportFailed(true);
12847
+ setImportNote(t("panel.importFailed", { message: error instanceof Error ? error.message : String(error) }));
12848
+ } finally {
12849
+ setImportBusy(false);
12850
+ }
12851
+ };
12490
12852
  const toggleOpen = () => {
12491
12853
  if (!open) setOpenedCount(files.length);
12492
12854
  setOpen((value) => !value);
12493
12855
  };
12856
+ (0, react.useEffect)(() => {
12857
+ if (noSession) setOpen(false);
12858
+ }, [noSession]);
12859
+ /** Run the same decision over every current-session file, sequentially. */
12860
+ const runBulk = async (kind) => {
12861
+ setBulkBusy(kind);
12862
+ try {
12863
+ for (const file of files) if (kind === "keep") await onKeep(file.sessionId, file.id);
12864
+ else await onRevert(file.sessionId, file.id);
12865
+ } finally {
12866
+ setBulkBusy(null);
12867
+ }
12868
+ };
12494
12869
  const renderEntry = (entry) => (0, react_jsx_runtime.jsx)(PendingFileRow, {
12495
12870
  file: entry,
12496
12871
  selected: selected === entry.id,
12872
+ failedMessage: failed.get(entry.id),
12497
12873
  t,
12498
- onSelect: setSelected
12874
+ onSelect: (id) => {
12875
+ if (id === selected) setJumpSignal((signal) => signal + 1);
12876
+ else setSelected(id);
12877
+ }
12499
12878
  }, entry.id);
12500
12879
  const selectedFile = files.find((file) => file.id === selected);
12880
+ const handleUndo = async (sessionId) => {
12881
+ const id = await onUndo(sessionId);
12882
+ if (id === void 0) return;
12883
+ if (id === selected) setUndoFlash((signal) => signal + 1);
12884
+ else setSelected(id);
12885
+ };
12886
+ const handleRedo = async (sessionId) => {
12887
+ const id = await onRedo(sessionId);
12888
+ if (id === void 0) return;
12889
+ if (id === selected) setUndoFlash((signal) => signal + 1);
12890
+ else setSelected(id);
12891
+ };
12892
+ (0, react.useEffect)(() => {
12893
+ if (!open || current === void 0) return;
12894
+ const onKeyDown = (event) => {
12895
+ if (!(event.ctrlKey || event.metaKey) || event.altKey) return;
12896
+ const key = event.key.toLowerCase();
12897
+ if (key !== "z" && key !== "y") return;
12898
+ const target = event.target;
12899
+ if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
12900
+ event.preventDefault();
12901
+ if (key === "z") {
12902
+ if (event.shiftKey) handleRedo(current);
12903
+ else handleUndo(current);
12904
+ } else handleRedo(current);
12905
+ };
12906
+ window.addEventListener("keydown", onKeyDown, true);
12907
+ return () => {
12908
+ window.removeEventListener("keydown", onKeyDown, true);
12909
+ };
12910
+ }, [
12911
+ open,
12912
+ current,
12913
+ handleUndo,
12914
+ handleRedo
12915
+ ]);
12501
12916
  /** Drag the list/detail divider; width follows the pointer within its bounds. */
12502
12917
  const startResize = (event) => {
12503
12918
  if (event.button !== 0) return;
@@ -12526,132 +12941,316 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12526
12941
  };
12527
12942
  return (0, react_jsx_runtime.jsxs)("div", {
12528
12943
  className: wide ? PendingPanel_module_css_default.layer : `${PendingPanel_module_css_default.layer} ${PendingPanel_module_css_default.rail}`,
12529
- children: [open && (0, react_jsx_runtime.jsxs)("section", {
12530
- className: PendingPanel_module_css_default.panel,
12531
- style: { bottom: expanded ? PANEL_INSET_PX : bottomPx },
12532
- "data-diff-approval-panel": true,
12533
- "aria-label": t("panel.title"),
12534
- children: [(0, react_jsx_runtime.jsxs)("header", {
12535
- className: PendingPanel_module_css_default.header,
12536
- children: [(0, react_jsx_runtime.jsx)("span", {
12537
- className: PendingPanel_module_css_default.title,
12538
- children: t("panel.title")
12539
- }), (0, react_jsx_runtime.jsxs)("div", {
12540
- className: PendingPanel_module_css_default.headerActions,
12541
- children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12542
- label: t(expanded ? "action.exitFullscreen" : "action.expand"),
12543
- side: "bottom",
12544
- delayMs: 500,
12545
- children: (0, react_jsx_runtime.jsx)("button", {
12546
- type: "button",
12547
- className: expanded ? `${PendingPanel_module_css_default.expand} ${PendingPanel_module_css_default.expandExpanded}` : PendingPanel_module_css_default.expand,
12548
- "data-diff-approval-expand": true,
12549
- "aria-label": t(expanded ? "action.exitFullscreen" : "action.expand"),
12550
- onClick: () => {
12551
- setExpanded((value) => !value);
12552
- },
12553
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
12944
+ children: [
12945
+ importToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
12946
+ text: importToast,
12947
+ onDone: () => {
12948
+ setImportToast(null);
12949
+ }
12950
+ }),
12951
+ actionToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
12952
+ text: actionToast,
12953
+ onDone: () => {
12954
+ setActionToast(null);
12955
+ }
12956
+ }),
12957
+ open && expanded && (0, react_jsx_runtime.jsx)("div", {
12958
+ className: PendingPanel_module_css_default.fullscreenBackdrop,
12959
+ "data-diff-fullscreen-backdrop": true
12960
+ }),
12961
+ open && (0, react_jsx_runtime.jsxs)("section", {
12962
+ className: PendingPanel_module_css_default.panel,
12963
+ style: { bottom: expanded ? PANEL_INSET_PX : bottomPx },
12964
+ "data-diff-approval-panel": true,
12965
+ "aria-label": t("panel.title"),
12966
+ children: [(0, react_jsx_runtime.jsxs)("header", {
12967
+ className: PendingPanel_module_css_default.header,
12968
+ children: [(0, react_jsx_runtime.jsx)("span", {
12969
+ className: PendingPanel_module_css_default.title,
12970
+ children: t("panel.title")
12971
+ }), (0, react_jsx_runtime.jsxs)("div", {
12972
+ className: PendingPanel_module_css_default.headerActions,
12973
+ children: [
12974
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12975
+ label: t("action.settings"),
12976
+ side: "bottom",
12977
+ delayMs: 500,
12978
+ children: (0, react_jsx_runtime.jsx)("button", {
12979
+ type: "button",
12980
+ className: PendingPanel_module_css_default.expand,
12981
+ "data-diff-approval-settings": true,
12982
+ "aria-label": t("action.settings"),
12983
+ onClick: () => {
12984
+ setOpen(false);
12985
+ openSettingsSection(t("settings.tabLabel"));
12986
+ },
12987
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 14 })
12988
+ })
12989
+ }),
12990
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12991
+ label: t(expanded ? "action.exitFullscreen" : "action.expand"),
12992
+ side: "bottom",
12993
+ delayMs: 500,
12994
+ children: (0, react_jsx_runtime.jsx)("button", {
12995
+ type: "button",
12996
+ className: expanded ? `${PendingPanel_module_css_default.expand} ${PendingPanel_module_css_default.expandExpanded}` : PendingPanel_module_css_default.expand,
12997
+ "data-diff-approval-expand": true,
12998
+ "aria-label": t(expanded ? "action.exitFullscreen" : "action.expand"),
12999
+ onClick: () => {
13000
+ setExpanded((value) => !value);
13001
+ },
13002
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
13003
+ })
13004
+ }),
13005
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13006
+ label: t("action.close"),
13007
+ side: "bottom",
13008
+ delayMs: 500,
13009
+ children: (0, react_jsx_runtime.jsx)("button", {
13010
+ type: "button",
13011
+ className: PendingPanel_module_css_default.close,
13012
+ "data-diff-approval-close": true,
13013
+ "aria-label": t("action.close"),
13014
+ onClick: () => {
13015
+ setOpen(false);
13016
+ },
13017
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
13018
+ })
13019
+ })
13020
+ ]
13021
+ })]
13022
+ }), snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
13023
+ className: PendingPanel_module_css_default.states,
13024
+ children: [
13025
+ snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
13026
+ className: PendingPanel_module_css_default.readError,
13027
+ role: "alert",
13028
+ children: t("panel.readFailed", { message: snapshot.error })
13029
+ }),
13030
+ !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
13031
+ className: PendingPanel_module_css_default.note,
13032
+ children: t("panel.loading")
13033
+ }),
13034
+ snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsxs)("div", {
13035
+ className: PendingPanel_module_css_default.emptyState,
13036
+ children: [
13037
+ (0, react_jsx_runtime.jsx)("p", {
13038
+ className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
13039
+ children: t("panel.empty")
13040
+ }),
13041
+ (0, react_jsx_runtime.jsx)("button", {
13042
+ type: "button",
13043
+ className: PendingPanel_module_css_default.importButton,
13044
+ "data-diff-import-vcs": true,
13045
+ disabled: importBusy,
13046
+ onClick: () => {
13047
+ runImportVcs();
13048
+ },
13049
+ children: importBusy ? t("action.importVcsBusy") : t("action.importVcs")
13050
+ }),
13051
+ importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
13052
+ className: PendingPanel_module_css_default.importNote,
13053
+ role: importFailed ? "alert" : void 0,
13054
+ children: importNote
13055
+ })
13056
+ ]
12554
13057
  })
12555
- }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12556
- label: t("action.close"),
12557
- side: "bottom",
12558
- delayMs: 500,
12559
- children: (0, react_jsx_runtime.jsx)("button", {
12560
- type: "button",
12561
- className: PendingPanel_module_css_default.close,
12562
- "data-diff-approval-close": true,
12563
- "aria-label": t("action.close"),
12564
- onClick: () => {
12565
- setOpen(false);
12566
- },
12567
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
13058
+ ]
13059
+ }) : (0, react_jsx_runtime.jsxs)("div", {
13060
+ className: PendingPanel_module_css_default.split,
13061
+ children: [
13062
+ (0, react_jsx_runtime.jsxs)("nav", {
13063
+ className: PendingPanel_module_css_default.fileList,
13064
+ style: { width: listWidth },
13065
+ "data-diff-approval-file-list": true,
13066
+ children: [(0, react_jsx_runtime.jsx)("div", {
13067
+ className: PendingPanel_module_css_default.listScroll,
13068
+ children: files.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
13069
+ className: PendingPanel_module_css_default.group,
13070
+ children: t("panel.group.current")
13071
+ }), (0, react_jsx_runtime.jsx)("ul", {
13072
+ className: PendingPanel_module_css_default.rows,
13073
+ children: files.map(renderEntry)
13074
+ })] })
13075
+ }), files.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
13076
+ className: PendingPanel_module_css_default.bulkActions,
13077
+ children: [(0, react_jsx_runtime.jsx)("button", {
13078
+ type: "button",
13079
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
13080
+ "data-diff-keep-all": true,
13081
+ disabled: bulkBusy !== null,
13082
+ onClick: () => {
13083
+ runBulk("keep");
13084
+ },
13085
+ children: bulkBusy === "keep" ? t("action.busy") : t("action.keepAll")
13086
+ }), (0, react_jsx_runtime.jsx)("button", {
13087
+ type: "button",
13088
+ className: PendingPanel_module_css_default.action,
13089
+ "data-diff-revert-all": true,
13090
+ disabled: bulkBusy !== null,
13091
+ onClick: () => {
13092
+ runBulk("revert");
13093
+ },
13094
+ children: bulkBusy === "revert" ? t("action.busy") : t("action.revertAll")
13095
+ })]
13096
+ })]
13097
+ }),
13098
+ (0, react_jsx_runtime.jsx)("div", {
13099
+ className: PendingPanel_module_css_default.resizeHandle,
13100
+ "data-diff-resize": true,
13101
+ onMouseDown: startResize
13102
+ }),
13103
+ (0, react_jsx_runtime.jsx)("div", {
13104
+ className: PendingPanel_module_css_default.detail,
13105
+ children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
13106
+ className: PendingPanel_module_css_default.detailEmpty,
13107
+ children: t("panel.selectHint")
13108
+ }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
13109
+ file: selectedFile,
13110
+ busy: snapshot.busy.has(selectedFile.id),
13111
+ workspacePath: snapshot.workspacePath,
13112
+ jumpSignal,
13113
+ undoFlash,
13114
+ failedMessage: failed.get(selectedFile.id),
13115
+ onPasteReference,
13116
+ t,
13117
+ onKeep,
13118
+ onRevert,
13119
+ onBlockKeep,
13120
+ onBlockRevert,
13121
+ onOpen
13122
+ })
12568
13123
  })
12569
- })]
13124
+ ]
12570
13125
  })]
12571
- }), snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
12572
- className: PendingPanel_module_css_default.states,
12573
- children: [
12574
- snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
12575
- className: PendingPanel_module_css_default.readError,
12576
- role: "alert",
12577
- children: t("panel.readFailed", { message: snapshot.error })
12578
- }),
12579
- !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
12580
- className: PendingPanel_module_css_default.note,
12581
- children: t("panel.loading")
12582
- }),
12583
- snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsx)("p", {
12584
- className: PendingPanel_module_css_default.note,
12585
- children: t("panel.empty")
12586
- })
12587
- ]
12588
- }) : (0, react_jsx_runtime.jsxs)("div", {
12589
- className: PendingPanel_module_css_default.split,
12590
- children: [
12591
- (0, react_jsx_runtime.jsxs)("nav", {
12592
- className: PendingPanel_module_css_default.fileList,
12593
- style: { width: listWidth },
12594
- "data-diff-approval-file-list": true,
12595
- children: [mine.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
12596
- className: PendingPanel_module_css_default.group,
12597
- children: t("panel.group.current")
12598
- }), (0, react_jsx_runtime.jsx)("ul", {
12599
- className: PendingPanel_module_css_default.rows,
12600
- children: mine.map(renderEntry)
12601
- })] }), theirs.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
12602
- className: PendingPanel_module_css_default.group,
12603
- children: t("panel.group.others")
12604
- }), (0, react_jsx_runtime.jsx)("ul", {
12605
- className: PendingPanel_module_css_default.rows,
12606
- children: theirs.map(renderEntry)
12607
- })] })]
12608
- }),
12609
- (0, react_jsx_runtime.jsx)("div", {
12610
- className: PendingPanel_module_css_default.resizeHandle,
12611
- "data-diff-resize": true,
12612
- onMouseDown: startResize
12613
- }),
12614
- (0, react_jsx_runtime.jsx)("div", {
12615
- className: PendingPanel_module_css_default.detail,
12616
- children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
12617
- className: PendingPanel_module_css_default.detailEmpty,
12618
- children: t("panel.selectHint")
12619
- }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
12620
- file: selectedFile,
12621
- busy: snapshot.busy.has(selectedFile.id),
12622
- workspacePath: snapshot.workspacePath,
12623
- t,
12624
- onKeep,
12625
- onRevert,
12626
- onBlockKeep,
12627
- onBlockRevert,
12628
- onOpen
13126
+ }),
13127
+ (0, react_jsx_runtime.jsx)("div", {
13128
+ className: PendingPanel_module_css_default.footerButtons,
13129
+ children: (0, react_jsx_runtime.jsxs)("button", {
13130
+ type: "button",
13131
+ className: noSession ? `${PendingPanel_module_css_default.badge} ${PendingPanel_module_css_default.badgeDisabled}` : PendingPanel_module_css_default.badge,
13132
+ "data-diff-approval-badge": files.length,
13133
+ "data-active": open ? "" : void 0,
13134
+ "aria-label": t("panel.aria"),
13135
+ "aria-expanded": open,
13136
+ disabled: noSession,
13137
+ onClick: toggleOpen,
13138
+ children: [
13139
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: wide ? 16 : 18 }),
13140
+ wide && (0, react_jsx_runtime.jsx)("span", {
13141
+ className: PendingPanel_module_css_default.badgeLabel,
13142
+ children: t("panel.aria")
13143
+ }),
13144
+ (wide || files.length > 0) && (0, react_jsx_runtime.jsx)("span", {
13145
+ className: PendingPanel_module_css_default.badgeCount,
13146
+ children: files.length
12629
13147
  })
12630
- })
12631
- ]
12632
- })]
12633
- }), (0, react_jsx_runtime.jsx)("div", {
12634
- className: PendingPanel_module_css_default.footerButtons,
12635
- children: (0, react_jsx_runtime.jsxs)("button", {
12636
- type: "button",
12637
- className: PendingPanel_module_css_default.badge,
12638
- "data-diff-approval-badge": files.length,
12639
- "data-active": open ? "" : void 0,
12640
- "aria-label": t("panel.aria"),
12641
- "aria-expanded": open,
12642
- onClick: toggleOpen,
12643
- children: [
12644
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: wide ? 16 : 18 }),
12645
- wide && (0, react_jsx_runtime.jsx)("span", {
12646
- className: PendingPanel_module_css_default.badgeLabel,
12647
- children: t("panel.aria")
12648
- }),
12649
- (wide || files.length > 0) && (0, react_jsx_runtime.jsx)("span", {
12650
- className: PendingPanel_module_css_default.badgeCount,
12651
- children: files.length
12652
- })
12653
- ]
13148
+ ]
13149
+ })
12654
13150
  })
13151
+ ]
13152
+ });
13153
+ }
13154
+ //#endregion
13155
+ //#region lib/types/client/SettingsTab.js
13156
+ /** DSH Settings top-level section for this plugin's preferences. */
13157
+ /** A pill picker offering the two boolean states 打开 / 关闭. */
13158
+ function OnOffPicker({ value, open, onOpenChange, onSelect, dataAttribute, t }) {
13159
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
13160
+ open,
13161
+ onClose: () => {
13162
+ onOpenChange(false);
13163
+ },
13164
+ items: [{
13165
+ id: "on",
13166
+ label: t("action.toggleOn")
13167
+ }, {
13168
+ id: "off",
13169
+ label: t("action.toggleOff")
13170
+ }],
13171
+ selectedId: value ? "on" : "off",
13172
+ onSelect: (id) => {
13173
+ onOpenChange(false);
13174
+ onSelect(id === "on");
13175
+ },
13176
+ align: "end",
13177
+ portal: true,
13178
+ anchor: (0, react_jsx_runtime.jsxs)("button", {
13179
+ type: "button",
13180
+ className: PendingPanel_module_css_default.settingsSelector,
13181
+ "aria-haspopup": "menu",
13182
+ "aria-expanded": open,
13183
+ onClick: () => {
13184
+ onOpenChange(!open);
13185
+ },
13186
+ [dataAttribute]: true,
13187
+ children: [value ? t("action.toggleOn") : t("action.toggleOff"), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: PendingPanel_module_css_default.settingsSelectorChevron })]
13188
+ })
13189
+ });
13190
+ }
13191
+ /** One Agent-preset-style preference row: title + description, pill picker right. */
13192
+ function PreferenceRow({ title, description, value, open, onOpenChange, onSelect, dataAttribute, t }) {
13193
+ return (0, react_jsx_runtime.jsxs)("div", {
13194
+ className: PendingPanel_module_css_default.settingsRow,
13195
+ children: [(0, react_jsx_runtime.jsxs)("div", {
13196
+ className: PendingPanel_module_css_default.settingsRowText,
13197
+ children: [(0, react_jsx_runtime.jsx)("div", {
13198
+ className: PendingPanel_module_css_default.settingsRowTitle,
13199
+ children: title
13200
+ }), (0, react_jsx_runtime.jsx)("div", {
13201
+ className: PendingPanel_module_css_default.settingsRowDesc,
13202
+ children: description
13203
+ })]
13204
+ }), (0, react_jsx_runtime.jsx)(OnOffPicker, {
13205
+ value,
13206
+ open,
13207
+ onOpenChange,
13208
+ onSelect,
13209
+ dataAttribute,
13210
+ t
13211
+ })]
13212
+ });
13213
+ }
13214
+ /**
13215
+ * The plugin's preferences: auto-paste a copied reference into the input, and
13216
+ * whether importing workspace VCS changes includes untracked files. Each row
13217
+ * mirrors the harness's Agent-preset row (title + description on the left, a
13218
+ * pill picker on the right) with the two boolean states 打开 / 关闭.
13219
+ */
13220
+ function DiffApprovalSettingsTab({ t }) {
13221
+ const [pasteOnCopy, setPasteOnCopyState] = (0, react.useState)(pasteOnCopyEnabled);
13222
+ const [pasteOnCopyOpen, setPasteOnCopyOpen] = (0, react.useState)(false);
13223
+ const [includeUntracked, setIncludeUntrackedState] = (0, react.useState)(includeUntrackedEnabled);
13224
+ const [includeUntrackedOpen, setIncludeUntrackedOpen] = (0, react.useState)(false);
13225
+ const setPasteOnCopy = (value) => {
13226
+ setPasteOnCopyState(value);
13227
+ setPasteOnCopyEnabled(value);
13228
+ };
13229
+ const setIncludeUntracked = (value) => {
13230
+ setIncludeUntrackedState(value);
13231
+ setIncludeUntrackedEnabled(value);
13232
+ };
13233
+ return (0, react_jsx_runtime.jsxs)("div", {
13234
+ className: PendingPanel_module_css_default.settingsPage,
13235
+ "data-diff-settings": true,
13236
+ children: [(0, react_jsx_runtime.jsx)(PreferenceRow, {
13237
+ title: t("panel.pasteOnCopy"),
13238
+ description: t("panel.pasteOnCopyDesc"),
13239
+ value: pasteOnCopy,
13240
+ open: pasteOnCopyOpen,
13241
+ onOpenChange: setPasteOnCopyOpen,
13242
+ onSelect: setPasteOnCopy,
13243
+ dataAttribute: "data-diff-paste-on-copy-select",
13244
+ t
13245
+ }), (0, react_jsx_runtime.jsx)(PreferenceRow, {
13246
+ title: t("panel.importUntracked"),
13247
+ description: t("panel.importUntrackedDesc"),
13248
+ value: includeUntracked,
13249
+ open: includeUntrackedOpen,
13250
+ onOpenChange: setIncludeUntrackedOpen,
13251
+ onSelect: setIncludeUntracked,
13252
+ dataAttribute: "data-diff-import-untracked-select",
13253
+ t
12655
13254
  })]
12656
13255
  });
12657
13256
  }
@@ -12700,6 +13299,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12700
13299
  block
12701
13300
  }));
12702
13301
  },
13302
+ async undo(sessionId) {
13303
+ return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "undo", { sessionId }));
13304
+ },
13305
+ async redo(sessionId) {
13306
+ return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "redo", { sessionId }));
13307
+ },
13308
+ async importVcs(sessionId, includeUntracked) {
13309
+ return importValueOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "vcs-import", {
13310
+ sessionId,
13311
+ includeUntracked
13312
+ }));
13313
+ },
12703
13314
  async open(sessionId, id, action) {
12704
13315
  return openOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "open", {
12705
13316
  sessionId,
@@ -12756,8 +13367,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12756
13367
  const value = result.value;
12757
13368
  if (typeof value !== "object" || value === null) throw new Error("the action returned a malformed value");
12758
13369
  const outcome = value.outcome;
12759
- if (outcome !== "kept" && outcome !== "reverted" && outcome !== "missing") throw new Error("the action returned a malformed outcome");
12760
- return { outcome };
13370
+ if (outcome !== "kept" && outcome !== "reverted" && outcome !== "missing" && outcome !== "undone" && outcome !== "redone" && outcome !== "nothing") throw new Error("the action returned a malformed outcome");
13371
+ const id = value.id;
13372
+ return {
13373
+ outcome,
13374
+ id: typeof id === "string" && id.length > 0 ? id : void 0
13375
+ };
13376
+ }
13377
+ /** Narrow the vcs-import endpoint's value; a malformed wire value is a failure. */
13378
+ function importValueOf(result) {
13379
+ if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
13380
+ const value = result.value;
13381
+ if (typeof value !== "object" || value === null) throw new Error("the import returned a malformed value");
13382
+ const imported = value.imported;
13383
+ const detected = value.detected;
13384
+ if (typeof imported !== "number" || typeof detected !== "boolean") throw new Error("the import returned a malformed value");
13385
+ return {
13386
+ imported,
13387
+ detected
13388
+ };
12761
13389
  }
12762
13390
  /** Narrow the open endpoint's value; a malformed wire value is an open failure. */
12763
13391
  function openOf(result) {
@@ -12779,6 +13407,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12779
13407
  */
12780
13408
  /** An empty busy set reused as the snapshot's canonical absent value. */
12781
13409
  const EMPTY_BUSY = /* @__PURE__ */ new Set();
13410
+ /** An empty failure map reused as the snapshot's canonical absent value. */
13411
+ const EMPTY_FAILED = /* @__PURE__ */ new Map();
13412
+ /** How long a failed keep/revert hint stays visible before it auto-clears. */
13413
+ const FAILED_HINT_MS = 5e3;
12782
13414
  /**
12783
13415
  * Create the store over the review-channel port.
12784
13416
  * @param port - the typed channel port.
@@ -12795,22 +13427,51 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12795
13427
  snapshot = next;
12796
13428
  for (const listener of [...listeners]) listener();
12797
13429
  };
13430
+ const failedOf = (value) => value.failed ?? EMPTY_FAILED;
13431
+ /** Record one entry's failed keep/revert with its message; auto-clears after a few seconds. */
13432
+ const markFailed = (id, message) => {
13433
+ publish({
13434
+ ...snapshot,
13435
+ failed: new Map([...failedOf(snapshot), [id, message]])
13436
+ });
13437
+ window.setTimeout(() => {
13438
+ if (failedOf(snapshot).get(id) !== message) return;
13439
+ const next = new Map(failedOf(snapshot));
13440
+ next.delete(id);
13441
+ publish({
13442
+ ...snapshot,
13443
+ failed: next
13444
+ });
13445
+ }, FAILED_HINT_MS);
13446
+ };
13447
+ /** Drop one entry's failure marker after a successful retry. */
13448
+ const clearFailed = (id) => {
13449
+ if (!failedOf(snapshot).has(id)) return;
13450
+ const next = new Map(failedOf(snapshot));
13451
+ next.delete(id);
13452
+ publish({
13453
+ ...snapshot,
13454
+ failed: next
13455
+ });
13456
+ };
12798
13457
  const withBusy = async (id, action) => {
12799
13458
  const { error: _cleared, ...base } = snapshot;
12800
13459
  publish({
12801
13460
  ...base,
13461
+ failed: failedOf(snapshot),
12802
13462
  busy: /* @__PURE__ */ new Set([...snapshot.busy, id])
12803
13463
  });
12804
13464
  try {
12805
13465
  await action();
12806
13466
  } catch (error) {
13467
+ markFailed(id, error instanceof Error ? error.message : String(error));
12807
13468
  publish({
12808
13469
  ...snapshot,
12809
- error: error instanceof Error ? error.message : String(error),
12810
13470
  busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
12811
13471
  });
12812
13472
  return;
12813
13473
  }
13474
+ clearFailed(id);
12814
13475
  publish({
12815
13476
  ...snapshot,
12816
13477
  files: snapshot.files.filter((file) => file.id !== id),
@@ -12830,7 +13491,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12830
13491
  publish({
12831
13492
  read: true,
12832
13493
  files: [],
12833
- busy: EMPTY_BUSY
13494
+ busy: EMPTY_BUSY,
13495
+ failed: failedOf(snapshot)
12834
13496
  });
12835
13497
  return;
12836
13498
  }
@@ -12840,7 +13502,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12840
13502
  read: true,
12841
13503
  files,
12842
13504
  workspacePath,
12843
- busy: EMPTY_BUSY
13505
+ busy: EMPTY_BUSY,
13506
+ failed: failedOf(snapshot)
12844
13507
  });
12845
13508
  } catch (error) {
12846
13509
  publish({
@@ -12848,7 +13511,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12848
13511
  files: snapshot.files,
12849
13512
  workspacePath: snapshot.workspacePath,
12850
13513
  error: error instanceof Error ? error.message : String(error),
12851
- busy: snapshot.busy
13514
+ busy: snapshot.busy,
13515
+ failed: failedOf(snapshot)
12852
13516
  });
12853
13517
  }
12854
13518
  },
@@ -12871,13 +13535,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12871
13535
  try {
12872
13536
  await port.blockKeep(sessionId, id, block);
12873
13537
  } catch (error) {
13538
+ markFailed(id, error instanceof Error ? error.message : String(error));
12874
13539
  publish({
12875
13540
  ...snapshot,
12876
- error: error instanceof Error ? error.message : String(error),
12877
13541
  busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
12878
13542
  });
12879
13543
  return;
12880
13544
  }
13545
+ clearFailed(id);
12881
13546
  await this.refresh(sessionId);
12882
13547
  },
12883
13548
  async blockRevert(sessionId, id, block) {
@@ -12889,14 +13554,40 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12889
13554
  try {
12890
13555
  await port.blockRevert(sessionId, id, block);
12891
13556
  } catch (error) {
13557
+ markFailed(id, error instanceof Error ? error.message : String(error));
12892
13558
  publish({
12893
13559
  ...snapshot,
12894
- error: error instanceof Error ? error.message : String(error),
12895
13560
  busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
12896
13561
  });
12897
13562
  return;
12898
13563
  }
13564
+ clearFailed(id);
13565
+ await this.refresh(sessionId);
13566
+ },
13567
+ async undo(sessionId) {
13568
+ try {
13569
+ const value = await port.undo(sessionId);
13570
+ await this.refresh(sessionId);
13571
+ const id = value.id;
13572
+ return id !== void 0 && snapshot.files.some((file) => file.id === id) ? id : void 0;
13573
+ } catch {
13574
+ return;
13575
+ }
13576
+ },
13577
+ async redo(sessionId) {
13578
+ try {
13579
+ const value = await port.redo(sessionId);
13580
+ await this.refresh(sessionId);
13581
+ const id = value.id;
13582
+ return id !== void 0 && snapshot.files.some((file) => file.id === id) ? id : void 0;
13583
+ } catch {
13584
+ return;
13585
+ }
13586
+ },
13587
+ async importVcs(sessionId, includeUntracked) {
13588
+ const value = await port.importVcs(sessionId, includeUntracked);
12899
13589
  await this.refresh(sessionId);
13590
+ return value;
12900
13591
  },
12901
13592
  async open(sessionId, id, action) {
12902
13593
  try {
@@ -12931,15 +13622,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12931
13622
  "panel.group.others": "其他会话",
12932
13623
  "panel.aria": "待处理改动",
12933
13624
  "panel.stats": "+{added} -{removed}",
13625
+ "panel.blockPosition": "第 {current}/{total} 块",
12934
13626
  "panel.selectHint": "点击每项查看整个文件的差异;选中文本后,用底部状态栏或 Ctrl+L 复制引用",
13627
+ "panel.searchPlaceholder": "搜索",
12935
13628
  "panel.missing": "文件已不存在",
12936
13629
  "panel.missingHint": "该文件已不存在,回退会恢复该文件。",
12937
13630
  "panel.createHint": "回退会删除该新建的文件。",
13631
+ "panel.pasteOnCopy": "复制引用后自动粘贴到消息输入框",
13632
+ "panel.pasteOnCopyDesc": "开启后,复制的引用会自动填入消息输入框,输入框会获得焦点。",
13633
+ "panel.importUntracked": "导入未跟踪的文件改动",
13634
+ "panel.importUntrackedDesc": "开启后,导入工作区改动会包含未跟踪/未版本化的新增文件(Git 未跟踪、SVN 未版本化、Perforce 未版本化)。收集改动需要扫描整个工作区,文件数量大时可能较慢。",
13635
+ "settings.tabLabel": "改动审批",
12938
13636
  "row.create": "新增文件",
13637
+ "row.failed": "失败",
12939
13638
  "row.added": "+{added}",
12940
13639
  "row.removed": "-{removed}",
12941
13640
  "action.keep": "保留",
12942
13641
  "action.revert": "回退",
13642
+ "action.keepAll": "全部保留",
13643
+ "action.revertAll": "全部回退",
12943
13644
  "action.openFile": "打开文件",
12944
13645
  "action.revealFile": "打开所在目录",
12945
13646
  "action.busy": "处理中…",
@@ -12950,6 +13651,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12950
13651
  "action.langAuto": "自动",
12951
13652
  "action.langAutoDetected": "自动:{lang}",
12952
13653
  "action.langSelect": "高亮语言",
13654
+ "action.toggleOn": "打开",
13655
+ "action.toggleOff": "关闭",
13656
+ "action.importVcs": "导入工作区改动",
13657
+ "action.importVcsBusy": "导入中…",
13658
+ "panel.importNone": "没有可导入的改动",
13659
+ "panel.importDone": "已导入 {count} 个改动",
13660
+ "panel.importNoVcs": "未检测到版本控制(Git / SVN / Perforce)",
13661
+ "panel.importFailed": "导入失败:{message}",
13662
+ "action.search": "搜索 (Ctrl+F)",
13663
+ "action.settings": "设置",
12953
13664
  "action.expand": "展开全屏",
12954
13665
  "action.exitFullscreen": "退出全屏",
12955
13666
  "action.close": "关闭",
@@ -12967,15 +13678,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12967
13678
  "panel.group.others": "Other sessions",
12968
13679
  "panel.aria": "Pending changes",
12969
13680
  "panel.stats": "+{added} -{removed}",
13681
+ "panel.blockPosition": "{current}/{total}",
12970
13682
  "panel.selectHint": "Select an item to review its whole-file diff; select text and copy its reference from the status bar (Ctrl+L)",
13683
+ "panel.searchPlaceholder": "Search",
12971
13684
  "panel.missing": "File is gone",
12972
13685
  "panel.missingHint": "This file no longer exists; reverting restores it.",
12973
13686
  "panel.createHint": "Reverting removes this created file.",
13687
+ "panel.pasteOnCopy": "Paste the copied reference into the composer",
13688
+ "panel.pasteOnCopyDesc": "When enabled, a copied reference is pasted into the composer, which then receives focus.",
13689
+ "panel.importUntracked": "Import changes to untracked files",
13690
+ "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.",
13691
+ "settings.tabLabel": "Diff Approval",
12974
13692
  "row.create": "New file",
13693
+ "row.failed": "Failed",
12975
13694
  "row.added": "+{added}",
12976
13695
  "row.removed": "-{removed}",
12977
13696
  "action.keep": "Keep",
12978
13697
  "action.revert": "Revert",
13698
+ "action.keepAll": "Keep all",
13699
+ "action.revertAll": "Revert all",
12979
13700
  "action.openFile": "Open file",
12980
13701
  "action.revealFile": "Reveal in folder",
12981
13702
  "action.busy": "Working…",
@@ -12986,6 +13707,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12986
13707
  "action.langAuto": "Auto",
12987
13708
  "action.langAutoDetected": "Auto: {lang}",
12988
13709
  "action.langSelect": "Highlight language",
13710
+ "action.toggleOn": "On",
13711
+ "action.toggleOff": "Off",
13712
+ "action.importVcs": "Import workspace changes",
13713
+ "action.importVcsBusy": "Importing…",
13714
+ "panel.importNone": "No changes to import",
13715
+ "panel.importDone": "Imported {count} changes",
13716
+ "panel.importNoVcs": "No version control detected (Git / SVN / Perforce)",
13717
+ "panel.importFailed": "Import failed: {message}",
13718
+ "action.search": "Search (Ctrl+F)",
13719
+ "action.settings": "Settings",
12989
13720
  "action.expand": "Expand fullscreen",
12990
13721
  "action.exitFullscreen": "Exit fullscreen",
12991
13722
  "action.close": "Close",
@@ -13012,6 +13743,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13012
13743
  zh,
13013
13744
  en
13014
13745
  }), "ui-diff-approval: dictionaries");
13746
+ const t = ctx.locale.bind(NS);
13015
13747
  const store = createPendingDiffStore(createDiffApprovalPort(ctx.get("connection").rpc));
13016
13748
  ctx.on("connection/reset", () => {
13017
13749
  store.reset();
@@ -13029,9 +13761,29 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13029
13761
  onRevert: (sessionId, path) => store.revert(sessionId, path),
13030
13762
  onBlockKeep: (sessionId, id, block) => store.blockKeep(sessionId, id, block),
13031
13763
  onBlockRevert: (sessionId, id, block) => store.blockRevert(sessionId, id, block),
13032
- onOpen: (sessionId, id, action) => store.open(sessionId, id, action)
13764
+ onOpen: (sessionId, id, action) => store.open(sessionId, id, action),
13765
+ onUndo: (sessionId) => store.undo(sessionId),
13766
+ onRedo: (sessionId) => store.redo(sessionId),
13767
+ onImportVcs: (sessionId, includeUntracked) => store.importVcs(sessionId, includeUntracked),
13768
+ onPasteReference: (sessionId, reference) => {
13769
+ const actx = ctx.get("sessions")?.scope(sessionId);
13770
+ if (actx === void 0) return;
13771
+ const conversation = actx.get("conversation");
13772
+ if (conversation?.input === void 0) return;
13773
+ const textarea = document.querySelector("[data-composer-card] textarea");
13774
+ const base = textarea?.value ?? "";
13775
+ conversation.input.for(actx).setDraft(base === "" ? reference : `${base} ${reference}`);
13776
+ textarea?.focus();
13777
+ }
13033
13778
  })
13034
13779
  }, PendingPanel));
13780
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
13781
+ name: "settings.section",
13782
+ id: "diff-approval",
13783
+ order: 100,
13784
+ label: () => t("settings.tabLabel"),
13785
+ locale: NS
13786
+ }, DiffApprovalSettingsTab));
13035
13787
  }
13036
13788
  //#endregion
13037
13789
  exports.DIFF_APPROVAL_CHANNEL = DIFF_APPROVAL_CHANNEL;