dsh-diff-approval 0.19.2 → 0.20.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
@@ -20,6 +20,539 @@ window.__ModuleLoader__.load({
20
20
  let react = require("react");
21
21
  let react_dom = require("react-dom");
22
22
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
23
+ //#region \0dsh-css:/home/runner/work/dsh-diff-approval/dsh-diff-approval/src/client/PendingPanel.module.css.mjs
24
+ 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_settingsGroup{border-bottom:1px solid var(--dsw-alias-border-l2);margin-top:8px}.F1KBNa_settingsGroupHeader{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:8px;padding:16px 0;display:flex}.F1KBNa_settingsGroupText{text-align:left;flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.F1KBNa_settingsGroupTitle{font-size:14px;font-weight:400;line-height:22px}.F1KBNa_settingsGroupDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.F1KBNa_settingsGroupChevron{margin-left:auto;transition:transform .15s}.F1KBNa_settingsGroup[data-open] .F1KBNa_settingsGroupChevron{transform:rotate(180deg)}.F1KBNa_settingsGroup[data-open] .F1KBNa_settingsGroupHeader{border-bottom:1px solid var(--dsw-alias-border-l2)}.F1KBNa_settingsGroupBody{flex-direction:column;display:flex}.F1KBNa_settingsGroupBody .F1KBNa_settingsRow{padding-left:16px}.F1KBNa_settingsGroupBody .F1KBNa_settingsRow:last-child,.F1KBNa_settingsPage>.F1KBNa_settingsRow:last-child{border-bottom:none}.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_colorPicker{position:relative}.F1KBNa_colorPickerTrigger{background:var(--dsw-alias-bg-module-platform);width:120px;height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:8px;padding:0 8px;font-size:12px;line-height:18px;display:inline-flex}.F1KBNa_colorPickerTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_colorSwatch{border:1px solid var(--dsw-alias-border-l2);border-radius:50%;flex:none;width:24px;height:24px}.F1KBNa_colorValue{text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums;flex:1;min-width:0;overflow:hidden}.F1KBNa_colorPickerChevron{flex:none}.F1KBNa_colorBackdrop{z-index:40;position:fixed;inset:0}.F1KBNa_colorPickerPopover{z-index:41;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:10px;padding:10px;position:absolute;top:calc(100% + 6px);right:0}.F1KBNa_colorPickerPanel{flex-direction:column;gap:8px;display:flex}.F1KBNa_colorPickerInputRow{align-items:center;gap:8px;display:flex}.F1KBNa_colorPickerPreview{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;flex:none;width:22px;height:22px}.F1KBNa_colorPickerInput{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-module-platform);min-width:0;height:30px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:0 8px;font-size:12px;line-height:28px}.F1KBNa_colorPickerInput:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-1px}.F1KBNa_colorSv{border:1px solid var(--dsw-alias-border-l1);cursor:crosshair;touch-action:none;border-radius:8px;height:140px;position:relative}.F1KBNa_colorSvCursor{pointer-events:none;border:2px solid #fff;border-radius:50%;width:14px;height:14px;position:absolute;transform:translate(-50%,-50%);box-shadow:0 0 0 1px #00000080}.F1KBNa_colorHue{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;touch-action:none;border-radius:7px;height:14px;position:relative}.F1KBNa_colorHueCursor{pointer-events:none;border:2px solid #fff;border-radius:50%;width:16px;height:16px;position:absolute;top:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 1px #00000080}.F1KBNa_diffPreview{z-index:2;border:1px solid var(--dsw-alias-border-l1);font:var(--dsw-font-markdown-code-block);background:var(--dsw-alias-markdown-code-block);border-radius:8px;margin:16px 0 8px;padding:8px;position:sticky;top:0;overflow:hidden}.F1KBNa_diffPreviewTitle{color:var(--dsw-alias-label-tertiary);margin-bottom:6px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_diffPreviewScroll{height:132px;overflow-y:auto}.F1KBNa_mdPreviewWrap{flex:1;min-width:0;min-height:0;display:flex;position:relative}.F1KBNa_mdPreviewBody{min-width:0;min-height:0;color:var(--dsw-alias-label-primary);flex:1;padding:12px 16px;position:relative;overflow:auto}.F1KBNa_mdPreviewContent{width:100%;min-width:0;margin:0 auto}.F1KBNa_mdPreviewSingle{display:block}.F1KBNa_mdPreviewDouble{display:flex}.F1KBNa_mdPreviewBody .mdBlock{border-radius:4px;margin-bottom:12px;padding:2px 6px}.F1KBNa_mdPreviewBody .mdAdd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_mdPreviewBody .mdDel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_mdPreviewBody .mdWordDel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 34%, transparent);border-radius:2px;padding:0 1px}.F1KBNa_mdPreviewBody .mdWordAdd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 34%, transparent);border-radius:2px;padding:0 1px}.F1KBNa_mdPreviewBody .mdDouble{flex-direction:column;width:100%;min-width:0;display:flex}.F1KBNa_mdPreviewBody .mdDoubleRow{align-items:flex-start;width:100%;min-width:0;display:flex}.F1KBNa_mdPreviewBody .mdDoubleCol{flex:1;min-width:0}.F1KBNa_mdPreviewBody .mdDoubleRule{background:var(--dsw-alias-border-l2);flex:none;align-self:stretch;width:1px;margin:0 12px}.F1KBNa_mdPreviewFallback{font-family:var(--dsw-font-markdown-code-block);color:var(--dsw-alias-label-tertiary);padding:12px 16px;font-size:12px;line-height:18px}.F1KBNa_mdPreviewBody{word-break:break-word;font-size:13px;line-height:20px}.F1KBNa_mdPreviewBody>:first-child{margin-top:0}.F1KBNa_mdPreviewBody>:last-child{margin-bottom:0}.F1KBNa_mdPreviewBody h1,.F1KBNa_mdPreviewBody h2,.F1KBNa_mdPreviewBody h3,.F1KBNa_mdPreviewBody h4,.F1KBNa_mdPreviewBody h5,.F1KBNa_mdPreviewBody h6{margin:16px 0 8px;font-weight:600;line-height:1.3}.F1KBNa_mdPreviewBody h1{font-size:22px}.F1KBNa_mdPreviewBody h2{font-size:19px}.F1KBNa_mdPreviewBody h3{font-size:16px}.F1KBNa_mdPreviewBody h4{font-size:15px}.F1KBNa_mdPreviewBody h5{font-size:14px}.F1KBNa_mdPreviewBody h6{font-size:13px}.F1KBNa_mdPreviewBody p{margin:8px 0}.F1KBNa_mdPreviewBody ul,.F1KBNa_mdPreviewBody ol{margin:8px 0;padding-left:22px}.F1KBNa_mdPreviewBody li{margin:2px 0}.F1KBNa_mdPreviewBody a{color:var(--dsw-alias-state-business-primary);text-decoration:none}.F1KBNa_mdPreviewBody a:hover{text-decoration:underline}.F1KBNa_mdPreviewBody code{font-family:var(--dsw-font-markdown-code-block);background:#9e9e9e40;border-radius:4px;padding:1px 5px;font-size:12.5px}.F1KBNa_mdPreviewBody pre{border:1px solid var(--dsw-alias-border-l1);font-family:var(--dsw-font-markdown-code-block);background:#9e9e9e40;border-radius:8px;margin:8px 0;padding:10px 12px;font-size:12.5px;line-height:18px;overflow:auto}.F1KBNa_mdPreviewBody pre code{background:0 0;padding:0}.F1KBNa_mdPreviewBody blockquote{border-left:3px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);margin:8px 0;padding:0 12px}.F1KBNa_mdPreviewBody hr{background:var(--dsw-alias-border-l1);border:none;height:1px;margin:16px 0}.F1KBNa_mdPreviewBody table{border-collapse:collapse;margin:8px 0}.F1KBNa_mdPreviewBody th,.F1KBNa_mdPreviewBody td{border:1px solid var(--dsw-alias-border-l1);padding:4px 10px}.F1KBNa_mdPreviewBody img{max-width:100%}.F1KBNa_mdPreviewBody .mdImageFallback{border:1px dashed var(--dsw-alias-border-l2);background:color-mix(in srgb, var(--dsw-alias-markdown-code-block) 45%, transparent);width:100%;min-height:120px;color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-markdown-code-block);text-align:center;white-space:pre-wrap;word-break:break-all;border-radius:4px;justify-content:center;align-items:center;margin:0 auto;padding:8px 12px;font-size:12px;line-height:16px;display:flex}.F1KBNa_toggle{border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:18px;flex:none;width:60px;height:36px;padding:0;position:relative}.F1KBNa_toggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_toggle:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.F1KBNa_toggleOn{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary)}.F1KBNa_toggleOn:hover{background:var(--dsw-alias-state-business-primary)}.F1KBNa_toggleThumb{background:var(--dsw-alias-label-primary);border-radius:50%;width:28px;height:28px;transition:left .12s;position:absolute;top:3px;left:3px}.F1KBNa_toggleOn .F1KBNa_toggleThumb{background:#fff;left:27px}.F1KBNa_stepper{align-items:center;gap:6px;display:inline-flex}.F1KBNa_stepperButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);width:32px;height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;padding:0;position:relative}.F1KBNa_stepperButton:before,.F1KBNa_stepperButton:after{content:\"\";background:currentColor;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.F1KBNa_stepperButton:before{width:10px;height:1px}.F1KBNa_stepperButton:after{width:1px;height:10px;display:none}.F1KBNa_stepperButtonUp:after{display:block}.F1KBNa_stepperButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_stepperButton:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_stepperValue{text-align:center;min-width:2ch;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:14px;line-height:22px}.F1KBNa_states{flex-direction:column;flex:1;min-height:0;padding:4px 12px 12px;display:flex;overflow-y:auto}.F1KBNa_split{flex:1;align-items:stretch;min-height:0;display:flex;position:relative}.F1KBNa_fileListFloat{z-index:40;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:12px;flex-direction:column;padding:6px 8px 10px;display:flex;position:absolute;overflow:hidden}.F1KBNa_fileList{box-sizing:border-box;border-right:1px solid var(--dsw-alias-border-l2);flex-direction:column;flex:none;width:240px;min-height:0;padding:4px 2px 12px 8px;display:flex}.F1KBNa_listScroll{scrollbar-gutter:stable;flex:1;min-height:0;overflow-y:auto}.F1KBNa_bulkActions{flex:none;gap:6px;padding:8px 8px 0 0;display:flex}.F1KBNa_bulkActions .F1KBNa_action{text-align:center;flex:1}.F1KBNa_resizeHandle{cursor:col-resize;background:0 0;flex:none;width:5px;margin:0 -2px}.F1KBNa_resizeHandle:hover{background:var(--dsw-alias-border-l2)}.F1KBNa_detail{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.F1KBNa_detailEmpty{color:var(--dsw-alias-label-tertiary);text-align:center;flex:1;justify-content:center;align-items:center;margin:0;padding:24px;font-size:12px;line-height:18px;display:flex}.F1KBNa_confirmBackdrop{z-index:5;background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, transparent);justify-content:center;align-items:center;padding:24px;display:flex;position:absolute;inset:0}.F1KBNa_confirmCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:340px;box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:12px;padding:16px;display:flex}.F1KBNa_confirmText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0;line-height:1.5}.F1KBNa_confirmActions{flex:none;justify-content:flex-end;align-items:center;gap:8px;display:flex}.F1KBNa_pickerCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:420px;max-width:100%;height:70%;max-height:100%;box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:8px;padding:14px 16px 16px;display:flex;position:relative;overflow:hidden}.F1KBNa_pickerTitle{color:var(--dsw-alias-label-primary);flex:none;font-size:13px;font-weight:500;line-height:20px}.F1KBNa_pickerClose{width:22px;height:22px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;padding:0;display:inline-flex;position:absolute;top:10px;right:10px}.F1KBNa_pickerClose:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_pickerForm{flex:none;gap:8px;display:flex}.F1KBNa_pickerInput{border:1px solid var(--dsw-alias-border-l1);min-width:0;color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;flex:auto;padding:3px 8px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_pickerInput:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}.F1KBNa_tree{scrollbar-gutter:stable;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex:auto;min-height:0;padding:4px 2px 4px 4px;overflow:auto}.F1KBNa_treeRow{border-radius:6px;align-items:center;gap:2px;padding-right:4px;display:flex}.F1KBNa_treeRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_treeRow[data-selected]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent)}.F1KBNa_treeCaret{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_treeCaret:hover{color:var(--dsw-alias-label-primary)}.F1KBNa_treeName{min-width:0;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:6px;flex:auto;align-items:center;gap:6px;padding:2px 4px;font-family:inherit;font-size:12px;line-height:18px;display:flex}.F1KBNa_treeName:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_treeNote{color:var(--dsw-alias-label-tertiary);margin:2px 0;font-size:12px;line-height:18px}.F1KBNa_pickerIcon{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.F1KBNa_pickerName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.F1KBNa_pickerNote{color:var(--dsw-alias-label-tertiary);flex:none;margin:0;font-size:12px;line-height:18px}.F1KBNa_pickerCheck{color:var(--dsw-alias-label-secondary);cursor:pointer;align-items:center;gap:6px;margin-right:auto;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_groupHead{align-items:center;gap:4px;margin:8px 0 4px;padding-right:4px;display:flex}.F1KBNa_action.F1KBNa_addButton{box-sizing:border-box;border-radius:6px;justify-content:center;align-items:center;width:20px;height:20px;min-height:0;padding:0;display:inline-flex}.F1KBNa_groupHead>.F1KBNa_group{margin:0}.F1KBNa_emptyActions{flex-wrap:wrap;justify-content:center;gap:8px;display:flex}.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(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 60%, var(--dsw-alias-label-primary))}.F1KBNa_delCount{color:color-mix(in srgb, var(--dsh-diff-del-color,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{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block);-webkit-user-select:text;user-select:text;flex:1;font-size:12px;line-height:18px;overflow:auto 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_divider{background:var(--dsw-alias-border-l1);align-self:stretch;width:1px;margin:2px 0}.F1KBNa_action{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_action:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_actionPrimary{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_actionPrimary:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}.F1KBNa_actionPrimary:disabled{background:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:.55}.F1KBNa_actionQuietDisabled:disabled{cursor:pointer;color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1);background:0 0}.F1KBNa_actionPrimary.F1KBNa_actionQuietDisabled:disabled{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:1}.F1KBNa_iconAction{justify-content:center;align-items:center;min-height:25px;padding:4px 6px;display:inline-flex}.F1KBNa_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expand{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_expand:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expandExpanded svg{transform:rotate(180deg)}.F1KBNa_diffBodyWrap{flex:1;min-height:0;display:flex;position:relative}.F1KBNa_diffBody{min-width:0;font:var(--dsw-font-markdown-code-block);-webkit-text-size-adjust:100%;text-size-adjust:100%;cursor:text;outline:none;flex:1;padding:0;position:relative;overflow:auto}.F1KBNa_diffBody::-webkit-scrollbar,.F1KBNa_diffBody::-webkit-scrollbar-track,.F1KBNa_diffBody::-webkit-scrollbar-thumb{cursor:default}.F1KBNa_blockActions{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);z-index:2;border-radius:10px;align-items:center;gap:9px;padding:5px;display:flex;position:absolute;right:8px}.F1KBNa_blockPosition{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;margin-top:1px;margin-left:7px;font-size:12px;line-height:16px}.F1KBNa_searchBar{z-index:3;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);border-radius:10px;align-items:center;gap:4px;padding:4px;display:flex;position:absolute;top:8px;right:8px}.F1KBNa_searchInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:150px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:3px 8px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_searchInput:focus{border-color:var(--dsw-alias-state-business-primary)}.F1KBNa_searchCount{text-align:center;min-width:34px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px;line-height:16px}.F1KBNa_searchToggle{border:1px solid var(--dsw-alias-border-l1);min-height:25px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;justify-content:center;align-items:center;padding:4px 6px;display:inline-flex}.F1KBNa_searchToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_searchToggleOn{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-state-business-primary)}.F1KBNa_searchMatch{color:inherit;background-color:#facc1542;border-radius:2px}.F1KBNa_searchMatchCurrent{color:inherit;background-color:#facc1599;border-radius:2px}.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_diffFlashFade;position:absolute;left:0;right:0}.F1KBNa_blockFlashShake{animation:.7s ease-out forwards F1KBNa_diffFlashShake}@keyframes F1KBNa_diffFlashFade{0%{opacity:1}to{opacity:0}}@keyframes F1KBNa_diffFlashShake{0%{opacity:1;transform:translateY(0)}10%{transform:translateY(1px)}20%{transform:translateY(-1px)}30%{transform:translateY(1px)}40%{transform:translateY(-1px)}50%{transform:translateY(1px)}60%{transform:translateY(-1px)}70%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(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%;font-size:calc(1em * var(--dsh-diff-font-scale,1));display:table}.F1KBNa_line{height:var(--dsh-diff-line-height,22px);line-height:var(--dsh-diff-line-height,22px);-webkit-user-select:none;user-select:none;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;-webkit-user-select:none;user-select:none;cursor:default;padding-right:10px;display:table-cell}.F1KBNa_code{white-space:pre;-webkit-user-select:text;user-select:text;display:table-cell}.F1KBNa_context{color:var(--dsw-alias-label-primary)}.F1KBNa_del{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_add{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_statusBar{box-sizing:border-box;border-top:1px solid var(--dsw-alias-border-l2);height:34px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-markdown-code-block);font-variant-numeric:tabular-nums;flex:none;align-items:center;gap:8px;padding:0 8px;font-size:12px;line-height:18px;display:flex}.F1KBNa_statusAction{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);white-space:nowrap;cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_statusAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langSelect{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;flex:none;align-items:center;gap:4px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px;display:inline-flex}.F1KBNa_langSelect:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langLabel{text-overflow:ellipsis;white-space:nowrap;max-width:140px;overflow:hidden}.F1KBNa_notice{z-index:60;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:360px;box-shadow:var(--dsw-shadow-lv3);border-radius:10px;align-items:flex-start;gap:12px;padding:12px 14px;display:flex;position:fixed;bottom:24px;right:24px}.F1KBNa_noticeText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);flex:1;margin:0;line-height:1.45}.F1KBNa_noticeButton{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font:var(--dsw-font-caption);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px}.F1KBNa_noticeButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_wrap .F1KBNa_line{height:auto;min-height:var(--dsh-diff-line-height,22px)}.F1KBNa_subline{height:var(--dsh-diff-line-height,22px);line-height:var(--dsh-diff-line-height,22px);white-space:pre;display:block;overflow:hidden}.F1KBNa_wrapActive{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_wrapActive:hover{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}.F1KBNa_splitRoot{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.F1KBNa_diffBodySplit{flex:1;min-height:0;overflow-x:hidden}.F1KBNa_splitCols{width:100%;min-width:0;display:flex}.F1KBNa_splitCol{box-sizing:border-box;flex:1 1 0;min-width:0;overflow:hidden}.F1KBNa_splitDivider{background:var(--dsw-alias-border-l2);flex:0 0 1px}.F1KBNa_splitCol .F1KBNa_gutter,.F1KBNa_splitCol .F1KBNa_code{vertical-align:top}.F1KBNa_splitHScrollRow{border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex:none;width:100%;min-width:0;display:flex}.F1KBNa_splitHScroll{flex:1 1 0;min-width:0;height:15px;overflow:auto hidden}.F1KBNa_splitHScroll::-webkit-scrollbar{height:15px}.F1KBNa_splitHScroll::-webkit-scrollbar-track{cursor:default}.F1KBNa_splitHScroll::-webkit-scrollbar-thumb{cursor:default}.F1KBNa_splitHScrollFill{height:1px}.F1KBNa_splitLdel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_splitLadd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_splitRdel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_splitRadd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_intraDel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 26%, transparent);text-decoration:line-through}.F1KBNa_intraAdd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 24%, transparent)}";
25
+ const tagId = "dsh-diff-approval/PendingPanel.module.css";
26
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
27
+ const tag = document.createElement("style");
28
+ tag.dataset.plugin = "dsh-diff-approval";
29
+ tag.dataset.pluginCss = tagId;
30
+ tag.textContent = css;
31
+ document.head.appendChild(tag);
32
+ }
33
+ var PendingPanel_module_css_default = {
34
+ "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
35
+ "vSpacer": "F1KBNa_vSpacer",
36
+ "colorPickerPopover": "F1KBNa_colorPickerPopover",
37
+ "pickerCard": "F1KBNa_pickerCard",
38
+ "colorHueCursor": "F1KBNa_colorHueCursor",
39
+ "overviewRuler": "F1KBNa_overviewRuler",
40
+ "line": "F1KBNa_line",
41
+ "wrap": "F1KBNa_wrap",
42
+ "colorPickerPreview": "F1KBNa_colorPickerPreview",
43
+ "searchInput": "F1KBNa_searchInput",
44
+ "pickerForm": "F1KBNa_pickerForm",
45
+ "notice": "F1KBNa_notice",
46
+ "splitCols": "F1KBNa_splitCols",
47
+ "mdPreviewContent": "F1KBNa_mdPreviewContent",
48
+ "pickerClose": "F1KBNa_pickerClose",
49
+ "diffStats": "F1KBNa_diffStats",
50
+ "settingsGroupText": "F1KBNa_settingsGroupText",
51
+ "splitRoot": "F1KBNa_splitRoot",
52
+ "colorBackdrop": "F1KBNa_colorBackdrop",
53
+ "splitHScroll": "F1KBNa_splitHScroll",
54
+ "states": "F1KBNa_states",
55
+ "addCount": "F1KBNa_addCount",
56
+ "iconAction": "F1KBNa_iconAction",
57
+ "blockPosition": "F1KBNa_blockPosition",
58
+ "pickerTitle": "F1KBNa_pickerTitle",
59
+ "readError": "F1KBNa_readError",
60
+ "tree": "F1KBNa_tree",
61
+ "diff": "F1KBNa_diff",
62
+ "colorPickerPanel": "F1KBNa_colorPickerPanel",
63
+ "divergedHint": "F1KBNa_divergedHint",
64
+ "statusBar": "F1KBNa_statusBar",
65
+ "mdPreviewBody": "F1KBNa_mdPreviewBody",
66
+ "rowFailed": "F1KBNa_rowFailed",
67
+ "blockFlash": "F1KBNa_blockFlash",
68
+ "diffFlashFade": "F1KBNa_diffFlashFade",
69
+ "diffPreviewTitle": "F1KBNa_diffPreviewTitle",
70
+ "splitDivider": "F1KBNa_splitDivider",
71
+ "divider": "F1KBNa_divider",
72
+ "mdPreviewFallback": "F1KBNa_mdPreviewFallback",
73
+ "blockActions": "F1KBNa_blockActions",
74
+ "treeRow": "F1KBNa_treeRow",
75
+ "noticeText": "F1KBNa_noticeText",
76
+ "delCount": "F1KBNa_delCount",
77
+ "expandExpanded": "F1KBNa_expandExpanded",
78
+ "pickerInput": "F1KBNa_pickerInput",
79
+ "diffBody": "F1KBNa_diffBody",
80
+ "splitHScrollFill": "F1KBNa_splitHScrollFill",
81
+ "splitRdel": "F1KBNa_splitRdel",
82
+ "settingsGroupBody": "F1KBNa_settingsGroupBody",
83
+ "settingsRowTitle": "F1KBNa_settingsRowTitle",
84
+ "toggle": "F1KBNa_toggle",
85
+ "confirmText": "F1KBNa_confirmText",
86
+ "searchBar": "F1KBNa_searchBar",
87
+ "confirmBackdrop": "F1KBNa_confirmBackdrop",
88
+ "action": "F1KBNa_action",
89
+ "settingsRowText": "F1KBNa_settingsRowText",
90
+ "layer": "F1KBNa_layer",
91
+ "colorSv": "F1KBNa_colorSv",
92
+ "settingsGroup": "F1KBNa_settingsGroup",
93
+ "listScroll": "F1KBNa_listScroll",
94
+ "searchCount": "F1KBNa_searchCount",
95
+ "code": "F1KBNa_code",
96
+ "stepperButton": "F1KBNa_stepperButton",
97
+ "footerButtons": "F1KBNa_footerButtons",
98
+ "settingsRow": "F1KBNa_settingsRow",
99
+ "settingsSelector": "F1KBNa_settingsSelector",
100
+ "stepperButtonUp": "F1KBNa_stepperButtonUp",
101
+ "intraDel": "F1KBNa_intraDel",
102
+ "detailEmpty": "F1KBNa_detailEmpty",
103
+ "rowMeta": "F1KBNa_rowMeta",
104
+ "flexSpacer": "F1KBNa_flexSpacer",
105
+ "searchMatchCurrent": "F1KBNa_searchMatchCurrent",
106
+ "note": "F1KBNa_note",
107
+ "hint": "F1KBNa_hint",
108
+ "row": "F1KBNa_row",
109
+ "langLabel": "F1KBNa_langLabel",
110
+ "settingsGroupTitle": "F1KBNa_settingsGroupTitle",
111
+ "importButton": "F1KBNa_importButton",
112
+ "splitHScrollRow": "F1KBNa_splitHScrollRow",
113
+ "confirmActions": "F1KBNa_confirmActions",
114
+ "markerAdd": "F1KBNa_markerAdd",
115
+ "colorPickerInput": "F1KBNa_colorPickerInput",
116
+ "mdPreviewWrap": "F1KBNa_mdPreviewWrap",
117
+ "kindTag": "F1KBNa_kindTag",
118
+ "actionError": "F1KBNa_actionError",
119
+ "headerActions": "F1KBNa_headerActions",
120
+ "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
121
+ "expand": "F1KBNa_expand",
122
+ "blockFlashShake": "F1KBNa_blockFlashShake",
123
+ "del": "F1KBNa_del",
124
+ "splitCol": "F1KBNa_splitCol",
125
+ "panel": "F1KBNa_panel",
126
+ "mdPreviewSingle": "F1KBNa_mdPreviewSingle",
127
+ "searchMatch": "F1KBNa_searchMatch",
128
+ "mdPreviewDouble": "F1KBNa_mdPreviewDouble",
129
+ "missing": "F1KBNa_missing",
130
+ "badgeLabel": "F1KBNa_badgeLabel",
131
+ "treeNote": "F1KBNa_treeNote",
132
+ "groupHead": "F1KBNa_groupHead",
133
+ "addButton": "F1KBNa_addButton",
134
+ "kindHint": "F1KBNa_kindHint",
135
+ "diffFlashShake": "F1KBNa_diffFlashShake",
136
+ "markerDel": "F1KBNa_markerDel",
137
+ "splitLadd": "F1KBNa_splitLadd",
138
+ "treeCaret": "F1KBNa_treeCaret",
139
+ "settingsRowDesc": "F1KBNa_settingsRowDesc",
140
+ "stepper": "F1KBNa_stepper",
141
+ "emptyActions": "F1KBNa_emptyActions",
142
+ "pickerIcon": "F1KBNa_pickerIcon",
143
+ "pickerName": "F1KBNa_pickerName",
144
+ "close": "F1KBNa_close",
145
+ "confirmCard": "F1KBNa_confirmCard",
146
+ "settingsGroupChevron": "F1KBNa_settingsGroupChevron",
147
+ "rowPath": "F1KBNa_rowPath",
148
+ "rail": "F1KBNa_rail",
149
+ "stepperValue": "F1KBNa_stepperValue",
150
+ "toggleOn": "F1KBNa_toggleOn",
151
+ "treeName": "F1KBNa_treeName",
152
+ "statusAction": "F1KBNa_statusAction",
153
+ "badge": "F1KBNa_badge",
154
+ "actionPrimary": "F1KBNa_actionPrimary",
155
+ "splitRadd": "F1KBNa_splitRadd",
156
+ "diffPreviewScroll": "F1KBNa_diffPreviewScroll",
157
+ "colorValue": "F1KBNa_colorValue",
158
+ "pickerCheck": "F1KBNa_pickerCheck",
159
+ "title": "F1KBNa_title",
160
+ "group": "F1KBNa_group",
161
+ "diffBodyWrap": "F1KBNa_diffBodyWrap",
162
+ "pickerNote": "F1KBNa_pickerNote",
163
+ "intraAdd": "F1KBNa_intraAdd",
164
+ "settingsPage": "F1KBNa_settingsPage",
165
+ "settingsGroupHeader": "F1KBNa_settingsGroupHeader",
166
+ "colorPickerInputRow": "F1KBNa_colorPickerInputRow",
167
+ "diffPreview": "F1KBNa_diffPreview",
168
+ "colorPickerTrigger": "F1KBNa_colorPickerTrigger",
169
+ "colorPickerChevron": "F1KBNa_colorPickerChevron",
170
+ "diffHeader": "F1KBNa_diffHeader",
171
+ "gutter": "F1KBNa_gutter",
172
+ "add": "F1KBNa_add",
173
+ "fileListFloat": "F1KBNa_fileListFloat",
174
+ "rows": "F1KBNa_rows",
175
+ "context": "F1KBNa_context",
176
+ "noteCentered": "F1KBNa_noteCentered",
177
+ "colorPicker": "F1KBNa_colorPicker",
178
+ "overviewMarker": "F1KBNa_overviewMarker",
179
+ "bulkActions": "F1KBNa_bulkActions",
180
+ "diffBodySplit": "F1KBNa_diffBodySplit",
181
+ "missingHint": "F1KBNa_missingHint",
182
+ "searchToggle": "F1KBNa_searchToggle",
183
+ "langSelect": "F1KBNa_langSelect",
184
+ "fileList": "F1KBNa_fileList",
185
+ "colorSvCursor": "F1KBNa_colorSvCursor",
186
+ "settingsGroupDesc": "F1KBNa_settingsGroupDesc",
187
+ "split": "F1KBNa_split",
188
+ "splitLdel": "F1KBNa_splitLdel",
189
+ "noticeButton": "F1KBNa_noticeButton",
190
+ "detail": "F1KBNa_detail",
191
+ "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
192
+ "importNote": "F1KBNa_importNote",
193
+ "rowHead": "F1KBNa_rowHead",
194
+ "wrapActive": "F1KBNa_wrapActive",
195
+ "emptyState": "F1KBNa_emptyState",
196
+ "searchToggleOn": "F1KBNa_searchToggleOn",
197
+ "colorSwatch": "F1KBNa_colorSwatch",
198
+ "header": "F1KBNa_header",
199
+ "resizeHandle": "F1KBNa_resizeHandle",
200
+ "colorHue": "F1KBNa_colorHue",
201
+ "lines": "F1KBNa_lines",
202
+ "diffPath": "F1KBNa_diffPath",
203
+ "diffActions": "F1KBNa_diffActions",
204
+ "badgeCount": "F1KBNa_badgeCount",
205
+ "toggleThumb": "F1KBNa_toggleThumb",
206
+ "subline": "F1KBNa_subline"
207
+ };
208
+ //#endregion
209
+ //#region lib/types/client/PathPicker.js
210
+ /**
211
+ * The add-path dialog: pick one path in a lazily-loaded workspace tree — or type
212
+ * it outright — then press Add. Nothing is added by pointing at it: the path and
213
+ * the no-change box are prepared first, and the host judges them once the button
214
+ * is pressed.
215
+ *
216
+ * The path box is the single source of truth for the selection: a tree row is
217
+ * highlighted while its path is the box's text, and editing the text by hand
218
+ * simply leaves no row highlighted.
219
+ *
220
+ * The dialog is a modal inside the panel: it owns Escape while it is on screen
221
+ * (the panel's own chords stand down, see `pathPickerOpen`).
222
+ * @module dsh-diff-approval/client/PathPicker
223
+ */
224
+ /** Whether this panel's add-path dialog is on screen. The panel's global chords
225
+ * consult this so the modal owns the keyboard while it is open. */
226
+ function pathPickerOpen() {
227
+ return typeof document !== "undefined" && document.querySelector("[data-diff-path-picker]") !== null;
228
+ }
229
+ /**
230
+ * The file glyph the icon library lacks (it ships folders only): a page with a
231
+ * folded corner, drawn on the same 16px grid as the folder icons.
232
+ * @returns the 16px file glyph.
233
+ */
234
+ function FileIcon() {
235
+ return (0, react_jsx_runtime.jsx)("svg", {
236
+ width: 16,
237
+ height: 16,
238
+ viewBox: "0 0 16 16",
239
+ fill: "none",
240
+ xmlns: "http://www.w3.org/2000/svg",
241
+ "aria-hidden": "true",
242
+ "data-diff-file-icon": true,
243
+ children: (0, react_jsx_runtime.jsx)("path", {
244
+ fill: "currentColor",
245
+ fillRule: "evenodd",
246
+ clipRule: "evenodd",
247
+ d: "M3.5 1.5h5l4 4v9h-9zM8.5 1.5v4h4z"
248
+ })
249
+ });
250
+ }
251
+ /**
252
+ * The add-path dialog.
253
+ * @param props - the workspace root, the host calls, and the close/toast sinks.
254
+ * @returns the modal (rendered only while open by its parent).
255
+ */
256
+ function PathPicker({ rootPath, onBrowse, onAdd, onToast, onClose, t }) {
257
+ /** The path box: the target, and the tree's selection, in one string. */
258
+ const [path, setPath] = (0, react.useState)("");
259
+ const [levels, setLevels] = (0, react.useState)({});
260
+ const [expanded, setExpanded] = (0, react.useState)(() => /* @__PURE__ */ new Set([""]));
261
+ /** Every open starts unticked: the box is a per-add decision. */
262
+ const [includeUnchanged, setIncludeUnchanged] = (0, react.useState)(false);
263
+ const [busy, setBusy] = (0, react.useState)(false);
264
+ const inputRef = (0, react.useRef)(null);
265
+ const browseRef = (0, react.useRef)(onBrowse);
266
+ browseRef.current = onBrowse;
267
+ const addRef = (0, react.useRef)(onAdd);
268
+ addRef.current = onAdd;
269
+ const toastRef = (0, react.useRef)(onToast);
270
+ toastRef.current = onToast;
271
+ const closeRef = (0, react.useRef)(onClose);
272
+ closeRef.current = onClose;
273
+ /** Load one directory level; `dir` is workspace-relative (`''` is the root). */
274
+ const load = (0, react.useCallback)(async (dir) => {
275
+ setLevels((prev) => ({
276
+ ...prev,
277
+ [dir]: { status: "loading" }
278
+ }));
279
+ try {
280
+ const value = await browseRef.current(dir === "" ? void 0 : dir);
281
+ setLevels((prev) => ({
282
+ ...prev,
283
+ [dir]: {
284
+ status: "ready",
285
+ entries: value.entries,
286
+ truncated: value.truncated
287
+ }
288
+ }));
289
+ } catch (error) {
290
+ setLevels((prev) => ({
291
+ ...prev,
292
+ [dir]: {
293
+ status: "failed",
294
+ error: error instanceof Error ? error.message : String(error)
295
+ }
296
+ }));
297
+ }
298
+ }, []);
299
+ (0, react.useEffect)(() => {
300
+ load("");
301
+ inputRef.current?.focus();
302
+ }, [load]);
303
+ (0, react.useEffect)(() => {
304
+ const onKeyDown = (event) => {
305
+ if (event.key !== "Escape") return;
306
+ event.preventDefault();
307
+ closeRef.current();
308
+ };
309
+ window.addEventListener("keydown", onKeyDown, true);
310
+ return () => {
311
+ window.removeEventListener("keydown", onKeyDown, true);
312
+ };
313
+ }, []);
314
+ /** Report one add outcome; only a landed add closes the dialog. */
315
+ const report = (value) => {
316
+ const toast = toastRef.current;
317
+ if (value.outcome === "added") {
318
+ toast(value.truncated === true ? t("panel.addDoneTruncated", { count: value.added }) : t("panel.addDone", { count: value.added }));
319
+ closeRef.current();
320
+ return;
321
+ }
322
+ if (value.outcome === "duplicate") toast(t("panel.addDuplicate"));
323
+ else if (value.outcome === "unchanged") toast(t("panel.addUnchanged"));
324
+ else if (value.outcome === "empty") toast(t("panel.addEmpty"));
325
+ else if (value.outcome === "missing") toast(t("panel.addMissing"));
326
+ else if (value.outcome === "outside") toast(t("panel.addOutside"));
327
+ else if (value.outcome === "no-vcs") toast(t("panel.importNoVcs"));
328
+ else toast(t("panel.addFailed", { message: value.message ?? "" }));
329
+ };
330
+ /** The dialog's one action: the host judges the path here, not on selection. */
331
+ const submit = async () => {
332
+ const target = path.trim();
333
+ if (target === "" || busy) return;
334
+ setBusy(true);
335
+ try {
336
+ report(await addRef.current(target, includeUnchanged));
337
+ } catch (error) {
338
+ toastRef.current(t("panel.addFailed", { message: error instanceof Error ? error.message : String(error) }));
339
+ } finally {
340
+ setBusy(false);
341
+ }
342
+ };
343
+ /** Expand or collapse one directory, loading its level the first time. */
344
+ const toggle = (dir) => {
345
+ setExpanded((prev) => {
346
+ const next = new Set(prev);
347
+ if (next.has(dir)) next.delete(dir);
348
+ else next.add(dir);
349
+ return next;
350
+ });
351
+ if (levels[dir] === void 0) load(dir);
352
+ };
353
+ /** One tree row: caret, icon, name. Selecting only fills the path box. */
354
+ const row = (entry, depth) => {
355
+ const isDirectory = entry.type === "directory";
356
+ const isOpen = expanded.has(entry.path);
357
+ const selected = path === entry.path;
358
+ return (0, react_jsx_runtime.jsxs)("div", {
359
+ className: PendingPanel_module_css_default.treeRow,
360
+ style: { paddingLeft: `${4 + depth * 14}px` },
361
+ "data-diff-picker-row": entry.path,
362
+ "data-selected": selected ? "" : void 0,
363
+ children: [isDirectory ? (0, react_jsx_runtime.jsx)("button", {
364
+ type: "button",
365
+ className: PendingPanel_module_css_default.treeCaret,
366
+ "data-diff-picker-toggle": entry.path,
367
+ "aria-expanded": isOpen,
368
+ "aria-label": isOpen ? t("action.collapseRow") : t("action.expandRow"),
369
+ onClick: () => {
370
+ toggle(entry.path);
371
+ },
372
+ children: isOpen ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, { size: 14 })
373
+ }) : (0, react_jsx_runtime.jsx)("span", {
374
+ className: PendingPanel_module_css_default.treeCaret,
375
+ "aria-hidden": "true"
376
+ }), (0, react_jsx_runtime.jsxs)("button", {
377
+ type: "button",
378
+ className: PendingPanel_module_css_default.treeName,
379
+ "data-diff-picker-select": entry.path,
380
+ "aria-pressed": selected,
381
+ onClick: () => {
382
+ setPath(entry.path);
383
+ },
384
+ children: [isDirectory ? isOpen ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
385
+ size: 16,
386
+ className: PendingPanel_module_css_default.pickerIcon
387
+ }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {
388
+ size: 16,
389
+ className: PendingPanel_module_css_default.pickerIcon
390
+ }) : (0, react_jsx_runtime.jsx)("span", {
391
+ className: PendingPanel_module_css_default.pickerIcon,
392
+ children: (0, react_jsx_runtime.jsx)(FileIcon, {})
393
+ }), (0, react_jsx_runtime.jsx)("span", {
394
+ className: PendingPanel_module_css_default.pickerName,
395
+ children: entry.name
396
+ })]
397
+ })]
398
+ }, entry.path);
399
+ };
400
+ /** One loaded level's children, expanded directories recursing into their own.
401
+ * `trail` carries the branch's ancestors: a directory that lists itself (a
402
+ * symlink loop) must stop the walk rather than recurse forever. */
403
+ const levelRows = (dir, depth, trail) => {
404
+ const level = levels[dir];
405
+ if (level === void 0 || level.status === "loading") return (0, react_jsx_runtime.jsx)("p", {
406
+ className: PendingPanel_module_css_default.treeNote,
407
+ style: { paddingLeft: `${10 + depth * 14}px` },
408
+ children: t("panel.loading")
409
+ });
410
+ if (level.status === "failed") return (0, react_jsx_runtime.jsx)("p", {
411
+ className: PendingPanel_module_css_default.treeNote,
412
+ style: { paddingLeft: `${10 + depth * 14}px` },
413
+ role: "alert",
414
+ children: level.error
415
+ });
416
+ if (level.entries.length === 0) return (0, react_jsx_runtime.jsx)("p", {
417
+ className: PendingPanel_module_css_default.treeNote,
418
+ style: { paddingLeft: `${10 + depth * 14}px` },
419
+ children: t("panel.addNoEntries")
420
+ });
421
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [level.entries.map((entry) => {
422
+ const descends = entry.type === "directory" && expanded.has(entry.path) && !trail.has(entry.path);
423
+ return (0, react_jsx_runtime.jsxs)("div", { children: [row(entry, depth), descends ? levelRows(entry.path, depth + 1, /* @__PURE__ */ new Set([...trail, entry.path])) : null] }, entry.path);
424
+ }), level.truncated && (0, react_jsx_runtime.jsx)("p", {
425
+ className: PendingPanel_module_css_default.treeNote,
426
+ style: { paddingLeft: `${10 + depth * 14}px` },
427
+ children: t("panel.addTruncated", { count: level.entries.length })
428
+ })] });
429
+ };
430
+ const rootSelected = rootPath !== void 0 && path === rootPath;
431
+ return (0, react_jsx_runtime.jsx)("div", {
432
+ className: PendingPanel_module_css_default.confirmBackdrop,
433
+ "data-diff-path-picker": true,
434
+ children: (0, react_jsx_runtime.jsxs)("div", {
435
+ className: PendingPanel_module_css_default.pickerCard,
436
+ role: "dialog",
437
+ "aria-modal": "true",
438
+ "aria-label": t("panel.addTitle"),
439
+ children: [
440
+ (0, react_jsx_runtime.jsx)("span", {
441
+ className: PendingPanel_module_css_default.pickerTitle,
442
+ children: t("panel.addTitle")
443
+ }),
444
+ (0, react_jsx_runtime.jsx)("div", {
445
+ className: PendingPanel_module_css_default.pickerForm,
446
+ children: (0, react_jsx_runtime.jsx)("input", {
447
+ ref: inputRef,
448
+ className: PendingPanel_module_css_default.pickerInput,
449
+ "data-diff-picker-input": true,
450
+ value: path,
451
+ placeholder: t("panel.addPathPlaceholder"),
452
+ onChange: (event) => {
453
+ setPath(event.target.value);
454
+ },
455
+ onKeyDown: (event) => {
456
+ if (event.key !== "Enter") return;
457
+ event.preventDefault();
458
+ submit();
459
+ }
460
+ })
461
+ }),
462
+ (0, react_jsx_runtime.jsxs)("div", {
463
+ className: PendingPanel_module_css_default.tree,
464
+ "data-diff-picker-tree": true,
465
+ children: [(0, react_jsx_runtime.jsxs)("div", {
466
+ className: PendingPanel_module_css_default.treeRow,
467
+ style: { paddingLeft: "4px" },
468
+ "data-diff-picker-row": rootPath ?? "",
469
+ "data-selected": rootSelected ? "" : void 0,
470
+ children: [(0, react_jsx_runtime.jsx)("button", {
471
+ type: "button",
472
+ className: PendingPanel_module_css_default.treeCaret,
473
+ "data-diff-picker-toggle": "",
474
+ "aria-expanded": expanded.has(""),
475
+ "aria-label": expanded.has("") ? t("action.collapseRow") : t("action.expandRow"),
476
+ onClick: () => {
477
+ toggle("");
478
+ },
479
+ children: expanded.has("") ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, { size: 14 })
480
+ }), (0, react_jsx_runtime.jsxs)("button", {
481
+ type: "button",
482
+ className: PendingPanel_module_css_default.treeName,
483
+ "data-diff-picker-select": "",
484
+ "aria-pressed": rootSelected,
485
+ disabled: rootPath === void 0,
486
+ onClick: () => {
487
+ setPath(rootPath ?? "");
488
+ },
489
+ children: [expanded.has("") ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
490
+ size: 16,
491
+ className: PendingPanel_module_css_default.pickerIcon
492
+ }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {
493
+ size: 16,
494
+ className: PendingPanel_module_css_default.pickerIcon
495
+ }), (0, react_jsx_runtime.jsx)("span", {
496
+ className: PendingPanel_module_css_default.pickerName,
497
+ children: t("panel.addRoot")
498
+ })]
499
+ })]
500
+ }), expanded.has("") ? levelRows("", 1, /* @__PURE__ */ new Set([""])) : null]
501
+ }),
502
+ (0, react_jsx_runtime.jsx)("p", {
503
+ className: PendingPanel_module_css_default.pickerNote,
504
+ children: t("panel.addHint")
505
+ }),
506
+ (0, react_jsx_runtime.jsxs)("div", {
507
+ className: PendingPanel_module_css_default.confirmActions,
508
+ children: [
509
+ (0, react_jsx_runtime.jsxs)("label", {
510
+ className: PendingPanel_module_css_default.pickerCheck,
511
+ children: [(0, react_jsx_runtime.jsx)("input", {
512
+ type: "checkbox",
513
+ "data-diff-picker-unchanged": true,
514
+ checked: includeUnchanged,
515
+ onChange: (event) => {
516
+ setIncludeUnchanged(event.target.checked);
517
+ }
518
+ }), t("panel.addIncludeUnchanged")]
519
+ }),
520
+ (0, react_jsx_runtime.jsx)("button", {
521
+ type: "button",
522
+ className: PendingPanel_module_css_default.action,
523
+ "data-diff-picker-cancel": true,
524
+ onClick: () => {
525
+ closeRef.current();
526
+ },
527
+ children: t("action.cancel")
528
+ }),
529
+ (0, react_jsx_runtime.jsx)("button", {
530
+ type: "button",
531
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
532
+ "data-diff-picker-submit": true,
533
+ disabled: busy || path.trim() === "",
534
+ onClick: () => {
535
+ submit();
536
+ },
537
+ children: t("panel.addPathGo")
538
+ })
539
+ ]
540
+ }),
541
+ (0, react_jsx_runtime.jsx)("button", {
542
+ type: "button",
543
+ className: PendingPanel_module_css_default.pickerClose,
544
+ "data-diff-picker-close": true,
545
+ "aria-label": t("action.close"),
546
+ onClick: () => {
547
+ closeRef.current();
548
+ },
549
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
550
+ })
551
+ ]
552
+ })
553
+ });
554
+ }
555
+ //#endregion
23
556
  //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js
