dsh-diff-approval 0.7.0 → 0.9.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: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_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}.F1KBNa_notice{z-index:60;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:360px;box-shadow:var(--dsw-shadow-lv3);border-radius:10px;align-items:flex-start;gap:12px;padding:12px 14px;display:flex;position:fixed;bottom:24px;right:24px}.F1KBNa_noticeText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);flex:1;margin:0;line-height:1.45}.F1KBNa_noticeButton{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font:var(--dsw-font-caption);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px}.F1KBNa_noticeButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}";
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,73 +11662,95 @@ 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
- "gutter": "F1KBNa_gutter",
11635
- "missingHint": "F1KBNa_missingHint",
11665
+ "rows": "F1KBNa_rows",
11666
+ "flexSpacer": "F1KBNa_flexSpacer",
11667
+ "importNote": "F1KBNa_importNote",
11668
+ "detailEmpty": "F1KBNa_detailEmpty",
11669
+ "divergedHint": "F1KBNa_divergedHint",
11670
+ "detail": "F1KBNa_detail",
11671
+ "note": "F1KBNa_note",
11672
+ "settingsRowDesc": "F1KBNa_settingsRowDesc",
11673
+ "add": "F1KBNa_add",
11636
11674
  "searchCount": "F1KBNa_searchCount",
11637
- "hint": "F1KBNa_hint",
11638
- "layer": "F1KBNa_layer",
11639
11675
  "footerButtons": "F1KBNa_footerButtons",
11640
- "langSelect": "F1KBNa_langSelect",
11641
- "addCount": "F1KBNa_addCount",
11642
- "rowPath": "F1KBNa_rowPath",
11643
- "context": "F1KBNa_context",
11676
+ "settingsRowTitle": "F1KBNa_settingsRowTitle",
11677
+ "title": "F1KBNa_title",
11678
+ "hint": "F1KBNa_hint",
11679
+ "overviewRuler": "F1KBNa_overviewRuler",
11680
+ "gutter": "F1KBNa_gutter",
11681
+ "code": "F1KBNa_code",
11682
+ "badge": "F1KBNa_badge",
11683
+ "badgeLabel": "F1KBNa_badgeLabel",
11684
+ "rail": "F1KBNa_rail",
11685
+ "diffBody": "F1KBNa_diffBody",
11686
+ "rowMeta": "F1KBNa_rowMeta",
11687
+ "resizeHandle": "F1KBNa_resizeHandle",
11644
11688
  "kindTag": "F1KBNa_kindTag",
11645
11689
  "states": "F1KBNa_states",
11646
- "resizeHandle": "F1KBNa_resizeHandle",
11647
- "diffStats": "F1KBNa_diffStats",
11690
+ "panel": "F1KBNa_panel",
11691
+ "diff": "F1KBNa_diff",
11692
+ "statusAction": "F1KBNa_statusAction",
11693
+ "blockPosition": "F1KBNa_blockPosition",
11694
+ "notice": "F1KBNa_notice",
11695
+ "noticeText": "F1KBNa_noticeText",
11696
+ "rowFailed": "F1KBNa_rowFailed",
11697
+ "context": "F1KBNa_context",
11648
11698
  "del": "F1KBNa_del",
11649
- "searchInput": "F1KBNa_searchInput",
11650
- "expand": "F1KBNa_expand",
11651
- "searchBar": "F1KBNa_searchBar",
11652
11699
  "markerDel": "F1KBNa_markerDel",
11653
- "title": "F1KBNa_title",
11654
- "vSpacer": "F1KBNa_vSpacer",
11700
+ "langLabel": "F1KBNa_langLabel",
11701
+ "missing": "F1KBNa_missing",
11702
+ "diffStats": "F1KBNa_diffStats",
11703
+ "actionError": "F1KBNa_actionError",
11704
+ "blockFlash": "F1KBNa_blockFlash",
11705
+ "noticeButton": "F1KBNa_noticeButton",
11706
+ "iconAction": "F1KBNa_iconAction",
11707
+ "diffFlash": "F1KBNa_diffFlash",
11708
+ "noteCentered": "F1KBNa_noteCentered",
11655
11709
  "badgeCount": "F1KBNa_badgeCount",
11656
- "kindHint": "F1KBNa_kindHint",
11657
- "rail": "F1KBNa_rail",
11710
+ "statusBar": "F1KBNa_statusBar",
11711
+ "listScroll": "F1KBNa_listScroll",
11658
11712
  "expandExpanded": "F1KBNa_expandExpanded",
11659
- "action": "F1KBNa_action",
11660
- "diffHeader": "F1KBNa_diffHeader",
11661
- "fileList": "F1KBNa_fileList",
11662
- "diffActions": "F1KBNa_diffActions",
11713
+ "lines": "F1KBNa_lines",
11663
11714
  "readError": "F1KBNa_readError",
11664
- "note": "F1KBNa_note",
11665
- "line": "F1KBNa_line",
11666
- "divergedHint": "F1KBNa_divergedHint",
11667
- "group": "F1KBNa_group",
11668
- "diff": "F1KBNa_diff",
11669
- "code": "F1KBNa_code",
11715
+ "layer": "F1KBNa_layer",
11716
+ "expand": "F1KBNa_expand",
11717
+ "blockActions": "F1KBNa_blockActions",
11718
+ "actionPrimary": "F1KBNa_actionPrimary",
11719
+ "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
11720
+ "diffBodyWrap": "F1KBNa_diffBodyWrap",
11721
+ "diffHeader": "F1KBNa_diffHeader",
11722
+ "rowPath": "F1KBNa_rowPath",
11670
11723
  "headerActions": "F1KBNa_headerActions",
11671
- "detail": "F1KBNa_detail",
11672
- "missing": "F1KBNa_missing",
11673
- "add": "F1KBNa_add",
11674
- "badgeLabel": "F1KBNa_badgeLabel",
11675
- "iconAction": "F1KBNa_iconAction",
11676
- "markerAdd": "F1KBNa_markerAdd",
11677
- "row": "F1KBNa_row",
11678
- "diffBody": "F1KBNa_diffBody",
11679
- "langLabel": "F1KBNa_langLabel",
11680
- "lines": "F1KBNa_lines",
11681
- "panel": "F1KBNa_panel",
11724
+ "kindHint": "F1KBNa_kindHint",
11725
+ "diffPath": "F1KBNa_diffPath",
11682
11726
  "split": "F1KBNa_split",
11683
- "badge": "F1KBNa_badge",
11727
+ "importButton": "F1KBNa_importButton",
11728
+ "diffActions": "F1KBNa_diffActions",
11684
11729
  "delCount": "F1KBNa_delCount",
11685
- "overviewRuler": "F1KBNa_overviewRuler",
11686
- "rowMeta": "F1KBNa_rowMeta",
11687
- "blockActions": "F1KBNa_blockActions",
11688
- "rows": "F1KBNa_rows",
11689
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
11690
- "detailEmpty": "F1KBNa_detailEmpty",
11691
- "close": "F1KBNa_close",
11692
- "statusBar": "F1KBNa_statusBar",
11693
- "actionPrimary": "F1KBNa_actionPrimary",
11694
11730
  "rowHead": "F1KBNa_rowHead",
11695
- "statusAction": "F1KBNa_statusAction",
11696
- "diffPath": "F1KBNa_diffPath",
11697
- "flexSpacer": "F1KBNa_flexSpacer",
11698
- "blockPosition": "F1KBNa_blockPosition",
11731
+ "overviewMarker": "F1KBNa_overviewMarker",
11699
11732
  "header": "F1KBNa_header",
11700
- "overviewMarker": "F1KBNa_overviewMarker"
11733
+ "searchInput": "F1KBNa_searchInput",
11734
+ "markerAdd": "F1KBNa_markerAdd",
11735
+ "vSpacer": "F1KBNa_vSpacer",
11736
+ "settingsRowText": "F1KBNa_settingsRowText",
11737
+ "missingHint": "F1KBNa_missingHint",
11738
+ "group": "F1KBNa_group",
11739
+ "emptyState": "F1KBNa_emptyState",
11740
+ "bulkActions": "F1KBNa_bulkActions",
11741
+ "settingsSelector": "F1KBNa_settingsSelector",
11742
+ "searchBar": "F1KBNa_searchBar",
11743
+ "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
11744
+ "row": "F1KBNa_row",
11745
+ "close": "F1KBNa_close",
11746
+ "line": "F1KBNa_line",
11747
+ "settingsRow": "F1KBNa_settingsRow",
11748
+ "fileList": "F1KBNa_fileList",
11749
+ "settingsPage": "F1KBNa_settingsPage",
11750
+ "addCount": "F1KBNa_addCount",
11751
+ "action": "F1KBNa_action",
11752
+ "langSelect": "F1KBNa_langSelect",
11753
+ "actionQuietDisabled": "F1KBNa_actionQuietDisabled"
11701
11754
  };
