dsh-vscode-mode 0.17.0 → 0.19.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.
Files changed (43) hide show
  1. package/README.md +8 -4
  2. package/lib/client.debug.js +934 -0
  3. package/lib/client.debug.js.map +1 -0
  4. package/lib/client.js +1425 -1724
  5. package/lib/client.js.map +1 -1
  6. package/lib/client.rules.js +793 -0
  7. package/lib/client.rules.js.map +1 -0
  8. package/lib/index.js +1078 -286
  9. package/lib/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/skills/dsh-vscodemode-mcp/SKILL.md +24 -7
  12. package/src/client/index.ts +24 -0
  13. package/src/client/lazyPanel.ts +68 -0
  14. package/src/client/mcpSessionState.ts +108 -0
  15. package/src/client/monaco/lsp/providers.ts +32 -3
  16. package/src/client/sidebar/PanelBoundary.ts +57 -0
  17. package/src/client/sidebar/SidebarView.ts +58 -21
  18. package/src/client/sidebar/panels/FileExplorer.ts +80 -27
  19. package/src/client/sidebar/panels/SvnPanel.ts +24 -8
  20. package/src/client/sidebar/panels/index.ts +3 -4
  21. package/src/client/sidebar/panels/treeWindow.ts +106 -0
  22. package/src/client/sidebar/panels/useExplorerReveal.ts +5 -2
  23. package/src/client/sidebar/registry.ts +4 -1
  24. package/src/client/state/modelWarmer.ts +198 -0
  25. package/src/client/styles/editor.css +13 -1
  26. package/src/client/textPages.ts +135 -0
  27. package/src/client/ui/EditorPane.ts +16 -4
  28. package/src/client/ui/EditorView.ts +147 -29
  29. package/src/client/ui/LargeTextView.ts +94 -0
  30. package/src/client/ui/McpSessionStatus.ts +75 -0
  31. package/src/client/ui/McpSettings.ts +47 -13
  32. package/src/mcp.ts +1 -0
  33. package/src/mcpBridge.ts +163 -0
  34. package/src/mcpDiscover.ts +38 -0
  35. package/src/mcpPool.ts +245 -0
  36. package/src/mcpProject.ts +15 -12
  37. package/src/mcpRuntime.ts +299 -254
  38. package/src/rpc.ts +42 -8
  39. package/src/shared/mcp.ts +47 -0
  40. package/src/shared/rpc.ts +8 -2
  41. package/src/shared/textPages.ts +28 -0
  42. package/src/textPages.ts +149 -0
  43. package/src/treeIndex.ts +62 -37