24
557
  var Diff = class {
25
558
  diff(oldStr, newStr, options = {}) {
@@ -625,25 +1158,91 @@ window.__ModuleLoader__.load({
625
1158
  }
626
1159
  }
627
1160
  /**
628
- * Split `text` into an array of lines, including the trailing newline character (where present)
629
- */
630
- function splitLines$1(text) {
631
- const hasTrailingNl = text.endsWith("\n");
632
- const result = text.split("\n").map((line) => line + "\n");
633
- if (hasTrailingNl) result.pop();
634
- else result.push(result.pop().slice(0, -1));
635
- return result;
636
- }
637
- //#endregion
638
- //#region lib/types/client/whole-file-diff.js
639
- /**
640
- * Whole-file diff model for the pending review viewer: one unified row list
641
- * over the complete old and new contents, so the viewer renders the entire
642
- * file with changed lines marked and unchanged lines as context. Pure
643
- * derivation; the panel owns rendering. The `diff` package is browser-safe.
644
- * @module dsh-diff-approval/client/whole-file-diff
645
- */
646
- /**
1161
+ * Split `text` into an array of lines, including the trailing newline character (where present)
1162
+ */
1163
+ function splitLines$1(text) {
1164
+ const hasTrailingNl = text.endsWith("\n");
1165
+ const result = text.split("\n").map((line) => line + "\n");
1166
+ if (hasTrailingNl) result.pop();
1167
+ else result.push(result.pop().slice(0, -1));
1168
+ return result;
1169
+ }
1170
+ //#endregion
1171
+ //#region lib/types/client/whole-file-diff.js
1172
+ /**
1173
+ * Whole-file diff model for the pending review viewer: one unified row list
1174
+ * over the complete old and new contents, so the viewer renders the entire
1175
+ * file with changed lines marked and unchanged lines as context. Pure
1176
+ * derivation; the panel owns rendering. The `diff` package is browser-safe.
1177
+ * @module dsh-diff-approval/client/whole-file-diff
1178
+ */
1179
+ /** Split a row list into maximal runs of non-context rows. */
1180
+ function changeBlocksOf(diff) {
1181
+ const blocks = [];
1182
+ let start = -1;
1183
+ diff.rows.forEach((row, index) => {
1184
+ if (row.kind !== "context") {
1185
+ if (start === -1) start = index;
1186
+ } else if (start !== -1) {
1187
+ blocks.push({
1188
+ start,
1189
+ end: index - 1
1190
+ });
1191
+ start = -1;
1192
+ }
1193
+ });
1194
+ if (start !== -1) blocks.push({
1195
+ start,
1196
+ end: diff.rows.length - 1
1197
+ });
1198
+ return blocks;
1199
+ }
1200
+ /**
1201
+ * One diff block's old/new line ranges, 1-based inclusive, for block-level
1202
+ * keep/revert. A side with no lines (a pure addition or deletion) is empty; its
1203
+ * start is that side's insertion point — the line after the surrounding context
1204
+ * — so the host can insert there.
1205
+ *
1206
+ * Shared by the source view and the Markdown preview: both derive blocks from
1207
+ * the same rows, so a preview element and a source row name the same target.
1208
+ * @param rows - the whole-file diff rows.
1209
+ * @param block - the block's row range.
1210
+ * @returns the old and new line ranges.
1211
+ */
1212
+ function blockRangesOf(rows, block) {
1213
+ let oldStart = Infinity;
1214
+ let oldEnd = -Infinity;
1215
+ let newStart = Infinity;
1216
+ let newEnd = -Infinity;
1217
+ for (let index = block.start; index <= block.end; index++) {
1218
+ const row = rows[index];
1219
+ if (row === void 0) continue;
1220
+ if (row.oldLine !== void 0) {
1221
+ oldStart = Math.min(oldStart, row.oldLine);
1222
+ oldEnd = Math.max(oldEnd, row.oldLine);
1223
+ }
1224
+ if (row.newLine !== void 0) {
1225
+ newStart = Math.min(newStart, row.newLine);
1226
+ newEnd = Math.max(newEnd, row.newLine);
1227
+ }
1228
+ }
1229
+ const before = rows[block.start - 1];
1230
+ if (oldStart === Infinity) {
1231
+ oldStart = (before?.oldLine ?? 0) + 1;
1232
+ oldEnd = oldStart - 1;
1233
+ }
1234
+ if (newStart === Infinity) {
1235
+ newStart = (before?.newLine ?? 0) + 1;
1236
+ newEnd = newStart - 1;
1237
+ }
1238
+ return {
1239
+ oldStart,
1240
+ oldEnd,
1241
+ newStart,
1242
+ newEnd
1243
+ };
1244
+ }
1245
+ /**
647
1246
  * Compute the whole-file view between two contents. The context budget is the
648
1247
  * side lengths, so every hunk covers the file and unchanged lines survive as
649
1248
  * context rows; the `` patch marker is annotation
@@ -5224,21 +5823,28 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5224
5823
  function computeDiffOf(oldText, newText) {
5225
5824
  return computeWholeFileDiff(oldText, newText);
5226
5825
  }
5227
- /** Group the whole-file diff rows into contiguous runs of the same kind. */
5826
+ /** Group the whole-file diff rows into contiguous same-kind runs, tagged with
5827
+ * their change block (a run never spans two blocks: context separates them). */
5228
5828
  function diffRuns(oldText, newText) {
5229
5829
  const diff = computeDiffOf(oldText, newText);
5830
+ const blockOfRow = /* @__PURE__ */ new Map();
5831
+ changeBlocksOf(diff).forEach((block, index) => {
5832
+ for (let row = block.start; row <= block.end; row++) blockOfRow.set(row, index);
5833
+ });
5230
5834
  const runs = [];
5231
5835
  let current = null;
5232
- for (const row of diff.rows) {
5836
+ diff.rows.forEach((row, index) => {
5233
5837
  const kind = row.kind === "add" ? "add" : row.kind === "del" ? "del" : "context";
5234
- if (current === null || current.kind !== kind) {
5838
+ const block = blockOfRow.get(index);
5839
+ if (current === null || current.kind !== kind || current.block !== block) {
5235
5840
  current = {
5236
5841
  kind,
5237
- text: row.text
5842
+ text: row.text,
5843
+ block
5238
5844
  };
5239
5845
  runs.push(current);
5240
5846
  } else current.text += `\n${row.text}`;
5241
- }
5847
+ });
5242
5848
  return runs;
5243
5849
  }
5244
5850
  /** Render one text run to sanitized HTML (Markdown). */
@@ -5291,9 +5897,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5291
5897
  add: wrapChanged(runs.add, "add")
5292
5898
  };
5293
5899
  }
5294
- /** One run rendered as a block carrying its diff-kind class. */
5900
+ /** The change-block attributes one block-carrying run renders with; empty for
5901
+ * context, which belongs to no block and takes no keep/revert action. */
5902
+ function blockAttrs(run) {
5903
+ return run.block === void 0 ? "" : ` data-md-block="${run.block}" data-md-kind="${run.kind}"`;
5904
+ }
5905
+ /** One run rendered as a block carrying its diff-kind class and, for a changed
5906
+ * run, the change block it belongs to. */
5295
5907
  function renderBlock(run) {
5296
- return `<div class="${run.kind === "add" ? "mdBlock mdAdd" : run.kind === "del" ? "mdBlock mdDel" : "mdBlock"}">${renderRun(run)}</div>`;
5908
+ return `<div class="${run.kind === "add" ? "mdBlock mdAdd" : run.kind === "del" ? "mdBlock mdDel" : "mdBlock"}"${blockAttrs(run)}>${renderRun(run)}</div>`;
5297
5909
  }
5298
5910
  /**
5299
5911
  * Pair the diff runs into aligned rows so a deletion and the addition that
@@ -5351,8 +5963,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5351
5963
  if (before !== void 0 && after !== void 0 && before.kind === "del" && after.kind === "add") {
5352
5964
  const hl = wordHighlight(before.text, after.text);
5353
5965
  if (hl !== void 0) {
5354
- beforeHtml = `<div class="mdBlock mdDel">${renderMarkdownInline(hl.del)}</div>`;
5355
- afterHtml = `<div class="mdBlock mdAdd">${renderMarkdownInline(hl.add)}</div>`;
5966
+ beforeHtml = `<div class="mdBlock mdDel"${blockAttrs(before)}>${renderMarkdownInline(hl.del)}</div>`;
5967
+ afterHtml = `<div class="mdBlock mdAdd"${blockAttrs(after)}>${renderMarkdownInline(hl.add)}</div>`;
5356
5968
  }
5357
5969
  }
5358
5970
  return `<div class="mdDoubleRow"><div class="mdDoubleCol">${beforeHtml}</div><div class="mdDoubleRule"></div><div class="mdDoubleCol">${afterHtml}</div></div>`;
@@ -5448,6 +6060,51 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5448
6060
  }
5449
6061
  }
5450
6062
  //#endregion
6063
+ //#region lib/types/client/search.js
6064
+ /**
6065
+ * The in-file search matcher, shared by the unified diff and the split view so
6066
+ * both highlight and count the same occurrences.
6067
+ * @module dsh-diff-approval/client/search
6068
+ */
6069
+ /** The plain search: case-insensitive, substring. */
6070
+ const DEFAULT_SEARCH_OPTIONS = {
6071
+ caseSensitive: false,
6072
+ wholeWord: false
6073
+ };
6074
+ /**
6075
+ * Whether `at` holds an identifier character (the ASCII set editors use), or
6076
+ * false outside the string, so a match at either edge counts as a whole word.
6077
+ * @param text - the searched line.
6078
+ * @param at - the character index to test.
6079
+ * @returns whether that position is part of a word.
6080
+ */
6081
+ function isWordCharAt(text, at) {
6082
+ if (at < 0 || at >= text.length) return false;
6083
+ return /[A-Za-z0-9_]/.test(text.charAt(at));
6084
+ }
6085
+ /**
6086
+ * Character ranges of every occurrence of `query` in `text` that the options
6087
+ * accept. Case-insensitive and substring-based unless narrowed.
6088
+ * @param text - the line to search.
6089
+ * @param query - the search text; empty matches nothing.
6090
+ * @param options - case and whole-word narrowing.
6091
+ * @returns `[start, end)` ranges in ascending order.
6092
+ */
6093
+ function matchRangesOf(text, query, options = DEFAULT_SEARCH_OPTIONS) {
6094
+ if (query === "") return [];
6095
+ const haystack = options.caseSensitive ? text : text.toLowerCase();
6096
+ const needle = options.caseSensitive ? query : query.toLowerCase();
6097
+ const out = [];
6098
+ let from = 0;
6099
+ for (;;) {
6100
+ const at = haystack.indexOf(needle, from);
6101
+ if (at === -1) return out;
6102
+ const end = at + needle.length;
6103
+ if (!options.wholeWord || !isWordCharAt(text, at - 1) && !isWordCharAt(text, end)) out.push([at, end]);
6104
+ from = end;
6105
+ }
6106
+ }
6107
+ //#endregion
5451
6108
  //#region lib/types/client/split-diff.js
5452
6109
  /**
5453
6110
  * Side-by-side (split) diff model: the unified whole-file diff rows are
@@ -5553,23 +6210,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5553
6210
  };
5554
6211
  }
5555
6212
  /**
5556
- * Pair indices whose left or right text contains the query (case-insensitive).
5557
- * A pair counts once however many times the query appears, so split search
5558
- * highlights the whole pair on both columns and a single "current" pair is
5559
- * stepped through — not each individual left/right occurrence.
6213
+ * Pair indices whose left or right text matches the query. A pair counts once
6214
+ * however many times the query appears, so split search highlights the whole pair
6215
+ * on both columns and a single "current" pair is stepped through — not each
6216
+ * individual left/right occurrence.
5560
6217
  * @param pairs - the split pairs.
5561
6218
  * @param query - the query text; an empty query matches nothing.
6219
+ * @param options - case and whole-word narrowing.
5562
6220
  * @returns matching pair indices, in file order.
5563
6221
  */
5564
- function searchPairs(pairs, query) {
6222
+ function searchPairs(pairs, query, options = DEFAULT_SEARCH_OPTIONS) {
5565
6223
  if (query === "") return [];
5566
- const lower = query.toLowerCase();
5567
6224
  const matches = [];
5568
6225
  for (let index = 0; index < pairs.length; index++) {
5569
6226
  const p = pairs[index];
5570
6227
  if (p === void 0) continue;
5571
- if (p.left !== void 0 && p.left.text.toLowerCase().includes(lower)) matches.push(index);
5572
- else if (p.right !== void 0 && p.right.text.toLowerCase().includes(lower)) matches.push(index);
6228
+ if (p.left !== void 0 && matchRangesOf(p.left.text, query, options).length > 0) matches.push(index);
6229
+ else if (p.right !== void 0 && matchRangesOf(p.right.text, query, options).length > 0) matches.push(index);
5573
6230
  }
5574
6231
  return matches;
5575
6232
  }
@@ -16182,31 +16839,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16182
16839
  /**
16183
16840
  * Tokenize guards: the viewer must never let the synchronous JS-regex engine
16184
16841
  * block the main thread on a hostile file. Lines above the length cap are
16185
- * returned plain by shiki, the per-line budget caps a single pathological
16186
- * line, and whole files above the size/line caps skip highlighting entirely
16187
- * (the diff still shows its added/deleted coloring). Company-workload files
16188
- * (generated bundles, minified output, huge data dumps) otherwise stall the
16189
- * tab for tens of seconds and can OOM it.
16842
+ * returned plain by shiki, the per-line budget caps a single pathological line,
16843
+ * and a window whose own text is enormous degrades to plain. Windows are what
16844
+ * keeps this bounded in the first place the viewer never tokenizes more than
16845
+ * the lines it is about to show (see the windowed highlight hook), so a huge
16846
+ * file is no longer skipped wholesale: it is highlighted a screen at a time.
16190
16847
  */
16191
16848
  const MAX_LINE_LENGTH = 2e3;
16192
16849
  const TOKENIZE_TIME_LIMIT_MS = 100;
16193
- const MAX_HIGHLIGHT_CHARS = 3e5;
16194
- const MAX_HIGHLIGHT_LINES = 1e4;
16850
+ const MAX_WINDOW_CHARS = 5e5;
16195
16851
  /**
16196
- * Bounded tokenize cache keyed by the code string: reselecting a recently
16197
- * viewed file (or a file whose content reference reappears) reuses the runs
16198
- * instead of re-running the engine. Insertion-ordered, so the oldest entry is
16199
- * evicted first; 16 codes bounds both memory and the eviction cost.
16852
+ * Bounded tokenize cache keyed by the window's own text and language: scrolling
16853
+ * back over a window that was just highlighted reuses its runs instead of
16854
+ * re-running the engine. Insertion-ordered, so the oldest entry is evicted first;
16855
+ * 24 entries bounds both memory and the eviction cost. Windows highlighted from a
16856
+ * saved grammar state are not cached (the state is opaque, so it cannot be part
16857
+ * of the key).
16200
16858
  */
16201
- const TOKENIZE_CACHE_MAX = 16;
16859
+ const TOKENIZE_CACHE_MAX = 24;
16202
16860
  const tokenizeCache = /* @__PURE__ */ new Map();
16203
- /** Whether `code` is too large to highlight safely (short-circuits fast). */
16204
- function tooLargeToHighlight(code) {
16205
- if (code.length > MAX_HIGHLIGHT_CHARS) return true;
16206
- let newlines = 0;
16207
- for (let index = 0; index < code.length; index++) if (code.charCodeAt(index) === 10 && ++newlines > MAX_HIGHLIGHT_LINES) return true;
16208
- return false;
16209
- }
16210
16861
  /** All grammars this bundle registers; each entry's own `name` is the tokenize id. */
16211
16862
  const LANGS = [
16212
16863
  c_default,
@@ -16347,43 +16998,316 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16347
16998
  highlighter();
16348
16999
  }, 0).unref?.();
16349
17000
  /**
16350
- * Tokenize `code` into per-line highlighted runs when `lang` names a
16351
- * registered grammar; `undefined` means the caller renders its plain fallback.
16352
- * Each run's color is a `--shiki-*` custom property, keeping token colors on
16353
- * the harness theme's sheets. The trailing newline shiki appends as a final
16354
- * empty line is dropped so the run count matches the caller's own line array.
16355
- * @param code - the source text.
17001
+ * Tokenize the lines `[from, to)` into per-line highlighted runs when `lang`
17002
+ * names a registered grammar; `undefined` means the caller renders its plain
17003
+ * fallback. Each run's color is a `--shiki-*` custom property, keeping token
17004
+ * colors on the harness theme's sheets. The trailing newline shiki appends as a
17005
+ * final empty line is dropped so the run count matches the caller's own array.
17006
+ *
17007
+ * This is the viewer's only entry point, and it is deliberately windowed: the
17008
+ * code view renders a virtual window of rows, so highlighting a whole file to
17009
+ * show one screenful is almost all waste (measured on a 3818-line file: ~945 ms
17010
+ * for both sides whole-file against ~8 ms for one window). A window is continued
17011
+ * *exactly* from a previous one's `state`, or approximately from `context` lines
17012
+ * when no state is known yet — which is what lets a jump straight to the middle
17013
+ * of a file colour correctly without tokenizing anything above it.
17014
+ * @param lines - the side's lines, indexed from 0 as the caller's line numbers are.
16356
17015
  * @param lang - the Shiki grammar id, or `undefined` for plain text.
16357
- * @returns one entry per source line (each an array of runs), or `undefined` when unhighlightable.
17016
+ * @param from - the first line index to highlight (inclusive, clamped).
17017
+ * @param to - one past the last line index to highlight (clamped).
17018
+ * @param options - the grammar state to resume from and/or context lines for the grammar.
17019
+ * @returns the window's runs and its end state, or `undefined` when the language
17020
+ * is unknown, the range is empty, or the window's own text is too large.
16358
17021
  */
16359
- function highlightLines(code, lang) {
16360
- if (lang === void 0 || code === "") return void 0;
17022
+ function highlightWindow(lines, lang, from, to, options = {}) {
17023
+ if (lang === void 0) return void 0;
16361
17024
  if (!highlighter().getLoadedLanguages().includes(lang)) return void 0;
16362
- if (tooLargeToHighlight(code)) return void 0;
16363
- const cached = tokenizeCache.get(code)?.get(lang);
16364
- if (cached !== void 0) return cached;
16365
- const { tokens } = highlighter().codeToTokens(code, {
17025
+ const start = Math.max(0, Math.min(from, lines.length));
17026
+ const end = Math.max(start, Math.min(to, lines.length));
17027
+ if (end === start) return void 0;
17028
+ const context = options.state === void 0 ? Math.max(0, options.context ?? 0) : 0;
17029
+ const contextFrom = Math.max(0, start - context);
17030
+ const text = lines.slice(contextFrom, end).join("\n");
17031
+ if (text.length > MAX_WINDOW_CHARS) return void 0;
17032
+ const spans = end - start;
17033
+ const cacheKey = options.state === void 0 ? `${lang}\u0000${contextFrom}\u0000${text}` : void 0;
17034
+ const cached = cacheKey === void 0 ? void 0 : tokenizeCache.get(cacheKey);
17035
+ if (cached !== void 0 && cached.length === spans) return {
17036
+ runs: cached,
17037
+ state: void 0
17038
+ };
17039
+ const { tokens, grammarState } = highlighter().codeToTokens(text, {
16366
17040
  lang,
16367
17041
  theme: "css-variables",
16368
17042
  tokenizeTimeLimit: TOKENIZE_TIME_LIMIT_MS,
16369
- tokenizeMaxLineLength: MAX_LINE_LENGTH
17043
+ tokenizeMaxLineLength: MAX_LINE_LENGTH,
17044
+ ...options.state === void 0 ? {} : { grammarState: options.state }
16370
17045
  });
16371
- const last = tokens[tokens.length - 1];
16372
- const runs = (tokens.length > 1 && last !== void 0 && last.length === 0 ? tokens.slice(0, -1) : tokens).map((line) => line.map((token) => ({
17046
+ const runs = tokens.slice(start - contextFrom).map((line) => line.map((token) => ({
16373
17047
  text: token.content,
16374
17048
  style: { color: token.color }
16375
17049
  })));
16376
- let byLang = tokenizeCache.get(code);
16377
- if (byLang === void 0) {
17050
+ if (cacheKey !== void 0) {
16378
17051
  if (tokenizeCache.size >= TOKENIZE_CACHE_MAX) {
16379
17052
  const oldest = tokenizeCache.keys().next().value;
16380
17053
  if (oldest !== void 0) tokenizeCache.delete(oldest);
16381
17054
  }
16382
- byLang = /* @__PURE__ */ new Map();
16383
- tokenizeCache.set(code, byLang);
17055
+ tokenizeCache.set(cacheKey, runs);
16384
17056
  }
16385
- byLang.set(lang, runs);
16386
- return runs;
17057
+ return {
17058
+ runs,
17059
+ state: grammarState
17060
+ };
17061
+ }
17062
+ //#endregion
17063
+ //#region lib/types/client/windowed-highlight.js
17064
+ /**
17065
+ * Windowed syntax highlighting for the diff viewer.
17066
+ *
17067
+ * The code view renders a virtual window of rows, so the viewer only ever needs
17068
+ * the colors of the lines near the viewport. Highlighting a whole file to show
17069
+ * one screenful is almost all waste — measured on a 3818-line file, the two
17070
+ * sides whole-file cost ~945 ms of main-thread time against ~8 ms for one window
17071
+ * — so this hook keeps a per-side store of highlighted lines and fills it:
17072
+ *
17073
+ * 1. **The window first.** Whenever the visible line range changes, the lines
17074
+ * that are missing are highlighted (with a margin either side, debounced, so a
17075
+ * fast scroll computes the window it lands on instead of every window it
17076
+ * passes). A window with no state yet gets `context` lines above it so a
17077
+ * construct that started earlier still colours correctly.
17078
+ * 2. **Already-highlighted lines are kept.** Scrolling back, or nudging the
17079
+ * window by a few rows, finds the lines present and does no work at all.
17080
+ * 3. **An idle backfill** walks the file top-down in chunks when the pane reports
17081
+ * a real viewport, recording the exact grammar state at each chunk boundary.
17082
+ * Windows inside a backfilled region then resume exactly (no context guess),
17083
+ * and a random scroll position is coloured before the user gets there.
17084
+ *
17085
+ * Lines that have never been highlighted are holes in the returned arrays: the
17086
+ * viewer renders them plain, which is the honest state of the work done so far.
17087
+ * @module dsh-diff-approval/client/windowed-highlight
17088
+ */
17089
+ /** Lines either side of the visible window to highlight, so a small scroll reuses
17090
+ * what is already there instead of starting a new pass. */
17091
+ const WINDOW_MARGIN_LINES = 40;
17092
+ /** Ceiling on one pass. A huge viewport (or a pane that reports no viewport at
17093
+ * all, where the virtual window falls back to the whole file) must not turn one
17094
+ * pass into a whole-file tokenize. */
17095
+ const MAX_WINDOW_LINES = 400;
17096
+ /** Lines above a window that only inform the grammar when no state is saved. */
17097
+ const CONTEXT_LINES = 40;
17098
+ /** A window pass waits this long, so scrolling computes where it lands, not every
17099
+ * window it crosses (a crossed window costs ~8 ms, a swept file ~1.3 s). */
17100
+ const DEBOUNCE_MS = 90;
17101
+ /** How much of the file the idle backfill tokenizes per step. */
17102
+ const BACKFILL_CHUNK_LINES = 400;
17103
+ /** Where the idle backfill stops, per side. A few screens ahead is where the
17104
+ * payoff is; past that the window pass colours whatever is looked at anyway, and
17105
+ * a huge file must not churn in the background indefinitely. */
17106
+ const BACKFILL_MAX_LINES = 6e3;
17107
+ /** Idle delay before the backfill's next chunk (also its `requestIdleCallback`
17108
+ * timeout, so a busy page still makes progress). */
17109
+ const BACKFILL_DELAY_MS = 120;
17110
+ function emptySide() {
17111
+ return {
17112
+ runs: [],
17113
+ states: /* @__PURE__ */ new Map(),
17114
+ filled: 0
17115
+ };
17116
+ }
17117
+ function emptyStore(key) {
17118
+ return {
17119
+ key,
17120
+ old: emptySide(),
17121
+ new: emptySide()
17122
+ };
17123
+ }
17124
+ /** The greatest saved state at or before `line`, or undefined when the file's own
17125
+ * top is the only known state. States are only written at chunk boundaries, so
17126
+ * the map stays tiny; the scan is over those, never over the file. */
17127
+ function stateAt(side, line) {
17128
+ let best = -1;
17129
+ let state;
17130
+ for (const [at, value] of side.states) if (at <= line && at > best) {
17131
+ best = at;
17132
+ state = value;
17133
+ }
17134
+ return state;
17135
+ }
17136
+ /**
17137
+ * Whether every line of `[from, to)` already has runs.
17138
+ * @param side - the side's store.
17139
+ * @param from - first line index.
17140
+ * @param to - one past the last line index.
17141
+ * @returns whether the range is fully covered.
17142
+ */
17143
+ function covered(side, from, to) {
17144
+ for (let line = from; line < to; line++) if (side.runs[line] === void 0) return false;
17145
+ return true;
17146
+ }
17147
+ /**
17148
+ * Highlight one side's line range into its store, resuming exactly from any saved
17149
+ * state at or above it (and from context lines when there is none).
17150
+ * @param side - the side's store.
17151
+ * @param lines - the side's lines.
17152
+ * @param lang - the grammar id.
17153
+ * @param from - first line index.
17154
+ * @param to - one past the last line index.
17155
+ * @returns whether anything was written.
17156
+ */
17157
+ function fill(side, lines, lang, from, to) {
17158
+ let start = Math.max(0, Math.min(from, lines.length));
17159
+ let end = Math.max(0, Math.min(to, lines.length));
17160
+ if (end <= start) return false;
17161
+ while (start < end && side.runs[start] !== void 0) start++;
17162
+ while (end > start && side.runs[end - 1] !== void 0) end--;
17163
+ if (end <= start) return false;
17164
+ const state = stateAt(side, start);
17165
+ const result = highlightWindow(lines, lang, start, end, { ...state === void 0 ? { context: CONTEXT_LINES } : { state } });
17166
+ if (result === void 0) return false;
17167
+ result.runs.forEach((runs, index) => {
17168
+ side.runs[start + index] = runs;
17169
+ });
17170
+ side.states.set(end, result.state);
17171
+ return true;
17172
+ }
17173
+ /**
17174
+ * Keep syntax highlighting for the pane's visible window, plus (with a live
17175
+ * viewport) an idle top-down backfill of the rest.
17176
+ * @param input - the file's lines, language, and the visible line ranges.
17177
+ * @returns the runs per side, indexed by line - 1, with holes for unhighlighted lines.
17178
+ */
17179
+ function useWindowedHighlight(input) {
17180
+ const storeRef = (0, react.useRef)(emptyStore(input.key));
17181
+ const [sides, setSides] = (0, react.useState)({
17182
+ oldRuns: [],
17183
+ newRuns: []
17184
+ });
17185
+ const windowPendingRef = (0, react.useRef)(false);
17186
+ const publish = () => {
17187
+ const store = storeRef.current;
17188
+ setSides({
17189
+ oldRuns: [...store.old.runs],
17190
+ newRuns: [...store.new.runs]
17191
+ });
17192
+ };
17193
+ (0, react.useEffect)(() => {
17194
+ if (Object.is(storeRef.current.key, input.key)) return;
17195
+ storeRef.current = emptyStore(input.key);
17196
+ setSides({
17197
+ oldRuns: [],
17198
+ newRuns: []
17199
+ });
17200
+ }, [input.key]);
17201
+ const { oldLines, newLines, lang, oldRange, newRange, live } = input;
17202
+ const oldFrom = oldRange?.from;
17203
+ const oldTo = oldRange?.to;
17204
+ const newFrom = newRange?.from;
17205
+ const newTo = newRange?.to;
17206
+ (0, react.useEffect)(() => {
17207
+ const store = storeRef.current;
17208
+ if (!Object.is(store.key, input.key)) return;
17209
+ const pending = [{
17210
+ side: store.old,
17211
+ lines: oldLines,
17212
+ range: oldRange
17213
+ }, {
17214
+ side: store.new,
17215
+ lines: newLines,
17216
+ range: newRange
17217
+ }].filter(({ side, lines, range }) => {
17218
+ if (range === void 0) return false;
17219
+ return !covered(side, Math.max(0, range.from - 1), Math.min(lines.length, range.to));
17220
+ });
17221
+ if (pending.length === 0) {
17222
+ windowPendingRef.current = false;
17223
+ return;
17224
+ }
17225
+ windowPendingRef.current = true;
17226
+ const first = store.old.runs.length === 0 && store.new.runs.length === 0;
17227
+ const timer = window.setTimeout(() => {
17228
+ windowPendingRef.current = false;
17229
+ if (!Object.is(storeRef.current.key, input.key)) return;
17230
+ let wrote = false;
17231
+ for (const { side, lines, range } of pending) {
17232
+ if (range === void 0) continue;
17233
+ const visibleFrom = Math.max(0, range.from - 1);
17234
+ const visibleTo = Math.min(lines.length, range.to);
17235
+ const from = Math.max(0, visibleFrom - WINDOW_MARGIN_LINES);
17236
+ const to = Math.min(lines.length, visibleTo + WINDOW_MARGIN_LINES, from + MAX_WINDOW_LINES);
17237
+ wrote = fill(side, lines, lang, from, to) || wrote;
17238
+ }
17239
+ if (wrote) publish();
17240
+ }, first ? 0 : DEBOUNCE_MS);
17241
+ return () => {
17242
+ window.clearTimeout(timer);
17243
+ windowPendingRef.current = false;
17244
+ };
17245
+ }, [
17246
+ input.key,
17247
+ oldLines,
17248
+ newLines,
17249
+ lang,
17250
+ oldFrom,
17251
+ oldTo,
17252
+ newFrom,
17253
+ newTo,
17254
+ oldRange,
17255
+ newRange
17256
+ ]);
17257
+ (0, react.useEffect)(() => {
17258
+ if (!live) return;
17259
+ let cancelled = false;
17260
+ let timer;
17261
+ let idleHandle;
17262
+ const requestIdle = window.requestIdleCallback;
17263
+ const step = () => {
17264
+ if (cancelled || windowPendingRef.current) {
17265
+ schedule();
17266
+ return;
17267
+ }
17268
+ const store = storeRef.current;
17269
+ if (!Object.is(store.key, input.key)) return;
17270
+ const next = [{
17271
+ side: store.old,
17272
+ lines: oldLines
17273
+ }, {
17274
+ side: store.new,
17275
+ lines: newLines
17276
+ }].sort((a, b) => a.side.filled / (a.lines.length || 1) - b.side.filled / (b.lines.length || 1)).find(({ side, lines }) => side.filled < Math.min(lines.length, BACKFILL_MAX_LINES));
17277
+ if (next === void 0) return;
17278
+ const to = Math.min(next.lines.length, next.side.filled + BACKFILL_CHUNK_LINES, BACKFILL_MAX_LINES);
17279
+ const state = stateAt(next.side, next.side.filled);
17280
+ const result = highlightWindow(next.lines, lang, next.side.filled, to, { state });
17281
+ if (result !== void 0) {
17282
+ result.runs.forEach((runs, index) => {
17283
+ next.side.runs[next.side.filled + index] = runs;
17284
+ });
17285
+ next.side.states.set(to, result.state);
17286
+ next.side.filled = to;
17287
+ publish();
17288
+ } else next.side.filled = to;
17289
+ schedule();
17290
+ };
17291
+ const schedule = () => {
17292
+ if (cancelled) return;
17293
+ if (requestIdle !== void 0) idleHandle = requestIdle(step, { timeout: BACKFILL_DELAY_MS });
17294
+ else timer = window.setTimeout(step, BACKFILL_DELAY_MS);
17295
+ };
17296
+ schedule();
17297
+ return () => {
17298
+ cancelled = true;
17299
+ if (timer !== void 0) window.clearTimeout(timer);
17300
+ const cancelIdle = window.cancelIdleCallback;
17301
+ if (idleHandle !== void 0 && cancelIdle !== void 0) cancelIdle(idleHandle);
17302
+ };
17303
+ }, [
17304
+ input.key,
17305
+ live,
17306
+ oldLines,
17307
+ newLines,
17308
+ lang
17309
+ ]);
17310
+ return sides;
16387
17311
  }
16388
17312
  //#endregion
16389
17313
  //#region lib/types/client/lang.js
@@ -16442,10 +17366,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16442
17366
  * @returns the language id, or `undefined` when unknown.
16443
17367
  */
16444
17368
  function langFromPath(path) {
16445
- const match = /\.([^./\\]+)$/.exec(path);
16446
- if (match === null) return void 0;
16447
- const extension = match[1];
16448
- return extension === void 0 ? void 0 : EXTENSION_LANGS[extension.toLowerCase()];
17369
+ const suffix = suffixOfPath(path);
17370
+ return suffix === void 0 ? void 0 : EXTENSION_LANGS[suffix];
17371
+ }
17372
+ /**
17373
+ * The lowercase extension of a file path, without the dot — the key a manual
17374
+ * language choice is remembered under. A name with no dot has no suffix, so a
17375
+ * per-suffix preference never leaks onto every extension-less file at once.
17376
+ * @param path - the file path, any separator style.
17377
+ * @returns the suffix, or `undefined` when the name carries none.
17378
+ */
17379
+ function suffixOfPath(path) {
17380
+ const suffix = /\.([^./\\]+)$/.exec(path)?.[1];
17381
+ return suffix === void 0 || suffix === "" ? void 0 : suffix.toLowerCase();
16449
17382
  }
16450
17383
  //#endregion
16451
17384
  //#region lib/types/client/reference.js
@@ -16697,6 +17630,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16697
17630
  /** Client preferences for the review panel, persisted in localStorage. */
16698
17631
  const PASTE_ON_COPY_KEY = "diff-approval:paste-on-copy";
16699
17632
  const IMPORT_UNTRACKED_KEY = "diff-approval:import-untracked";
17633
+ const CONFIRM_FILE_REMOVE_KEY = "diff-approval:confirm-file-remove";
17634
+ const SEARCH_CASE_KEY = "diff-approval:search-case";
17635
+ const SEARCH_WORD_KEY = "diff-approval:search-word";
16700
17636
  const TAB_WIDTH_KEY = "diff-approval:tab-size";
16701
17637
  const SPLIT_MODE_KEY = "diff-approval:split-mode";
16702
17638
  const MD_PREVIEW_KEY = "diff-approval:md-preview";
@@ -16706,6 +17642,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16706
17642
  const DIFF_LINE_HEIGHT_KEY = "diff-approval:diff-line-height";
16707
17643
  const DIFF_ADD_COLOR_KEY = "diff-approval:diff-add-color";
16708
17644
  const DIFF_DEL_COLOR_KEY = "diff-approval:diff-del-color";
17645
+ const LANG_BY_SUFFIX_KEY = "diff-approval:lang-by-suffix";
16709
17646
  const WRAP_PREFIX = "diff-approval:wrap:";
16710
17647
  const MD_MAX_WIDTH_MAX = 1600;
16711
17648
  /**
@@ -16735,374 +17672,289 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16735
17672
  localStorage.setItem(IMPORT_UNTRACKED_KEY, value ? "1" : "0");
16736
17673
  }
16737
17674
  /**
16738
- * Whether lines wrap (auto-wrap) in the diff for one highlight language.
16739
- * Defaults to off; only an explicit `'1'` enables it. Stored per language, so
16740
- * a language's preference never leaks into another's.
16741
- * @param lang - the highlight language (or `''` for the auto/default bucket).
16742
- * @returns whether lines wrap.
16743
- */
16744
- function wrapEnabled(lang) {
16745
- return localStorage.getItem(`${WRAP_PREFIX}${lang}`) === "1";
16746
- }
16747
- /** Persist the per-language auto-wrap preference. */
16748
- function setWrapEnabled(lang, value) {
16749
- localStorage.setItem(`${WRAP_PREFIX}${lang}`, value ? "1" : "0");
16750
- }
16751
- /**
16752
- * The diff's tab width in spaces. Defaults to 4; the settings UI offers 2/4/8,
16753
- * but any positive integer is accepted. This drives both the rendered
16754
- * `tab-size` and the wrapped-line tab measurement, so they always agree.
16755
- * @returns the number of spaces one tab advances.
16756
- */
16757
- function tabWidth() {
16758
- const value = Number.parseInt(localStorage.getItem(TAB_WIDTH_KEY) ?? "", 10);
16759
- return Number.isInteger(value) && value > 0 ? value : 4;
16760
- }
16761
- /** Persist the diff's tab width (in spaces). */
16762
- function setTabWidth(value) {
16763
- localStorage.setItem(TAB_WIDTH_KEY, String(value));
16764
- }
16765
- /**
16766
- * The diff code font size as a percentage of the current (theme) size. Defaults
16767
- * to 100 (the current look); the settings UI steps it by ±10.
16768
- * @returns the font-size scale, as a percentage (e.g. 100, 110, 90).
16769
- */
16770
- function diffFontScale() {
16771
- const raw = Number.parseInt(localStorage.getItem(DIFF_FONT_SCALE_KEY) ?? "", 10);
16772
- if (!Number.isFinite(raw)) return 100;
16773
- return Math.max(50, Math.min(200, raw));
16774
- }
16775
- /** Persist the diff's code font-size scale (a percentage). */
16776
- function setDiffFontScale(value) {
16777
- localStorage.setItem(DIFF_FONT_SCALE_KEY, String(Math.max(50, Math.min(200, value))));
16778
- }
16779
- /**
16780
- * The diff code line height in px (the fixed row height the virtual window and
16781
- * jump math are keyed to). Defaults to 22 (the pre-customization value).
16782
- * @returns the line height in px.
16783
- */
16784
- function diffLineHeight() {
16785
- const raw = Number.parseInt(localStorage.getItem(DIFF_LINE_HEIGHT_KEY) ?? "", 10);
16786
- if (!Number.isFinite(raw)) return 22;
16787
- return Math.max(10, Math.min(36, raw));
16788
- }
16789
- /** Persist the diff's code line height (px). */
16790
- function setDiffLineHeight(value) {
16791
- localStorage.setItem(DIFF_LINE_HEIGHT_KEY, String(Math.max(10, Math.min(36, value))));
16792
- }
16793
- /** Read a theme CSS custom property, with a fallback when it is unavailable. */
16794
- function themeColor(name, fallback) {
16795
- if (typeof document !== "undefined") {
16796
- const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
16797
- if (/^#[0-9a-f]{6}$/i.test(v)) return v;
16798
- }
16799
- return fallback;
16800
- }
16801
- /** The added-line base color the user chose, or `undefined` (theme color). */
16802
- function diffAddColor() {
16803
- const v = localStorage.getItem(DIFF_ADD_COLOR_KEY);
16804
- return v !== null && v.length > 0 ? v : void 0;
16805
- }
16806
- /** Persist the added-line base color (a hex like `#22c55e`). */
16807
- function setDiffAddColor(value) {
16808
- localStorage.setItem(DIFF_ADD_COLOR_KEY, value);
16809
- }
16810
- /** The removed-line base color the user chose, or `undefined` (theme color). */
16811
- function diffDelColor() {
16812
- const v = localStorage.getItem(DIFF_DEL_COLOR_KEY);
16813
- return v !== null && v.length > 0 ? v : void 0;
16814
- }
16815
- /** Persist the removed-line base color (a hex like `#ef4444`). */
16816
- function setDiffDelColor(value) {
16817
- localStorage.setItem(DIFF_DEL_COLOR_KEY, value);
16818
- }
16819
- /** The theme's added-line base color, for the settings swatch default. */
16820
- function currentDiffAddColor() {
16821
- return themeColor("--dsw-alias-state-success-primary", "#22c55e");
16822
- }
16823
- /** The theme's removed-line base color, for the settings swatch default. */
16824
- function currentDiffDelColor() {
16825
- return themeColor("--dsw-alias-state-error-primary", "#ef4444");
16826
- }
16827
- /**
16828
- * Whether the whole-file diff view uses the two-column (side-by-side) layout.
16829
- * Default off (single column): the unified diff. Only an explicit `'1'` enables
16830
- * split mode.
16831
- * @returns whether the split (two-column) diff view is used.
17675
+ * Whether a whole-file keep/revert asks before dropping the resolved file from
17676
+ * the list. Defaults to on; only an explicit `'0'` disables it, which then
17677
+ * removes the file straight away.
17678
+ * @returns whether the remove prompt is enabled.
16832
17679
  */
16833
- function splitMode() {
16834
- return localStorage.getItem(SPLIT_MODE_KEY) === "1";
17680
+ function confirmFileRemoveEnabled() {
17681
+ return localStorage.getItem(CONFIRM_FILE_REMOVE_KEY) !== "0";
16835
17682
  }
16836
- /** Persist the split-view preference. */
16837
- function setSplitMode(value) {
16838
- localStorage.setItem(SPLIT_MODE_KEY, value ? "1" : "0");
17683
+ /** Persist the whole-file remove-prompt preference. */
17684
+ function setConfirmFileRemoveEnabled(value) {
17685
+ localStorage.setItem(CONFIRM_FILE_REMOVE_KEY, value ? "1" : "0");
16839
17686
  }
16840
17687
  /**
16841
- * Whether the rendered Markdown preview is shown by default (for Markdown files).
16842
- * Default off: the source diff is shown unless the panel toggle is used. Only an
16843
- * explicit `'1'` enables the preview default.
16844
- * @returns whether the Markdown preview default is enabled.
17688
+ * Whether the in-file search matches letter case exactly. Defaults to off, so a
17689
+ * plain query keeps matching either case; only an explicit `'1'` turns it on.
17690
+ * @returns whether the search is case-sensitive.
16845
17691
  */
16846
- function mdPreviewEnabled() {
16847
- return localStorage.getItem(MD_PREVIEW_KEY) === "1";
17692
+ function searchCaseSensitive() {
17693
+ return localStorage.getItem(SEARCH_CASE_KEY) === "1";
16848
17694
  }
16849
- /** Persist the Markdown-preview default preference. */
16850
- function setMdPreviewEnabled(value) {
16851
- localStorage.setItem(MD_PREVIEW_KEY, value ? "1" : "0");
17695
+ /** Persist the search case-sensitivity preference. */
17696
+ function setSearchCaseSensitive(value) {
17697
+ localStorage.setItem(SEARCH_CASE_KEY, value ? "1" : "0");
16852
17698
  }
16853
17699
  /**
16854
- * The Markdown-preview content max width, in pixels (single column). Defaults to
16855
- * 800; the double-column view totals 2x this. An out-of-range or non-integer
16856
- * value falls back to the default.
16857
- * @returns the max width in pixels.
17700
+ * Whether the in-file search only matches whole words. Defaults to off; only an
17701
+ * explicit `'1'` enables it.
17702
+ * @returns whether the search matches whole words only.
16858
17703
  */
16859
- function mdMaxWidth() {
16860
- const raw = Number.parseInt(localStorage.getItem(MD_MAX_WIDTH_KEY) ?? "", 10);
16861
- if (!Number.isInteger(raw)) return 800;
16862
- return Math.max(480, Math.min(MD_MAX_WIDTH_MAX, raw));
17704
+ function searchWholeWord() {
17705
+ return localStorage.getItem(SEARCH_WORD_KEY) === "1";
16863
17706
  }
16864
- /** Persist the Markdown-preview content max width. */
16865
- function setMdMaxWidth(value) {
16866
- localStorage.setItem(MD_MAX_WIDTH_KEY, String(Math.max(480, Math.min(MD_MAX_WIDTH_MAX, value))));
17707
+ /** Persist the search whole-word preference. */
17708
+ function setSearchWholeWord(value) {
17709
+ localStorage.setItem(SEARCH_WORD_KEY, value ? "1" : "0");
16867
17710
  }
16868
17711
  /**
16869
- * How many rows of lead the diff block jump leaves above the jumped-to block,
16870
- * and how far the anchored navigation scans. Defaults to 2; an out-of-range or
16871
- * non-integer value falls back to the default.
16872
- * @returns the lead row count.
16873
- */
16874
- function navLeadRows() {
16875
- const raw = Number.parseInt(localStorage.getItem(NAV_LEAD_KEY) ?? "", 10);
16876
- if (!Number.isInteger(raw)) return 2;
16877
- return Math.max(0, Math.min(10, raw));
17712
+ * Whether lines wrap (auto-wrap) in the diff for one highlight language.
17713
+ * Defaults to off; only an explicit `'1'` enables it. Stored per language, so
17714
+ * a language's preference never leaks into another's.
17715
+ * @param lang - the highlight language (or `''` for the auto/default bucket).
17716
+ * @returns whether lines wrap.
17717
+ */
17718
+ function wrapEnabled(lang) {
17719
+ return localStorage.getItem(`${WRAP_PREFIX}${lang}`) === "1";
16878
17720
  }
16879
- /** Persist the block-jump lead row count. */
16880
- function setNavLeadRows(value) {
16881
- localStorage.setItem(NAV_LEAD_KEY, String(Math.max(0, Math.min(10, value))));
17721
+ /** Persist the per-language auto-wrap preference. */
17722
+ function setWrapEnabled(lang, value) {
17723
+ localStorage.setItem(`${WRAP_PREFIX}${lang}`, value ? "1" : "0");
16882
17724
  }
16883
- const QUICK_SUMMON_KEY = "diff-approval:quick-summon-key";
16884
17725
  /**
16885
- * The quick-summon chord. Stored as `Modifier+...+Key`; falls back to
16886
- * {@link DEFAULT_QUICK_SUMMON}.
16887
- * @returns the chord string.
17726
+ * The diff's tab width in spaces. Defaults to 4; the settings UI offers 2/4/8,
17727
+ * but any positive integer is accepted. This drives both the rendered
17728
+ * `tab-size` and the wrapped-line tab measurement, so they always agree.
17729
+ * @returns the number of spaces one tab advances.
16888
17730
  */
16889
- function quickSummonKey() {
16890
- return localStorage.getItem(QUICK_SUMMON_KEY) ?? "Ctrl+D";
17731
+ function tabWidth() {
17732
+ const value = Number.parseInt(localStorage.getItem(TAB_WIDTH_KEY) ?? "", 10);
17733
+ return Number.isInteger(value) && value > 0 ? value : 4;
16891
17734
  }
16892
- /** Persist the quick-summon chord. */
16893
- function setQuickSummonKey(value) {
16894
- localStorage.setItem(QUICK_SUMMON_KEY, value);
17735
+ /** Persist the diff's tab width (in spaces). */
17736
+ function setTabWidth(value) {
17737
+ localStorage.setItem(TAB_WIDTH_KEY, String(value));
16895
17738
  }
16896
- const KEY_PREFIX = "diff-approval:key:";
16897
- /** Default chord for each configurable action (every supported key except the
16898
- * panel's own ESC-to-close, which is intentionally not remapped). */
16899
- const DEFAULT_KEYBINDINGS = {
16900
- jumpUp: "Ctrl+ArrowUp",
16901
- jumpDown: "Ctrl+ArrowDown",
16902
- copyRef: "Ctrl+L",
16903
- openSearch: "Ctrl+F",
16904
- searchNext: "F3",
16905
- searchPrev: "Shift+F3",
16906
- undo: "Ctrl+Z",
16907
- redo: "Ctrl+Shift+Z",
16908
- cycleNext: "Ctrl+Tab",
16909
- cyclePrev: "Ctrl+Shift+Tab"
16910
- };
16911
- /** The currently configured chord for one action; falls back to its default. */
16912
- function keybindingOf(action) {
16913
- return localStorage.getItem(`${KEY_PREFIX}${action}`) ?? DEFAULT_KEYBINDINGS[action] ?? "";
17739
+ /**
17740
+ * The diff code font size as a percentage of the current (theme) size. Defaults
17741
+ * to 100 (the current look); the settings UI steps it by ±10.
17742
+ * @returns the font-size scale, as a percentage (e.g. 100, 110, 90).
17743
+ */
17744
+ function diffFontScale() {
17745
+ const raw = Number.parseInt(localStorage.getItem(DIFF_FONT_SCALE_KEY) ?? "", 10);
17746
+ if (!Number.isFinite(raw)) return 100;
17747
+ return Math.max(50, Math.min(200, raw));
16914
17748
  }
16915
- /** Persist one action's chord. */
16916
- function setKeybinding(action, chord) {
16917
- localStorage.setItem(`${KEY_PREFIX}${action}`, chord);
17749
+ /** Persist the diff's code font-size scale (a percentage). */
17750
+ function setDiffFontScale(value) {
17751
+ localStorage.setItem(DIFF_FONT_SCALE_KEY, String(Math.max(50, Math.min(200, value))));
16918
17752
  }
16919
17753
  /**
16920
- * Whether a keyboard event matches a chord string like `Ctrl+D`. Modifier
16921
- * names are matched case-insensitively (`Ctrl`/`Control`, `Alt`/`Option`,
16922
- * `Shift`, `Meta`/`Cmd`/`Command`/`Win`); the final part is the key. Exact
16923
- * modifier set is required (extra modifiers do not match).
16924
- * @param event - the keydown event.
16925
- * @param shortcut - the chord string.
16926
- * @returns whether the event matches.
17754
+ * The diff code line height in px (the fixed row height the virtual window and
17755
+ * jump math are keyed to). Defaults to 22 (the pre-customization value).
17756
+ * @returns the line height in px.
16927
17757
  */
16928
- function matchesShortcut(event, shortcut) {
16929
- const parts = shortcut.split("+").map((part) => part.trim().toLowerCase());
16930
- const key = parts.pop();
16931
- if (key === void 0 || key === "") return false;
16932
- const mods = new Set(parts);
16933
- const ctrl = mods.has("ctrl") || mods.has("control");
16934
- const alt = mods.has("alt") || mods.has("option");
16935
- const shift = mods.has("shift");
16936
- const meta = mods.has("meta") || mods.has("cmd") || mods.has("command") || mods.has("win");
16937
- return event.key.toLowerCase() === key && event.ctrlKey === ctrl && event.altKey === alt && event.shiftKey === shift && event.metaKey === meta;
17758
+ function diffLineHeight() {
17759
+ const raw = Number.parseInt(localStorage.getItem(DIFF_LINE_HEIGHT_KEY) ?? "", 10);
17760
+ if (!Number.isFinite(raw)) return 22;
17761
+ return Math.max(10, Math.min(36, raw));
16938
17762
  }
16939
- //#endregion
16940
- //#region \0dsh-css:/home/runner/work/dsh-diff-approval/dsh-diff-approval/src/client/PendingPanel.module.css.mjs
16941
- 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_settingsGroup{border-bottom:1px solid var(--dsw-alias-border-l2);margin-top:8px}.F1KBNa_settingsGroupHeader{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:8px;padding:12px 0;display:flex}.F1KBNa_settingsGroupText{text-align:left;flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.F1KBNa_settingsGroupTitle{font-size:14px;font-weight:400;line-height:22px}.F1KBNa_settingsGroupDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.F1KBNa_settingsGroupChevron{margin-left:auto;transition:transform .15s}.F1KBNa_settingsGroup[data-open] .F1KBNa_settingsGroupChevron{transform:rotate(180deg)}.F1KBNa_settingsGroup[data-open] .F1KBNa_settingsGroupHeader{border-bottom:1px solid var(--dsw-alias-border-l2)}.F1KBNa_settingsGroupBody{flex-direction:column;display:flex}.F1KBNa_settingsGroupBody .F1KBNa_settingsRow{padding-left:16px}.F1KBNa_settingsGroupBody .F1KBNa_settingsRow:last-child,.F1KBNa_settingsPage>.F1KBNa_settingsRow:last-child{border-bottom:none}.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_colorPicker{position:relative}.F1KBNa_colorPickerTrigger{background:var(--dsw-alias-bg-module-platform);width:120px;height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:8px;padding:0 8px;font-size:12px;line-height:18px;display:inline-flex}.F1KBNa_colorPickerTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_colorSwatch{border:1px solid var(--dsw-alias-border-l2);border-radius:50%;flex:none;width:24px;height:24px}.F1KBNa_colorValue{text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums;flex:1;min-width:0;overflow:hidden}.F1KBNa_colorPickerChevron{flex:none}.F1KBNa_colorBackdrop{z-index:40;position:fixed;inset:0}.F1KBNa_colorPickerPopover{z-index:41;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:10px;padding:10px;position:absolute;top:calc(100% + 6px);right:0}.F1KBNa_colorPickerPanel{flex-direction:column;gap:8px;display:flex}.F1KBNa_colorPickerInputRow{align-items:center;gap:8px;display:flex}.F1KBNa_colorPickerPreview{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;flex:none;width:22px;height:22px}.F1KBNa_colorPickerInput{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-module-platform);min-width:0;height:30px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:0 8px;font-size:12px;line-height:28px}.F1KBNa_colorPickerInput:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-1px}.F1KBNa_colorSv{border:1px solid var(--dsw-alias-border-l1);cursor:crosshair;touch-action:none;border-radius:8px;height:140px;position:relative}.F1KBNa_colorSvCursor{pointer-events:none;border:2px solid #fff;border-radius:50%;width:14px;height:14px;position:absolute;transform:translate(-50%,-50%);box-shadow:0 0 0 1px #00000080}.F1KBNa_colorHue{border:1px solid var(--dsw-alias-border-l1);cursor:pointer;touch-action:none;border-radius:7px;height:14px;position:relative}.F1KBNa_colorHueCursor{pointer-events:none;border:2px solid #fff;border-radius:50%;width:16px;height:16px;position:absolute;top:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 1px #00000080}.F1KBNa_diffPreview{z-index:2;border:1px solid var(--dsw-alias-border-l1);font:var(--dsw-font-markdown-code-block);background:var(--dsw-alias-markdown-code-block);border-radius:8px;margin-bottom:8px;padding:8px;position:sticky;top:0;overflow:hidden}.F1KBNa_diffPreviewTitle{color:var(--dsw-alias-label-tertiary);margin-bottom:6px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_diffPreviewScroll{height:132px;overflow-y:auto}.F1KBNa_mdPreviewWrap{flex:1;min-width:0;min-height:0;display:flex;position:relative}.F1KBNa_mdPreviewBody{min-width:0;min-height:0;color:var(--dsw-alias-label-primary);flex:1;padding:12px 16px;position:relative;overflow:auto}.F1KBNa_mdPreviewContent{width:100%;min-width:0;margin:0 auto}.F1KBNa_mdPreviewSingle{display:block}.F1KBNa_mdPreviewDouble{display:flex}.F1KBNa_mdPreviewBody .mdBlock{border-radius:4px;margin-bottom:12px;padding:2px 6px}.F1KBNa_mdPreviewBody .mdAdd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_mdPreviewBody .mdDel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_mdPreviewBody .mdWordDel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 34%, transparent);border-radius:2px;padding:0 1px}.F1KBNa_mdPreviewBody .mdWordAdd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 34%, transparent);border-radius:2px;padding:0 1px}.F1KBNa_mdPreviewBody .mdDouble{flex-direction:column;width:100%;min-width:0;display:flex}.F1KBNa_mdPreviewBody .mdDoubleRow{align-items:flex-start;width:100%;min-width:0;display:flex}.F1KBNa_mdPreviewBody .mdDoubleCol{flex:1;min-width:0}.F1KBNa_mdPreviewBody .mdDoubleRule{background:var(--dsw-alias-border-l2);flex:none;align-self:stretch;width:1px;margin:0 12px}.F1KBNa_mdPreviewFallback{font-family:var(--dsw-font-markdown-code-block);color:var(--dsw-alias-label-tertiary);padding:12px 16px;font-size:12px;line-height:18px}.F1KBNa_mdPreviewBody{word-break:break-word;font-size:13px;line-height:20px}.F1KBNa_mdPreviewBody>:first-child{margin-top:0}.F1KBNa_mdPreviewBody>:last-child{margin-bottom:0}.F1KBNa_mdPreviewBody h1,.F1KBNa_mdPreviewBody h2,.F1KBNa_mdPreviewBody h3,.F1KBNa_mdPreviewBody h4,.F1KBNa_mdPreviewBody h5,.F1KBNa_mdPreviewBody h6{margin:16px 0 8px;font-weight:600;line-height:1.3}.F1KBNa_mdPreviewBody h1{font-size:22px}.F1KBNa_mdPreviewBody h2{font-size:19px}.F1KBNa_mdPreviewBody h3{font-size:16px}.F1KBNa_mdPreviewBody h4{font-size:15px}.F1KBNa_mdPreviewBody h5{font-size:14px}.F1KBNa_mdPreviewBody h6{font-size:13px}.F1KBNa_mdPreviewBody p{margin:8px 0}.F1KBNa_mdPreviewBody ul,.F1KBNa_mdPreviewBody ol{margin:8px 0;padding-left:22px}.F1KBNa_mdPreviewBody li{margin:2px 0}.F1KBNa_mdPreviewBody a{color:var(--dsw-alias-state-business-primary);text-decoration:none}.F1KBNa_mdPreviewBody a:hover{text-decoration:underline}.F1KBNa_mdPreviewBody code{font-family:var(--dsw-font-markdown-code-block);background:#9e9e9e40;border-radius:4px;padding:1px 5px;font-size:12.5px}.F1KBNa_mdPreviewBody pre{border:1px solid var(--dsw-alias-border-l1);font-family:var(--dsw-font-markdown-code-block);background:#9e9e9e40;border-radius:8px;margin:8px 0;padding:10px 12px;font-size:12.5px;line-height:18px;overflow:auto}.F1KBNa_mdPreviewBody pre code{background:0 0;padding:0}.F1KBNa_mdPreviewBody blockquote{border-left:3px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);margin:8px 0;padding:0 12px}.F1KBNa_mdPreviewBody hr{background:var(--dsw-alias-border-l1);border:none;height:1px;margin:16px 0}.F1KBNa_mdPreviewBody table{border-collapse:collapse;margin:8px 0}.F1KBNa_mdPreviewBody th,.F1KBNa_mdPreviewBody td{border:1px solid var(--dsw-alias-border-l1);padding:4px 10px}.F1KBNa_mdPreviewBody img{max-width:100%}.F1KBNa_mdPreviewBody .mdImageFallback{border:1px dashed var(--dsw-alias-border-l2);background:color-mix(in srgb, var(--dsw-alias-markdown-code-block) 45%, transparent);width:100%;min-height:120px;color:var(--dsw-alias-label-secondary);font-family:var(--dsw-font-markdown-code-block);text-align:center;white-space:pre-wrap;word-break:break-all;border-radius:4px;justify-content:center;align-items:center;margin:0 auto;padding:8px 12px;font-size:12px;line-height:16px;display:flex}.F1KBNa_toggle{border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:18px;flex:none;width:60px;height:36px;padding:0;position:relative}.F1KBNa_toggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_toggle:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.F1KBNa_toggleOn{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary)}.F1KBNa_toggleOn:hover{background:var(--dsw-alias-state-business-primary)}.F1KBNa_toggleThumb{background:var(--dsw-alias-label-primary);border-radius:50%;width:28px;height:28px;transition:left .12s;position:absolute;top:3px;left:3px}.F1KBNa_toggleOn .F1KBNa_toggleThumb{background:#fff;left:27px}.F1KBNa_stepper{align-items:center;gap:6px;display:inline-flex}.F1KBNa_stepperButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);width:32px;height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;padding:0;position:relative}.F1KBNa_stepperButton:before,.F1KBNa_stepperButton:after{content:\"\";background:currentColor;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.F1KBNa_stepperButton:before{width:10px;height:1px}.F1KBNa_stepperButton:after{width:1px;height:10px;display:none}.F1KBNa_stepperButtonUp:after{display:block}.F1KBNa_stepperButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_stepperButton:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_stepperValue{text-align:center;min-width:2ch;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:14px;line-height:22px}.F1KBNa_states{flex-direction:column;flex:1;min-height:0;padding:4px 12px 12px;display:flex;overflow-y:auto}.F1KBNa_split{flex:1;align-items:stretch;min-height:0;display:flex;position:relative}.F1KBNa_fileListFloat{z-index:40;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:12px;flex-direction:column;padding:6px 8px 10px;display:flex;position:absolute;overflow:hidden}.F1KBNa_fileList{box-sizing:border-box;border-right:1px solid var(--dsw-alias-border-l2);flex-direction:column;flex:none;width:240px;min-height:0;padding:4px 8px 12px;display:flex}.F1KBNa_listScroll{flex:1;min-height:0;overflow-y:auto}.F1KBNa_bulkActions{flex:none;gap:6px;padding-top:8px;display:flex}.F1KBNa_bulkActions .F1KBNa_action{text-align:center;flex:1}.F1KBNa_resizeHandle{cursor:col-resize;background:0 0;flex:none;width:5px;margin:0 -2px}.F1KBNa_resizeHandle:hover{background:var(--dsw-alias-border-l2)}.F1KBNa_detail{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.F1KBNa_detailEmpty{color:var(--dsw-alias-label-tertiary);text-align:center;flex:1;justify-content:center;align-items:center;margin:0;padding:24px;font-size:12px;line-height:18px;display:flex}.F1KBNa_confirmBackdrop{z-index:5;background:color-mix(in srgb, var(--dsw-alias-bg-base) 55%, transparent);justify-content:center;align-items:center;padding:24px;display:flex;position:absolute;inset:0}.F1KBNa_confirmCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:340px;box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:12px;padding:16px;display:flex}.F1KBNa_confirmText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0;line-height:1.5}.F1KBNa_confirmActions{justify-content:flex-end;gap:8px;display:flex}.F1KBNa_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}.F1KBNa_note,.F1KBNa_readError,.F1KBNa_hint{color:var(--dsw-alias-label-tertiary);margin:4px 0;font-size:12px;line-height:18px}.F1KBNa_noteCentered{text-align:center;margin:auto}.F1KBNa_emptyState{flex-direction:column;align-items:center;gap:10px;margin:auto;display:flex}.F1KBNa_importButton{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:5px 14px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_importButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_importButton:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_importNote{color:var(--dsw-alias-label-tertiary);text-align:center;margin:0;font-size:12px;line-height:18px}.F1KBNa_readError{color:var(--dsw-alias-state-error-primary)}.F1KBNa_group{color:var(--dsw-alias-label-tertiary);margin:8px 0 4px;font-size:12px;font-weight:500;line-height:16px}.F1KBNa_rows{margin:0;padding:0;list-style:none}.F1KBNa_row{border:1px solid #0000;border-radius:10px;margin:2px 0}.F1KBNa_rowHead{width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:baseline;gap:8px;padding:6px 8px;display:flex}.F1KBNa_rowHead:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_rowHead[data-selected]{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_rowPath{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:var(--dsw-font-markdown-code-block);overflow:hidden}.F1KBNa_kindTag{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_kindHint{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:16px}.F1KBNa_divergedHint,.F1KBNa_missingHint{border-bottom:1px solid var(--dsw-alias-border-l2);margin:0;padding:6px 8px;font-size:12px;line-height:18px}.F1KBNa_missing{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary));white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_rowFailed{border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.F1KBNa_actionError{border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;margin:0;padding:6px 8px;font-size:12px;line-height:18px}.F1KBNa_missingHint{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary))}.F1KBNa_rowMeta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;gap:6px;margin-left:auto;font-size:12px;line-height:16px;display:inline-flex}.F1KBNa_addCount{color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 60%, var(--dsw-alias-label-primary))}.F1KBNa_delCount{color:color-mix(in srgb, var(--dsh-diff-del-color,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{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block);-webkit-user-select:text;user-select:text;flex:1;font-size:12px;line-height:18px;overflow:auto 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_divider{background:var(--dsw-alias-border-l1);align-self:stretch;width:1px;margin:2px 0}.F1KBNa_action{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_action:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.F1KBNa_actionPrimary{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_actionPrimary:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}.F1KBNa_actionPrimary:disabled{background:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:.55}.F1KBNa_actionQuietDisabled:disabled{cursor:pointer;color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1);background:0 0}.F1KBNa_actionPrimary.F1KBNa_actionQuietDisabled:disabled{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50);opacity:1}.F1KBNa_iconAction{justify-content:center;align-items:center;min-height:25px;padding:4px 6px;display:inline-flex}.F1KBNa_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expand{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_expand:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_expandExpanded svg{transform:rotate(180deg)}.F1KBNa_diffBodyWrap{flex:1;min-height:0;display:flex;position:relative}.F1KBNa_diffBody{min-width:0;font:var(--dsw-font-markdown-code-block);-webkit-text-size-adjust:100%;text-size-adjust:100%;cursor:text;outline:none;flex:1;padding:0;position:relative;overflow:auto}.F1KBNa_diffBody::-webkit-scrollbar,.F1KBNa_diffBody::-webkit-scrollbar-track,.F1KBNa_diffBody::-webkit-scrollbar-thumb{cursor:default}.F1KBNa_blockActions{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);z-index:2;border-radius:10px;align-items:center;gap:9px;padding:5px;display:flex;position:absolute;right:8px}.F1KBNa_blockPosition{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;margin-top:1px;margin-left:7px;font-size:12px;line-height:16px}.F1KBNa_searchBar{z-index:3;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-markdown-code-block);box-shadow:var(--dsw-shadow-lv1);border-radius:10px;align-items:center;gap:4px;padding:4px;display:flex;position:absolute;top:8px;right:8px}.F1KBNa_searchInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);width:150px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:3px 8px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_searchInput:focus{border-color:var(--dsw-alias-state-business-primary)}.F1KBNa_searchCount{text-align:center;min-width:34px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px;line-height:16px}.F1KBNa_searchMatch{background-color:#facc1542;border-radius:2px}.F1KBNa_searchMatchCurrent{background-color:#facc1599;border-radius:2px}.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_diffFlashFade;position:absolute;left:0;right:0}.F1KBNa_blockFlashShake{animation:.7s ease-out forwards F1KBNa_diffFlashShake}@keyframes F1KBNa_diffFlashFade{0%{opacity:1}to{opacity:0}}@keyframes F1KBNa_diffFlashShake{0%{opacity:1;transform:translateY(0)}10%{transform:translateY(1px)}20%{transform:translateY(-1px)}30%{transform:translateY(1px)}40%{transform:translateY(-1px)}50%{transform:translateY(1px)}60%{transform:translateY(-1px)}70%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(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%;font-size:calc(1em * var(--dsh-diff-font-scale,1));display:table}.F1KBNa_line{height:var(--dsh-diff-line-height,22px);line-height:var(--dsh-diff-line-height,22px);-webkit-user-select:none;user-select:none;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;-webkit-user-select:none;user-select:none;cursor:default;padding-right:10px;display:table-cell}.F1KBNa_code{white-space:pre;-webkit-user-select:text;user-select:text;display:table-cell}.F1KBNa_context{color:var(--dsw-alias-label-primary)}.F1KBNa_del{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_add{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_statusBar{box-sizing:border-box;border-top:1px solid var(--dsw-alias-border-l2);height:34px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-markdown-code-block);font-variant-numeric:tabular-nums;flex:none;align-items:center;gap:8px;padding:0 8px;font-size:12px;line-height:18px;display:flex}.F1KBNa_statusAction{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);white-space:nowrap;cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.F1KBNa_statusAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langSelect{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;flex:none;align-items:center;gap:4px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px;display:inline-flex}.F1KBNa_langSelect:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_langLabel{text-overflow:ellipsis;white-space:nowrap;max-width:140px;overflow:hidden}.F1KBNa_notice{z-index:60;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);max-width:360px;box-shadow:var(--dsw-shadow-lv3);border-radius:10px;align-items:flex-start;gap:12px;padding:12px 14px;display:flex;position:fixed;bottom:24px;right:24px}.F1KBNa_noticeText{font:var(--dsw-font-caption);color:var(--dsw-alias-label-primary);flex:1;margin:0;line-height:1.45}.F1KBNa_noticeButton{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font:var(--dsw-font-caption);cursor:pointer;border-radius:6px;flex:none;padding:4px 10px}.F1KBNa_noticeButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.F1KBNa_wrap .F1KBNa_line{height:auto;min-height:var(--dsh-diff-line-height,22px)}.F1KBNa_subline{height:var(--dsh-diff-line-height,22px);line-height:var(--dsh-diff-line-height,22px);white-space:pre;display:block;overflow:hidden}.F1KBNa_wrapActive{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-static-neutral-bluish-50)}.F1KBNa_wrapActive:hover{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 85%, var(--dsw-static-neutral-bluish-1000))}.F1KBNa_splitRoot{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.F1KBNa_diffBodySplit{flex:1;min-height:0;overflow-x:hidden}.F1KBNa_splitCols{width:100%;min-width:0;display:flex}.F1KBNa_splitCol{box-sizing:border-box;flex:1 1 0;min-width:0;overflow:hidden}.F1KBNa_splitDivider{background:var(--dsw-alias-border-l2);flex:0 0 1px}.F1KBNa_splitCol .F1KBNa_gutter,.F1KBNa_splitCol .F1KBNa_code{vertical-align:top}.F1KBNa_splitHScrollRow{border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex:none;width:100%;min-width:0;display:flex}.F1KBNa_splitHScroll{flex:1 1 0;min-width:0;height:15px;overflow:auto hidden}.F1KBNa_splitHScroll::-webkit-scrollbar{height:15px}.F1KBNa_splitHScroll::-webkit-scrollbar-track{cursor:default}.F1KBNa_splitHScroll::-webkit-scrollbar-thumb{cursor:default}.F1KBNa_splitHScrollFill{height:1px}.F1KBNa_splitLdel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_splitLadd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_splitRdel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 12%, transparent)}.F1KBNa_splitRadd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 10%, transparent)}.F1KBNa_intraDel{background-color:color-mix(in srgb, var(--dsh-diff-del-color,var(--dsw-alias-state-error-primary)) 26%, transparent);text-decoration:line-through}.F1KBNa_intraAdd{background-color:color-mix(in srgb, var(--dsh-diff-add-color,var(--dsw-alias-state-success-primary)) 24%, transparent)}";
16942
- const tagId = "dsh-diff-approval/PendingPanel.module.css";
16943
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
16944
- const tag = document.createElement("style");
16945
- tag.dataset.plugin = "dsh-diff-approval";
16946
- tag.dataset.pluginCss = tagId;
16947
- tag.textContent = css;
16948
- document.head.appendChild(tag);
17763
+ /** Persist the diff's code line height (px). */
17764
+ function setDiffLineHeight(value) {
17765
+ localStorage.setItem(DIFF_LINE_HEIGHT_KEY, String(Math.max(10, Math.min(36, value))));
16949
17766
  }
16950
- var PendingPanel_module_css_default = {
16951
- "header": "F1KBNa_header",
16952
- "confirmActions": "F1KBNa_confirmActions",
16953
- "flexSpacer": "F1KBNa_flexSpacer",
16954
- "code": "F1KBNa_code",
16955
- "action": "F1KBNa_action",
16956
- "colorPickerPreview": "F1KBNa_colorPickerPreview",
16957
- "diffBody": "F1KBNa_diffBody",
16958
- "splitDivider": "F1KBNa_splitDivider",
16959
- "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
16960
- "colorSv": "F1KBNa_colorSv",
16961
- "colorValue": "F1KBNa_colorValue",
16962
- "rail": "F1KBNa_rail",
16963
- "layer": "F1KBNa_layer",
16964
- "statusBar": "F1KBNa_statusBar",
16965
- "splitRoot": "F1KBNa_splitRoot",
16966
- "importNote": "F1KBNa_importNote",
16967
- "confirmCard": "F1KBNa_confirmCard",
16968
- "toggleThumb": "F1KBNa_toggleThumb",
16969
- "emptyState": "F1KBNa_emptyState",
16970
- "rows": "F1KBNa_rows",
16971
- "wrapActive": "F1KBNa_wrapActive",
16972
- "actionError": "F1KBNa_actionError",
16973
- "noteCentered": "F1KBNa_noteCentered",
16974
- "diffActions": "F1KBNa_diffActions",
16975
- "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
16976
- "delCount": "F1KBNa_delCount",
16977
- "colorSwatch": "F1KBNa_colorSwatch",
16978
- "markerAdd": "F1KBNa_markerAdd",
16979
- "detailEmpty": "F1KBNa_detailEmpty",
16980
- "split": "F1KBNa_split",
16981
- "colorSvCursor": "F1KBNa_colorSvCursor",
16982
- "group": "F1KBNa_group",
16983
- "searchBar": "F1KBNa_searchBar",
16984
- "langSelect": "F1KBNa_langSelect",
16985
- "diffBodySplit": "F1KBNa_diffBodySplit",
16986
- "missing": "F1KBNa_missing",
16987
- "markerDel": "F1KBNa_markerDel",
16988
- "rowFailed": "F1KBNa_rowFailed",
16989
- "colorPickerInputRow": "F1KBNa_colorPickerInputRow",
16990
- "colorHue": "F1KBNa_colorHue",
16991
- "fileList": "F1KBNa_fileList",
16992
- "settingsGroupText": "F1KBNa_settingsGroupText",
16993
- "add": "F1KBNa_add",
16994
- "badge": "F1KBNa_badge",
16995
- "colorPicker": "F1KBNa_colorPicker",
16996
- "colorHueCursor": "F1KBNa_colorHueCursor",
16997
- "confirmText": "F1KBNa_confirmText",
16998
- "mdPreviewFallback": "F1KBNa_mdPreviewFallback",
16999
- "rowMeta": "F1KBNa_rowMeta",
17000
- "colorPickerInput": "F1KBNa_colorPickerInput",
17001
- "bulkActions": "F1KBNa_bulkActions",
17002
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
17003
- "splitHScroll": "F1KBNa_splitHScroll",
17004
- "footerButtons": "F1KBNa_footerButtons",
17005
- "states": "F1KBNa_states",
17006
- "blockPosition": "F1KBNa_blockPosition",
17007
- "panel": "F1KBNa_panel",
17008
- "diffStats": "F1KBNa_diffStats",
17009
- "intraDel": "F1KBNa_intraDel",
17010
- "settingsGroupChevron": "F1KBNa_settingsGroupChevron",
17011
- "fileListFloat": "F1KBNa_fileListFloat",
17012
- "settingsGroupHeader": "F1KBNa_settingsGroupHeader",
17013
- "colorPickerPanel": "F1KBNa_colorPickerPanel",
17014
- "line": "F1KBNa_line",
17015
- "blockFlash": "F1KBNa_blockFlash",
17016
- "badgeCount": "F1KBNa_badgeCount",
17017
- "gutter": "F1KBNa_gutter",
17018
- "toggleOn": "F1KBNa_toggleOn",
17019
- "note": "F1KBNa_note",
17020
- "settingsSelector": "F1KBNa_settingsSelector",
17021
- "mdPreviewBody": "F1KBNa_mdPreviewBody",
17022
- "settingsGroup": "F1KBNa_settingsGroup",
17023
- "statusAction": "F1KBNa_statusAction",
17024
- "rowHead": "F1KBNa_rowHead",
17025
- "expandExpanded": "F1KBNa_expandExpanded",
17026
- "settingsRowTitle": "F1KBNa_settingsRowTitle",
17027
- "title": "F1KBNa_title",
17028
- "headerActions": "F1KBNa_headerActions",
17029
- "colorPickerPopover": "F1KBNa_colorPickerPopover",
17030
- "actionPrimary": "F1KBNa_actionPrimary",
17031
- "addCount": "F1KBNa_addCount",
17032
- "noticeText": "F1KBNa_noticeText",
17033
- "divider": "F1KBNa_divider",
17034
- "importButton": "F1KBNa_importButton",
17035
- "subline": "F1KBNa_subline",
17036
- "splitHScrollRow": "F1KBNa_splitHScrollRow",
17037
- "toggle": "F1KBNa_toggle",
17038
- "colorPickerChevron": "F1KBNa_colorPickerChevron",
17039
- "stepperButtonUp": "F1KBNa_stepperButtonUp",
17040
- "confirmBackdrop": "F1KBNa_confirmBackdrop",
17041
- "hint": "F1KBNa_hint",
17042
- "listScroll": "F1KBNa_listScroll",
17043
- "diffPreviewScroll": "F1KBNa_diffPreviewScroll",
17044
- "diffPath": "F1KBNa_diffPath",
17045
- "iconAction": "F1KBNa_iconAction",
17046
- "blockActions": "F1KBNa_blockActions",
17047
- "missingHint": "F1KBNa_missingHint",
17048
- "settingsGroupTitle": "F1KBNa_settingsGroupTitle",
17049
- "resizeHandle": "F1KBNa_resizeHandle",
17050
- "noticeButton": "F1KBNa_noticeButton",
17051
- "diffHeader": "F1KBNa_diffHeader",
17052
- "diffPreviewTitle": "F1KBNa_diffPreviewTitle",
17053
- "settingsRowText": "F1KBNa_settingsRowText",
17054
- "divergedHint": "F1KBNa_divergedHint",
17055
- "notice": "F1KBNa_notice",
17056
- "stepperValue": "F1KBNa_stepperValue",
17057
- "searchInput": "F1KBNa_searchInput",
17058
- "mdPreviewContent": "F1KBNa_mdPreviewContent",
17059
- "kindTag": "F1KBNa_kindTag",
17060
- "colorPickerTrigger": "F1KBNa_colorPickerTrigger",
17061
- "mdPreviewWrap": "F1KBNa_mdPreviewWrap",
17062
- "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
17063
- "colorBackdrop": "F1KBNa_colorBackdrop",
17064
- "stepperButton": "F1KBNa_stepperButton",
17065
- "splitHScrollFill": "F1KBNa_splitHScrollFill",
17066
- "splitLdel": "F1KBNa_splitLdel",
17067
- "intraAdd": "F1KBNa_intraAdd",
17068
- "mdPreviewDouble": "F1KBNa_mdPreviewDouble",
17069
- "kindHint": "F1KBNa_kindHint",
17070
- "blockFlashShake": "F1KBNa_blockFlashShake",
17071
- "splitCols": "F1KBNa_splitCols",
17072
- "badgeLabel": "F1KBNa_badgeLabel",
17073
- "detail": "F1KBNa_detail",
17074
- "lines": "F1KBNa_lines",
17075
- "searchMatch": "F1KBNa_searchMatch",
17076
- "wrap": "F1KBNa_wrap",
17077
- "overviewRuler": "F1KBNa_overviewRuler",
17078
- "settingsGroupBody": "F1KBNa_settingsGroupBody",
17079
- "settingsRow": "F1KBNa_settingsRow",
17080
- "stepper": "F1KBNa_stepper",
17081
- "overviewMarker": "F1KBNa_overviewMarker",
17082
- "close": "F1KBNa_close",
17083
- "del": "F1KBNa_del",
17084
- "splitCol": "F1KBNa_splitCol",
17085
- "diffFlashShake": "F1KBNa_diffFlashShake",
17086
- "vSpacer": "F1KBNa_vSpacer",
17087
- "diffPreview": "F1KBNa_diffPreview",
17088
- "row": "F1KBNa_row",
17089
- "splitRadd": "F1KBNa_splitRadd",
17090
- "diff": "F1KBNa_diff",
17091
- "rowPath": "F1KBNa_rowPath",
17092
- "langLabel": "F1KBNa_langLabel",
17093
- "splitLadd": "F1KBNa_splitLadd",
17094
- "settingsGroupDesc": "F1KBNa_settingsGroupDesc",
17095
- "expand": "F1KBNa_expand",
17096
- "settingsPage": "F1KBNa_settingsPage",
17097
- "searchCount": "F1KBNa_searchCount",
17098
- "searchMatchCurrent": "F1KBNa_searchMatchCurrent",
17099
- "splitRdel": "F1KBNa_splitRdel",
17100
- "mdPreviewSingle": "F1KBNa_mdPreviewSingle",
17101
- "context": "F1KBNa_context",
17102
- "settingsRowDesc": "F1KBNa_settingsRowDesc",
17103
- "diffFlashFade": "F1KBNa_diffFlashFade",
17104
- "readError": "F1KBNa_readError"
17767
+ /** Read a theme CSS custom property, with a fallback when it is unavailable. */
17768
+ function themeColor(name, fallback) {
17769
+ if (typeof document !== "undefined") {
17770
+ const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
17771
+ if (/^#[0-9a-f]{6}$/i.test(v)) return v;
17772
+ }
17773
+ return fallback;
17774
+ }
17775
+ /** The added-line base color the user chose, or `undefined` (theme color). */
17776
+ function diffAddColor() {
17777
+ const v = localStorage.getItem(DIFF_ADD_COLOR_KEY);
17778
+ return v !== null && v.length > 0 ? v : void 0;
17779
+ }
17780
+ /** Persist the added-line base color (a hex like `#22c55e`). */
17781
+ function setDiffAddColor(value) {
17782
+ localStorage.setItem(DIFF_ADD_COLOR_KEY, value);
17783
+ }
17784
+ /** The removed-line base color the user chose, or `undefined` (theme color). */
17785
+ function diffDelColor() {
17786
+ const v = localStorage.getItem(DIFF_DEL_COLOR_KEY);
17787
+ return v !== null && v.length > 0 ? v : void 0;
17788
+ }
17789
+ /** Persist the removed-line base color (a hex like `#ef4444`). */
17790
+ function setDiffDelColor(value) {
17791
+ localStorage.setItem(DIFF_DEL_COLOR_KEY, value);
17792
+ }
17793
+ /** The theme's added-line base color, for the settings swatch default. */
17794
+ function currentDiffAddColor() {
17795
+ return themeColor("--dsw-alias-state-success-primary", "#22c55e");
17796
+ }
17797
+ /** The theme's removed-line base color, for the settings swatch default. */
17798
+ function currentDiffDelColor() {
17799
+ return themeColor("--dsw-alias-state-error-primary", "#ef4444");
17800
+ }
17801
+ /**
17802
+ * The highlight language chosen by hand for one file suffix, remembered so the
17803
+ * choice sticks for every file with that suffix. Not a Settings row: it is a
17804
+ * per-suffix consequence of using the language picker, so it lives here as
17805
+ * storage only.
17806
+ * @param suffix - the lowercase extension without the dot (see `suffixOfPath`).
17807
+ * @returns the remembered language id, or undefined for "auto".
17808
+ */
17809
+ function languageForSuffix(suffix) {
17810
+ const language = rememberedLanguages()[suffix];
17811
+ return language === void 0 || language === "" ? void 0 : language;
17812
+ }
17813
+ /**
17814
+ * Remember (or clear) the language chosen for one suffix. A `null` language
17815
+ * forgets the suffix, which is what picking "auto" means.
17816
+ * @param suffix - the lowercase extension without the dot.
17817
+ * @param language - the language id, or null to forget the suffix.
17818
+ */
17819
+ function setLanguageForSuffix(suffix, language) {
17820
+ const stored = rememberedLanguages();
17821
+ if (language === null) {
17822
+ if (stored[suffix] === void 0) return;
17823
+ delete stored[suffix];
17824
+ } else {
17825
+ if (stored[suffix] === language) return;
17826
+ stored[suffix] = language;
17827
+ }
17828
+ localStorage.setItem(LANG_BY_SUFFIX_KEY, JSON.stringify(stored));
17829
+ }
17830
+ /** The whole suffix → language map (empty when unset or unreadable). */
17831
+ function rememberedLanguages() {
17832
+ const raw = localStorage.getItem(LANG_BY_SUFFIX_KEY);
17833
+ if (raw === null) return {};
17834
+ try {
17835
+ const parsed = JSON.parse(raw);
17836
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
17837
+ const cleaned = {};
17838
+ for (const [suffix, language] of Object.entries(parsed)) if (typeof language === "string" && language !== "") cleaned[suffix] = language;
17839
+ return cleaned;
17840
+ } catch {
17841
+ return {};
17842
+ }
17843
+ }
17844
+ /**
17845
+ * Whether the whole-file diff view uses the two-column (side-by-side) layout.
17846
+ * Default off (single column): the unified diff. Only an explicit `'1'` enables
17847
+ * split mode.
17848
+ * @returns whether the split (two-column) diff view is used.
17849
+ */
17850
+ function splitMode() {
17851
+ return localStorage.getItem(SPLIT_MODE_KEY) === "1";
17852
+ }
17853
+ /** Persist the split-view preference. */
17854
+ function setSplitMode(value) {
17855
+ localStorage.setItem(SPLIT_MODE_KEY, value ? "1" : "0");
17856
+ }
17857
+ /**
17858
+ * Whether the rendered Markdown preview is shown by default (for Markdown files).
17859
+ * Default off: the source diff is shown unless the panel toggle is used. Only an
17860
+ * explicit `'1'` enables the preview default.
17861
+ * @returns whether the Markdown preview default is enabled.
17862
+ */
17863
+ function mdPreviewEnabled() {
17864
+ return localStorage.getItem(MD_PREVIEW_KEY) === "1";
17865
+ }
17866
+ /** Persist the Markdown-preview default preference. */
17867
+ function setMdPreviewEnabled(value) {
17868
+ localStorage.setItem(MD_PREVIEW_KEY, value ? "1" : "0");
17869
+ }
17870
+ /**
17871
+ * The Markdown-preview content max width, in pixels (single column). Defaults to
17872
+ * 800; the double-column view totals 2x this. An out-of-range or non-integer
17873
+ * value falls back to the default.
17874
+ * @returns the max width in pixels.
17875
+ */
17876
+ function mdMaxWidth() {
17877
+ const raw = Number.parseInt(localStorage.getItem(MD_MAX_WIDTH_KEY) ?? "", 10);
17878
+ if (!Number.isInteger(raw)) return 800;
17879
+ return Math.max(480, Math.min(MD_MAX_WIDTH_MAX, raw));
17880
+ }
17881
+ /** Persist the Markdown-preview content max width. */
17882
+ function setMdMaxWidth(value) {
17883
+ localStorage.setItem(MD_MAX_WIDTH_KEY, String(Math.max(480, Math.min(MD_MAX_WIDTH_MAX, value))));
17884
+ }
17885
+ /**
17886
+ * How many rows of lead the diff block jump leaves above the jumped-to block,
17887
+ * and how far the anchored navigation scans. Defaults to 2; an out-of-range or
17888
+ * non-integer value falls back to the default.
17889
+ * @returns the lead row count.
17890
+ */
17891
+ function navLeadRows() {
17892
+ const raw = Number.parseInt(localStorage.getItem(NAV_LEAD_KEY) ?? "", 10);
17893
+ if (!Number.isInteger(raw)) return 2;
17894
+ return Math.max(0, Math.min(10, raw));
17895
+ }
17896
+ /** Persist the block-jump lead row count. */
17897
+ function setNavLeadRows(value) {
17898
+ localStorage.setItem(NAV_LEAD_KEY, String(Math.max(0, Math.min(10, value))));
17899
+ }
17900
+ const QUICK_SUMMON_KEY = "diff-approval:quick-summon-key";
17901
+ /**
17902
+ * The quick-summon chord. Stored as `Modifier+...+Key`; falls back to
17903
+ * {@link DEFAULT_QUICK_SUMMON}.
17904
+ * @returns the chord string.
17905
+ */
17906
+ function quickSummonKey() {
17907
+ return localStorage.getItem(QUICK_SUMMON_KEY) ?? "Ctrl+D";
17908
+ }
17909
+ /** Persist the quick-summon chord. */
17910
+ function setQuickSummonKey(value) {
17911
+ localStorage.setItem(QUICK_SUMMON_KEY, value);
17912
+ }
17913
+ const KEY_PREFIX = "diff-approval:key:";
17914
+ /** Default chord for each configurable action (every supported key except the
17915
+ * panel's own ESC-to-close, which is intentionally not remapped). */
17916
+ const DEFAULT_KEYBINDINGS = {
17917
+ jumpUp: "Ctrl+ArrowUp",
17918
+ jumpDown: "Ctrl+ArrowDown",
17919
+ copyRef: "Ctrl+L",
17920
+ openSearch: "Ctrl+F",
17921
+ searchNext: "F3",
17922
+ searchPrev: "Shift+F3",
17923
+ matchCase: "Alt+C",
17924
+ matchWholeWord: "Alt+W",
17925
+ undo: "Ctrl+Z",
17926
+ redo: "Ctrl+Shift+Z",
17927
+ cycleNext: "Ctrl+Tab",
17928
+ cyclePrev: "Ctrl+Shift+Tab"
17105
17929
  };
17930
+ /** The currently configured chord for one action; falls back to its default. */
17931
+ function keybindingOf(action) {
17932
+ return localStorage.getItem(`${KEY_PREFIX}${action}`) ?? DEFAULT_KEYBINDINGS[action] ?? "";
17933
+ }
17934
+ /** Persist one action's chord. */
17935
+ function setKeybinding(action, chord) {
17936
+ localStorage.setItem(`${KEY_PREFIX}${action}`, chord);
17937
+ }
17938
+ /**
17939
+ * Whether a keyboard event matches a chord string like `Ctrl+D`. Modifier
17940
+ * names are matched case-insensitively (`Ctrl`/`Control`, `Alt`/`Option`,
17941
+ * `Shift`, `Meta`/`Cmd`/`Command`/`Win`); the final part is the key. Exact
17942
+ * modifier set is required (extra modifiers do not match).
17943
+ * @param event - the keydown event.
17944
+ * @param shortcut - the chord string.
17945
+ * @returns whether the event matches.
17946
+ */
17947
+ function matchesShortcut(event, shortcut) {
17948
+ const parts = shortcut.split("+").map((part) => part.trim().toLowerCase());
17949
+ const key = parts.pop();
17950
+ if (key === void 0 || key === "") return false;
17951
+ const mods = new Set(parts);
17952
+ const ctrl = mods.has("ctrl") || mods.has("control");
17953
+ const alt = mods.has("alt") || mods.has("option");
17954
+ const shift = mods.has("shift");
17955
+ const meta = mods.has("meta") || mods.has("cmd") || mods.has("command") || mods.has("win");
17956
+ return event.key.toLowerCase() === key && event.ctrlKey === ctrl && event.altKey === alt && event.shiftKey === shift && event.metaKey === meta;
17957
+ }
17106
17958
  //#endregion
17107
17959
  //#region lib/types/client/PendingPanel.js
17108
17960
  /** Sidebar-foot pending-edit review action and the split review panel it opens. */
@@ -17126,8 +17978,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17126
17978
  const COMPOSER_HEIGHT_VAR = "--dsh-composer-height";
17127
17979
  /** Interactive composer/approval cards; clicking these keeps the panel open.
17128
17980
  Deliberately the cards themselves, not the seat: the approval frame's wide
17129
- side gutters are blank space, so a click there must still close. */
17130
- const KEEP_OPEN_SELECTOR = "[data-composer-card],[data-question-key] > *,[data-plan-review-key] > *,[data-approval-key] > *";
17981
+ side gutters are blank space, so a click there must still close.
17982
+ `[role="menu"]` covers the portaled menus this panel opens (the highlight
17983
+ language picker): the list is rendered into `document.body`, so picking an
17984
+ item is a pointerdown outside the panel element and would otherwise close it. */
17985
+ const KEEP_OPEN_SELECTOR = "[data-composer-card],[data-question-key] > *,[data-plan-review-key] > *,[data-approval-key] > *,[role=\"menu\"]";
17131
17986
  /** Seat counts as docked when its bottom is this close to the window bottom. */
17132
17987
  const DOCKED_TOLERANCE_PX = 48;
17133
17988
  /** File-list pane width bounds for the manual split drag, in px. */
@@ -17379,6 +18234,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17379
18234
  * 5px frame padding on each side + 1px border on each side, plus a little
17380
18235
  * breathing room so the bottom padding never sits flush against it. */
17381
18236
  const BLOCK_ACTIONS_FRAME_PX = 40;
18237
+ /** How far a floating block-action frame stays inside the surface it is anchored
18238
+ * to (the content's right edge in the Markdown preview, the pane's in the code
18239
+ * view). */
18240
+ const FRAME_INSET_PX = 8;
17382
18241
  /** The trailing file-name segment of a path, used for row display. */
17383
18242
  function basenameOf(path) {
17384
18243
  const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
@@ -17431,6 +18290,29 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17431
18290
  Array.from(document.querySelectorAll("button")).find((button) => button.textContent?.trim() === sectionLabel)?.click();
17432
18291
  });
17433
18292
  }
18293
+ /**
18294
+ * Search narrowing glyphs, taken from VS Code's Codicons so the two find-widget
18295
+ * toggles read exactly as they do in that editor: `case-sensitive` (an upper- and
18296
+ * lower-case letter pair) and `whole-word` (the same letter pair over a bar that
18297
+ * marks the word boundary). Inline SVG because the icon library ships neither.
18298
+ * @param props.kind - which glyph to draw.
18299
+ * @returns the 16px-grid glyph.
18300
+ */
18301
+ function SearchOptionIcon({ kind }) {
18302
+ return (0, react_jsx_runtime.jsx)("svg", {
18303
+ width: 16,
18304
+ height: 16,
18305
+ viewBox: "0 0 16 16",
18306
+ fill: "none",
18307
+ xmlns: "http://www.w3.org/2000/svg",
18308
+ children: (0, react_jsx_runtime.jsx)("path", {
18309
+ fill: "currentColor",
18310
+ fillRule: "evenodd",
18311
+ clipRule: "evenodd",
18312
+ d: kind === "case" ? `M4.02602 3.34176C4.16218 2.93404 4.83818 2.93398 4.97426 3.34176L6.97426 9.34274C6.97526 9.34674 6.97817 9.35544 6.97817 9.35544L7.97426 12.3427C8.06126 12.6047 7.91984 12.8875 7.65786 12.9756C7.60486 12.9926 7.55165 13.0009 7.49965 13.0009C7.29082 13.0008 7.09602 12.868 7.02602 12.6591L6.14028 10.0009H2.86L1.97426 12.6591C1.88728 12.919 1.60634 13.0634 1.34243 12.9746C1.08043 12.8866 0.93902 12.6038 1.02602 12.3418L2.02211 9.35544C2.02311 9.35144 2.02602 9.34274 2.02602 9.34274L4.02602 3.34176ZM3.19399 8.99997H5.80629L4.49965 5.08102L3.19399 8.99997ZM11.8581 6.66794C13.165 6.73296 13.9427 7.48427 13.9967 8.69626L13.9997 8.83297V12.5078C13.9957 12.7568 13.809 12.9621 13.568 12.9951L13.4997 13C13.2469 12.9998 13.0376 12.8121 13.0045 12.5683L12.9997 12.5V12.4297C12.3407 12.8066 11.7316 13 11.1666 13C9.94081 12.9998 8.99965 12.1369 8.99965 10.833C8.99967 9.68299 9.79211 8.82889 11.1061 8.66989C11.7279 8.59493 12.3589 8.64164 12.9987 8.80954C12.9915 8.07194 12.6279 7.70704 11.8082 7.66598C11.1672 7.63398 10.7158 7.72415 10.4518 7.90915C10.2258 8.06799 9.91347 8.01301 9.75551 7.78708C9.59671 7.56115 9.65178 7.24878 9.87758 7.09079C10.3165 6.78283 10.9138 6.64715 11.6666 6.6611L11.8581 6.66794ZM12.7965 9.8154C12.2587 9.66749 11.7361 9.62551 11.2262 9.68747C10.4042 9.78747 9.99868 10.2244 9.99868 10.8574C9.99884 11.5881 10.474 12.0242 11.1657 12.0244C11.6196 12.0244 12.1777 11.8137 12.8336 11.3818L12.9987 11.2695V9.87594L12.7965 9.8154Z` : `M4.8584 5.6709C6.16516 5.73603 6.94308 6.48734 6.99707 7.69922L7 7.83594V11.5107C6.996 11.7596 6.80919 11.9649 6.56836 11.998L6.5 12.0029C6.24709 12.0029 6.038 11.8152 6.00488 11.5713L6 11.5029V11.4326C5.341 11.8096 4.73199 12.0029 4.16699 12.0029C2.941 12.0029 2 11.1399 2 9.83594C2.00003 8.68597 2.79247 7.83185 4.10645 7.67285C4.7283 7.59793 5.35918 7.64552 5.99902 7.81348C5.99202 7.07548 5.62762 6.70995 4.80762 6.66895C4.16686 6.637 3.7161 6.72717 3.45215 6.91211C3.22615 7.07111 2.91386 7.01604 2.75586 6.79004C2.5969 6.56404 2.65194 6.25174 2.87793 6.09375C3.31692 5.78579 3.91404 5.65006 4.66699 5.66406L4.8584 5.6709ZM5.79688 8.81836C5.25888 8.67037 4.73558 8.62843 4.22559 8.69043C3.40389 8.79054 2.99902 9.22747 2.99902 9.86035C2.99917 10.5911 3.47413 11.0273 4.16602 11.0273C4.62001 11.0273 5.17799 10.8168 5.83398 10.3848L5.99902 10.2725V8.87891L5.79688 8.81836ZM9.55078 2.00586C9.78578 2.02986 9.97307 2.21715 9.99707 2.45215C10 2.46907 10 2.48601 10 2.50293V6.60254C10.418 6.22566 10.9371 6.00293 11.5 6.00293C12.881 6.00293 14 7.34596 14 9.00293C14 10.6599 12.881 12.0029 11.5 12.0029C10.9371 12.0029 10.418 11.7802 10 11.4033V11.5029C10 11.7619 9.80278 11.974 9.55078 12C9.53385 12.003 9.51693 12.0029 9.5 12.0029C9.224 12.0029 9 11.7789 9 11.5029V2.50293C9 2.486 9.00095 2.46907 9.00293 2.45215C9.02793 2.20015 9.241 2.00293 9.5 2.00293C9.51692 2.00293 9.53386 2.00388 9.55078 2.00586ZM11.4355 7.00391C11.0307 7.03208 10.5769 7.31545 10.29 7.82227C10.1232 8.12611 10.018 8.49479 10.002 8.89453C9.99995 8.92952 10 8.96597 10 9.00195C10 9.03795 10.001 9.07438 10.002 9.10938C10.018 9.50814 10.1222 9.87582 10.2891 10.1797C10.576 10.6875 11.0307 10.9728 11.4355 11C11.4565 11.002 11.478 11.002 11.5 11.002C11.522 11.002 11.5435 11.001 11.5645 11C11.9693 10.9728 12.424 10.6875 12.7109 10.1797C12.8778 9.87582 12.982 9.50814 12.998 9.10938C13 9.07438 13 9.03795 13 9.00195C13 8.96597 12.999 8.92952 12.998 8.89453C12.982 8.49479 12.8768 8.12611 12.71 7.82227C12.4231 7.31545 11.9693 7.03109 11.5645 7.00391C11.5435 7.00191 11.522 7.00195 11.5 7.00195C11.478 7.00195 11.4565 7.00291 11.4355 7.00391ZM15.5 12.5C15.776 12.5 16 12.724 16 13V13.5C16 14.327 15.327 15 14.5 15H1.5C0.673 15 0 14.327 0 13.5V13C0 12.724 0.224 12.5 0.5 12.5C0.776 12.5 1 12.724 1 13V13.5C1 13.775 1.224 14 1.5 14H14.5C14.776 14 15 13.775 15 13.5V13C15 12.724 15.224 12.5 15.5 12.5Z`
18313
+ })
18314
+ });
18315
+ }
17434
18316
  /** The diff body's row class per line kind. */
17435
18317
  const ROW_CLASS = {
17436
18318
  context: PendingPanel_module_css_default.context,
@@ -17509,27 +18391,13 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17509
18391
  }, i);
17510
18392
  });
17511
18393
  }
17512
- /** Character ranges of each case-insensitive occurrence of `query` in `text`. */
17513
- function matchRangesOf(text, query) {
17514
- if (query === "") return [];
17515
- const lower = text.toLowerCase();
17516
- const q = query.toLowerCase();
17517
- const out = [];
17518
- let from = 0;
17519
- for (;;) {
17520
- const at = lower.indexOf(q, from);
17521
- if (at === -1) return out;
17522
- out.push([at, at + query.length]);
17523
- from = at + query.length;
17524
- }
17525
- }
17526
18394
  /** Render `text[segStart, segEnd)` with every `query` match wrapped in a search
17527
18395
  * highlight, keeping the syntax highlight and intra-line chips on the non-match
17528
18396
  * parts. When no match is present it renders exactly as before (syntax / intra /
17529
18397
  * plain). `segStart`/`segEnd` let the caller render a wrapped sub-line range. */
17530
- function textWithSearch(text, runs, intra, query, segStart = 0, segEnd = text.length, current = false) {
18398
+ function textWithSearch(text, runs, intra, options, query, segStart = 0, segEnd = text.length, current = false) {
17531
18399
  const segText = text.slice(segStart, segEnd);
17532
- const ranges = matchRangesOf(segText, query);
18400
+ const ranges = matchRangesOf(segText, query, options);
17533
18401
  if (ranges.length === 0) return intra !== void 0 && intra.length > 0 ? renderIntra(runs, intra, segStart, segEnd) : runs !== void 0 && runs.length > 0 ? clipRuns(runs, segStart, segEnd) : segText === "" ? "\xA0" : segText;
17534
18402
  const nodes = [];
17535
18403
  const push = (absStart, absEnd, isMatch) => {
@@ -17556,6 +18424,96 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17556
18424
  if (cursor < segEnd) push(cursor, segEnd, false);
17557
18425
  return nodes;
17558
18426
  }
18427
+ /** Arrow keys render as arrows in a tooltip hint (`Ctrl+↑`, not `Ctrl+ArrowUp`). */
18428
+ const CHORD_KEY_GLYPHS = {
18429
+ ArrowUp: "↑",
18430
+ ArrowDown: "↓",
18431
+ ArrowLeft: "←",
18432
+ ArrowRight: "→"
18433
+ };
18434
+ /**
18435
+ * A tooltip label with the action's configured chord appended. The hint states
18436
+ * the binding the user actually has — including one rebound in Settings — rather
18437
+ * than a default baked into the label.
18438
+ * @param label - the translated action label.
18439
+ * @param action - the keybinding action id (see `DEFAULT_KEYBINDINGS`).
18440
+ * @returns the label, with ` (chord)` appended when one is configured.
18441
+ */
18442
+ function withChord(label, action) {
18443
+ const chord = keybindingOf(action);
18444
+ if (chord === "") return label;
18445
+ return `${label} (${chord.split("+").map((part) => CHORD_KEY_GLYPHS[part] ?? part).join("+")})`;
18446
+ }
18447
+ /**
18448
+ * Whether a key event's target is one of the panel's search query boxes. The
18449
+ * bar's own chords (step, narrow) are scoped this way: the caret has to be in
18450
+ * the box, so an open bar is not enough — the same chord must stay free for
18451
+ * whatever else happens to have focus (the chat composer above all), and a mouse
18452
+ * click on a bar button hands the focus straight back to the box.
18453
+ * @param event - the keydown event.
18454
+ * @returns whether the event came from a search query box.
18455
+ */
18456
+ function isSearchInputEvent(event) {
18457
+ const target = event.target;
18458
+ return target instanceof HTMLInputElement && target.hasAttribute("data-diff-search-input");
18459
+ }
18460
+ /**
18461
+ * Whether a key event came from inside the plugin's own panel. Esc is split along
18462
+ * that line: a press inside the panel is the panel's to dismiss (its search bar
18463
+ * first, otherwise the panel itself), while anything outside it — the chat
18464
+ * composer above all — dismisses the panel and keeps its own Esc besides.
18465
+ * @param event - the keydown event.
18466
+ * @returns whether the event came from inside the panel.
18467
+ */
18468
+ function isInPanelEvent(event) {
18469
+ const target = event.target;
18470
+ return target instanceof Element && target.closest("[data-diff-approval-panel]") !== null;
18471
+ }
18472
+ /**
18473
+ * Whether a key event came from a text field that owns its own keys (`Esc`,
18474
+ * cursor moves). The chat composer is the one that matters: the panel leaves it
18475
+ * alone even while its own search bar is open.
18476
+ * @param event - the keydown event.
18477
+ * @returns whether the event came from a text field.
18478
+ */
18479
+ function isTextFieldEvent(event) {
18480
+ const target = event.target;
18481
+ return target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null;
18482
+ }
18483
+ /**
18484
+ * The in-file search's narrowing toggles. The persisted preference is the
18485
+ * source of truth, so the choice survives a reopen and the two views cannot
18486
+ * disagree: each bar owns one of these, and it re-reads the stored flags with
18487
+ * {@link sync} whenever its bar opens. The options object is memoized so the
18488
+ * memoized rows only re-render when a toggle changes.
18489
+ * @returns the options, their current values, the two toggles, and the re-read.
18490
+ */
18491
+ function useSearchOptions() {
18492
+ const [caseSensitive, setCase] = (0, react.useState)(searchCaseSensitive);
18493
+ const [wholeWord, setWord] = (0, react.useState)(searchWholeWord);
18494
+ return {
18495
+ options: (0, react.useMemo)(() => ({
18496
+ caseSensitive,
18497
+ wholeWord
18498
+ }), [caseSensitive, wholeWord]),
18499
+ caseSensitive,
18500
+ wholeWord,
18501
+ toggleCase: (0, react.useCallback)(() => {
18502
+ const next = !searchCaseSensitive();
18503
+ setSearchCaseSensitive(next);
18504
+ setCase(next);
18505
+ }, []),
18506
+ toggleWord: (0, react.useCallback)(() => {
18507
+ const next = !searchWholeWord();
18508
+ setSearchWholeWord(next);
18509
+ setWord(next);
18510
+ }, []),
18511
+ sync: (0, react.useCallback)(() => {
18512
+ setCase(searchCaseSensitive());
18513
+ setWord(searchWholeWord());
18514
+ }, [])
18515
+ };
18516
+ }
17559
18517
  /**
17560
18518
  * One rendered diff row, memoized so a poll or an unrelated state change
17561
18519
  * does not re-render rows whose content, highlight, and focus are unchanged.
@@ -17564,18 +18522,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17564
18522
  * is `wrappedLines.length * 22` by construction.
17565
18523
  */
17566
18524
  const DiffRow = (0, react.memo)(function DiffRow(props) {
17567
- const { index, row, runs, focused, searchHit, searchCurrent, searchQuery, onRowHover, wrappedLines } = props;
18525
+ const { index, row, runs, focused, searchHit, searchCurrent, searchQuery, searchOptions, onRowHover, wrappedLines } = props;
17568
18526
  const lineNumber = row.kind === "del" ? row.oldLine : row.newLine;
17569
18527
  const sideRuns = row.kind === "del" ? runs?.oldRuns : runs?.newRuns;
17570
18528
  const lineRuns = lineNumber === void 0 ? void 0 : sideRuns?.[lineNumber - 1];
17571
18529
  let code;
17572
- if (wrappedLines === void 0) code = textWithSearch(row.text, lineRuns, void 0, searchQuery, 0, row.text.length, searchCurrent);
18530
+ if (wrappedLines === void 0) code = textWithSearch(row.text, lineRuns, void 0, searchOptions, searchQuery, 0, row.text.length, searchCurrent);
17573
18531
  else {
17574
18532
  let offset = 0;
17575
18533
  code = wrappedLines.map((line, lineIndex) => {
17576
18534
  const start = offset;
17577
18535
  offset += line.length;
17578
- const content = textWithSearch(row.text, lineRuns, void 0, searchQuery, start, offset, searchCurrent);
18536
+ const content = textWithSearch(row.text, lineRuns, void 0, searchOptions, searchQuery, start, offset, searchCurrent);
17579
18537
  return (0, react_jsx_runtime.jsx)("div", {
17580
18538
  className: PendingPanel_module_css_default.subline,
17581
18539
  children: content
@@ -17611,69 +18569,6 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17611
18569
  ]
17612
18570
  });
17613
18571
  });
17614
- /**
17615
- * One diff block's old/new line ranges, 1-based inclusive, for block-level
17616
- * keep/revert. A side with no lines (a pure addition or deletion) is empty;
17617
- * its start is that side's insertion point — the line after the surrounding
17618
- * context — so the host can insert there.
17619
- * @param rows - the whole-file diff rows.
17620
- * @param block - the block's row range.
17621
- * @returns the old and new line ranges.
17622
- */
17623
- function blockRangesOf(rows, block) {
17624
- let oldStart = Infinity;
17625
- let oldEnd = -Infinity;
17626
- let newStart = Infinity;
17627
- let newEnd = -Infinity;
17628
- for (let index = block.start; index <= block.end; index++) {
17629
- const row = rows[index];
17630
- if (row === void 0) continue;
17631
- if (row.oldLine !== void 0) {
17632
- oldStart = Math.min(oldStart, row.oldLine);
17633
- oldEnd = Math.max(oldEnd, row.oldLine);
17634
- }
17635
- if (row.newLine !== void 0) {
17636
- newStart = Math.min(newStart, row.newLine);
17637
- newEnd = Math.max(newEnd, row.newLine);
17638
- }
17639
- }
17640
- const before = rows[block.start - 1];
17641
- if (oldStart === Infinity) {
17642
- oldStart = (before?.oldLine ?? 0) + 1;
17643
- oldEnd = oldStart - 1;
17644
- }
17645
- if (newStart === Infinity) {
17646
- newStart = (before?.newLine ?? 0) + 1;
17647
- newEnd = newStart - 1;
17648
- }
17649
- return {
17650
- oldStart,
17651
- oldEnd,
17652
- newStart,
17653
- newEnd
17654
- };
17655
- }
17656
- /** Split a row list into maximal runs of non-context rows. */
17657
- function changeBlocksOf(diff) {
17658
- const blocks = [];
17659
- let start = -1;
17660
- diff.rows.forEach((row, index) => {
17661
- if (row.kind !== "context") {
17662
- if (start === -1) start = index;
17663
- } else if (start !== -1) {
17664
- blocks.push({
17665
- start,
17666
- end: index - 1
17667
- });
17668
- start = -1;
17669
- }
17670
- });
17671
- if (start !== -1) blocks.push({
17672
- start,
17673
- end: diff.rows.length - 1
17674
- });
17675
- return blocks;
17676
- }
17677
18572
  /** Whether a block keep/revert range covers the file's entire change region, so
17678
18573
  * applying it leaves the file with no pending diff — the "remove or keep in
17679
18574
  * list" prompt applies. This generalises the single-block case to a selection
@@ -17695,23 +18590,60 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17695
18590
  }
17696
18591
  return block.oldStart <= oldMin && block.oldEnd >= oldMax && block.newStart <= newMin && block.newEnd >= newMax;
17697
18592
  }
17698
- /** Row indices whose text contains `query` (case-insensitive); empty for ''. */
17699
- function matchingRows(rows, query) {
18593
+ /** Row indices whose text matches `query` under the options; empty for ''. Counted
18594
+ * through the same matcher the highlights use, so the tally can never disagree
18595
+ * with what is drawn. */
18596
+ function matchingRows(rows, query, options) {
17700
18597
  if (query === "") return [];
17701
- const lower = query.toLowerCase();
17702
18598
  const out = [];
17703
- for (let i = 0; i < rows.length; i++) if (rows[i].text.toLowerCase().includes(lower)) out.push(i);
18599
+ for (let i = 0; i < rows.length; i++) if (matchRangesOf(rows[i].text, query, options).length > 0) out.push(i);
17704
18600
  return out;
17705
18601
  }
18602
+ /** The source lines one row window covers on a side (1-based, inclusive), or
18603
+ * undefined when the window shows none of that side — an all-added or
18604
+ * all-deleted stretch, where the other side has nothing to highlight. */
18605
+ function windowLineRange(rows, from, to, side) {
18606
+ let first = Infinity;
18607
+ let last = -Infinity;
18608
+ for (let index = from; index < to; index++) {
18609
+ const row = rows[index];
18610
+ if (row === void 0) continue;
18611
+ const line = side === "old" ? row.oldLine : row.newLine;
18612
+ if (line === void 0) continue;
18613
+ if (line < first) first = line;
18614
+ if (line > last) last = line;
18615
+ }
18616
+ return Number.isFinite(first) ? {
18617
+ from: first,
18618
+ to: last
18619
+ } : void 0;
18620
+ }
18621
+ /** The source lines one split-view pair window covers on a side (1-based,
18622
+ * inclusive), or undefined when the window shows none of that side. */
18623
+ function pairLineRange(pairs, from, to, side) {
18624
+ let first = Infinity;
18625
+ let last = -Infinity;
18626
+ for (let index = from; index < to; index++) {
18627
+ const pair = pairs[index];
18628
+ const line = side === "left" ? pair?.left?.line : pair?.right?.line;
18629
+ if (line === void 0) continue;
18630
+ if (line < first) first = line;
18631
+ if (line > last) last = line;
18632
+ }
18633
+ return Number.isFinite(first) ? {
18634
+ from: first,
18635
+ to: last
18636
+ } : void 0;
18637
+ }
17706
18638
  /** One side's line-content for the split view: the highlighted runs or plain text. */
17707
- function splitSideContent(side, wrapped, runs, intra, query, current) {
18639
+ function splitSideContent(side, wrapped, runs, intra, options, query, current) {
17708
18640
  if (side === void 0) return "";
17709
- if (wrapped === void 0) return textWithSearch(side.text, runs, intra, query, 0, side.text.length, current);
18641
+ if (wrapped === void 0) return textWithSearch(side.text, runs, intra, options, query, 0, side.text.length, current);
17710
18642
  let offset = 0;
17711
18643
  return wrapped.map((line, i) => {
17712
18644
  const start = offset;
17713
18645
  offset += line.length;
17714
- const content = textWithSearch(side.text, runs, intra, query, start, offset, current);
18646
+ const content = textWithSearch(side.text, runs, intra, options, query, start, offset, current);
17715
18647
  return (0, react_jsx_runtime.jsx)("div", {
17716
18648
  className: PendingPanel_module_css_default.subline,
17717
18649
  children: content
@@ -17727,7 +18659,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17727
18659
  * one side is longer. The gutter and code are top-aligned so sub-lines line up
17728
18660
  * across the divider.
17729
18661
  */
17730
- function SplitSideRow({ index, side, wrapped, runs, kind, isLeft, height, focused, searchHit, searchCurrent, searchQuery, onHover, intra }) {
18662
+ function SplitSideRow({ index, side, wrapped, runs, kind, isLeft, height, focused, searchHit, searchCurrent, searchQuery, searchOptions, onHover, intra }) {
17731
18663
  const tint = isLeft ? kind === "del" || kind === "replace" ? PendingPanel_module_css_default.splitLdel : "" : kind === "add" || kind === "replace" ? PendingPanel_module_css_default.splitRadd : "";
17732
18664
  return (0, react_jsx_runtime.jsxs)("div", {
17733
18665
  className: PendingPanel_module_css_default.line,
@@ -17746,12 +18678,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17746
18678
  className: `${PendingPanel_module_css_default.code} ${tint}`,
17747
18679
  "data-diff-code": true,
17748
18680
  "data-diff-code-side": isLeft ? "left" : "right",
17749
- children: splitSideContent(side, wrapped, runs, intra, searchQuery, searchCurrent)
18681
+ children: splitSideContent(side, wrapped, runs, intra, searchOptions, searchQuery, searchCurrent)
17750
18682
  })]
17751
18683
  });
17752
18684
  }
17753
18685
  /** The two-column (side-by-side) whole-file diff view. */
17754
- const SplitDiff = (0, react.forwardRef)(function SplitDiff({ file, model, runs, langWrap, tabWidthSpaces, busy, t, selection, leadRows, onBlockKeep, onBlockRevert, onWrapToast }, ref) {
18686
+ const SplitDiff = (0, react.forwardRef)(function SplitDiff({ file, model, runs, langWrap, tabWidthSpaces, busy, t, selection, leadRows, onBlockKeep, onBlockRevert, onWrapToast, onVisibleLines }, ref) {
17755
18687
  const ROW_HEIGHT_PX = diffLineHeight();
17756
18688
  const NAV_ANCHOR_TOLERANCE_PX = ROW_HEIGHT_PX / 4;
17757
18689
  const { pairs, pairOfRow } = (0, react.useMemo)(() => computeSideBySideDiff(model.diff.rows, true), [model]);
@@ -17791,6 +18723,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17791
18723
  });
17792
18724
  const [searchOpen, setSearchOpen] = (0, react.useState)(false);
17793
18725
  const [searchQuery, setSearchQuery] = (0, react.useState)("");
18726
+ const search = useSearchOptions();
17794
18727
  const [searchIndex, setSearchIndex] = (0, react.useState)(0);
17795
18728
  const searchInputRef = (0, react.useRef)(null);
17796
18729
  (0, react.useEffect)(() => {
@@ -17916,14 +18849,43 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17916
18849
  if (strip === null || col === null) return;
17917
18850
  col.scrollLeft = strip.scrollLeft;
17918
18851
  }, []);
17919
- const searchMatches = (0, react.useMemo)(() => searchPairs(pairs, searchQuery), [pairs, searchQuery]);
18852
+ const searchMatches = (0, react.useMemo)(() => searchPairs(pairs, searchQuery, search.options), [
18853
+ pairs,
18854
+ searchQuery,
18855
+ search.options
18856
+ ]);
17920
18857
  const searchHitSet = (0, react.useMemo)(() => new Set(searchMatches), [searchMatches]);
17921
18858
  const currentSearchPair = searchMatches.length === 0 ? void 0 : searchMatches[searchIndex % searchMatches.length];
17922
18859
  const closeSearch = () => {
18860
+ if (!searchOpen) return false;
17923
18861
  setSearchOpen(false);
17924
18862
  setSearchQuery("");
17925
18863
  setSearchIndex(0);
17926
18864
  bodyRef.current?.focus();
18865
+ return true;
18866
+ };
18867
+ /** Run one bar control's action, then hand the focus back to the query box:
18868
+ * the bar's chords are scoped to the box (its Esc to the bar), so a mouse
18869
+ * click on a bar button must not leave them dead on the button it landed on. */
18870
+ const andRefocus = (action) => {
18871
+ action();
18872
+ searchInputRef.current?.focus();
18873
+ };
18874
+ /** The shared toolbar's search button: the split view owns its own bar, so the
18875
+ * button has to toggle this one rather than the single-column state. */
18876
+ const toggleSearch = () => {
18877
+ if (searchOpen) closeSearch();
18878
+ else openSearch();
18879
+ };
18880
+ const toggleMatchCase = () => {
18881
+ if (!searchOpen) return false;
18882
+ search.toggleCase();
18883
+ return true;
18884
+ };
18885
+ const toggleMatchWholeWord = () => {
18886
+ if (!searchOpen) return false;
18887
+ search.toggleWord();
18888
+ return true;
17927
18889
  };
17928
18890
  const openSearch = () => {
17929
18891
  if (searchOpen) {
@@ -17931,13 +18893,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17931
18893
  searchInputRef.current?.select();
17932
18894
  return;
17933
18895
  }
18896
+ search.sync();
17934
18897
  const live = window.getSelection();
17935
18898
  const liveRange = splitRowRangeOf(live);
17936
18899
  const range = liveRange !== void 0 ? liveRange : selection;
17937
18900
  const liveText = (live?.toString() ?? "").trim();
17938
18901
  const value = liveText !== "" && !liveText.includes("\n") ? liveText : "";
17939
18902
  setSearchQuery(value);
17940
- const matches = value === "" ? [] : searchPairs(pairs, value);
18903
+ const matches = value === "" ? [] : searchPairs(pairs, value, search.options);
17941
18904
  let index = 0;
17942
18905
  if (matches.length > 0) {
17943
18906
  const inSel = range === void 0 ? -1 : matches.findIndex((i) => i >= range.start && i <= range.end);
@@ -18010,6 +18973,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18010
18973
  end = Math.max(end, selection.end + 1);
18011
18974
  }
18012
18975
  const visiblePairs = pairs.slice(start, end);
18976
+ (0, react.useEffect)(() => {
18977
+ onVisibleLines({
18978
+ oldRange: pairLineRange(pairs, start, end, "left"),
18979
+ newRange: pairLineRange(pairs, start, end, "right"),
18980
+ live: viewportH > 0
18981
+ });
18982
+ }, [
18983
+ onVisibleLines,
18984
+ pairs,
18985
+ start,
18986
+ end,
18987
+ viewportH
18988
+ ]);
18013
18989
  const wrapArmedRef = (0, react.useRef)(0);
18014
18990
  const jump = (direction, wrapGuard = false, singleToast = wrapGuard) => {
18015
18991
  const count = blockOfPair.length;
@@ -18051,11 +19027,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18051
19027
  (0, react.useImperativeHandle)(ref, () => ({
18052
19028
  jump,
18053
19029
  openSearch,
18054
- searchNext
19030
+ toggleSearch,
19031
+ closeSearch,
19032
+ searchNext,
19033
+ toggleMatchCase,
19034
+ toggleMatchWholeWord
18055
19035
  }), [
18056
19036
  jump,
18057
19037
  openSearch,
18058
- searchNext
19038
+ toggleSearch,
19039
+ closeSearch,
19040
+ searchNext,
19041
+ toggleMatchCase,
19042
+ toggleMatchWholeWord
18059
19043
  ]);
18060
19044
  (0, react.useLayoutEffect)(() => {
18061
19045
  if (pairCount === 0) return;
@@ -18139,6 +19123,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18139
19123
  searchHit: searchHitSet.has(index),
18140
19124
  searchCurrent: index === currentSearchPair,
18141
19125
  searchQuery,
19126
+ searchOptions: search.options,
18142
19127
  onHover: () => onPairHover(index),
18143
19128
  intra: sideIndex?.left === void 0 ? void 0 : model.intra.get(sideIndex.left)
18144
19129
  }, index);
@@ -18181,6 +19166,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18181
19166
  searchHit: searchHitSet.has(index),
18182
19167
  searchCurrent: index === currentSearchPair,
18183
19168
  searchQuery,
19169
+ searchOptions: search.options,
18184
19170
  onHover: () => onPairHover(index),
18185
19171
  intra: sideIndex?.right === void 0 ? void 0 : model.intra.get(sideIndex.right)
18186
19172
  }, index);
@@ -18246,7 +19232,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18246
19232
  setSearchQuery(value);
18247
19233
  if (value !== "") {
18248
19234
  const body = bodyRef.current;
18249
- const matches = searchPairs(pairs, value);
19235
+ const matches = searchPairs(pairs, value, search.options);
18250
19236
  const anchor = currentSearchPair ?? (body === null ? 0 : pairAtY(body.scrollTop));
18251
19237
  const at = matches.findIndex((index) => index >= anchor);
18252
19238
  setSearchIndex(at === -1 ? 0 : at);
@@ -18256,7 +19242,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18256
19242
  if (event.key === "Enter") {
18257
19243
  event.preventDefault();
18258
19244
  goSearch(event.shiftKey ? -1 : 1);
18259
- } else if (event.key === "Escape") closeSearch();
19245
+ }
18260
19246
  }
18261
19247
  }),
18262
19248
  (0, react_jsx_runtime.jsx)("span", {
@@ -18265,7 +19251,45 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18265
19251
  children: searchMatches.length === 0 ? "0/0" : `${searchIndex % searchMatches.length + 1}/${searchMatches.length}`
18266
19252
  }),
18267
19253
  (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
18268
- label: `${t("action.prevDiff")} (Shift+F3)`,
19254
+ label: withChord(t("action.matchCase"), "matchCase"),
19255
+ side: "bottom",
19256
+ delayMs: 500,
19257
+ children: (0, react_jsx_runtime.jsx)("button", {
19258
+ type: "button",
19259
+ className: search.caseSensitive ? `${PendingPanel_module_css_default.searchToggle} ${PendingPanel_module_css_default.searchToggleOn}` : PendingPanel_module_css_default.searchToggle,
19260
+ "data-diff-search-case": true,
19261
+ "data-on": search.caseSensitive ? "" : void 0,
19262
+ "aria-label": t("action.matchCase"),
19263
+ "aria-pressed": search.caseSensitive,
19264
+ onClick: () => {
19265
+ andRefocus(() => {
19266
+ search.toggleCase();
19267
+ });
19268
+ },
19269
+ children: (0, react_jsx_runtime.jsx)(SearchOptionIcon, { kind: "case" })
19270
+ })
19271
+ }),
19272
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
19273
+ label: withChord(t("action.matchWholeWord"), "matchWholeWord"),
19274
+ side: "bottom",
19275
+ delayMs: 500,
19276
+ children: (0, react_jsx_runtime.jsx)("button", {
19277
+ type: "button",
19278
+ className: search.wholeWord ? `${PendingPanel_module_css_default.searchToggle} ${PendingPanel_module_css_default.searchToggleOn}` : PendingPanel_module_css_default.searchToggle,
19279
+ "data-diff-search-word": true,
19280
+ "data-on": search.wholeWord ? "" : void 0,
19281
+ "aria-label": t("action.matchWholeWord"),
19282
+ "aria-pressed": search.wholeWord,
19283
+ onClick: () => {
19284
+ andRefocus(() => {
19285
+ search.toggleWord();
19286
+ });
19287
+ },
19288
+ children: (0, react_jsx_runtime.jsx)(SearchOptionIcon, { kind: "word" })
19289
+ })
19290
+ }),
19291
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
19292
+ label: withChord(t("action.prevDiff"), "searchPrev"),
18269
19293
  side: "bottom",
18270
19294
  delayMs: 500,
18271
19295
  children: (0, react_jsx_runtime.jsx)("button", {
@@ -18275,13 +19299,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18275
19299
  "aria-label": t("action.prevDiff"),
18276
19300
  disabled: searchMatches.length === 0,
18277
19301
  onClick: () => {
18278
- goSearch(-1);
19302
+ andRefocus(() => {
19303
+ goSearch(-1);
19304
+ });
18279
19305
  },
18280
19306
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
18281
19307
  })
18282
19308
  }),
18283
19309
  (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
18284
- label: `${t("action.nextDiff")} (F3)`,
19310
+ label: withChord(t("action.nextDiff"), "searchNext"),
18285
19311
  side: "bottom",
18286
19312
  delayMs: 500,
18287
19313
  children: (0, react_jsx_runtime.jsx)("button", {
@@ -18291,7 +19317,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18291
19317
  "aria-label": t("action.nextDiff"),
18292
19318
  disabled: searchMatches.length === 0,
18293
19319
  onClick: () => {
18294
- goSearch(1);
19320
+ andRefocus(() => {
19321
+ goSearch(1);
19322
+ });
18295
19323
  },
18296
19324
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
18297
19325
  })
@@ -18422,6 +19450,21 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18422
19450
  function codeCellAt(node) {
18423
19451
  return ((node instanceof Element ? node : node.parentElement)?.closest("[data-diff-row], [data-diff-split-row]"))?.querySelector("[data-diff-code]") ?? null;
18424
19452
  }
19453
+ /**
19454
+ * The change block a rendered Markdown-preview node belongs to, or undefined (a
19455
+ * context block, or a node outside any tagged block). The preview tags every
19456
+ * changed run's element with `data-md-block`, carrying the same block index the
19457
+ * source view's `changeBlocksOf` derives over the same contents — so a preview
19458
+ * element and a source block name the same keep/revert target.
19459
+ * @param node - the node under the pointer.
19460
+ * @returns the block index, or undefined.
19461
+ */
19462
+ function previewBlockAt(node) {
19463
+ const raw = (node instanceof Element ? node : node?.parentElement ?? null)?.closest("[data-md-block]")?.getAttribute("data-md-block");
19464
+ if (raw === null || raw === void 0) return void 0;
19465
+ const index = Number(raw);
19466
+ return Number.isInteger(index) && index >= 0 ? index : void 0;
19467
+ }
18425
19468
  /** Character offset of a selection boundary within its line's code text. A
18426
19469
  * boundary outside the code cell (the line-number gutter) sits at the line's
18427
19470
  * start — offset 0, never the line's end — so it never skips the line. */
@@ -18590,7 +19633,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18590
19633
  });
18591
19634
  }
18592
19635
  /** The selected file's diff, actions, jump controls, and copy toolbar. */
18593
- function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, failedMessage, onPasteReference, onToast, t, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, floatMode, floatOpen, onToggleFileList }) {
19636
+ function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, failedMessage, onPasteReference, onToast, t, onKeep, onRevert, onRefreshVcs, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, floatMode, floatOpen, onToggleFileList }) {
18594
19637
  const [langOverride, setLangOverride] = (0, react.useState)(void 0);
18595
19638
  const [langMenuOpen, setLangMenuOpen] = (0, react.useState)(false);
18596
19639
  const langMenuItems = (0, react.useMemo)(() => [{
@@ -18601,8 +19644,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18601
19644
  label: languageDisplayName(language)
18602
19645
  }))], [t]);
18603
19646
  const detectedLang = (0, react.useMemo)(() => langFromPath(file.path), [file.path]);
18604
- const lang = (0, react.useMemo)(() => langOverride ?? detectedLang, [detectedLang, langOverride]);
18605
- const langLabel = langOverride === void 0 ? detectedLang === void 0 ? t("action.langAuto") : t("action.langAutoDetected", { lang: languageDisplayName(detectedLang) }) : languageDisplayName(langOverride);
19647
+ const langSuffix = (0, react.useMemo)(() => suffixOfPath(file.path), [file.path]);
19648
+ const rememberedLang = (0, react.useMemo)(() => langSuffix === void 0 ? void 0 : languageForSuffix(langSuffix), [langSuffix]);
19649
+ const effectiveLang = langOverride ?? rememberedLang;
19650
+ const lang = (0, react.useMemo)(() => effectiveLang ?? detectedLang, [detectedLang, effectiveLang]);
19651
+ const langLabel = effectiveLang === void 0 ? detectedLang === void 0 ? t("action.langAuto") : t("action.langAutoDetected", { lang: languageDisplayName(detectedLang) }) : languageDisplayName(effectiveLang);
18606
19652
  const wrapKey = lang ?? "";
18607
19653
  const [langWrap, setLangWrap] = (0, react.useState)(() => wrapEnabled(wrapKey));
18608
19654
  (0, react.useEffect)(() => {
@@ -18616,6 +19662,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18616
19662
  const [tabWidthSpaces] = (0, react.useState)(() => tabWidth());
18617
19663
  const [splitView, setSplitView] = (0, react.useState)(() => splitMode());
18618
19664
  const [mdPreview, setMdPreview] = (0, react.useState)(() => mdPreviewEnabled());
19665
+ const previewActive = mdPreview && lang === "markdown";
18619
19666
  const mdPreviewBodyRef = (0, react.useRef)(null);
18620
19667
  const [mdRulerMarkers, setMdRulerMarkers] = (0, react.useState)([]);
18621
19668
  const [mdImageTick, setMdImageTick] = (0, react.useState)(0);
@@ -18657,12 +19704,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18657
19704
  ]);
18658
19705
  const splitPairs = (0, react.useMemo)(() => splitView ? computeSideBySideDiff(model.diff.rows, true).pairs : null, [splitView, model]);
18659
19706
  (0, react.useEffect)(() => {
18660
- if (!mdPreview) return;
19707
+ if (!previewActive) return;
18661
19708
  const body = mdPreviewBodyRef.current;
18662
19709
  if (body === null) return;
18663
19710
  resolvePreviewImages(body, file.path, workspacePath, (path) => onPreviewImage(file.sessionId, path)).then(() => setMdImageTick((tick) => tick + 1));
18664
19711
  }, [
18665
- mdPreview,
19712
+ previewActive,
18666
19713
  splitView,
18667
19714
  file.path,
18668
19715
  file.sessionId,
@@ -18705,37 +19752,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18705
19752
  return markers;
18706
19753
  }, [model]);
18707
19754
  (0, react.useLayoutEffect)(() => {
18708
- if (!mdPreview || splitView) return;
19755
+ if (!previewActive) return;
18709
19756
  const body = mdPreviewBodyRef.current;
18710
19757
  if (body === null) return;
18711
19758
  const measured = markdownPreviewMarkers(body);
18712
19759
  setMdRulerMarkers(measured.length > 0 ? measured : rulerMarkers);
18713
19760
  }, [
18714
- mdPreview,
19761
+ previewActive,
18715
19762
  splitView,
18716
19763
  file.oldText,
18717
19764
  file.newText,
18718
19765
  mdImageTick,
18719
19766
  rulerMarkers
18720
19767
  ]);
18721
- const [runs, setRuns] = (0, react.useState)(void 0);
18722
- (0, react.useEffect)(() => {
18723
- setRuns(void 0);
18724
- const timer = window.setTimeout(() => {
18725
- setRuns({
18726
- oldRuns: highlightLines(file.oldText, lang),
18727
- newRuns: highlightLines(file.newText, lang)
18728
- });
18729
- }, 0);
18730
- return () => {
18731
- window.clearTimeout(timer);
18732
- };
18733
- }, [
19768
+ const oldLines = (0, react.useMemo)(() => file.oldText.split("\n"), [file.oldText]);
19769
+ const newLines = (0, react.useMemo)(() => file.newText.split("\n"), [file.newText]);
19770
+ const highlightKey = (0, react.useMemo)(() => ({
19771
+ file: file.id,
19772
+ lang
19773
+ }), [
18734
19774
  file.id,
19775
+ lang,
18735
19776
  file.oldText,
18736
- file.newText,
18737
- lang
19777
+ file.newText
18738
19778
  ]);
19779
+ const [splitVisible, setSplitVisible] = (0, react.useState)(void 0);
18739
19780
  const bodyRef = (0, react.useRef)(null);
18740
19781
  const [focus, setFocus] = (0, react.useState)(0);
18741
19782
  const [scrollTick, setScrollTick] = (0, react.useState)(0);
@@ -18745,10 +19786,23 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18745
19786
  const [hScrollbarPx, setHScrollbarPx] = (0, react.useState)(0);
18746
19787
  const [hoveredBlock, setHoveredBlock] = (0, react.useState)(void 0);
18747
19788
  const [selection, setSelection] = (0, react.useState)(void 0);
19789
+ const [previewFrame, setPreviewFrame] = (0, react.useState)(void 0);
19790
+ const [previewCovered, setPreviewCovered] = (0, react.useState)([]);
19791
+ const [previewSelectionFrame, setPreviewSelectionFrame] = (0, react.useState)(void 0);
19792
+ const previewScrollTopRef = (0, react.useRef)(0);
19793
+ const previewHoverFrameRef = (0, react.useRef)(null);
19794
+ const previewSelectionFrameRef = (0, react.useRef)(null);
19795
+ const previewSearchHitsRef = (0, react.useRef)([]);
19796
+ const [previewHitCount, setPreviewHitCount] = (0, react.useState)(0);
19797
+ const previewSearchAnchorRef = (0, react.useRef)(false);
19798
+ const [previewFlash, setPreviewFlash] = (0, react.useState)(void 0);
19799
+ const previewFlashRef = (0, react.useRef)(null);
19800
+ const previewFlashPendingRef = (0, react.useRef)(false);
18748
19801
  const selectionTextRef = (0, react.useRef)("");
18749
19802
  const [copied, setCopied] = (0, react.useState)(false);
18750
19803
  const [searchOpen, setSearchOpen] = (0, react.useState)(false);
18751
19804
  const [searchQuery, setSearchQuery] = (0, react.useState)("");
19805
+ const search = useSearchOptions();
18752
19806
  const [searchIndex, setSearchIndex] = (0, react.useState)(0);
18753
19807
  const searchInputRef = (0, react.useRef)(null);
18754
19808
  const [flashKey, setFlashKey] = (0, react.useState)(0);
@@ -18756,6 +19810,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18756
19810
  const bumpFlash = (shake) => {
18757
19811
  pinShakeRef.current = shake;
18758
19812
  setFlashKey((prev) => prev + 1);
19813
+ previewFlashPendingRef.current = true;
18759
19814
  };
18760
19815
  (0, react.useEffect)(() => {
18761
19816
  setFocus(0);
@@ -18781,6 +19836,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18781
19836
  return model.blocks.map((block) => blockRangesOf(model.diff.rows, block));
18782
19837
  }, [model]);
18783
19838
  const coveredBlockIndices = (0, react.useMemo)(() => {
19839
+ if (previewActive) return previewCovered;
18784
19840
  if (selection === void 0 || splitView) return [];
18785
19841
  const covered = [];
18786
19842
  for (let index = 0; index < model.blocks.length; index++) {
@@ -18791,7 +19847,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18791
19847
  }, [
18792
19848
  selection,
18793
19849
  splitView,
18794
- model
19850
+ model,
19851
+ mdPreview,
19852
+ lang,
19853
+ previewCovered
18795
19854
  ]);
18796
19855
  const selectionRange = (0, react.useMemo)(() => {
18797
19856
  if (coveredBlockIndices.length === 0) return void 0;
@@ -18806,10 +19865,324 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18806
19865
  end: last.end
18807
19866
  });
18808
19867
  }, [coveredBlockIndices, model]);
18809
- const searchMatches = (0, react.useMemo)(() => matchingRows(model.diff.rows, searchQuery), [model, searchQuery]);
19868
+ const searchMatches = (0, react.useMemo)(() => matchingRows(model.diff.rows, searchQuery, search.options), [
19869
+ model,
19870
+ searchQuery,
19871
+ search.options
19872
+ ]);
18810
19873
  const searchHitSet = (0, react.useMemo)(() => new Set(searchMatches), [searchMatches]);
18811
19874
  const currentSearchRow = searchMatches.length === 0 ? void 0 : searchMatches[searchIndex % searchMatches.length];
19875
+ const searchMatchCount = previewActive ? previewHitCount : searchMatches.length;
19876
+ /**
19877
+ * Measure one frame's placement for a set of change blocks in the Markdown
19878
+ * preview. The block's bottom is stored in *content* coordinates (scroll
19879
+ * independent), so the scroll path can move the frame without re-measuring or
19880
+ * re-rendering — the preview's markdown re-render is far too heavy to run per
19881
+ * scroll event.
19882
+ *
19883
+ * - vertically at the block group's bottom edge, clamped into the pane exactly
19884
+ * like the code view (`pane height - frame height`), so a block near or past
19885
+ * the bottom keeps its actions visible at the pane's bottom edge;
19886
+ * - horizontally at the *content's* right edge, because the preview content is
19887
+ * centred under a max width — anchoring to the pane would leave the frame
19888
+ * stranded in the empty margin on a wide panel.
19889
+ * @param indices - the block indices the frame acts on.
19890
+ * @returns the placement, or undefined when nothing is rendered.
19891
+ */
19892
+ const previewFrameFor = (0, react.useCallback)((indices) => {
19893
+ const body = mdPreviewBodyRef.current;
19894
+ if (body === null || indices.length === 0) return void 0;
19895
+ const bodyRect = body.getBoundingClientRect();
19896
+ let bottom = -Infinity;
19897
+ for (const index of indices) for (const element of body.querySelectorAll(`[data-md-block="${index}"]`)) bottom = Math.max(bottom, element.getBoundingClientRect().bottom);
19898
+ if (!Number.isFinite(bottom)) return void 0;
19899
+ const content = body.querySelector("[data-diff-md-preview-content]");
19900
+ const contentRight = content === null ? bodyRect.right : content.getBoundingClientRect().right;
19901
+ previewScrollTopRef.current = body.scrollTop;
19902
+ return {
19903
+ contentBottom: bottom - bodyRect.top + body.scrollTop,
19904
+ maxTop: Math.max(0, body.clientHeight - BLOCK_ACTIONS_FRAME_PX),
19905
+ right: Math.max(0, bodyRect.right - contentRight) + FRAME_INSET_PX
19906
+ };
19907
+ }, []);
19908
+ /** One placement's viewport top at a given scroll offset, clamped into the pane. */
19909
+ const previewFrameTop = (frame, scrollTop) => frame === void 0 ? void 0 : Math.min(Math.max(0, frame.contentBottom - scrollTop - 2), frame.maxTop);
19910
+ /** Write one placement onto its element right away, so a scroll tracks the pane
19911
+ * in the same frame as the content instead of waiting for a React render. */
19912
+ const applyPreviewFrame = (element, frame) => {
19913
+ if (element === null || frame === void 0) return;
19914
+ const top = previewFrameTop(frame, previewScrollTopRef.current);
19915
+ if (top === void 0) return;
19916
+ element.style.top = `${top}px`;
19917
+ element.style.right = `${frame.right}px`;
19918
+ };
19919
+ /**
19920
+ * Measure the flash box for one preview change block: the union of the block's
19921
+ * rendered elements, in content coordinates. The box is the background-diff
19922
+ * area itself — the code view's outline spans its pane the same way — so
19923
+ * vertically it is the tinted block, and horizontally the widest thing that
19924
+ * tint belongs to: its own edge in the single column, and the *aligned row* in
19925
+ * the double column, which spans both columns even when the change exists on
19926
+ * one side only. The code view derives that from row offsets; rendered
19927
+ * Markdown has no rows, so the elements are measured.
19928
+ * @param index - the change block to outline.
19929
+ * @returns the placement, or undefined when the block is not rendered.
19930
+ */
19931
+ const previewFlashFor = (index) => {
19932
+ const body = mdPreviewBodyRef.current;
19933
+ if (body === null) return void 0;
19934
+ const bodyRect = body.getBoundingClientRect();
19935
+ let top = Infinity;
19936
+ let bottom = -Infinity;
19937
+ let left = Infinity;
19938
+ let right = -Infinity;
19939
+ for (const element of body.querySelectorAll(`[data-md-block="${index}"]`)) {
19940
+ const rect = element.getBoundingClientRect();
19941
+ top = Math.min(top, rect.top);
19942
+ bottom = Math.max(bottom, rect.bottom);
19943
+ const row = element.closest(".mdDoubleRow");
19944
+ const span = row === null ? rect : row.getBoundingClientRect();
19945
+ left = Math.min(left, span.left);
19946
+ right = Math.max(right, span.right);
19947
+ }
19948
+ if (!Number.isFinite(top) || !Number.isFinite(bottom)) return void 0;
19949
+ return {
19950
+ contentTop: top - bodyRect.top + body.scrollTop,
19951
+ contentBottom: bottom - bodyRect.top + body.scrollTop,
19952
+ left: Math.max(0, left - bodyRect.left),
19953
+ right: Math.max(0, bodyRect.right - right)
19954
+ };
19955
+ };
19956
+ /** One flash's viewport box at a given scroll offset. The block is clamped into
19957
+ * the pane exactly like the code view clamps its rows, so a block taller than
19958
+ * the pane outlines the visible part instead of running past the edge. */
19959
+ const previewFlashBox = (flash, scrollTop) => {
19960
+ if (flash === void 0) return void 0;
19961
+ const paneHeight = mdPreviewBodyRef.current?.clientHeight ?? 0;
19962
+ const top = Math.max(0, flash.contentTop - scrollTop);
19963
+ const bottom = Math.min(paneHeight > 0 ? paneHeight : Number.POSITIVE_INFINITY, flash.contentBottom - scrollTop);
19964
+ return {
19965
+ top,
19966
+ height: Math.max(0, bottom - top)
19967
+ };
19968
+ };
19969
+ /** Write the flash box onto its element right away (the scroll path, so the box
19970
+ * tracks its block for the second it is visible). */
19971
+ const applyPreviewFlash = (element, flash) => {
19972
+ if (element === null || flash === void 0) return;
19973
+ const box = previewFlashBox(flash, previewScrollTopRef.current);
19974
+ if (box === void 0) return;
19975
+ element.style.top = `${box.top}px`;
19976
+ element.style.height = `${box.height}px`;
19977
+ element.style.left = `${flash.left}px`;
19978
+ element.style.right = `${flash.right}px`;
19979
+ };
19980
+ /** Re-place the two action frames at the mirrored scroll offset. */
19981
+ const applyPreviewFrames = () => {
19982
+ applyPreviewFrame(previewHoverFrameRef.current, previewFrame);
19983
+ applyPreviewFrame(previewSelectionFrameRef.current, previewSelectionFrame);
19984
+ };
19985
+ /** Re-place every preview overlay: the frames follow the pointer and the
19986
+ * selection, the flash box the last jump. Only a user scroll uses this — the
19987
+ * landing path re-measures the flash instead, and writing the previous
19988
+ * placement there would stomp the element that React then declines to
19989
+ * rewrite (its style values come out equal, so the write is skipped). */
19990
+ const applyPreviewOverlays = () => {
19991
+ applyPreviewFrames();
19992
+ applyPreviewFlash(previewFlashRef.current, previewFlash);
19993
+ };
19994
+ (0, react.useLayoutEffect)(() => {
19995
+ if (!previewActive) return;
19996
+ setPreviewFrame(hoveredBlock === void 0 ? void 0 : previewFrameFor([hoveredBlock]));
19997
+ }, [
19998
+ mdPreview,
19999
+ lang,
20000
+ hoveredBlock,
20001
+ file.oldText,
20002
+ file.newText,
20003
+ splitView,
20004
+ mdImageTick,
20005
+ previewFrameFor
20006
+ ]);
20007
+ (0, react.useLayoutEffect)(() => {
20008
+ if (!previewActive) return;
20009
+ setPreviewSelectionFrame(previewFrameFor(previewCovered));
20010
+ }, [
20011
+ mdPreview,
20012
+ lang,
20013
+ previewCovered,
20014
+ file.oldText,
20015
+ file.newText,
20016
+ splitView,
20017
+ mdImageTick,
20018
+ previewFrameFor
20019
+ ]);
20020
+ (0, react.useEffect)(() => {
20021
+ setHoveredBlock(void 0);
20022
+ setPreviewCovered([]);
20023
+ }, [mdPreview, lang]);
20024
+ /** The change blocks a live native selection inside the preview fully covers,
20025
+ * in order — the preview's counterpart of the code view's row-range rule. */
20026
+ const coveredPreviewBlocks = () => {
20027
+ const body = mdPreviewBodyRef.current;
20028
+ const live = window.getSelection();
20029
+ if (body === null || live === null || live.isCollapsed || live.rangeCount === 0) return [];
20030
+ const range = live.getRangeAt(0);
20031
+ if (!body.contains(range.commonAncestorContainer)) return [];
20032
+ const covered = [];
20033
+ for (let index = 0; index < model.blocks.length; index++) {
20034
+ const elements = body.querySelectorAll(`[data-md-block="${index}"]`);
20035
+ if (elements.length === 0) continue;
20036
+ let inside = true;
20037
+ for (const element of elements) {
20038
+ const blockRange = document.createRange();
20039
+ blockRange.selectNode(element);
20040
+ if (range.compareBoundaryPoints(Range.START_TO_START, blockRange) > 0 || range.compareBoundaryPoints(Range.END_TO_END, blockRange) < 0) {
20041
+ inside = false;
20042
+ break;
20043
+ }
20044
+ }
20045
+ if (inside) covered.push(index);
20046
+ }
20047
+ return covered;
20048
+ };
20049
+ /**
20050
+ * Bring one preview block into view. The code view scrolls arithmetically on
20051
+ * row offsets; rendered Markdown has no rows to count, so the block's own
20052
+ * element is measured instead. Its top edge lands `leadRows` code rows below
20053
+ * the pane's top — the same lead the code view leaves — clamped to the scroll
20054
+ * range, so a jump always lands the block in the same place (a bare
20055
+ * `scrollIntoView({ block: 'nearest' })` left it flush against whichever edge
20056
+ * it came from). The settled offset is mirrored into the frames right away: a
20057
+ * programmatic `scrollTop` fires `scroll` no earlier than the next task, and
20058
+ * the frames must not lag the content.
20059
+ * @param index - the change block to bring into view.
20060
+ */
20061
+ const scrollPreviewBlockIntoView = (index) => {
20062
+ const body = mdPreviewBodyRef.current;
20063
+ if (body === null) return;
20064
+ const element = body.querySelector(`[data-md-block="${index}"]`);
20065
+ if (element === null) return;
20066
+ const elementTop = element.getBoundingClientRect().top - body.getBoundingClientRect().top + body.scrollTop;
20067
+ const maxTop = Math.max(0, body.scrollHeight - body.clientHeight);
20068
+ const target = Math.max(0, Math.min(elementTop - leadRows * ROW_HEIGHT_PX, maxTop));
20069
+ if (body.scrollTop !== target) body.scrollTop = target;
20070
+ previewScrollTopRef.current = body.scrollTop;
20071
+ applyPreviewFrames();
20072
+ };
20073
+ /**
20074
+ * Bring one preview search occurrence into view. The code view's search rule is
20075
+ * reused: a hit that is already fully visible is left alone (a search must not
20076
+ * yank the pane off what the user can see), one above the viewport lands with
20077
+ * the lead rows above it — the block-jump rule — and one below lands at the
20078
+ * bottom edge. The settled offset is mirrored into the frames, as for a jump.
20079
+ * @param hit - the highlighted occurrence to reveal.
20080
+ */
20081
+ const scrollPreviewHitIntoView = (hit) => {
20082
+ const body = mdPreviewBodyRef.current;
20083
+ if (body === null) return;
20084
+ const bodyRect = body.getBoundingClientRect();
20085
+ const hitTop = hit.getBoundingClientRect().top - bodyRect.top + body.scrollTop;
20086
+ const hitBottom = hit.getBoundingClientRect().bottom - bodyRect.top + body.scrollTop;
20087
+ const viewTop = body.scrollTop;
20088
+ const viewBottom = viewTop + body.clientHeight;
20089
+ let target;
20090
+ if (hitTop - leadRows * ROW_HEIGHT_PX < viewTop) target = hitTop - leadRows * ROW_HEIGHT_PX;
20091
+ else if (hitBottom > viewBottom) target = hitBottom - body.clientHeight;
20092
+ if (target === void 0) return;
20093
+ const maxTop = Math.max(0, body.scrollHeight - body.clientHeight);
20094
+ const clamped = Math.max(0, Math.min(target, maxTop));
20095
+ if (body.scrollTop !== clamped) body.scrollTop = clamped;
20096
+ previewScrollTopRef.current = body.scrollTop;
20097
+ applyPreviewOverlays();
20098
+ };
20099
+ /**
20100
+ * Highlight the query inside the rendered preview. The preview's HTML is one
20101
+ * sanitized string (a flat blob to React) and re-rendering the Markdown per
20102
+ * keystroke is far too heavy, so the matches are wrapped onto the text nodes
20103
+ * that are already there — same matcher, same colors as the code view. The
20104
+ * wrapped elements become the preview's match list, since rendered Markdown
20105
+ * has no rows to map hits onto. A match Markdown split across elements (say
20106
+ * `**bo**ld` for `bold`) is not found: matching what is rendered cannot see
20107
+ * through the markup, which is the honest trade for highlighting the text the
20108
+ * user is actually looking at.
20109
+ */
20110
+ (0, react.useLayoutEffect)(() => {
20111
+ for (const hit of previewSearchHitsRef.current) {
20112
+ hit.replaceWith(document.createTextNode(hit.textContent ?? ""));
20113
+ hit.parentNode?.normalize();
20114
+ }
20115
+ previewSearchHitsRef.current = [];
20116
+ const body = mdPreviewBodyRef.current;
20117
+ const content = body?.querySelector("[data-diff-md-preview-content]") ?? null;
20118
+ if (!previewActive || content === null || searchQuery === "") {
20119
+ setPreviewHitCount(0);
20120
+ return;
20121
+ }
20122
+ const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT);
20123
+ const texts = [];
20124
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) texts.push(node);
20125
+ const hits = [];
20126
+ for (const node of texts) {
20127
+ const parent = node.parentElement;
20128
+ if (parent === null || parent.closest("script,style") !== null) continue;
20129
+ const ranges = matchRangesOf(node.data, searchQuery, search.options);
20130
+ if (ranges.length === 0) continue;
20131
+ const marks = [];
20132
+ for (let index = ranges.length - 1; index >= 0; index--) {
20133
+ const [start, end] = ranges[index];
20134
+ const range = document.createRange();
20135
+ range.setStart(node, start);
20136
+ range.setEnd(node, end);
20137
+ const mark = document.createElement("mark");
20138
+ mark.className = PendingPanel_module_css_default.searchMatch ?? "";
20139
+ mark.setAttribute("data-diff-search-match", "hit");
20140
+ range.surroundContents(mark);
20141
+ marks.push(mark);
20142
+ }
20143
+ hits.push(...marks.reverse());
20144
+ }
20145
+ previewSearchHitsRef.current = hits;
20146
+ setPreviewHitCount(hits.length);
20147
+ if (previewSearchAnchorRef.current) {
20148
+ previewSearchAnchorRef.current = false;
20149
+ const bodyTop = body === null ? 0 : body.getBoundingClientRect().top;
20150
+ const scrollTop = body?.scrollTop ?? 0;
20151
+ const at = hits.findIndex((hit) => hit.getBoundingClientRect().top - bodyTop + scrollTop >= scrollTop - 1);
20152
+ setSearchIndex(at === -1 ? 0 : at);
20153
+ }
20154
+ }, [
20155
+ previewActive,
20156
+ searchQuery,
20157
+ search.options,
20158
+ file.oldText,
20159
+ file.newText,
20160
+ splitView
20161
+ ]);
20162
+ (0, react.useEffect)(() => {
20163
+ const hits = previewSearchHitsRef.current;
20164
+ if (!previewActive || hits.length === 0) return;
20165
+ const current = searchIndex % hits.length;
20166
+ hits.forEach((hit, index) => {
20167
+ const isCurrent = index === current;
20168
+ hit.className = (isCurrent ? PendingPanel_module_css_default.searchMatchCurrent : PendingPanel_module_css_default.searchMatch) ?? "";
20169
+ hit.setAttribute("data-diff-search-match", isCurrent ? "current" : "hit");
20170
+ });
20171
+ }, [
20172
+ previewActive,
20173
+ previewHitCount,
20174
+ searchIndex
20175
+ ]);
18812
20176
  const goSearch = (direction) => {
20177
+ if (previewActive) {
20178
+ cursorPosRef.current = void 0;
20179
+ const hits = previewSearchHitsRef.current;
20180
+ if (hits.length === 0) return;
20181
+ const next = (searchIndex % hits.length + direction + hits.length) % hits.length;
20182
+ setSearchIndex(next);
20183
+ scrollPreviewHitIntoView(hits[next]);
20184
+ return;
20185
+ }
18813
20186
  if (searchMatches.length === 0) return;
18814
20187
  if (cursorPosRef.current !== void 0) {
18815
20188
  setSearchIndex(startIndexFor(searchQuery));
@@ -18824,7 +20197,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18824
20197
  * start (the "no cursor" fallback).
18825
20198
  */
18826
20199
  const startIndexFor = (value) => {
18827
- const matches = value === "" ? [] : matchingRows(model.diff.rows, value);
20200
+ const matches = value === "" ? [] : matchingRows(model.diff.rows, value, search.options);
18828
20201
  const pos = cursorPosRef.current;
18829
20202
  cursorPosRef.current = void 0;
18830
20203
  if (matches.length === 0) return 0;
@@ -18843,15 +20216,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18843
20216
  searchInputRef.current?.select();
18844
20217
  return;
18845
20218
  }
20219
+ search.sync();
18846
20220
  const live = window.getSelection();
18847
- const liveRange = splitView ? splitRowRangeOf(live) : rowRangeOf(live);
20221
+ const liveRange = splitView && !previewActive ? splitRowRangeOf(live) : rowRangeOf(live);
18848
20222
  const pos = liveRange !== void 0 ? liveRange : selection;
18849
20223
  cursorPosRef.current = pos;
18850
20224
  lastRecordedCursorRef.current = pos;
18851
20225
  const liveText = (live?.toString() ?? "").trim();
18852
20226
  const value = liveText !== "" && !liveText.includes("\n") ? liveText : selectionTextRef.current;
18853
20227
  setSearchQuery(value);
18854
- setSearchIndex(startIndexFor(value));
20228
+ if (previewActive) {
20229
+ previewSearchAnchorRef.current = true;
20230
+ setSearchIndex(0);
20231
+ } else setSearchIndex(startIndexFor(value));
18855
20232
  setSearchOpen(true);
18856
20233
  requestAnimationFrame(() => {
18857
20234
  searchInputRef.current?.focus();
@@ -18862,7 +20239,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18862
20239
  openSearchRef.current = openSearchWithSelection;
18863
20240
  const searchOpenRef = (0, react.useRef)(searchOpen);
18864
20241
  searchOpenRef.current = searchOpen;
20242
+ /** Run one bar control's action, then hand the focus back to the query box:
20243
+ * the bar's chords are scoped to the box (its Esc to the bar), so a mouse
20244
+ * click on a bar button must not leave them dead on the button it landed on. */
20245
+ const andRefocus = (action) => {
20246
+ action();
20247
+ searchInputRef.current?.focus();
20248
+ };
18865
20249
  const toggleSearch = () => {
20250
+ if (splitView && !previewActive) {
20251
+ splitDiffRef.current?.toggleSearch();
20252
+ return;
20253
+ }
18866
20254
  if (searchOpen) {
18867
20255
  cursorPosRef.current = void 0;
18868
20256
  lastRecordedCursorRef.current = void 0;
@@ -18965,6 +20353,28 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18965
20353
  end = Math.max(end, selection.end + 1);
18966
20354
  }
18967
20355
  const visibleRows = rows.slice(start, end);
20356
+ const oldWindow = (0, react.useMemo)(() => windowLineRange(rows, start, end, "old"), [
20357
+ rows,
20358
+ start,
20359
+ end
20360
+ ]);
20361
+ const newWindow = (0, react.useMemo)(() => windowLineRange(rows, start, end, "new"), [
20362
+ rows,
20363
+ start,
20364
+ end
20365
+ ]);
20366
+ const onSplitVisibleLines = (0, react.useCallback)((visible) => {
20367
+ setSplitVisible(visible);
20368
+ }, []);
20369
+ const runs = useWindowedHighlight({
20370
+ key: highlightKey,
20371
+ oldLines,
20372
+ newLines,
20373
+ lang,
20374
+ oldRange: splitView ? splitVisible?.oldRange : oldWindow,
20375
+ newRange: splitView ? splitVisible?.newRange : newWindow,
20376
+ live: splitView ? splitVisible?.live ?? false : viewportHeight > 0
20377
+ });
18968
20378
  const blockEnd = hoveredBlock === void 0 ? void 0 : model.blocks[hoveredBlock]?.end;
18969
20379
  const blockActionsTop = blockEnd === void 0 ? 0 : Math.max(0, Math.min(offsetOf(blockEnd + 1) - scrollTop - 2, Math.max(0, viewportHeight - BLOCK_ACTIONS_FRAME_PX)));
18970
20380
  const selectionBlockEnd = (() => {
@@ -18994,7 +20404,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18994
20404
  }, [
18995
20405
  file.id,
18996
20406
  splitView,
18997
- mdPreview
20407
+ previewActive
18998
20408
  ]);
18999
20409
  (0, react.useEffect)(() => {
19000
20410
  const body = bodyRef.current;
@@ -19012,12 +20422,20 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19012
20422
  }, [
19013
20423
  file.id,
19014
20424
  langWrap,
19015
- mdPreview
20425
+ previewActive
19016
20426
  ]);
19017
20427
  (0, react.useLayoutEffect)(() => {
19018
20428
  if (rowCount === 0) return;
19019
20429
  const block = model.blocks[focus];
19020
20430
  if (block === void 0) return;
20431
+ if (previewActive) {
20432
+ scrollPreviewBlockIntoView(focus);
20433
+ if (previewFlashPendingRef.current) {
20434
+ previewFlashPendingRef.current = false;
20435
+ setPreviewFlash(previewFlashFor(focus));
20436
+ }
20437
+ return;
20438
+ }
19021
20439
  const body = bodyRef.current;
19022
20440
  if (body === null) return;
19023
20441
  const target = offsetOf(block.start) - leadRows * ROW_HEIGHT_PX;
@@ -19027,7 +20445,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19027
20445
  }, [
19028
20446
  scrollTick,
19029
20447
  rowOffsets === null,
19030
- mdPreview
20448
+ previewActive
19031
20449
  ]);
19032
20450
  const wrapArmedRef = (0, react.useRef)(0);
19033
20451
  const jump = (direction, wrapGuard = false, singleToast = wrapGuard) => {
@@ -19047,7 +20465,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19047
20465
  wrapArmedRef.current = 0;
19048
20466
  } else wrapArmedRef.current = 0;
19049
20467
  }
19050
- setFocus((current) => {
20468
+ const targetOf = (current) => {
19051
20469
  if (direction === -1) return (current - 1 + count) % count;
19052
20470
  const top = bodyRef.current?.scrollTop ?? 0;
19053
20471
  for (let index = current + 1; index < count; index++) {
@@ -19056,12 +20474,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19056
20474
  if (offsetOf(block.start) >= top) return index;
19057
20475
  }
19058
20476
  return 0;
19059
- });
20477
+ };
20478
+ const landed = targetOf(focus);
20479
+ setFocus(landed);
19060
20480
  setScrollTick((tick) => tick + 1);
19061
20481
  bumpFlash(false);
19062
20482
  };
19063
20483
  const jumpBlock = (direction, wrapGuard = false, singleToast = wrapGuard) => {
19064
- if (splitView) {
20484
+ if (splitView && !previewActive) {
19065
20485
  splitDiffRef.current?.jump(direction, wrapGuard, singleToast);
19066
20486
  return;
19067
20487
  }
@@ -19100,6 +20520,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19100
20520
  if (firstCovered === void 0) return;
19101
20521
  await runBlockAction(action, selectionRange, firstCovered);
19102
20522
  setSelection(void 0);
20523
+ setPreviewCovered([]);
19103
20524
  window.getSelection()?.removeAllRanges?.();
19104
20525
  };
19105
20526
  (0, react.useEffect)(() => {
@@ -19130,6 +20551,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19130
20551
  };
19131
20552
  (0, react.useEffect)(() => {
19132
20553
  const update = () => {
20554
+ if (previewActive) {
20555
+ setPreviewCovered(coveredPreviewBlocks());
20556
+ selectionTextRef.current = "";
20557
+ return;
20558
+ }
19133
20559
  const live = window.getSelection();
19134
20560
  const range = splitView ? splitRowRangeOf(live) : rowRangeOf(live);
19135
20561
  let text = "";
@@ -19151,7 +20577,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19151
20577
  return () => {
19152
20578
  document.removeEventListener("selectionchange", update);
19153
20579
  };
19154
- }, [file.id, splitView]);
20580
+ }, [
20581
+ file.id,
20582
+ splitView,
20583
+ mdPreview,
20584
+ lang
20585
+ ]);
19155
20586
  (0, react.useEffect)(() => {
19156
20587
  const onCopy = (event) => {
19157
20588
  const anchor = window.getSelection()?.anchorNode;
@@ -19219,8 +20650,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19219
20650
  (0, react.useEffect)(() => {
19220
20651
  const onKeyDown = (event) => {
19221
20652
  if (!matchesShortcut(event, keybindingOf("openSearch"))) return;
20653
+ if (pathPickerOpen()) return;
19222
20654
  event.preventDefault();
19223
- if (splitView) {
20655
+ if (splitView && !previewActive) {
19224
20656
  splitDiffRef.current?.openSearch();
19225
20657
  return;
19226
20658
  }
@@ -19233,15 +20665,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19233
20665
  }, [searchOpen, splitView]);
19234
20666
  (0, react.useEffect)(() => {
19235
20667
  const onKeyDown = (event) => {
20668
+ if (!isSearchInputEvent(event)) return;
19236
20669
  let direction = 0;
19237
20670
  if (matchesShortcut(event, keybindingOf("searchNext"))) direction = 1;
19238
20671
  else if (matchesShortcut(event, keybindingOf("searchPrev"))) direction = -1;
19239
20672
  if (direction === 0) return;
19240
- if (splitView) {
20673
+ if (splitView && !previewActive) {
19241
20674
  if (splitDiffRef.current?.searchNext(direction)) event.preventDefault();
19242
20675
  return;
19243
20676
  }
19244
- if (searchOpen && searchMatches.length > 0) {
20677
+ if (searchOpen && searchMatchCount > 0) {
19245
20678
  event.preventDefault();
19246
20679
  goSearch(direction);
19247
20680
  }
@@ -19253,7 +20686,55 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19253
20686
  }, [
19254
20687
  searchOpen,
19255
20688
  searchMatches,
19256
- splitView
20689
+ searchMatchCount,
20690
+ splitView,
20691
+ previewActive
20692
+ ]);
20693
+ (0, react.useEffect)(() => {
20694
+ const onKeyDown = (event) => {
20695
+ if (!isSearchInputEvent(event)) return;
20696
+ const option = matchesShortcut(event, keybindingOf("matchCase")) ? "case" : matchesShortcut(event, keybindingOf("matchWholeWord")) ? "word" : void 0;
20697
+ if (option === void 0) return;
20698
+ if (splitView && !previewActive) {
20699
+ const handle = splitDiffRef.current;
20700
+ if (option === "case" ? handle?.toggleMatchCase() === true : handle?.toggleMatchWholeWord() === true) event.preventDefault();
20701
+ return;
20702
+ }
20703
+ if (!searchOpen) return;
20704
+ event.preventDefault();
20705
+ if (option === "case") search.toggleCase();
20706
+ else search.toggleWord();
20707
+ };
20708
+ window.addEventListener("keydown", onKeyDown, true);
20709
+ return () => {
20710
+ window.removeEventListener("keydown", onKeyDown, true);
20711
+ };
20712
+ }, [
20713
+ searchOpen,
20714
+ splitView,
20715
+ previewActive,
20716
+ search
20717
+ ]);
20718
+ (0, react.useEffect)(() => {
20719
+ const onKeyDown = (event) => {
20720
+ if (event.key !== "Escape" || !isInPanelEvent(event)) return;
20721
+ if (pathPickerOpen()) return;
20722
+ if (!previewActive && splitView) {
20723
+ if (splitDiffRef.current?.closeSearch() === true) event.preventDefault();
20724
+ return;
20725
+ }
20726
+ if (!searchOpen) return;
20727
+ event.preventDefault();
20728
+ closeSearch();
20729
+ };
20730
+ window.addEventListener("keydown", onKeyDown, true);
20731
+ return () => {
20732
+ window.removeEventListener("keydown", onKeyDown, true);
20733
+ };
20734
+ }, [
20735
+ searchOpen,
20736
+ splitView,
20737
+ previewActive
19257
20738
  ]);
19258
20739
  const jumpRef = (0, react.useRef)(jumpBlock);
19259
20740
  jumpRef.current = jumpBlock;
@@ -19263,8 +20744,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19263
20744
  if (matchesShortcut(event, keybindingOf("jumpUp"))) direction = -1;
19264
20745
  else if (matchesShortcut(event, keybindingOf("jumpDown"))) direction = 1;
19265
20746
  if (direction === 0) return;
19266
- const target = event.target;
19267
- if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
20747
+ if (pathPickerOpen()) return;
20748
+ if (isTextFieldEvent(event)) return;
19268
20749
  event.preventDefault();
19269
20750
  jumpRef.current(direction, true);
19270
20751
  };
@@ -19284,6 +20765,123 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19284
20765
  const flashTop = focusedBlock === void 0 ? 0 : Math.max(0, offsetOf(focusedBlock.start) - scrollTop);
19285
20766
  const flashBottom = focusedBlock === void 0 ? 0 : Math.min(viewportHeight > 0 ? viewportHeight : Number.POSITIVE_INFINITY, offsetOf(focusedBlock.end + 1) - scrollTop);
19286
20767
  const flashHeight = Math.max(0, flashBottom - flashTop);
20768
+ const previewFlashNow = previewActive ? previewFlashBox(previewFlash, previewScrollTopRef.current) : void 0;
20769
+ const searchBar = searchOpen ? (0, react_jsx_runtime.jsxs)("div", {
20770
+ className: PendingPanel_module_css_default.searchBar,
20771
+ "data-diff-searchbar": true,
20772
+ children: [
20773
+ (0, react_jsx_runtime.jsx)("input", {
20774
+ ref: searchInputRef,
20775
+ className: PendingPanel_module_css_default.searchInput,
20776
+ "data-diff-search-input": true,
20777
+ value: searchQuery,
20778
+ placeholder: t("panel.searchPlaceholder"),
20779
+ onChange: (event) => {
20780
+ const value = event.target.value;
20781
+ setSearchQuery(value);
20782
+ if (previewActive) {
20783
+ previewSearchAnchorRef.current = true;
20784
+ setSearchIndex(0);
20785
+ return;
20786
+ }
20787
+ setSearchIndex(startIndexFor(value));
20788
+ },
20789
+ onKeyDown: (event) => {
20790
+ if (event.key === "Enter") {
20791
+ event.preventDefault();
20792
+ goSearch(event.shiftKey ? -1 : 1);
20793
+ }
20794
+ }
20795
+ }),
20796
+ (0, react_jsx_runtime.jsx)("span", {
20797
+ className: PendingPanel_module_css_default.searchCount,
20798
+ "data-diff-search-count": true,
20799
+ children: searchMatchCount === 0 ? "0/0" : `${searchIndex % searchMatchCount + 1}/${searchMatchCount}`
20800
+ }),
20801
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20802
+ label: withChord(t("action.matchCase"), "matchCase"),
20803
+ side: "bottom",
20804
+ delayMs: 500,
20805
+ children: (0, react_jsx_runtime.jsx)("button", {
20806
+ type: "button",
20807
+ className: search.caseSensitive ? `${PendingPanel_module_css_default.searchToggle} ${PendingPanel_module_css_default.searchToggleOn}` : PendingPanel_module_css_default.searchToggle,
20808
+ "data-diff-search-case": true,
20809
+ "data-on": search.caseSensitive ? "" : void 0,
20810
+ "aria-label": t("action.matchCase"),
20811
+ "aria-pressed": search.caseSensitive,
20812
+ onClick: () => {
20813
+ andRefocus(() => {
20814
+ search.toggleCase();
20815
+ });
20816
+ },
20817
+ children: (0, react_jsx_runtime.jsx)(SearchOptionIcon, { kind: "case" })
20818
+ })
20819
+ }),
20820
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20821
+ label: withChord(t("action.matchWholeWord"), "matchWholeWord"),
20822
+ side: "bottom",
20823
+ delayMs: 500,
20824
+ children: (0, react_jsx_runtime.jsx)("button", {
20825
+ type: "button",
20826
+ className: search.wholeWord ? `${PendingPanel_module_css_default.searchToggle} ${PendingPanel_module_css_default.searchToggleOn}` : PendingPanel_module_css_default.searchToggle,
20827
+ "data-diff-search-word": true,
20828
+ "data-on": search.wholeWord ? "" : void 0,
20829
+ "aria-label": t("action.matchWholeWord"),
20830
+ "aria-pressed": search.wholeWord,
20831
+ onClick: () => {
20832
+ andRefocus(() => {
20833
+ search.toggleWord();
20834
+ });
20835
+ },
20836
+ children: (0, react_jsx_runtime.jsx)(SearchOptionIcon, { kind: "word" })
20837
+ })
20838
+ }),
20839
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20840
+ label: withChord(t("action.prevDiff"), "searchPrev"),
20841
+ side: "bottom",
20842
+ delayMs: 500,
20843
+ children: (0, react_jsx_runtime.jsx)("button", {
20844
+ type: "button",
20845
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20846
+ "data-diff-search-prev": true,
20847
+ "aria-label": t("action.prevDiff"),
20848
+ disabled: searchMatchCount === 0,
20849
+ onClick: () => {
20850
+ andRefocus(() => {
20851
+ goSearch(-1);
20852
+ });
20853
+ },
20854
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
20855
+ })
20856
+ }),
20857
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20858
+ label: withChord(t("action.nextDiff"), "searchNext"),
20859
+ side: "bottom",
20860
+ delayMs: 500,
20861
+ children: (0, react_jsx_runtime.jsx)("button", {
20862
+ type: "button",
20863
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20864
+ "data-diff-search-next": true,
20865
+ "aria-label": t("action.nextDiff"),
20866
+ disabled: searchMatchCount === 0,
20867
+ onClick: () => {
20868
+ andRefocus(() => {
20869
+ goSearch(1);
20870
+ });
20871
+ },
20872
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
20873
+ })
20874
+ }),
20875
+ (0, react_jsx_runtime.jsx)("button", {
20876
+ type: "button",
20877
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20878
+ "data-diff-search-close": true,
20879
+ "aria-label": t("action.close"),
20880
+ onClick: closeSearch,
20881
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
20882
+ })
20883
+ ]
20884
+ }) : null;
19287
20885
  return (0, react_jsx_runtime.jsxs)("div", {
19288
20886
  className: PendingPanel_module_css_default.diff,
19289
20887
  "data-diff-approval-diff": true,
@@ -19352,7 +20950,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19352
20950
  })
19353
20951
  }),
19354
20952
  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, {
19355
- label: `${t("action.prevDiff")} (Ctrl+↑)`,
20953
+ label: withChord(t("action.prevDiff"), "jumpUp"),
19356
20954
  side: "bottom",
19357
20955
  delayMs: 500,
19358
20956
  children: (0, react_jsx_runtime.jsx)("button", {
@@ -19367,7 +20965,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19367
20965
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
19368
20966
  })
19369
20967
  }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
19370
- label: `${t("action.nextDiff")} (Ctrl+↓)`,
20968
+ label: withChord(t("action.nextDiff"), "jumpDown"),
19371
20969
  side: "bottom",
19372
20970
  delayMs: 500,
19373
20971
  children: (0, react_jsx_runtime.jsx)("button", {
@@ -19383,7 +20981,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19383
20981
  })
19384
20982
  })] }),
19385
20983
  (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
19386
- label: t("action.search"),
20984
+ label: withChord(t("action.search"), "openSearch"),
19387
20985
  side: "bottom",
19388
20986
  delayMs: 500,
19389
20987
  children: (0, react_jsx_runtime.jsx)("button", {
@@ -19426,6 +21024,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19426
21024
  children: (0, react_jsx_runtime.jsx)(MarkdownModeIcon, { preview: mdPreview })
19427
21025
  })
19428
21026
  }),
21027
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
21028
+ label: t("action.refreshVcs"),
21029
+ side: "bottom",
21030
+ delayMs: 500,
21031
+ children: (0, react_jsx_runtime.jsx)("button", {
21032
+ type: "button",
21033
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
21034
+ "data-diff-refresh-vcs": true,
21035
+ disabled: busy,
21036
+ "aria-label": t("action.refreshVcs"),
21037
+ onClick: () => {
21038
+ onRefreshVcs(file);
21039
+ },
21040
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 14 })
21041
+ })
21042
+ }),
19429
21043
  (0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
19430
21044
  (0, react_jsx_runtime.jsx)("button", {
19431
21045
  type: "button",
@@ -19458,7 +21072,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19458
21072
  className: PendingPanel_module_css_default.missingHint,
19459
21073
  children: t("panel.missingHint")
19460
21074
  }),
19461
- mdPreview && lang === "markdown" ? (0, react_jsx_runtime.jsx)(MarkdownPreviewBoundary, {
21075
+ previewActive ? (0, react_jsx_runtime.jsx)(MarkdownPreviewBoundary, {
19462
21076
  fallback: (0, react_jsx_runtime.jsx)("div", {
19463
21077
  className: PendingPanel_module_css_default.mdPreviewFallback,
19464
21078
  "data-diff-md-preview-fallback": true,
@@ -19466,29 +21080,146 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19466
21080
  }),
19467
21081
  children: (0, react_jsx_runtime.jsxs)("div", {
19468
21082
  className: PendingPanel_module_css_default.mdPreviewWrap,
19469
- children: [(0, react_jsx_runtime.jsx)("div", {
19470
- className: `${PendingPanel_module_css_default.mdPreviewBody} ${splitView ? PendingPanel_module_css_default.mdPreviewDouble : PendingPanel_module_css_default.mdPreviewSingle}`,
19471
- "data-diff-md-preview-body": true,
19472
- "data-diff-md-mode": splitView ? "double" : "single",
19473
- ref: mdPreviewBodyRef,
19474
- children: (0, react_jsx_runtime.jsx)("div", {
19475
- className: PendingPanel_module_css_default.mdPreviewContent,
19476
- style: { maxWidth: splitView ? mdPreviewMaxWidthPx * 2 : mdPreviewMaxWidthPx },
19477
- dangerouslySetInnerHTML: { __html: renderMarkdownPreview(file.oldText, file.newText, splitView ? "double" : "single") }
19478
- })
19479
- }), !splitView && mdRulerMarkers.length > 0 && (0, react_jsx_runtime.jsx)("div", {
19480
- className: PendingPanel_module_css_default.overviewRuler,
19481
- "data-diff-approval-ruler": true,
19482
- "aria-hidden": "true",
19483
- children: mdRulerMarkers.map((marker, index) => (0, react_jsx_runtime.jsx)("div", {
19484
- className: `${PendingPanel_module_css_default.overviewMarker} ${marker.kind === "del" ? PendingPanel_module_css_default.markerDel : PendingPanel_module_css_default.markerAdd}`,
19485
- "data-diff-ruler-marker": marker.kind,
21083
+ onMouseLeave: () => {
21084
+ setHoveredBlock(void 0);
21085
+ },
21086
+ children: [
21087
+ (0, react_jsx_runtime.jsx)("div", {
21088
+ className: `${PendingPanel_module_css_default.mdPreviewBody} ${splitView ? PendingPanel_module_css_default.mdPreviewDouble : PendingPanel_module_css_default.mdPreviewSingle}`,
21089
+ "data-diff-md-preview-body": true,
21090
+ "data-diff-md-mode": splitView ? "double" : "single",
21091
+ ref: mdPreviewBodyRef,
21092
+ onScroll: (event) => {
21093
+ previewScrollTopRef.current = event.currentTarget.scrollTop;
21094
+ applyPreviewOverlays();
21095
+ },
21096
+ onMouseOver: (event) => {
21097
+ const target = event.target;
21098
+ if (target instanceof Element && target.closest("[data-diff-block-actions],[data-diff-selection-actions]") !== null) return;
21099
+ const index = previewBlockAt(target);
21100
+ if (index !== hoveredBlock) setHoveredBlock(index);
21101
+ },
21102
+ children: (0, react_jsx_runtime.jsx)("div", {
21103
+ className: PendingPanel_module_css_default.mdPreviewContent,
21104
+ "data-diff-md-preview-content": true,
21105
+ style: { maxWidth: splitView ? mdPreviewMaxWidthPx * 2 : mdPreviewMaxWidthPx },
21106
+ dangerouslySetInnerHTML: { __html: renderMarkdownPreview(file.oldText, file.newText, splitView ? "double" : "single") }
21107
+ })
21108
+ }),
21109
+ searchBar,
21110
+ previewFlash !== void 0 && previewFlashNow !== void 0 && (0, react_jsx_runtime.jsx)("div", {
21111
+ ref: previewFlashRef,
21112
+ className: pinShakeRef.current ? `${PendingPanel_module_css_default.blockFlash} ${PendingPanel_module_css_default.blockFlashShake}` : PendingPanel_module_css_default.blockFlash,
21113
+ "data-diff-block-flash": true,
19486
21114
  style: {
19487
- top: `${marker.top}%`,
19488
- height: `${marker.height}%`
21115
+ top: previewFlashNow.top,
21116
+ height: previewFlashNow.height,
21117
+ left: previewFlash.left,
21118
+ right: previewFlash.right
19489
21119
  }
19490
- }, index))
19491
- })]
21120
+ }, flashKey),
21121
+ previewCovered.length > 0 && selectionRange !== void 0 && previewSelectionFrame !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
21122
+ ref: previewSelectionFrameRef,
21123
+ className: PendingPanel_module_css_default.blockActions,
21124
+ "data-diff-selection-actions": true,
21125
+ style: {
21126
+ top: previewFrameTop(previewSelectionFrame, previewScrollTopRef.current) ?? 0,
21127
+ right: previewSelectionFrame.right
21128
+ },
21129
+ children: [(0, react_jsx_runtime.jsx)("button", {
21130
+ type: "button",
21131
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
21132
+ "data-diff-selection-keep": true,
21133
+ disabled: busy,
21134
+ onClick: () => {
21135
+ handleSelectionAction("keep");
21136
+ },
21137
+ children: t("action.keep")
21138
+ }), (0, react_jsx_runtime.jsx)("button", {
21139
+ type: "button",
21140
+ className: PendingPanel_module_css_default.action,
21141
+ "data-diff-selection-revert": true,
21142
+ disabled: busy,
21143
+ onClick: () => {
21144
+ handleSelectionAction("revert");
21145
+ },
21146
+ children: t("action.revert")
21147
+ })]
21148
+ }) : hoveredBlock !== void 0 && model.blocks[hoveredBlock] !== void 0 && previewFrame !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
21149
+ ref: previewHoverFrameRef,
21150
+ className: PendingPanel_module_css_default.blockActions,
21151
+ "data-diff-block-actions": true,
21152
+ style: {
21153
+ top: previewFrameTop(previewFrame, previewScrollTopRef.current) ?? 0,
21154
+ right: previewFrame.right
21155
+ },
21156
+ children: [
21157
+ (0, react_jsx_runtime.jsx)("span", {
21158
+ className: PendingPanel_module_css_default.blockPosition,
21159
+ "data-diff-block-position": true,
21160
+ children: t("panel.blockPosition", {
21161
+ current: hoveredBlock + 1,
21162
+ total: model.blocks.length
21163
+ })
21164
+ }),
21165
+ (0, react_jsx_runtime.jsx)("button", {
21166
+ type: "button",
21167
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
21168
+ "data-diff-block-prev": true,
21169
+ "aria-label": t("action.prevDiff"),
21170
+ disabled: busy,
21171
+ onClick: () => {
21172
+ stepBlock(-1);
21173
+ },
21174
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
21175
+ }),
21176
+ (0, react_jsx_runtime.jsx)("button", {
21177
+ type: "button",
21178
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
21179
+ "data-diff-block-next": true,
21180
+ "aria-label": t("action.nextDiff"),
21181
+ disabled: busy,
21182
+ onClick: () => {
21183
+ stepBlock(1);
21184
+ },
21185
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
21186
+ }),
21187
+ (0, react_jsx_runtime.jsx)("button", {
21188
+ type: "button",
21189
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
21190
+ "data-diff-block-keep": true,
21191
+ disabled: busy,
21192
+ onClick: () => {
21193
+ handleBlockAction("keep");
21194
+ },
21195
+ children: t("action.keep")
21196
+ }),
21197
+ (0, react_jsx_runtime.jsx)("button", {
21198
+ type: "button",
21199
+ className: PendingPanel_module_css_default.action,
21200
+ "data-diff-block-revert": true,
21201
+ disabled: busy,
21202
+ onClick: () => {
21203
+ handleBlockAction("revert");
21204
+ },
21205
+ children: t("action.revert")
21206
+ })
21207
+ ]
21208
+ }) : null,
21209
+ mdRulerMarkers.length > 0 && (0, react_jsx_runtime.jsx)("div", {
21210
+ className: PendingPanel_module_css_default.overviewRuler,
21211
+ "data-diff-approval-ruler": true,
21212
+ "aria-hidden": "true",
21213
+ children: mdRulerMarkers.map((marker, index) => (0, react_jsx_runtime.jsx)("div", {
21214
+ className: `${PendingPanel_module_css_default.overviewMarker} ${marker.kind === "del" ? PendingPanel_module_css_default.markerDel : PendingPanel_module_css_default.markerAdd}`,
21215
+ "data-diff-ruler-marker": marker.kind,
21216
+ style: {
21217
+ top: `${marker.top}%`,
21218
+ height: `${marker.height}%`
21219
+ }
21220
+ }, index))
21221
+ })
21222
+ ]
19492
21223
  })
19493
21224
  }) : splitView ? (0, react_jsx_runtime.jsx)(SplitDiff, {
19494
21225
  ref: splitDiffRef,
@@ -19503,7 +21234,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19503
21234
  leadRows,
19504
21235
  onBlockKeep,
19505
21236
  onBlockRevert,
19506
- onWrapToast: (text) => onToast(text)
21237
+ onWrapToast: (text) => onToast(text),
21238
+ onVisibleLines: onSplitVisibleLines
19507
21239
  }) : (0, react_jsx_runtime.jsxs)("div", {
19508
21240
  className: PendingPanel_module_css_default.diffBodyWrap,
19509
21241
  onMouseLeave: () => {
@@ -19539,6 +21271,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19539
21271
  searchHit: searchHitSet.has(index),
19540
21272
  searchCurrent: index === currentSearchRow,
19541
21273
  searchQuery,
21274
+ searchOptions: search.options,
19542
21275
  onRowHover,
19543
21276
  wrappedLines: rowWrapped?.[index]
19544
21277
  }, index);
@@ -19640,75 +21373,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19640
21373
  width: flashWidth
19641
21374
  }
19642
21375
  }, flashKey),
19643
- searchOpen && (0, react_jsx_runtime.jsxs)("div", {
19644
- className: PendingPanel_module_css_default.searchBar,
19645
- "data-diff-searchbar": true,
19646
- children: [
19647
- (0, react_jsx_runtime.jsx)("input", {
19648
- ref: searchInputRef,
19649
- className: PendingPanel_module_css_default.searchInput,
19650
- "data-diff-search-input": true,
19651
- value: searchQuery,
19652
- placeholder: t("panel.searchPlaceholder"),
19653
- onChange: (event) => {
19654
- const value = event.target.value;
19655
- setSearchQuery(value);
19656
- setSearchIndex(startIndexFor(value));
19657
- },
19658
- onKeyDown: (event) => {
19659
- if (event.key === "Enter") {
19660
- event.preventDefault();
19661
- goSearch(event.shiftKey ? -1 : 1);
19662
- } else if (event.key === "Escape") closeSearch();
19663
- }
19664
- }),
19665
- (0, react_jsx_runtime.jsx)("span", {
19666
- className: PendingPanel_module_css_default.searchCount,
19667
- "data-diff-search-count": true,
19668
- children: searchMatches.length === 0 ? "0/0" : `${searchIndex % searchMatches.length + 1}/${searchMatches.length}`
19669
- }),
19670
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
19671
- label: `${t("action.prevDiff")} (Shift+F3)`,
19672
- side: "bottom",
19673
- delayMs: 500,
19674
- children: (0, react_jsx_runtime.jsx)("button", {
19675
- type: "button",
19676
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
19677
- "data-diff-search-prev": true,
19678
- "aria-label": t("action.prevDiff"),
19679
- disabled: searchMatches.length === 0,
19680
- onClick: () => {
19681
- goSearch(-1);
19682
- },
19683
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
19684
- })
19685
- }),
19686
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
19687
- label: `${t("action.nextDiff")} (F3)`,
19688
- side: "bottom",
19689
- delayMs: 500,
19690
- children: (0, react_jsx_runtime.jsx)("button", {
19691
- type: "button",
19692
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
19693
- "data-diff-search-next": true,
19694
- "aria-label": t("action.nextDiff"),
19695
- disabled: searchMatches.length === 0,
19696
- onClick: () => {
19697
- goSearch(1);
19698
- },
19699
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
19700
- })
19701
- }),
19702
- (0, react_jsx_runtime.jsx)("button", {
19703
- type: "button",
19704
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
19705
- "data-diff-search-close": true,
19706
- "aria-label": t("action.close"),
19707
- onClick: closeSearch,
19708
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
19709
- })
19710
- ]
19711
- }),
21376
+ searchBar,
19712
21377
  rulerMarkers.length > 0 && (0, react_jsx_runtime.jsx)("div", {
19713
21378
  className: PendingPanel_module_css_default.overviewRuler,
19714
21379
  "data-diff-approval-ruler": true,
@@ -19730,7 +21395,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19730
21395
  "data-diff-status-bar": true,
19731
21396
  children: [
19732
21397
  selectionReference === void 0 ? null : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
19733
- label: copied ? t("action.copied") : `${t("action.copyHint")} (Ctrl+L)`,
21398
+ label: copied ? t("action.copied") : withChord(t("action.copyHint"), "copyRef"),
19734
21399
  side: "top",
19735
21400
  delayMs: 300,
19736
21401
  children: (0, react_jsx_runtime.jsx)("span", {
@@ -19755,15 +21420,17 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19755
21420
  })
19756
21421
  }),
19757
21422
  (0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
19758
- !mdPreview && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
21423
+ !previewActive && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
19759
21424
  open: langMenuOpen,
19760
21425
  portal: true,
19761
21426
  compact: true,
19762
21427
  align: "end",
19763
21428
  items: langMenuItems,
19764
- selectedId: langOverride ?? "",
21429
+ selectedId: effectiveLang ?? "",
19765
21430
  onSelect: (id) => {
19766
- setLangOverride(id === "" ? void 0 : id);
21431
+ const next = id === "" ? void 0 : id;
21432
+ setLangOverride(next);
21433
+ if (langSuffix !== void 0) setLanguageForSuffix(langSuffix, next ?? null);
19767
21434
  setLangMenuOpen(false);
19768
21435
  },
19769
21436
  onClose: () => {
@@ -19810,7 +21477,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19810
21477
  });
19811
21478
  }