11702
11755
  //#endregion
11703
11756
  //#region lib/types/client/PendingPanel.js
@@ -11731,6 +11784,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11731
11784
  const MAX_LIST_WIDTH_PX = 560;
11732
11785
  /** Fixed diff-row height in px; the virtual window and jump math are built on it. */
11733
11786
  const ROW_HEIGHT_PX = 22;
11787
+ /** The overview ruler's width in px (mirrors `.overviewRuler`). The flash is
11788
+ * kept off it even when the scroller has no vertical scrollbar. */
11789
+ const OVERVIEW_RULER_WIDTH_PX = 4;
11734
11790
  /** Rows rendered beyond the visible window in each direction. */
11735
11791
  const OVERSCAN_ROWS = 8;
11736
11792
  /** Height of the floating per-block Keep/Revert frame in px: 26px actions +
@@ -11742,12 +11798,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11742
11798
  const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
11743
11799
  return index < 0 ? path : path.slice(index + 1);
11744
11800
  }
11801
+ /**
11802
+ * Open the DSH settings dialog and switch to this plugin's section. The
11803
+ * settings shell keeps its open state and the active section id as
11804
+ * component-local viewing state with no cross-plugin service, so the dialog
11805
+ * is opened by clicking the sidebar's settings trigger and then the nav cell
11806
+ * for this section. `element.click()` still fires React's delegated click
11807
+ * handlers, reaching the same state transitions a user gesture would.
11808
+ * @param sectionLabel - the nav label of this plugin's settings section.
11809
+ */
11810
+ function openSettingsSection(sectionLabel) {
11811
+ const trigger = document.querySelector("button[aria-haspopup=\"dialog\"]");
11812
+ if (trigger === null) return;
11813
+ trigger.click();
11814
+ requestAnimationFrame(() => {
11815
+ Array.from(document.querySelectorAll("button")).find((button) => button.textContent?.trim() === sectionLabel)?.click();
11816
+ });
11817
+ }
11745
11818
  /** The diff body's row class per line kind. */
11746
11819
  const ROW_CLASS = {
11747
11820
  context: PendingPanel_module_css_default.context,
11748
11821
  del: PendingPanel_module_css_default.del,
11749
11822
  add: PendingPanel_module_css_default.add
11750
11823
  };
11824
+ /** Empty failure map reused as the snapshot's canonical absent value. */
11825
+ const EMPTY_FAILED_MAP = /* @__PURE__ */ new Map();
11751
11826
  /**
11752
11827
  * One rendered diff row, memoized so a poll or an unrelated state change
11753
11828
  * does not re-render rows whose content, highlight, and focus are unchanged.
@@ -11909,7 +11984,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11909
11984
  };
11910
11985
  }
11911
11986
  /** One row of the file list: the clickable head in the left pane. */
