The close settled on auxclick delivery, which the browser redirects to the nearest common ancestor of the press/release targets when they differ — any drift, middle-button autoscroll scroll, or tab-list reflow between press and release swallowed the close (the strip hides its scrollbar, so Chrome's autoscroll indicator is inert here and only added confusion). Now the press target is recorded on middle mousedown (preventDefaulted to disarm autoscroll at the source) and the close settles on the first middle mouseup OVER that same tab — release-position semantics matching VS Code (microsoft/vscode#101028) and user expectations for Chrome tabs (crbug/40679924): pressing on a tab and releasing elsewhere cancels. Left-button activate/drag and wheel scrolling are untouched (tests/tab-bar-middle-click.spec.tsx).
dsh-better-sidebar
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.
✨ Features
- 🗂️ File Workbench: file explorer (lazy-loading tree; symlinks show their target kind — directory links expand, dangling links flagged) + CodeMirror editor; inline preview for images / Markdown (incl. Mermaid diagrams, strict-mode safe rendering + click-to-zoom) / HTML / PDF / Office
- 🌐 Embedded Browser: multiple web tabs with back / forward / refresh; content runs in a sandboxed iframe; external links are routed by protocol by default — HTTP opens in the sidebar, HTTPS goes to the system browser (both adjustable in settings)
- 💻 Real Terminal: xterm.js + node-pty real shell, reconnect with transcript replay; optionally injects
terminal_*tools for the model - 🌿 Git Panel: real diff + VSCode-style diff tabs, history, right-click to stage / commit / revert
- 🧩 Background Tasks: agent topology + background tasks (exit codes / live output / force-kill)
- 🪟 Dual Workbench: right sidebar + bottom panel; drag tabs to split / merge panes (cross-panel), mobile auto-merges into a full-width drawer
- 🔁 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 (terminal / editor / mermaid diagrams) load on demand (design)
- 🌏 i18n: UI text follows DSH's language (zh / en) with live switching
🔌 Core principle: service-first — the 7 built-in tabs + 6 viewers register through the same
ctx.betterSidebarAPI as third-party plugins, with fully equal capabilities; anything the ecosystem can provide better is delegated to ecosystem plugins. See the "🔌 Service" section below and the external plugin guide.
🆕 Recent Updates
v0.13.1
✨ New features
- 📊 Safe Mermaid rendering in the Markdown preview (#164): when a previewed md file contains mermaid fences, a
client-mermaid.jschunk (~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*/hrefattributes 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.ymlbetter-sidebar.configcan setshell/shellArgs(a non-emptyshellArgsfully 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.ymldisables the plugin's ownbetter-sidebarrow so/sidebar/apiis no longer registered twice (duplicate prefix routecrashing the whole plugin tree /dsh web); standalone installs behave as before - 🔧 Peer dependencies aligned to DSH 0.1.0-rc.7 (#207, fixes #206): all
@deepseek-ai/*peer/devDependencies raised from^0.1.0-rc.6to^0.1.0-rc.7, and the CI mount lane pins the same version — eliminating the mixed rc.6/rc.7 dependency tree (after the main framework moved to rc.7) that surfaced asagent-presets: refusing to compose an unscoped contextwhen picking a model / sending a message
v0.13.0
✨ New features
- 📁 Files window merged with the explorer (#151): new
editorExplorersetting (default on, 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 hostfs.searchroute with a hard budget, skipping.gitand symlink dirs); in merged mode tree clicks / Enter in the path input switch the current tab in place, separate mode opens by path; fresh sessions seed an empty Files window instead of the explorer tab, and a path-less window is a chrome'd empty file window in merged mode / a bare file manager in separate 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'(optionswith value/title/desc/icon,multistores the picked values as an array); options with icons render big-icon option cards and keep the icon in the closed anchor;editorExplorerbecame an iconed select (merged vs separate); the capability list gainedsettingSelect - 🔀 Mutual exclusion with the dsh-web-ui family right panel (#181): reads the
aionui-panelsettings 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
📝 Other
- Install / update commands unified to
dsh-better-sidebar@latest(bilingual README sync)
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); anddsh-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)
- 🐛 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
🚀 Engineering
- npm publishing wired to GitHub Releases (Trusted Publishing, provenance-attached tarballs); tagging a release publishes automatically (#148)
v0.12.2
- 📐 Position compat mode: new "Position compatibility mode" setting: reserves top space for the native Windows title bar (top-right) so the sidebar buttons and content sit below it (off by default); the shift distance is customizable in the gear popup (0–120px)
- 🔌 Service API base: complete type exports +
version/featurescapability detection, state subscription (getSnapshot/subscribeState), tabbadge,onOpen/onActivate/onCloselifecycle callbacks,updateTab/activateTab/openFile, targeted open,metapersisted across reloads, plugin-owned settings (pluginToggles/render), external-link claim (urlTarget) - ➕ Add Plugins: recommended plugin catalog in settings + one-click copy install command; built-in Office preview moved to the recommended plugin
- 🖱️ Tab-bar scroll: mouse-wheel horizontal scrolling on the tab bar
- 🐛 Fixes: remote access 403 (trust fence now uses
trustedHosts), sidebar crash #31, Windows HTML-preview drive-path
v0.12.1
- 🔌 Service API base: complete type exports +
version/featurescapability detection, state subscription (getSnapshot/subscribeState), tabbadge,onOpen/onActivate/onCloselifecycle callbacks,updateTab/activateTab/openFile, targeted open,metapersisted across reloads, plugin-owned settings (pluginToggles/render) - ➕ Add Plugins: recommended plugin catalog in settings + one-click copy install command; built-in Office preview moved to the recommended plugin
- 🖱️ Tab-bar scroll: mouse-wheel horizontal scrolling on the tab bar
- 🐛 Fixes: remote access 403 (trust fence now uses
trustedHosts), sidebar crash #31, Windows HTML-preview drive-path
📝 Note: the 0.12.0 final could not be reused (npm reported the version as already published), so the public release became 0.12.1 — both carry identical content.
v0.12.0
- 🔌 Service API base: complete type exports +
version/featurescapability detection, state subscription, tab badges, lifecycle callbacks, targeted open,metapersisted across reloads, plugin-owned settings - ➕ Add Plugins: recommended plugin catalog in settings + one-click copy install command; built-in Office preview moved to the recommended plugin
- 🖱️ Tab-bar scroll: mouse-wheel horizontal scrolling on the tab bar
- 🐛 Fixes: remote access 403 (trust fence now uses
trustedHosts), sidebar crash #31, Windows HTML-preview drive-path
🚀 Installation
Prerequisites: DSH installed (dsh web boots), Node.js ≥ 20, pnpm ≥ 10.
dsh plugin --profile web add dsh-better-sidebar@latest
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).
Updating
dsh plugin --profile web add dsh-better-sidebar@latest
or bump the version in ~/.dsh/profiles/web/package.json (e.g. "^0.13.0") 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 build scripts. Run pnpm approve-builds --all in the profile directory (~/.dsh/profiles/web). |
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. |
| Terminal fails on Windows | node-pty relies on prebuilt binaries; if none match your Node version, install a build toolchain (VS Build Tools). Mainstream Node versions are usually covered. |
| Terminal shows "node-pty failed to load" | The node-pty install is missing or broken (e.g. pnpm skipped its build script). The terminal banner shows a repair command — copy it into a terminal/cmd on the DSH machine and run it (in ~/.dsh/profiles/web: pnpm approve-builds --all && pnpm rebuild node-pty), then restart DSH and click Retry. The plugin and DSH core share the same node-pty@^1.1.0, so the repair restores both. |
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 (to pick the terminal shell, add `config.shell`; `config.shellArgs` starts it with explicit args — when non-empty they replace the default `-l`. When omitted the host resolves `$SHELL` / the login shell / powershell.exe):
- insert:
- id: better-sidebar
name: 'dsh-better-sidebar'
config:
shell: /bin/zsh
shellArgs:
- --noprofile
- --no-rc
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 "dsh-better-sidebar": "^0.13.0" 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.
⌨️ Keyboard Shortcuts
| Action | Keys |
|---|---|
| Save edits | Ctrl/Cmd + S |
| Git commit | Ctrl + Enter |
| Close tab | Middle mouse button |
| 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: register tabs & file viewers
Since v0.4.0 the plugin exposes the ctx.betterSidebar service — other plugins can register sidebar pages and file viewers (the 7 built-in tabs + 6 viewers register through the same service):
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} />,
}))
}
v0.12.1+ base capabilities (complete type exports, capability detection, state subscription, tab badge, lifecycle callbacks, targeted open, plugin-owned settings, etc.) — see the integration docs below.
Full integration docs:
AGENTS.md— the in-repo integration doc (full fields, matching algorithm, HMR pitfalls, declarative settings, version detection);docs/external-plugin-guide.md— the external-plugin guide (with a complete minimal example).
➕ Add Plugins (recommended plugin catalog)
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/* resolved from npm (^0.1.0-rc.7, published) — no token needed
pnpm typecheck # tsc --noEmit
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
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/ws/terminal WebSocket (fs / git / pty / preview, all session-scoped with a trust fence); client (src/client/index.tsx): portal sidebar + views + interception; 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.writeis atomic; media/preview routes only serve files inside the session cwd; git only shells out to the CLI and never sets identity - HTML preview and browser tab content render in opaque-origin sandboxed iframes (no
allow-same-origin/allow-top-navigation,no-referrer, all permission policies disabled); the/sidebar/htmlroute carries a CSPsandbox+ size/path bounds; the address bar rejectsjavascript:/data:/file:and local addresses like localhost - The UI shows the sandbox status live (red warning when off) and can temporarily unlock the current page; the settings page can disable the sandbox per feature (disabled by default, with a warning) — when off, content shares the origin with the UI; only recommended for fully trusted content
⚠️ Known Limitations
- Git has no push/pull/fetch; no file watcher (manual refresh); tool inline file-open buttons cannot be intercepted
- Dragging a terminal tab to another pane remounts it (shell restarts)
- Office-suite preview (.docx/.xlsx/.pptx) moved to the recommended office plugin (see the "Add plugins" modals in settings); without it these files fall through to the code/download fallbacks
- Browser sandbox has no login state / third-party cookies are restricted; some sites need popup login; sites that refuse embedding via
X-Frame-Options/frame-ancestors(e.g. arxiv.org) show a reason panel (with "Open in browser"); in-iframe navigation does not enter the back stack - 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; auto-open terminal on first bottom-panel expand does not trigger on mobile
🖥️ Platform Support
Windows / Linux / macOS (macOS validated daily; the rest covered by unit tests); node-pty prefers prebuilt binaries, otherwise a build toolchain is required (Windows VS Build Tools / Linux make+g+++python3 / macOS Xcode CLT).
🔗 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