19812
21479
  /** Render the pending-edit review panel and its unified footer action. */
19813
- function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onKeepAll, onRevertAll, onAckRedoCleared, collapseSidebar, t }) {
21480
+ function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onRefreshVcs, onBrowse, onAddPath, onKeepAll, onRevertAll, onAckRedoCleared, collapseSidebar, t }) {
19814
21481
  const current = useSessions((state) => state.current);
19815
21482
  const currentBlank = useSessions((state) => {
19816
21483
  const id = state.current;
@@ -19872,6 +21539,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19872
21539
  /** A last-block keep/revert awaiting the user's remove-or-keep choice; the
19873
21540
  * choice rides the same block RPC as its `removeWhenResolved` flag. */
19874
21541
  const [blockPrompt, setBlockPrompt] = (0, react.useState)(null);
21542
+ const [filePrompt, setFilePrompt] = (0, react.useState)(null);
21543
+ /** Whether the add-path dialog is open. One dialog covers both shapes: what
21544
+ * the browser settles on decides whether a file or a directory is added. */
21545
+ const [addOpen, setAddOpen] = (0, react.useState)(false);
19875
21546
  /** Bottom offset tracking the chat composer's top edge so the input stays visible. */
19876
21547
  const [bottomPx, setBottomPx] = (0, react.useState)(FALLBACK_BOTTOM_PX);
19877
21548
  /** Fullscreen expanded: the panel bottom pins to the window edge, ignoring the composer offset. */
@@ -20036,6 +21707,28 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20036
21707
  }
20037
21708
  return removeWhenResolved === void 0 ? onBlockRevert(sessionId, id, block) : onBlockRevert(sessionId, id, block, removeWhenResolved);
20038
21709
  };
21710
+ const keepWithPrompt = (sessionId, id, keepListed) => {
21711
+ if (keepListed === void 0 && confirmFileRemoveEnabled()) {
21712
+ setFilePrompt({
21713
+ action: "keep",
21714
+ sessionId,
21715
+ id
21716
+ });
21717
+ return Promise.resolve();
21718
+ }
21719
+ return keepListed === void 0 ? onKeep(sessionId, id) : onKeep(sessionId, id, keepListed);
21720
+ };
21721
+ const revertWithPrompt = (sessionId, id, keepListed) => {
21722
+ if (keepListed === void 0 && confirmFileRemoveEnabled()) {
21723
+ setFilePrompt({
21724
+ action: "revert",
21725
+ sessionId,
21726
+ id
21727
+ });
21728
+ return Promise.resolve();
21729
+ }
21730
+ return keepListed === void 0 ? onRevert(sessionId, id) : onRevert(sessionId, id, keepListed);
21731
+ };
20039
21732
  /** Per-file keep/revert failures, surfaced inline on the row and detail. */
20040
21733
  const failed = snapshot.failed ?? EMPTY_FAILED_MAP;
20041
21734
  const failedInitialized = (0, react.useRef)(false);
@@ -20088,6 +21781,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20088
21781
  (0, react.useEffect)(() => {
20089
21782
  if (noSession) setOpen(false);
20090
21783
  }, [noSession]);
21784
+ (0, react.useEffect)(() => {
21785
+ if (open) return;
21786
+ setAddOpen(false);
21787
+ setBlockPrompt(null);
21788
+ setFilePrompt(null);
21789
+ }, [open]);
20091
21790
  /** Run the same decision over every current-session file, sequentially. */
20092
21791
  const runBulk = async (kind) => {
20093
21792
  if (current === void 0) return;
@@ -20099,6 +21798,40 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20099
21798
  setBulkBusy(null);
20100
21799
  }
20101
21800
  };
