dsh-vscode-mode 0.22.0 → 0.23.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/README.md +5 -3
- package/lib/client.js +3370 -85
- package/lib/client.js.map +2 -2
- package/lib/index.js +1167 -266
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +166 -0
- package/src/client/quickMenuHost.ts +46 -0
- package/src/client/quickMenuStore.ts +260 -0
- package/src/client/state/diffSummary.ts +9 -4
- package/src/client/styles/quickmenu.css +1049 -0
- package/src/client/ui/QuickMenuDialog.tsx +835 -0
- package/src/client/ui/QuickMenuLauncher.tsx +792 -0
- package/src/client/ui/QuickMenuPickers.ts +577 -0
- package/src/index.ts +2 -2
- package/src/paths.ts +10 -0
- package/src/pending/capture.ts +104 -75
- package/src/pending/persist.ts +6 -1
- package/src/pending/store.ts +10 -5
- package/src/pending/vcs.ts +2 -2
- package/src/pendingCompat.ts +3 -8
- package/src/pendingRpc.ts +140 -58
- package/src/quickMenu.ts +269 -0
- package/src/rpc.ts +37 -0
- package/src/shared/pathKey.ts +54 -0
- package/src/shared/quickMenu.ts +1009 -0
- package/src/shared/quickPick.ts +67 -0
- package/src/shared/rpc.ts +10 -0
- package/src/shared/skillCatalog.ts +60 -0
- package/src/skillCatalog.ts +138 -0
package/lib/client.js
CHANGED
|
@@ -55,8 +55,19 @@ window.__ModuleLoader__.load({
|
|
|
55
55
|
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
56
56
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
57
57
|
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
|
|
58
|
-
const css$3 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 主题:跟随官方 --dsw-alias-* 令牌(DSH 明暗与第三方皮肤生效)。\n 本作用域不再重定义官方令牌(旧实现曾在此硬覆盖浅色盘,导致插件不跟随 DSH 主题);\n 旧环境/缺令牌场景由每条声明的 var(--dsw-alias-*, 回落值) 兜底。\n @author ddj 2026-09-10 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; flex-shrink: 0; overscroll-behavior-x: contain; } /* overscroll-behavior-x: 横滚到边界不把滚动链传给页面(滚轮横滚接管更可控)@author ddj 2026年09月18号 */\n[data-edrv-view] .edrv-tab { user-select: none; -webkit-user-select: none; display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-star { color: var(--dsw-alias-brand-primary, #4f8cff); font-size: 13px; font-weight: 700; line-height: 1; flex-shrink: 0; } /* 未保存修改星号 @author ddj 2026年09月09号 */\n[data-edrv-view] .edrv-tab .edrv-tab-x { appearance: none; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 16px; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 4px; background: transparent; box-shadow: none; color: var(--dsw-alias-label-tertiary, #888); font-family: inherit; font-size: 11px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:focus-visible { outline: 1px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: 1px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.12)); color: var(--dsw-alias-label-primary, #eee); }\n/* 固定页签:📌 标记 + 免关闭按钮(固定页签不渲染 ×,仅可经右键菜单取消固定) */\n[data-edrv-view] .edrv-tab .edrv-tab-pin { flex-shrink: 0; font-size: 10px; line-height: 1; opacity: .85; }\n[data-edrv-view] .edrv-tab.edrv-tab-pinned { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,.04)); }\n/* 页签行 = 可横滚的页签区 + 行尾固定工具列(计划预览页签与磁盘页签同处这一行的滚动区)@author ddj 2026年10月20号 */\n[data-edrv-view] .edrv-tabs-row { display: flex; align-items: center; min-width: 0; flex: 0 0 auto; background: var(--dsw-alias-bg-layer-1, transparent); }\n/* 特异性需盖过下面 .edrv-group .edrv-tabs 的 flex: 0 0 auto,页签区才能吃掉行走宽、把工具列压到行尾 */\n[data-edrv-view] .edrv-group .edrv-tabs-row > .edrv-tabs { flex: 1 1 auto; min-width: 0; background: transparent; }\n[data-edrv-view] .edrv-tabs-tools { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; padding: 0 6px 0 2px; }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }\n/* 非焦点分组的静态路径条(GroupPathbar):可点击聚焦所属组,悬停给反馈 @author ddj 2026-10-16 */\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static] { cursor: pointer; }\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-pb-update { display: inline-flex; align-items: center; gap: 4px; height: 20px; flex: none; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 4px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; cursor: pointer; }\n[data-edrv-view] .edrv-pb-update:hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-pb-update:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 1px; }\n@media (max-width: 620px) { [data-edrv-view] .edrv-pb-update { width: 24px; padding: 0 4px; overflow: hidden; font-size: 0; } }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n/* 键盘高亮项(↑↓ 选择):与 hover 同特异性但声明在后,故选中态稳定可辨 @author ddj 2026年09月11号 */\n[data-edrv-view] .edrv-search-item.edrv-search-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.14)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n/* Ctrl+hover 可导航标识符下划线提示(绑定 Monaco inlineClassName 装饰) */\n[data-edrv-view] .edrv-nav-underline { text-decoration: underline; text-decoration-color: var(--dsw-alias-interactive-primary, #4fc1ff); text-underline-offset: 1px; cursor: pointer; }\n/* 跳转目标区域高亮(LSP 定义/引用、搜索命中落点):落点一眼可见,约 1.2s 后由 EditorView 自动清除。\n 用官方令牌并带回落值,不在插件作用域重定义 --dsw-* (否则永久脱离官方主题)。 */\n[data-edrv-view] .edrv-nav-target { background: var(--dsw-alias-interactive-bg-hover, rgba(79,193,255,.28)); border-radius: 2px; box-shadow: 0 0 0 1px var(--dsw-alias-interactive-primary, rgba(79,193,255,.55)); }\n[data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-chip-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }\n/* 行内和原生 Monaco diff 共用主题色盘;不再借用普通 hover 的灰底。 */\n[data-edrv-view] .edrv-mn-add-line { background: var(--edrv-diff-add, #e6f4e8); }\n[data-edrv-view] .edrv-mn-gutter-add { background: var(--edrv-diff-add, #e6f4e8); color: var(--dsw-alias-state-success-primary, #0f9d58) !important; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone, [data-edrv-view] .edrv-del-margin { background: var(--edrv-diff-del, #fbe7e3); color: var(--edrv-diff-fg, #1e1e1e); }\n[data-edrv-view] .edrv-del-row { white-space: pre; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-del-margin-row { position: relative; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-number { position: absolute; left: var(--edrv-number-left); width: var(--edrv-number-width); color: var(--edrv-diff-number, #6c6c6c); text-align: right; }\n[data-edrv-view] .edrv-del-marker { position: absolute; left: var(--edrv-marker-left); width: var(--edrv-marker-width); color: var(--dsw-alias-state-error-primary, #d9534f); text-align: center; }\n[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n[data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n/* 图片预览:居中交给子项 margin:auto(auto 外边距优先吃正向空闲空间;溢出时空闲为负 → 外边距归 0、\n flex-start 起点对齐 → 放大后四边均可滚动到达,规避 justify-content:center 双侧溢出不可达问题) */\n[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }\n[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; flex-shrink: 0; margin: auto; align-self: flex-start; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }\n[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); margin: auto; }\n[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }\n[data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }\n[data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }\n[data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }\n/* Markdown 预览(需求 4):工具条 + 居中阅读栏;正文排版由官方 MarkdownText 自带的 --dsw-* 样式负责 */\n[data-edrv-view] .edrv-mdview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-mdview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-mdview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-mdview-stage { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 20px 28px; }\n[data-edrv-view] .edrv-mdview-doc { max-width: 880px; margin: 0 auto; width: 100%; }\n[data-edrv-view] .edrv-mdview-fallback { font-size: 12px; line-height: 1.7; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-mdview-pre { margin: 8px 0 0; padding: 10px 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }\n[data-edrv-view] .edrv-pb-mdbtn { height: 20px; padding: 0 8px; font-size: 11px; flex-shrink: 0; }\n[data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-state-success-primary, #0f7a45); border: 1px solid var(--dsw-alias-state-success-primary, rgba(15,157,88,.4)); }\n[data-edrv-view] .edrv-pill-undo { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); color: var(--dsw-alias-state-error-primary, #c0392b); border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-pill-ghost { background: var(--dsw-alias-bg-layer-1, #ffffff); color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(页签右键菜单 + 文件树右键菜单共用)。\n 页签菜单 11 条 + 键位提示,故限高可滚(ContextMenu 以实测尺寸做 viewport clamp)。\n ⚠️ z-index 71 在既有浮层阶梯内(见文件末尾命令栏注释),不得上调越过命令栏/片段浮窗。 */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 220px; max-width: 360px; max-height: min(70vh, 520px); overflow-y: auto; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 菜单项文案(弹性占宽,超出省略)与右侧键位提示(不缩放,等宽字体) */\n[data-edrv-view] .edrv-ctxmenu-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-hint { flex: 0 0 auto; padding-left: 18px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-ctxmenu-item:disabled .edrv-ctxmenu-hint,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled .edrv-ctxmenu-hint { color: var(--dsw-alias-label-disabled, #b0b7c0); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-primary, rgba(217,119,6,.5)); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */\n[data-edrv-diff-dock-shell] {\n box-sizing: border-box;\n width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n margin: 0 auto;\n padding: 0;\n flex: none;\n border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);\n border-radius: 12px;\n background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));\n box-shadow: none;\n overflow: hidden;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock {\n box-sizing: border-box;\n position: relative;\n left: auto;\n right: auto;\n bottom: auto;\n z-index: auto;\n width: 100%;\n max-width: none;\n margin: 0;\n display: flex;\n flex-direction: column;\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-nav,\n[data-edrv-diff-dock-shell] .edrv-diffbar-files,\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }\n[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }\n@media (max-width: 620px) {\n [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }\n [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }\n}\n/* 侧栏差异栏在编辑内容底部居中悬浮,按内容宽度收缩;窄栏只在工具条内横滚。@author ddj 2026年09月29号 */\n[data-edrv-view] .edrv-side-diff-dock[data-edrv-diff-dock-shell] { position: absolute; left: 50%; bottom: 28px; z-index: 25; width: max-content; max-width: calc(100% - 20px); max-height: min(300px, calc(100% - 38px)); margin: 0; transform: translateX(-50%); border-radius: 12px; background: var(--dsw-alias-bg-overlay, #fff); box-shadow: 0 6px 20px rgba(0,0,0,.18); overflow-x: hidden; overflow-y: auto; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-row { padding-inline: 8px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-content { gap: 5px; width: max-content; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-nav,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-actions { gap: 3px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-pill { padding-inline: 5px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files .edrv-diffbar-file { min-width: 0; max-width: 72px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-details-toggle { width: 26px; min-width: 26px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-body { max-height: min(180px, 25vh); max-width: 100%; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diff-details { max-height: min(260px, 35vh); overflow-y: auto; }\n\n/* Official review: file context above, navigation/actions below. Theme-owned surfaces; no neon status outlines. @author ddj 2026年10月27号 */\n[data-edrv-diff-dock-shell] .edrv-reviewbar {\n width: 520px; max-width: 100%; min-width: 0; box-sizing: border-box;\n color: var(--dsw-alias-label-primary, #26313d); font-family: var(--dsw-font-family, sans-serif);\n font-size: 12px; line-height: 1.4; container: edrv-review / inline-size;\n}\n[data-edrv-diff-dock-shell] .edrv-review-global { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e5e9ef); }\n[data-edrv-diff-dock-shell] .edrv-review-all-actions { display: flex; align-items: center; gap: 4px; }\n[data-edrv-diff-dock-shell] .edrv-review-total strong { font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-review-heading {\n display: flex; align-items: center; gap: 8px; min-width: 0; padding: 10px 12px 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, #e5e9ef);\n}\n[data-edrv-diff-dock-shell] .edrv-review-filemark { display: inline-flex; flex: none; color: var(--dsw-alias-label-tertiary, #697586); }\n[data-edrv-diff-dock-shell] .edrv-review-filename { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-review-pending { flex: none; display: inline-flex; gap: 5px; align-items: center; color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; white-space: nowrap; }\n[data-edrv-diff-dock-shell] .edrv-review-pending strong { font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-primary, #26313d); }\n[data-edrv-diff-dock-shell] .edrv-review-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 7px 8px; }\n[data-edrv-diff-dock-shell] .edrv-review-nav { display: flex; align-items: center; flex: none; gap: 1px; }\n[data-edrv-diff-dock-shell] .edrv-review-blocknav { border-left: 1px solid var(--dsw-alias-border-l1, #e5e9ef); padding-left: 6px; }\n[data-edrv-diff-dock-shell] .edrv-review-position { min-width: 32px; text-align: center; font-size: 11px; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #697586); }\n[data-edrv-diff-dock-shell] .edrv-review-actions { display: flex; align-items: center; flex: none; gap: 4px; margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button { height: 28px; min-height: 28px; padding: 0 8px; border-radius: 6px; font-family: inherit; font-size: 12px; font-weight: 500; white-space: nowrap; gap: 5px; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-icon { width: 28px; min-width: 28px; padding: 0; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-record { padding: 0 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #697586); }\n[data-edrv-diff-dock-shell] .edrv-review-record svg { width: 12px; height: 12px; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-keep { background: var(--dsw-alias-brand-primary, #3b63d9); color: var(--dsw-alias-label-primary-inverted, #fff); border: 1px solid transparent; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-keep:hover:not(:disabled) { filter: brightness(.94); }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-undo:hover:not(:disabled) { color: var(--dsw-alias-state-error-primary, #b64043); }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button:disabled { opacity: .4; cursor: not-allowed; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #3b63d9); outline-offset: 2px; }\n[data-edrv-diff-dock-shell] .edrv-review-notice { padding: 7px 12px; border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; overflow-wrap: anywhere; }\n[data-edrv-diff-dock-shell] .edrv-review-error { color: var(--dsw-alias-state-error-primary, #b64043); }\n[data-edrv-diff-dock-shell] .edrv-review-notice-warn { color: var(--dsw-alias-state-warning-primary, #a86a1f); }\n[data-edrv-diff-dock-shell] .edrv-review-source { flex: none; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-secondary, #d4dae3); color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; line-height: 16px; white-space: nowrap; }\n[data-edrv-diff-dock-shell] .edrv-sidecar-down { display: flex; align-items: center; gap: 8px; }\n[data-edrv-diff-dock-shell] .edrv-sidecar-down-text { flex: 1; min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-review-body { border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); max-height: min(240px, 30vh); overflow: auto; padding: 8px 12px; }\n@container edrv-review (max-width: 420px) {\n .edrv-reviewbar .edrv-review-heading { gap: 6px; padding-inline: 10px; }\n .edrv-reviewbar .edrv-review-actions { justify-content: flex-end; flex: 1 0 100%; border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); padding-top: 6px; }\n}\n\n/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\n[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col > .edrv-pathbar,\n[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }\n[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 1; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }\n/* 树行图标:官方原语(文件夹/文件类型),尺寸与行高对齐 @author ddj 2026-09-10 */\n[data-edrv-view] .edrv-tree-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active .edrv-tree-icon { color: var(--dsw-alias-label-secondary, #52606d); }\n/* SVN overlays: official file icon remains visible, independent local/remote signs. ddj 2026-10-09 */\n[data-edrv-view] .edrv-tree-iconbox { position: relative; display: inline-flex; width: 20px; height: 20px; align-items: center; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-overlay { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-svn-local { position: absolute; right: -1px; bottom: -2px; display: flex; border-radius: 50%; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-svn-icon-clean { color: #16853b; }\n[data-edrv-view] .edrv-svn-icon-modified, [data-edrv-view] .edrv-svn-icon-deleted { color: #d12e35; }\n[data-edrv-view] .edrv-svn-icon-conflict { color: #c27300; }\n[data-edrv-view] .edrv-svn-icon-added { color: #2465c9; }\n[data-edrv-view] .edrv-svn-icon-unversioned, [data-edrv-view] .edrv-svn-icon-ignored, [data-edrv-view] .edrv-svn-icon-external { color: #75808c; }\n[data-edrv-view] .edrv-svn-remote { position: absolute; right: -3px; top: -4px; color: #2465c9; background: var(--dsw-alias-bg-layer-1, #fff); border-radius: 3px; font-size: 11px; font-weight: 900; line-height: 12px; }\n[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n/* Virtual rows and placeholders share the same geometry as treeWindow.ts. @author ddj 2026年10月22号 */\n[data-edrv-view] .edrv-tree.edrv-tree-virtual { padding-top: 0; padding-bottom: 0; }\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-row,\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-loading { height: 24px; min-height: 24px; box-sizing: border-box; }\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-loading { line-height: 24px; }\n[data-edrv-view] .edrv-large-text { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-large-text-toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }\n[data-edrv-view] .edrv-large-text-content { margin: 0; flex: 1; min-height: 0; overflow: auto; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); user-select: text; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n/* 「在资源管理器视图中显示」的定位高亮(短时淡出,区别于常驻的 active 态) */\n[data-edrv-view] .edrv-tree-row.edrv-tree-reveal { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.18)); animation: edrv-tree-reveal-fade 2s ease-out; }\n@keyframes edrv-tree-reveal-fade { from { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.32)); } to { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.18)); } }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }\n[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n/* SVN 状态独立占位:换行不覆盖文件树,长错误在栏内滚动。@author ddj 2026-10-12 */\n[data-edrv-view] .edrv-tree-status { flex: 0 0 auto; min-width: 0; max-height: 96px; overflow: auto; padding: 4px 8px; box-sizing: border-box; line-height: 18px; white-space: normal; overflow-wrap: anywhere; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== SVN 变更面板(工作副本状态列表;颜色分级对齐 TortoiseSVN 口径) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svn-toolbar { display: flex; align-items: center; gap: 12px; padding: 4px 10px 6px 12px; flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svn-check { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-check input { margin: 0; cursor: pointer; }\n[data-edrv-view] .edrv-svn-bulk { display: flex; align-items: center; gap: 6px; padding: 0 10px 6px 12px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-count { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* AI 助手 dock(12-panel-ai-bar 设计定稿):manifest 分带之 assistant 带,accent 左竖轨为唯一记忆点。\n AI 线程色三处同轨:竖轨 / dock 按钮描边 / 弹窗进度条(--dsw-alias-state-info-primary)。 @author ddj 2026-09-23 */\n[data-edrv-view] .edrv-svn-ai-bar { position: relative; display: flex; align-items: center; gap: 8px; padding: 6px 10px 6px 14px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-ai-rail { position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-ai-label { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svn-ai-btn { border-color: var(--dsw-alias-state-info-primary, #2f6fed); color: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-ai-btn:disabled { border-color: var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 状态位定宽右对齐(就绪/分析中…/等待助手投递…互换不跳版) */\n[data-edrv-view] .edrv-svn-ai-status { flex-shrink: 0; min-width: 88px; text-align: right; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n/* 批量条语义分隔(导出簇 ┊ 动作簇之间 1px 竖线) */\n[data-edrv-view] .edrv-svn-sep { width: 1px; height: 14px; background: var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n/* 头部图标组(检查远端/刷新成组,gap 收紧) */\n[data-edrv-view] .edrv-svn-head-icons { display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-list { flex: 1; min-height: 0; overflow: auto; padding: 2px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-svn-group { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; margin: 2px 0; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; font-weight: 600; cursor: pointer; user-select: none; }\n[data-edrv-view] .edrv-svn-group:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svn-fold-ch { width: 10px; flex-shrink: 0; text-align: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svn-group-n { margin-left: auto; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-weight: 400; }\n[data-edrv-view] .edrv-svn-row { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svn-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-row:hover .edrv-svn-acts { display: inline-flex; }\n[data-edrv-view] .edrv-svn-act { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; padding: 1px 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 14px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-svn-act-danger { color: var(--dsw-alias-state-error-primary, #d9534f); border-color: var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-svn-ch { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-svn-ch-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* .edrv-svn-ch-* 动作词表补全:replaced(替换)= 琥珀;侧栏状态分级(SVN_STATUS_TONE)不受影响 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svn-ch-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); }\n/* 文件树行尾 SVN 状态徽标(与差异角标并列;方形字母,区别圆形差异计数) */\n[data-edrv-view] .edrv-tree-svn { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* ===== SVN 基线差异视图(BASE 左 / 工作区右,只读并排) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svndiff { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-svndiff-bar { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svndiff-title { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-svndiff-tag { padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svndiff-host { flex: 1 1 auto; min-height: 0; }\n[data-edrv-view] .edrv-svndiff-empty { display: flex; flex: 1 1 auto; align-items: center; justify-content: center; padding: 24px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 12px; text-align: center; }\n/* 差异整行色块与行首 +/- 指示符:vendored Monaco(diffEditorWidget2)静态 CSS 无\n .line-insert/.line-delete 背景规则,整行色块靠主题键 + 此处固定色兜底保证可见。\n 注意不要用 --dsw-alias-interactive-bg-hover 类 token:实测为 92% 透明中性色,肉眼不可见。\n 固定色取自研差异 UI 同色相(绿 #0f9d58 / 红 #d9534f),alpha 提到肉眼可辨。 */\n[data-edrv-view] .monaco-editor .line-insert { background: rgba(15, 157, 88, 0.18); }\n[data-edrv-view] .monaco-editor .line-delete { background: rgba(217, 83, 79, 0.16); }\n[data-edrv-view] .monaco-editor .gutter-insert { background: rgba(15, 157, 88, 0.35); }\n[data-edrv-view] .monaco-editor .gutter-delete { background: rgba(217, 83, 79, 0.35); }\n[data-edrv-view] .monaco-editor .insert-sign { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .monaco-editor .delete-sign { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* 差异条快速跳转簇(对齐自研审查条 ↑ i/N ↓ 的形态) */\n[data-edrv-view] .edrv-svndiff-nav { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svndiff-nav-count { min-width: 36px; text-align: center; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n/* W1-3 差异统计徽标(+新增/修改 −删除/修改;色相与行块绿红一致) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svndiff-stat { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svndiff-stat-add { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-svndiff-stat-del { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W1-1 差异视图选项条(右端开关组;复用 .edrv-svn-check 的 label 形态) */\n[data-edrv-view] .edrv-svndiff-opts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n/* W1-2 变更面板名称过滤输入框(工具条行内,紧凑) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-name-filter { width: 168px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; line-height: 14px; }\n[data-edrv-view] .edrv-svn-name-filter:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-svn-name-filter::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* W2-3 远端更新提示条(工具条上方;warn = 失败降级) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-hintbar { display: flex; align-items: center; gap: 6px; margin: 0 10px 4px 12px; padding: 3px 8px; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-hintbar-warn { background: rgba(217, 83, 79, 0.12); color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W2-5 疑似改名标记(行内 ⇄;hover 见 tooltip) */\n[data-edrv-view] .edrv-svn-pair-mark { flex-shrink: 0; color: var(--dsw-alias-state-warn-primary, #7c3aed); font-size: 11px; cursor: help; }\n/* W2-1 补丁对话框 / W2-2 目录对比对话框 @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-patch-dialog { max-width: calc(100vw - 48px); }\n[data-edrv-view] .edrv-svn-patch-head { display: flex; align-items: center; gap: 8px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-patch-notes { display: flex; flex-direction: column; gap: 4px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svn-patch-warn { color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-svn-patch-pre { max-height: 46vh; overflow: auto; margin: 0 0 8px; padding: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.5; white-space: pre; user-select: text; }\n[data-edrv-view] .edrv-svn-patch-foot { display: flex; align-items: center; gap: 8px; }\n/* AI 智能整理弹窗(11-ai-changelist-triage):预览三分组卡 + 执行进度 @author ddj 2026-09-23 */\n[data-edrv-view] .edrv-svnplan-dialog { max-width: calc(100vw - 48px); }\n[data-edrv-view] .edrv-svnplan-head { display: flex; align-items: center; gap: 8px; padding-bottom: 4px; }\n[data-edrv-view] .edrv-svnplan-meta { display: flex; gap: 10px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svnplan-note { color: var(--dsw-alias-state-warn-primary, #b7791f); font-size: 10px; }\n[data-edrv-view] .edrv-svnplan-sec { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 6px 8px; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnplan-sechead { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnplan-sectitle { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-list { max-height: 26vh; overflow: auto; padding-left: 18px; }\n[data-edrv-view] .edrv-svnplan-card { border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); padding: 3px 0; }\n[data-edrv-view] .edrv-svnplan-row { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnplan-item { display: flex; align-items: center; gap: 6px; padding-left: 18px; }\n[data-edrv-view] .edrv-svnplan-groupname { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46ch; }\n[data-edrv-view] .edrv-svnplan-reason { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30ch; }\n[data-edrv-view] .edrv-svnplan-stage { display: flex; align-items: center; gap: 8px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svnplan-badge { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; padding: 1px 8px; font-size: 10px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-alias-bg-layer-2, #f7f9f9); white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-badge-cur { font-family: var(--ds-font-family-code, ui-monospace, monospace); overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnplan-bar { height: 6px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnplan-bar-in { height: 100%; background: var(--dsw-alias-state-info-primary, #2f6fed); transition: width .2s; }\n[data-edrv-view] .edrv-svnplan-logwrap { max-height: 40vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 6px 8px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); }\n[data-edrv-view] .edrv-svnplan-log { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #52606d); user-select: text; }\n[data-edrv-view] .edrv-svnplan-log-bad { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-svnplan-foot { display: flex; align-items: center; gap: 8px; }\n[data-edrv-view] .edrv-svnplan-primary { font-weight: 600; border-color: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-sum-form { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-sum-list { max-height: 44vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 2px; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnlog-path-kind { flex-shrink: 0; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; }\n\n/* ===== 通用弹窗外壳(ModalShell)+ SVN 日志弹窗 @author ddj 2026-09-16 ===== */\n/* z-index 190:落在既有阶梯内(命令栏 200 > 片段二级 197 > 片段一级 195 > 190),\n 不遮挡命令栏;既有浮层数值不受影响(见 tests/overlayZOrder.test.ts)。 */\n[data-edrv-view] .edrv-modal-mask { position: fixed; inset: 0; z-index: 190; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, #000000aa); }\n[data-edrv-view] .edrv-svnlog-dialog { position: relative; display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-head { display: flex; align-items: center; gap: 10px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-title { margin: 0; font-size: 16px; font-weight: 650; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n/* 头部目标路径 = 代码 chip(mono + 底色 + 全圆角;补丁/目录对比弹窗共用同视觉) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-target { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n/* 关闭按钮图标化(透明底,hover 显形;dialog 前缀提高特异性,压过底部按钮统一尺寸) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act { border-color: transparent; background: transparent; width: 24px; height: 24px; padding: 0; font-size: 12px; line-height: 1; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act:hover { border-color: var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n/* 过滤栏:浅色表面条 + 统一 24px 控件 + 焦点边框变色 + 日期组靠右 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-filter { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-field { flex: 0 0 auto; height: 24px; box-sizing: border-box; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-search { min-width: 160px; flex: 1 1 220px; height: 24px; box-sizing: border-box; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-field:focus, [data-edrv-view] .edrv-svnlog-search:focus, [data-edrv-view] .edrv-svnlog-date:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-dates { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }\n[data-edrv-view] .edrv-svnlog-date { height: 24px; box-sizing: border-box; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-check { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-check input[type=\"checkbox\"] { margin: 0; cursor: pointer; accent-color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-error { padding: 4px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* P0-14 上中下三段全宽:body 纵向铺满弹窗(外框高度由拖拽受控) */\n[data-edrv-view] .edrv-svnlog-body { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-height: 0; }\n/* 上段修订列表(13-log-ui-polish 变更台账):\n 1px 发丝边框 + 底色面 + 左侧时间脊线(每行 ::before 画节点圆点、行盒左缘 ::after 连线段,\n 行行相接即整列脊线;选中行节点实心着色 + 2px 左强调条)。列宽由 cell 类与 nth-child 对齐(P0-12/D30)。 */\n[data-edrv-view] .edrv-svnlog-list { position: relative; flex: 0 0 auto; display: flex; flex-direction: column; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 0 4px 4px; background: var(--dsw-alias-bg-layer-1, #fff); }\n/* 列头 sticky:滚动常驻 + 实底防透;z-index 20 低于一切浮层(190 起),仅在列表滚动容器内生效 */\n[data-edrv-view] .edrv-svnlog-cols { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(1) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(2) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(3) { width: 88px; flex: none; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(4) { width: 82px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(5) { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-col-sortable { cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-col-sortable:hover { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-col-on { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-sort-reset { flex: none; }\n/* 行:position:relative 承载脊线节点(::before)与连线段(::after);选中 = 强调条 + 节点实心 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { position: relative; display: flex; align-items: center; gap: 6px; padding: 3px 6px 3px 16px; border-radius: 6px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-item::before { content: ''; position: absolute; left: 6px; top: 50%; transform: translateY(-50%); width: 7px; height: 7px; box-sizing: border-box; border-radius: 50%; border: 1.5px solid var(--dsw-alias-border-l2, #cbd2d9); background: var(--dsw-alias-bg-layer-1, #fff); z-index: 1; }\n[data-edrv-view] .edrv-svnlog-item::after { content: ''; position: absolute; left: 9px; top: 0; bottom: 0; width: 1px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-item:last-child::after { bottom: 50%; }\n[data-edrv-view] .edrv-svnlog-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on::before { border-color: var(--dsw-alias-brand-primary, #2563eb); background: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-cell-msg,\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-rev { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-cell { overflow: hidden; text-overflow: ellipsis; font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-cell-rev { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-act { width: 52px; flex: none; display: inline-flex; gap: 3px; }\n[data-edrv-view] .edrv-svnlog-cell-author { width: 88px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-date { width: 82px; flex: none; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-cell-msg { flex: 1 1 auto; }\n[data-edrv-view] .edrv-svnlog-rev { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-weight: 700; color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-badge { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; }\n/* 动作语义色(13-log-ui-polish 归位):A=success 绿 · D=error 红 · M=brand 蓝 · R=warn 琥珀 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-badge-modified { color: var(--dsw-alias-state-info-primary, #2f6fed); background: rgba(47,111,237,.14); }\n[data-edrv-view] .edrv-svnlog-badge-added { color: var(--dsw-alias-state-success-primary, #0f9d58); background: rgba(15,157,88,.14); }\n[data-edrv-view] .edrv-svnlog-badge-deleted { color: var(--dsw-alias-state-error-primary, #d9534f); background: rgba(217,83,79,.14); }\n[data-edrv-view] .edrv-svnlog-badge-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); background: rgba(217,119,6,.16); }\n[data-edrv-view] .edrv-svnlog-badge-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-more { padding: 6px 4px 2px; text-align: center; }\n/* P0-14 三段 pane 公共外观(13-log-ui-polish:2px 粗框 → 1px 发丝线 + 面板底色) */\n[data-edrv-view] .edrv-svnlog-pane { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 8px 10px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-svnlog-msgpane { flex: 0 0 auto; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-svnlog-pathspane { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }\n/* P0-13/P0-14:分段横向拖拽分隔条 + 变更路径独立滚动容器 */\n[data-edrv-view] .edrv-svnlog-hsplit { flex: 0 0 6px; cursor: row-resize; margin: 2px 0; border-radius: 3px; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-hsplit:hover, [data-edrv-view] .edrv-svnlog-hsplit:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.28)); }\n[data-edrv-view] .edrv-svnlog-paths-wrap { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; }\n/* 提交信息 meta 行:rev/日期 chip 化,author 常规字重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-rev { padding: 1px 8px; border-radius: 999px; background: var(--dsw-alias-state-info-primary, #2f6fed); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-date { height: auto; padding: 1px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-date { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 正文块:左侧品牌强调线 +1.7 行高(长提交信息可读性) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-msgpane pre.edrv-svnlog-body { margin: 8px 0 0; padding: 8px 10px; border-radius: 0 6px 6px 0; border-left: 2px solid var(--dsw-alias-state-info-primary, #2f6fed); background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11.5px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; overflow: auto; flex: 1 1 auto; min-height: 0; }\n/* P0-14 外框尺寸手柄(13-log-ui-polish 增量3 八向:n/s/w/e 四边 + nw/ne/sw/se 四角);\n 热区避开内边距带(边8px/角14px 落在卡片22/24px padding 内不遮内容),角手柄后渲染盖过相邻边2px overlap。\n chevron 角标四向各自朝向,hover 显形。 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-grip { position: absolute; z-index: 2; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-grip-n { top: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-s { bottom: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-w { left: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-e { right: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-nw { left: 0; top: 0; width: 14px; height: 14px; cursor: nwse-resize; }\n[data-edrv-view] .edrv-svnlog-grip-ne { right: 0; top: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-sw { left: 0; bottom: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-se { right: 0; bottom: 0; width: 14px; height: 14px; cursor: nwse-resize; background: transparent; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after,\n[data-edrv-view] .edrv-svnlog-grip-ne::after,\n[data-edrv-view] .edrv-svnlog-grip-sw::after,\n[data-edrv-view] .edrv-svnlog-grip-se::after { content: ''; position: absolute; width: 6px; height: 6px; opacity: .5; border-color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-grip-se::after { right: 3px; bottom: 3px; border-right: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-sw::after { left: 3px; bottom: 3px; border-left: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-ne::after { right: 3px; top: 3px; border-right: 1.5px solid; border-top: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after { left: 3px; top: 3px; border-left: 1.5px solid; border-top: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip:hover, [data-edrv-view] .edrv-svnlog-grip:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.35)); }\n[data-edrv-view] .edrv-svnlog-grip:hover::after, [data-edrv-view] .edrv-svnlog-grip:active::after { opacity: 1; border-color: var(--dsw-alias-label-primary, #1f2933); }\n/* 行反馈微动效(仅 hover/选中背景过渡;用户要求减少动效时关闭) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { transition: background-color .12s ease, box-shadow .12s ease; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-svnlog-item { transition: none; }\n}\n/*13-log-ui-polish 增量:行容器可滚动(pane 仍 overflow:hidden 裁圆角),列头不被压缩 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-paths-head { display: flex; align-items: center; gap: 6px; flex: none; font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); margin-bottom: 4px; padding-bottom: 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-hint { font-weight: 400; letter-spacing: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-paths { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-svnlog-path { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: default; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-path:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-pathtext,\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-copyfrom { opacity: .45; }\n[data-edrv-view] .edrv-svnlog-pathtext { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n/* 路径两段着色:目录段弱化 + 文件名加重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-pdir { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-pbase { color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-svnlog-copyfrom { flex: 0 1 160px; overflow: hidden; text-overflow: ellipsis; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-svnlog-kind { font-size: 10px; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-path:hover .edrv-svnlog-acts { display: inline-flex; }\n/* 底部:状态行与按钮组分层 + 组间发丝线(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-footwrap { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; padding-top: 6px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-btns { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup { display: inline-flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup + .edrv-svnlog-btngroup { margin-left: 6px; padding-left: 10px; border-left: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act { font-size: 11px; padding: 2px 9px; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2563eb); outline-offset: 1px; }\n/* ===== SVN 日志统计窗(P1-10)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-stats { display: flex; flex-direction: column; max-height: 82vh; }\n[data-edrv-view] .edrv-svnlog-stats-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px 0; }\n[data-edrv-view] .edrv-svnlog-stats-body { display: flex; flex-direction: column; gap: 14px; padding: 10px 16px 16px; overflow: auto; }\n[data-edrv-view] .edrv-svnlog-stat-line { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-block { display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-stat-label { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-duo { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-stat-hist { flex: 1 1 260px; display: flex; flex-direction: column; gap: 4px; min-width: 220px; }\n[data-edrv-view] .edrv-svnlog-stat-row { display: flex; align-items: center; gap: 8px; font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-stat-name { width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-barwrap { flex: 1; height: 12px; border-radius: 6px; background: rgba(127,127,127,.15); overflow: hidden; }\n[data-edrv-view] .edrv-svnlog-stat-bar { display: block; height: 100%; border-radius: 6px; }\n[data-edrv-view] .edrv-svnlog-stat-count { width: 36px; text-align: right; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-pie { width: 120px; height: 120px; flex-shrink: 0; border-radius: 50%; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-stat-days { display: flex; align-items: flex-end; gap: 2px; height: 120px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow-x: auto; }\n[data-edrv-view] .edrv-svnlog-stat-day { flex: 1 0 6px; min-width: 6px; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; }\n[data-edrv-view] .edrv-svnlog-stat-seg { display: block; width: 100%; }\n/* ===== SVN 日志区间弹窗(P1-6)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-range-body { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px 16px; }\n[data-edrv-view] .edrv-svnlog-range-row { display: flex; gap: 14px; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-range-input { width: 110px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-range-btns { display: flex; gap: 8px; justify-content: flex-end; }\n/* SVN 日志 WC 版号行加粗(P1-9) */\n[data-edrv-view] .edrv-svnlog-item-wc { font-weight: 700; }\n/* SVN 日志 merged 行(P1-5):灰字 + 缩进(缩进量按层级内联),前置「↳」标记 */\n[data-edrv-view] .edrv-svnlog-item-merged { color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-merge-mark { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-cell-msg { font-style: italic; }\n/* ===== 诊断日志弹窗(LogDialog)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-dlog-dialog { display: flex; flex-direction: column; gap: 8px; max-height: 88vh; }\n[data-edrv-view] .edrv-dlog-head { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-dlog-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-file { max-width: 200px; padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-level { padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-search { min-width: 120px; flex: 0 1 200px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-auto { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-dlog-error { padding: 4px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-scroll { flex: 1 1 auto; min-height: 240px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 4px 0; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-dlog-row { display: flex; align-items: baseline; gap: 8px; padding: 1px 8px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.55; }\n[data-edrv-view] .edrv-dlog-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-dlog-at { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-lvl { flex-shrink: 0; width: 26px; text-align: center; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-text { flex: 1; min-width: 0; white-space: pre-wrap; word-break: break-word; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dlog-row-error .edrv-dlog-text { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-dlog-row-warn .edrv-dlog-text { color: var(--dsw-alias-state-warning-primary, #b45309); }\n[data-edrv-view] .edrv-dlog-row-debug .edrv-dlog-text { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-dlog-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-stat { white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-switch { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; flex-shrink: 0; }\n/* 原生 SVN 更新窗口:逐条状态与路径是主信息;跟随 DSH 主题令牌。 */\n[data-edrv-view] .edrv-update-dialog { display: flex; flex-direction: column; padding: 0; border-radius: 8px; overflow: hidden; background: var(--dsw-alias-bg-overlay, #fff); }\n[data-edrv-view] .edrv-update-head { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 0 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-heading { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }\n[data-edrv-view] .edrv-update-heading h3 { margin: 0; flex: none; font-size: 15px; }\n[data-edrv-view] .edrv-update-target { min-width: 0; color: var(--dsw-alias-label-tertiary, #65737c); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }\n[data-edrv-view] .edrv-update-phase { flex: none; font-size: 12px; color: var(--dsw-alias-label-secondary, #485762); }\n[data-edrv-view] .edrv-update-close { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border: 0; border-radius: 4px; background: none; color: var(--dsw-alias-label-secondary, #485762); cursor: pointer; }\n[data-edrv-view] .edrv-update-close:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-update-close:not(:disabled):hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-update-table { display: flex; flex-direction: column; flex: 1; min-height: 0; margin: 12px 14px 0; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 5px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { display: grid; grid-template-columns: 110px minmax(0, 1fr) 160px; align-items: center; }\n[data-edrv-view] .edrv-update-sort { width: 100%; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }\n[data-edrv-view] .edrv-update-sort:hover { color: var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-sort:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-jump { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-cols { flex: none; height: 30px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; font-weight: 600; }\n[data-edrv-view] .edrv-update-cols > span, [data-edrv-view] .edrv-update-row > span { min-width: 0; padding: 0 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-update-cols > span + span, [data-edrv-view] .edrv-update-row > span + span { border-left: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-list { position: relative; flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; }\n[data-edrv-view] .edrv-update-space { position: relative; }\n[data-edrv-view] .edrv-update-pos { position: absolute; left: 0; right: 0; }\n[data-edrv-view] .edrv-update-row { height: 100%; font-size: 11px; cursor: default; color: var(--dsw-alias-label-primary, #202932); }\n[data-edrv-view] .edrv-update-row:hover { background: var(--dsw-alias-interactive-bg-hover, #ecf3f9); }\n[data-edrv-view] .edrv-update-row.is-selected { background: var(--dsw-alias-interactive-bg-hover, #dceaf9); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-action { font-weight: 600; }\n[data-edrv-view] .edrv-update-action.tone-added { color: var(--dsw-alias-state-success-primary, #23704e); }\n[data-edrv-view] .edrv-update-action.tone-deleted, [data-edrv-view] .edrv-update-action.tone-conflict { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-action.tone-merged { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-action.tone-updated { color: var(--dsw-alias-state-info-primary, #2869b4); }\n[data-edrv-view] .edrv-update-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-update-type { color: var(--dsw-alias-label-tertiary, #65737c); }\n[data-edrv-view] .edrv-update-empty { position: absolute; top: 50%; left: 0; right: 0; transform: translateY(-50%); text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #65737c); pointer-events: none; }\n[data-edrv-view] .edrv-update-telemetry { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px; padding: 10px 15px; min-height: 43px; box-sizing: border-box; font-size: 11px; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-error { color: var(--dsw-alias-state-error-primary, #b93b43); overflow-wrap: anywhere; }\n[data-edrv-view] .edrv-update-warning { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-locked { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot .edrv-update-cleanup:not(:disabled) { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--dsw-alias-border-l1, #d9e0e4); font-size: 11px; }\n[data-edrv-view] .edrv-update-counts { flex: 1; min-width: 0; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act { flex: none; min-width: 74px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:focus-visible, [data-edrv-view] .edrv-update-close:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-update-raw { max-height: 120px; overflow: auto; padding: 0 14px 10px; font-size: 11px; }\n[data-edrv-view] .edrv-update-raw pre { white-space: pre-wrap; overflow-wrap: anywhere; margin: 5px 0; }\n@media (max-width: 620px) {\n [data-edrv-view] .edrv-update-head { gap: 6px; padding: 0 10px; }\n [data-edrv-view] .edrv-update-table { margin: 8px 8px 0; }\n [data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { grid-template-columns: 76px minmax(0, 1fr); }\n [data-edrv-view] .edrv-update-cols > span:nth-child(3), [data-edrv-view] .edrv-update-row > span:nth-child(3) { display: none; }\n [data-edrv-view] .edrv-update-path { white-space: normal !important; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }\n [data-edrv-view] .edrv-update-foot { flex-wrap: wrap; padding: 8px; }\n [data-edrv-view] .edrv-update-counts { flex-basis: 100%; }\n}\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; height: 26px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: var(--dsw-alias-label-primary-inverted, #1f2933); border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-warn-primary, rgba(240,173,78,.45)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(240,173,78,.12)); color: var(--dsw-alias-state-warn-primary, #b8860b); font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* ===== rules panel (sidebar, Codebuddy rules view) ===== */\n[data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }\n[data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-rules-row { display: flex; align-items: center; gap: 4px; padding: 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; }\n[data-edrv-view] .edrv-rules-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-row.off .edrv-rules-main { opacity: .5; }\n[data-edrv-view] .edrv-rules-main { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-rules-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sublabel { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }\n[data-edrv-view] .edrv-rules-type-always { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.15)); color: var(--dsw-alias-state-success-primary, #0b7c46); }\n[data-edrv-view] .edrv-rules-type-auto { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(79,140,255,.16)); color: var(--dsw-alias-link, #3367c9); }\n[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }\n[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }\n[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: var(--dsw-alias-border-l2, #b9c4cc); cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }\n[data-edrv-view] .edrv-rules-switch.on { background: var(--dsw-alias-brand-primary, #7c5cff); }\n[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--dsw-alias-bg-layer-1, #fff); transition: left .15s; }\n[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }\n[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }\n[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }\n[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-editor-ctl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-ctl-label { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select { height: 24px; padding: 0 4px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-desc-input { flex: 1; min-width: 0; }\n[data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }\n\n/* 命令栏(Ctrl+Shift+P / F1):body 级 portal 浮层,故不依赖编辑区几何。\n ⚠️ 浮层 z-index 阶梯(自下而上;改动务必保持顺序,tests/overlayZOrder.test.ts 守约):\n 编辑器内浮层 60(.edrv-search-pop) / 71(.edrv-ctxmenu)\n < 190 命令栏遮罩 < 200 命令栏\n < 195 片段浮窗遮罩 < 197 片段二级弹窗遮罩\n 命令栏必须**高于**片段浮窗:曾因片段遮罩(192) 压过命令栏(191),导致片段浮窗打开时\n 命令栏被整体盖住,按 Ctrl+Shift+P 因「已打开即早退」而毫无反应(表现为快捷键失效)。 */\n[data-edrv-view] .edrv-palette-mask { position: fixed; inset: 0; z-index: 190; background: rgba(0, 0, 0, .28); }\n[data-edrv-view] .edrv-palette { position: fixed; top: 12vh; left: 50%; transform: translateX(-50%); z-index: 200; width: min(580px, calc(100vw - 32px)); max-height: 64vh; display: flex; flex-direction: column; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 12px 34px rgba(31, 41, 51, .24); overflow: hidden; }\n[data-edrv-view] .edrv-palette-input { height: 38px; flex: 0 0 auto; padding: 0 12px; border: none; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: transparent; color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; outline: none; }\n[data-edrv-view] .edrv-palette-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 4px; }\n[data-edrv-view] .edrv-palette-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; }\n[data-edrv-view] .edrv-palette-row.edrv-palette-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 157, 88, .10)); }\n[data-edrv-view] .edrv-palette-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-palette-cat { flex: 0 0 auto; font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-palette-key { flex: 0 0 auto; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 10px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-palette-empty { padding: 14px 10px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #7b8794); text-align: center; }\n[data-edrv-view] .edrv-palette-notice { flex: 0 0 auto; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-state-warn-primary, #b7791f); }\n\n/* 代码片段选择器(配置 / 插入):居中浮窗 + 新建二级弹窗。\n 卡片复用 mcp.css 的 .vsm-mcp-dialog(与「添加 MCP」「工作区选择」同一视觉语言),\n 此处只补遮罩、尺寸与列表布局。⚠️ 根节点须带 data-edrv-view,否则这些规则全部失配。 */\n[data-edrv-view] .edrv-snip-mask { position: fixed; inset: 0; z-index: 195; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, rgba(0, 0, 0, .55)); }\n/* 二级弹窗叠在一级浮窗之上(仍在命令栏之下,见上方阶梯说明) */\n[data-edrv-view] .edrv-snip-mask-top { z-index: 197; }\n[data-edrv-view] .edrv-snip-dialog { display: flex; flex-direction: column; overflow: hidden; width: min(620px, calc(100vw - 48px)); }\n[data-edrv-view] .edrv-snip-hint { margin: 0 0 12px; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-list { display: flex; flex-direction: column; gap: 1px; min-height: 0; max-height: 46vh; overflow: auto; }\n[data-edrv-view] .edrv-snip-group { padding: 12px 2px 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-row { display: flex; align-items: center; gap: 4px; width: 100%; box-sizing: border-box; padding: 2px; border: none; border-radius: 8px; background: transparent; text-align: left; color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; }\n[data-edrv-view] .edrv-snip-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, .06)); }\n[data-edrv-view] button.edrv-snip-row { cursor: pointer; }\n[data-edrv-view] .edrv-snip-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: none; border-radius: 6px; background: transparent; text-align: left; cursor: pointer; color: inherit; font: inherit; }\n[data-edrv-view] .edrv-snip-label { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #e6e6e6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-src { flex: 0 0 auto; padding-right: 6px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-warn { flex: 0 0 auto; font-size: 11px; padding: 2px 7px; border-radius: 999px; background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .16)); color: var(--dsw-alias-state-warn-primary, #e5b93f); white-space: nowrap; }\n[data-edrv-view] .edrv-snip-act { flex: 0 0 auto; width: 26px; height: 26px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-snip-act:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n[data-edrv-view] .edrv-snip-empty { padding: 12px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n/* 当前文件语言提示条(片段按语言绑定,明确告知生效范围) */\n[data-edrv-view] .edrv-snip-langbar { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin: 0 0 10px; padding: 8px 10px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #2b2b2b); font-size: 12px; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-langbar b { color: var(--dsw-alias-label-primary, #e6e6e6); }\n/* 二级弹窗的动作按钮(原生 .vsm-mcp-dialog-actions 已提供排版与按钮样式) */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-dialog-actions { flex-wrap: wrap; }\n/* 删除图标按钮:.vsm-danger 的规则全部限定在 .vsm-mcp-actions button 之下,裸用无样式,此处补自有类 */\n[data-edrv-view] .edrv-snip-act.edrv-snip-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n/* 浮窗内的原生空态(.vsm-mcp-empty 默认为整页尺寸)压缩为对话框尺寸 */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-empty { padding: 26px 16px; border: 0; }\n\n\n\n\n\n/* 调试(DAP):断点圆点 / 停帧行 / 浮动调试工具条。\n 圆点挂在 Monaco glyphMargin(glyphMarginClassName),行背景为 isWholeLine className;\n 工具条绝对定位于编辑区右上(editorArea relative 由 .edrv-editor-area 自身定位)。 */\n/* 断点圆点:装饰类占满整行,圆点经 ::before 在本行盒子内居中(行归属明确、不跨行界) */\n[data-edrv-view] .edrv-dap-bp { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dap-bp-off { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-off::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n/* 未验证断点(适配器真实回执 verified:false):空心红圈,对齐 VS Code 空心圆语义 */\n[data-edrv-view] .edrv-dap-bp-unverified { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-unverified::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n[data-edrv-view] .edrv-dap-paused-line { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .22)); }\n[data-edrv-view] .edrv-dap-paused-glyph { cursor: pointer; }\n[data-edrv-view] .edrv-dap-paused-glyph::before { content: ''; display: block; width: 5px; height: 12px; margin: 4px auto 0; border-radius: 2px; background: var(--dsw-alias-state-warn-primary, #e5b93f); }\n[data-edrv-view] .edrv-editor-area { position: relative; }\n/* launch.json 右下角「添加配置」按钮(VSCode 同款形态):仅 launch.json 打开时渲染 */\n[data-edrv-view] .edrv-addcfg { position: absolute; right: 14px; bottom: 10px; z-index: 13; padding: 4px 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 18px; cursor: pointer; box-shadow: 0 1px 4px rgba(0, 0, 0, .1); }\n[data-edrv-view] .edrv-addcfg:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dapbar { position: absolute; top: 6px; right: 14px; z-index: 12; display: none; align-items: center; gap: 3px; padding: 3px 5px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #f7f9fa); box-shadow: 0 2px 8px rgba(0, 0, 0, .12); }\n[data-edrv-view] .edrv-dapbar.on { display: flex; border-color: var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapbar-grip { flex: 0 0 auto; width: 16px; height: 22px; display: inline-flex; align-items: center; justify-content: center; cursor: grab; user-select: none; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 12px; }\n[data-edrv-view] .edrv-dapbar-grip:active, [data-edrv-view] .edrv-dapbar.dragging .edrv-dapbar-grip { cursor: grabbing; }\n[data-edrv-view] .edrv-dapbar.dragging { user-select: none; }\n[data-edrv-view] .edrv-dapbar-cfg { max-width: 220px; height: 22px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapbar-btn { min-width: 24px; height: 22px; padding: 0 6px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 12px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dapbar-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dapbar-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-dapbar-phase { padding: 0 4px; font-size: 11px; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbar-phase.paused { color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dapbar-err { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--dsw-alias-state-error-primary, #e05252); }\n\n/* 调试面板(活动栏「调试」页签):四段折叠区 + 断点列表 + REPL。 */\n[data-edrv-view] .edrv-dappanel { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; overflow: auto; padding: 2px 2px 8px; }\n[data-edrv-view] .edrv-dappanel-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; row-gap: 4px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dappanel-phase { padding: 2px 8px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dappanel-phase.paused { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .18)); color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dappanel-cfg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dappanel-pid { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-dappanel-err { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapseg { display: flex; flex-direction: column; gap: 2px; min-height: 0; }\n[data-edrv-view] .edrv-dapseg-body { min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }\n/* 段间分隔条(splitview sash):拖动调整相邻两段高度占比;各段高度由 DebugPanel 内联接管。 */\n[data-edrv-view] .edrv-dapseg-sash { flex: 0 0 auto; height: 4px; margin: 2px 0; border-radius: 2px; background: transparent; cursor: row-resize; }\n[data-edrv-view] .edrv-dapseg-sash:hover, [data-edrv-view] .edrv-dapseg-sash.dragging { background: var(--dsw-alias-border-l2, #cdd6da); }\n/* 折叠段头:chevron + 标题 + 悬浮动作区(动作 hover 显隐;REPL 头不参与折叠)。 */\n[data-edrv-view] .edrv-dapseg-head { display: flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-tertiary, #7b8794); padding: 2px 2px; cursor: pointer; border-radius: 4px; }\n[data-edrv-view] .edrv-dapseg-head:hover { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-daprepl .edrv-dapseg-head { cursor: default; padding: 2px 0; }\n[data-edrv-view] .edrv-dapseg-chevron { flex: 0 0 auto; width: 14px; text-align: center; font-size: 10px; }\n[data-edrv-view] .edrv-dapseg-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapseg-act { flex: 0 0 auto; min-width: 20px; height: 18px; padding: 0 4px; border: none; border-radius: 4px; background: transparent; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 11px; line-height: 1; cursor: pointer; opacity: 0; }\n[data-edrv-view] .edrv-dapseg-head:hover .edrv-dapseg-act { opacity: 1; }\n[data-edrv-view] .edrv-dapseg-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapempty { font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 4px 2px; }\n[data-edrv-view] .edrv-dapframe { display: flex; align-items: baseline; gap: 8px; padding: 3px 4px; border-radius: 6px; cursor: pointer; font-size: 12px; }\n[data-edrv-view] .edrv-dapframe:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapframe-name { color: var(--dsw-alias-label-primary, inherit); font-weight: 600; }\n[data-edrv-view] .edrv-dapframe-loc { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapscope { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); padding: 4px 2px 1px; }\n[data-edrv-view] .edrv-dapvar { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; font-size: 12px; padding: 1px 2px; min-width: 0; }\n[data-edrv-view] .edrv-dapvar-tw { flex: 0 0 auto; width: 14px; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 10px; padding: 0; }\n[data-edrv-view] span.edrv-dapvar-tw { cursor: default; }\n[data-edrv-view] .edrv-dapvar-name { flex: 0 1 34%; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, inherit); font-family: var(--ds-font-family-code, ui-monospace, monospace); white-space: nowrap; }\n[data-edrv-view] .edrv-dapvar-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapvar-type { flex: 0 0 auto; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapframe.selected { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .16)); outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapwatch { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; min-width: 0; font-size: 12px; padding: 1px 2px; }\n[data-edrv-view] .edrv-dapwatch-x { border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapwatch-x:hover { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapwatch-add { padding: 2px 0; }\n[data-edrv-view] .edrv-dapbp { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 2px 2px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-dapbp:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapbp:active { background: var(--dsw-alias-interactive-bg-pressed, rgba(127, 127, 127, .2)); }\n[data-edrv-view] .edrv-dapbp-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dapbp-dot.off { background: transparent; border: 1.5px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n[data-edrv-view] .edrv-dapbp-file { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: none; background: transparent; color: var(--dsw-alias-label-primary, inherit); cursor: pointer; padding: 0; font-size: 12px; }\n[data-edrv-view] .edrv-dapbp-dir { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; text-align: right; }\n[data-edrv-view] .edrv-dapbp-line { flex: 0 0 auto; font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbp-x { flex: 0 0 auto; border: none; background: transparent; color: transparent; cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapbp:hover .edrv-dapbp-x { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapbp-x:hover { color: var(--dsw-alias-state-error-primary, #e05252) !important; }\n[data-edrv-view] .edrv-daprepl { display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-daprepl-log { display: flex; flex-direction: column; gap: 1px; max-height: 180px; overflow: auto; }\n[data-edrv-view] .edrv-daprepl-line { font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); color: var(--dsw-alias-label-secondary, #52606d); white-space: pre-wrap; word-break: break-all; display: flex; gap: 6px; }\n[data-edrv-view] .edrv-daprepl-line.out { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-daprepl-in { width: 100%; box-sizing: border-box; height: 26px; padding: 0 8px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dappanel-run { flex: 0 0 auto; width: 24px; height: 24px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dappanel-run:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dappanel-run:disabled { opacity: .35; cursor: default; background: transparent; }\n/* 面板顶部配置下拉(与编辑器浮动条 .edrv-dapbar-cfg 同视觉)。 */\n[data-edrv-view] .edrv-dappanel-cfg-select { max-width: 190px; height: 24px; padding: 0 4px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n/* 断点行内编辑行(面板内展开编辑,控件复用 .edrv-bpwidget-* 视觉;左缩进对齐断点行文字列)。 */\n[data-edrv-view] .edrv-dapbp-edit { display: flex; align-items: center; gap: 6px; padding: 3px 2px 3px 20px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-mode { height: 20px; }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-input { height: 20px; }\n/* 断点回执:未验证空心红圈(须排在 .off 之后,禁用态优先展示灰圈语义)。 */\n[data-edrv-view] .edrv-dapbp-dot.unverified { background: transparent; border: 1.5px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n/* 断点行内编辑浮层(Monaco content widget,锚定断点行上方;对齐 VS Code breakpointWidget 形态)。 */\n/* View Zone 浮层:宽度/左移由 BpWidget.syncWidth 按 layout 内联设置\n (Monaco .view-zones 仅内容区宽,需补偿 contentLeft 与滚动条),此处只管视觉。 */\n[data-edrv-view] .edrv-bpwidget { position: relative; box-sizing: border-box; display: flex; align-items: center; gap: 6px; padding: 3px 8px; border-top: 1px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-layer-1, #f7f9fa); }\n/* 断点编辑中的目标行高亮(浮层打开期间) */\n[data-edrv-view] .edrv-dap-editing-line { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-bpwidget-mode { flex: 0 0 auto; height: 22px; padding: 0 4px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); cursor: pointer; }\n[data-edrv-view] .edrv-bpwidget-input { flex: 1 1 auto !important; min-width: 0; width: auto !important; box-sizing: border-box; height: 22px; padding: 0 6px; font-size: 12px; font-family: var(--ds-font-family-code, ui-monospace, monospace); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-bpwidget-input:focus { outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n\n/* 断点 hover 预览(glyph 区):半透明小红点 + pointer 光标(对齐 VS Code .codicon-debug-hint)。 */\n[data-edrv-view] .edrv-dap-bp-hint { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-hint::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); opacity: .4; }\n/* 行号区也给出可点提示(与 VS Code 一致:glyph 区与行号区都可点/可 hover);\n 不拦截折叠箭头等自有控件的光标。 */\n[data-edrv-view] .edrv-editor-row .monaco-editor .margin-view-overlays .line-numbers:hover { cursor: pointer; }\n\n/* 暂停态 hover 调试块 + 变量树(成员全量直显 + table 成员折叠展开)。\n ⚠️ 选择器一律用 data-*:本仓 vendored Monaco 的 markdown→DOM 管线会剥掉我们 HTML 里的\n class(supportHtml/isTrusted 都不豁免),只有 data-* 存活;用 class 会静默失效。 */\n[data-edrv-view] [data-edrv-dap] { display: block; min-width: 0; }\n[data-edrv-view] [data-edrv-head] { display: flex; align-items: baseline; gap: 2px; }\n[data-edrv-view] [data-edrv-head] [data-edrv-expr] { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-head] [data-edrv-val] { font-family: var(--ds-font-family-code, ui-monospace, monospace); word-break: break-all; }\n[data-edrv-view] [data-edrv-type] { margin-top: 2px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-tree] { margin: 2px 0 0; padding: 0; list-style: none; font-variant-numeric: tabular-nums; }\n[data-edrv-view] [data-edrv-line] { display: flex; align-items: baseline; gap: 4px; padding: 1px 2px; border-radius: 3px; white-space: nowrap; }\n[data-edrv-view] [data-edrv-line]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n/* 折叠按钮(对齐 CodeBuddy 调试浮窗变量树的 twist):热区 14px + hover 反馈,未可展开时留空占位 */\n[data-edrv-view] [data-edrv-twist] { flex: 0 0 auto; box-sizing: border-box; width: 14px; height: 14px; line-height: 13px; text-align: center; border-radius: 3px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-twist]:not(:empty) { cursor: pointer; user-select: none; }\n[data-edrv-view] [data-edrv-twist]:not(:empty):hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .2)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-name] { flex: 0 0 auto; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-line] [data-edrv-eq] { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-line] [data-edrv-val] { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] [data-edrv-note] { color: var(--dsw-alias-label-tertiary, #8a949b); font-style: italic; }\n/* Alt 提示层:由 hoverTree 注入到 .monaco-hover(滚动区之外)贴底常驻;容器预留同高内边距避免遮挡末行。\n 不再用 sticky / `:has()`:滚动链在 .monaco-hover-content 上,sticky 会被滚走。 */\n[data-edrv-view] .monaco-hover[data-edrv-hint-host=\"1\"] { padding-bottom: 22px; }\n[data-edrv-view] [data-edrv-hint=\"1\"] { position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box; height: 22px; padding: 2px 8px; font-size: 11px; line-height: 18px; color: var(--dsw-alias-label-tertiary, #8a949b); background: var(--dsw-alias-bg-overlay, #ffffff); border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n\n/* Workbench panes: existing theme, action-driven drop previews. ddj 2026年10月14号 */\n[data-edrv-view] .edrv-group, [data-edrv-view] .edrv-group-body,\n[data-edrv-view] .edrv-full-slot, [data-edrv-view] .edrv-full-view,\n[data-edrv-view] .edrv-passive-view { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-group { border: 0; }\n[data-edrv-view] .edrv-group .edrv-tabs { flex: 0 0 auto; touch-action: none; }\n[data-edrv-view] .edrv-group-tab-name { overflow: hidden; text-overflow: ellipsis; max-width: 180px; }\n[data-edrv-view] .edrv-split { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-vertical { flex-direction: column; }\n[data-edrv-view] .edrv-split-child { display: flex; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-handle { flex: 0 0 5px; background: var(--dsw-alias-border-l1, #e0e6e8); cursor: col-resize; touch-action: none; }\n[data-edrv-view] .edrv-split-vertical > .edrv-split-handle { cursor: row-resize; }\n[data-edrv-view] .edrv-split-handle:hover, [data-edrv-view] .edrv-split-handle:focus-visible { background: var(--dsw-alias-state-info-primary, #4d6bfe); }\n[data-edrv-view].edrv-floating { position: fixed; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-base, #fff); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: 0 8px 28px rgba(0,0,0,.22); }\n[data-edrv-view] .edrv-floating-title { display: flex; align-items: center; gap: 8px; flex: 0 0 32px; padding: 0 8px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); cursor: move; touch-action: none; user-select: none; }\n[data-edrv-view] .edrv-floating-title > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-floating-content { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; overflow: auto; box-sizing: border-box; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-dialog { padding: 12px; overflow: auto; }\n[data-edrv-view] .edrv-floating-content.edrv-svn-patch-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svn-sum-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svnplan-dialog { padding: 12px; max-width: none; gap: 8px; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-stats { max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svn-patch-pre,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-list { flex: 1; min-height: 0; max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-form { flex-wrap: wrap; }\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-head,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-foot { flex-shrink: 0; }\n[data-edrv-view] .edrv-floating-title > button { border: 0; background: transparent; color: inherit; cursor: pointer; }\n[data-edrv-view] .edrv-floating-grip { position: absolute; width: 16px; height: 16px; bottom: 0; right: 0; cursor: nwse-resize; touch-action: none; border-right: 3px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 3px solid var(--dsw-alias-border-l2, #cdd6da); }\n.edrv-drag-overlay { position: fixed; z-index: 150; pointer-events: none; box-sizing: border-box; border: 0; background: color-mix(in srgb, var(--dsw-alias-state-info-primary, #4d6bfe) 25%, transparent); }\n.edrv-drop-label, .edrv-drag-ghost { position: fixed; z-index: 151; pointer-events: none; padding: 4px 8px; background: var(--dsw-alias-bg-overlay, #fff); color: var(--dsw-alias-label-primary, #1f2933); border: 1px solid var(--dsw-alias-border-l2, #cdd6da); font-size: 12px; white-space: nowrap; }\n.edrv-drop-label { position: absolute; left: 4px; top: 4px; }\n[data-edrv-view] .edrv-tab:focus-visible { outline: 2px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: -2px; }\n@media (prefers-reduced-motion: reduce) { .edrv-drag-overlay { transition: none; } }\n";
|
|
59
|
-
const tagId$
|
|
58
|
+
const css$4 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 主题:跟随官方 --dsw-alias-* 令牌(DSH 明暗与第三方皮肤生效)。\n 本作用域不再重定义官方令牌(旧实现曾在此硬覆盖浅色盘,导致插件不跟随 DSH 主题);\n 旧环境/缺令牌场景由每条声明的 var(--dsw-alias-*, 回落值) 兜底。\n @author ddj 2026-09-10 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; flex-shrink: 0; overscroll-behavior-x: contain; } /* overscroll-behavior-x: 横滚到边界不把滚动链传给页面(滚轮横滚接管更可控)@author ddj 2026年09月18号 */\n[data-edrv-view] .edrv-tab { user-select: none; -webkit-user-select: none; display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-star { color: var(--dsw-alias-brand-primary, #4f8cff); font-size: 13px; font-weight: 700; line-height: 1; flex-shrink: 0; } /* 未保存修改星号 @author ddj 2026年09月09号 */\n[data-edrv-view] .edrv-tab .edrv-tab-x { appearance: none; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 16px; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 4px; background: transparent; box-shadow: none; color: var(--dsw-alias-label-tertiary, #888); font-family: inherit; font-size: 11px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:focus-visible { outline: 1px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: 1px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.12)); color: var(--dsw-alias-label-primary, #eee); }\n/* 固定页签:📌 标记 + 免关闭按钮(固定页签不渲染 ×,仅可经右键菜单取消固定) */\n[data-edrv-view] .edrv-tab .edrv-tab-pin { flex-shrink: 0; font-size: 10px; line-height: 1; opacity: .85; }\n[data-edrv-view] .edrv-tab.edrv-tab-pinned { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,.04)); }\n/* 页签行 = 可横滚的页签区 + 行尾固定工具列(计划预览页签与磁盘页签同处这一行的滚动区)@author ddj 2026年10月20号 */\n[data-edrv-view] .edrv-tabs-row { display: flex; align-items: center; min-width: 0; flex: 0 0 auto; background: var(--dsw-alias-bg-layer-1, transparent); }\n/* 特异性需盖过下面 .edrv-group .edrv-tabs 的 flex: 0 0 auto,页签区才能吃掉行走宽、把工具列压到行尾 */\n[data-edrv-view] .edrv-group .edrv-tabs-row > .edrv-tabs { flex: 1 1 auto; min-width: 0; background: transparent; }\n[data-edrv-view] .edrv-tabs-tools { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; padding: 0 6px 0 2px; }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }\n/* 非焦点分组的静态路径条(GroupPathbar):可点击聚焦所属组,悬停给反馈 @author ddj 2026-10-16 */\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static] { cursor: pointer; }\n[data-edrv-view] .edrv-pathbar[data-edrv-pb-static]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-pb-update { display: inline-flex; align-items: center; gap: 4px; height: 20px; flex: none; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 4px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; cursor: pointer; }\n[data-edrv-view] .edrv-pb-update:hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-pb-update:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 1px; }\n@media (max-width: 620px) { [data-edrv-view] .edrv-pb-update { width: 24px; padding: 0 4px; overflow: hidden; font-size: 0; } }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n/* 键盘高亮项(↑↓ 选择):与 hover 同特异性但声明在后,故选中态稳定可辨 @author ddj 2026年09月11号 */\n[data-edrv-view] .edrv-search-item.edrv-search-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.14)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-lsp { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress-message { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-statusbar .edrv-sp-progress { flex: 0 0 auto; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-lsp-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-lsp-status-dot.ready { background: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-lsp-status-dot.busy { background: var(--dsw-alias-state-warn-primary, #d97706); animation: edrv-lsp-pulse 1.2s ease-in-out infinite; }\n@keyframes edrv-lsp-pulse { 50% { opacity: .45; } }\n/* Ctrl+hover 可导航标识符下划线提示(绑定 Monaco inlineClassName 装饰) */\n[data-edrv-view] .edrv-nav-underline { text-decoration: underline; text-decoration-color: var(--dsw-alias-interactive-primary, #4fc1ff); text-underline-offset: 1px; cursor: pointer; }\n/* 跳转目标区域高亮(LSP 定义/引用、搜索命中落点):落点一眼可见,约 1.2s 后由 EditorView 自动清除。\n 用官方令牌并带回落值,不在插件作用域重定义 --dsw-* (否则永久脱离官方主题)。 */\n[data-edrv-view] .edrv-nav-target { background: var(--dsw-alias-interactive-bg-hover, rgba(79,193,255,.28)); border-radius: 2px; box-shadow: 0 0 0 1px var(--dsw-alias-interactive-primary, rgba(79,193,255,.55)); }\n[data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-chip-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-chip-btn:disabled:hover { background: transparent; }\n/* 行内和原生 Monaco diff 共用主题色盘;不再借用普通 hover 的灰底。 */\n[data-edrv-view] .edrv-mn-add-line { background: var(--edrv-diff-add, #e6f4e8); }\n[data-edrv-view] .edrv-mn-gutter-add { background: var(--edrv-diff-add, #e6f4e8); color: var(--dsw-alias-state-success-primary, #0f9d58) !important; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone, [data-edrv-view] .edrv-del-margin { background: var(--edrv-diff-del, #fbe7e3); color: var(--edrv-diff-fg, #1e1e1e); }\n[data-edrv-view] .edrv-del-row { white-space: pre; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-del-margin-row { position: relative; height: var(--edrv-line-height, 20px); line-height: var(--edrv-line-height, 20px); }\n[data-edrv-view] .edrv-del-number { position: absolute; left: var(--edrv-number-left); width: var(--edrv-number-width); color: var(--edrv-diff-number, #6c6c6c); text-align: right; }\n[data-edrv-view] .edrv-del-marker { position: absolute; left: var(--edrv-marker-left); width: var(--edrv-marker-width); color: var(--dsw-alias-state-error-primary, #d9534f); text-align: center; }\n[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n[data-edrv-view] .edrv-imgview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-imgview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-imgview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n/* 图片预览:居中交给子项 margin:auto(auto 外边距优先吃正向空闲空间;溢出时空闲为负 → 外边距归 0、\n flex-start 起点对齐 → 放大后四边均可滚动到达,规避 justify-content:center 双侧溢出不可达问题) */\n[data-edrv-view] .edrv-imgview-stage { flex: 1 1 auto; min-height: 0; display: flex; overflow: auto; padding: 14px; background-color: var(--dsw-alias-bg-layer-1, #f0f4f4); background-image: conic-gradient(rgba(128,128,128,.14) 25%, transparent 0 50%, rgba(128,128,128,.14) 0 75%, transparent 0); background-size: 16px 16px; }\n[data-edrv-view] .edrv-imgview-img { max-width: 100%; max-height: 100%; flex-shrink: 0; margin: auto; align-self: flex-start; object-fit: contain; border-radius: 4px; box-shadow: 0 2px 12px rgba(31,41,51,.18); }\n[data-edrv-view] .edrv-imgview-broken { display: flex; flex-direction: column; align-items: center; gap: 10px; font-size: 13px; color: var(--dsw-alias-label-tertiary, #777); margin: auto; }\n[data-edrv-view] .edrv-pdf-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-host > .edrv-pdf { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-pdf { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-pdf-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-pdf-page { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; min-width: 52px; text-align: center; }\n[data-edrv-view] .edrv-pdf-zoom { min-width: 26px; text-align: center; padding: 3px 7px; }\n[data-edrv-view] .edrv-pdf-sep { width: 1px; height: 16px; background: var(--dsw-alias-border-l1, #e0e6e8); margin: 0 2px; }\n[data-edrv-view] .edrv-pdf-mode-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-brand-primary, #0f9d58); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-pdf-dirty { color: var(--dsw-alias-state-warn-primary, #d97706); border-color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-pdf-stage { position: absolute; top: 34px; left: 0; right: 0; bottom: 0; overflow: auto; }\n/* Markdown 预览(需求 4):工具条 + 居中阅读栏;正文排版由官方 MarkdownText 自带的 --dsw-* 样式负责 */\n[data-edrv-view] .edrv-mdview { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }\n[data-edrv-view] .edrv-mdview-bar { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, transparent); }\n[data-edrv-view] .edrv-mdview-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n[data-edrv-view] .edrv-mdview-stage { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 20px 28px; }\n[data-edrv-view] .edrv-mdview-doc { max-width: 880px; margin: 0 auto; width: 100%; }\n[data-edrv-view] .edrv-mdview-fallback { font-size: 12px; line-height: 1.7; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-mdview-pre { margin: 8px 0 0; padding: 10px 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }\n[data-edrv-view] .edrv-pb-mdbtn { height: 20px; padding: 0 8px; font-size: 11px; flex-shrink: 0; }\n[data-edrv-view] .edrv-pdf-stage .edrv-pdf-viewer { margin: 0 auto; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-state-success-primary, #0f7a45); border: 1px solid var(--dsw-alias-state-success-primary, rgba(15,157,88,.4)); }\n[data-edrv-view] .edrv-pill-undo { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.14)); color: var(--dsw-alias-state-error-primary, #c0392b); border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-pill-ghost { background: var(--dsw-alias-bg-layer-1, #ffffff); color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(页签右键菜单 + 文件树右键菜单共用)。\n 页签菜单 11 条 + 键位提示,故限高可滚(ContextMenu 以实测尺寸做 viewport clamp)。\n ⚠️ z-index 71 在既有浮层阶梯内(见文件末尾命令栏注释),不得上调越过命令栏/片段浮窗。 */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 220px; max-width: 360px; max-height: min(70vh, 520px); overflow-y: auto; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-item:disabled,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled { color: var(--dsw-alias-label-disabled, #b0b7c0); cursor: default; background: transparent; }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 菜单项文案(弹性占宽,超出省略)与右侧键位提示(不缩放,等宽字体) */\n[data-edrv-view] .edrv-ctxmenu-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-hint { flex: 0 0 auto; padding-left: 18px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-ctxmenu-item:disabled .edrv-ctxmenu-hint,\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-disabled .edrv-ctxmenu-hint { color: var(--dsw-alias-label-disabled, #b0b7c0); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-primary, rgba(217,119,6,.5)); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */\n[data-edrv-diff-dock-shell] {\n box-sizing: border-box;\n width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n margin: 0 auto;\n padding: 0;\n flex: none;\n border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);\n border-radius: 12px;\n background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));\n box-shadow: none;\n overflow: hidden;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock {\n box-sizing: border-box;\n position: relative;\n left: auto;\n right: auto;\n bottom: auto;\n z-index: auto;\n width: 100%;\n max-width: none;\n margin: 0;\n display: flex;\n flex-direction: column;\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-nav,\n[data-edrv-diff-dock-shell] .edrv-diffbar-files,\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }\n[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }\n@media (max-width: 620px) {\n [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }\n [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }\n}\n/* 侧栏差异栏在编辑内容底部居中悬浮,按内容宽度收缩;窄栏只在工具条内横滚。@author ddj 2026年09月29号 */\n[data-edrv-view] .edrv-side-diff-dock[data-edrv-diff-dock-shell] { position: absolute; left: 50%; bottom: 28px; z-index: 25; width: max-content; max-width: calc(100% - 20px); max-height: min(300px, calc(100% - 38px)); margin: 0; transform: translateX(-50%); border-radius: 12px; background: var(--dsw-alias-bg-overlay, #fff); box-shadow: 0 6px 20px rgba(0,0,0,.18); overflow-x: hidden; overflow-y: auto; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-row { padding-inline: 8px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-editor-content { gap: 5px; width: max-content; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-nav,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files,\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-actions { gap: 3px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-pill { padding-inline: 5px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-files .edrv-diffbar-file { min-width: 0; max-width: 72px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-details-toggle { width: 26px; min-width: 26px; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diffbar-body { max-height: min(180px, 25vh); max-width: 100%; }\n[data-edrv-view] .edrv-side-diff-dock .edrv-diff-details { max-height: min(260px, 35vh); overflow-y: auto; }\n\n/* Official review: file context above, navigation/actions below. Theme-owned surfaces; no neon status outlines. @author ddj 2026年10月27号 */\n[data-edrv-diff-dock-shell] .edrv-reviewbar {\n width: 520px; max-width: 100%; min-width: 0; box-sizing: border-box;\n color: var(--dsw-alias-label-primary, #26313d); font-family: var(--dsw-font-family, sans-serif);\n font-size: 12px; line-height: 1.4; container: edrv-review / inline-size;\n}\n[data-edrv-diff-dock-shell] .edrv-review-global { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e5e9ef); }\n[data-edrv-diff-dock-shell] .edrv-review-all-actions { display: flex; align-items: center; gap: 4px; }\n[data-edrv-diff-dock-shell] .edrv-review-total strong { font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-review-heading {\n display: flex; align-items: center; gap: 8px; min-width: 0; padding: 10px 12px 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, #e5e9ef);\n}\n[data-edrv-diff-dock-shell] .edrv-review-filemark { display: inline-flex; flex: none; color: var(--dsw-alias-label-tertiary, #697586); }\n[data-edrv-diff-dock-shell] .edrv-review-filename { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-review-pending { flex: none; display: inline-flex; gap: 5px; align-items: center; color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; white-space: nowrap; }\n[data-edrv-diff-dock-shell] .edrv-review-pending strong { font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-primary, #26313d); }\n[data-edrv-diff-dock-shell] .edrv-review-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 7px 8px; }\n[data-edrv-diff-dock-shell] .edrv-review-nav { display: flex; align-items: center; flex: none; gap: 1px; }\n[data-edrv-diff-dock-shell] .edrv-review-blocknav { border-left: 1px solid var(--dsw-alias-border-l1, #e5e9ef); padding-left: 6px; }\n[data-edrv-diff-dock-shell] .edrv-review-position { min-width: 32px; text-align: center; font-size: 11px; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary, #697586); }\n[data-edrv-diff-dock-shell] .edrv-review-actions { display: flex; align-items: center; flex: none; gap: 4px; margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button { height: 28px; min-height: 28px; padding: 0 8px; border-radius: 6px; font-family: inherit; font-size: 12px; font-weight: 500; white-space: nowrap; gap: 5px; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-icon { width: 28px; min-width: 28px; padding: 0; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-record { padding: 0 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #697586); }\n[data-edrv-diff-dock-shell] .edrv-review-record svg { width: 12px; height: 12px; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-keep { background: var(--dsw-alias-brand-primary, #3b63d9); color: var(--dsw-alias-label-primary-inverted, #fff); border: 1px solid transparent; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-keep:hover:not(:disabled) { filter: brightness(.94); }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button.edrv-review-undo:hover:not(:disabled) { color: var(--dsw-alias-state-error-primary, #b64043); }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button:disabled { opacity: .4; cursor: not-allowed; }\n[data-edrv-diff-dock-shell] .edrv-reviewbar button:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #3b63d9); outline-offset: 2px; }\n[data-edrv-diff-dock-shell] .edrv-review-notice { padding: 7px 12px; border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; overflow-wrap: anywhere; }\n[data-edrv-diff-dock-shell] .edrv-review-error { color: var(--dsw-alias-state-error-primary, #b64043); }\n[data-edrv-diff-dock-shell] .edrv-review-notice-warn { color: var(--dsw-alias-state-warning-primary, #a86a1f); }\n[data-edrv-diff-dock-shell] .edrv-review-source { flex: none; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-secondary, #d4dae3); color: var(--dsw-alias-label-secondary, #697586); font-size: 11px; line-height: 16px; white-space: nowrap; }\n[data-edrv-diff-dock-shell] .edrv-sidecar-down { display: flex; align-items: center; gap: 8px; }\n[data-edrv-diff-dock-shell] .edrv-sidecar-down-text { flex: 1; min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-review-body { border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); max-height: min(240px, 30vh); overflow: auto; padding: 8px 12px; }\n@container edrv-review (max-width: 420px) {\n .edrv-reviewbar .edrv-review-heading { gap: 6px; padding-inline: 10px; }\n .edrv-reviewbar .edrv-review-actions { justify-content: flex-end; flex: 1 0 100%; border-top: 1px solid var(--dsw-alias-border-l1, #e5e9ef); padding-top: 6px; }\n}\n\n/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\n[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col > .edrv-pathbar,\n[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }\n[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 1; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }\n/* 树行图标:官方原语(文件夹/文件类型),尺寸与行高对齐 @author ddj 2026-09-10 */\n[data-edrv-view] .edrv-tree-icon { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active .edrv-tree-icon { color: var(--dsw-alias-label-secondary, #52606d); }\n/* SVN overlays: official file icon remains visible, independent local/remote signs. ddj 2026-10-09 */\n[data-edrv-view] .edrv-tree-iconbox { position: relative; display: inline-flex; width: 20px; height: 20px; align-items: center; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-overlay { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-svn-local { position: absolute; right: -1px; bottom: -2px; display: flex; border-radius: 50%; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-svn-icon-clean { color: #16853b; }\n[data-edrv-view] .edrv-svn-icon-modified, [data-edrv-view] .edrv-svn-icon-deleted { color: #d12e35; }\n[data-edrv-view] .edrv-svn-icon-conflict { color: #c27300; }\n[data-edrv-view] .edrv-svn-icon-added { color: #2465c9; }\n[data-edrv-view] .edrv-svn-icon-unversioned, [data-edrv-view] .edrv-svn-icon-ignored, [data-edrv-view] .edrv-svn-icon-external { color: #75808c; }\n[data-edrv-view] .edrv-svn-remote { position: absolute; right: -3px; top: -4px; color: #2465c9; background: var(--dsw-alias-bg-layer-1, #fff); border-radius: 3px; font-size: 11px; font-weight: 900; line-height: 12px; }\n[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n/* Virtual rows and placeholders share the same geometry as treeWindow.ts. @author ddj 2026年10月22号 */\n[data-edrv-view] .edrv-tree.edrv-tree-virtual { padding-top: 0; padding-bottom: 0; }\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-row,\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-loading { height: 24px; min-height: 24px; box-sizing: border-box; }\n[data-edrv-view] .edrv-tree-virtual .edrv-tree-loading { line-height: 24px; }\n[data-edrv-view] .edrv-large-text { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-large-text-toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }\n[data-edrv-view] .edrv-large-text-content { margin: 0; flex: 1; min-height: 0; overflow: auto; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); user-select: text; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-context { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); }\n/* 「在资源管理器视图中显示」的定位高亮(短时淡出,区别于常驻的 active 态) */\n[data-edrv-view] .edrv-tree-row.edrv-tree-reveal { box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary, #0f9d58); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.18)); animation: edrv-tree-reveal-fade 2s ease-out; }\n@keyframes edrv-tree-reveal-fade { from { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.32)); } to { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.18)); } }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }\n[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n/* SVN 状态独立占位:换行不覆盖文件树,长错误在栏内滚动。@author ddj 2026-10-12 */\n[data-edrv-view] .edrv-tree-status { flex: 0 0 auto; min-width: 0; max-height: 96px; overflow: auto; padding: 4px 8px; box-sizing: border-box; line-height: 18px; white-space: normal; overflow-wrap: anywhere; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* 大纲面板:kind 字形 / detail / 行号 */\n[data-edrv-view] .edrv-outline-kind { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-size: 12px; font-weight: 600; text-align: center; }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-func { color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-type { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-data { color: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-ns { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-outline-kind.edrv-ol-key { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-outline-detail { flex-shrink: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-outline-ln { flex-shrink: 0; padding-right: 2px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== SVN 变更面板(工作副本状态列表;颜色分级对齐 TortoiseSVN 口径) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svn-toolbar { display: flex; align-items: center; gap: 12px; padding: 4px 10px 6px 12px; flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svn-check { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-check input { margin: 0; cursor: pointer; }\n[data-edrv-view] .edrv-svn-bulk { display: flex; align-items: center; gap: 6px; padding: 0 10px 6px 12px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-count { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* AI 助手 dock(12-panel-ai-bar 设计定稿):manifest 分带之 assistant 带,accent 左竖轨为唯一记忆点。\n AI 线程色三处同轨:竖轨 / dock 按钮描边 / 弹窗进度条(--dsw-alias-state-info-primary)。 @author ddj 2026-09-23 */\n[data-edrv-view] .edrv-svn-ai-bar { position: relative; display: flex; align-items: center; gap: 8px; padding: 6px 10px 6px 14px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-ai-rail { position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-ai-label { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svn-ai-btn { border-color: var(--dsw-alias-state-info-primary, #2f6fed); color: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-ai-btn:disabled { border-color: var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 状态位定宽右对齐(就绪/分析中…/等待助手投递…互换不跳版) */\n[data-edrv-view] .edrv-svn-ai-status { flex-shrink: 0; min-width: 88px; text-align: right; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; }\n/* 批量条语义分隔(导出簇 ┊ 动作簇之间 1px 竖线) */\n[data-edrv-view] .edrv-svn-sep { width: 1px; height: 14px; background: var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n/* 头部图标组(检查远端/刷新成组,gap 收紧) */\n[data-edrv-view] .edrv-svn-head-icons { display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-list { flex: 1; min-height: 0; overflow: auto; padding: 2px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-svn-group { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; margin: 2px 0; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; font-weight: 600; cursor: pointer; user-select: none; }\n[data-edrv-view] .edrv-svn-group:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svn-fold-ch { width: 10px; flex-shrink: 0; text-align: center; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svn-group-n { margin-left: auto; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-weight: 400; }\n[data-edrv-view] .edrv-svn-row { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svn-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-row:hover .edrv-svn-acts { display: inline-flex; }\n[data-edrv-view] .edrv-svn-act { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; padding: 1px 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 14px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svn-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-svn-act-danger { color: var(--dsw-alias-state-error-primary, #d9534f); border-color: var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); }\n[data-edrv-view] .edrv-svn-ch { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-svn-ch-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* .edrv-svn-ch-* 动作词表补全:replaced(替换)= 琥珀;侧栏状态分级(SVN_STATUS_TONE)不受影响 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svn-ch-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); }\n/* 文件树行尾 SVN 状态徽标(与差异角标并列;方形字母,区别圆形差异计数) */\n[data-edrv-view] .edrv-tree-svn { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-modified { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-added { color: var(--dsw-alias-state-warn-primary, #7c3aed); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-deleted { color: var(--dsw-alias-label-secondary, #b91c1c); }\n[data-edrv-view] .edrv-tree-svn.edrv-svn-ch-conflict { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* ===== SVN 基线差异视图(BASE 左 / 工作区右,只读并排) @author ddj 2026-09-16 ===== */\n[data-edrv-view] .edrv-svndiff { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-svndiff-bar { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px; flex-shrink: 0; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svndiff-title { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-svndiff-tag { padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svndiff-host { flex: 1 1 auto; min-height: 0; }\n[data-edrv-view] .edrv-svndiff-empty { display: flex; flex: 1 1 auto; align-items: center; justify-content: center; padding: 24px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 12px; text-align: center; }\n/* 差异整行色块与行首 +/- 指示符:vendored Monaco(diffEditorWidget2)静态 CSS 无\n .line-insert/.line-delete 背景规则,整行色块靠主题键 + 此处固定色兜底保证可见。\n 注意不要用 --dsw-alias-interactive-bg-hover 类 token:实测为 92% 透明中性色,肉眼不可见。\n 固定色取自研差异 UI 同色相(绿 #0f9d58 / 红 #d9534f),alpha 提到肉眼可辨。 */\n[data-edrv-view] .monaco-editor .line-insert { background: rgba(15, 157, 88, 0.18); }\n[data-edrv-view] .monaco-editor .line-delete { background: rgba(217, 83, 79, 0.16); }\n[data-edrv-view] .monaco-editor .gutter-insert { background: rgba(15, 157, 88, 0.35); }\n[data-edrv-view] .monaco-editor .gutter-delete { background: rgba(217, 83, 79, 0.35); }\n[data-edrv-view] .monaco-editor .insert-sign { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .monaco-editor .delete-sign { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* 差异条快速跳转簇(对齐自研审查条 ↑ i/N ↓ 的形态) */\n[data-edrv-view] .edrv-svndiff-nav { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svndiff-nav-count { min-width: 36px; text-align: center; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); }\n/* W1-3 差异统计徽标(+新增/修改 −删除/修改;色相与行块绿红一致) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svndiff-stat { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svndiff-stat-add { color: var(--dsw-alias-state-success-primary, #0f9d58); }\n[data-edrv-view] .edrv-svndiff-stat-del { color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W1-1 差异视图选项条(右端开关组;复用 .edrv-svn-check 的 label 形态) */\n[data-edrv-view] .edrv-svndiff-opts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n/* W1-2 变更面板名称过滤输入框(工具条行内,紧凑) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-name-filter { width: 168px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; line-height: 14px; }\n[data-edrv-view] .edrv-svn-name-filter:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-svn-name-filter::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* W2-3 远端更新提示条(工具条上方;warn = 失败降级) @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-hintbar { display: flex; align-items: center; gap: 6px; margin: 0 10px 4px 12px; padding: 3px 8px; border-radius: 5px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svn-hintbar-warn { background: rgba(217, 83, 79, 0.12); color: var(--dsw-alias-state-error-primary, #d9534f); }\n/* W2-5 疑似改名标记(行内 ⇄;hover 见 tooltip) */\n[data-edrv-view] .edrv-svn-pair-mark { flex-shrink: 0; color: var(--dsw-alias-state-warn-primary, #7c3aed); font-size: 11px; cursor: help; }\n/* W2-1 补丁对话框 / W2-2 目录对比对话框 @author ddj 2026-09-20 */\n[data-edrv-view] .edrv-svn-patch-dialog { max-width: calc(100vw - 48px); }\n[data-edrv-view] .edrv-svn-patch-head { display: flex; align-items: center; gap: 8px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-patch-notes { display: flex; flex-direction: column; gap: 4px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svn-patch-warn { color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-svn-patch-pre { max-height: 46vh; overflow: auto; margin: 0 0 8px; padding: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); color: var(--dsw-alias-label-primary, #1f2933); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.5; white-space: pre; user-select: text; }\n[data-edrv-view] .edrv-svn-patch-foot { display: flex; align-items: center; gap: 8px; }\n/* AI 智能整理弹窗(11-ai-changelist-triage):预览三分组卡 + 执行进度 @author ddj 2026-09-23 */\n[data-edrv-view] .edrv-svnplan-dialog { max-width: calc(100vw - 48px); }\n[data-edrv-view] .edrv-svnplan-head { display: flex; align-items: center; gap: 8px; padding-bottom: 4px; }\n[data-edrv-view] .edrv-svnplan-meta { display: flex; gap: 10px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svnplan-note { color: var(--dsw-alias-state-warn-primary, #b7791f); font-size: 10px; }\n[data-edrv-view] .edrv-svnplan-sec { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 6px 8px; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnplan-sechead { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnplan-sectitle { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-list { max-height: 26vh; overflow: auto; padding-left: 18px; }\n[data-edrv-view] .edrv-svnplan-card { border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); padding: 3px 0; }\n[data-edrv-view] .edrv-svnplan-row { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnplan-item { display: flex; align-items: center; gap: 6px; padding-left: 18px; }\n[data-edrv-view] .edrv-svnplan-groupname { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46ch; }\n[data-edrv-view] .edrv-svnplan-reason { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30ch; }\n[data-edrv-view] .edrv-svnplan-stage { display: flex; align-items: center; gap: 8px; padding-bottom: 6px; }\n[data-edrv-view] .edrv-svnplan-badge { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; padding: 1px 8px; font-size: 10px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-alias-bg-layer-2, #f7f9f9); white-space: nowrap; }\n[data-edrv-view] .edrv-svnplan-badge-cur { font-family: var(--ds-font-family-code, ui-monospace, monospace); overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnplan-bar { height: 6px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnplan-bar-in { height: 100%; background: var(--dsw-alias-state-info-primary, #2f6fed); transition: width .2s; }\n[data-edrv-view] .edrv-svnplan-logwrap { max-height: 40vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 6px 8px; background: var(--dsw-alias-bg-layer-2, #f7f9f9); }\n[data-edrv-view] .edrv-svnplan-log { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.6; color: var(--dsw-alias-label-secondary, #52606d); user-select: text; }\n[data-edrv-view] .edrv-svnplan-log-bad { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-svnplan-foot { display: flex; align-items: center; gap: 8px; }\n[data-edrv-view] .edrv-svnplan-primary { font-weight: 600; border-color: var(--dsw-alias-state-info-primary, #2f6fed); }\n[data-edrv-view] .edrv-svn-sum-form { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; }\n[data-edrv-view] .edrv-svn-sum-list { max-height: 44vh; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; padding: 2px; margin-bottom: 8px; }\n[data-edrv-view] .edrv-svnlog-path-kind { flex-shrink: 0; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; }\n\n/* ===== 通用弹窗外壳(ModalShell)+ SVN 日志弹窗 @author ddj 2026-09-16 ===== */\n/* z-index 190:落在既有阶梯内(命令栏 200 > 片段二级 197 > 片段一级 195 > 190),\n 不遮挡命令栏;既有浮层数值不受影响(见 tests/overlayZOrder.test.ts)。 */\n[data-edrv-view] .edrv-modal-mask { position: fixed; inset: 0; z-index: 190; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, #000000aa); }\n[data-edrv-view] .edrv-svnlog-dialog { position: relative; display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-head { display: flex; align-items: center; gap: 10px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-svnlog-title { margin: 0; font-size: 16px; font-weight: 650; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n/* 头部目标路径 = 代码 chip(mono + 底色 + 全圆角;补丁/目录对比弹窗共用同视觉) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-target { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n/* 关闭按钮图标化(透明底,hover 显形;dialog 前缀提高特异性,压过底部按钮统一尺寸) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act { border-color: transparent; background: transparent; width: 24px; height: 24px; padding: 0; font-size: 12px; line-height: 1; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svnlog-head .edrv-svn-act:hover { border-color: var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.10)); }\n/* 过滤栏:浅色表面条 + 统一 24px 控件 + 焦点边框变色 + 日期组靠右 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-filter { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; padding: 6px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-field { flex: 0 0 auto; height: 24px; box-sizing: border-box; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-search { min-width: 160px; flex: 1 1 220px; height: 24px; box-sizing: border-box; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-field:focus, [data-edrv-view] .edrv-svnlog-search:focus, [data-edrv-view] .edrv-svnlog-date:focus { outline: none; border-color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-dates { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }\n[data-edrv-view] .edrv-svnlog-date { height: 24px; box-sizing: border-box; padding: 0 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-check { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-check input[type=\"checkbox\"] { margin: 0; cursor: pointer; accent-color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-error { padding: 4px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n/* P0-14 上中下三段全宽:body 纵向铺满弹窗(外框高度由拖拽受控) */\n[data-edrv-view] .edrv-svnlog-body { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-height: 0; }\n/* 上段修订列表(13-log-ui-polish 变更台账):\n 1px 发丝边框 + 底色面 + 左侧时间脊线(每行 ::before 画节点圆点、行盒左缘 ::after 连线段,\n 行行相接即整列脊线;选中行节点实心着色 + 2px 左强调条)。列宽由 cell 类与 nth-child 对齐(P0-12/D30)。 */\n[data-edrv-view] .edrv-svnlog-list { position: relative; flex: 0 0 auto; display: flex; flex-direction: column; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 0 4px 4px; background: var(--dsw-alias-bg-layer-1, #fff); }\n/* 列头 sticky:滚动常驻 + 实底防透;z-index 20 低于一切浮层(190 起),仅在列表滚动容器内生效 */\n[data-edrv-view] .edrv-svnlog-cols { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(1) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(2) { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(3) { width: 88px; flex: none; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(4) { width: 82px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cols .edrv-svnlog-col:nth-child(5) { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-svnlog-col-sortable { cursor: pointer; }\n[data-edrv-view] .edrv-svnlog-col-sortable:hover { color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-col-on { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-sort-reset { flex: none; }\n/* 行:position:relative 承载脊线节点(::before)与连线段(::after);选中 = 强调条 + 节点实心 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { position: relative; display: flex; align-items: center; gap: 6px; padding: 3px 6px 3px 16px; border-radius: 6px; cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-item::before { content: ''; position: absolute; left: 6px; top: 50%; transform: translateY(-50%); width: 7px; height: 7px; box-sizing: border-box; border-radius: 50%; border: 1.5px solid var(--dsw-alias-border-l2, #cbd2d9); background: var(--dsw-alias-bg-layer-1, #fff); z-index: 1; }\n[data-edrv-view] .edrv-svnlog-item::after { content: ''; position: absolute; left: 9px; top: 0; bottom: 0; width: 1px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-item:last-child::after { bottom: 50%; }\n[data-edrv-view] .edrv-svnlog-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-item.edrv-svnlog-item-on::before { border-color: var(--dsw-alias-brand-primary, #2563eb); background: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-cell-msg,\n[data-edrv-view] .edrv-svnlog-item-copied .edrv-svnlog-rev { color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-cell { overflow: hidden; text-overflow: ellipsis; font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-cell-rev { width: 52px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-act { width: 52px; flex: none; display: inline-flex; gap: 3px; }\n[data-edrv-view] .edrv-svnlog-cell-author { width: 88px; flex: none; }\n[data-edrv-view] .edrv-svnlog-cell-date { width: 82px; flex: none; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-cell-msg { flex: 1 1 auto; }\n[data-edrv-view] .edrv-svnlog-rev { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-weight: 700; color: var(--dsw-alias-brand-primary, #2563eb); }\n[data-edrv-view] .edrv-svnlog-badge { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border-radius: 4px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 9px; font-weight: 700; }\n/* 动作语义色(13-log-ui-polish 归位):A=success 绿 · D=error 红 · M=brand 蓝 · R=warn 琥珀 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-badge-modified { color: var(--dsw-alias-state-info-primary, #2f6fed); background: rgba(47,111,237,.14); }\n[data-edrv-view] .edrv-svnlog-badge-added { color: var(--dsw-alias-state-success-primary, #0f9d58); background: rgba(15,157,88,.14); }\n[data-edrv-view] .edrv-svnlog-badge-deleted { color: var(--dsw-alias-state-error-primary, #d9534f); background: rgba(217,83,79,.14); }\n[data-edrv-view] .edrv-svnlog-badge-replaced { color: var(--dsw-alias-state-warn-primary, #d97706); background: rgba(217,119,6,.16); }\n[data-edrv-view] .edrv-svnlog-badge-plain { color: var(--dsw-alias-label-tertiary, #9aa5b1); background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-svnlog-more { padding: 6px 4px 2px; text-align: center; }\n/* P0-14 三段 pane 公共外观(13-log-ui-polish:2px 粗框 → 1px 发丝线 + 面板底色) */\n[data-edrv-view] .edrv-svnlog-pane { border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 8px 10px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-svnlog-msgpane { flex: 0 0 auto; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-svnlog-pathspane { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }\n/* P0-13/P0-14:分段横向拖拽分隔条 + 变更路径独立滚动容器 */\n[data-edrv-view] .edrv-svnlog-hsplit { flex: 0 0 6px; cursor: row-resize; margin: 2px 0; border-radius: 3px; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-hsplit:hover, [data-edrv-view] .edrv-svnlog-hsplit:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.28)); }\n[data-edrv-view] .edrv-svnlog-paths-wrap { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; }\n/* 提交信息 meta 行:rev/日期 chip 化,author 常规字重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-rev { padding: 1px 8px; border-radius: 999px; background: var(--dsw-alias-state-info-primary, #2f6fed); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 11px; font-weight: 700; }\n[data-edrv-view] .edrv-svnlog-meta .edrv-svnlog-date { height: auto; padding: 1px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-date { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n/* 正文块:左侧品牌强调线 +1.7 行高(长提交信息可读性) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-msgpane pre.edrv-svnlog-body { margin: 8px 0 0; padding: 8px 10px; border-radius: 0 6px 6px 0; border-left: 2px solid var(--dsw-alias-state-info-primary, #2f6fed); background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11.5px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; overflow: auto; flex: 1 1 auto; min-height: 0; }\n/* P0-14 外框尺寸手柄(13-log-ui-polish 增量3 八向:n/s/w/e 四边 + nw/ne/sw/se 四角);\n 热区避开内边距带(边8px/角14px 落在卡片22/24px padding 内不遮内容),角手柄后渲染盖过相邻边2px overlap。\n chevron 角标四向各自朝向,hover 显形。 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-grip { position: absolute; z-index: 2; touch-action: none; }\n[data-edrv-view] .edrv-svnlog-grip-n { top: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-s { bottom: 0; left: 12px; right: 12px; height: 8px; cursor: ns-resize; }\n[data-edrv-view] .edrv-svnlog-grip-w { left: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-e { right: 0; top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }\n[data-edrv-view] .edrv-svnlog-grip-nw { left: 0; top: 0; width: 14px; height: 14px; cursor: nwse-resize; }\n[data-edrv-view] .edrv-svnlog-grip-ne { right: 0; top: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-sw { left: 0; bottom: 0; width: 14px; height: 14px; cursor: nesw-resize; }\n[data-edrv-view] .edrv-svnlog-grip-se { right: 0; bottom: 0; width: 14px; height: 14px; cursor: nwse-resize; background: transparent; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after,\n[data-edrv-view] .edrv-svnlog-grip-ne::after,\n[data-edrv-view] .edrv-svnlog-grip-sw::after,\n[data-edrv-view] .edrv-svnlog-grip-se::after { content: ''; position: absolute; width: 6px; height: 6px; opacity: .5; border-color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-grip-se::after { right: 3px; bottom: 3px; border-right: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-sw::after { left: 3px; bottom: 3px; border-left: 1.5px solid; border-bottom: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-ne::after { right: 3px; top: 3px; border-right: 1.5px solid; border-top: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip-nw::after { left: 3px; top: 3px; border-left: 1.5px solid; border-top: 1.5px solid; }\n[data-edrv-view] .edrv-svnlog-grip:hover, [data-edrv-view] .edrv-svnlog-grip:active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.35)); }\n[data-edrv-view] .edrv-svnlog-grip:hover::after, [data-edrv-view] .edrv-svnlog-grip:active::after { opacity: 1; border-color: var(--dsw-alias-label-primary, #1f2933); }\n/* 行反馈微动效(仅 hover/选中背景过渡;用户要求减少动效时关闭) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-item { transition: background-color .12s ease, box-shadow .12s ease; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-svnlog-item { transition: none; }\n}\n/*13-log-ui-polish 增量:行容器可滚动(pane 仍 overflow:hidden 裁圆角),列头不被压缩 @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-paths-head { display: flex; align-items: center; gap: 6px; flex: none; font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-tertiary, #9aa5b1); margin-bottom: 4px; padding-bottom: 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-hint { font-weight: 400; letter-spacing: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-paths { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-svnlog-path { display: flex; align-items: center; gap: 6px; height: 22px; padding: 0 6px; border-radius: 5px; cursor: default; white-space: nowrap; }\n[data-edrv-view] .edrv-svnlog-path:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-pathtext,\n[data-edrv-view] .edrv-svnlog-path-dim .edrv-svnlog-copyfrom { opacity: .45; }\n[data-edrv-view] .edrv-svnlog-pathtext { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); }\n/* 路径两段着色:目录段弱化 + 文件名加重(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-pdir { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-pbase { color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-svnlog-copyfrom { flex: 0 1 160px; overflow: hidden; text-overflow: ellipsis; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-svnlog-kind { font-size: 10px; padding: 0 5px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-svnlog-acts { display: none; align-items: center; gap: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-svnlog-path:hover .edrv-svnlog-acts { display: inline-flex; }\n/* 底部:状态行与按钮组分层 + 组间发丝线(13-log-ui-polish) @author ddj 2026年09月24号 */\n[data-edrv-view] .edrv-svnlog-footwrap { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; padding-top: 6px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-svnlog-btns { display: flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup { display: inline-flex; align-items: center; gap: 6px; }\n[data-edrv-view] .edrv-svnlog-btngroup + .edrv-svnlog-btngroup { margin-left: 6px; padding-left: 10px; border-left: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act { font-size: 11px; padding: 2px 9px; }\n[data-edrv-view] .edrv-svnlog-dialog .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-range .edrv-svn-act:focus-visible,\n[data-edrv-view] .edrv-svnlog-stats .edrv-svn-act:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2563eb); outline-offset: 1px; }\n/* ===== SVN 日志统计窗(P1-10)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-stats { display: flex; flex-direction: column; max-height: 82vh; }\n[data-edrv-view] .edrv-svnlog-stats-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px 0; }\n[data-edrv-view] .edrv-svnlog-stats-body { display: flex; flex-direction: column; gap: 14px; padding: 10px 16px 16px; overflow: auto; }\n[data-edrv-view] .edrv-svnlog-stat-line { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-block { display: flex; flex-direction: column; gap: 8px; }\n[data-edrv-view] .edrv-svnlog-stat-label { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-svnlog-stat-duo { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-stat-hist { flex: 1 1 260px; display: flex; flex-direction: column; gap: 4px; min-width: 220px; }\n[data-edrv-view] .edrv-svnlog-stat-row { display: flex; align-items: center; gap: 8px; font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-stat-name { width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-barwrap { flex: 1; height: 12px; border-radius: 6px; background: rgba(127,127,127,.15); overflow: hidden; }\n[data-edrv-view] .edrv-svnlog-stat-bar { display: block; height: 100%; border-radius: 6px; }\n[data-edrv-view] .edrv-svnlog-stat-count { width: 36px; text-align: right; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-svnlog-stat-pie { width: 120px; height: 120px; flex-shrink: 0; border-radius: 50%; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-svnlog-stat-days { display: flex; align-items: flex-end; gap: 2px; height: 120px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow-x: auto; }\n[data-edrv-view] .edrv-svnlog-stat-day { flex: 1 0 6px; min-width: 6px; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; }\n[data-edrv-view] .edrv-svnlog-stat-seg { display: block; width: 100%; }\n/* ===== SVN 日志区间弹窗(P1-6)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-svnlog-range-body { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px 16px; }\n[data-edrv-view] .edrv-svnlog-range-row { display: flex; gap: 14px; flex-wrap: wrap; }\n[data-edrv-view] .edrv-svnlog-range-input { width: 110px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-svnlog-range-btns { display: flex; gap: 8px; justify-content: flex-end; }\n/* SVN 日志 WC 版号行加粗(P1-9) */\n[data-edrv-view] .edrv-svnlog-item-wc { font-weight: 700; }\n/* SVN 日志 merged 行(P1-5):灰字 + 缩进(缩进量按层级内联),前置「↳」标记 */\n[data-edrv-view] .edrv-svnlog-item-merged { color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-merge-mark { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-svnlog-item-merged .edrv-svnlog-cell-msg { font-style: italic; }\n/* ===== 诊断日志弹窗(LogDialog)@author ddj 2026-09-17 ===== */\n[data-edrv-view] .edrv-dlog-dialog { display: flex; flex-direction: column; gap: 8px; max-height: 88vh; }\n[data-edrv-view] .edrv-dlog-head { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-dlog-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-file { max-width: 200px; padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-level { padding: 4px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-search { min-width: 120px; flex: 0 1 200px; padding: 4px 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-auto { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-dlog-error { padding: 4px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-dlog-scroll { flex: 1 1 auto; min-height: 240px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 8px; padding: 4px 0; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-dlog-row { display: flex; align-items: baseline; gap: 8px; padding: 1px 8px; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; line-height: 1.55; }\n[data-edrv-view] .edrv-dlog-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.06)); }\n[data-edrv-view] .edrv-dlog-at { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-lvl { flex-shrink: 0; width: 26px; text-align: center; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dlog-text { flex: 1; min-width: 0; white-space: pre-wrap; word-break: break-word; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dlog-row-error .edrv-dlog-text { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-dlog-row-warn .edrv-dlog-text { color: var(--dsw-alias-state-warning-primary, #b45309); }\n[data-edrv-view] .edrv-dlog-row-debug .edrv-dlog-text { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dlog-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-dlog-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-stat { white-space: nowrap; }\n[data-edrv-view] .edrv-dlog-switch { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); white-space: nowrap; cursor: pointer; flex-shrink: 0; }\n/* 原生 SVN 更新窗口:逐条状态与路径是主信息;跟随 DSH 主题令牌。 */\n[data-edrv-view] .edrv-update-dialog { display: flex; flex-direction: column; padding: 0; border-radius: 8px; overflow: hidden; background: var(--dsw-alias-bg-overlay, #fff); }\n[data-edrv-view] .edrv-update-head { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 0 16px; border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-heading { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }\n[data-edrv-view] .edrv-update-heading h3 { margin: 0; flex: none; font-size: 15px; }\n[data-edrv-view] .edrv-update-target { min-width: 0; color: var(--dsw-alias-label-tertiary, #65737c); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }\n[data-edrv-view] .edrv-update-phase { flex: none; font-size: 12px; color: var(--dsw-alias-label-secondary, #485762); }\n[data-edrv-view] .edrv-update-close { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border: 0; border-radius: 4px; background: none; color: var(--dsw-alias-label-secondary, #485762); cursor: pointer; }\n[data-edrv-view] .edrv-update-close:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-update-close:not(:disabled):hover { background: var(--dsw-alias-interactive-bg-hover, #e7edf0); }\n[data-edrv-view] .edrv-update-table { display: flex; flex-direction: column; flex: 1; min-height: 0; margin: 12px 14px 0; border: 1px solid var(--dsw-alias-border-l1, #d9e0e4); border-radius: 5px; overflow: hidden; background: var(--dsw-alias-bg-layer-1, #fff); }\n[data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { display: grid; grid-template-columns: 110px minmax(0, 1fr) 160px; align-items: center; }\n[data-edrv-view] .edrv-update-sort { width: 100%; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }\n[data-edrv-view] .edrv-update-sort:hover { color: var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-sort:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-jump { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-cols { flex: none; height: 30px; background: var(--dsw-alias-bg-layer-2, #f1f4f5); border-bottom: 1px solid var(--dsw-alias-border-l1, #d9e0e4); color: var(--dsw-alias-label-secondary, #485762); font-size: 11px; font-weight: 600; }\n[data-edrv-view] .edrv-update-cols > span, [data-edrv-view] .edrv-update-row > span { min-width: 0; padding: 0 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-update-cols > span + span, [data-edrv-view] .edrv-update-row > span + span { border-left: 1px solid var(--dsw-alias-border-l1, #d9e0e4); }\n[data-edrv-view] .edrv-update-list { position: relative; flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; }\n[data-edrv-view] .edrv-update-space { position: relative; }\n[data-edrv-view] .edrv-update-pos { position: absolute; left: 0; right: 0; }\n[data-edrv-view] .edrv-update-row { height: 100%; font-size: 11px; cursor: default; color: var(--dsw-alias-label-primary, #202932); }\n[data-edrv-view] .edrv-update-row:hover { background: var(--dsw-alias-interactive-bg-hover, #ecf3f9); }\n[data-edrv-view] .edrv-update-row.is-selected { background: var(--dsw-alias-interactive-bg-hover, #dceaf9); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #2869b4); }\n[data-edrv-view] .edrv-update-action { font-weight: 600; }\n[data-edrv-view] .edrv-update-action.tone-added { color: var(--dsw-alias-state-success-primary, #23704e); }\n[data-edrv-view] .edrv-update-action.tone-deleted, [data-edrv-view] .edrv-update-action.tone-conflict { color: var(--dsw-alias-state-error-primary, #b93b43); }\n[data-edrv-view] .edrv-update-action.tone-merged { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-action.tone-updated { color: var(--dsw-alias-state-info-primary, #2869b4); }\n[data-edrv-view] .edrv-update-path { font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-update-type { color: var(--dsw-alias-label-tertiary, #65737c); }\n[data-edrv-view] .edrv-update-empty { position: absolute; top: 50%; left: 0; right: 0; transform: translateY(-50%); text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #65737c); pointer-events: none; }\n[data-edrv-view] .edrv-update-telemetry { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 18px; padding: 10px 15px; min-height: 43px; box-sizing: border-box; font-size: 11px; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-error { color: var(--dsw-alias-state-error-primary, #b93b43); overflow-wrap: anywhere; }\n[data-edrv-view] .edrv-update-warning { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-locked { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot .edrv-update-cleanup:not(:disabled) { color: var(--dsw-alias-state-warn-primary, #a05d22); }\n[data-edrv-view] .edrv-update-foot { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--dsw-alias-border-l1, #d9e0e4); font-size: 11px; }\n[data-edrv-view] .edrv-update-counts { flex: 1; min-width: 0; color: var(--dsw-alias-label-secondary, #485762); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act { flex: none; min-width: 74px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:focus-visible, [data-edrv-view] .edrv-update-close:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #2869b4); outline-offset: 2px; }\n[data-edrv-view] .edrv-update-foot .edrv-svn-act:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-update-raw { max-height: 120px; overflow: auto; padding: 0 14px 10px; font-size: 11px; }\n[data-edrv-view] .edrv-update-raw pre { white-space: pre-wrap; overflow-wrap: anywhere; margin: 5px 0; }\n@media (max-width: 620px) {\n [data-edrv-view] .edrv-update-head { gap: 6px; padding: 0 10px; }\n [data-edrv-view] .edrv-update-table { margin: 8px 8px 0; }\n [data-edrv-view] .edrv-update-cols, [data-edrv-view] .edrv-update-row { grid-template-columns: 76px minmax(0, 1fr); }\n [data-edrv-view] .edrv-update-cols > span:nth-child(3), [data-edrv-view] .edrv-update-row > span:nth-child(3) { display: none; }\n [data-edrv-view] .edrv-update-path { white-space: normal !important; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }\n [data-edrv-view] .edrv-update-foot { flex-wrap: wrap; padding: 8px; }\n [data-edrv-view] .edrv-update-counts { flex-basis: 100%; }\n}\n\n/* ===== 侧栏形态(betterSidebar 面板内编辑区) ===== */\n[data-edrv-view][data-edrv-layout='side'] { box-sizing: border-box; height: 100%; }\n\n/* ===== 侧边栏引导条(旧页签形态、未装 betterSidebar 时) ===== */\n[data-edrv-view] .edrv-side-hint { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; padding: 6px 12px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-side-hint-text { white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-cmd { font-size: 11px; padding: 2px 6px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #ffffff); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-hint-close { margin-left: auto; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-hint-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n/* ===== search panel (sidebar, VSCode search view) ===== */\n[data-edrv-view] .edrv-search-panel { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-search-qrow { display: flex; gap: 4px; }\n[data-edrv-view] .edrv-search-input { flex: 1; min-width: 0; height: 26px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-opts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-search-opt { min-width: 24px; height: 22px; padding: 0 6px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); cursor: pointer; }\n[data-edrv-view] .edrv-search-opt:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-opt.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-border-l2, #d5dde3); }\n[data-edrv-view] .edrv-search-spacer { flex: 1; }\n[data-edrv-view] .edrv-search-summary { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 2px 4px; }\n[data-edrv-view] .edrv-search-results { flex: 1; min-height: 0; overflow: auto; font-size: 12px; }\n[data-edrv-view] .edrv-search-group { margin-bottom: 2px; }\n[data-edrv-view] .edrv-search-file { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-search-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-search-fname { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-search-fdir { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-search-fcount { flex-shrink: 0; min-width: 16px; height: 15px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); font-size: 10px; line-height: 15px; text-align: center; }\n[data-edrv-view] .edrv-search-line { display: flex; gap: 6px; padding: 1px 4px 1px 16px; border-radius: 4px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-search-line.alt { background: var(--dsw-alias-bg-layer-1, #ffffff); }\n[data-edrv-view] .edrv-search-line:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-ln { flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; min-width: 26px; text-align: right; user-select: none; }\n[data-edrv-view] .edrv-search-text { overflow: hidden; text-overflow: ellipsis; white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-search-hit { background: var(--dsw-alias-state-warn-primary, #e5b93f); color: var(--dsw-alias-label-primary-inverted, #1f2933); border-radius: 2px; padding: 0 1px; }\n[data-edrv-view] .edrv-search-empty { padding: 8px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-error { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-search-warn { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-warn-primary, rgba(240,173,78,.45)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(240,173,78,.12)); color: var(--dsw-alias-state-warn-primary, #b8860b); font-size: 11px; word-break: break-all; }\n\n/* include/exclude filter rows (collapsible) */\n[data-edrv-view] .edrv-search-rows { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }\n[data-edrv-view] .edrv-search-row { display: flex; align-items: center; gap: 4px; }\n[data-edrv-view] .edrv-search-field { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; }\n[data-edrv-view] .edrv-search-field:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-field.dim { opacity: .45; }\n[data-edrv-view] .edrv-search-field::placeholder { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-search-toggle { min-width: 24px; height: 24px; padding: 0 5px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 1; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-search-toggle:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-search-toggle.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); border-color: var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-search-toggle:disabled { opacity: .4; cursor: default; }\n\n/* ===== rules panel (sidebar, Codebuddy rules view) ===== */\n[data-edrv-view] .edrv-rules-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; padding: 6px; gap: 4px; }\n[data-edrv-view] .edrv-rules-tabs { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-tab { height: 24px; padding: 0 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-tab.on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); color: var(--dsw-alias-label-primary, #1f2933); font-weight: 600; }\n[data-edrv-view] .edrv-rules-new { margin-left: auto; height: 24px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1f2933); font-size: 11px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-new:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-ws-line { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 auto; user-select: none; }\n[data-edrv-view] .edrv-rules-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 0 2px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-list { flex: 1; min-height: 0; overflow: auto; }\n[data-edrv-view] .edrv-rules-row { display: flex; align-items: center; gap: 4px; padding: 6px 4px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; }\n[data-edrv-view] .edrv-rules-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-row.off .edrv-rules-main { opacity: .5; }\n[data-edrv-view] .edrv-rules-main { flex: 1; min-width: 0; cursor: pointer; overflow: hidden; }\n[data-edrv-view] .edrv-rules-line1 { display: flex; align-items: center; gap: 6px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sub { display: flex; align-items: center; gap: 5px; margin-top: 3px; min-width: 0; }\n[data-edrv-view] .edrv-rules-sublabel { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-file { font-weight: 600; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-path { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-type { flex-shrink: 0; height: 15px; padding: 0 5px; border-radius: 999px; font-size: 10px; line-height: 15px; }\n[data-edrv-view] .edrv-rules-type-always { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.15)); color: var(--dsw-alias-state-success-primary, #0b7c46); }\n[data-edrv-view] .edrv-rules-type-auto { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(79,140,255,.16)); color: var(--dsw-alias-link, #3367c9); }\n[data-edrv-view] .edrv-rules-type-manual { background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-rules-offtag { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }\n[data-edrv-view] .edrv-rules-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }\n[data-edrv-view] .edrv-rules-act { min-width: 22px; height: 22px; padding: 0 4px; border: none; border-radius: 5px; background: transparent; font-size: 12px; line-height: 1; cursor: pointer; color: var(--dsw-alias-label-secondary, #52606d); display: inline-flex; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-rules-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rules-switch { position: relative; width: 30px; height: 16px; border-radius: 999px; border: none; background: var(--dsw-alias-border-l2, #b9c4cc); cursor: pointer; transition: background .15s; flex-shrink: 0; padding: 0; }\n[data-edrv-view] .edrv-rules-switch.on { background: var(--dsw-alias-brand-primary, #7c5cff); }\n[data-edrv-view] .edrv-rules-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--dsw-alias-bg-layer-1, #fff); transition: left .15s; }\n[data-edrv-view] .edrv-rules-switch.on .edrv-rules-knob { left: 16px; }\n[data-edrv-view] .edrv-rules-empty { padding: 10px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n[data-edrv-view] .edrv-rules-err { padding: 6px 8px; border: 1px solid var(--dsw-alias-state-error-primary, rgba(217,83,79,.4)); border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217,83,79,.12)); color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; word-break: break-all; }\n[data-edrv-view] .edrv-rules-editor { display: flex; flex-direction: column; min-height: 0; flex: 1; gap: 6px; }\n[data-edrv-view] .edrv-rules-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-crumb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-rules-editor-ctl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-ctl-label { font-size: 11px; color: var(--dsw-alias-label-secondary, #52606d); flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select { height: 24px; padding: 0 4px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rules-select:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-desc-input { flex: 1; min-width: 0; }\n[data-edrv-view] .edrv-rules-input { flex: 1; min-width: 0; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 11px; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-input:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-body { flex: 1; min-height: 0; resize: none; padding: 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; line-height: 1.5; outline: none; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-rules-body:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-rules-editor-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.5; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-editor-foot { display: flex; justify-content: flex-end; flex: 0 0 auto; }\n[data-edrv-view] .edrv-rules-save { height: 24px; padding: 0 14px; border: none; border-radius: 6px; background: var(--dsw-alias-brand-primary, #0f9d58); color: var(--dsw-alias-label-primary-inverted, #fff); font-size: 12px; cursor: pointer; }\n[data-edrv-view] .edrv-rules-save:disabled { opacity: .5; cursor: default; }\n\n/* 命令栏(Ctrl+Shift+P / F1):body 级 portal 浮层,故不依赖编辑区几何。\n ⚠️ 浮层 z-index 阶梯(自下而上;改动务必保持顺序,tests/overlayZOrder.test.ts 守约):\n 编辑器内浮层 60(.edrv-search-pop) / 71(.edrv-ctxmenu)\n < 190 命令栏遮罩 < 200 命令栏\n < 195 片段浮窗遮罩 < 197 片段二级弹窗遮罩\n 命令栏必须**高于**片段浮窗:曾因片段遮罩(192) 压过命令栏(191),导致片段浮窗打开时\n 命令栏被整体盖住,按 Ctrl+Shift+P 因「已打开即早退」而毫无反应(表现为快捷键失效)。 */\n[data-edrv-view] .edrv-palette-mask { position: fixed; inset: 0; z-index: 190; background: rgba(0, 0, 0, .28); }\n[data-edrv-view] .edrv-palette { position: fixed; top: 12vh; left: 50%; transform: translateX(-50%); z-index: 200; width: min(580px, calc(100vw - 32px)); max-height: 64vh; display: flex; flex-direction: column; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 12px 34px rgba(31, 41, 51, .24); overflow: hidden; }\n[data-edrv-view] .edrv-palette-input { height: 38px; flex: 0 0 auto; padding: 0 12px; border: none; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: transparent; color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; outline: none; }\n[data-edrv-view] .edrv-palette-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 4px; }\n[data-edrv-view] .edrv-palette-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; font-size: 12px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; }\n[data-edrv-view] .edrv-palette-row.edrv-palette-sel { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 157, 88, .10)); }\n[data-edrv-view] .edrv-palette-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-palette-cat { flex: 0 0 auto; font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-tertiary, #7b8794); }\n[data-edrv-view] .edrv-palette-key { flex: 0 0 auto; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 10px; padding: 1px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 5px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-palette-empty { padding: 14px 10px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #7b8794); text-align: center; }\n[data-edrv-view] .edrv-palette-notice { flex: 0 0 auto; padding: 6px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 11px; color: var(--dsw-alias-state-warn-primary, #b7791f); }\n\n/* 代码片段选择器(配置 / 插入):居中浮窗 + 新建二级弹窗。\n 卡片复用 mcp.css 的 .vsm-mcp-dialog(与「添加 MCP」「工作区选择」同一视觉语言),\n 此处只补遮罩、尺寸与列表布局。⚠️ 根节点须带 data-edrv-view,否则这些规则全部失配。 */\n[data-edrv-view] .edrv-snip-mask { position: fixed; inset: 0; z-index: 195; display: flex; align-items: center; justify-content: center; padding: 24px; box-sizing: border-box; background: var(--dsw-alias-bg-mask-drop, rgba(0, 0, 0, .55)); }\n/* 二级弹窗叠在一级浮窗之上(仍在命令栏之下,见上方阶梯说明) */\n[data-edrv-view] .edrv-snip-mask-top { z-index: 197; }\n[data-edrv-view] .edrv-snip-dialog { display: flex; flex-direction: column; overflow: hidden; width: min(620px, calc(100vw - 48px)); }\n[data-edrv-view] .edrv-snip-hint { margin: 0 0 12px; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-list { display: flex; flex-direction: column; gap: 1px; min-height: 0; max-height: 46vh; overflow: auto; }\n[data-edrv-view] .edrv-snip-group { padding: 12px 2px 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-row { display: flex; align-items: center; gap: 4px; width: 100%; box-sizing: border-box; padding: 2px; border: none; border-radius: 8px; background: transparent; text-align: left; color: var(--dsw-alias-label-primary, #e6e6e6); font: inherit; }\n[data-edrv-view] .edrv-snip-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, .06)); }\n[data-edrv-view] button.edrv-snip-row { cursor: pointer; }\n[data-edrv-view] .edrv-snip-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border: none; border-radius: 6px; background: transparent; text-align: left; cursor: pointer; color: inherit; font: inherit; }\n[data-edrv-view] .edrv-snip-label { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #e6e6e6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-snip-src { flex: 0 0 auto; padding-right: 6px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-snip-warn { flex: 0 0 auto; font-size: 11px; padding: 2px 7px; border-radius: 999px; background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .16)); color: var(--dsw-alias-state-warn-primary, #e5b93f); white-space: nowrap; }\n[data-edrv-view] .edrv-snip-act { flex: 0 0 auto; width: 26px; height: 26px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-snip-act:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n[data-edrv-view] .edrv-snip-empty { padding: 12px 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); line-height: 1.6; }\n/* 当前文件语言提示条(片段按语言绑定,明确告知生效范围) */\n[data-edrv-view] .edrv-snip-langbar { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin: 0 0 10px; padding: 8px 10px; border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #2b2b2b); font-size: 12px; color: var(--dsw-alias-label-secondary, #aaa); }\n[data-edrv-view] .edrv-snip-langbar b { color: var(--dsw-alias-label-primary, #e6e6e6); }\n/* 二级弹窗的动作按钮(原生 .vsm-mcp-dialog-actions 已提供排版与按钮样式) */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-dialog-actions { flex-wrap: wrap; }\n/* 删除图标按钮:.vsm-danger 的规则全部限定在 .vsm-mcp-actions button 之下,裸用无样式,此处补自有类 */\n[data-edrv-view] .edrv-snip-act.edrv-snip-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(217, 83, 79, .16)); color: var(--dsw-alias-state-error-primary, #f07878); }\n/* 浮窗内的原生空态(.vsm-mcp-empty 默认为整页尺寸)压缩为对话框尺寸 */\n[data-edrv-view] .edrv-snip-dialog .vsm-mcp-empty { padding: 26px 16px; border: 0; }\n\n\n\n\n\n/* 调试(DAP):断点圆点 / 停帧行 / 浮动调试工具条。\n 圆点挂在 Monaco glyphMargin(glyphMarginClassName),行背景为 isWholeLine className;\n 工具条绝对定位于编辑区右上(editorArea relative 由 .edrv-editor-area 自身定位)。 */\n/* 断点圆点:装饰类占满整行,圆点经 ::before 在本行盒子内居中(行归属明确、不跨行界) */\n[data-edrv-view] .edrv-dap-bp { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dap-bp-off { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-off::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n/* 未验证断点(适配器真实回执 verified:false):空心红圈,对齐 VS Code 空心圆语义 */\n[data-edrv-view] .edrv-dap-bp-unverified { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-unverified::before { content: ''; display: block; box-sizing: border-box; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; border: 1px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n[data-edrv-view] .edrv-dap-paused-line { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .22)); }\n[data-edrv-view] .edrv-dap-paused-glyph { cursor: pointer; }\n[data-edrv-view] .edrv-dap-paused-glyph::before { content: ''; display: block; width: 5px; height: 12px; margin: 4px auto 0; border-radius: 2px; background: var(--dsw-alias-state-warn-primary, #e5b93f); }\n[data-edrv-view] .edrv-editor-area { position: relative; }\n/* launch.json 右下角「添加配置」按钮(VSCode 同款形态):仅 launch.json 打开时渲染 */\n[data-edrv-view] .edrv-addcfg { position: absolute; right: 14px; bottom: 10px; z-index: 13; padding: 4px 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; line-height: 18px; cursor: pointer; box-shadow: 0 1px 4px rgba(0, 0, 0, .1); }\n[data-edrv-view] .edrv-addcfg:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] .edrv-dapbar { position: absolute; top: 6px; right: 14px; z-index: 12; display: none; align-items: center; gap: 3px; padding: 3px 5px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); background: var(--dsw-alias-bg-layer-1, #f7f9fa); box-shadow: 0 2px 8px rgba(0, 0, 0, .12); }\n[data-edrv-view] .edrv-dapbar.on { display: flex; border-color: var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapbar-grip { flex: 0 0 auto; width: 16px; height: 22px; display: inline-flex; align-items: center; justify-content: center; cursor: grab; user-select: none; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 12px; }\n[data-edrv-view] .edrv-dapbar-grip:active, [data-edrv-view] .edrv-dapbar.dragging .edrv-dapbar-grip { cursor: grabbing; }\n[data-edrv-view] .edrv-dapbar.dragging { user-select: none; }\n[data-edrv-view] .edrv-dapbar-cfg { max-width: 220px; height: 22px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapbar-btn { min-width: 24px; height: 22px; padding: 0 6px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 12px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dapbar-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dapbar-btn:disabled { opacity: .35; cursor: default; }\n[data-edrv-view] .edrv-dapbar-phase { padding: 0 4px; font-size: 11px; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbar-phase.paused { color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dapbar-err { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--dsw-alias-state-error-primary, #e05252); }\n\n/* 调试面板(活动栏「调试」页签):四段折叠区 + 断点列表 + REPL。 */\n[data-edrv-view] .edrv-dappanel { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; overflow: auto; padding: 2px 2px 8px; }\n[data-edrv-view] .edrv-dappanel-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; row-gap: 4px; font-size: 12px; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dappanel-phase { padding: 2px 8px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dappanel-phase.paused { background: var(--dsw-alias-state-warn-tertiary, rgba(229, 185, 63, .18)); color: var(--dsw-alias-state-warn-primary, #b7791f); font-weight: 600; }\n[data-edrv-view] .edrv-dappanel-cfg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dappanel-pid { font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; }\n[data-edrv-view] .edrv-dappanel-err { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapseg { display: flex; flex-direction: column; gap: 2px; min-height: 0; }\n[data-edrv-view] .edrv-dapseg-body { min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }\n/* 段间分隔条(splitview sash):拖动调整相邻两段高度占比;各段高度由 DebugPanel 内联接管。 */\n[data-edrv-view] .edrv-dapseg-sash { flex: 0 0 auto; height: 4px; margin: 2px 0; border-radius: 2px; background: transparent; cursor: row-resize; }\n[data-edrv-view] .edrv-dapseg-sash:hover, [data-edrv-view] .edrv-dapseg-sash.dragging { background: var(--dsw-alias-border-l2, #cdd6da); }\n/* 折叠段头:chevron + 标题 + 悬浮动作区(动作 hover 显隐;REPL 头不参与折叠)。 */\n[data-edrv-view] .edrv-dapseg-head { display: flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-tertiary, #7b8794); padding: 2px 2px; cursor: pointer; border-radius: 4px; }\n[data-edrv-view] .edrv-dapseg-head:hover { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-daprepl .edrv-dapseg-head { cursor: default; padding: 2px 0; }\n[data-edrv-view] .edrv-dapseg-chevron { flex: 0 0 auto; width: 14px; text-align: center; font-size: 10px; }\n[data-edrv-view] .edrv-dapseg-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapseg-act { flex: 0 0 auto; min-width: 20px; height: 18px; padding: 0 4px; border: none; border-radius: 4px; background: transparent; color: var(--dsw-alias-label-tertiary, #7b8794); font-size: 11px; line-height: 1; cursor: pointer; opacity: 0; }\n[data-edrv-view] .edrv-dapseg-head:hover .edrv-dapseg-act { opacity: 1; }\n[data-edrv-view] .edrv-dapseg-act:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dapempty { font-size: 12px; color: var(--dsw-alias-label-tertiary, #9aa5b1); padding: 4px 2px; }\n[data-edrv-view] .edrv-dapframe { display: flex; align-items: baseline; gap: 8px; padding: 3px 4px; border-radius: 6px; cursor: pointer; font-size: 12px; }\n[data-edrv-view] .edrv-dapframe:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapframe-name { color: var(--dsw-alias-label-primary, inherit); font-weight: 600; }\n[data-edrv-view] .edrv-dapframe-loc { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-dapscope { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, #52606d); padding: 4px 2px 1px; }\n[data-edrv-view] .edrv-dapvar { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; font-size: 12px; padding: 1px 2px; min-width: 0; }\n[data-edrv-view] .edrv-dapvar-tw { flex: 0 0 auto; width: 14px; border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; font-size: 10px; padding: 0; }\n[data-edrv-view] span.edrv-dapvar-tw { cursor: default; }\n[data-edrv-view] .edrv-dapvar-name { flex: 0 1 34%; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-primary, inherit); font-family: var(--ds-font-family-code, ui-monospace, monospace); white-space: nowrap; }\n[data-edrv-view] .edrv-dapvar-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapvar-type { flex: 0 0 auto; font-size: 10px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapframe.selected { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .16)); outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n[data-edrv-view] .edrv-dapwatch { display: flex; align-items: baseline; gap: 6px; width: 100%; box-sizing: border-box; min-width: 0; font-size: 12px; padding: 1px 2px; }\n[data-edrv-view] .edrv-dapwatch-x { border: none; background: transparent; color: var(--dsw-alias-label-tertiary, #9aa5b1); cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapwatch-x:hover { color: var(--dsw-alias-state-error-primary, #e05252); }\n[data-edrv-view] .edrv-dapwatch-add { padding: 2px 0; }\n[data-edrv-view] .edrv-dapbp { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 2px 2px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-dapbp:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-dapbp:active { background: var(--dsw-alias-interactive-bg-pressed, rgba(127, 127, 127, .2)); }\n[data-edrv-view] .edrv-dapbp-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); }\n[data-edrv-view] .edrv-dapbp-dot.off { background: transparent; border: 1.5px solid var(--dsw-alias-label-tertiary, #9aa5b1); opacity: .6; }\n[data-edrv-view] .edrv-dapbp-file { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: none; background: transparent; color: var(--dsw-alias-label-primary, inherit); cursor: pointer; padding: 0; font-size: 12px; }\n[data-edrv-view] .edrv-dapbp-dir { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; text-align: right; }\n[data-edrv-view] .edrv-dapbp-line { flex: 0 0 auto; font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-dapbp-x { flex: 0 0 auto; border: none; background: transparent; color: transparent; cursor: pointer; width: 14px; }\n[data-edrv-view] .edrv-dapbp:hover .edrv-dapbp-x { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-dapbp-x:hover { color: var(--dsw-alias-state-error-primary, #e05252) !important; }\n[data-edrv-view] .edrv-daprepl { display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-daprepl-log { display: flex; flex-direction: column; gap: 1px; max-height: 180px; overflow: auto; }\n[data-edrv-view] .edrv-daprepl-line { font-size: 11px; font-family: var(--ds-font-family-code, ui-monospace, monospace); color: var(--dsw-alias-label-secondary, #52606d); white-space: pre-wrap; word-break: break-all; display: flex; gap: 6px; }\n[data-edrv-view] .edrv-daprepl-line.out { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-daprepl-in { width: 100%; box-sizing: border-box; height: 26px; padding: 0 8px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-dappanel-run { flex: 0 0 auto; width: 24px; height: 24px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; line-height: 1; cursor: pointer; }\n[data-edrv-view] .edrv-dappanel-run:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .15)); }\n[data-edrv-view] .edrv-dappanel-run:disabled { opacity: .35; cursor: default; background: transparent; }\n/* 面板顶部配置下拉(与编辑器浮动条 .edrv-dapbar-cfg 同视觉)。 */\n[data-edrv-view] .edrv-dappanel-cfg-select { max-width: 190px; height: 24px; padding: 0 4px; font-size: 11px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n/* 断点行内编辑行(面板内展开编辑,控件复用 .edrv-bpwidget-* 视觉;左缩进对齐断点行文字列)。 */\n[data-edrv-view] .edrv-dapbp-edit { display: flex; align-items: center; gap: 6px; padding: 3px 2px 3px 20px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-mode { height: 20px; }\n[data-edrv-view] .edrv-dapbp-edit .edrv-bpwidget-input { height: 20px; }\n/* 断点回执:未验证空心红圈(须排在 .off 之后,禁用态优先展示灰圈语义)。 */\n[data-edrv-view] .edrv-dapbp-dot.unverified { background: transparent; border: 1.5px solid var(--dsw-alias-state-error-primary, #e51400); opacity: .75; }\n/* 断点行内编辑浮层(Monaco content widget,锚定断点行上方;对齐 VS Code breakpointWidget 形态)。 */\n/* View Zone 浮层:宽度/左移由 BpWidget.syncWidth 按 layout 内联设置\n (Monaco .view-zones 仅内容区宽,需补偿 contentLeft 与滚动条),此处只管视觉。 */\n[data-edrv-view] .edrv-bpwidget { position: relative; box-sizing: border-box; display: flex; align-items: center; gap: 6px; padding: 3px 8px; border-top: 1px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-layer-1, #f7f9fa); }\n/* 断点编辑中的目标行高亮(浮层打开期间) */\n[data-edrv-view] .edrv-dap-editing-line { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n[data-edrv-view] .edrv-bpwidget-mode { flex: 0 0 auto; height: 22px; padding: 0 4px; font-size: 12px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); cursor: pointer; }\n[data-edrv-view] .edrv-bpwidget-input { flex: 1 1 auto !important; min-width: 0; width: auto !important; box-sizing: border-box; height: 22px; padding: 0 6px; font-size: 12px; font-family: var(--ds-font-family-code, ui-monospace, monospace); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 3px; background: var(--dsw-alias-bg-base, transparent); color: var(--dsw-alias-label-primary, inherit); }\n[data-edrv-view] .edrv-bpwidget-input:focus { outline: 1px solid var(--dsw-alias-border-l2, #cdd6da); }\n\n/* 断点 hover 预览(glyph 区):半透明小红点 + pointer 光标(对齐 VS Code .codicon-debug-hint)。 */\n[data-edrv-view] .edrv-dap-bp-hint { cursor: pointer; }\n[data-edrv-view] .edrv-dap-bp-hint::before { content: ''; display: block; width: 7px; height: 7px; margin: 6px auto 0; border-radius: 50%; background: var(--dsw-alias-state-error-primary, #e51400); opacity: .4; }\n/* 行号区也给出可点提示(与 VS Code 一致:glyph 区与行号区都可点/可 hover);\n 不拦截折叠箭头等自有控件的光标。 */\n[data-edrv-view] .edrv-editor-row .monaco-editor .margin-view-overlays .line-numbers:hover { cursor: pointer; }\n\n/* 暂停态 hover 调试块 + 变量树(成员全量直显 + table 成员折叠展开)。\n ⚠️ 选择器一律用 data-*:本仓 vendored Monaco 的 markdown→DOM 管线会剥掉我们 HTML 里的\n class(supportHtml/isTrusted 都不豁免),只有 data-* 存活;用 class 会静默失效。 */\n[data-edrv-view] [data-edrv-dap] { display: block; min-width: 0; }\n[data-edrv-view] [data-edrv-head] { display: flex; align-items: baseline; gap: 2px; }\n[data-edrv-view] [data-edrv-head] [data-edrv-expr] { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-head] [data-edrv-val] { font-family: var(--ds-font-family-code, ui-monospace, monospace); word-break: break-all; }\n[data-edrv-view] [data-edrv-type] { margin-top: 2px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-tree] { margin: 2px 0 0; padding: 0; list-style: none; font-variant-numeric: tabular-nums; }\n[data-edrv-view] [data-edrv-line] { display: flex; align-items: baseline; gap: 4px; padding: 1px 2px; border-radius: 3px; white-space: nowrap; }\n[data-edrv-view] [data-edrv-line]:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .12)); }\n/* 折叠按钮(对齐 CodeBuddy 调试浮窗变量树的 twist):热区 14px + hover 反馈,未可展开时留空占位 */\n[data-edrv-view] [data-edrv-twist] { flex: 0 0 auto; box-sizing: border-box; width: 14px; height: 14px; line-height: 13px; text-align: center; border-radius: 3px; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-twist]:not(:empty) { cursor: pointer; user-select: none; }\n[data-edrv-view] [data-edrv-twist]:not(:empty):hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .2)); color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-name] { flex: 0 0 auto; color: var(--dsw-alias-label-primary, #1f2933); }\n[data-edrv-view] [data-edrv-line] [data-edrv-eq] { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary, #8a949b); }\n[data-edrv-view] [data-edrv-line] [data-edrv-val] { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] [data-edrv-note] { color: var(--dsw-alias-label-tertiary, #8a949b); font-style: italic; }\n/* Alt 提示层:由 hoverTree 注入到 .monaco-hover(滚动区之外)贴底常驻;容器预留同高内边距避免遮挡末行。\n 不再用 sticky / `:has()`:滚动链在 .monaco-hover-content 上,sticky 会被滚走。 */\n[data-edrv-view] .monaco-hover[data-edrv-hint-host=\"1\"] { padding-bottom: 22px; }\n[data-edrv-view] [data-edrv-hint=\"1\"] { position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box; height: 22px; padding: 2px 8px; font-size: 11px; line-height: 18px; color: var(--dsw-alias-label-tertiary, #8a949b); background: var(--dsw-alias-bg-overlay, #ffffff); border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n\n/* Workbench panes: existing theme, action-driven drop previews. ddj 2026年10月14号 */\n[data-edrv-view] .edrv-group, [data-edrv-view] .edrv-group-body,\n[data-edrv-view] .edrv-full-slot, [data-edrv-view] .edrv-full-view,\n[data-edrv-view] .edrv-passive-view { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-group { border: 0; }\n[data-edrv-view] .edrv-group .edrv-tabs { flex: 0 0 auto; touch-action: none; }\n[data-edrv-view] .edrv-group-tab-name { overflow: hidden; text-overflow: ellipsis; max-width: 180px; }\n[data-edrv-view] .edrv-split { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-vertical { flex-direction: column; }\n[data-edrv-view] .edrv-split-child { display: flex; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-split-handle { flex: 0 0 5px; background: var(--dsw-alias-border-l1, #e0e6e8); cursor: col-resize; touch-action: none; }\n[data-edrv-view] .edrv-split-vertical > .edrv-split-handle { cursor: row-resize; }\n[data-edrv-view] .edrv-split-handle:hover, [data-edrv-view] .edrv-split-handle:focus-visible { background: var(--dsw-alias-state-info-primary, #4d6bfe); }\n[data-edrv-view].edrv-floating { position: fixed; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2, #cdd6da); background: var(--dsw-alias-bg-base, #fff); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: 0 8px 28px rgba(0,0,0,.22); }\n[data-edrv-view] .edrv-floating-title { display: flex; align-items: center; gap: 8px; flex: 0 0 32px; padding: 0 8px; background: var(--dsw-alias-bg-layer-1, #f7f9fa); cursor: move; touch-action: none; user-select: none; }\n[data-edrv-view] .edrv-floating-title > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-floating-content { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; overflow: auto; box-sizing: border-box; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-dialog { padding: 12px; overflow: auto; }\n[data-edrv-view] .edrv-floating-content.edrv-svn-patch-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svn-sum-dialog,\n[data-edrv-view] .edrv-floating-content.edrv-svnplan-dialog { padding: 12px; max-width: none; gap: 8px; }\n[data-edrv-view] .edrv-floating-content.edrv-svnlog-stats { max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svn-patch-pre,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-list { flex: 1; min-height: 0; max-height: none; }\n[data-edrv-view] .edrv-floating-content .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content .edrv-svn-sum-form { flex-wrap: wrap; }\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-head,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-filter,\n[data-edrv-view] .edrv-floating-content > .edrv-svnlog-foot { flex-shrink: 0; }\n[data-edrv-view] .edrv-floating-title > button { border: 0; background: transparent; color: inherit; cursor: pointer; }\n[data-edrv-view] .edrv-floating-grip { position: absolute; width: 16px; height: 16px; bottom: 0; right: 0; cursor: nwse-resize; touch-action: none; border-right: 3px solid var(--dsw-alias-border-l2, #cdd6da); border-bottom: 3px solid var(--dsw-alias-border-l2, #cdd6da); }\n.edrv-drag-overlay { position: fixed; z-index: 150; pointer-events: none; box-sizing: border-box; border: 0; background: color-mix(in srgb, var(--dsw-alias-state-info-primary, #4d6bfe) 25%, transparent); }\n.edrv-drop-label, .edrv-drag-ghost { position: fixed; z-index: 151; pointer-events: none; padding: 4px 8px; background: var(--dsw-alias-bg-overlay, #fff); color: var(--dsw-alias-label-primary, #1f2933); border: 1px solid var(--dsw-alias-border-l2, #cdd6da); font-size: 12px; white-space: nowrap; }\n.edrv-drop-label { position: absolute; left: 4px; top: 4px; }\n[data-edrv-view] .edrv-tab:focus-visible { outline: 2px solid var(--dsw-alias-state-info-primary, #4d6bfe); outline-offset: -2px; }\n@media (prefers-reduced-motion: reduce) { .edrv-drag-overlay { transition: none; } }\n";
|
|
59
|
+
const tagId$4 = "dsh-vscode-mode/editor.css";
|
|
60
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
|
|
61
|
+
const tag = document.createElement("style");
|
|
62
|
+
tag.dataset.plugin = "dsh-vscode-mode";
|
|
63
|
+
tag.dataset.pluginCss = tagId$4;
|
|
64
|
+
tag.textContent = css$4;
|
|
65
|
+
document.head.appendChild(tag);
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/quickmenu.css.mjs
|
|
69
|
+
const css$3 = "/* dsh-vscode-mode — 快捷菜单(悬浮按钮 + SAO 风格多列级联菜单 + 配置弹窗)\n * 作者 ddj 2026年10月30号\n *\n * 浮层约定:根节点带 data-edrv-view(editor.css 的浮层样式同款作用域),\n * 颜色全部走 DSH 设计令牌并带浅色兜底,避免深/浅主题下不可读。\n *\n * 设计取向:整个菜单只花一次「亮色」——当前行的 2px 强调条与悬浮按钮的强调色;\n * 其余全部收成中性灰阶与发丝线,靠层级、间距和一处滑入动效表达结奏,\n * 不做每行图标、不做渐变装饰、不做逐卡片阴影。\n */\n\n/* ── 令牌 ─────────────────────────────────────────────────── */\n/* 定义在 [data-edrv-view] 上:菜单(portal 到 body 的独立根)与 ModalShell 弹窗\n * 各自是不同根节点,令牌若只挂在菜单根上,弹窗里的按钮/芯片会取不到颜色。 */\n[data-edrv-view] {\n --qm-surface: var(--dsw-alias-bg-overlay, #ffffff);\n --qm-sunken: var(--dsw-alias-bg-layer-1, #f4f7fa);\n --qm-ink: var(--dsw-alias-label-primary, #1f2933);\n --qm-ink-2: var(--dsw-alias-label-secondary, #55616e);\n --qm-ink-3: var(--dsw-alias-label-tertiary, #8a95a1);\n --qm-line: var(--dsw-alias-border-l2, #d8dfe6);\n --qm-accent: var(--dsw-alias-brand-primary, #2f6fed);\n --qm-accent-soft: var(--dsw-alias-interactive-bg-hover, rgba(47, 111, 237, .1));\n --qm-hover: var(--dsw-alias-interactive-bg-hover, rgba(31, 41, 51, .06));\n --qm-warn: var(--dsw-alias-state-warn-primary, #cf8b1f);\n --qm-danger: var(--dsw-alias-state-error-primary, #d64545);\n --qm-radius: 10px;\n --qm-row: 34px;\n --qm-elev:\n 0 12px 30px -12px rgba(16, 24, 40, .3),\n 0 2px 6px -2px rgba(16, 24, 40, .12);\n}\n\n/* 根层:铺满浮层、默认点击穿透;z-index 要压过同槽位的常驻浮层(如音乐条),否则菜单被盖住 */\n[data-edrv-view].edrv-qm-root {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 60;\n font-size: 12px;\n line-height: 1.4;\n}\n\n/* ── 悬浮按钮 ─────────────────────────────────────────────── */\n[data-edrv-view] .edrv-qm-launcher {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: 9px;\n border: 1px solid var(--qm-line);\n background: var(--qm-surface);\n color: var(--qm-accent);\n box-shadow: 0 1px 2px rgba(16, 24, 40, .12);\n pointer-events: auto;\n cursor: grab;\n touch-action: none;\n user-select: none;\n transition: transform .14s cubic-bezier(.2, .8, .3, 1), box-shadow .14s ease, border-color .14s ease;\n}\n\n[data-edrv-view] .edrv-qm-launcher:hover {\n transform: translateY(-1px);\n border-color: var(--qm-accent);\n box-shadow: 0 4px 14px -4px rgba(16, 24, 40, .3), 0 0 0 3px var(--qm-accent-soft);\n}\n\n[data-edrv-view] .edrv-qm-launcher:active {\n transform: scale(.96);\n}\n\n[data-edrv-view] .edrv-qm-launcher--drag {\n cursor: grabbing;\n transform: scale(1.04);\n box-shadow: 0 8px 20px -6px rgba(16, 24, 40, .38);\n}\n\n[data-edrv-view] .edrv-qm-launcher--open {\n border-color: var(--qm-accent);\n background: var(--qm-accent);\n color: #fff;\n box-shadow: 0 0 0 3px var(--qm-accent-soft);\n}\n\n/* 隐藏态:半透明小圆点(仍可拖动/点开,防止关掉后无处恢复) */\n[data-edrv-view] .edrv-qm-launcher--mini {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n border-color: transparent;\n opacity: .4;\n box-shadow: none;\n transition: opacity .14s ease, transform .14s ease;\n}\n\n[data-edrv-view] .edrv-qm-launcher--mini:hover {\n opacity: .9;\n transform: scale(1.2);\n box-shadow: none;\n}\n\n[data-edrv-view] .edrv-qm-launcher svg {\n width: 19px;\n height: auto;\n display: block;\n pointer-events: none;\n}\n\n/* ── 菜单容器与列 ─────────────────────────────────────────── */\n[data-edrv-view] .edrv-qm-menu {\n position: absolute;\n pointer-events: auto;\n}\n\n/* 菜单本体(portal 到 body):铺满视口的穿透层,层级高于输入框卡片与其它浮层条目 */\n[data-edrv-view].edrv-qm-menu {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 150;\n}\n\n[data-edrv-view] .edrv-qm-col {\n position: absolute;\n display: flex;\n flex-direction: column;\n width: 220px;\n box-sizing: border-box;\n pointer-events: auto;\n border-radius: var(--qm-radius);\n border: 1px solid var(--qm-line);\n background: var(--qm-surface);\n box-shadow: var(--qm-elev);\n overflow: hidden;\n animation: edrv-qm-col-in .14s cubic-bezier(.2, .8, .3, 1);\n}\n\n@keyframes edrv-qm-col-in {\n from { opacity: 0; transform: translateX(-6px); }\n to { opacity: 1; transform: none; }\n}\n\n[data-edrv-view] .edrv-qm-scroll {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 8px 7px;\n scrollbar-width: none;\n}\n\n/* 上下渐隐只在内容确实超出列高时启用(短列被遮掉会看不清) */\n[data-edrv-view] .edrv-qm-scroll--fade {\n mask-image: linear-gradient(to bottom, transparent 0, #000 36px, #000 calc(100% - 36px), transparent 100%);\n -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 36px, #000 calc(100% - 36px), transparent 100%);\n}\n\n[data-edrv-view] .edrv-qm-scroll::-webkit-scrollbar {\n width: 0;\n height: 0;\n}\n\n[data-edrv-view] .edrv-qm-list {\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n/* ── 行 ──────────────────────────────────────────────────── */\n[data-edrv-view] .edrv-qm-row {\n position: relative;\n display: flex;\n align-items: center;\n height: var(--qm-row);\n min-height: var(--qm-row);\n box-sizing: border-box;\n padding: 0 7px 0 10px;\n border-radius: 7px;\n color: var(--qm-ink);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n user-select: none;\n transform-origin: center center;\n transition: background .1s ease, color .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-row:hover,\n[data-edrv-view] .edrv-qm-row--active {\n background: var(--qm-hover);\n}\n\n/* 唯一的强调色落点:当前行左缘的短竖条 */\n[data-edrv-view] .edrv-qm-row--active::before {\n content: '';\n position: absolute;\n left: 3px;\n top: 50%;\n width: 2px;\n height: 15px;\n margin-top: -7.5px;\n border-radius: 2px;\n background: var(--qm-accent);\n}\n\n[data-edrv-view] .edrv-qm-row:focus-visible,\n[data-edrv-view] .edrv-qm-launcher:focus-visible,\n[data-edrv-view] .edrv-qm-foot:focus-visible,\n[data-edrv-view] .edrv-qm-btn:focus-visible,\n[data-edrv-view] .edrv-qm-input:focus-visible,\n[data-edrv-view] .edrv-qm-textarea:focus-visible,\n[data-edrv-view] .edrv-qm-select:focus-visible,\n[data-edrv-view] .edrv-qm-chip:focus-visible {\n outline: 2px solid var(--qm-accent);\n outline-offset: 1px;\n}\n\n[data-edrv-view] .edrv-qm-row-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* 组行只留一枚折角箭头,文字「组」属于冗余信息 */\n[data-edrv-view] .edrv-qm-row-chevron {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n color: var(--qm-ink-3);\n transition: color .1s ease, transform .12s ease;\n}\n\n[data-edrv-view] .edrv-qm-row-chevron svg {\n width: 12px;\n height: 12px;\n display: block;\n}\n\n[data-edrv-view] .edrv-qm-row--group:hover .edrv-qm-row-chevron,\n[data-edrv-view] .edrv-qm-row--group.edrv-qm-row--active .edrv-qm-row-chevron {\n color: var(--qm-accent);\n transform: translateX(1px);\n}\n\n[data-edrv-view] .edrv-qm-row-kind {\n flex: 0 0 auto;\n color: var(--qm-ink-3);\n font-size: 10px;\n font-weight: 400;\n}\n\n[data-edrv-view] .edrv-qm-row-badge {\n flex: 0 0 auto;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--qm-warn);\n}\n\n[data-edrv-view] .edrv-qm-row-badge--error {\n background: var(--qm-danger);\n}\n\n/* 行尾「⋯」:占位常驻,避免悬停时行内容左右跳动 */\n[data-edrv-view] .edrv-qm-row-more {\n flex: 0 0 auto;\n width: 18px;\n height: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 5px;\n background: transparent;\n color: var(--qm-ink-3);\n cursor: pointer;\n font-size: 13px;\n line-height: 1;\n opacity: 0;\n transition: opacity .1s ease, background .1s ease, color .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-row:hover .edrv-qm-row-more,\n[data-edrv-view] .edrv-qm-row--active .edrv-qm-row-more,\n[data-edrv-view] .edrv-qm-row-more:focus-visible {\n opacity: 1;\n}\n\n[data-edrv-view] .edrv-qm-row-more:hover {\n background: var(--qm-surface);\n color: var(--qm-accent);\n}\n\n[data-edrv-view] .edrv-qm-row--add {\n margin-top: 5px;\n border: 1px dashed var(--qm-line);\n color: var(--qm-ink-2);\n font-size: 11.5px;\n font-weight: 400;\n justify-content: center;\n}\n\n[data-edrv-view] .edrv-qm-row--add:hover {\n border-color: var(--qm-accent);\n color: var(--qm-accent);\n background: var(--qm-accent-soft);\n}\n\n/* ── 底部固定栏(菜单设置) ──────────────────────────────── */\n[data-edrv-view] .edrv-qm-foot {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 32px;\n padding: 0 10px;\n border-top: 1px solid var(--qm-line);\n background: var(--qm-sunken);\n color: var(--qm-ink-2);\n font-size: 11.5px;\n cursor: pointer;\n user-select: none;\n transition: color .1s ease, background .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-foot:hover {\n color: var(--qm-accent);\n background: var(--qm-hover);\n}\n\n[data-edrv-view] .edrv-qm-foot-icon {\n display: flex;\n align-items: center;\n}\n\n[data-edrv-view] .edrv-qm-foot-icon svg {\n width: 13px;\n height: 13px;\n display: block;\n}\n\n/* ── 空态:邀请而不是气氛 ────────────────────────────────── */\n[data-edrv-view] .edrv-qm-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n padding: 14px 10px 12px;\n text-align: center;\n color: var(--qm-ink-3);\n font-size: 11px;\n font-weight: 400;\n}\n\n[data-edrv-view] .edrv-qm-empty-title {\n color: var(--qm-ink-2);\n font-size: 12px;\n font-weight: 600;\n}\n\n[data-edrv-view] .edrv-qm-empty .edrv-qm-btn {\n margin-top: 7px;\n}\n\n/* ── 弹窗表单 ────────────────────────────────────────────── */\n[data-edrv-view] .edrv-qm-dialog {\n width: min(620px, calc(100vw - 48px));\n max-height: min(84vh, 720px);\n box-sizing: border-box;\n /* 复位 .vsm-mcp-dialog 的整卡内边距(本弹窗自带 头/体/脚 三段布局) */\n padding: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* 固定头:长表单滚动时仍知道在编辑哪一项 */\n[data-edrv-view] .edrv-qm-head {\n flex: 0 0 auto;\n padding: 14px 16px 12px;\n border-bottom: 1px solid var(--qm-line);\n}\n\n[data-edrv-view] .edrv-qm-head-title {\n color: var(--qm-ink);\n font-size: 14px;\n font-weight: 650;\n line-height: 1.35;\n}\n\n[data-edrv-view] .edrv-qm-head-sub {\n margin-top: 3px;\n color: var(--qm-ink-3);\n font-size: 11px;\n font-weight: 400;\n}\n\n[data-edrv-view] .edrv-qm-sep {\n height: 1px;\n margin: 2px 0;\n background: var(--qm-line);\n opacity: .75;\n}\n\n[data-edrv-view] .edrv-qm-dialog-body {\n flex: 1 1 auto;\n min-height: 0;\n padding: 14px 16px;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n[data-edrv-view] .edrv-qm-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n /* 抵消 .vsm-mcp-dialog label 的 12px 上下外边距(与 body 的 gap 重复) */\n margin: 0;\n}\n\n[data-edrv-view] .edrv-qm-field-label {\n color: var(--qm-ink-2);\n font-size: 11px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n[data-edrv-view] .edrv-qm-field-hint {\n color: var(--qm-ink-3);\n font-size: 10px;\n font-weight: 400;\n}\n\n/* 标签行:标签在左、紧凑控件(分段)在右,避免控件挤成两行 */\n[data-edrv-view] .edrv-qm-field-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-height: 18px;\n}\n\n/* 分组区块:只用标题左侧的强调条分隔,不堆卡片、不加阴影 */\n[data-edrv-view] .edrv-qm-section {\n display: flex;\n flex-direction: column;\n gap: 9px;\n}\n\n[data-edrv-view] .edrv-qm-section-head {\n position: relative;\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding-left: 9px;\n}\n\n[data-edrv-view] .edrv-qm-section-head::before {\n content: '';\n position: absolute;\n left: 0;\n top: 3px;\n bottom: 3px;\n width: 3px;\n border-radius: 2px;\n background: var(--qm-accent);\n}\n\n[data-edrv-view] .edrv-qm-section-title {\n color: var(--qm-ink);\n font-size: 11.5px;\n font-weight: 650;\n}\n\n[data-edrv-view] .edrv-qm-section-hint {\n color: var(--qm-ink-3);\n font-size: 10px;\n font-weight: 400;\n}\n\n[data-edrv-view] .edrv-qm-section-body {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* 开关行:左侧标题与说明,右侧滑动开关 */\n[data-edrv-view] .edrv-qm-switch-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n[data-edrv-view] .edrv-qm-switch-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n[data-edrv-view] .edrv-qm-switch {\n position: relative;\n flex: 0 0 auto;\n box-sizing: border-box;\n width: 34px;\n height: 19px;\n padding: 0;\n border: 1px solid var(--qm-line);\n border-radius: 999px;\n background: var(--qm-sunken);\n cursor: pointer;\n transition: background .12s ease, border-color .12s ease;\n}\n\n[data-edrv-view] .edrv-qm-switch-knob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 13px;\n height: 13px;\n border-radius: 50%;\n background: var(--dsw-alias-bg-base, #fff);\n box-shadow: 0 1px 2px rgba(0, 0, 0, .18);\n transition: transform .12s ease;\n}\n\n[data-edrv-view] .edrv-qm-switch--on {\n border-color: var(--qm-accent);\n background: var(--qm-accent);\n}\n\n[data-edrv-view] .edrv-qm-switch--on .edrv-qm-switch-knob {\n transform: translateX(15px);\n}\n\n[data-edrv-view] .edrv-qm-switch:focus-visible {\n outline: 2px solid var(--qm-accent);\n outline-offset: 2px;\n}\n\n[data-edrv-view] .edrv-qm-switch:disabled {\n opacity: .5;\n cursor: not-allowed;\n}\n\n/* 字段里的按钮按内容宽度靠左,不拉满整行 */\n[data-edrv-view] .edrv-qm-field > .edrv-qm-btn {\n align-self: flex-start;\n}\n\n[data-edrv-view] .edrv-qm-input,\n[data-edrv-view] .edrv-qm-textarea,\n[data-edrv-view] .edrv-qm-select {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid var(--qm-line);\n border-radius: 7px;\n background: var(--dsw-alias-bg-base, #fff);\n color: var(--qm-ink);\n font: inherit;\n padding: 6px 9px;\n transition: border-color .1s ease, box-shadow .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-input:focus,\n[data-edrv-view] .edrv-qm-textarea:focus,\n[data-edrv-view] .edrv-qm-select:focus {\n outline: none;\n border-color: var(--qm-accent);\n box-shadow: 0 0 0 3px var(--qm-accent-soft);\n}\n\n[data-edrv-view] .edrv-qm-textarea {\n min-height: 96px;\n resize: vertical;\n font-family: ui-monospace, SFMono-Regular, Consolas, monospace;\n font-size: 11.5px;\n line-height: 1.55;\n}\n\n[data-edrv-view] .edrv-qm-input:disabled,\n[data-edrv-view] .edrv-qm-select:disabled {\n background: var(--qm-sunken);\n color: var(--qm-ink-3);\n cursor: not-allowed;\n}\n\n[data-edrv-view] .edrv-qm-row2 {\n display: flex;\n gap: 10px;\n}\n\n[data-edrv-view] .edrv-qm-row2 > * {\n flex: 1;\n min-width: 0;\n}\n\n/* 分段控件:凹槽轨道 + 浮起选中块,比「整块填色」更克制 */\n[data-edrv-view] .edrv-qm-seg {\n display: flex;\n gap: 2px;\n width: 100%;\n box-sizing: border-box;\n padding: 2px;\n border: 1px solid var(--qm-line);\n border-radius: 7px;\n background: var(--qm-sunken);\n}\n\n[data-edrv-view] .edrv-qm-seg-btn {\n flex: 1 1 0;\n min-width: 0;\n border: 0;\n border-radius: 5px;\n background: transparent;\n color: var(--qm-ink-2);\n font: inherit;\n font-size: 11.5px;\n font-weight: 600;\n padding: 4px 8px;\n cursor: pointer;\n transition: background .1s ease, color .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-seg-btn:hover {\n color: var(--qm-ink);\n background: var(--qm-hover);\n}\n\n[data-edrv-view] .edrv-qm-seg-btn--on {\n color: var(--qm-accent);\n background: var(--qm-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, .08);\n}\n\n[data-edrv-view] .edrv-qm-seg-btn:focus-visible {\n outline: 2px solid var(--qm-accent);\n outline-offset: -2px;\n}\n\n[data-edrv-view] .edrv-qm-seg-btn:disabled {\n opacity: .5;\n cursor: not-allowed;\n}\n\n/* 放在标签行里的分段按内容宽度收起,不撑满整行 */\n[data-edrv-view] .edrv-qm-field-head .edrv-qm-seg {\n width: auto;\n flex: 0 0 auto;\n}\n\n[data-edrv-view] .edrv-qm-field-head .edrv-qm-seg-btn {\n flex: 0 0 auto;\n padding: 3px 9px;\n font-size: 11px;\n}\n\n/* ── 下拉选择(技能 / 规则 / 工作区) ────────────────────── */\n[data-edrv-view] .edrv-qm-pick {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n[data-edrv-view] .edrv-qm-pick-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n box-sizing: border-box;\n min-height: 30px;\n padding: 5px 9px;\n border: 1px solid var(--qm-line);\n border-radius: 7px;\n background: var(--dsw-alias-bg-base, #fff);\n color: var(--qm-ink);\n font: inherit;\n font-size: 11.5px;\n text-align: left;\n cursor: pointer;\n transition: border-color .1s ease, box-shadow .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-pick-trigger:hover {\n border-color: var(--qm-accent);\n}\n\n[data-edrv-view] .edrv-qm-pick-trigger:focus-visible {\n outline: none;\n border-color: var(--qm-accent);\n box-shadow: 0 0 0 3px var(--qm-accent-soft);\n}\n\n[data-edrv-view] .edrv-qm-pick-trigger--open {\n border-color: var(--qm-accent);\n box-shadow: 0 0 0 3px var(--qm-accent-soft);\n}\n\n[data-edrv-view] .edrv-qm-pick-trigger:disabled {\n border-color: var(--qm-line);\n background: var(--qm-sunken);\n color: var(--qm-ink-3);\n cursor: not-allowed;\n}\n\n[data-edrv-view] .edrv-qm-pick-summary {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n[data-edrv-view] .edrv-qm-pick-badge {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--qm-warn);\n color: #fff;\n font-size: 10px;\n font-weight: 700;\n}\n\n[data-edrv-view] .edrv-qm-pick-caret {\n flex: 0 0 auto;\n width: 0;\n height: 0;\n border-left: 4px solid transparent;\n border-right: 4px solid transparent;\n border-top: 5px solid var(--qm-ink-3);\n transition: transform .12s ease;\n}\n\n[data-edrv-view] .edrv-qm-pick-trigger--open .edrv-qm-pick-caret {\n transform: rotate(180deg);\n}\n\n/* 弹层是 position: fixed + 视口坐标,不受弹窗滚动区裁剪 */\n[data-edrv-view] .edrv-qm-pick-panel {\n position: fixed;\n z-index: 260;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 5px;\n border: 1px solid var(--qm-line);\n border-radius: var(--qm-radius);\n background: var(--qm-surface);\n box-shadow: var(--qm-elev);\n overflow: hidden;\n}\n\n[data-edrv-view] .edrv-qm-pick-list {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-height: 0;\n overflow: auto;\n}\n\n[data-edrv-view] .edrv-qm-pick-group {\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n[data-edrv-view] .edrv-qm-pick-group-title {\n padding: 6px 7px 3px;\n color: var(--qm-ink-3);\n font-size: 10px;\n font-weight: 600;\n}\n\n[data-edrv-view] .edrv-qm-pick-option {\n display: flex;\n align-items: center;\n gap: 7px;\n width: 100%;\n box-sizing: border-box;\n padding: 5px 7px;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--qm-ink);\n font: inherit;\n font-size: 11.5px;\n text-align: left;\n cursor: pointer;\n transition: background .1s ease, color .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-pick-option:hover {\n background: var(--qm-hover);\n}\n\n[data-edrv-view] .edrv-qm-pick-option:focus-visible {\n outline: 2px solid var(--qm-accent);\n outline-offset: -2px;\n}\n\n[data-edrv-view] .edrv-qm-pick-option--on {\n color: var(--qm-accent);\n background: var(--qm-accent-soft);\n}\n\n[data-edrv-view] .edrv-qm-pick-check {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 13px;\n height: 13px;\n border: 1px solid var(--qm-line);\n border-radius: 4px;\n background: var(--dsw-alias-bg-base, #fff);\n color: #fff;\n font-size: 9px;\n line-height: 1;\n}\n\n[data-edrv-view] .edrv-qm-pick-check--on {\n border-color: var(--qm-accent);\n background: var(--qm-accent);\n}\n\n[data-edrv-view] .edrv-qm-pick-name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n[data-edrv-view] .edrv-qm-pick-hint {\n flex: 0 0 auto;\n max-width: 45%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--qm-ink-3);\n font-size: 10px;\n}\n\n[data-edrv-view] .edrv-qm-pick-dot {\n flex: 0 0 auto;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--qm-warn);\n}\n\n[data-edrv-view] .edrv-qm-pick-dot--error {\n background: var(--qm-danger);\n}\n\n[data-edrv-view] .edrv-qm-pick-empty {\n padding: 10px 8px;\n color: var(--qm-ink-3);\n font-size: 11px;\n text-align: center;\n}\n\n[data-edrv-view] .edrv-qm-pick-more {\n border: 1px dashed var(--qm-line);\n border-radius: 6px;\n background: transparent;\n color: var(--qm-ink-2);\n font: inherit;\n font-size: 11px;\n padding: 5px 7px;\n cursor: pointer;\n text-align: center;\n}\n\n[data-edrv-view] .edrv-qm-pick-more:hover {\n border-color: var(--qm-accent);\n color: var(--qm-accent);\n background: var(--qm-accent-soft);\n}\n\n[data-edrv-view] .edrv-qm-warn-list {\n margin: 0;\n padding-left: 16px;\n color: var(--qm-warn);\n font-size: 11px;\n}\n\n[data-edrv-view] .edrv-qm-error-list {\n margin: 0;\n padding-left: 16px;\n color: var(--qm-danger);\n font-size: 11px;\n}\n\n[data-edrv-view] .edrv-qm-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 11px 16px;\n border-top: 1px solid var(--qm-line);\n background: var(--qm-sunken);\n}\n\n[data-edrv-view] .edrv-qm-actions-grow {\n flex: 1;\n}\n\n/* 按钮统一盒模型:固定高度 + 内容居中,同一行里的按钮才能等高对齐 */\n[data-edrv-view] .edrv-qm-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 5px;\n box-sizing: border-box;\n height: 28px;\n padding: 0 12px;\n border: 1px solid var(--qm-line);\n border-radius: 7px;\n background: var(--dsw-alias-bg-base, #fff);\n color: var(--qm-ink);\n font: inherit;\n font-size: 11.5px;\n font-weight: 500;\n line-height: 1;\n white-space: nowrap;\n user-select: none;\n cursor: pointer;\n transition: background .1s ease, border-color .1s ease, color .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-btn--sm {\n height: 24px;\n padding: 0 9px;\n font-size: 11px;\n}\n\n[data-edrv-view] .edrv-qm-btn:hover {\n background: var(--qm-hover);\n}\n\n[data-edrv-view] .edrv-qm-btn--primary {\n border-color: var(--qm-accent);\n background: var(--qm-accent);\n color: #fff;\n}\n\n[data-edrv-view] .edrv-qm-btn--primary:hover {\n filter: brightness(1.06);\n background: var(--qm-accent);\n}\n\n[data-edrv-view] .edrv-qm-btn--danger {\n border-color: var(--qm-danger);\n color: var(--qm-danger);\n}\n\n[data-edrv-view] .edrv-qm-btn:disabled {\n opacity: .5;\n cursor: not-allowed;\n}\n\n[data-edrv-view] .edrv-qm-btn:focus-visible {\n outline: 2px solid var(--qm-accent);\n outline-offset: 2px;\n}\n\n/* 按钮组:按内容宽度排布,不再等分拉满(导出/导入/重置宽度本就不同) */\n[data-edrv-view] .edrv-qm-btn-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n\n/* 同一行里的输入框与按钮 */\n[data-edrv-view] .edrv-qm-inline {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n[data-edrv-view] .edrv-qm-input--order {\n width: 74px;\n flex: 0 0 74px;\n}\n\n[data-edrv-view] .edrv-qm-row2 > .edrv-qm-field--narrow {\n flex: 0 0 136px;\n}\n\n/* 悬浮按钮右键小菜单 */\n[data-edrv-view] .edrv-qm-context {\n position: absolute;\n min-width: 148px;\n padding: 4px;\n border-radius: 9px;\n border: 1px solid var(--qm-line);\n background: var(--qm-surface);\n box-shadow: var(--qm-elev);\n pointer-events: auto;\n}\n\n[data-edrv-view] .edrv-qm-context-item {\n padding: 5px 9px;\n border-radius: 6px;\n cursor: pointer;\n color: var(--qm-ink);\n font-size: 12px;\n white-space: nowrap;\n transition: background .1s ease, color .1s ease;\n}\n\n[data-edrv-view] .edrv-qm-context-item:hover {\n background: var(--qm-hover);\n}\n\n[data-edrv-view] .edrv-qm-context-item--danger {\n color: var(--qm-danger);\n}\n\n[data-edrv-view] .edrv-qm-context-sep {\n height: 1px;\n margin: 4px 2px;\n background: var(--qm-line);\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-qm-col { animation: none; }\n [data-edrv-view] .edrv-qm-launcher,\n [data-edrv-view] .edrv-qm-row,\n [data-edrv-view] .edrv-qm-row-chevron { transition: none; }\n [data-edrv-view] .edrv-qm-launcher:hover { transform: none; }\n}\n";
|
|
70
|
+
const tagId$3 = "dsh-vscode-mode/quickmenu.css";
|
|
60
71
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
61
72
|
const tag = document.createElement("style");
|
|
62
73
|
tag.dataset.plugin = "dsh-vscode-mode";
|
|
@@ -3753,6 +3764,39 @@ window.__ModuleLoader__.load({
|
|
|
3753
3764
|
/** 待审条目在面板列表里的 id 前缀;客户端与 Host 共用这一份口径。 */
|
|
3754
3765
|
const PENDING_PREFIX = "pending:";
|
|
3755
3766
|
//#endregion
|
|
3767
|
+
//#region src/shared/pathKey.ts
|
|
3768
|
+
/**
|
|
3769
|
+
* 路径比较口径:Host 与客户端共用同一套「远端 / 盘符 / 相对路径」判定,只在平台折叠上分工。
|
|
3770
|
+
* @author ddj 2026年10月30号
|
|
3771
|
+
*/
|
|
3772
|
+
/** 远端仓库身份:`scheme://...` 区分大小写,绝不能折叠。 */
|
|
3773
|
+
const REMOTE = /^[a-z]+:\/\//i;
|
|
3774
|
+
/** 盘符绝对路径:大小写不敏感与宿主平台无关,两边都必须折叠。 */
|
|
3775
|
+
const DRIVE = /^[a-z]:\//i;
|
|
3776
|
+
/**
|
|
3777
|
+
* @author ddj 2026年10月30号
|
|
3778
|
+
* @description: 统一分隔符为 `/`,其余字符原样保留。
|
|
3779
|
+
* @param path 原始路径
|
|
3780
|
+
* @returns 反斜杠已归一化的路径
|
|
3781
|
+
*/
|
|
3782
|
+
function unifiedPath(path) {
|
|
3783
|
+
return path.replace(/\\/g, "/");
|
|
3784
|
+
}
|
|
3785
|
+
/**
|
|
3786
|
+
* @author ddj 2026年10月30号
|
|
3787
|
+
* @description: 生成路径比较键。盘符路径恒折叠大小写;其余路径是否折叠由调用方按运行平台决定
|
|
3788
|
+
* (Host 在 Windows 上折叠,客户端不在宿主进程里,取 false)。
|
|
3789
|
+
* @param path 原始路径
|
|
3790
|
+
* @param foldCase 非盘符路径是否折叠大小写
|
|
3791
|
+
* @returns 比较键
|
|
3792
|
+
*/
|
|
3793
|
+
function pathKeyFor(path, foldCase) {
|
|
3794
|
+
const unified = unifiedPath(path);
|
|
3795
|
+
if (REMOTE.test(unified)) return unified;
|
|
3796
|
+
if (DRIVE.test(unified)) return unified.toLowerCase();
|
|
3797
|
+
return foldCase ? unified.toLowerCase() : unified;
|
|
3798
|
+
}
|
|
3799
|
+
//#endregion
|
|
3756
3800
|
//#region src/shared/diff.ts
|
|
3757
3801
|
/** 统一取对应 hunk;callHunk 只用于旧记录缺失 hunks 时兼容。 */
|
|
3758
3802
|
function preciseHunk(record, idx) {
|
|
@@ -4050,11 +4094,15 @@ window.__ModuleLoader__.load({
|
|
|
4050
4094
|
//#endregion
|
|
4051
4095
|
//#region src/client/state/diffSummary.ts
|
|
4052
4096
|
/** Source-aware metadata projection; official patches never become legacy hunks. @author ddj 2026年10月26号 */
|
|
4053
|
-
/**
|
|
4097
|
+
/**
|
|
4098
|
+
* @public
|
|
4099
|
+
* @author ddj 2026年10月26号
|
|
4100
|
+
* @description: 客户端路径比较口径:浏览器里没有宿主平台信息,非盘符路径不折叠大小写。
|
|
4101
|
+
* @param path 文件身份或相对路径
|
|
4102
|
+
* @returns 比较键
|
|
4103
|
+
*/
|
|
4054
4104
|
function diffPathKey(path) {
|
|
4055
|
-
|
|
4056
|
-
const normalized = path.replace(/\\/g, "/");
|
|
4057
|
-
return /^[a-z]:\//i.test(normalized) ? normalized.toLowerCase() : normalized;
|
|
4105
|
+
return pathKeyFor(path, false);
|
|
4058
4106
|
}
|
|
4059
4107
|
/** @public @author ddj 2026年10月26号 @param path Current editor file. @param page Authoritative metadata, including decided paths. @returns Whether legacy actions must be hidden for this file. */
|
|
4060
4108
|
function officialPath(path, page) {
|
|
@@ -87762,7 +87810,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
87762
87810
|
bottom: "top",
|
|
87763
87811
|
top: "bottom"
|
|
87764
87812
|
};
|
|
87765
|
-
function clamp$
|
|
87813
|
+
function clamp$4(start, value, end) {
|
|
87766
87814
|
return max$1(start, min$1(value, end));
|
|
87767
87815
|
}
|
|
87768
87816
|
function evaluate$1(value, param) {
|
|
@@ -88062,7 +88110,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
88062
88110
|
const maxPadding = min$1(paddingObject[maxProp], largestPossiblePadding);
|
|
88063
88111
|
const max = clientSize - arrowDimensions[length] - maxPadding;
|
|
88064
88112
|
const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
|
|
88065
|
-
const offset = clamp$
|
|
88113
|
+
const offset = clamp$4(minPadding, center, max);
|
|
88066
88114
|
const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < minPadding ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
|
|
88067
88115
|
const alignmentOffset = shouldAddOffset ? center < minPadding ? center - minPadding : center - max : 0;
|
|
88068
88116
|
return {
|
|
@@ -88281,7 +88329,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
88281
88329
|
const mainAxis = getOppositeAxis(crossAxis);
|
|
88282
88330
|
let mainAxisCoord = coords[mainAxis];
|
|
88283
88331
|
let crossAxisCoord = coords[crossAxis];
|
|
88284
|
-
const clampCoord = (axis, coord) => clamp$
|
|
88332
|
+
const clampCoord = (axis, coord) => clamp$4(coord + overflow[axis === "y" ? "top" : "left"], coord, coord - overflow[axis === "y" ? "bottom" : "right"]);
|
|
88285
88333
|
if (checkMainAxis) mainAxisCoord = clampCoord(mainAxis, mainAxisCoord);
|
|
88286
88334
|
if (checkCrossAxis) crossAxisCoord = clampCoord(crossAxis, crossAxisCoord);
|
|
88287
88335
|
const limitedCoords = limiter.fn({
|
|
@@ -94042,7 +94090,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
94042
94090
|
const LINE_DELTA_PIXEL_SIZE = 16;
|
|
94043
94091
|
const PAGE_DELTA_PIXEL_SIZE = 800;
|
|
94044
94092
|
const ZOOM_RATIO_PRECISION = 100;
|
|
94045
|
-
function clamp$1$
|
|
94093
|
+
function clamp$1$2(value, min, max) {
|
|
94046
94094
|
return Math.min(max, Math.max(min, value));
|
|
94047
94095
|
}
|
|
94048
94096
|
function normalizeWheelDelta(event) {
|
|
@@ -94057,10 +94105,10 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
94057
94105
|
function getNextWheelZoomRatio(currentRatio, event) {
|
|
94058
94106
|
const validCurrentRatio = Number.isFinite(currentRatio) ? currentRatio : 1;
|
|
94059
94107
|
const normalizedDelta = normalizeWheelDelta(event);
|
|
94060
|
-
if (normalizedDelta === 0) return clamp$1$
|
|
94108
|
+
if (normalizedDelta === 0) return clamp$1$2(validCurrentRatio, MIN_ZOOM_RATIO, MAX_ZOOM_RATIO);
|
|
94061
94109
|
const maxStep = validCurrentRatio < 1 ? MAX_WHEEL_ZOOM_STEP_BELOW_ONE : MAX_WHEEL_ZOOM_STEP;
|
|
94062
|
-
const ratioDelta = clamp$1$
|
|
94063
|
-
return clamp$1$
|
|
94110
|
+
const ratioDelta = clamp$1$2(-normalizedDelta * PIXEL_DELTA_TO_ZOOM_RATIO, -maxStep, maxStep);
|
|
94111
|
+
return clamp$1$2(Math.round((validCurrentRatio + ratioDelta) * ZOOM_RATIO_PRECISION) / ZOOM_RATIO_PRECISION, MIN_ZOOM_RATIO, MAX_ZOOM_RATIO);
|
|
94064
94112
|
}
|
|
94065
94113
|
const BASE_OBJECT_ARRAY = [
|
|
94066
94114
|
"top",
|
|
@@ -94825,14 +94873,14 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
94825
94873
|
}
|
|
94826
94874
|
};
|
|
94827
94875
|
const DRAWINGML_GLOW_BLUR_SCALE = .5;
|
|
94828
|
-
function clamp$
|
|
94876
|
+
function clamp$3(value, min, max) {
|
|
94829
94877
|
return Math.max(min, Math.min(max, value));
|
|
94830
94878
|
}
|
|
94831
94879
|
function colorWithOpacity(color, opacity) {
|
|
94832
94880
|
if (opacity === void 0) return color;
|
|
94833
94881
|
const parsedColor = new ColorKit(color);
|
|
94834
94882
|
if (!parsedColor.isValid) return color;
|
|
94835
|
-
return parsedColor.setAlpha(parsedColor.getAlpha() * clamp$
|
|
94883
|
+
return parsedColor.setAlpha(parsedColor.getAlpha() * clamp$3(opacity, 0, 1)).toRgbString();
|
|
94836
94884
|
}
|
|
94837
94885
|
function isTransparentColor(color) {
|
|
94838
94886
|
const parsedColor = new ColorKit(color);
|
|
@@ -145676,10 +145724,10 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
145676
145724
|
var require_useMergeRef = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
145677
145725
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
145678
145726
|
exports.useMergeRefs = void 0;
|
|
145679
|
-
var React$
|
|
145727
|
+
var React$68 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports)).__importStar(require("react"));
|
|
145680
145728
|
var assignRef_1 = require_assignRef();
|
|
145681
145729
|
var useRef_1 = require_useRef();
|
|
145682
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React$
|
|
145730
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React$68.useLayoutEffect : React$68.useEffect;
|
|
145683
145731
|
var currentValues = /* @__PURE__ */ new WeakMap();
|
|
145684
145732
|
/**
|
|
145685
145733
|
* Merges two or more refs together providing a single interface to set their value
|
|
@@ -145959,7 +146007,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
145959
146007
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
145960
146008
|
exports.sidecar = void 0;
|
|
145961
146009
|
var tslib_1 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports));
|
|
145962
|
-
var React$
|
|
146010
|
+
var React$67 = tslib_1.__importStar(require("react"));
|
|
145963
146011
|
var hook_1 = require_hook$1();
|
|
145964
146012
|
function sidecar(importer, errorComponent) {
|
|
145965
146013
|
var ErrorCase = function() {
|
|
@@ -145968,7 +146016,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
145968
146016
|
return function Sidecar(props) {
|
|
145969
146017
|
var _a = (0, hook_1.useSidecar)(importer, props.sideCar), Car = _a[0];
|
|
145970
146018
|
if (_a[1] && errorComponent) return ErrorCase;
|
|
145971
|
-
return Car ? React$
|
|
146019
|
+
return Car ? React$67.createElement(Car, tslib_1.__assign({}, props)) : null;
|
|
145972
146020
|
};
|
|
145973
146021
|
}
|
|
145974
146022
|
exports.sidecar = sidecar;
|
|
@@ -146083,7 +146131,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146083
146131
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
146084
146132
|
exports.renderCar = void 0;
|
|
146085
146133
|
var tslib_1 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports));
|
|
146086
|
-
var React$
|
|
146134
|
+
var React$66 = tslib_1.__importStar(require("react"));
|
|
146087
146135
|
var react_1 = require("react");
|
|
146088
146136
|
function renderCar(WrappedComponent, defaults) {
|
|
146089
146137
|
function State(_a) {
|
|
@@ -146096,9 +146144,9 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146096
146144
|
});
|
|
146097
146145
|
return null;
|
|
146098
146146
|
}, []);
|
|
146099
|
-
return React$
|
|
146147
|
+
return React$66.createElement(WrappedComponent, tslib_1.__assign({}, props, { children: renderTarget }));
|
|
146100
146148
|
}
|
|
146101
|
-
var Children = React$
|
|
146149
|
+
var Children = React$66.memo(function(_a) {
|
|
146102
146150
|
var stateRef = _a.stateRef, defaultState = _a.defaultState, children = _a.children;
|
|
146103
146151
|
var _b = (0, react_1.useState)(defaultState.current), state = _b[0], setState = _b[1];
|
|
146104
146152
|
(0, react_1.useEffect)(function() {
|
|
@@ -146109,14 +146157,14 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146109
146157
|
return true;
|
|
146110
146158
|
});
|
|
146111
146159
|
return function Combiner(props) {
|
|
146112
|
-
var defaultState = React$
|
|
146113
|
-
var ref = React$
|
|
146160
|
+
var defaultState = React$66.useRef(defaults(props));
|
|
146161
|
+
var ref = React$66.useRef(function(state) {
|
|
146114
146162
|
return defaultState.current = state;
|
|
146115
146163
|
});
|
|
146116
|
-
return React$
|
|
146164
|
+
return React$66.createElement(React$66.Fragment, null, React$66.createElement(State, {
|
|
146117
146165
|
stateRef: ref,
|
|
146118
146166
|
props
|
|
146119
|
-
}), React$
|
|
146167
|
+
}), React$66.createElement(Children, {
|
|
146120
146168
|
stateRef: ref,
|
|
146121
146169
|
defaultState,
|
|
146122
146170
|
children: props.children
|
|
@@ -146131,13 +146179,13 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146131
146179
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
146132
146180
|
exports.exportSidecar = void 0;
|
|
146133
146181
|
var tslib_1 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports));
|
|
146134
|
-
var React$
|
|
146182
|
+
var React$65 = tslib_1.__importStar(require("react"));
|
|
146135
146183
|
var SideCar = function(_a) {
|
|
146136
146184
|
var sideCar = _a.sideCar, rest = tslib_1.__rest(_a, ["sideCar"]);
|
|
146137
146185
|
if (!sideCar) throw new Error("Sidecar: please provide `sideCar` property to import the right car");
|
|
146138
146186
|
var Target = sideCar.read();
|
|
146139
146187
|
if (!Target) throw new Error("Sidecar medium not found");
|
|
146140
|
-
return React$
|
|
146188
|
+
return React$65.createElement(Target, tslib_1.__assign({}, rest));
|
|
146141
146189
|
};
|
|
146142
146190
|
SideCar.isSideCarExport = true;
|
|
146143
146191
|
function exportSidecar(medium, exported) {
|
|
@@ -146213,7 +146261,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146213
146261
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
146214
146262
|
exports.RemoveScroll = void 0;
|
|
146215
146263
|
var tslib_1 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports));
|
|
146216
|
-
var React$
|
|
146264
|
+
var React$64 = tslib_1.__importStar(require("react"));
|
|
146217
146265
|
var constants_1 = require_constants();
|
|
146218
146266
|
var use_callback_ref_1 = require_es5$5();
|
|
146219
146267
|
var medium_1 = require_medium();
|
|
@@ -146221,9 +146269,9 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146221
146269
|
/**
|
|
146222
146270
|
* Removes scrollbar from the page and contain the scroll within the Lock
|
|
146223
146271
|
*/
|
|
146224
|
-
var RemoveScroll = React$
|
|
146225
|
-
var ref = React$
|
|
146226
|
-
var _a = React$
|
|
146272
|
+
var RemoveScroll = React$64.forwardRef(function(props, parentRef) {
|
|
146273
|
+
var ref = React$64.useRef(null);
|
|
146274
|
+
var _a = React$64.useState({
|
|
146227
146275
|
onScrollCapture: nothing,
|
|
146228
146276
|
onWheelCapture: nothing,
|
|
146229
146277
|
onTouchMoveCapture: nothing
|
|
@@ -146246,7 +146294,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146246
146294
|
var SideCar = sideCar;
|
|
146247
146295
|
var containerRef = (0, use_callback_ref_1.useMergeRefs)([ref, parentRef]);
|
|
146248
146296
|
var containerProps = tslib_1.__assign(tslib_1.__assign({}, rest), callbacks);
|
|
146249
|
-
return React$
|
|
146297
|
+
return React$64.createElement(React$64.Fragment, null, enabled && React$64.createElement(SideCar, {
|
|
146250
146298
|
sideCar: medium_1.effectCar,
|
|
146251
146299
|
removeScrollBar,
|
|
146252
146300
|
shards,
|
|
@@ -146257,7 +146305,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146257
146305
|
allowPinchZoom: !!allowPinchZoom,
|
|
146258
146306
|
lockRef: ref,
|
|
146259
146307
|
gapMode
|
|
146260
|
-
}), forwardProps ? React$
|
|
146308
|
+
}), forwardProps ? React$64.cloneElement(React$64.Children.only(children), tslib_1.__assign(tslib_1.__assign({}, containerProps), { ref: containerRef })) : React$64.createElement(Container, tslib_1.__assign({}, containerProps, {
|
|
146261
146309
|
className,
|
|
146262
146310
|
ref: containerRef
|
|
146263
146311
|
}), children));
|
|
@@ -146336,7 +146384,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146336
146384
|
var require_hook = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
146337
146385
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
146338
146386
|
exports.styleHookSingleton = void 0;
|
|
146339
|
-
var React$
|
|
146387
|
+
var React$63 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports)).__importStar(require("react"));
|
|
146340
146388
|
var singleton_1 = require_singleton();
|
|
146341
146389
|
/**
|
|
146342
146390
|
* creates a hook to control style singleton
|
|
@@ -146350,7 +146398,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146350
146398
|
var styleHookSingleton = function() {
|
|
146351
146399
|
var sheet = (0, singleton_1.stylesheetSingleton)();
|
|
146352
146400
|
return function(styles, isDynamic) {
|
|
146353
|
-
React$
|
|
146401
|
+
React$63.useEffect(function() {
|
|
146354
146402
|
sheet.add(styles);
|
|
146355
146403
|
return function() {
|
|
146356
146404
|
sheet.remove();
|
|
@@ -146455,7 +146503,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146455
146503
|
var require_component = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
146456
146504
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
146457
146505
|
exports.RemoveScrollBar = exports.useLockAttribute = exports.lockAttribute = void 0;
|
|
146458
|
-
var React$
|
|
146506
|
+
var React$62 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports)).__importStar(require("react"));
|
|
146459
146507
|
var react_style_singleton_1 = require_es5$2();
|
|
146460
146508
|
var constants_1 = require_constants();
|
|
146461
146509
|
var utils_1 = require_utils();
|
|
@@ -146475,7 +146523,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146475
146523
|
return isFinite(counter) ? counter : 0;
|
|
146476
146524
|
};
|
|
146477
146525
|
var useLockAttribute = function() {
|
|
146478
|
-
React$
|
|
146526
|
+
React$62.useEffect(function() {
|
|
146479
146527
|
document.body.setAttribute(exports.lockAttribute, (getCurrentUseCounter() + 1).toString());
|
|
146480
146528
|
return function() {
|
|
146481
146529
|
var newCounter = getCurrentUseCounter() - 1;
|
|
@@ -146491,10 +146539,10 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146491
146539
|
var RemoveScrollBar = function(_a) {
|
|
146492
146540
|
var noRelative = _a.noRelative, noImportant = _a.noImportant, _b = _a.gapMode, gapMode = _b === void 0 ? "margin" : _b;
|
|
146493
146541
|
(0, exports.useLockAttribute)();
|
|
146494
|
-
var gap = React$
|
|
146542
|
+
var gap = React$62.useMemo(function() {
|
|
146495
146543
|
return (0, utils_1.getGapWidth)(gapMode);
|
|
146496
146544
|
}, [gapMode]);
|
|
146497
|
-
return React$
|
|
146545
|
+
return React$62.createElement(Style, { styles: getStyles(gap, !noRelative, gapMode, !noImportant ? "!important" : "") });
|
|
146498
146546
|
};
|
|
146499
146547
|
exports.RemoveScrollBar = RemoveScrollBar;
|
|
146500
146548
|
}));
|
|
@@ -146656,7 +146704,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146656
146704
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
146657
146705
|
exports.RemoveScrollSideCar = exports.getDeltaXY = exports.getTouchXY = void 0;
|
|
146658
146706
|
var tslib_1 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports));
|
|
146659
|
-
var React$
|
|
146707
|
+
var React$61 = tslib_1.__importStar(require("react"));
|
|
146660
146708
|
var react_remove_scroll_bar_1 = require_es5$1();
|
|
146661
146709
|
var react_style_singleton_1 = require_es5$2();
|
|
146662
146710
|
var aggresiveCapture_1 = require_aggresiveCapture();
|
|
@@ -146681,16 +146729,16 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146681
146729
|
var idCounter = 0;
|
|
146682
146730
|
var lockStack = [];
|
|
146683
146731
|
function RemoveScrollSideCar(props) {
|
|
146684
|
-
var shouldPreventQueue = React$
|
|
146685
|
-
var touchStartRef = React$
|
|
146686
|
-
var activeAxis = React$
|
|
146687
|
-
var id = React$
|
|
146688
|
-
var Style = React$
|
|
146689
|
-
var lastProps = React$
|
|
146690
|
-
React$
|
|
146732
|
+
var shouldPreventQueue = React$61.useRef([]);
|
|
146733
|
+
var touchStartRef = React$61.useRef([0, 0]);
|
|
146734
|
+
var activeAxis = React$61.useRef();
|
|
146735
|
+
var id = React$61.useState(idCounter++)[0];
|
|
146736
|
+
var Style = React$61.useState(react_style_singleton_1.styleSingleton)[0];
|
|
146737
|
+
var lastProps = React$61.useRef(props);
|
|
146738
|
+
React$61.useEffect(function() {
|
|
146691
146739
|
lastProps.current = props;
|
|
146692
146740
|
}, [props]);
|
|
146693
|
-
React$
|
|
146741
|
+
React$61.useEffect(function() {
|
|
146694
146742
|
if (props.inert) {
|
|
146695
146743
|
document.body.classList.add("block-interactivity-".concat(id));
|
|
146696
146744
|
var allow_1 = tslib_1.__spreadArray([props.lockRef.current], (props.shards || []).map(extractRef), true).filter(Boolean);
|
|
@@ -146709,7 +146757,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146709
146757
|
props.lockRef.current,
|
|
146710
146758
|
props.shards
|
|
146711
146759
|
]);
|
|
146712
|
-
var shouldCancelEvent = React$
|
|
146760
|
+
var shouldCancelEvent = React$61.useCallback(function(event, parent) {
|
|
146713
146761
|
if ("touches" in event && event.touches.length === 2 || event.type === "wheel" && event.ctrlKey) return !lastProps.current.allowPinchZoom;
|
|
146714
146762
|
var touch = (0, exports.getTouchXY)(event);
|
|
146715
146763
|
var touchStart = touchStartRef.current;
|
|
@@ -146735,7 +146783,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146735
146783
|
var cancelingAxis = activeAxis.current || currentAxis;
|
|
146736
146784
|
return (0, handleScroll_1.handleScroll)(cancelingAxis, parent, event, cancelingAxis === "h" ? deltaX : deltaY, true);
|
|
146737
146785
|
}, []);
|
|
146738
|
-
var shouldPrevent = React$
|
|
146786
|
+
var shouldPrevent = React$61.useCallback(function(_event) {
|
|
146739
146787
|
var event = _event;
|
|
146740
146788
|
if (!lockStack.length || lockStack[lockStack.length - 1] !== Style) return;
|
|
146741
146789
|
var delta = "deltaY" in event ? (0, exports.getDeltaXY)(event) : (0, exports.getTouchXY)(event);
|
|
@@ -146755,7 +146803,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146755
146803
|
}
|
|
146756
146804
|
}
|
|
146757
146805
|
}, []);
|
|
146758
|
-
var shouldCancel = React$
|
|
146806
|
+
var shouldCancel = React$61.useCallback(function(name, delta, target, should) {
|
|
146759
146807
|
var event = {
|
|
146760
146808
|
name,
|
|
146761
146809
|
delta,
|
|
@@ -146770,17 +146818,17 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146770
146818
|
});
|
|
146771
146819
|
}, 1);
|
|
146772
146820
|
}, []);
|
|
146773
|
-
var scrollTouchStart = React$
|
|
146821
|
+
var scrollTouchStart = React$61.useCallback(function(event) {
|
|
146774
146822
|
touchStartRef.current = (0, exports.getTouchXY)(event);
|
|
146775
146823
|
activeAxis.current = void 0;
|
|
146776
146824
|
}, []);
|
|
146777
|
-
var scrollWheel = React$
|
|
146825
|
+
var scrollWheel = React$61.useCallback(function(event) {
|
|
146778
146826
|
shouldCancel(event.type, (0, exports.getDeltaXY)(event), event.target, shouldCancelEvent(event, props.lockRef.current));
|
|
146779
146827
|
}, []);
|
|
146780
|
-
var scrollTouchMove = React$
|
|
146828
|
+
var scrollTouchMove = React$61.useCallback(function(event) {
|
|
146781
146829
|
shouldCancel(event.type, (0, exports.getTouchXY)(event), event.target, shouldCancelEvent(event, props.lockRef.current));
|
|
146782
146830
|
}, []);
|
|
146783
|
-
React$
|
|
146831
|
+
React$61.useEffect(function() {
|
|
146784
146832
|
lockStack.push(Style);
|
|
146785
146833
|
props.setCallbacks({
|
|
146786
146834
|
onScrollCapture: scrollWheel,
|
|
@@ -146800,7 +146848,7 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146800
146848
|
};
|
|
146801
146849
|
}, []);
|
|
146802
146850
|
var removeScrollBar = props.removeScrollBar, inert = props.inert;
|
|
146803
|
-
return React$
|
|
146851
|
+
return React$61.createElement(React$61.Fragment, null, inert ? React$61.createElement(Style, { styles: generateStyle(id) }) : null, removeScrollBar ? React$61.createElement(react_remove_scroll_bar_1.RemoveScrollBar, {
|
|
146804
146852
|
noRelative: props.noRelative,
|
|
146805
146853
|
gapMode: props.gapMode
|
|
146806
146854
|
}) : null);
|
|
@@ -146832,11 +146880,11 @@ For more info please visit our website: https://redi.wzhu.dev/en-US/docs/faq#imp
|
|
|
146832
146880
|
var require_Combination = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
146833
146881
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
146834
146882
|
var tslib_1 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports));
|
|
146835
|
-
var React$
|
|
146883
|
+
var React$60 = tslib_1.__importStar(require("react"));
|
|
146836
146884
|
var UI_1 = require_UI();
|
|
146837
146885
|
var sidecar_1 = tslib_1.__importDefault(require_sidecar());
|
|
146838
|
-
var ReactRemoveScroll = React$
|
|
146839
|
-
return React$
|
|
146886
|
+
var ReactRemoveScroll = React$60.forwardRef(function(props, ref) {
|
|
146887
|
+
return React$60.createElement(UI_1.RemoveScroll, tslib_1.__assign({}, props, {
|
|
146840
146888
|
ref,
|
|
146841
146889
|
sideCar: sidecar_1.default
|
|
146842
146890
|
}));
|
|
@@ -194844,9 +194892,9 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
194844
194892
|
movedRange: params.sourceRange
|
|
194845
194893
|
};
|
|
194846
194894
|
const dataStreamLength = body.dataStream.length;
|
|
194847
|
-
const startOffset = clamp$1(params.sourceRange.startOffset, 0, dataStreamLength);
|
|
194848
|
-
const endOffset = clamp$1(params.sourceRange.endOffset, startOffset, dataStreamLength);
|
|
194849
|
-
const targetOffset = clamp$1(params.targetOffset, 0, dataStreamLength);
|
|
194895
|
+
const startOffset = clamp$1$1(params.sourceRange.startOffset, 0, dataStreamLength);
|
|
194896
|
+
const endOffset = clamp$1$1(params.sourceRange.endOffset, startOffset, dataStreamLength);
|
|
194897
|
+
const targetOffset = clamp$1$1(params.targetOffset, 0, dataStreamLength);
|
|
194850
194898
|
if (startOffset === endOffset || targetOffset >= startOffset && targetOffset <= endOffset) return {
|
|
194851
194899
|
nextDocumentData,
|
|
194852
194900
|
movedRange: {
|
|
@@ -194890,9 +194938,9 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
194890
194938
|
}
|
|
194891
194939
|
function buildMoveBodyTextAction(previousBody, nextBody, move) {
|
|
194892
194940
|
const dataStreamLength = previousBody.dataStream.length;
|
|
194893
|
-
const startOffset = clamp$1(move.sourceRange.startOffset, 0, dataStreamLength);
|
|
194894
|
-
const endOffset = clamp$1(move.sourceRange.endOffset, startOffset, dataStreamLength);
|
|
194895
|
-
const targetOffset = clamp$1(move.targetOffset, 0, dataStreamLength);
|
|
194941
|
+
const startOffset = clamp$1$1(move.sourceRange.startOffset, 0, dataStreamLength);
|
|
194942
|
+
const endOffset = clamp$1$1(move.sourceRange.endOffset, startOffset, dataStreamLength);
|
|
194943
|
+
const targetOffset = clamp$1$1(move.targetOffset, 0, dataStreamLength);
|
|
194896
194944
|
const moveLength = endOffset - startOffset;
|
|
194897
194945
|
if (moveLength <= 0 || targetOffset >= startOffset && targetOffset <= endOffset) return null;
|
|
194898
194946
|
const textX = new TextX();
|
|
@@ -195050,7 +195098,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
195050
195098
|
if (targetOffset > endOffset && index >= endOffset && index <= targetOffset) return index - moveLength;
|
|
195051
195099
|
return remapIndexAfterMove(index, startOffset, endOffset, targetOffset, insertOffset, moveLength);
|
|
195052
195100
|
}
|
|
195053
|
-
function clamp$1(value, min, max) {
|
|
195101
|
+
function clamp$1$1(value, min, max) {
|
|
195054
195102
|
return Math.min(Math.max(value, min), max);
|
|
195055
195103
|
}
|
|
195056
195104
|
const HorizontalLineCommand = {
|
|
@@ -219192,7 +219240,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
219192
219240
|
function ceil$1(x) {
|
|
219193
219241
|
return finalise(x = new this(x), x.e + 1, 2);
|
|
219194
219242
|
}
|
|
219195
|
-
function clamp(x, min, max) {
|
|
219243
|
+
function clamp$1(x, min, max) {
|
|
219196
219244
|
return new this(x).clamp(min, max);
|
|
219197
219245
|
}
|
|
219198
219246
|
function config(obj) {
|
|
@@ -219352,7 +219400,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
219352
219400
|
Decimal.atan2 = atan2;
|
|
219353
219401
|
Decimal.cbrt = cbrt;
|
|
219354
219402
|
Decimal.ceil = ceil$1;
|
|
219355
|
-
Decimal.clamp = clamp;
|
|
219403
|
+
Decimal.clamp = clamp$1;
|
|
219356
219404
|
Decimal.cos = cos;
|
|
219357
219405
|
Decimal.cosh = cosh;
|
|
219358
219406
|
Decimal.div = div;
|
|
@@ -344164,7 +344212,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
344164
344212
|
};
|
|
344165
344213
|
return nextTick;
|
|
344166
344214
|
};
|
|
344167
|
-
const TYPE_OPTIONS = [
|
|
344215
|
+
const TYPE_OPTIONS$1 = [
|
|
344168
344216
|
{
|
|
344169
344217
|
label: "sheets-numfmt-ui.general",
|
|
344170
344218
|
component: GeneralPanel
|
|
@@ -344208,7 +344256,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
344208
344256
|
const [type, setType] = (0, react.useState)(findDefaultType);
|
|
344209
344257
|
const [key, setKey] = (0, react.useState)(() => `${row}_${col}_${defaultPattern}`);
|
|
344210
344258
|
const { mark, userHabitCurrency } = useCurrencyOptions(() => setKey(`${row}_${col}_${defaultPattern}_userCurrency`));
|
|
344211
|
-
const BusinessComponent = (_TYPE_OPTIONS$find = TYPE_OPTIONS.find((item) => item.label === type)) === null || _TYPE_OPTIONS$find === void 0 ? void 0 : _TYPE_OPTIONS$find.component;
|
|
344259
|
+
const BusinessComponent = (_TYPE_OPTIONS$find = TYPE_OPTIONS$1.find((item) => item.label === type)) === null || _TYPE_OPTIONS$find === void 0 ? void 0 : _TYPE_OPTIONS$find.component;
|
|
344212
344260
|
function findDefaultType() {
|
|
344213
344261
|
return [
|
|
344214
344262
|
isGeneralPanel,
|
|
@@ -344216,9 +344264,9 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
344216
344264
|
isCurrencyPanel,
|
|
344217
344265
|
isDatePanel,
|
|
344218
344266
|
isThousandthPercentilePanel
|
|
344219
|
-
].reduce((pre, curFn, index) => pre || (curFn(defaultPattern) ? TYPE_OPTIONS[index].label : ""), "") || "sheets-numfmt-ui.customFormat";
|
|
344267
|
+
].reduce((pre, curFn, index) => pre || (curFn(defaultPattern) ? TYPE_OPTIONS$1[index].label : ""), "") || "sheets-numfmt-ui.customFormat";
|
|
344220
344268
|
}
|
|
344221
|
-
const selectOptions = TYPE_OPTIONS.map((option) => ({
|
|
344269
|
+
const selectOptions = TYPE_OPTIONS$1.map((option) => ({
|
|
344222
344270
|
label: localeService.t(option.label),
|
|
344223
344271
|
value: option.label
|
|
344224
344272
|
}));
|
|
@@ -382993,7 +383041,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
382993
383041
|
//#endregion
|
|
382994
383042
|
//#region src/client/sessionScope.ts
|
|
382995
383043
|
/** 对象窄化(非对象返回 undefined,供后续安全取字段)。 */
|
|
382996
|
-
function asRecord(value) {
|
|
383044
|
+
function asRecord$1(value) {
|
|
382997
383045
|
return value && typeof value === "object" ? value : void 0;
|
|
382998
383046
|
}
|
|
382999
383047
|
/** 非空字符串取值(其他类型一律视为缺失)。 */
|
|
@@ -383010,9 +383058,9 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
383010
383058
|
* @returns sessionId,或 undefined
|
|
383011
383059
|
*/
|
|
383012
383060
|
function uiSessionIdOf(snapshot) {
|
|
383013
|
-
const binding = asRecord(snapshot);
|
|
383061
|
+
const binding = asRecord$1(snapshot);
|
|
383014
383062
|
if (!binding) return void 0;
|
|
383015
|
-
return strOf(binding.key) ?? strOf(asRecord(binding.value)?.key);
|
|
383063
|
+
return strOf(binding.key) ?? strOf(asRecord$1(binding.value)?.key);
|
|
383016
383064
|
}
|
|
383017
383065
|
/**
|
|
383018
383066
|
* 从 `sessions.list` 快照读出 sessionId。
|
|
@@ -383023,14 +383071,14 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
383023
383071
|
* @returns sessionId,或 undefined
|
|
383024
383072
|
*/
|
|
383025
383073
|
function listSessionId(list) {
|
|
383026
|
-
const snap = asRecord(list);
|
|
383074
|
+
const snap = asRecord$1(list);
|
|
383027
383075
|
if (!snap) return void 0;
|
|
383028
383076
|
const current = strOf(snap.current);
|
|
383029
383077
|
if (current) return current;
|
|
383030
|
-
const byId = asRecord(snap.byId);
|
|
383078
|
+
const byId = asRecord$1(snap.byId);
|
|
383031
383079
|
if (!byId) return void 0;
|
|
383032
383080
|
for (const [id, row] of Object.entries(byId)) {
|
|
383033
|
-
const mainView = asRecord(asRecord(row)?.retainedBy)?.mainView;
|
|
383081
|
+
const mainView = asRecord$1(asRecord$1(row)?.retainedBy)?.mainView;
|
|
383034
383082
|
if (typeof mainView === "number" && mainView > 0) return id;
|
|
383035
383083
|
}
|
|
383036
383084
|
}
|
|
@@ -383044,7 +383092,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
383044
383092
|
function pickSessionScope(sources) {
|
|
383045
383093
|
const sessionId = uiSessionIdOf(sources.uiSession) ?? listSessionId(sources.list) ?? strOf(sources.fallbackSessionId);
|
|
383046
383094
|
if (!sessionId) return {};
|
|
383047
|
-
const cwd = strOf(asRecord(asRecord(asRecord(sources.list)?.byId)?.[sessionId])?.cwd);
|
|
383095
|
+
const cwd = strOf(asRecord$1(asRecord$1(asRecord$1(sources.list)?.byId)?.[sessionId])?.cwd);
|
|
383048
383096
|
return cwd ? {
|
|
383049
383097
|
sessionId,
|
|
383050
383098
|
cwd
|
|
@@ -383060,7 +383108,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
383060
383108
|
}
|
|
383061
383109
|
/** 取 `uiSession.current` 快照(服务或订阅面缺失时返回 undefined)。 */
|
|
383062
383110
|
function uiCurrentSnapshot(ctx) {
|
|
383063
|
-
const current = asRecord(asRecord(safeGet(ctx, "uiSession"))?.current);
|
|
383111
|
+
const current = asRecord$1(asRecord$1(safeGet(ctx, "uiSession"))?.current);
|
|
383064
383112
|
const getSnapshot = current?.getSnapshot;
|
|
383065
383113
|
if (typeof getSnapshot !== "function") return void 0;
|
|
383066
383114
|
try {
|
|
@@ -383071,7 +383119,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
383071
383119
|
}
|
|
383072
383120
|
/** 取 `sessions.list` 快照(服务缺失时返回 undefined)。 */
|
|
383073
383121
|
function listSnapshot(ctx) {
|
|
383074
|
-
const list = asRecord(asRecord(safeGet(ctx, "sessions"))?.list);
|
|
383122
|
+
const list = asRecord$1(asRecord$1(safeGet(ctx, "sessions"))?.list);
|
|
383075
383123
|
const getSnapshot = list?.getSnapshot;
|
|
383076
383124
|
if (typeof getSnapshot !== "function") return void 0;
|
|
383077
383125
|
try {
|
|
@@ -383106,15 +383154,15 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
383106
383154
|
function subscribeScope(ctx, listener) {
|
|
383107
383155
|
const disposers = [];
|
|
383108
383156
|
const attach = (owner) => {
|
|
383109
|
-
const subscribe = asRecord(owner)?.subscribe;
|
|
383157
|
+
const subscribe = asRecord$1(owner)?.subscribe;
|
|
383110
383158
|
if (typeof subscribe !== "function") return;
|
|
383111
383159
|
try {
|
|
383112
383160
|
const disposer = subscribe.call(owner, listener);
|
|
383113
383161
|
if (typeof disposer === "function") disposers.push(disposer);
|
|
383114
383162
|
} catch {}
|
|
383115
383163
|
};
|
|
383116
|
-
attach(asRecord(asRecord(safeGet(ctx, "uiSession"))?.current));
|
|
383117
|
-
attach(asRecord(asRecord(safeGet(ctx, "sessions"))?.list));
|
|
383164
|
+
attach(asRecord$1(asRecord$1(safeGet(ctx, "uiSession"))?.current));
|
|
383165
|
+
attach(asRecord$1(asRecord$1(safeGet(ctx, "sessions"))?.list));
|
|
383118
383166
|
return () => {
|
|
383119
383167
|
for (const disposer of disposers.splice(0)) try {
|
|
383120
383168
|
disposer();
|
|
@@ -403869,6 +403917,3080 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
403869
403917
|
}
|
|
403870
403918
|
return ClaimRouter;
|
|
403871
403919
|
}
|
|
403920
|
+
/** 提示词长度上限。 */
|
|
403921
|
+
const QUICK_MENU_PROMPT_CAP = 8192;
|
|
403922
|
+
/** 单条规则内联正文上限。 */
|
|
403923
|
+
const QUICK_MENU_RULE_CAP = 8192;
|
|
403924
|
+
/** 技能名合法形态。 */
|
|
403925
|
+
const QUICK_MENU_SKILL_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
403926
|
+
/** 未命名项兜底名。 */
|
|
403927
|
+
const FALLBACK_NAME = "未命名";
|
|
403928
|
+
/**
|
|
403929
|
+
* 数值夹取(NaN/非有限回落到 fallback)。
|
|
403930
|
+
* @author ddj 2026年10月30号
|
|
403931
|
+
* @param value 原始值
|
|
403932
|
+
* @param min 下限
|
|
403933
|
+
* @param max 上限
|
|
403934
|
+
* @param fallback 非法值兜底
|
|
403935
|
+
* @returns 夹取后的数值
|
|
403936
|
+
*/
|
|
403937
|
+
function clampNumber(value, min, max, fallback) {
|
|
403938
|
+
const number = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
403939
|
+
if (number < min) return min;
|
|
403940
|
+
if (number > max) return max;
|
|
403941
|
+
return number;
|
|
403942
|
+
}
|
|
403943
|
+
/**
|
|
403944
|
+
* 取对象形态(非对象/数组返回 null)。
|
|
403945
|
+
* @author ddj 2026年10月30号
|
|
403946
|
+
* @param value 原始值
|
|
403947
|
+
* @returns 普通对象或 null
|
|
403948
|
+
*/
|
|
403949
|
+
function asRecord(value) {
|
|
403950
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
403951
|
+
return value;
|
|
403952
|
+
}
|
|
403953
|
+
/**
|
|
403954
|
+
* 取字符串(非字符串回落空串)。
|
|
403955
|
+
* @author ddj 2026年10月30号
|
|
403956
|
+
* @param value 原始值
|
|
403957
|
+
* @returns 字符串
|
|
403958
|
+
*/
|
|
403959
|
+
function asString(value) {
|
|
403960
|
+
return typeof value === "string" ? value : "";
|
|
403961
|
+
}
|
|
403962
|
+
/**
|
|
403963
|
+
* 文本截断(超长补省略号)。
|
|
403964
|
+
* @author ddj 2026年10月30号
|
|
403965
|
+
* @param text 原文
|
|
403966
|
+
* @param cap 上限字符数
|
|
403967
|
+
* @returns 截断后文本
|
|
403968
|
+
*/
|
|
403969
|
+
function cutText(text, cap) {
|
|
403970
|
+
if (text.length <= cap) return text;
|
|
403971
|
+
return text.slice(0, Math.max(0, cap - 1)) + "…";
|
|
403972
|
+
}
|
|
403973
|
+
/**
|
|
403974
|
+
* 归一化工作区路径(统一分隔符、去尾分隔符、win32 转小写),用于比较而非展示。
|
|
403975
|
+
* @author ddj 2026年10月30号
|
|
403976
|
+
* @param path 原始路径
|
|
403977
|
+
* @returns 归一化路径
|
|
403978
|
+
*/
|
|
403979
|
+
function normalizePath(path) {
|
|
403980
|
+
const unified = path.trim().replace(/\//g, "\\").replace(/\\+$/, "");
|
|
403981
|
+
if (unified === "") return "";
|
|
403982
|
+
return unified.toLowerCase();
|
|
403983
|
+
}
|
|
403984
|
+
/**
|
|
403985
|
+
* 判断一条限定工作区列表是否覆盖当前工作区(含子目录,按路径边界匹配)。
|
|
403986
|
+
* @author ddj 2026年10月30号
|
|
403987
|
+
* @param paths 限定列表;null 表示全部工作区
|
|
403988
|
+
* @param cwd 当前工作区路径;空串表示未知(视为可见)
|
|
403989
|
+
* @returns 是否可见
|
|
403990
|
+
*/
|
|
403991
|
+
function matchWorkspace(paths, cwd) {
|
|
403992
|
+
if (paths === null || paths === void 0) return true;
|
|
403993
|
+
if (paths.length === 0) return true;
|
|
403994
|
+
const normalizedCwd = normalizePath(cwd ?? "");
|
|
403995
|
+
if (normalizedCwd === "") return true;
|
|
403996
|
+
return paths.some((path) => {
|
|
403997
|
+
const normalized = normalizePath(path);
|
|
403998
|
+
if (normalized === "") return false;
|
|
403999
|
+
return normalizedCwd === normalized || normalizedCwd.startsWith(normalized + "\\");
|
|
404000
|
+
});
|
|
404001
|
+
}
|
|
404002
|
+
/**
|
|
404003
|
+
* 默认文档(首次打开菜单时的初始状态)。
|
|
404004
|
+
* @author ddj 2026年10月30号
|
|
404005
|
+
* @returns 默认文档
|
|
404006
|
+
*/
|
|
404007
|
+
function defaultDoc() {
|
|
404008
|
+
return {
|
|
404009
|
+
version: 1,
|
|
404010
|
+
revision: 0,
|
|
404011
|
+
updatedAt: "",
|
|
404012
|
+
enabled: true,
|
|
404013
|
+
clickAction: "send",
|
|
404014
|
+
maxDepth: 3,
|
|
404015
|
+
launcher: {
|
|
404016
|
+
x: null,
|
|
404017
|
+
y: null
|
|
404018
|
+
},
|
|
404019
|
+
items: []
|
|
404020
|
+
};
|
|
404021
|
+
}
|
|
404022
|
+
/**
|
|
404023
|
+
* 生成新菜单项 id(类型前缀 + 时间 + 随机,跨会话不重复)。
|
|
404024
|
+
* @author ddj 2026年10月30号
|
|
404025
|
+
* @param type 项类型
|
|
404026
|
+
* @returns 新 id
|
|
404027
|
+
*/
|
|
404028
|
+
function newQuickId(type) {
|
|
404029
|
+
const prefix = type === "group" ? "g" : "b";
|
|
404030
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
404031
|
+
return prefix + "_" + Date.now().toString(36) + random;
|
|
404032
|
+
}
|
|
404033
|
+
/**
|
|
404034
|
+
* 归一化限定工作区列表(去空、去重、归一化;空结果回落到 null=全部)。
|
|
404035
|
+
* @author ddj 2026年10月30号
|
|
404036
|
+
* @param raw 原始值
|
|
404037
|
+
* @returns 限定列表或 null
|
|
404038
|
+
*/
|
|
404039
|
+
function normalizeWorkspaces(raw) {
|
|
404040
|
+
if (!Array.isArray(raw)) return null;
|
|
404041
|
+
const out = [];
|
|
404042
|
+
for (const entry of raw) {
|
|
404043
|
+
if (typeof entry !== "string") continue;
|
|
404044
|
+
const normalized = normalizePath(entry);
|
|
404045
|
+
if (normalized === "") continue;
|
|
404046
|
+
if (out.includes(normalized)) continue;
|
|
404047
|
+
out.push(normalized);
|
|
404048
|
+
}
|
|
404049
|
+
return out.length > 0 ? out : null;
|
|
404050
|
+
}
|
|
404051
|
+
/**
|
|
404052
|
+
* 绑定规则的稳定键(校验、去重、内联正文映射共用)。
|
|
404053
|
+
* @author ddj 2026年10月30号
|
|
404054
|
+
* @param binding 规则绑定
|
|
404055
|
+
* @returns 键
|
|
404056
|
+
*/
|
|
404057
|
+
function bindingKey(binding) {
|
|
404058
|
+
const workspace = normalizePath(binding.workspacePath ?? "");
|
|
404059
|
+
return binding.scope + "|" + workspace + "|" + binding.file;
|
|
404060
|
+
}
|
|
404061
|
+
/**
|
|
404062
|
+
* 归一化单条规则绑定(scope/file 必填,项目规则必须带工作区)。
|
|
404063
|
+
* @author ddj 2026年10月30号
|
|
404064
|
+
* @param raw 原始值
|
|
404065
|
+
* @returns 绑定或 null
|
|
404066
|
+
*/
|
|
404067
|
+
function normalizeRule(raw) {
|
|
404068
|
+
const record = asRecord(raw);
|
|
404069
|
+
if (!record) return null;
|
|
404070
|
+
const file = asString(record.file).trim();
|
|
404071
|
+
if (file === "") return null;
|
|
404072
|
+
const scope = record.scope === "project" ? "project" : "user";
|
|
404073
|
+
const binding = {
|
|
404074
|
+
scope,
|
|
404075
|
+
file
|
|
404076
|
+
};
|
|
404077
|
+
if (scope === "project") {
|
|
404078
|
+
const workspacePath = asString(record.workspacePath).trim();
|
|
404079
|
+
if (workspacePath === "") return null;
|
|
404080
|
+
binding.workspacePath = workspacePath;
|
|
404081
|
+
}
|
|
404082
|
+
if (record.origin === "current" || record.origin === "legacy") binding.origin = record.origin;
|
|
404083
|
+
return binding;
|
|
404084
|
+
}
|
|
404085
|
+
/**
|
|
404086
|
+
* 归一化规则绑定列表(去重 + 上限)。
|
|
404087
|
+
* @author ddj 2026年10月30号
|
|
404088
|
+
* @param raw 原始值
|
|
404089
|
+
* @returns 绑定列表
|
|
404090
|
+
*/
|
|
404091
|
+
function normalizeRules(raw) {
|
|
404092
|
+
if (!Array.isArray(raw)) return [];
|
|
404093
|
+
const out = [];
|
|
404094
|
+
for (const entry of raw) {
|
|
404095
|
+
const binding = normalizeRule(entry);
|
|
404096
|
+
if (!binding) continue;
|
|
404097
|
+
if (out.some((existing) => bindingKey(existing) === bindingKey(binding))) continue;
|
|
404098
|
+
out.push(binding);
|
|
404099
|
+
}
|
|
404100
|
+
return out.slice(0, 24);
|
|
404101
|
+
}
|
|
404102
|
+
/**
|
|
404103
|
+
* 归一化技能名列表(只保留 kebab-case,去重 + 上限)。
|
|
404104
|
+
* @author ddj 2026年10月30号
|
|
404105
|
+
* @param raw 原始值
|
|
404106
|
+
* @returns 技能名列表
|
|
404107
|
+
*/
|
|
404108
|
+
function normalizeSkills(raw) {
|
|
404109
|
+
if (!Array.isArray(raw)) return [];
|
|
404110
|
+
const out = [];
|
|
404111
|
+
for (const entry of raw) {
|
|
404112
|
+
if (typeof entry !== "string") continue;
|
|
404113
|
+
const name = entry.trim();
|
|
404114
|
+
if (name === "" || !QUICK_MENU_SKILL_RE.test(name)) continue;
|
|
404115
|
+
if (out.includes(name)) continue;
|
|
404116
|
+
out.push(name);
|
|
404117
|
+
}
|
|
404118
|
+
return out.slice(0, 24);
|
|
404119
|
+
}
|
|
404120
|
+
/**
|
|
404121
|
+
* 归一化单个菜单项(非法类型/缺 id 返回 null)。
|
|
404122
|
+
* @author ddj 2026年10月30号
|
|
404123
|
+
* @param raw 原始值
|
|
404124
|
+
* @returns 菜单项或 null
|
|
404125
|
+
*/
|
|
404126
|
+
function normalizeItem(raw) {
|
|
404127
|
+
const record = asRecord(raw);
|
|
404128
|
+
if (!record) return null;
|
|
404129
|
+
const type = record.type === "group" ? "group" : "button";
|
|
404130
|
+
const id = asString(record.id).trim();
|
|
404131
|
+
if (id === "") return null;
|
|
404132
|
+
const name = cutText(asString(record.name).trim() || FALLBACK_NAME, 40);
|
|
404133
|
+
const order = clampNumber(record.order, 0, 999, 100);
|
|
404134
|
+
const rawParent = record.parentId;
|
|
404135
|
+
const base = {
|
|
404136
|
+
id,
|
|
404137
|
+
type,
|
|
404138
|
+
name,
|
|
404139
|
+
order,
|
|
404140
|
+
parentId: typeof rawParent === "string" && rawParent.trim() !== "" ? rawParent.trim() : null,
|
|
404141
|
+
workspaces: normalizeWorkspaces(record.workspaces)
|
|
404142
|
+
};
|
|
404143
|
+
if (type === "group") return {
|
|
404144
|
+
...base,
|
|
404145
|
+
type: "group"
|
|
404146
|
+
};
|
|
404147
|
+
const button = {
|
|
404148
|
+
...base,
|
|
404149
|
+
type: "button",
|
|
404150
|
+
prompt: cutText(asString(record.prompt), QUICK_MENU_PROMPT_CAP),
|
|
404151
|
+
skills: normalizeSkills(record.skills),
|
|
404152
|
+
rules: normalizeRules(record.rules),
|
|
404153
|
+
ruleMode: record.ruleMode === "inline" ? "inline" : "reference"
|
|
404154
|
+
};
|
|
404155
|
+
if (record.clickAction === "send" || record.clickAction === "fill") button.clickAction = record.clickAction;
|
|
404156
|
+
return button;
|
|
404157
|
+
}
|
|
404158
|
+
/**
|
|
404159
|
+
* 判断从起点上溯的父链是否会回到目标项(环检测;超深链也按非法处理)。
|
|
404160
|
+
* @author ddj 2026年10月30号
|
|
404161
|
+
* @param byId id → 项
|
|
404162
|
+
* @param startId 起点父 id
|
|
404163
|
+
* @param targetId 目标项 id
|
|
404164
|
+
* @returns 是否成环
|
|
404165
|
+
*/
|
|
404166
|
+
function chainHas(byId, startId, targetId) {
|
|
404167
|
+
let current = startId;
|
|
404168
|
+
let guard = 0;
|
|
404169
|
+
while (current !== null) {
|
|
404170
|
+
if (current === targetId) return true;
|
|
404171
|
+
if (guard > 7) return true;
|
|
404172
|
+
const parent = byId.get(current);
|
|
404173
|
+
if (!parent) return false;
|
|
404174
|
+
current = parent.parentId;
|
|
404175
|
+
guard += 1;
|
|
404176
|
+
}
|
|
404177
|
+
return false;
|
|
404178
|
+
}
|
|
404179
|
+
/**
|
|
404180
|
+
* 修补父链问题:父不存在/父是按钮/自指/形成环时把子项上抬到最近合法祖组或顶层。
|
|
404181
|
+
* @author ddj 2026年10月30号
|
|
404182
|
+
* @param items 归一化后的项
|
|
404183
|
+
* @returns 修补后的项
|
|
404184
|
+
*/
|
|
404185
|
+
function repairParents(items) {
|
|
404186
|
+
const byId = /* @__PURE__ */ new Map();
|
|
404187
|
+
for (const item of items) byId.set(item.id, item);
|
|
404188
|
+
for (const item of items) {
|
|
404189
|
+
let resolved = item.parentId;
|
|
404190
|
+
let guard = 0;
|
|
404191
|
+
while (resolved !== null && guard <= 7) {
|
|
404192
|
+
const parent = byId.get(resolved);
|
|
404193
|
+
if (!parent) {
|
|
404194
|
+
resolved = null;
|
|
404195
|
+
break;
|
|
404196
|
+
}
|
|
404197
|
+
if (parent.type === "group") break;
|
|
404198
|
+
resolved = parent.parentId;
|
|
404199
|
+
guard += 1;
|
|
404200
|
+
}
|
|
404201
|
+
if (resolved !== null && chainHas(byId, resolved, item.id)) resolved = null;
|
|
404202
|
+
item.parentId = resolved;
|
|
404203
|
+
}
|
|
404204
|
+
return items;
|
|
404205
|
+
}
|
|
404206
|
+
/**
|
|
404207
|
+
* 按父级分组重排优先级(order 归一为 10/20/30…,保证展示稳定且无并列)。
|
|
404208
|
+
* @author ddj 2026年10月30号
|
|
404209
|
+
* @param items 菜单项
|
|
404210
|
+
* @returns 同一数组(就地写入 order)
|
|
404211
|
+
*/
|
|
404212
|
+
function normalizeOrder(items) {
|
|
404213
|
+
const groups = /* @__PURE__ */ new Map();
|
|
404214
|
+
for (const item of items) {
|
|
404215
|
+
const key = item.parentId ?? "";
|
|
404216
|
+
const bucket = groups.get(key);
|
|
404217
|
+
if (bucket) bucket.push(item);
|
|
404218
|
+
else groups.set(key, [item]);
|
|
404219
|
+
}
|
|
404220
|
+
for (const bucket of groups.values()) {
|
|
404221
|
+
bucket.sort((left, right) => {
|
|
404222
|
+
if (left.order !== right.order) return left.order - right.order;
|
|
404223
|
+
if (left.name !== right.name) return left.name < right.name ? -1 : 1;
|
|
404224
|
+
return left.id < right.id ? -1 : 1;
|
|
404225
|
+
});
|
|
404226
|
+
bucket.forEach((item, index) => {
|
|
404227
|
+
item.order = (index + 1) * 10;
|
|
404228
|
+
});
|
|
404229
|
+
}
|
|
404230
|
+
return items;
|
|
404231
|
+
}
|
|
404232
|
+
/**
|
|
404233
|
+
* 归一化整份文档(任何脏数据都收敛到可用形态,绝不抛错)。
|
|
404234
|
+
* @author ddj 2026年10月30号
|
|
404235
|
+
* @param raw 原始值(JSON.parse 结果或任意 unknown)
|
|
404236
|
+
* @returns 合法文档
|
|
404237
|
+
*/
|
|
404238
|
+
function normalizeDoc(raw) {
|
|
404239
|
+
const fallback = defaultDoc();
|
|
404240
|
+
const record = asRecord(raw);
|
|
404241
|
+
if (!record) return fallback;
|
|
404242
|
+
const rawItems = Array.isArray(record.items) ? record.items : [];
|
|
404243
|
+
const seen = /* @__PURE__ */ new Set();
|
|
404244
|
+
const items = [];
|
|
404245
|
+
for (const entry of rawItems) {
|
|
404246
|
+
const item = normalizeItem(entry);
|
|
404247
|
+
if (!item || seen.has(item.id)) continue;
|
|
404248
|
+
seen.add(item.id);
|
|
404249
|
+
items.push(item);
|
|
404250
|
+
if (items.length >= 200) break;
|
|
404251
|
+
}
|
|
404252
|
+
const x = typeof record.launcher === "object" && record.launcher !== null ? asRecord(record.launcher) : null;
|
|
404253
|
+
return {
|
|
404254
|
+
version: 1,
|
|
404255
|
+
revision: Math.floor(clampNumber(record.revision, 0, Number.MAX_SAFE_INTEGER, 0)),
|
|
404256
|
+
updatedAt: cutText(asString(record.updatedAt), 40),
|
|
404257
|
+
enabled: typeof record.enabled === "boolean" ? record.enabled : true,
|
|
404258
|
+
clickAction: record.clickAction === "fill" ? "fill" : "send",
|
|
404259
|
+
maxDepth: Math.round(clampNumber(record.maxDepth, 1, 5, 3)),
|
|
404260
|
+
launcher: {
|
|
404261
|
+
x: x && typeof x.x === "number" && Number.isFinite(x.x) ? x.x : null,
|
|
404262
|
+
y: x && typeof x.y === "number" && Number.isFinite(x.y) ? x.y : null
|
|
404263
|
+
},
|
|
404264
|
+
items: normalizeOrder(repairParents(items))
|
|
404265
|
+
};
|
|
404266
|
+
}
|
|
404267
|
+
/**
|
|
404268
|
+
* 结构迁移入口(当前只有 v1;后续版本在此收敛旧结构)。
|
|
404269
|
+
* @author ddj 2026年10月30号
|
|
404270
|
+
* @param raw 原始值
|
|
404271
|
+
* @returns 合法文档
|
|
404272
|
+
*/
|
|
404273
|
+
function migrateDoc(raw) {
|
|
404274
|
+
return normalizeDoc(raw);
|
|
404275
|
+
}
|
|
404276
|
+
/**
|
|
404277
|
+
* 序列化文档(host 落盘与导入导出共用)。
|
|
404278
|
+
* @author ddj 2026年10月30号
|
|
404279
|
+
* @param doc 文档
|
|
404280
|
+
* @returns JSON 文本
|
|
404281
|
+
*/
|
|
404282
|
+
function serializeDoc(doc) {
|
|
404283
|
+
return JSON.stringify(normalizeDoc(doc), null, 2);
|
|
404284
|
+
}
|
|
404285
|
+
/**
|
|
404286
|
+
* 解析文档文本(解析失败返回默认文档并给出告警文案)。
|
|
404287
|
+
* @author ddj 2026年10月30号
|
|
404288
|
+
* @param text 文件内容
|
|
404289
|
+
* @returns 文档 + 可选告警
|
|
404290
|
+
*/
|
|
404291
|
+
function parseDoc(text) {
|
|
404292
|
+
try {
|
|
404293
|
+
return { doc: migrateDoc(JSON.parse(text)) };
|
|
404294
|
+
} catch (error) {
|
|
404295
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
404296
|
+
return {
|
|
404297
|
+
doc: defaultDoc(),
|
|
404298
|
+
warning: "快捷菜单配置解析失败(" + reason + "),已回落默认配置"
|
|
404299
|
+
};
|
|
404300
|
+
}
|
|
404301
|
+
}
|
|
404302
|
+
/**
|
|
404303
|
+
* 计算项所在层级(root 的直接子项为第 1 层;孤儿按第 1 层)。
|
|
404304
|
+
* @author ddj 2026年10月30号
|
|
404305
|
+
* @param doc 文档
|
|
404306
|
+
* @param id 项 id
|
|
404307
|
+
* @returns 层级(找不到返回 0)
|
|
404308
|
+
*/
|
|
404309
|
+
function depthOf(doc, id) {
|
|
404310
|
+
const byId = /* @__PURE__ */ new Map();
|
|
404311
|
+
for (const item of doc.items) byId.set(item.id, item);
|
|
404312
|
+
let current = byId.get(id);
|
|
404313
|
+
if (!current) return 0;
|
|
404314
|
+
let depth = 1;
|
|
404315
|
+
let guard = 0;
|
|
404316
|
+
while (current.parentId !== null && guard < 6) {
|
|
404317
|
+
const parent = byId.get(current.parentId);
|
|
404318
|
+
if (!parent) break;
|
|
404319
|
+
depth += 1;
|
|
404320
|
+
current = parent;
|
|
404321
|
+
guard += 1;
|
|
404322
|
+
}
|
|
404323
|
+
return depth;
|
|
404324
|
+
}
|
|
404325
|
+
/**
|
|
404326
|
+
* 判断能否在指定父级下新增子项(达到最大嵌套层数时禁止)。
|
|
404327
|
+
* @author ddj 2026年10月30号
|
|
404328
|
+
* @param doc 文档
|
|
404329
|
+
* @param parentId 父组 id(null 为顶层)
|
|
404330
|
+
* @returns 是否可新增
|
|
404331
|
+
*/
|
|
404332
|
+
function canCreateAt(doc, parentId) {
|
|
404333
|
+
if (parentId === null) return doc.maxDepth >= 1;
|
|
404334
|
+
const depth = depthOf(doc, parentId);
|
|
404335
|
+
if (depth <= 0) return false;
|
|
404336
|
+
return depth + 1 <= doc.maxDepth;
|
|
404337
|
+
}
|
|
404338
|
+
/**
|
|
404339
|
+
* 取某组下可见的子项(按优先级排序;cwd 为空时不做工作区过滤)。
|
|
404340
|
+
* @author ddj 2026年10月30号
|
|
404341
|
+
* @param doc 文档
|
|
404342
|
+
* @param parentId 父组 id(null 为顶层)
|
|
404343
|
+
* @param cwd 当前工作区路径
|
|
404344
|
+
* @returns 可见子项
|
|
404345
|
+
*/
|
|
404346
|
+
function childrenOf(doc, parentId, cwd) {
|
|
404347
|
+
return doc.items.filter((item) => item.parentId === parentId).filter((item) => itemVisibleIn(doc, item, cwd)).sort((left, right) => {
|
|
404348
|
+
if (left.order !== right.order) return left.order - right.order;
|
|
404349
|
+
return left.name < right.name ? -1 : 1;
|
|
404350
|
+
});
|
|
404351
|
+
}
|
|
404352
|
+
/**
|
|
404353
|
+
* 计算项的有效工作区限定(沿祖先链最近的显式限定生效)。
|
|
404354
|
+
* @author ddj 2026年10月30号
|
|
404355
|
+
* @param doc 文档
|
|
404356
|
+
* @param item 菜单项
|
|
404357
|
+
* @returns 限定列表或 null
|
|
404358
|
+
*/
|
|
404359
|
+
function inheritWorkspaces(doc, item) {
|
|
404360
|
+
const byId = /* @__PURE__ */ new Map();
|
|
404361
|
+
for (const entry of doc.items) byId.set(entry.id, entry);
|
|
404362
|
+
let current = item;
|
|
404363
|
+
let guard = 0;
|
|
404364
|
+
while (current && guard < 7) {
|
|
404365
|
+
if (current.workspaces !== null) return current.workspaces;
|
|
404366
|
+
current = current.parentId === null ? void 0 : byId.get(current.parentId);
|
|
404367
|
+
guard += 1;
|
|
404368
|
+
}
|
|
404369
|
+
return null;
|
|
404370
|
+
}
|
|
404371
|
+
/**
|
|
404372
|
+
* 判断项在当前工作区是否可见(继承父组限定)。
|
|
404373
|
+
* @author ddj 2026年10月30号
|
|
404374
|
+
* @param doc 文档
|
|
404375
|
+
* @param item 菜单项
|
|
404376
|
+
* @param cwd 当前工作区路径
|
|
404377
|
+
* @returns 是否可见
|
|
404378
|
+
*/
|
|
404379
|
+
function itemVisibleIn(doc, item, cwd) {
|
|
404380
|
+
return matchWorkspace(inheritWorkspaces(doc, item), cwd);
|
|
404381
|
+
}
|
|
404382
|
+
/**
|
|
404383
|
+
* 收集某组的所有后代 id(防止把组设为自己的子组)。
|
|
404384
|
+
* @author ddj 2026年10月30号
|
|
404385
|
+
* @param doc 文档
|
|
404386
|
+
* @param id 组 id
|
|
404387
|
+
* @returns 后代 id 列表(含自身)
|
|
404388
|
+
*/
|
|
404389
|
+
function descendantIds(doc, id) {
|
|
404390
|
+
const out = [id];
|
|
404391
|
+
let index = 0;
|
|
404392
|
+
while (index < out.length) {
|
|
404393
|
+
const current = out[index];
|
|
404394
|
+
index += 1;
|
|
404395
|
+
for (const item of doc.items) {
|
|
404396
|
+
if (item.parentId !== current) continue;
|
|
404397
|
+
if (out.includes(item.id)) continue;
|
|
404398
|
+
out.push(item.id);
|
|
404399
|
+
}
|
|
404400
|
+
}
|
|
404401
|
+
return out;
|
|
404402
|
+
}
|
|
404403
|
+
/**
|
|
404404
|
+
* 在校验上下文里定位规则文件(用户规则按 origin 消歧、项目规则按工作区定位)。
|
|
404405
|
+
* @author ddj 2026年10月30号
|
|
404406
|
+
* @param binding 规则绑定
|
|
404407
|
+
* @param snapshot 规则列表快照
|
|
404408
|
+
* @returns 命中信息:规则元数据 / 工作区目录缺失 / 未找到
|
|
404409
|
+
*/
|
|
404410
|
+
function lookupRule(binding, snapshot) {
|
|
404411
|
+
if (binding.scope === "user") {
|
|
404412
|
+
const hit = snapshot.user.find((rule) => {
|
|
404413
|
+
if (rule.file !== binding.file) return false;
|
|
404414
|
+
if (binding.origin && rule.origin !== binding.origin) return false;
|
|
404415
|
+
return true;
|
|
404416
|
+
});
|
|
404417
|
+
return hit ? { info: hit } : {};
|
|
404418
|
+
}
|
|
404419
|
+
const wanted = normalizePath(binding.workspacePath ?? "");
|
|
404420
|
+
const project = snapshot.projects.find((entry) => normalizePath(entry.workspacePath) === wanted);
|
|
404421
|
+
if (!project) return {};
|
|
404422
|
+
if (project.missingDir) return { missingDir: project.workspacePath };
|
|
404423
|
+
const hit = project.rules.find((rule) => rule.file === binding.file);
|
|
404424
|
+
return hit ? { info: hit } : {};
|
|
404425
|
+
}
|
|
404426
|
+
/**
|
|
404427
|
+
* 校验一条规则绑定(文件缺失/停用/自身报错/跨工作区)。
|
|
404428
|
+
* @author ddj 2026年10月30号
|
|
404429
|
+
* @param binding 规则绑定
|
|
404430
|
+
* @param ctx 校验上下文
|
|
404431
|
+
* @returns 绑定状态
|
|
404432
|
+
*/
|
|
404433
|
+
function validateRule(binding, ctx) {
|
|
404434
|
+
if (!ctx.rules) return {
|
|
404435
|
+
state: "unknown",
|
|
404436
|
+
reason: "未校验(规则列表未加载)"
|
|
404437
|
+
};
|
|
404438
|
+
const found = lookupRule(binding, ctx.rules);
|
|
404439
|
+
if (found.missingDir) return {
|
|
404440
|
+
state: "error",
|
|
404441
|
+
reason: "工作区规则目录不存在:" + found.missingDir + "\\.dsh\\rules"
|
|
404442
|
+
};
|
|
404443
|
+
if (!found.info) return {
|
|
404444
|
+
state: "error",
|
|
404445
|
+
reason: "规则文件不存在或已移除:" + binding.file
|
|
404446
|
+
};
|
|
404447
|
+
if (found.info.error) return {
|
|
404448
|
+
state: "error",
|
|
404449
|
+
reason: "规则解析失败:" + found.info.error
|
|
404450
|
+
};
|
|
404451
|
+
if (!found.info.enabled) return {
|
|
404452
|
+
state: "warn",
|
|
404453
|
+
reason: "规则已停用,不会生效:" + binding.file
|
|
404454
|
+
};
|
|
404455
|
+
if (binding.scope === "project") {
|
|
404456
|
+
const wanted = normalizePath(binding.workspacePath ?? "");
|
|
404457
|
+
const current = normalizePath(ctx.cwd ?? "");
|
|
404458
|
+
if (current !== "" && wanted !== current) return {
|
|
404459
|
+
state: "warn",
|
|
404460
|
+
reason: "规则来自其他工作区,发送时会跳过:" + binding.file
|
|
404461
|
+
};
|
|
404462
|
+
}
|
|
404463
|
+
return { state: "ok" };
|
|
404464
|
+
}
|
|
404465
|
+
/**
|
|
404466
|
+
* 校验一条技能绑定(技能按会话枚举,不可见不等同于已删除)。
|
|
404467
|
+
* @author ddj 2026年10月30号
|
|
404468
|
+
* @param name 技能名
|
|
404469
|
+
* @param ctx 校验上下文
|
|
404470
|
+
* @returns 绑定状态
|
|
404471
|
+
*/
|
|
404472
|
+
function validateSkill(name, ctx) {
|
|
404473
|
+
if (ctx.skillNames === void 0 || ctx.skillNames === null) return {
|
|
404474
|
+
state: "unknown",
|
|
404475
|
+
reason: "未校验(当前没有活跃会话)"
|
|
404476
|
+
};
|
|
404477
|
+
if (ctx.skillNames.includes(name)) return { state: "ok" };
|
|
404478
|
+
return {
|
|
404479
|
+
state: "warn",
|
|
404480
|
+
reason: "当前会话不可见(可能已移除或未被该会话加载):" + name
|
|
404481
|
+
};
|
|
404482
|
+
}
|
|
404483
|
+
/**
|
|
404484
|
+
* 聚合单个菜单项的绑定状态(组无绑定,恒为空)。
|
|
404485
|
+
* @author ddj 2026年10月30号
|
|
404486
|
+
* @param item 菜单项
|
|
404487
|
+
* @param ctx 校验上下文
|
|
404488
|
+
* @returns 失效与告警原因
|
|
404489
|
+
*/
|
|
404490
|
+
function itemStatus(item, ctx) {
|
|
404491
|
+
const status = {
|
|
404492
|
+
errors: [],
|
|
404493
|
+
warnings: []
|
|
404494
|
+
};
|
|
404495
|
+
if (item.type !== "button") return status;
|
|
404496
|
+
if (item.prompt.trim() === "") status.warnings.push("提示词为空,只发送绑定的技能与规则");
|
|
404497
|
+
for (const binding of item.rules) {
|
|
404498
|
+
const result = validateRule(binding, ctx);
|
|
404499
|
+
if (result.state === "error") status.errors.push(result.reason ?? "规则失效");
|
|
404500
|
+
else if (result.state === "warn") status.warnings.push(result.reason ?? "规则需注意");
|
|
404501
|
+
}
|
|
404502
|
+
for (const name of item.skills) {
|
|
404503
|
+
const result = validateSkill(name, ctx);
|
|
404504
|
+
if (result.state === "error") status.errors.push(result.reason ?? "技能失效");
|
|
404505
|
+
else if (result.state === "warn") status.warnings.push(result.reason ?? "技能需注意");
|
|
404506
|
+
}
|
|
404507
|
+
return status;
|
|
404508
|
+
}
|
|
404509
|
+
/**
|
|
404510
|
+
* 校验编辑中的草稿(保存前拦截明显错误)。
|
|
404511
|
+
* @author ddj 2026年10月30号
|
|
404512
|
+
* @param draft 草稿
|
|
404513
|
+
* @param doc 当前文档(用于兄弟重名与父链判断)
|
|
404514
|
+
* @returns 错误与告警
|
|
404515
|
+
*/
|
|
404516
|
+
function validateItem(draft, doc) {
|
|
404517
|
+
const status = {
|
|
404518
|
+
errors: [],
|
|
404519
|
+
warnings: []
|
|
404520
|
+
};
|
|
404521
|
+
const name = draft.name.trim();
|
|
404522
|
+
if (name === "") status.errors.push("名称不能为空");
|
|
404523
|
+
if (name.length > 40) status.errors.push("名称过长(上限 40 字)");
|
|
404524
|
+
if (draft.order < 0 || draft.order > 999) status.errors.push("优先级需在 0 到 999 之间");
|
|
404525
|
+
if (draft.parentId !== null) {
|
|
404526
|
+
const parent = doc.items.find((item) => item.id === draft.parentId);
|
|
404527
|
+
if (!parent) status.errors.push("归属组不存在");
|
|
404528
|
+
else if (parent.type !== "group") status.errors.push("只能归属到组");
|
|
404529
|
+
else if (descendantIds(doc, draft.id).includes(parent.id)) status.errors.push("不能把组归属到自己的子组");
|
|
404530
|
+
else if (depthOf(doc, parent.id) + 1 > doc.maxDepth) status.errors.push("已达最大嵌套层数(上限 " + doc.maxDepth + ")");
|
|
404531
|
+
}
|
|
404532
|
+
const siblings = doc.items.filter((item) => item.parentId === draft.parentId && item.id !== draft.id);
|
|
404533
|
+
if (name !== "" && siblings.some((item) => item.name === name)) status.warnings.push("同级已有同名项");
|
|
404534
|
+
if (draft.type === "button") {
|
|
404535
|
+
if (draft.prompt.length > 8192) status.errors.push("提示词过长(上限 8192 字符)");
|
|
404536
|
+
if (draft.prompt.trim() === "" && draft.skills.length === 0 && draft.rules.length === 0) status.errors.push("提示词与绑定都为空,点击后没有内容可发送");
|
|
404537
|
+
for (const name of draft.skills) if (!QUICK_MENU_SKILL_RE.test(name)) status.errors.push("技能名不合法:" + name);
|
|
404538
|
+
for (const binding of draft.rules) if (binding.scope === "project" && !binding.workspacePath) status.errors.push("项目规则缺少工作区:" + binding.file);
|
|
404539
|
+
}
|
|
404540
|
+
return status;
|
|
404541
|
+
}
|
|
404542
|
+
/**
|
|
404543
|
+
* 挑选可用技能(不可见技能不进消息,并留下告警)。
|
|
404544
|
+
* @author ddj 2026年10月30号
|
|
404545
|
+
* @param item 按钮
|
|
404546
|
+
* @param ctx 合成上下文
|
|
404547
|
+
* @param warnings 告警收集器
|
|
404548
|
+
* @returns 实际发送的技能名
|
|
404549
|
+
*/
|
|
404550
|
+
function pickSkills(item, ctx, warnings) {
|
|
404551
|
+
const out = [];
|
|
404552
|
+
for (const name of item.skills) {
|
|
404553
|
+
const result = validateSkill(name, ctx);
|
|
404554
|
+
if (result.state === "error" || result.state === "warn") {
|
|
404555
|
+
warnings.push(result.reason ?? "技能不可用:" + name);
|
|
404556
|
+
continue;
|
|
404557
|
+
}
|
|
404558
|
+
out.push(name);
|
|
404559
|
+
}
|
|
404560
|
+
return out;
|
|
404561
|
+
}
|
|
404562
|
+
/**
|
|
404563
|
+
* 挑选可用规则(失效/停用/跨工作区规则不注入,并留下告警)。
|
|
404564
|
+
* @author ddj 2026年10月30号
|
|
404565
|
+
* @param item 按钮
|
|
404566
|
+
* @param ctx 合成上下文
|
|
404567
|
+
* @param warnings 告警收集器
|
|
404568
|
+
* @returns 发送的规则绑定与元数据
|
|
404569
|
+
*/
|
|
404570
|
+
function pickRules(item, ctx, warnings) {
|
|
404571
|
+
const out = [];
|
|
404572
|
+
for (const binding of item.rules) {
|
|
404573
|
+
const result = validateRule(binding, ctx);
|
|
404574
|
+
if (result.state !== "ok") {
|
|
404575
|
+
warnings.push(result.reason ?? "规则不可用:" + binding.file);
|
|
404576
|
+
continue;
|
|
404577
|
+
}
|
|
404578
|
+
const found = ctx.rules ? lookupRule(binding, ctx.rules) : {};
|
|
404579
|
+
if (found.info) out.push({ info: found.info });
|
|
404580
|
+
}
|
|
404581
|
+
return out;
|
|
404582
|
+
}
|
|
404583
|
+
/**
|
|
404584
|
+
* 生成规则引用段(只给名字与绝对路径,由模型按需读取)。
|
|
404585
|
+
* @author ddj 2026年10月30号
|
|
404586
|
+
* @param rules 规则元数据
|
|
404587
|
+
* @returns 引用段落(无规则返回空串)
|
|
404588
|
+
*/
|
|
404589
|
+
function buildRuleRefs(rules) {
|
|
404590
|
+
if (rules.length === 0) return "";
|
|
404591
|
+
return "---\n引用的规则:\n" + rules.map((rule) => "- " + rule.info.file + "(绝对路径:" + rule.info.absPath + ")").join("\n") + "\n(需要时按需读取上述规则文件)";
|
|
404592
|
+
}
|
|
404593
|
+
/**
|
|
404594
|
+
* 生成规则内联段(缺正文或超长时截断并告警)。
|
|
404595
|
+
* @author ddj 2026年10月30号
|
|
404596
|
+
* @param item 按钮
|
|
404597
|
+
* @param rules 规则元数据
|
|
404598
|
+
* @param ctx 合成上下文
|
|
404599
|
+
* @param warnings 告警收集器
|
|
404600
|
+
* @returns 内联段落(无内容返回空串)
|
|
404601
|
+
*/
|
|
404602
|
+
function buildInline(item, rules, ctx, warnings) {
|
|
404603
|
+
if (rules.length === 0) return "";
|
|
404604
|
+
const contents = ctx.inlineContents ?? {};
|
|
404605
|
+
const blocks = [];
|
|
404606
|
+
for (const rule of rules) {
|
|
404607
|
+
const raw = contents[bindingKey({
|
|
404608
|
+
scope: rule.info.scope,
|
|
404609
|
+
file: rule.info.file,
|
|
404610
|
+
workspacePath: rule.info.scope === "project" ? ctx.cwd : void 0
|
|
404611
|
+
})];
|
|
404612
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
404613
|
+
warnings.push("未能读取规则正文,已跳过内联:" + rule.info.file);
|
|
404614
|
+
continue;
|
|
404615
|
+
}
|
|
404616
|
+
blocks.push("### " + rule.info.file + "\n" + cutText(raw, QUICK_MENU_RULE_CAP));
|
|
404617
|
+
}
|
|
404618
|
+
if (blocks.length === 0) return "";
|
|
404619
|
+
return "## 已绑定规则(节选)\n\n" + blocks.join("\n\n");
|
|
404620
|
+
}
|
|
404621
|
+
/**
|
|
404622
|
+
* 替换提示词占位符(未知占位符原样保留)。
|
|
404623
|
+
* @author ddj 2026年10月30号
|
|
404624
|
+
* @param prompt 原文
|
|
404625
|
+
* @param values 占位符取值
|
|
404626
|
+
* @returns 替换结果
|
|
404627
|
+
*/
|
|
404628
|
+
function fillPrompt(prompt, values) {
|
|
404629
|
+
let out = prompt;
|
|
404630
|
+
for (const [key, value] of Object.entries(values)) out = out.split("{{" + key + "}}").join(value);
|
|
404631
|
+
return out.trim();
|
|
404632
|
+
}
|
|
404633
|
+
/**
|
|
404634
|
+
* 合成发送文本:提示词 + 技能 token + 规则引用/内联(自动剔失效绑定)。
|
|
404635
|
+
* @author ddj 2026年10月30号
|
|
404636
|
+
* @param item 按钮
|
|
404637
|
+
* @param ctx 合成上下文
|
|
404638
|
+
* @returns 文本与告警
|
|
404639
|
+
*/
|
|
404640
|
+
function composePrompt(item, ctx) {
|
|
404641
|
+
const warnings = [];
|
|
404642
|
+
const skills = pickSkills(item, ctx, warnings);
|
|
404643
|
+
const rules = pickRules(item, ctx, warnings);
|
|
404644
|
+
const tokens = skills.map((name) => "/" + name).join(" ");
|
|
404645
|
+
const promptText = fillPrompt(item.prompt, {
|
|
404646
|
+
workspace: ctx.cwd ?? "",
|
|
404647
|
+
selection: ctx.selection ?? "",
|
|
404648
|
+
date: ctx.date ?? "",
|
|
404649
|
+
rules: rules.map((rule) => rule.info.file).join("、"),
|
|
404650
|
+
skills: tokens
|
|
404651
|
+
});
|
|
404652
|
+
const blocks = [];
|
|
404653
|
+
if (promptText !== "") blocks.push(promptText);
|
|
404654
|
+
if (tokens !== "") blocks.push(tokens);
|
|
404655
|
+
if (item.ruleMode === "reference") {
|
|
404656
|
+
const refs = buildRuleRefs(rules);
|
|
404657
|
+
if (refs !== "") blocks.push(refs);
|
|
404658
|
+
} else {
|
|
404659
|
+
const inline = buildInline(item, rules, ctx, warnings);
|
|
404660
|
+
if (inline !== "") blocks.push(inline);
|
|
404661
|
+
}
|
|
404662
|
+
return {
|
|
404663
|
+
text: blocks.join("\n\n").trim(),
|
|
404664
|
+
warnings
|
|
404665
|
+
};
|
|
404666
|
+
}
|
|
404667
|
+
/**
|
|
404668
|
+
* 计算悬浮按钮默认位置(输入框左上角上方让出一个按钮高度)。
|
|
404669
|
+
* @author ddj 2026年10月30号
|
|
404670
|
+
* @param seat 输入框锚点矩形(浮层坐标系;缺失时回落左上角)
|
|
404671
|
+
* @param layer 浮层矩形
|
|
404672
|
+
* @param size 按钮尺寸
|
|
404673
|
+
* @param margin 边距
|
|
404674
|
+
* @returns 位置
|
|
404675
|
+
*/
|
|
404676
|
+
function anchorLauncher(seat, layer, size, margin = 8) {
|
|
404677
|
+
const maxLeft = Math.max(margin, layer.width - size.width - margin);
|
|
404678
|
+
const maxTop = Math.max(margin, layer.height - size.height - margin);
|
|
404679
|
+
if (!seat) return {
|
|
404680
|
+
left: clampNumber(margin, margin, maxLeft, margin),
|
|
404681
|
+
top: clampNumber(margin, margin, maxTop, margin)
|
|
404682
|
+
};
|
|
404683
|
+
const left = seat.left - layer.left;
|
|
404684
|
+
const top = seat.top - layer.top - size.height - margin;
|
|
404685
|
+
return {
|
|
404686
|
+
left: clampNumber(left, margin, maxLeft, margin),
|
|
404687
|
+
top: clampNumber(top, margin, maxTop, margin)
|
|
404688
|
+
};
|
|
404689
|
+
}
|
|
404690
|
+
/**
|
|
404691
|
+
* 计算级联子列位置(右侧放不下则翻到左侧,纵向夹取进视口)。
|
|
404692
|
+
* @author ddj 2026年10月30号
|
|
404693
|
+
* @param anchor 父列或父行矩形
|
|
404694
|
+
* @param size 子列尺寸
|
|
404695
|
+
* @param viewport 浮层尺寸
|
|
404696
|
+
* @param gap 列间距
|
|
404697
|
+
* @returns 子列矩形
|
|
404698
|
+
*/
|
|
404699
|
+
function placeColumn(anchor, size, viewport, gap = 6) {
|
|
404700
|
+
const margin = 8;
|
|
404701
|
+
const maxLeft = Math.max(margin, viewport.width - size.width - margin);
|
|
404702
|
+
const maxTop = Math.max(margin, viewport.height - size.height - margin);
|
|
404703
|
+
const rightLeft = anchor.left + anchor.width + gap;
|
|
404704
|
+
const left = rightLeft + size.width > viewport.width - margin ? anchor.left - size.width - gap : rightLeft;
|
|
404705
|
+
const top = clampNumber(anchor.top, margin, maxTop, margin);
|
|
404706
|
+
return {
|
|
404707
|
+
left: clampNumber(left, margin, maxLeft, margin),
|
|
404708
|
+
top,
|
|
404709
|
+
width: size.width,
|
|
404710
|
+
height: size.height
|
|
404711
|
+
};
|
|
404712
|
+
}
|
|
404713
|
+
/**
|
|
404714
|
+
* 计算 SAO 风格居中突出缩放(离列中心越远越小越淡)。
|
|
404715
|
+
*
|
|
404716
|
+
* 幅度按「列内可用宽度」定:列 220px、左右内边距 7px + 1px 边框,行宽 204px,
|
|
404717
|
+
* 中心行放到 1.03(约 210px)刚好还在列内、不裁字;再大就会顶出滚动区。
|
|
404718
|
+
* 曲线用 smoothstep,让中间三四行连成一片「实」的区域,两端快速退淡,
|
|
404719
|
+
* 与 CSS 的上下虚化叠出「居中突出」的体积感。
|
|
404720
|
+
* @author ddj 2026年10月30号
|
|
404721
|
+
* @param index 行下标
|
|
404722
|
+
* @param count 行数
|
|
404723
|
+
* @returns 缩放与透明度
|
|
404724
|
+
*/
|
|
404725
|
+
function centerScale(index, count) {
|
|
404726
|
+
if (count <= 1) return {
|
|
404727
|
+
scale: 1,
|
|
404728
|
+
opacity: 1
|
|
404729
|
+
};
|
|
404730
|
+
const center = (count - 1) / 2;
|
|
404731
|
+
const distance = Math.abs(index - center) / Math.max(center, .5);
|
|
404732
|
+
const ratio = Math.min(1, Math.max(0, distance));
|
|
404733
|
+
const eased = ratio * ratio * (3 - 2 * ratio);
|
|
404734
|
+
return {
|
|
404735
|
+
scale: 1.03 - .075 * eased,
|
|
404736
|
+
opacity: 1 - .35 * eased
|
|
404737
|
+
};
|
|
404738
|
+
}
|
|
404739
|
+
//#endregion
|
|
404740
|
+
//#region src/client/quickMenuStore.ts
|
|
404741
|
+
/**
|
|
404742
|
+
* dsh-vscode-mode client — 快捷菜单 store(文档 + 绑定快照 + 坐标防抖)。
|
|
404743
|
+
*
|
|
404744
|
+
* 为什么客户端要 store:菜单在多列里同时展示,多列共享同一份文档与绑定校验结果;
|
|
404745
|
+
* 用外部 store + useSyncExternalStore,避免把整棵树塞进一个组件的 state。
|
|
404746
|
+
* 写入一律「先改内存 → 整体提交 → host 归一后回传」;冲突(其他窗口改过)时重拉,
|
|
404747
|
+
* 不拿本地旧版本覆盖磁盘。
|
|
404748
|
+
* 作者 ddj 2026年10月30号
|
|
404749
|
+
*/
|
|
404750
|
+
/** 绑定快照有效期(菜单打开/聚焦时刷新,期间复用)。 */
|
|
404751
|
+
const BINDINGS_TTL_MS = 6e4;
|
|
404752
|
+
/** 坐标落盘防抖。 */
|
|
404753
|
+
const LAUNCHER_DEBOUNCE_MS = 500;
|
|
404754
|
+
/**
|
|
404755
|
+
* 创建快捷菜单 store。
|
|
404756
|
+
* @author ddj 2026年10月30号
|
|
404757
|
+
* @param loaders 外部数据加载器
|
|
404758
|
+
* @returns store 实例
|
|
404759
|
+
*/
|
|
404760
|
+
function createMenuStore(loaders) {
|
|
404761
|
+
let state = {
|
|
404762
|
+
status: "idle",
|
|
404763
|
+
doc: defaultDoc()
|
|
404764
|
+
};
|
|
404765
|
+
let version = 0;
|
|
404766
|
+
let mounted = false;
|
|
404767
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
404768
|
+
/** 绑定快照(cwd/sessionId 变化或超期即重拉)。 */
|
|
404769
|
+
let bindings = { at: 0 };
|
|
404770
|
+
/** 待落盘坐标与定时器。 */
|
|
404771
|
+
let pending = null;
|
|
404772
|
+
let timer = null;
|
|
404773
|
+
/**
|
|
404774
|
+
* 写入状态并通知订阅者。
|
|
404775
|
+
* @author ddj 2026年10月30号
|
|
404776
|
+
* @param next 新状态
|
|
404777
|
+
*/
|
|
404778
|
+
function publish(next) {
|
|
404779
|
+
state = next;
|
|
404780
|
+
version += 1;
|
|
404781
|
+
for (const listener of listeners) listener();
|
|
404782
|
+
}
|
|
404783
|
+
/**
|
|
404784
|
+
* 合并补丁到当前状态。
|
|
404785
|
+
* @author ddj 2026年10月30号
|
|
404786
|
+
* @param patch 局部更新
|
|
404787
|
+
*/
|
|
404788
|
+
function patch(patchState) {
|
|
404789
|
+
publish({
|
|
404790
|
+
...state,
|
|
404791
|
+
...patchState
|
|
404792
|
+
});
|
|
404793
|
+
}
|
|
404794
|
+
/**
|
|
404795
|
+
* 读取规则与技能快照(失败保留旧值,不打断菜单)。
|
|
404796
|
+
* @author ddj 2026年10月30号
|
|
404797
|
+
* @param force 是否强制重拉
|
|
404798
|
+
*/
|
|
404799
|
+
async function refreshBindings(force = false) {
|
|
404800
|
+
const scope = loaders.scope();
|
|
404801
|
+
if (!force && Date.now() - bindings.at < BINDINGS_TTL_MS && bindings.cwd === scope.cwd && bindings.sessionId === scope.sessionId) return;
|
|
404802
|
+
bindings = {
|
|
404803
|
+
...bindings,
|
|
404804
|
+
at: Date.now(),
|
|
404805
|
+
cwd: scope.cwd,
|
|
404806
|
+
sessionId: scope.sessionId
|
|
404807
|
+
};
|
|
404808
|
+
try {
|
|
404809
|
+
bindings.rules = await loaders.rules();
|
|
404810
|
+
} catch {
|
|
404811
|
+
bindings.rules = void 0;
|
|
404812
|
+
}
|
|
404813
|
+
try {
|
|
404814
|
+
bindings.skillNames = scope.sessionId ? await loaders.skills(scope.sessionId) : null;
|
|
404815
|
+
} catch {
|
|
404816
|
+
bindings.skillNames = null;
|
|
404817
|
+
}
|
|
404818
|
+
version += 1;
|
|
404819
|
+
for (const listener of listeners) listener();
|
|
404820
|
+
}
|
|
404821
|
+
/**
|
|
404822
|
+
* 拉取文档(并顺带刷新绑定快照)。
|
|
404823
|
+
* @author ddj 2026年10月30号
|
|
404824
|
+
* @param force 是否强制重拉
|
|
404825
|
+
*/
|
|
404826
|
+
async function load(force = false) {
|
|
404827
|
+
if (state.status === "loading") return;
|
|
404828
|
+
patch({ status: "loading" });
|
|
404829
|
+
try {
|
|
404830
|
+
const result = await rpc("quickMenu.get", {});
|
|
404831
|
+
if (result.ok) patch({
|
|
404832
|
+
status: "ready",
|
|
404833
|
+
doc: result.doc,
|
|
404834
|
+
warning: result.warning,
|
|
404835
|
+
error: void 0
|
|
404836
|
+
});
|
|
404837
|
+
else patch({
|
|
404838
|
+
status: "error",
|
|
404839
|
+
error: result.error
|
|
404840
|
+
});
|
|
404841
|
+
} catch (error) {
|
|
404842
|
+
patch({
|
|
404843
|
+
status: "error",
|
|
404844
|
+
error: "读取快捷菜单失败:" + String(error)
|
|
404845
|
+
});
|
|
404846
|
+
}
|
|
404847
|
+
await refreshBindings(force);
|
|
404848
|
+
}
|
|
404849
|
+
/**
|
|
404850
|
+
* 提交整份文档(host 归一后回传,冲突时重拉)。
|
|
404851
|
+
* @author ddj 2026年10月30号
|
|
404852
|
+
* @param doc 待提交文档
|
|
404853
|
+
* @returns 写结果
|
|
404854
|
+
*/
|
|
404855
|
+
async function save(doc) {
|
|
404856
|
+
try {
|
|
404857
|
+
const result = await rpc("quickMenu.save", {
|
|
404858
|
+
doc,
|
|
404859
|
+
baseRevision: state.doc.revision
|
|
404860
|
+
});
|
|
404861
|
+
if (result.ok) {
|
|
404862
|
+
patch({
|
|
404863
|
+
status: "ready",
|
|
404864
|
+
doc: result.doc,
|
|
404865
|
+
error: void 0,
|
|
404866
|
+
warning: void 0
|
|
404867
|
+
});
|
|
404868
|
+
return { ok: true };
|
|
404869
|
+
}
|
|
404870
|
+
await load(true);
|
|
404871
|
+
return {
|
|
404872
|
+
ok: false,
|
|
404873
|
+
error: result.error
|
|
404874
|
+
};
|
|
404875
|
+
} catch (error) {
|
|
404876
|
+
return {
|
|
404877
|
+
ok: false,
|
|
404878
|
+
error: "保存快捷菜单失败:" + String(error)
|
|
404879
|
+
};
|
|
404880
|
+
}
|
|
404881
|
+
}
|
|
404882
|
+
/**
|
|
404883
|
+
* 新增或替换一项(按 id 去重)。
|
|
404884
|
+
* @author ddj 2026年10月30号
|
|
404885
|
+
* @param draft 菜单项
|
|
404886
|
+
* @returns 写结果
|
|
404887
|
+
*/
|
|
404888
|
+
async function upsert(draft) {
|
|
404889
|
+
const items = state.doc.items.filter((item) => item.id !== draft.id);
|
|
404890
|
+
items.push(draft);
|
|
404891
|
+
return save(normalizeDoc({
|
|
404892
|
+
...state.doc,
|
|
404893
|
+
items
|
|
404894
|
+
}));
|
|
404895
|
+
}
|
|
404896
|
+
/**
|
|
404897
|
+
* 删除一项及其全部子项。
|
|
404898
|
+
* @author ddj 2026年10月30号
|
|
404899
|
+
* @param id 项 id
|
|
404900
|
+
* @returns 写结果
|
|
404901
|
+
*/
|
|
404902
|
+
async function removeItem(id) {
|
|
404903
|
+
const doomed = descendantIds(state.doc, id);
|
|
404904
|
+
const items = state.doc.items.filter((item) => !doomed.includes(item.id));
|
|
404905
|
+
return save(normalizeDoc({
|
|
404906
|
+
...state.doc,
|
|
404907
|
+
items
|
|
404908
|
+
}));
|
|
404909
|
+
}
|
|
404910
|
+
/**
|
|
404911
|
+
* 定时把待写坐标落盘。
|
|
404912
|
+
* @author ddj 2026年10月30号
|
|
404913
|
+
*/
|
|
404914
|
+
async function flushLauncher() {
|
|
404915
|
+
if (timer !== null) {
|
|
404916
|
+
clearTimeout(timer);
|
|
404917
|
+
timer = null;
|
|
404918
|
+
}
|
|
404919
|
+
const target = pending;
|
|
404920
|
+
pending = null;
|
|
404921
|
+
if (!target) return;
|
|
404922
|
+
try {
|
|
404923
|
+
const result = await rpc("quickMenu.launcher", target);
|
|
404924
|
+
if (result.ok) patch({ doc: result.doc });
|
|
404925
|
+
} catch {}
|
|
404926
|
+
}
|
|
404927
|
+
/**
|
|
404928
|
+
* 记录坐标并防抖落盘。
|
|
404929
|
+
* @author ddj 2026年10月30号
|
|
404930
|
+
* @param x 横坐标
|
|
404931
|
+
* @param y 纵坐标
|
|
404932
|
+
*/
|
|
404933
|
+
function patchLauncher(x, y) {
|
|
404934
|
+
pending = {
|
|
404935
|
+
x,
|
|
404936
|
+
y
|
|
404937
|
+
};
|
|
404938
|
+
if (timer !== null) clearTimeout(timer);
|
|
404939
|
+
timer = setTimeout(() => {
|
|
404940
|
+
flushLauncher();
|
|
404941
|
+
}, LAUNCHER_DEBOUNCE_MS);
|
|
404942
|
+
}
|
|
404943
|
+
return {
|
|
404944
|
+
subscribe(listener) {
|
|
404945
|
+
listeners.add(listener);
|
|
404946
|
+
return () => {
|
|
404947
|
+
listeners.delete(listener);
|
|
404948
|
+
};
|
|
404949
|
+
},
|
|
404950
|
+
getVersion() {
|
|
404951
|
+
return version;
|
|
404952
|
+
},
|
|
404953
|
+
getState() {
|
|
404954
|
+
return state;
|
|
404955
|
+
},
|
|
404956
|
+
getContext() {
|
|
404957
|
+
return {
|
|
404958
|
+
cwd: loaders.scope().cwd,
|
|
404959
|
+
rules: bindings.rules,
|
|
404960
|
+
skillNames: bindings.skillNames ?? null
|
|
404961
|
+
};
|
|
404962
|
+
},
|
|
404963
|
+
load,
|
|
404964
|
+
save,
|
|
404965
|
+
createItem: upsert,
|
|
404966
|
+
updateItem: upsert,
|
|
404967
|
+
removeItem,
|
|
404968
|
+
patchLauncher,
|
|
404969
|
+
flushLauncher,
|
|
404970
|
+
refreshBindings,
|
|
404971
|
+
markMounted(value) {
|
|
404972
|
+
mounted = value;
|
|
404973
|
+
version += 1;
|
|
404974
|
+
for (const listener of listeners) listener();
|
|
404975
|
+
},
|
|
404976
|
+
isMounted() {
|
|
404977
|
+
return mounted;
|
|
404978
|
+
},
|
|
404979
|
+
nextId(type) {
|
|
404980
|
+
return newQuickId(type);
|
|
404981
|
+
}
|
|
404982
|
+
};
|
|
404983
|
+
}
|
|
404984
|
+
//#endregion
|
|
404985
|
+
//#region src/shared/quickPick.ts
|
|
404986
|
+
/**
|
|
404987
|
+
* dsh-vscode-mode 快捷菜单共享纯函数 — 下拉选择器的状态与摘要文案。
|
|
404988
|
+
*
|
|
404989
|
+
* 纯函数模块:禁 node/react 导入(与 shared/quickMenu.ts 同约束)。
|
|
404990
|
+
* 为什么抽:工作区/技能/规则三个下拉的「触发器上显示什么」「怎么切换一项」是同一类
|
|
404991
|
+
* 纯计算,放在 shared 里可被 vitest 直接覆盖,避免只能靠肉眼在弹窗里验。
|
|
404992
|
+
*
|
|
404993
|
+
* 作者 ddj 2026年10月30号
|
|
404994
|
+
*/
|
|
404995
|
+
/**
|
|
404996
|
+
* 推导工作区下拉的摘要(null / 空数组都算「全部工作区」)。
|
|
404997
|
+
* @author ddj 2026年10月30号
|
|
404998
|
+
* @param paths 已选路径列表(null = 全部工作区)
|
|
404999
|
+
* @param cwd 当前工作区(缺失时无法判定「当前工作区」态)
|
|
405000
|
+
* @returns 摘要(mode + 文案 + 原始路径)
|
|
405001
|
+
*/
|
|
405002
|
+
function workspaceLabel(paths, cwd) {
|
|
405003
|
+
const list = (paths ?? []).slice();
|
|
405004
|
+
if (list.length === 0) return {
|
|
405005
|
+
mode: "all",
|
|
405006
|
+
label: "全部工作区",
|
|
405007
|
+
paths: []
|
|
405008
|
+
};
|
|
405009
|
+
if (list.length === 1 && cwd !== void 0 && normalizePath(cwd) !== "" && normalizePath(list[0]) === normalizePath(cwd)) return {
|
|
405010
|
+
mode: "current",
|
|
405011
|
+
label: "当前工作区",
|
|
405012
|
+
paths: list
|
|
405013
|
+
};
|
|
405014
|
+
return {
|
|
405015
|
+
mode: "some",
|
|
405016
|
+
label: "已选 " + list.length + " 个工作区",
|
|
405017
|
+
paths: list
|
|
405018
|
+
};
|
|
405019
|
+
}
|
|
405020
|
+
/**
|
|
405021
|
+
* 多选摘要文案(0 项、1~2 项全写、更多折叠成「A、B 等 N 项」)。
|
|
405022
|
+
* @author ddj 2026年10月30号
|
|
405023
|
+
* @param labels 已选项文案
|
|
405024
|
+
* @param unit 折叠时的单位(如「项技能」「条规则」)
|
|
405025
|
+
* @returns 触发器文案
|
|
405026
|
+
*/
|
|
405027
|
+
function selectionLabel(labels, unit) {
|
|
405028
|
+
if (labels.length === 0) return "未绑定";
|
|
405029
|
+
if (labels.length <= 2) return labels.join("、");
|
|
405030
|
+
return labels.slice(0, 2).join("、") + " 等 " + labels.length + " " + unit;
|
|
405031
|
+
}
|
|
405032
|
+
/**
|
|
405033
|
+
* 工作区集合切换(归一化比较去重;清空即回到「全部工作区」的 null 语义)。
|
|
405034
|
+
* @author ddj 2026年10月30号
|
|
405035
|
+
* @param paths 当前列表(null = 全部)
|
|
405036
|
+
* @param target 目标工作区路径
|
|
405037
|
+
* @returns 新列表;空集合返回 null
|
|
405038
|
+
*/
|
|
405039
|
+
function toggleWorkspace(paths, target) {
|
|
405040
|
+
const list = (paths ?? []).slice();
|
|
405041
|
+
const key = normalizePath(target);
|
|
405042
|
+
const index = list.findIndex((path) => normalizePath(path) === key);
|
|
405043
|
+
if (index >= 0) list.splice(index, 1);
|
|
405044
|
+
else list.push(target);
|
|
405045
|
+
return list.length === 0 ? null : list;
|
|
405046
|
+
}
|
|
405047
|
+
//#endregion
|
|
405048
|
+
//#region src/client/ui/QuickMenuPickers.ts
|
|
405049
|
+
/**
|
|
405050
|
+
* dsh-vscode-mode client — 快捷菜单配置弹窗的通用选择控件。
|
|
405051
|
+
*
|
|
405052
|
+
* 提供三件控件:多选下拉(技能/规则)、限定工作区下拉(全部/当前/更多)、设置项开关与分段。
|
|
405053
|
+
* 弹层用 position: fixed 挂在 body 之上的坐标,因此不受弹窗 body 的 overflow 裁剪;
|
|
405054
|
+
* 组件仍用 React.createElement(项目内无 JSX),样式落在 quickmenu.css 的 edrv-qm-pick* 类上。
|
|
405055
|
+
* 作者 ddj 2026年10月30号
|
|
405056
|
+
*/
|
|
405057
|
+
/** 弹层最小/最大高度(决定向上还是向下展开)。 */
|
|
405058
|
+
const PANEL_MIN = 180;
|
|
405059
|
+
const PANEL_MAX = 320;
|
|
405060
|
+
/** 弹层与触发器/视口边缘的间距。 */
|
|
405061
|
+
const PANEL_GAP = 6;
|
|
405062
|
+
const EDGE_GAP = 8;
|
|
405063
|
+
/**
|
|
405064
|
+
* 拼接 className,过滤假值。
|
|
405065
|
+
* @author ddj 2026年10月30号
|
|
405066
|
+
* @param names 候选类名(false / undefined 会被忽略)
|
|
405067
|
+
* @returns 空格分隔的 className
|
|
405068
|
+
*/
|
|
405069
|
+
function classes(...names) {
|
|
405070
|
+
return names.filter(Boolean).join(" ");
|
|
405071
|
+
}
|
|
405072
|
+
/**
|
|
405073
|
+
* 按触发器位置计算弹层坐标,空间不足时向上展开。
|
|
405074
|
+
* @author ddj 2026年10月30号
|
|
405075
|
+
* @param trigger 触发器元素
|
|
405076
|
+
* @returns 弹层定位参数
|
|
405077
|
+
*/
|
|
405078
|
+
function placePanel(trigger) {
|
|
405079
|
+
const rect = trigger.getBoundingClientRect();
|
|
405080
|
+
const width = Math.max(rect.width, 280);
|
|
405081
|
+
const below = window.innerHeight - rect.bottom - PANEL_GAP - EDGE_GAP;
|
|
405082
|
+
const above = rect.top - PANEL_GAP - EDGE_GAP;
|
|
405083
|
+
const useAbove = below < PANEL_MIN && above > below;
|
|
405084
|
+
const maxHeight = Math.max(PANEL_MIN, Math.min(PANEL_MAX, useAbove ? above : below));
|
|
405085
|
+
const maxLeft = Math.max(EDGE_GAP, window.innerWidth - width - EDGE_GAP);
|
|
405086
|
+
const left = Math.min(Math.max(EDGE_GAP, rect.left), maxLeft);
|
|
405087
|
+
if (useAbove) return {
|
|
405088
|
+
left,
|
|
405089
|
+
width,
|
|
405090
|
+
maxHeight,
|
|
405091
|
+
bottom: window.innerHeight - rect.top + PANEL_GAP
|
|
405092
|
+
};
|
|
405093
|
+
return {
|
|
405094
|
+
left,
|
|
405095
|
+
width,
|
|
405096
|
+
maxHeight,
|
|
405097
|
+
top: rect.bottom + PANEL_GAP
|
|
405098
|
+
};
|
|
405099
|
+
}
|
|
405100
|
+
/**
|
|
405101
|
+
* 受控弹层的位置与开合状态。
|
|
405102
|
+
*
|
|
405103
|
+
* Esc 的处理依赖「子组件 effect 先于父组件 effect 执行」:本 hook 在 ModalShell 内部的
|
|
405104
|
+
* 子组件里挂载,因此先注册 window 捕获监听,才能在弹层打开时拦下 Esc,避免连外层弹窗一起关掉。
|
|
405105
|
+
* @author ddj 2026年10月30号
|
|
405106
|
+
* @returns 弹层句柄
|
|
405107
|
+
*/
|
|
405108
|
+
function useAnchored() {
|
|
405109
|
+
const triggerRef = react$1.default.useRef(null);
|
|
405110
|
+
const panelRef = react$1.default.useRef(null);
|
|
405111
|
+
const [open, setOpen] = react$1.default.useState(false);
|
|
405112
|
+
const [pos, setPos] = react$1.default.useState(null);
|
|
405113
|
+
const openRef = react$1.default.useRef(false);
|
|
405114
|
+
openRef.current = open;
|
|
405115
|
+
const close = react$1.default.useCallback(() => {
|
|
405116
|
+
setOpen(false);
|
|
405117
|
+
setPos(null);
|
|
405118
|
+
}, []);
|
|
405119
|
+
const toggle = react$1.default.useCallback(() => {
|
|
405120
|
+
if (openRef.current) {
|
|
405121
|
+
close();
|
|
405122
|
+
return;
|
|
405123
|
+
}
|
|
405124
|
+
const trigger = triggerRef.current;
|
|
405125
|
+
setPos(trigger ? placePanel(trigger) : null);
|
|
405126
|
+
setOpen(true);
|
|
405127
|
+
}, [close]);
|
|
405128
|
+
react$1.default.useEffect(() => {
|
|
405129
|
+
const onKey = (event) => {
|
|
405130
|
+
if (event.key !== "Escape" || !openRef.current) return;
|
|
405131
|
+
event.stopImmediatePropagation();
|
|
405132
|
+
event.preventDefault();
|
|
405133
|
+
close();
|
|
405134
|
+
};
|
|
405135
|
+
window.addEventListener("keydown", onKey, true);
|
|
405136
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
405137
|
+
}, [close]);
|
|
405138
|
+
react$1.default.useEffect(() => {
|
|
405139
|
+
if (!open) return;
|
|
405140
|
+
const reposition = () => {
|
|
405141
|
+
const trigger = triggerRef.current;
|
|
405142
|
+
if (trigger) setPos(placePanel(trigger));
|
|
405143
|
+
};
|
|
405144
|
+
const onDown = (event) => {
|
|
405145
|
+
const target = event.target;
|
|
405146
|
+
if (!target) return;
|
|
405147
|
+
if (triggerRef.current?.contains(target)) return;
|
|
405148
|
+
if (panelRef.current?.contains(target)) return;
|
|
405149
|
+
close();
|
|
405150
|
+
};
|
|
405151
|
+
const onScroll = (event) => {
|
|
405152
|
+
const target = event.target;
|
|
405153
|
+
if (target && panelRef.current?.contains(target)) return;
|
|
405154
|
+
reposition();
|
|
405155
|
+
};
|
|
405156
|
+
window.addEventListener("mousedown", onDown, true);
|
|
405157
|
+
window.addEventListener("resize", reposition);
|
|
405158
|
+
window.addEventListener("scroll", onScroll, true);
|
|
405159
|
+
return () => {
|
|
405160
|
+
window.removeEventListener("mousedown", onDown, true);
|
|
405161
|
+
window.removeEventListener("resize", reposition);
|
|
405162
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
405163
|
+
};
|
|
405164
|
+
}, [open, close]);
|
|
405165
|
+
return {
|
|
405166
|
+
open,
|
|
405167
|
+
style: pos ? {
|
|
405168
|
+
left: pos.left,
|
|
405169
|
+
top: pos.top,
|
|
405170
|
+
bottom: pos.bottom,
|
|
405171
|
+
width: pos.width,
|
|
405172
|
+
maxHeight: pos.maxHeight
|
|
405173
|
+
} : {},
|
|
405174
|
+
triggerRef,
|
|
405175
|
+
panelRef,
|
|
405176
|
+
toggle,
|
|
405177
|
+
close
|
|
405178
|
+
};
|
|
405179
|
+
}
|
|
405180
|
+
/**
|
|
405181
|
+
* 收集所有分组的选项键→标签映射。
|
|
405182
|
+
* @author ddj 2026年10月30号
|
|
405183
|
+
* @param groups 分组选项
|
|
405184
|
+
* @returns 选项键到标签的映射
|
|
405185
|
+
*/
|
|
405186
|
+
function labelsOf(groups) {
|
|
405187
|
+
const labels = /* @__PURE__ */ new Map();
|
|
405188
|
+
for (const group of groups) for (const option of group.options) labels.set(option.key, option.label);
|
|
405189
|
+
return labels;
|
|
405190
|
+
}
|
|
405191
|
+
/**
|
|
405192
|
+
* 统计已选项里带警告/错误状态的数量。
|
|
405193
|
+
* @author ddj 2026年10月30号
|
|
405194
|
+
* @param groups 分组选项
|
|
405195
|
+
* @param selected 已选键
|
|
405196
|
+
* @returns 异常状态条目数
|
|
405197
|
+
*/
|
|
405198
|
+
function issueCount(groups, selected) {
|
|
405199
|
+
const picked = new Set(selected);
|
|
405200
|
+
let count = 0;
|
|
405201
|
+
for (const group of groups) for (const option of group.options) if (picked.has(option.key) && option.state && option.state !== "ok") count += 1;
|
|
405202
|
+
return count;
|
|
405203
|
+
}
|
|
405204
|
+
/**
|
|
405205
|
+
* 下拉触发器:显示当前选择摘要与展开箭头。
|
|
405206
|
+
* @author ddj 2026年10月30号
|
|
405207
|
+
* @param props 触发器属性
|
|
405208
|
+
* @returns 触发器按钮
|
|
405209
|
+
*/
|
|
405210
|
+
function PickTrigger(props) {
|
|
405211
|
+
return react$1.default.createElement("button", {
|
|
405212
|
+
ref: props.triggerRef,
|
|
405213
|
+
id: props.buttonId,
|
|
405214
|
+
type: "button",
|
|
405215
|
+
className: classes("edrv-qm-pick-trigger", props.open && "edrv-qm-pick-trigger--open"),
|
|
405216
|
+
disabled: props.disabled,
|
|
405217
|
+
title: props.hint,
|
|
405218
|
+
"aria-haspopup": "listbox",
|
|
405219
|
+
"aria-expanded": props.open,
|
|
405220
|
+
onClick: props.onClick
|
|
405221
|
+
}, react$1.default.createElement("span", { className: "edrv-qm-pick-summary" }, props.summary), props.issueCount ? react$1.default.createElement("span", { className: "edrv-qm-pick-badge" }, String(props.issueCount)) : null, react$1.default.createElement("span", {
|
|
405222
|
+
className: "edrv-qm-pick-caret",
|
|
405223
|
+
"aria-hidden": "true"
|
|
405224
|
+
}));
|
|
405225
|
+
}
|
|
405226
|
+
/**
|
|
405227
|
+
* 绝对定位的弹层容器。
|
|
405228
|
+
* @author ddj 2026年10月30号
|
|
405229
|
+
* @param props 弹层属性
|
|
405230
|
+
* @returns 弹层节点
|
|
405231
|
+
*/
|
|
405232
|
+
function PickPanel(props) {
|
|
405233
|
+
return react$1.default.createElement("div", {
|
|
405234
|
+
ref: props.panelRef,
|
|
405235
|
+
className: "edrv-qm-pick-panel",
|
|
405236
|
+
style: props.style,
|
|
405237
|
+
role: "listbox",
|
|
405238
|
+
"aria-labelledby": props.labelledBy
|
|
405239
|
+
}, props.children);
|
|
405240
|
+
}
|
|
405241
|
+
/**
|
|
405242
|
+
* 可勾选的多选项行。
|
|
405243
|
+
* @author ddj 2026年10月30号
|
|
405244
|
+
* @param props 选项属性
|
|
405245
|
+
* @returns 选项按钮
|
|
405246
|
+
*/
|
|
405247
|
+
function PickOptionRow(props) {
|
|
405248
|
+
const option = props.option;
|
|
405249
|
+
const state = option.state ?? "ok";
|
|
405250
|
+
return react$1.default.createElement("button", {
|
|
405251
|
+
type: "button",
|
|
405252
|
+
role: "option",
|
|
405253
|
+
className: classes("edrv-qm-pick-option", props.picked && "edrv-qm-pick-option--on"),
|
|
405254
|
+
"aria-selected": props.picked,
|
|
405255
|
+
title: option.reason ?? option.hint,
|
|
405256
|
+
onClick: () => props.onToggle(option.key)
|
|
405257
|
+
}, react$1.default.createElement("span", {
|
|
405258
|
+
className: classes("edrv-qm-pick-check", props.picked && "edrv-qm-pick-check--on"),
|
|
405259
|
+
"aria-hidden": "true"
|
|
405260
|
+
}, props.picked ? "✓" : ""), react$1.default.createElement("span", { className: "edrv-qm-pick-name" }, option.label), state === "ok" ? null : react$1.default.createElement("span", {
|
|
405261
|
+
className: classes("edrv-qm-pick-dot", "edrv-qm-pick-dot--" + state),
|
|
405262
|
+
"aria-hidden": "true"
|
|
405263
|
+
}), option.hint ? react$1.default.createElement("span", { className: "edrv-qm-pick-hint" }, option.hint) : null);
|
|
405264
|
+
}
|
|
405265
|
+
/**
|
|
405266
|
+
* 多选下拉:技能与规则共用,按来源分组展示。
|
|
405267
|
+
* @author ddj 2026年10月30号
|
|
405268
|
+
* @param props 多选下拉属性
|
|
405269
|
+
* @returns 多选下拉组件
|
|
405270
|
+
*/
|
|
405271
|
+
function PickMulti(props) {
|
|
405272
|
+
const anchor = useAnchored();
|
|
405273
|
+
const labels = labelsOf(props.groups);
|
|
405274
|
+
const summary = selectionLabel(props.selected.map((key) => labels.get(key) ?? key), props.unit);
|
|
405275
|
+
const issues = issueCount(props.groups, props.selected);
|
|
405276
|
+
const rows = props.groups.filter((group) => group.options.length > 0).map((group) => react$1.default.createElement("div", {
|
|
405277
|
+
key: group.key,
|
|
405278
|
+
className: "edrv-qm-pick-group"
|
|
405279
|
+
}, react$1.default.createElement("div", { className: "edrv-qm-pick-group-title" }, group.label), group.options.map((option) => react$1.default.createElement(PickOptionRow, {
|
|
405280
|
+
key: option.key,
|
|
405281
|
+
option,
|
|
405282
|
+
picked: props.selected.includes(option.key),
|
|
405283
|
+
onToggle: props.onToggle
|
|
405284
|
+
}))));
|
|
405285
|
+
return react$1.default.createElement("div", { className: "edrv-qm-pick" }, react$1.default.createElement(PickTrigger, {
|
|
405286
|
+
triggerRef: anchor.triggerRef,
|
|
405287
|
+
buttonId: props.buttonId,
|
|
405288
|
+
summary,
|
|
405289
|
+
open: anchor.open,
|
|
405290
|
+
disabled: props.disabled === true,
|
|
405291
|
+
issueCount: issues,
|
|
405292
|
+
hint: props.disabled ? props.disabledHint : void 0,
|
|
405293
|
+
onClick: anchor.toggle
|
|
405294
|
+
}), anchor.open ? react$1.default.createElement(PickPanel, {
|
|
405295
|
+
panelRef: anchor.panelRef,
|
|
405296
|
+
style: anchor.style,
|
|
405297
|
+
labelledBy: props.buttonId
|
|
405298
|
+
}, rows.length > 0 ? react$1.default.createElement("div", { className: "edrv-qm-pick-list" }, rows) : react$1.default.createElement("div", { className: "edrv-qm-pick-empty" }, props.emptyText)) : null);
|
|
405299
|
+
}
|
|
405300
|
+
/**
|
|
405301
|
+
* 限定工作区下拉:全部 / 当前 / 更多(更多展开全部工作区多选)。
|
|
405302
|
+
* @author ddj 2026年10月30号
|
|
405303
|
+
* @param props 工作区下拉属性
|
|
405304
|
+
* @returns 工作区下拉组件
|
|
405305
|
+
*/
|
|
405306
|
+
function PickWorkspace(props) {
|
|
405307
|
+
const anchor = useAnchored();
|
|
405308
|
+
const [more, setMore] = react$1.default.useState(false);
|
|
405309
|
+
const summary = workspaceLabel(props.selected, props.cwd);
|
|
405310
|
+
const picked = props.selected ?? [];
|
|
405311
|
+
const disabled = props.inherited === true;
|
|
405312
|
+
const currentPath = props.cwd;
|
|
405313
|
+
const isAll = picked.length === 0;
|
|
405314
|
+
const isCurrent = picked.length === 1 && !!currentPath && summary.mode === "current";
|
|
405315
|
+
const onPickAll = () => {
|
|
405316
|
+
props.onChange(null);
|
|
405317
|
+
anchor.close();
|
|
405318
|
+
};
|
|
405319
|
+
const onPickCurrent = () => {
|
|
405320
|
+
if (!currentPath) return;
|
|
405321
|
+
props.onChange([currentPath]);
|
|
405322
|
+
anchor.close();
|
|
405323
|
+
};
|
|
405324
|
+
const quick = [react$1.default.createElement(PickOptionRow, {
|
|
405325
|
+
key: "__all__",
|
|
405326
|
+
option: {
|
|
405327
|
+
key: "__all__",
|
|
405328
|
+
label: "全部工作区",
|
|
405329
|
+
hint: "菜单在所有工作区可见"
|
|
405330
|
+
},
|
|
405331
|
+
picked: isAll,
|
|
405332
|
+
onToggle: onPickAll
|
|
405333
|
+
}), react$1.default.createElement(PickOptionRow, {
|
|
405334
|
+
key: "__current__",
|
|
405335
|
+
option: {
|
|
405336
|
+
key: "__current__",
|
|
405337
|
+
label: "当前工作区",
|
|
405338
|
+
hint: currentPath || "当前没有活跃工作区"
|
|
405339
|
+
},
|
|
405340
|
+
picked: isCurrent,
|
|
405341
|
+
onToggle: onPickCurrent
|
|
405342
|
+
})];
|
|
405343
|
+
const rows = props.workspaces.map((workspace) => react$1.default.createElement(PickOptionRow, {
|
|
405344
|
+
key: workspace.path,
|
|
405345
|
+
option: {
|
|
405346
|
+
key: workspace.path,
|
|
405347
|
+
label: workspace.title,
|
|
405348
|
+
hint: workspace.path
|
|
405349
|
+
},
|
|
405350
|
+
picked: picked.some((path) => path === workspace.path),
|
|
405351
|
+
onToggle: (path) => props.onChange(toggleWorkspace(props.selected, path))
|
|
405352
|
+
}));
|
|
405353
|
+
const moreToggle = react$1.default.createElement("button", {
|
|
405354
|
+
type: "button",
|
|
405355
|
+
className: classes("edrv-qm-pick-more", more && "edrv-qm-pick-more--on"),
|
|
405356
|
+
"aria-expanded": more,
|
|
405357
|
+
onClick: () => setMore(!more)
|
|
405358
|
+
}, more ? "收起工作区列表" : "更多工作区(" + String(props.workspaces.length) + ")");
|
|
405359
|
+
return react$1.default.createElement("div", { className: "edrv-qm-pick" }, react$1.default.createElement(PickTrigger, {
|
|
405360
|
+
triggerRef: anchor.triggerRef,
|
|
405361
|
+
buttonId: props.buttonId,
|
|
405362
|
+
summary: summary.label,
|
|
405363
|
+
open: anchor.open,
|
|
405364
|
+
disabled,
|
|
405365
|
+
hint: disabled ? props.inheritedHint : "点开选择生效范围",
|
|
405366
|
+
onClick: anchor.toggle
|
|
405367
|
+
}), anchor.open ? react$1.default.createElement(PickPanel, {
|
|
405368
|
+
panelRef: anchor.panelRef,
|
|
405369
|
+
style: anchor.style,
|
|
405370
|
+
labelledBy: props.buttonId
|
|
405371
|
+
}, react$1.default.createElement("div", { className: "edrv-qm-pick-list" }, quick), moreToggle, more ? react$1.default.createElement("div", { className: "edrv-qm-pick-list" }, rows) : null) : null);
|
|
405372
|
+
}
|
|
405373
|
+
/**
|
|
405374
|
+
* 开关行:左侧标题与说明,右侧滑动开关。
|
|
405375
|
+
* @author ddj 2026年10月30号
|
|
405376
|
+
* @param props 开关属性
|
|
405377
|
+
* @returns 开关行
|
|
405378
|
+
*/
|
|
405379
|
+
function PickSwitch(props) {
|
|
405380
|
+
return react$1.default.createElement("div", { className: "edrv-qm-switch-row" }, react$1.default.createElement("div", { className: "edrv-qm-switch-text" }, react$1.default.createElement("div", { className: "edrv-qm-pick-name" }, props.label), props.hint ? react$1.default.createElement("div", { className: "edrv-qm-pick-hint" }, props.hint) : null), react$1.default.createElement("button", {
|
|
405381
|
+
id: props.buttonId,
|
|
405382
|
+
type: "button",
|
|
405383
|
+
role: "switch",
|
|
405384
|
+
className: classes("edrv-qm-switch", props.checked && "edrv-qm-switch--on"),
|
|
405385
|
+
"aria-checked": props.checked,
|
|
405386
|
+
"aria-label": props.label,
|
|
405387
|
+
disabled: props.disabled === true,
|
|
405388
|
+
onClick: () => props.onChange(!props.checked)
|
|
405389
|
+
}, react$1.default.createElement("span", {
|
|
405390
|
+
className: "edrv-qm-switch-knob",
|
|
405391
|
+
"aria-hidden": "true"
|
|
405392
|
+
})));
|
|
405393
|
+
}
|
|
405394
|
+
/**
|
|
405395
|
+
* 分段控件:等宽按钮组,用于 1–5 层数这类小范围枚举。
|
|
405396
|
+
* @author ddj 2026年10月30号
|
|
405397
|
+
* @param props 分段控件属性
|
|
405398
|
+
* @returns 分段控件
|
|
405399
|
+
*/
|
|
405400
|
+
function PickSeg(props) {
|
|
405401
|
+
return react$1.default.createElement("div", {
|
|
405402
|
+
className: "edrv-qm-seg",
|
|
405403
|
+
role: "group",
|
|
405404
|
+
"aria-label": props.label
|
|
405405
|
+
}, props.options.map((option) => react$1.default.createElement("button", {
|
|
405406
|
+
key: option.value,
|
|
405407
|
+
type: "button",
|
|
405408
|
+
className: classes("edrv-qm-seg-btn", option.value === props.value && "edrv-qm-seg-btn--on"),
|
|
405409
|
+
"aria-pressed": option.value === props.value,
|
|
405410
|
+
disabled: props.disabled === true,
|
|
405411
|
+
onClick: () => props.onChange(option.value)
|
|
405412
|
+
}, option.label)));
|
|
405413
|
+
}
|
|
405414
|
+
//#endregion
|
|
405415
|
+
//#region src/shared/skillCatalog.ts
|
|
405416
|
+
/** 来源分组标题文案。 */
|
|
405417
|
+
const SKILL_SOURCE_LABEL = {
|
|
405418
|
+
user: "全局技能",
|
|
405419
|
+
project: "当前工作区技能"
|
|
405420
|
+
};
|
|
405421
|
+
/**
|
|
405422
|
+
* 按来源分组(空组不出现;面板全空时由 UI 统一渲染空态)。
|
|
405423
|
+
* @author ddj 2026年10月30号
|
|
405424
|
+
* @param input 技能候选快照
|
|
405425
|
+
* @returns 非空分组(全局在前、工作区在后)
|
|
405426
|
+
*/
|
|
405427
|
+
function groupSkills(input) {
|
|
405428
|
+
if (!input) return [];
|
|
405429
|
+
const groups = [];
|
|
405430
|
+
if (input.user.length > 0) groups.push({
|
|
405431
|
+
key: "user",
|
|
405432
|
+
label: SKILL_SOURCE_LABEL.user,
|
|
405433
|
+
skills: input.user
|
|
405434
|
+
});
|
|
405435
|
+
if (input.project.length > 0) groups.push({
|
|
405436
|
+
key: "project",
|
|
405437
|
+
label: SKILL_SOURCE_LABEL.project,
|
|
405438
|
+
skills: input.project
|
|
405439
|
+
});
|
|
405440
|
+
return groups;
|
|
405441
|
+
}
|
|
405442
|
+
//#endregion
|
|
405443
|
+
//#region src/client/ui/QuickMenuDialog.tsx
|
|
405444
|
+
/**
|
|
405445
|
+
* dsh-vscode-mode client — 快捷菜单的配置弹窗(新增/编辑项 + 全局设置)。
|
|
405446
|
+
*
|
|
405447
|
+
* 设计要点:配置不再有独立设置页,全部从菜单就地入口打开;
|
|
405448
|
+
* 表单按「类型」动态取舍字段(组只要名称/归属/优先级,按钮额外要提示词与绑定);
|
|
405449
|
+
* 工作区限定沿祖先链继承——父组已限定时本项只读展示继承值,避免出现互相矛盾的配置。
|
|
405450
|
+
* 技能与规则都走多选下拉(候选来自目录扫描,不再手输名字);分段与开关统一由 QuickMenuPickers 提供。
|
|
405451
|
+
* 作者 ddj 2026年10月30号
|
|
405452
|
+
*/
|
|
405453
|
+
/** 类型分段的可选值。 */
|
|
405454
|
+
const TYPE_OPTIONS = [{
|
|
405455
|
+
value: "button",
|
|
405456
|
+
label: "按钮"
|
|
405457
|
+
}, {
|
|
405458
|
+
value: "group",
|
|
405459
|
+
label: "组"
|
|
405460
|
+
}];
|
|
405461
|
+
/** 规则注入方式分段的可选值。 */
|
|
405462
|
+
const MODE_OPTIONS = [{
|
|
405463
|
+
value: "reference",
|
|
405464
|
+
label: "引用"
|
|
405465
|
+
}, {
|
|
405466
|
+
value: "inline",
|
|
405467
|
+
label: "内联正文"
|
|
405468
|
+
}];
|
|
405469
|
+
/** 最大嵌套层数分段的可选值(1–5)。 */
|
|
405470
|
+
const DEPTH_OPTIONS = Array.from({ length: 5 }, (_, index) => {
|
|
405471
|
+
const depth = 1 + index;
|
|
405472
|
+
return {
|
|
405473
|
+
value: String(depth),
|
|
405474
|
+
label: String(depth)
|
|
405475
|
+
};
|
|
405476
|
+
});
|
|
405477
|
+
/** 技能描述在候选行里的最大长度。 */
|
|
405478
|
+
const SKILL_HINT_CAP = 28;
|
|
405479
|
+
/**
|
|
405480
|
+
* 截断技能描述,作为候选行的次要说明。
|
|
405481
|
+
* @author ddj 2026年10月30号
|
|
405482
|
+
* @param description 技能描述
|
|
405483
|
+
* @returns 单行摘要
|
|
405484
|
+
*/
|
|
405485
|
+
function brief(description) {
|
|
405486
|
+
const text = description.trim().replace(/\s+/g, " ");
|
|
405487
|
+
if (text === "") return void 0;
|
|
405488
|
+
return text.length > SKILL_HINT_CAP ? text.slice(0, SKILL_HINT_CAP) + "…" : text;
|
|
405489
|
+
}
|
|
405490
|
+
/**
|
|
405491
|
+
* 单个技能候选(状态点来自会话可见性校验)。
|
|
405492
|
+
* @author ddj 2026年10月30号
|
|
405493
|
+
* @param name 技能名
|
|
405494
|
+
* @param ctx 校验上下文
|
|
405495
|
+
* @param hint 次要说明
|
|
405496
|
+
* @returns 候选项
|
|
405497
|
+
*/
|
|
405498
|
+
function skillOption(name, ctx, hint) {
|
|
405499
|
+
const status = validateSkill(name, ctx);
|
|
405500
|
+
return {
|
|
405501
|
+
key: name,
|
|
405502
|
+
label: name,
|
|
405503
|
+
...hint ? { hint } : {},
|
|
405504
|
+
...status.state === "ok" ? {} : { state: status.state === "warn" ? "warn" : void 0 },
|
|
405505
|
+
...status.reason ? { reason: status.reason } : {}
|
|
405506
|
+
};
|
|
405507
|
+
}
|
|
405508
|
+
/**
|
|
405509
|
+
* 技能候选分组:全局技能 / 当前工作区技能 / 已绑定但不在候选内。
|
|
405510
|
+
* @author ddj 2026年10月30号
|
|
405511
|
+
* @param catalog 目录扫描结果(读取失败为 null)
|
|
405512
|
+
* @param names 当前会话可见技能名(候选为空时的兜底来源)
|
|
405513
|
+
* @param selected 已绑定技能名
|
|
405514
|
+
* @param ctx 校验上下文
|
|
405515
|
+
* @returns 分组候选
|
|
405516
|
+
*/
|
|
405517
|
+
function skillGroups(catalog, names, selected, ctx) {
|
|
405518
|
+
const groups = [];
|
|
405519
|
+
const known = /* @__PURE__ */ new Set();
|
|
405520
|
+
for (const group of groupSkills(catalog)) {
|
|
405521
|
+
const options = group.skills.map((info) => skillOption(info.name, ctx, brief(info.description)));
|
|
405522
|
+
for (const option of options) known.add(option.key);
|
|
405523
|
+
if (options.length > 0) groups.push({
|
|
405524
|
+
key: group.key,
|
|
405525
|
+
label: group.label,
|
|
405526
|
+
options
|
|
405527
|
+
});
|
|
405528
|
+
}
|
|
405529
|
+
if (groups.length === 0 && names && names.length > 0) {
|
|
405530
|
+
const options = names.map((name) => skillOption(name, ctx));
|
|
405531
|
+
for (const option of options) known.add(option.key);
|
|
405532
|
+
groups.push({
|
|
405533
|
+
key: "session",
|
|
405534
|
+
label: "当前会话技能",
|
|
405535
|
+
options
|
|
405536
|
+
});
|
|
405537
|
+
}
|
|
405538
|
+
const extra = selected.filter((name) => !known.has(name));
|
|
405539
|
+
if (extra.length > 0) groups.push({
|
|
405540
|
+
key: "bound",
|
|
405541
|
+
label: "已绑定(不在候选内)",
|
|
405542
|
+
options: extra.map((name) => skillOption(name, ctx, "候选目录里没有该技能,可取消绑定"))
|
|
405543
|
+
});
|
|
405544
|
+
return groups;
|
|
405545
|
+
}
|
|
405546
|
+
/**
|
|
405547
|
+
* 单个规则候选(状态点来自 validateRule)。
|
|
405548
|
+
* @author ddj 2026年10月30号
|
|
405549
|
+
* @param choice 规则候选
|
|
405550
|
+
* @param ctx 校验上下文
|
|
405551
|
+
* @returns 候选项
|
|
405552
|
+
*/
|
|
405553
|
+
function ruleOption(choice, ctx) {
|
|
405554
|
+
const status = validateRule(choice.binding, ctx);
|
|
405555
|
+
const state = status.state === "warn" || status.state === "error" ? status.state : void 0;
|
|
405556
|
+
return {
|
|
405557
|
+
key: bindingKey(choice.binding),
|
|
405558
|
+
label: choice.label,
|
|
405559
|
+
hint: choice.hint,
|
|
405560
|
+
...state ? { state } : {},
|
|
405561
|
+
...status.reason ? { reason: status.reason } : {}
|
|
405562
|
+
};
|
|
405563
|
+
}
|
|
405564
|
+
/**
|
|
405565
|
+
* 规则候选分组:用户规则 / 当前工作区规则 / 已绑定但已失效。
|
|
405566
|
+
* @author ddj 2026年10月30号
|
|
405567
|
+
* @param choices 规则候选
|
|
405568
|
+
* @param selected 已绑定规则
|
|
405569
|
+
* @param ctx 校验上下文
|
|
405570
|
+
* @returns 分组候选
|
|
405571
|
+
*/
|
|
405572
|
+
function ruleGroups(choices, selected, ctx) {
|
|
405573
|
+
const known = new Set(choices.map((choice) => bindingKey(choice.binding)));
|
|
405574
|
+
const user = choices.filter((choice) => choice.binding.scope === "user");
|
|
405575
|
+
const project = choices.filter((choice) => choice.binding.scope === "project");
|
|
405576
|
+
const groups = [];
|
|
405577
|
+
if (user.length > 0) groups.push({
|
|
405578
|
+
key: "user",
|
|
405579
|
+
label: "用户规则",
|
|
405580
|
+
options: user.map((choice) => ruleOption(choice, ctx))
|
|
405581
|
+
});
|
|
405582
|
+
if (project.length > 0) groups.push({
|
|
405583
|
+
key: "project",
|
|
405584
|
+
label: "当前工作区规则",
|
|
405585
|
+
options: project.map((choice) => ruleOption(choice, ctx))
|
|
405586
|
+
});
|
|
405587
|
+
const extra = selected.filter((binding) => !known.has(bindingKey(binding)));
|
|
405588
|
+
if (extra.length > 0) groups.push({
|
|
405589
|
+
key: "bound",
|
|
405590
|
+
label: "已绑定(规则已不存在)",
|
|
405591
|
+
options: extra.map((binding) => {
|
|
405592
|
+
const status = validateRule(binding, ctx);
|
|
405593
|
+
return {
|
|
405594
|
+
key: bindingKey(binding),
|
|
405595
|
+
label: binding.file,
|
|
405596
|
+
hint: binding.scope === "user" ? "用户规则" : "项目规则",
|
|
405597
|
+
state: status.state === "warn" ? "warn" : "error",
|
|
405598
|
+
reason: status.reason ?? "候选里没有该规则,可取消绑定"
|
|
405599
|
+
};
|
|
405600
|
+
})
|
|
405601
|
+
});
|
|
405602
|
+
return groups;
|
|
405603
|
+
}
|
|
405604
|
+
/**
|
|
405605
|
+
* 构造待保存草稿(编辑时沿用原 id,校验需要它判断自身子树)。
|
|
405606
|
+
* @author ddj 2026年10月30号
|
|
405607
|
+
* @param id 项 id
|
|
405608
|
+
* @param fields 表单字段
|
|
405609
|
+
* @returns 菜单项
|
|
405610
|
+
*/
|
|
405611
|
+
function buildDraft(id, fields) {
|
|
405612
|
+
const base = {
|
|
405613
|
+
id,
|
|
405614
|
+
name: fields.name,
|
|
405615
|
+
order: fields.order,
|
|
405616
|
+
parentId: fields.parentId,
|
|
405617
|
+
workspaces: fields.workspaces
|
|
405618
|
+
};
|
|
405619
|
+
if (fields.type === "group") return {
|
|
405620
|
+
...base,
|
|
405621
|
+
type: "group"
|
|
405622
|
+
};
|
|
405623
|
+
const button = {
|
|
405624
|
+
...base,
|
|
405625
|
+
type: "button",
|
|
405626
|
+
prompt: fields.prompt,
|
|
405627
|
+
skills: fields.skills,
|
|
405628
|
+
rules: fields.rules,
|
|
405629
|
+
ruleMode: fields.ruleMode
|
|
405630
|
+
};
|
|
405631
|
+
if (fields.clickAction !== "inherit") button.clickAction = fields.clickAction;
|
|
405632
|
+
return button;
|
|
405633
|
+
}
|
|
405634
|
+
/**
|
|
405635
|
+
* 归属组候选(组自身与自身子树不能当父级,否则会成环)。
|
|
405636
|
+
* @author ddj 2026年10月30号
|
|
405637
|
+
* @param doc 文档
|
|
405638
|
+
* @param selfId 正在编辑的项 id
|
|
405639
|
+
* @returns 候选(顶层 + 可选组)
|
|
405640
|
+
*/
|
|
405641
|
+
function groupChoices(doc, selfId) {
|
|
405642
|
+
const banned = selfId === "" ? [] : descendantIds(doc, selfId);
|
|
405643
|
+
const out = [{
|
|
405644
|
+
id: "",
|
|
405645
|
+
label: "顶层(root 组)"
|
|
405646
|
+
}];
|
|
405647
|
+
for (const item of doc.items) {
|
|
405648
|
+
if (item.type !== "group" || banned.includes(item.id)) continue;
|
|
405649
|
+
out.push({
|
|
405650
|
+
id: item.id,
|
|
405651
|
+
label: item.name + "(第 " + depthOf(doc, item.id) + " 层)"
|
|
405652
|
+
});
|
|
405653
|
+
}
|
|
405654
|
+
return out;
|
|
405655
|
+
}
|
|
405656
|
+
/**
|
|
405657
|
+
* 规则候选(用户规则 + 当前工作区的项目规则)。
|
|
405658
|
+
* @author ddj 2026年10月30号
|
|
405659
|
+
* @param snapshot 规则快照
|
|
405660
|
+
* @param cwd 当前工作区
|
|
405661
|
+
* @returns 候选列表
|
|
405662
|
+
*/
|
|
405663
|
+
function ruleChoices(snapshot, cwd) {
|
|
405664
|
+
if (!snapshot) return [];
|
|
405665
|
+
const out = [];
|
|
405666
|
+
for (const info of snapshot.user) {
|
|
405667
|
+
const binding = {
|
|
405668
|
+
scope: "user",
|
|
405669
|
+
file: info.file
|
|
405670
|
+
};
|
|
405671
|
+
if (info.origin) binding.origin = info.origin;
|
|
405672
|
+
out.push({
|
|
405673
|
+
binding,
|
|
405674
|
+
label: info.file,
|
|
405675
|
+
hint: "用户规则 · " + (info.type === "always" ? "总是生效" : info.type === "auto" ? "按 globs" : "手动")
|
|
405676
|
+
});
|
|
405677
|
+
}
|
|
405678
|
+
const wanted = normalizePath(cwd ?? "");
|
|
405679
|
+
for (const project of snapshot.projects) {
|
|
405680
|
+
if (wanted !== "" && normalizePath(project.workspacePath) !== wanted) continue;
|
|
405681
|
+
for (const info of project.rules) out.push({
|
|
405682
|
+
binding: {
|
|
405683
|
+
scope: "project",
|
|
405684
|
+
workspacePath: project.workspacePath,
|
|
405685
|
+
file: info.file
|
|
405686
|
+
},
|
|
405687
|
+
label: info.file,
|
|
405688
|
+
hint: "项目规则 · " + project.title
|
|
405689
|
+
});
|
|
405690
|
+
}
|
|
405691
|
+
return out;
|
|
405692
|
+
}
|
|
405693
|
+
/**
|
|
405694
|
+
* 表单里的分组区块(标题 + 说明 + 内容),把长表单切成可扫读的几段。
|
|
405695
|
+
* @author ddj 2026年10月30号
|
|
405696
|
+
* @param props 区块属性
|
|
405697
|
+
* @returns 区块元素
|
|
405698
|
+
*/
|
|
405699
|
+
function Section(props) {
|
|
405700
|
+
return react$1.default.createElement("div", { className: "edrv-qm-section" }, react$1.default.createElement("div", { className: "edrv-qm-section-head" }, react$1.default.createElement("span", { className: "edrv-qm-section-title" }, props.title), props.hint ? react$1.default.createElement("span", { className: "edrv-qm-section-hint" }, props.hint) : null), react$1.default.createElement("div", { className: "edrv-qm-section-body" }, props.children));
|
|
405701
|
+
}
|
|
405702
|
+
/**
|
|
405703
|
+
* 新增/编辑菜单项弹窗。
|
|
405704
|
+
* @author ddj 2026年10月30号
|
|
405705
|
+
* @param props host、待编辑项、父组、关闭回调
|
|
405706
|
+
* @returns 弹窗元素
|
|
405707
|
+
*/
|
|
405708
|
+
function QuickItemDialog(props) {
|
|
405709
|
+
const { host, item, parentId } = props;
|
|
405710
|
+
const doc = host.store.getState().doc;
|
|
405711
|
+
const editing = item;
|
|
405712
|
+
const [type, setType] = react$1.default.useState(editing?.type ?? "button");
|
|
405713
|
+
const [name, setName] = react$1.default.useState(editing?.name ?? "");
|
|
405714
|
+
const [order, setOrder] = react$1.default.useState(editing?.order ?? 100);
|
|
405715
|
+
const [parent, setParent] = react$1.default.useState(editing?.parentId ?? parentId);
|
|
405716
|
+
const [workspaces, setWorkspaces] = react$1.default.useState(editing?.workspaces ?? null);
|
|
405717
|
+
const [prompt, setPrompt] = react$1.default.useState(editing?.type === "button" ? editing.prompt : "");
|
|
405718
|
+
const [skills, setSkills] = react$1.default.useState(editing?.type === "button" ? editing.skills.slice() : []);
|
|
405719
|
+
const [rules, setRules] = react$1.default.useState(editing?.type === "button" ? editing.rules.slice() : []);
|
|
405720
|
+
const [ruleMode, setRuleMode] = react$1.default.useState(editing?.type === "button" ? editing.ruleMode : "reference");
|
|
405721
|
+
const [clickAction, setClickAction] = react$1.default.useState(editing?.type === "button" ? editing.clickAction ?? "inherit" : "inherit");
|
|
405722
|
+
const [catalog, setCatalog] = react$1.default.useState(null);
|
|
405723
|
+
const [snapshot, setSnapshot] = react$1.default.useState(null);
|
|
405724
|
+
const [skillNames, setSkillNames] = react$1.default.useState(null);
|
|
405725
|
+
const [busy, setBusy] = react$1.default.useState(false);
|
|
405726
|
+
const [error, setError] = react$1.default.useState("");
|
|
405727
|
+
react$1.default.useEffect(() => {
|
|
405728
|
+
let alive = true;
|
|
405729
|
+
const cwd = host.store.getContext().cwd;
|
|
405730
|
+
(async () => {
|
|
405731
|
+
const [loadedRules, loadedSkills, loadedCatalog] = await Promise.all([
|
|
405732
|
+
host.rules().catch(() => null),
|
|
405733
|
+
host.skills().catch(() => null),
|
|
405734
|
+
host.skillCatalog(cwd).catch(() => null)
|
|
405735
|
+
]);
|
|
405736
|
+
if (!alive) return;
|
|
405737
|
+
setSnapshot(loadedRules);
|
|
405738
|
+
setSkillNames(loadedSkills);
|
|
405739
|
+
setCatalog(loadedCatalog);
|
|
405740
|
+
})();
|
|
405741
|
+
return () => {
|
|
405742
|
+
alive = false;
|
|
405743
|
+
};
|
|
405744
|
+
}, [host]);
|
|
405745
|
+
const before = host.store.getContext();
|
|
405746
|
+
const ctx = {
|
|
405747
|
+
cwd: before.cwd,
|
|
405748
|
+
rules: snapshot ?? void 0,
|
|
405749
|
+
skillNames
|
|
405750
|
+
};
|
|
405751
|
+
const draftId = editing?.id ?? "";
|
|
405752
|
+
const status = validateItem(buildDraft(draftId === "" ? "draft" : draftId, {
|
|
405753
|
+
type,
|
|
405754
|
+
name,
|
|
405755
|
+
order,
|
|
405756
|
+
parentId: parent,
|
|
405757
|
+
workspaces,
|
|
405758
|
+
prompt,
|
|
405759
|
+
skills,
|
|
405760
|
+
rules,
|
|
405761
|
+
ruleMode,
|
|
405762
|
+
clickAction
|
|
405763
|
+
}), doc);
|
|
405764
|
+
const choices = ruleChoices(snapshot, before.cwd);
|
|
405765
|
+
const parentItem = parent === null ? null : doc.items.find((entry) => entry.id === parent);
|
|
405766
|
+
const inherited = parentItem ? inheritWorkspaces(doc, parentItem) : null;
|
|
405767
|
+
const workspaceList = host.workspaces();
|
|
405768
|
+
const skillPick = skillGroups(catalog, skillNames, skills, ctx);
|
|
405769
|
+
const rulePick = ruleGroups(choices, rules, ctx);
|
|
405770
|
+
const ruleByKey = new Map(choices.map((choice) => [bindingKey(choice.binding), choice.binding]));
|
|
405771
|
+
/**
|
|
405772
|
+
* 切换技能绑定。
|
|
405773
|
+
* @author ddj 2026年10月30号
|
|
405774
|
+
* @param target 技能名
|
|
405775
|
+
*/
|
|
405776
|
+
function toggleSkill(target) {
|
|
405777
|
+
setSkills((current) => current.includes(target) ? current.filter((entry) => entry !== target) : current.concat(target));
|
|
405778
|
+
}
|
|
405779
|
+
/**
|
|
405780
|
+
* 切换规则绑定(键来自下拉,失效规则也能取消绑定)。
|
|
405781
|
+
* @author ddj 2026年10月30号
|
|
405782
|
+
* @param key bindingKey 结果
|
|
405783
|
+
*/
|
|
405784
|
+
function toggleRuleKey(key) {
|
|
405785
|
+
const binding = ruleByKey.get(key);
|
|
405786
|
+
setRules((current) => {
|
|
405787
|
+
if (current.some((entry) => bindingKey(entry) === key)) return current.filter((entry) => bindingKey(entry) !== key);
|
|
405788
|
+
return binding ? current.concat(binding) : current;
|
|
405789
|
+
});
|
|
405790
|
+
}
|
|
405791
|
+
/**
|
|
405792
|
+
* 保存(新建或更新);冲突/校验失败时留在弹窗内提示。
|
|
405793
|
+
* @author ddj 2026年10月30号
|
|
405794
|
+
*/
|
|
405795
|
+
async function onSave() {
|
|
405796
|
+
if (status.errors.length > 0) return;
|
|
405797
|
+
setBusy(true);
|
|
405798
|
+
const finalItem = buildDraft(editing?.id ?? host.store.nextId(type), {
|
|
405799
|
+
type,
|
|
405800
|
+
name,
|
|
405801
|
+
order,
|
|
405802
|
+
parentId: parent,
|
|
405803
|
+
workspaces,
|
|
405804
|
+
prompt,
|
|
405805
|
+
skills,
|
|
405806
|
+
rules,
|
|
405807
|
+
ruleMode,
|
|
405808
|
+
clickAction
|
|
405809
|
+
});
|
|
405810
|
+
const result = editing ? await host.store.updateItem(finalItem) : await host.store.createItem(finalItem);
|
|
405811
|
+
setBusy(false);
|
|
405812
|
+
if (!result.ok) {
|
|
405813
|
+
setError(result.error ?? "保存失败");
|
|
405814
|
+
return;
|
|
405815
|
+
}
|
|
405816
|
+
props.onClose();
|
|
405817
|
+
}
|
|
405818
|
+
/**
|
|
405819
|
+
* 删除该项及其子项。
|
|
405820
|
+
* @author ddj 2026年10月30号
|
|
405821
|
+
*/
|
|
405822
|
+
async function onDelete() {
|
|
405823
|
+
if (!editing) return;
|
|
405824
|
+
const suffix = editing.type === "group" ? "及其全部子项" : "";
|
|
405825
|
+
if (!window.confirm("确认删除「" + editing.name + "」" + suffix + "?")) return;
|
|
405826
|
+
setBusy(true);
|
|
405827
|
+
const result = await host.store.removeItem(editing.id);
|
|
405828
|
+
setBusy(false);
|
|
405829
|
+
if (!result.ok) {
|
|
405830
|
+
setError(result.error ?? "删除失败");
|
|
405831
|
+
return;
|
|
405832
|
+
}
|
|
405833
|
+
props.onClose();
|
|
405834
|
+
}
|
|
405835
|
+
/**
|
|
405836
|
+
* 类型与名称。
|
|
405837
|
+
* @author ddj 2026年10月30号
|
|
405838
|
+
* @returns 表单行
|
|
405839
|
+
*/
|
|
405840
|
+
function basicRow() {
|
|
405841
|
+
return react$1.default.createElement("div", { className: "edrv-qm-row2" }, react$1.default.createElement("div", { className: "edrv-qm-field edrv-qm-field--narrow" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "类型"), react$1.default.createElement(PickSeg, {
|
|
405842
|
+
options: TYPE_OPTIONS,
|
|
405843
|
+
value: type,
|
|
405844
|
+
label: "菜单项类型",
|
|
405845
|
+
onChange: (next) => setType(next)
|
|
405846
|
+
})), react$1.default.createElement("label", { className: "edrv-qm-field" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "名称"), react$1.default.createElement("input", {
|
|
405847
|
+
className: "edrv-qm-input",
|
|
405848
|
+
value: name,
|
|
405849
|
+
maxLength: 40,
|
|
405850
|
+
placeholder: type === "group" ? "例如:代码审查" : "例如:审查当前改动",
|
|
405851
|
+
onChange: (event) => setName(event.target.value)
|
|
405852
|
+
})));
|
|
405853
|
+
}
|
|
405854
|
+
/**
|
|
405855
|
+
* 归属组与同级优先级。
|
|
405856
|
+
* @author ddj 2026年10月30号
|
|
405857
|
+
* @returns 表单行
|
|
405858
|
+
*/
|
|
405859
|
+
function placeRow() {
|
|
405860
|
+
return react$1.default.createElement("div", { className: "edrv-qm-row2" }, react$1.default.createElement("label", { className: "edrv-qm-field" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "归属组"), react$1.default.createElement("select", {
|
|
405861
|
+
className: "edrv-qm-select",
|
|
405862
|
+
value: parent ?? "",
|
|
405863
|
+
onChange: (event) => setParent(event.target.value === "" ? null : event.target.value)
|
|
405864
|
+
}, groupChoices(doc, draftId).map((choice) => react$1.default.createElement("option", {
|
|
405865
|
+
key: choice.id,
|
|
405866
|
+
value: choice.id
|
|
405867
|
+
}, choice.label)))), react$1.default.createElement("div", { className: "edrv-qm-field" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "同级优先级(越小越靠前)"), react$1.default.createElement("div", { className: "edrv-qm-inline" }, react$1.default.createElement("input", {
|
|
405868
|
+
className: "edrv-qm-input edrv-qm-input--order",
|
|
405869
|
+
type: "number",
|
|
405870
|
+
min: 0,
|
|
405871
|
+
max: 999,
|
|
405872
|
+
value: order,
|
|
405873
|
+
onChange: (event) => setOrder(Number(event.target.value) || 0)
|
|
405874
|
+
}), react$1.default.createElement("button", {
|
|
405875
|
+
type: "button",
|
|
405876
|
+
className: "edrv-qm-btn edrv-qm-btn--sm",
|
|
405877
|
+
onClick: () => setOrder((value) => Math.max(0, value - 10))
|
|
405878
|
+
}, "上移"), react$1.default.createElement("button", {
|
|
405879
|
+
type: "button",
|
|
405880
|
+
className: "edrv-qm-btn edrv-qm-btn--sm",
|
|
405881
|
+
onClick: () => setOrder((value) => Math.min(999, value + 10))
|
|
405882
|
+
}, "下移"))));
|
|
405883
|
+
}
|
|
405884
|
+
/**
|
|
405885
|
+
* 限定工作区(继承上级组时只读)。
|
|
405886
|
+
* @author ddj 2026年10月30号
|
|
405887
|
+
* @returns 表单行
|
|
405888
|
+
*/
|
|
405889
|
+
function workspaceField() {
|
|
405890
|
+
return react$1.default.createElement("div", { className: "edrv-qm-field" }, react$1.default.createElement("div", { className: "edrv-qm-field-head" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "限定工作区"), react$1.default.createElement("span", { className: "edrv-qm-field-hint" }, inherited ? "遵循上级组:" + inherited.join("、") + "(此项不可编辑)" : "默认全部工作区可见,可切到当前工作区或展开更多")), react$1.default.createElement(PickWorkspace, {
|
|
405891
|
+
buttonId: "edrv-qm-pick-workspaces",
|
|
405892
|
+
workspaces: workspaceList,
|
|
405893
|
+
selected: inherited ?? workspaces,
|
|
405894
|
+
cwd: before.cwd,
|
|
405895
|
+
inherited: inherited !== null,
|
|
405896
|
+
inheritedHint: "遵循上级组,此项不可编辑",
|
|
405897
|
+
onChange: (next) => setWorkspaces(next)
|
|
405898
|
+
}));
|
|
405899
|
+
}
|
|
405900
|
+
/**
|
|
405901
|
+
* 提示词输入。
|
|
405902
|
+
* @author ddj 2026年10月30号
|
|
405903
|
+
* @returns 表单行
|
|
405904
|
+
*/
|
|
405905
|
+
function promptField() {
|
|
405906
|
+
return react$1.default.createElement("div", { className: "edrv-qm-field" }, react$1.default.createElement("div", { className: "edrv-qm-field-head" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "预填提示词"), react$1.default.createElement("span", { className: "edrv-qm-field-hint" }, "占位符:{{workspace}} {{selection}} {{date}} {{rules}} {{skills}}")), react$1.default.createElement("textarea", {
|
|
405907
|
+
className: "edrv-qm-textarea",
|
|
405908
|
+
value: prompt,
|
|
405909
|
+
maxLength: QUICK_MENU_PROMPT_CAP,
|
|
405910
|
+
placeholder: "点击后发送给 AI 的内容…",
|
|
405911
|
+
onChange: (event) => setPrompt(event.target.value)
|
|
405912
|
+
}));
|
|
405913
|
+
}
|
|
405914
|
+
/**
|
|
405915
|
+
* 绑定技能(多选下拉,候选来自全局与工作区技能目录)。
|
|
405916
|
+
* @author ddj 2026年10月30号
|
|
405917
|
+
* @returns 表单行
|
|
405918
|
+
*/
|
|
405919
|
+
function skillField() {
|
|
405920
|
+
return react$1.default.createElement("div", { className: "edrv-qm-field" }, react$1.default.createElement("div", { className: "edrv-qm-field-head" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "绑定技能"), react$1.default.createElement("span", { className: "edrv-qm-field-hint" }, "发送时写成 /技能名;黄点=当前会话不可见")), react$1.default.createElement(PickMulti, {
|
|
405921
|
+
buttonId: "edrv-qm-pick-skills",
|
|
405922
|
+
groups: skillPick,
|
|
405923
|
+
selected: skills,
|
|
405924
|
+
unit: "个技能",
|
|
405925
|
+
emptyText: "没有可选技能(全局技能目录与当前工作区 .dsh/skills 均为空)",
|
|
405926
|
+
onToggle: toggleSkill
|
|
405927
|
+
}));
|
|
405928
|
+
}
|
|
405929
|
+
/**
|
|
405930
|
+
* 绑定规则(多选下拉 + 引用/内联切换)。
|
|
405931
|
+
* @author ddj 2026年10月30号
|
|
405932
|
+
* @returns 表单行
|
|
405933
|
+
*/
|
|
405934
|
+
function ruleField() {
|
|
405935
|
+
return react$1.default.createElement("div", { className: "edrv-qm-field" }, react$1.default.createElement("div", { className: "edrv-qm-field-head" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "绑定规则"), react$1.default.createElement(PickSeg, {
|
|
405936
|
+
options: MODE_OPTIONS,
|
|
405937
|
+
value: ruleMode,
|
|
405938
|
+
label: "规则注入方式",
|
|
405939
|
+
onChange: (next) => setRuleMode(next)
|
|
405940
|
+
})), react$1.default.createElement(PickMulti, {
|
|
405941
|
+
buttonId: "edrv-qm-pick-rules",
|
|
405942
|
+
groups: rulePick,
|
|
405943
|
+
selected: rules.map((binding) => bindingKey(binding)),
|
|
405944
|
+
unit: "条规则",
|
|
405945
|
+
emptyText: "没有可选规则(可在规则面板新建 .mdc 规则)",
|
|
405946
|
+
onToggle: toggleRuleKey
|
|
405947
|
+
}));
|
|
405948
|
+
}
|
|
405949
|
+
/**
|
|
405950
|
+
* 点击动作(跟随全局 / 直接发送 / 只填入)。
|
|
405951
|
+
* @author ddj 2026年10月30号
|
|
405952
|
+
* @returns 表单行
|
|
405953
|
+
*/
|
|
405954
|
+
function clickField() {
|
|
405955
|
+
return react$1.default.createElement("label", { className: "edrv-qm-field" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "点击动作"), react$1.default.createElement("select", {
|
|
405956
|
+
className: "edrv-qm-select",
|
|
405957
|
+
value: clickAction,
|
|
405958
|
+
onChange: (event) => setClickAction(event.target.value)
|
|
405959
|
+
}, react$1.default.createElement("option", { value: "inherit" }, "跟随全局"), react$1.default.createElement("option", { value: "send" }, "直接发送"), react$1.default.createElement("option", { value: "fill" }, "只填入输入框")));
|
|
405960
|
+
}
|
|
405961
|
+
const parentName = parent === null ? null : doc.items.find((entry) => entry.id === parent)?.name ?? null;
|
|
405962
|
+
const typeLabel = type === "group" ? "组" : "按钮";
|
|
405963
|
+
const headTitle = editing ? "编辑「" + editing.name + "」" : parent === null ? "新建顶层项" : "新建子项";
|
|
405964
|
+
const headSub = (editing ? editing.type === "group" ? "组" : "按钮" : typeLabel) + (parentName ? " · 位于「" + parentName + "」" : " · 位于顶层");
|
|
405965
|
+
const header = react$1.default.createElement("div", { className: "edrv-qm-head" }, react$1.default.createElement("div", { className: "edrv-qm-head-title" }, headTitle), react$1.default.createElement("div", { className: "edrv-qm-head-sub" }, headSub));
|
|
405966
|
+
const body = react$1.default.createElement("div", { className: "edrv-qm-dialog-body" }, react$1.default.createElement(Section, { title: "基本信息" }, basicRow(), placeRow(), workspaceField()), type === "button" ? react$1.default.createElement(Section, {
|
|
405967
|
+
title: "提示词",
|
|
405968
|
+
hint: "点击后发给 AI 的内容"
|
|
405969
|
+
}, promptField()) : null, type === "button" ? react$1.default.createElement(Section, { title: "绑定" }, skillField(), ruleField()) : null, type === "button" ? react$1.default.createElement(Section, { title: "行为" }, clickField()) : null, error !== "" ? react$1.default.createElement("div", { className: "edrv-qm-error-list" }, error) : null, status.errors.length > 0 ? react$1.default.createElement("ul", { className: "edrv-qm-error-list" }, status.errors.map((text) => react$1.default.createElement("li", { key: text }, text))) : null, status.warnings.length > 0 ? react$1.default.createElement("ul", { className: "edrv-qm-warn-list" }, status.warnings.map((text) => react$1.default.createElement("li", { key: text }, text))) : null);
|
|
405970
|
+
const actions = react$1.default.createElement("div", { className: "edrv-qm-actions" }, editing ? react$1.default.createElement("button", {
|
|
405971
|
+
type: "button",
|
|
405972
|
+
className: "edrv-qm-btn edrv-qm-btn--danger",
|
|
405973
|
+
disabled: busy,
|
|
405974
|
+
onClick: () => {
|
|
405975
|
+
onDelete();
|
|
405976
|
+
}
|
|
405977
|
+
}, "删除") : null, react$1.default.createElement("span", { className: "edrv-qm-actions-grow" }), react$1.default.createElement("button", {
|
|
405978
|
+
type: "button",
|
|
405979
|
+
className: "edrv-qm-btn",
|
|
405980
|
+
onClick: props.onClose
|
|
405981
|
+
}, "取消"), react$1.default.createElement("button", {
|
|
405982
|
+
type: "button",
|
|
405983
|
+
className: "edrv-qm-btn edrv-qm-btn--primary",
|
|
405984
|
+
disabled: busy || status.errors.length > 0,
|
|
405985
|
+
onClick: () => {
|
|
405986
|
+
onSave();
|
|
405987
|
+
}
|
|
405988
|
+
}, busy ? "保存中…" : editing ? "保存" : "创建"));
|
|
405989
|
+
return react$1.default.createElement(ModalShell, {
|
|
405990
|
+
dialogClass: "edrv-qm-dialog",
|
|
405991
|
+
zIndex: 220,
|
|
405992
|
+
onClose: props.onClose,
|
|
405993
|
+
closeOnMask: false
|
|
405994
|
+
}, header, body, actions);
|
|
405995
|
+
}
|
|
405996
|
+
/**
|
|
405997
|
+
* 菜单全局设置(显隐、点击动作、最大嵌套层数、位置重置、导入导出、失效绑定清理)。
|
|
405998
|
+
* @author ddj 2026年10月30号
|
|
405999
|
+
* @param props host 与关闭回调
|
|
406000
|
+
* @returns 弹窗元素
|
|
406001
|
+
*/
|
|
406002
|
+
function MenuGlobalDialog(props) {
|
|
406003
|
+
const { host } = props;
|
|
406004
|
+
const doc = host.store.getState().doc;
|
|
406005
|
+
const [enabled, setEnabled] = react$1.default.useState(doc.enabled);
|
|
406006
|
+
const [clickAction, setClickAction] = react$1.default.useState(doc.clickAction);
|
|
406007
|
+
const [maxDepth, setMaxDepth] = react$1.default.useState(doc.maxDepth || 3);
|
|
406008
|
+
const [busy, setBusy] = react$1.default.useState(false);
|
|
406009
|
+
const [error, setError] = react$1.default.useState("");
|
|
406010
|
+
const ctx = host.store.getContext();
|
|
406011
|
+
const broken = collectBroken(doc, ctx);
|
|
406012
|
+
const fileRef = react$1.default.useRef(null);
|
|
406013
|
+
/**
|
|
406014
|
+
* 保存全局设置。
|
|
406015
|
+
* @author ddj 2026年10月30号
|
|
406016
|
+
*/
|
|
406017
|
+
async function onSave() {
|
|
406018
|
+
setBusy(true);
|
|
406019
|
+
const result = await host.store.save({
|
|
406020
|
+
...doc,
|
|
406021
|
+
enabled,
|
|
406022
|
+
clickAction,
|
|
406023
|
+
maxDepth
|
|
406024
|
+
});
|
|
406025
|
+
setBusy(false);
|
|
406026
|
+
if (!result.ok) {
|
|
406027
|
+
setError(result.error ?? "保存失败");
|
|
406028
|
+
return;
|
|
406029
|
+
}
|
|
406030
|
+
props.onClose();
|
|
406031
|
+
}
|
|
406032
|
+
/**
|
|
406033
|
+
* 清理全部失效绑定(移除失效规则/技能,保留提示词)。
|
|
406034
|
+
* @author ddj 2026年10月30号
|
|
406035
|
+
*/
|
|
406036
|
+
async function onClean() {
|
|
406037
|
+
setBusy(true);
|
|
406038
|
+
const items = doc.items.map((item) => {
|
|
406039
|
+
if (item.type !== "button") return item;
|
|
406040
|
+
const rules = item.rules.filter((binding) => validateRule(binding, ctx).state === "ok");
|
|
406041
|
+
const skills = item.skills.filter((name) => validateSkill(name, ctx).state === "ok");
|
|
406042
|
+
return {
|
|
406043
|
+
...item,
|
|
406044
|
+
rules,
|
|
406045
|
+
skills
|
|
406046
|
+
};
|
|
406047
|
+
});
|
|
406048
|
+
const result = await host.store.save({
|
|
406049
|
+
...doc,
|
|
406050
|
+
enabled,
|
|
406051
|
+
clickAction,
|
|
406052
|
+
maxDepth,
|
|
406053
|
+
items
|
|
406054
|
+
});
|
|
406055
|
+
setBusy(false);
|
|
406056
|
+
if (!result.ok) setError(result.error ?? "清理失败");
|
|
406057
|
+
else host.notify("已清理失效绑定");
|
|
406058
|
+
}
|
|
406059
|
+
/**
|
|
406060
|
+
* 配置备份与迁移(导出 / 导入 / 重置按钮位置)。
|
|
406061
|
+
* @author ddj 2026年10月30号
|
|
406062
|
+
* @returns 按钮行
|
|
406063
|
+
*/
|
|
406064
|
+
function backupRow() {
|
|
406065
|
+
return react$1.default.createElement("div", { className: "edrv-qm-btn-row" }, react$1.default.createElement("input", {
|
|
406066
|
+
ref: fileRef,
|
|
406067
|
+
type: "file",
|
|
406068
|
+
accept: "application/json",
|
|
406069
|
+
style: { display: "none" },
|
|
406070
|
+
onChange: (event) => {
|
|
406071
|
+
onImport(event, host, setError);
|
|
406072
|
+
}
|
|
406073
|
+
}), react$1.default.createElement("button", {
|
|
406074
|
+
type: "button",
|
|
406075
|
+
className: "edrv-qm-btn",
|
|
406076
|
+
onClick: () => downloadDoc(doc)
|
|
406077
|
+
}, "导出 JSON"), react$1.default.createElement("button", {
|
|
406078
|
+
type: "button",
|
|
406079
|
+
className: "edrv-qm-btn",
|
|
406080
|
+
onClick: () => fileRef.current?.click()
|
|
406081
|
+
}, "导入 JSON"), react$1.default.createElement("button", {
|
|
406082
|
+
type: "button",
|
|
406083
|
+
className: "edrv-qm-btn",
|
|
406084
|
+
onClick: () => {
|
|
406085
|
+
host.store.patchLauncher(null, null);
|
|
406086
|
+
host.store.flushLauncher();
|
|
406087
|
+
host.notify("已重置悬浮按钮位置");
|
|
406088
|
+
}
|
|
406089
|
+
}, "重置按钮位置"));
|
|
406090
|
+
}
|
|
406091
|
+
const header = react$1.default.createElement("div", { className: "edrv-qm-head" }, react$1.default.createElement("div", { className: "edrv-qm-head-title" }, "菜单设置"), react$1.default.createElement("div", { className: "edrv-qm-head-sub" }, "悬浮按钮、交互行为与配置迁移"));
|
|
406092
|
+
const body = react$1.default.createElement("div", { className: "edrv-qm-dialog-body" }, react$1.default.createElement(Section, { title: "外观" }, react$1.default.createElement(PickSwitch, {
|
|
406093
|
+
buttonId: "edrv-qm-switch-enabled",
|
|
406094
|
+
label: "显示悬浮按钮",
|
|
406095
|
+
hint: "关闭后左侧不再显示快捷菜单按钮,配置会保留",
|
|
406096
|
+
checked: enabled,
|
|
406097
|
+
onChange: setEnabled
|
|
406098
|
+
})), react$1.default.createElement(Section, { title: "交互" }, react$1.default.createElement("label", { className: "edrv-qm-field" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "点击默认动作"), react$1.default.createElement("select", {
|
|
406099
|
+
className: "edrv-qm-select",
|
|
406100
|
+
value: clickAction,
|
|
406101
|
+
onChange: (event) => setClickAction(event.target.value)
|
|
406102
|
+
}, react$1.default.createElement("option", { value: "send" }, "直接发送给 AI"), react$1.default.createElement("option", { value: "fill" }, "只填入输入框"))), react$1.default.createElement("div", { className: "edrv-qm-field" }, react$1.default.createElement("div", { className: "edrv-qm-field-head" }, react$1.default.createElement("span", { className: "edrv-qm-field-label" }, "最大嵌套层数"), react$1.default.createElement("span", { className: "edrv-qm-field-hint" }, "root 列里的项是第 1 层;调小不删除已有深层项,只禁止继续新增")), react$1.default.createElement(PickSeg, {
|
|
406103
|
+
options: DEPTH_OPTIONS,
|
|
406104
|
+
value: String(maxDepth),
|
|
406105
|
+
label: "最大嵌套层数",
|
|
406106
|
+
onChange: (next) => setMaxDepth(Number(next) || 3)
|
|
406107
|
+
}))), react$1.default.createElement(Section, { title: "配置备份与迁移" }, backupRow()), react$1.default.createElement(Section, {
|
|
406108
|
+
title: "绑定状态",
|
|
406109
|
+
hint: broken.length === 0 ? "全部可用" : String(broken.length) + " 项失效"
|
|
406110
|
+
}, broken.length > 0 ? react$1.default.createElement("ul", { className: "edrv-qm-error-list" }, broken.map((entry) => react$1.default.createElement("li", { key: entry.key }, entry.label))) : null, broken.length > 0 ? react$1.default.createElement("div", { className: "edrv-qm-btn-row" }, react$1.default.createElement("button", {
|
|
406111
|
+
type: "button",
|
|
406112
|
+
className: "edrv-qm-btn",
|
|
406113
|
+
disabled: busy,
|
|
406114
|
+
onClick: () => {
|
|
406115
|
+
onClean();
|
|
406116
|
+
}
|
|
406117
|
+
}, "清理失效绑定")) : null), error !== "" ? react$1.default.createElement("div", { className: "edrv-qm-error-list" }, error) : null, react$1.default.createElement("div", { className: "edrv-qm-field-hint" }, "左侧按钮 / 菜单项" + (host.store.isMounted() ? "(当前界面已挂载)" : "(当前 DSH 未提供浮层槽,按钮不显示)")));
|
|
406118
|
+
return react$1.default.createElement(ModalShell, {
|
|
406119
|
+
dialogClass: "edrv-qm-dialog",
|
|
406120
|
+
zIndex: 220,
|
|
406121
|
+
onClose: props.onClose,
|
|
406122
|
+
closeOnMask: false
|
|
406123
|
+
}, header, body, react$1.default.createElement("div", { className: "edrv-qm-actions" }, react$1.default.createElement("span", { className: "edrv-qm-actions-grow" }), react$1.default.createElement("button", {
|
|
406124
|
+
type: "button",
|
|
406125
|
+
className: "edrv-qm-btn",
|
|
406126
|
+
onClick: props.onClose
|
|
406127
|
+
}, "取消"), react$1.default.createElement("button", {
|
|
406128
|
+
type: "button",
|
|
406129
|
+
className: "edrv-qm-btn edrv-qm-btn--primary",
|
|
406130
|
+
disabled: busy,
|
|
406131
|
+
onClick: () => {
|
|
406132
|
+
onSave();
|
|
406133
|
+
}
|
|
406134
|
+
}, busy ? "保存中…" : "保存")));
|
|
406135
|
+
}
|
|
406136
|
+
/**
|
|
406137
|
+
* 收集全菜单的失效绑定(设置弹窗顶部汇总)。
|
|
406138
|
+
* @author ddj 2026年10月30号
|
|
406139
|
+
* @param doc 文档
|
|
406140
|
+
* @param ctx 校验上下文
|
|
406141
|
+
* @returns 失效条目文案
|
|
406142
|
+
*/
|
|
406143
|
+
function collectBroken(doc, ctx) {
|
|
406144
|
+
const out = [];
|
|
406145
|
+
for (const item of doc.items) {
|
|
406146
|
+
if (item.type !== "button") continue;
|
|
406147
|
+
const status = itemStatus(item, ctx);
|
|
406148
|
+
status.errors.concat(status.warnings).forEach((reason, index) => {
|
|
406149
|
+
out.push({
|
|
406150
|
+
key: item.id + ":" + index,
|
|
406151
|
+
label: item.name + " → " + reason
|
|
406152
|
+
});
|
|
406153
|
+
});
|
|
406154
|
+
}
|
|
406155
|
+
return out;
|
|
406156
|
+
}
|
|
406157
|
+
/**
|
|
406158
|
+
* 下载当前文档为 JSON(浏览器侧 Blob,不经 host)。
|
|
406159
|
+
* @author ddj 2026年10月30号
|
|
406160
|
+
* @param doc 文档
|
|
406161
|
+
*/
|
|
406162
|
+
function downloadDoc(doc) {
|
|
406163
|
+
const blob = new Blob([serializeDoc(doc)], { type: "application/json" });
|
|
406164
|
+
const url = URL.createObjectURL(blob);
|
|
406165
|
+
const anchor = document.createElement("a");
|
|
406166
|
+
anchor.href = url;
|
|
406167
|
+
anchor.download = "quick-menu.json";
|
|
406168
|
+
anchor.click();
|
|
406169
|
+
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
406170
|
+
}
|
|
406171
|
+
/**
|
|
406172
|
+
* 导入 JSON(解析失败/校验不通过时给错误文案)。
|
|
406173
|
+
* @author ddj 2026年10月30号
|
|
406174
|
+
* @param event 文件输入事件
|
|
406175
|
+
* @param host 宿主
|
|
406176
|
+
* @param setError 错误回写
|
|
406177
|
+
*/
|
|
406178
|
+
async function onImport(event, host, setError) {
|
|
406179
|
+
const file = event.target.files?.[0];
|
|
406180
|
+
event.target.value = "";
|
|
406181
|
+
if (!file) return;
|
|
406182
|
+
let text = "";
|
|
406183
|
+
try {
|
|
406184
|
+
text = await file.text();
|
|
406185
|
+
} catch (error) {
|
|
406186
|
+
setError("读取文件失败:" + String(error));
|
|
406187
|
+
return;
|
|
406188
|
+
}
|
|
406189
|
+
const parsed = parseDoc(text);
|
|
406190
|
+
if (parsed.warning) {
|
|
406191
|
+
setError(parsed.warning);
|
|
406192
|
+
return;
|
|
406193
|
+
}
|
|
406194
|
+
const result = await host.store.save(parsed.doc);
|
|
406195
|
+
if (!result.ok) setError(result.error ?? "导入失败");
|
|
406196
|
+
else host.notify("已导入快捷菜单配置");
|
|
406197
|
+
}
|
|
406198
|
+
//#endregion
|
|
406199
|
+
//#region src/client/ui/QuickMenuLauncher.tsx
|
|
406200
|
+
/**
|
|
406201
|
+
* dsh-vscode-mode client — 快捷菜单悬浮按钮与多列级联菜单(SAO Utils 2 风格)。
|
|
406202
|
+
*
|
|
406203
|
+
* 交互约定:
|
|
406204
|
+
* - 按钮可拖动(3px 阈值区分点击),松手落盘坐标;无保存坐标时锚定输入框左上角。
|
|
406205
|
+
* - 点开是纵向滑动列表:行靠近列中心放大、向两端递减透明度,上下各 48px 渐隐(CSS mask)。
|
|
406206
|
+
* - 每组列最后一行是「+ 新建」;悬停组 120ms 后在右侧展开它的一列(放不下自动翻左侧)。
|
|
406207
|
+
* - 行悬停浮出「⋯」:编辑(弹窗内可删)/ 复制 / 删除;root 列底部固定「菜单设置」。
|
|
406208
|
+
* 作者 ddj 2026年10月30号
|
|
406209
|
+
*/
|
|
406210
|
+
/** 悬浮按钮尺寸。 */
|
|
406211
|
+
const LAUNCHER_SIZE = {
|
|
406212
|
+
width: 32,
|
|
406213
|
+
height: 32
|
|
406214
|
+
};
|
|
406215
|
+
/** 列宽。 */
|
|
406216
|
+
const COL_WIDTH = 220;
|
|
406217
|
+
/** 行高(与 CSS 的 --qm-row 一致,用于计算子列对齐位置)。 */
|
|
406218
|
+
const ROW_HEIGHT = 34;
|
|
406219
|
+
/** 列内上下内边距(与 CSS 一致)。 */
|
|
406220
|
+
const COL_PAD = 8;
|
|
406221
|
+
/** 列间距。 */
|
|
406222
|
+
const COL_GAP = 6;
|
|
406223
|
+
/** 列最大高度。 */
|
|
406224
|
+
const COL_MAX_HEIGHT = 520;
|
|
406225
|
+
/** root 列底部「⚙ 菜单设置」底栏高度(与 CSS 的 flex-basis 一致),会占掉滚动区高度。 */
|
|
406226
|
+
const FOOT_HEIGHT = 32;
|
|
406227
|
+
/** 行与行之间的间隔(与 CSS 的 .edrv-qm-list gap 一致)。 */
|
|
406228
|
+
const ROW_GAP = 1;
|
|
406229
|
+
/** 空态卡片的内容高度:标题 + 说明 + 主按钮,短于此会被裁掉说明文字。 */
|
|
406230
|
+
const EMPTY_HEIGHT = 132;
|
|
406231
|
+
/** 列的上下边框(border-box,需从内容高度里让出)。 */
|
|
406232
|
+
const COL_BORDER = 2;
|
|
406233
|
+
/** 拖动判定阈值(像素)。 */
|
|
406234
|
+
const DRAG_SLOP = 3;
|
|
406235
|
+
/** 悬停展开意图延迟。 */
|
|
406236
|
+
const HOVER_DELAY = 120;
|
|
406237
|
+
/**
|
|
406238
|
+
* 悬浮按钮的鲸鱼标记:取自 DSH 顶栏品牌鲸(同一条可视化轮廓),
|
|
406239
|
+
* 实心填充在 19px 下比描边更认得出是鲸鱼而不是一个气泡。
|
|
406240
|
+
*/
|
|
406241
|
+
const WHALE_PATH = "M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z";
|
|
406242
|
+
/**
|
|
406243
|
+
* 数值夹取。
|
|
406244
|
+
* @author ddj 2026年10月30号
|
|
406245
|
+
* @param value 原始值
|
|
406246
|
+
* @param min 下限
|
|
406247
|
+
* @param max 上限
|
|
406248
|
+
* @returns 夹取结果
|
|
406249
|
+
*/
|
|
406250
|
+
function clamp(value, min, max) {
|
|
406251
|
+
if (!Number.isFinite(value)) return min;
|
|
406252
|
+
return Math.min(max, Math.max(min, value));
|
|
406253
|
+
}
|
|
406254
|
+
/**
|
|
406255
|
+
* 两个矩形是否等价(测量轮询用,避免无意义重渲染)。
|
|
406256
|
+
* @author ddj 2026年10月30号
|
|
406257
|
+
* @param left 矩形 A
|
|
406258
|
+
* @param right 矩形 B
|
|
406259
|
+
* @returns 是否等价
|
|
406260
|
+
*/
|
|
406261
|
+
function sameRect(left, right) {
|
|
406262
|
+
return left.left === right.left && left.top === right.top && left.width === right.width && left.height === right.height;
|
|
406263
|
+
}
|
|
406264
|
+
/**
|
|
406265
|
+
* 取当前可见的输入框锚点。
|
|
406266
|
+
* 页面里可能同时存在多个(隐藏会话的 seat 位于视口外,量到的 top 会超过浮层高度,
|
|
406267
|
+
* 把按钮夹到屏幕底边压住输入框),因此优先选真正落在视口内、面积最大的那个。
|
|
406268
|
+
* @author ddj 2026年10月30号
|
|
406269
|
+
* @param layerRect 浮层矩形(按钮坐标以它为基准)
|
|
406270
|
+
* @returns 相对浮层的输入框矩形;没有可用锚点时返回 null
|
|
406271
|
+
*/
|
|
406272
|
+
function pickSeatRect(layerRect) {
|
|
406273
|
+
const seats = Array.from(document.querySelectorAll("[data-composer-seat]"));
|
|
406274
|
+
let best = null;
|
|
406275
|
+
for (const seat of seats) {
|
|
406276
|
+
const rect = seat.getBoundingClientRect();
|
|
406277
|
+
if (rect.width < 40 || rect.height < 20) continue;
|
|
406278
|
+
const visibleTop = Math.max(rect.top, 0);
|
|
406279
|
+
const visibleBottom = Math.min(rect.bottom, window.innerHeight);
|
|
406280
|
+
if (visibleBottom <= visibleTop) continue;
|
|
406281
|
+
const visibleArea = (visibleBottom - visibleTop) * rect.width;
|
|
406282
|
+
if (best && best.visibleArea >= visibleArea) continue;
|
|
406283
|
+
best = {
|
|
406284
|
+
visibleArea,
|
|
406285
|
+
rect: {
|
|
406286
|
+
left: rect.left - layerRect.left,
|
|
406287
|
+
top: rect.top - layerRect.top,
|
|
406288
|
+
width: rect.width,
|
|
406289
|
+
height: rect.height
|
|
406290
|
+
}
|
|
406291
|
+
};
|
|
406292
|
+
}
|
|
406293
|
+
return best ? best.rect : null;
|
|
406294
|
+
}
|
|
406295
|
+
/**
|
|
406296
|
+
* 今日日期文本({{date}} 占位符)。
|
|
406297
|
+
* @author ddj 2026年10月30号
|
|
406298
|
+
* @returns YYYY-MM-DD
|
|
406299
|
+
*/
|
|
406300
|
+
function todayText() {
|
|
406301
|
+
const now = /* @__PURE__ */ new Date();
|
|
406302
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
406303
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
406304
|
+
return now.getFullYear() + "-" + month + "-" + day;
|
|
406305
|
+
}
|
|
406306
|
+
/**
|
|
406307
|
+
* 当前文档选择文本({{selection}} 占位符;拿不到就是空串)。
|
|
406308
|
+
* @author ddj 2026年10月30号
|
|
406309
|
+
* @returns 选中文本
|
|
406310
|
+
*/
|
|
406311
|
+
function selectionText() {
|
|
406312
|
+
try {
|
|
406313
|
+
return window.getSelection()?.toString() ?? "";
|
|
406314
|
+
} catch {
|
|
406315
|
+
return "";
|
|
406316
|
+
}
|
|
406317
|
+
}
|
|
406318
|
+
/**
|
|
406319
|
+
* 悬浮按钮 + 多列级联菜单。
|
|
406320
|
+
* @author ddj 2026年10月30号
|
|
406321
|
+
* @param props host(store + 发送/规则/技能桥接)
|
|
406322
|
+
* @returns 菜单元素(不显示时为 null)
|
|
406323
|
+
*/
|
|
406324
|
+
function QuickMenuLauncher(props) {
|
|
406325
|
+
const { host } = props;
|
|
406326
|
+
const store = host.store;
|
|
406327
|
+
react$1.default.useSyncExternalStore(store.subscribe, store.getVersion, store.getVersion);
|
|
406328
|
+
const state = store.getState();
|
|
406329
|
+
const doc = state.doc;
|
|
406330
|
+
const [layerSize, setLayerSize] = react$1.default.useState({
|
|
406331
|
+
width: 1280,
|
|
406332
|
+
height: 800
|
|
406333
|
+
});
|
|
406334
|
+
const [measured, setMeasured] = react$1.default.useState(false);
|
|
406335
|
+
const [seat, setSeat] = react$1.default.useState(null);
|
|
406336
|
+
const [open, setOpen] = react$1.default.useState(false);
|
|
406337
|
+
const [dragging, setDragging] = react$1.default.useState(false);
|
|
406338
|
+
const [dragPos, setDragPos] = react$1.default.useState(null);
|
|
406339
|
+
const [openPath, setOpenPath] = react$1.default.useState([]);
|
|
406340
|
+
const [hoverRow, setHoverRow] = react$1.default.useState({});
|
|
406341
|
+
const [scrolls, setScrolls] = react$1.default.useState({});
|
|
406342
|
+
const [rowMenu, setRowMenu] = react$1.default.useState(null);
|
|
406343
|
+
const [dialog, setDialog] = react$1.default.useState({ kind: "none" });
|
|
406344
|
+
const [active, setActive] = react$1.default.useState({
|
|
406345
|
+
depth: 0,
|
|
406346
|
+
index: 0
|
|
406347
|
+
});
|
|
406348
|
+
const hoverTimer = react$1.default.useRef(null);
|
|
406349
|
+
/**
|
|
406350
|
+
* 测量浮层与输入框锚点(浮层坐标系)。
|
|
406351
|
+
* 值不变时返回原状态,避免轮询测量引起无意义重渲染。
|
|
406352
|
+
* @author ddj 2026年10月30号
|
|
406353
|
+
*/
|
|
406354
|
+
const measure = react$1.default.useCallback(() => {
|
|
406355
|
+
const layerEl = document.querySelector("[data-shell-overlay]");
|
|
406356
|
+
if (!layerEl) {
|
|
406357
|
+
setLayerSize((prev) => prev.width === window.innerWidth && prev.height === window.innerHeight ? prev : {
|
|
406358
|
+
width: window.innerWidth,
|
|
406359
|
+
height: window.innerHeight
|
|
406360
|
+
});
|
|
406361
|
+
setSeat(null);
|
|
406362
|
+
return;
|
|
406363
|
+
}
|
|
406364
|
+
const layerRect = layerEl.getBoundingClientRect();
|
|
406365
|
+
setLayerSize((prev) => prev.width === layerRect.width && prev.height === layerRect.height ? prev : {
|
|
406366
|
+
width: layerRect.width,
|
|
406367
|
+
height: layerRect.height
|
|
406368
|
+
});
|
|
406369
|
+
setMeasured((prev) => prev ? prev : true);
|
|
406370
|
+
const next = pickSeatRect(layerRect);
|
|
406371
|
+
setSeat((prev) => prev && next && sameRect(prev, next) ? prev : next);
|
|
406372
|
+
}, []);
|
|
406373
|
+
react$1.default.useEffect(() => {
|
|
406374
|
+
store.markMounted(true);
|
|
406375
|
+
measure();
|
|
406376
|
+
store.load();
|
|
406377
|
+
const onResize = () => measure();
|
|
406378
|
+
window.addEventListener("resize", onResize);
|
|
406379
|
+
const timers = [
|
|
406380
|
+
80,
|
|
406381
|
+
320,
|
|
406382
|
+
900,
|
|
406383
|
+
2e3
|
|
406384
|
+
].map((delay) => setTimeout(measure, delay));
|
|
406385
|
+
const layerEl = document.querySelector("[data-shell-overlay]");
|
|
406386
|
+
const seatEl = document.querySelector("[data-composer-seat]");
|
|
406387
|
+
let observer = null;
|
|
406388
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
406389
|
+
observer = new ResizeObserver(() => measure());
|
|
406390
|
+
if (layerEl) observer.observe(layerEl);
|
|
406391
|
+
if (seatEl) observer.observe(seatEl);
|
|
406392
|
+
}
|
|
406393
|
+
const poll = setInterval(measure, 1200);
|
|
406394
|
+
return () => {
|
|
406395
|
+
window.removeEventListener("resize", onResize);
|
|
406396
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
406397
|
+
clearInterval(poll);
|
|
406398
|
+
observer?.disconnect();
|
|
406399
|
+
store.markMounted(false);
|
|
406400
|
+
};
|
|
406401
|
+
}, [measure, store]);
|
|
406402
|
+
react$1.default.useEffect(() => {
|
|
406403
|
+
if (!open) return;
|
|
406404
|
+
measure();
|
|
406405
|
+
store.refreshBindings();
|
|
406406
|
+
}, [
|
|
406407
|
+
open,
|
|
406408
|
+
measure,
|
|
406409
|
+
store
|
|
406410
|
+
]);
|
|
406411
|
+
const ctx = store.getContext();
|
|
406412
|
+
const basePos = react$1.default.useMemo(() => {
|
|
406413
|
+
const maxLeft = Math.max(8, layerSize.width - LAUNCHER_SIZE.width - 8);
|
|
406414
|
+
const maxTop = Math.max(8, layerSize.height - LAUNCHER_SIZE.height - 8);
|
|
406415
|
+
const savedX = doc.launcher.x;
|
|
406416
|
+
const savedY = doc.launcher.y;
|
|
406417
|
+
if (savedX !== null && savedY !== null) {
|
|
406418
|
+
if (savedX >= 8 && savedY >= 8 && savedX <= maxLeft && savedY <= maxTop || !measured) return {
|
|
406419
|
+
left: clamp(savedX, 8, maxLeft),
|
|
406420
|
+
top: clamp(savedY, 8, maxTop)
|
|
406421
|
+
};
|
|
406422
|
+
}
|
|
406423
|
+
const layer = {
|
|
406424
|
+
left: 0,
|
|
406425
|
+
top: 0,
|
|
406426
|
+
width: layerSize.width,
|
|
406427
|
+
height: layerSize.height
|
|
406428
|
+
};
|
|
406429
|
+
return anchorLauncher(seat, layer, LAUNCHER_SIZE, 8);
|
|
406430
|
+
}, [
|
|
406431
|
+
doc.launcher.x,
|
|
406432
|
+
doc.launcher.y,
|
|
406433
|
+
seat,
|
|
406434
|
+
layerSize,
|
|
406435
|
+
measured
|
|
406436
|
+
]);
|
|
406437
|
+
const launcherPos = dragPos ?? basePos;
|
|
406438
|
+
/**
|
|
406439
|
+
* 计算每一列的矩形(独立定位,子列右展放不下则自动翻到左侧)。
|
|
406440
|
+
* @author ddj 2026年10月30号
|
|
406441
|
+
*/
|
|
406442
|
+
const columnRects = react$1.default.useMemo(() => {
|
|
406443
|
+
const rects = [];
|
|
406444
|
+
const layerRect = {
|
|
406445
|
+
left: 0,
|
|
406446
|
+
top: 0,
|
|
406447
|
+
width: layerSize.width,
|
|
406448
|
+
height: layerSize.height
|
|
406449
|
+
};
|
|
406450
|
+
let anchor = {
|
|
406451
|
+
left: launcherPos.left,
|
|
406452
|
+
top: launcherPos.top,
|
|
406453
|
+
width: LAUNCHER_SIZE.width,
|
|
406454
|
+
height: LAUNCHER_SIZE.height
|
|
406455
|
+
};
|
|
406456
|
+
const depthCount = openPath.length + 1;
|
|
406457
|
+
for (let depth = 0; depth < depthCount; depth += 1) {
|
|
406458
|
+
const parentId = depth === 0 ? null : openPath[depth - 1];
|
|
406459
|
+
const items = childrenOf(doc, parentId, ctx.cwd);
|
|
406460
|
+
const extra = canCreateAt(doc, parentId) ? 1 : 0;
|
|
406461
|
+
const foot = depth === 0 ? FOOT_HEIGHT : 0;
|
|
406462
|
+
const rowCount = items.length + extra;
|
|
406463
|
+
const rowHeight = items.length === 0 ? EMPTY_HEIGHT : items.length * ROW_HEIGHT + (extra > 0 ? 39 : 0) + Math.max(0, rowCount - 1) * ROW_GAP + 16;
|
|
406464
|
+
const height = Math.min(COL_MAX_HEIGHT, Math.max(68 + foot, rowHeight + foot + COL_BORDER));
|
|
406465
|
+
let rect = { ...placeColumn(anchor, {
|
|
406466
|
+
width: COL_WIDTH,
|
|
406467
|
+
height
|
|
406468
|
+
}, layerSize, COL_GAP) };
|
|
406469
|
+
if (depth > 0) {
|
|
406470
|
+
const rowIndex = hoverRow[depth - 1] ?? 0;
|
|
406471
|
+
const wantedTop = anchor.top + COL_PAD + rowIndex * ROW_HEIGHT - (scrolls[depth - 1] ?? 0);
|
|
406472
|
+
rect = {
|
|
406473
|
+
...rect,
|
|
406474
|
+
top: clamp(wantedTop, 8, Math.max(8, layerRect.height - height - 8))
|
|
406475
|
+
};
|
|
406476
|
+
}
|
|
406477
|
+
rects.push(rect);
|
|
406478
|
+
anchor = rect;
|
|
406479
|
+
}
|
|
406480
|
+
return rects;
|
|
406481
|
+
}, [
|
|
406482
|
+
doc,
|
|
406483
|
+
ctx.cwd,
|
|
406484
|
+
launcherPos.left,
|
|
406485
|
+
launcherPos.top,
|
|
406486
|
+
layerSize,
|
|
406487
|
+
openPath,
|
|
406488
|
+
hoverRow,
|
|
406489
|
+
scrolls
|
|
406490
|
+
]);
|
|
406491
|
+
/**
|
|
406492
|
+
* 关闭菜单并清空级联状态。
|
|
406493
|
+
* @author ddj 2026年10月30号
|
|
406494
|
+
*/
|
|
406495
|
+
const closeMenu = react$1.default.useCallback(() => {
|
|
406496
|
+
setOpen(false);
|
|
406497
|
+
setRowMenu(null);
|
|
406498
|
+
setOpenPath([]);
|
|
406499
|
+
setHoverRow({});
|
|
406500
|
+
setScrolls({});
|
|
406501
|
+
setActive({
|
|
406502
|
+
depth: 0,
|
|
406503
|
+
index: 0
|
|
406504
|
+
});
|
|
406505
|
+
}, []);
|
|
406506
|
+
react$1.default.useEffect(() => {
|
|
406507
|
+
if (!open) return;
|
|
406508
|
+
const onPointerDown = (event) => {
|
|
406509
|
+
const target = event.target;
|
|
406510
|
+
if (target && target.closest("[data-qm-root=\"1\"]")) return;
|
|
406511
|
+
closeMenu();
|
|
406512
|
+
};
|
|
406513
|
+
const onKeyDown = (event) => {
|
|
406514
|
+
if (event.key === "Escape") {
|
|
406515
|
+
event.stopPropagation();
|
|
406516
|
+
closeMenu();
|
|
406517
|
+
return;
|
|
406518
|
+
}
|
|
406519
|
+
const parentId = active.depth === 0 ? null : openPath[active.depth - 1];
|
|
406520
|
+
const items = childrenOf(doc, parentId, ctx.cwd);
|
|
406521
|
+
if (items.length === 0) return;
|
|
406522
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
406523
|
+
event.preventDefault();
|
|
406524
|
+
const delta = event.key === "ArrowDown" ? 1 : -1;
|
|
406525
|
+
const index = (active.index + delta + items.length) % items.length;
|
|
406526
|
+
setActive({
|
|
406527
|
+
depth: active.depth,
|
|
406528
|
+
index
|
|
406529
|
+
});
|
|
406530
|
+
setHoverRow((prev) => ({
|
|
406531
|
+
...prev,
|
|
406532
|
+
[active.depth]: index
|
|
406533
|
+
}));
|
|
406534
|
+
return;
|
|
406535
|
+
}
|
|
406536
|
+
if (event.key === "ArrowRight") {
|
|
406537
|
+
const item = items[Math.min(active.index, items.length - 1)];
|
|
406538
|
+
if (item && item.type === "group") {
|
|
406539
|
+
event.preventDefault();
|
|
406540
|
+
setOpenPath((prev) => prev.slice(0, active.depth).concat(item.id));
|
|
406541
|
+
setActive({
|
|
406542
|
+
depth: active.depth + 1,
|
|
406543
|
+
index: 0
|
|
406544
|
+
});
|
|
406545
|
+
}
|
|
406546
|
+
return;
|
|
406547
|
+
}
|
|
406548
|
+
if (event.key === "ArrowLeft" && active.depth > 0) {
|
|
406549
|
+
event.preventDefault();
|
|
406550
|
+
setOpenPath((prev) => prev.slice(0, active.depth - 1));
|
|
406551
|
+
setActive({
|
|
406552
|
+
depth: active.depth - 1,
|
|
406553
|
+
index: 0
|
|
406554
|
+
});
|
|
406555
|
+
return;
|
|
406556
|
+
}
|
|
406557
|
+
if (event.key === "Enter") {
|
|
406558
|
+
const item = items[Math.min(active.index, items.length - 1)];
|
|
406559
|
+
if (item) activate(item);
|
|
406560
|
+
}
|
|
406561
|
+
};
|
|
406562
|
+
window.addEventListener("pointerdown", onPointerDown, true);
|
|
406563
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
406564
|
+
return () => {
|
|
406565
|
+
window.removeEventListener("pointerdown", onPointerDown, true);
|
|
406566
|
+
window.removeEventListener("keydown", onKeyDown, true);
|
|
406567
|
+
};
|
|
406568
|
+
});
|
|
406569
|
+
/**
|
|
406570
|
+
* 拖动与点击的分流:移动超过阈值才算拖拽,否则当作点击开关。
|
|
406571
|
+
* @author ddj 2026年10月30号
|
|
406572
|
+
* @param event 指针按下事件
|
|
406573
|
+
*/
|
|
406574
|
+
function beginDrag(event) {
|
|
406575
|
+
if (event.button !== 0) return;
|
|
406576
|
+
event.preventDefault();
|
|
406577
|
+
event.stopPropagation();
|
|
406578
|
+
const start = {
|
|
406579
|
+
pointerId: event.pointerId,
|
|
406580
|
+
x: event.clientX,
|
|
406581
|
+
y: event.clientY,
|
|
406582
|
+
left: launcherPos.left,
|
|
406583
|
+
top: launcherPos.top,
|
|
406584
|
+
moved: false
|
|
406585
|
+
};
|
|
406586
|
+
let last = {
|
|
406587
|
+
left: launcherPos.left,
|
|
406588
|
+
top: launcherPos.top
|
|
406589
|
+
};
|
|
406590
|
+
const onMove = (moveEvent) => {
|
|
406591
|
+
if (moveEvent.pointerId !== start.pointerId) return;
|
|
406592
|
+
const dx = moveEvent.clientX - start.x;
|
|
406593
|
+
const dy = moveEvent.clientY - start.y;
|
|
406594
|
+
if (!start.moved && Math.abs(dx) + Math.abs(dy) < DRAG_SLOP) return;
|
|
406595
|
+
start.moved = true;
|
|
406596
|
+
setDragging(true);
|
|
406597
|
+
last = {
|
|
406598
|
+
left: clamp(start.left + dx, 8, Math.max(8, layerSize.width - LAUNCHER_SIZE.width - 8)),
|
|
406599
|
+
top: clamp(start.top + dy, 8, Math.max(8, layerSize.height - LAUNCHER_SIZE.height - 8))
|
|
406600
|
+
};
|
|
406601
|
+
setDragPos(last);
|
|
406602
|
+
};
|
|
406603
|
+
const finish = () => {
|
|
406604
|
+
window.removeEventListener("pointermove", onMove, true);
|
|
406605
|
+
window.removeEventListener("pointerup", onUp, true);
|
|
406606
|
+
window.removeEventListener("pointercancel", onUp, true);
|
|
406607
|
+
window.removeEventListener("blur", onUp, true);
|
|
406608
|
+
setDragging(false);
|
|
406609
|
+
setDragPos(null);
|
|
406610
|
+
if (start.moved) {
|
|
406611
|
+
store.patchLauncher(last.left, last.top);
|
|
406612
|
+
store.flushLauncher();
|
|
406613
|
+
return;
|
|
406614
|
+
}
|
|
406615
|
+
if (open) closeMenu();
|
|
406616
|
+
else setOpen(true);
|
|
406617
|
+
};
|
|
406618
|
+
const onUp = () => finish();
|
|
406619
|
+
window.addEventListener("pointermove", onMove, true);
|
|
406620
|
+
window.addEventListener("pointerup", onUp, true);
|
|
406621
|
+
window.addEventListener("pointercancel", onUp, true);
|
|
406622
|
+
window.addEventListener("blur", onUp, true);
|
|
406623
|
+
}
|
|
406624
|
+
/**
|
|
406625
|
+
* 悬停行:按钮只高亮;组延迟后在右侧展开(防抖动)。
|
|
406626
|
+
* @author ddj 2026年10月30号
|
|
406627
|
+
* @param depth 所在列序号
|
|
406628
|
+
* @param index 行序号
|
|
406629
|
+
* @param item 菜单项
|
|
406630
|
+
*/
|
|
406631
|
+
function hoverItem(depth, index, item) {
|
|
406632
|
+
setHoverRow((prev) => ({
|
|
406633
|
+
...prev,
|
|
406634
|
+
[depth]: index
|
|
406635
|
+
}));
|
|
406636
|
+
setActive({
|
|
406637
|
+
depth,
|
|
406638
|
+
index
|
|
406639
|
+
});
|
|
406640
|
+
if (hoverTimer.current !== null) clearTimeout(hoverTimer.current);
|
|
406641
|
+
if (item.type !== "group") {
|
|
406642
|
+
setOpenPath((prev) => prev.slice(0, depth));
|
|
406643
|
+
return;
|
|
406644
|
+
}
|
|
406645
|
+
hoverTimer.current = setTimeout(() => {
|
|
406646
|
+
setOpenPath((prev) => prev.slice(0, depth).concat(item.id));
|
|
406647
|
+
}, HOVER_DELAY);
|
|
406648
|
+
}
|
|
406649
|
+
/**
|
|
406650
|
+
* 执行一项:组则展开,按钮则合成文本后发送/填入。
|
|
406651
|
+
* @author ddj 2026年10月30号
|
|
406652
|
+
* @param item 菜单项
|
|
406653
|
+
*/
|
|
406654
|
+
async function activate(item) {
|
|
406655
|
+
if (item.type === "group") return;
|
|
406656
|
+
const inlineContents = {};
|
|
406657
|
+
if (item.ruleMode === "inline") for (const binding of item.rules) {
|
|
406658
|
+
if (validateRule(binding, ctx).state !== "ok") continue;
|
|
406659
|
+
try {
|
|
406660
|
+
inlineContents[bindingKey(binding)] = await host.readRule(binding);
|
|
406661
|
+
} catch {}
|
|
406662
|
+
}
|
|
406663
|
+
const composed = composePrompt(item, {
|
|
406664
|
+
...ctx,
|
|
406665
|
+
inlineContents,
|
|
406666
|
+
selection: selectionText(),
|
|
406667
|
+
date: todayText()
|
|
406668
|
+
});
|
|
406669
|
+
if (composed.text.trim() === "") {
|
|
406670
|
+
host.notify("「" + item.name + "」没有可发送的内容");
|
|
406671
|
+
return;
|
|
406672
|
+
}
|
|
406673
|
+
if (composed.warnings.length > 0) host.notify("已跳过 " + composed.warnings.length + " 项失效绑定:" + composed.warnings[0]);
|
|
406674
|
+
const action = item.clickAction ?? doc.clickAction;
|
|
406675
|
+
closeMenu();
|
|
406676
|
+
const result = action === "fill" ? await host.fill(composed.text) : await host.send(composed.text);
|
|
406677
|
+
if (!result.ok) host.notify(result.message ?? "操作失败");
|
|
406678
|
+
}
|
|
406679
|
+
/**
|
|
406680
|
+
* 复制一项(同组同级追加副本,名字加「副本」)。
|
|
406681
|
+
* @author ddj 2026年10月30号
|
|
406682
|
+
* @param item 源项
|
|
406683
|
+
*/
|
|
406684
|
+
async function duplicate(item) {
|
|
406685
|
+
const copy = item.type === "group" ? {
|
|
406686
|
+
...item,
|
|
406687
|
+
id: store.nextId("group"),
|
|
406688
|
+
name: item.name + " 副本",
|
|
406689
|
+
order: item.order + 1
|
|
406690
|
+
} : {
|
|
406691
|
+
...item,
|
|
406692
|
+
id: store.nextId("button"),
|
|
406693
|
+
name: item.name + " 副本",
|
|
406694
|
+
order: item.order + 1
|
|
406695
|
+
};
|
|
406696
|
+
const result = await store.createItem(copy);
|
|
406697
|
+
if (!result.ok) host.notify(result.error ?? "复制失败");
|
|
406698
|
+
setRowMenu(null);
|
|
406699
|
+
}
|
|
406700
|
+
/**
|
|
406701
|
+
* 删除一项(组连带子项)。
|
|
406702
|
+
* @author ddj 2026年10月30号
|
|
406703
|
+
* @param item 目标项
|
|
406704
|
+
*/
|
|
406705
|
+
async function removeItem(item) {
|
|
406706
|
+
setRowMenu(null);
|
|
406707
|
+
const suffix = item.type === "group" ? "及其全部子项" : "";
|
|
406708
|
+
if (!window.confirm("确认删除「" + item.name + "」" + suffix + "?")) return;
|
|
406709
|
+
const result = await store.removeItem(item.id);
|
|
406710
|
+
if (!result.ok) host.notify(result.error ?? "删除失败");
|
|
406711
|
+
setOpenPath([]);
|
|
406712
|
+
}
|
|
406713
|
+
const launcher = /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406714
|
+
className: "edrv-qm-launcher" + (dragging ? " edrv-qm-launcher--drag" : "") + (open ? " edrv-qm-launcher--open" : ""),
|
|
406715
|
+
style: {
|
|
406716
|
+
left: launcherPos.left,
|
|
406717
|
+
top: launcherPos.top
|
|
406718
|
+
},
|
|
406719
|
+
title: "快捷菜单(拖动可移动,右键更多)",
|
|
406720
|
+
role: "button",
|
|
406721
|
+
tabIndex: 0,
|
|
406722
|
+
"aria-label": "快捷菜单",
|
|
406723
|
+
"aria-expanded": open,
|
|
406724
|
+
onPointerDown: beginDrag,
|
|
406725
|
+
onKeyDown: (event) => {
|
|
406726
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
406727
|
+
event.preventDefault();
|
|
406728
|
+
if (open) closeMenu();
|
|
406729
|
+
else setOpen(true);
|
|
406730
|
+
},
|
|
406731
|
+
onContextMenu: (event) => {
|
|
406732
|
+
event.preventDefault();
|
|
406733
|
+
setOpen(false);
|
|
406734
|
+
setDialog({ kind: "global" });
|
|
406735
|
+
}
|
|
406736
|
+
}, /* @__PURE__ */ react$1.default.createElement("svg", {
|
|
406737
|
+
viewBox: "0 0 23.16 17.04",
|
|
406738
|
+
"aria-hidden": "true"
|
|
406739
|
+
}, /* @__PURE__ */ react$1.default.createElement("path", {
|
|
406740
|
+
d: WHALE_PATH,
|
|
406741
|
+
fill: "currentColor"
|
|
406742
|
+
})));
|
|
406743
|
+
const miniDot = /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406744
|
+
className: "edrv-qm-launcher edrv-qm-launcher--mini",
|
|
406745
|
+
style: {
|
|
406746
|
+
left: launcherPos.left,
|
|
406747
|
+
top: launcherPos.top
|
|
406748
|
+
},
|
|
406749
|
+
title: "快捷菜单已隐藏,点击展开(右键打开设置)",
|
|
406750
|
+
role: "button",
|
|
406751
|
+
tabIndex: 0,
|
|
406752
|
+
"aria-label": "快捷菜单(已隐藏)",
|
|
406753
|
+
onPointerDown: beginDrag,
|
|
406754
|
+
onKeyDown: (event) => {
|
|
406755
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
406756
|
+
event.preventDefault();
|
|
406757
|
+
if (open) closeMenu();
|
|
406758
|
+
else setOpen(true);
|
|
406759
|
+
},
|
|
406760
|
+
onContextMenu: (event) => {
|
|
406761
|
+
event.preventDefault();
|
|
406762
|
+
setOpen(false);
|
|
406763
|
+
setDialog({ kind: "global" });
|
|
406764
|
+
}
|
|
406765
|
+
});
|
|
406766
|
+
const panels = open ? columnRects.map((rect, depth) => {
|
|
406767
|
+
const parentId = depth === 0 ? null : openPath[depth - 1];
|
|
406768
|
+
return /* @__PURE__ */ react$1.default.createElement(QuickColumn, {
|
|
406769
|
+
key: depth,
|
|
406770
|
+
depth,
|
|
406771
|
+
rect,
|
|
406772
|
+
items: childrenOf(doc, parentId, ctx.cwd),
|
|
406773
|
+
canAdd: canCreateAt(doc, parentId),
|
|
406774
|
+
ctx,
|
|
406775
|
+
status: state.status,
|
|
406776
|
+
error: state.error,
|
|
406777
|
+
activeIndex: active.depth === depth ? active.index : -1,
|
|
406778
|
+
scrollTop: scrolls[depth] ?? 0,
|
|
406779
|
+
onScroll: (top) => setScrolls((prev) => ({
|
|
406780
|
+
...prev,
|
|
406781
|
+
[depth]: top
|
|
406782
|
+
})),
|
|
406783
|
+
onHover: (index, item) => hoverItem(depth, index, item),
|
|
406784
|
+
onActivate: (item) => {
|
|
406785
|
+
activate(item);
|
|
406786
|
+
},
|
|
406787
|
+
onMore: (item, rowRect) => setRowMenu({
|
|
406788
|
+
item,
|
|
406789
|
+
rect: rowRect
|
|
406790
|
+
}),
|
|
406791
|
+
onAdd: () => setDialog({
|
|
406792
|
+
kind: "item",
|
|
406793
|
+
parentId
|
|
406794
|
+
}),
|
|
406795
|
+
onSettings: depth === 0 ? () => {
|
|
406796
|
+
setOpen(false);
|
|
406797
|
+
setDialog({ kind: "global" });
|
|
406798
|
+
} : void 0,
|
|
406799
|
+
onRetry: () => {
|
|
406800
|
+
store.load(true);
|
|
406801
|
+
},
|
|
406802
|
+
onEmptyHint: depth === 0 ? "把常做的提问连同技能、规则存成一个按钮,点一下就发出去。" : "组里还没有内容,新建一个按钮或子组。"
|
|
406803
|
+
});
|
|
406804
|
+
}) : null;
|
|
406805
|
+
return /* @__PURE__ */ react$1.default.createElement(react$1.default.Fragment, null, /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406806
|
+
"data-edrv-view": "1",
|
|
406807
|
+
className: "edrv-qm-root",
|
|
406808
|
+
"data-qm-root": "1"
|
|
406809
|
+
}, doc.enabled ? launcher : miniDot), open ? (0, react_dom.createPortal)(/* @__PURE__ */ react$1.default.createElement("div", {
|
|
406810
|
+
"data-edrv-view": "1",
|
|
406811
|
+
className: "edrv-qm-menu",
|
|
406812
|
+
"data-qm-root": "1"
|
|
406813
|
+
}, panels, rowMenu ? /* @__PURE__ */ react$1.default.createElement(RowMenu, {
|
|
406814
|
+
state: rowMenu,
|
|
406815
|
+
onEdit: () => {
|
|
406816
|
+
setDialog({
|
|
406817
|
+
kind: "item",
|
|
406818
|
+
item: rowMenu.item
|
|
406819
|
+
});
|
|
406820
|
+
setRowMenu(null);
|
|
406821
|
+
},
|
|
406822
|
+
onDuplicate: () => {
|
|
406823
|
+
duplicate(rowMenu.item);
|
|
406824
|
+
},
|
|
406825
|
+
onDelete: () => {
|
|
406826
|
+
removeItem(rowMenu.item);
|
|
406827
|
+
}
|
|
406828
|
+
}) : null), document.body) : null, dialog.kind === "item" ? /* @__PURE__ */ react$1.default.createElement(QuickItemDialog, {
|
|
406829
|
+
host,
|
|
406830
|
+
item: dialog.item,
|
|
406831
|
+
parentId: dialog.item ? dialog.item.parentId : dialog.parentId ?? null,
|
|
406832
|
+
onClose: () => setDialog({ kind: "none" })
|
|
406833
|
+
}) : null, dialog.kind === "global" ? /* @__PURE__ */ react$1.default.createElement(MenuGlobalDialog, {
|
|
406834
|
+
host,
|
|
406835
|
+
onClose: () => setDialog({ kind: "none" })
|
|
406836
|
+
}) : null);
|
|
406837
|
+
}
|
|
406838
|
+
/**
|
|
406839
|
+
* 一列(纵向滑动列表 + 末行「+ 新建」 + root 底部设置)。
|
|
406840
|
+
* @author ddj 2026年10月30号
|
|
406841
|
+
* @param props 列数据与回调
|
|
406842
|
+
* @returns 列元素
|
|
406843
|
+
*/
|
|
406844
|
+
function QuickColumn(props) {
|
|
406845
|
+
const scrollRef = react$1.default.useRef(null);
|
|
406846
|
+
const count = props.items.length + (props.canAdd ? 1 : 0);
|
|
406847
|
+
const isEmpty = props.items.length === 0 && props.status !== "loading" && props.status !== "error";
|
|
406848
|
+
const [overflow, setOverflow] = react$1.default.useState(false);
|
|
406849
|
+
react$1.default.useEffect(() => {
|
|
406850
|
+
props.onScroll(scrollRef.current?.scrollTop ?? 0);
|
|
406851
|
+
}, []);
|
|
406852
|
+
react$1.default.useEffect(() => {
|
|
406853
|
+
const viewport = scrollRef.current;
|
|
406854
|
+
if (!viewport) return;
|
|
406855
|
+
const measure = () => setOverflow(viewport.scrollHeight > viewport.clientHeight + 8);
|
|
406856
|
+
measure();
|
|
406857
|
+
const observer = new ResizeObserver(measure);
|
|
406858
|
+
observer.observe(viewport);
|
|
406859
|
+
return () => observer.disconnect();
|
|
406860
|
+
}, [
|
|
406861
|
+
props.items.length,
|
|
406862
|
+
props.canAdd,
|
|
406863
|
+
props.status
|
|
406864
|
+
]);
|
|
406865
|
+
return /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406866
|
+
className: "edrv-qm-col",
|
|
406867
|
+
style: {
|
|
406868
|
+
left: props.rect.left,
|
|
406869
|
+
top: props.rect.top,
|
|
406870
|
+
height: props.rect.height
|
|
406871
|
+
},
|
|
406872
|
+
role: "menu"
|
|
406873
|
+
}, /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406874
|
+
className: "edrv-qm-scroll" + (overflow ? " edrv-qm-scroll--fade" : ""),
|
|
406875
|
+
ref: scrollRef,
|
|
406876
|
+
onScroll: (event) => props.onScroll(event.currentTarget.scrollTop)
|
|
406877
|
+
}, props.status === "loading" && props.items.length === 0 ? /* @__PURE__ */ react$1.default.createElement("div", { className: "edrv-qm-empty" }, "正在读取菜单…") : null, props.status === "error" && props.items.length === 0 ? /* @__PURE__ */ react$1.default.createElement("div", { className: "edrv-qm-empty" }, /* @__PURE__ */ react$1.default.createElement("div", { className: "edrv-qm-empty-title" }, "菜单没读出来"), /* @__PURE__ */ react$1.default.createElement("div", null, props.error ?? "读取失败"), /* @__PURE__ */ react$1.default.createElement("button", {
|
|
406878
|
+
type: "button",
|
|
406879
|
+
className: "edrv-qm-btn",
|
|
406880
|
+
onClick: props.onRetry
|
|
406881
|
+
}, "重试")) : null, isEmpty ? /* @__PURE__ */ react$1.default.createElement("div", { className: "edrv-qm-empty" }, /* @__PURE__ */ react$1.default.createElement("div", { className: "edrv-qm-empty-title" }, props.depth === 0 ? "还没有快捷项" : "这一组是空的"), /* @__PURE__ */ react$1.default.createElement("div", null, props.onEmptyHint), props.canAdd ? /* @__PURE__ */ react$1.default.createElement("button", {
|
|
406882
|
+
type: "button",
|
|
406883
|
+
className: "edrv-qm-btn edrv-qm-btn--primary",
|
|
406884
|
+
onClick: props.onAdd
|
|
406885
|
+
}, props.depth === 0 ? "新建第一个" : "新建一项") : null) : null, /* @__PURE__ */ react$1.default.createElement("div", { className: "edrv-qm-list" }, props.items.map((item, index) => {
|
|
406886
|
+
const scale = index === props.activeIndex ? {
|
|
406887
|
+
scale: 1,
|
|
406888
|
+
opacity: 1
|
|
406889
|
+
} : centerScale(index, count);
|
|
406890
|
+
const flags = itemStatus(item, props.ctx);
|
|
406891
|
+
const badge = flags.errors.length > 0 ? " edrv-qm-row-badge--error" : "";
|
|
406892
|
+
const tip = flags.errors.concat(flags.warnings).join("\n");
|
|
406893
|
+
return /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406894
|
+
key: item.id,
|
|
406895
|
+
className: "edrv-qm-row" + (item.type === "group" ? " edrv-qm-row--group" : "") + (props.activeIndex === index ? " edrv-qm-row--active" : ""),
|
|
406896
|
+
style: {
|
|
406897
|
+
transform: "scale(" + scale.scale.toFixed(3) + ")",
|
|
406898
|
+
opacity: scale.opacity
|
|
406899
|
+
},
|
|
406900
|
+
title: tip,
|
|
406901
|
+
"data-qm-row": "1",
|
|
406902
|
+
role: "menuitem",
|
|
406903
|
+
tabIndex: -1,
|
|
406904
|
+
"aria-label": item.type === "group" ? item.name + "(组)" : item.name,
|
|
406905
|
+
onPointerEnter: () => props.onHover(index, item),
|
|
406906
|
+
onClick: () => props.onActivate(item)
|
|
406907
|
+
}, /* @__PURE__ */ react$1.default.createElement("span", { className: "edrv-qm-row-label" }, item.name), badge !== "" ? /* @__PURE__ */ react$1.default.createElement("span", { className: "edrv-qm-row-badge" + badge }) : null, item.type === "group" ? /* @__PURE__ */ react$1.default.createElement("span", {
|
|
406908
|
+
className: "edrv-qm-row-chevron",
|
|
406909
|
+
"aria-hidden": "true"
|
|
406910
|
+
}, /* @__PURE__ */ react$1.default.createElement("svg", {
|
|
406911
|
+
viewBox: "0 0 12 12",
|
|
406912
|
+
fill: "none",
|
|
406913
|
+
stroke: "currentColor",
|
|
406914
|
+
strokeWidth: "1.6",
|
|
406915
|
+
strokeLinecap: "round",
|
|
406916
|
+
strokeLinejoin: "round"
|
|
406917
|
+
}, /* @__PURE__ */ react$1.default.createElement("path", { d: "M4.5 2.5 8 6l-3.5 3.5" }))) : null, /* @__PURE__ */ react$1.default.createElement("button", {
|
|
406918
|
+
type: "button",
|
|
406919
|
+
className: "edrv-qm-row-more",
|
|
406920
|
+
title: "编辑 / 复制 / 删除",
|
|
406921
|
+
"aria-label": "「" + item.name + "」的更多操作",
|
|
406922
|
+
onClick: (event) => {
|
|
406923
|
+
event.stopPropagation();
|
|
406924
|
+
const rowRect = {
|
|
406925
|
+
left: props.rect.left,
|
|
406926
|
+
top: props.rect.top + COL_PAD + index * ROW_HEIGHT - props.scrollTop,
|
|
406927
|
+
width: COL_WIDTH,
|
|
406928
|
+
height: ROW_HEIGHT
|
|
406929
|
+
};
|
|
406930
|
+
props.onMore(item, rowRect);
|
|
406931
|
+
}
|
|
406932
|
+
}, "⋯"));
|
|
406933
|
+
}), props.canAdd && !isEmpty ? /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406934
|
+
className: "edrv-qm-row edrv-qm-row--add",
|
|
406935
|
+
"data-qm-row": "1",
|
|
406936
|
+
role: "menuitem",
|
|
406937
|
+
"aria-label": "新建",
|
|
406938
|
+
onClick: props.onAdd
|
|
406939
|
+
}, "+ 新建") : null)), props.onSettings ? /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406940
|
+
className: "edrv-qm-foot",
|
|
406941
|
+
onClick: props.onSettings,
|
|
406942
|
+
title: "显示、点击动作、最大嵌套层数、导入导出",
|
|
406943
|
+
role: "button",
|
|
406944
|
+
tabIndex: 0,
|
|
406945
|
+
onKeyDown: (event) => {
|
|
406946
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
406947
|
+
event.preventDefault();
|
|
406948
|
+
props.onSettings?.();
|
|
406949
|
+
}
|
|
406950
|
+
}, /* @__PURE__ */ react$1.default.createElement("span", {
|
|
406951
|
+
className: "edrv-qm-foot-icon",
|
|
406952
|
+
"aria-hidden": "true"
|
|
406953
|
+
}, /* @__PURE__ */ react$1.default.createElement("svg", {
|
|
406954
|
+
viewBox: "0 0 16 16",
|
|
406955
|
+
fill: "none",
|
|
406956
|
+
stroke: "currentColor",
|
|
406957
|
+
strokeWidth: "1.3",
|
|
406958
|
+
strokeLinecap: "round"
|
|
406959
|
+
}, /* @__PURE__ */ react$1.default.createElement("circle", {
|
|
406960
|
+
cx: "8",
|
|
406961
|
+
cy: "8",
|
|
406962
|
+
r: "2.3"
|
|
406963
|
+
}), /* @__PURE__ */ react$1.default.createElement("path", { d: "M8 1.9v1.7M8 12.4v1.7M1.9 8h1.7M12.4 8h1.7M3.7 3.7l1.2 1.2M11.1 11.1l1.2 1.2M12.3 3.7l-1.2 1.2M4.9 11.1l-1.2 1.2" }))), /* @__PURE__ */ react$1.default.createElement("span", null, "菜单设置")) : null);
|
|
406964
|
+
}
|
|
406965
|
+
/**
|
|
406966
|
+
* 行内小菜单(编辑 / 复制 / 删除)。
|
|
406967
|
+
* @author ddj 2026年10月30号
|
|
406968
|
+
* @param props 状态与动作
|
|
406969
|
+
* @returns 小菜单元素
|
|
406970
|
+
*/
|
|
406971
|
+
function RowMenu(props) {
|
|
406972
|
+
return /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406973
|
+
className: "edrv-qm-context",
|
|
406974
|
+
style: {
|
|
406975
|
+
left: props.state.rect.left + COL_WIDTH - 10,
|
|
406976
|
+
top: props.state.rect.top + 26
|
|
406977
|
+
},
|
|
406978
|
+
"data-qm-root": "1",
|
|
406979
|
+
role: "menu"
|
|
406980
|
+
}, /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406981
|
+
className: "edrv-qm-context-item",
|
|
406982
|
+
role: "menuitem",
|
|
406983
|
+
onClick: props.onEdit
|
|
406984
|
+
}, "编辑"), /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406985
|
+
className: "edrv-qm-context-item",
|
|
406986
|
+
role: "menuitem",
|
|
406987
|
+
onClick: props.onDuplicate
|
|
406988
|
+
}, "复制"), /* @__PURE__ */ react$1.default.createElement("div", { className: "edrv-qm-context-sep" }), /* @__PURE__ */ react$1.default.createElement("div", {
|
|
406989
|
+
className: "edrv-qm-context-item edrv-qm-context-item--danger",
|
|
406990
|
+
role: "menuitem",
|
|
406991
|
+
onClick: props.onDelete
|
|
406992
|
+
}, "删除"));
|
|
406993
|
+
}
|
|
403872
406994
|
//#endregion
|
|
403873
406995
|
//#region src/client/sidebar/registry.ts
|
|
403874
406996
|
/** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
|
|
@@ -409563,6 +412685,154 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
409563
412685
|
/** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
|
|
409564
412686
|
const addToConversation = createAddToConversation(ctx);
|
|
409565
412687
|
/**
|
|
412688
|
+
* 读取规则列表(rules.list;失败抛错,由快捷菜单按「未校验」降级)。
|
|
412689
|
+
* @author ddj 2026年10月30号
|
|
412690
|
+
* @returns 用户规则 + 项目规则
|
|
412691
|
+
*/
|
|
412692
|
+
const loadRules = async () => {
|
|
412693
|
+
const result = await rpc("rules.list", {});
|
|
412694
|
+
if (!result.ok) throw new Error(result.error);
|
|
412695
|
+
return {
|
|
412696
|
+
user: result.user,
|
|
412697
|
+
projects: result.projects
|
|
412698
|
+
};
|
|
412699
|
+
};
|
|
412700
|
+
/**
|
|
412701
|
+
* 会话技能枚举服务(remote.skills 动态注入;旧版 DSH 无该服务时保持 null = 「未校验」)。
|
|
412702
|
+
* @author ddj 2026年10月30号
|
|
412703
|
+
*/
|
|
412704
|
+
let skillEnumerator = null;
|
|
412705
|
+
/**
|
|
412706
|
+
* 读取会话可见技能名(服务不可用时返回 null,界面按「未校验」处理)。
|
|
412707
|
+
* @author ddj 2026年10月30号
|
|
412708
|
+
* @param sessionId 会话 id
|
|
412709
|
+
* @returns 技能名清单或 null
|
|
412710
|
+
*/
|
|
412711
|
+
const loadSkillNames = async (sessionId) => {
|
|
412712
|
+
const list = skillEnumerator?.list;
|
|
412713
|
+
if (typeof list !== "function") return null;
|
|
412714
|
+
const result = await list.call(skillEnumerator, { sessionId });
|
|
412715
|
+
return ((result.value ?? result)?.skills ?? []).map((entry) => entry.name).filter((name) => typeof name === "string");
|
|
412716
|
+
};
|
|
412717
|
+
/**
|
|
412718
|
+
* 读取技能候选(全局技能 + 当前工作区技能;只读枚举,失败返回 null)。
|
|
412719
|
+
* @author ddj 2026年10月30号
|
|
412720
|
+
* @param cwd 目标工作区(缺省取当前会话作用域)
|
|
412721
|
+
* @returns 技能候选快照;host 无该 RPC 或读取失败时为 null
|
|
412722
|
+
*/
|
|
412723
|
+
const loadSkillCatalog = async (cwd) => {
|
|
412724
|
+
const target = cwd ?? readSessionScope(ctx).cwd;
|
|
412725
|
+
try {
|
|
412726
|
+
const result = await rpc("skills.list", target ? { cwd: target } : {});
|
|
412727
|
+
if (!result.ok) return null;
|
|
412728
|
+
return {
|
|
412729
|
+
user: result.user,
|
|
412730
|
+
project: result.project,
|
|
412731
|
+
...result.projectRoot ? { projectRoot: result.projectRoot } : {}
|
|
412732
|
+
};
|
|
412733
|
+
} catch {
|
|
412734
|
+
return null;
|
|
412735
|
+
}
|
|
412736
|
+
};
|
|
412737
|
+
/**
|
|
412738
|
+
* 确保存在可写入的会话(无会话时新建独立会话)。
|
|
412739
|
+
* @author ddj 2026年10月30号
|
|
412740
|
+
* @returns 会话 id 或错误文案
|
|
412741
|
+
*/
|
|
412742
|
+
const ensureSession = async () => {
|
|
412743
|
+
const scope = readSessionScope(ctx);
|
|
412744
|
+
if (scope.sessionId) return { id: scope.sessionId };
|
|
412745
|
+
const draft = await addToConversation.startDraftSession(scope.cwd);
|
|
412746
|
+
if (!draft.ok || !draft.id) return { error: "当前没有可用会话,新建会话也失败了" };
|
|
412747
|
+
return { id: draft.id };
|
|
412748
|
+
};
|
|
412749
|
+
/**
|
|
412750
|
+
* 发送快捷菜单文本(优先发到当前会话,会话忙碌时如实回报)。
|
|
412751
|
+
* @author ddj 2026年10月30号
|
|
412752
|
+
* @param text 合成后的消息文本
|
|
412753
|
+
* @returns 是否发送成功
|
|
412754
|
+
*/
|
|
412755
|
+
const sendQuickText = async (text) => {
|
|
412756
|
+
const session = await ensureSession();
|
|
412757
|
+
if ("error" in session) return {
|
|
412758
|
+
ok: false,
|
|
412759
|
+
message: session.error
|
|
412760
|
+
};
|
|
412761
|
+
return await addToConversation.sendTask(session.id, text) ? { ok: true } : {
|
|
412762
|
+
ok: false,
|
|
412763
|
+
message: "发送被拒绝(会话可能正忙,稍后再点)"
|
|
412764
|
+
};
|
|
412765
|
+
};
|
|
412766
|
+
/**
|
|
412767
|
+
* 只把快捷菜单文本填入输入框。
|
|
412768
|
+
* @author ddj 2026年10月30号
|
|
412769
|
+
* @param text 合成后的消息文本
|
|
412770
|
+
* @returns 是否填入成功
|
|
412771
|
+
*/
|
|
412772
|
+
const fillQuickText = async (text) => {
|
|
412773
|
+
const session = await ensureSession();
|
|
412774
|
+
if ("error" in session) return {
|
|
412775
|
+
ok: false,
|
|
412776
|
+
message: session.error
|
|
412777
|
+
};
|
|
412778
|
+
const outcome = await addToConversation.appendText(session.id, text);
|
|
412779
|
+
if (outcome === "ok" || outcome === "busy") return { ok: true };
|
|
412780
|
+
return {
|
|
412781
|
+
ok: false,
|
|
412782
|
+
message: "写入输入框失败(" + outcome + "),请重试"
|
|
412783
|
+
};
|
|
412784
|
+
};
|
|
412785
|
+
/** 快捷菜单宿主桥接:菜单组件不直接摸 ctx,规则/技能/发送/工作区都从这里注入。 */
|
|
412786
|
+
const quickMenuHost = {
|
|
412787
|
+
store: createMenuStore({
|
|
412788
|
+
rules: loadRules,
|
|
412789
|
+
skills: loadSkillNames,
|
|
412790
|
+
scope: () => {
|
|
412791
|
+
const scope = readSessionScope(ctx);
|
|
412792
|
+
return {
|
|
412793
|
+
sessionId: scope.sessionId,
|
|
412794
|
+
cwd: scope.cwd
|
|
412795
|
+
};
|
|
412796
|
+
}
|
|
412797
|
+
}),
|
|
412798
|
+
scope: () => {
|
|
412799
|
+
const scope = readSessionScope(ctx);
|
|
412800
|
+
return {
|
|
412801
|
+
sessionId: scope.sessionId,
|
|
412802
|
+
cwd: scope.cwd
|
|
412803
|
+
};
|
|
412804
|
+
},
|
|
412805
|
+
send: sendQuickText,
|
|
412806
|
+
fill: fillQuickText,
|
|
412807
|
+
rules: loadRules,
|
|
412808
|
+
skills: async () => {
|
|
412809
|
+
const scope = readSessionScope(ctx);
|
|
412810
|
+
return scope.sessionId ? loadSkillNames(scope.sessionId) : null;
|
|
412811
|
+
},
|
|
412812
|
+
skillCatalog: loadSkillCatalog,
|
|
412813
|
+
readRule: async (binding) => {
|
|
412814
|
+
const result = await rpc("rules.read", {
|
|
412815
|
+
scope: binding.scope,
|
|
412816
|
+
file: binding.file,
|
|
412817
|
+
...binding.workspacePath ? { workspacePath: binding.workspacePath } : {},
|
|
412818
|
+
...binding.origin ? { origin: binding.origin } : {}
|
|
412819
|
+
});
|
|
412820
|
+
if (!result.ok) throw new Error(result.error);
|
|
412821
|
+
return result.content;
|
|
412822
|
+
},
|
|
412823
|
+
workspaces: () => (workspaces?.list.getSnapshot().items ?? []).map((item) => ({
|
|
412824
|
+
path: item.path,
|
|
412825
|
+
title: item.title
|
|
412826
|
+
})),
|
|
412827
|
+
notify: (message) => {
|
|
412828
|
+
toastDom(message);
|
|
412829
|
+
}
|
|
412830
|
+
};
|
|
412831
|
+
if (typeof ctx.inject === "function") ctx.inject(["remote.skills"], (scope) => {
|
|
412832
|
+
skillEnumerator = scope.remote?.skills ?? null;
|
|
412833
|
+
quickMenuHost.store.refreshBindings(true);
|
|
412834
|
+
});
|
|
412835
|
+
/**
|
|
409566
412836
|
* Build applicable client diagnostics from live installation state.
|
|
409567
412837
|
* @private @author ddj 2026年10月13号
|
|
409568
412838
|
* @param version Running DSH version supplied by the compat RPC.
|
|
@@ -409999,6 +413269,21 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
409999
413269
|
id: "edrv-plugin-status",
|
|
410000
413270
|
order: 40
|
|
410001
413271
|
}, (props) => react$1.default.createElement(PluginDetailSection, Object.assign({}, props)));
|
|
413272
|
+
ctx.effect(() => {
|
|
413273
|
+
const dispose = registerSlotSafely(ctx, {
|
|
413274
|
+
name: "shell.overlay",
|
|
413275
|
+
id: "edrv-quick-menu",
|
|
413276
|
+
order: 120,
|
|
413277
|
+
label: "快捷菜单"
|
|
413278
|
+
}, () => react$1.default.createElement(QuickMenuLauncher, { host: quickMenuHost }));
|
|
413279
|
+
return () => {
|
|
413280
|
+
dispose?.();
|
|
413281
|
+
quickMenuHost.store.flushLauncher();
|
|
413282
|
+
};
|
|
413283
|
+
}, "vscode-mode: quick menu launcher");
|
|
413284
|
+
ctx.effect(() => subscribeScope(ctx, () => {
|
|
413285
|
+
quickMenuHost.store.refreshBindings(true);
|
|
413286
|
+
}), "vscode-mode: quick menu scope refresh");
|
|
410002
413287
|
ctx.effect(() => () => {
|
|
410003
413288
|
setPanelImport();
|
|
410004
413289
|
try {
|