dsh-diff-approval 0.20.1 → 0.22.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
@@ -16,12 +16,12 @@ window.__ModuleLoader__.load({
16
16
  return target;
17
17
  };
18
18
  //#endregion
19
- let react_jsx_runtime = require("react/jsx-runtime");
20
19
  let react = require("react");
20
+ let react_jsx_runtime = require("react/jsx-runtime");
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
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{min-width:0;height:var(--dsh-scrollbar-width,8px);flex:1 1 0;overflow:auto hidden}.F1KBNa_splitHScroll::-webkit-scrollbar{height:var(--dsh-scrollbar-width,8px)}.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)}";
24
+ const css = ".F1KBNa_panelFailure{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:8px;padding:8px 10px;font-size:12px;line-height:16px;display:flex}.F1KBNa_panelFailureText{min-width:0}.F1KBNa_panelFailureRetry{border:1px solid var(--dsw-alias-border-l2);color:inherit;font:inherit;cursor:pointer;background:0 0;border-radius:6px;flex:none;margin-left:auto;padding:2px 8px}.F1KBNa_panelFailureRetry:hover{background:var(--dsw-alias-interactive-bg-hover)}.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_headerEntry{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:28px;flex:none;justify-content:center;align-items:center;padding:6px;display:inline-flex;position:relative}.F1KBNa_headerEntry:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_headerEntry[data-active]{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.F1KBNa_headerEntry:disabled{color:var(--dsw-alias-label-tertiary);cursor:default;background:0 0}.F1KBNa_headerEntryCount{box-sizing:border-box;background:var(--dsw-alias-button-primary-fill);min-width:14px;height:14px;color:var(--dsw-alias-label-primary-foreground);text-align:center;font-variant-numeric:tabular-nums;pointer-events:none;border-radius:7px;padding:0 3px;font-size:10px;line-height:14px;position:absolute;top:-1px;right:-1px}.F1KBNa_headerEntry svg{display:block}.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_coverBackdrop{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_panelDocked{width:100%;max-width:none;height:100%;box-shadow:none;border:none;border-radius:0;position:static;inset:auto}.F1KBNa_dockHost{flex-direction:column;min-height:0;display:flex;position:absolute;inset:0}.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)}.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);min-width:72px;height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.F1KBNa_settingsSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_settingsSelector[data-recording]{box-shadow:inset 0 0 0 1px var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-secondary)}.F1KBNa_settingsReset{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;margin-left:6px;padding:0;display:inline-flex}.F1KBNa_settingsReset:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_settingsReset:disabled{opacity:.4;cursor:default}.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_fileListKnob{z-index:39;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:28px;height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;box-shadow:var(--dsw-shadow-lv2);border-radius:8px;justify-content:center;align-items:center;padding:0;display:inline-flex;position:absolute}.F1KBNa_fileListKnob:hover{background:var(--dsw-alias-interactive-bg-hover)}.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);transform-origin:0 0;border-radius:12px;flex-direction:column;padding:6px 8px 10px;animation:.14s ease-out F1KBNa_fileListGrow;display:flex;position:absolute;overflow:hidden}@keyframes F1KBNa_fileListGrow{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion:reduce){.F1KBNa_fileListFloat{animation:none}}.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_floatResizeHandle{z-index:41;cursor:col-resize;background:0 0;width:8px;position:absolute;top:0;bottom:0;right:0}.F1KBNa_floatResizeHandle: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;scrollbar-width:none;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_diffPath::-webkit-scrollbar{display:none}.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_presentationSelect{cursor:pointer;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;gap:2px;padding:0 6px;display:inline-flex}.F1KBNa_presentationSelect:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_chip{justify-content:flex-end;align-items:center;gap:5px;min-width:0;padding-right:14px;display:inline-flex}.F1KBNa_chipLabel{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.F1KBNa_coverHost{align-items:center;display:inline-flex;position:relative}.F1KBNa_coverCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:12px;align-items:center;gap:4px;padding:6px;display:flex}.F1KBNa_coverPopover{z-index:50;position:absolute;top:calc(100% + 6px);right:0}.F1KBNa_coverNotice{z-index:80;pointer-events:none;animation:1.25s ease-out forwards F1KBNa_coverNoticeFade;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%)}.F1KBNa_coverNoticeCard{gap:4px;padding:6px 8px}@keyframes F1KBNa_coverNoticeFade{0%,78%{opacity:1}to{opacity:0}}.F1KBNa_coverNoticeGlyph{width:44px;height:44px;color:var(--dsw-alias-label-tertiary);border-radius:10px;justify-content:center;align-items:center;display:inline-flex}.F1KBNa_coverNoticeGlyph[data-on]{color:var(--dsw-alias-label-primary)}.F1KBNa_coverNoticeGlyph[data-changed]{background:var(--dsw-alias-interactive-bg-hover)}.F1KBNa_coverNoticeText{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.F1KBNa_coverButton{width:32px;height:32px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:8px;justify-content:center;align-items:center;padding:0;display:inline-flex}.F1KBNa_coverButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_coverButton[data-on]{color:var(--dsw-alias-label-primary)}.F1KBNa_presentationHost{align-items:center;display:inline-flex}.F1KBNa_chipPresentation{min-width:36px;height:20px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:20px;flex:none;justify-content:center;align-items:center;gap:2px;padding:0 6px;display:inline-flex}.F1KBNa_chipPresentation:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.F1KBNa_mirrored{transform:scaleX(-1)}.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{min-width:0;height:var(--dsh-scrollbar-width,8px);flex:1 1 0;overflow:auto hidden}.F1KBNa_splitHScroll::-webkit-scrollbar{height:var(--dsh-scrollbar-width,8px)}.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
25
  const tagId = "dsh-diff-approval/PendingPanel.module.css";
26
26
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
27
27
  const tag = document.createElement("style");
@@ -31,179 +31,204 @@ window.__ModuleLoader__.load({
31
31
  document.head.appendChild(tag);
32
32
  }
33
33
  var PendingPanel_module_css_default = {
34
- "settingsGroupTitle": "F1KBNa_settingsGroupTitle",
35
- "addCount": "F1KBNa_addCount",
36
- "delCount": "F1KBNa_delCount",
37
- "kindTag": "F1KBNa_kindTag",
38
- "treeRow": "F1KBNa_treeRow",
39
- "divider": "F1KBNa_divider",
40
- "searchToggleOn": "F1KBNa_searchToggleOn",
41
- "settingsGroupText": "F1KBNa_settingsGroupText",
42
- "treeName": "F1KBNa_treeName",
43
- "iconAction": "F1KBNa_iconAction",
44
- "expandExpanded": "F1KBNa_expandExpanded",
45
- "settingsRowDesc": "F1KBNa_settingsRowDesc",
46
- "split": "F1KBNa_split",
47
- "splitHScrollFill": "F1KBNa_splitHScrollFill",
34
+ "flexSpacer": "F1KBNa_flexSpacer",
35
+ "rowPath": "F1KBNa_rowPath",
36
+ "code": "F1KBNa_code",
37
+ "noticeButton": "F1KBNa_noticeButton",
38
+ "settingsGroupDesc": "F1KBNa_settingsGroupDesc",
39
+ "settingsRowTitle": "F1KBNa_settingsRowTitle",
40
+ "actionPrimary": "F1KBNa_actionPrimary",
41
+ "emptyActions": "F1KBNa_emptyActions",
42
+ "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
43
+ "header": "F1KBNa_header",
44
+ "settingsGroupChevron": "F1KBNa_settingsGroupChevron",
45
+ "confirmCard": "F1KBNa_confirmCard",
46
+ "group": "F1KBNa_group",
47
+ "settingsRowText": "F1KBNa_settingsRowText",
48
+ "wrapActive": "F1KBNa_wrapActive",
49
+ "splitLdel": "F1KBNa_splitLdel",
50
+ "langSelect": "F1KBNa_langSelect",
51
+ "mdPreviewWrap": "F1KBNa_mdPreviewWrap",
52
+ "pickerCard": "F1KBNa_pickerCard",
53
+ "splitHScroll": "F1KBNa_splitHScroll",
54
+ "intraDel": "F1KBNa_intraDel",
55
+ "panelDocked": "F1KBNa_panelDocked",
56
+ "importNote": "F1KBNa_importNote",
48
57
  "mdPreviewBody": "F1KBNa_mdPreviewBody",
49
- "badge": "F1KBNa_badge",
50
- "blockPosition": "F1KBNa_blockPosition",
51
- "searchBar": "F1KBNa_searchBar",
52
- "badgeLabel": "F1KBNa_badgeLabel",
53
- "pickerTitle": "F1KBNa_pickerTitle",
54
- "splitRoot": "F1KBNa_splitRoot",
58
+ "chip": "F1KBNa_chip",
59
+ "diffStats": "F1KBNa_diffStats",
60
+ "colorPickerInputRow": "F1KBNa_colorPickerInputRow",
61
+ "noticeText": "F1KBNa_noticeText",
62
+ "addButton": "F1KBNa_addButton",
63
+ "toggleThumb": "F1KBNa_toggleThumb",
64
+ "pickerCheck": "F1KBNa_pickerCheck",
65
+ "blockActions": "F1KBNa_blockActions",
66
+ "colorSvCursor": "F1KBNa_colorSvCursor",
67
+ "tree": "F1KBNa_tree",
68
+ "panelFailure": "F1KBNa_panelFailure",
69
+ "markerDel": "F1KBNa_markerDel",
70
+ "mirrored": "F1KBNa_mirrored",
71
+ "colorPickerChevron": "F1KBNa_colorPickerChevron",
72
+ "note": "F1KBNa_note",
73
+ "settingsGroupBody": "F1KBNa_settingsGroupBody",
74
+ "diffFlashShake": "F1KBNa_diffFlashShake",
75
+ "iconAction": "F1KBNa_iconAction",
55
76
  "splitCol": "F1KBNa_splitCol",
56
- "wrapActive": "F1KBNa_wrapActive",
77
+ "panel": "F1KBNa_panel",
57
78
  "missing": "F1KBNa_missing",
58
- "splitLadd": "F1KBNa_splitLadd",
59
- "blockFlash": "F1KBNa_blockFlash",
60
- "diffActions": "F1KBNa_diffActions",
61
- "confirmBackdrop": "F1KBNa_confirmBackdrop",
62
- "splitHScroll": "F1KBNa_splitHScroll",
63
- "pickerNote": "F1KBNa_pickerNote",
64
- "statusAction": "F1KBNa_statusAction",
65
- "colorBackdrop": "F1KBNa_colorBackdrop",
79
+ "missingHint": "F1KBNa_missingHint",
80
+ "searchMatchCurrent": "F1KBNa_searchMatchCurrent",
81
+ "rows": "F1KBNa_rows",
82
+ "diff": "F1KBNa_diff",
83
+ "settingsGroupText": "F1KBNa_settingsGroupText",
84
+ "context": "F1KBNa_context",
85
+ "treeName": "F1KBNa_treeName",
86
+ "searchToggle": "F1KBNa_searchToggle",
87
+ "hint": "F1KBNa_hint",
66
88
  "close": "F1KBNa_close",
67
- "notice": "F1KBNa_notice",
68
- "layer": "F1KBNa_layer",
69
- "detailEmpty": "F1KBNa_detailEmpty",
70
- "settingsRowTitle": "F1KBNa_settingsRowTitle",
71
- "pickerIcon": "F1KBNa_pickerIcon",
89
+ "stepperButtonUp": "F1KBNa_stepperButtonUp",
90
+ "settingsPage": "F1KBNa_settingsPage",
72
91
  "fileList": "F1KBNa_fileList",
73
- "settingsGroupChevron": "F1KBNa_settingsGroupChevron",
74
- "searchCount": "F1KBNa_searchCount",
75
- "splitHScrollRow": "F1KBNa_splitHScrollRow",
76
- "toggleOn": "F1KBNa_toggleOn",
77
- "title": "F1KBNa_title",
78
- "blockFlashShake": "F1KBNa_blockFlashShake",
79
- "colorHue": "F1KBNa_colorHue",
92
+ "colorPickerTrigger": "F1KBNa_colorPickerTrigger",
93
+ "pickerInput": "F1KBNa_pickerInput",
94
+ "searchInput": "F1KBNa_searchInput",
95
+ "langLabel": "F1KBNa_langLabel",
96
+ "resizeHandle": "F1KBNa_resizeHandle",
97
+ "row": "F1KBNa_row",
98
+ "splitRoot": "F1KBNa_splitRoot",
99
+ "treeRow": "F1KBNa_treeRow",
100
+ "diffBodyWrap": "F1KBNa_diffBodyWrap",
101
+ "importButton": "F1KBNa_importButton",
80
102
  "rowHead": "F1KBNa_rowHead",
81
- "splitRdel": "F1KBNa_splitRdel",
82
- "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
103
+ "layer": "F1KBNa_layer",
104
+ "fileListKnob": "F1KBNa_fileListKnob",
105
+ "statusBar": "F1KBNa_statusBar",
106
+ "detailEmpty": "F1KBNa_detailEmpty",
107
+ "listScroll": "F1KBNa_listScroll",
108
+ "colorSv": "F1KBNa_colorSv",
109
+ "colorPickerPreview": "F1KBNa_colorPickerPreview",
110
+ "actionError": "F1KBNa_actionError",
111
+ "noteCentered": "F1KBNa_noteCentered",
83
112
  "diffPreview": "F1KBNa_diffPreview",
84
- "diffPath": "F1KBNa_diffPath",
85
- "detail": "F1KBNa_detail",
86
- "importNote": "F1KBNa_importNote",
87
- "colorPickerPanel": "F1KBNa_colorPickerPanel",
88
- "row": "F1KBNa_row",
89
- "code": "F1KBNa_code",
113
+ "expandExpanded": "F1KBNa_expandExpanded",
114
+ "settingsGroupTitle": "F1KBNa_settingsGroupTitle",
115
+ "readError": "F1KBNa_readError",
116
+ "dockHost": "F1KBNa_dockHost",
117
+ "coverNoticeGlyph": "F1KBNa_coverNoticeGlyph",
118
+ "diffHeader": "F1KBNa_diffHeader",
119
+ "panelFailureRetry": "F1KBNa_panelFailureRetry",
120
+ "confirmActions": "F1KBNa_confirmActions",
121
+ "blockPosition": "F1KBNa_blockPosition",
122
+ "kindTag": "F1KBNa_kindTag",
123
+ "mdPreviewFallback": "F1KBNa_mdPreviewFallback",
90
124
  "mdPreviewDouble": "F1KBNa_mdPreviewDouble",
91
- "gutter": "F1KBNa_gutter",
92
- "splitLdel": "F1KBNa_splitLdel",
93
- "bulkActions": "F1KBNa_bulkActions",
94
- "listScroll": "F1KBNa_listScroll",
95
- "pickerInput": "F1KBNa_pickerInput",
96
- "stepperButtonUp": "F1KBNa_stepperButtonUp",
97
- "settingsSelector": "F1KBNa_settingsSelector",
98
- "toggleThumb": "F1KBNa_toggleThumb",
99
- "rowPath": "F1KBNa_rowPath",
100
- "rowFailed": "F1KBNa_rowFailed",
101
- "stepperValue": "F1KBNa_stepperValue",
102
- "addButton": "F1KBNa_addButton",
103
- "searchInput": "F1KBNa_searchInput",
104
- "searchMatch": "F1KBNa_searchMatch",
105
- "add": "F1KBNa_add",
106
- "actionPrimary": "F1KBNa_actionPrimary",
125
+ "presentationHost": "F1KBNa_presentationHost",
126
+ "diffPreviewScroll": "F1KBNa_diffPreviewScroll",
127
+ "chipPresentation": "F1KBNa_chipPresentation",
128
+ "splitRadd": "F1KBNa_splitRadd",
129
+ "coverHost": "F1KBNa_coverHost",
130
+ "stepperButton": "F1KBNa_stepperButton",
107
131
  "rail": "F1KBNa_rail",
108
- "colorSvCursor": "F1KBNa_colorSvCursor",
109
- "diffStats": "F1KBNa_diffStats",
110
- "intraAdd": "F1KBNa_intraAdd",
111
- "colorPickerChevron": "F1KBNa_colorPickerChevron",
112
- "treeNote": "F1KBNa_treeNote",
113
- "headerActions": "F1KBNa_headerActions",
114
- "settingsGroupBody": "F1KBNa_settingsGroupBody",
115
- "settingsRowText": "F1KBNa_settingsRowText",
116
- "mdPreviewFallback": "F1KBNa_mdPreviewFallback",
132
+ "kindHint": "F1KBNa_kindHint",
117
133
  "line": "F1KBNa_line",
134
+ "headerEntry": "F1KBNa_headerEntry",
135
+ "bulkActions": "F1KBNa_bulkActions",
136
+ "floatResizeHandle": "F1KBNa_floatResizeHandle",
137
+ "pickerNote": "F1KBNa_pickerNote",
138
+ "vSpacer": "F1KBNa_vSpacer",
139
+ "splitHScrollFill": "F1KBNa_splitHScrollFill",
140
+ "colorPicker": "F1KBNa_colorPicker",
141
+ "delCount": "F1KBNa_delCount",
142
+ "coverPopover": "F1KBNa_coverPopover",
143
+ "badgeCount": "F1KBNa_badgeCount",
144
+ "badgeLabel": "F1KBNa_badgeLabel",
145
+ "notice": "F1KBNa_notice",
118
146
  "wrap": "F1KBNa_wrap",
119
- "rows": "F1KBNa_rows",
120
- "missingHint": "F1KBNa_missingHint",
121
- "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
147
+ "colorPickerPopover": "F1KBNa_colorPickerPopover",
148
+ "searchBar": "F1KBNa_searchBar",
149
+ "settingsGroupHeader": "F1KBNa_settingsGroupHeader",
150
+ "headerEntryCount": "F1KBNa_headerEntryCount",
151
+ "settingsRowDesc": "F1KBNa_settingsRowDesc",
152
+ "coverNotice": "F1KBNa_coverNotice",
153
+ "chipLabel": "F1KBNa_chipLabel",
154
+ "subline": "F1KBNa_subline",
155
+ "addCount": "F1KBNa_addCount",
156
+ "diffPath": "F1KBNa_diffPath",
122
157
  "mdPreviewContent": "F1KBNa_mdPreviewContent",
123
- "group": "F1KBNa_group",
124
- "badgeCount": "F1KBNa_badgeCount",
125
- "context": "F1KBNa_context",
158
+ "settingsRow": "F1KBNa_settingsRow",
159
+ "emptyState": "F1KBNa_emptyState",
160
+ "divergedHint": "F1KBNa_divergedHint",
161
+ "headerActions": "F1KBNa_headerActions",
162
+ "confirmBackdrop": "F1KBNa_confirmBackdrop",
163
+ "diffBody": "F1KBNa_diffBody",
164
+ "panelFailureText": "F1KBNa_panelFailureText",
165
+ "searchMatch": "F1KBNa_searchMatch",
166
+ "markerAdd": "F1KBNa_markerAdd",
167
+ "coverNoticeCard": "F1KBNa_coverNoticeCard",
168
+ "coverNoticeFade": "F1KBNa_coverNoticeFade",
169
+ "badge": "F1KBNa_badge",
170
+ "states": "F1KBNa_states",
126
171
  "del": "F1KBNa_del",
127
- "flexSpacer": "F1KBNa_flexSpacer",
172
+ "colorHue": "F1KBNa_colorHue",
128
173
  "colorHueCursor": "F1KBNa_colorHueCursor",
129
- "colorSv": "F1KBNa_colorSv",
130
- "confirmCard": "F1KBNa_confirmCard",
131
- "kindHint": "F1KBNa_kindHint",
132
- "expand": "F1KBNa_expand",
133
- "pickerCheck": "F1KBNa_pickerCheck",
134
- "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
135
- "colorValue": "F1KBNa_colorValue",
136
- "footerButtons": "F1KBNa_footerButtons",
137
- "divergedHint": "F1KBNa_divergedHint",
138
- "stepperButton": "F1KBNa_stepperButton",
174
+ "colorSwatch": "F1KBNa_colorSwatch",
175
+ "mdPreviewSingle": "F1KBNa_mdPreviewSingle",
176
+ "coverBackdrop": "F1KBNa_coverBackdrop",
177
+ "confirmText": "F1KBNa_confirmText",
178
+ "toggleOn": "F1KBNa_toggleOn",
179
+ "detail": "F1KBNa_detail",
139
180
  "pickerClose": "F1KBNa_pickerClose",
140
- "panel": "F1KBNa_panel",
141
- "importButton": "F1KBNa_importButton",
142
- "searchToggle": "F1KBNa_searchToggle",
143
- "diffHeader": "F1KBNa_diffHeader",
144
- "markerAdd": "F1KBNa_markerAdd",
145
- "colorPickerPreview": "F1KBNa_colorPickerPreview",
146
- "pickerCard": "F1KBNa_pickerCard",
147
- "mdPreviewWrap": "F1KBNa_mdPreviewWrap",
148
- "colorPickerInputRow": "F1KBNa_colorPickerInputRow",
149
- "emptyActions": "F1KBNa_emptyActions",
150
- "note": "F1KBNa_note",
151
- "lines": "F1KBNa_lines",
152
- "colorPickerPopover": "F1KBNa_colorPickerPopover",
153
- "stepper": "F1KBNa_stepper",
154
- "diffFlashShake": "F1KBNa_diffFlashShake",
155
- "header": "F1KBNa_header",
156
- "groupHead": "F1KBNa_groupHead",
157
- "resizeHandle": "F1KBNa_resizeHandle",
158
- "treeCaret": "F1KBNa_treeCaret",
159
181
  "rowMeta": "F1KBNa_rowMeta",
160
- "markerDel": "F1KBNa_markerDel",
161
- "vSpacer": "F1KBNa_vSpacer",
162
- "fileListFloat": "F1KBNa_fileListFloat",
163
- "noticeButton": "F1KBNa_noticeButton",
164
- "intraDel": "F1KBNa_intraDel",
165
- "mdPreviewSingle": "F1KBNa_mdPreviewSingle",
166
- "settingsGroup": "F1KBNa_settingsGroup",
167
- "tree": "F1KBNa_tree",
168
- "searchMatchCurrent": "F1KBNa_searchMatchCurrent",
169
- "diffBody": "F1KBNa_diffBody",
170
- "splitRadd": "F1KBNa_splitRadd",
171
- "action": "F1KBNa_action",
172
- "emptyState": "F1KBNa_emptyState",
173
- "confirmActions": "F1KBNa_confirmActions",
174
- "overviewMarker": "F1KBNa_overviewMarker",
175
- "noticeText": "F1KBNa_noticeText",
176
- "actionError": "F1KBNa_actionError",
177
- "diffPreviewScroll": "F1KBNa_diffPreviewScroll",
182
+ "settingsReset": "F1KBNa_settingsReset",
183
+ "lines": "F1KBNa_lines",
178
184
  "diffPreviewTitle": "F1KBNa_diffPreviewTitle",
179
- "blockActions": "F1KBNa_blockActions",
185
+ "colorBackdrop": "F1KBNa_colorBackdrop",
186
+ "fileListGrow": "F1KBNa_fileListGrow",
187
+ "pickerTitle": "F1KBNa_pickerTitle",
188
+ "treeCaret": "F1KBNa_treeCaret",
189
+ "treeNote": "F1KBNa_treeNote",
190
+ "searchCount": "F1KBNa_searchCount",
191
+ "presentationSelect": "F1KBNa_presentationSelect",
192
+ "title": "F1KBNa_title",
193
+ "blockFlash": "F1KBNa_blockFlash",
194
+ "divider": "F1KBNa_divider",
195
+ "overviewRuler": "F1KBNa_overviewRuler",
196
+ "coverCard": "F1KBNa_coverCard",
197
+ "coverButton": "F1KBNa_coverButton",
198
+ "colorPickerInput": "F1KBNa_colorPickerInput",
199
+ "splitDivider": "F1KBNa_splitDivider",
200
+ "rowFailed": "F1KBNa_rowFailed",
201
+ "splitHScrollRow": "F1KBNa_splitHScrollRow",
202
+ "diffBodySplit": "F1KBNa_diffBodySplit",
180
203
  "diffFlashFade": "F1KBNa_diffFlashFade",
181
- "subline": "F1KBNa_subline",
182
- "langSelect": "F1KBNa_langSelect",
204
+ "splitLadd": "F1KBNa_splitLadd",
205
+ "pickerName": "F1KBNa_pickerName",
206
+ "diffActions": "F1KBNa_diffActions",
207
+ "splitRdel": "F1KBNa_splitRdel",
208
+ "add": "F1KBNa_add",
209
+ "expand": "F1KBNa_expand",
210
+ "overviewMarker": "F1KBNa_overviewMarker",
211
+ "coverNoticeText": "F1KBNa_coverNoticeText",
212
+ "intraAdd": "F1KBNa_intraAdd",
183
213
  "splitCols": "F1KBNa_splitCols",
184
- "colorPickerTrigger": "F1KBNa_colorPickerTrigger",
185
- "readError": "F1KBNa_readError",
186
- "colorPicker": "F1KBNa_colorPicker",
214
+ "settingsSelector": "F1KBNa_settingsSelector",
215
+ "pickerIcon": "F1KBNa_pickerIcon",
187
216
  "toggle": "F1KBNa_toggle",
188
- "noteCentered": "F1KBNa_noteCentered",
189
- "overviewRuler": "F1KBNa_overviewRuler",
190
- "hint": "F1KBNa_hint",
191
- "splitDivider": "F1KBNa_splitDivider",
192
- "pickerName": "F1KBNa_pickerName",
193
- "langLabel": "F1KBNa_langLabel",
194
- "diffBodySplit": "F1KBNa_diffBodySplit",
195
- "settingsGroupHeader": "F1KBNa_settingsGroupHeader",
196
- "settingsGroupDesc": "F1KBNa_settingsGroupDesc",
197
- "pickerForm": "F1KBNa_pickerForm",
198
- "states": "F1KBNa_states",
199
- "settingsRow": "F1KBNa_settingsRow",
200
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
201
- "confirmText": "F1KBNa_confirmText",
202
- "statusBar": "F1KBNa_statusBar",
203
- "colorSwatch": "F1KBNa_colorSwatch",
204
- "diff": "F1KBNa_diff",
205
- "colorPickerInput": "F1KBNa_colorPickerInput",
206
- "settingsPage": "F1KBNa_settingsPage"
217
+ "footerButtons": "F1KBNa_footerButtons",
218
+ "stepperValue": "F1KBNa_stepperValue",
219
+ "colorPickerPanel": "F1KBNa_colorPickerPanel",
220
+ "colorValue": "F1KBNa_colorValue",
221
+ "stepper": "F1KBNa_stepper",
222
+ "action": "F1KBNa_action",
223
+ "statusAction": "F1KBNa_statusAction",
224
+ "settingsGroup": "F1KBNa_settingsGroup",
225
+ "blockFlashShake": "F1KBNa_blockFlashShake",
226
+ "fileListFloat": "F1KBNa_fileListFloat",
227
+ "gutter": "F1KBNa_gutter",
228
+ "searchToggleOn": "F1KBNa_searchToggleOn",
229
+ "groupHead": "F1KBNa_groupHead",
230
+ "split": "F1KBNa_split",
231
+ "pickerForm": "F1KBNa_pickerForm"
207
232
  };
208
233
  //#endregion
209
234
  //#region lib/types/client/PathPicker.js
@@ -553,201 +578,1096 @@ window.__ModuleLoader__.load({
553
578
  });
554
579
  }
555
580
  //#endregion
