dsh-vscode-scrollbar
Manifest validGives the DSH conversation the Visual Studio Code editor chrome: a 14px always-visible scrollbar with a flat, translucent thumb, plus a VS Code-style minimap in the right margin (half-window column, m
dsh-vscode-scrollbar
A DSH (DeepSeek Harness) plugin that gives the conversation the Visual Studio Code editor chrome:
- the VS Code scrollbar — a 14px always-visible bar with a flat, translucent thumb that darkens on hover and lightens while you drag it, and
- the VS Code minimap — a scaled-down preview column on the right that shows the whole conversation, with a viewport slider you can drag or click to navigate.
The stock Harness scrollbar is a 5px, rounded, overlay-style bar and the conversation has no minimap at all. This plugin changes only the conversation's scroll column; every other scrollbar in the app (menus, code blocks, sidebars, the composer) keeps the stock look.
What changes
Scrollbar
| Property | Harness default | With this plugin |
|---|---|---|
| Track width | 5px | 14px |
| Track background | transparent | transparent |
| Thumb shape | pill (border-radius:999px, inset border) | flat rectangle, no radius |
| Thumb — light theme | theme scrollbar alias | rgba(100,100,100,.4) (#64646466) |
| Thumb — dark theme | theme scrollbar alias | rgba(121,121,121,.4) (#79797966) |
| Hover | theme scrollbar alias | rgba(100,100,100,.7) (#646464B3) |
| Dragging (active) | theme scrollbar alias | rgba(191,191,191,.4) (#BFBFBF66) |
| Scrollbar buttons | none | none |
These are VS Code's own defaults (scrollbarSlider.* in its light and dark themes;
editor.scrollbar.verticalScrollbarSize = 14). Dark mode is read from the
body[data-ds-dark-theme] attribute that the Harness theme service sets, so the colors follow the
active palette exactly like VS Code does — no plugin-wide color override, no second theme system.
Minimap
| Property | Harness default | With this plugin |
|---|---|---|
| Preview column | none | 80px column flush against the scrollbar, capped at half the window height |
| Content | none | snapshot of the whole conversation, scaled into the column; images are painted, heavyweight media (video/canvas/SVG/forms) is dropped |
| Viewport slider | none | VS Code minimapSlider: square translucent band, visible only while hovering the minimap or dragging |
| Checkpoint rail | right-aligned at the edge, hidden under the preview | parked 110px from the right edge, left of the column and fully visible |
| Click | n/a | jumps the conversation to that point and keeps dragging from there |
| Drag / wheel | n/a | drag scrubs the conversation, the wheel scrolls it |
| "加载更早" pages | collapsed behind a button | expanded automatically, so older turns are never folded away |
| Minimum pane width | n/a | 1180px — below that the column hides instead of covering text |
Behaviour
- The minimap only appears when there is spare room. The chat column is capped at 920px, so on wide windows the preview sits in the empty right margin; on a narrow window it hides rather than covering the messages. It also stays hidden while the conversation is shorter than the viewport.
- The column is flush against the scrollbar and capped at half the window height. Inside that column the whole conversation is mapped 1:1 through the preview scale, so the slider marks the exact preview slice the viewport is showing. (VS Code itself uses the full editor height; the cap is this plugin's only deliberate deviation from it.)
- The slider is VS Code's
minimapSlider: a square translucent band in the scrollbar-slider colors that only appears while the pointer is over the minimap or while dragging (showSlider: mouseover). Its size and offset come from the conversation's own numbers (viewportHeight * scaleY,scrollTop * scaleY), never from the native scrollbar thumb. - Clicking anywhere in the minimap centres the viewport on that line and then keeps the same gesture as a drag, so press-and-drag scrubs continuously. The wheel over the minimap scrolls the conversation, as it does over VS Code's minimap.
- Harness's turn-navigation rail (the short checkpoint bars on the right edge) keeps its stock position until the preview appears, then moves out of the column's way and stays clickable.
- Harness paginates old turns behind a "加载更早" / "Load earlier" button. The plugin clicks it as soon as it renders (paced while the button is in its disabled "加载中" state, capped at 500 pages), so the conversation is not folded into that bucket. This cannot be turned off without disabling the plugin.
- The preview refreshes after new content settles, debounced by 350 ms, so a streaming answer does not rebuild the clone on every token. Scrolling is rAF-throttled.
- It is read-only: interactions pass through the preview, and the clone has its ids, SVG, canvas, video, form controls and running animations removed, so it stays cheap and cannot interfere with the real conversation.
Install / 安装
Install from GitHub into the profile you use:
dsh plugin --profile <profile> add github:azkbzxf/dsh-vscode-scrollbar
Or install a local checkout through the plugin manager tool:
{ "action": "install_bundle", "target": "D:\\path\\to\\dsh-vscode-scrollbar" }
A newly added bundle activates on the running instance: the Host row is live immediately and the Client half hot-loads into the open page. Replacing an already-installed copy needs the page to be reloaded (or DSH restarted) before the fresh client build is used.
Remove it again with:
dsh plugin --profile <profile> remove dsh-vscode-scrollbar
How it works
The conversation scroll container ships no stable class name — its classes are CSS-module hashes
(_727C8a_scrollBody) that change between builds. It does ship a stable DOM contract, though: every
conversation body renders as a [data-slot="conversation.session"] element, and the shared shell
itself styles that relationship (its composer-overlay rules already use
[data-slot="conversation.session"]). Both halves start from that anchor.
Scrollbar
- find each
[data-slot="conversation.session"]anchor, - walk up to the nearest ancestor whose computed
overflow-yisauto/scroll— that is the chat scroll container, - tag it with
data-dsh-vscode-scrollbar, and - let a scoped stylesheet own
::-webkit-scrollbar*for the tagged element only.
The stylesheet also carries a :has(> [data-slot="conversation.session"]) selector branch, so the
bar renders correctly even before the marker is applied.
Minimap
- A fixed-position layer is appended to
document.bodyand placed from the scroll container'sgetBoundingClientRect(): flush against the 14px bar (right - scrollbarWidth - 80px), with the container's top, the container's width, and a height capped at half the window (or the container height when that is shorter). It isz-index:30, read-only and never takes layout space. While it is shown, a stylesheet rule parks Harness's turn-navigation rail (nav[aria-label="轮次导航"]) atright:110px, clear of the 80px column; with the column hidden the rail returns to its stockright:12px. - The chat column is measured on the live tree: every message row is a
[data-chat-flow-kind]flex child of a column capped atmax-width: var(--dsh-chat-content-width)and centred, so a rendered row reports the column width exactly. Measuring the clone instead would be wrong — the clone is detached from the ancestors that publish that CSS variable, so its rows reflow to the full pane width. - The anchor is cloned, sanitized in one tree walk, and given its own layout box: slot anchors render
with
display: contents(the outlet wrapper takes no box), so the clone is forced todisplay: block, sized from the pane's content width, and handed the measured column width as--dsh-chat-content-width. It is appended untransformed first, because the natural height is what the preview is fitted to. - The clone is transformed with
scale(sx, sy) translate(-columnLeft, 0):sx = 80px / columnWidthandsy = columnHeight / naturalHeight, so the whole conversation fills the (capped) column the way VS Code's minimap fits a file into its column. Images stay painted (a chat minimap with empty holes where screenshots were reads as broken); stateful media (video, canvas, SVG, forms) is dropped from the clone. - The slider lives in the preview's own coordinate system, not the scrollbar's: its height is
viewportHeight * scaleYand its top isscrollTop * scaleY, clamped to the track, so it always marks the viewport the preview is showing. The same scale inverts pointer positions: a click mapsy / scaleY - viewportHeight / 2back toscrollTop, and a drag addsdeltaY / scaleY. Its colour, square shape and hover-only visibility copy VS Code'sminimapSlider(#64646466light /#79797966dark, hover#646464B3, active#BFBFBF33). - A
MutationObserveron the anchor rebuilds the clone (trailing 350 ms debounce), aResizeObserverplus a window resize listener re-measure the column and pane, and scroll events schedule a rAF-throttled slider update. - A body
MutationObserverre-tags columns when the conversation re-renders, sessions switch, or a split view opens; scans are coalesced to at most one per 120 ms so streaming stays cheap. - Everything hangs off the plugin effect: the stylesheet, layer, markers, observers and listeners are all removed on disposal, so disabling the plugin restores the stock UI without a reload.
- DSH's "加载更早" button is expanded on a timer: it is clicked as soon as it renders, retried with backoff while it reads "加载中" / "Loading", and the click budget resets when the conversation changes. A disabled or missing button never spins.
Configuration
No settings are required. The scrollbar values are exposed as CSS custom properties on the tagged container, so a theme plugin or a user stylesheet can tune them without forking this package:
--dsh-vscode-scrollbar-size /* track width/height, default 14px */
--dsh-vscode-scrollbar-thumb /* idle thumb color */
--dsh-vscode-scrollbar-thumb-hover /* hovered thumb color */
--dsh-vscode-scrollbar-thumb-active /* dragged thumb color */
Minimap geometry is fixed (80px column flush against the scrollbar, half-window height cap, 1180px minimum pane width); it has no settings of its own. Automatic expansion of "加载更早" pages is also unconditional.
Compatibility
- Verified against DSH Desktop 2.0.18 (client
0.2.1-rc.2, Electron 44 / Chromium). - Uses only
::-webkit-scrollbar, DOM cloning and the two observers — no imports of Harness Client packages, no React tree edits, no DOM of other plugins. - Chromium-based targets only. Browsers without
::-webkit-scrollbar(Firefox, Safari) keep the platform scrollbar and get no minimap; the plugin does not crash there, it simply has no effect.
What's new
- 1.2.1 — the minimap paints images again. 1.2.0 hid them to mirror VS Code, but in a chat that reads as empty holes where screenshots used to be; images are painted, stateful media (video/canvas/SVG/forms) is still dropped, and the half-height cap and hover slider stay.
- 1.2.0 — the minimap now follows VS Code's own behaviour instead of only its geometry: the column
is flush against the scrollbar and capped at half the window height (VS Code uses the full height);
the slider is the real
minimapSlider— square, scrollbar-slider colours, visible only on hover or while dragging; images are painted, stateful media is dropped; pressing anywhere in the minimap jumps and starts a drag; the wheel over the minimap scrolls the conversation. DSH's "加载更早" pagination is expanded automatically, so older turns are no longer folded behind it. - 1.1.3 — the minimap no longer hides Harness's turn-navigation rail: while the column is shown the
rail is parked 110px from the right edge, clear of the preview and still clickable. The slider is now
a pure position overlay — height
viewportHeight * scaleY, offsetscrollTop * scaleY— so it marks the preview line being viewed instead of imitating the native scrollbar thumb's size. - 1.1.2 — fixes the minimap against the real conversation: the chat column is measured from the
live
[data-chat-flow-kind]rows (a detached clone reflows to the full pane), images stay in the preview so its height matches the conversation, the preview is fitted to the pane, and the slider now uses the scroll container's own viewport/scroll ratio, so it tracks the 14px scrollbar exactly. - 1.1.1 — fixes the minimap layout: slot anchors are
display: contents, so the preview clone now gets its own box instead of reflowing inside the 80px column. - 1.1.0 — adds the VS Code minimap preview column next to the scrollbar.
- 1.0.0 — VS Code-style conversation scrollbar.
Development
Files:
client.js Client half: tagging + stylesheet + minimap
index.js Host half (empty apply — the plugin is pure Client UI)
cordis.patch.yml composition row
locale/en.json plugin title/description shown by the plugin manager
locale/zh.json
icon.svg plugin icon
test/harness.mjs offline harness for both features
Run the harness (needs only Node — no browser, no install):
node test/harness.mjs client.js
It stubs the DOM, layout rectangles and the observers, and asserts: the stylesheet is attached once;
the nearest scrollable ancestor of a conversation anchor is tagged while a non-scrollable wrapper is
left alone; the minimap measures the chat column from the live message rows, reproduces it on the
clone, fills the pane, and maps the slider through the preview scale; scrolling moves only
the slider; a click maps back to scrollTop; dragging the thumb scrolls proportionally; the preview
hides on a narrow pane and returns when widened; conversation mutations rebuild the clone; and
disposal removes the sheet, markers, layer, observers and listeners.
Package for a release tarball:
npm pack
Known limitations
- Only the conversation scroll column is restyled and previewed; code blocks, menus and sidebars keep the stock bar and no minimap. A 14px bar inside a code block would be noise.
- The preview is a snapshot refreshed after 350 ms of quiet, not a live mirror: fast streaming shows up in steps rather than continuously.
- The clone keeps images and message structure but drops stateful media — SVG, canvas, video, audio, forms and editors stay out — and events are not cloned, so the preview is inert and cheap.
- Below 1180px of pane width the minimap hides instead of overlapping messages.
- If a future Harness build drops the
data-slot="conversation.session"contract and renames the scroll container, the plugin stops applying (it does not break anything); the fix is a selector update.
License
MIT
Versions
| Latest version | Published | Size |
|---|---|---|
| 1.2.1 | — | — |
Comments
Loading…
Similar plugins
by k-ying
Embeds a real VSCode editor (code-server, or your local desktop VS Code via the bundled bridge extension) as a GUI tab: agent file edits open turn-scoped red/green diffs that survive tab closes and pa
★ 8
↓ 888/wk
MIT
JavaScript
Oct 9, 2026
dsh plugin --profile web add dsh-vsceditorby yangshen830-eng
VS Code-style code editor: sidebar file tree over the workspace, Monaco editing, file read/write, Git status and diff, search, and Markdown preview.
★ 3
↓ 316/wk
MIT
JavaScript
Aug 19, 2026
dsh plugin --profile web add dsh-editorGoogle AI Studio-style floating preview for the Web UI: a draggable/resizable iframe preview with aspect-ratio and resolution presets, plus a VS Code-style code window with direct editing, confirmed w
★ 0
dsh plugin --profile web add @dsh-external/dsh-web-preview-floatIndependent per-pane zoom for the DSH web shell: Ctrl+wheel over a pane, Ctrl+± / Ctrl+0 for the focused pane. Works with the official look and every skin.
★ 0
MIT
TypeScript
dsh plugin --profile web add @beyondandsharp/dsh-plugin-scalingby WindyPro-rourou
Code Studio for DSH Web GUI - VS Code + Cline hybrid: file tree, syntax-highlighted editor, Cline-style line diffs that auto-appear when the agent edits files.
★ 2
↓ 341/wk
MIT
JavaScript
Oct 1, 2026
dsh plugin --profile web add @windypro-rourou/dsh-code-studioby tingfeng347
DeepSeek Harness 的 VS Code 风格本地开发工作台:文件管理、Monaco 编辑、文档预览、搜索、Git、分屏终端与 DSH 对话一体化。
★ 12
↓ 253/wk
MIT
TypeScript
Oct 16, 2026
dsh plugin --profile web add dsh-vscode-workbench