# 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](docs/external-plugin-guide.md).
>
> **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](#-installation).**
## 📑 Contents
- [✨ Features](#-features)
- [🚀 Installation](#-installation)
- [🖼️ Feature Tour](#-feature-tour)
- [🌐 Plugin Ecosystem](#-plugin-ecosystem)
- [🆕 Recent Updates](#-recent-updates)
- [⌨️ Keyboard Shortcuts](#-keyboard-shortcuts)
- [🔌 Service API](#-service-api)
- [🛠️ Development & Build](#-development--build)
- [🔐 Security](#-security) · [⚠️ Known Limitations](#-known-limitations) · [🖥️ Platform Support](#-platform-support)
- [💬 Community](#-community) · [🤝 Contributing](#-contributing) · [⭐ Star History](#-star-history) · [🔗 Friends](#-friends)
## ✨ 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 / `` 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](docs/plans/2026-08-12-lazy-chunks-design.md))
- **🌏 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](./docs/external-plugin-guide.md).
## 🚀 Installation
**Prerequisites**: DSH installed (`dsh web` boots), Node.js ≥ 20, pnpm ≥ 10.
**Supported DSH versions**:
> 📌 **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.
```sh
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:
```text
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
```sh
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:
```text
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:"
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](https://github.com/dsh-external/plugin-registry) integrated (`dsh registry` available). **Enabling both channels double-mounts** (the Node half loads twice, the page gets two sidebars).
```sh
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.
| **📝 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 ``, `` blocks nesting markdown, inline tags in table cells — DOMPurify-sanitized, `