556
- //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js
557
- var Diff = class {
558
- diff(oldStr, newStr, options = {}) {
559
- let callback;
560
- if (typeof options === "function") {
561
- callback = options;
562
- options = {};
563
- } else if ("callback" in options) callback = options.callback;
564
- const oldString = this.castInput(oldStr, options);
565
- const newString = this.castInput(newStr, options);
566
- const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
567
- const newTokens = this.removeEmpty(this.tokenize(newString, options));
568
- return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
569
- }
570
- diffWithOptionsObj(oldTokens, newTokens, options, callback) {
571
- var _a;
572
- const done = (value) => {
573
- value = this.postProcess(value, options);
574
- if (callback) {
575
- setTimeout(function() {
576
- callback(value);
577
- }, 0);
578
- return;
579
- } else return value;
580
- };
581
- const newLen = newTokens.length, oldLen = oldTokens.length;
582
- let editLength = 1;
583
- let maxEditLength = newLen + oldLen;
584
- if (options.maxEditLength != null) maxEditLength = Math.min(maxEditLength, options.maxEditLength);
585
- const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
586
- const abortAfterTimestamp = Date.now() + maxExecutionTime;
587
- const bestPath = [{
588
- oldPos: -1,
589
- lastComponent: void 0
590
- }];
591
- let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
592
- if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
593
- let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
594
- const execEditLength = () => {
595
- for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
596
- let basePath;
597
- const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
598
- if (removePath) bestPath[diagonalPath - 1] = void 0;
599
- let canAdd = false;
600
- if (addPath) {
601
- const addPathNewPos = addPath.oldPos - diagonalPath;
602
- canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
603
- }
604
- const canRemove = removePath && removePath.oldPos + 1 < oldLen;
605
- if (!canAdd && !canRemove) {
606
- bestPath[diagonalPath] = void 0;
607
- continue;
608
- }
609
- if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) basePath = this.addToPath(addPath, true, false, 0, options);
610
- else basePath = this.addToPath(removePath, false, true, 1, options);
611
- newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
612
- if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
613
- else {
614
- bestPath[diagonalPath] = basePath;
615
- if (basePath.oldPos + 1 >= oldLen) maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
616
- if (newPos + 1 >= newLen) minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
617
- }
618
- }
619
- editLength++;
620
- };
621
- if (callback) (function exec() {
622
- setTimeout(function() {
623
- if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) return callback(void 0);
624
- if (!execEditLength()) exec();
625
- }, 0);
626
- })();
627
- else while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
628
- const ret = execEditLength();
629
- if (ret) return ret;
630
- }
631
- }
632
- addToPath(path, added, removed, oldPosInc, options) {
633
- const last = path.lastComponent;
634
- if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) return {
635
- oldPos: path.oldPos + oldPosInc,
636
- lastComponent: {
637
- count: last.count + 1,
638
- added,
639
- removed,
640
- previousComponent: last.previousComponent
641
- }
642
- };
643
- else return {
644
- oldPos: path.oldPos + oldPosInc,
645
- lastComponent: {
646
- count: 1,
647
- added,
648
- removed,
649
- previousComponent: last
650
- }
651
- };
652
- }
653
- extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
654
- const newLen = newTokens.length, oldLen = oldTokens.length;
655
- let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
656
- while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
657
- newPos++;
658
- oldPos++;
659
- commonCount++;
660
- if (options.oneChangePerToken) basePath.lastComponent = {
661
- count: 1,
662
- previousComponent: basePath.lastComponent,
663
- added: false,
664
- removed: false
665
- };
666
- }
667
- if (commonCount && !options.oneChangePerToken) basePath.lastComponent = {
668
- count: commonCount,
669
- previousComponent: basePath.lastComponent,
670
- added: false,
671
- removed: false
672
- };
673
- basePath.oldPos = oldPos;
674
- return newPos;
675
- }
676
- equals(left, right, options) {
677
- if (options.comparator) return options.comparator(left, right);
678
- else return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
679
- }
680
- removeEmpty(array) {
681
- const ret = [];
682
- for (let i = 0; i < array.length; i++) if (array[i]) ret.push(array[i]);
683
- return ret;
684
- }
685
- castInput(value, options) {
686
- return value;
687
- }
688
- tokenize(value, options) {
689
- return Array.from(value);
690
- }
691
- join(chars) {
692
- return chars.join("");
693
- }
694
- postProcess(changeObjects, options) {
695
- return changeObjects;
696
- }
697
- get useLongestToken() {
698
- return false;
699
- }
700
- buildValues(lastComponent, newTokens, oldTokens) {
701
- const components = [];
702
- let nextComponent;
703
- while (lastComponent) {
704
- components.push(lastComponent);
705
- nextComponent = lastComponent.previousComponent;
706
- delete lastComponent.previousComponent;
707
- lastComponent = nextComponent;
708
- }
709
- components.reverse();
710
- const componentLen = components.length;
711
- let componentPos = 0, newPos = 0, oldPos = 0;
712
- for (; componentPos < componentLen; componentPos++) {
713
- const component = components[componentPos];
714
- if (!component.removed) {
715
- if (!component.added && this.useLongestToken) {
716
- let value = newTokens.slice(newPos, newPos + component.count);
717
- value = value.map(function(value, i) {
718
- const oldValue = oldTokens[oldPos + i];
719
- return oldValue.length > value.length ? oldValue : value;
720
- });
721
- component.value = this.join(value);
722
- } else component.value = this.join(newTokens.slice(newPos, newPos + component.count));
723
- newPos += component.count;
724
- if (!component.added) oldPos += component.count;
725
- } else {
726
- component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
727
- oldPos += component.count;
728
- }
729
- }
730
- return components;
731
- }
732
- };
733
- //#endregion
734
- //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/character.js
735
- var CharacterDiff = class extends Diff {};
736
- new CharacterDiff();
737
- //#endregion
738
- //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/util/string.js
739
- function longestCommonPrefix(str1, str2) {
740
- let i;
741
- for (i = 0; i < str1.length && i < str2.length; i++) if (str1[i] != str2[i]) return str1.slice(0, i);
742
- return str1.slice(0, i);
581
+ //#region lib/types/client/presentation-menu.js
582
+ /**
583
+ * The mode switch: where the panel shows — floating over the app, or docked as a
584
+ * tab in the app's right sidebar. (What the floating panel *covers* is the
585
+ * coverage control's business; see the panel's own header.)
586
+ *
587
+ * One implementation, two hosts. The floating panel draws it in its own header
588
+ * beside the gear. A *docked* panel draws no header at all — the tab is the
589
+ * frame, its chip already carries the title and a close button — so the chip
590
+ * (`sidebar.right.pane.tab.title`, which is this plugin's own component) hosts
591
+ * the same control instead: the seat's convention is a glyph *before* the title
592
+ * text, and the chip's tail is masked for the kit's own close button.
593
+ *
594
+ * @module dsh-diff-approval/client/presentation-menu
595
+ */
596
+ /**
597
+ * The floating-overlay glyph: a window with another peeking out behind it. The
598
+ * product's icon set has no "float" mark, so this one is drawn to the set's
599
+ * conventions — a 16px grid, ~1.4px ink, `currentColor`.
600
+ * @param props - the drawn size in px.
601
+ * @returns the glyph.
602
+ */
603
+ function FloatWindowIcon({ size }) {
604
+ return (0, react_jsx_runtime.jsxs)("svg", {
605
+ width: size,
606
+ height: size,
607
+ viewBox: "0 0 16 16",
608
+ fill: "none",
609
+ xmlns: "http://www.w3.org/2000/svg",
610
+ "aria-hidden": true,
611
+ children: [(0, react_jsx_runtime.jsx)("path", {
612
+ d: "M5.4 3.2H3.2A1.2 1.2 0 0 0 2 4.4v6.2",
613
+ stroke: "currentColor",
614
+ strokeWidth: "1.4",
615
+ strokeLinecap: "round"
616
+ }), (0, react_jsx_runtime.jsx)("rect", {
617
+ x: "5.4",
618
+ y: "5.4",
619
+ width: "9.4",
620
+ height: "7",
621
+ rx: "1.4",
622
+ stroke: "currentColor",
623
+ strokeWidth: "1.4"
624
+ })]
625
+ });
743
626
  }
744
- function longestCommonSuffix(str1, str2) {
745
- let i;
746
- if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) return "";
747
- for (i = 0; i < str1.length && i < str2.length; i++) if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) return str1.slice(-i);
748
- return str1.slice(-i);
627
+ /**
628
+ * The mode switch's mark: the state it currently shows, and the same glyph each
629
+ * menu row leads with. The dock mark is the icon the app's own right sidebar uses
630
+ * (`IconPanelLeftOutline16`, mirrored see ui-sidebar-right's ExpandButton), so
631
+ * "dock" points at the same affordance the column does.
632
+ * @param props - the state, and the drawn size in px.
633
+ * @returns the glyph.
634
+ */
635
+ function PresentationIcon({ state, size }) {
636
+ if (state === "dock") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPanelLeftOutline16, {
637
+ size,
638
+ className: PendingPanel_module_css_default.mirrored
639
+ });
640
+ return (0, react_jsx_runtime.jsx)(FloatWindowIcon, { size });
749
641
  }
