ymac 3728e9036b fix(native): release a tab type whose slot registration failed
A plugin reload (or a disable/enable) tears the client fiber down while this
module's own service/store subscriptions are still live: unregistering each
built-in descriptor notifies the plugin's service, and every notification ran
`sync()` again. The `files` takeover is never a descriptor — its key is
`FILES_KIND` — so the drop loop released it every single time, and the block
below re-created it. Re-registering on the now-inactive context throws inside
`registerSlots` (`ctx.slots.inject` cannot create an effect on an inactive
context) *after* `tabs.register` already put the type into the host registry,
so that registration's disposer was lost with the exception.

The id then stayed taken for the rest of the page's life: every later attempt
was refused with

  sidebarRight: tab type id "dsh-better-sidebar:files" is already registered

and the kind rendered the host's "nothing here can view this kind of content
yet" face until a page refresh.

- `sync()` skips the `files` takeover in its drop loop: the takeover is owned
  by the editor-type switch, so an unrelated notification no longer puts its
  host-side type through a tear-down/re-register window;
- a registration that fails after its type was registered releases the type
  again (and whatever slot disposers it collected), so the same id stays
  registrable and a retry can succeed;
- one descriptor that cannot register no longer aborts the whole sync, so the
  remaining types are still brought up;
- teardown releases every registration even when one release throws.

Verified: tests/native-registration.spec.ts (new, 4 cases) is red 4/4 against
the unfixed code and green with the fix; tsc/eslint clean and the full unit
suite passes.
2026-09-27 14:21:25 +08:00
2026-09-23 22:24:56 +08:00
2026-09-23 22:24:56 +08:00
2026-08-08 00:04:01 +08:00
2026-09-23 22:24:56 +08:00

dsh-better-sidebar

Important

Built on DSH's native sidebar API (since v0.19.0): the right column is DSH's own sidebar — every plugin tab type and tab body registers and opens through ctx.sidebarRightTabs / ctx.sidebarRight, every file open from the chat goes through ctx.sidebarRight.openResource('dsh-resource://file/…'), and the plugin no longer draws a right panel of its own (the old free-window capability is gone with it). The self-drawn bottom workbench and the ctx.betterSidebar service other plugins register against are unchanged — see the plugin integration guide.