11912
- function PendingFileRow({ file, selected, t, onSelect }) {
11987
+ function PendingFileRow({ file, selected, failedMessage, t, onSelect }) {
11913
11988
  const stats = (0, react.useMemo)(() => computeWholeFileDiff(file.oldText, file.newText), [file.oldText, file.newText]);
11914
11989
  return (0, react_jsx_runtime.jsx)("li", {
11915
11990
  className: PendingPanel_module_css_default.row,
@@ -11938,6 +12013,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11938
12013
  title: t("panel.missingHint"),
11939
12014
  children: t("panel.missing")
11940
12015
  }),
12016
+ failedMessage !== void 0 && (0, react_jsx_runtime.jsx)("span", {
12017
+ className: PendingPanel_module_css_default.rowFailed,
12018
+ title: failedMessage,
12019
+ children: t("row.failed")
12020
+ }),
11941
12021
  (0, react_jsx_runtime.jsxs)("span", {
11942
12022
  className: PendingPanel_module_css_default.rowMeta,
11943
12023
  children: [(0, react_jsx_runtime.jsx)("span", {
@@ -11954,7 +12034,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11954
12034
  });
11955
12035
  }
11956
12036
  /** The selected file's diff, actions, jump controls, and copy toolbar. */
11957
- function PendingDiff({ file, busy, workspacePath, jumpSignal, t, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen }) {
12037
+ function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, failedMessage, onPasteReference, t, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen }) {
11958
12038
  const [langOverride, setLangOverride] = (0, react.useState)(void 0);
11959
12039
  const [langMenuOpen, setLangMenuOpen] = (0, react.useState)(false);
11960
12040
  const langMenuItems = (0, react.useMemo)(() => [{
@@ -12039,10 +12119,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12039
12119
  const [searchQuery, setSearchQuery] = (0, react.useState)("");
12040
12120
  const [searchIndex, setSearchIndex] = (0, react.useState)(0);
12041
12121
  const searchInputRef = (0, react.useRef)(null);
12122
+ const [flashKey, setFlashKey] = (0, react.useState)(0);
12042
12123
  (0, react.useEffect)(() => {
12043
12124
  setFocus(0);
12044
- setScrollTop(0);
12045
- if (bodyRef.current !== null) bodyRef.current.scrollTop = 0;
12125
+ bodyRef.current?.focus();
12126
+ setFlashKey((key) => key + 1);
12046
12127
  setHoveredBlock(void 0);
12047
12128
  setSelection(void 0);
12048
12129
  setLangOverride(void 0);
@@ -12052,6 +12133,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12052
12133
  setSearchQuery("");
12053
12134
  setSearchIndex(0);
12054
12135
  }, [file.id]);
12136
+ (0, react.useEffect)(() => {
12137
+ if (undoFlash === 0) return;
12138
+ setFocus(0);
12139
+ setScrollTick((tick) => tick + 1);
12140
+ setFlashKey((key) => key + 1);
12141
+ }, [undoFlash]);
12055
12142
  const blockRanges = (0, react.useMemo)(() => {
12056
12143
  return model.blocks.map((block) => blockRangesOf(model.diff.rows, block));
12057
12144
  }, [model]);
@@ -12086,7 +12173,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12086
12173
  (0, react.useEffect)(() => {
12087
12174
  setSearchIndex(0);
12088
12175
  }, [searchQuery, file.id]);
12089
- (0, react.useEffect)(() => {
12176
+ (0, react.useLayoutEffect)(() => {
12090
12177
  const row = currentSearchRow;
12091
12178
  if (row === void 0) return;
12092
12179
  const body = bodyRef.current;
@@ -12132,7 +12219,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12132
12219
  observer?.disconnect();
12133
12220
  };
12134
12221
  }, [file.id]);
12135
- (0, react.useEffect)(() => {
12222
+ (0, react.useLayoutEffect)(() => {
12136
12223
  if (rowCount === 0) return;
12137
12224
  const block = model.blocks[focus];
12138
12225
  if (block === void 0) return;
@@ -12161,6 +12248,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12161
12248
  return 0;
12162
12249
  });
12163
12250
  setScrollTick((tick) => tick + 1);
12251
+ setFlashKey((key) => key + 1);
12164
12252
  };
12165
12253
  (0, react.useEffect)(() => {
12166
12254
  if (jumpSignal === 0) return;
@@ -12190,10 +12278,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12190
12278
  if (selectionReference === void 0) return;
12191
12279
  if (!await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(selectionReference)) return;
12192
12280
  setCopied(true);
12281
+ if (pasteOnCopyEnabled()) onPasteReference(file.sessionId, selectionReference);
12193
12282
  window.setTimeout(() => {
12194
12283
  setCopied(false);
12195
12284
  }, 1500);
12196
- }, [selectionReference]);
12285
+ }, [
12286
+ file.sessionId,
12287
+ onPasteReference,
12288
+ selectionReference
12289
+ ]);
12197
12290
  (0, react.useEffect)(() => {
12198
12291
  const onKeyDown = (event) => {
12199
12292
  if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
@@ -12202,9 +12295,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12202
12295
  event.preventDefault();
12203
12296
  copySelection();
12204
12297
  };
12205
- document.addEventListener("keydown", onKeyDown);
12298
+ window.addEventListener("keydown", onKeyDown, true);
12206
12299
  return () => {
12207
- document.removeEventListener("keydown", onKeyDown);
12300
+ window.removeEventListener("keydown", onKeyDown, true);
12208
12301
  };
12209
12302
  }, [copySelection]);
12210
12303
  (0, react.useEffect)(() => {
@@ -12221,8 +12314,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12221
12314
  window.removeEventListener("keydown", onKeyDown, true);
12222
12315
  };
12223
12316
  }, []);
12317
+ const jumpRef = (0, react.useRef)(jump);
12318
+ jumpRef.current = jump;
12319
+ (0, react.useEffect)(() => {
12320
+ const onKeyDown = (event) => {
12321
+ if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return;
12322
+ const key = event.key.toLowerCase();
12323
+ if (key !== "arrowup" && key !== "arrowdown") return;
12324
+ const target = event.target;
12325
+ if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
12326
+ event.preventDefault();
12327
+ jumpRef.current(key === "arrowup" ? -1 : 1);
12328
+ };
12329
+ window.addEventListener("keydown", onKeyDown, true);
12330
+ return () => {
12331
+ window.removeEventListener("keydown", onKeyDown, true);
12332
+ };
12333
+ }, []);
12224
12334
  const focusedBlock = model.blocks.length > 0 ? model.blocks[focus] : void 0;
12225
12335
  const inFocusedBlock = (index) => focusedBlock !== void 0 && index >= focusedBlock.start && index <= focusedBlock.end;
12336
+ const flashWidth = (() => {
12337
+ const scroller = bodyRef.current;
12338
+ if (scroller === null) return 0;
12339
+ const scrollbarWidth = scroller.offsetWidth - scroller.clientWidth;
12340
+ return Math.max(0, scroller.clientWidth - (scrollbarWidth > 0 ? 0 : OVERVIEW_RULER_WIDTH_PX));
12341
+ })();
12226
12342
  return (0, react_jsx_runtime.jsxs)("div", {
12227
12343
  className: PendingPanel_module_css_default.diff,
12228
12344
  "data-diff-approval-diff": true,
@@ -12281,7 +12397,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12281
12397
  children: t("panel.createHint")
12282
12398
  }),
12283
12399
  model.blocks.length > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12284
- label: t("action.prevDiff"),
12400
+ label: `${t("action.prevDiff")} (Ctrl+↑)`,
12285
12401
  side: "bottom",
12286
12402
  delayMs: 500,
12287
12403
  children: (0, react_jsx_runtime.jsx)("button", {
@@ -12296,7 +12412,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12296
12412
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
12297
12413
  })
12298
12414
  }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12299
- label: t("action.nextDiff"),
12415
+ label: `${t("action.nextDiff")} (Ctrl+↓)`,
12300
12416
  side: "bottom",
12301
12417
  delayMs: 500,
12302
12418
  children: (0, react_jsx_runtime.jsx)("button", {
@@ -12327,26 +12443,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12327
12443
  (0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
12328
12444
  (0, react_jsx_runtime.jsx)("button", {
12329
12445
  type: "button",
12330
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
12446
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary} ${PendingPanel_module_css_default.actionQuietDisabled}`,
12331
12447
  "data-diff-keep": true,
12332
12448
  disabled: busy,
12333
12449
  onClick: () => {
12334
12450
  onKeep(file.sessionId, file.id);
12335
12451
  },
12336
- children: busy ? t("action.busy") : t("action.keep")
12452
+ children: t("action.keep")
12337
12453
  }),
12338
12454
  (0, react_jsx_runtime.jsx)("button", {
12339
12455
  type: "button",
12340
- className: PendingPanel_module_css_default.action,
12456
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionQuietDisabled}`,
12341
12457
  "data-diff-revert": true,
12342
12458
  disabled: busy,
12343
12459
  onClick: () => {
12344
12460
  onRevert(file.sessionId, file.id);
12345
12461
  },
12346
- children: busy ? t("action.busy") : t("action.revert")
12462
+ children: t("action.revert")
12347
12463
  })
12348
12464
  ]
12349
12465
  }),
12466
+ failedMessage !== void 0 && (0, react_jsx_runtime.jsx)("p", {
12467
+ className: PendingPanel_module_css_default.actionError,
12468
+ "data-diff-action-error": true,
12469
+ children: failedMessage
12470
+ }),
12350
12471
  file.missing && (0, react_jsx_runtime.jsx)("p", {
12351
12472
  className: PendingPanel_module_css_default.missingHint,
12352
12473
  children: t("panel.missingHint")
@@ -12357,6 +12478,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12357
12478
  (0, react_jsx_runtime.jsxs)("div", {
12358
12479
  className: PendingPanel_module_css_default.diffBody,
12359
12480
  ref: bodyRef,
12481
+ tabIndex: 0,
12360
12482
  onScroll,
12361
12483
  onMouseLeave: () => {
12362
12484
  setHoveredBlock(void 0);
@@ -12430,6 +12552,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12430
12552
  ]
12431
12553
  })]
12432
12554
  }),
12555
+ focusedBlock !== void 0 && flashKey > 0 && (0, react_jsx_runtime.jsx)("div", {
12556
+ className: PendingPanel_module_css_default.blockFlash,
12557
+ "data-diff-block-flash": true,
12558
+ style: {
12559
+ top: focusedBlock.start * ROW_HEIGHT_PX - scrollTop,
12560
+ height: Math.min((focusedBlock.end - focusedBlock.start + 1) * ROW_HEIGHT_PX, viewportHeight > 0 ? viewportHeight : Number.POSITIVE_INFINITY),
12561
+ width: flashWidth
12562
+ }
12563
+ }, flashKey),
12433
12564
  searchOpen && (0, react_jsx_runtime.jsxs)("div", {
12434
12565
  className: PendingPanel_module_css_default.searchBar,
12435
12566
  "data-diff-searchbar": true,
@@ -12563,16 +12694,36 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12563
12694
  });
12564
12695
  }
12565
12696
  /** Render the pending-edit review panel and its unified footer action. */
12566
- function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, t }) {
12697
+ function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPasteReference, onUndo, onRedo, onImportVcs, onAckRedoCleared, t }) {
12567
12698
  const current = useSessions((state) => state.current);
12699
+ const currentBlank = useSessions((state) => {
12700
+ const id = state.current;
12701
+ return id === void 0 ? false : state.byId[id]?.blank ?? false;
12702
+ });
12703
+ const noSession = current === void 0 || currentBlank;
12568
12704
  const snapshot = usePending((snapshot) => snapshot);
12569
12705
  const [open, setOpen] = (0, react.useState)(false);
12570
12706
  const [selected, setSelected] = (0, react.useState)("");
12571
12707
  /** Bumped when the already-open file is clicked again, to jump to the next
12572
12708
  * diff block in the open file's detail pane. */
12573
12709
  const [jumpSignal, setJumpSignal] = (0, react.useState)(0);
12574
- /** Pending count captured at open time; auto-close needs a list that emptied. */
12575
- const [openedCount, setOpenedCount] = (0, react.useState)(0);
12710
+ /** Bumped when an undo/redo affected the already-open file, so its detail
12711
+ * pane re-selects the undone diff (flash). */
12712
+ const [undoFlash, setUndoFlash] = (0, react.useState)(0);
12713
+ /** Which bulk decision (keep-all / revert-all) is running; null when idle. */
12714
+ const [bulkBusy, setBulkBusy] = (0, react.useState)(null);
12715
+ /** True while a workspace-changes import is running. */
12716
+ const [importBusy, setImportBusy] = (0, react.useState)(false);
12717
+ /** Feedback under the empty note after an import (no VCS, or a failure). */
12718
+ const [importNote, setImportNote] = (0, react.useState)(void 0);
12719
+ /** Whether the last import note is a failure (drives the alert role). */
12720
+ const [importFailed, setImportFailed] = (0, react.useState)(false);
12721
+ /** A transient banner for an import that found nothing to bring in. */
12722
+ const [importToast, setImportToast] = (0, react.useState)(null);
12723
+ /** A transient banner for a keep/revert failure. */
12724
+ const [actionToast, setActionToast] = (0, react.useState)(null);
12725
+ /** Whether the redo-cleared notice is showing (bottom-right, OK to dismiss). */
12726
+ const [redoClearedNotice, setRedoClearedNotice] = (0, react.useState)(false);
12576
12727
  /** Bottom offset tracking the chat composer's top edge so the input stays visible. */
12577
12728
  const [bottomPx, setBottomPx] = (0, react.useState)(FALLBACK_BOTTOM_PX);
12578
12729
  /** Fullscreen expanded: the panel bottom pins to the window edge, ignoring the composer offset. */
@@ -12589,6 +12740,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12589
12740
  clearInterval(timer);
12590
12741
  };
12591
12742
  }, [current, onRefresh]);