750
- function replacePrefix(string, oldPrefix, newPrefix) {
642
+ /**
643
+ * The trigger's wrapper. The header's control says what it is on hover; the
644
+ * chip's carries no tooltip, because that bubble renders exactly where the menu
645
+ * list goes (both hang under the button) and would take the row clicks.
646
+ * @param props - the label to show, when there is one, and the trigger.
647
+ * @returns the trigger, wrapped or bare.
648
+ */
649
+ function Trigger({ title, children }) {
650
+ if (title === void 0) return children;
651
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
652
+ label: title,
653
+ side: "bottom",
654
+ delayMs: 500,
655
+ children
656
+ });
657
+ }
658
+ /**
659
+ * Render the trigger and its menu. The trigger carries
660
+ * `data-diff-approval-presentation` in both hosts, so a test addresses whichever
661
+ * one the presentation it is testing actually draws.
662
+ * @param props - copy, the current state, what a choice does, and the extras.
663
+ * @returns the trigger element with its dropdown.
664
+ */
665
+ /** Stop one event from leaving this control (see the boundary note below). */
666
+ function stopAll(event) {
667
+ event.stopPropagation();
668
+ }
669
+ function PresentationMenu({ t, current, onChoose, footer, onFooter, compact = false }) {
670
+ const [open, setOpen] = (0, react.useState)(false);
671
+ const host = compact ? "chip" : "header";
672
+ const title = t("action.presentationCurrent", { state: t(current === "dock" ? "action.presentationDocked" : "action.presentationFloat") });
673
+ const items = (0, react.useMemo)(() => [{
674
+ id: "float",
675
+ label: t("action.presentationFloat"),
676
+ icon: (0, react_jsx_runtime.jsx)(PresentationIcon, {
677
+ state: "float",
678
+ size: 16
679
+ })
680
+ }, {
681
+ id: "dock",
682
+ label: t("action.presentationDock"),
683
+ icon: (0, react_jsx_runtime.jsx)(PresentationIcon, {
684
+ state: "dock",
685
+ size: 16
686
+ })
687
+ }], [t]);
688
+ return (0, react_jsx_runtime.jsx)("span", {
689
+ className: PendingPanel_module_css_default.presentationHost,
690
+ ...compact ? {
691
+ onPointerDown: stopAll,
692
+ onPointerUp: stopAll,
693
+ onClick: stopAll,
694
+ onContextMenu: stopAll,
695
+ onMouseDown: stopAll
696
+ } : {},
697
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
698
+ open,
699
+ portal: true,
700
+ compact: true,
701
+ align: "end",
702
+ items,
703
+ ...footer === void 0 ? {} : { footer },
704
+ selectedId: current,
705
+ onSelect: (id) => {
706
+ setOpen(false);
707
+ if (id === "float" || id === "dock") onChoose(id);
708
+ else onFooter?.(id);
709
+ },
710
+ onClose: () => {
711
+ setOpen(false);
712
+ },
713
+ anchor: (0, react_jsx_runtime.jsx)(Trigger, {
714
+ title: compact ? void 0 : title,
715
+ children: (0, react_jsx_runtime.jsxs)("button", {
716
+ type: "button",
717
+ className: compact ? PendingPanel_module_css_default.chipPresentation : PendingPanel_module_css_default.presentationSelect,
718
+ "data-diff-approval-presentation": true,
719
+ "data-diff-approval-presentation-host": host,
720
+ "aria-label": title,
721
+ "aria-haspopup": "menu",
722
+ "aria-expanded": open,
723
+ onPointerDown: (event) => {
724
+ event.stopPropagation();
725
+ },
726
+ onClick: (event) => {
727
+ event.stopPropagation();
728
+ setOpen((value) => !value);
729
+ },
730
+ children: [(0, react_jsx_runtime.jsx)(PresentationIcon, {
731
+ state: current,
732
+ size: compact ? 12 : 14
733
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: compact ? 10 : 12 })]
734
+ })
735
+ })
736
+ })
737
+ });
738
+ }
739
+ //#endregion
740
+ //#region lib/types/client/settings.js
741
+ /** Client preferences for the review panel, persisted in localStorage. */
742
+ const PASTE_ON_COPY_KEY = "diff-approval:paste-on-copy";
743
+ const IMPORT_UNTRACKED_KEY = "diff-approval:import-untracked";
744
+ const CONFIRM_FILE_REMOVE_KEY = "diff-approval:confirm-file-remove";
745
+ const SEARCH_CASE_KEY = "diff-approval:search-case";
746
+ const SEARCH_WORD_KEY = "diff-approval:search-word";
747
+ const TAB_WIDTH_KEY = "diff-approval:tab-size";
748
+ const SPLIT_MODE_KEY = "diff-approval:split-mode";
749
+ const MD_PREVIEW_KEY = "diff-approval:md-preview";
750
+ const MD_MAX_WIDTH_KEY = "diff-approval:md-max-width";
751
+ const NAV_LEAD_KEY = "diff-approval:nav-lead-rows";
752
+ const DIFF_FONT_SCALE_KEY = "diff-approval:diff-font-scale";
753
+ const DIFF_LINE_HEIGHT_KEY = "diff-approval:diff-line-height";
754
+ const DIFF_ADD_COLOR_KEY = "diff-approval:diff-add-color";
755
+ const DIFF_DEL_COLOR_KEY = "diff-approval:diff-del-color";
756
+ const LANG_BY_SUFFIX_KEY = "diff-approval:lang-by-suffix";
757
+ const PRESENTATION_KEY = "diff-approval:presentation";
758
+ const FLOAT_COVER_KEY = "diff-approval:float-cover";
759
+ const FILE_LIST_FLOAT_KEY = "diff-approval:file-list-float";
760
+ const WRAP_PREFIX = "diff-approval:wrap:";
761
+ /** The floating panel's default coverage: the header and both sidebars, not the
762
+ * composer. */
763
+ const FLOAT_COVER_DEFAULT = {
764
+ top: true,
765
+ left: true,
766
+ right: true,
767
+ composer: false
768
+ };
769
+ const MD_MAX_WIDTH_MAX = 1600;
770
+ /**
771
+ * Whether copying a reference should also paste it into the chat input and
772
+ * focus it. Defaults to on; only an explicit `'0'` disables it.
773
+ * @returns whether auto-paste is enabled.
774
+ */
775
+ function pasteOnCopyEnabled() {
776
+ return localStorage.getItem(PASTE_ON_COPY_KEY) !== "0";
777
+ }
778
+ /** Persist the auto-paste preference. */
779
+ function setPasteOnCopyEnabled(value) {
780
+ localStorage.setItem(PASTE_ON_COPY_KEY, value ? "1" : "0");
781
+ }
782
+ /**
783
+ * Whether importing workspace VCS changes includes new/untracked files (git
784
+ * `??`, svn `?`, p4 unversioned). Defaults to off: collecting them scans the
785
+ * whole workspace, which can be slow on large trees, so the preference is
786
+ * opt-in. Only an explicit `'1'` enables it.
787
+ * @returns whether untracked files are imported.
788
+ */
789
+ function includeUntrackedEnabled() {
790
+ return localStorage.getItem(IMPORT_UNTRACKED_KEY) === "1";
791
+ }
792
+ /** Persist the import-untracked preference. */
793
+ function setIncludeUntrackedEnabled(value) {
794
+ localStorage.setItem(IMPORT_UNTRACKED_KEY, value ? "1" : "0");
795
+ }
796
+ /**
797
+ * Whether a whole-file keep/revert asks before dropping the resolved file from
798
+ * the list. Defaults to on; only an explicit `'0'` disables it, which then
799
+ * removes the file straight away.
800
+ * @returns whether the remove prompt is enabled.
801
+ */
802
+ function confirmFileRemoveEnabled() {
803
+ return localStorage.getItem(CONFIRM_FILE_REMOVE_KEY) !== "0";
804
+ }
805
+ /** Persist the whole-file remove-prompt preference. */
806
+ function setConfirmFileRemoveEnabled(value) {
807
+ localStorage.setItem(CONFIRM_FILE_REMOVE_KEY, value ? "1" : "0");
808
+ }
809
+ /**
810
+ * Whether the in-file search matches letter case exactly. Defaults to off, so a
811
+ * plain query keeps matching either case; only an explicit `'1'` turns it on.
812
+ * @returns whether the search is case-sensitive.
813
+ */
814
+ function searchCaseSensitive() {
815
+ return localStorage.getItem(SEARCH_CASE_KEY) === "1";
816
+ }
817
+ /** Persist the search case-sensitivity preference. */
818
+ function setSearchCaseSensitive(value) {
819
+ localStorage.setItem(SEARCH_CASE_KEY, value ? "1" : "0");
820
+ }
821
+ /**
822
+ * Whether the in-file search only matches whole words. Defaults to off; only an
823
+ * explicit `'1'` enables it.
824
+ * @returns whether the search matches whole words only.
825
+ */
826
+ function searchWholeWord() {
827
+ return localStorage.getItem(SEARCH_WORD_KEY) === "1";
828
+ }
829
+ /** Persist the search whole-word preference. */
830
+ function setSearchWholeWord(value) {
831
+ localStorage.setItem(SEARCH_WORD_KEY, value ? "1" : "0");
832
+ }
833
+ /**
834
+ * Where the review panel shows, as the user last had it. Two states, both
835
+ * remembered so the footer entry can restore the one they were using:
836
+ *
837
+ * - `float` — the panel floating over the app, covering what
838
+ * {@link panelCover} says (and the composer only when that says so);
839
+ * - `dock` — the panel docked in the app's right sidebar, as its own tab.
840
+ *
841
+ * A stored `fullscreen` — the third state before coverage became three switches —
842
+ * reads as `float`, with {@link panelCover} supplying the all-on coverage it
843
+ * meant.
844
+ * @returns the remembered presentation; `float` when unset or unrecognized.
845
+ */
846
+ function panelPresentation() {
847
+ return localStorage.getItem(PRESENTATION_KEY) === "dock" ? "dock" : "float";
848
+ }
849
+ /** Persist the panel's presentation (see {@link panelPresentation}). */
850
+ function setPanelPresentation(value) {
851
+ localStorage.setItem(PRESENTATION_KEY, value);
852
+ }
853
+ /**
854
+ * What the floating panel covers (see {@link DiffApprovalCover}). Defaults to
855
+ * both sidebars and not the composer; a stored `fullscreen` presentation from
856
+ * before the switches means all three.
857
+ * @returns the remembered coverage.
858
+ */
859
+ function panelCover() {
860
+ const raw = localStorage.getItem(FLOAT_COVER_KEY);
861
+ if (raw === null) return localStorage.getItem(PRESENTATION_KEY) === "fullscreen" ? {
862
+ top: true,
863
+ left: true,
864
+ right: true,
865
+ composer: true
866
+ } : FLOAT_COVER_DEFAULT;
867
+ try {
868
+ const parsed = JSON.parse(raw);
869
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return FLOAT_COVER_DEFAULT;
870
+ const stored = parsed;
871
+ const flag = (name) => typeof stored[name] === "boolean" ? stored[name] : FLOAT_COVER_DEFAULT[name];
872
+ return {
873
+ top: flag("top"),
874
+ left: flag("left"),
875
+ right: flag("right"),
876
+ composer: flag("composer")
877
+ };
878
+ } catch {
879
+ return FLOAT_COVER_DEFAULT;
880
+ }
881
+ }
882
+ /** Window event: the floating panel's coverage changed (see {@link setPanelCover}). */
883
+ const COVER_CHANGED_EVENT = "diff-approval:cover";
884
+ /** Persist what the floating panel covers. */
885
+ function setPanelCover(value) {
886
+ localStorage.setItem(FLOAT_COVER_KEY, JSON.stringify(value));
887
+ if (typeof window !== "undefined") window.dispatchEvent(new CustomEvent(COVER_CHANGED_EVENT));
888
+ }
889
+ /**
890
+ * Whether the file list always folds into its floating card, whatever width the
891
+ * panel has. Defaults to off, where the width decides: the list sits beside the
892
+ * diff while there is room for both. Only an explicit `'1'` forces the fold.
893
+ * @returns whether the file list is always folded.
894
+ */
895
+ function fileListFloat() {
896
+ return localStorage.getItem(FILE_LIST_FLOAT_KEY) === "1";
897
+ }
898
+ /** Persist the always-fold preference for the file list. */
899
+ function setFileListFloat(value) {
900
+ localStorage.setItem(FILE_LIST_FLOAT_KEY, value ? "1" : "0");
901
+ }
902
+ /**
903
+ * Whether lines wrap (auto-wrap) in the diff for one highlight language.
904
+ * Defaults to off; only an explicit `'1'` enables it. Stored per language, so
905
+ * a language's preference never leaks into another's.
906
+ * @param lang - the highlight language (or `''` for the auto/default bucket).
907
+ * @returns whether lines wrap.
908
+ */
909
+ function wrapEnabled(lang) {
910
+ return localStorage.getItem(`${WRAP_PREFIX}${lang}`) === "1";
911
+ }
912
+ /** Persist the per-language auto-wrap preference. */
913
+ function setWrapEnabled(lang, value) {
914
+ localStorage.setItem(`${WRAP_PREFIX}${lang}`, value ? "1" : "0");
915
+ }
916
+ /**
917
+ * The diff's tab width in spaces. Defaults to 4; the settings UI offers 2/4/8,
918
+ * but any positive integer is accepted. This drives both the rendered
919
+ * `tab-size` and the wrapped-line tab measurement, so they always agree.
920
+ * @returns the number of spaces one tab advances.
921
+ */
922
+ function tabWidth() {
923
+ const value = Number.parseInt(localStorage.getItem(TAB_WIDTH_KEY) ?? "", 10);
924
+ return Number.isInteger(value) && value > 0 ? value : 4;
925
+ }
926
+ /** Persist the diff's tab width (in spaces). */
927
+ function setTabWidth(value) {
928
+ localStorage.setItem(TAB_WIDTH_KEY, String(value));
929
+ }
930
+ /**
931
+ * The diff code font size as a percentage of the current (theme) size. Defaults
932
+ * to 100 (the current look); the settings UI steps it by ±10.
933
+ * @returns the font-size scale, as a percentage (e.g. 100, 110, 90).
934
+ */
935
+ function diffFontScale() {
936
+ const raw = Number.parseInt(localStorage.getItem(DIFF_FONT_SCALE_KEY) ?? "", 10);
937
+ if (!Number.isFinite(raw)) return 100;
938
+ return Math.max(50, Math.min(200, raw));
939
+ }
940
+ /** Persist the diff's code font-size scale (a percentage). */
941
+ function setDiffFontScale(value) {
942
+ localStorage.setItem(DIFF_FONT_SCALE_KEY, String(Math.max(50, Math.min(200, value))));
943
+ }
944
+ /**
945
+ * The diff code line height in px (the fixed row height the virtual window and
946
+ * jump math are keyed to). Defaults to 22 (the pre-customization value).
947
+ * @returns the line height in px.
948
+ */
949
+ function diffLineHeight() {
950
+ const raw = Number.parseInt(localStorage.getItem(DIFF_LINE_HEIGHT_KEY) ?? "", 10);
951
+ if (!Number.isFinite(raw)) return 22;
952
+ return Math.max(10, Math.min(36, raw));
953
+ }
954
+ /** Persist the diff's code line height (px). */
955
+ function setDiffLineHeight(value) {
956
+ localStorage.setItem(DIFF_LINE_HEIGHT_KEY, String(Math.max(10, Math.min(36, value))));
957
+ }
958
+ /** Read a theme CSS custom property, with a fallback when it is unavailable. */
959
+ function themeColor(name, fallback) {
960
+ if (typeof document !== "undefined") {
961
+ const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
962
+ if (/^#[0-9a-f]{6}$/i.test(v)) return v;
963
+ }
964
+ return fallback;
965
+ }
966
+ /** The added-line base color the user chose, or `undefined` (theme color). */
967
+ function diffAddColor() {
968
+ const v = localStorage.getItem(DIFF_ADD_COLOR_KEY);
969
+ return v !== null && v.length > 0 ? v : void 0;
970
+ }
971
+ /** Persist the added-line base color (a hex like `#22c55e`). */
972
+ function setDiffAddColor(value) {
973
+ localStorage.setItem(DIFF_ADD_COLOR_KEY, value);
974
+ }
975
+ /** The removed-line base color the user chose, or `undefined` (theme color). */
976
+ function diffDelColor() {
977
+ const v = localStorage.getItem(DIFF_DEL_COLOR_KEY);
978
+ return v !== null && v.length > 0 ? v : void 0;
979
+ }
980
+ /** Persist the removed-line base color (a hex like `#ef4444`). */
981
+ function setDiffDelColor(value) {
982
+ localStorage.setItem(DIFF_DEL_COLOR_KEY, value);
983
+ }
984
+ /** The theme's added-line base color, for the settings swatch default. */
985
+ function currentDiffAddColor() {
986
+ return themeColor("--dsw-alias-state-success-primary", "#22c55e");
987
+ }
988
+ /** The theme's removed-line base color, for the settings swatch default. */
989
+ function currentDiffDelColor() {
990
+ return themeColor("--dsw-alias-state-error-primary", "#ef4444");
991
+ }
992
+ /**
993
+ * The highlight language chosen by hand for one file suffix, remembered so the
994
+ * choice sticks for every file with that suffix. Not a Settings row: it is a
995
+ * per-suffix consequence of using the language picker, so it lives here as
996
+ * storage only.
997
+ * @param suffix - the lowercase extension without the dot (see `suffixOfPath`).
998
+ * @returns the remembered language id, or undefined for "auto".
999
+ */
1000
+ function languageForSuffix(suffix) {
1001
+ const language = rememberedLanguages()[suffix];
1002
+ return language === void 0 || language === "" ? void 0 : language;
1003
+ }
1004
+ /**
1005
+ * Remember (or clear) the language chosen for one suffix. A `null` language
1006
+ * forgets the suffix, which is what picking "auto" means.
1007
+ * @param suffix - the lowercase extension without the dot.
1008
+ * @param language - the language id, or null to forget the suffix.
1009
+ */
1010
+ function setLanguageForSuffix(suffix, language) {
1011
+ const stored = rememberedLanguages();
1012
+ if (language === null) {
1013
+ if (stored[suffix] === void 0) return;
1014
+ delete stored[suffix];
1015
+ } else {
1016
+ if (stored[suffix] === language) return;
1017
+ stored[suffix] = language;
1018
+ }
1019
+ localStorage.setItem(LANG_BY_SUFFIX_KEY, JSON.stringify(stored));
1020
+ }
1021
+ /** The whole suffix → language map (empty when unset or unreadable). */
1022
+ function rememberedLanguages() {
1023
+ const raw = localStorage.getItem(LANG_BY_SUFFIX_KEY);
1024
+ if (raw === null) return {};
1025
+ try {
1026
+ const parsed = JSON.parse(raw);
1027
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
1028
+ const cleaned = {};
1029
+ for (const [suffix, language] of Object.entries(parsed)) if (typeof language === "string" && language !== "") cleaned[suffix] = language;
1030
+ return cleaned;
1031
+ } catch {
1032
+ return {};
1033
+ }
1034
+ }
1035
+ /**
1036
+ * Whether the whole-file diff view uses the two-column (side-by-side) layout.
1037
+ * Default off (single column): the unified diff. Only an explicit `'1'` enables
1038
+ * split mode.
1039
+ * @returns whether the split (two-column) diff view is used.
1040
+ */
1041
+ function splitMode() {
1042
+ return localStorage.getItem(SPLIT_MODE_KEY) === "1";
1043
+ }
1044
+ /** Persist the split-view preference. */
1045
+ function setSplitMode(value) {
1046
+ localStorage.setItem(SPLIT_MODE_KEY, value ? "1" : "0");
1047
+ }
1048
+ /**
1049
+ * Whether the rendered Markdown preview is shown by default (for Markdown files).
1050
+ * Default off: the source diff is shown unless the panel toggle is used. Only an
1051
+ * explicit `'1'` enables the preview default.
1052
+ * @returns whether the Markdown preview default is enabled.
1053
+ */
1054
+ function mdPreviewEnabled() {
1055
+ return localStorage.getItem(MD_PREVIEW_KEY) === "1";
1056
+ }
1057
+ /** Persist the Markdown-preview default preference. */
1058
+ function setMdPreviewEnabled(value) {
1059
+ localStorage.setItem(MD_PREVIEW_KEY, value ? "1" : "0");
1060
+ }
1061
+ /**
1062
+ * The Markdown-preview content max width, in pixels (single column). Defaults to
1063
+ * 800; the double-column view totals 2x this. An out-of-range or non-integer
1064
+ * value falls back to the default.
1065
+ * @returns the max width in pixels.
1066
+ */
1067
+ function mdMaxWidth() {
1068
+ const raw = Number.parseInt(localStorage.getItem(MD_MAX_WIDTH_KEY) ?? "", 10);
1069
+ if (!Number.isInteger(raw)) return 800;
1070
+ return Math.max(480, Math.min(MD_MAX_WIDTH_MAX, raw));
1071
+ }
1072
+ /** Persist the Markdown-preview content max width. */
1073
+ function setMdMaxWidth(value) {
1074
+ localStorage.setItem(MD_MAX_WIDTH_KEY, String(Math.max(480, Math.min(MD_MAX_WIDTH_MAX, value))));
1075
+ }
1076
+ /**
1077
+ * How many rows of lead the diff block jump leaves above the jumped-to block,
1078
+ * and how far the anchored navigation scans. Defaults to 2; an out-of-range or
1079
+ * non-integer value falls back to the default.
1080
+ * @returns the lead row count.
1081
+ */
1082
+ function navLeadRows() {
1083
+ const raw = Number.parseInt(localStorage.getItem(NAV_LEAD_KEY) ?? "", 10);
1084
+ if (!Number.isInteger(raw)) return 2;
1085
+ return Math.max(0, Math.min(10, raw));
1086
+ }
1087
+ /** Persist the block-jump lead row count. */
1088
+ function setNavLeadRows(value) {
1089
+ localStorage.setItem(NAV_LEAD_KEY, String(Math.max(0, Math.min(10, value))));
1090
+ }
1091
+ const QUICK_SUMMON_KEY = "diff-approval:quick-summon-key";
1092
+ /**
1093
+ * The quick-summon chord. Stored as `Modifier+...+Key`; falls back to
1094
+ * {@link DEFAULT_QUICK_SUMMON}.
1095
+ * @returns the chord string.
1096
+ */
1097
+ function quickSummonKey() {
1098
+ return localStorage.getItem(QUICK_SUMMON_KEY) ?? "Ctrl+D";
1099
+ }
1100
+ /** Persist the quick-summon chord. */
1101
+ function setQuickSummonKey(value) {
1102
+ localStorage.setItem(QUICK_SUMMON_KEY, value);
1103
+ }
1104
+ const KEY_PREFIX = "diff-approval:key:";
1105
+ /** Default chord for each configurable action (every supported key except the
1106
+ * panel's own ESC-to-close, which is intentionally not remapped). The coverage
1107
+ * switches are keyed like the edges they toggle, in the order the panel lists
1108
+ * them: left, top, right, bottom. */
1109
+ const DEFAULT_KEYBINDINGS = {
1110
+ jumpUp: "Ctrl+ArrowUp",
1111
+ jumpDown: "Ctrl+ArrowDown",
1112
+ copyRef: "Ctrl+L",
1113
+ openSearch: "Ctrl+F",
1114
+ searchNext: "F3",
1115
+ searchPrev: "Shift+F3",
1116
+ matchCase: "Alt+C",
1117
+ matchWholeWord: "Alt+W",
1118
+ undo: "Ctrl+Z",
1119
+ redo: "Ctrl+Shift+Z",
1120
+ cycleNext: "Ctrl+Tab",
1121
+ cyclePrev: "Ctrl+Shift+Tab",
1122
+ coverLeft: "Ctrl+Shift+ArrowLeft",
1123
+ coverTop: "Ctrl+Shift+ArrowUp",
1124
+ coverRight: "Ctrl+Shift+ArrowRight",
1125
+ coverComposer: "Ctrl+Shift+ArrowDown"
1126
+ };
1127
+ /** The currently configured chord for one action; falls back to its default. */
1128
+ function keybindingOf(action) {
1129
+ return localStorage.getItem(`${KEY_PREFIX}${action}`) ?? DEFAULT_KEYBINDINGS[action] ?? "";
1130
+ }
1131
+ /** Persist one action's chord. */
1132
+ function setKeybinding(action, chord) {
1133
+ localStorage.setItem(`${KEY_PREFIX}${action}`, chord);
1134
+ }
1135
+ /**
1136
+ * Whether a keyboard event matches a chord string like `Ctrl+D`. Modifier
1137
+ * names are matched case-insensitively (`Ctrl`/`Control`, `Alt`/`Option`,
1138
+ * `Shift`, `Meta`/`Cmd`/`Command`/`Win`); the final part is the key. Exact
1139
+ * modifier set is required (extra modifiers do not match).
1140
+ * @param event - the keydown event.
1141
+ * @param shortcut - the chord string.
1142
+ * @returns whether the event matches.
1143
+ */
1144
+ function matchesShortcut(event, shortcut) {
1145
+ const parts = shortcut.split("+").map((part) => part.trim().toLowerCase());
1146
+ const key = parts.pop();
1147
+ if (key === void 0 || key === "") return false;
1148
+ const mods = new Set(parts);
1149
+ const ctrl = mods.has("ctrl") || mods.has("control");
1150
+ const alt = mods.has("alt") || mods.has("option");
1151
+ const shift = mods.has("shift");
1152
+ const meta = mods.has("meta") || mods.has("cmd") || mods.has("command") || mods.has("win");
1153
+ return event.key.toLowerCase() === key && event.ctrlKey === ctrl && event.altKey === alt && event.shiftKey === shift && event.metaKey === meta;
1154
+ }
1155
+ //#endregion
1156
+ //#region lib/types/client/chords.js
1157
+ /**
1158
+ * How a keybinding is spelled in a tooltip.
1159
+ *
1160
+ * Every hint reads the binding at render time rather than baking in a default, so
1161
+ * a rebind in Settings shows up everywhere the chord is advertised. That is also
1162
+ * why the strings live here: the panel's header, its diff toolbars, and the
1163
+ * coverage popover all advertise chords, and they must spell them the same way.
1164
+ *
1165
+ * @module dsh-diff-approval/client/chords
1166
+ */
1167
+ /** Arrow keys render as arrows in a hint (`Ctrl+↑`, not `Ctrl+ArrowUp`). */
1168
+ const CHORD_KEY_GLYPHS = {
1169
+ ArrowUp: "↑",
1170
+ ArrowDown: "↓",
1171
+ ArrowLeft: "←",
1172
+ ArrowRight: "→"
1173
+ };
1174
+ /**
1175
+ * One stored chord as a hint renders it: modifiers as written, arrow keys as
1176
+ * glyphs (`Ctrl+ArrowUp` → `Ctrl+↑`).
1177
+ * @param chord - the stored chord.
1178
+ * @returns the hint text; `''` for an unbound action.
1179
+ */
1180
+ function chordHint(chord) {
1181
+ return chord.split("+").map((part) => CHORD_KEY_GLYPHS[part] ?? part).join("+");
1182
+ }
1183
+ /**
1184
+ * One action's chord as a hint.
1185
+ * @param action - the keybinding action id (see `DEFAULT_KEYBINDINGS`).
1186
+ * @returns the hint text; `''` when the action has no chord.
1187
+ */
1188
+ function chordLabel$1(action) {
1189
+ return chordHint(keybindingOf(action));
1190
+ }
1191
+ /**
1192
+ * A tooltip label with the action's configured chord appended. The hint states
1193
+ * the binding the user actually has — including one rebound in Settings, or none
1194
+ * at all, which simply adds nothing.
1195
+ * @param label - the translated action label.
1196
+ * @param action - the keybinding action id (see `DEFAULT_KEYBINDINGS`).
1197
+ * @returns the label, with ` (chord)` appended when one is configured.
1198
+ */
1199
+ function withChord(label, action) {
1200
+ const chord = chordLabel$1(action);
1201
+ return chord === "" ? label : `${label} (${chord})`;
1202
+ }
1203
+ /**
1204
+ * How the panel's close button names its two ways out: Escape, and the
1205
+ * quick-summon chord when the user still has one bound. Unbinding the chord
1206
+ * leaves Escape as the only answer, so the hint then says just that.
1207
+ * @param t - the panel's translator.
1208
+ * @returns the tooltip label.
1209
+ */
1210
+ function closeHint(t) {
1211
+ const hint = chordHint(quickSummonKey());
1212
+ return hint === "" ? t("action.closeHintEsc") : t("action.closeHint", { chord: hint });
1213
+ }
1214
+ /**
1215
+ * How an entry button names what it opens: the pending-changes panel, plus the
1216
+ * chord that does the same. With the chord unbound the plain label is the whole
1217
+ * hint — never an empty parenthetical.
1218
+ * @param t - the panel's translator.
1219
+ * @returns the tooltip label.
1220
+ */
1221
+ function summonHint(t) {
1222
+ const hint = chordHint(quickSummonKey());
1223
+ return hint === "" ? t("panel.aria") : t("action.summonHint", { chord: hint });
1224
+ }
1225
+ //#endregion
1226
+ //#region lib/types/client/coverage-control.js
1227
+ /**
1228
+ * The floating panel's coverage control: one header button, and a popover of
1229
+ * three glyph buttons — cover the left sidebar, the composer below, the right
1230
+ * sidebar. All three on is what the panel used to call "fullscreen".
1231
+ *
1232
+ * Each glyph is the same rounded rectangle with one band highlighted, drawn to
1233
+ * the product's icon conventions (16px grid, ~1.4px ink, `currentColor`): the
1234
+ * band is filled while that side is covered and hollow while it is not, so the
1235
+ * shape says *which* edge and the fill says whether it is on. The popover is a
1236
+ * row rather than the kit's vertical menu because these are three independent
1237
+ * switches, not a list of choices.
1238
+ *
1239
+ * @module dsh-diff-approval/client/coverage-control
1240
+ */
1241
+ /** The switches in the popover, in the order the panel names them: left, top,
1242
+ * right, bottom. */
1243
+ const COVER_ROWS = [
1244
+ {
1245
+ key: "left",
1246
+ action: "coverLeft",
1247
+ label: "cover.left"
1248
+ },
1249
+ {
1250
+ key: "top",
1251
+ action: "coverTop",
1252
+ label: "cover.top"
1253
+ },
1254
+ {
1255
+ key: "right",
1256
+ action: "coverRight",
1257
+ label: "cover.right"
1258
+ },
1259
+ {
1260
+ key: "composer",
1261
+ action: "coverComposer",
1262
+ label: "cover.composer"
1263
+ }
1264
+ ];
1265
+ /** The rectangle every coverage glyph is built on: a wide panel in the 16 grid. */
1266
+ const FRAME = {
1267
+ x: 1.4,
1268
+ y: 3,
1269
+ w: 13.2,
1270
+ h: 10,
1271
+ r: 1.6
1272
+ };
1273
+ /** How thick the highlighted band is, along the edge it marks. */
1274
+ const BAND = 3.4;
1275
+ /**
1276
+ * One coverage glyph: the frame, with the edge named by `side` marked — filled
1277
+ * while that side is covered, hollow while it is not.
1278
+ * @param props - which edge, whether it is on, and the drawn size in px.
1279
+ * @returns the glyph.
1280
+ */
1281
+ function CoverEdgeIcon({ side, on, size }) {
1282
+ const { x, y, w, h } = FRAME;
1283
+ const band = side === "left" ? {
1284
+ x,
1285
+ y,
1286
+ width: BAND,
1287
+ height: h
1288
+ } : side === "right" ? {
1289
+ x: x + w - BAND,
1290
+ y,
1291
+ width: BAND,
1292
+ height: h
1293
+ } : side === "top" ? {
1294
+ x,
1295
+ y,
1296
+ width: w,
1297
+ height: BAND
1298
+ } : {
1299
+ x,
1300
+ y: y + h - BAND,
1301
+ width: w,
1302
+ height: BAND
1303
+ };
1304
+ return (0, react_jsx_runtime.jsxs)("svg", {
1305
+ width: size,
1306
+ height: size,
1307
+ viewBox: "0 0 16 16",
1308
+ fill: "none",
1309
+ xmlns: "http://www.w3.org/2000/svg",
1310
+ "aria-hidden": true,
1311
+ children: [(0, react_jsx_runtime.jsx)("rect", {
1312
+ x,
1313
+ y,
1314
+ width: w,
1315
+ height: h,
1316
+ rx: FRAME.r,
1317
+ stroke: "currentColor",
1318
+ strokeWidth: "1.4"
1319
+ }), (0, react_jsx_runtime.jsx)("rect", {
1320
+ x: band.x,
1321
+ y: band.y,
1322
+ width: band.width,
1323
+ height: band.height,
1324
+ rx: side === "composer" || side === "top" ? .8 : FRAME.r - .4,
1325
+ ...on ? { fill: "currentColor" } : {
1326
+ stroke: "currentColor",
1327
+ strokeWidth: "1.4"
1328
+ }
1329
+ })]
1330
+ });
1331
+ }
1332
+ /**
1333
+ * The coverage control's own mark: a frame with its four corners drawn, reading
1334
+ * as "how much of the frame the panel takes" — distinct from the two-arrow
1335
+ * fullscreen glyph the product ships.
1336
+ * @param props - the drawn size in px.
1337
+ * @returns the glyph.
1338
+ */
1339
+ function CoverFrameIcon({ size }) {
1340
+ return (0, react_jsx_runtime.jsx)("svg", {
1341
+ width: size,
1342
+ height: size,
1343
+ viewBox: "0 0 16 16",
1344
+ fill: "none",
1345
+ xmlns: "http://www.w3.org/2000/svg",
1346
+ "aria-hidden": true,
1347
+ children: [
1348
+ `M2.2 6.4V3.2h3.2`,
1349
+ `M9.600000000000001 3.2h3.2v3.2`,
1350
+ `M12.8 9.600000000000001h-3.2v-3.2`,
1351
+ `M6.4 12.8H3.2v-3.2`
1352
+ ].map((d) => (0, react_jsx_runtime.jsx)("path", {
1353
+ d,
1354
+ stroke: "currentColor",
1355
+ strokeWidth: "1.4",
1356
+ strokeLinecap: "round",
1357
+ strokeLinejoin: "round"
1358
+ }, d))
1359
+ });
1360
+ }
1361
+ /** How long the chord's on-screen echo stays up, matching its CSS animation. */
1362
+ const COVER_NOTICE_MS = 1250;
1363
+ /**
1364
+ * The chord's echo: the same row of glyphs the popover shows, centred on screen,
1365
+ * shown the moment a coverage chord flips a switch and fading out after a second.
1366
+ * It is a report, not a control — nothing in it is pressable — so a keyboard flip
1367
+ * is as visible as a click, without a pointer target appearing under the mouse.
1368
+ * @param props - copy, the coverage after the flip, and which edge just changed.
1369
+ * @returns the notice.
1370
+ */
1371
+ function CoverageNotice({ t, cover, changed }) {
1372
+ const label = COVER_ROWS.find((row) => row.key === changed);
1373
+ return (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
1374
+ className: PendingPanel_module_css_default.coverNotice,
1375
+ "data-diff-approval-cover-notice": true,
1376
+ role: "status",
1377
+ "aria-live": "polite",
1378
+ children: [(0, react_jsx_runtime.jsx)("div", {
1379
+ className: `${PendingPanel_module_css_default.coverCard} ${PendingPanel_module_css_default.coverNoticeCard}`,
1380
+ "aria-hidden": true,
1381
+ children: COVER_ROWS.map(({ key }) => (0, react_jsx_runtime.jsx)("span", {
1382
+ className: PendingPanel_module_css_default.coverNoticeGlyph,
1383
+ "data-on": cover[key] ? "" : void 0,
1384
+ "data-changed": key === changed ? "" : void 0,
1385
+ "data-diff-approval-cover-notice-glyph": key,
1386
+ children: (0, react_jsx_runtime.jsx)(CoverEdgeIcon, {
1387
+ side: key,
1388
+ on: cover[key],
1389
+ size: 28
1390
+ })
1391
+ }, key))
1392
+ }), (0, react_jsx_runtime.jsx)("span", {
1393
+ className: PendingPanel_module_css_default.coverNoticeText,
1394
+ children: t("cover.notice", {
1395
+ label: t(label?.label ?? "cover.left"),
1396
+ state: t(cover[changed] ? "cover.on" : "cover.off")
1397
+ })
1398
+ })]
1399
+ }), document.body);
1400
+ }
1401
+ /**
1402
+ * The trigger and its popover. Marked with `data-diff-approval-cover`, and each
1403
+ * switch with `data-diff-approval-cover-<key>`, so the two halves are addressable
1404
+ * apart.
1405
+ * @param props - copy, the current coverage, and what a switch does.
1406
+ * @returns the header control.
1407
+ */
1408
+ function CoverageControl({ t, cover, onToggle }) {
1409
+ const [open, setOpen] = (0, react.useState)(false);
1410
+ const hostRef = (0, react.useRef)(null);
1411
+ (0, react.useEffect)(() => {
1412
+ if (!open) return;
1413
+ const onPointerDown = (event) => {
1414
+ if (event.target instanceof Node && hostRef.current?.contains(event.target) === true) return;
1415
+ setOpen(false);
1416
+ };
1417
+ const onKeyDown = (event) => {
1418
+ if (event.key === "Escape") setOpen(false);
1419
+ };
1420
+ document.addEventListener("pointerdown", onPointerDown, true);
1421
+ document.addEventListener("keydown", onKeyDown);
1422
+ return () => {
1423
+ document.removeEventListener("pointerdown", onPointerDown, true);
1424
+ document.removeEventListener("keydown", onKeyDown);
1425
+ };
1426
+ }, [open]);
1427
+ return (0, react_jsx_runtime.jsxs)("span", {
1428
+ className: PendingPanel_module_css_default.coverHost,
1429
+ ref: hostRef,
1430
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1431
+ label: t("action.cover"),
1432
+ side: "bottom",
1433
+ delayMs: 500,
1434
+ children: (0, react_jsx_runtime.jsx)("button", {
1435
+ type: "button",
1436
+ className: PendingPanel_module_css_default.expand,
1437
+ "data-diff-approval-cover": true,
1438
+ "aria-label": t("action.cover"),
1439
+ "aria-haspopup": "true",
1440
+ "aria-expanded": open,
1441
+ onClick: () => {
1442
+ setOpen((value) => !value);
1443
+ },
1444
+ children: (0, react_jsx_runtime.jsx)(CoverFrameIcon, { size: 14 })
1445
+ })
1446
+ }), open && (0, react_jsx_runtime.jsx)("div", {
1447
+ className: `${PendingPanel_module_css_default.coverCard} ${PendingPanel_module_css_default.coverPopover}`,
1448
+ "data-diff-approval-cover-popover": true,
1449
+ role: "group",
1450
+ "aria-label": t("action.cover"),
1451
+ children: COVER_ROWS.map(({ key, action, label }) => (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1452
+ label: withChord(t(label), action),
1453
+ side: "bottom",
1454
+ delayMs: 0,
1455
+ children: (0, react_jsx_runtime.jsx)("button", {
1456
+ type: "button",
1457
+ className: PendingPanel_module_css_default.coverButton,
1458
+ "data-diff-approval-cover-switch": key,
1459
+ "data-on": cover[key] ? "" : void 0,
1460
+ "aria-label": withChord(t(label), action),
1461
+ "aria-pressed": cover[key],
1462
+ onClick: () => {
1463
+ onToggle(key);
1464
+ },
1465
+ children: (0, react_jsx_runtime.jsx)(CoverEdgeIcon, {
1466
+ side: key,
1467
+ on: cover[key],
1468
+ size: 20
1469
+ })
1470
+ })
1471
+ }, key))
1472
+ })]
1473
+ });
1474
+ }
1475
+ //#endregion
1476
+ //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js
1477
+ var Diff = class {
1478
+ diff(oldStr, newStr, options = {}) {
1479
+ let callback;
1480
+ if (typeof options === "function") {
1481
+ callback = options;
1482
+ options = {};
1483
+ } else if ("callback" in options) callback = options.callback;
1484
+ const oldString = this.castInput(oldStr, options);
1485
+ const newString = this.castInput(newStr, options);
1486
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
1487
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
1488
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
1489
+ }
1490
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
1491
+ var _a;
1492
+ const done = (value) => {
1493
+ value = this.postProcess(value, options);
1494
+ if (callback) {
1495
+ setTimeout(function() {
1496
+ callback(value);
1497
+ }, 0);
1498
+ return;
1499
+ } else return value;
1500
+ };
1501
+ const newLen = newTokens.length, oldLen = oldTokens.length;
1502
+ let editLength = 1;
1503
+ let maxEditLength = newLen + oldLen;
1504
+ if (options.maxEditLength != null) maxEditLength = Math.min(maxEditLength, options.maxEditLength);
1505
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
1506
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
1507
+ const bestPath = [{
1508
+ oldPos: -1,
1509
+ lastComponent: void 0
1510
+ }];
1511
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
1512
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
1513
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
1514
+ const execEditLength = () => {
1515
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
1516
+ let basePath;
1517
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
1518
+ if (removePath) bestPath[diagonalPath - 1] = void 0;
1519
+ let canAdd = false;
1520
+ if (addPath) {
1521
+ const addPathNewPos = addPath.oldPos - diagonalPath;
1522
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
1523
+ }
1524
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
1525
+ if (!canAdd && !canRemove) {
1526
+ bestPath[diagonalPath] = void 0;
1527
+ continue;
1528
+ }
1529
+ if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) basePath = this.addToPath(addPath, true, false, 0, options);
1530
+ else basePath = this.addToPath(removePath, false, true, 1, options);
1531
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
1532
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
1533
+ else {
1534
+ bestPath[diagonalPath] = basePath;
1535
+ if (basePath.oldPos + 1 >= oldLen) maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
1536
+ if (newPos + 1 >= newLen) minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
1537
+ }
1538
+ }
1539
+ editLength++;
1540
+ };
1541
+ if (callback) (function exec() {
1542
+ setTimeout(function() {
1543
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) return callback(void 0);
1544
+ if (!execEditLength()) exec();
1545
+ }, 0);
1546
+ })();
1547
+ else while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
1548
+ const ret = execEditLength();
1549
+ if (ret) return ret;
1550
+ }
1551
+ }
1552
+ addToPath(path, added, removed, oldPosInc, options) {
1553
+ const last = path.lastComponent;
1554
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) return {
1555
+ oldPos: path.oldPos + oldPosInc,
1556
+ lastComponent: {
1557
+ count: last.count + 1,
1558
+ added,
1559
+ removed,
1560
+ previousComponent: last.previousComponent
1561
+ }
1562
+ };
1563
+ else return {
1564
+ oldPos: path.oldPos + oldPosInc,
1565
+ lastComponent: {
1566
+ count: 1,
1567
+ added,
1568
+ removed,
1569
+ previousComponent: last
1570
+ }
1571
+ };
1572
+ }
1573
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
1574
+ const newLen = newTokens.length, oldLen = oldTokens.length;
1575
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
1576
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
1577
+ newPos++;
1578
+ oldPos++;
1579
+ commonCount++;
1580
+ if (options.oneChangePerToken) basePath.lastComponent = {
1581
+ count: 1,
1582
+ previousComponent: basePath.lastComponent,
1583
+ added: false,
1584
+ removed: false
1585
+ };
1586
+ }
1587
+ if (commonCount && !options.oneChangePerToken) basePath.lastComponent = {
1588
+ count: commonCount,
1589
+ previousComponent: basePath.lastComponent,
1590
+ added: false,
1591
+ removed: false
1592
+ };
1593
+ basePath.oldPos = oldPos;
1594
+ return newPos;
1595
+ }
1596
+ equals(left, right, options) {
1597
+ if (options.comparator) return options.comparator(left, right);
1598
+ else return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
1599
+ }
1600
+ removeEmpty(array) {
1601
+ const ret = [];
1602
+ for (let i = 0; i < array.length; i++) if (array[i]) ret.push(array[i]);
1603
+ return ret;
1604
+ }
1605
+ castInput(value, options) {
1606
+ return value;
1607
+ }
1608
+ tokenize(value, options) {
1609
+ return Array.from(value);
1610
+ }
1611
+ join(chars) {
1612
+ return chars.join("");
1613
+ }
1614
+ postProcess(changeObjects, options) {
1615
+ return changeObjects;
1616
+ }
1617
+ get useLongestToken() {
1618
+ return false;
1619
+ }
1620
+ buildValues(lastComponent, newTokens, oldTokens) {
1621
+ const components = [];
1622
+ let nextComponent;
1623
+ while (lastComponent) {
1624
+ components.push(lastComponent);
1625
+ nextComponent = lastComponent.previousComponent;
1626
+ delete lastComponent.previousComponent;
1627
+ lastComponent = nextComponent;
1628
+ }
1629
+ components.reverse();
1630
+ const componentLen = components.length;
1631
+ let componentPos = 0, newPos = 0, oldPos = 0;
1632
+ for (; componentPos < componentLen; componentPos++) {
1633
+ const component = components[componentPos];
1634
+ if (!component.removed) {
1635
+ if (!component.added && this.useLongestToken) {
1636
+ let value = newTokens.slice(newPos, newPos + component.count);
1637
+ value = value.map(function(value, i) {
1638
+ const oldValue = oldTokens[oldPos + i];
1639
+ return oldValue.length > value.length ? oldValue : value;
1640
+ });
1641
+ component.value = this.join(value);
1642
+ } else component.value = this.join(newTokens.slice(newPos, newPos + component.count));
1643
+ newPos += component.count;
1644
+ if (!component.added) oldPos += component.count;
1645
+ } else {
1646
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
1647
+ oldPos += component.count;
1648
+ }
1649
+ }
1650
+ return components;
1651
+ }
1652
+ };
1653
+ //#endregion
1654
+ //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/character.js
1655
+ var CharacterDiff = class extends Diff {};
1656
+ new CharacterDiff();
1657
+ //#endregion
1658
+ //#region node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/util/string.js
1659
+ function longestCommonPrefix(str1, str2) {
1660
+ let i;
1661
+ for (i = 0; i < str1.length && i < str2.length; i++) if (str1[i] != str2[i]) return str1.slice(0, i);
1662
+ return str1.slice(0, i);
1663
+ }
1664
+ function longestCommonSuffix(str1, str2) {
1665
+ let i;
1666
+ if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) return "";
1667
+ for (i = 0; i < str1.length && i < str2.length; i++) if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) return str1.slice(-i);
1668
+ return str1.slice(-i);
1669
+ }
1670
+ function replacePrefix(string, oldPrefix, newPrefix) {
751
1671
  if (string.slice(0, oldPrefix.length) != oldPrefix) throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
752
1672
  return newPrefix + string.slice(oldPrefix.length);
753
1673
  }
@@ -17618,342 +18538,460 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17618
18538
  });
17619
18539
  const onResize = () => inject();
17620
18540
  window.addEventListener("resize", onResize);