package/lib/client.js CHANGED
@@ -29,11 +29,11 @@ window.__ModuleLoader__.load({
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
31
  let react_dom = require("react-dom");
32
+ let react_dom_client = require("react-dom/client");
32
33
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
33
34
  _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
34
- let react_dom_client = require("react-dom/client");
35
35
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
36
- const css$1 = "/* 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[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/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\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[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-workbench-tools { flex: 0 0 auto; }\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";
36
+ const css$1 = "/* 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/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\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";
37
37
  const tagId$1 = "dsh-vscode-mode/editor.css";
38
38
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
39
39
  const tag = document.createElement("style");
@@ -199,7 +199,7 @@ window.__ModuleLoader__.load({
199
199
  * 作者 ddj 2026-08-28
200
200
  */
201
201
  /** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
202
- const KEY_PREFIX$5 = CACHE_KEY.viewstate;
202
+ const KEY_PREFIX$4 = CACHE_KEY.viewstate;
203
203
  /**
204
204
  * 判断对象是否为合法的 Monaco view state(保守形状校验,非深校验)。
205
205
  * @author ddj 2026年08月28号
@@ -277,7 +277,7 @@ window.__ModuleLoader__.load({
277
277
  */
278
278
  function viewStatesLoad(scope) {
279
279
  try {
280
- return parseViewStates(window.localStorage.getItem(KEY_PREFIX$5 + String(scope))) ?? {};
280
+ return parseViewStates(window.localStorage.getItem(KEY_PREFIX$4 + String(scope))) ?? {};
281
281
  } catch (error) {
282
282
  return {};
283
283
  }
@@ -290,7 +290,7 @@ window.__ModuleLoader__.load({
290
290
  */
291
291
  function viewStatesSave(scope, states) {
292
292
  try {
293
- window.localStorage.setItem(KEY_PREFIX$5 + String(scope), serializeViewStates(states));
293
+ window.localStorage.setItem(KEY_PREFIX$4 + String(scope), serializeViewStates(states));
294
294
  } catch (error) {}
295
295
  }
296
296
  //#endregion
@@ -352,8 +352,10 @@ window.__ModuleLoader__.load({
352
352
  /** @public @author ddj 2026年10月14号 @param props Group callbacks and file view. @returns Accessible tab strip and drop target. */
353
353
  function EditorPane(props) {
354
354
  const { group } = props;
355
- /** @private @author ddj 2026年10月14号 @param tab File record. @returns Draggable tab with isolated close control. */
355
+ const suppressActive = props.suppressActive === true;
356
+ /** @private @author ddj 2026年10月20号 @param tab File record. @returns Draggable tab with isolated close control. */
356
357
  function renderTab(tab) {
358
+ const selected = !suppressActive && tab.path === group.active;
357
359
  /** @private @author ddj 2026年10月14号 @param event Primary press. Start trusted file drag. */
358
360
  function press(event) {
359
361
  props.onDrag(tab.path, event);
@@ -389,8 +391,8 @@ window.__ModuleLoader__.load({
389
391
  "data-edrv-tab-path": tab.path,
390
392
  role: "tab",
391
393
  tabIndex: 0,
392
- "aria-selected": tab.path === group.active,
393
- className: "edrv-tab" + (tab.path === group.active ? " edrv-tab-active" : "") + (tab.pinned ? " edrv-tab-pinned" : ""),
394
+ "aria-selected": selected,
395
+ className: "edrv-tab" + (selected ? " edrv-tab-active" : "") + (tab.pinned ? " edrv-tab-pinned" : ""),
394
396
  title: tab.path + (tab.pinned ? "(已固定)" : ""),
395
397
  onPointerDown: press,
396
398
  onClick: select,
@@ -416,11 +418,12 @@ window.__ModuleLoader__.load({
416
418
  className: "edrv-group" + (props.focused ? " edrv-group-focused" : ""),
417
419
  "data-edrv-group": group.id,
418
420
  onPointerDown: focusEmpty
419
- }, react.default.createElement("div", {
421
+ }, react.default.createElement("div", { className: "edrv-tabs-row" }, react.default.createElement("div", {
420
422
  className: "edrv-tabs",
421
423
  role: "tablist",
422
- "data-edrv-group-tabs": group.id
423
- }, group.tabs.map(renderTab), props.extras), react.default.createElement("div", {
424
+ "data-edrv-group-tabs": group.id,
425
+ ref: props.tabsRef
426
+ }, group.tabs.map(renderTab), props.extras), props.tools), react.default.createElement("div", {
424
427
  className: "edrv-group-body",
425
428
  role: "tabpanel"
426
429
  }, props.children));
@@ -1502,6 +1505,236 @@ window.__ModuleLoader__.load({
1502
1505
  actions
1503
1506
  }, props.children);
1504
1507
  }
1508
+ const TEXT_VIEW_BYTES = 8388608;
1509
+ //#endregion
1510
+ //#region src/client/textPages.ts
1511
+ /**
1512
+ * Own one preview identity and one in-flight request; no polling or automatic continuation.
1513
+ * @public @author ddj 2026年10月22号
1514
+ * @param load Authorized RPC loader. @param changed Snapshot observer.
1515
+ * @returns Pager lifecycle and explicit load controls.
1516
+ */
1517
+ function createTextPager(load, changed) {
1518
+ let generation = 0;
1519
+ let disposed = false;
1520
+ let target = null;
1521
+ let cursor;
1522
+ let version;
1523
+ let inFlight = false;
1524
+ let retainedBytes = 0;
1525
+ let state = emptyState();
1526
+ /** @private @author ddj 2026年10月22号 @returns Publish immutable snapshot. */
1527
+ function publish() {
1528
+ changed({ ...state });
1529
+ }
1530
+ /** @public @author ddj 2026年10月22号 @param args New file/session identity. @returns Reset state; old responses are ignored. */
1531
+ function reset(args) {
1532
+ generation++;
1533
+ target = {
1534
+ sessionId: args.sessionId,
1535
+ path: args.path
1536
+ };
1537
+ cursor = void 0;
1538
+ version = void 0;
1539
+ retainedBytes = 0;
1540
+ state = emptyState();
1541
+ state.loading = inFlight;
1542
+ if (!disposed) publish();
1543
+ }
1544
+ /** @public @author ddj 2026年10月22号 @returns Explicitly load one page, or no-op when busy/finished. */
1545
+ async function loadNext() {
1546
+ if (disposed || !target || inFlight || state.eof || state.capped) return;
1547
+ state.error = null;
1548
+ const requestGeneration = generation;
1549
+ const request = {
1550
+ ...target,
1551
+ cursor
1552
+ };
1553
+ inFlight = true;
1554
+ state.loading = true;
1555
+ publish();
1556
+ try {
1557
+ const page = await load(request);
1558
+ if (disposed || requestGeneration !== generation) return;
1559
+ if (!page.ok) {
1560
+ state.error = page.error;
1561
+ return;
1562
+ }
1563
+ if (!validPage(page, state.offset, version)) {
1564
+ state.error = "Invalid or stale text page";
1565
+ return;
1566
+ }
1567
+ appendPage(page);
1568
+ } catch (error) {
1569
+ if (!disposed && requestGeneration === generation) state.error = String(error);
1570
+ } finally {
1571
+ inFlight = false;
1572
+ if (!disposed) {
1573
+ state.loading = false;
1574
+ publish();
1575
+ }
1576
+ }
1577
+ }
1578
+ /** @private @author ddj 2026年10月22号 @param page Verified page. @returns Append only within byte/line limits. */
1579
+ function appendPage(page) {
1580
+ let text = page.text;
1581
+ const encoded = new TextEncoder().encode(text);
1582
+ const remaining = TEXT_VIEW_BYTES - retainedBytes;
1583
+ if (encoded.length > remaining) {
1584
+ let end = remaining;
1585
+ while (end > 0 && (encoded[end] & 192) === 128) end--;
1586
+ text = new TextDecoder("utf-8", { fatal: true }).decode(encoded.subarray(0, end));
1587
+ state.capped = true;
1588
+ }
1589
+ let lines = state.lines;
1590
+ for (let index = 0; index < text.length; index++) {
1591
+ if (text[index] !== "\n" && (text[index] !== "\r" || text[index + 1] === "\n")) continue;
1592
+ if (lines >= 5e4) {
1593
+ const end = text[index] === "\n" && text[index - 1] === "\r" ? index - 1 : index;
1594
+ text = text.slice(0, end);
1595
+ state.capped = true;
1596
+ break;
1597
+ }
1598
+ lines++;
1599
+ }
1600
+ retainedBytes += new TextEncoder().encode(text).length;
1601
+ state.text += text;
1602
+ state.lines = lines;
1603
+ state.offset = page.offset;
1604
+ state.size = page.size;
1605
+ state.eof = page.eof;
1606
+ state.partialLine = page.partialLine || state.capped;
1607
+ state.capped ||= page.offset >= 8388608 || retainedBytes >= 8388608 || lines >= 5e4;
1608
+ version = page.version;
1609
+ cursor = page.nextCursor ?? void 0;
1610
+ }
1611
+ /** @public @author ddj 2026年10月22号 @returns Invalidate identity while retaining the transport lock. */
1612
+ function invalidate() {
1613
+ generation++;
1614
+ target = null;
1615
+ state = emptyState();
1616
+ }
1617
+ /** @public @author ddj 2026年10月22号 @returns Invalidate pending responses and release preview content. */
1618
+ function dispose() {
1619
+ disposed = true;
1620
+ invalidate();
1621
+ }
1622
+ return {
1623
+ reset,
1624
+ loadNext,
1625
+ invalidate,
1626
+ dispose,
1627
+ getState: () => ({ ...state })
1628
+ };
1629
+ }
1630
+ /** @private @author ddj 2026年10月22号 @returns Empty bounded preview state. */
1631
+ function emptyState() {
1632
+ return {
1633
+ text: "",
1634
+ loading: false,
1635
+ eof: false,
1636
+ capped: false,
1637
+ error: null,
1638
+ offset: 0,
1639
+ size: 0,
1640
+ lines: 1,
1641
+ partialLine: false
1642
+ };
1643
+ }
1644
+ /**
1645
+ * Validate the RPC envelope before retaining any content.
1646
+ * @private @author ddj 2026年10月22号
1647
+ * @param page Server page. @param offset Previous byte position. @param version Bound version.
1648
+ * @returns Whether bounds and continuation invariants hold.
1649
+ */
1650
+ function validPage(page, offset, version) {
1651
+ return typeof page.text === "string" && typeof page.version === "string" && (version === void 0 || version === page.version) && Number.isSafeInteger(page.offset) && Number.isSafeInteger(page.size) && page.size >= 0 && page.offset >= offset && page.offset <= page.size && (page.offset > offset || page.eof) && page.offset - offset <= 262144 && new TextEncoder().encode(page.text).length <= 262145 && page.encoding === "utf-8" && typeof page.partialLine === "boolean" && typeof page.eof === "boolean" && (page.eof ? page.nextCursor === null : typeof page.nextCursor === "string" && page.nextCursor.length > 0) && page.eof === (page.offset === page.size);
1652
+ }
1653
+ //#endregion
1654
+ //#region src/client/ui/LargeTextView.ts
1655
+ const Button$1 = _deepseek_ai_dsh_client_ui_primitives.Button;
1656
+ /**
1657
+ * Read-only paged text surface: never creates a Monaco model or exposes a save action.
1658
+ * @public @author ddj 2026年10月22号
1659
+ * @param props Exact session/file identity and authorized page loader.
1660
+ * @returns Bounded selectable text with explicit continuation and status.
1661
+ */
1662
+ function LargeTextView(props) {
1663
+ const [snapshot, setSnapshot] = react.default.useState(null);
1664
+ const [identity, setIdentity] = react.default.useState("");
1665
+ const pagerRef = react.default.useRef(null);
1666
+ const loaderRef = react.default.useRef(props.loadPage);
1667
+ const mountedRef = react.default.useRef(false);
1668
+ const firstPending = react.default.useRef(false);
1669
+ loaderRef.current = props.loadPage;
1670
+ const currentIdentity = JSON.stringify([props.sessionId, props.path]);
1671
+ /** @private @author ddj 2026年10月22号 @param args Request identity. @returns Current injected loader response. */
1672
+ function loadPage(args) {
1673
+ return loaderRef.current(args);
1674
+ }
1675
+ /** @private @author ddj 2026年10月22号 @param state Latest pager snapshot. @returns Publish only while mounted. */
1676
+ function onSnapshot(state) {
1677
+ if (!mountedRef.current) return;
1678
+ setSnapshot(state);
1679
+ if (!state.loading && firstPending.current) queueMicrotask(loadFirst);
1680
+ }
1681
+ /** @private @author ddj 2026年10月22号 @description Start only the new identity's deferred first page, never automatic continuation. */
1682
+ function loadFirst() {
1683
+ const pager = pagerRef.current;
1684
+ if (!mountedRef.current || !firstPending.current || !pager || pager.getState().loading || typeof document !== "undefined" && document.hidden) return;
1685
+ firstPending.current = false;
1686
+ pager.loadNext();
1687
+ }
1688
+ /** @private @author ddj 2026年10月22号 @returns Bind identity without replacing the single transport lock. */
1689
+ function mountPager() {
1690
+ mountedRef.current = true;
1691
+ if (!pagerRef.current) pagerRef.current = createTextPager(loadPage, onSnapshot);
1692
+ const pager = pagerRef.current;
1693
+ setIdentity(currentIdentity);
1694
+ firstPending.current = true;
1695
+ pager.reset({
1696
+ sessionId: props.sessionId,
1697
+ path: props.path
1698
+ });
1699
+ loadFirst();
1700
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", loadFirst);
1701
+ /** @private @author ddj 2026年10月22号 @returns Release retained text and ignore pending responses. */
1702
+ function unmountPager() {
1703
+ mountedRef.current = false;
1704
+ firstPending.current = false;
1705
+ if (typeof document !== "undefined") document.removeEventListener("visibilitychange", loadFirst);
1706
+ pager.invalidate();
1707
+ }
1708
+ return unmountPager;
1709
+ }
1710
+ react.default.useEffect(mountPager, [props.sessionId, props.path]);
1711
+ /** @private @author ddj 2026年10月22号 @returns Load exactly one additional page on user intent. */
1712
+ function loadNext() {
1713
+ if (typeof document !== "undefined" && document.hidden) return;
1714
+ if (firstPending.current) {
1715
+ loadFirst();
1716
+ return;
1717
+ }
1718
+ pagerRef.current?.loadNext();
1719
+ }
1720
+ const state = identity === currentIdentity ? snapshot : null;
1721
+ let status = "正在加载只读文本…";
1722
+ if (state?.error) status = state.error;
1723
+ else if (state?.capped) status = "已达预览上限(8 MiB / 50,000 行),请使用外部编辑器查看完整文件";
1724
+ else if (state?.eof) status = "文件末尾 · 只读";
1725
+ else if (state) status = `已读取 ${state.offset.toLocaleString()} / ${state.size.toLocaleString()} 字节 · 只读`;
1726
+ if (state?.partialLine) status += " · 末行尚未完整";
1727
+ return react.default.createElement("section", {
1728
+ className: "edrv-large-text",
1729
+ "aria-label": "大文件只读预览"
1730
+ }, react.default.createElement("div", { className: "edrv-large-text-toolbar" }, react.default.createElement("span", { role: "status" }, status), react.default.createElement(Button$1, {
1731
+ onClick: loadNext,
1732
+ disabled: !state || state.loading || state.eof || state.capped
1733
+ }, state?.loading ? "加载中…" : state?.error ? "重试本段" : "加载下一段")), react.default.createElement("pre", {
1734
+ className: "edrv-large-text-content",
1735
+ tabIndex: 0
1736
+ }, state?.text ?? ""));
1737
+ }
1505
1738
  //#endregion
1506
1739
  //#region src/client/tabDrag.ts
1507
1740
  /** Internal pointer drag: never converts external text into file paths. @author ddj 2026年10月14号 */
@@ -8430,7 +8663,7 @@ window.__ModuleLoader__.load({
8430
8663
  //#endregion
8431
8664
  //#region src/client/dap/breakpoints.ts
8432
8665
  /** localStorage key 前缀。 */
8433
- const KEY_PREFIX$4 = "edrv.dap.bp.";
8666
+ const KEY_PREFIX$3 = "edrv.dap.bp.";
8434
8667
  /**
8435
8668
  * 读断点表(缺失/损坏返回空表)。
8436
8669
  * @author ddj 2026年09月29号
@@ -8438,7 +8671,7 @@ window.__ModuleLoader__.load({
8438
8671
  */
8439
8672
  function loadBpMap(scope) {
8440
8673
  try {
8441
- const raw = localStorage.getItem(KEY_PREFIX$4 + scope);
8674
+ const raw = localStorage.getItem(KEY_PREFIX$3 + scope);
8442
8675
  if (!raw) return {};
8443
8676
  const data = JSON.parse(raw);
8444
8677
  if (!data || typeof data !== "object" || Array.isArray(data)) return {};
@@ -8474,8 +8707,8 @@ window.__ModuleLoader__.load({
8474
8707
  */
8475
8708
  function saveBpMap(scope, map) {
8476
8709
  try {
8477
- if (!Object.keys(map).length) localStorage.removeItem(KEY_PREFIX$4 + scope);
8478
- else localStorage.setItem(KEY_PREFIX$4 + scope, JSON.stringify(map));
8710
+ if (!Object.keys(map).length) localStorage.removeItem(KEY_PREFIX$3 + scope);
8711
+ else localStorage.setItem(KEY_PREFIX$3 + scope, JSON.stringify(map));
8479
8712
  } catch {}
8480
8713
  }
8481
8714
  /**
@@ -8582,23 +8815,6 @@ window.__ModuleLoader__.load({
8582
8815
  [path]: list
8583
8816
  };
8584
8817
  }
8585
- /**
8586
- * 推导断点行的跳转目标(调试面板点击断点项 → 打开文件并定位到断点行)。
8587
- * 路径缺失或行号非 ≥1 的有限数时返回 null(提前返回,不发起无效导航)。
8588
- * @author ddj 2026年09月21号
8589
- * @param row 面板行视图(文件路径 + 断点条目)
8590
- * @returns 跳转目标;入参不合法时返回 null
8591
- */
8592
- function bpJumpTargetOf(row) {
8593
- const path = typeof row?.path === "string" ? row.path : "";
8594
- const line = row?.entry?.line;
8595
- if (!path || typeof line !== "number" || !Number.isFinite(line) || line < 1) return null;
8596
- return {
8597
- path,
8598
- line: Math.floor(line),
8599
- column: 1
8600
- };
8601
- }
8602
8818
  //#endregion
8603
8819
  //#region src/client/dap/pidPick.ts
8604
8820
  /**
@@ -10910,6 +11126,82 @@ window.__ModuleLoader__.load({
10910
11126
  }, "✕ 关闭")), react.default.createElement("div", { className: "edrv-launch-body" }, body));
10911
11127
  }
10912
11128
  //#endregion
11129
+ //#region src/client/sidebar/PanelBoundary.ts
11130
+ /** Panel-local callback and React subtree isolation, not a security sandbox. @author ddj 2026年10月22号 */
11131
+ const panelIds = /* @__PURE__ */ new WeakMap();
11132
+ let nextPanelId = 0;
11133
+ /** @private @author ddj 2026年10月22号 @param props Subtree ownership. @returns Stable identity that changes on registration, scope or session replacement. */
11134
+ function ownerKey(props) {
11135
+ if (!panelIds.has(props.panel)) panelIds.set(props.panel, ++nextPanelId);
11136
+ return JSON.stringify([
11137
+ panelIds.get(props.panel),
11138
+ props.scope,
11139
+ props.sessionId
11140
+ ]);
11141
+ }
11142
+ /** @public @author ddj 2026年10月22号 @param panel Registered definition. @param ctx Current scope. @returns Hidden marker or isolated visible/badge result; failed guards stay reachable for retry. */
11143
+ function inspectPanel(panel, ctx) {
11144
+ try {
11145
+ if (panel.visible && panel.visible(ctx) !== true) return null;
11146
+ return {
11147
+ panel,
11148
+ badge: panel.badge?.(ctx) ?? null
11149
+ };
11150
+ } catch (error) {
11151
+ return {
11152
+ panel,
11153
+ badge: null,
11154
+ error: error instanceof Error ? error : new Error(String(error))
11155
+ };
11156
+ }
11157
+ }
11158
+ /** @public @author ddj 2026年10月22号 @description Reset a failed subtree when registration or scope ownership changes. */
11159
+ var PanelBoundary = class extends react.default.Component {
11160
+ state = {
11161
+ panel: this.props.panel,
11162
+ scope: this.props.scope,
11163
+ sessionId: this.props.sessionId,
11164
+ error: null
11165
+ };
11166
+ /** @public @author ddj 2026年10月22号 @param props Current ownership. @param state Previous ownership/error. @returns Reset only when the owner changes. */
11167
+ static getDerivedStateFromProps(props, state) {
11168
+ if (state.panel === props.panel && state.scope === props.scope && state.sessionId === props.sessionId) return null;
11169
+ return {
11170
+ panel: props.panel,
11171
+ scope: props.scope,
11172
+ sessionId: props.sessionId,
11173
+ error: null
11174
+ };
11175
+ }
11176
+ /** @public @author ddj 2026年10月22号 @param error React render/lifecycle error. @returns Local fallback state. */
11177
+ static getDerivedStateFromError(error) {
11178
+ return { error };
11179
+ }
11180
+ /** @private @author ddj 2026年10月22号 @description Remount only this failed subtree and reevaluate its callbacks. */
11181
+ retry = () => {
11182
+ this.setState({ error: null });
11183
+ };
11184
+ /** @public @author ddj 2026年10月22号 @returns Original subtree or panel-local retry UI. */
11185
+ render() {
11186
+ if (!this.state.error) return react.default.createElement(react.default.Fragment, { key: ownerKey(this.props) }, this.props.children);
11187
+ if (this.props.compact) return react.default.createElement("span", {
11188
+ role: "img",
11189
+ title: "图标加载失败",
11190
+ "aria-label": "图标加载失败"
11191
+ }, "!");
11192
+ return react.default.createElement("div", {
11193
+ className: "edrv-empty",
11194
+ role: "alert"
11195
+ }, this.props.panel.title + ":" + String(this.state.error.message), react.default.createElement("button", { onClick: this.retry }, "重试"));
11196
+ }
11197
+ };
11198
+ /** @public @author ddj 2026年10月22号 @param props Isolated panel invocation below the boundary. @returns Panel subtree. @throws Extension callback error for the nearest boundary. */
11199
+ function PanelContent(props) {
11200
+ const checked = inspectPanel(props.panel, props.ctx);
11201
+ if (checked?.error) throw checked.error;
11202
+ return checked ? props.panel.render(props.ctx) : null;
11203
+ }
11204
+ //#endregion
10913
11205
  //#region src/client/sidebar/SidebarView.ts
10914
11206
  /**
10915
11207
  * dsh-vscode-mode client — 侧边栏容器(活动栏常显 + 可隐藏面板区 + 拖拽调宽)。
@@ -10952,7 +11244,8 @@ window.__ModuleLoader__.load({
10952
11244
  */
10953
11245
  function SidebarView(props) {
10954
11246
  const ctx = props.ctx;
10955
- const panels = (props.registry?.list?.() ?? []).filter((panel) => typeof panel.visible !== "function" || panel.visible(ctx) === true);
11247
+ const checked = (props.registry?.list?.() ?? []).map((panel) => inspectPanel(panel, ctx)).filter(Boolean);
11248
+ const panels = checked.map((entry) => entry.panel);
10956
11249
  const activePanel = props.activePanel;
10957
11250
  const onActive = props.onActive;
10958
11251
  const visible = props.visible !== false;
@@ -10962,26 +11255,71 @@ window.__ModuleLoader__.load({
10962
11255
  const onHide = props.onHide;
10963
11256
  const wMin = Math.max(120, Math.min(W_MAX, Math.round(Number(props.minWidth) || 300)));
10964
11257
  const active = panels.find((p) => p.id === activePanel) || panels[0];
11258
+ const dragCleanup = react.default.useRef(null);
11259
+ /** @private @author ddj 2026年10月22号 @description End the active drag; safe on repeated teardown. */
11260
+ function stopDrag() {
11261
+ dragCleanup.current?.();
11262
+ }
11263
+ const seamOpening = react.default.useRef(false);
11264
+ const lastVisible = react.default.useRef(visible);
11265
+ /** @private @author ddj 2026年10月22号 @description Always release listeners on unmount, including a pending seam expansion. */
11266
+ function dragEffect() {
11267
+ return stopDrag;
11268
+ }
11269
+ react.default.useEffect(dragEffect, []);
11270
+ /** @private @author ddj 2026年10月22号 @description Preserve only the seam's own expansion; external visibility changes cancel ownership. */
11271
+ function visibilityEffect() {
11272
+ if (lastVisible.current !== visible) {
11273
+ if (!(visible && seamOpening.current)) stopDrag();
11274
+ seamOpening.current = false;
11275
+ lastVisible.current = visible;
11276
+ }
11277
+ }
11278
+ react.default.useEffect(visibilityEffect, [visible]);
11279
+ /** @private @author ddj 2026年10月22号 @param pointerId Owning pointer. @param move Pointer movement callback. @description Replace the previous drag and own every exit path. */
11280
+ function bindDrag(pointerId, move) {
11281
+ stopDrag();
11282
+ /** @author ddj 2026年10月22号 @param event Pointer event. @description Ignore another pointer's movement. */
11283
+ function onMove(event) {
11284
+ if (event.pointerId === pointerId) move(event);
11285
+ }
11286
+ /** @author ddj 2026年10月22号 @param event Pointer completion. @description Only the owning pointer ends the drag. */
11287
+ function onEnd(event) {
11288
+ if (event.pointerId === pointerId) cleanup();
11289
+ }
11290
+ /** @author ddj 2026年10月22号 @description Remove all drag listeners and release ownership. */
11291
+ function cleanup() {
11292
+ window.removeEventListener("pointermove", onMove);
11293
+ window.removeEventListener("pointerup", onEnd);
11294
+ window.removeEventListener("pointercancel", onEnd);
11295
+ window.removeEventListener("blur", cleanup);
11296
+ if (dragCleanup.current === cleanup) {
11297
+ dragCleanup.current = null;
11298
+ seamOpening.current = false;
11299
+ }
11300
+ }
11301
+ dragCleanup.current = cleanup;
11302
+ window.addEventListener("pointermove", onMove);
11303
+ window.addEventListener("pointerup", onEnd);
11304
+ window.addEventListener("pointercancel", onEnd);
11305
+ window.addEventListener("blur", cleanup);
11306
+ }
11307
+ /** @private @author ddj 2026年10月22号 @param e Starting pointer. @description Resize the visible panel until completion or collapse. */
10965
11308
  const onPointerDown = (e) => {
10966
11309
  e.preventDefault();
10967
11310
  const startX = e.clientX;
10968
11311
  const startW = width;
10969
- const cleanup = () => {
10970
- window.removeEventListener("pointermove", onMove);
10971
- window.removeEventListener("pointerup", onUp);
10972
- };
10973
- const onUp = () => cleanup();
11312
+ /** @author ddj 2026年10月22号 @param ev Owning pointer movement. @description Apply this drag's width calculation. */
10974
11313
  const onMove = (ev) => {
10975
11314
  const raw = startW + (ev.clientX - startX);
10976
11315
  if (raw < wMin) {
10977
- cleanup();
11316
+ stopDrag();
10978
11317
  onHide();
10979
11318
  return;
10980
11319
  }
10981
11320
  onWidth(Math.min(W_MAX, raw));
10982
11321
  };
10983
- window.addEventListener("pointermove", onMove);
10984
- window.addEventListener("pointerup", onUp);
11322
+ bindDrag(e.pointerId, onMove);
10985
11323
  };
10986
11324
  /**
10987
11325
  * 隐藏态接缝拖拽:向右拖过阈值把面板区拉出来(以记忆宽度展开),继续拖动实时调宽。
@@ -10993,23 +11331,19 @@ window.__ModuleLoader__.load({
10993
11331
  const baseW = Math.max(wMin, Math.min(W_MAX, Math.round(Number(width) || wMin)));
10994
11332
  let expanded = false;
10995
11333
  let expandStartX = 0;
10996
- const cleanup = () => {
10997
- window.removeEventListener("pointermove", onMove);
10998
- window.removeEventListener("pointerup", onUp);
10999
- };
11000
- const onUp = () => cleanup();
11334
+ /** @author ddj 2026年10月22号 @param ev Owning pointer movement. @description Apply this drag's width calculation. */
11001
11335
  const onMove = (ev) => {
11002
11336
  if (!expanded) {
11003
11337
  if (ev.clientX - startX < PULL_OUT_THRESHOLD) return;
11004
11338
  expanded = true;
11005
11339
  expandStartX = ev.clientX;
11340
+ seamOpening.current = true;
11006
11341
  onShow();
11007
11342
  return;
11008
11343
  }
11009
11344
  onWidth(Math.max(wMin, Math.min(W_MAX, baseW + (ev.clientX - expandStartX))));
11010
11345
  };
11011
- window.addEventListener("pointermove", onMove);
11012
- window.addEventListener("pointerup", onUp);
11346
+ bindDrag(e.pointerId, onMove);
11013
11347
  };
11014
11348
  const rail = react.default.createElement("div", { className: "edrv-rail" }, react.default.createElement("div", { style: {
11015
11349
  flex: 1,
@@ -11017,7 +11351,7 @@ window.__ModuleLoader__.load({
11017
11351
  flexDirection: "column",
11018
11352
  gap: 2
11019
11353
  } }, panels.map((p) => {
11020
- const count = typeof p.badge === "function" ? p.badge(ctx) : null;
11354
+ const count = checked.find((entry) => entry.panel === p)?.badge;
11021
11355
  return react.default.createElement("button", {
11022
11356
  key: p.id,
11023
11357
  className: "edrv-rail-btn" + (visible && active && p.id === active.id ? " edrv-rail-on" : ""),
@@ -11026,7 +11360,12 @@ window.__ModuleLoader__.load({
11026
11360
  onActive(p.id);
11027
11361
  if (!visible && typeof onShow === "function") onShow();
11028
11362
  }
11029
- }, react.default.createElement("span", { className: "edrv-rail-icon" }, railIconEl(p.icon)), count > 0 ? react.default.createElement("span", { className: "edrv-rail-badge" }, String(count)) : null);
11363
+ }, react.default.createElement("span", { className: "edrv-rail-icon" }, react.default.createElement(PanelBoundary, {
11364
+ panel: p,
11365
+ scope: ctx?.scope,
11366
+ sessionId: ctx?.sessionId,
11367
+ compact: true
11368
+ }, railIconEl(p.icon))), count > 0 ? react.default.createElement("span", { className: "edrv-rail-badge" }, String(count)) : null);
11030
11369
  })), react.default.createElement("button", {
11031
11370
  className: "edrv-rail-btn",
11032
11371
  title: (visible ? "隐藏面板区" : "展开面板区") + " (Ctrl+B)",
@@ -11038,7 +11377,14 @@ window.__ModuleLoader__.load({
11038
11377
  width: visible ? width : 49,
11039
11378
  maxWidth: "100%"
11040
11379
  }
11041
- }, rail, visible ? react.default.createElement("div", { className: "edrv-side-body" }, active ? active.render(ctx) : null) : null, visible ? react.default.createElement("div", {
11380
+ }, rail, visible ? react.default.createElement("div", { className: "edrv-side-body" }, active ? react.default.createElement(PanelBoundary, {
11381
+ panel: active,
11382
+ scope: ctx?.scope,
11383
+ sessionId: ctx?.sessionId
11384
+ }, react.default.createElement(PanelContent, {
11385
+ panel: active,
11386
+ ctx
11387
+ })) : null) : null, visible ? react.default.createElement("div", {
11042
11388
  className: "edrv-side-resize",
11043
11389
  onPointerDown
11044
11390
  }) : react.default.createElement("div", {
@@ -12216,6 +12562,137 @@ window.__ModuleLoader__.load({
12216
12562
  disposeIfFree(evicted);
12217
12563
  }
12218
12564
  }
12565
+ /** 预热失败后该路径的冷却窗口,避免每次 hover 都重试同一失败目标。 */
12566
+ const WARM_COOLDOWN_MS = 1e4;
12567
+ /** 当前安装的预热实现(EditorView 挂载时注入,未挂载则为 null)。 */
12568
+ let impl = null;
12569
+ /** path → 进行中的预热(单飞:连续 hover 同一目标只读一次盘)。 */
12570
+ const inFlight$1 = /* @__PURE__ */ new Map();
12571
+ /** path → 冷却截止时间戳(毫秒)。 */
12572
+ const cooled = /* @__PURE__ */ new Map();
12573
+ /**
12574
+ * 清空进行中/冷却状态(安装或卸载实现时复位,避免跨实现串味)。
12575
+ * @author ddj 2026年10月20号
12576
+ */
12577
+ function resetWarmerState() {
12578
+ inFlight$1.clear();
12579
+ cooled.clear();
12580
+ }
12581
+ /**
12582
+ * 该路径是否处于失败冷却窗口内(过期的顺手清掉)。
12583
+ * @author ddj 2026年10月20号
12584
+ * @param path 工作区相对路径
12585
+ * @returns 仍在冷却中返回 true
12586
+ */
12587
+ function isCooling(path) {
12588
+ const until = cooled.get(path);
12589
+ if (until === void 0) return false;
12590
+ if (until > Date.now()) return true;
12591
+ cooled.delete(path);
12592
+ return false;
12593
+ }
12594
+ /**
12595
+ * 记录一次失败冷却,并把冷却表裁剪到上限以内。
12596
+ * @author ddj 2026年10月20号
12597
+ * @param path 工作区相对路径
12598
+ */
12599
+ function coolDown(path) {
12600
+ cooled.delete(path);
12601
+ cooled.set(path, Date.now() + WARM_COOLDOWN_MS);
12602
+ while (cooled.size > 64) {
12603
+ const oldest = cooled.keys().next();
12604
+ if (oldest.done) break;
12605
+ cooled.delete(oldest.value);
12606
+ }
12607
+ }
12608
+ /**
12609
+ * 结算一次预热:清 inFlight(仅当仍是自己那条)、按结果维护冷却。
12610
+ * @author ddj 2026年10月20号
12611
+ * @param path 工作区相对路径
12612
+ * @param run 该次预热的 promise(用于识别是否已被更新的一次替换)
12613
+ * @param ok 实现返回的成功与否
12614
+ */
12615
+ function settleWarm(path, run, ok) {
12616
+ if (inFlight$1.get(path) === run) inFlight$1.delete(path);
12617
+ if (ok) {
12618
+ cooled.delete(path);
12619
+ return;
12620
+ }
12621
+ coolDown(path);
12622
+ }
12623
+ /**
12624
+ * 启动一次底层预热:登记 inFlight、把实现异常折叠成 false(绝不向调用方抛)。
12625
+ * @author ddj 2026年10月20号
12626
+ * @param path 工作区相对路径
12627
+ * @param fn 预热实现
12628
+ * @returns 永不 reject 的布尔 promise(已登记到 inFlight)
12629
+ */
12630
+ function startWarm(path, fn) {
12631
+ const run = Promise.resolve().then(() => fn(path)).then((ok) => ok === true, () => false);
12632
+ inFlight$1.set(path, run);
12633
+ run.then((ok) => settleWarm(path, run, ok));
12634
+ return run;
12635
+ }
12636
+ /**
12637
+ * 给底层预热挂一个等待上限:超时只是本次调用放行,底层继续跑(仍可能建成 model
12638
+ * 惠及下一次 hover),因此超时既不取消 promise,也不提前摘掉 inFlight。
12639
+ * @author ddj 2026年10月20号
12640
+ * @param path 工作区相对路径
12641
+ * @param run 底层预热 promise(永不 reject)
12642
+ * @returns 最多 WARM_TIMEOUT_MS 内结算的布尔结果
12643
+ */
12644
+ function withTimeout(path, run) {
12645
+ return new Promise((resolve) => {
12646
+ let settled = false;
12647
+ let timer = null;
12648
+ const finish = (ok) => {
12649
+ if (settled) return;
12650
+ settled = true;
12651
+ if (timer) clearTimeout(timer);
12652
+ resolve(ok);
12653
+ };
12654
+ timer = setTimeout(() => {
12655
+ coolDown(path);
12656
+ finish(false);
12657
+ }, 400);
12658
+ run.then(finish, () => finish(false));
12659
+ });
12660
+ }
12661
+ /**
12662
+ * 安装预热实现(EditorView 挂载时调用;传 null 视同卸载)。
12663
+ * @author ddj 2026年10月20号
12664
+ * @param fn 预热实现,或 null 表示卸载
12665
+ */
12666
+ function setModelWarmer(fn) {
12667
+ impl = typeof fn === "function" ? fn : null;
12668
+ if (!impl) resetWarmerState();
12669
+ }
12670
+ /**
12671
+ * 卸载预热实现:仅当传入的仍是当前实现才清(防卸载竞态误清新挂载的实现)。
12672
+ * @author ddj 2026年10月20号
12673
+ * @param fn 当初安装的实现
12674
+ */
12675
+ function clearModelWarmer(fn) {
12676
+ if (impl !== fn) return;
12677
+ impl = null;
12678
+ resetWarmerState();
12679
+ }
12680
+ /**
12681
+ * 确保目标路径的 Monaco model 存在(供 hover 定义预览的 createModelReference 命中)。
12682
+ *
12683
+ * 降级语义:未安装实现(编辑器页签未挂载)、路径为空、处于失败冷却、实现失败或超时,
12684
+ * 一律返回 false 且不抛异常——此时行为等价于修复前(预览不显示,但不再产生未捕获异常)。
12685
+ * @author ddj 2026年10月20号
12686
+ * @param path 工作区相对路径(或绝对路径,由实现决定如何解析)
12687
+ * @returns 预热成功返回 true;被跳过/失败/超时返回 false
12688
+ */
12689
+ function warmModelFor(path) {
12690
+ if (!path || !impl) return Promise.resolve(false);
12691
+ const running = inFlight$1.get(path);
12692
+ if (running) return withTimeout(path, running);
12693
+ if (isCooling(path)) return Promise.resolve(false);
12694
+ return withTimeout(path, startWarm(path, impl));
12695
+ }
12219
12696
  //#endregion
12220
12697
  //#region src/client/navHighlight.ts
12221
12698
  /**
@@ -13841,7 +14318,8 @@ window.__ModuleLoader__.load({
13841
14318
  * 交互层:edrv.goToDefinition(F12)/ edrv.findReferences(Shift+F12 / 右键)均走 Monaco 原生命令;
13842
14319
  * Ctrl+点击使用处交给原生;Lua 表枚举字段声明处改为打开引用 Peek,
13843
14320
  * 数据经 host 的 LSP RPC 提供(registerEditorOpener 接管 edrv:// 点击跳转)。
13844
- * 作者 ddj 2026-08-27 / 2026-09-11
14321
+ * 定义结果返回前预热目标 model,避免 hover 定义链接预览抛 "Model not found"。
14322
+ * 作者 ddj 2026-08-27 / 2026-09-11 / 2026-10-20
13845
14323
  */
13846
14324
  const LSP_LANGS = ["lua", "csharp"];
13847
14325
  /**
@@ -13916,6 +14394,28 @@ window.__ModuleLoader__.load({
13916
14394
  endColumn
13917
14395
  };
13918
14396
  }
14397
+ /**
14398
+ * 预热定义目标文件的 Monaco model(供 Ctrl+hover 定义链接预览使用)。
14399
+ *
14400
+ * 为什么需要:Monaco 的 GotoDefinitionAtPositionEditorContribution 在渲染定义链接预览时,
14401
+ * 对目标 uri 调 `createModelReference` 后**只挂 then、没有 catch**;本插件的定义目标是
14402
+ * 自造的 `edrv://` uri,目标文件若从未打开过就没有已注册 model,`createModelReference`
14403
+ * 会 reject("Model not found") 变成未捕获的 promise 异常(弹到 Console)。
14404
+ * 本 vendored Monaco 也没有 `registerTextModelContentProvider` 可惰性喂内容,故只能提前建模型。
14405
+ *
14406
+ * 只预热单一定义:多定义分支(`locations.length > 1`)Monaco 走列表、不调
14407
+ * `createModelReference`,没必要读盘。预热纯属增强,任何异常都在此吞掉,
14408
+ * 不得让跳转查询失败(F12 与 Ctrl+点击不依赖模型是否已建)。
14409
+ * @author ddj 2026年10月20号
14410
+ * @param locations findDefinition 的客户端结果(含 edrv uri)
14411
+ */
14412
+ async function prewarmDefinitionTarget(locations) {
14413
+ if (!Array.isArray(locations) || locations.length !== 1) return;
14414
+ try {
14415
+ const target = lspUriToAbs(locations[0]?.uri);
14416
+ if (target) await warmModelFor(target);
14417
+ } catch (error) {}
14418
+ }
13919
14419
  /** 注册全部 Monaco LSP provider 与文档跟踪(幂等)。 */
13920
14420
  function registerLspProviders(monaco) {
13921
14421
  if (registered$3) return;
@@ -13964,10 +14464,12 @@ window.__ModuleLoader__.load({
13964
14464
  if (!path) return false;
13965
14465
  openAt(path, navTargetOf(selectionOrPosition));
13966
14466
  return true;
13967
- } }), monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: (model, position, token) => {
14467
+ } }), monaco.languages.registerDefinitionProvider(LSP_LANGS, { provideDefinition: async (model, position, token) => {
13968
14468
  const path = pathOfModel(model);
13969
14469
  if (!path) return [];
13970
- return findDefinition(path, model.getValue(), position);
14470
+ const locations = await findDefinition(path, model.getValue(), position);
14471
+ await prewarmDefinitionTarget(locations);
14472
+ return locations;
13971
14473
  } }), monaco.languages.registerReferenceProvider(LSP_LANGS, { provideReferences: (model, position, context, token) => {
13972
14474
  const path = pathOfModel(model);
13973
14475
  if (!path) return [];
@@ -22187,7 +22689,7 @@ window.__ModuleLoader__.load({
22187
22689
  }
22188
22690
  //#endregion
22189
22691
  //#region src/client/dap/toolbarDrag.ts
22190
- const KEY_PREFIX$3 = "edrv.dap.toolbar.";
22692
+ const KEY_PREFIX$2 = "edrv.dap.toolbar.";
22191
22693
  /** 将工具条位置限制在编辑区内。 */
22192
22694
  function clampToolbarPosition(point, bounds) {
22193
22695
  const margin = bounds.margin ?? 6;
@@ -22202,7 +22704,7 @@ window.__ModuleLoader__.load({
22202
22704
  function loadToolbarPosition(scope) {
22203
22705
  if (!scope) return null;
22204
22706
  try {
22205
- const raw = localStorage.getItem(KEY_PREFIX$3 + scope);
22707
+ const raw = localStorage.getItem(KEY_PREFIX$2 + scope);
22206
22708
  if (!raw) return null;
22207
22709
  const value = JSON.parse(raw);
22208
22710
  if (!value || !Number.isFinite(value.left) || !Number.isFinite(value.top)) return null;
@@ -22218,7 +22720,7 @@ window.__ModuleLoader__.load({
22218
22720
  function saveToolbarPosition(scope, point) {
22219
22721
  if (!scope) return;
22220
22722
  try {
22221
- localStorage.setItem(KEY_PREFIX$3 + scope, JSON.stringify(point));
22723
+ localStorage.setItem(KEY_PREFIX$2 + scope, JSON.stringify(point));
22222
22724
  } catch {}
22223
22725
  }
22224
22726
  //#endregion
@@ -22410,6 +22912,7 @@ window.__ModuleLoader__.load({
22410
22912
  const planActiveRef = react.default.useRef(null);
22411
22913
  planActiveRef.current = planActive;
22412
22914
  const planHostRef = react.default.useRef(null);
22915
+ const planReturnRef = react.default.useRef(null);
22413
22916
  react.default.useEffect(() => {
22414
22917
  setPlanTabs([]);
22415
22918
  setPlanActive(null);
@@ -22424,6 +22927,7 @@ window.__ModuleLoader__.load({
22424
22927
  const [layoutScope, setLayoutScope] = react.default.useState(null);
22425
22928
  const benchRef = react.default.useRef(workbench);
22426
22929
  benchRef.current = workbench;
22930
+ const rowOwner = workbench.floats.some((item) => item.group === workbench.focused) ? MAIN_GROUP : workbench.focused;
22427
22931
  const [dragPreview, setDragPreview] = react.default.useState(null);
22428
22932
  const dragRef = react.default.useRef(null);
22429
22933
  const benchOps = react.default.useRef(null);
@@ -22466,6 +22970,15 @@ window.__ModuleLoader__.load({
22466
22970
  const aiStatusTimer = react.default.useRef(null);
22467
22971
  const [error, setError] = react.default.useState(null);
22468
22972
  const [loadError, setLoadError] = react.default.useState(null);
22973
+ const [largeCandidate, setLargeCandidate] = react.default.useState(null);
22974
+ const [pagedFile, setPagedFile] = react.default.useState(null);
22975
+ const [pageChanged, setPageChanged] = react.default.useState(null);
22976
+ const pageIdentity = JSON.stringify([
22977
+ scope,
22978
+ sessionId,
22979
+ active
22980
+ ]);
22981
+ const paging = pagedFile === pageIdentity;
22469
22982
  const [loadStage, setLoadStage] = react.default.useState({
22470
22983
  progress: 0,
22471
22984
  message: "准备加载编辑器…"
@@ -23178,6 +23691,9 @@ window.__ModuleLoader__.load({
23178
23691
  status: "loading"
23179
23692
  });
23180
23693
  setLoadError(null);
23694
+ setLargeCandidate(null);
23695
+ setPagedFile(null);
23696
+ setPageChanged(null);
23181
23697
  /**
23182
23698
  * 记下磁盘版本基线;markRead=true 表示本次真的从磁盘读了内容
23183
23699
  * (已读版本台账随之失效,下一轮轮询按新版本重新比对)。
@@ -23234,6 +23750,11 @@ window.__ModuleLoader__.load({
23234
23750
  }));
23235
23751
  setStatus("已加载");
23236
23752
  } else {
23753
+ if (res?.code === "file-too-large") setLargeCandidate(JSON.stringify([
23754
+ scope,
23755
+ sid,
23756
+ path
23757
+ ]));
23237
23758
  const base = res?.error ? String(res.error) : "读取失败";
23238
23759
  dapTrace("read-failed", {
23239
23760
  path,
@@ -23414,7 +23935,7 @@ window.__ModuleLoader__.load({
23414
23935
  * @author ddj 2026年09月15号
23415
23936
  * @param change 轮询判定出的外部变更
23416
23937
  */
23417
- const onDiskChange = (change) => {
23938
+ function onDiskChange(change) {
23418
23939
  if (!change || !change.path) return;
23419
23940
  const eventId = emitFileChanged(change.path, {
23420
23941
  scope,
@@ -23443,6 +23964,18 @@ window.__ModuleLoader__.load({
23443
23964
  }).catch(() => {});
23444
23965
  return;
23445
23966
  }
23967
+ if (change.kind === "modified" && (paging || pageChanged === pageIdentity) && change.path === activeRef.current) {
23968
+ loadContent(change.path, sessionId, true);
23969
+ setPageChanged(pageIdentity);
23970
+ setStatus("文件已变化,请重新读取");
23971
+ emitRefresh({
23972
+ scope,
23973
+ paths: [change.path],
23974
+ kind: "text",
23975
+ eventId
23976
+ });
23977
+ return;
23978
+ }
23446
23979
  if (change.kind === "modified") {
23447
23980
  loadContent(change.path, sessionId, true);
23448
23981
  clearSync(scope, change.path);
@@ -23463,7 +23996,7 @@ window.__ModuleLoader__.load({
23463
23996
  at: Date.now()
23464
23997
  });
23465
23998
  setStatus(kind === "deleted" ? "文件已被外部删除" : "外部已修改(未保存的编辑保留)");
23466
- };
23999
+ }
23467
24000
  diskChangeRef.current = onDiskChange;
23468
24001
  /**
23469
24002
  * 版本变化时读磁盘并与缓冲比对:内容相同 → 只推进基线(不做无意义重载);
@@ -23472,7 +24005,8 @@ window.__ModuleLoader__.load({
23472
24005
  * @param path 文件路径
23473
24006
  * @returns 比对结果;模型不可用/读失败 → null(按「需要重载」处理)
23474
24007
  */
23475
- const readDiskCompare = (path) => {
24008
+ function readDiskCompare(path) {
24009
+ if (path === activeRef.current && (paging || pageChanged === pageIdentity)) return Promise.resolve({ equal: false });
23476
24010
  const model = modelsRef.current?.get(path);
23477
24011
  if (!model || typeof model.getValue !== "function") return Promise.resolve(null);
23478
24012
  return rpc("edrv.read", {
@@ -23482,7 +24016,7 @@ window.__ModuleLoader__.load({
23482
24016
  if (!res || !res.ok) return null;
23483
24017
  return { equal: res.content === model.getValue() };
23484
24018
  }).catch(() => null);
23485
- };
24019
+ }
23486
24020
  useFileWatch({
23487
24021
  sessionId,
23488
24022
  scope,
@@ -24094,8 +24628,14 @@ window.__ModuleLoader__.load({
24094
24628
  const right = left + el.offsetWidth;
24095
24629
  if (left < host.scrollLeft) host.scrollLeft = left;
24096
24630
  else if (right > host.scrollLeft + host.clientWidth) host.scrollLeft = right - host.clientWidth;
24097
- }, [active, tabs.length]);
24098
- react.default.useEffect(() => attachHorizontalWheel(tabsHostRef.current), []);
24631
+ }, [
24632
+ active,
24633
+ planActive,
24634
+ planTabs.length,
24635
+ tabs.length,
24636
+ rowOwner
24637
+ ]);
24638
+ react.default.useEffect(() => attachHorizontalWheel(tabsHostRef.current), [rowOwner]);
24099
24639
  react.default.useEffect(() => {
24100
24640
  const onPointer = (e) => {
24101
24641
  if (e?.button !== 3 && e?.button !== 4) return;
@@ -24295,6 +24835,41 @@ window.__ModuleLoader__.load({
24295
24835
  }
24296
24836
  return model;
24297
24837
  };
24838
+ const warmRef = react.default.useRef(null);
24839
+ /**
24840
+ * 让目标文件的 Monaco model 存在(预热入口,供 Ctrl+hover 定义链接预览)。
24841
+ *
24842
+ * 为什么需要:Monaco 在渲染定义链接的源码预览时对目标 uri 调 `createModelReference`
24843
+ * 后只挂 then、没有 catch;本插件的定义目标是自造 `edrv://` uri,目标文件若从未打开过
24844
+ * 就没有已注册 model,会 reject("Model not found") 成为未捕获异常(见 state/modelWarmer.ts)。
24845
+ * 已缓存的路径直接返回成功:模型已在 Monaco 注册表里,无需再读盘。
24846
+ * 预热出的 model 走 modelsRef 常规 LRU(40 条、未附着超限被 dispose);正式打开该文件时
24847
+ * 由 loadContent 的 cached-model 分支 + checkCached 后台校验接管,不会显示陈旧内容。
24848
+ * @private
24849
+ * @author ddj 2026年10月20号
24850
+ * @param path 工作区相对路径
24851
+ * @returns 模型是否已存在/成功建出(失败一律 false,绝不抛)
24852
+ */
24853
+ warmRef.current = (path) => {
24854
+ if (!path) return Promise.resolve(false);
24855
+ if (modelsRef.current.get(path)) return Promise.resolve(true);
24856
+ if (isImagePath(path) || isPdfPath(path)) return Promise.resolve(false);
24857
+ const sid = sessionId;
24858
+ if (!sid) return Promise.resolve(false);
24859
+ return rpc("edrv.read", {
24860
+ sessionId: sid,
24861
+ path
24862
+ }).then((res) => {
24863
+ if (!res?.ok || typeof res.content !== "string") return false;
24864
+ getModel(path, res.content);
24865
+ return true;
24866
+ }).catch(() => false);
24867
+ };
24868
+ react.default.useEffect(() => {
24869
+ const warmImpl = (path) => warmRef.current?.(path) ?? Promise.resolve(false);
24870
+ setModelWarmer(warmImpl);
24871
+ return () => clearModelWarmer(warmImpl);
24872
+ }, []);
24298
24873
  /**
24299
24874
  * 提交待执行的防抖保存(**真正执行保存**,不是取消)。
24300
24875
  * 语义与缺陷背景见 saveDebounce.ts:`schedule` 的返回值是只 clearTimeout 的 disposer,
@@ -26006,6 +26581,7 @@ window.__ModuleLoader__.load({
26006
26581
  if (!document) return;
26007
26582
  flushSave();
26008
26583
  saveViewState(active);
26584
+ if (!planActiveRef.current) planReturnRef.current = active;
26009
26585
  setDiskActive(null);
26010
26586
  setPlanTabs((prev) => [...prev.filter((item) => item.address !== document.address), document].slice(-32));
26011
26587
  setPlanActive(document.address);
@@ -26016,7 +26592,10 @@ window.__ModuleLoader__.load({
26016
26592
  function closePlan(address, event) {
26017
26593
  event?.stopPropagation();
26018
26594
  setPlanTabs((prev) => prev.filter((item) => item.address !== address));
26019
- if (planActiveRef.current === address) setActive(tabsRef.current[0]?.path ?? null);
26595
+ if (planActiveRef.current !== address) return;
26596
+ const previous = planReturnRef.current;
26597
+ planReturnRef.current = null;
26598
+ setActive(tabsRef.current.some((tab) => tab.path === previous) ? previous : tabsRef.current[0]?.path ?? null);
26020
26599
  }
26021
26600
  /** @private @author ddj 2026年10月08号 @param request Claimed disk or virtual open. Flushes saves without replacing dirty models. */
26022
26601
  function openReceiptFile(request) {
@@ -26147,7 +26726,8 @@ window.__ModuleLoader__.load({
26147
26726
  window.removeEventListener("edrv.command.debugStop", onStop);
26148
26727
  };
26149
26728
  }, []);
26150
- const openPath = () => {
26729
+ /** @private @author ddj 2026年10月22号 @description Probe a typed path; let oversized files enter the existing read-only paging flow. @returns Probe completion. */
26730
+ function openPath() {
26151
26731
  const p = (pathDraft || "").trim();
26152
26732
  if (!p) return;
26153
26733
  if (isImagePath(p) || isPdfPath(p)) {
@@ -26159,11 +26739,11 @@ window.__ModuleLoader__.load({
26159
26739
  return;
26160
26740
  }
26161
26741
  setStatus("打开中…");
26162
- rpc("edrv.read", {
26742
+ return rpc("edrv.read", {
26163
26743
  sessionId,
26164
26744
  path: p
26165
26745
  }).then((res) => {
26166
- if (res && res.ok) {
26746
+ if (res && (res.ok || res.code === "file-too-large")) {
26167
26747
  openFile(p, false);
26168
26748
  setOpenInput(false);
26169
26749
  setPathDraft("");
@@ -26177,7 +26757,7 @@ window.__ModuleLoader__.load({
26177
26757
  setStatus("打开失败");
26178
26758
  setError("打开异常:" + String(e));
26179
26759
  });
26180
- };
26760
+ }
26181
26761
  /** @private @author ddj 2026年10月14号 @param group Group id. @param path Optional selection. Route global actions only to this group. */
26182
26762
  function selectGroup(group, path) {
26183
26763
  saveViewState();
@@ -26359,9 +26939,21 @@ window.__ModuleLoader__.load({
26359
26939
  host.removeEventListener("contextmenu", onCtx);
26360
26940
  };
26361
26941
  }
26942
+ /** @private @author ddj 2026年10月22号 @param args Read-only page request. @returns Current Host capability result. */
26943
+ function loadTextPage(args) {
26944
+ return rpc("edrv.textPage", args);
26945
+ }
26946
+ /** @private @author ddj 2026年10月22号 @description Never replace a dirty or saving document with partial text. */
26947
+ function openTextPages() {
26948
+ if (largeCandidate !== pageIdentity || dirtyRef.current[active] || savingRef.current.has(active)) return;
26949
+ flushSave();
26950
+ if (dirtyRef.current[active] || savingRef.current.has(active)) return;
26951
+ setPageChanged(null);
26952
+ setPagedFile(pageIdentity);
26953
+ }
26362
26954
  /** @private @author ddj 2026年10月16号 @param host Passive body. @param path Canonical file. @param groupId Owning group id(find 快照按组隔离). @returns View-only cleanup, models remain workbench-owned. */
26363
26955
  function mountPassive(host, path, groupId) {
26364
- let disposed = false, keeper = null, subscription = null, observer = null;
26956
+ let disposed = false, keeper = null, subscription = null, observer = null, pageRoot = null;
26365
26957
  const focus = () => {
26366
26958
  const owner = ownerOf(benchRef.current, path);
26367
26959
  if (owner) selectGroup(owner.id, path);
@@ -26388,6 +26980,27 @@ window.__ModuleLoader__.load({
26388
26980
  host.textContent = "";
26389
26981
  if (!result?.ok) {
26390
26982
  host.textContent = result?.error || "文件加载失败";
26983
+ if (result?.code === "file-too-large" && !binary) {
26984
+ const button = document.createElement("button");
26985
+ button.textContent = "只读分段查看";
26986
+ /** @author ddj 2026年10月22号 @param event Pointer event. @description Do not focus and unmount the passive group before click. */
26987
+ button.onpointerdown = (event) => event.stopPropagation();
26988
+ /** @author ddj 2026年10月22号 @description Keep partial text out of passive models, autosave and editing commands. */
26989
+ button.onclick = () => {
26990
+ if (disposed || dirtyRef.current[path] || savingRef.current.has(path)) return;
26991
+ flushSave();
26992
+ if (dirtyRef.current[path] || savingRef.current.has(path)) return;
26993
+ host.textContent = "";
26994
+ host.removeEventListener("pointerdown", focus);
26995
+ pageRoot = (0, react_dom_client.createRoot)(host);
26996
+ pageRoot.render(react.default.createElement(LargeTextView, {
26997
+ sessionId,
26998
+ path,
26999
+ loadPage: loadTextPage
27000
+ }));
27001
+ };
27002
+ host.appendChild(button);
27003
+ }
26391
27004
  return;
26392
27005
  }
26393
27006
  if (binary) {
@@ -26438,6 +27051,7 @@ window.__ModuleLoader__.load({
26438
27051
  });
26439
27052
  return () => {
26440
27053
  disposed = true;
27054
+ pageRoot?.unmount();
26441
27055
  host.removeEventListener("pointerdown", focus);
26442
27056
  observer?.disconnect();
26443
27057
  subscription?.dispose();
@@ -26472,15 +27086,21 @@ window.__ModuleLoader__.load({
26472
27086
  }));
26473
27087
  }
26474
27088
  /** @private @author ddj 2026年10月14号 @param id Leaf id. @returns Tabs and either focused full-feature editor or independent passive view. */
26475
- function renderGroup(id) {
27089
+ function renderGroup(id, floating) {
26476
27090
  const group = workbench.groups.find((item) => item.id === id);
26477
27091
  if (!group) return null;
26478
27092
  const focused = id === workbench.focused;
27093
+ const chrome = !floating && id === rowOwner;
27094
+ const hideDiskActive = Boolean(planActive) && (chrome || focused);
26479
27095
  return react.default.createElement(EditorPane, {
26480
27096
  key: id,
26481
27097
  group,
26482
27098
  focused,
26483
27099
  dirty: dirtyMap,
27100
+ suppressActive: hideDiskActive,
27101
+ tabsRef: chrome ? tabsHostRef : void 0,
27102
+ extras: chrome ? planTabExtras : void 0,
27103
+ tools: chrome ? rowTools : void 0,
26484
27104
  onSelect: selectGroup,
26485
27105
  onClose: closeTab,
26486
27106
  onMenu: paneMenu,
@@ -26493,7 +27113,11 @@ window.__ModuleLoader__.load({
26493
27113
  path: group.active,
26494
27114
  onFocus: () => selectGroup(id)
26495
27115
  }), react.default.createElement(PassiveEditor, {
26496
- key: String(scope) + ":" + group.active,
27116
+ key: JSON.stringify([
27117
+ scope,
27118
+ sessionId,
27119
+ group.active
27120
+ ]),
26497
27121
  path: group.active,
26498
27122
  groupId: id,
26499
27123
  mount: passiveMount
@@ -26510,27 +27134,17 @@ window.__ModuleLoader__.load({
26510
27134
  onRatio: (ratio) => changeRatio(node, ratio)
26511
27135
  });
26512
27136
  }
26513
- const tabsEl = react.default.createElement("div", {
26514
- className: "edrv-tabs",
26515
- ref: tabsHostRef,
26516
- style: {
26517
- flex: "1 1 auto",
26518
- minWidth: 0
26519
- }
26520
- }, planTabs.map((plan) => react.default.createElement("div", {
27137
+ const planTabExtras = planTabs.length ? planTabs.map((plan) => react.default.createElement("div", {
26521
27138
  key: plan.address,
26522
- className: "edrv-tab" + (plan.address === planActive ? " edrv-tab-active" : ""),
27139
+ className: "edrv-tab edrv-tab-plan" + (plan.address === planActive ? " edrv-tab-active" : ""),
26523
27140
  title: plan.title + "(计划预览 · 只读)",
26524
27141
  onClick: () => openPlanDoc(plan)
26525
- }, react.default.createElement("span", { style: {
26526
- overflow: "hidden",
26527
- textOverflow: "ellipsis",
26528
- maxWidth: 180
26529
- } }, plan.title), react.default.createElement("span", {
27142
+ }, react.default.createElement("span", { className: "edrv-group-tab-name" }, plan.title), react.default.createElement("span", {
26530
27143
  className: "edrv-tab-x",
26531
27144
  title: "关闭",
26532
27145
  onClick: (event) => closePlan(plan.address, event)
26533
- }, "×"))), openInput ? react.default.createElement("input", {
27146
+ }, "×"))) : null;
27147
+ const openControl = openInput ? react.default.createElement("input", {
26534
27148
  className: "edrv-path-input",
26535
27149
  autoFocus: true,
26536
27150
  placeholder: "输入工作区相对/绝对路径,回车打开",
@@ -26544,7 +27158,7 @@ window.__ModuleLoader__.load({
26544
27158
  className: "edrv-tab-add",
26545
27159
  title: "打开文件(输入路径)",
26546
27160
  onClick: () => setOpenInput(true)
26547
- }, "+"));
27161
+ }, "+");
26548
27162
  const activePlan = planTabs.find((plan) => plan.address === planActive);
26549
27163
  const pathBar = activePlan ? react.default.createElement("div", {
26550
27164
  className: "edrv-pathbar",
@@ -26623,12 +27237,7 @@ window.__ModuleLoader__.load({
26623
27237
  const navBackTarget = navRef.current.peekBack();
26624
27238
  const navForwardTarget = navRef.current.peekForward();
26625
27239
  const navNameOf = (entry) => entry && entry.path ? String(entry.path).split(/[\\/]/).pop() : "";
26626
- const tabRow = react.default.createElement("div", { style: {
26627
- display: "flex",
26628
- alignItems: "center",
26629
- background: "var(--dsw-alias-bg-layer-1,transparent)",
26630
- flexShrink: 0
26631
- } }, tabsEl, sum.totalFiles > 0 ? react.default.createElement("button", {
27240
+ const rowTools = react.default.createElement("div", { className: "edrv-tabs-tools" }, openControl, sum.totalFiles > 0 ? react.default.createElement("button", {
26632
27241
  className: "edrv-diffchip",
26633
27242
  title: "打开差异总览/归档",
26634
27243
  onClick: () => setLauncherOpen((o) => !o)
@@ -26734,7 +27343,20 @@ window.__ModuleLoader__.load({
26734
27343
  ref: planHostRef,
26735
27344
  "data-plan-address": activePlan.address
26736
27345
  }, react.default.createElement("div", { className: "edrv-mdview-stage" }, react.default.createElement("div", { className: "edrv-mdview-doc" }, render)));
26737
- } else if (!monaco && !monacoErr) body = loadingBody(loadStage.message, loadStage.progress);
27346
+ } else if (pageChanged === pageIdentity && largeCandidate === pageIdentity) body = react.default.createElement("div", {
27347
+ className: "edrv-empty",
27348
+ role: "status"
27349
+ }, react.default.createElement("div", null, "文件已变化,请重新读取"), react.default.createElement("button", {
27350
+ className: "edrv-pill edrv-pill-ghost",
27351
+ onClick: openTextPages
27352
+ }, "重新读取首段"));
27353
+ else if (paging) body = react.default.createElement(LargeTextView, {
27354
+ key: pageIdentity,
27355
+ sessionId,
27356
+ path: active,
27357
+ loadPage: loadTextPage
27358
+ });
27359
+ else if (!monaco && !monacoErr) body = loadingBody(loadStage.message, loadStage.progress);
26738
27360
  else if (monacoErr) body = react.default.createElement("div", { className: "edrv-empty" }, react.default.createElement("div", null, "Monaco 编辑器加载失败:" + String(monacoErr)), react.default.createElement("div", { style: { fontSize: 11 } }, "请确认插件包 assets/vendor/monaco 完整(/edrv/vendor 路由可达)"), react.default.createElement("button", {
26739
27361
  className: "edrv-pill edrv-pill-ghost",
26740
27362
  onClick: () => {
@@ -26766,7 +27388,7 @@ window.__ModuleLoader__.load({
26766
27388
  loadContent(active, sessionId);
26767
27389
  }
26768
27390
  }, "重试"));
26769
- else if (content === null && loadError) body = loadingBody("文件加载失败:" + loadError, 0, () => {
27391
+ else if (content === null && loadError) body = react.default.createElement(react.default.Fragment, null, loadingBody("文件加载失败:" + loadError, 0, () => {
26770
27392
  setLoadError(null);
26771
27393
  setContent(null);
26772
27394
  setContentPath(null);
@@ -26775,7 +27397,10 @@ window.__ModuleLoader__.load({
26775
27397
  message: "重新读取文件…"
26776
27398
  });
26777
27399
  loadContent(active, sessionId);
26778
- });
27400
+ }), largeCandidate === pageIdentity && react.default.createElement("button", {
27401
+ className: "edrv-pill edrv-pill-ghost",
27402
+ onClick: openTextPages
27403
+ }, "只读分段查看"));
26779
27404
  else if (isImageActive && imageSrc) body = imagePanel();
26780
27405
  else if (isImageActive) body = loadingBody(loadStage.message || "读取图片…", loadStage.progress);
26781
27406
  else if (isPdfActive && pdfBytes) body = react.default.createElement("div", {
@@ -27119,7 +27744,7 @@ window.__ModuleLoader__.load({
27119
27744
  flexDirection: "column",
27120
27745
  overflow: "hidden"
27121
27746
  }
27122
- }, react.default.createElement("div", { className: "edrv-workbench-tools" }, tabRow), renderTree(workbench.tree), fullView, statusBar, workbench.floats.map((item, layer) => react.default.createElement(FloatingEditor, {
27747
+ }, renderTree(workbench.tree), fullView, statusBar, workbench.floats.map((item, layer) => react.default.createElement(FloatingEditor, {
27123
27748
  key: item.group,
27124
27749
  owner: String(scope),
27125
27750
  group: item.group,
@@ -27129,7 +27754,7 @@ window.__ModuleLoader__.load({
27129
27754
  onBounds: floatBounds,
27130
27755
  onFocus: selectGroup,
27131
27756
  onReturn: returnPane
27132
- }, renderGroup(item.group))), dragPreview ? (0, react_dom.createPortal)(react.default.createElement(react.default.Fragment, null, dragPreview.rect ? react.default.createElement("div", {
27757
+ }, renderGroup(item.group, true))), dragPreview ? (0, react_dom.createPortal)(react.default.createElement(react.default.Fragment, null, dragPreview.rect ? react.default.createElement("div", {
27133
27758
  className: "edrv-drag-overlay",
27134
27759
  style: dragPreview.rect
27135
27760
  }, react.default.createElement("span", { className: "edrv-drop-label" }, dragPreview.label)) : null, react.default.createElement("div", {
@@ -27744,6 +28369,24 @@ window.__ModuleLoader__.load({
27744
28369
  return config;
27745
28370
  }
27746
28371
  //#endregion
28372
+ //#region src/shared/mcp.ts
28373
+ /** @public @author ddj 2026年10月20号 @param input Project-only policy. @returns Validated defaults. @throws Invalid explicit policy. */
28374
+ function parseMcpPolicy(input) {
28375
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) throw new Error("vscodeMode 必须是对象");
28376
+ const policy = input ?? {};
28377
+ const startup = policy.startup ?? "on-demand";
28378
+ const sharing = policy.sharing ?? "agent";
28379
+ const startupTimeoutMs = policy.startupTimeoutMs ?? 6e4;
28380
+ if (!["on-demand", "background"].includes(startup)) throw new Error("不支持的 MCP startup");
28381
+ if (!["agent", "workspace"].includes(sharing)) throw new Error("不支持的 MCP sharing");
28382
+ if (!Number.isInteger(startupTimeoutMs) || startupTimeoutMs < 1e3 || startupTimeoutMs > 3e5) throw new Error("MCP startupTimeoutMs 必须为 1000–300000 的整数");
28383
+ return {
28384
+ startup,
28385
+ sharing,
28386
+ startupTimeoutMs
28387
+ };
28388
+ }
28389
+ //#endregion
27747
28390
  //#region src/client/mcpState.ts
27748
28391
  /** Owns MCP read lifetimes and one synchronous mutation lock for the entire management page. */
27749
28392
  var McpState = class {
@@ -28712,7 +29355,7 @@ window.__ModuleLoader__.load({
28712
29355
  if (hasStoredConfig(config[status.languageId])) return true;
28713
29356
  return Array.isArray(status.missingEnv) && status.missingEnv.length > 0;
28714
29357
  }
28715
- const PHASE_LABEL$1 = {
29358
+ const PHASE_LABEL = {
28716
29359
  idle: "未启动",
28717
29360
  starting: "启动中",
28718
29361
  ready: "就绪",
@@ -28766,7 +29409,7 @@ window.__ModuleLoader__.load({
28766
29409
  className: "vsm-switch " + (config?.enabled !== false ? "on" : ""),
28767
29410
  onClick: () => onToggle(lang.id, config?.enabled !== false ? false : true),
28768
29411
  "aria-label": "启用/禁用"
28769
- }, config?.enabled !== false ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, PHASE_LABEL$1[phase] ?? phase, " · ", SOURCE_LABEL[status?.source] ?? status?.source, status?.providerName ? " · " + status.providerName : "", status?.version ? " · v" + status.version : "", phase === "ready" && status?.root ? " · " + String(status.root).split(/[\\/]/).pop() : ""), status?.reason ? react.default.createElement("div", { className: "vsm-mcp-error" }, status.reason) : null, Array.isArray(status?.candidates) && status.candidates.length > 1 ? react.default.createElement("div", { className: "vsm-lsp-hint" }, "候选(" + status.candidates.length + "):" + status.candidates.map((c) => c.name + (c.version ? " v" + c.version : "") + (c.chosen ? "(当前)" : "")).join(" · ")) : null, Array.isArray(status?.missingEnv) && status.missingEnv.length ? react.default.createElement("div", { className: "vsm-lsp-hint" }, "缺少运行环境(一键安装后自动生效):", status.missingEnv.map((req) => react.default.createElement("div", {
29412
+ }, config?.enabled !== false ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, PHASE_LABEL[phase] ?? phase, " · ", SOURCE_LABEL[status?.source] ?? status?.source, status?.providerName ? " · " + status.providerName : "", status?.version ? " · v" + status.version : "", phase === "ready" && status?.root ? " · " + String(status.root).split(/[\\/]/).pop() : ""), status?.reason ? react.default.createElement("div", { className: "vsm-mcp-error" }, status.reason) : null, Array.isArray(status?.candidates) && status.candidates.length > 1 ? react.default.createElement("div", { className: "vsm-lsp-hint" }, "候选(" + status.candidates.length + "):" + status.candidates.map((c) => c.name + (c.version ? " v" + c.version : "") + (c.chosen ? "(当前)" : "")).join(" · ")) : null, Array.isArray(status?.missingEnv) && status.missingEnv.length ? react.default.createElement("div", { className: "vsm-lsp-hint" }, "缺少运行环境(一键安装后自动生效):", status.missingEnv.map((req) => react.default.createElement("div", {
28770
29413
  key: req.id,
28771
29414
  className: "vsm-mcp-actions"
28772
29415
  }, react.default.createElement("span", null, req.label, req.detail ? "(" + req.detail + ")" : ""), req.installable ? react.default.createElement("button", {
@@ -29911,10 +30554,13 @@ window.__ModuleLoader__.load({
29911
30554
  args: "",
29912
30555
  cwd: "",
29913
30556
  url: "",
29914
- headers: ""
30557
+ headers: "",
30558
+ startup: "on-demand",
30559
+ sharing: "agent",
30560
+ startupTimeoutMs: 6e4
29915
30561
  };
29916
30562
  /** @private @author ddj 2026年10月08号 @param props Server snapshot, page-wide busy state and actions. @returns Lifecycle-evidence card; enabled does not imply online. */
29917
- function ServerCard({ server, busy, onRefresh, onToggle, onRemove }) {
30563
+ function ServerCard({ server, busy, onRefresh, onToggle, onRemove, onConnect, onPolicy }) {
29918
30564
  return react.default.createElement("article", { className: "vsm-mcp-card" }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot " + statusTone(server) }), server.serverName), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
29919
30565
  disabled: !!busy,
29920
30566
  onClick: () => onRefresh(server.id),
@@ -29931,12 +30577,46 @@ window.__ModuleLoader__.load({
29931
30577
  title: "配置启用不代表连接在线",
29932
30578
  "aria-label": server.enabled ? "禁用配置(不代表在线)" : "启用配置(不代表在线)",
29933
30579
  "aria-pressed": !!server.enabled
29934
- }, server.enabled ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, statusOf(server), " · ", server.toolCount, " 个工具 · ", server.transport, server.instanceCount !== void 0 ? " · " + server.instanceCount + " 个会话实例(各自连接)" : ""), server.error && react.default.createElement("div", { className: "vsm-mcp-error" }, server.error), react.default.createElement("div", { className: "vsm-mcp-tools" }, server.tools.map((tool) => react.default.createElement("span", {
30580
+ }, server.enabled ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, statusOf(server), " · ", server.toolCount, " 个工具 · ", server.transport, server.instanceCount !== void 0 ? " · " + server.instanceCount + " 个连接实例" : "", server.sessionCount !== void 0 ? " · " + server.sessionCount + " 个会话使用" : ""), server.policy && react.default.createElement(ProjectPolicy, {
30581
+ server,
30582
+ busy,
30583
+ onConnect,
30584
+ onPolicy
30585
+ }), server.error && react.default.createElement("div", { className: "vsm-mcp-error" }, server.error), react.default.createElement("div", { className: "vsm-mcp-tools" }, server.tools.map((tool) => react.default.createElement("span", {
29935
30586
  key: tool.name,
29936
30587
  className: "vsm-mcp-chip",
29937
30588
  title: tool.description || tool.name
29938
30589
  }, tool.name))));
29939
30590
  }
30591
+ /** @private @author ddj 2026年10月20号 @param props Project policy and locked actions. @returns Explicit strategy controls and sharing risk notice. */
30592
+ function ProjectPolicy({ server, busy, onConnect, onPolicy }) {
30593
+ /** @author ddj 2026年10月20号 @param event Strategy selection. Saves only an explicit user change. */
30594
+ const startup = (event) => onPolicy?.(server, {
30595
+ ...server.policy,
30596
+ startup: event.target.value
30597
+ });
30598
+ /** @author ddj 2026年10月20号 @param event Sharing selection. Requires acknowledgement before state sharing. */
30599
+ const sharing = (event) => {
30600
+ const mode = event.target.value;
30601
+ if (mode === "workspace" && !window.confirm("同一工作区的会话将共享此 MCP 的连接和内部状态;工具串行不等于业务状态隔离。确认服务适合共享?")) return;
30602
+ onPolicy?.(server, {
30603
+ ...server.policy,
30604
+ sharing: mode
30605
+ });
30606
+ };
30607
+ return react.default.createElement("div", { className: "vsm-mcp-meta" }, react.default.createElement("label", null, "启动 ", react.default.createElement("select", {
30608
+ disabled: !!busy,
30609
+ value: server.policy.startup,
30610
+ onChange: startup
30611
+ }, react.default.createElement("option", { value: "on-demand" }, "按需启动"), react.default.createElement("option", { value: "background" }, "后台预热"))), react.default.createElement("label", null, " 连接 ", react.default.createElement("select", {
30612
+ disabled: !!busy,
30613
+ value: server.policy.sharing,
30614
+ onChange: sharing
30615
+ }, react.default.createElement("option", { value: "agent" }, "逐会话隔离"), react.default.createElement("option", { value: "workspace" }, "工作区共享(共享状态)"))), react.default.createElement("button", {
30616
+ disabled: !!busy || !server.enabled,
30617
+ onClick: onConnect
30618
+ }, "连接当前活跃会话"), react.default.createElement("small", null, "默认按需启动,不阻塞新对话;首次使用仍需等待。共享仅显式启用。"));
30619
+ }
29940
30620
  /** 按项目标题或路径过滤,大小写不敏感。 */
29941
30621
  function filterProjects(projects, query) {
29942
30622
  const needle = String(query ?? "").trim().toLowerCase();
@@ -30010,7 +30690,7 @@ window.__ModuleLoader__.load({
30010
30690
  }, react.default.createElement("span", null, project.title), react.default.createElement("small", null, project.workspacePath, " · ", project.servers.length, " 个 MCP"))) : react.default.createElement("div", { className: "vsm-project-picker-empty" }, "没有匹配的项目"))));
30011
30691
  }
30012
30692
  /** @private @author ddj 2026年10月08号 @param props Project view, shared busy lock and actions. @returns Project cards with actions disabled for any active mutation. */
30013
- function ProjectGroup({ project, busy, onAdd, onRefresh, onToggle, onRemove }) {
30693
+ function ProjectGroup({ project, busy, onAdd, onRefresh, onToggle, onRemove, onConnect, onPolicy }) {
30014
30694
  const head = react.default.createElement("div", { className: "vsm-project-head" }, react.default.createElement("div", { className: "vsm-project-title" }, react.default.createElement("span", { className: "vsm-project-icon" }, "▣"), react.default.createElement("div", null, react.default.createElement("span", { className: "vsm-project-name" }, project.title), react.default.createElement("span", { className: "vsm-project-path" }, project.workspacePath))), react.default.createElement("button", {
30015
30695
  className: "vsm-primary vsm-small",
30016
30696
  disabled: !!busy,
@@ -30024,6 +30704,8 @@ window.__ModuleLoader__.load({
30024
30704
  key: server.serverName,
30025
30705
  server,
30026
30706
  busy,
30707
+ onConnect: () => onConnect(project, server),
30708
+ onPolicy: (server, policy) => onPolicy(project, server, policy),
30027
30709
  onRefresh: (id) => onRefresh(project, server, id),
30028
30710
  onToggle: () => onToggle(project, server),
30029
30711
  onRemove: (id) => onRemove(project, server, id)
@@ -30468,6 +31150,7 @@ window.__ModuleLoader__.load({
30468
31150
  save: () => saveGlobal(closeGlobalForm),
30469
31151
  close: closeGlobalForm
30470
31152
  }) : projectForm ? react.default.createElement(McpForm, {
31153
+ project: true,
30471
31154
  title: "添加项目 MCP · " + projectForm.title,
30472
31155
  draft,
30473
31156
  busy,
@@ -30509,7 +31192,9 @@ window.__ModuleLoader__.load({
30509
31192
  onAdd: add,
30510
31193
  onRefresh: refreshProject,
30511
31194
  onToggle: toggleProject,
30512
- onRemove: removeProject
31195
+ onRemove: removeProject,
31196
+ onConnect: props.connectProject,
31197
+ onPolicy: props.policyProject
30513
31198
  }) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目"));
30514
31199
  }
30515
31200
  /** @private @author ddj 2026年10月08号 @returns Mounted controller reference and its published view. */
@@ -30570,7 +31255,7 @@ window.__ModuleLoader__.load({
30570
31255
  return state?.mutate(path + ":" + name, request, commit);
30571
31256
  }
30572
31257
  /** @private @author ddj 2026年10月08号 @param ref Mounted controller reference. @returns Global/project card actions sharing a synchronous lock. */
30573
- function cardActions(ref) {
31258
+ function cardActions(ref, getSession) {
30574
31259
  /** @author ddj 2026年10月08号 @param id Global identity. @returns Locked refresh. */
30575
31260
  const refreshGlobal = (id) => globalAction(ref.current, id, "mcp.refresh", { id });
30576
31261
  /** @author ddj 2026年10月08号 @param server Current configuration. @returns Locked enable change. */
@@ -30586,13 +31271,29 @@ window.__ModuleLoader__.load({
30586
31271
  const toggleProject = (project, server) => projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectToggle", { enabled: !server.enabled });
30587
31272
  /** @author ddj 2026年10月08号 @param project Project. @param server Configuration. @returns Confirmed locked deletion. */
30588
31273
  const removeProject = (project, server) => !ref.current?.view.busy && window.confirm("确认删除此项目的 MCP「" + server.serverName + "」?") && projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectRemove");
31274
+ /** @author ddj 2026年10月20号 @param project Owner workspace. @param server Enabled service. @returns Explicit connection for an already-active current session. */
31275
+ const connectProject = (project, server) => {
31276
+ const sessionId = getSession?.();
31277
+ if (!sessionId) return ref.current?.mutate("connect", async () => ({
31278
+ ok: false,
31279
+ error: "请先打开此项目的活跃会话"
31280
+ }), () => void 0);
31281
+ return projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectConnect", { sessionId });
31282
+ };
31283
+ /** @author ddj 2026年10月20号 @param project Owner. @param server Masked editable config. @param policy Explicit user strategy. @returns Serialized strategy persistence. */
31284
+ const policyProject = (project, server, policy) => projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectSave", {
31285
+ config: server.config,
31286
+ policy
31287
+ });
30589
31288
  return {
30590
31289
  refreshGlobal,
30591
31290
  toggleGlobal,
30592
31291
  removeGlobal,
30593
31292
  refreshProject,
30594
31293
  toggleProject,
30595
- removeProject
31294
+ removeProject,
31295
+ connectProject,
31296
+ policyProject
30596
31297
  };
30597
31298
  }
30598
31299
  /** @private @author ddj 2026年10月08号 @param state Controller. @param draft Editable fields. @param setDraft Draft setter. @param path Optional project target. @param close Success-only close callback. @returns Locked save, validation included. */
@@ -30603,7 +31304,12 @@ window.__ModuleLoader__.load({
30603
31304
  return path === null ? rpc("mcp.save", { config }) : rpc("mcp.projectSave", {
30604
31305
  workspacePath: path,
30605
31306
  serverName: config.serverName,
30606
- config
31307
+ config,
31308
+ policy: parseMcpPolicy({
31309
+ startup: draft.startup,
31310
+ sharing: draft.sharing,
31311
+ startupTimeoutMs: Number(draft.startupTimeoutMs)
31312
+ })
30607
31313
  });
30608
31314
  };
30609
31315
  /** @author ddj 2026年10月08号 @param result Save result. @description Update current view and only then reset/close the form. */
@@ -30636,7 +31342,7 @@ window.__ModuleLoader__.load({
30636
31342
  return cleanup;
30637
31343
  };
30638
31344
  react.default.useEffect(selectTab, [tab]);
30639
- const { refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject } = cardActions(ref);
31345
+ const { refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject, connectProject, policyProject } = cardActions(ref, props?.getMcpSession);
30640
31346
  /** @author ddj 2026年10月08号 @param close Success-only close callback. @returns Global save completion. */
30641
31347
  const saveGlobal = (close) => saveDraft(ref.current, draft, setDraft, null, close);
30642
31348
  /** @author ddj 2026年10月08号 @param path Target project. @param close Success-only close callback. @returns Project save completion. */
@@ -30667,6 +31373,8 @@ window.__ModuleLoader__.load({
30667
31373
  resetDraft,
30668
31374
  saveGlobal,
30669
31375
  saveProject,
31376
+ connectProject,
31377
+ policyProject,
30670
31378
  refreshGlobal,
30671
31379
  toggleGlobal,
30672
31380
  removeGlobal,
@@ -30699,7 +31407,7 @@ window.__ModuleLoader__.load({
30699
31407
  }, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body);
30700
31408
  }
30701
31409
  /** @private @author ddj 2026年10月08号 @param props Draft, shared busy lock, edit/save/close callbacks. @returns Lossless argument/header form; failed save stays open. */
30702
- function McpForm({ title, draft, busy, edit, save, close }) {
31410
+ function McpForm({ title, draft, busy, edit, save, close, project }) {
30703
31411
  /** @author ddj 2026年10月08号 @param key Draft field. @returns Short input change callback. */
30704
31412
  const change = (key) => (event) => edit(key, event.target.value);
30705
31413
  /** @author ddj 2026年10月08号 @param key Draft field. @param placeholder Input hint. @returns Disabled-while-busy input. */
@@ -30720,7 +31428,15 @@ window.__ModuleLoader__.load({
30720
31428
  disabled: !!busy,
30721
31429
  value: draft.transport,
30722
31430
  onChange: change("transport")
30723
- }, react.default.createElement("option", { value: "stdio" }, "stdio"), react.default.createElement("option", { value: "streamable-http" }, "streamable-http"))), fields, react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", {
31431
+ }, react.default.createElement("option", { value: "stdio" }, "stdio"), react.default.createElement("option", { value: "streamable-http" }, "streamable-http"))), fields, project && react.default.createElement("label", null, "启动策略", react.default.createElement("select", {
31432
+ disabled: !!busy,
31433
+ value: draft.startup,
31434
+ onChange: change("startup")
31435
+ }, react.default.createElement("option", { value: "on-demand" }, "按需启动(默认)"), react.default.createElement("option", { value: "background" }, "后台预热"))), project && react.default.createElement("label", null, "连接模式", react.default.createElement("select", {
31436
+ disabled: !!busy,
31437
+ value: draft.sharing,
31438
+ onChange: change("sharing")
31439
+ }, react.default.createElement("option", { value: "agent" }, "逐会话隔离(默认)"), react.default.createElement("option", { value: "workspace" }, "工作区共享(共享服务内部状态)"))), project && react.default.createElement("label", null, "初始化预算(毫秒,1000–300000)", input("startupTimeoutMs", "60000")), project && react.default.createElement("small", null, "新对话不等待连接。共享必须确认服务适合共享状态;不为 CodeGraph 自动启用。"), react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", {
30724
31440
  disabled: !!busy,
30725
31441
  onClick: close
30726
31442
  }, "取消"), react.default.createElement("button", {
@@ -30730,6 +31446,218 @@ window.__ModuleLoader__.load({
30730
31446
  }, "保存配置"))));
30731
31447
  }
30732
31448
  //#endregion
31449
+ //#region src/client/mcpSessionState.ts
31450
+ /** Shared, read-only sidebar MCP polling. @author ddj 2026年10月20号 */
31451
+ const unavailable = { stale: true };
31452
+ const inactive = { stale: false };
31453
+ /** One poller for all mounted row/hover consumers; never retains or activates sessions. */
31454
+ var McpSessionState = class {
31455
+ fetchStatus;
31456
+ page;
31457
+ views = /* @__PURE__ */ new Map();
31458
+ listeners = /* @__PURE__ */ new Map();
31459
+ timer;
31460
+ request;
31461
+ epoch = 0;
31462
+ stale = true;
31463
+ disposed = false;
31464
+ /** @public @author ddj 2026年10月20号 @param fetchStatus Authenticated read-only query. @param page Visibility boundary, omitted for server tests. */
31465
+ constructor(fetchStatus, page) {
31466
+ this.fetchStatus = fetchStatus;
31467
+ this.page = page;
31468
+ }
31469
+ /** @public @author ddj 2026年10月20号 @param sessionId Row identity. @returns Stable view until the next observation. */
31470
+ get(sessionId) {
31471
+ return this.views.get(sessionId) ?? (this.stale ? unavailable : inactive);
31472
+ }
31473
+ /** @public @author ddj 2026年10月20号 @param sessionId Row identity. @param listener React invalidation. @returns Idempotent unsubscribe; the last consumer stops polling. */
31474
+ subscribe(sessionId, listener) {
31475
+ if (this.disposed) return () => void 0;
31476
+ const first = this.listeners.size === 0;
31477
+ const listeners = this.listeners.get(sessionId) ?? /* @__PURE__ */ new Set();
31478
+ listeners.add(listener);
31479
+ this.listeners.set(sessionId, listeners);
31480
+ if (first) {
31481
+ this.page?.addEventListener("visibilitychange", this.visibility);
31482
+ this.poll();
31483
+ }
31484
+ return () => {
31485
+ listeners.delete(listener);
31486
+ if (listeners.size === 0) this.listeners.delete(sessionId);
31487
+ if (this.listeners.size === 0) this.pause();
31488
+ };
31489
+ }
31490
+ /** @private @author ddj 2026年10月20号 Stops pending work when hidden; resuming never accepts an old response. */
31491
+ visibility = () => {
31492
+ if (this.page?.hidden) this.invalidate();
31493
+ else this.poll();
31494
+ };
31495
+ /** @private @author ddj 2026年10月20号 Invalidates responses without allowing overlapping transport requests. */
31496
+ invalidate() {
31497
+ this.epoch += 1;
31498
+ clearTimeout(this.timer);
31499
+ this.timer = void 0;
31500
+ this.request?.abort();
31501
+ this.publish(void 0);
31502
+ }
31503
+ /** @private @author ddj 2026年10月20号 Cleans listener lifetime as well as timers. */
31504
+ pause() {
31505
+ this.page?.removeEventListener("visibilitychange", this.visibility);
31506
+ this.invalidate();
31507
+ }
31508
+ /** @public @author ddj 2026年10月20号 Permanently disposes HMR-owned polling. */
31509
+ dispose() {
31510
+ this.disposed = true;
31511
+ this.pause();
31512
+ this.listeners.clear();
31513
+ this.views.clear();
31514
+ }
31515
+ /** @private @author ddj 2026年10月20号 @param snapshot Fresh ledger, or undefined to mark all previous observations stale. */
31516
+ publish(snapshot) {
31517
+ this.stale = snapshot === void 0;
31518
+ if (snapshot) this.views = new Map(snapshot.sessions.map((status) => [status.sessionId, {
31519
+ status,
31520
+ stale: false
31521
+ }]));
31522
+ else for (const [id, view] of this.views) this.views.set(id, {
31523
+ ...view,
31524
+ stale: true
31525
+ });
31526
+ for (const listeners of this.listeners.values()) for (const listener of listeners) listener();
31527
+ }
31528
+ /** @private @author ddj 2026年10月20号 Queries once, with a 10-second deadline and a 2.5-second gap after settlement. */
31529
+ async poll() {
31530
+ if (this.disposed || this.request || !this.listeners.size || this.page?.hidden) return;
31531
+ const request = new AbortController();
31532
+ const epoch = this.epoch;
31533
+ this.request = request;
31534
+ /** @author ddj 2026年10月20号 Marks overdue responses unusable even if a transport ignores cancellation. */
31535
+ const expire = () => {
31536
+ request.abort();
31537
+ if (epoch === this.epoch) this.publish(void 0);
31538
+ };
31539
+ const deadline = setTimeout(expire, 1e4);
31540
+ try {
31541
+ const snapshot = await this.fetchStatus(request.signal);
31542
+ if (epoch === this.epoch && !request.signal.aborted) this.publish(snapshot);
31543
+ } catch {
31544
+ if (epoch === this.epoch) this.publish(void 0);
31545
+ } finally {
31546
+ clearTimeout(deadline);
31547
+ this.request = void 0;
31548
+ if (!this.disposed && this.listeners.size && !this.page?.hidden) this.timer = setTimeout(() => {
31549
+ this.poll();
31550
+ }, 2500);
31551
+ }
31552
+ }
31553
+ };
31554
+ /** @public @author ddj 2026年10月20号 @returns Browser-owned singleton per plugin apply; legacy Hosts fail safely as stale. */
31555
+ function mcpSessionState() {
31556
+ /** @author ddj 2026年10月20号 @param signal Fetch lifetime. @returns Current in-memory host ledger. @throws RPC errors. */
31557
+ const fetchStatus = async (signal) => {
31558
+ const result = await rpc("mcp.sessionSnapshot", {}, signal);
31559
+ if (!result.ok) throw new Error("MCP 状态暂不可用");
31560
+ return result;
31561
+ };
31562
+ return new McpSessionState(fetchStatus, document);
31563
+ }
31564
+ //#endregion
31565
+ //#region src/client/ui/McpSessionStatus.ts
31566
+ /** Official session-row slots; no DOM patching or session activation. @author ddj 2026年10月20号 */
31567
+ const labels = {
31568
+ idle: "待使用,尚未启动",
31569
+ preparing: "读取项目配置",
31570
+ queued: "等待 MCP 启动名额",
31571
+ loading: "加载 MCP 插件模块",
31572
+ starting: "等待 MCP 插件启动",
31573
+ stopping: "释放旧 MCP 实例",
31574
+ ready: "初始化完成,连接健康未验证",
31575
+ error: "初始化异常",
31576
+ none: "无需加载项目 MCP"
31577
+ };
31578
+ const busy = /* @__PURE__ */ new Set([
31579
+ "preparing",
31580
+ "queued",
31581
+ "loading",
31582
+ "starting",
31583
+ "stopping"
31584
+ ]);
31585
+ /** @private @author ddj 2026年10月20号 @param props Root row identity and shared poller. @returns Stable per-session observation. */
31586
+ function useMcpView({ sessionId, store }) {
31587
+ const subscribe = (0, react.useCallback)((notify) => store.subscribe(sessionId, notify), [store, sessionId]);
31588
+ const snapshot = (0, react.useCallback)(() => store.get(sessionId), [store, sessionId]);
31589
+ return (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot);
31590
+ }
31591
+ /** @public @author ddj 2026年10月20号 @param view Current observation. @returns Accessible row label; steady states deliberately do not occupy the leading slot. */
31592
+ function mcpRowLabel(view) {
31593
+ if (view.stale) return "项目 MCP 状态暂不可用(上次状态已过期)";
31594
+ if (!view.status || !busy.has(view.status.phase) && view.status.phase !== "error") return void 0;
31595
+ return "项目 MCP:" + labels[view.status.phase];
31596
+ }
31597
+ /** @public @author ddj 2026年10月20号 @param props Official row sessionId and poller. @returns 16px loading/error marker; official running/approval states retain precedence. */
31598
+ function McpSessionIcon(props) {
31599
+ const view = useMcpView(props);
31600
+ const label = mcpRowLabel(view);
31601
+ if (!label) return null;
31602
+ const warning = view.stale || view.status?.phase === "error";
31603
+ return react.default.createElement("span", {
31604
+ role: "img",
31605
+ "aria-label": label,
31606
+ title: label,
31607
+ style: {
31608
+ display: "inline-flex",
31609
+ width: 16,
31610
+ height: 16,
31611
+ alignItems: "center",
31612
+ justifyContent: "center",
31613
+ color: warning ? "var(--color-warning, #b7791f)" : "var(--color-text-secondary, currentColor)",
31614
+ fontSize: 13
31615
+ }
31616
+ }, warning ? "⚠" : "◌");
31617
+ }
31618
+ /** @private @author ddj 2026年10月20号 @param start Stage start. @param end Stage finish or local clock. @returns Human-readable elapsed time, without claiming a timeout. */
31619
+ function elapsed(start, end) {
31620
+ return Math.max(0, (end - start) / 1e3).toFixed(1) + " 秒";
31621
+ }
31622
+ /** @public @author ddj 2026年10月20号 @param props Official hover sessionId and poller. @returns Project-only diagnostic detail, never transport credentials. */
31623
+ function McpSessionDetail(props) {
31624
+ const view = useMcpView(props);
31625
+ const [now, setNow] = (0, react.useState)(Date.now);
31626
+ const status = view.status;
31627
+ (0, react.useEffect)(() => {
31628
+ if (!status || view.stale || !busy.has(status.phase)) return;
31629
+ const tick = setInterval(() => setNow(Date.now()), 1e3);
31630
+ return () => clearInterval(tick);
31631
+ }, [status, view.stale]);
31632
+ const children = [react.default.createElement("strong", { key: "title" }, "项目 MCP")];
31633
+ if (view.stale) children.push(react.default.createElement("div", {
31634
+ key: "stale",
31635
+ role: "status"
31636
+ }, "状态暂不可用;以下如有记录,为上次观测结果"));
31637
+ if (!status) children.push(react.default.createElement("div", { key: "empty" }, view.stale ? "等待状态查询" : "当前无活跃 MCP 实例"));
31638
+ else {
31639
+ const end = status.finishedAt ?? now;
31640
+ const completed = status.servers.filter((server) => server.phase === "ready").length;
31641
+ const slow = !view.stale && busy.has(status.phase) && now - status.phaseAt > 1e4;
31642
+ children.push(react.default.createElement("div", { key: "phase" }, labels[status.phase], slow ? "(耗时较长,不代表失败)" : ""));
31643
+ children.push(react.default.createElement("div", { key: "time" }, `${completed}/${status.servers.length} 已初始化 · 总耗时 ${elapsed(status.startedAt, end)} · 当前阶段 ${elapsed(status.phaseAt, end)}`));
31644
+ if (status.configMs !== void 0) children.push(react.default.createElement("div", { key: "config" }, `配置准备 ${elapsed(0, status.configMs)}`));
31645
+ if (status.error) children.push(react.default.createElement("div", { key: "error" }, status.error));
31646
+ for (const server of status.servers) children.push(react.default.createElement("div", { key: "server:" + server.serverName }, `${server.serverName}:${labels[server.phase]}${server.reused ? " · 复用已有连接" : ""}${server.phase === "idle" ? "" : " · " + elapsed(server.startedAt, server.finishedAt ?? now)}`, server.queuedMs === void 0 ? "" : " · 排队 " + elapsed(0, server.queuedMs), server.connectMs === void 0 ? "" : " · 初始化 " + elapsed(0, server.connectMs), server.firstCallMs === void 0 ? "" : " · 首次工具执行 " + elapsed(0, server.firstCallMs), server.error ? " · " + server.error : ""));
31647
+ }
31648
+ children.push(react.default.createElement("small", { key: "scope" }, "仅 VSCodeMode 管理的项目 MCP;不含全局或 preset MCP。"));
31649
+ return react.default.createElement("section", {
31650
+ "aria-label": "项目 MCP 状态",
31651
+ style: {
31652
+ display: "grid",
31653
+ gap: 4,
31654
+ maxWidth: 320,
31655
+ overflowWrap: "anywhere",
31656
+ fontSize: 12
31657
+ }
31658
+ }, children);
31659
+ }
31660
+ //#endregion
30733
31661
  //#region src/client/ui/PluginDetailSection.ts
30734
31662
  /**
30735
31663
  * dsh-vscode-mode client — 官方插件管理器详情页的只读状态段。
@@ -31815,8 +32743,11 @@ window.__ModuleLoader__.load({
31815
32743
  function createSidebarPanelRegistry() {
31816
32744
  const entries = /* @__PURE__ */ new Map();
31817
32745
  const listeners = /* @__PURE__ */ new Set();
32746
+ /** @private @author ddj 2026年10月22号 @description Isolate subscribers and snapshot notification membership against reentrant changes. */
31818
32747
  const notify = () => {
31819
- for (const listener of listeners) listener();
32748
+ for (const listener of [...listeners]) try {
32749
+ listener();
32750
+ } catch {}
31820
32751
  };
31821
32752
  const list = () => [...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
31822
32753
  return {
@@ -31932,7 +32863,7 @@ window.__ModuleLoader__.load({
31932
32863
  * 作者 ddj 2026-08-28
31933
32864
  */
31934
32865
  /** localStorage 键前缀(按工作区作用域隔离,无 cwd 会话回退会话键)。 */
31935
- const KEY_PREFIX$2 = CACHE_KEY.expanded;
32866
+ const KEY_PREFIX$1 = CACHE_KEY.expanded;
31936
32867
  /**
31937
32868
  * 归一化展开路径:去空白、反斜杠→斜杠、去首尾斜杠,`..` 段拒绝。
31938
32869
  * @author ddj 2026年08月28号
@@ -32010,7 +32941,7 @@ window.__ModuleLoader__.load({
32010
32941
  */
32011
32942
  function explorerLoad(scope) {
32012
32943
  try {
32013
- return parseExplorer(window.localStorage.getItem(KEY_PREFIX$2 + String(scope)));
32944
+ return parseExplorer(window.localStorage.getItem(KEY_PREFIX$1 + String(scope)));
32014
32945
  } catch (error) {
32015
32946
  return null;
32016
32947
  }
@@ -32023,12 +32954,12 @@ window.__ModuleLoader__.load({
32023
32954
  */
32024
32955
  function explorerSave(scope, data) {
32025
32956
  try {
32026
- window.localStorage.setItem(KEY_PREFIX$2 + String(scope), serializeExplorer(data));
32957
+ window.localStorage.setItem(KEY_PREFIX$1 + String(scope), serializeExplorer(data));
32027
32958
  } catch (error) {}
32028
32959
  }
32029
32960
  //#endregion
32030
32961
  //#region src/client/state/explorerEntriesCache.ts
32031
- const KEY_PREFIX$1 = CACHE_KEY.entries;
32962
+ const KEY_PREFIX = CACHE_KEY.entries;
32032
32963
  /** 裁剪到上限(目录按 ts 逐出最旧;单目录条目截断)。 */
32033
32964
  function entriesTrim(data, maxDirs = 300, maxEntries = 500) {
32034
32965
  const dirs = {};
@@ -32102,7 +33033,7 @@ window.__ModuleLoader__.load({
32102
33033
  /** localStorage 读取(不可用 → null)。 */
32103
33034
  function readRaw(scope) {
32104
33035
  try {
32105
- return window.localStorage.getItem(KEY_PREFIX$1 + scope);
33036
+ return window.localStorage.getItem(KEY_PREFIX + scope);
32106
33037
  } catch (error) {
32107
33038
  return null;
32108
33039
  }
@@ -32110,7 +33041,7 @@ window.__ModuleLoader__.load({
32110
33041
  /** localStorage 写入(配额/隐私模式失败静默)。 */
32111
33042
  function writeRaw(scope, text) {
32112
33043
  try {
32113
- window.localStorage.setItem(KEY_PREFIX$1 + scope, text);
33044
+ window.localStorage.setItem(KEY_PREFIX + scope, text);
32114
33045
  } catch (error) {}
32115
33046
  }
32116
33047
  /** 取(或建)作用域缓存镜像。 */
@@ -32730,12 +33661,12 @@ window.__ModuleLoader__.load({
32730
33661
  setTarget(next);
32731
33662
  if (next) current.prepare(next.path);
32732
33663
  }
32733
- /** @private @author ddj 2026年10月14号 Attempts the latest target after row commit or container resize. Consumes only a successful, current request. */
33664
+ /** @private @author ddj 2026年10月22号 Attempts virtual data-index location or the original mounted-row fallback after commit/resize; consumes only a successful current request. */
32734
33665
  function tryReveal() {
32735
33666
  const next = pending.current;
32736
33667
  const current = latest.current;
32737
33668
  if (!next || next.scope !== current.scope || next.active !== current.activePath) return;
32738
- if (!revealTreeRow(current.treeRef.current, next.path)) return;
33669
+ if (!(current.locate ? current.locate(next.path) : revealTreeRow(current.treeRef.current, next.path))) return;
32739
33670
  pending.current = null;
32740
33671
  setRevealPath(next.path);
32741
33672
  clearHighlight();
@@ -32778,6 +33709,106 @@ window.__ModuleLoader__.load({
32778
33709
  requestReveal
32779
33710
  };
32780
33711
  }
33712
+ /**
33713
+ * @public @author ddj 2026年10月22号
33714
+ * @description Preserve the first visible path when rows above it expand or collapse.
33715
+ * @param previous Previous complete model.
33716
+ * @param rows New complete model.
33717
+ * @param top Previous scroll offset.
33718
+ * @returns Anchored offset, or original offset for subsequent clamping if path disappeared.
33719
+ */
33720
+ function treeAnchorTop(previous, rows, top) {
33721
+ const anchor = previous[Math.floor(Math.max(0, top) / 24)]?.key;
33722
+ if (!anchor) return top;
33723
+ const nextIndex = rows.findIndex((row) => row.key === anchor);
33724
+ if (nextIndex < 0) return top;
33725
+ return nextIndex * 24 + top % 24;
33726
+ }
33727
+ /**
33728
+ * @public @author ddj 2026年10月22号
33729
+ * @description Flatten all expanded entries in their supplied order, including metadata files.
33730
+ * @param entriesOf Directory snapshot lookup; null means unavailable.
33731
+ * @param expanded Current directory expansion flags.
33732
+ * @param loading Current loading flags.
33733
+ * @returns Complete row model, independent of viewport mounting.
33734
+ */
33735
+ function flattenTree(entriesOf, expanded, loading) {
33736
+ const rows = [];
33737
+ const pending = [{
33738
+ rel: "",
33739
+ depth: 0
33740
+ }];
33741
+ while (pending.length) {
33742
+ const current = pending.pop();
33743
+ const entry = current.entry;
33744
+ if (entry) {
33745
+ const isDir = entry.type === "directory";
33746
+ const isOpen = expanded[entry.path] === true;
33747
+ rows.push({
33748
+ key: entry.path,
33749
+ e: entry,
33750
+ depth: current.depth,
33751
+ isDir,
33752
+ isOpen
33753
+ });
33754
+ if (isDir && isOpen) pending.push({
33755
+ rel: entry.path,
33756
+ depth: current.depth + 1
33757
+ });
33758
+ continue;
33759
+ }
33760
+ const entries = entriesOf(current.rel);
33761
+ if (!entries) {
33762
+ rows.push({
33763
+ key: current.rel + ":loading",
33764
+ depth: current.depth,
33765
+ text: loading[current.rel] ? "加载中…" : "无法加载"
33766
+ });
33767
+ continue;
33768
+ }
33769
+ for (let index = entries.length - 1; index >= 0; index--) pending.push({
33770
+ rel: current.rel,
33771
+ depth: current.depth,
33772
+ entry: entries[index]
33773
+ });
33774
+ }
33775
+ return rows;
33776
+ }
33777
+ /**
33778
+ * @public @author ddj 2026年10月22号
33779
+ * @description Clamp scroll state before reusing the existing fixed-height range calculation.
33780
+ * @param count Complete row count.
33781
+ * @param viewport Latest scroll position and measured viewport height.
33782
+ * @returns Clamped scroll offset and overscanned exclusive row range.
33783
+ */
33784
+ function treeWindow(count, viewport) {
33785
+ const height = Math.max(0, viewport.height);
33786
+ const top = Math.min(Math.max(0, viewport.top), Math.max(0, count * 24 - height));
33787
+ return {
33788
+ top,
33789
+ ...updateRange(count, top, height, 24)
33790
+ };
33791
+ }
33792
+ /**
33793
+ * @public @author ddj 2026年10月22号
33794
+ * @description Locate an unmounted row using model indices, without querying row DOM.
33795
+ * @param rows Complete flattened model.
33796
+ * @param path Requested relative file path.
33797
+ * @param viewport Current viewport geometry.
33798
+ * @returns New scroll offset, or null while ancestors/viewport are unavailable.
33799
+ */
33800
+ function treeRevealTop(rows, path, viewport) {
33801
+ if (viewport.height <= 0) return null;
33802
+ const index = rows.findIndex((row) => row.e?.path === path);
33803
+ if (index < 0) return null;
33804
+ const current = treeWindow(rows.length, viewport).top;
33805
+ const rowTop = index * 24;
33806
+ if (rowTop >= current && rowTop + 24 <= current + viewport.height) return current;
33807
+ return treeWindow(rows.length, {
33808
+ top: rowTop - (viewport.height - 24) / 2,
33809
+ height: viewport.height
33810
+ }).top;
33811
+ }
32781
33812
  //#endregion
32782
33813
  //#region src/client/sidebar/panels/FileExplorer.ts
32783
33814
  /**
@@ -32791,7 +33822,6 @@ window.__ModuleLoader__.load({
32791
33822
  * 远端更新箭头由显式只读检查提供,与本地状态并存,不执行更新。
32792
33823
  * 作者 ddj 2026-08-26 / 2026-08-27 / 2026-08-31 / 2026-09-10 / 2026-09-16 / 2026-10-09
32793
33824
  */
32794
- const DIR_CAP = 4e3;
32795
33825
  const SAVE_DEBOUNCE_MS = 300;
32796
33826
  const FOLLOW_INTERVAL_MS = 1e4;
32797
33827
  const PREFETCH_MAX = 4;
@@ -32890,6 +33920,11 @@ window.__ModuleLoader__.load({
32890
33920
  const loadDirRef = react.default.useRef(null);
32891
33921
  const refreshRef = react.default.useRef(null);
32892
33922
  const treeRef = react.default.useRef(null);
33923
+ const [viewport, setViewport] = react.default.useState({
33924
+ scope,
33925
+ top: 0,
33926
+ height: 0
33927
+ });
32893
33928
  const reloadDirRef = react.default.useRef(null);
32894
33929
  const changedTimerRef = react.default.useRef(null);
32895
33930
  const changedRelRef = react.default.useRef(/* @__PURE__ */ new Set());
@@ -33096,12 +34131,6 @@ window.__ModuleLoader__.load({
33096
34131
  window.addEventListener("edrv:refresh", onRefresh);
33097
34132
  return () => window.removeEventListener("edrv:refresh", onRefresh);
33098
34133
  }, []);
33099
- const { revealPath } = useExplorerReveal({
33100
- scope,
33101
- activePath,
33102
- treeRef,
33103
- prepare: prepareReveal
33104
- });
33105
34134
  const activeTreePath = treeRevealPath(activePath);
33106
34135
  react.default.useEffect(() => {
33107
34136
  if (!scope) return;
@@ -33113,39 +34142,9 @@ window.__ModuleLoader__.load({
33113
34142
  }, FOLLOW_INTERVAL_MS);
33114
34143
  return () => clearInterval(timer);
33115
34144
  }, [scope]);
33116
- /** @private @author ddj 2026年10月10号 @returns Expanded tree descriptors; icon refreshes never re-walk the tree. */
34145
+ /** @private @author ddj 2026年10月22号 @returns Complete expanded model; icon-only updates do not flatten again. */
33117
34146
  function flatten() {
33118
- const result = [];
33119
- /** @private @author ddj 2026年10月10号 @param rel Directory. @param depth Indent level. Appends only expanded rows and placeholders. */
33120
- function visit(rel, depth) {
33121
- const entries = dirs[rel] ?? entriesCacheGet(scope, rel);
33122
- if (!entries) {
33123
- result.push({
33124
- key: rel + ":loading",
33125
- depth,
33126
- text: loading[rel] ? "加载中…" : "无法加载"
33127
- });
33128
- return;
33129
- }
33130
- for (const e of entries) {
33131
- const isDir = e.type === "directory", isOpen = expanded[e.path] === true;
33132
- result.push({
33133
- key: e.path,
33134
- e,
33135
- depth,
33136
- isDir,
33137
- isOpen
33138
- });
33139
- if (isDir && isOpen) visit(e.path, depth + 1);
33140
- }
33141
- if (entries.length >= DIR_CAP) result.push({
33142
- key: rel + ":cap",
33143
- depth,
33144
- text: "目录条目过多,仅显示前 4000 项"
33145
- });
33146
- }
33147
- visit("", 0);
33148
- return result;
34147
+ return flattenTree(entriesOf, expanded, loading);
33149
34148
  }
33150
34149
  const treeRows = react.default.useMemo(flatten, [
33151
34150
  dirs,
@@ -33153,6 +34152,99 @@ window.__ModuleLoader__.load({
33153
34152
  loading,
33154
34153
  scope
33155
34154
  ]);
34155
+ const previousRows = react.default.useRef({
34156
+ scope,
34157
+ rows: treeRows
34158
+ });
34159
+ let anchoredTop = 0;
34160
+ if (viewport.scope === scope) {
34161
+ anchoredTop = viewport.top;
34162
+ if (previousRows.current.scope === scope && previousRows.current.rows !== treeRows) anchoredTop = treeAnchorTop(previousRows.current.rows, treeRows, viewport.top);
34163
+ }
34164
+ const currentViewport = {
34165
+ top: anchoredTop,
34166
+ height: viewport.height
34167
+ };
34168
+ const windowRows = treeWindow(treeRows.length, currentViewport);
34169
+ /** @private @author ddj 2026年10月22号 Read current geometry after scroll, resize or scope restoration. */
34170
+ function measureTree() {
34171
+ const host = treeRef.current;
34172
+ if (!host || host.clientHeight <= 0) return;
34173
+ setViewport({
34174
+ scope,
34175
+ top: host.scrollTop,
34176
+ height: host.clientHeight
34177
+ });
34178
+ }
34179
+ const scrollFrame = react.default.useRef(null);
34180
+ /** @private @author ddj 2026年10月22号 Coalesce high-frequency scrolling into one measured render per frame. */
34181
+ function scrollTree() {
34182
+ if (scrollFrame.current !== null) return;
34183
+ scrollFrame.current = requestAnimationFrame(readScroll);
34184
+ }
34185
+ /** @private @author ddj 2026年10月22号 Read only after the pending animation frame fires. */
34186
+ function readScroll() {
34187
+ scrollFrame.current = null;
34188
+ measureTree();
34189
+ }
34190
+ /** @private @author ddj 2026年10月22号 @returns Resize observer and pending frame cleanup. Keep hidden trees measurable when shown. */
34191
+ function watchTree() {
34192
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(measureTree) : null;
34193
+ if (treeRef.current) observer?.observe(treeRef.current);
34194
+ measureTree();
34195
+ /** @private @author ddj 2026年10月22号 Stop all viewport callbacks owned by this scope. */
34196
+ function cleanup() {
34197
+ observer?.disconnect();
34198
+ if (scrollFrame.current !== null) cancelAnimationFrame(scrollFrame.current);
34199
+ scrollFrame.current = null;
34200
+ }
34201
+ return cleanup;
34202
+ }
34203
+ react.default.useLayoutEffect(watchTree, [scope]);
34204
+ /** @private @author ddj 2026年10月22号 Clamp actual scroll after collapse, filtering, or workspace switch. */
34205
+ function clampTree() {
34206
+ const host = treeRef.current;
34207
+ previousRows.current = {
34208
+ scope,
34209
+ rows: treeRows
34210
+ };
34211
+ if (!host || host.clientHeight <= 0) return;
34212
+ if (host.scrollTop !== windowRows.top) host.scrollTop = windowRows.top;
34213
+ if (viewport.scope !== scope || viewport.top !== windowRows.top) setViewport({
34214
+ scope,
34215
+ top: windowRows.top,
34216
+ height: host.clientHeight
34217
+ });
34218
+ }
34219
+ react.default.useLayoutEffect(clampTree, [
34220
+ scope,
34221
+ windowRows.top,
34222
+ treeRows
34223
+ ]);
34224
+ /** @private @author ddj 2026年10月22号 @param path Requested file. @returns Whether its complete model row is available. */
34225
+ function locateTree(path) {
34226
+ const host = treeRef.current;
34227
+ if (!host) return false;
34228
+ const top = treeRevealTop(treeRows, path, {
34229
+ top: host.scrollTop,
34230
+ height: host.clientHeight
34231
+ });
34232
+ if (top === null) return false;
34233
+ host.scrollTop = top;
34234
+ setViewport({
34235
+ scope,
34236
+ top,
34237
+ height: host.clientHeight
34238
+ });
34239
+ return true;
34240
+ }
34241
+ const { revealPath } = useExplorerReveal({
34242
+ scope,
34243
+ activePath,
34244
+ treeRef,
34245
+ prepare: prepareReveal,
34246
+ locate: locateTree
34247
+ });
33156
34248
  /** @private @author ddj 2026年10月10号 @returns Stable RPC row projection, independent of icon-only renders. */
33157
34249
  function projectRows() {
33158
34250
  return treeRows.filter((row) => row.e).map((row) => ({
@@ -33173,6 +34265,7 @@ window.__ModuleLoader__.load({
33173
34265
  event.preventDefault();
33174
34266
  event.stopPropagation();
33175
34267
  setMenu({
34268
+ scope,
33176
34269
  x: event.clientX,
33177
34270
  y: event.clientY,
33178
34271
  target: {
@@ -33182,7 +34275,7 @@ window.__ModuleLoader__.load({
33182
34275
  });
33183
34276
  }
33184
34277
  const onSelect = react.default.useCallback((path, isDir) => selectRef.current(path, isDir), []);
33185
- const onContext = react.default.useCallback(contextRow, []);
34278
+ const onContext = react.default.useCallback(contextRow, [scope]);
33186
34279
  const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
33187
34280
  /** @private @author ddj 2026年10月10号 @param row Flat descriptor. @returns Memo row; unaffected status objects retain identity. */
33188
34281
  function renderRow(row) {
@@ -33215,6 +34308,7 @@ window.__ModuleLoader__.load({
33215
34308
  ev.stopPropagation();
33216
34309
  if (!ev.currentTarget.contains(ev.target)) return;
33217
34310
  setMenu({
34311
+ scope,
33218
34312
  x: ev.clientX,
33219
34313
  y: ev.clientY,
33220
34314
  target: {
@@ -33223,7 +34317,13 @@ window.__ModuleLoader__.load({
33223
34317
  }
33224
34318
  });
33225
34319
  };
33226
- const menuEntries = menu ? buildTreeMenu(ctx?.fileMenuItems, menu.target, ctx).map((item) => ({
34320
+ const menuValid = menu?.scope === scope && (menu.target.path === "" || treeRows.some((row) => row.e?.path === menu.target.path));
34321
+ /** @private @author ddj 2026年10月22号 Close stale path menus instead of rebinding recycled rows. */
34322
+ function closeLostMenu() {
34323
+ if (menu && !menuValid) setMenu(null);
34324
+ }
34325
+ react.default.useEffect(closeLostMenu, [menu, menuValid]);
34326
+ const menuEntries = menuValid ? buildTreeMenu(ctx?.fileMenuItems, menu.target, ctx).map((item) => ({
33227
34327
  id: item.id,
33228
34328
  label: item.label,
33229
34329
  danger: item.danger,
@@ -33263,16 +34363,23 @@ window.__ModuleLoader__.load({
33263
34363
  }, refreshIconEl$1())), svn.message ? react.default.createElement("div", {
33264
34364
  className: "edrv-tree-status",
33265
34365
  role: "status"
33266
- }, svn.message) : null, react.default.createElement("div", {
33267
- className: "edrv-tree",
33268
- ref: treeRef
33269
- }, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
34366
+ }, svn.message) : null, errorText ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(errorText)), react.default.createElement("button", {
33270
34367
  className: "edrv-side-btn",
33271
34368
  onClick: () => emitRefresh({
33272
34369
  scope,
33273
34370
  kind: "unknown"
33274
34371
  })
33275
- }, "重试")) : null, treeRows.map(renderRow)), menu && menuEntries.length ? react.default.createElement(ContextMenu, {
34372
+ }, "重试")) : null, react.default.createElement("div", {
34373
+ className: "edrv-tree edrv-tree-virtual",
34374
+ ref: treeRef,
34375
+ onScroll: scrollTree
34376
+ }, react.default.createElement("div", {
34377
+ "aria-hidden": true,
34378
+ style: { height: windowRows.start * 24 }
34379
+ }), treeRows.slice(windowRows.start, windowRows.end).map(renderRow), react.default.createElement("div", {
34380
+ "aria-hidden": true,
34381
+ style: { height: (treeRows.length - windowRows.end) * 24 }
34382
+ })), menu && menuEntries.length ? react.default.createElement(ContextMenu, {
33276
34383
  x: menu.x,
33277
34384
  y: menu.y,
33278
34385
  entries: menuEntries,
@@ -33767,713 +34874,79 @@ window.__ModuleLoader__.load({
33767
34874
  })), optsBar, filterRows, summary, warnBanner, body);
33768
34875
  }
33769
34876
  //#endregion
33770
- //#region src/client/rulesMdc.ts
33771
- /**
33772
- * 去除值两侧成对引号(与 host stripQuotes 一致)。
33773
- * @author ddj 2026年09月07号
33774
- * @param raw 原始值
33775
- * @returns 去引号后的值
33776
- */
33777
- function stripQuotes(raw) {
33778
- const value = raw.trim();
33779
- if (value.length >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
33780
- return value;
33781
- }
33782
- /**
33783
- * 解析 .mdc 全文:首行 --- 且 100 行内有闭合 --- 才视为 frontmatter;
33784
- * 已知键 description/alwaysApply/globs/enabled,其余原行进 fmExtra;永不抛错。
33785
- * @author ddj 2026年09月07号
33786
- * @param text 文件全文
33787
- * @returns 解析结果
33788
- */
33789
- function parseRuleFm(text) {
33790
- const bom = text.startsWith("");
33791
- const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
33792
- const none = {
33793
- type: "manual",
33794
- description: "",
33795
- alwaysApply: false,
33796
- globs: [],
33797
- enabled: true,
33798
- hasFm: false,
33799
- body: text.replace(/^\uFEFF/, ""),
33800
- fmExtra: [],
33801
- bom,
33802
- hasEnabledLine: false
33803
- };
33804
- if (lines[0]?.trim() !== "---") return none;
33805
- let close = -1;
33806
- for (let i = 1; i < lines.length && i <= 101; i++) if (lines[i].trim() === "---") {
33807
- close = i;
33808
- break;
33809
- }
33810
- if (close < 0) return none;
33811
- const parsed = {
33812
- type: "manual",
33813
- description: "",
33814
- alwaysApply: false,
33815
- globs: [],
33816
- enabled: true,
33817
- hasFm: true,
33818
- body: lines.slice(close + 1).join("\n"),
33819
- fmExtra: [],
33820
- bom,
33821
- hasEnabledLine: false
33822
- };
33823
- for (let i = 1; i < close; i++) {
33824
- const match = /^([A-Za-z_-]+)[ \t]*:[ \t]?(.*)$/.exec(lines[i]);
33825
- if (!match) {
33826
- parsed.fmExtra.push(lines[i]);
33827
- continue;
33828
- }
33829
- const [, key, raw] = match;
33830
- if (key === "description") parsed.description = stripQuotes(raw);
33831
- else if (key === "alwaysApply") parsed.alwaysApply = stripQuotes(raw).toLowerCase() === "true";
33832
- else if (key === "enabled") {
33833
- parsed.enabled = stripQuotes(raw).toLowerCase() !== "false";
33834
- parsed.hasEnabledLine = true;
33835
- } else if (key === "globs") parsed.globs = parseGlobsValue(raw, lines, i, close);
33836
- else parsed.fmExtra.push(lines[i]);
33837
- }
33838
- parsed.type = fmTypeOf(parsed);
33839
- return parsed;
33840
- }
33841
- /**
33842
- * 解析 globs 值:内联逗号分隔串,或空值后接 YAML 短横列表(与 host parseGlobs 一致)。
33843
- * @author ddj 2026年09月07号
33844
- * @param value 内联值
33845
- * @param lines 全文行
33846
- * @param start globs 行下标
33847
- * @param close 闭合 --- 下标
33848
- * @returns glob 数组
33849
- */
33850
- function parseGlobsValue(value, lines, start, close) {
33851
- const inline = stripQuotes(value);
33852
- if (inline) return inline.split(",").map((item) => stripQuotes(item)).filter(Boolean);
33853
- const list = [];
33854
- for (let j = start + 1; j < close; j++) {
33855
- const item = /^[ \t]*-[ \t]*(.+)$/.exec(lines[j]);
33856
- if (!item) break;
33857
- list.push(stripQuotes(item[1]));
33858
- }
33859
- return list;
33860
- }
33861
- /**
33862
- * 由 frontmatter 推导类型:alwaysApply=true → 总是;有 globs → 自动;否则手动。
33863
- * @author ddj 2026年09月07号
33864
- * @param parsed 解析结果
33865
- * @returns 规则类型
33866
- */
33867
- function fmTypeOf(parsed) {
33868
- if (parsed.alwaysApply) return "always";
33869
- if (parsed.globs.length) return "auto";
33870
- return "manual";
33871
- }
33872
- /**
33873
- * 按最新控件值重写 frontmatter:known 键固定顺序 + fmExtra + 闭合 --- + 原 body;
33874
- * 无 frontmatter 且目标为手动时:有描述则新建仅含描述的 frontmatter,否则原文返回。
33875
- * @author ddj 2026年09月07号
33876
- * @param parsed parseRuleFm 的结果(必须来自同一文本)
33877
- * @param next 控件最新值
33878
- * @returns 重写后的全文
33879
- */
33880
- function applyRuleMeta(parsed, next) {
33881
- if (!parsed.hasFm && next.type === "manual") {
33882
- if (!next.description.trim()) return (parsed.bom ? "" : "") + parsed.body;
33883
- return (parsed.bom ? "" : "") + [
33884
- "---",
33885
- "description: " + next.description.trim(),
33886
- "---"
33887
- ].join("\n") + "\n" + parsed.body;
33888
- }
33889
- const lines = ["---"];
33890
- lines.push("description: " + next.description.trim());
33891
- if (next.type === "always") lines.push("alwaysApply: true");
33892
- if (next.type === "auto") lines.push("globs: " + next.globs.join(", "));
33893
- if (parsed.hasEnabledLine) lines.push("enabled: " + (parsed.enabled ? "true" : "false"));
33894
- lines.push(...parsed.fmExtra);
33895
- lines.push("---");
33896
- return (parsed.bom ? "" : "") + lines.join("\n") + "\n" + parsed.body;
33897
- }
33898
- //#endregion
33899
- //#region src/client/sidebar/panels/RulesPanel.ts
33900
- /**
33901
- * dsh-vscode-mode client — 侧边栏「规则」面板(Codebuddy 规则管理形态)。
33902
- * 双 Tab:用户规则(~/.dsh/rules/)与项目规则(<工作区>/.dsh/rules/);
33903
- * 列表行 = 文件名 + 相对提示,第二行类型徽标与描述,右侧 编辑/删除 图标 + 滑动启用开关;
33904
- * 新建/编辑 = 顶部面包屑 + 规则类型下拉(总是/自动/手动)+ 描述/globs 控件,与原始 .mdc
33905
- * 文本域双向同步(控件改写 frontmatter,文本域回读控件),保存走 rules.save。
33906
- * 作者 ddj 2026年09月03号(2026年09月07号 交互性调整)
33907
- */
33908
- /** 新建规则模板(frontmatter 字段与 host parseRuleMdc 对齐)。 */
33909
- const NEW_TEMPLATE = "---\ndescription: \nalwaysApply: true\n---\n\n";
33910
- const TYPE_LABEL = {
33911
- always: "总是",
33912
- auto: "自动",
33913
- manual: "手动"
34877
+ //#region src/client/lazyPanel.ts
34878
+ /** Package-local CJS panel loading; transport, revision URLs and HMR belong to DSH. @author ddj 2026年10月22号 */
34879
+ const FILES = {
34880
+ rules: "./client.rules.js",
34881
+ debug: "./client.debug.js"
33914
34882
  };
33915
- /** 列表行描述的基础显示上限(字符数;宽面板下的封顶值,超出省略号)。 */
33916
- const DESC_MAX = 24;
33917
- /** 动态字数下限(极窄面板的保底可见字符数)。 */
33918
- const DESC_CAP_MIN = 8;
33919
- /** 动态字数估算:描述之外的行内固定占用 px(动作区/类型标签/徽标/间距/省略余量)。 */
33920
- const DESC_RESERVE_PX = 210;
33921
- /** 动态字数估算:单字符平均宽度 px(11px 字号,中英混排)。 */
33922
- const DESC_CHAR_PX = 10;
33923
- /** 窄面板阈值:低于此宽度隐藏路径提示(编辑界面面包屑与悬停仍有完整路径)。 */
33924
- const NARROW_W = 420;
33925
- /**
33926
- * 读 localStorage(损坏/不可用安全)。
33927
- * @author ddj 2026年09月03号
33928
- * @param key 键
33929
- * @returns 值或 null
33930
- */
33931
- function readLocal(key) {
33932
- try {
33933
- return localStorage.getItem(key);
33934
- } catch (e) {
33935
- return null;
33936
- }
33937
- }
33938
- /**
33939
- * 写 localStorage(不可用安全)。
33940
- * @author ddj 2026年09月03号
33941
- * @param key 键
33942
- * @param value 值
33943
- */
33944
- function writeLocal(key, value) {
33945
- try {
33946
- localStorage.setItem(key, value);
33947
- } catch (e) {}
33948
- }
33949
- /**
33950
- * 类型徽标(总是=绿 / 自动=蓝 / 手动=灰)。
33951
- * @author ddj 2026年09月03号
33952
- * @param type 规则类型
33953
- * @returns 徽标元素
33954
- */
33955
- function typeBadge(type) {
33956
- return react.default.createElement("span", { className: "edrv-rules-type edrv-rules-type-" + type }, TYPE_LABEL[type] ?? type);
33957
- }
33958
- /**
33959
- * 按字符上限截断列表行描述,超出以省略号收尾(上限随面板宽度动态变化;
33960
- * 窄面板下 CSS 省略号仍会按像素提前截断,二者取更小)。
33961
- * @author ddj 2026年09月07号
33962
- * @param text 完整描述
33963
- * @param cap 本帧字符上限
33964
- * @returns 截断后的展示文本
33965
- */
33966
- function clampDesc(text, cap) {
33967
- const chars = Array.from(String(text ?? ""));
33968
- const limit = Math.max(4, Number(cap) || DESC_MAX);
33969
- return chars.length > limit ? chars.slice(0, limit).join("") + "…" : chars.join("");
33970
- }
33971
- /**
33972
- * 由面板宽度估算描述可用字符数:随宽度动态变化,并与基础上限取小、极窄时保底。
33973
- * @author ddj 2026年09月07号
33974
- * @param panelWidth 面板内容宽度(px)
33975
- * @returns 本帧描述字符上限
33976
- */
33977
- function descCapOf(panelWidth) {
33978
- const byWidth = Math.floor((Number(panelWidth) - DESC_RESERVE_PX) / DESC_CHAR_PX);
33979
- return Math.max(DESC_CAP_MIN, Math.min(DESC_MAX, byWidth));
33980
- }
33981
- /**
33982
- * 编辑图标(线性铅笔,与 DiffBox chevron 同风格:currentColor 描边装饰图)。
33983
- * @author ddj 2026年09月07号
33984
- * @returns 13×13 装饰性 SVG
33985
- */
33986
- function pencilIcon() {
33987
- return react.default.createElement("svg", {
33988
- width: 13,
33989
- height: 13,
33990
- viewBox: "0 0 24 24",
33991
- fill: "none",
33992
- "aria-hidden": true,
33993
- focusable: false
33994
- }, react.default.createElement("path", {
33995
- d: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z",
33996
- stroke: "currentColor",
33997
- strokeWidth: 2,
33998
- strokeLinecap: "round",
33999
- strokeLinejoin: "round"
34000
- }));
34001
- }
34002
- /**
34003
- * 删除图标(线性垃圾桶,风格同 pencilIcon)。
34004
- * @author ddj 2026年09月07号
34005
- * @returns 13×13 装饰性 SVG
34006
- */
34007
- function trashIcon() {
34008
- return react.default.createElement("svg", {
34009
- width: 13,
34010
- height: 13,
34011
- viewBox: "0 0 24 24",
34012
- fill: "none",
34013
- "aria-hidden": true,
34014
- focusable: false
34015
- }, react.default.createElement("path", {
34016
- d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14zM10 11v6M14 11v6",
34017
- stroke: "currentColor",
34018
- strokeWidth: 2,
34019
- strokeLinecap: "round",
34020
- strokeLinejoin: "round"
34021
- }));
34022
- }
34023
- /**
34024
- * 逗号分隔的 globs 输入串 → 数组(去空项)。
34025
- * @author ddj 2026年09月07号
34026
- * @param text 输入串
34027
- * @returns glob 数组
34028
- */
34029
- function globList(text) {
34030
- return String(text ?? "").split(",").map((item) => item.trim()).filter(Boolean);
34031
- }
34032
- /**
34033
- * 由控件值重建 .mdc 全文:frontmatter 按类型/描述/globs 重写,正文与其余键不动。
34034
- * @author ddj 2026年09月07号
34035
- * @param base 当前全文
34036
- * @param fmType 目标类型(always/auto/manual)
34037
- * @param desc 描述文本
34038
- * @param globsText globs 输入串
34039
- * @returns 重写后的全文
34040
- */
34041
- function rebuildContent(base, fmType, desc, globsText) {
34042
- return applyRuleMeta(parseRuleFm(base), {
34043
- type: fmType,
34044
- description: desc,
34045
- globs: globList(globsText)
34883
+ const EXPORTS = {
34884
+ rules: "RulesPanel",
34885
+ debug: "DebugPanel"
34886
+ };
34887
+ const loads = /* @__PURE__ */ new Map();
34888
+ let generation = 0;
34889
+ let importer;
34890
+ /** @public @author ddj 2026年10月22号 @param transport Injected ModuleLoader require.async, never a global require. @description Start a new plugin generation and discard older results. */
34891
+ function setPanelImport(transport) {
34892
+ generation++;
34893
+ loads.clear();
34894
+ importer = transport;
34895
+ }
34896
+ /** @public @author ddj 2026年10月22号 @param key Fixed built-in panel key. @returns Shared pending load or cached component. @throws Unsupported loader or invalid chunk export. */
34897
+ function loadPanel(key) {
34898
+ const cached = loads.get(key);
34899
+ if (cached?.component) return Promise.resolve(cached.component);
34900
+ if (cached?.pending) return cached.pending;
34901
+ if (!importer) return Promise.reject(/* @__PURE__ */ new Error("当前 DSH 不支持包内面板加载,请升级宿主"));
34902
+ const epoch = generation;
34903
+ const transport = importer;
34904
+ const state = {};
34905
+ const pending = Promise.resolve().then(() => transport(FILES[key])).then((exports) => {
34906
+ const component = exports[EXPORTS[key]];
34907
+ if (typeof component !== "function") throw new Error("面板脚本缺少组件导出");
34908
+ if (epoch !== generation) throw new Error("面板加载已失效,请重试");
34909
+ state.component = component;
34910
+ return state.component;
34911
+ }).catch((error) => {
34912
+ if (loads.get(key) === state) loads.delete(key);
34913
+ throw error;
34046
34914
  });
34915
+ state.pending = pending;
34916
+ loads.set(key, state);
34917
+ return pending;
34047
34918
  }
34048
- /**
34049
- * 渲染一条规则行:左侧两行(文件名+相对提示 / 类型徽标+描述),右侧 编辑/删除 图标 + 滑动开关。
34050
- * @author ddj 2026年09月03号
34051
- * @param props.rule 规则元信息
34052
- * @param props.onEdit 编辑回调
34053
- * @param props.onRemove 删除回调
34054
- * @param props.onToggle 开关回调
34055
- * @returns 行元素
34056
- */
34057
- function RuleRow(props) {
34058
- const rule = props.rule;
34059
- const desc = [rule.error ? "解析失败:" + rule.error : null, rule.description || (rule.type === "auto" ? "globs: " + rule.globs.join(", ") : "")].filter(Boolean).join(" · ");
34060
- return react.default.createElement("div", { className: "edrv-rules-row" + (rule.enabled ? "" : " off") }, react.default.createElement("div", {
34061
- className: "edrv-rules-main",
34062
- onClick: () => props.onEdit(rule),
34063
- title: rule.absPath
34064
- }, react.default.createElement("div", { className: "edrv-rules-line1" }, react.default.createElement("span", { className: "edrv-rules-file" }, rule.file), !props.narrow ? react.default.createElement("span", { className: "edrv-rules-path" }, "[" + rule.relHint + rule.file + "]") : null, props.narrow && rule.scope === "user" && rule.origin === "legacy" ? react.default.createElement("span", {
34065
- className: "edrv-rules-path",
34066
- title: rule.absPath
34067
- }, "旧目录") : null, !rule.enabled ? react.default.createElement("span", { className: "edrv-rules-offtag" }, "已停用") : null), react.default.createElement("div", { className: "edrv-rules-sub" }, react.default.createElement("span", { className: "edrv-rules-sublabel" }, "类型:"), typeBadge(rule.type), desc ? react.default.createElement("span", {
34068
- className: "edrv-rules-desc",
34069
- title: desc
34070
- }, clampDesc(desc, props.descCap)) : null)), react.default.createElement("div", { className: "edrv-rules-actions" }, react.default.createElement("button", {
34071
- className: "edrv-rules-act",
34072
- title: "编辑",
34073
- onClick: () => props.onEdit(rule)
34074
- }, pencilIcon()), react.default.createElement("button", {
34075
- className: "edrv-rules-act",
34076
- title: "删除",
34077
- onClick: () => props.onRemove(rule)
34078
- }, trashIcon()), react.default.createElement("button", {
34079
- className: "edrv-rules-switch" + (rule.enabled ? " on" : ""),
34080
- role: "switch",
34081
- "aria-checked": rule.enabled,
34082
- title: rule.enabled ? "已启用(点击停用)" : "已停用(点击启用)",
34083
- onClick: () => props.onToggle(rule)
34084
- }, react.default.createElement("span", { className: "edrv-rules-knob" }))));
34085
- }
34086
- /**
34087
- * 新建/编辑表单:面包屑头部 + 规则类型下拉/描述/globs 控件行 + 原始 .mdc 文本域 + 保存/取消。
34088
- * @author ddj 2026年09月03号
34089
- * @param props.form 表单状态(mode/scope/workspacePath/relHint/file/content/fmType/desc/globs/error/saving)
34090
- * @param props.onType 类型下拉变更回调
34091
- * @param props.onDesc 描述输入回调
34092
- * @param props.onGlobs globs 输入回调
34093
- * @param props.onBody 文本域编辑回调
34094
- * @param props.onSave 保存回调
34095
- * @param props.onCancel 取消回调
34096
- * @returns 表单元素
34097
- */
34098
- function RuleEditor(props) {
34099
- const form = props.form;
34100
- const isNew = form.mode === "new";
34101
- const crumb = (form.scope === "project" ? "项目规则" : "用户规则") + " > " + form.relHint + " > " + (form.file || "新建规则.mdc");
34102
- return react.default.createElement("div", { className: "edrv-rules-editor" }, react.default.createElement("div", { className: "edrv-rules-editor-head" }, react.default.createElement("span", {
34103
- className: "edrv-rules-crumb",
34104
- title: crumb
34105
- }, crumb), react.default.createElement("button", {
34106
- className: "edrv-rules-act",
34107
- title: "取消",
34108
- onClick: props.onCancel
34109
- }, "✕")), react.default.createElement("div", { className: "edrv-rules-editor-ctl" }, react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "规则类型"), react.default.createElement("select", {
34110
- className: "edrv-rules-select",
34111
- value: form.fmType,
34112
- onChange: props.onType
34113
- }, Object.keys(TYPE_LABEL).map((key) => react.default.createElement("option", {
34114
- key,
34115
- value: key
34116
- }, TYPE_LABEL[key]))), react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "描述"), react.default.createElement("input", {
34117
- className: "edrv-rules-input edrv-rules-desc-input",
34118
- value: form.desc,
34119
- spellCheck: false,
34120
- placeholder: isNew ? "规则用途说明(写入 frontmatter description)" : "规则用途说明",
34121
- onChange: props.onDesc
34122
- })), form.fmType === "auto" ? react.default.createElement("div", { className: "edrv-rules-editor-ctl" }, react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "globs"), react.default.createElement("input", {
34123
- className: "edrv-rules-input",
34124
- value: form.globs,
34125
- spellCheck: false,
34126
- placeholder: "src/**/*.ts, *.md(逗号分隔,命中才注入)",
34127
- onChange: props.onGlobs
34128
- })) : null, react.default.createElement("textarea", {
34129
- className: "edrv-rules-body",
34130
- spellCheck: false,
34131
- value: form.content,
34132
- onChange: props.onBody
34133
- }), react.default.createElement("div", { className: "edrv-rules-editor-hint" }, "类型/描述/globs 与 frontmatter 双向同步;enabled 与其余键原样保留;无 frontmatter 的纯 markdown 视为「手动」规则"), form.error ? react.default.createElement("div", { className: "edrv-rules-err" }, form.error) : null, react.default.createElement("div", { className: "edrv-rules-editor-foot" }, react.default.createElement("button", {
34134
- className: "edrv-rules-save",
34135
- disabled: form.saving,
34136
- onClick: props.onSave
34137
- }, form.saving ? "保存中…" : "保存")));
34138
- }
34139
- /**
34140
- * 规则面板主体。
34141
- * @param props.ctx 面板共享上下文(notify 可选)
34142
- */
34143
- function RulesPanel(props) {
34144
- const ctx = props?.ctx;
34145
- const notify = ctx?.notify ?? ((message) => {});
34146
- const cwd = ctx?.cwd ?? null;
34147
- const [tab, setTab] = react.default.useState(() => readLocal(CACHE_KEY.rules + "tab") === "project" ? "project" : "user");
34148
- const [data, setData] = react.default.useState(null);
34149
- const [error, setError] = react.default.useState("");
34150
- const panelRef = react.default.useRef(null);
34151
- const [panelW, setPanelW] = react.default.useState(0);
34152
- const [form, setForm] = react.default.useState(null);
34153
- const mountedRef = react.default.useRef(true);
34154
- /**
34155
- * 当前工作区对应的项目聚合(按 ctx.cwd 自动匹配;无会话/未注册返回 null)。
34156
- * @returns RuleProject 或 null
34157
- */
34158
- const activeProject = () => {
34159
- if (!cwd) return null;
34160
- return (data?.projects ?? []).find((p) => p.workspacePath === cwd) ?? null;
34161
- };
34162
- /** 拉取规则总列表(rules.list)。 */
34163
- const load = () => {
34164
- rpc("rules.list", {}).then((res) => {
34165
- if (!mountedRef.current) return;
34166
- if (res && res.ok) {
34167
- setData(res);
34168
- setError("");
34169
- } else setError(res?.error ?? "读取规则失败");
34170
- }).catch((e) => {
34171
- if (mountedRef.current) setError("读取规则异常: " + String(e));
34172
- });
34173
- };
34174
- react.default.useEffect(() => {
34175
- mountedRef.current = true;
34176
- load();
34919
+ /** @public @author ddj 2026年10月22号 @param props Fixed panel and current context. @returns Loading, explicit retry, or the loaded panel subtree. */
34920
+ function LazyPanel(props) {
34921
+ const [attempt, retry] = react.default.useState(0);
34922
+ const [view, setView] = react.default.useState({});
34923
+ /** @private @author ddj 2026年10月22号 @returns Cleanup invalidating callbacks after switch or unmount. */
34924
+ function loadEffect() {
34925
+ let active = true;
34926
+ setView({});
34927
+ /** @author ddj 2026年10月22号 @param component Loaded panel. */
34928
+ function ready(component) {
34929
+ if (active) setView({ component });
34930
+ }
34931
+ /** @author ddj 2026年10月22号 @param error Loader failure. */
34932
+ function failed(error) {
34933
+ if (active) setView({ error: String(error) });
34934
+ }
34935
+ loadPanel(props.panel).then(ready, failed);
34177
34936
  return () => {
34178
- mountedRef.current = false;
34937
+ active = false;
34179
34938
  };
34180
- }, []);
34181
- react.default.useEffect(() => {
34182
- const el = panelRef.current;
34183
- if (!el || typeof ResizeObserver === "undefined") return void 0;
34184
- const observer = new ResizeObserver(() => setPanelW(el.clientWidth));
34185
- observer.observe(el);
34186
- setPanelW(el.clientWidth);
34187
- return () => observer.disconnect();
34188
- }, []);
34189
- const descCap = panelW > 0 ? descCapOf(panelW) : DESC_MAX;
34190
- const narrow = panelW > 0 && panelW < NARROW_W;
34191
- /**
34192
- * 切换 Tab(记忆到 localStorage)。
34193
- * @param next 目标 Tab
34194
- */
34195
- const switchTab = (next) => {
34196
- setTab(next);
34197
- writeLocal(CACHE_KEY.rules + "tab", next);
34198
- };
34199
- /**
34200
- * 打开新建表单(按当前 Tab 决定作用域;项目规则跟随当前会话工作区)。
34201
- */
34202
- const openNew = () => {
34203
- const scope = tab === "project" ? "project" : "user";
34204
- if (scope === "project" && !cwd) {
34205
- notify("当前会话没有工作区,无法新建项目规则");
34206
- return;
34207
- }
34208
- const parsed = parseRuleFm(NEW_TEMPLATE);
34209
- setForm({
34210
- mode: "new",
34211
- scope,
34212
- origin: scope === "user" ? "current" : void 0,
34213
- workspacePath: scope === "project" ? cwd : void 0,
34214
- relHint: scope === "project" ? ".dsh/rules/" : "rules/",
34215
- file: "",
34216
- content: NEW_TEMPLATE,
34217
- fmType: parsed.type,
34218
- desc: parsed.description,
34219
- globs: parsed.globs.join(", "),
34220
- error: "",
34221
- saving: false
34222
- });
34223
- };
34224
- /**
34225
- * 打开编辑表单:先拉全文,再解析 frontmatter 回填类型/描述/globs 控件。
34226
- * @param rule 目标规则
34227
- */
34228
- const openEdit = (rule) => {
34229
- rpc("rules.read", {
34230
- scope: rule.scope,
34231
- origin: rule.origin,
34232
- workspacePath: rule.scope === "project" ? rule.workspacePath : void 0,
34233
- file: rule.file
34234
- }).then((res) => {
34235
- if (!res || !res.ok) {
34236
- notify(res?.error ?? "读取规则失败");
34237
- return;
34238
- }
34239
- const parsed = parseRuleFm(res.content);
34240
- setForm({
34241
- mode: "edit",
34242
- scope: rule.scope,
34243
- origin: rule.origin,
34244
- workspacePath: rule.workspacePath,
34245
- relHint: rule.relHint,
34246
- file: rule.file,
34247
- content: res.content,
34248
- fmType: parsed.type,
34249
- desc: parsed.description,
34250
- globs: parsed.globs.join(", "),
34251
- error: "",
34252
- saving: false
34253
- });
34254
- }).catch((e) => notify("读取规则异常: " + String(e)));
34255
- };
34256
- /** 类型下拉变更:frontmatter 按新类型重写,控件同步。 */
34257
- const onTypeChange = (e) => {
34258
- const nextType = e.target.value;
34259
- setForm((prev) => ({
34260
- ...prev,
34261
- fmType: nextType,
34262
- content: rebuildContent(prev.content, nextType, prev.desc, prev.globs),
34263
- error: ""
34264
- }));
34265
- };
34266
- /** 描述输入变更:同步 frontmatter description 行。 */
34267
- const onDescChange = (e) => {
34268
- const nextDesc = e.target.value;
34269
- setForm((prev) => ({
34270
- ...prev,
34271
- desc: nextDesc,
34272
- content: rebuildContent(prev.content, prev.fmType, nextDesc, prev.globs),
34273
- error: ""
34274
- }));
34275
- };
34276
- /** globs 输入变更(自动规则):同步 frontmatter globs 行。 */
34277
- const onGlobsChange = (e) => {
34278
- const nextGlobs = e.target.value;
34279
- setForm((prev) => ({
34280
- ...prev,
34281
- globs: nextGlobs,
34282
- content: rebuildContent(prev.content, prev.fmType, prev.desc, nextGlobs),
34283
- error: ""
34284
- }));
34285
- };
34286
- /** 文本域直接编辑:回读 frontmatter,同步类型/描述/globs 控件(文本域是唯一真源)。 */
34287
- const onBodyChange = (e) => {
34288
- const parsed = parseRuleFm(e.target.value);
34289
- setForm((prev) => ({
34290
- ...prev,
34291
- content: e.target.value,
34292
- fmType: parsed.type,
34293
- desc: parsed.description,
34294
- globs: parsed.globs.join(", ")
34295
- }));
34296
- };
34297
- /** 保存表单(rules.save;文件名非空 + 自动规则必须有 globs)。 */
34298
- const saveForm = () => {
34299
- const next = {
34300
- ...form,
34301
- saving: true,
34302
- error: ""
34303
- };
34304
- const file = String(form.file ?? "").trim();
34305
- if (!file) {
34306
- setForm({
34307
- ...next,
34308
- saving: false,
34309
- error: "文件名不能为空"
34310
- });
34311
- return;
34312
- }
34313
- if (form.fmType === "auto" && globList(form.globs).length === 0) {
34314
- setForm({
34315
- ...next,
34316
- saving: false,
34317
- error: "自动规则需要至少一个 glob(逗号分隔)"
34318
- });
34319
- return;
34320
- }
34321
- setForm(next);
34322
- rpc("rules.save", {
34323
- scope: form.scope,
34324
- origin: form.origin,
34325
- workspacePath: form.workspacePath,
34326
- file,
34327
- content: form.content
34328
- }).then((res) => {
34329
- if (res && res.ok) {
34330
- setForm(null);
34331
- notify("规则已保存:" + file);
34332
- load();
34333
- } else setForm((prev) => ({
34334
- ...prev,
34335
- saving: false,
34336
- error: res?.error ?? "保存失败"
34337
- }));
34338
- }).catch((e) => setForm((prev) => ({
34339
- ...prev,
34340
- saving: false,
34341
- error: "保存异常: " + String(e)
34342
- })));
34343
- };
34344
- /**
34345
- * 删除规则(confirm 后走 rules.remove)。
34346
- * @param rule 目标规则
34347
- */
34348
- const removeRule = (rule) => {
34349
- const reveal = rule.scope === "user" && rule.origin !== "legacy" ? "\n若旧目录有同名规则,删除后它将重新生效。" : "";
34350
- if (!window.confirm("删除规则 " + rule.absPath + "?(不可恢复)" + reveal)) return;
34351
- rpc("rules.remove", {
34352
- scope: rule.scope,
34353
- origin: rule.origin,
34354
- workspacePath: rule.workspacePath,
34355
- file: rule.file
34356
- }).then((res) => {
34357
- if (res && res.ok) {
34358
- notify("已删除:" + rule.file);
34359
- load();
34360
- } else notify(res?.error ?? "删除失败");
34361
- }).catch((e) => notify("删除异常: " + String(e)));
34362
- };
34363
- /**
34364
- * 切换启用开关:本地乐观翻转(按 workspacePath+file 定位,失败回滚)。
34365
- * @param rule 目标规则(project 已注入 workspacePath)
34366
- */
34367
- const toggleRule = (rule) => {
34368
- const enabled = !rule.enabled;
34369
- const applyLocal = (on) => setData((prev) => prev ? {
34370
- ...prev,
34371
- user: prev.user.map((r) => rule.scope === "user" && r.file === rule.file && (r.origin ?? "current") === (rule.origin ?? "current") ? {
34372
- ...r,
34373
- enabled: on
34374
- } : r),
34375
- projects: prev.projects.map((p) => p.workspacePath === rule.workspacePath ? {
34376
- ...p,
34377
- rules: p.rules.map((r) => r.file === rule.file ? {
34378
- ...r,
34379
- enabled: on
34380
- } : r)
34381
- } : p)
34382
- } : prev);
34383
- applyLocal(enabled);
34384
- rpc("rules.toggle", {
34385
- scope: rule.scope,
34386
- origin: rule.origin,
34387
- workspacePath: rule.workspacePath,
34388
- file: rule.file,
34389
- enabled
34390
- }).then((res) => {
34391
- if (res && res.ok) {
34392
- applyLocal(res.rule?.enabled === true);
34393
- notify(res.rule?.enabled === false ? "已停用:" + rule.file : "已启用:" + rule.file);
34394
- } else {
34395
- applyLocal(!enabled);
34396
- notify(res?.error ?? "切换失败");
34397
- }
34398
- }).catch((e) => {
34399
- applyLocal(!enabled);
34400
- notify("切换异常: " + String(e));
34401
- });
34402
- };
34403
- /** 当前列表数据(按 Tab 取用户规则或当前工作区的项目规则)。 */
34404
- const rowsOf = () => {
34405
- if (!data) return [];
34406
- if (tab === "user") return data.user;
34407
- return activeProject()?.rules ?? [];
34408
- };
34409
- const renderTabs = () => react.default.createElement("div", { className: "edrv-rules-tabs" }, react.default.createElement("button", {
34410
- className: "edrv-rules-tab" + (tab === "user" ? " on" : ""),
34411
- onClick: () => switchTab("user")
34412
- }, "用户规则"), react.default.createElement("button", {
34413
- className: "edrv-rules-tab" + (tab === "project" ? " on" : ""),
34414
- onClick: () => switchTab("project")
34415
- }, "项目规则"), react.default.createElement("button", {
34416
- className: "edrv-rules-new",
34417
- title: "新建规则",
34418
- onClick: openNew
34419
- }, "+ 新建规则"));
34420
- /** 项目 Tab 的工作区展示(不可编辑,自动跟随当前会话)。 */
34421
- const renderWsLine = () => {
34422
- if (tab !== "project") return null;
34423
- return react.default.createElement("div", {
34424
- className: "edrv-rules-ws-line",
34425
- title: cwd ?? ""
34426
- }, "工作区:" + (cwd ?? "当前会话未打开工作区"));
34427
- };
34428
- const renderEmpty = () => {
34429
- if (tab === "project") {
34430
- if (!cwd) return "当前会话没有工作区,项目规则不可用(项目规则跟随会话所在工作区)";
34431
- if (!data?.projects?.length) return "当前 DSH 没有已注册的工作区;在目标工作区打开一次会话后再来管理";
34432
- if (!activeProject()) return "当前工作区未注册为 DSH workspace,暂不能管理其项目规则";
34433
- return "<工作区>/.dsh/rules/ 还没有规则,点击「+ 新建规则」创建";
34434
- }
34435
- return "还没有用户规则(当前 DSH_HOME/rules/ 或旧目录 ~/.dsh/rules/),点击「+ 新建规则」创建";
34436
- };
34437
- const renderBody = () => {
34438
- if (form) return react.default.createElement(RuleEditor, {
34439
- form,
34440
- onType: onTypeChange,
34441
- onDesc: onDescChange,
34442
- onGlobs: onGlobsChange,
34443
- onBody: onBodyChange,
34444
- onSave: saveForm,
34445
- onCancel: () => setForm(null)
34446
- });
34447
- if (!data) return react.default.createElement("div", { className: "edrv-rules-empty" }, "加载中…");
34448
- if (error) return react.default.createElement("div", { className: "edrv-rules-err" }, error);
34449
- const rows = rowsOf();
34450
- if (!rows.length) return react.default.createElement("div", { className: "edrv-rules-empty" }, renderEmpty());
34451
- const common = {
34452
- descCap,
34453
- narrow,
34454
- onEdit: (rule) => openEdit({
34455
- ...rule,
34456
- workspacePath: rule.scope === "project" ? cwd : void 0
34457
- }),
34458
- onRemove: (rule) => removeRule({
34459
- ...rule,
34460
- workspacePath: rule.scope === "project" ? cwd : void 0
34461
- }),
34462
- onToggle: (rule) => toggleRule({
34463
- ...rule,
34464
- workspacePath: rule.scope === "project" ? cwd : void 0
34465
- })
34466
- };
34467
- return react.default.createElement("div", { className: "edrv-rules-list" }, rows.map((rule) => react.default.createElement(RuleRow, {
34468
- key: rule.scope + ":" + (rule.origin ?? "project") + ":" + rule.file,
34469
- rule,
34470
- ...common
34471
- })));
34472
- };
34939
+ }
34940
+ react.default.useEffect(loadEffect, [props.panel, attempt]);
34941
+ /** @private @author ddj 2026年10月22号 @description Explicit retry; failures never loop automatically. */
34942
+ function onRetry() {
34943
+ retry(attempt + 1);
34944
+ }
34945
+ if (view.component) return react.default.createElement(view.component, { ctx: props.ctx });
34473
34946
  return react.default.createElement("div", {
34474
- className: "edrv-rules-panel",
34475
- ref: panelRef
34476
- }, renderTabs(), renderWsLine(), react.default.createElement("div", { className: "edrv-rules-hint" }, tab === "user" ? "读取 DSH_HOME/rules/ 与 ~/.dsh/rules/;同名时当前目录优先,新建保存到当前目录" : "项目规则仅对所在工作区的会话生效(存放于 <工作区>/.dsh/rules/)"), renderBody());
34947
+ className: "edrv-empty",
34948
+ role: view.error ? "alert" : "status"
34949
+ }, view.error ?? "正在加载面板…", view.error && react.default.createElement("button", { onClick: onRetry }, "重试"));
34477
34950
  }
34478
34951
  //#endregion
34479
34952
  //#region src/client/ui/SvnAiPlanDialog.ts
@@ -35203,7 +35676,7 @@ window.__ModuleLoader__.load({
35203
35676
  const [busy, setBusy] = react.default.useState(false);
35204
35677
  const [remote, setRemote] = react.default.useState(null);
35205
35678
  const [remoteBusy, setRemoteBusy] = react.default.useState(false);
35206
- const [sizes, setSizes] = react.default.useState({});
35679
+ const [sizeSnapshot, setSizes] = react.default.useState(null);
35207
35680
  const [renderLimit, setRenderLimit] = react.default.useState(RENDER_STEP);
35208
35681
  const ctxRef = react.default.useRef(ctx);
35209
35682
  ctxRef.current = ctx;
@@ -35304,17 +35777,39 @@ window.__ModuleLoader__.load({
35304
35777
  const missingEntries = react.default.useMemo(() => (entries ?? []).filter((entry) => entry.status === "missing"), [entries]);
35305
35778
  const unversionedEntries = react.default.useMemo(() => (entries ?? []).filter((entry) => entry.status === "unversioned"), [entries]);
35306
35779
  const requested = react.default.useMemo(() => [...missingEntries, ...unversionedEntries].slice(0, 200).map((entry) => entry.path), [missingEntries, unversionedEntries]);
35307
- const candidateKey = react.default.useMemo(() => requested.join("\n"), [requested]);
35308
- const pairsNeeded = missingEntries.length > 0 && unversionedEntries.length > 0;
35309
- const pairsKnown = pairsNeeded && requested.every((path) => path in sizes);
35310
- react.default.useEffect(() => {
35311
- if (pairsKnown || !requested.length) return;
35312
- svnFileSizes(sessionId, requested).then((table) => setSizes(table));
35313
- }, [
35314
- pairsKnown,
35780
+ const candidateKey = JSON.stringify(requested);
35781
+ const sizeOwner = JSON.stringify([
35782
+ scope,
35783
+ sessionId,
35315
35784
  candidateKey,
35316
- sessionId
35785
+ managed
35317
35786
  ]);
35787
+ const sizeOwnerRef = react.default.useRef(sizeOwner);
35788
+ sizeOwnerRef.current = sizeOwner;
35789
+ const sizes = sizeSnapshot?.owner === sizeOwner ? sizeSnapshot.table : {};
35790
+ const pairsNeeded = managed && missingEntries.length > 0 && unversionedEntries.length > 0;
35791
+ const pairsKnown = pairsNeeded && requested.every((path) => path in sizes);
35792
+ /** @private @author ddj 2026年10月22号 @returns Cleanup invalidating this request. @description Keep sizes scoped to this candidate generation; failures do not retry in a render loop. */
35793
+ function loadPairSizes() {
35794
+ if (!pairsNeeded || !requested.length) return;
35795
+ let disposed = false;
35796
+ /** @author ddj 2026年10月22号 @param table Candidate sizes. @description Publish only to the current request owner. */
35797
+ function acceptSizes(table) {
35798
+ if (!disposed && sizeOwnerRef.current === sizeOwner) setSizes({
35799
+ owner: sizeOwner,
35800
+ table
35801
+ });
35802
+ }
35803
+ /** @author ddj 2026年10月22号 @description Failed reads leave pairing unknown without breaking the changes list. */
35804
+ function ignoreFailure() {}
35805
+ /** @author ddj 2026年10月22号 @description Revoke this request on dependency change or unmount. */
35806
+ function cancelSizes() {
35807
+ disposed = true;
35808
+ }
35809
+ svnFileSizes(sessionId, requested).then(acceptSizes, ignoreFailure);
35810
+ return cancelSizes;
35811
+ }
35812
+ react.default.useEffect(loadPairSizes, [sizeOwner, pairsNeeded]);
35318
35813
  const pairNoteOf = react.default.useMemo(() => {
35319
35814
  if (!pairsKnown) return {};
35320
35815
  const notes = {};
@@ -35786,851 +36281,6 @@ window.__ModuleLoader__.load({
35786
36281
  }), remoteEl, pairCapEl, ...toolbar, react.default.createElement("div", { className: "edrv-svn-list" }, svnListBody(listState, handlers)), menuEl, aiPlanEl);
35787
36282
  }
35788
36283
  //#endregion
35789
- //#region src/client/dap/variableTree.ts
35790
- /**
35791
- * 将变量树展平为 sibling rows;循环 ref 只展开一次,避免异常适配器数据造成递归。
35792
- * @author ddj 2026年09月29号
35793
- * @param roots 根变量
35794
- * @param expanded 已展开的 variablesReference
35795
- * @param cache ref → 子变量缓存
35796
- * @param options 行数/深度上限
35797
- */
35798
- function flattenVariableRows(roots, expanded, cache, options = {}) {
35799
- const rows = [];
35800
- const maxRows = options.maxRows ?? 1e3;
35801
- const maxDepth = options.maxDepth ?? 32;
35802
- const walk = (items, depth, ancestry, prefix) => {
35803
- if (depth > maxDepth || rows.length >= maxRows) return;
35804
- for (let index = 0; index < items.length && rows.length < maxRows; index += 1) {
35805
- const variable = items[index];
35806
- const key = prefix + "/" + index + ":" + variable.name;
35807
- rows.push({
35808
- key,
35809
- variable,
35810
- depth
35811
- });
35812
- if (variable.ref <= 0 || !expanded.has(variable.ref) || ancestry.has(variable.ref)) continue;
35813
- const children = cache.get(variable.ref);
35814
- if (!children?.length) continue;
35815
- const nextAncestry = new Set(ancestry);
35816
- nextAncestry.add(variable.ref);
35817
- walk(children, depth + 1, nextAncestry, key);
35818
- }
35819
- };
35820
- walk(roots, 0, /* @__PURE__ */ new Set(), "root");
35821
- return rows;
35822
- }
35823
- //#endregion
35824
- //#region src/client/dap/bpRowMenu.ts
35825
- /**
35826
- * 构造调试面板断点行右键菜单条目。
35827
- * @author ddj 2026年09月21号
35828
- * @param state 行内断点状态
35829
- * @returns 菜单条目列表(顺序即展示顺序)
35830
- */
35831
- function buildBpRowMenu(state) {
35832
- return [
35833
- {
35834
- id: "edit",
35835
- label: "编辑断点…"
35836
- },
35837
- {
35838
- id: "edit-condition",
35839
- label: "编辑条件…"
35840
- },
35841
- {
35842
- id: "edit-hit-count",
35843
- label: "编辑命中次数…"
35844
- },
35845
- {
35846
- id: "edit-logpoint",
35847
- label: "编辑记录点…"
35848
- },
35849
- state.enabled !== false ? {
35850
- id: "disable",
35851
- label: "禁用断点",
35852
- separator: true
35853
- } : {
35854
- id: "enable",
35855
- label: "启用断点",
35856
- separator: true
35857
- },
35858
- {
35859
- id: "remove",
35860
- label: "删除断点"
35861
- }
35862
- ];
35863
- }
35864
- //#endregion
35865
- //#region src/client/dap/panelSplit.ts
35866
- /**
35867
- * dsh-vscode-mode client — 调试面板四段分栏高度(纯函数 + localStorage 持久)。
35868
- * 对齐 VS Code/CodeBuddy splitview 语义:段间 sash 拖动时相邻两段高度重分配
35869
- * (拖下 = 上段增高、下段减矮;任一段触到夹取界时拖动量被"吸收",分隔条停在可行位置)。
35870
- * 存储:`edrv.dap.split.<scope>` → { variables, watch, stack, breakpoints }。
35871
- * 作者 ddj 2026年09月21号
35872
- */
35873
- /** 分栏段 key(与 DebugPanel section key 一致)。 */
35874
- const SPLIT_KEYS = [
35875
- "variables",
35876
- "watch",
35877
- "stack",
35878
- "breakpoints"
35879
- ];
35880
- /** 默认高度(沿用重构前各段 max-height 值,改前改后视觉一致)。 */
35881
- const DEFAULT_HEIGHTS = {
35882
- variables: 360,
35883
- watch: 220,
35884
- stack: 180,
35885
- breakpoints: 260
35886
- };
35887
- /** 单段最大高度(防无限拖爆面板)。 */
35888
- const MAX_H = 1200;
35889
- /** localStorage key 前缀。 */
35890
- const KEY_PREFIX = "edrv.dap.split.";
35891
- /**
35892
- * 夹取单段高度到 [MIN_H, MAX_H](非有限数回退最小值)。
35893
- * @author ddj 2026年09月21号
35894
- * @param px 目标高度
35895
- */
35896
- function clampSectionHeight(px) {
35897
- if (!Number.isFinite(px)) return 64;
35898
- return Math.min(MAX_H, Math.max(64, Math.round(px)));
35899
- }
35900
- /**
35901
- * 归一化高度表:非对象/缺 key 回退默认,非法值丢弃(损坏安全)。
35902
- * @author ddj 2026年09月21号
35903
- * @param input localStorage 解析结果
35904
- */
35905
- function sanitizeHeights(input) {
35906
- const out = { ...DEFAULT_HEIGHTS };
35907
- if (!input || typeof input !== "object" || Array.isArray(input)) return out;
35908
- const data = input;
35909
- for (const key of SPLIT_KEYS) {
35910
- const value = data[key];
35911
- if (typeof value === "number" && Number.isFinite(value)) out[key] = clampSectionHeight(value);
35912
- }
35913
- return out;
35914
- }
35915
- /**
35916
- * 相邻两段高度重分配:above += dy、below −= dy,各自夹取;
35917
- * 一段被夹取吸收的量补给另一段(分隔条停在可行位置,拖动量尽量生效)。
35918
- * @author ddj 2026年09月21号
35919
- * @param heights 拖动起点高度表(不修改入参)
35920
- * @param above 上邻段 key
35921
- * @param below 下邻段 key
35922
- * @param dy 拖动位移(>0 向下拖 = 上段增高)
35923
- * @returns 新高度表
35924
- */
35925
- function redistribute(heights, above, below, dy) {
35926
- const clean = Math.round(Number.isFinite(dy) ? dy : 0);
35927
- const next = { ...heights };
35928
- const startAbove = heights[above] ?? 64;
35929
- const startBelow = heights[below] ?? 64;
35930
- const targetAbove = clampSectionHeight(startAbove + clean);
35931
- const targetBelow = clampSectionHeight(startBelow - clean);
35932
- next[above] = targetAbove;
35933
- next[below] = targetBelow;
35934
- const lostAbove = startAbove + clean - targetAbove;
35935
- const lostBelow = startBelow - clean - targetBelow;
35936
- if (lostAbove > 0) next[below] = clampSectionHeight(targetBelow + lostAbove);
35937
- else if (lostBelow > 0) next[above] = clampSectionHeight(targetAbove + lostBelow);
35938
- return next;
35939
- }
35940
- /**
35941
- * 读高度表(缺失/损坏回退默认值)。
35942
- * @author ddj 2026年09月21号
35943
- * @param scope 状态作用域
35944
- */
35945
- function loadSectionHeights(scope) {
35946
- if (!scope) return { ...DEFAULT_HEIGHTS };
35947
- try {
35948
- const raw = localStorage.getItem(KEY_PREFIX + scope);
35949
- return sanitizeHeights(raw ? JSON.parse(raw) : null);
35950
- } catch {
35951
- return { ...DEFAULT_HEIGHTS };
35952
- }
35953
- }
35954
- /**
35955
- * 写高度表(写失败静默;写入前归一化)。
35956
- * @author ddj 2026年09月21号
35957
- * @param scope 状态作用域
35958
- * @param heights 高度表
35959
- */
35960
- function saveSectionHeights(scope, heights) {
35961
- try {
35962
- localStorage.setItem(KEY_PREFIX + scope, JSON.stringify(sanitizeHeights(heights)));
35963
- } catch {}
35964
- }
35965
- //#endregion
35966
- //#region src/client/sidebar/panels/DebugPanel.ts
35967
- /**
35968
- * dsh-vscode-mode client — 侧边栏「调试」面板(对齐 CodeBuddy「运行和调试」视图)。
35969
- * 顶部工具条:配置下拉 + 启动/继续 + 暂停 + 单步组 + 停止(禁用逻辑镜像编辑器浮动条)。
35970
- * 四段折叠区(变量 / 监视 / 调用堆栈 / 断点,图2 顺序)+ 底部调试控制台(REPL);
35971
- * 每段头部可折叠(状态按 scope 持久化)且带悬浮动作按钮。
35972
- * 断点多方式管理(对齐 CodeBuddy 断点区):段头「新建断点」菜单(条件断点/记录点,
35973
- * 落在活动编辑器光标行)、禁用/启用所有、删除所有;断点行右键菜单(编辑条件/命中次数/
35974
- * 记录点/启停/删除)→ 行内展开编辑器(Enter 提交 / Escape 取消,字段语义同 BpWidget);
35975
- * 行内点展示适配器回执(未验证 = 空心点 + tooltip)。
35976
- * 数据自取:paused 时经 dapStore 拉栈/作用域/变量/监视求值;断点读写 dapStore 断点表;
35977
- * 输出流 = host poll 事件缓存(outputs)。host 不可达或未暂停时各段空态降级。
35978
- * 断点行整行可点:除启停 checkbox 与删除 × 外,点击任一处即打开并定位到该断点行。
35979
- * 作者 ddj 2026年09月29号 / 2026年09月21号
35980
- */
35981
- /** 变量展开缓存上限(ref → 子级;防长会话累积)。 */
35982
- const VAR_CACHE_CAP = 200;
35983
- /** REPL 历史行上限。 */
35984
- const REPL_CAP = 200;
35985
- /** 监视持久化 key 前缀(CACHE_KEY 无调试段,自管命名空间)。 */
35986
- const WATCH_KEY = "edrv.dap.watch.";
35987
- /** 段折叠持久化 key 前缀(存折叠段名数组;损坏安全,默认全展开)。 */
35988
- const FOLD_KEY = "edrv.dap.fold.";
35989
- /** 调试配置文件(工作区相对路径;⚙ 按钮跳转/新建目标;与 VS Code 的 .vscode/launch.json 互不干扰)。 */
35990
- const LAUNCH_REL = DAP_LAUNCH_REL;
35991
- /** 新建调试配置模板(JSONC;示例按本机已装的 EmmyLua 适配器写)。 */
35992
- const LAUNCH_TEMPLATE = `{
35993
- // DSH 调试配置(插件专属 .dsh/launch.json,支持注释;改完整页刷新生效)
35994
- // type 来自本机已装扩展的 contributes.debuggers(EmmyLua:emmylua_attach / emmylua_launch / emmylua_new)
35995
- "version": "0.2.0",
35996
- "configurations": [
35997
- {
35998
- "name": "附加 Unity (xLua)",
35999
- "type": "emmylua_attach",
36000
- "request": "attach",
36001
- "processName": "Unity",
36002
- "captureLog": true
36003
- }
36004
- ]
36005
- }`;
36006
- /** 相位徽标文案。 */
36007
- const PHASE_LABEL = {
36008
- idle: "未启动",
36009
- starting: "启动中…",
36010
- waiting: "等待目标…",
36011
- running: "运行中",
36012
- paused: "已暂停",
36013
- terminated: "已结束"
36014
- };
36015
- /** 读取监视表达式列表(损坏安全)。 */
36016
- function loadWatches(scope) {
36017
- if (!scope) return [];
36018
- try {
36019
- const raw = window.localStorage.getItem(WATCH_KEY + scope);
36020
- const list = raw ? JSON.parse(raw) : [];
36021
- return Array.isArray(list) ? list.filter((item) => typeof item === "string" && item.trim()) : [];
36022
- } catch {
36023
- return [];
36024
- }
36025
- }
36026
- /** 写监视表达式列表。 */
36027
- function saveWatches(scope, list) {
36028
- try {
36029
- window.localStorage.setItem(WATCH_KEY + scope, JSON.stringify(list));
36030
- } catch {}
36031
- }
36032
- /** 读折叠段名列表(损坏安全;非字符串项丢弃)。 */
36033
- function loadFolds(scope) {
36034
- if (!scope) return [];
36035
- try {
36036
- const raw = window.localStorage.getItem(FOLD_KEY + scope);
36037
- const list = raw ? JSON.parse(raw) : [];
36038
- return Array.isArray(list) ? list.filter((item) => typeof item === "string" && item) : [];
36039
- } catch {
36040
- return [];
36041
- }
36042
- }
36043
- /** 写折叠段名列表(写失败静默)。 */
36044
- function saveFolds(scope, list) {
36045
- try {
36046
- window.localStorage.setItem(FOLD_KEY + scope, JSON.stringify(list));
36047
- } catch {}
36048
- }
36049
- /**
36050
- * 「调试」面板组件。
36051
- * @param ctx 侧栏上下文(openFileAt 跳转 / scope 持久化 / cwd / editor 光标)
36052
- */
36053
- function DebugPanel({ ctx }) {
36054
- const scope = ctx?.scope || "";
36055
- const [, setTick] = react.default.useState(0);
36056
- react.default.useEffect(() => dapStore.subscribe(() => setTick((t) => t + 1)), []);
36057
- const snap = dapStore.getSnapshot();
36058
- const paused = snap.phase === "paused";
36059
- const dapActive = snap.phase !== "idle" && snap.phase !== "terminated";
36060
- const [stacks, setStacks] = react.default.useState([]);
36061
- const [scopes, setScopes] = react.default.useState([]);
36062
- const [varCache, setVarCache] = react.default.useState(() => /* @__PURE__ */ new Map());
36063
- const [expanded, setExpanded] = react.default.useState(() => /* @__PURE__ */ new Set());
36064
- const [watches, setWatches] = react.default.useState(() => loadWatches(scope));
36065
- const [watchHits, setWatchHits] = react.default.useState({});
36066
- const [watchDraft, setWatchDraft] = react.default.useState("");
36067
- const [replLog, setReplLog] = react.default.useState([]);
36068
- const [replDraft, setReplDraft] = react.default.useState("");
36069
- const [folds, setFolds] = react.default.useState(() => new Set(loadFolds(scope)));
36070
- const [heights, setHeights] = react.default.useState(() => loadSectionHeights(scope));
36071
- const [splitDragging, setSplitDragging] = react.default.useState(false);
36072
- const [bpMenu, setBpMenu] = react.default.useState(null);
36073
- const [newBpMenu, setNewBpMenu] = react.default.useState(null);
36074
- const [bpEdit, setBpEdit] = react.default.useState(null);
36075
- const [pickPid, setPickPid] = react.default.useState(0);
36076
- const watchInputRef = react.default.useRef(null);
36077
- const stackTopKey = paused && snap.topFrame ? snap.topFrame.file + ":" + snap.topFrame.line : "";
36078
- react.default.useEffect(() => {
36079
- if (!ctx?.cwd) return;
36080
- dapStore.setWorkspace(ctx.cwd);
36081
- dapStore.ensureConfigs();
36082
- }, [ctx?.cwd]);
36083
- const [reloadTick, setReloadTick] = react.default.useState(0);
36084
- react.default.useEffect(() => {
36085
- if (!paused) {
36086
- setStacks([]);
36087
- setScopes([]);
36088
- setVarCache(/* @__PURE__ */ new Map());
36089
- setExpanded(/* @__PURE__ */ new Set());
36090
- setWatchHits({});
36091
- return;
36092
- }
36093
- let alive = true;
36094
- const load = async () => {
36095
- const frames = await dapStore.stackTrace();
36096
- if (!alive) return;
36097
- setStacks(frames);
36098
- const list = await dapStore.scopes(snap.selectedFrameId);
36099
- if (!alive) return;
36100
- setScopes(list);
36101
- const first = list[0];
36102
- if (first?.ref) {
36103
- const vars = await dapStore.variables(first.ref);
36104
- if (!alive) return;
36105
- setVarCache((prev) => new Map(prev).set(first.ref, vars));
36106
- setExpanded((prev) => new Set(prev).add(first.ref));
36107
- }
36108
- const hits = {};
36109
- for (const expr of watches) hits[expr] = await dapStore.evaluate(expr, snap.selectedFrameId);
36110
- if (alive) setWatchHits(hits);
36111
- };
36112
- load();
36113
- return () => {
36114
- alive = false;
36115
- };
36116
- }, [
36117
- paused,
36118
- stackTopKey,
36119
- snap.selectedFrameId,
36120
- watches,
36121
- reloadTick
36122
- ]);
36123
- const expandVar = async (ref) => {
36124
- setExpanded((prev) => {
36125
- const next = new Set(prev);
36126
- if (next.has(ref)) next.delete(ref);
36127
- else next.add(ref);
36128
- return next;
36129
- });
36130
- if (!varCache.has(ref)) {
36131
- const vars = await dapStore.variables(ref);
36132
- setVarCache((prev) => {
36133
- const next = new Map(prev);
36134
- next.set(ref, vars);
36135
- if (next.size > VAR_CACHE_CAP) next.delete(next.keys().next().value);
36136
- return next;
36137
- });
36138
- }
36139
- };
36140
- const addWatch = () => {
36141
- const expr = watchDraft.trim();
36142
- if (!expr || watches.includes(expr)) {
36143
- setWatchDraft("");
36144
- return;
36145
- }
36146
- const next = [...watches, expr];
36147
- setWatches(next);
36148
- saveWatches(scope, next);
36149
- setWatchDraft("");
36150
- if (paused) dapStore.evaluate(expr, snap.selectedFrameId).then((r) => setWatchHits((prev) => ({
36151
- ...prev,
36152
- [expr]: r
36153
- })));
36154
- };
36155
- const removeWatch = (expr) => {
36156
- const next = watches.filter((item) => item !== expr);
36157
- setWatches(next);
36158
- saveWatches(scope, next);
36159
- };
36160
- /** 移除全部监视(段头动作;持久化与命中缓存同步清)。 */
36161
- const clearWatches = () => {
36162
- setWatches([]);
36163
- saveWatches(scope, []);
36164
- setWatchHits({});
36165
- };
36166
- /** 聚焦监视新增输入(段折叠时先展开,二次点击聚焦)。 */
36167
- const focusWatchInput = () => {
36168
- if (folds.has("watch")) {
36169
- toggleFold("watch");
36170
- return;
36171
- }
36172
- watchInputRef.current?.focus?.();
36173
- };
36174
- const runRepl = async () => {
36175
- const expr = replDraft.trim();
36176
- if (!expr) return;
36177
- setReplDraft("");
36178
- const r = await dapStore.evaluate(expr, snap.selectedFrameId);
36179
- setReplLog((prev) => {
36180
- const next = [...prev, {
36181
- expr,
36182
- result: r.result,
36183
- type: r.type
36184
- }];
36185
- return next.length > REPL_CAP ? next.slice(next.length - REPL_CAP) : next;
36186
- });
36187
- };
36188
- /** 切换段折叠(持久化到 localStorage)。 */
36189
- const toggleFold = (key) => {
36190
- setFolds((prev) => {
36191
- const next = new Set(prev);
36192
- if (next.has(key)) next.delete(key);
36193
- else next.add(key);
36194
- saveFolds(scope, [...next]);
36195
- return next;
36196
- });
36197
- };
36198
- const splitCleanupRef = react.default.useRef(null);
36199
- react.default.useEffect(() => () => {
36200
- const cleanup = splitCleanupRef.current;
36201
- if (cleanup) cleanup();
36202
- }, []);
36203
- /** 段间 sash 拖动:以起点高度为基准按 dy 相邻重分配;pointerup 持久化(镜像 dapbar 拖动模式)。 */
36204
- const startSplitDrag = (above, below) => (e) => {
36205
- e.preventDefault();
36206
- const startY = e.clientY;
36207
- const startHeights = { ...heights };
36208
- const onMove = (ev) => {
36209
- setHeights(redistribute(startHeights, above, below, ev.clientY - startY));
36210
- };
36211
- const detach = () => {
36212
- window.removeEventListener("pointermove", onMove);
36213
- window.removeEventListener("pointerup", onUp);
36214
- splitCleanupRef.current = null;
36215
- setSplitDragging(false);
36216
- };
36217
- const onUp = () => {
36218
- detach();
36219
- setHeights((prev) => {
36220
- saveSectionHeights(scope, prev);
36221
- return prev;
36222
- });
36223
- };
36224
- splitCleanupRef.current = detach;
36225
- window.addEventListener("pointermove", onMove);
36226
- window.addEventListener("pointerup", onUp);
36227
- setSplitDragging(true);
36228
- };
36229
- const cwd = ctx?.cwd || dapStore.workspace();
36230
- const bpMap = dapStore.bpMapOf(scope);
36231
- const bpRows = Object.entries(bpMap).flatMap(([path, list]) => list.map((entry) => ({
36232
- path,
36233
- entry
36234
- }))).sort((a, b) => a.path.localeCompare(b.path) || a.entry.line - b.entry.line);
36235
- const allBpEnabled = bpRows.length > 0 && bpRows.every((row) => row.entry.enabled);
36236
- const toggleBpEnabled = (row) => dapStore.setBpEnabled(scope, cwd, row.path, row.entry.line, !row.entry.enabled);
36237
- const removeBp = (row) => dapStore.toggleAt(scope, cwd, row.path, row.entry.line);
36238
- /** 删除所有断点(confirm 守卫;逐文件同步空载荷清空 Monaco 装饰与 host 断点)。 */
36239
- const removeAllBps = () => {
36240
- if (ctx?.confirm && !ctx.confirm("删除所有断点?")) return;
36241
- dapStore.removeAllBps(scope, cwd);
36242
- setBpEdit(null);
36243
- };
36244
- const jumpBp = (row) => {
36245
- const target = bpJumpTargetOf(row);
36246
- if (target) ctx?.openFileAt?.(target.path, target.line, target.column);
36247
- };
36248
- const autoBpModeOf = (entry) => entry?.logMessage ? BP_MODE.LOG_MESSAGE : entry?.hitCondition ? BP_MODE.HIT_COUNT : BP_MODE.EXPRESSION;
36249
- const openBpRowEditor = (path, line, entry, mode) => {
36250
- setBpEdit({
36251
- path,
36252
- line,
36253
- mode: typeof mode === "number" ? mode : autoBpModeOf(entry),
36254
- drafts: {
36255
- condition: entry?.condition || "",
36256
- hitCondition: entry?.hitCondition || "",
36257
- logMessage: entry?.logMessage || ""
36258
- }
36259
- });
36260
- };
36261
- const commitBpEdit = () => {
36262
- if (!bpEdit) return;
36263
- dapStore.applyBpFields(scope, cwd, bpEdit.path, bpEdit.line, { ...bpEdit.drafts });
36264
- setBpEdit(null);
36265
- ctx?.notify?.("断点已更新");
36266
- };
36267
- /** 断点行右键菜单动作分发(菜单数据由 buildBpRowMenu 产出)。 */
36268
- const runBpRowAction = (action, row) => {
36269
- if (action === "remove") {
36270
- removeBp(row);
36271
- return;
36272
- }
36273
- if (action === "enable" || action === "disable") {
36274
- dapStore.setBpEnabled(scope, cwd, row.path, row.entry.line, action === "enable");
36275
- return;
36276
- }
36277
- const mode = action === "edit-condition" ? BP_MODE.EXPRESSION : action === "edit-hit-count" ? BP_MODE.HIT_COUNT : action === "edit-logpoint" ? BP_MODE.LOG_MESSAGE : void 0;
36278
- openBpRowEditor(row.path, row.entry.line, row.entry, mode);
36279
- };
36280
- /** 段头「新建断点」:条件断点/记录点落在活动编辑器光标行(已有断点则直接编辑不翻转)。 */
36281
- const addBpAtCursor = (mode) => {
36282
- const ed = ctx?.editor?.();
36283
- const path = editorModelPathOf(ed);
36284
- const line = ed?.getPosition?.()?.lineNumber;
36285
- if (!path || !line || !Number.isFinite(line)) {
36286
- ctx?.notify?.("无活动文件,无法添加断点");
36287
- return;
36288
- }
36289
- if (!(dapStore.bpMapOf(scope)[path] ?? []).find((it) => it.line === line)) dapStore.toggleAt(scope, cwd, path, line);
36290
- const entry = (dapStore.bpMapOf(scope)[path] ?? []).find((it) => it.line === line);
36291
- if (entry) openBpRowEditor(path, line, entry, mode);
36292
- };
36293
- /** ⚙ 打开/新建调试配置:存在即跳转;缺失经确认后写模板(edrv.save 无 rev 仅用于新建,绝不覆盖已有文件)。 */
36294
- const openLaunchConfig = async () => {
36295
- if (!ctx?.cwd) {
36296
- ctx?.notify?.("无工作区,无法定位 " + LAUNCH_REL);
36297
- return;
36298
- }
36299
- try {
36300
- if ((await rpc("edrv.read", {
36301
- sessionId: ctx?.sessionId,
36302
- path: LAUNCH_REL
36303
- }).catch(() => null))?.ok) {
36304
- ctx?.openFile?.(LAUNCH_REL);
36305
- return;
36306
- }
36307
- } catch {}
36308
- if (ctx?.confirm && !ctx.confirm("未找到 " + LAUNCH_REL + ",是否创建调试配置模板?")) return;
36309
- try {
36310
- const saved = await rpc("edrv.save", {
36311
- sessionId: ctx?.sessionId,
36312
- path: LAUNCH_REL,
36313
- content: LAUNCH_TEMPLATE
36314
- });
36315
- if (!saved?.ok) {
36316
- ctx?.notify?.(saved?.error || "创建 " + LAUNCH_REL + " 失败");
36317
- return;
36318
- }
36319
- dapStore.ensureConfigs(true);
36320
- ctx?.openFile?.(LAUNCH_REL);
36321
- ctx?.notify?.("已创建 " + LAUNCH_REL);
36322
- } catch (error) {
36323
- ctx?.notify?.("创建 " + LAUNCH_REL + " 失败:" + String(error));
36324
- }
36325
- };
36326
- /** 段头动作按钮(hover 显隐,样式见 .edrv-dapseg-act)。 */
36327
- const segAct = (key, label, title, onClick) => react.default.createElement("button", {
36328
- key,
36329
- className: "edrv-dapseg-act",
36330
- title,
36331
- onClick: (e) => {
36332
- e.stopPropagation();
36333
- onClick(e);
36334
- }
36335
- }, label);
36336
- /** 折叠段壳:chevron + 标题 + 悬浮动作区 + 可折叠主体(高度由分栏 state 内联接管)。 */
36337
- const section = (title, key, variant, actions, children) => {
36338
- const folded = folds.has(key);
36339
- return react.default.createElement("div", {
36340
- className: "edrv-dapseg edrv-dapseg-" + variant,
36341
- key
36342
- }, react.default.createElement("div", {
36343
- className: "edrv-dapseg-head",
36344
- onClick: () => toggleFold(key)
36345
- }, react.default.createElement("span", {
36346
- className: "edrv-dapseg-chevron",
36347
- title: folded ? "展开" : "折叠"
36348
- }, folded ? "▸" : "▾"), react.default.createElement("span", { className: "edrv-dapseg-title" }, title), react.default.createElement("span", { style: { flex: 1 } }), ...folded ? [] : actions || []), ...folded ? [] : [react.default.createElement("div", {
36349
- className: "edrv-dapseg-body",
36350
- style: { height: (heights[key] ?? 200) + "px" }
36351
- }, ...children)]);
36352
- };
36353
- /** 段间分隔条(两侧段都展开时才渲染;拖动调整相邻两段高度占比)。 */
36354
- const sash = (above, below) => folds.has(above) || folds.has(below) ? null : react.default.createElement("div", {
36355
- key: "sash-" + above + "-" + below,
36356
- className: "edrv-dapseg-sash" + (splitDragging ? " dragging" : ""),
36357
- title: "拖动调整上下两段高度占比",
36358
- onPointerDown: startSplitDrag(above, below)
36359
- });
36360
- const renderVarRow = (row) => {
36361
- const v = row.variable;
36362
- const depth = row.depth + 1;
36363
- return react.default.createElement("div", {
36364
- key: row.key,
36365
- className: "edrv-dapvar",
36366
- style: { paddingLeft: 8 + depth * 12 }
36367
- }, v.ref > 0 ? react.default.createElement("button", {
36368
- className: "edrv-dapvar-tw",
36369
- title: expanded.has(v.ref) ? "折叠" : "展开",
36370
- onClick: () => {
36371
- expandVar(v.ref);
36372
- }
36373
- }, expanded.has(v.ref) ? "▾" : "▸") : react.default.createElement("span", { className: "edrv-dapvar-tw" }, ""), react.default.createElement("span", {
36374
- className: "edrv-dapvar-name",
36375
- title: v.name
36376
- }, v.name), react.default.createElement("span", {
36377
- className: "edrv-dapvar-val",
36378
- title: v.value
36379
- }, v.value), v.type ? react.default.createElement("span", { className: "edrv-dapvar-type" }, v.type) : null);
36380
- };
36381
- const stackRows = stacks.length ? stacks.map((f) => react.default.createElement("div", {
36382
- key: f.id,
36383
- className: "edrv-dapframe" + (f.id === snap.selectedFrameId ? " selected" : ""),
36384
- title: f.rawFile,
36385
- onClick: () => {
36386
- dapStore.selectFrame(f.id);
36387
- const target = f.file || f.rawFile;
36388
- if (target) ctx?.openFileAt?.(target, f.line, 1);
36389
- }
36390
- }, react.default.createElement("span", { className: "edrv-dapframe-name" }, f.name || "(anonymous)"), react.default.createElement("span", { className: "edrv-dapframe-loc" }, (f.file || f.rawFile) + ":" + f.line))) : [react.default.createElement("div", {
36391
- key: "empty",
36392
- className: "edrv-dapempty"
36393
- }, paused ? "(无调用栈)" : "暂停时显示调用堆栈")];
36394
- const scopeRows = scopes.flatMap((s) => [react.default.createElement("div", {
36395
- key: "scope" + s.name,
36396
- className: "edrv-dapscope"
36397
- }, s.name), ...flattenVariableRows(varCache.get(s.ref) ?? [], expanded, varCache, { maxRows: 800 }).map(renderVarRow)]);
36398
- const varRows = scopes.length ? scopeRows : [react.default.createElement("div", {
36399
- key: "e",
36400
- className: "edrv-dapempty"
36401
- }, "暂停时显示变量")];
36402
- const watchRows = watches.flatMap((expr) => {
36403
- const hit = watchHits[expr];
36404
- const rows = [react.default.createElement("div", {
36405
- key: expr,
36406
- className: "edrv-dapwatch"
36407
- }, react.default.createElement("button", {
36408
- className: "edrv-dapwatch-x",
36409
- title: "移除监视",
36410
- onClick: () => removeWatch(expr)
36411
- }, "×"), hit?.ref > 0 ? react.default.createElement("button", {
36412
- className: "edrv-dapvar-tw",
36413
- title: expanded.has(hit.ref) ? "折叠 table" : "展开 table",
36414
- onClick: () => {
36415
- expandVar(hit.ref);
36416
- }
36417
- }, expanded.has(hit.ref) ? "▾" : "▸") : react.default.createElement("span", { className: "edrv-dapvar-tw" }, ""), react.default.createElement("span", {
36418
- className: "edrv-dapvar-name",
36419
- title: expr
36420
- }, expr), react.default.createElement("span", {
36421
- className: "edrv-dapvar-val",
36422
- title: hit?.result || ""
36423
- }, hit ? "= " + hit.result : "= 不可用"), hit?.type ? react.default.createElement("span", { className: "edrv-dapvar-type" }, hit.type) : null)];
36424
- if (hit?.ref > 0 && expanded.has(hit.ref)) rows.push(...flattenVariableRows(varCache.get(hit.ref) ?? [], expanded, varCache, { maxRows: 500 }).map(renderVarRow));
36425
- return rows;
36426
- });
36427
- const watchSection = section("监视", "watch", "watch", [segAct("watch-add", "+", "新增监视表达式", focusWatchInput), segAct("watch-clear", "🗑", "移除全部监视", clearWatches)], [...watchRows, react.default.createElement("div", {
36428
- key: "add",
36429
- className: "edrv-dapwatch-add"
36430
- }, react.default.createElement("input", {
36431
- ref: watchInputRef,
36432
- className: "edrv-daprepl-in",
36433
- placeholder: "新增监视表达式,回车确认",
36434
- value: watchDraft,
36435
- onChange: (e) => setWatchDraft(e.target.value),
36436
- onKeyDown: (e) => {
36437
- if (e.key === "Enter") addWatch();
36438
- }
36439
- }))]);
36440
- const bpSectionChildren = bpRows.length ? bpRows.flatMap((row) => {
36441
- const base = row.path.split("/").pop() || row.path;
36442
- const dir = row.path.includes("/") ? row.path.slice(0, row.path.lastIndexOf("/")) : "";
36443
- const ack = dapStore.ackOf(row.path, row.entry.line);
36444
- const unverified = Boolean(ack && !ack.verified);
36445
- const editing = bpEdit && bpEdit.path === row.path && bpEdit.line === row.entry.line;
36446
- const rows = [react.default.createElement("div", {
36447
- key: row.path + ":" + row.entry.line,
36448
- className: "edrv-dapbp",
36449
- title: "跳转到 " + row.path + ":" + row.entry.line + (unverified && ack.message ? "(" + ack.message + ")" : ""),
36450
- onClick: () => jumpBp(row),
36451
- onContextMenu: (e) => {
36452
- e.preventDefault();
36453
- setBpMenu({
36454
- x: e.clientX,
36455
- y: e.clientY,
36456
- row
36457
- });
36458
- }
36459
- }, react.default.createElement("input", {
36460
- type: "checkbox",
36461
- checked: row.entry.enabled,
36462
- title: row.entry.enabled ? "禁用断点" : "启用断点",
36463
- onChange: () => toggleBpEnabled(row),
36464
- onClick: (e) => e.stopPropagation()
36465
- }), react.default.createElement("span", {
36466
- className: "edrv-dapbp-dot" + (row.entry.enabled ? "" : " off") + (unverified ? " unverified" : ""),
36467
- title: unverified && ack.message ? ack.message : void 0
36468
- }), react.default.createElement("button", {
36469
- className: "edrv-dapbp-file",
36470
- title: row.path
36471
- }, base), react.default.createElement("span", {
36472
- className: "edrv-dapbp-dir",
36473
- title: dir
36474
- }, dir), react.default.createElement("span", { className: "edrv-dapbp-line" }, row.entry.line), react.default.createElement("button", {
36475
- className: "edrv-dapbp-x",
36476
- title: "删除断点",
36477
- onClick: (e) => {
36478
- e.stopPropagation();
36479
- removeBp(row);
36480
- }
36481
- }, "×"))];
36482
- if (editing) {
36483
- const fieldKey = fieldOfMode(bpEdit.mode);
36484
- const modeItem = MODES.find((m) => m.mode === bpEdit.mode) ?? MODES[0];
36485
- rows.push(react.default.createElement("div", {
36486
- key: row.path + ":" + row.entry.line + ":edit",
36487
- className: "edrv-dapbp-edit"
36488
- }, react.default.createElement("select", {
36489
- className: "edrv-bpwidget-mode",
36490
- title: "断点类型",
36491
- value: String(bpEdit.mode),
36492
- onChange: (e) => setBpEdit({
36493
- ...bpEdit,
36494
- mode: Number(e.target.value)
36495
- })
36496
- }, MODES.filter((m) => m.mode !== BP_MODE.WAIT).map((m) => react.default.createElement("option", {
36497
- key: m.mode,
36498
- value: String(m.mode)
36499
- }, m.label))), react.default.createElement("input", {
36500
- className: "edrv-bpwidget-input",
36501
- autoFocus: true,
36502
- spellCheck: false,
36503
- placeholder: modeItem?.placeholder ?? "",
36504
- hidden: fieldKey === null,
36505
- value: fieldKey ? bpEdit.drafts[fieldKey] || "" : "",
36506
- onChange: (e) => setBpEdit({
36507
- ...bpEdit,
36508
- drafts: {
36509
- ...bpEdit.drafts,
36510
- [fieldKey]: e.target.value
36511
- }
36512
- }),
36513
- onKeyDown: (e) => {
36514
- if (e.key === "Enter") {
36515
- e.preventDefault();
36516
- commitBpEdit();
36517
- return;
36518
- }
36519
- if (e.key === "Escape") {
36520
- e.preventDefault();
36521
- setBpEdit(null);
36522
- }
36523
- }
36524
- })));
36525
- }
36526
- return rows;
36527
- }) : [react.default.createElement("div", {
36528
- key: "e",
36529
- className: "edrv-dapempty"
36530
- }, "在编辑器行首点击或 F9 添加断点")];
36531
- const bpSection = section("断点", "breakpoints", "breakpoints", [
36532
- segAct("bp-new", "新建断点 ▾", "添加条件断点 / 记录点(落在当前编辑器光标行)", (e) => setNewBpMenu({
36533
- x: e.clientX,
36534
- y: e.clientY
36535
- })),
36536
- bpRows.length ? segAct("bp-toggle", allBpEnabled ? "⊘" : "◉", allBpEnabled ? "禁用所有断点" : "启用所有断点", () => dapStore.setAllBpEnabled(scope, cwd, !allBpEnabled)) : null,
36537
- bpRows.length ? segAct("bp-clear", "🗑", "删除所有断点", removeAllBps) : null
36538
- ], bpSectionChildren);
36539
- const outputRows = dapStore.outputs().slice(-60).map((ev, idx) => react.default.createElement("div", {
36540
- key: ev.seq + ":" + idx,
36541
- className: "edrv-daprepl-line out"
36542
- }, ev.text?.trimEnd() || ""));
36543
- const configReady = snap.configs.some((c) => snap.adapters.length === 0 || snap.adapters.find((a) => a.type === c.type)?.available === true);
36544
- const toolbarBtn = (key, label, title, disabled, onClick) => react.default.createElement("button", {
36545
- key,
36546
- className: "edrv-dappanel-run",
36547
- title,
36548
- disabled: disabled === true,
36549
- onClick
36550
- }, label);
36551
- const bpMenuEl = bpMenu ? react.default.createElement(ContextMenu, {
36552
- x: bpMenu.x,
36553
- y: bpMenu.y,
36554
- onClose: () => setBpMenu(null),
36555
- entries: buildBpRowMenu({ enabled: bpMenu.row.entry.enabled }).map((item) => ({
36556
- id: item.id,
36557
- label: item.label,
36558
- separator: item.separator,
36559
- onClick: () => runBpRowAction(item.id, bpMenu.row)
36560
- }))
36561
- }) : null;
36562
- const newBpMenuEl = newBpMenu ? react.default.createElement(ContextMenu, {
36563
- x: newBpMenu.x,
36564
- y: newBpMenu.y,
36565
- onClose: () => setNewBpMenu(null),
36566
- entries: [{
36567
- id: "add-conditional",
36568
- label: "添加条件断点...",
36569
- onClick: () => addBpAtCursor(BP_MODE.EXPRESSION)
36570
- }, {
36571
- id: "add-logpoint",
36572
- label: "添加记录点...",
36573
- onClick: () => addBpAtCursor(BP_MODE.LOG_MESSAGE)
36574
- }]
36575
- }) : null;
36576
- return react.default.createElement("div", { className: "edrv-dappanel" }, react.default.createElement("div", { className: "edrv-dappanel-head" }, ...snap.candidates ? [
36577
- react.default.createElement("select", {
36578
- key: "pick",
36579
- className: "edrv-dappanel-cfg-select",
36580
- title: "选择附加目标进程(processName 命中多个候选)",
36581
- value: String(pickPid || snap.candidates[0]?.pid || ""),
36582
- onChange: (e) => setPickPid(Number(e.target.value))
36583
- }, snap.candidates.map((it) => react.default.createElement("option", {
36584
- key: it.pid,
36585
- value: String(it.pid)
36586
- }, it.pid + " : " + it.name + (it.title ? " — " + it.title.slice(0, 44) : "")))),
36587
- toolbarBtn("pick-ok", "✓", "附加到选中进程", false, () => dapStore.confirmPick(pickPid || snap.candidates[0]?.pid || 0)),
36588
- toolbarBtn("pick-cancel", "✕", "取消", false, () => dapStore.cancelPick())
36589
- ] : [react.default.createElement("select", {
36590
- key: "cfg",
36591
- className: "edrv-dappanel-cfg-select",
36592
- title: "调试配置(读取工作区 .dsh/launch.json;适配器由扩展清单 contributes.debuggers 提供)",
36593
- value: snap.selectedConfig ?? "",
36594
- onChange: (e) => dapStore.selectConfig(e.target.value)
36595
- }, snap.configs.length ? snap.configs.map((c) => {
36596
- const adapter = snap.adapters.find((a) => a.type === c.type);
36597
- const ready = snap.adapters.length === 0 || adapter?.available === true;
36598
- const suffix = ready ? "" : "(" + (adapter?.reason || "未发现该类型适配器") + ")";
36599
- return react.default.createElement("option", {
36600
- key: c.name,
36601
- value: c.name,
36602
- disabled: !ready
36603
- }, c.name + suffix);
36604
- }) : react.default.createElement("option", { value: "" }, "无可用调试配置"))], toolbarBtn("run", "▶", paused ? "继续(F5)" : "启动调试(F5)", !paused && (!configReady || dapActive), () => dapStore.startOrContinue()), toolbarBtn("cfg-gear", "⚙", "打开/新建调试配置(" + LAUNCH_REL + ")", !ctx?.cwd, () => {
36605
- openLaunchConfig();
36606
- }), toolbarBtn("pause", "⏸", "暂停", !dapActive, () => {
36607
- dapStore.action("pause");
36608
- }), toolbarBtn("next", "⤵", "单步跳过(F10)", !paused, () => {
36609
- dapStore.action("next");
36610
- }), toolbarBtn("stepin", "↓", "单步步入(F11)", !paused, () => {
36611
- dapStore.action("stepIn");
36612
- }), toolbarBtn("stepout", "↑", "单步步出(Shift+F11)", !paused, () => {
36613
- dapStore.action("stepOut");
36614
- }), toolbarBtn("stop", "⏹", "停止(Shift+F5)", !dapActive, () => {
36615
- dapStore.stop();
36616
- }), react.default.createElement("span", { className: "edrv-dappanel-phase" + (paused ? " paused" : "") }, PHASE_LABEL[snap.phase] || snap.phase), snap.pid ? react.default.createElement("span", { className: "edrv-dappanel-pid" }, "pid " + snap.pid) : null, react.default.createElement("span", { style: { flex: 1 } }), snap.error ? react.default.createElement("span", {
36617
- className: "edrv-dappanel-err",
36618
- title: snap.error
36619
- }, "⚠") : null), section("变量", "variables", "variables", [segAct("var-reload", "⟳", "刷新变量", () => setReloadTick((t) => t + 1))], varRows), sash("variables", "watch"), watchSection, sash("watch", "stack"), section("调用堆栈", "stack", "stack", [], stackRows), sash("stack", "breakpoints"), bpSection, react.default.createElement("div", { className: "edrv-daprepl" }, react.default.createElement("div", { className: "edrv-dapseg-head" }, "调试控制台"), react.default.createElement("div", { className: "edrv-daprepl-log" }, ...replLog.map((row, idx) => react.default.createElement("div", {
36620
- key: idx,
36621
- className: "edrv-daprepl-line"
36622
- }, react.default.createElement("span", { className: "edrv-dapvar-name" }, row.expr), react.default.createElement("span", { className: "edrv-dapvar-val" }, "= " + row.result))), ...outputRows), react.default.createElement("input", {
36623
- className: "edrv-daprepl-in",
36624
- placeholder: paused ? "输入表达式求值,回车执行(如 self.itemId)" : "暂停时才可求值",
36625
- disabled: !paused,
36626
- value: replDraft,
36627
- onChange: (e) => setReplDraft(e.target.value),
36628
- onKeyDown: (e) => {
36629
- if (e.key === "Enter") runRepl();
36630
- }
36631
- })), bpMenuEl, newBpMenuEl);
36632
- }
36633
- //#endregion
36634
36284
  //#region src/client/sidebar/panels/index.ts
36635
36285
  /**
36636
36286
  * dsh-vscode-mode client — 侧边栏面板定义(文件管理 + 搜索 + 规则 + SVN 变更)。
@@ -36678,7 +36328,11 @@ window.__ModuleLoader__.load({
36678
36328
  title: "规则",
36679
36329
  icon: IconListPenOutline16,
36680
36330
  order: 20,
36681
- render: (ctx) => react.default.createElement(RulesPanel, { ctx })
36331
+ render: (ctx) => react.default.createElement(LazyPanel, {
36332
+ key: "rules",
36333
+ panel: "rules",
36334
+ ctx
36335
+ })
36682
36336
  };
36683
36337
  }
36684
36338
  /**
@@ -36731,7 +36385,11 @@ window.__ModuleLoader__.load({
36731
36385
  icon: "🐞",
36732
36386
  order: 22,
36733
36387
  badge: () => dapStore.getSnapshot().phase === "paused" ? 1 : null,
36734
- render: (ctx) => react.default.createElement(DebugPanel, { ctx })
36388
+ render: (ctx) => react.default.createElement(LazyPanel, {
36389
+ key: "debug",
36390
+ panel: "debug",
36391
+ ctx
36392
+ })
36735
36393
  };
36736
36394
  }
36737
36395
  //#endregion
@@ -38517,6 +38175,16 @@ window.__ModuleLoader__.load({
38517
38175
  * conversation.input.dock。
38518
38176
  * 作者 ddj 2026-08-20
38519
38177
  */
38178
+ /** Root-owned live objects shared with independent panel chunks; no copied stores or transport state. */
38179
+ const __lazyShared = {
38180
+ rpc,
38181
+ dbg,
38182
+ dapStore,
38183
+ BP_MODE,
38184
+ MODES,
38185
+ fieldOfMode,
38186
+ ContextMenu
38187
+ };
38520
38188
  const inject = [
38521
38189
  "slots",
38522
38190
  "timer",
@@ -39069,6 +38737,34 @@ window.__ModuleLoader__.load({
39069
38737
  }, 2e3);
39070
38738
  };
39071
38739
  retrySideService();
38740
+ ctx.effect(() => {
38741
+ const store = mcpSessionState();
38742
+ /** @author ddj 2026年10月20号 @param props Official root row identity. @returns Leading marker with shared polling. */
38743
+ const renderIcon = (props) => react.default.createElement(McpSessionIcon, {
38744
+ ...props,
38745
+ store
38746
+ });
38747
+ /** @author ddj 2026年10月20号 @param props Official hover identity. @returns Project-only initialization details. */
38748
+ const renderDetail = (props) => react.default.createElement(McpSessionDetail, {
38749
+ ...props,
38750
+ store
38751
+ });
38752
+ const leading = registerSlotSafely(ctx, {
38753
+ name: "sidebar.session.row.leading",
38754
+ id: "edrv-mcp-status",
38755
+ order: 20
38756
+ }, renderIcon);
38757
+ const hover = registerSlotSafely(ctx, {
38758
+ name: "sidebar.session.row.hover",
38759
+ id: "edrv-mcp-detail",
38760
+ order: 20
38761
+ }, renderDetail);
38762
+ return () => {
38763
+ leading?.();
38764
+ hover?.();
38765
+ store.dispose();
38766
+ };
38767
+ }, "vscode-mode: session MCP status");
39072
38768
  registerSlotSafely(ctx, {
39073
38769
  name: "conversation.input.dock",
39074
38770
  id: "edrv-diff-dock",
@@ -39095,6 +38791,7 @@ window.__ModuleLoader__.load({
39095
38791
  label: () => t("slot.settings")
39096
38792
  }, () => react.default.createElement(SettingsContext.Provider, { value: bridge.scope() }, react.default.createElement(McpSettings, {
39097
38793
  openerRegistry: registry,
38794
+ getMcpSession: () => readSessionScope(ctx).sessionId,
39098
38795
  compatSummary,
39099
38796
  getShortcuts: () => detectShortcuts(ctx)
39100
38797
  })));
@@ -39104,6 +38801,7 @@ window.__ModuleLoader__.load({
39104
38801
  order: 40
39105
38802
  }, (props) => react.default.createElement(PluginDetailSection, Object.assign({}, props)));
39106
38803
  ctx.effect(() => () => {
38804
+ setPanelImport();
39107
38805
  try {
39108
38806
  disposeSnippets();
39109
38807
  } catch {}
@@ -39131,8 +38829,11 @@ window.__ModuleLoader__.load({
39131
38829
  reportUiStateEvent("客户端装配完成:水合" + (uiStateSettled() ? "已结束" : "仍在进行(界面稍后重跑恢复)"));
39132
38830
  }
39133
38831
  //#endregion
38832
+ exports.__lazyShared = __lazyShared;
38833
+ exports.__setPanelImport = setPanelImport;
39134
38834
  exports.apply = apply;
39135
38835
  exports.inject = inject;
38836
+ module.exports.__setPanelImport(require.async?.bind(require));
39136
38837
  return module.exports;
39137
38838
  }
39138
38839
  });