dsh-minimal-UI-panels
All-in-one DeepSeek Harness UI panels — one bundle, one loader row: artifacts, long-term memory, terminal and notes paired two per tab into right-Sidebar tab types, each with a draggable split inside.
dsh-minimal-UI-panels merges three formerly separate DSH plugins into a single package mounted as one loader row. The panels are paired into right-Sidebar tab types (ctx.sidebarRightTabs) on @deepseek-ai/dsh-client-ui-sidebar-right — Artifacts + Terminal and Memory + Notes — and each tab stacks its two panels with a draggable divider, so two Sidebar panes side by side show all four panels at once. It also ships the host-side tools/routes — plug and play.
DSH 0.1.5 changed the architecture: the third-party-occupiable
detailscolumn is gone, replaced byrightbarand owned by the shipped right Sidebar (docking kit + tab-type registry). 0.2.0 completed that migration; 0.3.0 then paired the panels, because one tab per panel could only ever show two of them.
✨ Features
| Panel / capability | Description |
|---|---|
| Paired right-Sidebar tab types | One tab type = one pair of panels, stacked vertically with a draggable divider between them (the split is remembered per pairing; double-click the divider for 50/50). Docking, floating and splitting come from the shipped docking kit, so two panes side by side show all four panels. The way in is the strip's add control → the guide page |
| Artifacts panel | Scans workspace artifact files, groups/sorts by type/date/size/line count; syntax-highlighted code/config/data previews; inline base64 image previews (4 MiB cap); mp4/m4v/webm/ogv video streaming (Range requests) |
| Long-term memory panel | View/add/search/edit memory across three scopes (user/global/workspace); tag grouping; content highlighting; pairs with memory_* tools and the /memory command |
| Terminal panel | A dark-themed bash command runner (bash -lc), output collected and returned; common-command hints stay pinned below |
| Notes panel | Apple-Notes-style multi-note scratchpad: sidebar list + editor, create/delete, autosave (600 ms debounce), auto-title from the first line, stored at ~/.dsh/notes.json |
📸 Screenshots
The Artifacts + Terminal pairing open in the right Sidebar: the artifacts panel on top, the terminal below, and the draggable divider between them. The chip in the strip is the pairing's tab type.

Drag a tab to the left or right edge of a pane and it splits off a second pane — two panes side by side, all four panels on screen at once:

📦 Install
This plugin mounts as a local link dependency (consistent with other DSH local plugins).
1. Clone the repo
Pick a directory and clone it:
git clone https://github.com/windrover/dsh-minimal-UI-panels.git
# → <your-path>/dsh-minimal-UI-panels
2. Register it in your DSH profile
Edit ~/.dsh/profiles/web/package.json. Set link: to your clone path (<your-path> is wherever you ran the clone):
{
"dependencies": {
"dsh-minimal-ui-panels": "link:<your-path>/dsh-minimal-UI-panels"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-minimal-ui-panels"
]
}
}
}
Replace
<your-path>with the absolute path to the cloned folder, e.g.link:/Users/me/dev/dsh-minimal-UI-panels. The directory may keep its capitalised name (dsh-minimal-UI-panels) — the directory name does not matter; the identity that counts ispackage.json'sname.
3. Establish the link
cd ~/.dsh/profiles/web && pnpm install
4. Restart dsh web
Ctrl+C to quit → run dsh web again → refresh the browser. The loader scans profile bundles at startup, so the new code takes effect.
If you previously mounted the old plugins, remove them from
dsh.profile.bundles(this package supersedes them); also clean any stale rows in~/.dsh/cordis.patch.yml(e.g.- id: artifacts-panel) to avoidpatch: entry ... not foundwarnings.
🖥 Usage
- Open a session → the right Sidebar (the expand button in the conversation header) → the strip's add control → the guide page → pick Artifacts + Terminal or Memory + Notes.
- All four at once: drag a tab to the left or right edge of a pane to split off a second pane, then open one pairing in each; adjust the split inside a pairing with the divider between its halves (double-click it for 50/50). Widen the Sidebar when the columns feel tight.
- Terminal: type a command and press Enter (
bash -lc); output shows in the lower half; common-command hints stay pinned. - Notes: click New to create an entry; drag the sidebar divider to resize; clicking a title auto-hides the list to focus the editor; the toolbar toggle shows/hides the list manually.
/memorycommand (host side):/memory list|search|get|forget|export.- Host-side model tools:
memory_*(write/recall/list/forget/export/import/correct/batch/diagnose) andartifacts_list.
🏗 Architecture
dsh-minimal-UI-panels (one loader row / one package)
│
├── lib/index.js ── Host half 【thin composition entry】
│ └── import + invoke in order:
│ ├── lib/host/ltm.js ← original dsh-long-term-memory (verbatim)
│ │ └── store.js / threats.js / llm.js / automation.js
│ ├── lib/host/artifacts.js ← original dsh-artifacts-panel (verbatim)
│ └── lib/host/terminal-notes.js ← original dsh-terminal-notes (verbatim)
│ inject = tools, systemPrompt, commands, settings, agents,
│ webServer, workspaceRegistry, subprocess, fs
│
├── src/ ── Browser-half SOURCE 【edit panel UI here; version-controlled】
│ ├── composite/client.js ← the pairing container: owns EVERY right-Sidebar registration
│ ├── artifacts/client.js ← artifacts panel (contributes a component; registers nothing)
│ ├── long-term-memory/client.js ← memory panel (same, plus the Settings card)
│ └── terminal-notes/client.js ← terminal + notes panels (same)
│
└── lib/client.js ── Browser half 【single merged bundle = scripts/merge-client.mjs; do not edit】
├── function artifacts(react, react_jsx_runtime, panels) ← from src/artifacts/client.js
├── function ltm(react, react_jsx_runtime, panels) ← from src/long-term-memory/client.js
├── function terminalNotes(react, react_jsx_runtime, panels) ← from src/terminal-notes/client.js
├── function composite(react, react_jsx_runtime, panels) ← from src/composite/client.js
│ the three panel fragments only WRITE { Component, ns, titleKey, t } into `panels`;
│ composite READS it to render
└── function apply(ctx) ← main entry: const panels = {} → apply in order (composite LAST)
exports.inject = ["slots", "locale", "sidebarRightTabs"]
Right-Sidebar tab types (ONE PER PAIRING; each then registers a body under its own
id in the sidebar.right.pane.tab seat):
dsh-minimal-ui-panels/artifacts-terminal kind "artifacts-terminal" top: artifacts bottom: terminal
dsh-minimal-ui-panels/memory-notes kind "memory-notes" top: memory bottom: notes
Host tools/routes: memory_*(9) + artifacts_list, /api/artifacts/*, /api/terminal-notes/*
Merge pipeline (see scripts/merge-client.mjs):
src/artifacts/client.js ─┐
src/long-term-memory/client.js ├─ extract factory body → rewrite react/react_jsx_runtime
src/terminal-notes/client.js │ bindings, strip inner exports. statements
src/composite/client.js ─┘ (merged LAST: it reads the `panels` registry the others fill)
│
▼
lib/client.js (single __ModuleLoader__.load bundle)
📌 The sources live in this repo, under
src/<fragment>/client.js. They used to be read from sibling checkouts of the three original plugin repos — all of which are now archived on GitHub (read-only,git push→ 403), leaving the source with no version control behind it. An edit made directly in the generatedlib/client.jswas destroyed by the next merge and could not be recovered from git. Vendoring them here is what makes the browser half reproducible from a clone.
⚠️ Key contract: three names must match byte for byte (here the lowercase
dsh-minimal-ui-panels)
insert.nameincordis.patch.yml- the
__ModuleLoader__.load({ id })inlib/client.jspackage.json'snameWhy:
dsh-client-modules'nearestPackage()walks up from the module path the Loader row resolved and only accepts thepackage.jsonwhosenamestrictly equals that specifier;arrive()then assertsfactories.has(row.id). A case mismatch anywhere in that chain makes the entire browser half silently misswindow.__DSH_BOOT__while the host half (tools/routes) keeps working — exactly the state that reads as "the plugin is running".That was the last hidden bug in 0.1.x:
package.jsonhad already gone lowercase, butcordis.patch.ymland the client bundle still saidUI. The directory's own casing is irrelevant;package.json'snameis the source of truth.
Why the multi-panel container is gone — and why the panels are paired
The old four plugins had to be merged because they all fought over the one details slot. After DSH 0.1.5 moved to rightbar, each panel registers its own tab kind and body key, so they no longer contend — the container role belongs to the shipped right Sidebar. This package therefore dropped dsh-details-tabs (~1100 lines) along with its self-drawn layout persistence and DockRail.
But one tab per panel has a practical ceiling: the Sidebar splits into at most two panes, so at most two panels can be on screen, and reaching the others costs a switch. Since 0.3.0 the panels are paired — a tab stacks two panels with a draggable divider between them. Two panes side by side then show all four panels, and dragging a divider down to 15% gives the single-panel view back. The pairings live in the PAIRS array in src/composite/client.js; regrouping is a change to that one array.
🌐 Host routes
Unchanged from the originals:
| Method | Path | Description |
|---|---|---|
| POST | /api/artifacts/scan |
Scan a directory |
| GET | /api/artifacts/read |
Read file preview |
| GET | /api/artifacts/media |
Video stream (Range support) |
| POST | /api/terminal-notes/exec |
Run a bash command |
| GET/POST | /api/terminal-notes/notes |
List / create notes |
| GET/POST | /api/terminal-notes/note |
Read / save one note |
| POST | /api/terminal-notes/note-delete |
Delete a note |
🛠 Development & verification
Always run the precheck after changing code (DSH plugin workflow):
bash ~/.dsh/dsh-plugin-precheck.sh web --testsIt runs: plugin-tree load (
--dump-config) +node --checkper plugin + client bundle mock-load contract assertions + this repo's unit tests. Restart only after it passes; if you're locked out, use~/.dsh/dsh-safe-start.shfor a safe boot.This repo's contract test
test/client-contract.test.mjsgoes further than the precheck: it actually runsapply(ctx)against a stub context and pins the two composite tab types (id/kind/priority/guide entry), the twosidebar.right.pane.tabbodies, and then renders each body to check that both halves draw their own title, that both panels are seated withembedded: trueand with their own translator, and that the draggable divider is there:node test/client-contract.test.mjstest/artifacts-scroll.test.mjsrenders the artifacts panel for real with a minimal hook runtime and drives one preview round trip through its own state: the list offset must come back, and it must come back in a layout effect (before paint, so the return does not flash the top and then jump). It also checks that a directory change drops the remembered offset. Rendering the panel is what catches a temporal-dead-zone reference in an effect's dependency array — somethingnode --checkcannot see:node test/artifacts-scroll.test.mjstest/terminal-exec.test.mjscovers the host half: it applies the plugin against a mock context, captures the route registered at/api/terminal-notes/execand invokes it, asserting that the spawn asks forstdin: 'ignore'(fd 0 on /dev/null), carries an abort signal, and that a command killed at the deadline comes back as a classifiedtimeoutwith the output it managed to print:node test/terminal-exec.test.mjsThe browser half is a generated artifact; its source is
src/. Editsrc/<fragment>/client.js, then regenerate:node scripts/merge-client.mjs # write lib/client.js node scripts/merge-client.mjs --check # verify only; exits 1 when stale (fine for CI/precheck)The script extracts each factory body from
src/{composite,artifacts,long-term-memory,terminal-notes}/client.js, rewrites the react /react_jsx_runtimebindings, strips innerexports.statements, and passes one sharedpanelsregistry into every fragment as its third argument (the panel fragments write,compositereads).compositemust be merged last, or the registry is empty. A change made directly inlib/client.jsis lost on the next run — the script prints awould change +N/-Mwarning before overwriting, and--checknever writes at all.Data locations: long-term memory
~/.dsh/dsh-memory/{global,user}.jsonl, workspace.dsh/memory.jsonl; notes~/.dsh/notes.json.
🔒 Notes
- Notes are stored as a single JSON document (the fs service offers no unlink primitive; a single atomic JSON rewrite is more reliable).
- The terminal is not an interactive shell: it runs
bash -lc <the one line you typed>and the child's stdin is/dev/null, so commands that read stdin (bash,cat,read,python, an editor) get EOF and exit instead of hanging the panel. While a command runs, a Stop button appears (host routePOST /api/terminal-notes/exec-cancel, addressing the child by therunIdthe panel generated for that run). Every command also has a 120-second deadline; past it the command is killed and the output it already printed is returned with the failure. The deadline is the host configterminalNotes.timeoutMs. A manual stop and the deadline are different outcomes (cancelledvstimeout), and both keep the output printed so far. For anything interactive or long-running, use the session's own bash tool. - The panels no longer draw any column width or layout of their own: docking, floating, splitting and sizing all belong to the shipped
dsh-client-ui-sidebar-rightdocking kit. The only layout this package owns is the split inside a pairing. Upgrading dsh can no longer clobber a layout patch from this package, because there is no longer one to clobber. - This package's services/routes/tools are registered on the calling fiber's lifecycle; stopping or hot-reloading removes every side effect.
No comments yet. Be the first to write one.