12743
+ (0, react.useEffect)(() => {
12744
+ if (!open || !snapshot.redoCleared) return;
12745
+ onAckRedoCleared();
12746
+ setRedoClearedNotice(true);
12747
+ }, [
12748
+ open,
12749
+ snapshot.redoCleared,
12750
+ onAckRedoCleared
12751
+ ]);
12592
12752
  (0, react.useEffect)(() => {
12593
12753
  if (!open) return;
12594
12754
  const MEASURE_INTERVAL_MS = 400;
@@ -12633,6 +12793,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12633
12793
  };
12634
12794
  }, [open]);
12635
12795
  const files = snapshot.files.filter((file) => file.sessionId === current);
12796
+ /** Per-file keep/revert failures, surfaced inline on the row and detail. */
12797
+ const failed = snapshot.failed ?? EMPTY_FAILED_MAP;
12798
+ const failedInitialized = (0, react.useRef)(false);
12799
+ const failedRef = (0, react.useRef)(void 0);
12800
+ (0, react.useEffect)(() => {
12801
+ const current = snapshot.failed;
12802
+ if (!failedInitialized.current) {
12803
+ failedInitialized.current = true;
12804
+ failedRef.current = current;
12805
+ return;
12806
+ }
12807
+ const previous = failedRef.current;
12808
+ const fresh = [...(current ?? EMPTY_FAILED_MAP).entries()].filter(([id]) => previous === void 0 || !previous.has(id));
12809
+ if (fresh.length > 0) setActionToast(fresh[0][1]);
12810
+ failedRef.current = current;
12811
+ }, [snapshot.failed]);
12636
12812
  (0, react.useEffect)(() => {
12637
12813
  if (!open) return;
12638
12814
  if (selected !== "" && files.some((file) => file.id === selected)) return;
@@ -12644,25 +12820,44 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12644
12820
  files,
12645
12821
  selected
12646
12822
  ]);
12647
- (0, react.useEffect)(() => {
12648
- if (open && openedCount > 0 && snapshot.read && snapshot.error === void 0 && files.length === 0) {
12649
- setOpenedCount(0);
12650
- setOpen(false);
12823
+ const runImportVcs = async () => {
12824
+ if (current === void 0 || importBusy) return;
12825
+ setImportBusy(true);
12826
+ setImportNote(void 0);
12827
+ setImportFailed(false);
12828
+ setImportToast(null);
12829
+ try {
12830
+ const value = await onImportVcs(current, includeUntrackedEnabled());
12831
+ await onRefresh(current);
12832
+ if (!value.detected) setImportNote(t("panel.importNoVcs"));
12833
+ else if (value.imported === 0) setImportToast(t("panel.importNone"));
12834
+ } catch (error) {
12835
+ setImportFailed(true);
12836
+ setImportNote(t("panel.importFailed", { message: error instanceof Error ? error.message : String(error) }));
12837
+ } finally {
12838
+ setImportBusy(false);
12651
12839
  }
12652
- }, [
12653
- open,
12654
- openedCount,
12655
- snapshot.read,
12656
- snapshot.error,
12657
- files.length
12658
- ]);
12840
+ };
12659
12841
  const toggleOpen = () => {
12660
- if (!open) setOpenedCount(files.length);
12661
12842
  setOpen((value) => !value);
12662
12843
  };
12844
+ (0, react.useEffect)(() => {
12845
+ if (noSession) setOpen(false);
12846
+ }, [noSession]);
12847
+ /** Run the same decision over every current-session file, sequentially. */
12848
+ const runBulk = async (kind) => {
12849
+ setBulkBusy(kind);
12850
+ try {
12851
+ for (const file of files) if (kind === "keep") await onKeep(file.sessionId, file.id);
12852
+ else await onRevert(file.sessionId, file.id);
12853
+ } finally {
12854
+ setBulkBusy(null);
12855
+ }
12856
+ };
12663
12857
  const renderEntry = (entry) => (0, react_jsx_runtime.jsx)(PendingFileRow, {
12664
12858
  file: entry,
12665
12859
  selected: selected === entry.id,
12860
+ failedMessage: failed.get(entry.id),
12666
12861
  t,
12667
12862
  onSelect: (id) => {
12668
12863
  if (id === selected) setJumpSignal((signal) => signal + 1);
@@ -12670,6 +12865,42 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12670
12865
  }
12671
12866
  }, entry.id);
12672
12867
  const selectedFile = files.find((file) => file.id === selected);
12868
+ const handleUndo = async (sessionId) => {
12869
+ const id = await onUndo(sessionId);
12870
+ if (id === void 0) return;
12871
+ if (id === selected) setUndoFlash((signal) => signal + 1);
12872
+ else setSelected(id);
12873
+ };
12874
+ const handleRedo = async (sessionId) => {
12875
+ const id = await onRedo(sessionId);
12876
+ if (id === void 0) return;
12877
+ if (id === selected) setUndoFlash((signal) => signal + 1);
12878
+ else setSelected(id);
12879
+ };
12880
+ (0, react.useEffect)(() => {
12881
+ if (!open || current === void 0) return;
12882
+ const onKeyDown = (event) => {
12883
+ if (!(event.ctrlKey || event.metaKey) || event.altKey) return;
12884
+ const key = event.key.toLowerCase();
12885
+ if (key !== "z" && key !== "y") return;
12886
+ const target = event.target;
12887
+ if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
12888
+ event.preventDefault();
12889
+ if (key === "z") {
12890
+ if (event.shiftKey) handleRedo(current);
12891
+ else handleUndo(current);
12892
+ } else handleRedo(current);
12893
+ };
12894
+ window.addEventListener("keydown", onKeyDown, true);
12895
+ return () => {
12896
+ window.removeEventListener("keydown", onKeyDown, true);
12897
+ };
12898
+ }, [
12899
+ open,
12900
+ current,
12901
+ handleUndo,
12902
+ handleRedo
12903
+ ]);
12673
12904
  /** Drag the list/detail divider; width follows the pointer within its bounds. */
12674
12905
  const startResize = (event) => {
12675
12906
  if (event.button !== 0) return;
@@ -12698,127 +12929,333 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12698
12929
  };
12699
12930
  return (0, react_jsx_runtime.jsxs)("div", {
12700
12931
  className: wide ? PendingPanel_module_css_default.layer : `${PendingPanel_module_css_default.layer} ${PendingPanel_module_css_default.rail}`,
12701
- children: [open && (0, react_jsx_runtime.jsxs)("section", {
12702
- className: PendingPanel_module_css_default.panel,
12703
- style: { bottom: expanded ? PANEL_INSET_PX : bottomPx },
12704
- "data-diff-approval-panel": true,
12705
- "aria-label": t("panel.title"),
12706
- children: [(0, react_jsx_runtime.jsxs)("header", {
12707
- className: PendingPanel_module_css_default.header,
12708
- children: [(0, react_jsx_runtime.jsx)("span", {
12709
- className: PendingPanel_module_css_default.title,
12710
- children: t("panel.title")
12711
- }), (0, react_jsx_runtime.jsxs)("div", {
12712
- className: PendingPanel_module_css_default.headerActions,
12713
- children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12714
- label: t(expanded ? "action.exitFullscreen" : "action.expand"),
12715
- side: "bottom",
12716
- delayMs: 500,
12717
- children: (0, react_jsx_runtime.jsx)("button", {
12718
- type: "button",
12719
- className: expanded ? `${PendingPanel_module_css_default.expand} ${PendingPanel_module_css_default.expandExpanded}` : PendingPanel_module_css_default.expand,
12720
- "data-diff-approval-expand": true,
12721
- "aria-label": t(expanded ? "action.exitFullscreen" : "action.expand"),
12722
- onClick: () => {
12723
- setExpanded((value) => !value);
12724
- },
12725
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
12932
+ children: [
12933
+ importToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
12934
+ text: importToast,
12935
+ onDone: () => {
12936
+ setImportToast(null);
12937
+ }
12938
+ }),
12939
+ actionToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
12940
+ text: actionToast,
12941
+ onDone: () => {
12942
+ setActionToast(null);
12943
+ }
12944
+ }),
12945
+ open && expanded && (0, react_jsx_runtime.jsx)("div", {
12946
+ className: PendingPanel_module_css_default.fullscreenBackdrop,
12947
+ "data-diff-fullscreen-backdrop": true
12948
+ }),
12949
+ open && (0, react_jsx_runtime.jsxs)("section", {
12950
+ className: PendingPanel_module_css_default.panel,
12951
+ style: { bottom: expanded ? PANEL_INSET_PX : bottomPx },
12952
+ "data-diff-approval-panel": true,
12953
+ "aria-label": t("panel.title"),
12954
+ children: [(0, react_jsx_runtime.jsxs)("header", {
12955
+ className: PendingPanel_module_css_default.header,
12956
+ children: [(0, react_jsx_runtime.jsx)("span", {
12957
+ className: PendingPanel_module_css_default.title,
12958
+ children: t("panel.title")
12959
+ }), (0, react_jsx_runtime.jsxs)("div", {
12960
+ className: PendingPanel_module_css_default.headerActions,
12961
+ children: [
12962
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12963
+ label: t("action.settings"),
12964
+ side: "bottom",
12965
+ delayMs: 500,
12966
+ children: (0, react_jsx_runtime.jsx)("button", {
12967
+ type: "button",
12968
+ className: PendingPanel_module_css_default.expand,
12969
+ "data-diff-approval-settings": true,
12970
+ "aria-label": t("action.settings"),
12971
+ onClick: () => {
12972
+ setOpen(false);
12973
+ openSettingsSection(t("settings.tabLabel"));
12974
+ },
12975
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 14 })
12976
+ })
12977
+ }),
12978
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12979
+ label: t(expanded ? "action.exitFullscreen" : "action.expand"),
12980
+ side: "bottom",
12981
+ delayMs: 500,
12982
+ children: (0, react_jsx_runtime.jsx)("button", {
12983
+ type: "button",
12984
+ className: expanded ? `${PendingPanel_module_css_default.expand} ${PendingPanel_module_css_default.expandExpanded}` : PendingPanel_module_css_default.expand,
12985
+ "data-diff-approval-expand": true,
12986
+ "aria-label": t(expanded ? "action.exitFullscreen" : "action.expand"),
12987
+ onClick: () => {
12988
+ setExpanded((value) => !value);
12989
+ },
12990
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
12991
+ })
12992
+ }),
12993
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12994
+ label: t("action.close"),
12995
+ side: "bottom",
12996
+ delayMs: 500,
12997
+ children: (0, react_jsx_runtime.jsx)("button", {
12998
+ type: "button",
12999
+ className: PendingPanel_module_css_default.close,
13000
+ "data-diff-approval-close": true,
13001
+ "aria-label": t("action.close"),
13002
+ onClick: () => {
13003
+ setOpen(false);
13004
+ },
13005
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
13006
+ })
13007
+ })
13008
+ ]
13009
+ })]
13010
+ }), snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
13011
+ className: PendingPanel_module_css_default.states,
13012
+ children: [
13013
+ snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
13014
+ className: PendingPanel_module_css_default.readError,
13015
+ role: "alert",
13016
+ children: t("panel.readFailed", { message: snapshot.error })
13017
+ }),
13018
+ !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
13019
+ className: PendingPanel_module_css_default.note,
13020
+ children: t("panel.loading")
13021
+ }),
13022
+ snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsxs)("div", {
13023
+ className: PendingPanel_module_css_default.emptyState,
13024
+ children: [
13025
+ (0, react_jsx_runtime.jsx)("p", {
13026
+ className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
13027
+ children: t("panel.empty")
13028
+ }),
13029
+ (0, react_jsx_runtime.jsx)("button", {
13030
+ type: "button",
13031
+ className: PendingPanel_module_css_default.importButton,
13032
+ "data-diff-import-vcs": true,
13033
+ disabled: importBusy,
13034
+ onClick: () => {
13035
+ runImportVcs();
13036
+ },
13037
+ children: importBusy ? t("action.importVcsBusy") : t("action.importVcs")
13038
+ }),
13039
+ importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
13040
+ className: PendingPanel_module_css_default.importNote,
13041
+ role: importFailed ? "alert" : void 0,
13042
+ children: importNote
13043
+ })
13044
+ ]
12726
13045
  })