17621
- return () => {
17622
- observer.disconnect();
17623
- window.removeEventListener("resize", onResize);
17624
- document.querySelectorAll(`[${BUTTON_ATTR}]`).forEach((el) => el.remove());
17625
- if (ownedStyleEl !== null) ownedStyleEl.remove();
17626
- };
17627
- }
17628
- //#endregion
17629
- //#region lib/types/client/settings.js
17630
- /** Client preferences for the review panel, persisted in localStorage. */
17631
- const PASTE_ON_COPY_KEY = "diff-approval:paste-on-copy";
17632
- const IMPORT_UNTRACKED_KEY = "diff-approval:import-untracked";
17633
- const CONFIRM_FILE_REMOVE_KEY = "diff-approval:confirm-file-remove";
17634
- const SEARCH_CASE_KEY = "diff-approval:search-case";
17635
- const SEARCH_WORD_KEY = "diff-approval:search-word";
17636
- const TAB_WIDTH_KEY = "diff-approval:tab-size";
17637
- const SPLIT_MODE_KEY = "diff-approval:split-mode";
17638
- const MD_PREVIEW_KEY = "diff-approval:md-preview";
17639
- const MD_MAX_WIDTH_KEY = "diff-approval:md-max-width";
17640
- const NAV_LEAD_KEY = "diff-approval:nav-lead-rows";
17641
- const DIFF_FONT_SCALE_KEY = "diff-approval:diff-font-scale";
17642
- const DIFF_LINE_HEIGHT_KEY = "diff-approval:diff-line-height";
17643
- const DIFF_ADD_COLOR_KEY = "diff-approval:diff-add-color";
17644
- const DIFF_DEL_COLOR_KEY = "diff-approval:diff-del-color";
17645
- const LANG_BY_SUFFIX_KEY = "diff-approval:lang-by-suffix";
17646
- const WRAP_PREFIX = "diff-approval:wrap:";
17647
- const MD_MAX_WIDTH_MAX = 1600;
17648
- /**
17649
- * Whether copying a reference should also paste it into the chat input and
17650
- * focus it. Defaults to on; only an explicit `'0'` disables it.
17651
- * @returns whether auto-paste is enabled.
17652
- */
17653
- function pasteOnCopyEnabled() {
17654
- return localStorage.getItem(PASTE_ON_COPY_KEY) !== "0";
17655
- }
17656
- /** Persist the auto-paste preference. */
17657
- function setPasteOnCopyEnabled(value) {
17658
- localStorage.setItem(PASTE_ON_COPY_KEY, value ? "1" : "0");
17659
- }
17660
- /**
17661
- * Whether importing workspace VCS changes includes new/untracked files (git
17662
- * `??`, svn `?`, p4 unversioned). Defaults to off: collecting them scans the
17663
- * whole workspace, which can be slow on large trees, so the preference is
17664
- * opt-in. Only an explicit `'1'` enables it.
17665
- * @returns whether untracked files are imported.
17666
- */
17667
- function includeUntrackedEnabled() {
17668
- return localStorage.getItem(IMPORT_UNTRACKED_KEY) === "1";
17669
- }
17670
- /** Persist the import-untracked preference. */
17671
- function setIncludeUntrackedEnabled(value) {
17672
- localStorage.setItem(IMPORT_UNTRACKED_KEY, value ? "1" : "0");
17673
- }
17674
- /**
17675
- * Whether a whole-file keep/revert asks before dropping the resolved file from
17676
- * the list. Defaults to on; only an explicit `'0'` disables it, which then
17677
- * removes the file straight away.
17678
- * @returns whether the remove prompt is enabled.
17679
- */
17680
- function confirmFileRemoveEnabled() {
17681
- return localStorage.getItem(CONFIRM_FILE_REMOVE_KEY) !== "0";
17682
- }
17683
- /** Persist the whole-file remove-prompt preference. */
17684
- function setConfirmFileRemoveEnabled(value) {
17685
- localStorage.setItem(CONFIRM_FILE_REMOVE_KEY, value ? "1" : "0");
17686
- }
17687
- /**
17688
- * Whether the in-file search matches letter case exactly. Defaults to off, so a
17689
- * plain query keeps matching either case; only an explicit `'1'` turns it on.
17690
- * @returns whether the search is case-sensitive.
17691
- */
17692
- function searchCaseSensitive() {
17693
- return localStorage.getItem(SEARCH_CASE_KEY) === "1";
17694
- }
17695
- /** Persist the search case-sensitivity preference. */
17696
- function setSearchCaseSensitive(value) {
17697
- localStorage.setItem(SEARCH_CASE_KEY, value ? "1" : "0");
17698
- }
17699
- /**
17700
- * Whether the in-file search only matches whole words. Defaults to off; only an
17701
- * explicit `'1'` enables it.
17702
- * @returns whether the search matches whole words only.
17703
- */
17704
- function searchWholeWord() {
17705
- return localStorage.getItem(SEARCH_WORD_KEY) === "1";
17706
- }
17707
- /** Persist the search whole-word preference. */
17708
- function setSearchWholeWord(value) {
17709
- localStorage.setItem(SEARCH_WORD_KEY, value ? "1" : "0");
17710
- }
17711
- /**
17712
- * Whether lines wrap (auto-wrap) in the diff for one highlight language.
17713
- * Defaults to off; only an explicit `'1'` enables it. Stored per language, so
17714
- * a language's preference never leaks into another's.
17715
- * @param lang - the highlight language (or `''` for the auto/default bucket).
17716
- * @returns whether lines wrap.
17717
- */
17718
- function wrapEnabled(lang) {
17719
- return localStorage.getItem(`${WRAP_PREFIX}${lang}`) === "1";
17720
- }
17721
- /** Persist the per-language auto-wrap preference. */
17722
- function setWrapEnabled(lang, value) {
17723
- localStorage.setItem(`${WRAP_PREFIX}${lang}`, value ? "1" : "0");
17724
- }
17725
- /**
17726
- * The diff's tab width in spaces. Defaults to 4; the settings UI offers 2/4/8,
17727
- * but any positive integer is accepted. This drives both the rendered
17728
- * `tab-size` and the wrapped-line tab measurement, so they always agree.
17729
- * @returns the number of spaces one tab advances.
17730
- */
17731
- function tabWidth() {
17732
- const value = Number.parseInt(localStorage.getItem(TAB_WIDTH_KEY) ?? "", 10);
17733
- return Number.isInteger(value) && value > 0 ? value : 4;
17734
- }
17735
- /** Persist the diff's tab width (in spaces). */
17736
- function setTabWidth(value) {
17737
- localStorage.setItem(TAB_WIDTH_KEY, String(value));
18541
+ return () => {
18542
+ observer.disconnect();
18543
+ window.removeEventListener("resize", onResize);
18544
+ document.querySelectorAll(`[${BUTTON_ATTR}]`).forEach((el) => el.remove());
18545
+ if (ownedStyleEl !== null) ownedStyleEl.remove();
18546
+ };
17738
18547
  }
18548
+ //#endregion
18549
+ //#region lib/types/client/boundary.js
17739
18550
  /**
17740
- * The diff code font size as a percentage of the current (theme) size. Defaults
17741
- * to 100 (the current look); the settings UI steps it by ±10.
17742
- * @returns the font-size scale, as a percentage (e.g. 100, 110, 90).
18551
+ * A crash inside the panel must not take the review away for the rest of the page.
18552
+ *
18553
+ * The slot renderer wraps every seat in its own boundary, but a crash there
18554
+ * *abdicates* the entry: the footer row, the header button and the docked tab
18555
+ * disappear for the life of the page, with nothing on screen to say why and no way
18556
+ * back but a reload. This boundary keeps a failure inside our own tree instead —
18557
+ * the entry stays registered, it says what happened, and one press retries the
18558
+ * render — so a broken panel can never present as "the panel is gone" or as a
18559
+ * blank surface with no explanation.
18560
+ *
18561
+ * @module dsh-diff-approval/client/boundary
17743
18562
  */
17744
- function diffFontScale() {
17745
- const raw = Number.parseInt(localStorage.getItem(DIFF_FONT_SCALE_KEY) ?? "", 10);
17746
- if (!Number.isFinite(raw)) return 100;
17747
- return Math.max(50, Math.min(200, raw));
17748
- }
17749
- /** Persist the diff's code font-size scale (a percentage). */
17750
- function setDiffFontScale(value) {
17751
- localStorage.setItem(DIFF_FONT_SCALE_KEY, String(Math.max(50, Math.min(200, value))));
17752
- }
17753
18563
  /**
17754
- * The diff code line height in px (the fixed row height the virtual window and
17755
- * jump math are keyed to). Defaults to 22 (the pre-customization value).
17756
- * @returns the line height in px.
18564
+ * Keep one panel mount's crash inside its own slot entry, with a visible note and
18565
+ * a retry that re-renders it.
17757
18566
  */
17758
- function diffLineHeight() {
17759
- const raw = Number.parseInt(localStorage.getItem(DIFF_LINE_HEIGHT_KEY) ?? "", 10);
17760
- if (!Number.isFinite(raw)) return 22;
17761
- return Math.max(10, Math.min(36, raw));
17762
- }
17763
- /** Persist the diff's code line height (px). */
17764
- function setDiffLineHeight(value) {
17765
- localStorage.setItem(DIFF_LINE_HEIGHT_KEY, String(Math.max(10, Math.min(36, value))));
17766
- }
17767
- /** Read a theme CSS custom property, with a fallback when it is unavailable. */
17768
- function themeColor(name, fallback) {
17769
- if (typeof document !== "undefined") {
17770
- const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
17771
- if (/^#[0-9a-f]{6}$/i.test(v)) return v;
18567
+ var PanelBoundary = class extends react.Component {
18568
+ state = { failed: false };
18569
+ static getDerivedStateFromError() {
18570
+ return { failed: true };
17772
18571
  }
17773
- return fallback;
17774
- }
17775
- /** The added-line base color the user chose, or `undefined` (theme color). */
17776
- function diffAddColor() {
17777
- const v = localStorage.getItem(DIFF_ADD_COLOR_KEY);
17778
- return v !== null && v.length > 0 ? v : void 0;
17779
- }
17780
- /** Persist the added-line base color (a hex like `#22c55e`). */
17781
- function setDiffAddColor(value) {
17782
- localStorage.setItem(DIFF_ADD_COLOR_KEY, value);
17783
- }
17784
- /** The removed-line base color the user chose, or `undefined` (theme color). */
17785
- function diffDelColor() {
17786
- const v = localStorage.getItem(DIFF_DEL_COLOR_KEY);
17787
- return v !== null && v.length > 0 ? v : void 0;
17788
- }
17789
- /** Persist the removed-line base color (a hex like `#ef4444`). */
17790
- function setDiffDelColor(value) {
17791
- localStorage.setItem(DIFF_DEL_COLOR_KEY, value);
17792
- }
17793
- /** The theme's added-line base color, for the settings swatch default. */
17794
- function currentDiffAddColor() {
17795
- return themeColor("--dsw-alias-state-success-primary", "#22c55e");
17796
- }
17797
- /** The theme's removed-line base color, for the settings swatch default. */
17798
- function currentDiffDelColor() {
17799
- return themeColor("--dsw-alias-state-error-primary", "#ef4444");
17800
- }
18572
+ componentDidCatch(error, info) {
18573
+ console.error("diff-approval panel crashed:", error, info.componentStack);
18574
+ }
18575
+ render() {
18576
+ if (!this.state.failed) return this.props.children;
18577
+ const { t } = this.props;
18578
+ return (0, react_jsx_runtime.jsxs)("div", {
18579
+ className: PendingPanel_module_css_default.panelFailure,
18580
+ role: "alert",
18581
+ "data-diff-approval-failed": true,
18582
+ children: [(0, react_jsx_runtime.jsx)("span", {
18583
+ className: PendingPanel_module_css_default.panelFailureText,
18584
+ children: t("panel.crashed")
18585
+ }), (0, react_jsx_runtime.jsx)("button", {
18586
+ type: "button",
18587
+ className: PendingPanel_module_css_default.panelFailureRetry,
18588
+ "data-diff-approval-failed-retry": true,
18589
+ onClick: () => {
18590
+ this.setState({ failed: false });
18591
+ },
18592
+ children: t("action.retry")
18593
+ })]
18594
+ });
18595
+ }
18596
+ };
18597
+ //#endregion
18598
+ //#region lib/types/client/dock.js
17801
18599
  /**
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".
18600
+ * The app's right sidebar (the docking surface) as a host for the review panel.
18601
+ *
18602
+ * The sidebar is an extensible tab system: a tab type registers its static face
18603
+ * with the `sidebarRightTabs` service, its body in the keyed `sidebar.right.pane.tab`
18604
+ * seat under the same `id`, and is opened by `kind` through `sidebarRight`. The
18605
+ * harness documents this as the path a *package outside the product* takes (its
18606
+ * own document preview ships that way), so nothing here imports the sidebar's
18607
+ * package as a runtime value: the shapes below are the structural minimum, which
18608
+ * is exactly what lets this plugin keep loading on releases that have no right
18609
+ * sidebar at all — the type is simply never registered there.
18610
+ *
18611
+ * @module dsh-diff-approval/client/dock
17808
18612
  */
17809
- function languageForSuffix(suffix) {
17810
- const language = rememberedLanguages()[suffix];
17811
- return language === void 0 || language === "" ? void 0 : language;
18613
+ /** The tab type's `kind`: what `openTab` names. */
18614
+ const DIFF_DOCK_KIND = "diff-approval";
18615
+ /** The registration's `id`: the key its body and title register under. The
18616
+ * registry wants a name unique across implementations, and the package name is
18617
+ * the natural value. */
18618
+ const DIFF_DOCK_ID = "dsh-diff-approval";
18619
+ /** Window event a docked panel dispatches to move the panel back to the overlay.
18620
+ * The docked tab and the footer entry are separate mounts, so the ask travels as
18621
+ * an event — the same bridge the produced-file buttons use. */
18622
+ const SHOW_PANEL_EVENT = "diff-approval:show-panel";
18623
+ /** Ask the panel to toggle: what the header entry's button does. It runs the same
18624
+ * action as the footer badge, so the two entries can never disagree about what a
18625
+ * press means — including for a docked panel, whose tab the chip still closes. */
18626
+ const TOGGLE_PANEL_EVENT = "diff-approval:toggle-panel";
18627
+ /** The floating panel's own visibility, published on every change so a second
18628
+ * entry can light up while the panel is open (`detail.open`). */
18629
+ const PANEL_STATE_EVENT = "diff-approval:panel-state";
18630
+ /** Name a file for the panel to show, from a mount that is not the panel itself
18631
+ * (the produced-file chip): every mounted panel instance switches to it and lands
18632
+ * on its first change — the ask is "show me this diff", not "put me back where I
18633
+ * was". The file is also recorded as the last one, so an instance that only
18634
+ * appears afterwards (the docked tab) opens the same file the same way. */
18635
+ const OPEN_PANEL_FILE_EVENT = "diff-approval:panel-file";
18636
+ /** How often the optional sidebar services are looked up, and for how long. The
18637
+ * sidebar's plugin may attach after this one; ~15s of patience covers a slow
18638
+ * boot, and finding them stops the timer. */
18639
+ const DOCK_LOOKUP_INTERVAL_MS = 150;
18640
+ const DOCK_LOOKUP_ATTEMPTS = 100;
18641
+ /** A service this plugin already depends on, probed through the same lookup the
18642
+ * sidebar is found with. If it reads "ok" while the sidebar's do not, the lookup
18643
+ * itself works and the sidebar's services are simply not reachable from this
18644
+ * plugin's context — a different bug from "not loaded yet". */
18645
+ const PROBE_SERVICES = ["slots"];
18646
+ /** How one lookup result reads in the diagnostic string. Never throws: a probe
18647
+ * that fails says so instead of taking the whole dock wiring down with it. */
18648
+ function shape(ctx, name) {
18649
+ try {
18650
+ const value = ctx.get(name);
18651
+ if (value === void 0) return "missing";
18652
+ return typeof value === "object" || typeof value === "function" ? "ok" : typeof value;
18653
+ } catch (error) {
18654
+ return `threw(${error instanceof Error ? error.message : String(error)})`;
18655
+ }
17812
18656
  }
17813
18657
  /**
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.
18658
+ * What the lookup can see, for the "why is there no dock" message: the sidebar's
18659
+ * two services first (the decisive fact), then the services this plugin already
18660
+ * uses, then whether the tab seat exists at all — a seat without services means
18661
+ * the sidebar's plugin is loaded but its services are not reachable from here.
18662
+ * @param ctx - the client context (structurally, see {@link DockHostContext}).
18663
+ * @returns a one-line description, safe to show and to report.
17818
18664
  */
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 {};
18665
+ function diagnose(ctx) {
18666
+ const probed = PROBE_SERVICES.map((name) => `${name}=${shape(ctx, name)}`).join(" ");
18667
+ let seat = "unknown";
17834
18668
  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 {};
18669
+ seat = ctx.get("slots")?.spec?.("sidebar.right.pane.tab") === void 0 ? "missing" : "ok";
18670
+ } catch (error) {
18671
+ seat = `probe threw: ${error instanceof Error ? error.message : String(error)}`;
17842
18672
  }
18673
+ return `sidebarRight=${shape(ctx, "sidebarRight")} sidebarRightTabs=${shape(ctx, "sidebarRightTabs")}, ${probed}, seat(sidebar.right.pane.tab)=${seat}`;
17843
18674
  }
18675
+ /** Where the guide page lists the panel, relative to the product's own entries. */
18676
+ const DIFF_DOCK_GUIDE_ORDER = 40;
17844
18677
  /**
17845
- * Whether the whole-file diff view uses the two-column (side-by-side) layout.
17846
- * Default off (single column): the unified diff. Only an explicit `'1'` enables
17847
- * split mode.
17848
- * @returns whether the split (two-column) diff view is used.
18678
+ * The dock's live state, created at apply time rather than when the sidebar's
18679
+ * services appear. The footer entry is seated once, so a face captured before the
18680
+ * sidebar attaches would freeze on "no dock" and the panel would never offer the
18681
+ * switch the state below is therefore always present, and `attach` swaps the
18682
+ * live sidebar underneath it while `open` resolves the current one per call.
18683
+ * @returns the state; `attach` reports availability and the tab's own visibility.
17849
18684
  */
17850
- function splitMode() {
17851
- return localStorage.getItem(SPLIT_MODE_KEY) === "1";
17852
- }
17853
- /** Persist the split-view preference. */
17854
- function setSplitMode(value) {
17855
- localStorage.setItem(SPLIT_MODE_KEY, value ? "1" : "0");
18685
+ function createDockState() {
18686
+ let sidebar;
18687
+ let reason;
18688
+ /** Reported by the docked tab body: see {@link DockFace.setShowing}. */
18689
+ let showing = false;
18690
+ /** Published by the docked tab's chip: see {@link DockFace.setClose}. */
18691
+ let closeTab;
18692
+ let snapshot = {
18693
+ available: false,
18694
+ open: false,
18695
+ reason: void 0
18696
+ };
18697
+ const listeners = /* @__PURE__ */ new Set();
18698
+ const read = () => sidebar === void 0 ? {
18699
+ available: false,
18700
+ open: false,
18701
+ reason
18702
+ } : {
18703
+ available: true,
18704
+ open: showing,
18705
+ reason: void 0
18706
+ };
18707
+ const publish = () => {
18708
+ const next = read();
18709
+ if (next.available === snapshot.available && next.open === snapshot.open && next.reason === snapshot.reason) return;
18710
+ snapshot = next;
18711
+ for (const listener of [...listeners]) listener();
18712
+ };
18713
+ return {
18714
+ face: {
18715
+ hooks: { dock: {
18716
+ getSnapshot: () => snapshot,
18717
+ subscribe(listener) {
18718
+ listeners.add(listener);
18719
+ return () => {
18720
+ listeners.delete(listener);
18721
+ };
18722
+ }
18723
+ } },
18724
+ open: () => {
18725
+ sidebar?.openTab(DIFF_DOCK_KIND, { revealIfOpened: true });
18726
+ },
18727
+ setShowing: (next) => {
18728
+ if (showing === next) return;
18729
+ showing = next;
18730
+ publish();
18731
+ },
18732
+ setClose: (close) => {
18733
+ closeTab = close;
18734
+ },
18735
+ close: () => {
18736
+ closeTab?.();
18737
+ }
18738
+ },
18739
+ attach(next, why) {
18740
+ reason = why;
18741
+ sidebar = next;
18742
+ if (sidebar === void 0) showing = false;
18743
+ publish();
18744
+ return () => {
18745
+ sidebar = void 0;
18746
+ showing = false;
18747
+ publish();
18748
+ };
18749
+ }
18750
+ };
17856
18751
  }
17857
18752
  /**
17858
- * Whether the rendered Markdown preview is shown by default (for Markdown files).
17859
- * Default off: the source diff is shown unless the panel toggle is used. Only an
17860
- * explicit `'1'` enables the preview default.
17861
- * @returns whether the Markdown preview default is enabled.
18753
+ * The tab body: the review panel, docked. The panel's content is one portaled
18754
+ * block, so instead of restructuring it this hands it a host element of the
18755
+ * sidebar's own making to portal into — the tab body owns the element it fills.
18756
+ * @param props - the seat's runtime props, this plugin's face, and `useTabInfo`.
18757
+ * @returns the host element and, once it exists, the docked panel inside it.
17862
18758
  */
17863
- function mdPreviewEnabled() {
17864
- return localStorage.getItem(MD_PREVIEW_KEY) === "1";
17865
- }
17866
- /** Persist the Markdown-preview default preference. */
17867
- function setMdPreviewEnabled(value) {
17868
- localStorage.setItem(MD_PREVIEW_KEY, value ? "1" : "0");
18759
+ /** Keeps a failure inside the tab from taking the app's tree down with it, and
18760
+ * from leaving the pane blank: {@link PanelBoundary} says what happened and can
18761
+ * re-render the panel without a reload. */
18762
+ function DiffDockBody(props) {
18763
+ const { t, useTabInfo, onDockShowing, ...panelProps } = props;
18764
+ const [host, setHost] = (0, react.useState)(null);
18765
+ const visible = (useTabInfo?.())?.tab.visible === true;
18766
+ (0, react.useEffect)(() => {
18767
+ onDockShowing?.(visible);
18768
+ return () => {
18769
+ onDockShowing?.(false);
18770
+ };
18771
+ }, [visible, onDockShowing]);
18772
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
18773
+ ref: setHost,
18774
+ className: PendingPanel_module_css_default.dockHost,
18775
+ "data-diff-approval-dock": true
18776
+ }), host !== null && (0, react_jsx_runtime.jsx)(PanelBoundary, {
18777
+ t,
18778
+ children: (0, react_jsx_runtime.jsx)(PendingPanel, {
18779
+ ...panelProps,
18780
+ t,
18781
+ docked: true,
18782
+ dockHost: host
18783
+ })
18784
+ })] });
17869
18785
  }
17870
18786
  /**
17871
- * The Markdown-preview content max width, in pixels (single column). Defaults to
17872
- * 800; the double-column view totals 2x this. An out-of-range or non-integer
17873
- * value falls back to the default.
17874
- * @returns the max width in pixels.
18787
+ * The tab chip: the mode switch, then the panel's title with the pending count —
18788
+ * read live from the store so the chip tracks the list without re-registering the
18789
+ * tab. The glyph leads because that is this seat's convention (the document tab
18790
+ * puts its file-type icon there), and because the chip's tail is masked for the
18791
+ * kit's own close button.
18792
+ *
18793
+ * A docked panel draws no header of its own, so this chip is where the panel is
18794
+ * named *and* where it is told to leave: the mode switch offers the two overlay
18795
+ * states, and its menu footer keeps this plugin's Settings one click away, which
18796
+ * the docked panel has no gear for.
18797
+ * @param props - the same face the body gets; the count and the switch are drawn.
18798
+ * @returns the chip content.
17875
18799
  */
17876
- function mdMaxWidth() {
17877
- const raw = Number.parseInt(localStorage.getItem(MD_MAX_WIDTH_KEY) ?? "", 10);
17878
- if (!Number.isInteger(raw)) return 800;
17879
- return Math.max(480, Math.min(MD_MAX_WIDTH_MAX, raw));
17880
- }
17881
- /** Persist the Markdown-preview content max width. */
17882
- function setMdMaxWidth(value) {
17883
- localStorage.setItem(MD_MAX_WIDTH_KEY, String(Math.max(480, Math.min(MD_MAX_WIDTH_MAX, value))));
18800
+ function DiffDockTitle(props) {
18801
+ const { t, usePending, useTabInfo, onDockClose } = props;
18802
+ let count = 0;
18803
+ try {
18804
+ count = usePending?.((snapshot) => snapshot.files.length) ?? 0;
18805
+ } catch {
18806
+ count = 0;
18807
+ }
18808
+ const tab = useTabInfo?.();
18809
+ const tabRef = (0, react.useRef)(tab);
18810
+ tabRef.current = tab;
18811
+ (0, react.useEffect)(() => {
18812
+ onDockClose?.(() => {
18813
+ tabRef.current?.tab.actions.close();
18814
+ });
18815
+ return () => {
18816
+ onDockClose?.(void 0);
18817
+ };
18818
+ }, [onDockClose]);
18819
+ /** Leave the dock for an overlay: remember it, close this tab, and let the
18820
+ * footer entry (a separate mount) show the panel in the overlay. */
18821
+ const leaveDock = (next) => {
18822
+ if (next === "dock") return;
18823
+ setPanelPresentation(next);
18824
+ tabRef.current?.tab.actions.close();
18825
+ window.dispatchEvent(new CustomEvent(SHOW_PANEL_EVENT));
18826
+ };
18827
+ return (0, react_jsx_runtime.jsxs)("span", {
18828
+ className: PendingPanel_module_css_default.chip,
18829
+ "data-diff-approval-chip": true,
18830
+ children: [(0, react_jsx_runtime.jsx)("span", {
18831
+ className: PendingPanel_module_css_default.chipLabel,
18832
+ children: count > 0 ? `${t("panel.title")} · ${count}` : t("panel.title")
18833
+ }), (0, react_jsx_runtime.jsx)(PresentationMenu, {
18834
+ t,
18835
+ current: "dock",
18836
+ onChoose: leaveDock,
18837
+ compact: true
18838
+ })]
18839
+ });
17884
18840
  }
17885
18841
  /**
17886
- * How many rows of lead the diff block jump leaves above the jumped-to block,
17887
- * and how far the anchored navigation scans. Defaults to 2; an out-of-range or
17888
- * non-integer value falls back to the default.
17889
- * @returns the lead row count.
18842
+ * Find the sidebar's services and, once they are there, attach the panel to them:
18843
+ * register the tab type (with a guide entry, so the panel is discoverable from the
18844
+ * sidebar's own guide page) and hand the live sidebar to `attach`. Where they are
18845
+ * never provided — an older app, or a composition without the right sidebar
18846
+ * nothing is registered and nothing attaches, which is what makes the whole
18847
+ * feature optional rather than required.
18848
+ *
18849
+ * Discovery is a timed optional lookup rather than cordis's dynamic `inject`,
18850
+ * because the plugin sandbox forwards `effect`/`on`/timers but not `inject`, and
18851
+ * the sidebar may attach after this plugin does. Timers are the window's own (the
18852
+ * sandbox's ctx timer verbs want a `timer` declaration, and a host that withholds
18853
+ * the verb must not take the panel down with it).
18854
+ * @param ctx - the client context (structurally, see {@link DockHostContext}).
18855
+ * @param copy - the tab's copy, read per call so a language change needs no
18856
+ * re-registration.
18857
+ * @param attach - takes the live sidebar; returns its detach.
17890
18858
  */
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))));
18859
+ function attachDiffDock(ctx, copy, attach) {
18860
+ let timer;
18861
+ let attached = false;
18862
+ /** The last lookup's shape, kept so "no dock" can say which half was missing. */
18863
+ let detail = "not looked up yet";
18864
+ /** The shape already reported to `attach`, so the poll reports once per result
18865
+ * rather than forty times. */
18866
+ let reported = "";
18867
+ const stop = () => {
18868
+ if (timer === void 0) return;
18869
+ window.clearTimeout(timer);
18870
+ timer = void 0;
18871
+ };
18872
+ /** Hand "no sidebar, and here is what the lookup saw" to the state — once per
18873
+ * distinct shape, so a 100-tick poll reports once and then only on a change.
18874
+ * The panel surfaces it when the user asks for the dock. */
18875
+ const report = () => {
18876
+ if (detail === reported) return;
18877
+ reported = detail;
18878
+ attach(void 0, detail);
18879
+ };
18880
+ const look = () => {
18881
+ if (attached) return true;
18882
+ try {
18883
+ let sidebar;
18884
+ let registry;
18885
+ try {
18886
+ sidebar = ctx.get("sidebarRight");
18887
+ registry = ctx.get("sidebarRightTabs");
18888
+ } catch (error) {
18889
+ detail = `ctx.get threw: ${error instanceof Error ? error.message : String(error)}`;
18890
+ report();
18891
+ return false;
18892
+ }
18893
+ detail = diagnose(ctx);
18894
+ if (sidebar === void 0 || registry === void 0) {
18895
+ report();
18896
+ return false;
18897
+ }
18898
+ attached = true;
18899
+ ctx.effect(() => attach(sidebar), "diff-approval: dock sidebar");
18900
+ ctx.effect(() => registry.register({
18901
+ id: DIFF_DOCK_ID,
18902
+ kind: DIFF_DOCK_KIND,
18903
+ title: () => copy.title(),
18904
+ guide: [{
18905
+ order: DIFF_DOCK_GUIDE_ORDER,
18906
+ title: () => copy.title(),
18907
+ description: () => copy.guideDescription()
18908
+ }]
18909
+ }), "diff-approval: right-sidebar tab type");
18910
+ return true;
18911
+ } catch (error) {
18912
+ detail = `dock wiring threw: ${error instanceof Error ? error.message : String(error)}`;
18913
+ report();
18914
+ return false;
18915
+ }
18916
+ };
18917
+ ctx.effect(() => stop, "diff-approval: dock lookup");
18918
+ if (look()) return;
18919
+ let attempts = 0;
18920
+ const tick = () => {
18921
+ timer = void 0;
18922
+ if (look()) {
18923
+ stop();
18924
+ return;
18925
+ }
18926
+ if (++attempts >= DOCK_LOOKUP_ATTEMPTS) {
18927
+ stop();
18928
+ attach(void 0, `no right sidebar after ${DOCK_LOOKUP_ATTEMPTS} looks: ${detail}`);
18929
+ return;
18930
+ }
18931
+ timer = window.setTimeout(tick, DOCK_LOOKUP_INTERVAL_MS);
18932
+ };
18933
+ timer = window.setTimeout(tick, DOCK_LOOKUP_INTERVAL_MS);
17899
18934
  }
17900
- const QUICK_SUMMON_KEY = "diff-approval:quick-summon-key";
18935
+ //#endregion
18936
+ //#region lib/types/client/panel-memory.js
17901
18937
  /**
17902
- * The quick-summon chord. Stored as `Modifier+...+Key`; falls back to
17903
- * {@link DEFAULT_QUICK_SUMMON}.
17904
- * @returns the chord string.
18938
+ * Where the review panel was left, for this page's lifetime.
18939
+ *
18940
+ * Closing the panel is not "done reviewing": reopening it should put the reader
18941
+ * back on the file they were in, at the offset they were at. That is a fact about
18942
+ * this visit rather than a stored preference, so it lives in module state — which
18943
+ * also gives the two mounts that show the panel (the floating overlay and the
18944
+ * docked tab) one shared memory, so switching presentation does not lose the
18945
+ * place, and a reload starts clean.
18946
+ *
18947
+ * Keyed by session: two sessions have their own pending lists, and one session's
18948
+ * panel must not resume another's file.
18949
+ *
18950
+ * @module dsh-diff-approval/client/panel-memory
17905
18951
  */
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"
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] ?? "";
18952
+ const views = /* @__PURE__ */ new Map();
18953
+ /** This session's record, created on first use. */
18954
+ function viewOf(sessionId) {
18955
+ let view = views.get(sessionId);
18956
+ if (view === void 0) {
18957
+ view = { offsets: /* @__PURE__ */ new Map() };
18958
+ views.set(sessionId, view);
18959
+ }
18960
+ return view;
17933
18961
  }
