dsh-remote-vs 0.8.7 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -11,7 +11,7 @@ window.__ModuleLoader__.load({
11
11
  let react_jsx_runtime = require("react/jsx-runtime");
12
12
  let react_dom = require("react-dom");
13
13
  //#region \0dsh-css:E:\work_base\dsh_remote_vs\src\client\vsc\vsc.css.mjs
14
- const css$3 = "/**\n * VS Code-style design system for the remote explorer. Every surface below\n * is scoped under `.rvs-root` so the sheet can never leak into the host GUI,\n * and every host token carries a literal fallback: the plugin also renders in\n * the native right-Sidebar fallback and on hosts whose alias tokens differ.\n *\n * Palette and metrics are aligned with the reference implementation\n * (dsh-solution-explorer): 13px/20px panel type, 5px/10px tree rows with a\n * 10px radius, a 30px activity button with the badge tucked into the icon's\n * bottom-right corner, GitHub-Dark token colors, and the\n * `--e2b714 / #4ec9b0 / #f14c4c` modified/added/deleted triple.\n */\n\n.rvs-root {\n --rvs-fg: var(--dsw-alias-label-primary, #e6e6e6);\n --rvs-fg2: var(--dsw-alias-label-secondary, #b9b9b9);\n --rvs-fg3: var(--dsw-alias-label-tertiary, #8b8b8b);\n --rvs-hover: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .14));\n --rvs-active: var(--dsw-alias-interactive-bg-active, rgba(77, 107, 254, .22));\n --rvs-border: var(--dsw-alias-border-l1, rgba(127, 127, 127, .25));\n --rvs-border2: var(--dsw-alias-border-l2, rgba(127, 127, 127, .38));\n --rvs-input: var(--dsw-alias-bg-input, rgba(127, 127, 127, .12));\n --rvs-overlay: var(--dsw-alias-bg-overlay, #24262e);\n --rvs-layer3: var(--dsw-alias-bg-layer-3, rgba(127, 127, 127, .16));\n --rvs-brand: var(--dsw-alias-brand-primary, #4d6bfe);\n --rvs-accent: var(--dsw-alias-button-info-fill, #4d6bfe);\n --rvs-danger: var(--dsw-alias-state-error-primary, #e05555);\n --rvs-git-m: #e2b714;\n --rvs-git-a: #4ec9b0;\n --rvs-git-d: #f14c4c;\n --rvs-git-r: #4ec9b0;\n --rvs-git-c: #58a6ff;\n --rvs-git-u: #f14c4c;\n --rvs-git-x: #6e6e6e;\n --rvs-add-bg: rgba(78, 201, 176, .15);\n --rvs-del-bg: rgba(241, 76, 76, .15);\n /* Opaque equivalents, for elements that must not show what scrolls under\n them (the pinned diff line numbers). */\n --rvs-add-solid: #e4f6f1;\n --rvs-del-solid: #fdeaea;\n --rvs-mono: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Consolas, 'Courier New', monospace;\n /* Opaque code surfaces (Light+ default; the dark block below flips them). */\n --rvs-code-bg: #ffffff;\n --rvs-code-fg: #1f1f1f;\n --rvs-code-gutter-fg: #237893;\n\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n color: var(--rvs-fg);\n font-size: 13px;\n line-height: 20px;\n}\n\n/**\n * Code surfaces are OPAQUE, in the editor's own palette rather than the DSH\n * skin: syntax colors need a definite background to stay readable, and the\n * app shell marks its resolved scheme on <body> (same signal theme.ts reads).\n */\nbody[data-ds-dark-theme] .rvs-root {\n --rvs-code-bg: #1e1e1e;\n --rvs-code-fg: #d4d4d4;\n --rvs-code-gutter-fg: #858585;\n --rvs-add-solid: #16332e;\n --rvs-del-solid: #3b2325;\n}\n\n.rvs-edge {\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n.rvs-root *,\n.rvs-portal * {\n box-sizing: border-box;\n}\n\n/* ------------------------------------------------------------ activity rail */\n\n.rvs-rail {\n flex: none;\n width: 38px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n padding: 8px 0;\n border-right: 1px solid var(--rvs-border);\n background: var(--dsw-specific-sidebar-fill, transparent);\n}\n\n.rvs-rail-btn {\n position: relative;\n flex: none;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg3);\n cursor: pointer;\n}\n\n.rvs-rail-btn:hover {\n color: var(--rvs-fg);\n background: var(--rvs-hover);\n}\n\n.rvs-rail-btn.active {\n color: var(--rvs-fg);\n background: var(--rvs-hover);\n box-shadow: inset 2px 0 0 var(--rvs-brand);\n}\n\n.rvs-rail-btn.danger:hover {\n color: var(--rvs-danger);\n}\n\n.rvs-rail-badge {\n position: absolute;\n bottom: 1px;\n right: 1px;\n min-width: 14px;\n height: 14px;\n padding: 0 3px;\n border-radius: 7px;\n background: var(--rvs-accent);\n color: #fff;\n font-size: 9px;\n font-weight: 700;\n line-height: 14px;\n text-align: center;\n pointer-events: none;\n}\n\n.rvs-rail-sep {\n flex: none;\n width: 20px;\n height: 1px;\n margin: 4px 0;\n background: var(--rvs-border);\n}\n\n/* -------------------------------------------------------- panel + editor */\n\n.rvs-column {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.rvs-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n}\n\n/**\n * One lazily-mounted panel slot: a visited panel stays mounted and is merely\n * hidden, so returning to the explorer (or the terminal) neither refetches nor\n * flashes a reload.\n */\n.rvs-viewslot {\n flex: 1;\n min-height: 0;\n min-width: 0;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n\n/**\n * Padding for the legacy views (workspaces / terminal) that draw no chrome.\n *\n * It MUST be a flex column: the workspaces view scroller is its child and gets\n * its bounded height only from a flex parent (`flex:1; min-height:0`). As a\n * plain block it grew with its content instead, so the panel clipped at the\n * viewslot's `overflow:hidden` and the binding list could not scroll at all —\n * a seventh workspace (and the wizard under it) was simply unreachable.\n */\n.rvs-viewpad {\n display: flex;\n flex-direction: column;\n min-height: 0;\n padding: 10px 12px 14px;\n box-sizing: border-box;\n}\n\n.rvs-pane.grow {\n flex: 1;\n}\n\n.rvs-header {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 34px;\n padding: 0 8px 0 10px;\n border-bottom: 1px solid var(--rvs-border);\n}\n\n.rvs-header .rvs-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: .04em;\n text-transform: uppercase;\n color: var(--rvs-fg3);\n}\n\n.rvs-hbtn {\n flex: none;\n width: 22px;\n height: 22px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 5px;\n background: transparent;\n color: var(--rvs-fg3);\n cursor: pointer;\n}\n\n.rvs-hbtn:hover {\n color: var(--rvs-fg);\n background: var(--rvs-hover);\n}\n\n.rvs-hbtn.danger:hover {\n color: #fff;\n background: var(--rvs-danger);\n}\n\n.rvs-hbtn:disabled {\n opacity: .4;\n cursor: default;\n}\n\n.rvs-scroll {\n flex: 1;\n min-height: 0;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.rvs-globalbar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n border-bottom: 1px solid var(--rvs-border);\n font-size: 11px;\n}\n\n.rvs-scroll::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.rvs-scroll::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(127, 127, 127, .4));\n border-radius: 4px;\n}\n\n.rvs-scroll::-webkit-scrollbar-thumb:hover {\n background: var(--dsw-alias-scrollbar-hover-l2, rgba(127, 127, 127, .6));\n}\n\n.rvs-scroll::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-vsplit-handle {\n flex: none;\n position: relative;\n height: 7px;\n cursor: row-resize;\n touch-action: none;\n}\n\n.rvs-vsplit-handle::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n top: 3px;\n height: 1px;\n background: var(--rvs-border);\n}\n\n.rvs-vsplit-handle:hover::after,\n.rvs-vsplit-handle[data-dragging='true']::after {\n top: 2px;\n height: 3px;\n border-radius: 2px;\n background: var(--rvs-brand);\n}\n\n/* ------------------------------------------------------------------ tree */\n\n.rvs-tree {\n padding: 4px 6px 12px;\n min-width: 0;\n}\n\n.rvs-node {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n width: 100%;\n padding: 4px 8px;\n border: none;\n background: transparent;\n border-radius: 8px;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-node:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-node.selected {\n background: var(--rvs-active);\n}\n\n.rvs-node.cut {\n opacity: .5;\n}\n\n.rvs-node.drop-target {\n background: var(--rvs-active);\n outline: 1px solid var(--rvs-brand);\n outline-offset: -1px;\n}\n\n/* Where a file drag will land: a slip must not silently drop a tree into the\n wrong directory. */\n.rvs-drop-hint {\n margin: 2px 8px 4px;\n padding: 3px 8px;\n border: 1px dashed var(--rvs-brand);\n border-radius: 3px;\n font-size: 11px;\n color: var(--rvs-brand);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n unicode-bidi: plaintext;\n}\n/* Upload progress: one line under the header. A multi-minute transfer with no\n feedback is indistinguishable from a hung explorer, so the row names the\n count and the file on the wire. */\n.rvs-upload-bar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 3px 10px 4px;\n font-size: 11px;\n color: var(--dsw-alias-text-secondary, rgba(127, 127, 127, .95));\n}\n\n.rvs-upload-text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n unicode-bidi: plaintext;\n}\n\n.rvs-node-icon {\n flex: none;\n width: 16px;\n height: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--rvs-fg3);\n}\n\n.rvs-node-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-node-actions {\n flex: none;\n display: none;\n align-items: center;\n gap: 2px;\n}\n\n.rvs-node:hover .rvs-node-actions {\n display: flex;\n}\n\n.rvs-git-letter {\n flex: none;\n width: 14px;\n margin-left: 2px;\n font-size: 11px;\n font-weight: 700;\n text-align: center;\n}\n\n.rvs-git-M, .rvs-letter-M { color: var(--rvs-git-m); }\n.rvs-git-A, .rvs-letter-A { color: var(--rvs-git-a); }\n.rvs-git-D, .rvs-letter-D { color: var(--rvs-git-d); }\n.rvs-git-R, .rvs-letter-R { color: var(--rvs-git-r); }\n.rvs-git-C, .rvs-letter-C { color: var(--rvs-git-c); }\n.rvs-git-U, .rvs-letter-U { color: var(--rvs-git-u); }\n.rvs-git-q, .rvs-letter-q, .rvs-git-\\\\? { color: var(--rvs-git-a); }\n.rvs-git-x, .rvs-letter-x { color: var(--rvs-git-x); }\n.rvs-git-T, .rvs-letter-T { color: var(--rvs-git-m); }\n\n.rvs-rename-input {\n flex: 1;\n min-width: 0;\n padding: 0 4px;\n border: 1px solid var(--rvs-brand);\n border-radius: 4px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n outline: none;\n}\n\n.rvs-empty {\n padding: 18px 14px;\n color: var(--rvs-fg3);\n font-size: 12px;\n text-align: center;\n line-height: 1.7;\n}\n\n.rvs-banner {\n margin: 6px 8px;\n padding: 6px 9px;\n border-radius: 6px;\n font-size: 12px;\n line-height: 1.5;\n word-break: break-word;\n white-space: pre-wrap;\n}\n\n.rvs-banner.info {\n background: var(--rvs-hover);\n color: var(--rvs-fg2);\n}\n\n.rvs-banner.error {\n background: rgba(200, 60, 60, .16);\n color: var(--rvs-danger);\n}\n\n.rvs-banner.warn {\n background: rgba(226, 183, 20, .14);\n color: var(--rvs-git-m);\n}\n\n/* --------------------------------------------------------- source control */\n\n.rvs-scm-section {\n border-bottom: 1px solid var(--rvs-border);\n}\n\n.rvs-scm-head {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n padding: 0 6px 0 4px;\n cursor: pointer;\n user-select: none;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: .04em;\n text-transform: uppercase;\n color: var(--rvs-fg2);\n}\n\n.rvs-scm-head:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-scm-head .rvs-count {\n margin-left: 6px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0;\n color: var(--rvs-fg3);\n}\n\n.rvs-scm-head-actions {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n.rvs-scm-section.collapsed > *:not(.rvs-scm-head) {\n display: none !important;\n}\n\n.rvs-chevron {\n flex: none;\n transition: transform .12s ease;\n}\n\n.rvs-chevron.open {\n transform: rotate(90deg);\n}\n\n.rvs-scm-item {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 8px 3px 22px;\n border: none;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n line-height: 19px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-scm-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-scm-item.selected {\n background: var(--rvs-active);\n}\n\n.rvs-scm-path {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-scm-dir {\n flex: none;\n color: var(--rvs-fg3);\n}\n\n.rvs-scm-actions {\n flex: none;\n display: none;\n align-items: center;\n gap: 1px;\n}\n\n.rvs-scm-item:hover .rvs-scm-actions {\n display: flex;\n}\n\n.rvs-commit-box {\n flex: none;\n padding: 8px 10px;\n border-bottom: 1px solid var(--rvs-border);\n}\n\n.rvs-commit-input {\n width: 100%;\n min-height: 58px;\n max-height: 180px;\n padding: 6px 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n line-height: 18px;\n resize: vertical;\n outline: none;\n}\n\n.rvs-commit-input:focus {\n border-color: var(--rvs-brand);\n}\n\n.rvs-commit-row {\n display: flex;\n gap: 6px;\n margin-top: 6px;\n}\n\n.rvs-commit-btn {\n flex: 1;\n padding: 5px 8px;\n border: none;\n border-radius: 6px;\n background: var(--rvs-accent);\n color: #fff;\n font: inherit;\n font-size: 12.5px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.rvs-commit-btn:disabled {\n opacity: .45;\n cursor: default;\n}\n\n.rvs-repo-item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 10px 5px 12px;\n cursor: pointer;\n font-size: 12.5px;\n}\n\n.rvs-repo-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-repo-item.active {\n background: var(--rvs-active);\n}\n\n.rvs-repo-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-repo-branch {\n flex: none;\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n.rvs-pill {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 1px 8px;\n border-radius: 999px;\n background: var(--rvs-hover);\n font-size: 11px;\n font-weight: 600;\n color: var(--rvs-fg);\n}\n\n.rvs-subpanel {\n margin: 6px 8px;\n padding: 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n font-size: 12px;\n}\n\n.rvs-subpanel-head {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 6px;\n color: var(--rvs-fg2);\n font-weight: 600;\n}\n\n.rvs-chip {\n flex: none;\n padding: 2px 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 11.5px;\n cursor: pointer;\n}\n\n.rvs-chip:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-chip.primary {\n background: var(--rvs-accent);\n border-color: transparent;\n color: #fff;\n}\n\n.rvs-chip.danger:hover {\n background: var(--rvs-danger);\n border-color: transparent;\n color: #fff;\n}\n\n.rvs-input {\n flex: 1;\n min-width: 0;\n padding: 4px 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n\n.rvs-input:focus {\n border-color: var(--rvs-brand);\n}\n\n.rvs-select {\n flex: 1;\n min-width: 0;\n padding: 3px 6px;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n outline: none;\n color-scheme: light dark;\n}\n\n.rvs-select option {\n background: Canvas;\n color: CanvasText;\n}\n\n/* ------------------------------------------------- conversation-view mount */\n\n/**\n * Bound the center surface to the height the host gave the slot.\n *\n * `flex: 1 1 0` is the load-bearing declaration, and BOTH halves of it matter.\n * The host's own slot rules are:\n *\n * .wSkVaW_viewArea { flex: 1; min-height: 0; overflow: hidden }\n * [data-phase=active] .viewArea { flex: 1 0 auto; min-height: auto }\n * .wSkVaW_root { height: 100%; position: relative }\n *\n * While a conversation is active the slot's flex-basis is `auto`, so the slot's\n * height is driven by its child's INTRINSIC size. A full-height editor then\n * pushed it to 5510px for a 300-line file, the outer scroller took over, and\n * the tab strip and file info bar scrolled off the top with the code.\n *\n * `flex-basis: 0` breaks that cycle: the item stops contributing its content\n * height, so the slot stays the size the host intended and `.cm-scroller`\n * becomes the only scroller. Measured in the fixture (.spike/layout-fixture.html,\n * which mirrors the slot rules above) — only the bottom row fits:\n *\n * flex: 1 1 0 + min-height: 0 -> 736px OK\n * flex: 1 1 auto (i.e. `flex: 1`) -> 5510px broken\n * height: 0 + min-height: 100% -> 5510px broken\n * max-height: 100% -> 5510px broken\n * height: 100vh -> 5510px broken\n *\n * `height: 100%` is deliberately NOT set here any more. It resolves to `auto`\n * against a slot whose height is not definite — it reads like the fix but\n * measures as a no-op, which is how this regressed once already.\n *\n * NOT `position: absolute` either — that was another failed attempt: `.viewArea`\n * is not itself positioned, so `inset: 0` escaped to the host's `.wSkVaW_root`\n * (which is `position: relative`) and the plugin's global bar floated on top of\n * the host's 对话 / 轨迹 / 远程编辑器 tab row.\n *\n * The sidebar's `.rvs-viewslot` is deliberately not covered: it has a bounding\n * parent and this bug is specific to the conversation slot's `min-height: auto`\n * phase.\n */\n.rvs-center {\n flex: 1 1 0;\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n}\n\n/* ---------------------------------------------------------------- editor */\n\n.rvs-etabs {\n flex: none;\n display: flex;\n align-items: stretch;\n gap: 2px;\n height: 32px;\n padding: 0 4px;\n overflow-x: auto;\n overflow-y: hidden;\n border-bottom: 1px solid var(--rvs-border);\n scrollbar-width: none;\n}\n\n.rvs-etabs::-webkit-scrollbar {\n height: 0;\n}\n\n.rvs-etab {\n flex: none;\n display: flex;\n align-items: center;\n gap: 5px;\n max-width: 190px;\n margin: 4px 0;\n padding: 0 4px 0 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg3);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n\n.rvs-etab:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-etab.active {\n background: var(--rvs-layer3);\n color: var(--rvs-fg);\n}\n\n.rvs-etab-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-etab-dot {\n flex: none;\n font-size: 9px;\n color: var(--rvs-git-m);\n}\n\n.rvs-etab-close {\n flex: none;\n width: 16px;\n height: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n opacity: .65;\n}\n\n.rvs-etab-close:hover {\n background: var(--rvs-hover);\n opacity: 1;\n}\n\n.rvs-einfo {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 2px 8px 2px 10px;\n border-bottom: 1px solid var(--rvs-border);\n font-size: 11px;\n line-height: 18px;\n color: var(--rvs-fg3);\n}\n\n.rvs-einfo-path {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n /* Truncate from the LEFT (keep the tail) without reordering the text:\n `plaintext` gives the content its own LTR base direction, which is what\n stops a leading \"/\" from rendering as a trailing one. */\n direction: rtl;\n unicode-bidi: plaintext;\n text-align: left;\n}\n\n.rvs-einfo-spacer {\n margin-left: auto;\n}\n\n.rvs-status.dirty { color: var(--rvs-git-m); }\n.rvs-status.saved { color: var(--rvs-git-a); }\n.rvs-status.saving { color: var(--rvs-fg2); }\n\n.rvs-body {\n flex: 1;\n min-height: 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n /* Anchors portalled overlays (the find widget) outside every scroller. */\n position: relative;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n.rvs-editor-host {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background: var(--rvs-code-bg);\n}\n\n/* Keep a panel mounted but out of layout (and out of the a11y tree). Used by\n the editor host in preview mode: unmounting it destroyed the buffer and the\n undo stack, hiding it preserves both. */\n.rvs-hidden {\n display: none !important;\n}\n\n/* Find-match marks. Painted by our own StateField (`findMatchHighlight`), and\n deliberately the SAME colours as the `.cm-searchMatch` rules below so the\n two paths cannot drift apart. */\n.rvs-find-match {\n background-color: rgba(226, 183, 20, .30);\n outline: 1px solid rgba(226, 183, 20, .55);\n border-radius: 2px;\n}\n\n.rvs-find-match.current {\n background-color: rgba(226, 183, 20, .48);\n}\n\n/* ------------------------------------------------------- preview surfaces */\n\n.rvs-preview-img {\n flex: 1;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: auto;\n background-color: var(--rvs-code-bg);\n background-image:\n linear-gradient(45deg, rgba(127, 127, 127, .08) 25%, transparent 25%),\n linear-gradient(-45deg, rgba(127, 127, 127, .08) 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, rgba(127, 127, 127, .08) 75%),\n linear-gradient(-45deg, transparent 75%, rgba(127, 127, 127, .08) 75%);\n background-size: 20px 20px, 20px 20px, 20px 20px, 20px 20px;\n background-position: 0 0, 0 10px, 10px -10px, -10px 0;\n}\n\n.rvs-preview-img img {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n image-rendering: auto;\n}\n\n.rvs-markdown {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 12px 14px 24px;\n font-size: 13px;\n line-height: 1.7;\n color: var(--rvs-code-fg);\n background: var(--rvs-code-bg);\n user-select: text;\n}\n\n.rvs-markdown h1, .rvs-markdown h2, .rvs-markdown h3, .rvs-markdown h4 {\n margin: 14px 0 6px;\n line-height: 1.35;\n}\n\n.rvs-markdown h1 { font-size: 19px; border-bottom: 1px solid var(--rvs-border); padding-bottom: 5px; }\n.rvs-markdown h2 { font-size: 16px; border-bottom: 1px solid var(--rvs-border); padding-bottom: 4px; }\n.rvs-markdown h3 { font-size: 14px; }\n.rvs-markdown p { margin: 6px 0; }\n.rvs-markdown ul, .rvs-markdown ol { margin: 6px 0; padding-left: 22px; }\n.rvs-markdown li { margin: 2px 0; }\n.rvs-markdown code {\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--rvs-hover);\n font-family: var(--rvs-mono);\n font-size: 12px;\n}\n.rvs-markdown pre {\n margin: 8px 0;\n padding: 9px 11px;\n border-radius: 8px;\n background: var(--rvs-input);\n overflow-x: auto;\n}\n.rvs-markdown pre code { padding: 0; background: transparent; }\n.rvs-markdown blockquote {\n margin: 8px 0;\n padding: 2px 12px;\n border-left: 3px solid var(--rvs-border2);\n color: var(--rvs-fg2);\n}\n.rvs-markdown a { color: var(--dsw-alias-label-link, #58a6ff); text-decoration: none; }\n.rvs-markdown a:hover { text-decoration: underline; }\n.rvs-markdown table { border-collapse: collapse; margin: 8px 0; font-size: 12px; }\n.rvs-markdown th, .rvs-markdown td { border: 1px solid var(--rvs-border2); padding: 4px 8px; }\n.rvs-markdown hr { border: none; border-top: 1px solid var(--rvs-border); margin: 12px 0; }\n\n.rvs-html-frame {\n flex: 1;\n min-height: 0;\n width: 100%;\n border: none;\n background: #fff;\n}\n\n/* ------------------------------------------------------------------ diff */\n\n.rvs-diff {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.rvs-diff-toolbar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border-bottom: 1px solid var(--rvs-border);\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n.rvs-seg {\n display: inline-flex;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.rvs-seg button {\n padding: 2px 9px;\n border: none;\n background: transparent;\n color: var(--rvs-fg2);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n}\n\n.rvs-seg button.active {\n background: var(--rvs-active);\n color: var(--rvs-fg);\n}\n\n.rvs-seg button:hover:not(.active) {\n background: var(--rvs-hover);\n}\n\n.rvs-diff-grid {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n font-family: var(--rvs-mono);\n font-size: 12px;\n line-height: 18px;\n scrollbar-width: thin;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n/**\n * Top horizontal scrollbar.\n *\n * The two panes scroll horizontally on their own — so a long line can never\n * widen the layout — and this proxy bar drives both of them from the TOP of\n * the view, instead of forcing a scroll to the bottom of the file to reach a\n * horizontal bar. Its inner spacer is sized to the widest line, and the\n * component only renders it when a line really is wider than the grid.\n *\n * Sized and coloured to be SEEN: as a 13 px band with `scrollbar-width: thin`\n * and a faint thumb, it read as \"there is no horizontal scrollbar at all\".\n */\n.rvs-hbar {\n flex: none;\n height: 15px;\n overflow-x: auto;\n overflow-y: hidden;\n border-bottom: 1px solid var(--rvs-border);\n background: var(--rvs-hover);\n}\n\n.rvs-hbar-inner {\n height: 1px;\n}\n\n.rvs-hbar::-webkit-scrollbar {\n height: 11px;\n}\n\n.rvs-hbar::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(150, 160, 180, .75));\n border-radius: 6px;\n border: 2px solid transparent;\n background-clip: padding-box;\n}\n\n.rvs-hbar::-webkit-scrollbar-thumb:hover {\n background: var(--dsw-alias-scrollbar-hover-l2, rgba(170, 180, 200, .95));\n background-clip: padding-box;\n}\n\n.rvs-hbar::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-diff-cols {\n display: flex;\n align-items: stretch;\n min-width: 0;\n}\n\n/**\n * One diff pane. `flex: 1 1 0` keeps the panes EXACTLY equal however long a\n * line is (the previous row markup let a long line widen its own pane, which\n * is what broke the symmetry). Each pane keeps its own horizontal scroll, and\n * its scrollbar is now VISIBLE: the top proxy remains the convenient control,\n * but the pane's own bar is what a user reaches for first, and it sits right\n * under the content when the file is shorter than the view.\n */\n.rvs-diff-col {\n flex: 1 1 0;\n min-width: 0;\n overflow-x: auto;\n overflow-y: hidden;\n}\n\n.rvs-diff-col::-webkit-scrollbar {\n height: 9px;\n width: 0;\n}\n\n.rvs-diff-col::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(150, 160, 180, .6));\n border-radius: 5px;\n}\n\n.rvs-diff-col::-webkit-scrollbar-thumb:hover {\n background: var(--dsw-alias-scrollbar-hover-l2, rgba(170, 180, 200, .85));\n}\n\n.rvs-diff-col::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-dline {\n display: flex;\n align-items: flex-start;\n height: 18px;\n line-height: 18px;\n white-space: pre;\n}\n\n.rvs-dline-num {\n /* Pinned while the line scrolls sideways, so a long line never takes the\n line numbers away with it. */\n position: sticky;\n left: 0;\n z-index: 1;\n flex: none;\n width: 3.4em;\n padding-right: 6px;\n text-align: right;\n color: var(--rvs-fg3);\n opacity: .85;\n user-select: none;\n background: var(--rvs-code-bg);\n}\n\n.rvs-row-del .rvs-dline-num {\n background: var(--rvs-del-solid);\n}\n\n.rvs-row-add .rvs-dline-num {\n background: var(--rvs-add-solid);\n}\n\n.rvs-dline-text {\n flex: 1;\n padding-right: 10px;\n user-select: text;\n}\n\n.rvs-gutter-col {\n flex: none;\n width: 30px;\n border-left: 1px solid var(--rvs-border);\n border-right: 1px solid var(--rvs-border);\n user-select: none;\n}\n\n.rvs-gutter-line {\n height: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.rvs-diff-grid::-webkit-scrollbar {\n width: 9px;\n height: 0;\n}\n\n.rvs-diff-grid::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(127, 127, 127, .4));\n border-radius: 4px;\n}\n\n.rvs-num {\n flex: none;\n display: inline-block;\n width: 3.2em;\n padding-right: 6px;\n text-align: right;\n color: var(--rvs-fg3);\n opacity: .6;\n user-select: none;\n}\n\n.rvs-row-del {\n background: var(--rvs-del-bg);\n}\n\n.rvs-row-del .rvs-dline-text,\n.rvs-row-del .rvs-dline-num,\n.rvs-row-del .rvs-num {\n color: #f14c4c;\n}\n\n.rvs-row-add {\n background: var(--rvs-add-bg);\n}\n\n.rvs-row-add .rvs-dline-text,\n.rvs-row-add .rvs-dline-num,\n.rvs-row-add .rvs-num {\n color: #4ec9b0;\n}\n\n.rvs-row-empty {\n color: var(--rvs-fg3);\n opacity: .35;\n}\n\n.rvs-gutter {\n flex: none;\n width: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-left: 1px solid var(--rvs-border);\n border-right: 1px solid var(--rvs-border);\n user-select: none;\n}\n\n.rvs-gutter-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n opacity: .7;\n}\n\n.rvs-hunk-btns {\n display: flex;\n gap: 2px;\n}\n\n.rvs-hunk-btn {\n width: 13px;\n height: 15px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: transparent;\n color: var(--rvs-fg3);\n font-size: 11px;\n line-height: 1;\n cursor: pointer;\n}\n\n.rvs-hunk-btn:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-hunk-btn.stage:hover {\n color: var(--rvs-git-a);\n}\n\n.rvs-hunk-btn.revert:hover {\n color: var(--rvs-git-d);\n}\n\n/**\n * Token colors: VS Code Dark+ / Light+, matching the CodeMirror theme in\n * `vsc/editor-theme.ts`. Keeping ONE palette for both surfaces means a file\n * does not change colors when the diff is switched to its editable side.\n */\n.rvs-t-comment { color: #6a9955; }\n.rvs-t-string { color: #ce9178; }\n.rvs-t-keyword { color: #569cd6; }\n.rvs-t-number { color: #b5cea8; }\n.rvs-t-func { color: #dcdcaa; }\n.rvs-t-type { color: #4ec9b0; }\n.rvs-t-attr { color: #9cdcfe; }\n.rvs-t-variable { color: #9cdcfe; }\n.rvs-t-operator { color: #d4d4d4; }\n.rvs-t-punct { color: #d4d4d4; }\n.rvs-t-tag { color: #569cd6; }\n.rvs-t-meta { color: #c586c0; }\n.rvs-t-insert { color: #4ec9b0; }\n.rvs-t-delete { color: #f14c4c; }\n\nbody:not([data-ds-dark-theme]) .rvs-t-comment { color: #008000; }\nbody:not([data-ds-dark-theme]) .rvs-t-string { color: #a31515; }\nbody:not([data-ds-dark-theme]) .rvs-t-keyword { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-t-number { color: #098658; }\nbody:not([data-ds-dark-theme]) .rvs-t-func { color: #795e26; }\nbody:not([data-ds-dark-theme]) .rvs-t-type { color: #267f99; }\nbody:not([data-ds-dark-theme]) .rvs-t-attr,\nbody:not([data-ds-dark-theme]) .rvs-t-variable { color: #001080; }\nbody:not([data-ds-dark-theme]) .rvs-t-operator,\nbody:not([data-ds-dark-theme]) .rvs-t-punct { color: #1f1f1f; }\nbody:not([data-ds-dark-theme]) .rvs-t-meta { color: #af00db; }\n\n/**\n * CodeMirror token colors, applied through FIXED class names emitted by the\n * highlight style (`vsc/editor-palette.ts`). Fixed names mean the colors come\n * from this stylesheet — the same one that paints the rest of the UI — so a\n * code file can never go monochrome because a generated class name changed or\n * an injected style failed. Same Dark+ / Light+ palette as the diff panes\n * above, so a file keeps its colors when switching between the two views.\n */\n.rvs-ck-comment { color: #6a9955; }\n.rvs-ck-keyword { color: #569cd6; }\n.rvs-ck-control { color: #c586c0; }\n.rvs-ck-string { color: #ce9178; }\n.rvs-ck-regexp { color: #d16969; }\n.rvs-ck-number { color: #b5cea8; }\n.rvs-ck-constant { color: #569cd6; }\n.rvs-ck-func { color: #dcdcaa; }\n.rvs-ck-type { color: #4ec9b0; }\n.rvs-ck-variable { color: #9cdcfe; }\n.rvs-ck-attr { color: #9cdcfe; }\n.rvs-ck-meta { color: #c586c0; }\n.rvs-ck-punct { color: #d4d4d4; }\n.rvs-ck-op { color: #d4d4d4; }\n.rvs-ck-heading { color: #569cd6; font-weight: 700; }\n.rvs-ck-em { font-style: italic; }\n.rvs-ck-link { color: #9cdcfe; text-decoration: underline; }\n.rvs-ck-strike { text-decoration: line-through; }\n.rvs-ck-invalid { color: #f44747; }\n\nbody:not([data-ds-dark-theme]) .rvs-ck-comment { color: #008000; }\nbody:not([data-ds-dark-theme]) .rvs-ck-keyword { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-ck-control { color: #af00db; }\nbody:not([data-ds-dark-theme]) .rvs-ck-string { color: #a31515; }\nbody:not([data-ds-dark-theme]) .rvs-ck-regexp { color: #811f3f; }\nbody:not([data-ds-dark-theme]) .rvs-ck-number { color: #098658; }\nbody:not([data-ds-dark-theme]) .rvs-ck-constant { color: #0070c1; }\nbody:not([data-ds-dark-theme]) .rvs-ck-func { color: #795e26; }\nbody:not([data-ds-dark-theme]) .rvs-ck-type { color: #267f99; }\nbody:not([data-ds-dark-theme]) .rvs-ck-variable { color: #001080; }\nbody:not([data-ds-dark-theme]) .rvs-ck-attr { color: #e50000; }\nbody:not([data-ds-dark-theme]) .rvs-ck-meta { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-ck-punct,\nbody:not([data-ds-dark-theme]) .rvs-ck-op { color: #1f1f1f; }\nbody:not([data-ds-dark-theme]) .rvs-ck-heading { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-ck-link { color: #001080; }\nbody:not([data-ds-dark-theme]) .rvs-ck-invalid { color: #cd3131; }\n\n/**\n * Opaque editor surface, also as plain CSS: the CodeMirror theme paints it too\n * (following the scheme through a Compartment), but a transparent editor is\n * the one failure a user notices immediately, so the container states it as\n * well.\n */\n.rvs-edge .cm-editor,\n.rvs-edge .cm-scroller,\n.rvs-edge .cm-gutters {\n background-color: var(--rvs-code-bg);\n}\n\n.rvs-edge .cm-editor {\n color: var(--rvs-code-fg);\n}\n\n.rvs-edge .cm-gutters {\n color: var(--rvs-code-gutter-fg, var(--rvs-fg3));\n border-right: 1px solid var(--rvs-border);\n}\n\n.rvs-edge .cm-activeLine {\n background-color: rgba(127, 127, 127, .08);\n}\n\n.rvs-edge .cm-activeLineGutter {\n background-color: rgba(127, 127, 127, .12);\n}\n\n/**\n * The code editor's own scrollbars.\n *\n * These had NO rule at all: CodeMirror's scroller is `.cm-scroller`, which is\n * none of `.rvs-scroll` / `.rvs-diff-grid` / `.rvs-hbar`, so the editor fell\n * back to the browser default — too thin to hit with a mouse.\n *\n * Both notations are needed and must stay in step: Chromium honours\n * `scrollbar-width` over `::-webkit-scrollbar` when both apply, so setting\n * only the webkit half would silently have no effect. Keep the numbers equal.\n */\n.rvs-edge .cm-scroller {\n scrollbar-width: auto;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar {\n width: 14px;\n height: 14px;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-thumb {\n /* Opaque, not a translucent token: a thin wash over code is exactly the\n low-contrast target that is hard to grab. */\n background: rgba(121, 121, 121, .8);\n border: 3px solid transparent;\n border-radius: 7px;\n background-clip: padding-box;\n min-height: 36px;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-thumb:hover {\n background: rgba(150, 150, 150, .95);\n border: 3px solid transparent;\n background-clip: padding-box;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-corner {\n background: transparent;\n}\n\n/* ------------------------------------------------------------ find widget */\n\n/**\n * \"You landed here\": the transient highlight on the line a jump landed on\n * (search hit, @-reference).\n *\n * Deliberately NOT a warm/amber wash: over the dark editor that muddies into a\n * brown-black band that reads as a shadow and swallows annotation colors. This\n * is the accent blue instead — a tinted band with a bright edge on the left and\n * hairlines top/bottom, so it reads as a marked region while every token keeps\n * its own colour and contrast.\n */\n.rvs-reveal-line {\n background-color: rgba(88, 166, 255, .22);\n box-shadow:\n inset 3px 0 0 0 #58a6ff,\n inset 0 1px 0 0 rgba(88, 166, 255, .38),\n inset 0 -1px 0 0 rgba(88, 166, 255, .38);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-reveal-line {\n background-color: rgba(77, 107, 254, .14);\n box-shadow:\n inset 3px 0 0 0 #4d6bfe,\n inset 0 1px 0 0 rgba(77, 107, 254, .35),\n inset 0 -1px 0 0 rgba(77, 107, 254, .35);\n}\n\n/* --------------------------------------------------------- session changes */\n\n/**\n * 「本会话改了什么」:代码行背景 + 左侧改动栏(Cursor 风格)。\n *\n * 三种形态必须**一眼分得开**,光靠绿/红两个色相不够(色弱用户看新增行和删除行\n * 会糊成同一片),所以每种的左侧色条宽度/花样的语义也不同:\n * - 新增:实心绿条;\n * - 修改:实心蓝条(不是绿:修改块里既有留下的行也有新写的行,用\"中性的已变更\"\n * 色不会误导读者以为整块都是新内容);\n * - 删除:**斜纹**红底 + 更粗的红条 —— 这一行其实已经不存在了,只在中止位置上\n * 留个锚点,斜纹是这一层\"虚\"的信息里最不容易被当成真内容的花样。\n *\n * 选择器带上 `.cm-line`:`.cm-activeLine` 由 CodeMirror 的主题注入(运行时\n * `<style>`,通常排在本文档之后),只写 `.rvs-change-*` 两者同权重时会输给它,\n * 光标所在的那一行改动就会消失。\n */\n.rvs-edge .cm-line.rvs-change-add {\n background-color: rgba(46, 160, 67, .17);\n box-shadow:\n inset 3px 0 0 0 #2ea043,\n inset 0 -1px 0 0 rgba(46, 160, 67, .28);\n}\n\n.rvs-edge .cm-line.rvs-change-mod {\n background-color: rgba(56, 139, 253, .15);\n box-shadow:\n inset 3px 0 0 0 #388bfd,\n inset 0 -1px 0 0 rgba(56, 139, 253, .28);\n}\n\n.rvs-edge .cm-line.rvs-change-del {\n background-color: rgba(248, 81, 73, .09);\n background-image: repeating-linear-gradient(135deg, rgba(248, 81, 73, .17) 0 6px, transparent 6px 12px);\n box-shadow: inset 4px 0 0 0 #f85149;\n}\n\n/* 改动栏本身:符号 + 一列与行背景同色的窄条,读起来才是一条\"改动边\"。 */\n.rvs-edge .cm-gutterElement.rvs-change-gutter-add {\n box-shadow: inset 2px 0 0 0 #2ea043;\n}\n\n.rvs-edge .cm-gutterElement.rvs-change-gutter-mod {\n box-shadow: inset 2px 0 0 0 #388bfd;\n}\n\n.rvs-edge .cm-gutterElement.rvs-change-gutter-del {\n box-shadow: inset 2px 0 0 0 #f85149;\n}\n\n.rvs-edge .rvs-change-mark {\n display: inline-block;\n width: 12px;\n font-family: var(--rvs-mono);\n font-size: 12px;\n line-height: 1;\n text-align: center;\n user-select: none;\n}\n\n.rvs-edge .rvs-change-mark-add { color: #2ea043; }\n.rvs-edge .rvs-change-mark-mod { color: #388bfd; }\n.rvs-edge .rvs-change-mark-del { color: #f85149; }\n\n/**\n * 行内(字符级)差异:上面那三种底色回答\"哪几行变了\",这两条回答\"那一行的哪几个\n * 字变了\"。只改一个词的编辑,整行同色时完全看不出改了什么。\n *\n * `rvs-change-word-add` 刻意**只给背景、不给 `color`**:代码有自己的语法着色,\n * 覆盖前景色会把关键字/字符串/注释染成同一种颜色,比不标还难读。透明度压在行\n * 底色之上一点点,叠起来仍读得出\"整块改动\"的边界,又能看出块内哪几个字是新的。\n */\n.rvs-edge .rvs-change-word-add {\n background-color: rgba(46, 160, 67, .22);\n border-radius: 2px;\n}\n\n/**\n * 被删掉、在新文件里已经不存在的内容:零宽 widget,红底 + 删除线。\n *\n * 删除线画在 chip 自己身上(chip 是独立元素,不会波及周围代码);`white-space:\n * pre` 保留被删文本自己的缩进 —— 截断/去空白都会让 chip 谎报\"删了什么\"。\n * 字号略小 + 降一点不透明度,是让它读起来像\"残留物\"而不是一行真代码。\n */\n.rvs-edge .rvs-change-word-del {\n padding: 0 3px;\n margin: 0 1px;\n border-radius: 3px;\n background-color: rgba(248, 81, 73, .18);\n font-size: .92em;\n text-decoration: line-through;\n opacity: .85;\n white-space: pre;\n}\n\n/**\n * 撑宽度的隐形符号:只用它占位,绝不显示(见 `change-marks.ts` 的 SpacerMarker,\n * CodeMirror 会给这个 spacer 元素加 `visibility: hidden`,这里不重复声明)。\n */\n.rvs-edge .rvs-change-spacer {\n opacity: 0;\n}\n\n/* 浅色主题:同样的色相,压暗 + 提高不透明度,在近白底上才看得清。 */\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-line.rvs-change-add {\n background-color: rgba(31, 136, 61, .13);\n box-shadow:\n inset 3px 0 0 0 #1f883d,\n inset 0 -1px 0 0 rgba(31, 136, 61, .22);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-line.rvs-change-mod {\n background-color: rgba(9, 105, 218, .12);\n box-shadow:\n inset 3px 0 0 0 #0969da,\n inset 0 -1px 0 0 rgba(9, 105, 218, .22);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-line.rvs-change-del {\n background-color: rgba(207, 34, 46, .07);\n background-image: repeating-linear-gradient(135deg, rgba(207, 34, 46, .14) 0 6px, transparent 6px 12px);\n box-shadow: inset 4px 0 0 0 #cf222e;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-gutterElement.rvs-change-gutter-add {\n box-shadow: inset 2px 0 0 0 #1f883d;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-gutterElement.rvs-change-gutter-mod {\n box-shadow: inset 2px 0 0 0 #0969da;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-gutterElement.rvs-change-gutter-del {\n box-shadow: inset 2px 0 0 0 #cf222e;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-mark-add { color: #1f883d; }\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-mark-mod { color: #0969da; }\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-mark-del { color: #cf222e; }\n\n/* 浅色主题的行内差异:同样的色相压暗,在近白底上才看得清(暗色那套会糊成一片)。 */\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-word-add {\n background-color: rgba(26, 127, 55, .18);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-word-del {\n background-color: rgba(207, 34, 46, .14);\n}\n\n/**\n * 改动跳转按钮:浮在编辑器右上角。\n *\n * 放在编辑器上而不是面板里 —— 文件打开后光标就在这里,而面板可能被收起、被滚走。\n * 容器 `.rvs-body` 已经是 `position: relative`(它本来用于挂 find 浮层),所以这里\n * 只做绝对定位,不引入新的定位上下文。`pointer-events` 只开在按钮上:这块区域压在\n * 编辑器文字上方,整块可点会把右上角那几行变成死区。\n */\n.rvs-change-nav {\n position: absolute;\n top: 8px;\n right: 14px;\n z-index: 5;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 3px 4px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n background: Canvas;\n box-shadow: 0 2px 8px rgba(0, 0, 0, .28);\n pointer-events: none;\n}\n\n/* The counter is what makes this read as \"3 of 7 changes\" rather than two\n anonymous arrows — and it keeps the buttons from looking like decoration. */\n.rvs-change-nav-count {\n padding: 0 6px 0 4px;\n color: var(--rvs-fg2);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n line-height: 22px;\n white-space: nowrap;\n}\n\n.rvs-change-nav-btn {\n width: 26px;\n height: 22px;\n padding: 0;\n border: 1px solid transparent;\n border-radius: 5px;\n background: transparent;\n color: var(--rvs-fg);\n font-size: 13px;\n font-weight: 600;\n line-height: 20px;\n text-align: center;\n cursor: pointer;\n pointer-events: auto;\n}\n\n.rvs-change-nav-btn:hover {\n border-color: var(--rvs-border2);\n background: var(--rvs-hover);\n color: var(--rvs-brand);\n}\n\n.rvs-change-nav-btn:active { background: var(--rvs-active); }\n\n/**\n * 悬浮在改动行上出现的「撤销这处改动」按钮。\n *\n * 它**不在编辑器的 DOM 里**:`undo-affordance.ts` 把它当 `position: fixed` 的元素挂到\n * `document.body`(和「添加到对话」那个浮层同一套做法),所以这里的选择器不带\n * `.rvs-edge` —— 带上了就一条都不生效。固定定位 + 高 z-index 是刻意的:宿主容器有\n * `overflow: hidden`,挂在里面会被 CodeMirror 的滚动层裁掉。\n *\n * 颜色按\"危险动作\"给:默认红边红字、悬停实心红。底色不透明(不用半透明的 overlay\n * token),否则后面的代码文字会透出来。\n */\n.rvs-undo-affordance {\n position: fixed;\n z-index: 2100;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n max-width: 220px;\n padding: 3px 9px;\n border: 1px solid #f85149;\n border-radius: 6px;\n background-color: #1e2229;\n color: #ffb4ae;\n /* 按钮挂在 body 上,没有 .rvs-edge 的字体上下文:跟随宿主页面字体。 */\n font-family: inherit;\n font-size: 12px;\n line-height: 1.5;\n white-space: nowrap;\n cursor: pointer;\n box-shadow: 0 6px 18px rgba(0, 0, 0, .45);\n /* 纵向以悬停行的中线对齐(横向坐标由 JS 按行尾算好)。 */\n transform: translateY(-50%);\n}\n\n.rvs-undo-affordance:hover {\n background-color: #f85149;\n border-color: #f85149;\n color: #fff;\n}\n\n.rvs-undo-affordance:active {\n transform: translateY(-50%) scale(.97);\n}\n\n.rvs-undo-affordance:focus-visible {\n outline: 2px solid #58a6ff;\n outline-offset: 1px;\n}\n\n/* 删除锚点:这一行其实已经不在了,虚线边框把它和\"真实存在的行\"分开。 */\n.rvs-undo-affordance[data-kind=\"del\"] {\n border-style: dashed;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-undo-affordance {\n border-color: #cf222e;\n background-color: #ffffff;\n color: #a40e26;\n box-shadow: 0 6px 18px rgba(31, 35, 40, .18);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-undo-affordance:hover {\n background-color: #cf222e;\n border-color: #cf222e;\n color: #fff;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-undo-affordance:focus-visible {\n outline-color: #4d6bfe;\n}\n\n/**\n * CodeMirror's own search decorations, also stated here: the editor's theme is\n * injected by the library, and these must stay readable even when only this\n * stylesheet is in play.\n */\n.rvs-edge .cm-searchMatch {\n background-color: rgba(226, 183, 20, .30);\n outline: 1px solid rgba(226, 183, 20, .55);\n}\n\n.rvs-edge .cm-searchMatch-selected {\n background-color: rgba(226, 183, 20, .48);\n}\n\n.rvs-edge .cm-selectionBackground,\n.rvs-edge .cm-content ::selection {\n background-color: rgba(58, 110, 165, .55) !important;\n}\n\n/** Build stamp in the editor info bar: identifies the loaded client bundle. */\n.rvs-build {\n flex: none;\n padding: 0 5px;\n border: 1px solid var(--rvs-border);\n border-radius: 4px;\n color: var(--rvs-fg3);\n font-size: 10px;\n font-family: var(--rvs-mono);\n opacity: .85;\n user-select: text;\n}\n\n.rvs-find {\n position: absolute;\n top: 6px;\n right: 18px;\n z-index: 30;\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 3px 4px 3px 7px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n background-color: Canvas;\n background-image: linear-gradient(var(--dsw-alias-bg-overlay, transparent), var(--dsw-alias-bg-overlay, transparent));\n color: var(--rvs-fg);\n box-shadow: 0 4px 14px rgba(0, 0, 0, .28);\n font-size: 12px;\n}\n\n.rvs-find-input {\n width: 170px;\n padding: 2px 6px;\n border: 1px solid var(--rvs-border2);\n border-radius: 5px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n\n.rvs-find-input:focus {\n border-color: var(--rvs-brand);\n}\n\n.rvs-find-count {\n flex: none;\n min-width: 54px;\n text-align: center;\n color: var(--rvs-fg3);\n font-size: 11px;\n white-space: nowrap;\n}\n\n.rvs-find-count.none {\n color: var(--rvs-git-d);\n}\n\n.rvs-code {\n flex: 1;\n min-height: 0;\n overflow: auto;\n margin: 0;\n padding: 6px 0 16px;\n font-family: var(--rvs-mono);\n font-size: 12.5px;\n line-height: 18px;\n white-space: pre;\n scrollbar-width: thin;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n.rvs-code-line {\n display: flex;\n align-items: flex-start;\n min-width: max-content;\n}\n\n.rvs-code-line:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-code-text {\n flex: 1;\n padding-right: 10px;\n white-space: pre-wrap;\n word-break: break-word;\n user-select: text;\n}\n\n/* ---------------------------------------------------------- history list */\n\n.rvs-commit-item {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 4px 8px 4px 4px;\n border: none;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n line-height: 18px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-commit-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-commit-item.selected {\n background: var(--rvs-active);\n}\n\n.rvs-graph-svg {\n flex: none;\n display: block;\n}\n\n.rvs-commit-hash {\n flex: none;\n font-family: var(--rvs-mono);\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n.rvs-commit-msg {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-commit-date {\n flex: none;\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n.rvs-ref-badge {\n flex: none;\n padding: 0 5px;\n border-radius: 8px;\n border: 1px solid var(--rvs-border2);\n font-size: 10px;\n line-height: 15px;\n color: var(--rvs-fg2);\n}\n\n.rvs-ref-badge.head {\n border-color: var(--rvs-brand);\n background: rgba(77, 107, 254, .2);\n color: var(--rvs-fg);\n}\n\n.rvs-ref-badge.tag {\n border-color: rgba(226, 183, 20, .55);\n background: rgba(226, 183, 20, .16);\n color: var(--rvs-git-m);\n}\n\n.rvs-detail {\n margin: 0 6px 8px;\n padding: 8px 10px;\n border: 1px solid var(--rvs-border2);\n border-left: 3px solid var(--rvs-accent);\n border-radius: 8px;\n background: var(--rvs-layer3);\n font-size: 12px;\n}\n\n.rvs-detail-meta {\n color: var(--rvs-fg3);\n font-size: 11px;\n margin-bottom: 6px;\n white-space: pre-wrap;\n}\n\n.rvs-file-row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 4px;\n border-radius: 5px;\n cursor: pointer;\n}\n\n.rvs-file-row:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-file-path {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n /* Same tail-first truncation as the info bar; `plaintext` keeps the path's\n own LTR order (see .rvs-einfo-path). */\n direction: rtl;\n unicode-bidi: plaintext;\n text-align: left;\n}\n\n.rvs-file-status {\n flex: none;\n width: 16px;\n text-align: center;\n font-weight: 700;\n font-size: 11px;\n}\n\n/* --------------------------------------------------------- context menu */\n\n.rvs-portal {\n font-family: inherit;\n}\n\n/**\n * Floating surfaces (menu / modal / toast) paint an OPAQUE base first and then\n * lay the skin's overlay token on top. The token is frequently translucent, so\n * using it alone let the panel's own text bleed through the menu and overlap\n * the entries; `Canvas` follows the resolved light/dark scheme, so the base is\n * correct in either theme.\n */\n.rvs-menu,\n.rvs-modal-box,\n.rvs-toast {\n background-color: Canvas;\n background-image: linear-gradient(var(--dsw-alias-bg-overlay, transparent), var(--dsw-alias-bg-overlay, transparent));\n color: var(--rvs-fg);\n}\n\n.rvs-menu {\n position: fixed;\n z-index: 1000;\n min-width: 172px;\n max-height: calc(100vh - 16px);\n overflow-y: auto;\n padding: 4px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, .35);\n font-size: 12.5px;\n line-height: 18px;\n}\n\n.rvs-menu-sep {\n height: 1px;\n margin: 4px 6px;\n background: var(--rvs-border);\n}\n\n.rvs-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 5px 10px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-menu-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-menu-item.danger {\n color: var(--vsc-danger, #f14c4c);\n}\n\n.rvs-menu-item.danger:hover {\n background: rgba(241, 76, 76, .16);\n}\n\n.rvs-menu-item:disabled {\n opacity: .45;\n cursor: default;\n}\n\n.rvs-menu-item:disabled:hover {\n background: transparent;\n}\n\n.rvs-menu-hint {\n margin-left: auto;\n font-size: 11px;\n opacity: .6;\n}\n\n/* ------------------------------------------------------- modal + toast */\n\n.rvs-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 100000;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, .45);\n backdrop-filter: blur(2px);\n animation: rvsFade .12s ease;\n}\n\n.rvs-modal-box {\n width: min(420px, calc(100vw - 40px));\n padding: 16px 18px;\n border: 1px solid var(--rvs-border2);\n border-radius: 10px;\n color: var(--rvs-fg);\n box-shadow: 0 12px 40px rgba(0, 0, 0, .38);\n font-size: 13px;\n animation: rvsPop .14s ease;\n}\n\n.rvs-modal-title {\n margin-bottom: 8px;\n font-size: 14px;\n font-weight: 600;\n}\n\n.rvs-modal-text {\n margin-bottom: 12px;\n color: var(--rvs-fg2);\n font-size: 12.5px;\n line-height: 1.6;\n word-break: break-word;\n white-space: pre-wrap;\n}\n\n.rvs-modal-row {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 14px;\n}\n\n.rvs-modal-btn {\n padding: 5px 14px;\n border: 1px solid var(--rvs-border2);\n border-radius: 7px;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n cursor: pointer;\n}\n\n.rvs-modal-btn:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-modal-btn.primary {\n border-color: transparent;\n background: var(--rvs-accent);\n color: #fff;\n}\n\n.rvs-modal-btn.danger {\n border-color: transparent;\n background: var(--rvs-danger);\n color: #fff;\n}\n\n.rvs-toasts {\n position: fixed;\n right: 16px;\n bottom: 18px;\n z-index: 100001;\n display: flex;\n flex-direction: column;\n gap: 8px;\n align-items: flex-end;\n pointer-events: none;\n}\n\n.rvs-toast {\n max-width: 340px;\n padding: 7px 12px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n color: var(--rvs-fg);\n box-shadow: 0 6px 20px rgba(0, 0, 0, .35);\n font-size: 12.5px;\n line-height: 1.5;\n word-break: break-word;\n white-space: pre-wrap;\n animation: rvsPop .14s ease;\n}\n\n.rvs-toast.error {\n border-color: rgba(241, 76, 76, .6);\n background-image: linear-gradient(rgba(58, 31, 34, .92), rgba(58, 31, 34, .92));\n}\n\n/* ------------------------------------------------------------- terminal */\n\n.rvs-term-tabs {\n flex: none;\n display: flex;\n align-items: stretch;\n gap: 2px;\n height: 32px;\n padding: 0 4px;\n overflow-x: auto;\n overflow-y: hidden;\n border-bottom: 1px solid var(--rvs-border);\n scrollbar-width: none;\n}\n\n.rvs-term-tabs::-webkit-scrollbar {\n height: 0;\n}\n\n.rvs-term-tab {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n max-width: 180px;\n margin: 4px 0;\n padding: 0 6px 0 9px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg3);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Wrapper that pairs a tab with its close button. The two are siblings so the\n ✕ can be a real focusable <button> (a nested one is invalid HTML and left\n keyboard users no way to close a tab). */\n.rvs-term-tabwrap {\n position: relative;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n.rvs-term-tabwrap .rvs-term-tab {\n padding-right: 22px;\n}\n\n.rvs-term-tabwrap .rvs-etab-close {\n position: absolute;\n right: 3px;\n opacity: 0;\n}\n\n.rvs-term-tabwrap:hover .rvs-etab-close,\n.rvs-term-tabwrap:focus-within .rvs-etab-close {\n opacity: .8;\n}\n\n.rvs-term-tab:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-term-tab.active {\n background: var(--rvs-layer3);\n color: var(--rvs-fg);\n}\n\n.rvs-term-tab.dead {\n color: var(--rvs-git-d);\n}\n\n.rvs-term-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-term-body {\n flex: 1;\n min-height: 0;\n position: relative;\n overflow: hidden;\n}\n\n.rvs-term-slot {\n position: absolute;\n inset: 0;\n}\n\n.rvs-term-dead {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n/* `.rvs-toast.error` is styled with the overlay form further up, so the popup\n opacity convention (an opaque Canvas base plus a token layer) still governs\n it. A flat `background: #3a1f22` shorthand sat here and overrode that chain —\n same opaque result, but outside the convention. */\n\n@keyframes rvsFade {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes rvsPop {\n from { opacity: 0; transform: scale(.97) translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n.rvs-search-row {\n display: flex;\n gap: 6px;\n padding: 6px 8px;\n border-bottom: 1px solid var(--rvs-border);\n}\n\n/* --------------------------------------------------------- search options */\n\n.rvs-toggles {\n flex: none;\n display: inline-flex;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.rvs-toggle {\n min-width: 26px;\n height: 22px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n border: none;\n border-right: 1px solid var(--rvs-border);\n background: transparent;\n color: var(--rvs-fg3);\n font: inherit;\n cursor: pointer;\n}\n\n.rvs-toggle:last-child {\n border-right: none;\n}\n\n.rvs-toggle:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-toggle.active {\n background: var(--rvs-active);\n color: var(--rvs-fg);\n box-shadow: inset 0 0 0 1px var(--rvs-brand);\n}\n\n.rvs-glyph {\n font-family: var(--rvs-mono);\n font-weight: 600;\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n.rvs-glyph-underline {\n text-decoration: underline;\n}\n\n.rvs-scope-label {\n flex: none;\n align-self: center;\n font-size: 11px;\n color: var(--rvs-fg3);\n white-space: nowrap;\n}\n\n/* --------------------------------------------------------- search results */\n\n.rvs-hit {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 10px;\n border: none;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-hit:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-hit-file {\n font-weight: 600;\n}\n\n.rvs-hit-line {\n padding-left: 30px;\n align-items: flex-start;\n font-family: var(--rvs-mono);\n font-size: 12px;\n color: var(--rvs-fg2);\n}\n\n.rvs-hit-count {\n flex: none;\n min-width: 18px;\n padding: 0 5px;\n border-radius: 9px;\n background: var(--rvs-hover);\n font-size: 10.5px;\n line-height: 15px;\n text-align: center;\n color: var(--rvs-fg3);\n}\n\n.rvs-hit-lineno {\n flex: none;\n width: 3.2em;\n text-align: right;\n color: var(--rvs-fg3);\n opacity: .8;\n font-size: 11px;\n}\n\n.rvs-hit-text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: pre;\n direction: ltr;\n}\n\n.rvs-mark {\n padding: 0 1px;\n border-radius: 2px;\n background: rgba(226, 183, 20, .3);\n color: inherit;\n}\n\n.rvs-hit-dir {\n flex: none;\n color: var(--rvs-fg3);\n font-size: 11px;\n}\n\n.rvs-hit-name {\n flex: none;\n font-weight: 600;\n}\n\n.rvs-kbd {\n padding: 0 4px;\n border: 1px solid var(--rvs-border2);\n border-radius: 4px;\n font-size: 10px;\n color: var(--rvs-fg3);\n}\n\n/* --- SCM: repository directory row (git runs in THIS directory) ------------- */\n\n.rvs-repo-bar {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px 4px 10px;\n border-bottom: 1px solid var(--rvs-border);\n background: var(--rvs-bg2, var(--dsw-alias-bg-layer-1, #1b1b1b));\n font-size: 11px;\n flex: none;\n}\n\n.rvs-repo-bar-label {\n color: var(--rvs-fg3);\n white-space: nowrap;\n}\n\n.rvs-repo-bar-path {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n unicode-bidi: plaintext;\n text-align: left;\n padding: 2px 6px;\n border: 1px solid var(--rvs-border2);\n border-radius: 4px;\n background: var(--rvs-bg3, rgba(255, 255, 255, .04));\n color: var(--rvs-fg);\n font-family: var(--rvs-mono, ui-monospace, Consolas, monospace);\n font-size: 11px;\n cursor: pointer;\n}\n\n.rvs-repo-bar-path:hover {\n border-color: var(--rvs-brand);\n color: var(--rvs-brand-fg, var(--rvs-fg));\n}\n\n.rvs-repo-bar-fix {\n margin-top: 10px;\n display: flex;\n justify-content: center;\n}\n\n/* --- SCM: repository directory picker -------------------------------------- */\n\n.rvs-repopick {\n width: min(560px, calc(100vw - 40px));\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.rvs-repopick .rvs-modal-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.rvs-repopick-bar {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 2px;\n min-height: 22px;\n font-family: var(--rvs-mono, ui-monospace, Consolas, monospace);\n font-size: 12px;\n}\n\n.rvs-crumb-wrap {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n color: var(--rvs-fg3);\n}\n\n.rvs-crumb {\n border: none;\n background: none;\n padding: 1px 3px;\n border-radius: 3px;\n color: var(--rvs-brand-fg, var(--rvs-fg));\n font: inherit;\n cursor: pointer;\n}\n\n.rvs-crumb:hover {\n background: var(--rvs-row-hover, rgba(255, 255, 255, .07));\n}\n\n.rvs-repopick-probe {\n padding: 5px 8px;\n border-radius: 4px;\n font-size: 11.5px;\n border: 1px solid var(--rvs-border2);\n background: var(--rvs-bg3, rgba(255, 255, 255, .04));\n color: var(--rvs-fg2);\n}\n\n.rvs-repopick-probe.root {\n border-color: var(--rvs-ok, #2ea043);\n color: var(--rvs-ok, #2ea043);\n}\n\n.rvs-repopick-probe.inside {\n border-color: var(--rvs-brand);\n color: var(--rvs-brand-fg, var(--rvs-fg));\n}\n\n.rvs-repopick-found {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 4px;\n padding-bottom: 2px;\n}\n\n.rvs-repopick-found-label {\n color: var(--rvs-fg3);\n font-size: 11px;\n white-space: nowrap;\n}\n\n.rvs-repopick-list {\n flex: 1 1 auto;\n min-height: 150px;\n max-height: 42vh;\n overflow: auto;\n border: 1px solid var(--rvs-border);\n border-radius: 5px;\n background: var(--rvs-bg-layer, rgba(0, 0, 0, .18));\n padding: 2px;\n}\n\n.rvs-repopick-row {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 6px;\n border: none;\n border-radius: 3px;\n background: none;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-repopick-row:hover {\n background: var(--rvs-row-hover, rgba(255, 255, 255, .07));\n}\n\n.rvs-repopick-row span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-repopick-dim {\n color: var(--rvs-fg3);\n font-size: 11.5px;\n padding: 6px;\n}\n\n.rvs-repopick-type {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.rvs-repopick-type .rvs-input {\n flex: 1 1 auto;\n}\n\n.rvs-repopick-error {\n color: var(--rvs-error, #f85149);\n font-size: 11.5px;\n}\n";
14
+ const css$3 = "/**\n * VS Code-style design system for the remote explorer. Every surface below\n * is scoped under `.rvs-root` so the sheet can never leak into the host GUI,\n * and every host token carries a literal fallback: the plugin also renders in\n * the native right-Sidebar fallback and on hosts whose alias tokens differ.\n *\n * Palette and metrics are aligned with the reference implementation\n * (dsh-solution-explorer): 13px/20px panel type, 5px/10px tree rows with a\n * 10px radius, a 30px activity button with the badge tucked into the icon's\n * bottom-right corner, GitHub-Dark token colors, and the\n * `--e2b714 / #4ec9b0 / #f14c4c` modified/added/deleted triple.\n */\n\n.rvs-root {\n --rvs-fg: var(--dsw-alias-label-primary, #e6e6e6);\n --rvs-fg2: var(--dsw-alias-label-secondary, #b9b9b9);\n --rvs-fg3: var(--dsw-alias-label-tertiary, #8b8b8b);\n --rvs-hover: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .14));\n --rvs-active: var(--dsw-alias-interactive-bg-active, rgba(77, 107, 254, .22));\n --rvs-border: var(--dsw-alias-border-l1, rgba(127, 127, 127, .25));\n --rvs-border2: var(--dsw-alias-border-l2, rgba(127, 127, 127, .38));\n --rvs-input: var(--dsw-alias-bg-input, rgba(127, 127, 127, .12));\n --rvs-overlay: var(--dsw-alias-bg-overlay, #24262e);\n --rvs-layer3: var(--dsw-alias-bg-layer-3, rgba(127, 127, 127, .16));\n --rvs-brand: var(--dsw-alias-brand-primary, #4d6bfe);\n --rvs-accent: var(--dsw-alias-button-info-fill, #4d6bfe);\n --rvs-danger: var(--dsw-alias-state-error-primary, #e05555);\n --rvs-git-m: #e2b714;\n --rvs-git-a: #4ec9b0;\n --rvs-git-d: #f14c4c;\n --rvs-git-r: #4ec9b0;\n --rvs-git-c: #58a6ff;\n --rvs-git-u: #f14c4c;\n --rvs-git-x: #6e6e6e;\n --rvs-add-bg: rgba(78, 201, 176, .15);\n --rvs-del-bg: rgba(241, 76, 76, .15);\n /* Opaque equivalents, for elements that must not show what scrolls under\n them (the pinned diff line numbers). */\n --rvs-add-solid: #e4f6f1;\n --rvs-del-solid: #fdeaea;\n --rvs-mono: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Consolas, 'Courier New', monospace;\n /* Opaque code surfaces (Light+ default; the dark block below flips them). */\n --rvs-code-bg: #ffffff;\n --rvs-code-fg: #1f1f1f;\n --rvs-code-gutter-fg: #237893;\n\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n color: var(--rvs-fg);\n font-size: 13px;\n line-height: 20px;\n}\n\n/**\n * Code surfaces are OPAQUE, in the editor's own palette rather than the DSH\n * skin: syntax colors need a definite background to stay readable, and the\n * app shell marks its resolved scheme on <body> (same signal theme.ts reads).\n */\nbody[data-ds-dark-theme] .rvs-root {\n --rvs-code-bg: #1e1e1e;\n --rvs-code-fg: #d4d4d4;\n --rvs-code-gutter-fg: #858585;\n --rvs-add-solid: #16332e;\n --rvs-del-solid: #3b2325;\n}\n\n.rvs-edge {\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n.rvs-root *,\n.rvs-portal * {\n box-sizing: border-box;\n}\n\n/* ------------------------------------------------------------ activity rail */\n\n.rvs-rail {\n flex: none;\n width: 38px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n padding: 8px 0;\n border-right: 1px solid var(--rvs-border);\n background: var(--dsw-specific-sidebar-fill, transparent);\n}\n\n.rvs-rail-btn {\n position: relative;\n flex: none;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg3);\n cursor: pointer;\n}\n\n.rvs-rail-btn:hover {\n color: var(--rvs-fg);\n background: var(--rvs-hover);\n}\n\n.rvs-rail-btn.active {\n color: var(--rvs-fg);\n background: var(--rvs-hover);\n box-shadow: inset 2px 0 0 var(--rvs-brand);\n}\n\n.rvs-rail-btn.danger:hover {\n color: var(--rvs-danger);\n}\n\n.rvs-rail-badge {\n position: absolute;\n bottom: 1px;\n right: 1px;\n min-width: 14px;\n height: 14px;\n padding: 0 3px;\n border-radius: 7px;\n background: var(--rvs-accent);\n color: #fff;\n font-size: 9px;\n font-weight: 700;\n line-height: 14px;\n text-align: center;\n pointer-events: none;\n}\n\n.rvs-rail-sep {\n flex: none;\n width: 20px;\n height: 1px;\n margin: 4px 0;\n background: var(--rvs-border);\n}\n\n/* -------------------------------------------------------- panel + editor */\n\n.rvs-column {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.rvs-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n}\n\n/**\n * One lazily-mounted panel slot: a visited panel stays mounted and is merely\n * hidden, so returning to the explorer (or the terminal) neither refetches nor\n * flashes a reload.\n */\n.rvs-viewslot {\n flex: 1;\n min-height: 0;\n min-width: 0;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n\n/**\n * Padding for the legacy views (workspaces / terminal) that draw no chrome.\n *\n * It MUST be a flex column: the workspaces view scroller is its child and gets\n * its bounded height only from a flex parent (`flex:1; min-height:0`). As a\n * plain block it grew with its content instead, so the panel clipped at the\n * viewslot's `overflow:hidden` and the binding list could not scroll at all —\n * a seventh workspace (and the wizard under it) was simply unreachable.\n */\n.rvs-viewpad {\n display: flex;\n flex-direction: column;\n min-height: 0;\n padding: 10px 12px 14px;\n box-sizing: border-box;\n}\n\n.rvs-pane.grow {\n flex: 1;\n}\n\n.rvs-header {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 34px;\n padding: 0 8px 0 10px;\n border-bottom: 1px solid var(--rvs-border);\n}\n\n.rvs-header .rvs-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: .04em;\n text-transform: uppercase;\n color: var(--rvs-fg3);\n}\n\n.rvs-hbtn {\n flex: none;\n width: 22px;\n height: 22px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 5px;\n background: transparent;\n color: var(--rvs-fg3);\n cursor: pointer;\n}\n\n.rvs-hbtn:hover {\n color: var(--rvs-fg);\n background: var(--rvs-hover);\n}\n\n.rvs-hbtn.danger:hover {\n color: #fff;\n background: var(--rvs-danger);\n}\n\n.rvs-hbtn:disabled {\n opacity: .4;\n cursor: default;\n}\n\n.rvs-scroll {\n flex: 1;\n min-height: 0;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.rvs-globalbar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n border-bottom: 1px solid var(--rvs-border);\n font-size: 11px;\n}\n\n.rvs-scroll::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.rvs-scroll::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(127, 127, 127, .4));\n border-radius: 4px;\n}\n\n.rvs-scroll::-webkit-scrollbar-thumb:hover {\n background: var(--dsw-alias-scrollbar-hover-l2, rgba(127, 127, 127, .6));\n}\n\n.rvs-scroll::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-vsplit-handle {\n flex: none;\n position: relative;\n height: 7px;\n cursor: row-resize;\n touch-action: none;\n}\n\n.rvs-vsplit-handle::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n top: 3px;\n height: 1px;\n background: var(--rvs-border);\n}\n\n.rvs-vsplit-handle:hover::after,\n.rvs-vsplit-handle[data-dragging='true']::after {\n top: 2px;\n height: 3px;\n border-radius: 2px;\n background: var(--rvs-brand);\n}\n\n/* ------------------------------------------------------------------ tree */\n\n.rvs-tree {\n padding: 4px 6px 12px;\n min-width: 0;\n}\n\n.rvs-node {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n width: 100%;\n padding: 4px 8px;\n border: none;\n background: transparent;\n border-radius: 8px;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-node:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-node.selected {\n background: var(--rvs-active);\n}\n\n.rvs-node.cut {\n opacity: .5;\n}\n\n.rvs-node.drop-target {\n background: var(--rvs-active);\n outline: 1px solid var(--rvs-brand);\n outline-offset: -1px;\n}\n\n/* Where a file drag will land: a slip must not silently drop a tree into the\n wrong directory. */\n.rvs-drop-hint {\n margin: 2px 8px 4px;\n padding: 3px 8px;\n border: 1px dashed var(--rvs-brand);\n border-radius: 3px;\n font-size: 11px;\n color: var(--rvs-brand);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n unicode-bidi: plaintext;\n}\n/* Upload progress: one line under the header. A multi-minute transfer with no\n feedback is indistinguishable from a hung explorer, so the row names the\n count and the file on the wire. */\n.rvs-upload-bar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 3px 10px 4px;\n font-size: 11px;\n color: var(--dsw-alias-text-secondary, rgba(127, 127, 127, .95));\n}\n\n.rvs-upload-text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n unicode-bidi: plaintext;\n}\n\n.rvs-node-icon {\n flex: none;\n width: 16px;\n height: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--rvs-fg3);\n}\n\n.rvs-node-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-node-actions {\n flex: none;\n display: none;\n align-items: center;\n gap: 2px;\n}\n\n.rvs-node:hover .rvs-node-actions {\n display: flex;\n}\n\n.rvs-git-letter {\n flex: none;\n width: 14px;\n margin-left: 2px;\n font-size: 11px;\n font-weight: 700;\n text-align: center;\n}\n\n.rvs-git-M, .rvs-letter-M { color: var(--rvs-git-m); }\n.rvs-git-A, .rvs-letter-A { color: var(--rvs-git-a); }\n.rvs-git-D, .rvs-letter-D { color: var(--rvs-git-d); }\n.rvs-git-R, .rvs-letter-R { color: var(--rvs-git-r); }\n.rvs-git-C, .rvs-letter-C { color: var(--rvs-git-c); }\n.rvs-git-U, .rvs-letter-U { color: var(--rvs-git-u); }\n.rvs-git-q, .rvs-letter-q, .rvs-git-\\\\? { color: var(--rvs-git-a); }\n.rvs-git-x, .rvs-letter-x { color: var(--rvs-git-x); }\n.rvs-git-T, .rvs-letter-T { color: var(--rvs-git-m); }\n\n.rvs-rename-input {\n flex: 1;\n min-width: 0;\n padding: 0 4px;\n border: 1px solid var(--rvs-brand);\n border-radius: 4px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n outline: none;\n}\n\n.rvs-empty {\n padding: 18px 14px;\n color: var(--rvs-fg3);\n font-size: 12px;\n text-align: center;\n line-height: 1.7;\n}\n\n.rvs-banner {\n margin: 6px 8px;\n padding: 6px 9px;\n border-radius: 6px;\n font-size: 12px;\n line-height: 1.5;\n word-break: break-word;\n white-space: pre-wrap;\n}\n\n.rvs-banner.info {\n background: var(--rvs-hover);\n color: var(--rvs-fg2);\n}\n\n.rvs-banner.error {\n background: rgba(200, 60, 60, .16);\n color: var(--rvs-danger);\n}\n\n.rvs-banner.warn {\n background: rgba(226, 183, 20, .14);\n color: var(--rvs-git-m);\n}\n\n/* --------------------------------------------------------- source control */\n\n.rvs-scm-section {\n border-bottom: 1px solid var(--rvs-border);\n}\n\n.rvs-scm-head {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n padding: 0 6px 0 4px;\n cursor: pointer;\n user-select: none;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: .04em;\n text-transform: uppercase;\n color: var(--rvs-fg2);\n}\n\n.rvs-scm-head:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-scm-head .rvs-count {\n margin-left: 6px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0;\n color: var(--rvs-fg3);\n}\n\n.rvs-scm-head-actions {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n.rvs-scm-section.collapsed > *:not(.rvs-scm-head) {\n display: none !important;\n}\n\n.rvs-chevron {\n flex: none;\n transition: transform .12s ease;\n}\n\n.rvs-chevron.open {\n transform: rotate(90deg);\n}\n\n.rvs-scm-item {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 8px 3px 22px;\n border: none;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n line-height: 19px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-scm-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-scm-item.selected {\n background: var(--rvs-active);\n}\n\n.rvs-scm-path {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-scm-dir {\n flex: none;\n color: var(--rvs-fg3);\n}\n\n.rvs-scm-actions {\n flex: none;\n display: none;\n align-items: center;\n gap: 1px;\n}\n\n.rvs-scm-item:hover .rvs-scm-actions {\n display: flex;\n}\n\n.rvs-commit-box {\n flex: none;\n padding: 8px 10px;\n border-bottom: 1px solid var(--rvs-border);\n}\n\n.rvs-commit-input {\n width: 100%;\n min-height: 58px;\n max-height: 180px;\n padding: 6px 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n line-height: 18px;\n resize: vertical;\n outline: none;\n}\n\n.rvs-commit-input:focus {\n border-color: var(--rvs-brand);\n}\n\n.rvs-commit-row {\n display: flex;\n gap: 6px;\n margin-top: 6px;\n}\n\n.rvs-commit-btn {\n flex: 1;\n padding: 5px 8px;\n border: none;\n border-radius: 6px;\n background: var(--rvs-accent);\n color: #fff;\n font: inherit;\n font-size: 12.5px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.rvs-commit-btn:disabled {\n opacity: .45;\n cursor: default;\n}\n\n.rvs-repo-item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 10px 5px 12px;\n cursor: pointer;\n font-size: 12.5px;\n}\n\n.rvs-repo-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-repo-item.active {\n background: var(--rvs-active);\n}\n\n.rvs-repo-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-repo-branch {\n flex: none;\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n.rvs-pill {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 1px 8px;\n border-radius: 999px;\n background: var(--rvs-hover);\n font-size: 11px;\n font-weight: 600;\n color: var(--rvs-fg);\n}\n\n.rvs-subpanel {\n margin: 6px 8px;\n padding: 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n font-size: 12px;\n}\n\n.rvs-subpanel-head {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 6px;\n color: var(--rvs-fg2);\n font-weight: 600;\n}\n\n.rvs-chip {\n flex: none;\n padding: 2px 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 11.5px;\n cursor: pointer;\n}\n\n.rvs-chip:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-chip.primary {\n background: var(--rvs-accent);\n border-color: transparent;\n color: #fff;\n}\n\n.rvs-chip.danger:hover {\n background: var(--rvs-danger);\n border-color: transparent;\n color: #fff;\n}\n\n.rvs-input {\n flex: 1;\n min-width: 0;\n padding: 4px 8px;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n\n.rvs-input:focus {\n border-color: var(--rvs-brand);\n}\n\n.rvs-select {\n flex: 1;\n min-width: 0;\n padding: 3px 6px;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n outline: none;\n color-scheme: light dark;\n}\n\n.rvs-select option {\n background: Canvas;\n color: CanvasText;\n}\n\n/* ------------------------------------------------- conversation-view mount */\n\n/**\n * Bound the center surface to the height the host gave the slot.\n *\n * `flex: 1 1 0` is the load-bearing declaration, and BOTH halves of it matter.\n * The host's own slot rules are:\n *\n * .wSkVaW_viewArea { flex: 1; min-height: 0; overflow: hidden }\n * [data-phase=active] .viewArea { flex: 1 0 auto; min-height: auto }\n * .wSkVaW_root { height: 100%; position: relative }\n *\n * While a conversation is active the slot's flex-basis is `auto`, so the slot's\n * height is driven by its child's INTRINSIC size. A full-height editor then\n * pushed it to 5510px for a 300-line file, the outer scroller took over, and\n * the tab strip and file info bar scrolled off the top with the code.\n *\n * `flex-basis: 0` breaks that cycle: the item stops contributing its content\n * height, so the slot stays the size the host intended and `.cm-scroller`\n * becomes the only scroller. Measured in the fixture (.spike/layout-fixture.html,\n * which mirrors the slot rules above) — only the bottom row fits:\n *\n * flex: 1 1 0 + min-height: 0 -> 736px OK\n * flex: 1 1 auto (i.e. `flex: 1`) -> 5510px broken\n * height: 0 + min-height: 100% -> 5510px broken\n * max-height: 100% -> 5510px broken\n * height: 100vh -> 5510px broken\n *\n * `height: 100%` is deliberately NOT set here any more. It resolves to `auto`\n * against a slot whose height is not definite — it reads like the fix but\n * measures as a no-op, which is how this regressed once already.\n *\n * NOT `position: absolute` either — that was another failed attempt: `.viewArea`\n * is not itself positioned, so `inset: 0` escaped to the host's `.wSkVaW_root`\n * (which is `position: relative`) and the plugin's global bar floated on top of\n * the host's 对话 / 轨迹 / 远程编辑器 tab row.\n *\n * The sidebar's `.rvs-viewslot` is deliberately not covered: it has a bounding\n * parent and this bug is specific to the conversation slot's `min-height: auto`\n * phase.\n */\n.rvs-center {\n flex: 1 1 0;\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n}\n\n/* ---------------------------------------------------------------- editor */\n\n.rvs-etabs {\n flex: none;\n display: flex;\n align-items: stretch;\n gap: 2px;\n height: 32px;\n padding: 0 4px;\n overflow-x: auto;\n overflow-y: hidden;\n border-bottom: 1px solid var(--rvs-border);\n scrollbar-width: none;\n}\n\n.rvs-etabs::-webkit-scrollbar {\n height: 0;\n}\n\n.rvs-etab {\n flex: none;\n display: flex;\n align-items: center;\n gap: 5px;\n max-width: 190px;\n margin: 4px 0;\n padding: 0 4px 0 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg3);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n\n.rvs-etab:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-etab.active {\n background: var(--rvs-layer3);\n color: var(--rvs-fg);\n}\n\n.rvs-etab-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-etab-dot {\n flex: none;\n font-size: 9px;\n color: var(--rvs-git-m);\n}\n\n.rvs-etab-close {\n flex: none;\n width: 16px;\n height: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n opacity: .65;\n}\n\n.rvs-etab-close:hover {\n background: var(--rvs-hover);\n opacity: 1;\n}\n\n.rvs-einfo {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 2px 8px 2px 10px;\n border-bottom: 1px solid var(--rvs-border);\n font-size: 11px;\n line-height: 18px;\n color: var(--rvs-fg3);\n}\n\n.rvs-einfo-path {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n /* Truncate from the LEFT (keep the tail) without reordering the text:\n `plaintext` gives the content its own LTR base direction, which is what\n stops a leading \"/\" from rendering as a trailing one. */\n direction: rtl;\n unicode-bidi: plaintext;\n text-align: left;\n}\n\n.rvs-einfo-spacer {\n margin-left: auto;\n}\n\n.rvs-status.dirty { color: var(--rvs-git-m); }\n.rvs-status.saved { color: var(--rvs-git-a); }\n.rvs-status.saving { color: var(--rvs-fg2); }\n\n.rvs-body {\n flex: 1;\n min-height: 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n /* Anchors portalled overlays (the find widget) outside every scroller. */\n position: relative;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n.rvs-editor-host {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background: var(--rvs-code-bg);\n}\n\n/* Keep a panel mounted but out of layout (and out of the a11y tree). Used by\n the editor host in preview mode: unmounting it destroyed the buffer and the\n undo stack, hiding it preserves both. */\n.rvs-hidden {\n display: none !important;\n}\n\n/* Find-match marks. Painted by our own StateField (`findMatchHighlight`), and\n deliberately the SAME colours as the `.cm-searchMatch` rules below so the\n two paths cannot drift apart. */\n.rvs-find-match {\n background-color: rgba(226, 183, 20, .30);\n outline: 1px solid rgba(226, 183, 20, .55);\n border-radius: 2px;\n}\n\n.rvs-find-match.current {\n background-color: rgba(226, 183, 20, .48);\n}\n\n/* ------------------------------------------------------- preview surfaces */\n\n.rvs-preview-img {\n flex: 1;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: auto;\n background-color: var(--rvs-code-bg);\n background-image:\n linear-gradient(45deg, rgba(127, 127, 127, .08) 25%, transparent 25%),\n linear-gradient(-45deg, rgba(127, 127, 127, .08) 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, rgba(127, 127, 127, .08) 75%),\n linear-gradient(-45deg, transparent 75%, rgba(127, 127, 127, .08) 75%);\n background-size: 20px 20px, 20px 20px, 20px 20px, 20px 20px;\n background-position: 0 0, 0 10px, 10px -10px, -10px 0;\n}\n\n.rvs-preview-img img {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n image-rendering: auto;\n}\n\n.rvs-markdown {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 12px 14px 24px;\n font-size: 13px;\n line-height: 1.7;\n color: var(--rvs-code-fg);\n background: var(--rvs-code-bg);\n user-select: text;\n}\n\n.rvs-markdown h1, .rvs-markdown h2, .rvs-markdown h3, .rvs-markdown h4 {\n margin: 14px 0 6px;\n line-height: 1.35;\n}\n\n.rvs-markdown h1 { font-size: 19px; border-bottom: 1px solid var(--rvs-border); padding-bottom: 5px; }\n.rvs-markdown h2 { font-size: 16px; border-bottom: 1px solid var(--rvs-border); padding-bottom: 4px; }\n.rvs-markdown h3 { font-size: 14px; }\n.rvs-markdown p { margin: 6px 0; }\n.rvs-markdown ul, .rvs-markdown ol { margin: 6px 0; padding-left: 22px; }\n.rvs-markdown li { margin: 2px 0; }\n.rvs-markdown code {\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--rvs-hover);\n font-family: var(--rvs-mono);\n font-size: 12px;\n}\n.rvs-markdown pre {\n margin: 8px 0;\n padding: 9px 11px;\n border-radius: 8px;\n background: var(--rvs-input);\n overflow-x: auto;\n}\n.rvs-markdown pre code { padding: 0; background: transparent; }\n.rvs-markdown blockquote {\n margin: 8px 0;\n padding: 2px 12px;\n border-left: 3px solid var(--rvs-border2);\n color: var(--rvs-fg2);\n}\n.rvs-markdown a { color: var(--dsw-alias-label-link, #58a6ff); text-decoration: none; }\n.rvs-markdown a:hover { text-decoration: underline; }\n.rvs-markdown table { border-collapse: collapse; margin: 8px 0; font-size: 12px; }\n.rvs-markdown th, .rvs-markdown td { border: 1px solid var(--rvs-border2); padding: 4px 8px; }\n.rvs-markdown hr { border: none; border-top: 1px solid var(--rvs-border); margin: 12px 0; }\n\n.rvs-html-frame {\n flex: 1;\n min-height: 0;\n width: 100%;\n border: none;\n background: #fff;\n}\n\n/* ------------------------------------------------------------------ diff */\n\n.rvs-diff {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.rvs-diff-toolbar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border-bottom: 1px solid var(--rvs-border);\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n/**\n * Prev/next change, in the toolbar's free space.\n *\n * Two chevrons around a tabular counter: the pair then reads as \"jump to change\n * n of N\" instead of two anonymous arrows, and the counter doubles as the \"where\n * am I\" cue while scrolling by hand.\n */\n.rvs-hunknav {\n display: inline-flex;\n align-items: center;\n gap: 1px;\n margin-right: 2px;\n}\n\n.rvs-hunknav-pos {\n min-width: 30px;\n text-align: center;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--rvs-fg3);\n user-select: none;\n}\n\n.rvs-seg {\n display: inline-flex;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.rvs-seg button {\n padding: 2px 9px;\n border: none;\n background: transparent;\n color: var(--rvs-fg2);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n}\n\n.rvs-seg button.active {\n background: var(--rvs-active);\n color: var(--rvs-fg);\n}\n\n.rvs-seg button:hover:not(.active) {\n background: var(--rvs-hover);\n}\n\n.rvs-diff-grid {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n font-family: var(--rvs-mono);\n font-size: 12px;\n line-height: 18px;\n scrollbar-width: thin;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n/**\n * Top horizontal scrollbar.\n *\n * The two panes scroll horizontally on their own — so a long line can never\n * widen the layout — and this proxy bar drives both of them from the TOP of\n * the view, instead of forcing a scroll to the bottom of the file to reach a\n * horizontal bar. Its inner spacer is sized to the widest line, and the\n * component only renders it when a line really is wider than the grid.\n *\n * Sized and coloured to be SEEN: as a 13 px band with `scrollbar-width: thin`\n * and a faint thumb, it read as \"there is no horizontal scrollbar at all\".\n */\n.rvs-hbar {\n flex: none;\n height: 15px;\n overflow-x: auto;\n overflow-y: hidden;\n border-bottom: 1px solid var(--rvs-border);\n background: var(--rvs-hover);\n}\n\n.rvs-hbar-inner {\n height: 1px;\n}\n\n.rvs-hbar::-webkit-scrollbar {\n height: 11px;\n}\n\n.rvs-hbar::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(150, 160, 180, .75));\n border-radius: 6px;\n border: 2px solid transparent;\n background-clip: padding-box;\n}\n\n.rvs-hbar::-webkit-scrollbar-thumb:hover {\n background: var(--dsw-alias-scrollbar-hover-l2, rgba(170, 180, 200, .95));\n background-clip: padding-box;\n}\n\n.rvs-hbar::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-diff-cols {\n display: flex;\n align-items: stretch;\n min-width: 0;\n}\n\n/**\n * One diff pane. `flex: 1 1 0` keeps the panes EXACTLY equal however long a\n * line is (the previous row markup let a long line widen its own pane, which\n * is what broke the symmetry). Each pane keeps its own horizontal scroll, and\n * its scrollbar is now VISIBLE: the top proxy remains the convenient control,\n * but the pane's own bar is what a user reaches for first, and it sits right\n * under the content when the file is shorter than the view.\n */\n.rvs-diff-col {\n flex: 1 1 0;\n min-width: 0;\n overflow-x: auto;\n overflow-y: hidden;\n}\n\n.rvs-diff-col::-webkit-scrollbar {\n height: 9px;\n width: 0;\n}\n\n.rvs-diff-col::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(150, 160, 180, .6));\n border-radius: 5px;\n}\n\n.rvs-diff-col::-webkit-scrollbar-thumb:hover {\n background: var(--dsw-alias-scrollbar-hover-l2, rgba(170, 180, 200, .85));\n}\n\n.rvs-diff-col::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-dline {\n display: flex;\n align-items: flex-start;\n height: 18px;\n line-height: 18px;\n white-space: pre;\n}\n\n.rvs-dline-num {\n /* Pinned while the line scrolls sideways, so a long line never takes the\n line numbers away with it. */\n position: sticky;\n left: 0;\n z-index: 1;\n flex: none;\n width: 3.4em;\n padding-right: 6px;\n text-align: right;\n color: var(--rvs-fg3);\n opacity: .85;\n user-select: none;\n background: var(--rvs-code-bg);\n}\n\n.rvs-row-del .rvs-dline-num {\n background: var(--rvs-del-solid);\n}\n\n.rvs-row-add .rvs-dline-num {\n background: var(--rvs-add-solid);\n}\n\n.rvs-dline-text {\n flex: 1;\n padding-right: 10px;\n user-select: text;\n}\n\n.rvs-gutter-col {\n flex: none;\n width: 30px;\n border-left: 1px solid var(--rvs-border);\n border-right: 1px solid var(--rvs-border);\n user-select: none;\n}\n\n.rvs-gutter-line {\n height: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.rvs-diff-grid::-webkit-scrollbar {\n width: 9px;\n height: 0;\n}\n\n.rvs-diff-grid::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, rgba(127, 127, 127, .4));\n border-radius: 4px;\n}\n\n.rvs-num {\n flex: none;\n display: inline-block;\n width: 3.2em;\n padding-right: 6px;\n text-align: right;\n color: var(--rvs-fg3);\n opacity: .6;\n user-select: none;\n}\n\n.rvs-row-del {\n background: var(--rvs-del-bg);\n}\n\n.rvs-row-del .rvs-dline-text,\n.rvs-row-del .rvs-dline-num,\n.rvs-row-del .rvs-num {\n color: #f14c4c;\n}\n\n.rvs-row-add {\n background: var(--rvs-add-bg);\n}\n\n.rvs-row-add .rvs-dline-text,\n.rvs-row-add .rvs-dline-num,\n.rvs-row-add .rvs-num {\n color: #4ec9b0;\n}\n\n.rvs-row-empty {\n color: var(--rvs-fg3);\n opacity: .35;\n}\n\n.rvs-gutter {\n flex: none;\n width: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-left: 1px solid var(--rvs-border);\n border-right: 1px solid var(--rvs-border);\n user-select: none;\n}\n\n.rvs-gutter-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n opacity: .7;\n}\n\n.rvs-hunk-btns {\n display: flex;\n gap: 2px;\n}\n\n.rvs-hunk-btn {\n width: 13px;\n height: 15px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: transparent;\n color: var(--rvs-fg3);\n font-size: 11px;\n line-height: 1;\n cursor: pointer;\n}\n\n.rvs-hunk-btn:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-hunk-btn.stage:hover {\n color: var(--rvs-git-a);\n}\n\n.rvs-hunk-btn.revert:hover {\n color: var(--rvs-git-d);\n}\n\n/**\n * Token colors: VS Code Dark+ / Light+, matching the CodeMirror theme in\n * `vsc/editor-theme.ts`. Keeping ONE palette for both surfaces means a file\n * does not change colors when the diff is switched to its editable side.\n */\n.rvs-t-comment { color: #6a9955; }\n.rvs-t-string { color: #ce9178; }\n.rvs-t-keyword { color: #569cd6; }\n.rvs-t-number { color: #b5cea8; }\n.rvs-t-func { color: #dcdcaa; }\n.rvs-t-type { color: #4ec9b0; }\n.rvs-t-attr { color: #9cdcfe; }\n.rvs-t-variable { color: #9cdcfe; }\n.rvs-t-operator { color: #d4d4d4; }\n.rvs-t-punct { color: #d4d4d4; }\n.rvs-t-tag { color: #569cd6; }\n.rvs-t-meta { color: #c586c0; }\n.rvs-t-insert { color: #4ec9b0; }\n.rvs-t-delete { color: #f14c4c; }\n\nbody:not([data-ds-dark-theme]) .rvs-t-comment { color: #008000; }\nbody:not([data-ds-dark-theme]) .rvs-t-string { color: #a31515; }\nbody:not([data-ds-dark-theme]) .rvs-t-keyword { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-t-number { color: #098658; }\nbody:not([data-ds-dark-theme]) .rvs-t-func { color: #795e26; }\nbody:not([data-ds-dark-theme]) .rvs-t-type { color: #267f99; }\nbody:not([data-ds-dark-theme]) .rvs-t-attr,\nbody:not([data-ds-dark-theme]) .rvs-t-variable { color: #001080; }\nbody:not([data-ds-dark-theme]) .rvs-t-operator,\nbody:not([data-ds-dark-theme]) .rvs-t-punct { color: #1f1f1f; }\nbody:not([data-ds-dark-theme]) .rvs-t-meta { color: #af00db; }\n\n/**\n * CodeMirror token colors, applied through FIXED class names emitted by the\n * highlight style (`vsc/editor-palette.ts`). Fixed names mean the colors come\n * from this stylesheet — the same one that paints the rest of the UI — so a\n * code file can never go monochrome because a generated class name changed or\n * an injected style failed. Same Dark+ / Light+ palette as the diff panes\n * above, so a file keeps its colors when switching between the two views.\n */\n.rvs-ck-comment { color: #6a9955; }\n.rvs-ck-keyword { color: #569cd6; }\n.rvs-ck-control { color: #c586c0; }\n.rvs-ck-string { color: #ce9178; }\n.rvs-ck-regexp { color: #d16969; }\n.rvs-ck-number { color: #b5cea8; }\n.rvs-ck-constant { color: #569cd6; }\n.rvs-ck-func { color: #dcdcaa; }\n.rvs-ck-type { color: #4ec9b0; }\n.rvs-ck-variable { color: #9cdcfe; }\n.rvs-ck-attr { color: #9cdcfe; }\n.rvs-ck-meta { color: #c586c0; }\n.rvs-ck-punct { color: #d4d4d4; }\n.rvs-ck-op { color: #d4d4d4; }\n.rvs-ck-heading { color: #569cd6; font-weight: 700; }\n.rvs-ck-em { font-style: italic; }\n.rvs-ck-link { color: #9cdcfe; text-decoration: underline; }\n.rvs-ck-strike { text-decoration: line-through; }\n.rvs-ck-invalid { color: #f44747; }\n\nbody:not([data-ds-dark-theme]) .rvs-ck-comment { color: #008000; }\nbody:not([data-ds-dark-theme]) .rvs-ck-keyword { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-ck-control { color: #af00db; }\nbody:not([data-ds-dark-theme]) .rvs-ck-string { color: #a31515; }\nbody:not([data-ds-dark-theme]) .rvs-ck-regexp { color: #811f3f; }\nbody:not([data-ds-dark-theme]) .rvs-ck-number { color: #098658; }\nbody:not([data-ds-dark-theme]) .rvs-ck-constant { color: #0070c1; }\nbody:not([data-ds-dark-theme]) .rvs-ck-func { color: #795e26; }\nbody:not([data-ds-dark-theme]) .rvs-ck-type { color: #267f99; }\nbody:not([data-ds-dark-theme]) .rvs-ck-variable { color: #001080; }\nbody:not([data-ds-dark-theme]) .rvs-ck-attr { color: #e50000; }\nbody:not([data-ds-dark-theme]) .rvs-ck-meta { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-ck-punct,\nbody:not([data-ds-dark-theme]) .rvs-ck-op { color: #1f1f1f; }\nbody:not([data-ds-dark-theme]) .rvs-ck-heading { color: #0000ff; }\nbody:not([data-ds-dark-theme]) .rvs-ck-link { color: #001080; }\nbody:not([data-ds-dark-theme]) .rvs-ck-invalid { color: #cd3131; }\n\n/**\n * Opaque editor surface, also as plain CSS: the CodeMirror theme paints it too\n * (following the scheme through a Compartment), but a transparent editor is\n * the one failure a user notices immediately, so the container states it as\n * well.\n */\n.rvs-edge .cm-editor,\n.rvs-edge .cm-scroller,\n.rvs-edge .cm-gutters {\n background-color: var(--rvs-code-bg);\n}\n\n.rvs-edge .cm-editor {\n color: var(--rvs-code-fg);\n}\n\n.rvs-edge .cm-gutters {\n color: var(--rvs-code-gutter-fg, var(--rvs-fg3));\n border-right: 1px solid var(--rvs-border);\n}\n\n.rvs-edge .cm-activeLine {\n background-color: rgba(127, 127, 127, .08);\n}\n\n.rvs-edge .cm-activeLineGutter {\n background-color: rgba(127, 127, 127, .12);\n}\n\n/**\n * The code editor's own scrollbars.\n *\n * These had NO rule at all: CodeMirror's scroller is `.cm-scroller`, which is\n * none of `.rvs-scroll` / `.rvs-diff-grid` / `.rvs-hbar`, so the editor fell\n * back to the browser default — too thin to hit with a mouse.\n *\n * Both notations are needed and must stay in step: Chromium honours\n * `scrollbar-width` over `::-webkit-scrollbar` when both apply, so setting\n * only the webkit half would silently have no effect. Keep the numbers equal.\n */\n.rvs-edge .cm-scroller {\n scrollbar-width: auto;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar {\n width: 14px;\n height: 14px;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-thumb {\n /* Opaque, not a translucent token: a thin wash over code is exactly the\n low-contrast target that is hard to grab. */\n background: rgba(121, 121, 121, .8);\n border: 3px solid transparent;\n border-radius: 7px;\n background-clip: padding-box;\n min-height: 36px;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-thumb:hover {\n background: rgba(150, 150, 150, .95);\n border: 3px solid transparent;\n background-clip: padding-box;\n}\n\n.rvs-edge .cm-scroller::-webkit-scrollbar-corner {\n background: transparent;\n}\n\n/* ------------------------------------------------------------ find widget */\n\n/**\n * \"You landed here\": the transient highlight on the line a jump landed on\n * (search hit, @-reference).\n *\n * Deliberately NOT a warm/amber wash: over the dark editor that muddies into a\n * brown-black band that reads as a shadow and swallows annotation colors. This\n * is the accent blue instead — a tinted band with a bright edge on the left and\n * hairlines top/bottom, so it reads as a marked region while every token keeps\n * its own colour and contrast.\n */\n.rvs-reveal-line {\n background-color: rgba(88, 166, 255, .22);\n box-shadow:\n inset 3px 0 0 0 #58a6ff,\n inset 0 1px 0 0 rgba(88, 166, 255, .38),\n inset 0 -1px 0 0 rgba(88, 166, 255, .38);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-reveal-line {\n background-color: rgba(77, 107, 254, .14);\n box-shadow:\n inset 3px 0 0 0 #4d6bfe,\n inset 0 1px 0 0 rgba(77, 107, 254, .35),\n inset 0 -1px 0 0 rgba(77, 107, 254, .35);\n}\n\n/* --------------------------------------------------------- session changes */\n\n/**\n * 「本会话改了什么」:代码行背景 + 左侧改动栏(Cursor 风格)。\n *\n * 三种形态必须**一眼分得开**,光靠绿/红两个色相不够(色弱用户看新增行和删除行\n * 会糊成同一片),所以每种的左侧色条宽度/花样的语义也不同:\n * - 新增:实心绿条;\n * - 修改:实心蓝条(不是绿:修改块里既有留下的行也有新写的行,用\"中性的已变更\"\n * 色不会误导读者以为整块都是新内容);\n * - 删除:**斜纹**红底 + 更粗的红条 —— 这一行其实已经不存在了,只在中止位置上\n * 留个锚点,斜纹是这一层\"虚\"的信息里最不容易被当成真内容的花样。\n *\n * 选择器带上 `.cm-line`:`.cm-activeLine` 由 CodeMirror 的主题注入(运行时\n * `<style>`,通常排在本文档之后),只写 `.rvs-change-*` 两者同权重时会输给它,\n * 光标所在的那一行改动就会消失。\n */\n.rvs-edge .cm-line.rvs-change-add {\n background-color: rgba(46, 160, 67, .17);\n box-shadow:\n inset 3px 0 0 0 #2ea043,\n inset 0 -1px 0 0 rgba(46, 160, 67, .28);\n}\n\n.rvs-edge .cm-line.rvs-change-mod {\n background-color: rgba(56, 139, 253, .15);\n box-shadow:\n inset 3px 0 0 0 #388bfd,\n inset 0 -1px 0 0 rgba(56, 139, 253, .28);\n}\n\n.rvs-edge .cm-line.rvs-change-del {\n background-color: rgba(248, 81, 73, .09);\n background-image: repeating-linear-gradient(135deg, rgba(248, 81, 73, .17) 0 6px, transparent 6px 12px);\n box-shadow: inset 4px 0 0 0 #f85149;\n}\n\n/* 改动栏本身:符号 + 一列与行背景同色的窄条,读起来才是一条\"改动边\"。 */\n.rvs-edge .cm-gutterElement.rvs-change-gutter-add {\n box-shadow: inset 2px 0 0 0 #2ea043;\n}\n\n.rvs-edge .cm-gutterElement.rvs-change-gutter-mod {\n box-shadow: inset 2px 0 0 0 #388bfd;\n}\n\n.rvs-edge .cm-gutterElement.rvs-change-gutter-del {\n box-shadow: inset 2px 0 0 0 #f85149;\n}\n\n.rvs-edge .rvs-change-mark {\n display: inline-block;\n width: 12px;\n font-family: var(--rvs-mono);\n font-size: 12px;\n line-height: 1;\n text-align: center;\n user-select: none;\n}\n\n.rvs-edge .rvs-change-mark-add { color: #2ea043; }\n.rvs-edge .rvs-change-mark-mod { color: #388bfd; }\n.rvs-edge .rvs-change-mark-del { color: #f85149; }\n\n/**\n * 行内(字符级)差异:上面那三种底色回答\"哪几行变了\",这两条回答\"那一行的哪几个\n * 字变了\"。只改一个词的编辑,整行同色时完全看不出改了什么。\n *\n * `rvs-change-word-add` 刻意**只给背景、不给 `color`**:代码有自己的语法着色,\n * 覆盖前景色会把关键字/字符串/注释染成同一种颜色,比不标还难读。透明度压在行\n * 底色之上一点点,叠起来仍读得出\"整块改动\"的边界,又能看出块内哪几个字是新的。\n */\n.rvs-edge .rvs-change-word-add {\n background-color: rgba(46, 160, 67, .22);\n border-radius: 2px;\n}\n\n/**\n * 被删掉、在新文件里已经不存在的内容:零宽 widget,红底 + 删除线。\n *\n * 删除线画在 chip 自己身上(chip 是独立元素,不会波及周围代码);`white-space:\n * pre` 保留被删文本自己的缩进 —— 截断/去空白都会让 chip 谎报\"删了什么\"。\n * 字号略小 + 降一点不透明度,是让它读起来像\"残留物\"而不是一行真代码。\n */\n.rvs-edge .rvs-change-word-del {\n padding: 0 3px;\n margin: 0 1px;\n border-radius: 3px;\n background-color: rgba(248, 81, 73, .18);\n font-size: .92em;\n text-decoration: line-through;\n opacity: .85;\n white-space: pre;\n}\n\n/**\n * 撑宽度的隐形符号:只用它占位,绝不显示(见 `change-marks.ts` 的 SpacerMarker,\n * CodeMirror 会给这个 spacer 元素加 `visibility: hidden`,这里不重复声明)。\n */\n.rvs-edge .rvs-change-spacer {\n opacity: 0;\n}\n\n/* 浅色主题:同样的色相,压暗 + 提高不透明度,在近白底上才看得清。 */\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-line.rvs-change-add {\n background-color: rgba(31, 136, 61, .13);\n box-shadow:\n inset 3px 0 0 0 #1f883d,\n inset 0 -1px 0 0 rgba(31, 136, 61, .22);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-line.rvs-change-mod {\n background-color: rgba(9, 105, 218, .12);\n box-shadow:\n inset 3px 0 0 0 #0969da,\n inset 0 -1px 0 0 rgba(9, 105, 218, .22);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-line.rvs-change-del {\n background-color: rgba(207, 34, 46, .07);\n background-image: repeating-linear-gradient(135deg, rgba(207, 34, 46, .14) 0 6px, transparent 6px 12px);\n box-shadow: inset 4px 0 0 0 #cf222e;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-gutterElement.rvs-change-gutter-add {\n box-shadow: inset 2px 0 0 0 #1f883d;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-gutterElement.rvs-change-gutter-mod {\n box-shadow: inset 2px 0 0 0 #0969da;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .cm-gutterElement.rvs-change-gutter-del {\n box-shadow: inset 2px 0 0 0 #cf222e;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-mark-add { color: #1f883d; }\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-mark-mod { color: #0969da; }\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-mark-del { color: #cf222e; }\n\n/* 浅色主题的行内差异:同样的色相压暗,在近白底上才看得清(暗色那套会糊成一片)。 */\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-word-add {\n background-color: rgba(26, 127, 55, .18);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-edge .rvs-change-word-del {\n background-color: rgba(207, 34, 46, .14);\n}\n\n/**\n * 改动跳转按钮:浮在编辑器右上角。\n *\n * 放在编辑器上而不是面板里 —— 文件打开后光标就在这里,而面板可能被收起、被滚走。\n * 容器 `.rvs-body` 已经是 `position: relative`(它本来用于挂 find 浮层),所以这里\n * 只做绝对定位,不引入新的定位上下文。`pointer-events` 只开在按钮上:这块区域压在\n * 编辑器文字上方,整块可点会把右上角那几行变成死区。\n */\n.rvs-change-nav {\n position: absolute;\n top: 8px;\n right: 14px;\n z-index: 5;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 3px 4px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n background: Canvas;\n box-shadow: 0 2px 8px rgba(0, 0, 0, .28);\n pointer-events: none;\n}\n\n/* The counter is what makes this read as \"3 of 7 changes\" rather than two\n anonymous arrows — and it keeps the buttons from looking like decoration. */\n.rvs-change-nav-count {\n padding: 0 6px 0 4px;\n color: var(--rvs-fg2);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n line-height: 22px;\n white-space: nowrap;\n}\n\n.rvs-change-nav-btn {\n width: 26px;\n height: 22px;\n padding: 0;\n border: 1px solid transparent;\n border-radius: 5px;\n background: transparent;\n color: var(--rvs-fg);\n font-size: 13px;\n font-weight: 600;\n line-height: 20px;\n text-align: center;\n cursor: pointer;\n pointer-events: auto;\n}\n\n.rvs-change-nav-btn:hover {\n border-color: var(--rvs-border2);\n background: var(--rvs-hover);\n color: var(--rvs-brand);\n}\n\n.rvs-change-nav-btn:active { background: var(--rvs-active); }\n\n/**\n * 悬浮在改动行上出现的「撤销这处改动」按钮。\n *\n * 它**不在编辑器的 DOM 里**:`undo-affordance.ts` 把它当 `position: fixed` 的元素挂到\n * `document.body`(和「添加到对话」那个浮层同一套做法),所以这里的选择器不带\n * `.rvs-edge` —— 带上了就一条都不生效。固定定位 + 高 z-index 是刻意的:宿主容器有\n * `overflow: hidden`,挂在里面会被 CodeMirror 的滚动层裁掉。\n *\n * 颜色按\"危险动作\"给:默认红边红字、悬停实心红。底色不透明(不用半透明的 overlay\n * token),否则后面的代码文字会透出来。\n */\n.rvs-undo-affordance {\n position: fixed;\n z-index: 2100;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n max-width: 220px;\n padding: 3px 9px;\n border: 1px solid #f85149;\n border-radius: 6px;\n background-color: #1e2229;\n color: #ffb4ae;\n /* 按钮挂在 body 上,没有 .rvs-edge 的字体上下文:跟随宿主页面字体。 */\n font-family: inherit;\n font-size: 12px;\n line-height: 1.5;\n white-space: nowrap;\n cursor: pointer;\n box-shadow: 0 6px 18px rgba(0, 0, 0, .45);\n /* 纵向以悬停行的中线对齐(横向坐标由 JS 按行尾算好)。 */\n transform: translateY(-50%);\n}\n\n.rvs-undo-affordance:hover {\n background-color: #f85149;\n border-color: #f85149;\n color: #fff;\n}\n\n.rvs-undo-affordance:active {\n transform: translateY(-50%) scale(.97);\n}\n\n.rvs-undo-affordance:focus-visible {\n outline: 2px solid #58a6ff;\n outline-offset: 1px;\n}\n\n/* 删除锚点:这一行其实已经不在了,虚线边框把它和\"真实存在的行\"分开。 */\n.rvs-undo-affordance[data-kind=\"del\"] {\n border-style: dashed;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-undo-affordance {\n border-color: #cf222e;\n background-color: #ffffff;\n color: #a40e26;\n box-shadow: 0 6px 18px rgba(31, 35, 40, .18);\n}\n\nbody:not([data-ds-dark-theme]) .rvs-undo-affordance:hover {\n background-color: #cf222e;\n border-color: #cf222e;\n color: #fff;\n}\n\nbody:not([data-ds-dark-theme]) .rvs-undo-affordance:focus-visible {\n outline-color: #4d6bfe;\n}\n\n/**\n * CodeMirror's own search decorations, also stated here: the editor's theme is\n * injected by the library, and these must stay readable even when only this\n * stylesheet is in play.\n */\n.rvs-edge .cm-searchMatch {\n background-color: rgba(226, 183, 20, .30);\n outline: 1px solid rgba(226, 183, 20, .55);\n}\n\n.rvs-edge .cm-searchMatch-selected {\n background-color: rgba(226, 183, 20, .48);\n}\n\n.rvs-edge .cm-selectionBackground,\n.rvs-edge .cm-content ::selection {\n background-color: rgba(58, 110, 165, .55) !important;\n}\n\n/** Build stamp in the editor info bar: identifies the loaded client bundle. */\n.rvs-build {\n flex: none;\n padding: 0 5px;\n border: 1px solid var(--rvs-border);\n border-radius: 4px;\n color: var(--rvs-fg3);\n font-size: 10px;\n font-family: var(--rvs-mono);\n opacity: .85;\n user-select: text;\n}\n\n.rvs-find {\n position: absolute;\n top: 6px;\n right: 18px;\n z-index: 30;\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 3px 4px 3px 7px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n background-color: Canvas;\n background-image: linear-gradient(var(--dsw-alias-bg-overlay, transparent), var(--dsw-alias-bg-overlay, transparent));\n color: var(--rvs-fg);\n box-shadow: 0 4px 14px rgba(0, 0, 0, .28);\n font-size: 12px;\n}\n\n.rvs-find-input {\n width: 170px;\n padding: 2px 6px;\n border: 1px solid var(--rvs-border2);\n border-radius: 5px;\n background: var(--rvs-input);\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n\n.rvs-find-input:focus {\n border-color: var(--rvs-brand);\n}\n\n.rvs-find-count {\n flex: none;\n min-width: 54px;\n text-align: center;\n color: var(--rvs-fg3);\n font-size: 11px;\n white-space: nowrap;\n}\n\n.rvs-find-count.none {\n color: var(--rvs-git-d);\n}\n\n.rvs-code {\n flex: 1;\n min-height: 0;\n overflow: auto;\n margin: 0;\n padding: 6px 0 16px;\n font-family: var(--rvs-mono);\n font-size: 12.5px;\n line-height: 18px;\n white-space: pre;\n scrollbar-width: thin;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n.rvs-code-line {\n display: flex;\n align-items: flex-start;\n min-width: max-content;\n}\n\n.rvs-code-line:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-code-text {\n flex: 1;\n padding-right: 10px;\n white-space: pre-wrap;\n word-break: break-word;\n user-select: text;\n}\n\n/* ---------------------------------------------------------- history list */\n\n.rvs-commit-item {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 4px 8px 4px 4px;\n border: none;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n line-height: 18px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-commit-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-commit-item.selected {\n background: var(--rvs-active);\n}\n\n.rvs-graph-svg {\n flex: none;\n display: block;\n}\n\n.rvs-commit-hash {\n flex: none;\n font-family: var(--rvs-mono);\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n.rvs-commit-msg {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-commit-date {\n flex: none;\n font-size: 11px;\n color: var(--rvs-fg3);\n}\n\n.rvs-ref-badge {\n flex: none;\n padding: 0 5px;\n border-radius: 8px;\n border: 1px solid var(--rvs-border2);\n font-size: 10px;\n line-height: 15px;\n color: var(--rvs-fg2);\n}\n\n.rvs-ref-badge.head {\n border-color: var(--rvs-brand);\n background: rgba(77, 107, 254, .2);\n color: var(--rvs-fg);\n}\n\n.rvs-ref-badge.tag {\n border-color: rgba(226, 183, 20, .55);\n background: rgba(226, 183, 20, .16);\n color: var(--rvs-git-m);\n}\n\n.rvs-detail {\n margin: 0 6px 8px;\n padding: 8px 10px;\n border: 1px solid var(--rvs-border2);\n border-left: 3px solid var(--rvs-accent);\n border-radius: 8px;\n background: var(--rvs-layer3);\n font-size: 12px;\n}\n\n.rvs-detail-meta {\n color: var(--rvs-fg3);\n font-size: 11px;\n margin-bottom: 6px;\n white-space: pre-wrap;\n}\n\n.rvs-file-row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 4px;\n border-radius: 5px;\n cursor: pointer;\n}\n\n.rvs-file-row:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-file-path {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n /* Same tail-first truncation as the info bar; `plaintext` keeps the path's\n own LTR order (see .rvs-einfo-path). */\n direction: rtl;\n unicode-bidi: plaintext;\n text-align: left;\n}\n\n.rvs-file-status {\n flex: none;\n width: 16px;\n text-align: center;\n font-weight: 700;\n font-size: 11px;\n}\n\n/* --------------------------------------------------------- context menu */\n\n.rvs-portal {\n font-family: inherit;\n}\n\n/**\n * Floating surfaces (menu / modal / toast) paint an OPAQUE base first and then\n * lay the skin's overlay token on top. The token is frequently translucent, so\n * using it alone let the panel's own text bleed through the menu and overlap\n * the entries; `Canvas` follows the resolved light/dark scheme, so the base is\n * correct in either theme.\n */\n.rvs-menu,\n.rvs-modal-box,\n.rvs-toast {\n background-color: Canvas;\n background-image: linear-gradient(var(--dsw-alias-bg-overlay, transparent), var(--dsw-alias-bg-overlay, transparent));\n color: var(--rvs-fg);\n}\n\n.rvs-menu {\n position: fixed;\n z-index: 1000;\n min-width: 172px;\n max-height: calc(100vh - 16px);\n overflow-y: auto;\n padding: 4px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, .35);\n font-size: 12.5px;\n line-height: 18px;\n}\n\n.rvs-menu-sep {\n height: 1px;\n margin: 4px 6px;\n background: var(--rvs-border);\n}\n\n.rvs-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 5px 10px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-menu-item:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-menu-item.danger {\n color: var(--vsc-danger, #f14c4c);\n}\n\n.rvs-menu-item.danger:hover {\n background: rgba(241, 76, 76, .16);\n}\n\n.rvs-menu-item:disabled {\n opacity: .45;\n cursor: default;\n}\n\n.rvs-menu-item:disabled:hover {\n background: transparent;\n}\n\n.rvs-menu-hint {\n margin-left: auto;\n font-size: 11px;\n opacity: .6;\n}\n\n/* ------------------------------------------------------- modal + toast */\n\n.rvs-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 100000;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, .45);\n backdrop-filter: blur(2px);\n animation: rvsFade .12s ease;\n}\n\n.rvs-modal-box {\n width: min(420px, calc(100vw - 40px));\n padding: 16px 18px;\n border: 1px solid var(--rvs-border2);\n border-radius: 10px;\n color: var(--rvs-fg);\n box-shadow: 0 12px 40px rgba(0, 0, 0, .38);\n font-size: 13px;\n animation: rvsPop .14s ease;\n}\n\n.rvs-modal-title {\n margin-bottom: 8px;\n font-size: 14px;\n font-weight: 600;\n}\n\n.rvs-modal-text {\n margin-bottom: 12px;\n color: var(--rvs-fg2);\n font-size: 12.5px;\n line-height: 1.6;\n word-break: break-word;\n white-space: pre-wrap;\n}\n\n.rvs-modal-row {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 14px;\n}\n\n.rvs-modal-btn {\n padding: 5px 14px;\n border: 1px solid var(--rvs-border2);\n border-radius: 7px;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n cursor: pointer;\n}\n\n.rvs-modal-btn:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-modal-btn.primary {\n border-color: transparent;\n background: var(--rvs-accent);\n color: #fff;\n}\n\n.rvs-modal-btn.danger {\n border-color: transparent;\n background: var(--rvs-danger);\n color: #fff;\n}\n\n.rvs-toasts {\n position: fixed;\n right: 16px;\n bottom: 18px;\n z-index: 100001;\n display: flex;\n flex-direction: column;\n gap: 8px;\n align-items: flex-end;\n pointer-events: none;\n}\n\n.rvs-toast {\n max-width: 340px;\n padding: 7px 12px;\n border: 1px solid var(--rvs-border2);\n border-radius: 8px;\n color: var(--rvs-fg);\n box-shadow: 0 6px 20px rgba(0, 0, 0, .35);\n font-size: 12.5px;\n line-height: 1.5;\n word-break: break-word;\n white-space: pre-wrap;\n animation: rvsPop .14s ease;\n}\n\n.rvs-toast.error {\n border-color: rgba(241, 76, 76, .6);\n background-image: linear-gradient(rgba(58, 31, 34, .92), rgba(58, 31, 34, .92));\n}\n\n/* ------------------------------------------------------------- terminal */\n\n.rvs-term-tabs {\n flex: none;\n display: flex;\n align-items: stretch;\n gap: 2px;\n height: 32px;\n padding: 0 4px;\n overflow-x: auto;\n overflow-y: hidden;\n border-bottom: 1px solid var(--rvs-border);\n scrollbar-width: none;\n}\n\n.rvs-term-tabs::-webkit-scrollbar {\n height: 0;\n}\n\n.rvs-term-tab {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n max-width: 180px;\n margin: 4px 0;\n padding: 0 6px 0 9px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--rvs-fg3);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Wrapper that pairs a tab with its close button. The two are siblings so the\n ✕ can be a real focusable <button> (a nested one is invalid HTML and left\n keyboard users no way to close a tab). */\n.rvs-term-tabwrap {\n position: relative;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n.rvs-term-tabwrap .rvs-term-tab {\n padding-right: 22px;\n}\n\n.rvs-term-tabwrap .rvs-etab-close {\n position: absolute;\n right: 3px;\n opacity: 0;\n}\n\n.rvs-term-tabwrap:hover .rvs-etab-close,\n.rvs-term-tabwrap:focus-within .rvs-etab-close {\n opacity: .8;\n}\n\n.rvs-term-tab:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-term-tab.active {\n background: var(--rvs-layer3);\n color: var(--rvs-fg);\n}\n\n.rvs-term-tab.dead {\n color: var(--rvs-git-d);\n}\n\n.rvs-term-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-term-body {\n flex: 1;\n min-height: 0;\n position: relative;\n overflow: hidden;\n}\n\n.rvs-term-slot {\n position: absolute;\n inset: 0;\n}\n\n.rvs-term-dead {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n background: var(--rvs-code-bg);\n color: var(--rvs-code-fg);\n}\n\n/* `.rvs-toast.error` is styled with the overlay form further up, so the popup\n opacity convention (an opaque Canvas base plus a token layer) still governs\n it. A flat `background: #3a1f22` shorthand sat here and overrode that chain —\n same opaque result, but outside the convention. */\n\n@keyframes rvsFade {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes rvsPop {\n from { opacity: 0; transform: scale(.97) translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n.rvs-search-row {\n display: flex;\n gap: 6px;\n padding: 6px 8px;\n border-bottom: 1px solid var(--rvs-border);\n}\n\n/* --------------------------------------------------------- search options */\n\n.rvs-toggles {\n flex: none;\n display: inline-flex;\n border: 1px solid var(--rvs-border2);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.rvs-toggle {\n min-width: 26px;\n height: 22px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n border: none;\n border-right: 1px solid var(--rvs-border);\n background: transparent;\n color: var(--rvs-fg3);\n font: inherit;\n cursor: pointer;\n}\n\n.rvs-toggle:last-child {\n border-right: none;\n}\n\n.rvs-toggle:hover {\n background: var(--rvs-hover);\n color: var(--rvs-fg);\n}\n\n.rvs-toggle.active {\n background: var(--rvs-active);\n color: var(--rvs-fg);\n box-shadow: inset 0 0 0 1px var(--rvs-brand);\n}\n\n.rvs-glyph {\n font-family: var(--rvs-mono);\n font-weight: 600;\n line-height: 1;\n letter-spacing: -0.02em;\n}\n\n.rvs-glyph-underline {\n text-decoration: underline;\n}\n\n.rvs-scope-label {\n flex: none;\n align-self: center;\n font-size: 11px;\n color: var(--rvs-fg3);\n white-space: nowrap;\n}\n\n/* --------------------------------------------------------- search results */\n\n.rvs-hit {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 10px;\n border: none;\n background: transparent;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-hit:hover {\n background: var(--rvs-hover);\n}\n\n.rvs-hit-file {\n font-weight: 600;\n}\n\n.rvs-hit-line {\n padding-left: 30px;\n align-items: flex-start;\n font-family: var(--rvs-mono);\n font-size: 12px;\n color: var(--rvs-fg2);\n}\n\n.rvs-hit-count {\n flex: none;\n min-width: 18px;\n padding: 0 5px;\n border-radius: 9px;\n background: var(--rvs-hover);\n font-size: 10.5px;\n line-height: 15px;\n text-align: center;\n color: var(--rvs-fg3);\n}\n\n.rvs-hit-lineno {\n flex: none;\n width: 3.2em;\n text-align: right;\n color: var(--rvs-fg3);\n opacity: .8;\n font-size: 11px;\n}\n\n.rvs-hit-text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: pre;\n direction: ltr;\n}\n\n.rvs-mark {\n padding: 0 1px;\n border-radius: 2px;\n background: rgba(226, 183, 20, .3);\n color: inherit;\n}\n\n.rvs-hit-dir {\n flex: none;\n color: var(--rvs-fg3);\n font-size: 11px;\n}\n\n.rvs-hit-name {\n flex: none;\n font-weight: 600;\n}\n\n.rvs-kbd {\n padding: 0 4px;\n border: 1px solid var(--rvs-border2);\n border-radius: 4px;\n font-size: 10px;\n color: var(--rvs-fg3);\n}\n\n/* --- SCM: repository directory row (git runs in THIS directory) ------------- */\n\n.rvs-repo-bar {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px 4px 10px;\n border-bottom: 1px solid var(--rvs-border);\n background: var(--rvs-bg2, var(--dsw-alias-bg-layer-1, #1b1b1b));\n font-size: 11px;\n flex: none;\n}\n\n.rvs-repo-bar-label {\n color: var(--rvs-fg3);\n white-space: nowrap;\n}\n\n.rvs-repo-bar-path {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n direction: rtl;\n unicode-bidi: plaintext;\n text-align: left;\n padding: 2px 6px;\n border: 1px solid var(--rvs-border2);\n border-radius: 4px;\n background: var(--rvs-bg3, rgba(255, 255, 255, .04));\n color: var(--rvs-fg);\n font-family: var(--rvs-mono, ui-monospace, Consolas, monospace);\n font-size: 11px;\n cursor: pointer;\n}\n\n.rvs-repo-bar-path:hover {\n border-color: var(--rvs-brand);\n color: var(--rvs-brand-fg, var(--rvs-fg));\n}\n\n.rvs-repo-bar-fix {\n margin-top: 10px;\n display: flex;\n justify-content: center;\n}\n\n/* --- SCM: repository directory picker -------------------------------------- */\n\n.rvs-repopick {\n width: min(560px, calc(100vw - 40px));\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.rvs-repopick .rvs-modal-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.rvs-repopick-bar {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 2px;\n min-height: 22px;\n font-family: var(--rvs-mono, ui-monospace, Consolas, monospace);\n font-size: 12px;\n}\n\n.rvs-crumb-wrap {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n color: var(--rvs-fg3);\n}\n\n.rvs-crumb {\n border: none;\n background: none;\n padding: 1px 3px;\n border-radius: 3px;\n color: var(--rvs-brand-fg, var(--rvs-fg));\n font: inherit;\n cursor: pointer;\n}\n\n.rvs-crumb:hover {\n background: var(--rvs-row-hover, rgba(255, 255, 255, .07));\n}\n\n.rvs-repopick-probe {\n padding: 5px 8px;\n border-radius: 4px;\n font-size: 11.5px;\n border: 1px solid var(--rvs-border2);\n background: var(--rvs-bg3, rgba(255, 255, 255, .04));\n color: var(--rvs-fg2);\n}\n\n.rvs-repopick-probe.root {\n border-color: var(--rvs-ok, #2ea043);\n color: var(--rvs-ok, #2ea043);\n}\n\n.rvs-repopick-probe.inside {\n border-color: var(--rvs-brand);\n color: var(--rvs-brand-fg, var(--rvs-fg));\n}\n\n.rvs-repopick-found {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 4px;\n padding-bottom: 2px;\n}\n\n.rvs-repopick-found-label {\n color: var(--rvs-fg3);\n font-size: 11px;\n white-space: nowrap;\n}\n\n.rvs-repopick-list {\n flex: 1 1 auto;\n min-height: 150px;\n max-height: 42vh;\n overflow: auto;\n border: 1px solid var(--rvs-border);\n border-radius: 5px;\n background: var(--rvs-bg-layer, rgba(0, 0, 0, .18));\n padding: 2px;\n}\n\n.rvs-repopick-row {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 3px 6px;\n border: none;\n border-radius: 3px;\n background: none;\n color: var(--rvs-fg);\n font: inherit;\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n\n.rvs-repopick-row:hover {\n background: var(--rvs-row-hover, rgba(255, 255, 255, .07));\n}\n\n.rvs-repopick-row span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.rvs-repopick-dim {\n color: var(--rvs-fg3);\n font-size: 11.5px;\n padding: 6px;\n}\n\n.rvs-repopick-type {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.rvs-repopick-type .rvs-input {\n flex: 1 1 auto;\n}\n\n.rvs-repopick-error {\n color: var(--rvs-error, #f85149);\n font-size: 11.5px;\n}\n";
15
15
  const key$2 = "E:\\work_base\\dsh_remote_vs\\src\\client\\vsc\\vsc.css";
16
16
  const injected$2 = typeof document === "undefined" ? void 0 : Array.prototype.find.call(document.querySelectorAll("style[data-rvs-css]"), (tag) => tag.dataset.rvsCss === key$2);
17
17
  if (typeof document !== "undefined" && injected$2 === void 0) {
@@ -1413,6 +1413,8 @@ window.__ModuleLoader__.load({
1413
1413
  "editor.tokenCount": "语法着色 token 数(0 = 没有语法包或加载的是旧 bundle)",
1414
1414
  "search.empty": "没有匹配的文件。",
1415
1415
  "search.truncated": "结果过多,仅显示前一批。",
1416
+ "search.revealInExplorer": "打开文件所在位置",
1417
+ "search.openInTerminal": "在终端中打开",
1416
1418
  "scm.title": "源代码管理",
1417
1419
  "scm.refresh": "刷新",
1418
1420
  "scm.changes": "更改",
@@ -1514,6 +1516,9 @@ window.__ModuleLoader__.load({
1514
1516
  "scm.stagedReadonly": "已暂存(只读)",
1515
1517
  "scm.reloadDiff": "重新载入差异",
1516
1518
  "scm.hScrollHint": "有比窗口更宽的行 —— 拖动这里左右滚动(或按住 Shift 滚滚轮)",
1519
+ "scm.prevHunk": "上一处改动",
1520
+ "scm.nextHunk": "下一处改动",
1521
+ "scm.hunkNavHint": "在改动之间跳转(显示第 n 处 / 共 N 处)",
1517
1522
  "scm.stageHunk": "暂存该块",
1518
1523
  "scm.unstageHunk": "取消暂存该块",
1519
1524
  "scm.revertHunk": "还原该块",
@@ -1742,6 +1747,8 @@ window.__ModuleLoader__.load({
1742
1747
  "editor.tokenCount": "Colored syntax tokens (0 = no grammar, or a stale client bundle)",
1743
1748
  "search.empty": "No matching files.",
1744
1749
  "search.truncated": "Too many hits; showing the first batch.",
1750
+ "search.revealInExplorer": "Open containing folder",
1751
+ "search.openInTerminal": "Open in terminal",
1745
1752
  "scm.title": "Source control",
1746
1753
  "scm.refresh": "Refresh",
1747
1754
  "scm.changes": "Changes",
@@ -1843,6 +1850,9 @@ window.__ModuleLoader__.load({
1843
1850
  "scm.stagedReadonly": "staged (read-only)",
1844
1851
  "scm.reloadDiff": "Reload diff",
1845
1852
  "scm.hScrollHint": "A line is wider than the view — drag here to scroll sideways (or hold Shift and wheel)",
1853
+ "scm.prevHunk": "Previous change",
1854
+ "scm.nextHunk": "Next change",
1855
+ "scm.hunkNavHint": "Jump between changes (shows change n of N)",
1846
1856
  "scm.stageHunk": "Stage this hunk",
1847
1857
  "scm.unstageHunk": "Unstage this hunk",
1848
1858
  "scm.revertHunk": "Revert this hunk",
@@ -22574,6 +22584,10 @@ window.__ModuleLoader__.load({
22574
22584
  function IconChevronDown(props) {
22575
22585
  return svg(props.size ?? 14, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3.5 6 L8 10.5 L12.5 6" }), props.className);
22576
22586
  }
22587
+ /** Mirror of `IconChevronDown`, for the diff's "previous change" button. */
22588
+ function IconChevronUp(props) {
22589
+ return svg(props.size ?? 14, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3.5 10 L8 5.5 L12.5 10" }), props.className);
22590
+ }
22577
22591
  function IconFolder(props) {
22578
22592
  return props.open === true ? svg(props.size ?? 16, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M1.8 12.6V4.2a1 1 0 0 1 1-1h3l1.4 1.6h6a1 1 0 0 1 1 1v1H2.6l1 5.8a1 1 0 0 0 1 .8h7.6" }), props.className) : svg(props.size ?? 16, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M1.8 12.6V4.2a1 1 0 0 1 1-1h3l1.4 1.6h6a1 1 0 0 1 1 1v6.8a1 1 0 0 1-1 1H2.8a1 1 0 0 1-1-1Z" }), props.className);
22579
22593
  }
@@ -37980,7 +37994,7 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
37980
37994
  const primary = host.querySelectorAll("[class*=\"rvs-ck-\"]").length;
37981
37995
  const fallback = host.querySelectorAll("span[class^=\"rvs-t-\"]").length;
37982
37996
  const count = primary > 0 ? primary : fallback;
37983
- console.info(`[dsh-remote-vs] build 1023-0853 · ${source} · colored tokens: ${count} (${propsRef.current.path})`);
37997
+ console.info(`[dsh-remote-vs] build 1026-0953 · ${source} · colored tokens: ${count} (${propsRef.current.path})`);
37984
37998
  propsRef.current.onTokens?.(count);
37985
37999
  };
37986
38000
  const armFallback = () => {
@@ -38994,6 +39008,47 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
38994
39008
  removed
38995
39009
  };
38996
39010
  }
39011
+ /**
39012
+ * Where a prev/next jump lands, as an index into the ordered hunk list.
39013
+ *
39014
+ * Wraps at both ends (the last change's "next" is the first change, and the
39015
+ * first one's "previous" is the last), because a diff toolbar button that does
39016
+ * nothing at the end reads as broken. `cursor < 0` means "nothing jumped yet":
39017
+ * the first `next` lands on the first hunk and the first `previous` on the last.
39018
+ *
39019
+ * Pure, so `scripts/check-diff-ui.mjs` can pin the wrapping without a browser.
39020
+ *
39021
+ * @param count - how many hunks the model has (0 → -1, i.e. nothing to jump to).
39022
+ * @param cursor - the hunk the toolbar currently reports, or -1.
39023
+ * @param direction - 1 for "next change", -1 for "previous change".
39024
+ * @returns the hunk index to scroll to, or -1 when the model has no hunks.
39025
+ */
39026
+ function stepHunk(count, cursor, direction) {
39027
+ if (count <= 0) return -1;
39028
+ if (cursor < 0 || cursor >= count) return direction === 1 ? 0 : count - 1;
39029
+ return (cursor + direction + count) % count;
39030
+ }
39031
+ /**
39032
+ * The hunk the viewport is sitting in, derived from the top visible row.
39033
+ *
39034
+ * "Sitting in" = the last change whose first row is at or ABOVE the top row, so
39035
+ * scrolling into the middle of a change keeps reporting that change (and
39036
+ * scrolling above the first one reports -1). This is what keeps the toolbar's
39037
+ * `n / N` honest when the user scrolls by hand instead of using the buttons —
39038
+ * the jump math alone would report the last button press, not the screen.
39039
+ *
39040
+ * @param startRows - first row index of each hunk, ASCENDING.
39041
+ * @param topRow - the row index at the top of the viewport (may be fractional).
39042
+ * @returns the hunk index, or -1 when the viewport is above the first hunk.
39043
+ */
39044
+ function hunkAtRow(startRows, topRow) {
39045
+ let found = -1;
39046
+ for (let index = 0; index < startRows.length; index += 1) {
39047
+ if (startRows[index] > topRow) break;
39048
+ found = index;
39049
+ }
39050
+ return found;
39051
+ }
38997
39052
  //#endregion
38998
39053
  //#region src/client/changes/model.ts
38999
39054
  /** An empty set (a file that ended up unchanged). */
@@ -40272,7 +40327,64 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
40272
40327
  return next ?? current;
40273
40328
  }
40274
40329
  //#endregion
40330
+ //#region src/client/shell.ts
40331
+ /**
40332
+ * Shell quoting for the lines this plugin types into a remote terminal.
40333
+ *
40334
+ * One rule, one place: the client builds POSIX shell text in three spots (the
40335
+ * `cd` typed into a fresh terminal, the bounded suffix search for an unresolved
40336
+ * click target, and the terminal command a menu entry sends), and a second copy
40337
+ * of the quoting rule is how an injection bug reappears after it was fixed once.
40338
+ *
40339
+ * Single quotes are the only form that is safe for arbitrary paths — double
40340
+ * quotes still expand `$`, backticks and `\` — and the closing quote is the one
40341
+ * character they cannot carry, so it is spliced in as `'\''`.
40342
+ */
40343
+ /**
40344
+ * Quote one value for a POSIX shell, as a single word.
40345
+ *
40346
+ * @param value - the raw path or word.
40347
+ * @returns `'value'` with every embedded `'` spliced as `'\''`.
40348
+ */
40349
+ function shellQuote(value) {
40350
+ return `'${value.replace(/'/g, `'\\''`)}'`;
40351
+ }
40352
+ /**
40353
+ * The command typed into a fresh remote terminal to land in one directory.
40354
+ *
40355
+ * A trailing newline is NOT included: the caller writes the line and the
40356
+ * terminal's Enter, which keeps the same string reusable for display.
40357
+ *
40358
+ * @param dir - absolute remote directory.
40359
+ * @returns `cd '<dir>'`.
40360
+ */
40361
+ function cdCommand(dir) {
40362
+ return `cd ${shellQuote(dir)}`;
40363
+ }
40364
+ //#endregion
40275
40365
  //#region src/client/remote-file-open-target.ts
40366
+ /**
40367
+ * Where a clicked file reference should open when the owning session lives in
40368
+ * a remote workspace (pure — no React, no fetch, so the gate can drive it).
40369
+ *
40370
+ * DSH renders every file reference as a `dsh-resource://file/…` address and
40371
+ * opens it through its own right-sidebar preview. That preview resolves the
40372
+ * address path against the SESSION WORKSPACE ROOT. A model that quotes a path
40373
+ * relative to its CURRENT directory (e.g. `_xfer/reports/x.md` while working
40374
+ * in `<workspace>/<inner-repo>`) therefore misses by one segment, and a
40375
+ * remote session's workspace root only exists as an EMPTY local alias dir —
40376
+ * the click dies with the host-side ENOENT the user sees.
40377
+ *
40378
+ * This module answers "which binding, which remote paths to try" for the
40379
+ * client interceptor:
40380
+ * - relative paths are tried against the workspace root first and the
40381
+ * session's current working directory second (the model's usual writing
40382
+ * base), which is what makes the one-segment miss heal;
40383
+ * - absolute spellings (local alias, POSIX remote, drive-qualified alias)
40384
+ * are folded back onto the binding;
40385
+ * - anything that does not name a known binding answers `undefined`, so the
40386
+ * native preview keeps handling local sessions and unknown addresses.
40387
+ */
40276
40388
  const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
40277
40389
  function stripQueryAndFragment(address) {
40278
40390
  const end = address.search(/[?#]/);
@@ -40597,10 +40709,6 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
40597
40709
  }
40598
40710
  return bindingForHostPath(table, path);
40599
40711
  }
40600
- /** POSIX single-quote a word for a remote `bash -c` style command line. */
40601
- function shellQuote(value) {
40602
- return `'${value.replace(/'/g, `'\\''`)}'`;
40603
- }
40604
40712
  /**
40605
40713
  * Bounded remote search for a clicked path that no candidate resolved.
40606
40714
  *
@@ -40874,6 +40982,8 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
40874
40982
  editorSurface: "sidebar",
40875
40983
  view: readStoredView(),
40876
40984
  searchRequest: void 0,
40985
+ revealRequest: void 0,
40986
+ terminalRequest: void 0,
40877
40987
  untrackedDirs: /* @__PURE__ */ new Map()
40878
40988
  };
40879
40989
  /** Stable action identities — `build()` runs on every publish. */
@@ -40947,8 +41057,12 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
40947
41057
  editorSurface: state.editorSurface,
40948
41058
  view: state.view,
40949
41059
  searchRequest: state.searchRequest,
41060
+ revealRequest: state.revealRequest,
41061
+ terminalRequest: state.terminalRequest,
40950
41062
  setView,
40951
41063
  requestSearch,
41064
+ revealInExplorer,
41065
+ openTerminalAt,
40952
41066
  selectWorkspace,
40953
41067
  setRepoSubdir,
40954
41068
  discoverRepos,
@@ -41085,6 +41199,51 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
41085
41199
  } });
41086
41200
  setView("search");
41087
41201
  }
41202
+ /** Monotonic ticket for "show this folder" requests (0 = none yet). */
41203
+ let revealSeq = 0;
41204
+ /**
41205
+ * Show one remote directory in the explorer.
41206
+ *
41207
+ * Two stages, because the tree is lazily loaded: this raises the request and
41208
+ * switches the panel, and the explorer itself expands the ancestors (loading
41209
+ * each level) and scrolls the row into view — it is the only place that knows
41210
+ * which directories are currently expanded.
41211
+ *
41212
+ * @param dir - absolute remote DIRECTORY (a file's parent for a file hit).
41213
+ */
41214
+ function revealInExplorer(dir) {
41215
+ revealSeq += 1;
41216
+ patch({ revealRequest: {
41217
+ id: revealSeq,
41218
+ path: dir
41219
+ } });
41220
+ setView("explorer");
41221
+ }
41222
+ /** Monotonic ticket for "open a terminal here" requests. */
41223
+ let terminalSeq = 0;
41224
+ /**
41225
+ * Open a fresh terminal tab whose shell lands in `dir` — on the CURRENT
41226
+ * workspace's host.
41227
+ *
41228
+ * The terminal panel consumes the request (it owns the tabs) and the shell gets
41229
+ * a quoted `cd` once its PTY reports ready — the host's terminal route has no
41230
+ * "start in this directory" parameter, so a typed command is the honest way.
41231
+ *
41232
+ * The alias travels IN the request. The panel otherwise opens new tabs on the
41233
+ * host it last used, which put the shell on a different machine than the path
41234
+ * belongs to and made the `cd` fail with "No such file or directory".
41235
+ *
41236
+ * @param dir - absolute remote directory.
41237
+ */
41238
+ function openTerminalAt(dir) {
41239
+ terminalSeq += 1;
41240
+ patch({ terminalRequest: {
41241
+ id: terminalSeq,
41242
+ alias: currentWorkspace()?.alias ?? "",
41243
+ dir
41244
+ } });
41245
+ setView("terminal");
41246
+ }
41088
41247
  /**
41089
41248
  * Select the binding every view works against — the USER's choice.
41090
41249
  *
@@ -42230,6 +42389,40 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
42230
42389
  viewport: 0
42231
42390
  });
42232
42391
  const rowsInView = useRowWindow(tab.model?.rows ?? []);
42392
+ /** First row of every change, ascending — the prev/next jump targets. */
42393
+ const hunkRows = (0, react.useMemo)(() => tab.model === void 0 ? [] : tab.model.hunks.map((hunk) => hunk.startRow).sort((a, b) => a - b), [tab.model]);
42394
+ /**
42395
+ * The change the toolbar's `n / N` reports.
42396
+ *
42397
+ * A jump sets it explicitly instead of re-deriving it from `scrollTop`: near
42398
+ * the bottom of a file the browser CLAMPS the offset, so re-deriving would
42399
+ * report the change above the one the button just went to and the next click
42400
+ * would refuse to advance. A manual scroll does re-derive it (`hunkAtRow`),
42401
+ * which is what keeps the counter honest when the user drags instead of
42402
+ * clicking. `-1` = nothing jumped yet / the viewport is above the first
42403
+ * change.
42404
+ */
42405
+ const [hunkCursor, setHunkCursor] = (0, react.useState)(-1);
42406
+ /** The `scrollTop` a jump just wrote, so its own scroll event is not read as a manual scroll. */
42407
+ const jumpedTopRef = (0, react.useRef)(-1);
42408
+ /** Scroll to the next (1) or previous (-1) change, wrapping at both ends. */
42409
+ const jumpHunk = (direction) => {
42410
+ const grid = gridRef.current;
42411
+ if (grid === null) return;
42412
+ const target = stepHunk(hunkRows.length, hunkCursor, direction);
42413
+ if (target < 0) return;
42414
+ const top = hunkRows[target] * ROW_HEIGHT;
42415
+ jumpedTopRef.current = top;
42416
+ grid.scrollTop = top;
42417
+ setHunkCursor(target);
42418
+ };
42419
+ /** Keep `n / N` in step with the viewport while the user scrolls by hand. */
42420
+ const syncHunkCursor = (grid) => {
42421
+ if (hunkRows.length === 0) return;
42422
+ if (Math.abs(grid.scrollTop - jumpedTopRef.current) < 1) return;
42423
+ const next = hunkAtRow(hunkRows, grid.scrollTop / ROW_HEIGHT);
42424
+ setHunkCursor((previous) => previous === next ? previous : next);
42425
+ };
42233
42426
  /**
42234
42427
  * Measure the widest pane and the grid's own width. Runs after every model
42235
42428
  * change, on container resizes (a pane that is empty until its fonts settle
@@ -42417,6 +42610,26 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
42417
42610
  children: t("scm.stagedReadonly")
42418
42611
  }),
42419
42612
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rvs-einfo-spacer" }),
42613
+ mode === "split" && hunkRows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
42614
+ className: "rvs-hunknav",
42615
+ title: t("scm.hunkNavHint"),
42616
+ children: [
42617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
42618
+ title: t("scm.prevHunk"),
42619
+ onClick: () => jumpHunk(-1),
42620
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronUp, { size: 13 })
42621
+ }),
42622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
42623
+ className: "rvs-hunknav-pos",
42624
+ children: hunkCursor < 0 ? `-/${hunkRows.length}` : `${hunkCursor + 1}/${hunkRows.length}`
42625
+ }),
42626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
42627
+ title: t("scm.nextHunk"),
42628
+ onClick: () => jumpHunk(1),
42629
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { size: 13 })
42630
+ })
42631
+ ]
42632
+ }),
42420
42633
  busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniSpinner, {}),
42421
42634
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
42422
42635
  title: t("scm.reloadDiff"),
@@ -42446,7 +42659,10 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
42446
42659
  rowsInView.attachGrid(element);
42447
42660
  gridRef.current = element;
42448
42661
  },
42449
- onScroll: rowsInView.onScroll,
42662
+ onScroll: (event) => {
42663
+ rowsInView.onScroll(event);
42664
+ syncHunkCursor(event.currentTarget);
42665
+ },
42450
42666
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
42451
42667
  className: "rvs-diff-cols",
42452
42668
  children: [
@@ -43098,8 +43314,8 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
43098
43314
  }),
43099
43315
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
43100
43316
  className: "rvs-build",
43101
- title: `dsh-remote-vs build 1023-0853`,
43102
- children: "1023-0853"
43317
+ title: `dsh-remote-vs build 1026-0953`,
43318
+ children: "1026-0953"
43103
43319
  }),
43104
43320
  tokenCount !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
43105
43321
  className: "rvs-build",
@@ -43714,6 +43930,51 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
43714
43930
  });
43715
43931
  }
43716
43932
  }, [alias]);
43933
+ /**
43934
+ * "Show this folder in the tree" — raised by a search result's menu.
43935
+ *
43936
+ * Two stages, because the tree is lazy: expand every level from the root down
43937
+ * to the target (the listing of each level is fetched here) and then scroll
43938
+ * its row into view. The scroll is retried rather than delayed once: over SFTP
43939
+ * a deep level can take a moment, and the request carries a monotonic id so
43940
+ * asking for the SAME folder again still re-scrolls.
43941
+ */
43942
+ (0, react.useEffect)(() => {
43943
+ const request = store.revealRequest;
43944
+ if (request === void 0) return;
43945
+ const base = root.replace(/\/+$/, "");
43946
+ const target = request.path.replace(/\/+$/, "");
43947
+ if (target !== base && !target.startsWith(`${base}/`)) return;
43948
+ const chain = [];
43949
+ let walk = base;
43950
+ for (const part of target.slice(base.length).split("/")) {
43951
+ if (part === "") continue;
43952
+ walk = `${walk}/${part}`;
43953
+ chain.push(walk);
43954
+ }
43955
+ for (const dir of chain) loadDir(dir);
43956
+ if (chain.length > 0) setExpanded((previous) => {
43957
+ const next = new Set(previous);
43958
+ for (const dir of chain) next.add(dir);
43959
+ return next;
43960
+ });
43961
+ let attempts = 0;
43962
+ const timer = window.setInterval(() => {
43963
+ attempts += 1;
43964
+ const row = Array.from(document.querySelectorAll("[data-rvs-path]")).find((candidate) => candidate.dataset.rvsPath === target);
43965
+ if (row !== void 0) {
43966
+ window.clearInterval(timer);
43967
+ row.scrollIntoView({ block: "center" });
43968
+ return;
43969
+ }
43970
+ if (attempts > 20) window.clearInterval(timer);
43971
+ }, 150);
43972
+ return () => window.clearInterval(timer);
43973
+ }, [
43974
+ store.revealRequest,
43975
+ root,
43976
+ loadDir
43977
+ ]);
43717
43978
  const revision = store.revision;
43718
43979
  /**
43719
43980
  * The binding this tree was listed from and the revision it was listed at.
@@ -45148,7 +45409,7 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
45148
45409
  }
45149
45410
  function SearchView() {
45150
45411
  const store = useExplorer();
45151
- const { t, alias, root } = store;
45412
+ const { t, alias, root, repoRoot } = store;
45152
45413
  const [mode, setModeState] = (0, react.useState)(readMode);
45153
45414
  const [query, setQuery] = (0, react.useState)("");
45154
45415
  /** Absolute remote directory the search runs in ('' = workspace root). */
@@ -45163,6 +45424,7 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
45163
45424
  const [ran, setRan] = (0, react.useState)(false);
45164
45425
  const [error, setError] = (0, react.useState)(void 0);
45165
45426
  const seq = (0, react.useRef)(0);
45427
+ const menu = useContextMenu();
45166
45428
  const effectiveScope = scope === "" ? root : scope;
45167
45429
  const scopeLabel = (0, react.useMemo)(() => {
45168
45430
  if (effectiveScope === "" || root === "") return "";
@@ -45275,6 +45537,57 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
45275
45537
  const openHit = (path, line) => {
45276
45538
  store.openFile(path, line === void 0 ? void 0 : { line });
45277
45539
  };
45540
+ /** Absolute path of one NAME hit, plus the DIRECTORY a menu action should use. */
45541
+ const nameHitPaths = (hit) => {
45542
+ const abs = `${effectiveScope.replace(/\/+$/, "")}/${hit.path}`;
45543
+ return {
45544
+ abs,
45545
+ dir: hit.isDir ? abs : abs.slice(0, abs.lastIndexOf("/")) || "/"
45546
+ };
45547
+ };
45548
+ /**
45549
+ * Right-click on a name result: the four things one does with a found path.
45550
+ *
45551
+ * "Relative" is relative to the REPOSITORY when the workspace sits inside one
45552
+ * (the same relative form the tree's own menu copies), else absolute — which
45553
+ * is what makes the copied value paste straight into a shell that already ran
45554
+ * `cd` into the repo.
45555
+ */
45556
+ const openHitMenu = (event, hit) => {
45557
+ event.preventDefault();
45558
+ event.stopPropagation();
45559
+ const { abs, dir } = nameHitPaths(hit);
45560
+ const rel = repoRelative(repoRoot, abs) || abs;
45561
+ menu.open(event.clientX, event.clientY, [
45562
+ {
45563
+ label: t("files.copyAbs"),
45564
+ onSelect: () => {
45565
+ copyText$1(abs);
45566
+ toast(t("files.copiedClipboard"));
45567
+ }
45568
+ },
45569
+ {
45570
+ label: t("files.copyRel"),
45571
+ onSelect: () => {
45572
+ copyText$1(rel);
45573
+ toast(t("files.copiedClipboard"));
45574
+ }
45575
+ },
45576
+ {
45577
+ label: t("search.revealInExplorer"),
45578
+ onSelect: () => {
45579
+ store.revealInExplorer(dir);
45580
+ },
45581
+ separated: true
45582
+ },
45583
+ {
45584
+ label: t("search.openInTerminal"),
45585
+ onSelect: () => {
45586
+ store.openTerminalAt(dir);
45587
+ }
45588
+ }
45589
+ ]);
45590
+ };
45278
45591
  const groups = (0, react.useMemo)(() => groupHits(contentHits), [contentHits]);
45279
45592
  /**
45280
45593
  * The content query, compiled ONCE per (text, flags) instead of once per
@@ -45434,6 +45747,9 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
45434
45747
  className: "rvs-hit",
45435
45748
  title: hit.path,
45436
45749
  onClick: () => openHit(`${(effectiveScope === "" ? root : effectiveScope).replace(/\/+$/, "")}/${hit.path}`),
45750
+ onContextMenu: (event) => {
45751
+ openHitMenu(event, hit);
45752
+ },
45437
45753
  children: [
45438
45754
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45439
45755
  className: "rvs-node-icon",
@@ -45506,6 +45822,12 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
45506
45822
  })]
45507
45823
  }, hit.line))] }, group.path))] })
45508
45824
  ]
45825
+ }),
45826
+ menu.menu !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextMenu, {
45827
+ x: menu.menu.x,
45828
+ y: menu.menu.y,
45829
+ entries: menu.menu.entries,
45830
+ onClose: menu.close
45509
45831
  })
45510
45832
  ]
45511
45833
  });
@@ -60293,6 +60615,8 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
60293
60615
  fit.fit();
60294
60616
  const ws = new WebSocket(propsRef.current.wsUrl);
60295
60617
  let opened = false;
60618
+ /** The seeded line is typed at most once per mount (a reconnect re-mounts). */
60619
+ let seeded = false;
60296
60620
  let exitReported = false;
60297
60621
  const reportExit = (code, message) => {
60298
60622
  if (exitReported) return;
@@ -60361,8 +60685,17 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
60361
60685
  } catch {
60362
60686
  return;
60363
60687
  }
60364
- if (frame.t === "ready") propsRef.current.onReady(frame.id);
60365
- else if (frame.t === "out") term.write(frame.d);
60688
+ if (frame.t === "ready") {
60689
+ propsRef.current.onReady(frame.id);
60690
+ const seed = propsRef.current.initialInput;
60691
+ if (!seeded && seed !== void 0 && seed !== "") {
60692
+ seeded = true;
60693
+ send(ws, {
60694
+ t: "in",
60695
+ d: `${seed}\n`
60696
+ });
60697
+ }
60698
+ } else if (frame.t === "out") term.write(frame.d);
60366
60699
  else if (frame.t === "exit") {
60367
60700
  reportExit(frame.code);
60368
60701
  try {
@@ -60509,6 +60842,14 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
60509
60842
  function newId() {
60510
60843
  return `t${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
60511
60844
  }
60845
+ /**
60846
+ * The last "open a terminal here" request this page already acted on.
60847
+ *
60848
+ * Module scope, not a component ref: the panel is normally kept mounted, but a
60849
+ * remount would reset a ref and re-open a tab for the SAME old request. A new
60850
+ * request always carries a larger id, so one counter serves both goals.
60851
+ */
60852
+ let openedTerminalRequest = 0;
60512
60853
  function TerminalView(props) {
60513
60854
  const t = props.t;
60514
60855
  const [alias, setAlias] = (0, react.useState)(readAlias);
@@ -60539,17 +60880,48 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
60539
60880
  ...next
60540
60881
  } : tab));
60541
60882
  }, []);
60542
- const openTab = (0, react.useCallback)((host) => {
60883
+ const openTab = (0, react.useCallback)((host, initialCommand) => {
60543
60884
  if (host === "") return;
60544
60885
  const tab = {
60545
60886
  id: newId(),
60546
60887
  alias: host,
60547
60888
  nonce: 0,
60548
- connected: true
60889
+ connected: true,
60890
+ ...initialCommand === void 0 ? {} : { initialCommand }
60549
60891
  };
60550
60892
  setTabs((prev) => [...prev, tab]);
60551
60893
  setActiveId(tab.id);
60552
60894
  }, []);
60895
+ /**
60896
+ * "Open a terminal here" (a search result's menu): open one tab whose shell
60897
+ * lands in that folder, on the host the folder BELONGS to.
60898
+ *
60899
+ * The alias must come from the request, not from `effectiveAlias`: the panel
60900
+ * remembers whatever host the user last ran a shell on, so a menu action on a
60901
+ * workspace bound elsewhere used to open the shell on the wrong machine and
60902
+ * the `cd` failed with "No such file or directory" for a path that is fine on
60903
+ * its own host.
60904
+ *
60905
+ * The folder is reached by TYPING `cd` once the PTY reports ready (see
60906
+ * `XTerm`'s `initialInput`) — the host's terminal route has no "start in this
60907
+ * directory" parameter, and inventing one would mean a host-side change for a
60908
+ * display feature. The request id makes a repeat for the same folder still
60909
+ * open a second tab.
60910
+ */
60911
+ (0, react.useEffect)(() => {
60912
+ const request = props.request;
60913
+ if (request === void 0 || request.id === openedTerminalRequest) return;
60914
+ openedTerminalRequest = request.id;
60915
+ const target = request.alias !== "" && props.hosts.some((host) => host.alias === request.alias) ? request.alias : effectiveAlias;
60916
+ if (target !== alias) setAlias(target);
60917
+ openTab(target, cdCommand(request.dir));
60918
+ }, [
60919
+ props.request,
60920
+ props.hosts,
60921
+ effectiveAlias,
60922
+ alias,
60923
+ openTab
60924
+ ]);
60553
60925
  const closeTab = (0, react.useCallback)(async (tab) => {
60554
60926
  if (!await confirmDialog({
60555
60927
  title: t("term.closeTitle"),
@@ -60688,6 +61060,7 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
60688
61060
  rows: 24
60689
61061
  }),
60690
61062
  visible: props.visible && tab.id === active?.id,
61063
+ ...tab.initialCommand === void 0 ? {} : { initialInput: tab.initialCommand },
60691
61064
  onReady: (id) => patchTab(tab.id, {
60692
61065
  session: id,
60693
61066
  connected: true,
@@ -61083,7 +61456,8 @@ Integer Float Double Long Int32 Int64 Uint8 uint32_t int32_t uint64_t int64_t si
61083
61456
  case "terminal": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TerminalView, {
61084
61457
  t,
61085
61458
  hosts: store.hosts,
61086
- visible: panelLive("terminal")
61459
+ visible: panelLive("terminal"),
61460
+ request: store.terminalRequest
61087
61461
  });
61088
61462
  case "workspaces": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
61089
61463
  className: "rvs-viewpad",