12727
- }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
12728
- label: t("action.close"),
12729
- side: "bottom",
12730
- delayMs: 500,
12731
- children: (0, react_jsx_runtime.jsx)("button", {
12732
- type: "button",
12733
- className: PendingPanel_module_css_default.close,
12734
- "data-diff-approval-close": true,
12735
- "aria-label": t("action.close"),
12736
- onClick: () => {
12737
- setOpen(false);
12738
- },
12739
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
13046
+ ]
13047
+ }) : (0, react_jsx_runtime.jsxs)("div", {
13048
+ className: PendingPanel_module_css_default.split,
13049
+ children: [
13050
+ (0, react_jsx_runtime.jsxs)("nav", {
13051
+ className: PendingPanel_module_css_default.fileList,
13052
+ style: { width: listWidth },
13053
+ "data-diff-approval-file-list": true,
13054
+ children: [(0, react_jsx_runtime.jsx)("div", {
13055
+ className: PendingPanel_module_css_default.listScroll,
13056
+ children: files.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
13057
+ className: PendingPanel_module_css_default.group,
13058
+ children: t("panel.group.current")
13059
+ }), (0, react_jsx_runtime.jsx)("ul", {
13060
+ className: PendingPanel_module_css_default.rows,
13061
+ children: files.map(renderEntry)
13062
+ })] })
13063
+ }), files.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
13064
+ className: PendingPanel_module_css_default.bulkActions,
13065
+ children: [(0, react_jsx_runtime.jsx)("button", {
13066
+ type: "button",
13067
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
13068
+ "data-diff-keep-all": true,
13069
+ disabled: bulkBusy !== null,
13070
+ onClick: () => {
13071
+ runBulk("keep");
13072
+ },
13073
+ children: bulkBusy === "keep" ? t("action.busy") : t("action.keepAll")
13074
+ }), (0, react_jsx_runtime.jsx)("button", {
13075
+ type: "button",
13076
+ className: PendingPanel_module_css_default.action,
13077
+ "data-diff-revert-all": true,
13078
+ disabled: bulkBusy !== null,
13079
+ onClick: () => {
13080
+ runBulk("revert");
13081
+ },
13082
+ children: bulkBusy === "revert" ? t("action.busy") : t("action.revertAll")
13083
+ })]
13084
+ })]
13085
+ }),
13086
+ (0, react_jsx_runtime.jsx)("div", {
13087
+ className: PendingPanel_module_css_default.resizeHandle,
13088
+ "data-diff-resize": true,
13089
+ onMouseDown: startResize
13090
+ }),
13091
+ (0, react_jsx_runtime.jsx)("div", {
13092
+ className: PendingPanel_module_css_default.detail,
13093
+ children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
13094
+ className: PendingPanel_module_css_default.detailEmpty,
13095
+ children: t("panel.selectHint")
13096
+ }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
13097
+ file: selectedFile,
13098
+ busy: snapshot.busy.has(selectedFile.id),
13099
+ workspacePath: snapshot.workspacePath,
13100
+ jumpSignal,
13101
+ undoFlash,
13102
+ failedMessage: failed.get(selectedFile.id),
13103
+ onPasteReference,
13104
+ t,
13105
+ onKeep,
13106
+ onRevert,
13107
+ onBlockKeep,
13108
+ onBlockRevert,
13109
+ onOpen
13110
+ })
12740
13111
  })
12741
- })]
13112
+ ]
12742
13113
  })]