17934
- /** Persist one action's chord. */
17935
- function setKeybinding(action, chord) {
17936
- localStorage.setItem(`${KEY_PREFIX}${action}`, chord);
18962
+ /**
18963
+ * Remember that a session's panel was left showing one file, and where in it.
18964
+ * @param sessionId - the session the panel was reviewing; nothing is recorded
18965
+ * without one.
18966
+ * @param view - the pending entry that was open, and the code view's scrollTop.
18967
+ * An absent offset *forgets* any remembered place for that file, which is what
18968
+ * a caller that is about to open it at its first change wants: a mount that only
18969
+ * appears afterwards then lands there too, instead of on a stale offset.
18970
+ */
18971
+ function rememberPanelView(sessionId, view) {
18972
+ if (sessionId === void 0) return;
18973
+ const record = viewOf(sessionId);
18974
+ record.last = view.fileId;
18975
+ if (view.scrollTop === void 0) record.offsets.delete(view.fileId);
18976
+ else record.offsets.set(view.fileId, view.scrollTop);
17937
18977
  }
17938
18978
  /**
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.
18979
+ * The file a session's panel was last showing.
18980
+ * @param sessionId - the session, if any.
18981
+ * @returns the pending entry id, or undefined when that session's panel has never
18982
+ * closed on a file.
17946
18983
  */
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;
18984
+ function lastPanelFile(sessionId) {
18985
+ return sessionId === void 0 ? void 0 : views.get(sessionId)?.last;
18986
+ }
18987
+ /**
18988
+ * How far down one file was left, when it has been left before.
18989
+ * @param sessionId - the session, if any.
18990
+ * @param fileId - the pending entry id.
18991
+ * @returns the remembered scrollTop, or undefined when there is none.
18992
+ */
18993
+ function panelFileOffset(sessionId, fileId) {
18994
+ return sessionId === void 0 ? void 0 : views.get(sessionId)?.offsets.get(fileId);
17957
18995
  }
17958
18996
  //#endregion
17959
18997
  //#region lib/types/client/PendingPanel.js
@@ -17969,20 +19007,75 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
17969
19007
  const FALLBACK_BOTTOM_PX = 128;
17970
19008
  /** Gap kept between the composer's top edge and the panel bottom, in px. */
17971
19009
  const COMPOSER_GAP_PX = 12;
17972
- /** Fixed window-edge inset used when expanded, mirroring `.panel`'s top/left/right. */
19010
+ /** The coverage switch each chord toggles, in the order the popover lists them. */
19011
+ const COVER_ACTIONS = [
19012
+ ["coverLeft", "left"],
19013
+ ["coverTop", "top"],
19014
+ ["coverRight", "right"],
19015
+ ["coverComposer", "composer"]
19016
+ ];
19017
+ /** Fixed window-edge inset the floating panel keeps on every side, mirroring
19018
+ * `.panel`'s own left/right/top. */
17973
19019
  const PANEL_INSET_PX = 8;
19020
+ /** The smallest floating panel worth drawing. A window that cannot hold the
19021
+ * uncovered edges *and* a panel this big covers them instead — the edges are a
19022
+ * preference, an invisible panel is a bug, and a phone-sized window is exactly
19023
+ * the case where leaving a sidebar visible can squeeze the panel to nothing. */
19024
+ const MIN_PANEL_WIDTH_PX = 240;
19025
+ const MIN_PANEL_HEIGHT_PX = 200;
19026
+ /**
19027
+ * The floating panel's four insets for the current coverage. An edge the panel
19028
+ * covers keeps the small window inset; one it does not cover starts past what the
19029
+ * app draws there. If the result would leave the panel smaller than
19030
+ * {@link MIN_PANEL_WIDTH_PX} / {@link MIN_PANEL_HEIGHT_PX}, the sides are covered
19031
+ * again rather than drawn as an invisible sliver.
19032
+ * @param cover - which edges the panel is covering.
19033
+ * @param sideInset - what the app occupies on each side (see {@link frameInsets}).
19034
+ * @param bottomPx - the composer offset used when the composer is not covered.
19035
+ * @param viewport - the window's size in px.
19036
+ * @returns the insets for the panel's style.
19037
+ */
19038
+ function panelInsets(cover, sideInset, bottomPx, viewport) {
19039
+ const side = (covered, value) => covered ? PANEL_INSET_PX : value + PANEL_INSET_PX;
19040
+ let left = side(cover.left, sideInset.left);
19041
+ let right = side(cover.right, sideInset.right);
19042
+ if (viewport.width - left - right < MIN_PANEL_WIDTH_PX) {
19043
+ left = PANEL_INSET_PX;
19044
+ right = PANEL_INSET_PX;
19045
+ }
19046
+ let top = cover.top ? PANEL_INSET_PX : sideInset.top;
19047
+ let bottom = cover.composer ? PANEL_INSET_PX : bottomPx;
19048
+ if (viewport.height - top - bottom < MIN_PANEL_HEIGHT_PX) {
19049
+ top = PANEL_INSET_PX;
19050
+ bottom = PANEL_INSET_PX;
19051
+ }
19052
+ return {
19053
+ top,
19054
+ left,
19055
+ right,
19056
+ bottom
19057
+ };
19058
+ }
19059
+ /** Narrowest docked panel that still fits the file list beside the detail. Below
19060
+ * it the list folds into the floating card, exactly as it does in a narrow
19061
+ * floating panel — a right-sidebar column is narrow even on a wide window, so the
19062
+ * viewport width says nothing about it. */
19063
+ const DOCK_TWO_COLUMN_MIN_PX = 520;
17974
19064
  /** The harness composer seat (conversation scroll body + seat div). */
17975
19065
  const SCROLL_SELECTOR = "[data-conversation-scroll]";
17976
19066
  const SEAT_SELECTOR = "[data-composer-seat]";
19067
+ /** The chat composer's own editable box, where a closed panel hands the caret. */
19068
+ const COMPOSER_INPUT_SELECTOR = "[data-composer-input]";
19069
+ /**
19070
+ * Hand the caret back to the chat composer. Closing the review panel is a "done
19071
+ * reviewing, back to typing" move; a close the user made by clicking elsewhere is
19072
+ * the exception, and that path does not call this.
19073
+ */
19074
+ function focusComposer() {
19075
+ document.querySelector(COMPOSER_INPUT_SELECTOR)?.focus();
19076
+ }
17977
19077
  /** Seat height ui-conversation publishes for floating controls (its own seat observer). */
17978
19078
  const COMPOSER_HEIGHT_VAR = "--dsh-composer-height";
17979
- /** Interactive composer/approval cards; clicking these keeps the panel open.
17980
- Deliberately the cards themselves, not the seat: the approval frame's wide
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\"]";
17986
19079
  /** Seat counts as docked when its bottom is this close to the window bottom. */
17987
19080
  const DOCKED_TOLERANCE_PX = 48;
17988
19081
  /** File-list pane width bounds for the manual split drag, in px. */
@@ -18090,10 +19183,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18090
19183
  /** Total width of the two line-number gutters, subtracted from the code width
18091
19184
  * when measuring wrapped line heights. */
18092
19185
  const WRAP_GUTTERS_PX = 88;
18093
- /** The dsh shell's sidebar auto-collapse breakpoint (ui-layout columns.ts):
18094
- * below it the sidebar auto-collapses, and the file list floats on the same
18095
- * breakpoint so the two stay consistent. */
18096
- const SIDEBAR_AUTO_COLLAPSE_PX = 1024;
19186
+ /** How long a requested dock may take to actually show the panel before the
19187
+ * overlay steps in. The tab body mounts a frame or two after the ask, so the
19188
+ * wait is short — but a sidebar that accepts the ask and never brings the tab up
19189
+ * must not swallow the click. */
19190
+ const DOCK_REVEAL_GRACE_MS = 400;
18097
19191
  /** A shared canvas for measuring wrapped line heights (CPU-only, no DOM reflow). */
18098
19192
  let measureCanvas;
18099
19193
  /**
@@ -18313,6 +19407,78 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18313
19407
  })
18314
19408
  });
18315
19409
  }
19410
+ /**
19411
+ * The app's view tabs above the conversation (对话 / 轨迹 and the like). A panel
19412
+ * that leaves the header visible still covers these: the session's title row is
19413
+ * the part worth keeping, and the tab strip sits directly under it, above the
19414
+ * conversation body — a sibling of the scroller's own parent. Nothing else's
19415
+ * tablists count: the right sidebar's own tab strip, and any inside this panel,
19416
+ * are excluded.
19417
+ * @param scroller - the conversation scroll body.
19418
+ * @returns the strip's top edge in viewport px, or undefined when there is none.
19419
+ */
19420
+ function viewTabsTop(scroller) {
19421
+ for (const strip of document.querySelectorAll("[role=\"tablist\"]")) {
19422
+ if (strip.closest("[data-sidebar-right-panel]") !== null) continue;
19423
+ if (strip.closest("[data-diff-approval-panel]") !== null) continue;
19424
+ if ((strip.compareDocumentPosition(scroller) & Node.DOCUMENT_POSITION_FOLLOWING) === 0) continue;
19425
+ const rect = strip.getBoundingClientRect();
19426
+ if (rect.height > 0) return rect.top;
19427
+ }
19428
+ }
19429
+ /**
19430
+ * How much of the window the app itself occupies on each side of the conversation
19431
+ * — how far the floating panel has to start from an edge to leave that part
19432
+ * visible.
19433
+ *
19434
+ * The centre column's own box is the direct answer: the header above it, an
19435
+ * expanded sidebar, a collapsed rail, and a right panel hanging over the centre
19436
+ * all sit outside it. The frame's grid tracks fill in the rest — they name the
19437
+ * sidebar columns even before a conversation is mounted, and a right sidebar shown
19438
+ * without a track of its own draws over the centre, so its own panel is measured
19439
+ * instead. Relying on the frame's *resizers* alone was a bug: a collapsed sidebar
19440
+ * renders no resizer, so a collapsed rail read as "nothing on that side".
19441
+ * @returns each side's occupied size, 0 when that side shows nothing.
19442
+ */
19443
+ function frameInsets() {
19444
+ if (typeof document === "undefined") return {
19445
+ top: 0,
19446
+ bottom: 0,
19447
+ left: 0,
19448
+ right: 0
19449
+ };
19450
+ const viewport = window.innerWidth;
19451
+ const viewportHeight = window.innerHeight;
19452
+ let top = 0;
19453
+ let bottom = 0;
19454
+ let left = 0;
19455
+ let right = 0;
19456
+ const scroller = document.querySelector("[data-conversation-scroll]");
19457
+ const centre = scroller?.getBoundingClientRect();
19458
+ if (scroller != null && centre !== void 0 && centre.width > 0 && centre.height > 0) {
19459
+ top = Math.max(top, viewTabsTop(scroller) ?? centre.top);
19460
+ bottom = Math.max(bottom, viewportHeight - centre.bottom);
19461
+ left = Math.max(left, centre.left);
19462
+ right = Math.max(right, viewport - centre.right);
19463
+ }
19464
+ const frame = document.querySelector("[data-side=\"sidebar\"],[data-side=\"rightbar\"]")?.parentElement ?? document.querySelector("[data-sidebar-collapsed],[data-rightbar-collapsed],[data-rightbar-fullscreen]");
19465
+ const frameRect = frame?.getBoundingClientRect();
19466
+ if (frame != null && frameRect !== void 0) {
19467
+ const tracks = getComputedStyle(frame).gridTemplateColumns.split(" ").map((part) => Number.parseFloat(part)).filter(Number.isFinite);
19468
+ if (tracks.length > 0) {
19469
+ left = Math.max(left, frameRect.left + (tracks[0] ?? 0));
19470
+ right = Math.max(right, tracks.length > 1 ? tracks[tracks.length - 1] ?? 0 : 0);
19471
+ }
19472
+ }
19473
+ const rightPanel = document.querySelector("[data-sidebar-right-panel]")?.getBoundingClientRect();
19474
+ if (rightPanel !== void 0 && rightPanel.width > 0) right = Math.max(right, viewport - rightPanel.left);
19475
+ return {
19476
+ top,
19477
+ bottom,
19478
+ left,
19479
+ right
19480
+ };
19481
+ }
18316
19482
  /** The diff body's row class per line kind. */
18317
19483
  const ROW_CLASS = {
18318
19484
  context: PendingPanel_module_css_default.context,
@@ -18424,26 +19590,6 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18424
19590
  if (cursor < segEnd) push(cursor, segEnd, false);
18425
19591
  return nodes;
18426
19592
  }
18427
- /** Arrow keys render as arrows in a tooltip hint (`Ctrl+↑`, not `Ctrl+ArrowUp`). */
18428
- const CHORD_KEY_GLYPHS = {
18429
- ArrowUp: "↑",
18430
- ArrowDown: "↓",
18431
- ArrowLeft: "←",
18432
- ArrowRight: "→"
18433
- };
18434
- /**
18435
- * A tooltip label with the action's configured chord appended. The hint states
18436
- * the binding the user actually has — including one rebound in Settings — rather
18437
- * than a default baked into the label.
18438
- * @param label - the translated action label.
18439
- * @param action - the keybinding action id (see `DEFAULT_KEYBINDINGS`).
18440
- * @returns the label, with ` (chord)` appended when one is configured.
18441
- */
18442
- function withChord(label, action) {
18443
- const chord = keybindingOf(action);
18444
- if (chord === "") return label;
18445
- return `${label} (${chord.split("+").map((part) => CHORD_KEY_GLYPHS[part] ?? part).join("+")})`;
18446
- }
18447
19593
  /**
18448
19594
  * Whether a key event's target is one of the panel's search query boxes. The
18449
19595
  * bar's own chords (step, narrow) are scoped this way: the caret has to be in
@@ -18481,6 +19627,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
18481
19627
  return target instanceof Element && target.closest("input, textarea, [contenteditable=\"true\"]") !== null;
18482
19628
  }
18483
19629
  /**
19630
+ * Whether a key event came from the chat composer. The coverage chords fire
19631
+ * there — the user is usually typing when the panel's edges need rearranging —
19632
+ * while every other text field (this panel's own search box, the add-path
19633
+ * dialog's input) keeps its Ctrl+Shift+Arrow for word-wise selection.
19634
+ * @param event - the keydown event.
19635
+ * @returns whether the event came from the composer.
19636
+ */
19637
+ function isComposerEvent(event) {
19638
+ const target = event.target;
19639
+ return target instanceof Element && target.closest("[data-composer-input], [data-composer-card]") !== null;
19640
+ }
19641
+ /**
18484
19642
  * The in-file search's narrowing toggles. The persisted preference is the
18485
19643
  * source of truth, so the choice survives a reopen and the two views cannot
18486
19644
  * disagree: each bar owns one of these, and it re-reads the stored flags with
@@ -19324,13 +20482,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19324
20482
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
19325
20483
  })
19326
20484
  }),
19327
- (0, react_jsx_runtime.jsx)("button", {
19328
- type: "button",
19329
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
19330
- "data-diff-search-close": true,
19331
- "aria-label": t("action.close"),
19332
- onClick: closeSearch,
19333
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
20485
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20486
+ label: t("action.closeHintEsc"),
20487
+ side: "bottom",
20488
+ delayMs: 500,
20489
+ children: (0, react_jsx_runtime.jsx)("button", {
20490
+ type: "button",
20491
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20492
+ "data-diff-search-close": true,
20493
+ "aria-label": t("action.close"),
20494
+ onClick: closeSearch,
20495
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
20496
+ })
19334
20497
  })
19335
20498
  ]
19336
20499
  }),
@@ -19633,7 +20796,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19633
20796
  });
19634
20797
  }
19635
20798
  /** The selected file's diff, actions, jump controls, and copy toolbar. */
19636
- function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, failedMessage, onPasteReference, onToast, t, onKeep, onRevert, onRefreshVcs, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, floatMode, floatOpen, onToggleFileList }) {
20799
+ function PendingDiff({ file, busy, workspacePath, jumpSignal, undoFlash, landingTop, landingTick, failedMessage, onPasteReference, onToast, t, onKeep, onRevert, onRefreshVcs, onBlockKeep, onBlockRevert, onOpen, onPreviewImage }) {
19637
20800
  const [langOverride, setLangOverride] = (0, react.useState)(void 0);
19638
20801
  const [langMenuOpen, setLangMenuOpen] = (0, react.useState)(false);
19639
20802
  const langMenuItems = (0, react.useMemo)(() => [{
@@ -19798,6 +20961,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19798
20961
  const [previewFlash, setPreviewFlash] = (0, react.useState)(void 0);
19799
20962
  const previewFlashRef = (0, react.useRef)(null);
19800
20963
  const previewFlashPendingRef = (0, react.useRef)(false);
20964
+ /** The offset the file now showing was asked to open at, held until the landing
20965
+ * effect below spends it (that effect runs a render later than the switch). */
20966
+ const landingTopRef = (0, react.useRef)(void 0);
19801
20967
  const selectionTextRef = (0, react.useRef)("");
19802
20968
  const [copied, setCopied] = (0, react.useState)(false);
19803
20969
  const [searchOpen, setSearchOpen] = (0, react.useState)(false);
@@ -19813,10 +20979,13 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19813
20979
  previewFlashPendingRef.current = true;
19814
20980
  };
19815
20981
  (0, react.useEffect)(() => {
19816
- setFocus(0);
20982
+ landingTopRef.current = landingTop;
20983
+ if (landingTop === void 0) {
20984
+ setFocus(0);
20985
+ bumpFlash(false);
20986
+ } else setFocus(blockIndexAtOffset(landingTop));
19817
20987
  setScrollTick((tick) => tick + 1);
19818
20988
  bodyRef.current?.focus();
19819
- bumpFlash(false);
19820
20989
  setHoveredBlock(void 0);
19821
20990
  setSelection(void 0);
19822
20991
  setLangOverride(void 0);
@@ -19825,7 +20994,11 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
19825
20994
  setSearchOpen(false);
19826
20995
  setSearchQuery("");
19827
20996
  setSearchIndex(0);
19828
- }, [file.id]);
20997
+ }, [
20998
+ file.id,
20999
+ landingTop,
21000
+ landingTick
21001
+ ]);
19829
21002
  (0, react.useEffect)(() => {
19830
21003
  if (undoFlash === 0) return;
19831
21004
  setFocus(0);
@@ -20426,6 +21599,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20426
21599
  ]);
20427
21600
  (0, react.useLayoutEffect)(() => {
20428
21601
  if (rowCount === 0) return;
21602
+ const resumed = landingTopRef.current;
21603
+ if (resumed !== void 0) {
21604
+ landingTopRef.current = void 0;
21605
+ if (!previewActive) {
21606
+ const resumeBody = bodyRef.current;
21607
+ if (resumeBody !== null) {
21608
+ const clamped = Math.max(0, Math.min(resumed, resumeBody.scrollHeight - resumeBody.clientHeight));
21609
+ if (resumeBody.scrollTop !== clamped) resumeBody.scrollTop = clamped;
21610
+ setScrollTop(clamped);
21611
+ }
21612
+ }
21613
+ return;
21614
+ }
20429
21615
  const block = model.blocks[focus];
20430
21616
  if (block === void 0) return;
20431
21617
  if (previewActive) {
@@ -20527,6 +21713,23 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20527
21713
  if (jumpSignal === 0) return;
20528
21714
  jumpBlock(1, true, false);
20529
21715
  }, [jumpSignal]);
21716
+ /**
21717
+ * The change block the view is in at one scroll offset: the last block at or
21718
+ * above the offset, i.e. the same anchor `onScroll` re-focuses from. Used when
21719
+ * a resumed view starts away from the first change, so prev/next walk from the
21720
+ * block the reader was actually in.
21721
+ * @param offset - a code view scrollTop.
21722
+ * @returns the block index; 0 when the offset is above every change.
21723
+ */
21724
+ const blockIndexAtOffset = (offset) => {
21725
+ const anchor = offset + leadRows * ROW_HEIGHT_PX;
21726
+ let found = 0;
21727
+ for (let index = 0; index < model.blocks.length; index++) {
21728
+ const block = model.blocks[index];
21729
+ if (block !== void 0 && offsetOf(block.start) <= anchor + NAV_ANCHOR_TOLERANCE_PX) found = index;
21730
+ }
21731
+ return found;
21732
+ };
20530
21733
  const onScroll = () => {
20531
21734
  const body = bodyRef.current;
20532
21735
  if (body === null) return;
@@ -20872,13 +22075,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20872
22075
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
20873
22076
  })
20874
22077
  }),
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 })
22078
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
22079
+ label: t("action.closeHintEsc"),
22080
+ side: "bottom",
22081
+ delayMs: 500,
22082
+ children: (0, react_jsx_runtime.jsx)("button", {
22083
+ type: "button",
22084
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
22085
+ "data-diff-search-close": true,
22086
+ "aria-label": t("action.close"),
22087
+ onClick: closeSearch,
22088
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
22089
+ })
20882
22090
  })
20883
22091
  ]
20884
22092
  }) : null;
