dsh-panes
A full-screen workspace panel for DeepSeek Harness: a project sidebar on the left and, per project, a set of tabs — session, files, search, terminal, browser, source control and notes.

一个 DSH 全屏工作台面板:左侧是项目列表,右侧是该项目的 tab 集合(会话、文件、搜索、终端、浏览器、源代码管理、笔记)。切换项目就切换整套 tab。
| Panel | main + sidebar.panellist row — one panel, opened from the DSH sidebar |
| Tabs | session · file · explorer · search · terminal · browser · git · notes |
| Host | one same-origin POST /dsh-panes/api route, plugin-header fenced |
| Client | one bundled file (shadcn/ui + Tailwind, preflight off), React external |
| License | MIT |
Tabs
| Tab | What it does |
|---|---|
| Session | The conversation for this project, embedded in the tab strip. One tab per session. |
| File | Read-only code view with syntax highlighting (shiki), line numbers, breadcrumb, word wrap, ⌘G jump-to-line, paged loading for large files, UTF-8 detection. |
| Explorer | Directory tree: open, copy path, reveal in the system file manager. |
| Search | Project-wide content search: regex, case sensitivity, globs, split result list + preview, hit counts, truncation flag. Uses the machine's rg, then the rg bundled with @vscode/ripgrep, then a bounded JS walk — and tells you which engine ran. |
| Terminal | A real pty in the tab, via the host's webTerminals service, with fit-on-resize and live theme. |
| Browser | Embed an http(s) page next to the code, with back/forward/reload. |
| Source control | The Git tool window: changes, staged/unstaged, commit box, commit history graph, diffs, branch actions (16 rail buttons, each switchable), AI-generated commit messages. |
| Notes | A Notion-style Markdown vault: list + BlockNote editor, one .md per note, frontmatter preserved by the host, .trash on delete, etag-based conflict detection. |

Install
# from npm
dsh plugin --profile web add dsh-panes
Then add "dsh-panes" to dsh.profile.bundles in ~/.dsh/profiles/web/package.json
and restart the client once — a new bundle is composed into the boot graph only on
restart. After that, rebuilds are picked up by a page reload.
The plugin shows up as the Workbench / 工作台 row in the DSH sidebar.
From a checkout instead (development):
pnpm install && pnpm build # lib/ is the shipped artifact
dsh plugin --profile desktop add "$(pwd)" # pnpm link
# add "dsh-panes" to dsh.profile.bundles, restart
Uninstall: remove it from dsh.profile.bundles, then
dsh plugin --profile web remove dsh-panes.
Using it
- The sidebar lists every workspace, with its open-tab count (or a
runningdot when it has live sessions).+adds a project; the filter box narrows the list; the collapse button shrinks the sidebar to a 52px rail (automatic below 820px of panel width). +at the top right opens the NEW TAB menu: session, open file, explorer, search, terminal, browser, git, notes.- Tabs are scoped to the project: switching project switches the whole tab set. Each project's layout is persisted.
- The status bar shows branch + changed-file count (
±n, click opens Git), live session count, cursor line/column, encoding, language and light/dark.
Keyboard (the panel only — a focused editor or terminal keeps its own keys):
| Keys | Action |
|---|---|
⌘P / Ctrl+P |
Quick open a file by name |
⌘⇧F / Ctrl+Shift+F |
Project search |
⌘⇧N |
New session tab |
⌘W |
Close the current tab |
⌘⇧T |
Reopen the last closed tab |
⌘B |
Collapse / expand the project sidebar |
Tab / ⇧Tab |
Cycle tabs |
Esc |
Close the panel |
Per view: ⌘G jump to line (file), ⌘⇧V preview/source (Markdown), ⌘S save (note).
Settings
Plugins → dsh-panes. Every field is live — no restart.