12743
- }), snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
12744
- className: PendingPanel_module_css_default.states,
12745
- children: [
12746
- snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
12747
- className: PendingPanel_module_css_default.readError,
12748
- role: "alert",
12749
- children: t("panel.readFailed", { message: snapshot.error })
12750
- }),
12751
- !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
12752
- className: PendingPanel_module_css_default.note,
12753
- children: t("panel.loading")
12754
- }),
12755
- snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsx)("p", {
12756
- className: PendingPanel_module_css_default.note,
12757
- children: t("panel.empty")
12758
- })
12759
- ]
12760
- }) : (0, react_jsx_runtime.jsxs)("div", {
12761
- className: PendingPanel_module_css_default.split,
12762
- children: [
12763
- (0, react_jsx_runtime.jsx)("nav", {
12764
- className: PendingPanel_module_css_default.fileList,
12765
- style: { width: listWidth },
12766
- "data-diff-approval-file-list": true,
12767
- children: files.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
12768
- className: PendingPanel_module_css_default.group,
12769
- children: t("panel.group.current")
12770
- }), (0, react_jsx_runtime.jsx)("ul", {
12771
- className: PendingPanel_module_css_default.rows,
12772
- children: files.map(renderEntry)
12773
- })] })
12774
- }),
12775
- (0, react_jsx_runtime.jsx)("div", {
12776
- className: PendingPanel_module_css_default.resizeHandle,
12777
- "data-diff-resize": true,
12778
- onMouseDown: startResize
12779
- }),
12780
- (0, react_jsx_runtime.jsx)("div", {
12781
- className: PendingPanel_module_css_default.detail,
12782
- children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
12783
- className: PendingPanel_module_css_default.detailEmpty,
12784
- children: t("panel.selectHint")
12785
- }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
12786
- file: selectedFile,
12787
- busy: snapshot.busy.has(selectedFile.id),
12788
- workspacePath: snapshot.workspacePath,
12789
- jumpSignal,
12790
- t,
12791
- onKeep,
12792
- onRevert,
12793
- onBlockKeep,
12794
- onBlockRevert,
12795
- onOpen
13114
+ }),
13115
+ (0, react_jsx_runtime.jsx)("div", {
13116
+ className: PendingPanel_module_css_default.footerButtons,
13117
+ children: (0, react_jsx_runtime.jsxs)("button", {
13118
+ type: "button",
13119
+ className: noSession ? `${PendingPanel_module_css_default.badge} ${PendingPanel_module_css_default.badgeDisabled}` : PendingPanel_module_css_default.badge,
13120
+ "data-diff-approval-badge": files.length,
13121
+ "data-active": open ? "" : void 0,
13122
+ "aria-label": t("panel.aria"),
13123
+ "aria-expanded": open,
13124
+ disabled: noSession,
13125
+ onClick: toggleOpen,
13126
+ children: [
13127
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: wide ? 16 : 18 }),
13128
+ wide && (0, react_jsx_runtime.jsx)("span", {
13129
+ className: PendingPanel_module_css_default.badgeLabel,
13130
+ children: t("panel.aria")
13131
+ }),
13132
+ (wide || files.length > 0) && (0, react_jsx_runtime.jsx)("span", {
13133
+ className: PendingPanel_module_css_default.badgeCount,
13134
+ children: files.length
12796
13135
  })
12797
- })
12798
- ]
12799
- })]
12800
- }), (0, react_jsx_runtime.jsx)("div", {
12801
- className: PendingPanel_module_css_default.footerButtons,
12802
- children: (0, react_jsx_runtime.jsxs)("button", {
12803
- type: "button",
12804
- className: PendingPanel_module_css_default.badge,
12805
- "data-diff-approval-badge": files.length,
12806
- "data-active": open ? "" : void 0,
12807
- "aria-label": t("panel.aria"),
12808
- "aria-expanded": open,
12809
- onClick: toggleOpen,
12810
- children: [
12811
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: wide ? 16 : 18 }),
12812
- wide && (0, react_jsx_runtime.jsx)("span", {
12813
- className: PendingPanel_module_css_default.badgeLabel,
12814
- children: t("panel.aria")
12815
- }),
12816
- (wide || files.length > 0) && (0, react_jsx_runtime.jsx)("span", {
12817
- className: PendingPanel_module_css_default.badgeCount,
12818
- children: files.length
12819
- })
12820
- ]
13136
+ ]
13137
+ })
13138
+ }),
13139
+ redoClearedNotice && (0, react_jsx_runtime.jsxs)("div", {
13140
+ className: PendingPanel_module_css_default.notice,
13141
+ role: "status",
13142
+ "data-diff-approval-notice": true,
13143
+ children: [(0, react_jsx_runtime.jsx)("p", {
13144
+ className: PendingPanel_module_css_default.noticeText,
13145
+ children: t("panel.externalChanged")
13146
+ }), (0, react_jsx_runtime.jsx)("button", {
13147
+ type: "button",
13148
+ className: PendingPanel_module_css_default.noticeButton,
13149
+ "data-diff-notice-dismiss": true,
13150
+ onClick: () => {
13151
+ setRedoClearedNotice(false);
13152
+ },
13153
+ children: t("panel.dismiss")
13154
+ })]
12821
13155
  })
13156
+ ]
13157
+ });
13158
+ }
13159
+ //#endregion
13160
+ //#region lib/types/client/SettingsTab.js
13161
+ /** DSH Settings top-level section for this plugin's preferences. */
13162
+ /** A pill picker offering the two boolean states 打开 / 关闭. */
13163
+ function OnOffPicker({ value, open, onOpenChange, onSelect, dataAttribute, t }) {
13164
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
13165
+ open,
13166
+ onClose: () => {
13167
+ onOpenChange(false);
13168
+ },
13169
+ items: [{
13170
+ id: "on",
13171
+ label: t("action.toggleOn")
13172
+ }, {
13173
+ id: "off",
13174
+ label: t("action.toggleOff")
13175
+ }],
13176
+ selectedId: value ? "on" : "off",
13177
+ onSelect: (id) => {
13178
+ onOpenChange(false);
13179
+ onSelect(id === "on");
13180
+ },
13181
+ align: "end",
13182
+ portal: true,
13183
+ anchor: (0, react_jsx_runtime.jsxs)("button", {
13184
+ type: "button",
13185
+ className: PendingPanel_module_css_default.settingsSelector,
13186
+ "aria-haspopup": "menu",
13187
+ "aria-expanded": open,
13188
+ onClick: () => {
13189
+ onOpenChange(!open);
13190
+ },
13191
+ [dataAttribute]: true,
13192
+ 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 })]
13193
+ })
13194
+ });
13195
+ }
13196
+ /** One Agent-preset-style preference row: title + description, pill picker right. */
13197
+ function PreferenceRow({ title, description, value, open, onOpenChange, onSelect, dataAttribute, t }) {
13198
+ return (0, react_jsx_runtime.jsxs)("div", {
13199
+ className: PendingPanel_module_css_default.settingsRow,
13200
+ children: [(0, react_jsx_runtime.jsxs)("div", {
13201
+ className: PendingPanel_module_css_default.settingsRowText,
13202
+ children: [(0, react_jsx_runtime.jsx)("div", {
13203
+ className: PendingPanel_module_css_default.settingsRowTitle,
13204
+ children: title
13205
+ }), (0, react_jsx_runtime.jsx)("div", {
13206
+ className: PendingPanel_module_css_default.settingsRowDesc,
13207
+ children: description
13208
+ })]
13209
+ }), (0, react_jsx_runtime.jsx)(OnOffPicker, {
13210
+ value,
13211
+ open,
13212
+ onOpenChange,
13213
+ onSelect,
13214
+ dataAttribute,
13215
+ t
13216
+ })]
13217
+ });
13218
+ }
13219
+ /**
13220
+ * The plugin's preferences: auto-paste a copied reference into the input, and
13221
+ * whether importing workspace VCS changes includes untracked files. Each row
13222
+ * mirrors the harness's Agent-preset row (title + description on the left, a
13223
+ * pill picker on the right) with the two boolean states 打开 / 关闭.
13224
+ */
13225
+ function DiffApprovalSettingsTab({ t }) {
13226
+ const [pasteOnCopy, setPasteOnCopyState] = (0, react.useState)(pasteOnCopyEnabled);
13227
+ const [pasteOnCopyOpen, setPasteOnCopyOpen] = (0, react.useState)(false);
13228
+ const [includeUntracked, setIncludeUntrackedState] = (0, react.useState)(includeUntrackedEnabled);
13229
+ const [includeUntrackedOpen, setIncludeUntrackedOpen] = (0, react.useState)(false);
13230
+ const setPasteOnCopy = (value) => {
13231
+ setPasteOnCopyState(value);
13232
+ setPasteOnCopyEnabled(value);
13233
+ };
13234
+ const setIncludeUntracked = (value) => {
13235
+ setIncludeUntrackedState(value);
13236
+ setIncludeUntrackedEnabled(value);
13237
+ };
13238
+ return (0, react_jsx_runtime.jsxs)("div", {
13239
+ className: PendingPanel_module_css_default.settingsPage,
13240
+ "data-diff-settings": true,
13241
+ children: [(0, react_jsx_runtime.jsx)(PreferenceRow, {
13242
+ title: t("panel.pasteOnCopy"),
13243
+ description: t("panel.pasteOnCopyDesc"),
13244
+ value: pasteOnCopy,
13245
+ open: pasteOnCopyOpen,
13246
+ onOpenChange: setPasteOnCopyOpen,
13247
+ onSelect: setPasteOnCopy,
13248
+ dataAttribute: "data-diff-paste-on-copy-select",
13249
+ t
13250
+ }), (0, react_jsx_runtime.jsx)(PreferenceRow, {
13251
+ title: t("panel.importUntracked"),
13252
+ description: t("panel.importUntrackedDesc"),
13253
+ value: includeUntracked,
13254
+ open: includeUntrackedOpen,
13255
+ onOpenChange: setIncludeUntrackedOpen,
13256
+ onSelect: setIncludeUntracked,
13257
+ dataAttribute: "data-diff-import-untracked-select",
13258
+ t
12822
13259
  })]
12823
13260
  });
12824
13261
  }
