dsh-diff-approval 0.19.3 → 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 = {}) {
@@ -643,6 +1176,72 @@ window.__ModuleLoader__.load({
643
1176
  * derivation; the panel owns rendering. The `diff` package is browser-safe.
644
1177
  * @module dsh-diff-approval/client/whole-file-diff
645
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
+ }
646
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
@@ -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>`;
@@ -16227,31 +16839,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16227
16839
  /**
16228
16840
  * Tokenize guards: the viewer must never let the synchronous JS-regex engine
16229
16841
  * block the main thread on a hostile file. Lines above the length cap are
16230
- * returned plain by shiki, the per-line budget caps a single pathological
16231
- * line, and whole files above the size/line caps skip highlighting entirely
16232
- * (the diff still shows its added/deleted coloring). Company-workload files
16233
- * (generated bundles, minified output, huge data dumps) otherwise stall the
16234
- * 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.
16235
16847
  */
16236
16848
  const MAX_LINE_LENGTH = 2e3;
16237
16849
  const TOKENIZE_TIME_LIMIT_MS = 100;
16238
- const MAX_HIGHLIGHT_CHARS = 3e5;
16239
- const MAX_HIGHLIGHT_LINES = 1e4;
16850
+ const MAX_WINDOW_CHARS = 5e5;
16240
16851
  /**
16241
- * Bounded tokenize cache keyed by the code string: reselecting a recently
16242
- * viewed file (or a file whose content reference reappears) reuses the runs
16243
- * instead of re-running the engine. Insertion-ordered, so the oldest entry is
16244
- * 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).
16245
16858
  */
16246
- const TOKENIZE_CACHE_MAX = 16;
16859
+ const TOKENIZE_CACHE_MAX = 24;
16247
16860
  const tokenizeCache = /* @__PURE__ */ new Map();
16248
- /** Whether `code` is too large to highlight safely (short-circuits fast). */
16249
- function tooLargeToHighlight(code) {
16250
- if (code.length > MAX_HIGHLIGHT_CHARS) return true;
16251
- let newlines = 0;
16252
- for (let index = 0; index < code.length; index++) if (code.charCodeAt(index) === 10 && ++newlines > MAX_HIGHLIGHT_LINES) return true;
16253
- return false;
16254
- }
16255
16861
  /** All grammars this bundle registers; each entry's own `name` is the tokenize id. */
16256
16862
  const LANGS = [
16257
16863
  c_default,
@@ -16392,43 +16998,316 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16392
16998
  highlighter();
16393
16999
  }, 0).unref?.();
16394
17000
  /**
16395
- * Tokenize `code` into per-line highlighted runs when `lang` names a
16396
- * registered grammar; `undefined` means the caller renders its plain fallback.
16397
- * Each run's color is a `--shiki-*` custom property, keeping token colors on
16398
- * the harness theme's sheets. The trailing newline shiki appends as a final
16399
- * empty line is dropped so the run count matches the caller's own line array.
16400
- * @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.
16401
17015
  * @param lang - the Shiki grammar id, or `undefined` for plain text.
16402
- * @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.
16403
17021
  */
16404
- function highlightLines(code, lang) {
16405
- if (lang === void 0 || code === "") return void 0;
17022
+ function highlightWindow(lines, lang, from, to, options = {}) {
17023
+ if (lang === void 0) return void 0;
16406
17024
  if (!highlighter().getLoadedLanguages().includes(lang)) return void 0;
16407
- if (tooLargeToHighlight(code)) return void 0;
16408
- const cached = tokenizeCache.get(code)?.get(lang);
16409
- if (cached !== void 0) return cached;
16410
- 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, {
16411
17040
  lang,
16412
17041
  theme: "css-variables",
16413
17042
  tokenizeTimeLimit: TOKENIZE_TIME_LIMIT_MS,
16414
- tokenizeMaxLineLength: MAX_LINE_LENGTH
17043
+ tokenizeMaxLineLength: MAX_LINE_LENGTH,
17044
+ ...options.state === void 0 ? {} : { grammarState: options.state }
16415
17045
  });
16416
- const last = tokens[tokens.length - 1];
16417
- 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) => ({
16418
17047
  text: token.content,
16419
17048
  style: { color: token.color }
16420
17049
  })));
16421
- let byLang = tokenizeCache.get(code);
16422
- if (byLang === void 0) {
17050
+ if (cacheKey !== void 0) {
16423
17051
  if (tokenizeCache.size >= TOKENIZE_CACHE_MAX) {
16424
17052
  const oldest = tokenizeCache.keys().next().value;
16425
17053
  if (oldest !== void 0) tokenizeCache.delete(oldest);
16426
17054
  }
16427
- byLang = /* @__PURE__ */ new Map();
16428
- tokenizeCache.set(code, byLang);
17055
+ tokenizeCache.set(cacheKey, runs);
16429
17056
  }
16430
- byLang.set(lang, runs);
16431
- 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;
16432
17311
  }
16433
17312
  //#endregion
16434
17313
  //#region lib/types/client/lang.js
@@ -16487,10 +17366,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16487
17366
  * @returns the language id, or `undefined` when unknown.
16488
17367
  */
16489
17368
  function langFromPath(path) {
16490
- const match = /\.([^./\\]+)$/.exec(path);
16491
- if (match === null) return void 0;
16492
- const extension = match[1];
16493
- 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();
16494
17382
  }
16495
17383
  //#endregion
16496
17384
  //#region lib/types/client/reference.js
@@ -16754,6 +17642,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16754
17642
  const DIFF_LINE_HEIGHT_KEY = "diff-approval:diff-line-height";
16755
17643
  const DIFF_ADD_COLOR_KEY = "diff-approval:diff-add-color";
16756
17644
  const DIFF_DEL_COLOR_KEY = "diff-approval:diff-del-color";
17645
+ const LANG_BY_SUFFIX_KEY = "diff-approval:lang-by-suffix";
16757
17646
  const WRAP_PREFIX = "diff-approval:wrap:";
16758
17647
  const MD_MAX_WIDTH_MAX = 1600;
16759
17648
  /**
@@ -16910,6 +17799,49 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16910
17799
  return themeColor("--dsw-alias-state-error-primary", "#ef4444");
16911
17800
  }
16912
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
+ /**
16913
17845
  * Whether the whole-file diff view uses the two-column (side-by-side) layout.
16914
17846
  * Default off (single column): the unified diff. Only an explicit `'1'` enables
16915
17847
  * split mode.
@@ -16954,244 +17886,75 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
16954
17886
  * How many rows of lead the diff block jump leaves above the jumped-to block,
16955
17887
  * and how far the anchored navigation scans. Defaults to 2; an out-of-range or
16956
17888
  * non-integer value falls back to the default.
16957
- * @returns the lead row count.
16958
- */
16959
- function navLeadRows() {
16960
- const raw = Number.parseInt(localStorage.getItem(NAV_LEAD_KEY) ?? "", 10);
16961
- if (!Number.isInteger(raw)) return 2;
16962
- return Math.max(0, Math.min(10, raw));
16963
- }
16964
- /** Persist the block-jump lead row count. */
16965
- function setNavLeadRows(value) {
16966
- localStorage.setItem(NAV_LEAD_KEY, String(Math.max(0, Math.min(10, value))));
16967
- }
16968
- const QUICK_SUMMON_KEY = "diff-approval:quick-summon-key";
16969
- /**
16970
- * The quick-summon chord. Stored as `Modifier+...+Key`; falls back to
16971
- * {@link DEFAULT_QUICK_SUMMON}.
16972
- * @returns the chord string.
16973
- */
16974
- function quickSummonKey() {
16975
- return localStorage.getItem(QUICK_SUMMON_KEY) ?? "Ctrl+D";
16976
- }
16977
- /** Persist the quick-summon chord. */
16978
- function setQuickSummonKey(value) {
16979
- localStorage.setItem(QUICK_SUMMON_KEY, value);
16980
- }
16981
- const KEY_PREFIX = "diff-approval:key:";
16982
- /** Default chord for each configurable action (every supported key except the
16983
- * panel's own ESC-to-close, which is intentionally not remapped). */
16984
- const DEFAULT_KEYBINDINGS = {
16985
- jumpUp: "Ctrl+ArrowUp",
16986
- jumpDown: "Ctrl+ArrowDown",
16987
- copyRef: "Ctrl+L",
16988
- openSearch: "Ctrl+F",
16989
- searchNext: "F3",
16990
- searchPrev: "Shift+F3",
16991
- matchCase: "Alt+C",
16992
- matchWholeWord: "Alt+W",
16993
- undo: "Ctrl+Z",
16994
- redo: "Ctrl+Shift+Z",
16995
- cycleNext: "Ctrl+Tab",
16996
- cyclePrev: "Ctrl+Shift+Tab"
16997
- };
16998
- /** The currently configured chord for one action; falls back to its default. */
16999
- function keybindingOf(action) {
17000
- return localStorage.getItem(`${KEY_PREFIX}${action}`) ?? DEFAULT_KEYBINDINGS[action] ?? "";
17001
- }
17002
- /** Persist one action's chord. */
17003
- function setKeybinding(action, chord) {
17004
- localStorage.setItem(`${KEY_PREFIX}${action}`, chord);
17005
- }
17006
- /**
17007
- * Whether a keyboard event matches a chord string like `Ctrl+D`. Modifier
17008
- * names are matched case-insensitively (`Ctrl`/`Control`, `Alt`/`Option`,
17009
- * `Shift`, `Meta`/`Cmd`/`Command`/`Win`); the final part is the key. Exact
17010
- * modifier set is required (extra modifiers do not match).
17011
- * @param event - the keydown event.
17012
- * @param shortcut - the chord string.
17013
- * @returns whether the event matches.
17014
- */
17015
- function matchesShortcut(event, shortcut) {
17016
- const parts = shortcut.split("+").map((part) => part.trim().toLowerCase());
17017
- const key = parts.pop();
17018
- if (key === void 0 || key === "") return false;
17019
- const mods = new Set(parts);
17020
- const ctrl = mods.has("ctrl") || mods.has("control");
17021
- const alt = mods.has("alt") || mods.has("option");
17022
- const shift = mods.has("shift");
17023
- const meta = mods.has("meta") || mods.has("cmd") || mods.has("command") || mods.has("win");
17024
- return event.key.toLowerCase() === key && event.ctrlKey === ctrl && event.altKey === alt && event.shiftKey === shift && event.metaKey === meta;
17025
- }
17026
- //#endregion
17027
- //#region \0dsh-css:/home/runner/work/dsh-diff-approval/dsh-diff-approval/src/client/PendingPanel.module.css.mjs
17028
- 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_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{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)}";
17029
- const tagId = "dsh-diff-approval/PendingPanel.module.css";
17030
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
17031
- const tag = document.createElement("style");
17032
- tag.dataset.plugin = "dsh-diff-approval";
17033
- tag.dataset.pluginCss = tagId;
17034
- tag.textContent = css;
17035
- document.head.appendChild(tag);
17036
- }
17037
- var PendingPanel_module_css_default = {
17038
- "blockFlashShake": "F1KBNa_blockFlashShake",
17039
- "del": "F1KBNa_del",
17040
- "noticeButton": "F1KBNa_noticeButton",
17041
- "settingsGroupBody": "F1KBNa_settingsGroupBody",
17042
- "colorSwatch": "F1KBNa_colorSwatch",
17043
- "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
17044
- "confirmText": "F1KBNa_confirmText",
17045
- "diffActions": "F1KBNa_diffActions",
17046
- "splitLdel": "F1KBNa_splitLdel",
17047
- "toggle": "F1KBNa_toggle",
17048
- "importButton": "F1KBNa_importButton",
17049
- "diffPreview": "F1KBNa_diffPreview",
17050
- "delCount": "F1KBNa_delCount",
17051
- "addCount": "F1KBNa_addCount",
17052
- "settingsGroupChevron": "F1KBNa_settingsGroupChevron",
17053
- "searchToggleOn": "F1KBNa_searchToggleOn",
17054
- "settingsGroup": "F1KBNa_settingsGroup",
17055
- "colorHue": "F1KBNa_colorHue",
17056
- "searchCount": "F1KBNa_searchCount",
17057
- "searchMatchCurrent": "F1KBNa_searchMatchCurrent",
17058
- "lines": "F1KBNa_lines",
17059
- "mdPreviewContent": "F1KBNa_mdPreviewContent",
17060
- "rowMeta": "F1KBNa_rowMeta",
17061
- "expandExpanded": "F1KBNa_expandExpanded",
17062
- "listScroll": "F1KBNa_listScroll",
17063
- "kindHint": "F1KBNa_kindHint",
17064
- "fileList": "F1KBNa_fileList",
17065
- "settingsGroupHeader": "F1KBNa_settingsGroupHeader",
17066
- "divergedHint": "F1KBNa_divergedHint",
17067
- "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
17068
- "colorPickerPopover": "F1KBNa_colorPickerPopover",
17069
- "context": "F1KBNa_context",
17070
- "confirmActions": "F1KBNa_confirmActions",
17071
- "markerAdd": "F1KBNa_markerAdd",
17072
- "searchInput": "F1KBNa_searchInput",
17073
- "toggleOn": "F1KBNa_toggleOn",
17074
- "header": "F1KBNa_header",
17075
- "colorValue": "F1KBNa_colorValue",
17076
- "split": "F1KBNa_split",
17077
- "colorBackdrop": "F1KBNa_colorBackdrop",
17078
- "settingsRowTitle": "F1KBNa_settingsRowTitle",
17079
- "diffPath": "F1KBNa_diffPath",
17080
- "statusBar": "F1KBNa_statusBar",
17081
- "group": "F1KBNa_group",
17082
- "colorSv": "F1KBNa_colorSv",
17083
- "diffFlashFade": "F1KBNa_diffFlashFade",
17084
- "detailEmpty": "F1KBNa_detailEmpty",
17085
- "confirmCard": "F1KBNa_confirmCard",
17086
- "badge": "F1KBNa_badge",
17087
- "splitRdel": "F1KBNa_splitRdel",
17088
- "line": "F1KBNa_line",
17089
- "diffBody": "F1KBNa_diffBody",
17090
- "stepperValue": "F1KBNa_stepperValue",
17091
- "stepperButton": "F1KBNa_stepperButton",
17092
- "settingsSelector": "F1KBNa_settingsSelector",
17093
- "colorHueCursor": "F1KBNa_colorHueCursor",
17094
- "badgeLabel": "F1KBNa_badgeLabel",
17095
- "panel": "F1KBNa_panel",
17096
- "diffPreviewScroll": "F1KBNa_diffPreviewScroll",
17097
- "fileListFloat": "F1KBNa_fileListFloat",
17098
- "note": "F1KBNa_note",
17099
- "diff": "F1KBNa_diff",
17100
- "mdPreviewWrap": "F1KBNa_mdPreviewWrap",
17101
- "blockFlash": "F1KBNa_blockFlash",
17102
- "vSpacer": "F1KBNa_vSpacer",
17103
- "hint": "F1KBNa_hint",
17104
- "settingsRowDesc": "F1KBNa_settingsRowDesc",
17105
- "gutter": "F1KBNa_gutter",
17106
- "intraAdd": "F1KBNa_intraAdd",
17107
- "colorPickerTrigger": "F1KBNa_colorPickerTrigger",
17108
- "splitCol": "F1KBNa_splitCol",
17109
- "resizeHandle": "F1KBNa_resizeHandle",
17110
- "detail": "F1KBNa_detail",
17111
- "diffBodySplit": "F1KBNa_diffBodySplit",
17112
- "missing": "F1KBNa_missing",
17113
- "bulkActions": "F1KBNa_bulkActions",
17114
- "close": "F1KBNa_close",
17115
- "colorPickerChevron": "F1KBNa_colorPickerChevron",
17116
- "diffStats": "F1KBNa_diffStats",
17117
- "badgeCount": "F1KBNa_badgeCount",
17118
- "colorPickerPreview": "F1KBNa_colorPickerPreview",
17119
- "divider": "F1KBNa_divider",
17120
- "stepper": "F1KBNa_stepper",
17121
- "splitDivider": "F1KBNa_splitDivider",
17122
- "readError": "F1KBNa_readError",
17123
- "title": "F1KBNa_title",
17124
- "overviewMarker": "F1KBNa_overviewMarker",
17125
- "statusAction": "F1KBNa_statusAction",
17126
- "mdPreviewFallback": "F1KBNa_mdPreviewFallback",
17127
- "code": "F1KBNa_code",
17128
- "settingsRowText": "F1KBNa_settingsRowText",
17129
- "splitLadd": "F1KBNa_splitLadd",
17130
- "toggleThumb": "F1KBNa_toggleThumb",
17131
- "missingHint": "F1KBNa_missingHint",
17132
- "blockPosition": "F1KBNa_blockPosition",
17133
- "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
17134
- "settingsGroupText": "F1KBNa_settingsGroupText",
17135
- "colorPickerInputRow": "F1KBNa_colorPickerInputRow",
17136
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
17137
- "rail": "F1KBNa_rail",
17138
- "colorPicker": "F1KBNa_colorPicker",
17139
- "mdPreviewBody": "F1KBNa_mdPreviewBody",
17140
- "action": "F1KBNa_action",
17141
- "expand": "F1KBNa_expand",
17142
- "markerDel": "F1KBNa_markerDel",
17143
- "splitHScrollFill": "F1KBNa_splitHScrollFill",
17144
- "langLabel": "F1KBNa_langLabel",
17145
- "importNote": "F1KBNa_importNote",
17146
- "wrap": "F1KBNa_wrap",
17147
- "subline": "F1KBNa_subline",
17148
- "colorPickerInput": "F1KBNa_colorPickerInput",
17149
- "colorPickerPanel": "F1KBNa_colorPickerPanel",
17150
- "mdPreviewDouble": "F1KBNa_mdPreviewDouble",
17151
- "settingsRow": "F1KBNa_settingsRow",
17152
- "noteCentered": "F1KBNa_noteCentered",
17153
- "add": "F1KBNa_add",
17154
- "notice": "F1KBNa_notice",
17155
- "emptyState": "F1KBNa_emptyState",
17156
- "kindTag": "F1KBNa_kindTag",
17157
- "diffFlashShake": "F1KBNa_diffFlashShake",
17158
- "splitCols": "F1KBNa_splitCols",
17159
- "searchBar": "F1KBNa_searchBar",
17160
- "confirmBackdrop": "F1KBNa_confirmBackdrop",
17161
- "searchToggle": "F1KBNa_searchToggle",
17162
- "noticeText": "F1KBNa_noticeText",
17163
- "splitRoot": "F1KBNa_splitRoot",
17164
- "intraDel": "F1KBNa_intraDel",
17165
- "rows": "F1KBNa_rows",
17166
- "mdPreviewSingle": "F1KBNa_mdPreviewSingle",
17167
- "colorSvCursor": "F1KBNa_colorSvCursor",
17168
- "wrapActive": "F1KBNa_wrapActive",
17169
- "states": "F1KBNa_states",
17170
- "settingsGroupTitle": "F1KBNa_settingsGroupTitle",
17171
- "footerButtons": "F1KBNa_footerButtons",
17172
- "rowFailed": "F1KBNa_rowFailed",
17173
- "searchMatch": "F1KBNa_searchMatch",
17174
- "splitHScroll": "F1KBNa_splitHScroll",
17175
- "actionPrimary": "F1KBNa_actionPrimary",
17176
- "diffHeader": "F1KBNa_diffHeader",
17177
- "settingsGroupDesc": "F1KBNa_settingsGroupDesc",
17178
- "splitRadd": "F1KBNa_splitRadd",
17179
- "settingsPage": "F1KBNa_settingsPage",
17180
- "stepperButtonUp": "F1KBNa_stepperButtonUp",
17181
- "rowHead": "F1KBNa_rowHead",
17182
- "splitHScrollRow": "F1KBNa_splitHScrollRow",
17183
- "diffPreviewTitle": "F1KBNa_diffPreviewTitle",
17184
- "blockActions": "F1KBNa_blockActions",
17185
- "rowPath": "F1KBNa_rowPath",
17186
- "actionError": "F1KBNa_actionError",
17187
- "flexSpacer": "F1KBNa_flexSpacer",
17188
- "iconAction": "F1KBNa_iconAction",
17189
- "overviewRuler": "F1KBNa_overviewRuler",
17190
- "langSelect": "F1KBNa_langSelect",
17191
- "headerActions": "F1KBNa_headerActions",
17192
- "layer": "F1KBNa_layer",
17193
- "row": "F1KBNa_row"
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"
17194
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
+ }
17195
17958
  //#endregion
17196
17959
  //#region lib/types/client/PendingPanel.js
17197
17960
  /** Sidebar-foot pending-edit review action and the split review panel it opens. */
@@ -17215,8 +17978,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17215
17978
  const COMPOSER_HEIGHT_VAR = "--dsh-composer-height";
17216
17979
  /** Interactive composer/approval cards; clicking these keeps the panel open.
17217
17980
  Deliberately the cards themselves, not the seat: the approval frame's wide
17218
- side gutters are blank space, so a click there must still close. */
17219
- 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\"]";
17220
17986
  /** Seat counts as docked when its bottom is this close to the window bottom. */
17221
17987
  const DOCKED_TOLERANCE_PX = 48;
17222
17988
  /** File-list pane width bounds for the manual split drag, in px. */
@@ -17468,6 +18234,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17468
18234
  * 5px frame padding on each side + 1px border on each side, plus a little
17469
18235
  * breathing room so the bottom padding never sits flush against it. */
17470
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;
17471
18241
  /** The trailing file-name segment of a path, used for row display. */
17472
18242
  function basenameOf(path) {
17473
18243
  const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
@@ -17799,69 +18569,6 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17799
18569
  ]
17800
18570
  });
17801
18571
  });
17802
- /**
17803
- * One diff block's old/new line ranges, 1-based inclusive, for block-level
17804
- * keep/revert. A side with no lines (a pure addition or deletion) is empty;
17805
- * its start is that side's insertion point — the line after the surrounding
17806
- * context — so the host can insert there.
17807
- * @param rows - the whole-file diff rows.
17808
- * @param block - the block's row range.
17809
- * @returns the old and new line ranges.
17810
- */
17811
- function blockRangesOf(rows, block) {
17812
- let oldStart = Infinity;
17813
- let oldEnd = -Infinity;
17814
- let newStart = Infinity;
17815
- let newEnd = -Infinity;
17816
- for (let index = block.start; index <= block.end; index++) {
17817
- const row = rows[index];
17818
- if (row === void 0) continue;
17819
- if (row.oldLine !== void 0) {
17820
- oldStart = Math.min(oldStart, row.oldLine);
17821
- oldEnd = Math.max(oldEnd, row.oldLine);
17822
- }
17823
- if (row.newLine !== void 0) {
17824
- newStart = Math.min(newStart, row.newLine);
17825
- newEnd = Math.max(newEnd, row.newLine);
17826
- }
17827
- }
17828
- const before = rows[block.start - 1];
17829
- if (oldStart === Infinity) {
17830
- oldStart = (before?.oldLine ?? 0) + 1;
17831
- oldEnd = oldStart - 1;
17832
- }
17833
- if (newStart === Infinity) {
17834
- newStart = (before?.newLine ?? 0) + 1;
17835
- newEnd = newStart - 1;
17836
- }
17837
- return {
17838
- oldStart,
17839
- oldEnd,
17840
- newStart,
17841
- newEnd
17842
- };
17843
- }
17844
- /** Split a row list into maximal runs of non-context rows. */
17845
- function changeBlocksOf(diff) {
17846
- const blocks = [];
17847
- let start = -1;
17848
- diff.rows.forEach((row, index) => {
17849
- if (row.kind !== "context") {
17850
- if (start === -1) start = index;
17851
- } else if (start !== -1) {
17852
- blocks.push({
17853
- start,
17854
- end: index - 1
17855
- });
17856
- start = -1;
17857
- }
17858
- });
17859
- if (start !== -1) blocks.push({
17860
- start,
17861
- end: diff.rows.length - 1
17862
- });
17863
- return blocks;
17864
- }
17865
18572
  /** Whether a block keep/revert range covers the file's entire change region, so
17866
18573
  * applying it leaves the file with no pending diff — the "remove or keep in
17867
18574
  * list" prompt applies. This generalises the single-block case to a selection
@@ -17892,6 +18599,42 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17892
18599
  for (let i = 0; i < rows.length; i++) if (matchRangesOf(rows[i].text, query, options).length > 0) out.push(i);
17893
18600
  return out;
17894
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
+ }
17895
18638
  /** One side's line-content for the split view: the highlighted runs or plain text. */
17896
18639
  function splitSideContent(side, wrapped, runs, intra, options, query, current) {
17897
18640
  if (side === void 0) return "";
@@ -17940,7 +18683,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17940
18683
  });
17941
18684
  }
17942
18685
  /** The two-column (side-by-side) whole-file diff view. */
17943
- 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) {
17944
18687
  const ROW_HEIGHT_PX = diffLineHeight();
17945
18688
  const NAV_ANCHOR_TOLERANCE_PX = ROW_HEIGHT_PX / 4;
17946
18689
  const { pairs, pairOfRow } = (0, react.useMemo)(() => computeSideBySideDiff(model.diff.rows, true), [model]);
@@ -18230,6 +18973,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18230
18973
  end = Math.max(end, selection.end + 1);
18231
18974
  }
18232
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
+ ]);
18233
18989
  const wrapArmedRef = (0, react.useRef)(0);
18234
18990
  const jump = (direction, wrapGuard = false, singleToast = wrapGuard) => {
18235
18991
  const count = blockOfPair.length;
@@ -18694,6 +19450,21 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18694
19450
  function codeCellAt(node) {
18695
19451
  return ((node instanceof Element ? node : node.parentElement)?.closest("[data-diff-row], [data-diff-split-row]"))?.querySelector("[data-diff-code]") ?? null;
18696
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
+ }
18697
19468
  /** Character offset of a selection boundary within its line's code text. A
18698
19469
  * boundary outside the code cell (the line-number gutter) sits at the line's
18699
19470
  * start — offset 0, never the line's end — so it never skips the line. */
@@ -18873,8 +19644,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18873
19644
  label: languageDisplayName(language)
18874
19645
  }))], [t]);
18875
19646
  const detectedLang = (0, react.useMemo)(() => langFromPath(file.path), [file.path]);
18876
- const lang = (0, react.useMemo)(() => langOverride ?? detectedLang, [detectedLang, langOverride]);
18877
- 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);
18878
19652
  const wrapKey = lang ?? "";
18879
19653
  const [langWrap, setLangWrap] = (0, react.useState)(() => wrapEnabled(wrapKey));
18880
19654
  (0, react.useEffect)(() => {
@@ -18888,6 +19662,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18888
19662
  const [tabWidthSpaces] = (0, react.useState)(() => tabWidth());
18889
19663
  const [splitView, setSplitView] = (0, react.useState)(() => splitMode());
18890
19664
  const [mdPreview, setMdPreview] = (0, react.useState)(() => mdPreviewEnabled());
19665
+ const previewActive = mdPreview && lang === "markdown";
18891
19666
  const mdPreviewBodyRef = (0, react.useRef)(null);
18892
19667
  const [mdRulerMarkers, setMdRulerMarkers] = (0, react.useState)([]);
18893
19668
  const [mdImageTick, setMdImageTick] = (0, react.useState)(0);
@@ -18929,12 +19704,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18929
19704
  ]);
18930
19705
  const splitPairs = (0, react.useMemo)(() => splitView ? computeSideBySideDiff(model.diff.rows, true).pairs : null, [splitView, model]);
18931
19706
  (0, react.useEffect)(() => {
18932
- if (!mdPreview) return;
19707
+ if (!previewActive) return;
18933
19708
  const body = mdPreviewBodyRef.current;
18934
19709
  if (body === null) return;
18935
19710
  resolvePreviewImages(body, file.path, workspacePath, (path) => onPreviewImage(file.sessionId, path)).then(() => setMdImageTick((tick) => tick + 1));
18936
19711
  }, [
18937
- mdPreview,
19712
+ previewActive,
18938
19713
  splitView,
18939
19714
  file.path,
18940
19715
  file.sessionId,
@@ -18977,37 +19752,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18977
19752
  return markers;
18978
19753
  }, [model]);
18979
19754
  (0, react.useLayoutEffect)(() => {
18980
- if (!mdPreview || splitView) return;
19755
+ if (!previewActive) return;
18981
19756
  const body = mdPreviewBodyRef.current;
18982
19757
  if (body === null) return;
18983
19758
  const measured = markdownPreviewMarkers(body);
18984
19759
  setMdRulerMarkers(measured.length > 0 ? measured : rulerMarkers);
18985
19760
  }, [
18986
- mdPreview,
19761
+ previewActive,
18987
19762
  splitView,
18988
19763
  file.oldText,
18989
19764
  file.newText,
18990
19765
  mdImageTick,
18991
19766
  rulerMarkers
18992
19767
  ]);
18993
- const [runs, setRuns] = (0, react.useState)(void 0);
18994
- (0, react.useEffect)(() => {
18995
- setRuns(void 0);
18996
- const timer = window.setTimeout(() => {
18997
- setRuns({
18998
- oldRuns: highlightLines(file.oldText, lang),
18999
- newRuns: highlightLines(file.newText, lang)
19000
- });
19001
- }, 0);
19002
- return () => {
19003
- window.clearTimeout(timer);
19004
- };
19005
- }, [
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
+ }), [
19006
19774
  file.id,
19775
+ lang,
19007
19776
  file.oldText,
19008
- file.newText,
19009
- lang
19777
+ file.newText
19010
19778
  ]);
19779
+ const [splitVisible, setSplitVisible] = (0, react.useState)(void 0);
19011
19780
  const bodyRef = (0, react.useRef)(null);
19012
19781
  const [focus, setFocus] = (0, react.useState)(0);
19013
19782
  const [scrollTick, setScrollTick] = (0, react.useState)(0);
@@ -19017,6 +19786,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19017
19786
  const [hScrollbarPx, setHScrollbarPx] = (0, react.useState)(0);
19018
19787
  const [hoveredBlock, setHoveredBlock] = (0, react.useState)(void 0);
19019
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);
19020
19801
  const selectionTextRef = (0, react.useRef)("");
19021
19802
  const [copied, setCopied] = (0, react.useState)(false);
19022
19803
  const [searchOpen, setSearchOpen] = (0, react.useState)(false);
@@ -19029,6 +19810,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19029
19810
  const bumpFlash = (shake) => {
19030
19811
  pinShakeRef.current = shake;
19031
19812
  setFlashKey((prev) => prev + 1);
19813
+ previewFlashPendingRef.current = true;
19032
19814
  };
19033
19815
  (0, react.useEffect)(() => {
19034
19816
  setFocus(0);
@@ -19054,39 +19836,353 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19054
19836
  return model.blocks.map((block) => blockRangesOf(model.diff.rows, block));
19055
19837
  }, [model]);
19056
19838
  const coveredBlockIndices = (0, react.useMemo)(() => {
19839
+ if (previewActive) return previewCovered;
19057
19840
  if (selection === void 0 || splitView) return [];
19058
19841
  const covered = [];
19059
19842
  for (let index = 0; index < model.blocks.length; index++) {
19060
19843
  const block = model.blocks[index];
19061
19844
  if (selection.start <= block.start && block.end <= selection.end) covered.push(index);
19062
19845
  }
19063
- return covered;
19846
+ return covered;
19847
+ }, [
19848
+ selection,
19849
+ splitView,
19850
+ model,
19851
+ mdPreview,
19852
+ lang,
19853
+ previewCovered
19854
+ ]);
19855
+ const selectionRange = (0, react.useMemo)(() => {
19856
+ if (coveredBlockIndices.length === 0) return void 0;
19857
+ const firstIndex = coveredBlockIndices[0];
19858
+ const lastIndex = coveredBlockIndices[coveredBlockIndices.length - 1];
19859
+ if (firstIndex === void 0 || lastIndex === void 0) return void 0;
19860
+ const first = model.blocks[firstIndex];
19861
+ const last = model.blocks[lastIndex];
19862
+ if (first === void 0 || last === void 0) return void 0;
19863
+ return blockRangesOf(model.diff.rows, {
19864
+ start: first.start,
19865
+ end: last.end
19866
+ });
19867
+ }, [coveredBlockIndices, model]);
19868
+ const searchMatches = (0, react.useMemo)(() => matchingRows(model.diff.rows, searchQuery, search.options), [
19869
+ model,
19870
+ searchQuery,
19871
+ search.options
19872
+ ]);
19873
+ const searchHitSet = (0, react.useMemo)(() => new Set(searchMatches), [searchMatches]);
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
+ }
19064
20154
  }, [
19065
- selection,
19066
- splitView,
19067
- model
20155
+ previewActive,
20156
+ searchQuery,
20157
+ search.options,
20158
+ file.oldText,
20159
+ file.newText,
20160
+ splitView
19068
20161
  ]);
19069
- const selectionRange = (0, react.useMemo)(() => {
19070
- if (coveredBlockIndices.length === 0) return void 0;
19071
- const firstIndex = coveredBlockIndices[0];
19072
- const lastIndex = coveredBlockIndices[coveredBlockIndices.length - 1];
19073
- if (firstIndex === void 0 || lastIndex === void 0) return void 0;
19074
- const first = model.blocks[firstIndex];
19075
- const last = model.blocks[lastIndex];
19076
- if (first === void 0 || last === void 0) return void 0;
19077
- return blockRangesOf(model.diff.rows, {
19078
- start: first.start,
19079
- end: last.end
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");
19080
20170
  });
19081
- }, [coveredBlockIndices, model]);
19082
- const searchMatches = (0, react.useMemo)(() => matchingRows(model.diff.rows, searchQuery, search.options), [
19083
- model,
19084
- searchQuery,
19085
- search.options
20171
+ }, [
20172
+ previewActive,
20173
+ previewHitCount,
20174
+ searchIndex
19086
20175
  ]);
19087
- const searchHitSet = (0, react.useMemo)(() => new Set(searchMatches), [searchMatches]);
19088
- const currentSearchRow = searchMatches.length === 0 ? void 0 : searchMatches[searchIndex % searchMatches.length];
19089
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
+ }
19090
20186
  if (searchMatches.length === 0) return;
19091
20187
  if (cursorPosRef.current !== void 0) {
19092
20188
  setSearchIndex(startIndexFor(searchQuery));
@@ -19122,14 +20218,17 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19122
20218
  }
19123
20219
  search.sync();
19124
20220
  const live = window.getSelection();
19125
- const liveRange = splitView ? splitRowRangeOf(live) : rowRangeOf(live);
20221
+ const liveRange = splitView && !previewActive ? splitRowRangeOf(live) : rowRangeOf(live);
19126
20222
  const pos = liveRange !== void 0 ? liveRange : selection;
19127
20223
  cursorPosRef.current = pos;
19128
20224
  lastRecordedCursorRef.current = pos;
19129
20225
  const liveText = (live?.toString() ?? "").trim();
19130
20226
  const value = liveText !== "" && !liveText.includes("\n") ? liveText : selectionTextRef.current;
19131
20227
  setSearchQuery(value);
19132
- setSearchIndex(startIndexFor(value));
20228
+ if (previewActive) {
20229
+ previewSearchAnchorRef.current = true;
20230
+ setSearchIndex(0);
20231
+ } else setSearchIndex(startIndexFor(value));
19133
20232
  setSearchOpen(true);
19134
20233
  requestAnimationFrame(() => {
19135
20234
  searchInputRef.current?.focus();
@@ -19148,7 +20247,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19148
20247
  searchInputRef.current?.focus();
19149
20248
  };
19150
20249
  const toggleSearch = () => {
19151
- if (splitView) {
20250
+ if (splitView && !previewActive) {
19152
20251
  splitDiffRef.current?.toggleSearch();
19153
20252
  return;
19154
20253
  }
@@ -19254,6 +20353,28 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19254
20353
  end = Math.max(end, selection.end + 1);
19255
20354
  }
19256
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
+ });
19257
20378
  const blockEnd = hoveredBlock === void 0 ? void 0 : model.blocks[hoveredBlock]?.end;
19258
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)));
19259
20380
  const selectionBlockEnd = (() => {
@@ -19283,7 +20404,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19283
20404
  }, [
19284
20405
  file.id,
19285
20406
  splitView,
19286
- mdPreview
20407
+ previewActive
19287
20408
  ]);
19288
20409
  (0, react.useEffect)(() => {
19289
20410
  const body = bodyRef.current;
@@ -19301,12 +20422,20 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19301
20422
  }, [
19302
20423
  file.id,
19303
20424
  langWrap,
19304
- mdPreview
20425
+ previewActive
19305
20426
  ]);
19306
20427
  (0, react.useLayoutEffect)(() => {
19307
20428
  if (rowCount === 0) return;
19308
20429
  const block = model.blocks[focus];
19309
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
+ }
19310
20439
  const body = bodyRef.current;
19311
20440
  if (body === null) return;
19312
20441
  const target = offsetOf(block.start) - leadRows * ROW_HEIGHT_PX;
@@ -19316,7 +20445,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19316
20445
  }, [
19317
20446
  scrollTick,
19318
20447
  rowOffsets === null,
19319
- mdPreview
20448
+ previewActive
19320
20449
  ]);
19321
20450
  const wrapArmedRef = (0, react.useRef)(0);
19322
20451
  const jump = (direction, wrapGuard = false, singleToast = wrapGuard) => {
@@ -19336,7 +20465,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19336
20465
  wrapArmedRef.current = 0;
19337
20466
  } else wrapArmedRef.current = 0;
19338
20467
  }
19339
- setFocus((current) => {
20468
+ const targetOf = (current) => {
19340
20469
  if (direction === -1) return (current - 1 + count) % count;
19341
20470
  const top = bodyRef.current?.scrollTop ?? 0;
19342
20471
  for (let index = current + 1; index < count; index++) {
@@ -19345,12 +20474,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19345
20474
  if (offsetOf(block.start) >= top) return index;
19346
20475
  }
19347
20476
  return 0;
19348
- });
20477
+ };
20478
+ const landed = targetOf(focus);
20479
+ setFocus(landed);
19349
20480
  setScrollTick((tick) => tick + 1);
19350
20481
  bumpFlash(false);
19351
20482
  };
19352
20483
  const jumpBlock = (direction, wrapGuard = false, singleToast = wrapGuard) => {
19353
- if (splitView) {
20484
+ if (splitView && !previewActive) {
19354
20485
  splitDiffRef.current?.jump(direction, wrapGuard, singleToast);
19355
20486
  return;
19356
20487
  }
@@ -19389,6 +20520,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19389
20520
  if (firstCovered === void 0) return;
19390
20521
  await runBlockAction(action, selectionRange, firstCovered);
19391
20522
  setSelection(void 0);
20523
+ setPreviewCovered([]);
19392
20524
  window.getSelection()?.removeAllRanges?.();
19393
20525
  };
19394
20526
  (0, react.useEffect)(() => {
@@ -19419,6 +20551,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19419
20551
  };
19420
20552
  (0, react.useEffect)(() => {
19421
20553
  const update = () => {
20554
+ if (previewActive) {
20555
+ setPreviewCovered(coveredPreviewBlocks());
20556
+ selectionTextRef.current = "";
20557
+ return;
20558
+ }
19422
20559
  const live = window.getSelection();
19423
20560
  const range = splitView ? splitRowRangeOf(live) : rowRangeOf(live);
19424
20561
  let text = "";
@@ -19440,7 +20577,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19440
20577
  return () => {
19441
20578
  document.removeEventListener("selectionchange", update);
19442
20579
  };
19443
- }, [file.id, splitView]);
20580
+ }, [
20581
+ file.id,
20582
+ splitView,
20583
+ mdPreview,
20584
+ lang
20585
+ ]);
19444
20586
  (0, react.useEffect)(() => {
19445
20587
  const onCopy = (event) => {
19446
20588
  const anchor = window.getSelection()?.anchorNode;
@@ -19508,8 +20650,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19508
20650
  (0, react.useEffect)(() => {
19509
20651
  const onKeyDown = (event) => {
19510
20652
  if (!matchesShortcut(event, keybindingOf("openSearch"))) return;
20653
+ if (pathPickerOpen()) return;
19511
20654
  event.preventDefault();
19512
- if (splitView) {
20655
+ if (splitView && !previewActive) {
19513
20656
  splitDiffRef.current?.openSearch();
19514
20657
  return;
19515
20658
  }
@@ -19527,11 +20670,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19527
20670
  if (matchesShortcut(event, keybindingOf("searchNext"))) direction = 1;
19528
20671
  else if (matchesShortcut(event, keybindingOf("searchPrev"))) direction = -1;
19529
20672
  if (direction === 0) return;
19530
- if (splitView) {
20673
+ if (splitView && !previewActive) {
19531
20674
  if (splitDiffRef.current?.searchNext(direction)) event.preventDefault();
19532
20675
  return;
19533
20676
  }
19534
- if (searchOpen && searchMatches.length > 0) {
20677
+ if (searchOpen && searchMatchCount > 0) {
19535
20678
  event.preventDefault();
19536
20679
  goSearch(direction);
19537
20680
  }
@@ -19543,14 +20686,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19543
20686
  }, [
19544
20687
  searchOpen,
19545
20688
  searchMatches,
19546
- splitView
20689
+ searchMatchCount,
20690
+ splitView,
20691
+ previewActive
19547
20692
  ]);
19548
20693
  (0, react.useEffect)(() => {
19549
20694
  const onKeyDown = (event) => {
19550
20695
  if (!isSearchInputEvent(event)) return;
19551
20696
  const option = matchesShortcut(event, keybindingOf("matchCase")) ? "case" : matchesShortcut(event, keybindingOf("matchWholeWord")) ? "word" : void 0;
19552
20697
  if (option === void 0) return;
19553
- if (splitView) {
20698
+ if (splitView && !previewActive) {
19554
20699
  const handle = splitDiffRef.current;
19555
20700
  if (option === "case" ? handle?.toggleMatchCase() === true : handle?.toggleMatchWholeWord() === true) event.preventDefault();
19556
20701
  return;
@@ -19567,13 +20712,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19567
20712
  }, [
19568
20713
  searchOpen,
19569
20714
  splitView,
20715
+ previewActive,
19570
20716
  search
19571
20717
  ]);
19572
20718
  (0, react.useEffect)(() => {
19573
20719
  const onKeyDown = (event) => {
19574
20720
  if (event.key !== "Escape" || !isInPanelEvent(event)) return;
19575
- if (mdPreview && lang === "markdown") return;
19576
- if (splitView) {
20721
+ if (pathPickerOpen()) return;
20722
+ if (!previewActive && splitView) {
19577
20723
  if (splitDiffRef.current?.closeSearch() === true) event.preventDefault();
19578
20724
  return;
19579
20725
  }
@@ -19588,8 +20734,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19588
20734
  }, [
19589
20735
  searchOpen,
19590
20736
  splitView,
19591
- mdPreview,
19592
- lang
20737
+ previewActive
19593
20738
  ]);
19594
20739
  const jumpRef = (0, react.useRef)(jumpBlock);
19595
20740
  jumpRef.current = jumpBlock;
@@ -19599,6 +20744,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19599
20744
  if (matchesShortcut(event, keybindingOf("jumpUp"))) direction = -1;
19600
20745
  else if (matchesShortcut(event, keybindingOf("jumpDown"))) direction = 1;
19601
20746
  if (direction === 0) return;
20747
+ if (pathPickerOpen()) return;
19602
20748
  if (isTextFieldEvent(event)) return;
19603
20749
  event.preventDefault();
19604
20750
  jumpRef.current(direction, true);
@@ -19619,6 +20765,123 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19619
20765
  const flashTop = focusedBlock === void 0 ? 0 : Math.max(0, offsetOf(focusedBlock.start) - scrollTop);
19620
20766
  const flashBottom = focusedBlock === void 0 ? 0 : Math.min(viewportHeight > 0 ? viewportHeight : Number.POSITIVE_INFINITY, offsetOf(focusedBlock.end + 1) - scrollTop);
19621
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;
19622
20885
  return (0, react_jsx_runtime.jsxs)("div", {
19623
20886
  className: PendingPanel_module_css_default.diff,
19624
20887
  "data-diff-approval-diff": true,
@@ -19809,7 +21072,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19809
21072
  className: PendingPanel_module_css_default.missingHint,
19810
21073
  children: t("panel.missingHint")
19811
21074
  }),
19812
- mdPreview && lang === "markdown" ? (0, react_jsx_runtime.jsx)(MarkdownPreviewBoundary, {
21075
+ previewActive ? (0, react_jsx_runtime.jsx)(MarkdownPreviewBoundary, {
19813
21076
  fallback: (0, react_jsx_runtime.jsx)("div", {
19814
21077
  className: PendingPanel_module_css_default.mdPreviewFallback,
19815
21078
  "data-diff-md-preview-fallback": true,
@@ -19817,29 +21080,146 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19817
21080
  }),
19818
21081
  children: (0, react_jsx_runtime.jsxs)("div", {
19819
21082
  className: PendingPanel_module_css_default.mdPreviewWrap,
19820
- children: [(0, react_jsx_runtime.jsx)("div", {
19821
- className: `${PendingPanel_module_css_default.mdPreviewBody} ${splitView ? PendingPanel_module_css_default.mdPreviewDouble : PendingPanel_module_css_default.mdPreviewSingle}`,
19822
- "data-diff-md-preview-body": true,
19823
- "data-diff-md-mode": splitView ? "double" : "single",
19824
- ref: mdPreviewBodyRef,
19825
- children: (0, react_jsx_runtime.jsx)("div", {
19826
- className: PendingPanel_module_css_default.mdPreviewContent,
19827
- style: { maxWidth: splitView ? mdPreviewMaxWidthPx * 2 : mdPreviewMaxWidthPx },
19828
- dangerouslySetInnerHTML: { __html: renderMarkdownPreview(file.oldText, file.newText, splitView ? "double" : "single") }
19829
- })
19830
- }), !splitView && mdRulerMarkers.length > 0 && (0, react_jsx_runtime.jsx)("div", {
19831
- className: PendingPanel_module_css_default.overviewRuler,
19832
- "data-diff-approval-ruler": true,
19833
- "aria-hidden": "true",
19834
- children: mdRulerMarkers.map((marker, index) => (0, react_jsx_runtime.jsx)("div", {
19835
- className: `${PendingPanel_module_css_default.overviewMarker} ${marker.kind === "del" ? PendingPanel_module_css_default.markerDel : PendingPanel_module_css_default.markerAdd}`,
19836
- "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,
19837
21114
  style: {
19838
- top: `${marker.top}%`,
19839
- height: `${marker.height}%`
21115
+ top: previewFlashNow.top,
21116
+ height: previewFlashNow.height,
21117
+ left: previewFlash.left,
21118
+ right: previewFlash.right
19840
21119
  }
19841
- }, index))
19842
- })]
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
+ ]
19843
21223
  })
19844
21224
  }) : splitView ? (0, react_jsx_runtime.jsx)(SplitDiff, {
19845
21225
  ref: splitDiffRef,
@@ -19854,7 +21234,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19854
21234
  leadRows,
19855
21235
  onBlockKeep,
19856
21236
  onBlockRevert,
19857
- onWrapToast: (text) => onToast(text)
21237
+ onWrapToast: (text) => onToast(text),
21238
+ onVisibleLines: onSplitVisibleLines
19858
21239
  }) : (0, react_jsx_runtime.jsxs)("div", {
19859
21240
  className: PendingPanel_module_css_default.diffBodyWrap,
19860
21241
  onMouseLeave: () => {
@@ -19992,117 +21373,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19992
21373
  width: flashWidth
19993
21374
  }
19994
21375
  }, flashKey),
19995
- searchOpen && (0, react_jsx_runtime.jsxs)("div", {
19996
- className: PendingPanel_module_css_default.searchBar,
19997
- "data-diff-searchbar": true,
19998
- children: [
19999
- (0, react_jsx_runtime.jsx)("input", {
20000
- ref: searchInputRef,
20001
- className: PendingPanel_module_css_default.searchInput,
20002
- "data-diff-search-input": true,
20003
- value: searchQuery,
20004
- placeholder: t("panel.searchPlaceholder"),
20005
- onChange: (event) => {
20006
- const value = event.target.value;
20007
- setSearchQuery(value);
20008
- setSearchIndex(startIndexFor(value));
20009
- },
20010
- onKeyDown: (event) => {
20011
- if (event.key === "Enter") {
20012
- event.preventDefault();
20013
- goSearch(event.shiftKey ? -1 : 1);
20014
- }
20015
- }
20016
- }),
20017
- (0, react_jsx_runtime.jsx)("span", {
20018
- className: PendingPanel_module_css_default.searchCount,
20019
- "data-diff-search-count": true,
20020
- children: searchMatches.length === 0 ? "0/0" : `${searchIndex % searchMatches.length + 1}/${searchMatches.length}`
20021
- }),
20022
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20023
- label: withChord(t("action.matchCase"), "matchCase"),
20024
- side: "bottom",
20025
- delayMs: 500,
20026
- children: (0, react_jsx_runtime.jsx)("button", {
20027
- type: "button",
20028
- className: search.caseSensitive ? `${PendingPanel_module_css_default.searchToggle} ${PendingPanel_module_css_default.searchToggleOn}` : PendingPanel_module_css_default.searchToggle,
20029
- "data-diff-search-case": true,
20030
- "data-on": search.caseSensitive ? "" : void 0,
20031
- "aria-label": t("action.matchCase"),
20032
- "aria-pressed": search.caseSensitive,
20033
- onClick: () => {
20034
- andRefocus(() => {
20035
- search.toggleCase();
20036
- });
20037
- },
20038
- children: (0, react_jsx_runtime.jsx)(SearchOptionIcon, { kind: "case" })
20039
- })
20040
- }),
20041
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20042
- label: withChord(t("action.matchWholeWord"), "matchWholeWord"),
20043
- side: "bottom",
20044
- delayMs: 500,
20045
- children: (0, react_jsx_runtime.jsx)("button", {
20046
- type: "button",
20047
- className: search.wholeWord ? `${PendingPanel_module_css_default.searchToggle} ${PendingPanel_module_css_default.searchToggleOn}` : PendingPanel_module_css_default.searchToggle,
20048
- "data-diff-search-word": true,
20049
- "data-on": search.wholeWord ? "" : void 0,
20050
- "aria-label": t("action.matchWholeWord"),
20051
- "aria-pressed": search.wholeWord,
20052
- onClick: () => {
20053
- andRefocus(() => {
20054
- search.toggleWord();
20055
- });
20056
- },
20057
- children: (0, react_jsx_runtime.jsx)(SearchOptionIcon, { kind: "word" })
20058
- })
20059
- }),
20060
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20061
- label: withChord(t("action.prevDiff"), "searchPrev"),
20062
- side: "bottom",
20063
- delayMs: 500,
20064
- children: (0, react_jsx_runtime.jsx)("button", {
20065
- type: "button",
20066
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20067
- "data-diff-search-prev": true,
20068
- "aria-label": t("action.prevDiff"),
20069
- disabled: searchMatches.length === 0,
20070
- onClick: () => {
20071
- andRefocus(() => {
20072
- goSearch(-1);
20073
- });
20074
- },
20075
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
20076
- })
20077
- }),
20078
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20079
- label: withChord(t("action.nextDiff"), "searchNext"),
20080
- side: "bottom",
20081
- delayMs: 500,
20082
- children: (0, react_jsx_runtime.jsx)("button", {
20083
- type: "button",
20084
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20085
- "data-diff-search-next": true,
20086
- "aria-label": t("action.nextDiff"),
20087
- disabled: searchMatches.length === 0,
20088
- onClick: () => {
20089
- andRefocus(() => {
20090
- goSearch(1);
20091
- });
20092
- },
20093
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
20094
- })
20095
- }),
20096
- (0, react_jsx_runtime.jsx)("button", {
20097
- type: "button",
20098
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20099
- "data-diff-search-close": true,
20100
- "aria-label": t("action.close"),
20101
- onClick: closeSearch,
20102
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
20103
- })
20104
- ]
20105
- }),
21376
+ searchBar,
20106
21377
  rulerMarkers.length > 0 && (0, react_jsx_runtime.jsx)("div", {
20107
21378
  className: PendingPanel_module_css_default.overviewRuler,
20108
21379
  "data-diff-approval-ruler": true,
@@ -20149,15 +21420,17 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20149
21420
  })
20150
21421
  }),
20151
21422
  (0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
20152
- !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, {
20153
21424
  open: langMenuOpen,
20154
21425
  portal: true,
20155
21426
  compact: true,
20156
21427
  align: "end",
20157
21428
  items: langMenuItems,
20158
- selectedId: langOverride ?? "",
21429
+ selectedId: effectiveLang ?? "",
20159
21430
  onSelect: (id) => {
20160
- setLangOverride(id === "" ? void 0 : id);
21431
+ const next = id === "" ? void 0 : id;
21432
+ setLangOverride(next);
21433
+ if (langSuffix !== void 0) setLanguageForSuffix(langSuffix, next ?? null);
20161
21434
  setLangMenuOpen(false);
20162
21435
  },
20163
21436
  onClose: () => {
@@ -20204,7 +21477,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20204
21477
  });
20205
21478
  }
20206
21479
  /** Render the pending-edit review panel and its unified footer action. */
20207
- function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onRefreshVcs, 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 }) {
20208
21481
  const current = useSessions((state) => state.current);
20209
21482
  const currentBlank = useSessions((state) => {
20210
21483
  const id = state.current;
@@ -20267,6 +21540,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20267
21540
  * choice rides the same block RPC as its `removeWhenResolved` flag. */
20268
21541
  const [blockPrompt, setBlockPrompt] = (0, react.useState)(null);
20269
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);
20270
21546
  /** Bottom offset tracking the chat composer's top edge so the input stays visible. */
20271
21547
  const [bottomPx, setBottomPx] = (0, react.useState)(FALLBACK_BOTTOM_PX);
20272
21548
  /** Fullscreen expanded: the panel bottom pins to the window edge, ignoring the composer offset. */
@@ -20505,6 +21781,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20505
21781
  (0, react.useEffect)(() => {
20506
21782
  if (noSession) setOpen(false);
20507
21783
  }, [noSession]);
21784
+ (0, react.useEffect)(() => {
21785
+ if (open) return;
21786
+ setAddOpen(false);
21787
+ setBlockPrompt(null);
21788
+ setFilePrompt(null);
21789
+ }, [open]);
20508
21790
  /** Run the same decision over every current-session file, sequentially. */
20509
21791
  const runBulk = async (kind) => {
20510
21792
  if (current === void 0) return;
@@ -20565,37 +21847,63 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20565
21847
  const promptFile = blockPrompt === null ? void 0 : files.find((file) => file.id === blockPrompt.id);
20566
21848
  /** The file whose removal is being confirmed (a whole-file action), if any. */
20567
21849
  const promptEntry = filePrompt === null ? void 0 : files.find((file) => file.id === filePrompt.id);
20568
- const fileListBody = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
20569
- className: PendingPanel_module_css_default.listScroll,
20570
- children: files.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
20571
- className: PendingPanel_module_css_default.group,
20572
- children: t("panel.group.current")
20573
- }), (0, react_jsx_runtime.jsx)("ul", {
20574
- className: PendingPanel_module_css_default.rows,
20575
- children: files.map(renderEntry)
20576
- })] })
20577
- }), files.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
20578
- className: PendingPanel_module_css_default.bulkActions,
20579
- children: [(0, react_jsx_runtime.jsx)("button", {
20580
- type: "button",
20581
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
20582
- "data-diff-keep-all": true,
20583
- disabled: bulkBusy !== null,
20584
- onClick: () => {
20585
- runBulk("keep");
20586
- },
20587
- children: bulkBusy === "keep" ? t("action.busy") : t("action.keepAll")
20588
- }), (0, react_jsx_runtime.jsx)("button", {
20589
- type: "button",
20590
- className: PendingPanel_module_css_default.action,
20591
- "data-diff-revert-all": true,
20592
- disabled: bulkBusy !== null,
20593
- onClick: () => {
20594
- runBulk("revert");
20595
- },
20596
- children: bulkBusy === "revert" ? t("action.busy") : t("action.revertAll")
20597
- })]
20598
- })] });
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
+ ] });
20599
21907
  const handleUndo = async (sessionId) => {
20600
21908
  const id = await onUndo(sessionId);
20601
21909
  if (id === void 0) return;
@@ -20612,6 +21920,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20612
21920
  if (!open || current === void 0) return;
20613
21921
  const onKeyDown = (event) => {
20614
21922
  if (!(event.ctrlKey || event.metaKey) || event.altKey) return;
21923
+ if (pathPickerOpen()) return;
20615
21924
  const target = event.target;
20616
21925
  if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
20617
21926
  if (matchesShortcut(event, keybindingOf("undo"))) {
@@ -20641,6 +21950,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20641
21950
  if (matchesShortcut(event, keybindingOf("cycleNext"))) direction = 1;
20642
21951
  else if (matchesShortcut(event, keybindingOf("cyclePrev"))) direction = -1;
20643
21952
  if (direction === 0) return;
21953
+ if (pathPickerOpen()) return;
20644
21954
  const target = event.target;
20645
21955
  if (target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null) return;
20646
21956
  if (files.length === 0) return;
@@ -20662,6 +21972,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20662
21972
  (0, react.useEffect)(() => {
20663
21973
  const onKeyDown = (event) => {
20664
21974
  if (!matchesShortcut(event, quickSummonKey())) return;
21975
+ if (pathPickerOpen()) return;
20665
21976
  event.preventDefault();
20666
21977
  if (!open) collapseSidebar();
20667
21978
  setOpen((value) => !value);
@@ -20675,6 +21986,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20675
21986
  if (!open) return;
20676
21987
  const onKeyDown = (event) => {
20677
21988
  if (event.key !== "Escape") return;
21989
+ if (pathPickerOpen()) return;
20678
21990
  const target = event.target;
20679
21991
  if (target instanceof Node && panelRef.current?.contains(target) === true && document.querySelector("[data-diff-searchbar]") !== null) return;
20680
21992
  event.preventDefault();
@@ -20819,15 +22131,26 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20819
22131
  className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
20820
22132
  children: t("panel.empty")
20821
22133
  }),
20822
- (0, react_jsx_runtime.jsx)("button", {
20823
- type: "button",
20824
- className: PendingPanel_module_css_default.importButton,
20825
- "data-diff-import-vcs": true,
20826
- disabled: importBusy,
20827
- onClick: () => {
20828
- runImportVcs();
20829
- },
20830
- 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
+ })]
20831
22154
  }),
20832
22155
  importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
20833
22156
  className: PendingPanel_module_css_default.importNote,
@@ -20965,6 +22288,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20965
22288
  })]
20966
22289
  })]
20967
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
20968
22301
  })
20969
22302
  ]
20970
22303
  })] }), document.body),
@@ -21940,6 +23273,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21940
23273
  includeUntracked
21941
23274
  }));
21942
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
+ },
21943
23289
  async open(sessionId, id, action) {
21944
23290
  return openOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "open", {
21945
23291
  sessionId,
@@ -22048,6 +23394,55 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22048
23394
  if (outcome !== "refreshed" && outcome !== "unchanged" && outcome !== "no-change" && outcome !== "missing" && outcome !== "no-vcs") throw new Error("the refresh returned a malformed outcome");
22049
23395
  return { outcome };
22050
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
+ }
22051
23446
  /** Narrow the open endpoint's value; a malformed wire value is an open failure. */
22052
23447
  function openOf(result) {
22053
23448
  if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
@@ -22329,6 +23724,14 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22329
23724
  });
22330
23725
  }
22331
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
+ },
22332
23735
  async open(sessionId, id, action) {
22333
23736
  try {
22334
23737
  await port.open(sessionId, id, action);
@@ -22507,7 +23910,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22507
23910
  /** Simplified Chinese pending-edit review messages. */
22508
23911
  const zh = {
22509
23912
  "panel.title": "待处理改动",
22510
- "panel.empty": "没有待处理的改动",
23913
+ "panel.empty": "列表中暂无文件",
22511
23914
  "panel.loading": "读取中…",
22512
23915
  "panel.readFailed": "读取待处理改动失败:{message}",
22513
23916
  "panel.group.current": "当前会话",
@@ -22633,12 +24036,32 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22633
24036
  "panel.refreshUnchanged": "差异已是最新",
22634
24037
  "panel.refreshNone": "未检测到本地 VCS 差异",
22635
24038
  "panel.refreshUntrackedHint": "(新建/未跟踪文件需要开启「导入未跟踪的文件改动」)",
22636
- "panel.refreshFailed": "刷新失败:{message}"
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": "展开"
22637
24060
  };
22638
24061
  /** English pending-edit review messages. */
22639
24062
  const en = {
22640
24063
  "panel.title": "Pending changes",
22641
- "panel.empty": "No pending changes",
24064
+ "panel.empty": "No files in the list yet",
22642
24065
  "panel.loading": "Reading…",
22643
24066
  "panel.readFailed": "Reading pending changes failed: {message}",
22644
24067
  "panel.group.current": "This session",
@@ -22764,7 +24187,27 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22764
24187
  "panel.refreshUnchanged": "Already up to date",
22765
24188
  "panel.refreshNone": "No local VCS change found",
22766
24189
  "panel.refreshUntrackedHint": "(new or untracked files need \"Import changes to untracked files\")",
22767
- "panel.refreshFailed": "Refresh failed: {message}"
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"
22768
24211
  };
22769
24212
  //#endregion
22770
24213
  //#region lib/types/client/index.js
@@ -22897,6 +24340,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22897
24340
  onRedo: (sessionId) => store.redo(sessionId),
22898
24341
  onImportVcs: (sessionId, includeUntracked) => store.importVcs(sessionId, includeUntracked),
22899
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),
22900
24345
  onKeepAll: (sessionId) => store.keepAll(sessionId),
22901
24346
  onRevertAll: (sessionId) => store.revertAll(sessionId),
22902
24347
  onAckRedoCleared: () => store.clearRedoCleared(),