| Field | Meaning |
|---|---|
startup |
workspace (default) · last (only if the workbench was the last panel) · conversation |
fontUiFamily / fontUiSize |
UI font stack / size (px); empty = follow dsh |
fontCodeFamily / fontCodeSize / fontCodeLineHeight |
code font stack, size, line height |
notesVaultPath |
absolute notes directory; empty = $DSH_HOME/notes (an Obsidian vault works) |
experimentalRemoteOps |
shows and allows push / pull (default off) |
commitModel / commitReasoning / commitPrompt |
AI commit message model, reasoning strength, extra prompt |
rail* |
one switch per Git action-rail button (16 of them) |

How it is built
The package is a Cordis plugin with two halves, mounted by a bundle patch:
| Path | Role |
|---|---|
src/index.ts |
Host half: the Cordis plugin, POST /dsh-panes/api route, Config schema. |
src/client.tsx |
Browser half: registers the main panel and its sidebar.panellist row. |
src/panel/ |
Panel shell, file/explorer/search/Markdown/terminal/browser views, shared types. |
src/tabs.ts |
Pure tab model: per-project tab sets, idempotent open, persisted layout. |
src/notes.ts, src/notes/ |
Notes vault (host ops) and the list + BlockNote editor. |
src/git/ |
Forked Git tool window (host.js, panel.js, adapter); see src/git/UPSTREAM.md for provenance. |
src/ui/ |
shadcn primitives (button, input, dropdown-menu) + cn(). |
src/styles/tailwind.css |
Design tokens (host theme vars) + Tailwind layers. |
cordis.patch.yml |
Bundle patch: one insert row naming this package. |
Two registrations are needed for one panel: main holds the content and
sidebar.panellist (same id as the panel key) holds the sidebar row, its label
and its icon. Registering main alone shows nothing.
Host calls go through the single same-origin route with { op, args }; the
client must send x-dsh-plugin: dsh-panes, and the host answers only loopback or
same-origin callers.
Tailwind runs with preflight off and every component lives under
.workbench-root / .workbench-portal, so the panel cannot restyle the host.
Only react, react-dom and @deepseek-ai/* stay external; everything else is
bundled into lib/client.js (compiled to CJS and wrapped into the client-modules
factory form by scripts/wrap-client.mjs).
Development
pnpm install
pnpm build # build:styles (Tailwind -> src/generated/styles.ts) + tsdown + wrap
pnpm typecheck # tsc --noEmit
pnpm test # node --test: host route, tab model, search, notes, git guards
pnpm check # build + typecheck + test
Rebuild and reload the page to pick up client changes; a bundle added to a profile needs a client restart.
Known limits
- Read-only for code: no editor or write-back, no LSP. Notes are the only writable surface.
Add projectcannot register a workspace: the client half exposesworkspaces.modelbut noadd. The row and the+entry report that refusal instead of pretending.- Search and notes act on client-supplied absolute paths, fenced by the
plugin header + same-origin check rather than by "must be inside a registered
workspace" (the host exposes no workspace list to the host half). Treat the
route as trusted-page-only, the way
/apiis. - Git push / pull stay hidden by default: the buttons are absent until
experimentalRemoteOpsis on, and the host refuses those methods while it is off. No credential, proxy or conflict handling is claimed for them. - The Git panel is a fork of
dsh-ide-gitv0.21.1 (MIT), pinned here and no longer synced with upstream; it still carries its owndig-styling. - The Git status bar calls
summarywithstats: false— it shows±nchanged files, not added/removed line counts. - The browser tab can only frame sites that allow embedding.
- Notes: a symlink inside the vault is refused (
lstat), so a note can never be a link to a file outside the vault — read or write. - The browser bundle is a single ~9.4 MB file (≈2.1 MB gzip), most of it BlockNote. The editor is loaded lazily so a failure in it costs one tab, not the panel's boot — it does not save download time.
Credits
The Git tool window is forked from dsh-ide-git v0.21.1 (MIT); its license and
the exact list of local hunks live in src/git/LICENSE and src/git/UPSTREAM.md.
The design language follows DSH's own panel tokens.
MIT © flyfly6
No comments yet. Be the first to write one.