21801
+ /**
21802
+ * Replace one file's diff with its current local VCS change, then report what
21803
+ * the scan found. A scan that sees no change leaves the entry alone, so the
21804
+ * message names the file rather than silently blanking a review in progress.
21805
+ */
21806
+ const runRefreshVcs = async (entry) => {
21807
+ if (current === void 0) return;
21808
+ const includeUntracked = includeUntrackedEnabled();
21809
+ let outcome;
21810
+ try {
21811
+ outcome = (await onRefreshVcs(current, entry.id, includeUntracked)).outcome;
21812
+ } catch (error) {
21813
+ showCopyToast(t("panel.refreshFailed", { message: error instanceof Error ? error.message : String(error) }));
21814
+ return;
21815
+ }
21816
+ if (outcome === "refreshed") {
21817
+ showCopyToast(t("panel.refreshDone"));
21818
+ return;
21819
+ }
21820
+ if (outcome === "unchanged") {
21821
+ showCopyToast(t("panel.refreshUnchanged"));
21822
+ return;
21823
+ }
21824
+ if (outcome === "no-change") {
21825
+ const hint = includeUntracked ? "" : ` ${t("panel.refreshUntrackedHint")}`;
21826
+ showCopyToast(`${t("panel.refreshNone")}${hint}`);
21827
+ return;
21828
+ }
21829
+ if (outcome === "no-vcs") {
21830
+ showCopyToast(t("panel.importNoVcs"));
21831
+ return;
21832
+ }
21833
+ showCopyToast(t("panel.fileNotPending"));
21834
+ };
20102
21835
  const renderEntry = (entry) => (0, react_jsx_runtime.jsx)(PendingFileRow, {
20103
21836
  file: entry,
20104
21837
  selected: selected === entry.id,
@@ -20112,37 +21845,65 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20112
21845
  const selectedFile = files.find((file) => file.id === selected);
20113
21846
  /** The file whose removal is being confirmed (a last-block action), if any. */
20114
21847
  const promptFile = blockPrompt === null ? void 0 : files.find((file) => file.id === blockPrompt.id);
20115
- const fileListBody = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
20116
- className: PendingPanel_module_css_default.listScroll,
20117
- children: files.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
20118
- className: PendingPanel_module_css_default.group,
20119
- children: t("panel.group.current")
20120
- }), (0, react_jsx_runtime.jsx)("ul", {
20121
- className: PendingPanel_module_css_default.rows,
20122
- children: files.map(renderEntry)
20123
- })] })
20124
- }), files.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
20125
- className: PendingPanel_module_css_default.bulkActions,
20126
- children: [(0, react_jsx_runtime.jsx)("button", {
20127
- type: "button",
20128
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
20129
- "data-diff-keep-all": true,
20130
- disabled: bulkBusy !== null,
20131
- onClick: () => {
20132
- runBulk("keep");
20133
- },
20134
- children: bulkBusy === "keep" ? t("action.busy") : t("action.keepAll")
20135
- }), (0, react_jsx_runtime.jsx)("button", {
20136
- type: "button",
20137
- className: PendingPanel_module_css_default.action,
20138
- "data-diff-revert-all": true,
20139
- disabled: bulkBusy !== null,
20140
- onClick: () => {
20141
- runBulk("revert");
20142
- },
20143
- children: bulkBusy === "revert" ? t("action.busy") : t("action.revertAll")
20144
- })]
20145
- })] });
21848
+ /** The file whose removal is being confirmed (a whole-file action), if any. */
21849
+ const promptEntry = filePrompt === null ? void 0 : files.find((file) => file.id === filePrompt.id);
21850
+ const fileListBody = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
21851
+ files.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
21852
+ className: PendingPanel_module_css_default.groupHead,
21853
+ children: [
21854
+ (0, react_jsx_runtime.jsx)("h3", {
21855
+ className: PendingPanel_module_css_default.group,
21856
+ children: t("panel.group.current")
21857
+ }),
21858
+ (0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
21859
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
21860
+ label: t("action.addPath"),
21861
+ side: "bottom",
21862
+ delayMs: 500,
21863
+ children: (0, react_jsx_runtime.jsx)("button", {
21864
+ type: "button",
21865
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.addButton}`,
21866
+ "data-diff-add": true,
21867
+ "aria-label": t("action.addPath"),
21868
+ onClick: () => {
21869
+ setAddOpen(true);
21870
+ },
21871
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 12 })
21872
+ })
21873
+ })
21874
+ ]
21875
+ }),
21876
+ (0, react_jsx_runtime.jsx)("div", {
21877
+ className: PendingPanel_module_css_default.listScroll,
21878
+ "data-diff-list-scroll": true,
21879
+ children: files.length > 0 && (0, react_jsx_runtime.jsx)("ul", {
21880
+ className: PendingPanel_module_css_default.rows,
21881
+ children: files.map(renderEntry)
21882
+ })
21883
+ }),
21884
+ files.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
21885
+ className: PendingPanel_module_css_default.bulkActions,
21886
+ children: [(0, react_jsx_runtime.jsx)("button", {
21887
+ type: "button",
21888
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
21889
+ "data-diff-keep-all": true,
21890
+ disabled: bulkBusy !== null,
21891
+ onClick: () => {
21892
+ runBulk("keep");
21893
+ },
21894
+ children: bulkBusy === "keep" ? t("action.busy") : t("action.keepAll")
21895
+ }), (0, react_jsx_runtime.jsx)("button", {
21896
+ type: "button",
21897
+ className: PendingPanel_module_css_default.action,
21898
+ "data-diff-revert-all": true,
21899
+ disabled: bulkBusy !== null,
21900
+ onClick: () => {
21901
+ runBulk("revert");
21902
+ },
21903
+ children: bulkBusy === "revert" ? t("action.busy") : t("action.revertAll")
21904
+ })]
21905
+ })
21906
+ ] });
20146
21907
  const handleUndo = async (sessionId) => {
20147
21908
  const id = await onUndo(sessionId);
20148
21909
  if (id === void 0) return;
@@ -20159,6 +21920,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20159
21920
  if (!open || current === void 0) return;
20160
21921
  const onKeyDown = (event) => {
20161
21922
  if (!(event.ctrlKey || event.metaKey) || event.altKey) return;
21923
+ if (pathPickerOpen()) return;
20162
21924
  const target = event.target;
20163
21925
  if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
20164
21926
  if (matchesShortcut(event, keybindingOf("undo"))) {
@@ -20188,6 +21950,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20188
21950
  if (matchesShortcut(event, keybindingOf("cycleNext"))) direction = 1;
20189
21951
  else if (matchesShortcut(event, keybindingOf("cyclePrev"))) direction = -1;
20190
21952
  if (direction === 0) return;
21953
+ if (pathPickerOpen()) return;
20191
21954
  const target = event.target;
20192
21955
  if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
20193
21956
  if (files.length === 0) return;
@@ -20209,6 +21972,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20209
21972
  (0, react.useEffect)(() => {
20210
21973
  const onKeyDown = (event) => {
20211
21974
  if (!matchesShortcut(event, quickSummonKey())) return;
21975
+ if (pathPickerOpen()) return;
20212
21976
  event.preventDefault();
20213
21977
  if (!open) collapseSidebar();
20214
21978
  setOpen((value) => !value);
@@ -20222,8 +21986,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20222
21986
  if (!open) return;
20223
21987
  const onKeyDown = (event) => {
20224
21988
  if (event.key !== "Escape") return;
21989
+ if (pathPickerOpen()) return;
20225
21990
  const target = event.target;
20226
- if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
21991
+ if (target instanceof Node && panelRef.current?.contains(target) === true && document.querySelector("[data-diff-searchbar]") !== null) return;
20227
21992
  event.preventDefault();
20228
21993
  setOpen(false);
20229
21994
  };
@@ -20366,15 +22131,26 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20366
22131
  className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
20367
22132
  children: t("panel.empty")
20368
22133
  }),
20369
- (0, react_jsx_runtime.jsx)("button", {
20370
- type: "button",
20371
- className: PendingPanel_module_css_default.importButton,
20372
- "data-diff-import-vcs": true,
20373
- disabled: importBusy,
20374
- onClick: () => {
20375
- runImportVcs();
20376
- },
20377
- children: importBusy ? t("action.importVcsBusy") : t("action.importVcs")
22134
+ (0, react_jsx_runtime.jsxs)("div", {
22135
+ className: PendingPanel_module_css_default.emptyActions,
22136
+ children: [(0, react_jsx_runtime.jsx)("button", {
22137
+ type: "button",
22138
+ className: PendingPanel_module_css_default.importButton,
22139
+ "data-diff-import-vcs": true,
22140
+ disabled: importBusy,
22141
+ onClick: () => {
22142
+ runImportVcs();
22143
+ },
22144
+ children: importBusy ? t("action.importVcsBusy") : t("action.importVcs")
22145
+ }), (0, react_jsx_runtime.jsx)("button", {
22146
+ type: "button",
22147
+ className: PendingPanel_module_css_default.importButton,
22148
+ "data-diff-add": true,
22149
+ onClick: () => {
22150
+ setAddOpen(true);
22151
+ },
22152
+ children: t("action.addPath")
22153
+ })]
20378
22154
  }),
20379
22155
  importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
20380
22156
  className: PendingPanel_module_css_default.importNote,
@@ -20414,8 +22190,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20414
22190
  onPasteReference,
20415
22191
  onToast: showCopyToast,
20416
22192
  t,
20417
- onKeep,
20418
- onRevert,
22193
+ onKeep: keepWithPrompt,
22194
+ onRevert: revertWithPrompt,
22195
+ onRefreshVcs: (entry) => {
22196
+ runRefreshVcs(entry);
22197
+ },
20419
22198
  onBlockKeep: blockKeepWithPrompt,
20420
22199
  onBlockRevert: blockRevertWithPrompt,
20421
22200
  onOpen,
@@ -20473,6 +22252,52 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20473
22252
  })]
20474
22253
  })]
20475
22254
  })
22255
+ }),
22256
+ filePrompt !== null && promptEntry !== void 0 && (0, react_jsx_runtime.jsx)("div", {
22257
+ className: PendingPanel_module_css_default.confirmBackdrop,
22258
+ "data-diff-confirm-file": true,
22259
+ children: (0, react_jsx_runtime.jsxs)("div", {
22260
+ className: PendingPanel_module_css_default.confirmCard,
22261
+ role: "dialog",
22262
+ "aria-modal": "true",
22263
+ children: [(0, react_jsx_runtime.jsx)("p", {
22264
+ className: PendingPanel_module_css_default.confirmText,
22265
+ children: t(filePrompt.action === "keep" ? "panel.fileKeptAsk" : "panel.fileRevertedAsk", { file: basenameOf(promptEntry.path) })
22266
+ }), (0, react_jsx_runtime.jsxs)("div", {
22267
+ className: PendingPanel_module_css_default.confirmActions,
22268
+ children: [(0, react_jsx_runtime.jsx)("button", {
22269
+ type: "button",
22270
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
22271
+ "data-diff-file-confirm-remove": true,
22272
+ onClick: () => {
22273
+ setFilePrompt(null);
22274
+ const { action, sessionId, id } = filePrompt;
22275
+ action === "keep" ? onKeep(sessionId, id, false) : onRevert(sessionId, id, false);
22276
+ },
22277
+ children: t("row.dismiss")
22278
+ }), (0, react_jsx_runtime.jsx)("button", {
22279
+ type: "button",
22280
+ className: PendingPanel_module_css_default.action,
22281
+ "data-diff-file-confirm-keep": true,
22282
+ onClick: () => {
22283
+ setFilePrompt(null);
22284
+ const { action, sessionId, id } = filePrompt;
22285
+ action === "keep" ? onKeep(sessionId, id, true) : onRevert(sessionId, id, true);
22286
+ },
22287
+ children: t("panel.keepInList")
22288
+ })]
22289
+ })]
22290
+ })
22291
+ }),
22292
+ addOpen && current !== void 0 && (0, react_jsx_runtime.jsx)(PathPicker, {
22293
+ rootPath: snapshot.workspacePath,
22294
+ onBrowse: (path) => onBrowse(current, path),
22295
+ onAdd: (path, includeUnchanged) => onAddPath(current, path, includeUnchanged),
22296
+ onToast: showCopyToast,
22297
+ onClose: () => {
22298
+ setAddOpen(false);
22299
+ },
22300
+ t
20476
22301
  })
20477
22302
  ]
20478
22303
  })] }), document.body),
@@ -21105,6 +22930,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21105
22930
  function DiffApprovalSettingsTab({ t }) {
21106
22931
  const [pasteOnCopy, setPasteOnCopyState] = (0, react.useState)(pasteOnCopyEnabled);
21107
22932
  const [includeUntracked, setIncludeUntrackedState] = (0, react.useState)(includeUntrackedEnabled);
22933
+ const [confirmFileRemove, setConfirmFileRemoveState] = (0, react.useState)(confirmFileRemoveEnabled);
21108
22934
  const [tab, setTabState] = (0, react.useState)(tabWidth);
21109
22935
  const [tabOpen, setTabOpen] = (0, react.useState)(false);
21110
22936
  const [split, setSplitState] = (0, react.useState)(splitMode);
@@ -21144,6 +22970,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21144
22970
  setIncludeUntrackedState(value);
21145
22971
  setIncludeUntrackedEnabled(value);
21146
22972
  };
22973
+ const setConfirmFileRemove = (value) => {
22974
+ setConfirmFileRemoveState(value);
22975
+ setConfirmFileRemoveEnabled(value);
22976
+ };
21147
22977
  const setTab = (value) => {
21148
22978
  setTabState(value);
21149
22979
  setTabWidth(value);
@@ -21357,6 +23187,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21357
23187
  onSelect: setIncludeUntracked,
21358
23188
  dataAttribute: "data-diff-import-untracked-select",
21359
23189
  t
23190
+ }),
23191
+ (0, react_jsx_runtime.jsx)(PreferenceRow, {
23192
+ title: t("panel.confirmFileRemove"),
23193
+ description: t("panel.confirmFileRemoveDesc"),
23194
+ value: confirmFileRemove,
23195
+ onSelect: setConfirmFileRemove,
23196
+ dataAttribute: "data-diff-confirm-file-remove-select",
23197
+ t
21360
23198
  })
21361
23199
  ]
21362
23200
  });
@@ -21380,16 +23218,24 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21380
23218
  async list(sessionId) {
21381
23219
  return listValueOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "list", { sessionId }));
21382
23220
  },
21383
- async keep(sessionId, id) {
21384
- return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "keep", {
23221
+ async keep(sessionId, id, keepListed) {
23222
+ return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "keep", keepListed === void 0 ? {
21385
23223
  sessionId,
21386
23224
  id
23225
+ } : {
23226
+ sessionId,
23227
+ id,
23228
+ keepListed
21387
23229
  }));
21388
23230
  },
21389
- async revert(sessionId, id) {
21390
- return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "revert", {
23231
+ async revert(sessionId, id, keepListed) {
23232
+ return actionOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "revert", keepListed === void 0 ? {
21391
23233
  sessionId,
21392
23234
  id
23235
+ } : {
23236
+ sessionId,
23237
+ id,
23238
+ keepListed
21393
23239
  }));
21394
23240
  },
21395
23241
  async blockKeep(sessionId, id, block, removeWhenResolved) {
@@ -21420,6 +23266,26 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21420
23266
  includeUntracked
21421
23267
  }));
21422
23268
  },
23269
+ async refreshVcs(sessionId, id, includeUntracked) {
23270
+ return refreshValueOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "vcs-refresh", {
23271
+ sessionId,
23272
+ id,
23273
+ includeUntracked
23274
+ }));
23275
+ },
23276
+ async browse(sessionId, path) {
23277
+ return browseValueOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "list-path", path === void 0 ? { sessionId } : {
23278
+ sessionId,
23279
+ path
23280
+ }));
23281
+ },
23282
+ async addPath(sessionId, path, includeUnchanged) {
23283
+ return addValueOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "add-path", {
23284
+ sessionId,
23285
+ path,
23286
+ includeUnchanged
23287
+ }));
23288
+ },
21423
23289
  async open(sessionId, id, action) {
21424
23290
  return openOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "open", {
21425
23291
  sessionId,
@@ -21519,6 +23385,64 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21519
23385
  detected
21520
23386
  };
21521
23387
  }
23388
+ /** Narrow the vcs-refresh endpoint's value; a malformed wire value is a failure. */
23389
+ function refreshValueOf(result) {
23390
+ if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
23391
+ const value = result.value;
23392
+ if (typeof value !== "object" || value === null) throw new Error("the refresh returned a malformed value");
23393
+ const outcome = value.outcome;
23394
+ if (outcome !== "refreshed" && outcome !== "unchanged" && outcome !== "no-change" && outcome !== "missing" && outcome !== "no-vcs") throw new Error("the refresh returned a malformed outcome");
23395
+ return { outcome };
23396
+ }
23397
+ /** Narrow the list-path endpoint's value; a malformed wire value is a browse failure. */
23398
+ function browseValueOf(result) {
23399
+ if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
23400
+ const value = result.value;
23401
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("the browse returned a malformed value");
23402
+ const record = value;
23403
+ const path = record.path;
23404
+ if (typeof path !== "string") throw new Error("the browse returned a malformed value");
23405
+ const rows = record.entries;
23406
+ if (!Array.isArray(rows)) throw new Error("the browse returned a malformed value");
23407
+ const entries = [];
23408
+ for (const row of rows) {
23409
+ if (typeof row !== "object" || row === null || Array.isArray(row)) continue;
23410
+ const { name, type, path: childPath, size } = row;
23411
+ if (typeof name !== "string" || typeof childPath !== "string") continue;
23412
+ if (type !== "file" && type !== "directory" && type !== "other") continue;
23413
+ entries.push({
23414
+ name,
23415
+ type,
23416
+ path: childPath,
23417
+ size: typeof size === "number" && Number.isFinite(size) ? size : void 0
23418
+ });
23419
+ }
23420
+ return {
23421
+ path,
23422
+ entries,
23423
+ truncated: record.truncated === true
23424
+ };
23425
+ }
23426
+ /** Narrow the add-path endpoint's value; a malformed wire value is an add failure. */
23427
+ function addValueOf(result) {
23428
+ if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
23429
+ const value = result.value;
23430
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("the add returned a malformed value");
23431
+ const record = value;
23432
+ const outcome = record.outcome;
23433
+ if (outcome !== "added" && outcome !== "duplicate" && outcome !== "unchanged" && outcome !== "empty" && outcome !== "missing" && outcome !== "outside" && outcome !== "no-vcs" && outcome !== "failed") throw new Error("the add returned a malformed outcome");
23434
+ const added = record.added;
23435
+ const duplicates = record.duplicates;
23436
+ if (typeof added !== "number" || typeof duplicates !== "number") throw new Error("the add returned a malformed value");
23437
+ const message = record.message;
23438
+ return {
23439
+ outcome,
23440
+ added,
23441
+ duplicates,
23442
+ truncated: record.truncated === true ? true : void 0,
23443
+ message: typeof message === "string" && message.length > 0 ? message : void 0
23444
+ };
23445
+ }
21522
23446
  /** Narrow the open endpoint's value; a malformed wire value is an open failure. */
21523
23447
  function openOf(result) {
21524
23448
  if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
@@ -21633,6 +23557,36 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21633
23557
  busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
21634
23558
  });
21635
23559
  };
23560
+ /**
23561
+ * Run a whole-file action whose entry STAYS listed (the panel chose "keep in
23562
+ * list"). The local list must not drop it optimistically — that made a kept
23563
+ * file blink out and come back on the next poll — so this takes the block
23564
+ * actions' shape instead: mark busy, run, then re-read so the entry's now-empty
23565
+ * diff shows at once.
23566
+ * @param id - the entry being acted on.
23567
+ * @param refresh - the store's refresh, awaited after a successful action.
23568
+ * @param action - the port call to run.
23569
+ */
23570
+ const withKeptEntry = async (id, refresh, action) => {
23571
+ const { error: _cleared, ...base } = snapshot;
23572
+ publish({
23573
+ ...base,
23574
+ failed: failedOf(snapshot),
23575
+ busy: /* @__PURE__ */ new Set([...snapshot.busy, id])
23576
+ });
23577
+ try {
23578
+ await action();
23579
+ } catch (error) {
23580
+ markFailed(id, error instanceof Error ? error.message : String(error));
23581
+ publish({
23582
+ ...snapshot,
23583
+ busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
23584
+ });
23585
+ return;
23586
+ }
23587
+ clearFailed(id);
23588
+ await refresh();
23589
+ };
21636
23590
  return {
21637
23591
  getSnapshot: () => snapshot,
21638
23592
  subscribe(listener) {
@@ -21672,12 +23626,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21672
23626
  });
21673
23627
  }
21674
23628
  },
21675
- keep(sessionId, id) {
23629
+ keep(sessionId, id, keepListed) {
23630
+ if (keepListed === true) return withKeptEntry(id, () => this.refresh(sessionId), async () => {
23631
+ await port.keep(sessionId, id, true);
23632
+ });
21676
23633
  return withBusy(id, async () => {
21677
23634
  await port.keep(sessionId, id);
21678
23635
  });
21679
23636
  },
21680
- revert(sessionId, id) {
23637
+ revert(sessionId, id, keepListed) {
23638
+ if (keepListed === true) return withKeptEntry(id, () => this.refresh(sessionId), async () => {
23639
+ await port.revert(sessionId, id, true);
23640
+ });
21681
23641
  return withBusy(id, async () => {
21682
23642
  await port.revert(sessionId, id);
21683
23643
  });
@@ -21747,6 +23707,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21747
23707
  await this.refresh(sessionId);
21748
23708
  return value;
21749
23709
  },
23710
+ async refreshVcs(sessionId, id, includeUntracked) {
23711
+ const { error: _cleared, ...base } = snapshot;
23712
+ publish({
23713
+ ...base,
23714
+ busy: /* @__PURE__ */ new Set([...snapshot.busy, id])
23715
+ });
23716
+ try {
23717
+ const value = await port.refreshVcs(sessionId, id, includeUntracked);
23718
+ await this.refresh(sessionId);
23719
+ return value;
23720
+ } finally {
23721
+ publish({
23722
+ ...snapshot,
23723
+ busy: new Set([...snapshot.busy].filter((busy) => busy !== id))
23724
+ });
23725
+ }
23726
+ },
23727
+ browse(sessionId, path) {
23728
+ return port.browse(sessionId, path);
23729
+ },
23730
+ async addPath(sessionId, path, includeUnchanged) {
23731
+ const value = await port.addPath(sessionId, path, includeUnchanged);
23732
+ await this.refresh(sessionId);
23733
+ return value;
23734
+ },
21750
23735
  async open(sessionId, id, action) {
21751
23736
  try {
21752
23737
  await port.open(sessionId, id, action);
@@ -21925,7 +23910,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21925
23910
  /** Simplified Chinese pending-edit review messages. */
21926
23911
  const zh = {
21927
23912
  "panel.title": "待处理改动",
21928
- "panel.empty": "没有待处理的改动",
23913
+ "panel.empty": "列表中暂无文件",
21929
23914
  "panel.loading": "读取中…",
21930
23915
  "panel.readFailed": "读取待处理改动失败:{message}",
21931
23916
  "panel.group.current": "当前会话",
@@ -21981,6 +23966,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21981
23966
  "panel.key.openSearch": "打开差异搜索",
21982
23967
  "panel.key.searchNext": "下一个搜索结果",
21983
23968
  "panel.key.searchPrev": "上一个搜索结果",
23969
+ "panel.key.matchCase": "切换区分大小写",
23970
+ "panel.key.matchWholeWord": "切换全字匹配",
21984
23971
  "panel.key.undo": "撤销",
21985
23972
  "panel.key.redo": "重做",
21986
23973
  "panel.key.cycleNext": "下一个待处理文件",
@@ -22028,7 +24015,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22028
24015
  "panel.importDone": "已导入 {count} 个改动",
22029
24016
  "panel.importNoVcs": "未检测到版本控制(Git / SVN / Perforce)",
22030
24017
  "panel.importFailed": "导入失败:{message}",
22031
- "action.search": "搜索 (Ctrl+F)",
24018
+ "action.search": "搜索",
22032
24019
  "action.settings": "设置",
22033
24020
  "action.expand": "展开全屏",
22034
24021
  "action.exitFullscreen": "退出全屏",
@@ -22037,12 +24024,44 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22037
24024
  "status.reverted": "已回退",
22038
24025
  "status.missing": "该改动已不存在",
22039
24026
  "panel.resolvedAsk": "「{file}」的所有改动都已处理,是否从列表移除?",
22040
- "panel.keepInList": "保留在列表"
24027
+ "panel.keepInList": "保留在列表",
24028
+ "panel.fileKeptAsk": "「{file}」的改动已全部保留,是否从列表移除?",
24029
+ "panel.fileRevertedAsk": "「{file}」的改动已全部回退,是否从列表移除?",
24030
+ "panel.confirmFileRemove": "处理整文件后询问是否移出列表",
24031
+ "panel.confirmFileRemoveDesc": "对单个文件执行整文件 Keep/Revert 后,弹窗询问是否把该文件移出列表。关闭后直接移出(不影响批量 Keep All / Revert All)。",
24032
+ "action.refreshVcs": "刷新差异",
24033
+ "action.matchCase": "区分大小写",
24034
+ "action.matchWholeWord": "全字匹配",
24035
+ "panel.refreshDone": "差异已更新",
24036
+ "panel.refreshUnchanged": "差异已是最新",
24037
+ "panel.refreshNone": "未检测到本地 VCS 差异",
24038
+ "panel.refreshUntrackedHint": "(新建/未跟踪文件需要开启「导入未跟踪的文件改动」)",
24039
+ "panel.refreshFailed": "刷新失败:{message}",
24040
+ "action.addPath": "添加文件或目录",
24041
+ "panel.addTitle": "添加到列表",
24042
+ "panel.addPathPlaceholder": "输入或粘贴绝对路径",
24043
+ "panel.addPathGo": "添加",
24044
+ "panel.addHint": "展开目录逐级浏览,选中会把完整路径填到上方输入框;点「添加」时才判断有没有本地改动",
24045
+ "panel.addIncludeUnchanged": "包含无改动路径",
24046
+ "panel.addRoot": "工作区根目录",
24047
+ "panel.addTruncated": "目录内容过多,仅显示前 {count} 项",
24048
+ "panel.addDone": "已加入 {count} 个改动",
24049
+ "panel.addDoneTruncated": "已加入 {count} 个改动;没有改动的文件过多,其余未加入",
24050
+ "panel.addDuplicate": "该路径已在列表中",
24051
+ "panel.addUnchanged": "该文件没有本地差异;勾选「包含无改动路径」后再添加",
24052
+ "panel.addEmpty": "没有找到可加入的改动",
24053
+ "panel.addNoEntries": "此目录为空",
24054
+ "panel.addMissing": "路径不存在",
24055
+ "panel.addOutside": "路径不在当前工作区内",
24056
+ "panel.addFailed": "添加失败:{message}",
24057
+ "action.cancel": "取消",
24058
+ "action.collapseRow": "收起",
24059
+ "action.expandRow": "展开"
22041
24060
  };
22042
24061
  /** English pending-edit review messages. */
22043
24062
  const en = {
22044
24063
  "panel.title": "Pending changes",
22045
- "panel.empty": "No pending changes",
24064
+ "panel.empty": "No files in the list yet",
22046
24065
  "panel.loading": "Reading…",
22047
24066
  "panel.readFailed": "Reading pending changes failed: {message}",
22048
24067
  "panel.group.current": "This session",
@@ -22098,6 +24117,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22098
24117
  "panel.key.openSearch": "Open diff search",
22099
24118
  "panel.key.searchNext": "Next search result",
22100
24119
  "panel.key.searchPrev": "Previous search result",
24120
+ "panel.key.matchCase": "Toggle match case",
24121
+ "panel.key.matchWholeWord": "Toggle match whole word",
22101
24122
  "panel.key.undo": "Undo",
22102
24123
  "panel.key.redo": "Redo",
22103
24124
  "panel.key.cycleNext": "Next pending file",
@@ -22145,7 +24166,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22145
24166
  "panel.importDone": "Imported {count} changes",
22146
24167
  "panel.importNoVcs": "No version control detected (Git / SVN / Perforce)",
22147
24168
  "panel.importFailed": "Import failed: {message}",
22148
- "action.search": "Search (Ctrl+F)",
24169
+ "action.search": "Search",
22149
24170
  "action.settings": "Settings",
22150
24171
  "action.expand": "Expand fullscreen",
22151
24172
  "action.exitFullscreen": "Exit fullscreen",
@@ -22154,7 +24175,39 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22154
24175
  "status.reverted": "Reverted",
22155
24176
  "status.missing": "This change no longer exists",
22156
24177
  "panel.resolvedAsk": "All changes in \"{file}\" are resolved. Remove it from the list?",
22157
- "panel.keepInList": "Keep in list"
24178
+ "panel.keepInList": "Keep in list",
24179
+ "panel.fileKeptAsk": "All changes in \"{file}\" are kept. Remove it from the list?",
24180
+ "panel.fileRevertedAsk": "All changes in \"{file}\" are reverted. Remove it from the list?",
24181
+ "panel.confirmFileRemove": "Ask after a whole-file keep/revert",
24182
+ "panel.confirmFileRemoveDesc": "After a whole-file keep/revert on a single file, ask whether to remove that file from the list. When off, it is removed straight away (Keep All / Revert All are unaffected).",
24183
+ "action.refreshVcs": "Refresh diff",
24184
+ "action.matchCase": "Match case",
24185
+ "action.matchWholeWord": "Match whole word",
24186
+ "panel.refreshDone": "Diff updated",
24187
+ "panel.refreshUnchanged": "Already up to date",
24188
+ "panel.refreshNone": "No local VCS change found",
24189
+ "panel.refreshUntrackedHint": "(new or untracked files need \"Import changes to untracked files\")",
24190
+ "panel.refreshFailed": "Refresh failed: {message}",
24191
+ "action.addPath": "Add file or directory",
24192
+ "panel.addTitle": "Add to list",
24193
+ "panel.addPathPlaceholder": "Type or paste an absolute path",
24194
+ "panel.addPathGo": "Add",
24195
+ "panel.addHint": "Open directories to walk the tree; a selection fills the full path above, and Add is what checks for local changes",
24196
+ "panel.addIncludeUnchanged": "Include unchanged paths",
24197
+ "panel.addRoot": "Workspace root",
24198
+ "panel.addTruncated": "Too many entries to show; only the first {count} are listed",
24199
+ "panel.addDone": "Added {count} change(s)",
24200
+ "panel.addDoneTruncated": "Added {count} change(s); too many unchanged files to include the rest",
24201
+ "panel.addDuplicate": "That path is already in the list",
24202
+ "panel.addUnchanged": "That file has no local change; tick \"Include unchanged paths\" and add again",
24203
+ "panel.addEmpty": "No changes found to add",
24204
+ "panel.addNoEntries": "This directory is empty",
24205
+ "panel.addMissing": "No such path",
24206
+ "panel.addOutside": "That path is outside the workspace",
24207
+ "panel.addFailed": "Add failed: {message}",
24208
+ "action.cancel": "Cancel",
24209
+ "action.collapseRow": "Collapse",
24210
+ "action.expandRow": "Expand"
22158
24211
  };
22159
24212
  //#endregion
22160
24213
  //#region lib/types/client/index.js
@@ -22269,13 +24322,13 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22269
24322
  currentSessionId = sessionId;
22270
24323
  store.refresh(sessionId);
22271
24324
  },
22272
- onKeep: (sessionId, path) => store.keep(sessionId, path),
22273
- onRevert: (sessionId, path) => {
24325
+ onKeep: (sessionId, path, keepListed) => store.keep(sessionId, path, keepListed),
24326
+ onRevert: (sessionId, path, keepListed) => {
22274
24327
  const entry = store.getSnapshot().files.find((file) => file.id === path);
22275
24328
  const before = entry?.newText;
22276
24329
  const after = entry?.oldText;
22277
24330
  const filePath = entry?.path;
22278
- return store.revert(sessionId, path).then(() => {
24331
+ return store.revert(sessionId, path, keepListed).then(() => {
22279
24332
  if (filePath !== void 0 && before !== void 0 && after !== void 0) remapFile(filePath, before, after);
22280
24333
  });
22281
24334
  },
@@ -22286,6 +24339,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22286
24339
  onUndo: (sessionId) => store.undo(sessionId),
22287
24340
  onRedo: (sessionId) => store.redo(sessionId),
22288
24341
  onImportVcs: (sessionId, includeUntracked) => store.importVcs(sessionId, includeUntracked),
24342
+ onRefreshVcs: (sessionId, id, includeUntracked) => store.refreshVcs(sessionId, id, includeUntracked),
24343
+ onBrowse: (sessionId, path) => store.browse(sessionId, path),
24344
+ onAddPath: (sessionId, path, includeUnchanged) => store.addPath(sessionId, path, includeUnchanged),
22289
24345
  onKeepAll: (sessionId) => store.keepAll(sessionId),
22290
24346
  onRevertAll: (sessionId) => store.revertAll(sessionId),
22291
24347
  onAckRedoCleared: () => store.clearRedoCleared(),