dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
把 DSH 对话界面改成 Visual Studio Code 编辑器风格:14px 常驻滚动条、扁平半透明滑块,并在右侧加上 VS Code 式 Minimap(小地图):高度不超过半屏、悬停才显示滑块、点击/滚轮即可跳转滚动,同时自动展开“加载更早”的过往对话。
A DSH (DeepSeek Harness) plugin that gives the conversation the Visual Studio Code editor chrome:
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.
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 |
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.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
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
[data-slot="conversation.session"] anchor,overflow-y is auto/scroll — that is the chat
scroll container,data-dsh-vscode-scrollbar, and::-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
document.body and placed from the scroll container's
getBoundingClientRect(): 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 is z-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="轮次导航"]) at
right:110px, clear of the 80px column; with the column hidden the rail returns to its stock
right:12px.[data-chat-flow-kind]
flex child of a column capped at max-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.display: contents (the outlet wrapper takes no box), so the clone is forced to
display: 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.scale(sx, sy) translate(-columnLeft, 0): sx = 80px / columnWidth
and sy = 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.viewportHeight * scaleY and its top is scrollTop * scaleY, clamped to the track, so it always
marks the viewport the preview is showing. The same scale inverts pointer positions: a click maps
y / scaleY - viewportHeight / 2 back to scrollTop, and a drag adds deltaY / scaleY. Its colour,
square shape and hover-only visibility copy VS Code's minimapSlider (#64646466 light /
#79797966 dark, hover #646464B3, active #BFBFBF33).MutationObserver on the anchor rebuilds the clone (trailing 350 ms debounce), a
ResizeObserver plus a window resize listener re-measure the column and pane, and scroll events
schedule a rAF-throttled slider update.MutationObserver re-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.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.
0.2.1-rc.2, Electron 44 / Chromium).::-webkit-scrollbar, DOM cloning and the two observers — no imports of Harness Client
packages, no React tree edits, no DOM of other plugins.::-webkit-scrollbar (Firefox, Safari) keep the
platform scrollbar and get no minimap; the plugin does not crash there, it simply has no effect.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.viewportHeight * scaleY, offset scrollTop * scaleY — so it marks
the preview line being viewed instead of imitating the native scrollbar thumb's size.[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.display: contents, so the preview clone now
gets its own box instead of reflowing inside the 80px column.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
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.MIT