The vncode Web GUI's conversation-header package: four controls on that header, plus the pack's appearance overrides.
All four are occupants of the Session header's conversation.session.header.utilities list, which renders in ascending order — lower is
further left. The package is control and dress only: it forks nothing and disables no core row.
| Occupant | Order | Position |
|---|---|---|
| Page zoom | -40 |
leftmost |
| Screenshot | -30 |
left of Themes |
| Themes | -20 |
left of Open In… |
| Session-log download | 0 |
the shipped session-log-download seat |
Open In… (dsh-open-in-app) is -10 and dsh-cmdbar's button 30; no shipped order is changed.
- Themes (
-20) — a menu of Light / Dark / System plus every theme in the shipped registry. The package registers Nord, Monokai, Hacker and Cyber throughctx.theme.register, as alias-token overrides on the dark base. Hacker is the phosphor CRT (green-cast black, green text, amber and cyan accents); Cyber is the second high-contrast programmer palette and deliberately not its twin (blue-cast black, near-white body, magenta brand, cyan link).THEME_EXTENSIONSorder is menu order: Light, Dark, Nord, Monokai, Hacker, Cyber, System. The preference and its persistence stay ui-theme's: this control reads the published snapshot and callssetTheme(id); an extension id, which the durable schema cannot hold, lives inui-state's volatile config and is cleared by a built-in pick. - The Session-log download seat (
0) — the shipped three-dot More actions menu replaced by one download button, by the registry's own shadowing rule: the same occupantid(session-log-download) atpriority: -10, so the shipped registration stays mounted (it owns the export) and stops rendering. The control resolvessessionLogDownloadlazily, draws the same preparing / success / error dialog, and is disabled when that row is absent. - Screenshot (
-30) —getDisplayMediaon the current tab, one frame into a canvas, POSTed to this package's host route. The open tooltip bubble is kept out of the frame byhtml[data-dsh-screenshot] [role=tooltip]. - Page zoom (
-40) — Chrome's own ladder, cut at both ends:50, 67, 75, 80, 90, 100, 110, 125, 150, 175, 200. It writes one inlinezoomon the document element and removes it at 100%. It exists because a browser zoom reflows the whole shell while a page zoom does not - the frame keeps its layout, which is what makes it usable on a 1440px screen; the button sits inside the page it zooms, hence the 200% top rung. The level is kept inui-state's volatilepageZoom, with alocalStoragecopy underdsh-themes.page-zoom. - Appearance overrides, plain CSS: the Markdown paper (ui-theme's own light declarations re-read at
boot and re-declared on
body [data-document-markdown], plus white — all-or-nothing, re-installed ontheme/change); the Markdown chrome (the viewer menu hidden on the shipped Markdown renderer alone); the left column's top bar, whose branding band takes the other columns' hairline at y=76, with the VN branding (assets/vncode.svginlined as a data URI, a 24px mark, the namevncode) centred on the band's own top row; the header ring on[data-conversation-header-corner] button; the account menu's Feedback row, hidden on the paper-planepathartwork; the right bar's resize seam, placed from the open panel's own left edge as a CSS anchor underhtml[data-dsh-page-zoomed](the panel, not the column: at a zoom the solve reads a scaled rect, so at 90% the panel is drawn 616px wide in a 648px track and the column's edge is 32px off the seam); the band's action strip — Settings, Plugins, Media, Skills mounted as four of the app's own 28px.dst-buttoncontrols in the 36px row under thevncodeband, each naming itself in a tooltip, with the bottom rows they mirror hidden once the strip is there (the one override here that mounts DOM rather than CSS: the sidebar declares no slot for that row, so the theme adds a sibling of the branded identity and drives each destination the way its own row does). Two of the four are deliberate departures from the rows they mirror: Media opensdsh-audio'smediadialog (ctx.get('media').open()) instead of selecting the panel behind it, so the conversation stays in the main column; and Skills is the pack's ONE visible entry to that dialog — the header seatdsh-skillsstill registers is hidden by a:has([data-dsh-themes-band])rule, because it is what the band's button CLICKS (its click handler carries the session id) and adisplay:nonecontrol still takes a programmatic click. The left column's panel order (regionArea1,panelList2,footArea3); the band lines — the same.5pxhairline the band already ends in, drawn a second time on the frame's y=40 in ALL THREE columns: between the middle column's button row and its session tabs, under the left column's branding row, and in the right bar 2px into the row below its 38px tab strip (that band's own rows divide at 38, not 40, so the line cannot hang on the strip's edge if all three are to start at the same height) — and, with it, the top row's centre: that line splits every band into a 40px top row and a 36px one below, so all three columns' top-row furniture (the branding row's mark, name and collapse toggle, the conversation title and its 28px controls, the dock strip's 28px tab chips) is centred on the frame's y=20, 6px clear of the rule, rather than resting on it — plus the 38px bar the right bar's Start tab was missing, so every tab's hairline lands at y=76 like the Files, History, editor, Diagrams, PDF, image, video and audio tabs' own tool bars already do, and the surface for that row, because the right bar is an overlay: the tab body's own box is painted with the bar's own--dsw-alias-bg-baseand the Start tab's bar wears it as its border colour, so the row under the line is the bar rather than a window onto the conversation behind it; and the fullscreen right panel'swidth:100%while a zoom is in force. Hooks are stable app markers (data-conversation-header-corner,data-rightbar-col,data-dockkit-strip,role="menuitem") or hashed class names.
cordis.patch.ymlinserts thethemesrow and patches nothing else.lib/index.jsregisters one authenticated route,POST /api/dsh-themes/screenshot, whose body is the PNG.lib/client.jsdraws the four header occupants, the theme entries and the stylesheets. It injectsslotsplus@deepseek-ai/dsh-client-ui-themeand@deepseek-ai/dsh-client-ui-conversation;theme,uiStateandsessionLogDownloadusectx.get, neverinject, so a profile without them still gets a header.
- The screenshot route accepts
image/pngonly, requires the real PNG signature and a body under 64 MiB, and writes create-exclusively asvncode-<timestamp>.pngto the Desktop resolved per request — Windows or OneDrive-redirected,XDG_DESKTOP_DIR, home folder last. Failures are typed (415/400/413/500), the client never names a path, and without the host row the browser download is the fallback. - A hashed-class pin stops matching after a harness rename: a no-op, not a broken layout, and the Feedback row would simply return.
- The mid-band line is drawn in the wide left column only. Collapsed, the shell draws one 36px square where the wide row draws a mark, a name and a 28px button, and that square fills its whole 36px strip (y=2..38), so a line at y=40 would run through it.
- The right bar's row surface is painted by this package because the bar is an overlay on the frame: the tab
body's own box takes
--dsw-alias-bg-base, the token the forked bar's own stylesheet paints the dock with. A tab that wants a different surface paints it on top, as every tab body already does. - Under a page zoom,
vhand pointer coordinates stay unscaled whilegetBoundingClientRect()does not, so a drag that compares pointer deltas against a rect moves by the zoom factor.
node scripts/checks/check-client-bundles.mjs # seats, orders, the ladder, the paper, the seam, the band lines, the top row's centre
node scripts/checks/check-node-routes.mjs # the screenshot route: type, signature, cap, Desktop writescripts\setup\install.bat on Windows, ./scripts/setup/install.sh on macOS/Linux: the launcher auto-discovers this package as a standard dsh.bundle,
and a bundle the profile does not list yet is added by one plain run. The web profile live-links it from this repo, so a code edit needs a
restart of npx @deepseek-ai/dsh web plus a hard refresh; scripts\run-web.bat / ./scripts/run-web.sh starts it and opens the printed URL.