Since v0.21.1 the host support floor is DSH 0.1.7-rc.1 (peer floor ^0.1.7-rc.1; npm dist-tag alpha, while latest is still v0.19.1). DSH 0.1.7 ships a complete document preview of its own (spreadsheets / PDF / images / Office), so the plugin hands every read-only preview back to the built-in (keeping only Markdown / HTML / the editable code editor), narrows the external-link takeover to links a tab type explicitly claims through urlTarget (the three protocol-routing settings are gone), and rewrites how it reaches settings (preferences now live on this plugin's mount row in the profile, with the old settings.yaml section imported once on first boot); the file tree also gains live refresh. Hosts on 0.1.6-alpha.2 or earlier should stay on v0.19.1 — note that v0.20.0 was never published to npm; every one of these changes shipped in v0.21.1. The DSH-to-plugin version table is in Installation.

A service-oriented sidebar framework, and a complete workbench out of the box

npm version npm downloads CI GitHub stars License: MIT dshfind

Supported DSH versions (v0.21.1): 0.1.7-rc.1+ Plugin ecosystem: GitHub topic dsh-better-sidebar

File management Edit & preview Built-in browser Changes Background tasks Side Chat Plugin integration

A dual workbench (right sidebar + bottom panel) that opens its ctx.betterSidebar service to every plugin —
register new sidebar pages and file viewers via registerTab / registerFileViewer.
🌏 中文 · English
dsh-better-sidebar workbench

📑 Contents

✨ Features

  • 🗂️ File Workbench: file explorer (lazy-loading tree whose expanded directories the host watches per directory, re-listing them whenever they change; symlinks show their target kind — directory links expand, dangling links flagged; per-extension icons on tree rows and file tabs — markdown / images / PDF / code / config / archives — plus plugin-registered ones via registerFileIcon) + an editable CodeMirror editor; Markdown (incl. Mermaid diagrams, strict-mode safe rendering + click-to-zoom; README-level inline HTML — badge walls / <details> blocks / inline tags in table cells, DOMPurify-sanitized; floating table of contents) and HTML (sandboxed iframe plus the two escape hatches the host does not have) are still rendered by this plugin
  • 🌐 Browser and document preview (provided by DSH): web tabs are the host's own ui-sidebar-browser (multiple tabs, back / forward / reload, sandboxed iframe — desktop profile only since 0.1.7); spreadsheets / PDF / images / Office are the host's own ui-sidebar-documentpreview (host-side Office→PDF conversion, worker-backed spreadsheet tables, image / PDF zoom viewports, per-directory auto-refresh). The plugin no longer claims those formats and keeps only the half the host does not provide: the external-link takeover — only links a tab type explicitly claims through urlTarget, everything else is let through to the host (whose linkOpening user setting decides where prose links go)
  • 💻 Terminal (provided by DSH): the right-Sidebar terminal is provided by DSH's own ui-sidebar-terminal (shell picker, rename, reconnect, restore after reload, theme following). The plugin no longer ships a terminal of its own
  • 📂 Model-driven sidebar opens (opt-in): with the global setting on, the sidebar_open tool lets the model actively open files / folders (tree rooted there) / HTTP(S) pages in the sidebar (a web tab needs the host's browser kind, i.e. the desktop profile)
  • 🌿 Changes: one tab, two lenses — Git (real diff / history / stage·commit·revert / worktree & child-repo selection) and This Session (live tracking of every file the model reads / writes / edits, grouped by file with kind filters); a unified diff renderer (mod pairing + intra-line character highlights + syntax coloring incl. mjs/cjs/mts/cts, CSS/SCSS/Less, HTML/XML/SVG/Vue, GraphQL, JSONC/JSON5 + context folding), a draggable bottom preview pane, and one-click expansion into a dedicated diff tab; .md ops (read / write / edit) offer a reading-mode toggle in the preview header — the shared MarkdownText renders GFM tables / task lists / strikethrough / footnotes / math, with local images rewritten through the /sidebar/file media route; documents with ```mermaid fences render through the editor's lazy mermaid renderer (click-to-zoom / pan diagrams); secret redaction — credential-shaped paths mask whole files and ordinary files mask secret-shaped values (api_key: / Bearer / sk- / AKIA / ghp_ / PEM …, field names kept), on by default with a one-click preview-pane toggle (persisted in localStorage), display-only — session data untouched. Known edge: an unquoted mermaid label containing a redacted secret breaks the diagram (falls back to source); workaround: quote the label. .html ops (read / write / edit) gain a Render toggle in the preview head — the editor's /sidebar/html route iframe, with relative assets resolving inside the route and segmented reads rendering the full document; always sandboxed (opaque origin + CSP header, no escape hatch). .pdf ops (read / write / edit) gain the same Render toggle — the editor's PDF preview reused verbatim (media-route bytes + explicit Blob, the browser's native viewer inline, with a download fallback)
  • 🧩 Background Tasks: agent topology + background tasks (exit codes / live output / force-kill)
  • 💬 Side Chat (beta): Codex-style side threads — the child inherits the parent's FULL context (completed turns + the pending question + the in-progress turn's assistant output and tool activity, honestly frozen as "interrupted") and runs independently without entering the main conversation; threads support continuous follow-ups (auto-resumed after a DSH restart) and one-click "Save as new session" promotion to a top-level session
  • 🖥️ Native right sidebar + plugin bottom workbench: the right column belongs to DSH's native right sidebar — the plugin registers every tab type as a native tab (file opens go through dsh-resource://file/**, and the built-in Files page / file tree is taken over), keeping only its own bottom workbench (split panes / session persistence) whose toggle sits in the session header
  • 🔁 Session Isolation: layout / tabs / panels persisted per session, stale state auto-purged
  • ⚙️ Declarative Settings: per-item toggles in the "Side Cards" settings section, secondary settings via the gear dialog
  • ⚡ On-demand Loading: only ~325KB core at startup; heavy deps (editor / mermaid diagrams / third-language dictionaries) load on demand (design)
  • 🌏 i18n: UI text follows DSH's language (zh / en) with live switching; with the optional @huanlin/dsh-plugin-better-locale peer, 19 third-language overlays (ja / de / fr / …) are available

🔌 Core principle: service-first — the 5 built-in tabs + 3 viewers register through the same ctx.betterSidebar API as third-party plugins, with fully equal capabilities; anything the ecosystem can provide better is delegated to ecosystem plugins (28+ ecosystem plugins already — see "🌐 Plugin Ecosystem" below). See "🔌 Service API" and the external plugin guide.

🚀 Installation

Prerequisites: DSH installed (dsh web boots), Node.js ≥ 20, pnpm ≥ 10.

Supported DSH versions: Supported DSH versions (v0.21.1): 0.1.7-rc.1+

📌 Channel and support line: v0.21.1 is the stable release (npm dist-tag latest) and targets DSH 0.1.7-rc.1+ (peer floor ^0.1.7-rc.1, CI pins @deepseek-ai/dsh@0.1.7-rc.1). Pin the DSH version exactly: 0.1.7-rc.1 rides npm's next dist-tag while alpha currently points at 0.1.7-alpha.2 — npm i -g @deepseek-ai/dsh@0.1.7-rc.1. npm latest now moves from v0.19.1 to this version — note that v0.20.0 and v0.21.0-alpha.1 were never published to npm (the interim 0.21.0-rc.1 existed only on the alpha dist-tag); this repository jumped straight from 0.19.1 to this 0.21 line. The floor had to move: semver's prerelease rule means ^0.1.6-alpha.2 can never match any 0.1.7 prerelease. Hosts on DSH 0.1.6-alpha.2 or earlier (including the 0.1.5-rc.3 that is npm's latest) should pin dsh-better-sidebar@0.19.1 — 0.1.7's breakage is large enough (the dsh-settings rewrite, the wholesale rename of ui-primitives icon exports, session format v3→v4) that this version writes no runtime compatibility layer; DSH 0.1.5-alpha.2 and earlier should likewise stay on their old versions (0.19.0-alpha.1 / 0.18.x / 0.17.1).

🧭 Pick the plugin version that matches your DSH (from 0.21.1 the support line is DSH 0.1.7-rc.1 and any later 0.1.7 release; the two 0.1.7 alphas and 0.1.6-and-earlier are outside it):

Your DSH Install command Version / peer declared
0.1.7-rc.1+ (including a later 0.1.7 stable) dsh plugin --profile web add dsh-better-sidebar@latest 0.21.1, ^0.1.7-rc.1
0.1.7-alpha.1 / 0.1.7-alpha.2 nothing to install — move DSH to rc.1 first, then run the row above:
npm i -g @deepseek-ai/dsh@0.1.7-rc.1
—
0.1.6-alpha.2 and earlier, 0.1.5-rc.* (including the 0.1.5-rc.3 that is npm's latest) dsh plugin --profile web add dsh-better-sidebar@0.19.1 0.19.1 (= npm latest), ^0.1.5-rc.1
0.1.5-alpha.2 dsh plugin --profile web add dsh-better-sidebar@0.19.0-alpha.1 ^0.1.5-alpha.2
0.1.2-rc.* dsh plugin --profile web add dsh-better-sidebar@0.18.1 ^0.1.2-rc.1
0.1.2-alpha.2 dsh plugin --profile web add dsh-better-sidebar@0.18.0-alpha.0 ^0.1.2-alpha.2
0.1.0-rc.8 / 0.1.1 dsh plugin --profile web add dsh-better-sidebar@0.17.1 ^0.1.0-rc.8

Swap web for your own profile name. Older versions are pinned exactly (@0.19.1, not @latest), because latest moves forward with each new stable cut. The peer is a range, not an exact pin: ^0.1.7-rc.1 already admits every 0.1.7 rc and the stable release, so a later rc.2 or a stable 0.1.7 does not require a new plugin version. Conversely, do not install 0.19.1 on a 0.1.7 alpha — 0.1.7 rewrote the settings service and moved the icon exports and the session format, so it would simply break.

dsh plugin --profile web add dsh-better-sidebar@latest

@latest is this version: v0.21.1 is the stable release, and npm's latest has moved from v0.19.1 to it. It supports DSH 0.1.7-rc.1+ only — on DSH 0.1.6-alpha.2 or earlier (including the 0.1.5-rc.3 that is DSH's npm latest) pin dsh-better-sidebar@0.19.1 with the command in the table above instead of installing @latest. The plugin depends on no package that needs a build script (the terminal and node-pty went back to DSH wholesale), so installing is one step — no pnpm approve-builds re-run. DSH also ships a Web Plugins page (ui-plugin-manager) plus the plugin_manager tool, so you can enable / disable it right there once installed.

Then hard-refresh the browser (Cmd/Ctrl+Shift+R) to see the sidebar (DSH hot-reloads client changes; only host-half updates need a restart).

Or let DSH install it for you — paste this prompt into any DSH session:

Install the dsh-better-sidebar plugin (a sidebar workbench for DSH):
1. Run: dsh plugin --profile web add dsh-better-sidebar@latest (the alpha channel — `latest` is 0.19.1 and supports only DSH 0.1.5/0.1.6, so it is useless here)
2. When done, remind me to hard-refresh the browser (Cmd/Ctrl+Shift+R)
If anything fails, check the troubleshooting table in the README at https://github.com/omdsh-dev/DSH-better-sidebar

Option 3: one-shot script — from a clone of this repo, run bash scripts/install.sh (macOS / Linux / Windows Git Bash; native Windows uses install.ps1; -h for options) — it installs and registers the bundle in one go (including idempotent cleanup of an old manual mount row).

Updating
dsh plugin --profile web add dsh-better-sidebar@latest

or bump the version in ~/.dsh/profiles/web/package.json to the matching npm version (stable line "^0.19.1"; this line "^0.21.1") and run pnpm install. Then hard-refresh the browser (Cmd/Ctrl+Shift+R) — client changes do not need a DSH restart.

Troubleshooting
Symptom Cause & fix
Ignored build scripts pnpm 11 blocked a transitive dependency's build script. Run pnpm approve-builds (no --all) in the profile directory (~/.dsh/profiles/web) and follow its prompts — the plugin itself has no build-script dependency (node-pty left with the terminal).
minimum release age / version < 24h The release is younger than 24 hours. Wait, or re-run once (pnpm auto-adds minimumReleaseAgeExclude).
"profile directory not found" Run dsh web once so it initializes ~/.dsh/profiles/web.
Two sidebars on the page Double-mount. Old hand-written line: ~/.dsh/profiles/web/cordis.patch.yml still has - insert: ... better-sidebar ... — delete it (a same-id duplicate mount makes the loader fail loudly with duplicate loader entry id). When an aggregate bundle (e.g. @linxin666/dsh-web-ui-all) mounts this package under a different id, the plugin's own bundle patch backs off automatically since 0.13.x (it detects an already-enabled mount of the same package name and does not mount itself) — no manual fix needed; if it still double-mounts, make sure the aggregate bundle precedes dsh-better-sidebar in dsh.profile.bundles.
Where did my settings go after upgrading? DSH 0.1.7 removed the registrable settings namespace: preferences now live on this plugin's mount row in the profile (entry id better-sidebar by default), not in ~/.dsh/settings.yaml. On first boot the plugin imports the dsh-better-sidebar section of the old settings.yaml (renamed settings.yaml.imported by the host) exactly once — only fields the current schema still declares, and only while that row has no user values yet, so it never overwrites values set after the upgrade.
Terminal unusable / shell fails to start The terminal comes from DSH's own ui-sidebar-terminal (this plugin no longer ships a terminal or node-pty, and has no terminal settings). Consult DSH's own docs for problems; if the error mentions build scripts, see the row above.
dsh: command not found Install DSH first, or run npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest.
Install from source / develop (optional — alternative to the npm flow)

To debug local changes or track the dev branch, point the dependency at a local clone and build it yourself:

1. git clone https://github.com/omdsh-dev/DSH-better-sidebar.git ~/Code/DSH-better-sidebar
   cd ~/Code/DSH-better-sidebar && pnpm install && pnpm build
2. In ~/.dsh/profiles/web/package.json dependencies write "dsh-better-sidebar": "link:<absolute path of the clone>"
3. Append this mount line to ~/.dsh/profiles/web/cordis.patch.yml (this row's `config` IS this plugin's settings form: the deployment limits `readLimit` / `mediaLimit` / `uploadLimit` / `listLimit` plus the user-preference fields — that is what the settings page writes; omit it and every field falls back to its schema default):
   - insert:
       - id: better-sidebar
         name: 'dsh-better-sidebar'
         config:
           readLimit: 524288
4. Run pnpm install in ~/.dsh/profiles/web
5. Restart DSH and hard-refresh

Update: git pull && pnpm install && pnpm build → just hard-refresh the browser (client changes hot-reload; only host-half changes need a DSH restart). To switch back to the npm channel, restore the matching npm version (stable line "^0.19.1"; this line "^0.21.1") and re-run pnpm install.

Install via plugin-registry (optional — use either this or the main flow)

Prerequisite: DSH with plugin-registry integrated (dsh registry available). Enabling both channels double-mounts (the Node half loads twice, the page gets two sidebars).

git clone https://github.com/omdsh-dev/DSH-better-sidebar.git && cd DSH-better-sidebar
pnpm install && pnpm build
node scripts/package-registry.mjs   # assemble the registry/ staging (manifest + artifacts + README, not committed)
dsh registry install ./registry     # install (disabled by default)
dsh registry enable dsh-external/dsh-better-sidebar

Update: git pull && pnpm install && pnpm build → node scripts/package-registry.mjs → dsh registry uninstall/install/enable. Remove the other channel's mount before switching.

🖼️ Feature Tour

Below are real UI screenshots (two per row; click to zoom).

🗂️ File Workbench: Explorer
Two explorer modes: embedded in the file preview / standalone file tree. Lazy-loading directory tree whose expanded directories the host watches per directory and re-lists on change, symlinks classified by target kind (directory links expand, dangling links flagged), global filename search, file/folder upload buttons plus drag-drop upload, context menu (open in new tab / open to the side / copy paths), and a hover @file button that references a file straight into the composer.
File explorer
📝 Inline Preview: Markdown · HTML
The Markdown preview renders Mermaid diagrams (strict-mode safe rendering + a second sanitize pass; click a diagram for a zoom modal with wheel-zoom and drag-pan), README-level inline HTML (badge walls <div align=center>, <details> blocks nesting markdown, inline tags in table cells — DOMPurify-sanitized, <script> stripped, local media rewritten to the session media route) and a floating table of contents (appears with ≥3 headings, smooth-scroll jumping, auto-expanding folded blocks); HTML uses the plugin's own sandboxed preview with the two escape hatches the host does not have (htmlViewerNoSandbox / htmlViewerDefaultUnsafe). Images / PDF / spreadsheets / Office are no longer a plugin capability — the host's own document preview renders those formats.
Markdown + Mermaid preview
🖥️ CodeMirror editor
An editable text / code editor (save, syntax highlighting, preview toggle) — the host's own document preview is read-only, which is exactly why the plugin keeps its catch-all viewer.
CodeMirror editor
🖼️ Images / PDF / spreadsheets / Office (provided by DSH)
These read-only formats render in DSH's own ui-sidebar-documentpreview: host-side Office→PDF conversion, worker-backed spreadsheet tables, image / PDF zoom viewports, and per-directory auto-refresh. The plugin deleted its own image / pdf / download-fallback viewers and no longer claims those extensions.
Inline image preview
💻 Terminal (provided by DSH)
The right-Sidebar terminal is provided by DSH's own ui-sidebar-terminal: shell picker, double-click rename, reconnect, restore after reload, theme and contrast following. The plugin no longer ships xterm + node-pty.

⚠️ Model-side caveat: the plugin's own 8 terminal_* tools (off by default) were the model's only cross-call persistent terminal; the upstream equivalent @deepseek-ai/dsh-tool-terminal is not mounted by any shipped bundle, so if you need that capability, add a tool-terminal row to your profile's cordis.patch.yml yourself.

Terminal
🌿 Changes: Git lens + This-Session lens
Two lenses on "what changed?": the Git lens keeps the full source-control surface (stage / unstage / commit (Ctrl+Enter) / revert, history, worktree and child-repo selectors); the This Session lens folds the session event log live, recording every file the model read / wrote / edited (grouped by file, kind-filtered, op-count badge). Clicking any change previews it in the draggable bottom pane with the unified diff — del red / add green / mod-blue pairing + intra-line character highlights + syntax coloring + context folding — or expands into a VSCode-style dedicated diff tab (same rendering stack).
Changes
🌐 External-link takeover (browser view provided by DSH)
Web tabs are DSH's own ui-sidebar-browser (multiple tabs / back-forward-reload / address bar / sandboxed iframe), mounted only in the desktop profile since 0.1.7 — the Web profile has no such kind. The plugin keeps the half the host does not provide: it takes over only links a tab type explicitly claims through urlTarget (Ctrl/Cmd-clicks always pass through) and lets everything else through to the host (whose linkOpening user setting decides where prose links go); the three protocol-routing settings are gone, and a claim whose target type is unavailable at open time falls back to window.open.
Browser
🧩 Tasks: Agent Topology + Background Jobs
Live subagent-tree topology (run states, batched live previews) plus the background-jobs list (exit codes / live output / force-kill); new subagents / jobs can auto-activate the Tasks page, expanding the sidebar on wide viewports without forcing narrow full-screen drawers open (configurable).
Tasks: subagent topology
💬 Side Chat (beta)
Codex-style side threads: one independent tab per conversation; the thread inherits the parent's full context (including the in-progress turn, honestly frozen as "interrupted") and runs independently without polluting the main session; follow-ups survive restarts; one click promotes the thread to a top-level session.
Side Chat (beta)
🖥️ DSH's native right sidebar + plugin bottom workbench
The right column is DSH's own sidebar: the plugin registers every tab type as a native tab (including taking over the built-in Files page), so clicking a file in the chat lands there directly — formats the host's own document preview already covers are rendered by the host, and the plugin claims only Markdown / HTML / editable code; the plugin's own bottom panel can stay open alongside it — drag a tab to a pane edge to split, to the middle to merge, drag the top edge to resize; the toggle lives in the session header.
Dual workbench (right sidebar + bottom panel)
⚙️ Declarative Settings
The "Side card" section in DSH settings: one small card per tab / viewer with an independent toggle (highlighted enabled state + brand switch); secondary settings open from the "Feature settings" strip at the card bottom (switch / text / number / select rows); plugin-owned settings persist under pluginSettings, while the whole preference set lives on this plugin's mount row in the profile (since DSH 0.1.7 settings are addressed by Loader entry id).
Declarative settings: side cards
📱 Mobile
On narrow screens (<768px) the panels become a full-width drawer: bottom-panel tabs merge into the sidebar once, with touch-friendly dragging.
Mobile full-width drawer

🌐 Plugin Ecosystem

The ctx.betterSidebar service opens two extension points to every plugin: registerTab (sidebar pages) and registerFileViewer (file previewers). The 5 built-in tabs + 3 viewers register through the exact same API — fully equal capabilities.

import type {} from 'dsh-better-sidebar'  // triggers the ctx.betterSidebar type merge
export const inject = ['betterSidebar']
export function apply(ctx: Context) {
  ctx.effect(() => ctx.betterSidebar.registerTab({
    id: 'my-plugin:db', title: 'Database', component: ({ scope }) => <DbView sessionId={scope.sessionId} />,
  }))
  ctx.effect(() => ctx.betterSidebar.registerFileViewer({
    id: 'my-plugin:csv', exts: ['csv'], fetchStrategy: 'custom',
    load: async (path, scope) => parseCsv(await fetchText(scope, path)),
    component: ({ customData }) => <CsvGrid rows={customData} />,
  }))
}

The GitHub topic dsh-better-sidebar already hosts 28+ ecosystem plugins (and growing):

The built-in Add Plugins modal: recommended catalog + one-click install command
The built-in "Add plugins" modal in settings: recommended catalog + one-click install command + a direct link to the GitHub topic

📑 Tab Plugins (sidebar pages)

24 plugins (click to expand)
Plugin ⭐ Description
ChenRuoT/dsh-sidebar-qa stars Selection-based side Q&A — Codex-style side questions / Claude Code /btw
fuhefei/dsh-sentinel stars Condition-driven wakeup: file / command / HTTP / process / webhook watches that wake the agent; dock + sidebar branch + global dashboard
Fisfzy/ego-browser stars Agent browser: a local browsing tab (@dsh-external/ego-browser, auto-registers the sidebar page when better-sidebar is present, floating-bubble fallback otherwise)
jiuge2467/dsh-studio stars Full-stack enhancement workbench: multi-source MCP visual debugging hub, visual thinking engine
Iwctwbh/dsh-flowglass stars Flowglass: live session flowgraph (messages / tool groups / subagent branches)
FeatherHunter/dsh-mattpocock-skills-deck stars Game-like mission system for mattpocock/skills: fog-of-war map + task bar
GULI-lab/DSH-element-source stars Click any UI element on your dev page to jump to its Vue / React / Svelte / Angular source, straight into the chat
Lzh3070/dsh-file-review-tab stars File-change review tab: line-level red/green diffs + undo + chat-line deep links
yq04/dsh-git-remotes stars Git remotes tab: branches / upstream / ahead-behind, fetch with prune, ff-only pull, confirm-before-push
ztyhehe/dsh-better-sidebar-svn stars SVN source-control tab: status / diff / log / commit / update / revert / conflict resolution — symmetric to the built-in Git panel
Melody-max114/dsh-excel-panel stars Excel editing: xlsx preview/edit, live formula evaluation, merged cells, save back to the original file
v587d/dsh-anysearch-refs stars AnySearch results as sidebar cards: query, source snippets, highlighted keywords
mlosun/dsh-docs-panel stars Global docs panel: portable Markdown notes, readable from any workspace
lnyuqian/dsh-skill-sidebar stars Skills panel: scans local skill directories, one-click invocation copy, pinning
g-yixuan/dsh-sidenote stars Codex-style side chat + selection annotations (a thin consumer plugin)
thirsty5034/dsh-ssh-tunnel stars Multi-host SSH tunnels + SSH manager tab
thirsty5034/dsh-git-forge stars GitHub / Gitea accounts, project grants and push policy
YesSanSan/dsh-conversation-outline stars Conversation outline tab: per-turn structure, quick jump, one-line LLM titles
Wulabalabo/dsh-sidebar-Explorer-Plus stars File-manager tab: upload / move / delete / rename / new folder (write operations)
yq04/dsh-turn-review stars Turn review: review agent changes turn by turn
Ghz114514/dsh-refpics stars Pinterest-style reference-image search: masonry wall, sidebar board, downloads, save-to-Eagle
yzlin499/dsh-yzlin499-easy-plugins stars A handy utility bundle for a bare-bones DSH
dong-victor/dsh-better-sidebar-starter stars Run-configurations tab: IDEA-style Run/Debug configs (npm / springboot / python / custom) — one-click launch, history, WebSocket live logs (ANSI colors), parallel instances, cross-platform process-tree kill
baosfeng/my-dsh-plugins stars Personal multi-plugin collection (dsh-file-activity): a sidebar file-activity tab recording read / added / modified history and stats, flat-browsed by folder, opened with the native preview
Hoemr/dsh-better-overleaf stars Overleaf tab: direct-CDP browser login (third-party Chromium supported), project switching, local git mirrors under the workspace with two-way sync

🖼️ Viewer Plugins (file previewers)

3 plugins (click to expand)
Plugin ⭐ Description
HuanLinOTO/dsh-plugin-better-sidebar-plugin-office stars Office-suite preview (.docx / .xlsx / .pptx) as a separate bundle to slim the core (in the official recommended catalog)
zemul/dsh-video-preview stars Inline video preview: .mp4 / .webm / .mov / .mkv / .avi with a /video host route supporting HTTP Range scrubbing
dong-victor/dsh-better-sidebar-jupyter stars Runnable .ipynb notebook view: lazy-start Python kernel, streaming outputs, save-back

🧰 Enhancements & Tools

3 plugins (click to expand)
Plugin ⭐ Description
dong-victor/dsh-better-sidebar-terminal-plus stars Terminal enhancement: bundled Nerd Font icons, xterm glyph fixes, stable terminal cwd
Max-Null/dsh-sidebar-preview-select stars Preview selection boost: select text in any sidebar preview → floating "send to session"
Hoemr/dsh-quicklook stars QuickLook-style Space preview: press Space on the active file tab for a full-size image / PDF / text overlay; Space or Esc closes

📣 List your plugin: tag your repo with the dsh-better-sidebar topic to appear on the topic page; then PR one PluginEntry into src/client/plugins-tabs.ts / src/client/plugins-viewers.ts to join the built-in recommended catalog (data integrity is guarded by tests/plugin-list.spec.ts).

🆕 Recent Updates

Sidebar Service API base screenshot

Supported DSH versions: Supported DSH versions (v0.21.1): 0.1.7-rc.1+ · full release history on the Releases page

v0.21.1

🧪 alpha channel (npm dist-tag alpha, install dsh-better-sidebar@latest; npm latest is still v0.19.1): supports DSH 0.1.7-rc.1+ only (peer floor ^0.1.7-rc.1, CI pins @deepseek-ai/dsh@0.1.7-rc.1). Hosts on DSH 0.1.6-alpha.2 or earlier should stay on v0.19.1 — 0.1.7 moves three hard contracts (the settings service, the icon named exports and the session format) and this version writes no runtime compatibility layer. ⚠️ The previous v0.20.0 was never published to npm: its terminal / browser handover ships here too, so npm goes straight from 0.19.1 to this version.

  • 🗂️ Read-only file previews handed to DSH's document preview: DSH 0.1.7's ui-sidebar-documentpreview ships its own spreadsheet / PDF / image / Office rendering (host-side Office→PDF conversion, worker-backed spreadsheet tables, image / PDF zoom, per-directory auto-refresh), so the plugin deleted its image / pdf / binary-download viewers and refuses those extensions in editor.canOpen — xlsx xls csv tsv fods pdf png jpg jpeg gif webp svg bmp ico doc docx ppt pptx — handing the address back to the host. rc.1 takes nine of them back: xlsb / xlt / xltx / xltm / ots / dot / dotx / avif / ods have no host renderer at all (opening one only said "preview is not available"), yet before the handover they reached the plugin's download pane — a regression we introduced ourselves in the previous version. The plugin's code catch-all claims them again. fods stays handed over (the host shows that flat XML as plain text, which beats a download pane). — handing the file address back to the host. Three things the host does not have stay in the plugin: Markdown (its own renderer), HTML (its own sandboxed preview plus the htmlViewerNoSandbox / htmlViewerDefaultUnsafe safety switches), and the editable text / code editor (the built-in ones are read-only previews); unknown binaries (.zip / .wasm) still land on the code editor's download pane after the binary check, so nothing regresses.
  • 🔗 External-link takeover narrowed: the three protocol-routing external-link settings are gone (with their keys in all 20 locale dictionaries). The plugin now takes over only links a tab type explicitly claims through urlTarget and lets everything else through for the host to route (DSH 0.1.7 adds the user setting linkOpening, deciding whether prose links open in the sidebar or a new tab); when nothing claims a link it does not preventDefault; a successful claim whose target type is unavailable at open time falls back to window.open(url, '_blank', 'noopener,noreferrer') — which also fixes a real regression from the previous version: http links inside plugin-drawn markdown (Side Chat transcripts / editor previews / diff panes) did nothing when clicked. Separately, the host's browser kind is no longer mounted in the Web profile (0.1.7 mounts it in the desktop profile only).
  • ⚙️ Settings surface rewritten + preferences imported automatically: DSH 0.1.7 removed the registrable settings namespace in favour of looking a form up by the plugin Loader row's entry id (SettingsForms: only describe / update / replace / mutate / configure remain). Plugin preferences therefore live in the profile's cordis patch document (i.e. this plugin's mount row), not in ~/.dsh/settings.yaml; the schema comes from the plugin module's exported Config (this version merges the user preferences into Config and marks every preference field meta.volatile = true — that single flag is the entire "settings apply live, without remounting the plugin" mechanism). Your settings are not lost: on first boot the plugin imports the dsh-better-sidebar section of the old settings.yaml / settings.yaml.imported once (only while that row still has no user values, and only fields the current schema still declares). The entry id is discovered at runtime (this bundle defaults to better-sidebar; an aggregate bundle mounts it under a different id) and never hardcoded.
  • 🔄 Live-refreshing file tree: the plugin takes over the built-in Files page, so the host's own per-directory watch cannot cover that tree — this version adds /sidebar/ws/fs-watch: the client reports the directories it has expanded, the host watches exactly those with fs.watch (150ms debounce, a 64-handle cap per connection, paths going through the same workspace fence as fs.tree), and a change re-lists just that level; collapsing unsubscribes. Before this, the tree stayed stale until a manual refresh.
  • 🐛 Session following fixed: the plugin used to read a non-existent SessionListState.current field (its own type mirror invented it, so the compiler never complained), which meant per-session persistence was never actually bound and the narrow-viewport park gate was always false. It now uses DSH 0.1.7's ctx.sidebarRight.mounted (set only when the column really switches to another session).
  • 🖥️ The model-side cost is unchanged: the plugin's own 8 terminal_* tools (off by default) were already removed in the previous version, and the upstream equivalent @deepseek-ai/dsh-tool-terminal is still not mounted by any shipped bundle — add a tool-terminal row to your profile's cordis.patch.yml when you need a persistent terminal (otherwise the model only has one-shot bash / pwsh).
  • 📐 Baseline: every @deepseek-ai/dsh-* pins 0.1.7-rc.1, with the @deepseek-ai/cordis peer floor at ^4.0.3; ui-primitives renamed its whole family of named icon exports (Icon<Name><14|16> → Icon<Name>Regular / Medium, 26 named imports adapted); session format v3→v4 (the Side Chat boundary injection now uses plugin:dsh-better-sidebar, and tool-result messages use the top-level role: 'tool' shape, with parsers accepting both old and new shapes for historical logs).

v0.20.0 (dev line, never published)

🚫 This version never reached npm (npm latest is still v0.19.1): it was meant to be the stable release on the 0.1.6-alpha.2 line, but the 0.21 line replaced it before release, so everything below actually shipped in v0.21.1. Its original positioning: supports DSH 0.1.6-alpha.2+ only (peer floor ^0.1.6-alpha.2, CI pins @deepseek-ai/dsh@0.1.6-alpha.2). This is a breaking-change release: hosts on 0.1.5-rc.* should pin dsh-better-sidebar@0.19.1. See docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md for the adaptation record.

  • 🖥️ Terminal handed to DSH's built-in: DSH 0.1.6 ships its own right-column terminal (ui-sidebar-terminal), so the plugin deleted its whole terminal stack — pty-manager / agent-pty / pty-deps / the 8 terminal_* tools / the xterm view and font-linking / cross-session terminal pinning — along with the node-pty dependency, its allowBuilds grant and the installer's -Repair mode. Note: the model therefore loses a cross-call persistent terminal (only one-shot bash / pwsh remain); the upstream @deepseek-ai/dsh-tool-terminal is not mounted by any shipped bundle, so enable it yourself in the profile when you need it.
  • 🌐 Browser view handed to the built-in: the plugin deleted BrowserView / the sandbox status bar / the embeddability probe (including the host's browser.probe route) and the two settings that only configured its own iframe; it keeps the external-link takeover in the chat and UI (protocol-based routing is the capability the host does not have), now targeting DSH's built-in browser tab type.
  • 🧩 The produced-files row goes back to the host: DSH 0.1.6 turned conversation.chat.turnTail from a chain into a list (upstream deliberately lets several plugins append rather than replace each other), so the plugin's takeover could only duplicate the built-in deliverables card and was removed wholesale; click behaviour is unchanged (the built-in goes through the host's openFile → this plugin's editor). The cost is the one "reveal in the file tree" entry at the turn tail (the in-tree context menu remains).
  • 🐛 Two silent breakages fixed: once SidebarRightGuideEntry.id became required, a missing id left the whole native surface silently empty (the throw happens inside an ctx.inject callback and is swallowed); once turnTail became a list the old registration threw outright and the row died. Both are fixed and guarded by tests, with a registration-failure reporting hook added alongside.
  • 🎨 The empty-pane card now matches the built-in card recipe: .paneCard adopts DSH's guide-capsule recipe (0.5px l4 border / 24px radius / layer-1 fill / 56px minimum / 14×20 padding), with tests guarding its geometry, its tokenized colours, and that it does not copy upstream's undefined --dsw-alias-bg-l1/-l2 spellings.
  • 📐 Baseline: every @deepseek-ai/dsh-* package is pinned to 0.1.6-alpha.2, the vanished dsh-code-runtime package is removed, diff / simple-icons are hoisted into devDependencies as primitives' new bare imports, and ui-primitives' three breakages this release (IconSendOutline16 gone, TerminalBlockLabels.noExitCode added, ConnectionIndicator.reconnectLabel removed) are adapted.

v0.19.1

📌 Stable release (npm latest, no prerelease suffix): the pinned baseline moves to DSH 0.1.5-rc.2 (npm next) while the peer floor stays ^0.1.5-rc.1 — nothing in the rc.2 upstream delta touches this plugin (zero changes under packages/api|host|session|agent; the only real code edits are the message-feedback dialog, deliverables card CSS and the CodeFileIcon SVG data split), so rc.1 hosts need no DSH upgrade to run this version. Hosts on DSH 0.1.5-alpha.2 stay on v0.19.0-alpha.1; the 0.1.2-rc.1 stable line keeps using v0.18.1.

  • 🎯 Adapted to DSH 0.1.5-rc.2: devDependency pins, the CI mount lane and SIDEBAR_SERVICE_VERSION all move to rc.2; there is no plugin-side code change (the upstream delta does not touch this plugin — file-by-file audit in docs/plans/2026-09-10-dsh-0.1.5-rc.2-adaptation.md: the vast majority of the 300 changed files are single-line version bumps).
  • 🎨 File icons (#611) and the built-in tab glyphs are now colored (#531 + #594 merged, implementation redone for rc.2):
    • Files and folders use DSH's own artwork: the fallback link is the host's FileTypeIcon from ui-primitives (48 full-color official code/config glyphs plus the category-colored sheets for markdown, images, PDF, Office, video and folders), so the plugin ships no extension table and needs no icon chunk — #429's 563-entry color dataset and lib/client-file-icons.js (255 kB) are gone, along with the fileIconTheme switch. Color is simply the behaviour now, and the core bundle grew by 10 kB.
    • New public registerFileIcon API (capability 'fileIcons'): register your own glyphs by extension (exts), exact file name (names) or directory name (folderNames), ranked by priority then registration order. Caveat: the host classifier covers every path, so a catch-all (exts: []) claims every row the specific rules miss.
    • Colored built-in tab glyphs: Files / Changes / Tasks / Side chat / Terminal / Browser and the diff view all get a colored glyph, painted from --dsw-alias-* tokens so every skin still applies.
    • The native right Sidebar's tab chips carry a glyph too: the host's tab definition has no icon field, but the sidebar.right.pane.tab.title slot is the chip's content — the plugin renders [glyph][title] there (an editor tab with a path shows that file's icon). The glyph is aria-hidden, so the chip's accessible name is unchanged.
  • 🛠 CI fix 1: real Windows-lane timeouts. ci-windows went red eight times in the 2026-09-09/10 window; six of those were process-spawning cases hitting vitest's 5000 ms default: tests/agent-pty.spec.ts (real PowerShell + ConPTY per terminal) and tests/install-powershell.spec.ts (a cold powershell.exe start measured 12.1 s) now declare a 30 s budget each, vitest.config.ts raises the global testTimeout from its 5000 ms default to 15 s (the first real Windows run then lost a third file to the same default — tests/git.spec.ts:85 at 9607 ms; three files, one cause, so per-file patches were whack-a-mole), and waitForTranscript's inner poll budget dropped from 5000 ms to 15 s — the inner budget must sit below the outer one (they used to be the same 5000 ms, a structurally guaranteed timeout). ci-windows's Test step now runs pnpm test:windows (--maxWorkers=2) so the process-spawning specs stop starving each other on a 2-core runner.
  • 🛠 CI fix 2: the mount lane's npm install. plugin-mount's npm install -g @deepseek-ai/dsh@<ver> failed four times (two ETARGET, two JavaScript heap out of memory / exit 134): the pinned version declares its transitives as floating ^ ranges, so while upstream publishes a prerelease in stages (rc.2 appeared package by package between 14:43 and 14:57 UTC on 09-10) npm resolved a mixed rc.1/rc.2 peer graph (3062 ERESOLVE lines). The lane now pins an already-complete rc.2 (fixes ETARGET) and passes NODE_OPTIONS=--max-old-space-size=4096 (fixes the OOM). --legacy-peer-deps was tried on the way and rejected by real CI: it skips exactly the peers a global install must supply, and @deepseek-ai/cordis-plugin-group — required at boot by dsh-app-boot — is a peer rather than a dependency, so the CLI died with ERR_MODULE_NOT_FOUND.

v0.19.0

📌 Stable release (npm latest, no prerelease suffix): supports DSH 0.1.5-rc.1+ only (peer floor ^0.1.5-rc.1, CI pins @deepseek-ai/dsh@0.1.5-rc.1). Hosts on DSH 0.1.5-alpha.2 should stay on v0.19.0-alpha.1 (the npm alpha tag still points there); the 0.1.2-rc.1 stable line keeps using v0.18.1.

✨ New

  • 📝 The new-tab list gets an optional description back: DSH 0.1.5-rc.1 restored SidebarRightGuideEntry.description (optional), so the plugin restores TabDescriptor.description — each of the six built-in types declares a line again (all six guide rows — Files / Changes / Tasks / Side chat / Terminal / Browser — carry one), and the guideDesc* keys return to all 20 dictionaries. The host's native guide renders descriptions only while the listed entries are ≤ 4 (a longer list drops every description rather than truncating), and the plugin contributes six guide entries by default — so in the default composition no description is rendered; they appear once the reader disables enough tab types (per-type switches live on the plugin's settings page) to bring the guide down to four entries or fewer. An entry that declares none stays a single icon + title line (the plugin ships no generic fallback sentence). (#613)

🐛 Fixes

  • None. The rc.1 delta over alpha.2 is small (373 changed files, overwhelmingly upstream package version bumps, native-sidebar preview UI polish, and test snapshots); apart from the restored description field (plus upstream's files type now using its own coloured folder glyph as the guide glyph) nothing needed adapting.

🧰 CI & internals

  • Baseline moved to DSH 0.1.5-rc.1+ (#613): peer floor, devDependency pins, the CI mount lane, and dsh.plugin.json's engines.dsh all follow (rc.1 is both the latest and next dist-tag on npm).
  • Explicitly unchanged, no need to re-check: the global main-panel model (the main slot / sidebar.panellist / ctx.layout / root-scoped rightbar + rightbar.session, still not adopted), the file address grammar (packages/util/workspace-path only had its version bumped), the native tab-body host contract (.paneBody is still a block scroller with a definite height), the core / agent / session / subagent host APIs, and the ui-primitives export surface (only CodeBlock rendering changed).
  • @deepseek-ai/dsh-client-ui-primitives@0.1.5-rc.1 still declares no dependencies while its bundle still bare-imports anser / shiki / @shikijs/langs/* / mdast-util-* / micromark-* / katex — the third-party devDependencies hoisted in the previous release therefore stay, and must not be reverted.
  • Real-host verification (DSH 0.1.5-rc.1 + plugin 0.19.0): typecheck / lint / check:consumer-types green; unit suite 124 files · 1296 passed · 9 skipped; pnpm peers check clean; the mount smoke lane against real rc.1 7 passed (including the tab-body fill assertion and the new "descriptions render only at ≤4 guide entries" assertion); on the local 3080 instance the guide still shows six icon+title capsules (6 > 4, so upstream renders no descriptions — upstream also raised the capsule min-height from 48px to 56px), clicking AGENTS.md in the file tree lands in the plugin editor (CodeMirror mounted), the side-chat composer sits on the pane floor (host box 962px == pane body 962px, composer 8px off the bottom), the bottom workbench's edges coincide with the centre column, and pageerror is 0 (the only console noise is the third-party dsh-tauri-worktree /api/dsh-worktree/attach 500, unrelated to this change).

v0.19.0-alpha.1

🧪 alpha channel (npm dist-tag alpha, install dsh-better-sidebar@latest): supports DSH 0.1.5-alpha.2+ only (peer floor ^0.1.5-alpha.2, CI pins @deepseek-ai/dsh@0.1.5-alpha.2). Stay on v0.19.0-alpha.0 for 0.1.5-alpha.1; the 0.1.2-rc.1 stable line keeps using v0.18.1 (npm latest).

✨ New

  • 🪟 Every tab body now fills its pane (#609): the native sidebar's tab-body host is a block scroller with a definite height, not a flex container, and the plugin's tab roots declared only flex: 1 — so they collapsed to content height and the side-chat composer sat right after the transcript instead of at the pane bottom (a long transcript pushed it out of view). The native adapter now wraps every tab body in a height: 100% column flex host, restoring the same fill semantics the bottom workbench has for all plugin tabs, third-party registerTab descriptors included.

🐛 Fixes

  • 🧭 File address grammar follows DSH 0.1.5-alpha.2: fileAddressFor always produces a session-scoped address and absolute paths keep their leading /; parseFileAddress is prefix-based and ignores ?/# suffixes.
  • 🪟 Center-column anchor follows alpha.2's global panels: the conversation slot became main.conversation under the root-scoped keyed main slot, so the locator now resolves the new key and skips display: contents slot hosts (alpha.1's old key still works).

🧰 CI & internals

  • Baseline moved to DSH 0.1.5-alpha.2 (#609): peer floor, 22 devDependency pins, the CI mount lane, and dsh.plugin.json engines all follow; pnpm peers check is clean (hoisted the dsh-session-persistence transitive peer per §3-9).
  • TabDescriptor.description removed: alpha.2's native guide entries no longer render a second line (they are icon + title capsules), so the field and the six guideDesc* keys (20 dictionaries) are gone; a new pane's default page is now selected from the registry (exactly one guide entry opens that page directly).

v0.19.0-alpha.0

🧪 alpha channel (npm dist-tag alpha, install dsh-better-sidebar@latest): supports DSH 0.1.5-alpha.1+ only (peer floor ^0.1.5-alpha.1, CI pins @deepseek-ai/dsh@0.1.5-alpha.1). The 0.1.2-rc.1 stable line keeps using v0.18.1 (npm latest).

✨ New

  • 🖥️ DSH's native right sidebar (#604): the right column is now DSH's own sidebar — all seven plugin tab types register as native tab types with native tab bodies; every file open from the chat goes through ctx.sidebarRight.openResource(dsh-resource://file/…); the editor type claims file resources at extension priority (outranking the built-in text preview) and takes over the built-in Files page kind (restored on unregister); cross-session opens queue until the target session is on screen.
  • 🧩 Own right panel and free windows retired (#605): with the right column handed back to DSH, the plugin keeps only its bottom workbench (single split tree, per-session persistence) and registers its toggle into DSH's session-header utilities slot; the float API (floats / floatTab / dockFloat / raiseFloat / the "Move to Free Window" menu entry), the 'floatWindows' feature string, and the openByDefault / defaultWidthPercent / changesDiffFloat settings are gone (a persisted floats field is ignored, so old documents still load).
  • 🔗 DSH 0.1.5 host contracts (#603): the assistant/chunk event is gone — live deltas now fold from agent/assistant-stream frames (the side chat transcript's live field); sessionPersistence.inspect is gone — cold reads go through open(id,'read'); session headers use SESSION_FORMAT_VERSION.

🐛 Fixes

  • Eight session-event read sites follow the 0.1.5 contracts (side-chat transcript, jobs.output replay, fork inheritance); custom seeds now carry the fork marker pair, so they no longer inherit the parent's unclaimed inbox input.

🧰 CI & internals

  • The real-host mount smoke lane pins 0.1.5-alpha.1 and sweeps the native sidebar's guide page to open every plugin tab type; typecheck / lint / unit tests / mount lane all green.

v0.18.1

📌 Stable release (npm latest): the DSH baseline is unchanged (0.1.2-rc.1+, peer floor ^0.1.2-rc.1) — this is the incremental cut after v0.18.0: richer op previews in the changes panel, a writable file tree, and five fixes.

✨ Features

  • 📄 Op-preview upgrades in the changes panel (#499): markdown reading mode with mermaid fences, plus inline .html and .pdf render previews; diff syntax highlighting extended to mjs/cjs/mts/cts, CSS/SCSS/Less, HTML/XML/SVG/Vue, GraphQL, JSONC/JSON5; new secret redaction layer (on by default for previews, toggleable in the pane header)
  • 🗂️ File-tree rename / delete (#550): inline rename plus confirmed delete, slimmer context menus and viewport-clamped submenus
  • 🧩 Plugin catalog names and shell preset text localized (#535): they now follow the host language

🐛 Fixes

  • 🔀 Git diff gap folds really expand (#576, fixes #577): the fold row promised "n lines… click to expand" but did nothing (-U3 leaves gap segments without row text); the hidden rows now load on demand through git.show, sliced from both sides' contents, with loading / failure states and request deduplication — plus a fix to that route's rev:path addressing (it always returned empty before)
  • 💬 Side-chat seeds no longer inherit the parent's unclaimed inbox messages (#562): the seed carries the fork markers, so the "side chat sends earlier User messages first on long contexts" ghost message is gone
  • 🖼️ Local images in the split markdown renderer (#569): rewritten to reachable URLs instead of 404ing

🧰 CI and internals

  • ESLint flat config wired into CI and the Makefile (#536), Makefile command surface (#526), hardened e2e scripts (#527), shared component-test utilities (#524)
  • Refactors: Sidebar.tsx split by concern (#542), four polling idioms converged onto use-polling (#541), rc.7 __DSH_MODULES__ fallback removed (#540), One Dark/Light syntax palettes single-sourced (#534), duplicated implementations converged and dead code removed (#525)

v0.18.0

📌 Stable release (npm latest): this release targets DSH 0.1.2-rc.1+ only (peer floor ^0.1.2-rc.1); 0.1.0-rc.8 ~ 0.1.1-rc.2 are not supported — stable-DSH users should stay pinned to dsh-better-sidebar@0.17.1, hosts on 0.1.2-alpha.x keep dsh-better-sidebar@latest (v0.18.0-alpha.0).

All changes since v0.17.1 (the two intermediate version numbers v0.18.1-alpha.0 / v0.19.0-alpha.0 were never published; their content is folded into this release):

🔗 Host track graduation

  • Onto the DSH 0.1.2 line and graduating to stable: v0.18.0-alpha.0 dropped the 0.1.1-rc.x compatibility layer and fixed blank Side Chat transcripts on alpha.1+ (#472); the baseline then climbed through alpha.3 (#497) and alpha.5 (#516, Session.events → snapshotEvents()); the chat file-open funnel moved to remote.session.openWorkspacePath (#494) and the "Show in folder" reveal scroll was scoped to the tree body (#453). This release pins the baseline to DSH 0.1.2-rc.1 (zero source delta vs alpha.5 — a pure version bump; dsh-client-locale resumed publishing, everything aligned to rc.1; real-host mount smoke 14/14 in CI)

✨ Features

  • 🌿 Unified "Changes" tab (#475): one tab, two lenses — Git (real diff / history / stage·commit·revert / worktree & child-repo selection) and This Session (live tracking of every file the model reads / writes / edits); a unified diff renderer (mod pairing + intra-line character highlights + syntax coloring + context folding), a draggable bottom preview pane, and one-click expansion into a dedicated diff tab
  • 💬 Side Chat rendering upgrade (#486): main-conversation-grade Blocks structure, per-turn usage tails, reconnect banner
  • ⚙️ Workspace path fence toggle (#458): a new workspaceFence declarative settings key with a one-click off affordance and guidance on 403 error surfaces

⚡ Performance

  • 🚀 Core bundle -45% (#489): 19 non-zh/en dictionary chunks lazy-loaded, render stabilization (transcript row reuse / tree Sets / batched drags), startup & polling cost cuts (single settings fetch, one git process per tick); a new perf measurement lane; also fixes the bottom-pane drag leaking panel width into the host layout and jumping the native left sidebar
  • 📉 Fewer repeated center-column DOM queries (#456)

🐛 Fixes (selection)

  • ✏️ Editor / Markdown: SSH remote editor links open on the client (#522), reading position kept across preview/edit switches (#467), YAML frontmatter hidden in preview (#394), TOC dismiss-on-outside-click + popover z-index (#461), file basename kept in @-reference links (#417)
  • 💻 Terminal / platform: Windows custom shell executables resolve (#503), transient resize failures contained (#428), monospace fallback for unresolvable fonts (#366), Linux absolute paths in WSL workspaces (#455), Windows Explorer reveal selection preserved (#508)
  • 🗂️ Layout / state / file tree / chat: desktop shell layout coexists with the side card (#398), no forced mobile drawer on auto-activation (#373), restored free-window id conflicts (#385), tree auto-refresh on window focus (#469), reference affordance pinned to row tail (#509), selection-popup dismissal + caret-anchored draft insert (#427), collapsed toggle cluster aligned (#361), old-engine scroll-jump compat (#448), worktree listing on older Git (#454)

🧰 CI & internals

  • Windows CI lane (#520), Makefile command surface (#526), e2e script hardening + aggregate double-mount regression (#527), shared component test utils deduplicating boilerplate (#524), duplicate-implementation convergence + dead code removal (#525)

🌐 Ecosystem

  • 10+ new curated plugins: dsh-better-sidebar-icons (#441), dsh-sidenote (#451, formerly dsh-sidechat #470), dsh-github-workbench (#410), dsh-bilingual-reader (#379), dsh-server-deck (#413), dsh-md-export (#405), dsh-code-nav (#404), dsh-suhuang-scroll (#392), dsh-better-overleaf (#370), and more (each with 18+ language i18n coverage)

v0.19.0-alpha.0 (0.1.2-alpha.5 adaptation, unpublished, folded into v0.18.0)

🧪 Alpha track: this release targeted DSH 0.1.2-alpha.x only (peer floor ^0.1.2-alpha.5, npm dist-tag alpha). This number was never published separately — its content shipped in the v0.18.0 stable release, and the same number was later reused for the 0.1.5 adaptation line (see above).

  • 🔗 Adapted to DSH 0.1.2-alpha.5 (published to npm, alpha dist-tag): the CI mount gate's pin, the dsh.plugin.json engines floor, and the @deepseek-ai/* peer / devDependencies baseline moved up to 0.1.2-alpha.5 (verified by a real-host mount smoke 14/14). dsh-client-locale has no alpha.5 upstream (newest is 0.1.2-alpha.3), so its peer floor / devDep pin trail the baseline and already accept the alpha.5 runtime (pnpm peers check reports zero mismatches — no new transitive peers to hoist). The code adapts to alpha.4's compatibility-flagged change — the Session.events property was removed, migrated to the on-demand snapshotEvents() API (8 sites: sidechat transcript live reads, fork inheritance, jobs.output replay, subagent activity), and the seedLength meta field the host dropped was removed from thread creation; alpha.4's remaining changes (bidirectional send_message, custom-model discovery reusing Profile headers, SessionSeq/SessionLogOffset strong typing) and alpha.5's upgrade-startup fix were verified not to touch any other plugin surface.

v0.18.1-alpha.0

🧪 Alpha track: this release targets DSH 0.1.2-alpha.x only (peer floor ^0.1.2-alpha.3, npm dist-tag alpha, install dsh-better-sidebar@latest). This version number was never published separately; its content is folded into the v0.18.0 stable release.

  • 🔗 Adapted to DSH 0.1.2-alpha.3 (published to npm, alpha dist-tag): the CI mount gate's pin, the dsh.plugin.json engines floor, and the @deepseek-ai/* peer / devDependencies baseline moved up to 0.1.2-alpha.3 (verified by a real-host mount smoke 14/14). All 117 commits between alpha.2 and alpha.3 were audited point by point: every host contract this plugin relies on (token auth, slash RPC, MarkdownText labels, the event-stream and persistence APIs behind sidechat.events, SettingsNamespaceInput, SUBAGENT_DESCRIPTOR_VERSION (still 3), dsh-client-store, the profile loader, the node-pty pin) is unchanged, so no code adaptation was needed; alpha.3's breaking changes (the required BeginSubmissionInput.mode, the renamed attachment-invalid subagent error, the removed SQLite persistence backend, the identity-gated projection change feed) were all verified not to touch this plugin.

v0.18.0-alpha.0

🧪 Alpha track: this release targets DSH 0.1.2-alpha.x only (peer floor ^0.1.2-alpha.2, npm dist-tag alpha, install dsh-better-sidebar@latest); 0.1.0-rc.8 ~ 0.1.1-rc.2 are no longer supported — stable-DSH users should stay on v0.17.1 (npm latest).

  • 🔗 Adapted to DSH 0.1.2-alpha.2 (published to npm, alpha dist-tag): the CI mount gate's pin and the @deepseek-ai/* devDependencies baseline moved up to it (verified by a real-host mount smoke 14/14). Adaptation points: dsh-settings dropped the runtime settingsNamespace export (namespaces are now validated at compile time — the host passes the constant directly); the dsh-subagent descriptor version went 2→3 (stamped by the host package; the test assertion follows the SUBAGENT_DESCRIPTOR_VERSION constant); the restored SessionEvent.ignorable and the Remote gateway's unified RemoteError wrapping were verified to not affect this plugin.
  • 🐛 Fixed blank Side Chat transcripts on DSH 0.1.2-alpha.1+: transcript polling still called the ctx.connection.api face removed in alpha.1 (the error was silently swallowed, so the tab rendered an empty transcript forever). Transcripts now come from the plugin's own sidechat.events route (live threads read the in-memory event log, cold threads read session persistence, with afterSeq delta pulls, sidechat-routes.ts).
  • 🧹 Dropped the pre-alpha (0.1.1-rc.x and older) compatibility layer: the e2e host RPC collapsed from the dot/slash dual dialect to slash-only (token URL required, host-protocol.ts); MarkdownText labels collapsed to the nested single shape (markdown-labels.tsx, no more dual prop names); peerDependencies / devDependencies / dsh.client.inject / the chunk externals allowlist all dropped the defunct @deepseek-ai/dsh-client-runtime (four synchronized spots).

v0.17.1

  • 🔗 DSH 0.1.2-alpha.1 adaptation (dual-version compatible): fully adapted to DSH 0.1.2-alpha.1's Remote gateway, one-time-token browser authentication, and the MarkdownText labels contract change — the plugin works identically on 0.1.0-rc.8 ~ 0.1.1-rc.2 and 0.1.2-alpha.1 (the latter verified by a real-host mount smoke 14/14 against a source build of the GitHub tag; alpha.1 was never published to npm, and the CI pin moved to the npm-published 0.1.2-alpha.2 in v0.18.0-alpha.0). Highlights: all four MarkdownText render sites now go through the dual-shape labels helper (markdown-labels.tsx), fixing the reading 'code' crash in markdown/mermaid previews on alpha.1; the e2e mount smoke speaks both wire dialects (token-URL cookie exchange, slash /api endpoints with parameter-named args, tests/e2e/host-protocol.ts); dropped the @deepseek-ai/dsh-client-runtime peer removed upstream in 0.1.2-alpha.1

v0.16.1

All changes since v0.16.0:

🐛 Fixes

  • 🧊 Git panel freeze + restart loop (#376, fixes #369): opening the Source Control panel could freeze the whole page, and the frozen layout restored itself after every reload with no way out — three unbounded layers compounding, now all bounded: ① status truncation — the git status --untracked-files=all response is capped at 2,000 entries (capped results carry truncated and the panel shows a notice, mirroring fs.read's truncation semantics; worktree change counts inherit the bound), so a huge untracked set can no longer freeze the browser main thread; ② bounded repository discovery — a non-repository cwd (e.g. the home directory) no longer probes every visible child directory serially without limit: probe timeout 30s→5s, at most 200 probed directories, concurrent requests share one in-flight scan plus a 60s TTL cache, ending the git rev-parse spawn storm under ~; ③ reset escape hatch — loading with ?dsh-sidebar-reset drops the persisted layout (shared width included) and starts from the default, breaking the loop even when the page is already hung; persisting resumes once the param is gone; statusTruncated copy synced across all 19 dictionaries

v0.16.0

All changes since v0.15.2:

✨ New features

  • 🪟 Free windows (#354): drag any tab (built-in or plugin-registered) out of the tab bar onto the main conversation area — a dashed drop hint marks the landing zone and release turns it into a floating window (default 390×780, phone-portrait ratio, clamped to the viewport and centered at the drop point). Windows can be moved by dragging the header, resized from the SE corner (≥320×200), raised to the top by clicking anywhere, sent back with the header context menu ("Back to sidebar" / "Close"), and closed with X through the normal closeTab lifecycle (pty release etc.). Dragging over a sidebar pane highlights it and releasing docks the window back into that pane. floats persist per session (restored as-is after refresh, lenient sanitize + geometry clamping). Service semantics: features gains 'floatWindows' — openTab's dedupe/id focus on a floating tab raises its window (no duplicate open, no panel expansion), closeTab / activateTab work on floating tabs and still fire their lifecycle callbacks, visible is always true inside a float, and agent-terminal reconcile covers floating windows. Tab content reuses the regular render path; plugin tabs are fully supported. Also ships the 8px-grid spacing cleanup of file sub-pages (design)
  • 📂 Model-driven sidebar opens (sidebar_open tool) (#353): a new global setting agentOpenTools (off by default) injects one tool letting the model open local files (editor tab, deduped by path), folders (full-window tree rooted there, meta.dir) and HTTP(S) pages (browser tab, URL prefilled) in the caller's sidebar. Turning the setting off unregisters the tool and clears the undelivered queue; already-open tabs stay. Opens targeting non-active sessions queue and replay when visible (/sidebar/ws/agent-opens push, same trust fence). No new public API and no new /sidebar/api routes (design)
  • 📝 README-level inline HTML + table of contents (TOC) in Markdown previews (#360): previews now genuinely render block-level inline HTML — badge walls <div align=center>, <details> blocks nesting markdown, <br/>/<sub>/<img> inside table cells, <video>/<picture> — all sanitized through a DOMPurify allowlist (<script> and other active content stripped, <a> forced _blank rel=noopener), with local media src rewritten to the session media route. With ≥3 headings a floating TOC button appears: smooth scroll, auto-expanding folded <details> ancestors, collecting headings from HTML segments too. The renderer is still the host MarkdownText (shiki / KaTeX / GFM preserved); pure-markdown documents take the original path with zero regression (design)
  • 🌏 Third-language coverage (19 languages) (#339): optional peer @huanlin/dsh-plugin-better-locale adds full dictionaries for ja / de / fr / pt / ko / ar / hi / id / tr / vi / th / ru / it / nl / sv / pl / zh-HK / zh-TW / zh-MO (~340 keys each). Coverage borrows the DSH English slot: it is active only while DSH's active locale is en and is completely inert under zh (no mixed-language UI). The 19 dictionaries are also registered into better-locale so external ctx.locale.lookup('betterSidebar', key) callers get the override text. Without better-locale installed ctx.get('betterLocale') is undefined and the whole block is a no-op — zh/en behavior is unchanged
  • 🌿 Multi-repository Git selection + linked-worktree discovery (#326 #285): when the session cwd is a workspace container rather than a Git repository, the Git panel discovers direct child repositories and shows a repository selector — status / branch / history / diff / stage / commit / revert / cherry-pick / file opening are all threaded through the selected repository. Linked-worktree change discovery and per-worktree git operations are transactionally bound to the selected checkout (including delayed pagination responses), stale/prunable worktree targets are rejected, and individual inventory failures degrade safely
  • 🖥️ Browser loopback allowlist (#365): a new side-card setting browserAllowedLoopback (comma-separated host or host:port entries; bare hosts match every port, host:port matches exactly) lets the sidebar browser navigate to explicitly trusted local addresses. Allowlisted loopback pages additionally get the allow-same-origin iframe sandbox token — local dev servers (Vite etc.) need a real origin for their module/HMR/fetch pipeline and would otherwise render blank. The page stays cross-origin to the GUI and to every other site; the server-side browser.probe route mirrors the same allowlist
  • 📝 Vue + 28 legacy languages in the editor (#202): .vue maps to @codemirror/lang-vue (template / script / style dispatch on the lang attribute, <style lang="scss"> preprocessors); zero new dependencies bring scss/sass/less/stylus/ruby/lua/perl/r/dart/scala/groovy/powershell/diff/protobuf/cmake/pug/tcl/haskell/clojure/erlang/julia/pascal/vb/vhdl/stex/objectivecpp via legacy-modes. A throwing language factory degrades to plain text (console.warn) instead of breaking the editor; ambiguous .v / .m extensions stay unmapped on purpose
  • 🔄 Editor preview refresh trio (#215 #228, fixes #167): a manual refresh button for text previews; switching back to preview after an edit-save auto-reloads (suppressed while dirty so drafts survive); a successful save while in preview mode reloads on the saved edge. Automatic polling and the fs.stat version endpoint are gone (zero background API traffic)
  • 🖼️ Local / relative images in Markdown (#292): ![alt](./img.png), /cwd/img.png and reference-style [id]: url destinations are rewritten to /sidebar/file media URLs (still bounded by the session cwd) — previews no longer show just the alt text
  • ➕ New recommended-catalog entry: ego-browser (#340): @dsh-external/ego-browser Agent browser tab (registers a sidebar page automatically when better-sidebar is present, falls back to a floating bubble otherwise); description dictionary completed across 19 languages (#371)

🐛 Fixes

  • 🛒 DSH marketplace managed-install compatibility (#338): the public cordis entry was removed from peerDependencies (the market preview hard-rejects cordis in any dependency field — optional does not help), so the npm package satisfies the dsh-community-market install rules — catalog sources (dshfind / 1024Store) can re-issue the repository_backlink verified target and the plugin becomes installable through the Desktop market
  • 🔤 Type base migrated to @deepseek-ai/cordis (#338): the declaration surface (src/context-types.ts) no longer depends on or restates the public cordis — Context is now an intersection of the real vendored cordis Context with the structural service faces, and the ctx.betterSidebar type merge lives on @deepseek-ai/cordis. Consumer migration: change import type { Context } from 'cordis' to import type { Context } from '@deepseek-ai/cordis' (the import type {} from 'dsh-better-sidebar' merge path is unchanged); plugins that never used that import are unaffected
  • 🧩 All internal ctx.betterSidebar reads fixed (#357, fixes #356): on npm-installed DSH 0.1.1-rc.x (web bundle) the sidebar died on every page load (cannot get property "betterSidebar" without inject) — the 26 internal direct reads now go through ctx.get('betterSidebar') (root reflect-store resolution, immune to the fiber chain); external consumers keep inject: ['betterSidebar'] + ctx.betterSidebar
  • 🔐 Session-workspace boundary for file APIs (#345, fixes #328): fixed fs.tree / fs.read / fs.write workspace escapes; media, HTML preview and uploads share real-path symlink validation; regression tests for absolute paths, symlinks, uploads and nested Git sessions
  • 🪟 Panel-host layering and viewport clipping (#330 #278, fixes #277): panel host z-index 40→25 — below the DSH cordis dynamic-plugin layer (30), so the workbench no longer covers cordis inventory/approval surfaces (still above AppFrame's 20 and below the 100+ float stack); overflow: hidden on the host clips at the viewport edge, so collapsed panels no longer stretch the document into bidirectional scrolling (measured scrollWidth 2289→1672 / scrollHeight 1280→1032, any skin)
  • 📐 Layout-push hardening (#310 #130 #180): conversation column gets min-height: 0 + overflow: hidden + overflow-wrap: anywhere (long unbreakable URLs / OAuth links no longer push the composer and left-rail Settings out of the viewport); the layout-push effect is split into "set only" + "remove only on unmount" and the width push is gated on panelOpen — dragging the bottom height with the right panel closed no longer squeezes the conversation, and release no longer flashes full-width; useLayoutEffect removes cross-paint full-width frames; all three drags flush the final frame and sync centerRect.right before committing; bottom height caps at viewportHeight - PANEL_MIN; drag handles no longer highlight mid-drag
  • 📱 Mobile: unavailable-sidebar clarity + 1px overflow (#254): with no session the toggle uses aria-disabled — keeping the non-executable semantics while allowing touch/keyboard focus so the "select a session to use the sidebar" tooltip is reachable; the panel uses border-box so mobile 100vw includes the left border and no longer causes 1px horizontal overflow
  • 📏 Side-card width shared across sessions (#36): panel width is a layout preference, not session content — "last drag wins" writes the global dsh-sidebar:v1:width key that fresh and cached session switches adopt; without a global key (first run / pre-existing sessions) behavior is byte-for-byte unchanged
  • 🧹 Terminals close immediately on session delete (#130): new PtyManager.closeSession() subscribing to DSH's session/disposed — deleted sessions no longer wait for the 30s reconnect grace to release their terminals (agent terminals are agent-lifecycle-owned and untouched)
  • 🔍 Filename search skips noise directories (#342): node_modules / .pnpm-store / .yarn / .turbo / .next / dist / build / coverage etc. (case-insensitive, .git still skipped) — huge dependency trees no longer burn the 100k visit budget and return truncated early, so real files in later directories (docs/) are found; no .gitignore semantics, still filename lookup
  • 📝 Mermaid global error rendering suppressed (#341): suppressErrorRendering enabled — invalid diagrams no longer inject a large error SVG into document.body; the component-level error fallback and source display remain
  • 🖥️ Terminal Nerd Font icon fallback (#190): supplementary-plane PUA icons in starship / powerlevel10k prompts (Nerd Fonts v3 Material icon set) no longer render as tofu — withIconFontFallbacks() appends Nerd Font icon families to the winning base font (before the first generic family, deduped by family name, CSS global keywords filtered, no color-emoji fonts)
  • 🌐 HTML preview declares UTF-8 (#193, fixes #170): /sidebar/html responses carry charset=utf-8 (Chinese fragments without <meta charset> no longer mojibake); original file bytes preserved
  • 🧪 trust-fence Origin compared by hostname (#182): some Chromium builds (Edge 151) serialize the Origin of non-default-port loopback pages without the port — http://127.0.0.1 against Host: 127.0.0.1:3080 no longer 403s (mirrors DSH's official gateway fence); different hostnames and opaque null origins are still rejected
  • 🪟 "Show in folder" now reveals in the explorer (#94): the folder-reveal gesture no longer opens the directory as a file in the editor ("..." is a directory) — revealInExplorer switches to the explorer tab, auto-expands a collapsed panel, expands the parent folder and highlights/scrolls to the produced file; the produced-files selector now reads the engine's Turn deliverables (same source as ui-deliverables)
  • 🖱️ Panel-drag layout flash fixed (#180): dragging the bottom height with the right panel closed no longer shifts the conversation left; release no longer flashes full-width before snapping back
  • 🖥️ PowerShell installer fixes (#47): the remote entry is now "download the script → strip the UTF-8 BOM → run it in memory", so -Version / -DryRun work again under Windows PowerShell 5.1 (the BOM no longer eats the leading param(...)); pnpm --version is checked before touching the profile (major <10 fails with a clear error and exit code 1)
  • 🔄 Browser embed probing GET fallback (#69): when a HEAD response omits both Content-Security-Policy and X-Frame-Options, probe retries once with GET — sites like Alibaba Bailian that only advertise their embed policy on GET no longer show a misleading "refused to connect"; they get the friendly "site refuses embedding" panel with an "open in browser" button
  • 🔧 git-source installs fixed via unrun devDependency (#336): tsdown 0.22 loads its config through unrun and pnpm 11 does not auto-install peer deps — git-hosted prepare no longer fails with Failed to import module "unrun" (npm tarballs unaffected)
  • 🍃 Strict ctx.effect also fixed 4 latent sites: interception / IME-guard effect bodies returned undefined on failure instead of a disposer (invalid shape under the vendored cordis effect contract) — now a no-op disposer
Older releases (v0.12.0 – v0.15.2)

v0.15.2

All changes since v0.15.1:

✨ New features

  • 🗂️ "Open in app" submenu in the file tree (#334): the file-tree context menu gains an "Open in app >" submenu — built-in openers (reveal/select in file manager, VS Code, Cursor, Zed), each with a pin button that promotes it to a top-level context-menu item (click again to unpin); with an optional SSH host configured, VSCode-family entries switch to the vscode-remote/ssh-remote+<host>/<path> protocol and local-only entries hide automatically; custom editors supported (name + URL template with {path} + "VSCode-family" flag, configured in the Files card gear popup). Open actions go through the new host route POST /sidebar/api/open.external (argv-array spawn, no shell injection) (design)
  • 📑 Tab context menu (#331): right-clicking a tab offers Close / Close others / Close to the left / Close to the right, scoped to the current pane (tab group); items grey out when there is nothing to close; the menu only opens — it never switches the active tab; bulk closes go through the existing per-tab onClose path so lifecycle callbacks, pty release and agent-terminal shutdown all stay intact
  • 📄 Diff files collapsed by default (#270): each changed-file header is now an accessible expand/collapse control; recognized source files expand by default while tests, docs, generated files, lockfiles and unknown types stay collapsed; the existing 500-row cap for expanded content is preserved
  • 📖 README update: feature tour converted to a table (two screenshots per row, saves space); community section now shows the WeChat group and QQ group QR codes (#325, QQ group 577011007)

🐛 Fixes

  • 🪟 Prune empty restored panes (#268): persisted split panes left empty when ephemeral diff tabs were dropped no longer survive as full-size blanks — sanitizeState now also prunes the empty split leaf and repairs the stale active-pane pointer; a fully empty workbench keeps its single empty pane
  • 🖥️ Hide spawned git windows on Windows (#301, fixes #124): the shared runGit() spawn options now set windowsHide: true, so repo-status polling and git actions no longer flash console windows on Windows (no behavior change on other platforms)
  • 📁 Untracked files inside new folders (#242): git status switched from --untracked-files=normal to --untracked-files=all — each file inside a new folder now shows as its own row and its diff can be loaded (no more fs.read "is a directory" error), matching VSCode's default behavior
  • ⚡ Per-frame React re-renders eliminated for toggles/drags (closes #315): centerRect moved to a ref with direct DOM writes to the bottom bar (zero React renders); TabContent memoized with an explicit comparator; a new frame-batcher coalesces Divider/dock drags per frame; meaningless locate passes skipped during drags. 4x CPU-throttled A/B: >17ms frames on toggle collapse 19→6 / expand 24→4~6, p95 21ms→15ms; drags unchanged (non-regression)

v0.15.1

All changes since v0.15.0:

✨ New features

  • 💬 Codex-style transcript rework for Side Chat (#314): transcript rows became collapsible — tool calls, thinking and context injections share one quiet single-line chrome (chevron + label + one-line argument summary) that expands into an indented body on a hairline thread, no cards or fills; streaming labels and a creating hero shimmer (shimmer = generating), failed tools go danger, prefers-reduced-motion stills every loop; the first question is no longer swallowed by the boundary prompt — context injection and first contact are delivered as separate events (boundary + parked snapshot ride agent.inject, the question wakes the driver), so the transcript maps injections onto a collapsible injection row while genuine user messages — the first one included — render as user bubbles; legacy threads' first message is split out into its own bubble too
  • 📖 README rewrite: feature tour (real UI screenshots per feature), user-facing DSH compatibility badges, simplified install (add → approve-builds → add, node-pty-safe build, paste-to-DSH install prompt), 28+ plugin ecosystem with per-category collapsed listings

🐛 Fixes

  • 🖥️ Terminal pty stays alive across conversation switches (#323): switching sessions is no longer treated as a transient drop — the client sends a park control frame on unmount and the host skips the 30s reconnect-grace countdown; switching back (open() cancels parked) or explicitly closing the tab resumes the normal lifecycle; agent terminals keep their indefinite lifetime
  • 📂 File-tree upload overlay no longer intercepts Tab drags (#317): dragging tabs (reorder / cross-pane split) across the explorer no longer shows the upload overlay or swallows the event — gated on dataTransfer.types containing Files (consistent with the panel-host shield), so tabs land normally; OS file drags behave as before
  • 💬 Subagent auto-open debounced (#314): Side Chat thread creation no longer pops the task page — the 0→N trigger rearms for 500ms and re-evaluates the original baseline against the live snapshot, by which time the title filter recognizes the thread; genuine subagents still auto-activate the Tasks page (wide viewports expand the sidebar, while narrow viewports prepare the tab without forcing the drawer open)

v0.15.0

All changes since v0.14.0:

✨ New features

  • 💬 Side Chat (beta) tab (#286): Codex-style side threads, one independent tab per conversation — the child inherits the parent's full context (completed turns + pending messages + the in-progress turn's assistant output and tool activity, honestly frozen with an "interrupted" marker); created with an identical composition (same preset / provider / model) so the first request reuses the parent's input prefix cache; threads stay invisible in the main session list with zero subagent-catalog noise; follow-ups survive DSH restarts (auto cold-resume); one-click "Save as new session" promotes the thread to a top-level session (design)
  • 📤 Upload into the files window (#239): header "upload file / upload folder" buttons plus drag-drop (drop on the tree body = workspace root, on a directory row = that directory, on a file row = its parent directory, VSCode semantics); full-window blurred progress overlay while uploading (per-file progress + cancel / Esc); buttons disabled while busy, tree refreshes after the upload settles
  • 🧩 Desktop compatibility in four options (#284): "Position compatibility mode" is now a main-row dropdown — Auto-detect (default, conservative: only the standard Window Controls Overlay geometry contributes; real 32/36px caption-overlay heights per shell, live on maximize/restore; zero modification on plain web) / DSH official web (explicitly no adaptation) / Shell preset (built-in, opt-in; only shells that appeared in this repo's issues/PRs with 100+ stars, "detected" badge when the environment matches) / Custom (free-form CSS + shift distance). Documents that already carried compatibility values migrate to the custom scheme; interactive chrome opts out of desktop drag regions (no-drag); the bottom-push anchor is a composite selector ([data-pane] and :has(> [data-slot]))
  • 🎛️ Settings page UI/UX modernization (#300): the side-card secondary-settings entry is now a full-width "Feature settings" strip at the card bottom (replacing the invisible corner gear — much easier to discover); coordinated two-tone enabled state (brand activation accent + success-green check badge); every color is still --dsw-alias-* token-derived so skins follow automatically
  • ➕ New entries in the recommended-plugin catalog: dsh-docs-panel (global docs, #230), dsh-flowglass (#261), dsh-git-forge and dsh-ssh-tunnel (#204), dsh-turn-review (#102)

🐛 Fixes

  • ⚡ Batched live preview for the subagent page (#298): the old implementation polled subagents.history per running subagent, each poll triggering a full host-side enumeration — an O(N²) amplification that stalled the page with many concurrent subagents; now a single batch route subagents.live (one enumeration of the whole tree) plus one client poller with a single in-flight request; display logic and copy unchanged
  • 🖱️ Interrupted / fast-release drags no longer roll back (#249, closes #247 #248): interrupted or fast-released drags commit the last known position; HMR re-activation re-locates the center column (fixes the blank bottom panel after a hot reload)
  • 📐 Push variables stay effective while mounted (#259, fixes #258): the bottom panel no longer flashes full-width after a drag is released
  • 🔐 Workspace boundary hardening for file APIs (#328): fs.tree/read/write, media, HTML preview, and uploads now share real-path containment checks that reject outside absolute paths and symlink escapes
  • 🔧 Adapted to DSH 0.1.1-rc.1 / rc.2 (@next) (#297 #305): no code changes needed
  • 🔒 Upload-chain hardening (#239): empty and absolute relativePath segments are refused outright; uniquely named temp files (concurrent uploads stay independent, crashed processes never block later uploads); write-stream error listeners (a failing disk can no longer crash the host); client error codes unified with the wire (too-large), 413s localized

v0.14.0

⚠️ This release requires DSH ≥ 0.1.0-rc.8. All changes since v0.13.1:

✨ New features

  • 🖼️ Unified panel-host injection refactor (#232): panels/toggle clusters moved into a [data-dsh-panel-host] fixed containing block (fixed inset-0 z-40), immune to desktop-shell intermediate transforms hijacking fixed; mount self-check (page-level transform → data-dsh-panel-host-degraded degraded sync, judged on uncorrected geometry, exits only when the ancestor transform is gone); push anchor switched to #root [data-dsh-frame] > [data-pane="conversation"] + #root calc width against desktop-shell additive overflow; chunk revalidation on activation (HEAD+ETag keeps unchanged chunks, 5s timeout fails open); visualViewport keyboard inset + env(safe-area-inset-*) mobile adaptation
  • 📂 Separate file windows by default (#232): editorExplorer now defaults to separate — tree clicks / file opens create a new tab per path and the path-less window is a pure file manager; merged mode stays available as an opt-in
  • 🖥️ Terminal shell / shellArgs configurable from the settings page (#232): the terminal card's gear popup gains "Shell path" and "Shell arguments" rows (previously yaml-only via cordis.patch.yml) — saved values take effect immediately for terminals opened afterwards (UI terminals and model terminal_create alike); empty keeps the existing yaml → $SHELL / login shell / powershell.exe resolution order
  • 🏷️ Version badge on the settings page (#232): the side-card settings section now opens with a DSH-better-sidebar v0.14.0 identity badge (version synced with the service instance, test-guarded)
  • 🔍 Add-plugin catalog: search / grouping / independent scroll (#232): built for a growing plugin ecosystem — a live search box (filters by name / id / description), optional category grouping for entries, and an independently scrolling list (the modal no longer grows unbounded with catalog size)

🐛 Fixes

  • 🧩 rc.8 module-system migration (#232): rc.8 no longer exposes the window.__DSH_MODULES__ page global (it moved to the ctx.modules service), which broke every lazy chunk's externals resolution — the client now injects the modules service and shares it with chunk-bundle copies through a plugin-owned global (terminal / editor / Mermaid on-demand loading restored)
  • 🧩 Chunk revalidation barrier hardening (#232): HEAD revalidation gains a 5s timeout (fails open on a stuck route so the barrier can never wedge lazy loads); resetChunks clears a pending revalidation barrier
  • 🖱️ Drag robustness (#232): fast releases (browsers merge/lose pointermove bursts) commit the last known dragged position instead of rolling back; pointercancel / lost-capture interruptions keep the drag result too; the center column is re-measured right after commit (no mid-frame bottom-panel width jump); HMR re-activation re-locates the center column via an <html> style observer plus a retry when the bottom panel opens (fixes the blank bottom panel / shifted input bar after a hot reload)

v0.13.1

✨ New features

  • 📊 Safe Mermaid rendering in the Markdown preview (#164): when a previewed md file contains mermaid fences, a client-mermaid.js chunk (~7MB) is served on demand (zero load without mermaid); defense-in-depth rendering — securityLevel: 'strict' + htmlLabels: false (node labels use real SVG <text>) + a second sanitize pass before SVG injection (foreignObject/script/foreign HTML elements removed, @*/on*/href attributes stripped); click a diagram to zoom in a modal overlay (wheel zoom centered on the cursor, drag pan, toolbar & shortcuts), re-renders with light/dark theme, falls back to the raw code block on parse failure
  • 🖥️ Configurable terminal shell & shellArgs (#125): cordis.patch.yml better-sidebar.config can set shell / shellArgs (a non-empty shellArgs fully replaces the defaults; unset keeps the previous auto-resolution of $SHELL / login shell / powershell.exe), applied to both UI terminals and agent terminals (terminal_create); terminal tab titles now show the shell name (bash/zsh/powershell) and internal tab ids use UUIDs so the same shell can open multiple terminals

🐛 Fixes

  • 🔗 Aggregate double-mount auto-yield (#200): when an aggregate package (e.g. dsh-web-ui-all) mounts the same package under its own entry id, the guard expression in cordis.patch.yml disables the plugin's own better-sidebar row so /sidebar/api is no longer registered twice (duplicate prefix route crashing the whole plugin tree / dsh web); standalone installs behave as before
  • 🔧 Adapted to DSH 0.1.0-rc.7 (#207, fixes #206): fixes the agent-presets: refusing to compose an unscoped context error when picking a model / sending a message after DSH moved to rc.7

v0.13.0

✨ New features

  • 📁 Files window merged with the explorer (#151): new editorExplorer setting (editor card gear) — file tabs gain a path-input header plus a toggleable right-docked file tree (per-tab open/width memory, drag-resize 160–480px from the left edge, global filename search via the host fs.search route with a hard budget, skipping .git and symlink dirs); in separate mode (default) tree clicks / Enter in the path input open each file in its own new tab, merged mode switches the current tab in place; fresh sessions seed an empty Files window instead of the explorer tab, and a path-less window is a bare file manager in separate mode / a chrome'd empty file window in merged mode; the tree context menu offers "Open in new tab" and "Open to the side" (split)
  • 🎛️ Select rows for declarative settings (#151): settings rows gain type: 'select' (options with value/title/desc/icon, multi stores the picked values as an array); options with icons render big-icon option cards and keep the icon in the closed anchor; editorExplorer became an iconed select (merged vs separate); the capability list gained settingSelect
  • 🔀 Mutual exclusion with the dsh-web-ui family right panel (#181): reads the aionui-panel settings namespace's provider choice — when "Use aionui-panel" is selected, the whole better-sidebar (right sidebar / bottom panel / floating entry / all takeovers) does not mount; with DSH-better-sidebar (or no aionui installed) it behaves as before. Takes effect live after a settings save (settings-document push), no reload needed

v0.12.3

✨ New features

  • 🎨 Skin compatibility (token-driven): fully consumes DSH design tokens and follows the dsh-web-ui skin center's 10 skins automatically; terminal/editor surfaces fall back to opaque backgrounds under transparent/translucent-glass token values so text never scrolls over the skin art (#110, fixes #106 #105 #90 #60, also #52 #57 #92)
  • 🗂️ Unified path handling: UNC / symlink classification (directory symlinks expandable, broken links highlighted) + HTML-route platform guards (#134, #65 #67 #43 #79 #115)
  • 🖥️ Configurable terminal shell: custom shell setting with Windows pwsh auto-probe (#95)
  • 📝 Editor languages: C# / Kotlin / Swift syntax highlighting (#120)
  • 🧭 Settings nav icon: settings-page navigation icon and layout polish (#114)
  • ➕ Recommended-plugin catalog: added dsh-git-remotes — Git Remotes tab (branches/upstream/ahead-behind, fetch with prune, ff-only pull, confirm-before-push; does not replace the built-in stage/commit tab) (#91); and dsh-video-preview — inline video preview (.mp4/.webm/.mov/.mkv/.avi etc.) backed by a /video host route with HTTP Range (206) scrubbing, not capped by the 20MB mediaLimit (#126)

🐛 Fixes

  • 🔧 xterm migration: deprecated xterm dependency migrated to @xterm/xterm (Closes #122, #128)
  • 📝 Markdown editor: selection-to-conversation popup restored (#24)
  • 🖼️ Markdown preview renders local/relative images: image destinations that point at local files (relative/absolute paths, reference-style [id]: url) are rewritten to /sidebar/file media URLs and displayed in the preview (previously only absolute http(s) images rendered; relative paths showed just the alt text)
  • 🐛 node-pty load failure no longer crashes the server (#140): the host half now lazy-loads node-pty — when it is missing the plugin still mounts, the terminal shows a repair banner (copyable command + Retry button), and agent terminal tools are skipped
  • 🧪 Test engineering: unit spec split (#141) + flaky smoke cleanup fix

⌨️ Keyboard Shortcuts

Action Keys
Save edits Ctrl/Cmd + S
Git commit Ctrl + Enter
Close tab Middle mouse button
Tab context menu (right-click) Close / Close Other Tabs / Close Tabs to the Left / Close Tabs to the Right (current pane)
Split / merge panes Drag tab to pane edge / middle
Reference file to input Hover the @file button at end of line
Copy file path Right-click row → copy relative/absolute path

🔌 Service API

Since v0.4.0 the plugin exposes the ctx.betterSidebar service — other plugins can register sidebar pages and file viewers (the 5 built-in tabs + 3 viewers register through the same service). v0.12.1 completed the base capabilities (complete type exports, capability detection, state subscription, tab badges, lifecycle callbacks, targeted open, plugin-owned settings, etc.).

Full integration docs (complete fields, matching algorithm, HMR pitfalls, declarative settings, version detection, the native-sidebar surface and the skinning contract): docs/external-plugin-guide.md; repository rules (hard constraints / CI / release) live in AGENTS.md.

The dashed cards at the end of the "Sidebar content" / "File viewers" grids in the "Side Cards" settings section open the Add tab plugins / Add preview plugins modals: each declares its open extension point, offers a "Browse more plugins on GitHub" button (the GitHub topic dsh-better-sidebar), and lists the recommended catalog (name / repo / description / install script) — "Open" jumps to the repo, "Copy" writes the install command to the clipboard.

Curating a new plugin: append a PluginEntry to src/client/plugins-tabs.ts (tab registrations) or src/client/plugins-viewers.ts (file-previewer registrations) and tag your repo with the dsh-better-sidebar topic; data integrity is guarded by tests/plugin-list.spec.ts.

🛠️ Development & Build

pnpm install      # @deepseek-ai/* devDependencies resolve (baseline 0.1.7-rc.1, alpha dist-tag) — no token needed
pnpm typecheck    # tsc --noEmit
pnpm lint         # eslint . (flat config: js + typescript-eslint + react-hooks recommended)
pnpm build        # → lib/index.js + lib/invariant.js + lib/client.js + lib/client-registry.js + lib/types
pnpm test         # vitest (includes manifest consistency guard; build first)
pnpm watch        # tsdown --watch

Make thin wrappers (make help lists every target; package.json stays the single source of truth):

make check          # aggregate gate: typecheck → build → test → check:consumer-types (mirrors CI)
make mount          # real-mount smoke: build + pack → install Chromium → pnpm test:mount
make clean          # remove lib/, *.tgz, playwright-report/, test-results/

pnpm check:consumer-types: the consumer-facing declaration-surface guard — type-checks the built lib/types from a browser-only consumer's perspective (no @types/node, skipLibCheck: false); run pnpm build first.

Architecture: a single npm package with host/client halves — host (src/index.ts): /sidebar/api/* JSON API, /sidebar/file media route, /sidebar/html preview route, /sidebar/upload upload route, and two WebSockets (/sidebar/ws/agent-opens for model-driven opens, /sidebar/ws/fs-watch for the file tree's directory watch; fs / git / preview are all session-scoped behind a trust fence); client (src/client/index.tsx): portal sidebar + views + link takeover; state persisted per session in localStorage. Organized per DSH official conventions (no default export, dual client bundles); no dependency on npm / checkout at runtime (@deepseek-ai/* provided by the web profile).

🔐 Security

  • Routes protected by a Host-header trust fence (same as /api); fs.write is atomic; media/preview routes only serve files inside the session cwd (unless workspaceFence is turned off in settings); git only shells out to the CLI and never sets identity
  • HTML preview content renders in an opaque-origin sandboxed iframe (no allow-same-origin/allow-top-navigation, no-referrer, all permission policies disabled); the /sidebar/html route carries a CSP sandbox + size/path bounds
  • The settings page can disable the HTML preview's sandbox per feature (htmlViewerNoSandbox / htmlViewerDefaultUnsafe, off by default, with a warning) — when off, content shares the origin with the UI; only recommended for fully trusted content. The web tab's sandbox is no longer this plugin's surface: the browser view comes from the host (desktop profile); see DSH's own docs for its sandbox and navigation policy

⚠️ Known Limitations

  • Git has no push/pull/fetch; Markdown previews provide a manual refresh button with confirmation before discarding unsaved edits; the file tree only watches expanded directories (collapsed folders are unsubscribed, and there is no recursive whole-workspace scan); tool inline file-open buttons cannot be intercepted
  • Which read-only previews exist is the host's call: spreadsheets / PDF / images / Office go to DSH's own ui-sidebar-documentpreview, while the plugin renders only Markdown / HTML and the editable text buffer; the host implementation (rendering details, zoom, refresh timing) follows the DSH version
  • The browser view exists only in the desktop profile: the Web profile has no host browser kind and the plugin no longer ships a browser tab, so web tabs are desktop-only; login state / third-party cookies / X-Frame-Options limits follow the host implementation
  • HTML preview renders the saved file (not unsaved drafts)
  • No bottom panel on mobile (<768px): on narrow screens its tabs merge into the right sidebar once (after migrating back to desktop they stay in the right sidebar); the desktop bottom panel is only available on wide viewports. Without a selected session, tapping the subdued toggle shows the select-session message; with a selected session, it opens the full-width drawer

🖥️ Platform Support

Windows / Linux / macOS (macOS validated daily; the rest covered by unit tests). The plugin carries no native dependencies (the terminal and node-pty went back to DSH wholesale), so building needs only Node + pnpm, with no compiler toolchain.

💬 Community

WeChat / QQ group QR codes will live here. After uploading the QR images (drag them into any issue/comment to get a user-attachments link), replace src below and uncomment:

🤝 Contributing

  • Code changes go through PRs: develop on a feat/* / fix/* branch, then gh pr create; docs-only changes may be pushed to main directly
  • Curate an ecosystem plugin: tag your repo with dsh-better-sidebar + PR a PluginEntry into src/client/plugins-tabs.ts / plugins-viewers.ts
  • Before submitting: pnpm typecheck && pnpm build && pnpm test (or make check for the one-shot aggregate; CI additionally gates on npm-pack → real-mount → headless-render via pnpm test:mount, plus the aggregate double-mount regression pnpm test:mount:aggregate)
  • See AGENTS.md for the repository rules (hard constraints, CI lanes, release flow)

⭐ Star History

Star History Chart

👥 Contributors

Thanks to everyone who contributed:

Contributors

🔗 Friends

  • dsh-tianshu-tui: an interactive terminal UI plugin for DeepSeek Harness (its rendering core evolved from the self-developed harness agent Tianshu-Tui), adding TDD and evidence-gate workflows on top of the official harness
  • dsh-TUI: a Claude Code-style fullscreen interactive TUI plugin — pixel-whale top bar, live working-status row, streaming thought expansion, double-Esc rollback, context progress bar + TPS meter; one-command npm install
  • dshfind Plugin Market: a third-party plugin marketplace — a listing of public repos under the GitHub topic dsh-plugin, with stars, contributors and growth data synced daily
  • DeepSeek Harness Desktop: a modern desktop client for the DeepSeek Harness ecosystem — start and manage a local Harness service without configuring Node.js or running commands; official site

MIT License · Built for the DeepSeek Harness ecosystem · discover more on the dsh-better-sidebar topic
S
Description
WorkBuddy/Tutti-style right sidebar enhancement for DeepSeek Harness: Tasks / Agents / Profile tabs, configurable display name (default apes--sidebar, preset feyOS). Built on dsh-better-sidebar.
Readme MIT
6.6 MiB
Languages
TypeScript 93.5%
CSS 4%
Shell 1.6%
JavaScript 0.5%
PowerShell 0.4%