@@ -20889,6 +22097,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20889
22097
  children: [
20890
22098
  (0, react_jsx_runtime.jsxs)("div", {
20891
22099
  className: PendingPanel_module_css_default.diffHeader,
22100
+ "data-diff-toolbar": true,
20892
22101
  children: [
20893
22102
  (0, react_jsx_runtime.jsx)("span", {
20894
22103
  className: PendingPanel_module_css_default.diffPath,
@@ -20929,19 +22138,6 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
20929
22138
  (0, react_jsx_runtime.jsxs)("div", {
20930
22139
  className: PendingPanel_module_css_default.diffActions,
20931
22140
  children: [
20932
- floatMode && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
20933
- label: t(floatOpen ? "action.hideFileList" : "action.showFileList"),
20934
- side: "bottom",
20935
- delayMs: 500,
20936
- children: (0, react_jsx_runtime.jsx)("button", {
20937
- type: "button",
20938
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
20939
- "data-diff-file-list-toggle": true,
20940
- "aria-label": t(floatOpen ? "action.hideFileList" : "action.showFileList"),
20941
- onClick: onToggleFileList,
20942
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: 14 })
20943
- })
20944
- }),
20945
22141
  (model.diff.added !== 0 || model.diff.removed !== 0) && (0, react_jsx_runtime.jsx)("span", {
20946
22142
  className: PendingPanel_module_css_default.diffStats,
20947
22143
  children: t("panel.stats", {
@@ -21477,16 +22673,75 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21477
22673
  });
21478
22674
  }
21479
22675
  /** Render the pending-edit review panel and its unified footer action. */
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 }) {
22676
+ function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onRefreshVcs, onBrowse, onAddPath, onKeepAll, onRevertAll, onAckRedoCleared, collapseSidebar, t, docked = false, dockHost, onOpenDock, closeDock, useDock }) {
21481
22677
  const current = useSessions((state) => state.current);
21482
22678
  const currentBlank = useSessions((state) => {
21483
22679
  const id = state.current;
21484
22680
  return id === void 0 ? false : state.byId[id]?.blank ?? false;
21485
22681
  });
21486
22682
  const noSession = current === void 0 || currentBlank;
22683
+ const dockShowing = useDock?.((state) => state.open) === true;
22684
+ const dockAvailable = useDock?.((state) => state.available) === true;
22685
+ /** Why the dock is unavailable, when it is (shown once, if asked for). */
22686
+ const dockReason = useDock?.((state) => state.reason);
22687
+ /** The dock's visibility as the latest render has it, for the reveal fallback's
22688
+ * timer (which runs outside this render's closure). */
22689
+ const dockShowingRef = (0, react.useRef)(false);
22690
+ dockShowingRef.current = dockShowing;
22691
+ /** The pending "did the dock actually come up" check, if any. */
22692
+ const revealTimerRef = (0, react.useRef)(void 0);
22693
+ (0, react.useEffect)(() => () => {
22694
+ if (revealTimerRef.current !== void 0) window.clearTimeout(revealTimerRef.current);
22695
+ }, []);
22696
+ const revealRef = (0, react.useRef)(() => {});
22697
+ /** The header entry's toggle, deferred to the latest render (see `toggleOpen`):
22698
+ * this instance owns the overlay's open state, and the entry is a second mount
22699
+ * that can only ask. */
22700
+ const toggleRef = (0, react.useRef)(() => {});
22701
+ (0, react.useEffect)(() => {
22702
+ if (docked) return;
22703
+ const onShow = () => {
22704
+ revealRef.current();
22705
+ };
22706
+ const onToggle = () => {
22707
+ toggleRef.current();
22708
+ };
22709
+ window.addEventListener(SHOW_PANEL_EVENT, onShow);
22710
+ window.addEventListener(TOGGLE_PANEL_EVENT, onToggle);
22711
+ return () => {
22712
+ window.removeEventListener(SHOW_PANEL_EVENT, onShow);
22713
+ window.removeEventListener(TOGGLE_PANEL_EVENT, onToggle);
22714
+ };
22715
+ }, [docked]);
22716
+ (0, react.useEffect)(() => {
22717
+ if (docked) setPanelPresentation("dock");
22718
+ }, [docked]);
21487
22719
  const snapshot = usePending((snapshot) => snapshot);
21488
22720
  const [open, setOpen] = (0, react.useState)(false);
21489
22721
  const [selected, setSelected] = (0, react.useState)("");
22722
+ /**
22723
+ * Where the diff should land for the file it is about to show, and a nonce so a
22724
+ * repeated request for the *same* file lands again. `top` carries the offset the
22725
+ * reader was left at when the panel resumes a view (see `panel-memory`); it is
22726
+ * absent when the request means "show me this diff" — a file picked from the
22727
+ * list, the advance a decision leaves behind, or the produced-file chip — and
22728
+ * that lands on the file's first change.
22729
+ */
22730
+ const [landing, setLanding] = (0, react.useState)(void 0);
22731
+ /** Ask the diff to land on one file: its first change unless `top` says where.
22732
+ * The nonce makes the request an event rather than a value, so re-clicking the
22733
+ * chip for the file already open lands on its first change again. */
22734
+ const landOn = (fileId, top) => {
22735
+ setLanding((prev) => ({
22736
+ fileId,
22737
+ top,
22738
+ n: (prev?.n ?? 0) + 1
22739
+ }));
22740
+ };
22741
+ /** The selection as the latest render has it, for the closers that run from a
22742
+ * cleanup (the docked tab unmounting) rather than from a handler. */
22743
+ const selectedRef = (0, react.useRef)(selected);
22744
+ selectedRef.current = selected;
21490
22745
  /** Bumped when the already-open file is clicked again, to jump to the next
21491
22746
  * diff block in the open file's detail pane. */
21492
22747
  const [jumpSignal, setJumpSignal] = (0, react.useState)(0);
@@ -21520,8 +22775,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21520
22775
  showCopyToast(t("panel.fileNotPending"));
21521
22776
  return;
21522
22777
  }
21523
- setOpen(true);
21524
- setSelected(entry.id);
22778
+ revealPanel();
22779
+ rememberPanelView(current, { fileId: entry.id });
22780
+ window.dispatchEvent(new CustomEvent(OPEN_PANEL_FILE_EVENT, { detail: { fileId: entry.id } }));
21525
22781
  };
21526
22782
  (0, react.useEffect)(() => {
21527
22783
  const onOpenFile = (event) => {
@@ -21534,6 +22790,18 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21534
22790
  window.removeEventListener(OPEN_FILE_EVENT, onOpenFile);
21535
22791
  };
21536
22792
  }, []);
22793
+ (0, react.useEffect)(() => {
22794
+ const onPanelFile = (event) => {
22795
+ const detail = event.detail;
22796
+ if (detail === void 0 || typeof detail.fileId !== "string") return;
22797
+ setSelected(detail.fileId);
22798
+ landOn(detail.fileId);
22799
+ };
22800
+ window.addEventListener(OPEN_PANEL_FILE_EVENT, onPanelFile);
22801
+ return () => {
22802
+ window.removeEventListener(OPEN_PANEL_FILE_EVENT, onPanelFile);
22803
+ };
22804
+ }, []);
21537
22805
  /** Whether the redo-cleared notice is showing (bottom-right, OK to dismiss). */
21538
22806
  const [redoClearedNotice, setRedoClearedNotice] = (0, react.useState)(false);
21539
22807
  /** A last-block keep/revert awaiting the user's remove-or-keep choice; the
@@ -21545,17 +22813,34 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21545
22813
  const [addOpen, setAddOpen] = (0, react.useState)(false);
21546
22814
  /** Bottom offset tracking the chat composer's top edge so the input stays visible. */
21547
22815
  const [bottomPx, setBottomPx] = (0, react.useState)(FALLBACK_BOTTOM_PX);
21548
- /** Fullscreen expanded: the panel bottom pins to the window edge, ignoring the composer offset. */
21549
- const [expanded, setExpanded] = (0, react.useState)(false);
22816
+ /** The app frame's sidebar columns, in px: how far the floating panel has to
22817
+ * start from each window edge to leave that sidebar visible. */
22818
+ /** How much of the window the app occupies on each side of the conversation:
22819
+ * how far the floating panel starts from an edge it does not cover. */
22820
+ const [sideInset, setSideInset] = (0, react.useState)({
22821
+ top: 0,
22822
+ bottom: 0,
22823
+ left: 0,
22824
+ right: 0
22825
+ });
22826
+ /** What the floating panel covers: the app's two sidebars and the composer. */
22827
+ const [cover, setCover] = (0, react.useState)(() => panelCover());
22828
+ /** The chord's on-screen echo: the edge just flipped, and a nonce so a repeat
22829
+ * restarts the notice instead of re-rendering the same one. */
22830
+ const [coverNotice, setCoverNotice] = (0, react.useState)(null);
21550
22831
  /** File-list pane width, adjustable by dragging the divider. */
21551
22832
  const [listWidth, setListWidth] = (0, react.useState)(240);
21552
22833
  const resizeDrag = (0, react.useRef)(null);
21553
- /** Whether the floating (collapsed) file list is currently expanded. */
22834
+ /** Whether the floating (collapsed) file list is currently shown. */
21554
22835
  const [floatOpen, setFloatOpen] = (0, react.useState)(false);
22836
+ /** Whether the file list is always folded, whatever the width allows. */
22837
+ const [forceFloat, setForceFloat] = (0, react.useState)(() => fileListFloat());
21555
22838
  /** The review panel's width, measured so the file list can collapse when it
21556
22839
  * would take more than a third of it (browser zoom / window resize). */
21557
22840
  const [panelWidth, setPanelWidth] = (0, react.useState)(0);
21558
22841
  const [viewportWidth, setViewportWidth] = (0, react.useState)(() => window.innerWidth);
22842
+ /** The window's height, for the panel's minimum-size floor (see {@link panelInsets}). */
22843
+ const [viewportHeight, setViewportHeight] = (0, react.useState)(() => window.innerHeight);
21559
22844
  const panelRef = (0, react.useRef)(null);
21560
22845
  const splitRef = (0, react.useRef)(null);
21561
22846
  /** The code scroll box's bounds within the split, so the floating card is
@@ -21582,14 +22867,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21582
22867
  return () => {
21583
22868
  observer?.disconnect();
21584
22869
  };
21585
- }, [open]);
21586
- const floatMode = viewportWidth < SIDEBAR_AUTO_COLLAPSE_PX;
22870
+ }, [open, docked]);
22871
+ const floatMode = forceFloat || (docked ? panelWidth > 0 && panelWidth < DOCK_TWO_COLUMN_MIN_PX : viewportWidth < 1024);
21587
22872
  const toggleFileList = () => {
21588
22873
  setFloatOpen((value) => !value);
21589
22874
  };
22875
+ /** Flip the always-fold preference, remembered for the next open. */
22876
+ const toggleForceFloat = () => {
22877
+ const next = !forceFloat;
22878
+ setForceFloat(next);
22879
+ setFileListFloat(next);
22880
+ if (next) setFloatOpen(false);
22881
+ };
21590
22882
  (0, react.useEffect)(() => {
21591
22883
  const onResize = () => {
21592
22884
  setViewportWidth(window.innerWidth);
22885
+ setViewportHeight(window.innerHeight);
21593
22886
  };
21594
22887
  window.addEventListener("resize", onResize);
21595
22888
  return () => {
@@ -21610,23 +22903,50 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21610
22903
  el.removeEventListener("pointerdown", onPointerDown, true);
21611
22904
  };
21612
22905
  }, [floatMode, floatOpen]);
21613
- (0, react.useEffect)(() => {
21614
- if (!floatMode || !floatOpen) return;
22906
+ const measureFloatBox = () => {
22907
+ if (!floatMode) return;
22908
+ const panel = panelRef.current;
21615
22909
  const split = splitRef.current;
21616
- const body = panelRef.current?.querySelector("[data-diff-body],[data-diff-md-preview-body]");
21617
- if (split === null || body == null) return;
22910
+ if (panel === null || split === null) return;
22911
+ const body = panel.querySelector("[data-diff-body],[data-diff-md-preview-body]") ?? panel.querySelector("[data-diff-detail]");
22912
+ if (body === null) return;
21618
22913
  const s = split.getBoundingClientRect();
21619
22914
  const b = body.getBoundingClientRect();
21620
- setFloatBox({
22915
+ setFloatBox((current) => current !== null && current.left === b.left - s.left && current.top === b.top - s.top && current.width === b.width && current.height === b.height ? current : {
21621
22916
  left: b.left - s.left,
21622
22917
  top: b.top - s.top,
21623
22918
  width: b.width,
21624
22919
  height: b.height
21625
22920
  });
22921
+ };
22922
+ const measureFloatBoxRef = (0, react.useRef)(measureFloatBox);
22923
+ measureFloatBoxRef.current = measureFloatBox;
22924
+ (0, react.useLayoutEffect)(() => {
22925
+ measureFloatBoxRef.current();
22926
+ });
22927
+ (0, react.useEffect)(() => {
22928
+ if (!floatMode) return;
22929
+ const split = splitRef.current;
22930
+ if (split === null) return;
22931
+ const measure = () => {
22932
+ measureFloatBoxRef.current();
22933
+ };
22934
+ const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
22935
+ resize?.observe(split);
22936
+ const mutations = typeof MutationObserver === "undefined" ? null : new MutationObserver(measure);
22937
+ mutations?.observe(split, {
22938
+ childList: true,
22939
+ subtree: true
22940
+ });
22941
+ return () => {
22942
+ resize?.disconnect();
22943
+ mutations?.disconnect();
22944
+ };
21626
22945
  }, [
21627
22946
  floatMode,
21628
22947
  floatOpen,
21629
- panelWidth
22948
+ panelWidth,
22949
+ selected
21630
22950
  ]);
21631
22951
  (0, react.useEffect)(() => {
21632
22952
  if (!open || !snapshot.redoCleared) return;
@@ -21641,6 +22961,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21641
22961
  if (!open) return;
21642
22962
  const MEASURE_INTERVAL_MS = 400;
21643
22963
  const measure = () => {
22964
+ setSideInset(frameInsets());
21644
22965
  const scrollers = document.querySelectorAll(SCROLL_SELECTOR);
21645
22966
  for (const scroller of scrollers) {
21646
22967
  const seat = scroller.querySelector(SEAT_SELECTOR);
@@ -21666,20 +22987,6 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21666
22987
  window.removeEventListener("resize", measure);
21667
22988
  };
21668
22989
  }, [open]);
21669
- (0, react.useEffect)(() => {
21670
- if (!open) return;
21671
- const onPointerDown = (event) => {
21672
- const target = event.target;
21673
- if (target === null) return;
21674
- const element = target instanceof Element ? target : target.parentElement;
21675
- if (element !== null && (element.closest("[data-diff-approval-panel]") !== null || element.closest("[data-diff-approval-badge]") !== null || element.closest(KEEP_OPEN_SELECTOR) !== null)) return;
21676
- setOpen(false);
21677
- };
21678
- document.addEventListener("pointerdown", onPointerDown, true);
21679
- return () => {
21680
- document.removeEventListener("pointerdown", onPointerDown, true);
21681
- };
21682
- }, [open]);
21683
22990
  const files = snapshot.files.filter((file) => current !== void 0 && (file.sessionIds ?? [file.sessionId]).includes(current)).sort((left, right) => compareFileNames(left.path, right.path));
21684
22991
  const blockKeepWithPrompt = (sessionId, id, block, removeWhenResolved) => {
21685
22992
  const file = files.find((entry) => entry.id === id);
@@ -21745,13 +23052,23 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21745
23052
  if (fresh.length > 0) setActionToast(fresh[0][1]);
21746
23053
  failedRef.current = current;
21747
23054
  }, [snapshot.failed]);
21748
- (0, react.useEffect)(() => {
21749
- if (!open) return;
21750
- if (selected !== "" && files.some((file) => file.id === selected)) return;
21751
- const next = files[0];
21752
- if (next !== void 0 && next.id !== selected) setSelected(next.id);
23055
+ const wasShowingRef = (0, react.useRef)(false);
23056
+ (0, react.useLayoutEffect)(() => {
23057
+ const showing = open || docked;
23058
+ const started = showing && !wasShowingRef.current;
23059
+ wasShowingRef.current = showing;
23060
+ if (!showing) return;
23061
+ const pending = (id) => files.some((file) => file.id === id);
23062
+ const keep = selected !== "" && pending(selected) ? selected : void 0;
23063
+ const remembered = keep === void 0 && started ? lastPanelFile(current) : void 0;
23064
+ const resumed = remembered !== void 0 && pending(remembered) ? remembered : void 0;
23065
+ const pick = keep ?? resumed ?? files[0]?.id;
23066
+ if (pick === void 0) return;
23067
+ if (pick !== selected) setSelected(pick);
23068
+ if (started) landOn(pick, panelFileOffset(current, pick));
21753
23069
  }, [
21754
23070
  open,
23071
+ docked,
21755
23072
  current,
21756
23073
  files,
21757
23074
  selected
@@ -21774,10 +23091,166 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21774
23091
  setImportBusy(false);
21775
23092
  }
21776
23093
  };
23094
+ /**
23095
+ * Show the panel the way the user last had it. `dock` hands off to the app's
23096
+ * right sidebar, where the panel lives in its own tab; `float` opens this
23097
+ * floating panel, covering whatever {@link panelCover} says.
23098
+ *
23099
+ * A remembered dock is only taken when the dock can actually show the panel.
23100
+ * "dock" is remembered from a page where the right sidebar existed, and the ask
23101
+ * itself is not proof: `onOpenDock` is always wired, and a sidebar whose
23102
+ * services never came up (or a controller that has since gone stale) accepts the
23103
+ * call and shows nothing. That used to swallow the click whole — the badge, the
23104
+ * header entry, the chord and the produced-file chips all did nothing, and the
23105
+ * only switch back to floating lives *inside* the panel, so the page had to be
23106
+ * reloaded to reach the review again. Now the overlay steps in whenever the tab
23107
+ * does not come up.
23108
+ */
23109
+ const revealPanel = () => {
23110
+ if (panelPresentation() === "dock" && onOpenDock !== void 0 && dockAvailable) {
23111
+ try {
23112
+ onOpenDock();
23113
+ } catch {
23114
+ collapseSidebar();
23115
+ setOpen(true);
23116
+ return;
23117
+ }
23118
+ if (revealTimerRef.current !== void 0) window.clearTimeout(revealTimerRef.current);
23119
+ revealTimerRef.current = window.setTimeout(() => {
23120
+ revealTimerRef.current = void 0;
23121
+ if (dockShowingRef.current) return;
23122
+ collapseSidebar();
23123
+ setOpen(true);
23124
+ }, DOCK_REVEAL_GRACE_MS);
23125
+ return;
23126
+ }
23127
+ collapseSidebar();
23128
+ setOpen(true);
23129
+ };
23130
+ revealRef.current = revealPanel;
23131
+ const wasDockShowingRef = (0, react.useRef)(false);
23132
+ (0, react.useEffect)(() => {
23133
+ const was = wasDockShowingRef.current;
23134
+ wasDockShowingRef.current = dockShowing;
23135
+ if (docked) return;
23136
+ if (!was && dockShowing && open) setOpen(false);
23137
+ }, [
23138
+ docked,
23139
+ dockShowing,
23140
+ open
23141
+ ]);
23142
+ /** Move the panel into the right sidebar's tab: the tab is opened and this
23143
+ * overlay steps aside, with the presentation remembered for the entry. The
23144
+ * sidebar's controller throws when it is mounted but cannot act on a session
23145
+ * yet (no seat bound), so a failure is said out loud rather than swallowed. */
23146
+ const dockPanel = () => {
23147
+ if (onOpenDock === void 0) return;
23148
+ try {
23149
+ onOpenDock();
23150
+ } catch (error) {
23151
+ showCopyToast(`${t("panel.dockFailed")} (${error instanceof Error ? error.message : String(error)})`);
23152
+ return;
23153
+ }
23154
+ setPanelPresentation("dock");
23155
+ rememberView();
23156
+ setOpen(false);
23157
+ };
23158
+ /** Where the panel is showing right now: the mode switch's checked row. */
23159
+ const presentation = docked ? "dock" : "float";
23160
+ /**
23161
+ * Move the panel to the chosen presentation. Leaving the dock is not this
23162
+ * component's to do: a docked panel draws no header (its tab chip carries the
23163
+ * switch), and the chip closes the tab and hands the panel back to the footer
23164
+ * entry itself.
23165
+ */
23166
+ const choosePresentation = (next) => {
23167
+ if (next === "dock") {
23168
+ if (docked) return;
23169
+ if (!dockAvailable) {
23170
+ showCopyToast(`${t("panel.dockUnavailable")}${dockReason === void 0 ? "" : ` (${dockReason})`}`);
23171
+ return;
23172
+ }
23173
+ dockPanel();
23174
+ return;
23175
+ }
23176
+ if (docked) return;
23177
+ setPanelPresentation("float");
23178
+ };
23179
+ /** Flip one coverage switch, remembered for the next open. */
23180
+ const toggleCover = (key) => {
23181
+ const next = {
23182
+ ...cover,
23183
+ [key]: !cover[key]
23184
+ };
23185
+ setCover(next);
23186
+ setPanelCover(next);
23187
+ };
23188
+ (0, react.useEffect)(() => {
23189
+ if (coverNotice === null) return;
23190
+ const timer = window.setTimeout(() => {
23191
+ setCoverNotice(null);
23192
+ }, COVER_NOTICE_MS);
23193
+ return () => {
23194
+ window.clearTimeout(timer);
23195
+ };
23196
+ }, [coverNotice]);
23197
+ (0, react.useEffect)(() => {
23198
+ const onCover = () => {
23199
+ setCover(panelCover());
23200
+ };
23201
+ window.addEventListener(COVER_CHANGED_EVENT, onCover);
23202
+ return () => {
23203
+ window.removeEventListener(COVER_CHANGED_EVENT, onCover);
23204
+ };
23205
+ }, []);
23206
+ /**
23207
+ * Remember what this panel is showing, so the next open resumes it: the file
23208
+ * that is open and how far down it the code view is. Called on every way out —
23209
+ * the overlay's close, the hand-off to the dock, and the docked tab unmounting.
23210
+ * With no code view (the Markdown preview is showing) the file's offset is kept
23211
+ * as it was: the file is what the reader comes back to.
23212
+ */
23213
+ const rememberView = () => {
23214
+ const id = selectedRef.current;
23215
+ if (id === "" || current === void 0) return;
23216
+ const body = panelRef.current?.querySelector("[data-diff-body]");
23217
+ const previous = panelFileOffset(current, id);
23218
+ rememberPanelView(current, {
23219
+ fileId: id,
23220
+ scrollTop: body?.scrollTop ?? previous ?? 0
23221
+ });
23222
+ };
23223
+ /** The latest `rememberView`, for the docked instance's unmount cleanup: that
23224
+ * closer runs outside this render's closure. */
23225
+ const rememberViewRef = (0, react.useRef)(rememberView);
23226
+ rememberViewRef.current = rememberView;
23227
+ (0, react.useLayoutEffect)(() => {
23228
+ if (!docked) return;
23229
+ return () => {
23230
+ rememberViewRef.current();
23231
+ };
23232
+ }, [docked]);
23233
+ /**
23234
+ * Close the floating panel and hand the caret back to the chat composer:
23235
+ * closing the review is a "done reviewing, back to typing" move. A close the
23236
+ * user made by clicking somewhere else is the exception — that press is an
23237
+ * instruction to put the caret where they clicked, so the outside-click path
23238
+ * closes without touching focus.
23239
+ */
23240
+ const closePanel = () => {
23241
+ rememberView();
23242
+ setOpen(false);
23243
+ focusComposer();
23244
+ };
21777
23245
  const toggleOpen = () => {
21778
- if (!open) collapseSidebar();
21779
- setOpen((value) => !value);
23246
+ if (!open) revealPanel();
23247
+ else closePanel();
21780
23248
  };
23249
+ toggleRef.current = toggleOpen;
23250
+ (0, react.useEffect)(() => {
23251
+ if (docked) return;
23252
+ window.dispatchEvent(new CustomEvent(PANEL_STATE_EVENT, { detail: { open } }));
23253
+ }, [docked, open]);
21781
23254
  (0, react.useEffect)(() => {
21782
23255
  if (noSession) setOpen(false);
21783
23256
  }, [noSession]);
@@ -21839,7 +23312,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21839
23312
  t,
21840
23313
  onSelect: (id) => {
21841
23314
  if (id === selected) setJumpSignal((signal) => signal + 1);
21842
- else setSelected(id);
23315
+ else {
23316
+ setSelected(id);
23317
+ landOn(id);
23318
+ }
21843
23319
  }
21844
23320
  }, entry.id);
21845
23321
  const selectedFile = files.find((file) => file.id === selected);
@@ -21870,6 +23346,21 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21870
23346
  },
21871
23347
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 12 })
21872
23348
  })
23349
+ }),
23350
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
23351
+ label: t(forceFloat ? "action.fileListFloatOff" : "action.fileListFloatOn"),
23352
+ side: "bottom",
23353
+ delayMs: 500,
23354
+ children: (0, react_jsx_runtime.jsx)("button", {
23355
+ type: "button",
23356
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.addButton}`,
23357
+ "data-diff-file-list-float": true,
23358
+ "data-active": forceFloat ? "" : void 0,
23359
+ "aria-label": t(forceFloat ? "action.fileListFloatOff" : "action.fileListFloatOn"),
23360
+ "aria-pressed": forceFloat,
23361
+ onClick: toggleForceFloat,
23362
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPanelLeftOutline16, { size: 12 })
23363
+ })
21873
23364
  })
21874
23365
  ]
21875
23366
  }),
@@ -21908,13 +23399,19 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21908
23399
  const id = await onUndo(sessionId);
21909
23400
  if (id === void 0) return;
21910
23401
  if (id === selected) setUndoFlash((signal) => signal + 1);
21911
- else setSelected(id);
23402
+ else {
23403
+ setSelected(id);
23404
+ landOn(id);
23405
+ }
21912
23406
  };
21913
23407
  const handleRedo = async (sessionId) => {
21914
23408
  const id = await onRedo(sessionId);
21915
23409
  if (id === void 0) return;
21916
23410
  if (id === selected) setUndoFlash((signal) => signal + 1);
21917
- else setSelected(id);
23411
+ else {
23412
+ setSelected(id);
23413
+ landOn(id);
23414
+ }
21918
23415
  };
21919
23416
  (0, react.useEffect)(() => {
21920
23417
  if (!open || current === void 0) return;
@@ -21943,6 +23440,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21943
23440
  handleUndo,
21944
23441
  handleRedo
21945
23442
  ]);
23443
+ (0, react.useEffect)(() => {
23444
+ if (!open || docked) return;
23445
+ const onKeyDown = (event) => {
23446
+ const action = COVER_ACTIONS.find(([name]) => matchesShortcut(event, keybindingOf(name)));
23447
+ if (action === void 0) return;
23448
+ if (pathPickerOpen()) return;
23449
+ if (isTextFieldEvent(event) && !isComposerEvent(event)) return;
23450
+ event.preventDefault();
23451
+ const edge = action[1];
23452
+ toggleCover(edge);
23453
+ setCoverNotice({
23454
+ edge,
23455
+ n: (coverNotice?.n ?? 0) + 1
23456
+ });
23457
+ };
23458
+ window.addEventListener("keydown", onKeyDown, true);
23459
+ return () => {
23460
+ window.removeEventListener("keydown", onKeyDown, true);
23461
+ };
23462
+ }, [
23463
+ open,
23464
+ docked,
23465
+ cover,
23466
+ coverNotice
23467
+ ]);
21946
23468
  (0, react.useEffect)(() => {
21947
23469
  if (!open || current === void 0) return;
21948
23470
  const onKeyDown = (event) => {
@@ -21957,7 +23479,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21957
23479
  event.preventDefault();
21958
23480
  const index = files.findIndex((file) => file.id === selected);
21959
23481
  const next = files[(index + direction + files.length) % files.length];
21960
- if (next !== void 0) setSelected(next.id);
23482
+ if (next !== void 0) {
23483
+ setSelected(next.id);
23484
+ landOn(next.id);
23485
+ }
21961
23486
  };
21962
23487
  window.addEventListener("keydown", onKeyDown, true);
21963
23488
  return () => {
@@ -21970,37 +23495,51 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
21970
23495
  selected
21971
23496
  ]);
21972
23497
  (0, react.useEffect)(() => {
23498
+ if (docked) return;
21973
23499
  const onKeyDown = (event) => {
21974
23500
  if (!matchesShortcut(event, quickSummonKey())) return;
21975
23501
  if (pathPickerOpen()) return;
21976
23502
  event.preventDefault();
21977
- if (!open) collapseSidebar();
21978
- setOpen((value) => !value);
23503
+ if (dockShowing) {
23504
+ closeDock?.();
23505
+ focusComposer();
23506
+ return;
23507
+ }
23508
+ if (open) closePanel();
23509
+ else revealPanel();
21979
23510
  };
21980
23511
  window.addEventListener("keydown", onKeyDown, true);
21981
23512
  return () => {
21982
23513
  window.removeEventListener("keydown", onKeyDown, true);
21983
23514
  };
21984
- }, [open, collapseSidebar]);
23515
+ }, [
23516
+ docked,
23517
+ open,
23518
+ dockShowing,
23519
+ closeDock,
23520
+ collapseSidebar
23521
+ ]);
21985
23522
  (0, react.useEffect)(() => {
21986
- if (!open) return;
23523
+ if (!open || docked) return;
21987
23524
  const onKeyDown = (event) => {
21988
23525
  if (event.key !== "Escape") return;
21989
23526
  if (pathPickerOpen()) return;
23527
+ if (document.querySelector("[data-diff-approval-cover-popover]") !== null) return;
21990
23528
  const target = event.target;
21991
23529
  if (target instanceof Node && panelRef.current?.contains(target) === true && document.querySelector("[data-diff-searchbar]") !== null) return;
21992
23530
  event.preventDefault();
21993
- setOpen(false);
23531
+ closePanel();
21994
23532
  };
21995
23533
  window.addEventListener("keydown", onKeyDown, true);
21996
23534
  return () => {
21997
23535
  window.removeEventListener("keydown", onKeyDown, true);
21998
23536
  };
21999
- }, [open]);
23537
+ }, [open, docked]);
22000
23538
  /** Drag the list/detail divider; width follows the pointer within its bounds. */
22001
23539
  const startResize = (event) => {
22002
23540
  if (event.button !== 0) return;
22003
23541
  event.preventDefault();
23542
+ const cap = floatMode && floatBox !== null ? Math.max(MIN_LIST_WIDTH_PX, floatBox.width - 24) : MAX_LIST_WIDTH_PX;
22004
23543
  resizeDrag.current = {
22005
23544
  startX: event.clientX,
22006
23545
  startWidth: listWidth
@@ -22011,7 +23550,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22011
23550
  const start = resizeDrag.current;
22012
23551
  if (start === null) return;
22013
23552
  const next = start.startWidth + (move.clientX - start.startX);
22014
- setListWidth(Math.min(Math.max(next, MIN_LIST_WIDTH_PX), MAX_LIST_WIDTH_PX));
23553
+ setListWidth(Math.min(Math.max(next, MIN_LIST_WIDTH_PX), cap));
22015
23554
  };
22016
23555
  const onUp = () => {
22017
23556
  resizeDrag.current = null;
@@ -22023,293 +23562,318 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22023
23562
  window.addEventListener("mousemove", onMove);
22024
23563
  window.addEventListener("mouseup", onUp);
22025
23564
  };
22026
- return (0, react_jsx_runtime.jsxs)("div", {
22027
- className: wide ? PendingPanel_module_css_default.layer : `${PendingPanel_module_css_default.layer} ${PendingPanel_module_css_default.rail}`,
22028
- children: [
22029
- importToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
22030
- text: importToast,
22031
- onDone: () => {
22032
- setImportToast(null);
22033
- }
22034
- }),
22035
- actionToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
22036
- text: actionToast,
22037
- onDone: () => {
22038
- setActionToast(null);
22039
- }
23565
+ const content = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
23566
+ importToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
23567
+ text: importToast,
23568
+ onDone: () => {
23569
+ setImportToast(null);
23570
+ }
23571
+ }),
23572
+ actionToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
23573
+ text: actionToast,
23574
+ onDone: () => {
23575
+ setActionToast(null);
23576
+ }
23577
+ }),
23578
+ copyToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
23579
+ text: copyToast.text,
23580
+ onDone: () => {
23581
+ setCopyToast(null);
23582
+ }
23583
+ }, copyToast.n),
23584
+ coverNotice !== null && (0, react_jsx_runtime.jsx)(CoverageNotice, {
23585
+ t,
23586
+ cover,
23587
+ changed: coverNotice.edge
23588
+ }, coverNotice.n),
23589
+ (docked || open) && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!docked && cover.top && cover.left && cover.right && cover.composer && (0, react_jsx_runtime.jsx)("div", {
23590
+ className: PendingPanel_module_css_default.coverBackdrop,
23591
+ "data-diff-cover-backdrop": true
23592
+ }), (0, react_jsx_runtime.jsxs)("section", {
23593
+ className: docked ? `${PendingPanel_module_css_default.panel} ${PendingPanel_module_css_default.panelDocked}` : PendingPanel_module_css_default.panel,
23594
+ ref: panelRef,
23595
+ style: docked ? void 0 : panelInsets(cover, sideInset, bottomPx, {
23596
+ width: viewportWidth,
23597
+ height: viewportHeight
22040
23598
  }),
22041
- copyToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
22042
- text: copyToast.text,
22043
- onDone: () => {
22044
- setCopyToast(null);
22045
- }
22046
- }, copyToast.n),
22047
- open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [expanded && (0, react_jsx_runtime.jsx)("div", {
22048
- className: PendingPanel_module_css_default.fullscreenBackdrop,
22049
- "data-diff-fullscreen-backdrop": true
22050
- }), (0, react_jsx_runtime.jsxs)("section", {
22051
- className: PendingPanel_module_css_default.panel,
22052
- ref: panelRef,
22053
- style: { bottom: expanded ? PANEL_INSET_PX : bottomPx },
22054
- "data-diff-approval-panel": true,
22055
- "aria-label": t("panel.title"),
22056
- children: [
22057
- (0, react_jsx_runtime.jsxs)("header", {
22058
- className: PendingPanel_module_css_default.header,
22059
- children: [(0, react_jsx_runtime.jsx)("span", {
22060
- className: PendingPanel_module_css_default.title,
22061
- children: t("panel.title")
22062
- }), (0, react_jsx_runtime.jsxs)("div", {
22063
- className: PendingPanel_module_css_default.headerActions,
23599
+ "data-diff-approval-panel": true,
23600
+ "data-diff-docked": docked ? "" : void 0,
23601
+ "aria-label": t("panel.title"),
23602
+ children: [
23603
+ !docked && (0, react_jsx_runtime.jsxs)("header", {
23604
+ className: PendingPanel_module_css_default.header,
23605
+ children: [(0, react_jsx_runtime.jsx)("span", {
23606
+ className: PendingPanel_module_css_default.title,
23607
+ children: t("panel.title")
23608
+ }), (0, react_jsx_runtime.jsxs)("div", {
23609
+ className: PendingPanel_module_css_default.headerActions,
23610
+ children: [
23611
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
23612
+ label: t("action.settings"),
23613
+ side: "bottom",
23614
+ delayMs: 500,
23615
+ children: (0, react_jsx_runtime.jsx)("button", {
23616
+ type: "button",
23617
+ className: PendingPanel_module_css_default.expand,
23618
+ "data-diff-approval-settings": true,
23619
+ "aria-label": t("action.settings"),
23620
+ onClick: () => {
23621
+ if (!docked) setOpen(false);
23622
+ openSettingsSection(t("settings.tabLabel"));
23623
+ },
23624
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 14 })
23625
+ })
23626
+ }),
23627
+ !docked && (0, react_jsx_runtime.jsx)(CoverageControl, {
23628
+ t,
23629
+ cover,
23630
+ onToggle: toggleCover
23631
+ }),
23632
+ (0, react_jsx_runtime.jsx)(PresentationMenu, {
23633
+ t,
23634
+ current: presentation,
23635
+ onChoose: choosePresentation
23636
+ }),
23637
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
23638
+ label: closeHint(t),
23639
+ side: "bottom",
23640
+ delayMs: 500,
23641
+ children: (0, react_jsx_runtime.jsx)("button", {
23642
+ type: "button",
23643
+ className: PendingPanel_module_css_default.close,
23644
+ "data-diff-approval-close": true,
23645
+ "aria-label": t("action.close"),
23646
+ onClick: closePanel,
23647
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
23648
+ })
23649
+ })
23650
+ ]
23651
+ })]
23652
+ }),
23653
+ snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
23654
+ className: PendingPanel_module_css_default.states,
23655
+ children: [
23656
+ snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
23657
+ className: PendingPanel_module_css_default.readError,
23658
+ role: "alert",
23659
+ children: t("panel.readFailed", { message: snapshot.error })
23660
+ }),
23661
+ !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
23662
+ className: PendingPanel_module_css_default.note,
23663
+ children: t("panel.loading")
23664
+ }),
23665
+ snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsxs)("div", {
23666
+ className: PendingPanel_module_css_default.emptyState,
22064
23667
  children: [
22065
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
22066
- label: t("action.settings"),
22067
- side: "bottom",
22068
- delayMs: 500,
22069
- children: (0, react_jsx_runtime.jsx)("button", {
22070
- type: "button",
22071
- className: PendingPanel_module_css_default.expand,
22072
- "data-diff-approval-settings": true,
22073
- "aria-label": t("action.settings"),
22074
- onClick: () => {
22075
- setOpen(false);
22076
- openSettingsSection(t("settings.tabLabel"));
22077
- },
22078
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 14 })
22079
- })
23668
+ (0, react_jsx_runtime.jsx)("p", {
23669
+ className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
23670
+ children: t("panel.empty")
22080
23671
  }),
22081
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
22082
- label: t(expanded ? "action.exitFullscreen" : "action.expand"),
22083
- side: "bottom",
22084
- delayMs: 500,
22085
- children: (0, react_jsx_runtime.jsx)("button", {
23672
+ (0, react_jsx_runtime.jsxs)("div", {
23673
+ className: PendingPanel_module_css_default.emptyActions,
23674
+ children: [(0, react_jsx_runtime.jsx)("button", {
22086
23675
  type: "button",
22087
- className: expanded ? `${PendingPanel_module_css_default.expand} ${PendingPanel_module_css_default.expandExpanded}` : PendingPanel_module_css_default.expand,
22088
- "data-diff-approval-expand": true,
22089
- "aria-label": t(expanded ? "action.exitFullscreen" : "action.expand"),
23676
+ className: PendingPanel_module_css_default.importButton,
23677
+ "data-diff-import-vcs": true,
23678
+ disabled: importBusy,
22090
23679
  onClick: () => {
22091
- if (!expanded) collapseSidebar();
22092
- setExpanded((value) => !value);
23680
+ runImportVcs();
22093
23681
  },
22094
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { size: 14 })
22095
- })
22096
- }),
22097
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
22098
- label: t("action.close"),
22099
- side: "bottom",
22100
- delayMs: 500,
22101
- children: (0, react_jsx_runtime.jsx)("button", {
23682
+ children: importBusy ? t("action.importVcsBusy") : t("action.importVcs")
23683
+ }), (0, react_jsx_runtime.jsx)("button", {
22102
23684
  type: "button",
22103
- className: PendingPanel_module_css_default.close,
22104
- "data-diff-approval-close": true,
22105
- "aria-label": t("action.close"),
23685
+ className: PendingPanel_module_css_default.importButton,
23686
+ "data-diff-add": true,
22106
23687
  onClick: () => {
22107
- setOpen(false);
23688
+ setAddOpen(true);
22108
23689
  },
22109
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
22110
- })
23690
+ children: t("action.addPath")
23691
+ })]
23692
+ }),
23693
+ importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
23694
+ className: PendingPanel_module_css_default.importNote,
23695
+ role: importFailed ? "alert" : void 0,
23696
+ children: importNote
22111
23697
  })
22112
23698
  ]
22113
- })]
22114
- }),
22115
- snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
22116
- className: PendingPanel_module_css_default.states,
22117
- children: [
22118
- snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
22119
- className: PendingPanel_module_css_default.readError,
22120
- role: "alert",
22121
- children: t("panel.readFailed", { message: snapshot.error })
22122
- }),
22123
- !snapshot.read && snapshot.error === void 0 && (0, react_jsx_runtime.jsx)("p", {
22124
- className: PendingPanel_module_css_default.note,
22125
- children: t("panel.loading")
22126
- }),
22127
- snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsxs)("div", {
22128
- className: PendingPanel_module_css_default.emptyState,
22129
- children: [
22130
- (0, react_jsx_runtime.jsx)("p", {
22131
- className: `${PendingPanel_module_css_default.note} ${PendingPanel_module_css_default.noteCentered}`,
22132
- children: t("panel.empty")
22133
- }),
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
- })]
22154
- }),
22155
- importNote !== void 0 && (0, react_jsx_runtime.jsx)("p", {
22156
- className: PendingPanel_module_css_default.importNote,
22157
- role: importFailed ? "alert" : void 0,
22158
- children: importNote
22159
- })
22160
- ]
22161
- })
22162
- ]
22163
- }) : (0, react_jsx_runtime.jsxs)("div", {
22164
- className: PendingPanel_module_css_default.split,
22165
- ref: splitRef,
22166
- children: [
22167
- !floatMode && (0, react_jsx_runtime.jsx)("nav", {
22168
- className: PendingPanel_module_css_default.fileList,
22169
- style: { width: listWidth },
22170
- "data-diff-approval-file-list": true,
22171
- children: fileListBody
22172
- }),
22173
- !floatMode && (0, react_jsx_runtime.jsx)("div", {
22174
- className: PendingPanel_module_css_default.resizeHandle,
22175
- "data-diff-resize": true,
22176
- onMouseDown: startResize
22177
- }),
22178
- (0, react_jsx_runtime.jsx)("div", {
22179
- className: PendingPanel_module_css_default.detail,
22180
- children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
22181
- className: PendingPanel_module_css_default.detailEmpty,
22182
- children: t("panel.selectHint")
22183
- }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
22184
- file: selectedFile,
22185
- busy: snapshot.busy.has(selectedFile.id),
22186
- workspacePath: snapshot.workspacePath,
22187
- jumpSignal,
22188
- undoFlash,
22189
- failedMessage: failed.get(selectedFile.id),
22190
- onPasteReference,
22191
- onToast: showCopyToast,
22192
- t,
22193
- onKeep: keepWithPrompt,
22194
- onRevert: revertWithPrompt,
22195
- onRefreshVcs: (entry) => {
22196
- runRefreshVcs(entry);
22197
- },
22198
- onBlockKeep: blockKeepWithPrompt,
22199
- onBlockRevert: blockRevertWithPrompt,
22200
- onOpen,
22201
- onPreviewImage,
22202
- floatMode,
22203
- floatOpen,
22204
- onToggleFileList: toggleFileList
22205
- })
22206
- }),
22207
- floatMode && floatOpen && files.length > 0 && floatBox !== null && (0, react_jsx_runtime.jsx)("div", {
22208
- className: PendingPanel_module_css_default.fileListFloat,
23699
+ })
23700
+ ]
23701
+ }) : (0, react_jsx_runtime.jsxs)("div", {
23702
+ className: PendingPanel_module_css_default.split,
23703
+ ref: splitRef,
23704
+ children: [
23705
+ floatMode && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
23706
+ label: t(floatOpen ? "action.hideFileList" : "action.showFileList"),
23707
+ side: "bottom",
23708
+ delayMs: 500,
23709
+ children: (0, react_jsx_runtime.jsx)("button", {
23710
+ type: "button",
23711
+ className: PendingPanel_module_css_default.fileListKnob,
22209
23712
  style: {
22210
- left: floatBox.left + FLOAT_LIST_MARGIN_PX,
22211
- top: floatBox.top + FLOAT_LIST_MARGIN_PX,
22212
- width: Math.min(listWidth, Math.max(0, floatBox.width - 24)),
22213
- height: Math.max(0, floatBox.height - 24)
23713
+ left: (floatBox?.left ?? 0) + FLOAT_LIST_MARGIN_PX,
23714
+ top: (floatBox?.top ?? 0) + FLOAT_LIST_MARGIN_PX
22214
23715
  },
22215
- "data-diff-floating-file-list": true,
22216
- children: fileListBody
23716
+ "data-diff-file-list-toggle": true,
23717
+ "aria-label": t(floatOpen ? "action.hideFileList" : "action.showFileList"),
23718
+ "aria-expanded": floatOpen,
23719
+ onClick: toggleFileList,
23720
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: 14 })
22217
23721
  })
22218
- ]
22219
- }),
22220
- blockPrompt !== null && promptFile !== void 0 && (0, react_jsx_runtime.jsx)("div", {
22221
- className: PendingPanel_module_css_default.confirmBackdrop,
22222
- "data-diff-confirm": true,
22223
- children: (0, react_jsx_runtime.jsxs)("div", {
22224
- className: PendingPanel_module_css_default.confirmCard,
22225
- role: "dialog",
22226
- "aria-modal": "true",
22227
- children: [(0, react_jsx_runtime.jsx)("p", {
22228
- className: PendingPanel_module_css_default.confirmText,
22229
- children: t("panel.resolvedAsk", { file: basenameOf(promptFile.path) })
22230
- }), (0, react_jsx_runtime.jsxs)("div", {
22231
- className: PendingPanel_module_css_default.confirmActions,
22232
- children: [(0, react_jsx_runtime.jsx)("button", {
22233
- type: "button",
22234
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
22235
- "data-diff-confirm-remove": true,
22236
- onClick: () => {
22237
- setBlockPrompt(null);
22238
- const { action, sessionId, id, block } = blockPrompt;
22239
- action === "keep" ? onBlockKeep(sessionId, id, block, true) : onBlockRevert(sessionId, id, block, true);
22240
- },
22241
- children: t("row.dismiss")
22242
- }), (0, react_jsx_runtime.jsx)("button", {
22243
- type: "button",
22244
- className: PendingPanel_module_css_default.action,
22245
- "data-diff-confirm-keep": true,
22246
- onClick: () => {
22247
- setBlockPrompt(null);
22248
- const { action, sessionId, id, block } = blockPrompt;
22249
- action === "keep" ? onBlockKeep(sessionId, id, block, false) : onBlockRevert(sessionId, id, block, false);
22250
- },
22251
- children: t("panel.keepInList")
22252
- })]
23722
+ }),
23723
+ !floatMode && (0, react_jsx_runtime.jsx)("nav", {
23724
+ className: PendingPanel_module_css_default.fileList,
23725
+ style: { width: listWidth },
23726
+ "data-diff-approval-file-list": true,
23727
+ children: fileListBody
23728
+ }),
23729
+ !floatMode && (0, react_jsx_runtime.jsx)("div", {
23730
+ className: PendingPanel_module_css_default.resizeHandle,
23731
+ "data-diff-resize": true,
23732
+ onMouseDown: startResize
23733
+ }),
23734
+ (0, react_jsx_runtime.jsx)("div", {
23735
+ className: PendingPanel_module_css_default.detail,
23736
+ "data-diff-detail": true,
23737
+ children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
23738
+ className: PendingPanel_module_css_default.detailEmpty,
23739
+ children: t("panel.selectHint")
23740
+ }) : (0, react_jsx_runtime.jsx)(PendingDiff, {
23741
+ file: selectedFile,
23742
+ busy: snapshot.busy.has(selectedFile.id),
23743
+ workspacePath: snapshot.workspacePath,
23744
+ jumpSignal,
23745
+ undoFlash,
23746
+ landingTop: landing !== void 0 && landing.fileId === selectedFile.id ? landing.top : void 0,
23747
+ landingTick: landing !== void 0 && landing.fileId === selectedFile.id ? landing.n : 0,
23748
+ failedMessage: failed.get(selectedFile.id),
23749
+ onPasteReference,
23750
+ onToast: showCopyToast,
23751
+ t,
23752
+ onKeep: keepWithPrompt,
23753
+ onRevert: revertWithPrompt,
23754
+ onRefreshVcs: (entry) => {
23755
+ runRefreshVcs(entry);
23756
+ },
23757
+ onBlockKeep: blockKeepWithPrompt,
23758
+ onBlockRevert: blockRevertWithPrompt,
23759
+ onOpen,
23760
+ onPreviewImage
23761
+ })
23762
+ }),
23763
+ floatMode && floatOpen && files.length > 0 && floatBox !== null && (0, react_jsx_runtime.jsxs)("div", {
23764
+ className: PendingPanel_module_css_default.fileListFloat,
23765
+ style: {
23766
+ left: floatBox.left + FLOAT_LIST_MARGIN_PX,
23767
+ top: floatBox.top + FLOAT_LIST_MARGIN_PX,
23768
+ width: Math.min(listWidth, Math.max(0, floatBox.width - 24)),
23769
+ height: Math.max(0, floatBox.height - 24)
23770
+ },
23771
+ "data-diff-floating-file-list": true,
23772
+ children: [fileListBody, (0, react_jsx_runtime.jsx)("div", {
23773
+ className: PendingPanel_module_css_default.floatResizeHandle,
23774
+ "data-diff-float-resize": true,
23775
+ onMouseDown: startResize
22253
23776
  })]
22254
23777
  })
22255
- }),
22256
- filePrompt !== null && promptEntry !== void 0 && (0, react_jsx_runtime.jsx)("div", {
22257
- className: PendingPanel_module_css_default.confirmBackdrop,
22258
- "data-diff-confirm-file": true,
22259
- children: (0, react_jsx_runtime.jsxs)("div", {
22260
- className: PendingPanel_module_css_default.confirmCard,
22261
- role: "dialog",
22262
- "aria-modal": "true",
22263
- children: [(0, react_jsx_runtime.jsx)("p", {
22264
- className: PendingPanel_module_css_default.confirmText,
22265
- children: t(filePrompt.action === "keep" ? "panel.fileKeptAsk" : "panel.fileRevertedAsk", { file: basenameOf(promptEntry.path) })
22266
- }), (0, react_jsx_runtime.jsxs)("div", {
22267
- className: PendingPanel_module_css_default.confirmActions,
22268
- children: [(0, react_jsx_runtime.jsx)("button", {
22269
- type: "button",
22270
- className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
22271
- "data-diff-file-confirm-remove": true,
22272
- onClick: () => {
22273
- setFilePrompt(null);
22274
- const { action, sessionId, id } = filePrompt;
22275
- action === "keep" ? onKeep(sessionId, id, false) : onRevert(sessionId, id, false);
22276
- },
22277
- children: t("row.dismiss")
22278
- }), (0, react_jsx_runtime.jsx)("button", {
22279
- type: "button",
22280
- className: PendingPanel_module_css_default.action,
22281
- "data-diff-file-confirm-keep": true,
22282
- onClick: () => {
22283
- setFilePrompt(null);
22284
- const { action, sessionId, id } = filePrompt;
22285
- action === "keep" ? onKeep(sessionId, id, true) : onRevert(sessionId, id, true);
22286
- },
22287
- children: t("panel.keepInList")
22288
- })]
23778
+ ]
23779
+ }),
23780
+ blockPrompt !== null && promptFile !== void 0 && (0, react_jsx_runtime.jsx)("div", {
23781
+ className: PendingPanel_module_css_default.confirmBackdrop,
23782
+ "data-diff-confirm": true,
23783
+ children: (0, react_jsx_runtime.jsxs)("div", {
23784
+ className: PendingPanel_module_css_default.confirmCard,
23785
+ role: "dialog",
23786
+ "aria-modal": "true",
23787
+ children: [(0, react_jsx_runtime.jsx)("p", {
23788
+ className: PendingPanel_module_css_default.confirmText,
23789
+ children: t("panel.resolvedAsk", { file: basenameOf(promptFile.path) })
23790
+ }), (0, react_jsx_runtime.jsxs)("div", {
23791
+ className: PendingPanel_module_css_default.confirmActions,
23792
+ children: [(0, react_jsx_runtime.jsx)("button", {
23793
+ type: "button",
23794
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
23795
+ "data-diff-confirm-remove": true,
23796
+ onClick: () => {
23797
+ setBlockPrompt(null);
23798
+ const { action, sessionId, id, block } = blockPrompt;
23799
+ action === "keep" ? onBlockKeep(sessionId, id, block, true) : onBlockRevert(sessionId, id, block, true);
23800
+ },
23801
+ children: t("row.dismiss")
23802
+ }), (0, react_jsx_runtime.jsx)("button", {
23803
+ type: "button",
23804
+ className: PendingPanel_module_css_default.action,
23805
+ "data-diff-confirm-keep": true,
23806
+ onClick: () => {
23807
+ setBlockPrompt(null);
23808
+ const { action, sessionId, id, block } = blockPrompt;
23809
+ action === "keep" ? onBlockKeep(sessionId, id, block, false) : onBlockRevert(sessionId, id, block, false);
23810
+ },
23811
+ children: t("panel.keepInList")
22289
23812
  })]
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
23813
+ })]
22301
23814
  })
22302
- ]
22303
- })] }), document.body),
22304
- (0, react_jsx_runtime.jsx)("div", {
22305
- className: PendingPanel_module_css_default.footerButtons,
23815
+ }),
23816
+ filePrompt !== null && promptEntry !== void 0 && (0, react_jsx_runtime.jsx)("div", {
23817
+ className: PendingPanel_module_css_default.confirmBackdrop,
23818
+ "data-diff-confirm-file": true,
23819
+ children: (0, react_jsx_runtime.jsxs)("div", {
23820
+ className: PendingPanel_module_css_default.confirmCard,
23821
+ role: "dialog",
23822
+ "aria-modal": "true",
23823
+ children: [(0, react_jsx_runtime.jsx)("p", {
23824
+ className: PendingPanel_module_css_default.confirmText,
23825
+ children: t(filePrompt.action === "keep" ? "panel.fileKeptAsk" : "panel.fileRevertedAsk", { file: basenameOf(promptEntry.path) })
23826
+ }), (0, react_jsx_runtime.jsxs)("div", {
23827
+ className: PendingPanel_module_css_default.confirmActions,
23828
+ children: [(0, react_jsx_runtime.jsx)("button", {
23829
+ type: "button",
23830
+ className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.actionPrimary}`,
23831
+ "data-diff-file-confirm-remove": true,
23832
+ onClick: () => {
23833
+ setFilePrompt(null);
23834
+ const { action, sessionId, id } = filePrompt;
23835
+ action === "keep" ? onKeep(sessionId, id, false) : onRevert(sessionId, id, false);
23836
+ },
23837
+ children: t("row.dismiss")
23838
+ }), (0, react_jsx_runtime.jsx)("button", {
23839
+ type: "button",
23840
+ className: PendingPanel_module_css_default.action,
23841
+ "data-diff-file-confirm-keep": true,
23842
+ onClick: () => {
23843
+ setFilePrompt(null);
23844
+ const { action, sessionId, id } = filePrompt;
23845
+ action === "keep" ? onKeep(sessionId, id, true) : onRevert(sessionId, id, true);
23846
+ },
23847
+ children: t("panel.keepInList")
23848
+ })]
23849
+ })]
23850
+ })
23851
+ }),
23852
+ addOpen && current !== void 0 && (0, react_jsx_runtime.jsx)(PathPicker, {
23853
+ rootPath: snapshot.workspacePath,
23854
+ onBrowse: (path) => onBrowse(current, path),
23855
+ onAdd: (path, includeUnchanged) => onAddPath(current, path, includeUnchanged),
23856
+ onToast: showCopyToast,
23857
+ onClose: () => {
23858
+ setAddOpen(false);
23859
+ },
23860
+ t
23861
+ })
23862
+ ]
23863
+ })] }), docked && dockHost !== void 0 ? dockHost : document.body),
23864
+ !docked && (0, react_jsx_runtime.jsx)("div", {
23865
+ className: PendingPanel_module_css_default.footerButtons,
23866
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
23867
+ label: summonHint(t),
23868
+ side: "top",
23869
+ delayMs: 500,
22306
23870
  children: (0, react_jsx_runtime.jsxs)("button", {
22307
23871
  type: "button",
22308
23872
  className: noSession ? `${PendingPanel_module_css_default.badge} ${PendingPanel_module_css_default.badgeDisabled}` : PendingPanel_module_css_default.badge,
22309
23873
  "data-diff-approval-badge": files.length,
22310
- "data-active": open ? "" : void 0,
23874
+ "data-active": open || dockShowing ? "" : void 0,
22311
23875
  "aria-label": t("panel.aria"),
22312
- "aria-expanded": open,
23876
+ "aria-expanded": open || dockShowing,
22313
23877
  disabled: noSession,
22314
23878
  onClick: toggleOpen,
22315
23879
  children: [
@@ -22324,25 +23888,30 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22324
23888
  })
22325
23889
  ]
22326
23890
  })