@@ -12867,6 +13304,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12867
13304
  block
12868
13305
  }));
12869
13306
  },
13307
+ async undo(sessionId) {
13308
+ return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "undo", { sessionId }));
13309
+ },
13310
+ async redo(sessionId) {
13311
+ return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "redo", { sessionId }));
13312
+ },
13313
+ async importVcs(sessionId, includeUntracked) {
13314
+ return importValueOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "vcs-import", {
13315
+ sessionId,
13316
+ includeUntracked
13317
+ }));
13318
+ },
12870
13319
  async open(sessionId, id, action) {
12871
13320
  return openOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "open", {
12872
13321
  sessionId,
@@ -12912,7 +13361,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12912
13361
  const file = pendingFileOf(row);
12913
13362
  if (file !== void 0) files.push(file);
12914
13363
  }
12915
- return {
13364
+ return value.redoCleared === true ? {
13365
+ files,
13366
+ workspacePath,
13367
+ redoCleared: true
13368
+ } : {
12916
13369
  files,
12917
13370
  workspacePath
12918
13371
  };
@@ -12923,8 +13376,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12923
13376
  const value = result.value;
12924
13377
  if (typeof value !== "object" || value === null) throw new Error("the action returned a malformed value");
12925
13378
  const outcome = value.outcome;
12926
- if (outcome !== "kept" && outcome !== "reverted" && outcome !== "missing") throw new Error("the action returned a malformed outcome");
12927
- return { outcome };
13379
+ if (outcome !== "kept" && outcome !== "reverted" && outcome !== "missing" && outcome !== "undone" && outcome !== "redone" && outcome !== "nothing") throw new Error("the action returned a malformed outcome");
13380
+ const id = value.id;
13381
+ return {
13382
+ outcome,
13383
+ id: typeof id === "string" && id.length > 0 ? id : void 0
13384
+ };
13385
+ }
13386
+ /** Narrow the vcs-import endpoint's value; a malformed wire value is a failure. */
13387
+ function importValueOf(result) {
13388
+ if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
13389
+ const value = result.value;
13390
+ if (typeof value !== "object" || value === null) throw new Error("the import returned a malformed value");
13391
+ const imported = value.imported;
13392
+ const detected = value.detected;
13393
+ if (typeof imported !== "number" || typeof detected !== "boolean") throw new Error("the import returned a malformed value");
13394
+ return {
13395
+ imported,
13396
+ detected
13397
+ };
12928
13398
  }
12929
13399
  /** Narrow the open endpoint's value; a malformed wire value is an open failure. */
12930
13400
  function openOf(result) {
@@ -12946,6 +13416,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12946
13416
  */
12947
13417
  /** An empty busy set reused as the snapshot's canonical absent value. */
12948
13418
  const EMPTY_BUSY = /* @__PURE__ */ new Set();
13419
+ /** An empty failure map reused as the snapshot's canonical absent value. */
13420
+ const EMPTY_FAILED = /* @__PURE__ */ new Map();
13421
+ /** How long a failed keep/revert hint stays visible before it auto-clears. */
13422
+ const FAILED_HINT_MS = 5e3;
12949
13423
  /**
12950
13424
  * Create the store over the review-channel port.
12951
13425
  * @param port - the typed channel port.
@@ -12958,26 +13432,59 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12958
13432
  busy: EMPTY_BUSY
12959
13433
  };
12960
13434
  const listeners = /* @__PURE__ */ new Set();
13435
+ let redoCleared = false;
12961
13436
  const publish = (next) => {
12962
- snapshot = next;
13437
+ snapshot = redoCleared ? {
13438
+ ...next,
13439
+ redoCleared: true
13440
+ } : next;
12963
13441
  for (const listener of [...listeners]) listener();
12964
13442
  };
13443
+ const failedOf = (value) => value.failed ?? EMPTY_FAILED;
13444
+ /** Record one entry's failed keep/revert with its message; auto-clears after a few seconds. */
13445
+ const markFailed = (id, message) => {
13446
+ publish({
13447
+ ...snapshot,
13448
+ failed: new Map([...failedOf(snapshot), [id, message]])
13449
+ });
13450
+ window.setTimeout(() => {
13451
+ if (failedOf(snapshot).get(id) !== message) return;
13452
+ const next = new Map(failedOf(snapshot));
13453
+ next.delete(id);
13454
+ publish({
13455
+ ...snapshot,
13456
+ failed: next
13457
+ });
13458
+ }, FAILED_HINT_MS);
13459
+ };
13460
+ /** Drop one entry's failure marker after a successful retry. */
13461
+ const clearFailed = (id) => {
13462
+ if (!failedOf(snapshot).has(id)) return;
13463
+ const next = new Map(failedOf(snapshot));
13464
+ next.delete(id);
13465
+ publish({
13466
+ ...snapshot,
13467
+ failed: next
13468
+ });
13469
+ };
12965
13470
  const withBusy = async (id, action) => {
12966
13471
  const { error: _cleared, ...base } = snapshot;
12967
13472
  publish({
12968
13473
  ...base,
13474
+ failed: failedOf(snapshot),
12969
13475
  busy: /* @__PURE__ */ new Set([...snapshot.busy, id])
12970
13476
  });
12971
13477
  try {
12972
13478
  await action();
12973
13479
  } catch (error) {
13480
+ markFailed(id, error instanceof Error ? error.message : String(error));
12974
13481
  publish({
12975
13482
  ...snapshot,
12976
- error: error instanceof Error ? error.message : String(error),
12977
13483
  busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
12978
13484
  });
12979
13485
  return;
12980
13486
  }
13487
+ clearFailed(id);
12981
13488
  publish({
12982
13489
  ...snapshot,
12983
13490
  files: snapshot.files.filter((file) => file.id !== id),
@@ -12997,17 +13504,20 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12997
13504
  publish({
12998
13505
  read: true,
12999
13506
  files: [],
13000
- busy: EMPTY_BUSY
13507
+ busy: EMPTY_BUSY,
13508
+ failed: failedOf(snapshot)
13001
13509
  });
13002
13510
  return;
13003
13511
  }
13004
13512
  try {
13005
- const { files, workspacePath } = await port.list(sessionId);
13513
+ const { files, workspacePath, redoCleared: cleared } = await port.list(sessionId);
13514
+ if (cleared) redoCleared = true;
13006
13515
  publish({
13007
13516
  read: true,
13008
13517
  files,
13009
13518
  workspacePath,
13010
- busy: EMPTY_BUSY
13519
+ busy: EMPTY_BUSY,
13520
+ failed: failedOf(snapshot)
13011
13521
  });
13012
13522
  } catch (error) {
13013
13523
  publish({
@@ -13015,7 +13525,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13015
13525
  files: snapshot.files,
13016
13526
  workspacePath: snapshot.workspacePath,
13017
13527
  error: error instanceof Error ? error.message : String(error),
13018
- busy: snapshot.busy
13528
+ busy: snapshot.busy,
13529
+ failed: failedOf(snapshot)
13019
13530
  });
13020
13531
  }
13021
13532
  },
@@ -13038,13 +13549,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13038
13549
  try {
13039
13550
  await port.blockKeep(sessionId, id, block);
13040
13551
  } catch (error) {
13552
+ markFailed(id, error instanceof Error ? error.message : String(error));
13041
13553
  publish({
13042
13554
  ...snapshot,
13043
- error: error instanceof Error ? error.message : String(error),
13044
13555
  busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
13045
13556
  });
13046
13557
  return;
13047
13558
  }
13559
+ clearFailed(id);
13048
13560
  await this.refresh(sessionId);
13049
13561
  },
13050
13562
  async blockRevert(sessionId, id, block) {
@@ -13056,15 +13568,41 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13056
13568
  try {
13057
13569
  await port.blockRevert(sessionId, id, block);
13058
13570
  } catch (error) {
13571
+ markFailed(id, error instanceof Error ? error.message : String(error));
13059
13572
  publish({
13060
13573
  ...snapshot,
13061
- error: error instanceof Error ? error.message : String(error),
13062
13574
  busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
13063
13575
  });
13064
13576
  return;
13065
13577
  }
13578
+ clearFailed(id);
13066
13579
  await this.refresh(sessionId);
13067
13580
  },
13581
+ async undo(sessionId) {
13582
+ try {
13583
+ const value = await port.undo(sessionId);
13584
+ await this.refresh(sessionId);
13585
+ const id = value.id;
13586
+ return id !== void 0 && snapshot.files.some((file) => file.id === id) ? id : void 0;
13587
+ } catch {
13588
+ return;
13589
+ }
13590
+ },
13591
+ async redo(sessionId) {
13592
+ try {
13593
+ const value = await port.redo(sessionId);
13594
+ await this.refresh(sessionId);
13595
+ const id = value.id;
13596
+ return id !== void 0 && snapshot.files.some((file) => file.id === id) ? id : void 0;
13597
+ } catch {
13598
+ return;
13599
+ }
13600
+ },
13601
+ async importVcs(sessionId, includeUntracked) {
13602
+ const value = await port.importVcs(sessionId, includeUntracked);
13603
+ await this.refresh(sessionId);
13604
+ return value;
13605
+ },
13068
13606
  async open(sessionId, id, action) {
13069
13607
  try {
13070
13608
  await port.open(sessionId, id, action);
@@ -13081,6 +13619,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13081
13619
  files: [],
13082
13620
  busy: EMPTY_BUSY
13083
13621
  });
13622
+ },
13623
+ clearRedoCleared() {
13624
+ redoCleared = false;
13625
+ const { redoCleared: _omit, ...rest } = snapshot;
13626
+ publish(rest);
13084
13627
  }
13085
13628
  };
