vecnode/vncode--packages-dsh-themes ↗★ 3
dsh-themes
The vncode Web GUI's conversation-header package. It owns four controls on that header. The Page-zoom control (order -40, the leftmost of the four) drops a menu holding the level in force plus the two steps, Zoom in and Zoom out: it writes `zoom` on the document element along Chrome's own ladder cut at 50% and 200%, which is the browser's own Ctrl+ / Ctrl- PAGE zoom - the only way to get it in the native window the desktop launcher opens (a Tauri shell over the same dsh web, which has no such gesture at all) - and remembers the level per origin in localStorage, re-applying it before the first render. The Screenshot control (order -30, left of the Themes control) captures the whole window - the app is a fixed-viewport shell, so its 100% width and 100% height are exactly the tab's box - and saves the PNG to the Desktop of the machine running the app: the browser takes the pixels (getDisplayMedia on the current tab, one frame into a canvas) and this package's own host route writes the file, falling back to an ordinary browser download when that route is absent. The Themes control switches the app between the light, dark and system appearance, the same preference Settings > General > Appearance owns, and its menu also lists every theme REGISTERED into that same shipped registry - through ctx.theme.register, the documented third-party surface - which is where this package's own Nord, Monokai and Hacker come from (Nord's Polar Night surfaces, Snow Storm text, Frost accents and Aurora states; Monokai's near-black warm page, off-white body and pink/cyan/yellow/green syntax palette; Hacker's near-black green-cast page, phosphor body and signal-green/amber/cyan syntax palette - all three as alias-token overrides on the dark base; in-process by the shipped design, since the durable preference schema accepts the built-in three only). The button wears one static appearance mark rather than the active preference's sun/moon, and the menu is built from the registry's own list, so a theme is added by being registered. And the Session-log download seat replaces the shipped three-dot 'more actions' button (its only menu item was Download session log) with one plain download icon button that starts the export on the first click while the shipped sessionLogDownload controller keeps doing the work, its preparing/success/error dialog included. It also carries the pack's appearance overrides: the always-light paper the rendered Markdown view is drawn on, the chrome rule that hides the preview header's viewer menu on Markdown tabs so the page has exactly one renderer, the left column's top bar (the branding row becomes the same 76px band, ending in the same hairline, that the middle and right columns open with), the pack's VN branding on that row - the shipped mark and wordmark are replaced by a 24px black disc and the text vncode - and the header ring, so the right bar's own collapse/expand toggle in the header corner wears the same round hairline outline as this package's own header buttons. Alpha. 适合需要调整界面缩放比例、更换主题或导出聊天记录的用户。
같은 패키지 이름의 다른 저장소
설치
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:vecnode/vncode#b3a6a0d53b96ef29d452af3962e8d726a122db3a&path:packages/dsh-themesdsh-themes (alpha.19)
The pack's conversation-header package. It owns four controls on that header and the appearance overrides that dress it.
- Themes (order
-20) — the size and dress of the header's other icon buttons, immediately left of the shipped "Open In…" control (-10). Its menu holds Light, Dark and System plus every theme registered into the shipped theme registry, which is where this package's own Nord, Monokai and Hacker come from; the button wears one static appearance mark rather than the active preference's sun/moon. See Registered themes. - The Session-log download seat (order
0) — one plain download icon button where the shipped three-dot "more actions" button sits, which starts the session-log export behind a one-item menu, so one click starts the export. See The Session-log download seat. - The Screenshot control (order
-30) — captures the whole window (the app is a fixed-viewport shell, so the page's 100% width and 100% height are exactly the tab's box) and saves the PNG to the Desktop of the machine running the app through this package's own host route. See The screenshot control. - The Page-zoom control (order
-40, the leftmost) — a menu holding the level in force plus Zoom in and Zoom out, doing what the browser's own Ctrl+ / Ctrl- page zoom does:zoomon the document element along Chrome's own ladder. It exists because that gesture belongs to the browser — a Chrome tab has it, and the native window the desktop launcher opens (a Tauri shell over the very samedsh web) does not. See The page-zoom control.
It also carries the pack's appearance overrides — rules that hold one surface
on a fixed palette or a fixed shape whatever the app theme is: the Markdown
paper, the Markdown chrome, the left column's top bar with the pack's
VN branding, the header ring, and the right bar's resize seam. All of
them are plain engine-neutral CSS, so they hold in whichever browser the Web GUI
is opened in. It is a thin control, not a second theme system, and the
preference stays owned by the shipped @deepseek-ai/dsh-client-ui-theme: its
theme client service persists the choice in the ui-theme settings namespace,
resolves system through prefers-color-scheme, and ui-layout applies every
snapshot to the document (body[data-ds-dark-theme] + the --dsw-* tokens). This
bundle only reads the published snapshot and calls setTheme(id), exactly like
the Settings → General → Appearance row — one preference, one persistence path,
one palette. The service is resolved lazily (ctx.get('theme'), never in
inject), so a profile that never mounts ui-theme keeps its header, with the
button disabled and reading "The theme service is unavailable". It can also
register themes through that same service (ctx.theme.register, ui-theme's
documented third-party surface).
Registered themes
@deepseek-ai/dsh-client-ui-theme exposes register({ id, colorScheme, tokens });
ui-layout's presenter writes a registered theme's alias tokens as inline CSS
variables on body, over whichever base palette colorScheme selects, and
getTheme() publishes every registered theme in snapshot.themes. This bundle:
- registers each theme in
THEME_EXTENSIONSinside actx.effect, so the theme leaves the registry with the row. A profile without ui-theme simply has nothing to register into, and the control reports that the way it always did; - builds the control's menu from the registry (
snapshot.themes, with thesystempreference appended last), so a theme becomes selectable by being registered — there is no second list to keep in step; - draws the header button with one static appearance mark (a half-filled disc), because a registered palette has no shipped glyph to wear.
All three sit on the dark base palette in the same 93-token shape — 73
alias tokens, 11 --dsw-specific-* and the nine shiki syntax tokens — so one
theme cannot quietly become a subset of another. THEME_EXTENSIONS order is the
menu order: Light / Dark / Nord / Monokai / Hacker / System.
Nord
Nord (nordtheme.com) recolors the alias layer with the palette's own colours:
| Nord | Values | What they paint here |
|---|---|---|
| Polar Night | #2e3440 #3b4252 #434c5e #4c566a | the page, the surface ladder, menus, code blocks, scrollbars |
| Snow Storm | #d8dee9 #e5e9f0 #eceff4 | primary and secondary text, inverted foreground |
| Frost | #8fbcbb #88c0d0 #81a1c1 #5e81ac | brand, links, primary button, switches, focus rings, muted text |
| Aurora | #bf616a #d08770 #ebcb8b #a3be8c #b48ead | the error / warn / success states and the syntax tokens |
nord0 is the page for the conversation and the sidebar (the way vscode-nord
draws editor and sidebar alike — the columns are separated by the frame's own
hairline, not by a lighter rail), a code fence sits one step above it, and the
syntax colours follow the rules nord-vim follows: keywords and comments in Frost
#81a1c1, functions and links in #88c0d0, strings in Aurora green, numbers in
#b48ead.
Monokai
Monokai (monokai.nl) is the classic TextMate palette Wimer Hazenberg wrote for Coda:
| Monokai | Values | What they paint here |
|---|---|---|
| Surfaces | #272822 #2f3029 #3e3d32 #49483e | the page, the raised step, and the theme's own line-highlight and selection steps — menus, code blocks, scrollbars |
| Text | #f8f8f2 #dadad5 #b6b4a8 #75715e | the off-white body, its 12% darker step, the comment grey lifted halfway back to the body, and the comment grey itself as the quietest tier |
| Warm accents | #f92672 #fd971f #e6db74 | the pink brand — switches, focus rings, the primary button, errors — plus orange parameters and the warn step, and yellow strings and warning |
| Cool accents | #66d9ef #ae81ff #a6e22e | cyan links, the info state and the ghost-active border, purple constants, green functions and success |
#272822 is the page for the conversation and the sidebar, the theme's line
highlight (#3e3d32) is one step above it and its selection (#49483e) the step
above that, so a code fence, a menu and a selected row are all steps of the same
near-black. The syntax tokens carry the classic Monokai roles unchanged — keywords
and tags pink, constants purple, strings yellow, comments the olive grey, parameters
orange, functions green, punctuation the off-white body. Steps the classic palette
does not define are derived from its own colours rather than invented: the
primary button's hover is Monokai Pro's pink #ff6188, its dimmed fill is the pink
darkened 28% (#b31b52), the cyan's hover is the cyan lifted a fifth toward white
(#85e1f2), and the two text tiers between the body and the comment grey are the
body darkened 12% (#dadad5) and the comment grey lifted halfway back to the body
(#b6b4a8).
Hacker
Hacker is the phosphor terminal — the palette a monochrome CRT and a hacker film are both drawn with:
| Hacker | Values | What they paint here |
|---|---|---|
| Surfaces | #0a0e0a #101710 #162116 #1c2c1c | the page and the sidebar alike, the raised step, menus, bubbles and code blocks, then the selection and the toolbar one step above |
| Text | #d8ffe0 #bee0c5 #8bb595 #3f6b4a | the phosphor body, its 12% darker step, the quiet tier halfway back to the body, and the dim green as the quietest tier |
| Signal green | #00ff41 #33ff67 #00b82f | brand, primary button, focus rings, switches, success, and the keyword syntax token (the function token is the lighter step #5cff85; hover and dimmed are derived from the green) |
| Amber | #ffb000 #ffd166 | the warn state and its label, and the string tokens |
| Cyan | #00d9ff #33e1ff #7dd3fc | links, the info/business state, the ghost-active border, constants, parameters |
| Error red | #ff4d4d | the error state — Monokai's pink would be off-palette here |
#0a0e0a is the page for the conversation and the sidebar, #162116 is the
step a menu, a bubble and a code fence sit on, and #1c2c1c is the selection and
the inline chip, so a menu, a fence and a selected row are all steps of one
near-black with a green cast. The syntax tokens are one terminal's worth of
colour — keywords and functions green, strings amber, parameters and constants
cyan, comments the dim green, punctuation the phosphor body. Steps the palette
does not define are derived from its own colours by the same arithmetic the
Monokai section documents: the signal green lifts a fifth toward white for its
hover (#33ff67) and darkens 28% when dimmed (#00b82f), the cyan lifts the same
fifth (#33e1ff), and the two text tiers between the body and the dim green are
the body darkened 12% (#bee0c5) and the dim green lifted halfway back to the
body (#8bb595).
Why the alias layer, and how the choice is kept
A static is shared by roles that are not the same role — in the dark palette
neutral-bluish-50 is both the primary label and the brand fill — so recoloring
the --dsw-static-* ramp drags unrelated surfaces along with it. The alias layer
is the semantic one, and the layer ui-theme documents as the third-party surface.
Aliases a theme does not name keep their shipped dark value: the scrims
(bg-mask-*), the elevation strokes and the shadow scale are scheme-neutral
black/white alphas and read correctly on any registered dark theme unchanged.
ui-theme's durable preference schema accepts light / dark / system only, so
an extension theme id is written to the pack's own vncode section instead
(through the uiState service dsh-ui-state publishes) and put back on the next
load; picking a built-in clears that field so it reads as inherited again, and the
shipped preference is never faked. Keeping it in force is the other half: ui-theme
re-adopts its durable preference whenever the settings DOCUMENT changes, and an
extension theme is never in that section, so this control treats it as a DESIRED
STATE it keeps applied (desiredTheme + reconcileTheme on every theme/change)
rather than a one-shot choice. ui-theme's own namespace REVISION is the tie-break:
a re-adopt (revision unmoved) puts the theme back, while a built-in chosen in the
shipped Settings → Appearance row (revision moved) wins and the remembered id is
cleared. Re-picking the built-in that was ALREADY durable is the one case the
revision cannot see, so the extension is re-applied there and this control's own
menu is the escape.
Adding another theme is one entry in THEME_EXTENSIONS (id, label key,
colorScheme, token map, menu glyph) plus its theme. copy in both
dictionaries. The menu picks it up from the registry; nothing else changes.
The Markdown paper
The shipped document preview draws rendered Markdown into a container marked
data-document-markdown and paints it from the --dsw-* tokens. Those tokens are
declared on body (light) and overridden on body[data-ds-dark-theme] (dark),
so a subtree cannot un-dark itself by referencing them — it just inherits the dark
values, which is why the rendered document would otherwise go dark with the app.
This package injects one rule that re-declares ui-theme's own light
declarations on that container (the static palette, the ~80 alias tokens, and the
shiki token colours), then paints background:#fff on it:
body [data-document-markdown]{ /* the theme's light layer, verbatim */ background:#fff; … }
- Read, not hardcoded. The light layer is copied out of ui-theme's own
stylesheets at boot (every top-level
:root/bodyrule that is not the dark one), so a palette change on a harness bump carries over by itself instead of freezing today's hex values here. The read uses the CSSOM and falls back to the rule's text where an engine does not enumerate custom properties. - All or nothing. If the stylesheets cannot be read, nothing is injected: forcing white without the light tokens would paint light text on a white page, which is worse than leaving the view on the app theme.
- Scoped. Only the rendered Markdown document is pinned — chat Markdown, code
previews and every other surface keep following the app theme. A second selector
paints the preview's scrollport (
[data-textpreview-body], matched with:has([data-document-markdown])) white as well, so a short document does not sit on the app's dark canvas underneath; where:has()is unsupported that one rule is dropped and the document itself is still white. Because--dsl-code-block-*and--shiki-*resolve inside the document, the copied tokens also give the code blocks, inline code, links and lists their light styling for free. - Re-installed on every
theme/change, and once more on the tick after boot, in case ui-theme's stylesheets land after this row.
The editor's Preview button is what reaches this view for a Markdown file; the white page is this package's doing.
The Markdown chrome
A rendered Markdown page in this pack has exactly one viewer, but the shipped preview header builds its viewer menu out of every candidate implementation it resolved for the file — the Markdown body plus the shipped plain-text fallback — so a Markdown tab offers "Markdown" / "Plain text". The second entry is never what the pack wants: plain text is what the editor's own text surface is for, and the deliberate way there is the Edit button the editor's document body draws on the page. So the menu is hidden on Markdown tabs:
body [data-document-preview="@deepseek-ai/dsh-client-ui-sidebar-documentpreview/markdown"]
[data-document-viewer-menu]{display:none}
The preview stamps the selected implementation's id into data-document-preview
on the document root, so the rule is scoped by renderer, not by guesswork: it
matches the shipped Markdown id alone and a code or plain-text preview keeps its
menu, where the choice is real and the pack has no opinion about it. It is static
CSS, installed once (there is no palette to read) in its own tag
(dsh-themes/markdown-chrome.css), so it does not depend on the paper's read
succeeding. Only the header control goes: the path, the reload tool, the document
itself and the page's Edit button are untouched.
The left column's top bar
The frame opens with one band per column, and every column's band ends in the same hairline at y=76:
| column | its band | its line |
|---|---|---|
| left | .hHd-Xa_logoRow — the sidebar branding row | none at all |
| middle | the conversation header (min-height:76px, padding:10px 28px 0 20px) | .5px solid var(--dsw-alias-border-l3) at 76 |
| right | the docking kit's 38px tab strip, then the open tab's own 38px header (the shipped Files tab) | the same hairline at 38+38 = 76 |
The left column is the odd one out twice over: no rule under its branding row, and a collapsed rail that changes both the root's top padding (6px → 18px) and the row's height (60px → 36px), so anything drawn under that row moves with the toggle. The override gives the branding row the other two columns' band, in both rail states:
html .hHd-Xa_root.hHd-Xa_collapsed{padding-top:6px}
html .hHd-Xa_root .hHd-Xa_logoRow{
height:70px; /* 6px root padding + 70 = the 76px line */
margin:0 -12px 8px; /* rule to both edges; 8px under it */
padding:4px 12px 35.5px 16px; /* leaves a 30px content strip at the top */
align-items:center;
border-bottom:.5px solid var(--dsw-alias-border-l3,rgba(127,127,127,.18));
}
html .hHd-Xa_root.hHd-Xa_collapse