22327
- }),
22328
- redoClearedNotice && (0, react_jsx_runtime.jsxs)("div", {
22329
- className: PendingPanel_module_css_default.notice,
22330
- role: "status",
22331
- "data-diff-approval-notice": true,
22332
- children: [(0, react_jsx_runtime.jsx)("p", {
22333
- className: PendingPanel_module_css_default.noticeText,
22334
- children: t("panel.externalChanged")
22335
- }), (0, react_jsx_runtime.jsx)("button", {
22336
- type: "button",
22337
- className: PendingPanel_module_css_default.noticeButton,
22338
- "data-diff-notice-dismiss": true,
22339
- onClick: () => {
22340
- setRedoClearedNotice(false);
22341
- },
22342
- children: t("panel.dismiss")
22343
- })]
22344
23891
  })
22345
- ]
23892
+ }),
23893
+ redoClearedNotice && (0, react_jsx_runtime.jsxs)("div", {
23894
+ className: PendingPanel_module_css_default.notice,
23895
+ role: "status",
23896
+ "data-diff-approval-notice": true,
23897
+ children: [(0, react_jsx_runtime.jsx)("p", {
23898
+ className: PendingPanel_module_css_default.noticeText,
23899
+ children: t("panel.externalChanged")
23900
+ }), (0, react_jsx_runtime.jsx)("button", {
23901
+ type: "button",
23902
+ className: PendingPanel_module_css_default.noticeButton,
23903
+ "data-diff-notice-dismiss": true,
23904
+ onClick: () => {
23905
+ setRedoClearedNotice(false);
23906
+ },
23907
+ children: t("panel.dismiss")
23908
+ })]
23909
+ })
23910
+ ] });
23911
+ return docked ? content : (0, react_jsx_runtime.jsx)("div", {
23912
+ className: wide ? PendingPanel_module_css_default.layer : `${PendingPanel_module_css_default.layer} ${PendingPanel_module_css_default.rail}`,
23913
+ "data-diff-approval-layer": true,
23914
+ children: content
22346
23915
  });
22347
23916
  }
22348
23917
  //#endregion
@@ -22714,14 +24283,34 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22714
24283
  parts.push(key.length === 1 ? key.toUpperCase() : key);
22715
24284
  return parts.join("+");
22716
24285
  }