13086
13629
  }
@@ -13103,12 +13646,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13103
13646
  "panel.searchPlaceholder": "搜索",
13104
13647
  "panel.missing": "文件已不存在",
13105
13648
  "panel.missingHint": "该文件已不存在,回退会恢复该文件。",
13649
+ "panel.externalChanged": "检测到文件在外部被修改,已更新对比并新建撤销点;重做历史已被重置。",
13650
+ "panel.dismiss": "知道了",
13106
13651
  "panel.createHint": "回退会删除该新建的文件。",
13652
+ "panel.pasteOnCopy": "复制引用后自动粘贴到消息输入框",
13653
+ "panel.pasteOnCopyDesc": "开启后,复制的引用会自动填入消息输入框,输入框会获得焦点。",
13654
+ "panel.importUntracked": "导入未跟踪的文件改动",
13655
+ "panel.importUntrackedDesc": "开启后,导入工作区改动会包含未跟踪/未版本化的新增文件(Git 未跟踪、SVN 未版本化、Perforce 未版本化)。收集改动需要扫描整个工作区,文件数量大时可能较慢。",
13656
+ "settings.tabLabel": "改动审批",
13107
13657
  "row.create": "新增文件",
13658
+ "row.failed": "失败",
13108
13659
  "row.added": "+{added}",
13109
13660
  "row.removed": "-{removed}",
13110
13661
  "action.keep": "保留",
13111
13662
  "action.revert": "回退",
13663
+ "action.keepAll": "全部保留",
13664
+ "action.revertAll": "全部回退",
13112
13665
  "action.openFile": "打开文件",
13113
13666
  "action.revealFile": "打开所在目录",
13114
13667
  "action.busy": "处理中…",
@@ -13119,7 +13672,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13119
13672
  "action.langAuto": "自动",
13120
13673
  "action.langAutoDetected": "自动:{lang}",
13121
13674
  "action.langSelect": "高亮语言",
13675
+ "action.toggleOn": "打开",
13676
+ "action.toggleOff": "关闭",
13677
+ "action.importVcs": "导入工作区改动",
13678
+ "action.importVcsBusy": "导入中…",
13679
+ "panel.importNone": "没有可导入的改动",
13680
+ "panel.importDone": "已导入 {count} 个改动",
13681
+ "panel.importNoVcs": "未检测到版本控制(Git / SVN / Perforce)",
13682
+ "panel.importFailed": "导入失败:{message}",
13122
13683
  "action.search": "搜索 (Ctrl+F)",
13684
+ "action.settings": "设置",
13123
13685
  "action.expand": "展开全屏",
13124
13686
  "action.exitFullscreen": "退出全屏",
13125
13687
  "action.close": "关闭",
@@ -13142,12 +13704,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13142
13704
  "panel.searchPlaceholder": "Search",
13143
13705
  "panel.missing": "File is gone",
13144
13706
  "panel.missingHint": "This file no longer exists; reverting restores it.",
13707
+ "panel.externalChanged": "A file changed outside the review. The comparison was updated and a new undo point created; the redo history was reset.",
13708
+ "panel.dismiss": "Got it",
13145
13709
  "panel.createHint": "Reverting removes this created file.",
13710
+ "panel.pasteOnCopy": "Paste the copied reference into the composer",
13711
+ "panel.pasteOnCopyDesc": "When enabled, a copied reference is pasted into the composer, which then receives focus.",
13712
+ "panel.importUntracked": "Import changes to untracked files",
13713
+ "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.",
13714
+ "settings.tabLabel": "Diff Approval",
13146
13715
  "row.create": "New file",
13716
+ "row.failed": "Failed",
13147
13717
  "row.added": "+{added}",
13148
13718
  "row.removed": "-{removed}",
13149
13719
  "action.keep": "Keep",
13150
13720
  "action.revert": "Revert",
13721
+ "action.keepAll": "Keep all",
13722
+ "action.revertAll": "Revert all",
13151
13723
  "action.openFile": "Open file",
13152
13724
  "action.revealFile": "Reveal in folder",
13153
13725
  "action.busy": "Working…",
@@ -13158,7 +13730,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13158
13730
  "action.langAuto": "Auto",
13159
13731
  "action.langAutoDetected": "Auto: {lang}",
13160
13732
  "action.langSelect": "Highlight language",
13733
+ "action.toggleOn": "On",
13734
+ "action.toggleOff": "Off",
13735
+ "action.importVcs": "Import workspace changes",
13736
+ "action.importVcsBusy": "Importing…",
13737
+ "panel.importNone": "No changes to import",
13738
+ "panel.importDone": "Imported {count} changes",
13739
+ "panel.importNoVcs": "No version control detected (Git / SVN / Perforce)",
13740
+ "panel.importFailed": "Import failed: {message}",
13161
13741
  "action.search": "Search (Ctrl+F)",
13742
+ "action.settings": "Settings",
13162
13743
  "action.expand": "Expand fullscreen",
13163
13744
  "action.exitFullscreen": "Exit fullscreen",
13164
13745
  "action.close": "Close",
@@ -13185,6 +13766,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13185
13766
  zh,
13186
13767
  en
13187
13768
  }), "ui-diff-approval: dictionaries");
13769
+ const t = ctx.locale.bind(NS);
13188
13770
  const store = createPendingDiffStore(createDiffApprovalPort(ctx.get("connection").rpc));
13189
13771
  ctx.on("connection/reset", () => {
13190
13772
  store.reset();
@@ -13202,9 +13784,30 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13202
13784
  onRevert: (sessionId, path) => store.revert(sessionId, path),
13203
13785
  onBlockKeep: (sessionId, id, block) => store.blockKeep(sessionId, id, block),
13204
13786
  onBlockRevert: (sessionId, id, block) => store.blockRevert(sessionId, id, block),
13205
- onOpen: (sessionId, id, action) => store.open(sessionId, id, action)
13787
+ onOpen: (sessionId, id, action) => store.open(sessionId, id, action),
13788
+ onUndo: (sessionId) => store.undo(sessionId),
13789
+ onRedo: (sessionId) => store.redo(sessionId),
13790
+ onImportVcs: (sessionId, includeUntracked) => store.importVcs(sessionId, includeUntracked),
13791
+ onAckRedoCleared: () => store.clearRedoCleared(),
13792
+ onPasteReference: (sessionId, reference) => {
13793
+ const actx = ctx.get("sessions")?.scope(sessionId);
13794
+ if (actx === void 0) return;
13795
+ const conversation = actx.get("conversation");
13796
+ if (conversation?.input === void 0) return;
13797
+ const textarea = document.querySelector("[data-composer-card] textarea");
13798
+ const base = textarea?.value ?? "";
13799
+ conversation.input.for(actx).setDraft(base === "" ? reference : `${base} ${reference}`);
13800
+ textarea?.focus();
13801
+ }
13206
13802
  })
13207
13803
  }, PendingPanel));
13804
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
13805
+ name: "settings.section",
13806
+ id: "diff-approval",
13807
+ order: 100,
13808
+ label: () => t("settings.tabLabel"),
13809
+ locale: NS
13810
+ }, DiffApprovalSettingsTab));
13208
13811
  }
13209
13812
  //#endregion
13210
13813
  exports.DIFF_APPROVAL_CHANNEL = DIFF_APPROVAL_CHANNEL;