22717
- /** A button that records the next key chord (modifiers + key) as the shortcut. */
22718
- function ShortcutRecorder({ value, onChange, dataAttribute, placeholder }) {
24286
+ /**
24287
+ * A button that records the next key chord (modifiers + key) as the shortcut.
24288
+ * Clicking it starts recording, clicking it again stops; a press anywhere else
24289
+ * while recording means "no shortcut" — the row is the control, so a press
24290
+ * outside it is the same answer as pressing Delete would be.
24291
+ */
24292
+ function ShortcutRecorder({ value, onChange, dataAttribute, placeholder, noneLabel }) {
22719
24293
  const [recording, setRecording] = (0, react.useState)(false);
22720
- return (0, react_jsx_runtime.jsxs)("button", {
24294
+ const ref = (0, react.useRef)(null);
24295
+ (0, react.useEffect)(() => {
24296
+ if (!recording) return;
24297
+ const onPointerDown = (event) => {
24298
+ if (event.target instanceof Node && ref.current?.contains(event.target) === true) return;
24299
+ onChange("");
24300
+ setRecording(false);
24301
+ };
24302
+ document.addEventListener("pointerdown", onPointerDown, true);
24303
+ return () => {
24304
+ document.removeEventListener("pointerdown", onPointerDown, true);
24305
+ };
24306
+ }, [recording, onChange]);
24307
+ return (0, react_jsx_runtime.jsx)("button", {
24308
+ ref,
22721
24309
  type: "button",
22722
24310
  className: PendingPanel_module_css_default.settingsSelector,
24311
+ "data-recording": recording ? "" : void 0,
22723
24312
  onClick: () => {
22724
- setRecording(true);
24313
+ setRecording((value) => !value);
22725
24314
  },
22726
24315
  onBlur: () => {
22727
24316
  setRecording(false);
@@ -22740,28 +24329,44 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22740
24329
  }
22741
24330
  } : void 0,
22742
24331
  [dataAttribute]: true,
22743
- children: [recording ? placeholder : value, (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: PendingPanel_module_css_default.settingsSelectorChevron })]
24332
+ children: recording ? placeholder : value === "" ? noneLabel : value
22744
24333
  });
22745
24334
  }
22746
- /** One shortcut row: title + description, chord recorder right. */
22747
- function ShortcutRow({ title, description, value, onChange, dataAttribute, placeholder }) {
24335
+ /** One shortcut row: title + description, chord recorder and its reset right. */
24336
+ function ShortcutRow({ title, description, value, defaultValue, onChange, dataAttribute, placeholder, noneLabel, resetLabel }) {
22748
24337
  return (0, react_jsx_runtime.jsxs)("div", {
22749
24338
  className: PendingPanel_module_css_default.settingsRow,
22750
- children: [(0, react_jsx_runtime.jsxs)("div", {
22751
- className: PendingPanel_module_css_default.settingsRowText,
22752
- children: [(0, react_jsx_runtime.jsx)("div", {
22753
- className: PendingPanel_module_css_default.settingsRowTitle,
22754
- children: title
22755
- }), (0, react_jsx_runtime.jsx)("div", {
22756
- className: PendingPanel_module_css_default.settingsRowDesc,
22757
- children: description
22758
- })]
22759
- }), (0, react_jsx_runtime.jsx)(ShortcutRecorder, {
22760
- value,
22761
- onChange,
22762
- dataAttribute,
22763
- placeholder
22764
- })]
24339
+ children: [
24340
+ (0, react_jsx_runtime.jsxs)("div", {
24341
+ className: PendingPanel_module_css_default.settingsRowText,
24342
+ children: [(0, react_jsx_runtime.jsx)("div", {
24343
+ className: PendingPanel_module_css_default.settingsRowTitle,
24344
+ children: title
24345
+ }), (0, react_jsx_runtime.jsx)("div", {
24346
+ className: PendingPanel_module_css_default.settingsRowDesc,
24347
+ children: description
24348
+ })]
24349
+ }),
24350
+ (0, react_jsx_runtime.jsx)(ShortcutRecorder, {
24351
+ value,
24352
+ onChange,
24353
+ dataAttribute,
24354
+ placeholder,
24355
+ noneLabel
24356
+ }),
24357
+ (0, react_jsx_runtime.jsx)("button", {
24358
+ type: "button",
24359
+ className: PendingPanel_module_css_default.settingsReset,
24360
+ "data-reset": dataAttribute,
24361
+ "aria-label": resetLabel,
24362
+ title: resetLabel,
24363
+ disabled: value === defaultValue,
24364
+ onClick: () => {
24365
+ onChange(defaultValue);
24366
+ },
24367
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, { size: 14 })
24368
+ })
24369
+ ]
22765
24370
  });
22766
24371
  }
22767
24372
  /** One color-picker row: title + description left, a DSH pill trigger that opens
@@ -22944,6 +24549,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
22944
24549
  const addDefault = currentDiffAddColor();
22945
24550
  const delDefault = currentDiffDelColor();
22946
24551
  const [diffOpen, setDiffOpen] = (0, react.useState)(true);
24552
+ const [cover, setCoverState] = (0, react.useState)(panelCover);
24553
+ const [coverOpen, setCoverOpen] = (0, react.useState)(false);
24554
+ (0, react.useEffect)(() => {
24555
+ const onCover = () => {
24556
+ setCoverState(panelCover());
24557
+ };
24558
+ window.addEventListener(COVER_CHANGED_EVENT, onCover);
24559
+ return () => {
24560
+ window.removeEventListener(COVER_CHANGED_EVENT, onCover);
24561
+ };
24562
+ }, []);
24563
+ const setCoverFlag = (edge, next) => {
24564
+ const value = {
24565
+ ...cover,
24566
+ [edge]: next
24567
+ };
24568
+ setCoverState(value);
24569
+ setPanelCover(value);
24570
+ };
22947
24571
  const [summon, setSummonState] = (0, react.useState)(quickSummonKey);
22948
24572
  const [keysOpen, setKeysOpen] = (0, react.useState)(false);
22949
24573
  const [keybindings, setKeybindingsState] = (0, react.useState)(() => {
@@ -23165,21 +24789,82 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
23165
24789
  title: t("panel.quickSummon"),
23166
24790
  description: t("panel.quickSummonDesc"),
23167
24791
  value: summon,
24792
+ defaultValue: "Ctrl+D",
23168
24793
  onChange: setSummon,
23169
24794
  dataAttribute: "data-diff-quick-summon-key",
23170
- placeholder: t("panel.recordShortcut")
24795
+ placeholder: t("panel.recordShortcut"),
24796
+ noneLabel: t("panel.shortcutNone"),
24797
+ resetLabel: t("panel.shortcutReset")
23171
24798
  }), Object.keys(DEFAULT_KEYBINDINGS).map((action) => (0, react_jsx_runtime.jsx)(ShortcutRow, {
23172
24799
  title: t(`panel.key.${action}`),
23173
24800
  description: t("panel.keyDesc"),
23174
24801
  value: keybindings[action] ?? DEFAULT_KEYBINDINGS[action] ?? "",
24802
+ defaultValue: DEFAULT_KEYBINDINGS[action] ?? "",
23175
24803
  onChange: (chord) => {
23176
24804
  setKB(action, chord);
23177
24805
  },
23178
- dataAttribute: `data-diff-key-${action}`,
23179
- placeholder: t("panel.recordShortcut")
24806
+ dataAttribute: `data-diff-key-${action.toLowerCase()}`,
24807
+ placeholder: t("panel.recordShortcut"),
24808
+ noneLabel: t("panel.shortcutNone"),
24809
+ resetLabel: t("panel.shortcutReset")
23180
24810
  }, action))]
23181
24811
  })]
23182
24812
  }),
24813
+ (0, react_jsx_runtime.jsxs)("div", {
24814
+ className: PendingPanel_module_css_default.settingsGroup,
24815
+ "data-open": coverOpen || void 0,
24816
+ children: [(0, react_jsx_runtime.jsxs)("button", {
24817
+ type: "button",
24818
+ className: PendingPanel_module_css_default.settingsGroupHeader,
24819
+ onClick: () => {
24820
+ setCoverOpen((open) => !open);
24821
+ },
24822
+ "data-diff-cover-toggle": true,
24823
+ children: [(0, react_jsx_runtime.jsxs)("span", {
24824
+ className: PendingPanel_module_css_default.settingsGroupText,
24825
+ children: [(0, react_jsx_runtime.jsx)("span", {
24826
+ className: PendingPanel_module_css_default.settingsGroupTitle,
24827
+ children: t("settings.cover")
24828
+ }), (0, react_jsx_runtime.jsx)("span", {
24829
+ className: PendingPanel_module_css_default.settingsGroupDesc,
24830
+ children: t("settings.coverDesc")
24831
+ })]
24832
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: PendingPanel_module_css_default.settingsGroupChevron })]
24833
+ }), coverOpen && (0, react_jsx_runtime.jsx)("div", {
24834
+ className: PendingPanel_module_css_default.settingsGroupBody,
24835
+ children: [
24836
+ [
24837
+ "left",
24838
+ "cover.left",
24839
+ "cover.leftDesc"
24840
+ ],
24841
+ [
24842
+ "top",
24843
+ "cover.top",
24844
+ "cover.topDesc"
24845
+ ],
24846
+ [
24847
+ "right",
24848
+ "cover.right",
24849
+ "cover.rightDesc"
24850
+ ],
24851
+ [
24852
+ "composer",
24853
+ "cover.composer",
24854
+ "cover.composerDesc"
24855
+ ]
24856
+ ].map(([edge, title, description]) => (0, react_jsx_runtime.jsx)(PreferenceRow, {
24857
+ title: t(title),
24858
+ description: t(description),
24859
+ value: cover[edge],
24860
+ onSelect: (next) => {
24861
+ setCoverFlag(edge, next);
24862
+ },
24863
+ dataAttribute: `data-diff-cover-${edge}`,
24864
+ t
24865
+ }, edge))
24866
+ })]
24867
+ }),
23183
24868
  (0, react_jsx_runtime.jsx)(PreferenceRow, {
23184
24869
  title: t("panel.importUntracked"),
23185
24870
  description: t("panel.importUntrackedDesc"),
@@ -23904,6 +25589,69 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
23904
25589
  };
23905
25590
  }
23906
25591
  //#endregion
25592
+ //#region lib/types/client/header-entry.js
25593
+ /**
25594
+ * The pending-changes entry in the Session header.
25595
+ *
25596
+ * The sidebar footer's badge is only where the sidebar is: with the sidebar
25597
+ * collapsed to its rail, or hidden on a narrow window, the review has no entry.
25598
+ * The Session header's right-aligned utilities hold the app's own per-session
25599
+ * controls — "open in app" and the more-actions button the session-log export
25600
+ * lives behind — and are visible whatever the sidebar does. This seats the same
25601
+ * action there: one compact button carrying the pending count.
25602
+ *
25603
+ * The header entry is a *second mount* of one action, not a second panel: the
25604
+ * click travels to the mount that owns the open state ({@link TOGGLE_PANEL_EVENT})
25605
+ * and the visibility comes back ({@link PANEL_STATE_EVENT}), because the two sit
25606
+ * in different slot trees and neither owns the other. That is also why a press is
25607
+ * exactly the footer badge's press: a docked panel's tab is revealed, and still
25608
+ * closed by the chip on its own tab rather than from here.
25609
+ *
25610
+ * @module dsh-diff-approval/client/header-entry
25611
+ */
25612
+ /**
25613
+ * Render the Session header's pending-changes button.
25614
+ * @param props - the bound hooks, the session seat, and the translator.
25615
+ * @returns the icon button, with the pending count while there is one.
25616
+ */
25617
+ function DiffApprovalHeaderEntry({ usePending, useSessions, useDock, t }) {
25618
+ const count = usePending((snapshot) => snapshot.files.length);
25619
+ const dockShowing = useDock?.((state) => state.open) === true;
25620
+ const [open, setOpen] = (0, react.useState)(false);
25621
+ (0, react.useEffect)(() => {
25622
+ const onState = (event) => {
25623
+ setOpen(event.detail?.open === true);
25624
+ };
25625
+ window.addEventListener(PANEL_STATE_EVENT, onState);
25626
+ return () => {
25627
+ window.removeEventListener(PANEL_STATE_EVENT, onState);
25628
+ };
25629
+ }, []);
25630
+ const noSession = useSessions === void 0 ? false : useSessions((state) => state.current === void 0 || (state.byId[state.current]?.blank ?? false));
25631
+ const active = open || dockShowing;
25632
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
25633
+ label: summonHint(t),
25634
+ side: "bottom",
25635
+ delayMs: 500,
25636
+ children: (0, react_jsx_runtime.jsxs)("button", {
25637
+ type: "button",
25638
+ className: PendingPanel_module_css_default.headerEntry,
25639
+ "data-diff-approval-header-entry": count,
25640
+ "data-active": active ? "" : void 0,
25641
+ "aria-label": t("panel.aria"),
25642
+ "aria-expanded": active,
25643
+ disabled: noSession,
25644
+ onClick: () => {
25645
+ window.dispatchEvent(new CustomEvent(TOGGLE_PANEL_EVENT));
25646
+ },
25647
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, { size: 16 }), count > 0 && (0, react_jsx_runtime.jsx)("span", {
25648
+ className: PendingPanel_module_css_default.headerEntryCount,
25649
+ children: count
25650
+ })]
25651
+ })
25652
+ });
25653
+ }
25654
+ //#endregion
23907
25655
  //#region lib/types/client/locales.js
23908
25656
  /** Pending-edit review UI dictionaries. */
23909
25657
  const NS = "diff-approval";
@@ -23911,6 +25659,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
23911
25659
  const zh = {
23912
25660
  "panel.title": "待处理改动",
23913
25661
  "panel.empty": "列表中暂无文件",
25662
+ "panel.dockGuide": "在右侧边栏中打开待处理改动面板",
25663
+ "panel.dockUnavailable": "这个环境没有可停靠的右侧边栏",
25664
+ "panel.dockFailed": "右侧边栏现在还不能接收面板",
23914
25665
  "panel.loading": "读取中…",
23915
25666
  "panel.readFailed": "读取待处理改动失败:{message}",
23916
25667
  "panel.group.current": "当前会话",
@@ -23923,7 +25674,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
23923
25674
  "panel.blockSingle": "仅有一个差异块",
23924
25675
  "panel.viewDiff": "查看差异",
23925
25676
  "panel.fileNotPending": "该文件不在待处理差异列表中",
23926
- "panel.selectHint": "点击每项查看整个文件的差异;选中文本后,用底部状态栏或 Ctrl+L 复制引用",
25677
+ "panel.selectHint": "点击文件列表项目查看差异",
23927
25678
  "panel.searchPlaceholder": "搜索",
23928
25679
  "panel.missing": "文件已不存在",
23929
25680
  "panel.missingHint": "该文件已不存在,回退会恢复该文件。",
@@ -23959,7 +25710,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
23959
25710
  "settings.diffView": "差异视图",
23960
25711
  "settings.diffViewDesc": "调整差异的显示效果(字号、行高、新增/删除颜色)与布局偏好。",
23961
25712
  "settings.diffPreview": "预览",
23962
- "panel.keyDesc": "点击右侧按钮后按下新的按键组合即可修改。",
25713
+ "panel.keyDesc": "点击按钮后按下新的按键组合即可修改;录制时点击其他地方表示不绑定,右侧按钮恢复默认。",
25714
+ "panel.shortcutNone": "无",
25715
+ "panel.shortcutReset": "恢复默认",
23963
25716
  "panel.key.jumpUp": "跳转到上一个差异块",
23964
25717
  "panel.key.jumpDown": "跳转到下一个差异块",
23965
25718
  "panel.key.copyRef": "复制引用",
@@ -23972,6 +25725,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
23972
25725
  "panel.key.redo": "重做",
23973
25726
  "panel.key.cycleNext": "下一个待处理文件",
23974
25727
  "panel.key.cyclePrev": "上一个待处理文件",
25728
+ "panel.key.coverLeft": "浮窗:覆盖左侧边栏",
25729
+ "panel.key.coverComposer": "浮窗:覆盖下方输入框",
25730
+ "panel.key.coverRight": "浮窗:覆盖右侧边栏",
25731
+ "panel.key.coverTop": "浮窗:覆盖上方标题栏",
23975
25732
  "settings.tabLabel": "改动审批",
23976
25733
  "row.create": "新增",
23977
25734
  "row.failed": "失败",
@@ -23990,6 +25747,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
23990
25747
  "action.nextDiff": "下一处差异",
23991
25748
  "action.showFileList": "展开文件列表",
23992
25749
  "action.hideFileList": "收起文件列表",
25750
+ "action.fileListFloatOn": "文件列表始终折叠",
25751
+ "action.fileListFloatOff": "取消始终折叠",
23993
25752
  "action.viewSplit": "切换到双栏",
23994
25753
  "action.viewUnified": "切换到单栏",
23995
25754
  "action.viewPreview": "预览",
@@ -24017,9 +25776,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24017
25776
  "panel.importFailed": "导入失败:{message}",
24018
25777
  "action.search": "搜索",
24019
25778
  "action.settings": "设置",
24020
- "action.expand": "展开全屏",
24021
- "action.exitFullscreen": "退出全屏",
25779
+ "action.presentation": "呈现方式",
25780
+ "action.presentationCurrent": "呈现方式:{state}",
25781
+ "action.cover": "覆盖范围",
25782
+ "cover.left": "覆盖左侧边栏",
25783
+ "cover.right": "覆盖右侧边栏",
25784
+ "cover.composer": "覆盖下方输入框",
25785
+ "cover.top": "覆盖上方标题栏",
25786
+ "cover.leftDesc": "浮窗盖住左侧边栏;关闭时让开它(边栏折叠时让开图标栏)。",
25787
+ "cover.topDesc": "浮窗盖住标题区;关闭时露出会话标题行,视图标签栏仍会被盖住。",
25788
+ "cover.rightDesc": "浮窗盖住右侧边栏;关闭时让开它。",
25789
+ "cover.composerDesc": "浮窗盖住下方输入框;关闭时停在输入框上方(浮窗默认)。",
25790
+ "settings.cover": "浮窗覆盖",
25791
+ "settings.coverDesc": "浮窗形态下,面板四边是否分别盖住左侧边栏、上方标题栏、右侧边栏、下方输入框;四个全开即原来的全屏。",
25792
+ "cover.on": "开",
25793
+ "cover.off": "关",
25794
+ "cover.notice": "{label}:{state}",
25795
+ "action.presentationFloat": "浮窗",
25796
+ "action.presentationDock": "停靠到右侧边栏",
25797
+ "action.presentationDocked": "已停靠",
24022
25798
  "action.close": "关闭",
25799
+ "action.closeHint": "关闭(Esc / {chord})",
25800
+ "action.closeHintEsc": "关闭(Esc)",
25801
+ "action.summonHint": "待处理改动({chord})",
25802
+ "action.retry": "重试",
25803
+ "panel.crashed": "审阅面板出错了,详情见控制台",
24023
25804
  "status.kept": "已保留",
24024
25805
  "status.reverted": "已回退",
24025
25806
  "status.missing": "该改动已不存在",
@@ -24062,6 +25843,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24062
25843
  const en = {
24063
25844
  "panel.title": "Pending changes",
24064
25845
  "panel.empty": "No files in the list yet",
25846
+ "panel.dockGuide": "Open the pending-changes panel in the right sidebar",
25847
+ "panel.dockUnavailable": "This build has no right sidebar to dock into",
25848
+ "panel.dockFailed": "The right sidebar cannot take the panel yet",
24065
25849
  "panel.loading": "Reading…",
24066
25850
  "panel.readFailed": "Reading pending changes failed: {message}",
24067
25851
  "panel.group.current": "This session",
@@ -24074,7 +25858,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24074
25858
  "panel.blockSingle": "Only one diff block",
24075
25859
  "panel.viewDiff": "View diff",
24076
25860
  "panel.fileNotPending": "This file is not in the pending diff list",
24077
- "panel.selectHint": "Select an item to review its whole-file diff; select text and copy its reference from the status bar (Ctrl+L)",
25861
+ "panel.selectHint": "Click a file in the list to review its diff",
24078
25862
  "panel.searchPlaceholder": "Search",
24079
25863
  "panel.missing": "File is gone",
24080
25864
  "panel.missingHint": "This file no longer exists; reverting restores it.",
@@ -24110,7 +25894,9 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24110
25894
  "settings.diffView": "Diff view",
24111
25895
  "settings.diffViewDesc": "Adjust the diff appearance (font size, line height, add/delete colors) and layout preferences.",
24112
25896
  "settings.diffPreview": "Preview",
24113
- "panel.keyDesc": "Click the button, then press a new chord to rebind it.",
25897
+ "panel.keyDesc": "Click the button and press a new chord to rebind it; pressing elsewhere while recording leaves it unbound, and the button beside it restores the default.",
25898
+ "panel.shortcutNone": "None",
25899
+ "panel.shortcutReset": "Restore default",
24114
25900
  "panel.key.jumpUp": "Jump to the previous diff block",
24115
25901
  "panel.key.jumpDown": "Jump to the next diff block",
24116
25902
  "panel.key.copyRef": "Copy reference",
@@ -24123,6 +25909,10 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24123
25909
  "panel.key.redo": "Redo",
24124
25910
  "panel.key.cycleNext": "Next pending file",
24125
25911
  "panel.key.cyclePrev": "Previous pending file",
25912
+ "panel.key.coverLeft": "Float: cover the left sidebar",
25913
+ "panel.key.coverComposer": "Float: cover the composer below",
25914
+ "panel.key.coverRight": "Float: cover the right sidebar",
25915
+ "panel.key.coverTop": "Float: cover the header above",
24126
25916
  "settings.tabLabel": "Diff Approval",
24127
25917
  "row.create": "New",
24128
25918
  "row.failed": "Failed",
@@ -24141,6 +25931,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24141
25931
  "action.nextDiff": "Next diff",
24142
25932
  "action.showFileList": "Show file list",
24143
25933
  "action.hideFileList": "Hide file list",
25934
+ "action.fileListFloatOn": "Always fold the file list",
25935
+ "action.fileListFloatOff": "Stop always folding",
24144
25936
  "action.viewSplit": "Switch to side-by-side",
24145
25937
  "action.viewUnified": "Switch to unified",
24146
25938
  "action.viewPreview": "Preview",
@@ -24168,9 +25960,31 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24168
25960
  "panel.importFailed": "Import failed: {message}",
24169
25961
  "action.search": "Search",
24170
25962
  "action.settings": "Settings",
24171
- "action.expand": "Expand fullscreen",
24172
- "action.exitFullscreen": "Exit fullscreen",
25963
+ "action.presentation": "Presentation",
25964
+ "action.presentationCurrent": "Presentation: {state}",
25965
+ "action.cover": "Coverage",
25966
+ "cover.left": "Cover the left sidebar",
25967
+ "cover.right": "Cover the right sidebar",
25968
+ "cover.composer": "Cover the composer below",
25969
+ "cover.top": "Cover the header above",
25970
+ "cover.leftDesc": "The floating panel covers the left sidebar; off leaves it visible (its collapsed rail included).",
25971
+ "cover.topDesc": "The floating panel covers the header area; off leaves the session title row visible while the view tabs stay covered.",
25972
+ "cover.rightDesc": "The floating panel covers the right sidebar; off leaves it visible.",
25973
+ "cover.composerDesc": "The floating panel covers the composer below; off stops above it, which is the floating default.",
25974
+ "settings.cover": "Floating coverage",
25975
+ "settings.coverDesc": "While floating, whether the panel covers the left sidebar, the header above, the right sidebar, and the composer below; all four is what fullscreen used to be.",
25976
+ "cover.on": "on",
25977
+ "cover.off": "off",
25978
+ "cover.notice": "{label}: {state}",
25979
+ "action.presentationFloat": "Floating",
25980
+ "action.presentationDock": "Dock in the right sidebar",
25981
+ "action.presentationDocked": "Docked",
24173
25982
  "action.close": "Close",
25983
+ "action.closeHint": "Close (Esc / {chord})",
25984
+ "action.closeHintEsc": "Close (Esc)",
25985
+ "action.summonHint": "Pending changes ({chord})",
25986
+ "action.retry": "Retry",
25987
+ "panel.crashed": "The review panel hit an error; see the console for details",
24174
25988
  "status.kept": "Kept",
24175
25989
  "status.reverted": "Reverted",
24176
25990
  "status.missing": "This change no longer exists",
@@ -24212,6 +26026,25 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24212
26026
  //#endregion
24213
26027
  //#region lib/types/client/index.js
24214
26028
  /** Pending-edit review panel, browser half: footer action, pending list, and whole-file diff viewer. */
26029
+ /**
26030
+ * The footer seat's registrant: the panel inside its own error boundary, so a
26031
+ * crash there cannot retire the entry for the life of the page (see
26032
+ * {@link PanelBoundary}).
26033
+ * @param props - the seat's runtime props, this plugin's face, and the locale `t`.
26034
+ * @returns the boundary-wrapped panel.
26035
+ */
26036
+ function PendingPanelEntry(props) {
26037
+ return (0, react.createElement)(PanelBoundary, { t: props.t }, (0, react.createElement)(PendingPanel, props));
26038
+ }
26039
+ /**
26040
+ * The header seat's registrant: the entry button inside the same boundary, so a
26041
+ * crash costs a visible note rather than the whole button for the page's life.
26042
+ * @param props - the seat's runtime props, the face's hooks, and the locale `t`.
26043
+ * @returns the boundary-wrapped entry.
26044
+ */
26045
+ function HeaderEntry(props) {
26046
+ return (0, react.createElement)(PanelBoundary, { t: props.t }, (0, react.createElement)(DiffApprovalHeaderEntry, props));
26047
+ }
24215
26048
  /** Required services: locale, slots, the wire channel, the current session, and
24216
26049
  * the layout controller (this plugin collapses the sidebar before its modal opens). */
24217
26050
  const inject = [
@@ -24222,6 +26055,22 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24222
26055
  "layout"
24223
26056
  ];
24224
26057
  /**
26058
+ * Fill one slot, with the failure contained: a registration runs inside the
26059
+ * client's boot (or, for a seat declared later, inside the slot machinery), so a
26060
+ * throw here would take down more than this panel. A refused seat costs that one
26061
+ * surface, and the reason goes to the console rather than being swallowed.
26062
+ * @param slots - the client slot service.
26063
+ * @param name - the SlotMap key to fill.
26064
+ * @param register - the registration, run when the seat's declaration is live.
26065
+ */
26066
+ function seat(slots, name, register) {
26067
+ try {
26068
+ slots.inject(name, () => register());
26069
+ } catch (error) {
26070
+ console.error(`diff-approval could not fill ${name}:`, error);
26071
+ }
26072
+ }
26073
+ /**
24225
26074
  * The dsh web-react renderer gives `div[data-slot="sidebar.footer.action"]` an
24226
26075
  * inline `display: contents`, so every plugin's footer entry root participates
24227
26076
  * directly in the `.footerActions` flex row — several plugins (e.g. this one
@@ -24312,48 +26161,91 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
24312
26161
  ctx.on("connection/reset", () => {
24313
26162
  store.reset();
24314
26163
  });
24315
- ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
26164
+ const dock = createDockState();
26165
+ /** The face both the footer panel and the docked tab render with. */
26166
+ const buildFace = () => ({
26167
+ hooks: {
26168
+ pending: store,
26169
+ dock: dock.face.hooks.dock
26170
+ },
26171
+ onOpenDock: dock.face.open,
26172
+ onDockShowing: dock.face.setShowing,
26173
+ onDockClose: dock.face.setClose,
26174
+ closeDock: dock.face.close,
26175
+ onRefresh: (sessionId) => {
26176
+ currentSessionId = sessionId;
26177
+ store.refresh(sessionId);
26178
+ },
26179
+ onKeep: (sessionId, path, keepListed) => store.keep(sessionId, path, keepListed),
26180
+ onRevert: (sessionId, path, keepListed) => {
26181
+ const entry = store.getSnapshot().files.find((file) => file.id === path);
26182
+ const before = entry?.newText;
26183
+ const after = entry?.oldText;
26184
+ const filePath = entry?.path;
26185
+ return store.revert(sessionId, path, keepListed).then(() => {
26186
+ if (filePath !== void 0 && before !== void 0 && after !== void 0) remapFile(filePath, before, after);
26187
+ });
26188
+ },
26189
+ onBlockKeep: (sessionId, id, block, removeWhenResolved) => store.blockKeep(sessionId, id, block, removeWhenResolved),
26190
+ onBlockRevert: (sessionId, id, block, removeWhenResolved) => store.blockRevert(sessionId, id, block, removeWhenResolved),
26191
+ onOpen: (sessionId, id, action) => store.open(sessionId, id, action),
26192
+ onPreviewImage: (sessionId, path) => store.previewImage(sessionId, path),
26193
+ onUndo: (sessionId) => store.undo(sessionId),
26194
+ onRedo: (sessionId) => store.redo(sessionId),
26195
+ onImportVcs: (sessionId, includeUntracked) => store.importVcs(sessionId, includeUntracked),
26196
+ onRefreshVcs: (sessionId, id, includeUntracked) => store.refreshVcs(sessionId, id, includeUntracked),
26197
+ onBrowse: (sessionId, path) => store.browse(sessionId, path),
26198
+ onAddPath: (sessionId, path, includeUnchanged) => store.addPath(sessionId, path, includeUnchanged),
26199
+ onKeepAll: (sessionId) => store.keepAll(sessionId),
26200
+ onRevertAll: (sessionId) => store.revertAll(sessionId),
26201
+ onAckRedoCleared: () => store.clearRedoCleared(),
26202
+ onPasteReference: (sessionId, reference) => {
26203
+ conversationAccess(ctx, () => sessionId).appendDraft(reference);
26204
+ document.querySelector("[data-composer-input]")?.focus();
26205
+ },
26206
+ collapseSidebar
26207
+ });
26208
+ seat(ctx.slots, "sidebar.footer.action", () => ctx.slots.register({
24316
26209
  name: "sidebar.footer.action",
24317
26210
  id: "diff-approval-panel",
24318
26211
  locale: NS,
24319
- inject: () => ({
24320
- hooks: { pending: store },
24321
- onRefresh: (sessionId) => {
24322
- currentSessionId = sessionId;
24323
- store.refresh(sessionId);
24324
- },
24325
- onKeep: (sessionId, path, keepListed) => store.keep(sessionId, path, keepListed),
24326
- onRevert: (sessionId, path, keepListed) => {
24327
- const entry = store.getSnapshot().files.find((file) => file.id === path);
24328
- const before = entry?.newText;
24329
- const after = entry?.oldText;
24330
- const filePath = entry?.path;
24331
- return store.revert(sessionId, path, keepListed).then(() => {
24332
- if (filePath !== void 0 && before !== void 0 && after !== void 0) remapFile(filePath, before, after);
24333
- });
24334
- },
24335
- onBlockKeep: (sessionId, id, block, removeWhenResolved) => store.blockKeep(sessionId, id, block, removeWhenResolved),
24336
- onBlockRevert: (sessionId, id, block, removeWhenResolved) => store.blockRevert(sessionId, id, block, removeWhenResolved),
24337
- onOpen: (sessionId, id, action) => store.open(sessionId, id, action),
24338
- onPreviewImage: (sessionId, path) => store.previewImage(sessionId, path),
24339
- onUndo: (sessionId) => store.undo(sessionId),
24340
- onRedo: (sessionId) => store.redo(sessionId),
24341
- onImportVcs: (sessionId, includeUntracked) => store.importVcs(sessionId, includeUntracked),
24342
- onRefreshVcs: (sessionId, id, includeUntracked) => store.refreshVcs(sessionId, id, includeUntracked),
24343
- onBrowse: (sessionId, path) => store.browse(sessionId, path),
24344
- onAddPath: (sessionId, path, includeUnchanged) => store.addPath(sessionId, path, includeUnchanged),
24345
- onKeepAll: (sessionId) => store.keepAll(sessionId),
24346
- onRevertAll: (sessionId) => store.revertAll(sessionId),
24347
- onAckRedoCleared: () => store.clearRedoCleared(),
24348
- onPasteReference: (sessionId, reference) => {
24349
- conversationAccess(ctx, () => sessionId).appendDraft(reference);
24350
- document.querySelector("[data-composer-input]")?.focus();
24351
- },
24352
- collapseSidebar
24353
- })
24354
- }, PendingPanel));
26212
+ inject: buildFace
26213
+ }, PendingPanelEntry));
26214
+ const looseSlots = ctx.slots;
26215
+ try {
26216
+ looseSlots.inject("conversation.session.header.utilities", () => looseSlots.register({
26217
+ name: "conversation.session.header.utilities",
26218
+ id: "diff-approval-entry",
26219
+ order: -5,
26220
+ locale: NS,
26221
+ inject: () => ({ ...buildFace() })
26222
+ }, HeaderEntry));
26223
+ } catch {}
26224
+ try {
26225
+ looseSlots.inject("sidebar.right.pane.tab", () => looseSlots.register({
26226
+ name: "sidebar.right.pane.tab",
26227
+ key: DIFF_DOCK_ID,
26228
+ locale: NS,
26229
+ inject: () => ({
26230
+ ...buildFace(),
26231
+ docked: true
26232
+ })
26233
+ }, DiffDockBody));
26234
+ looseSlots.inject("sidebar.right.pane.tab.title", () => looseSlots.register({
26235
+ name: "sidebar.right.pane.tab.title",
26236
+ key: DIFF_DOCK_ID,
26237
+ locale: NS,
26238
+ inject: () => ({ ...buildFace() })
26239
+ }, DiffDockTitle));
26240
+ } catch {}
24355
26241
  if (typeof window !== "undefined") ctx.effect(() => startProducedDiffInjection(t("panel.viewDiff"), (path) => window.dispatchEvent(new CustomEvent(OPEN_FILE_EVENT, { detail: { path } }))), "diff-approval: produced-files diff buttons");
24356
- ctx.slots.inject("settings.section", () => ctx.slots.register({
26242
+ try {
26243
+ attachDiffDock(ctx, {
26244
+ title: () => t("panel.title"),
26245
+ guideDescription: () => t("panel.dockGuide")
26246
+ }, (sidebar, reason) => dock.attach(sidebar, reason));
26247
+ } catch {}
26248
+ seat(ctx.slots, "settings.section", () => ctx.slots.register({
24357
26249
  name: "settings.section",
24358
26250
  id: "diff-approval",
24359
26251
  order: 100,