A right-side tabbed sidebar for DeepSeek Harness (dsh) web: explorer (workspace file tree) and git (changes & commits) tabs, built on an extensible tab registry.
Status: development build in this workspace — NOT installed into any running dsh deployment.
Installing is a documented, deliberate step (see Installing below).
What it is
Two halves of one cordis plugin:
Host half (Node): registers the generic Connection RPC channel /better-sidebar with authority: loopback and serves eight endpoints: explorer/list, git/status, git/log, git/stage, git/unstage, git/commit-detail (full message + changed files of one commit), git/commit (stage + commit), and git/discard (restore/clean). Filesystem access uses node:fs/promises; git runs via a spawn wrapper with fixed arguments, optional stdin for commit messages, timeout and abort support — no shell interpolation.
Client half (browser): registers one entry into the layout's right details column (declared by ui-layout AppFrame; priority -1 shadows ui-conversation's DetailsPanel) that renders the sidebar with a tab bar. Because the dock is a real grid column, the conversation shrinks beside it — it never overlaps the main UI. The dock tab set comes from ctx.betterSidebar.tabs, a registry any plugin can contribute to.
Data flow (one round trip):
Tab panel -> ctx.betterSidebar.rpc.call(endpoint, payload, { signal })
-> ctx.connection.rpc.call(channel, ...) (browser)
-> host dispatch -> ExplorerService / GitService -> fs / git
-> SidebarResult in the RPC value slot (ADR-002)
Architecture
src/contract — dependency-free shared types: endpoint table, payloads, tree/git models, the SidebarResult error envelope, payload guards. Single source of truth for both halves.
src/client — cordis plugin entry providing ctx.betterSidebar ({ rpc, tabs, explorer }), the dock (the frame's right details column: open/close via ctx.layout, native drag resize, AppFrame's column border), the tab registry, the explorer and git tabs, inline-SVG icons, and en/zh locales.
Decisions are recorded in docs/adr/ (architecture, transport & error model, tab registry & dock, explorer & git scope). Design docs live in docs/design/. The dsh API facts everything builds on are in docs/architecture-brief.md.
Model Experience
Explorer and git are pure browser chrome: nothing here reaches a model request. The tabs render host filesystem and git state, fetched over the loopback RPC channel.
Token effect
Zero — no prompts, tool schemas, or model context are assembled or sent.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Installing into a dsh web deployment (later)
Not installed here; when the owner authorizes, the steps are:
Host: load the plugin entry in the host cordis config (e.g. cordis.yml plugins section) — the entry exports name, inject: [connection], Config, and apply(ctx, config). The client-connection plugin (which provides ctx.connection) must load first.
Client: include the ./client entry in the web bundle client plugin set (the dsh web loader module table). The plugin requires connection, slots, locale, and layout services and the details slot (declared by ui-layout AppFrame). The dock is session-scoped like dsh's native details column: it shows while a conversation is current.
Trust fence: the channel is registered with authority: loopback — non-loopback browsers are refused by the connection layer before the handler runs. The host also validates every payload path (absolute, existing, directory, inside allowedRoots when configured).
Config reference
Field
Type
Default
Meaning
allowedRoots
string[]
[]
Absolute roots the plugin may read; empty = any absolute directory the host process can read. Entries must be absolute (validated at load).
gitTimeoutMs
number
15000
Per git command timeout (clamped 100-120000).
maxEntriesPerListing
number
2000
Per-directory listing cap; excess is truncated with a flag.
maxLogEntries
number
100
git log -n cap / page size clamp.
maxStatusEntries
number
20000
git status entry cap.
untrackedFiles
all or normal
all
Porcelain untracked mode; normal collapses all-untracked dirs into dir/ entries.
hidePatterns
string[]
['.git','node_modules']
Basenames filtered from listings (no reveal toggle in v1).
gitExecutable
string
git
Test/override seam.
Extension guide — add a tab
Any client plugin can contribute a tab:
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
import type { TabDef } from 'dsh-better-sidebar-lite/client'
export function apply(ctx: ClientContext): void {
ctx.effect(() => {
const dispose = ctx.betterSidebar.tabs.register({
id: 'my-tab',
order: 30, // sorts after explorer(10), git(20)
label: () => 'My tab', // locale-aware via a function
icon: , // inline SVG
badge: () => undefined, // optional live badge
renderPanel: () => , // renders inside the dock
})
return dispose
}, 'my-plugin: tab')
}
Requirements: a stable unique id (duplicates throw TabRegisterError), registration inside ctx.effect (unload disposes), and panel components that read dsh session/workspace data through useDock() (the context the dock provides around every panel) or through props captured by the factory.
Built-in tabs are the reference implementation: explorer (id explorer, order 10) and git (id git, order 20), registered in the plugin own apply through the same public API.
Error codes
Domain errors travel inside the RPC value slot as SidebarResult (ADR-002 — dsh RpcError is a closed union; a plugin code in the error slot would break the browser response parser). The transport error slot is used only by the connection layer itself.
Code
Meaning
not-found
Path does not exist
permission-denied
fs read denied (EACCES/EPERM)
not-directory
Expected a directory
symlink-loop
ELOOP during stat
path-too-long
Payload path over the 4096-char guard
invalid-root
Root validation failure (relative path, ...)
outside-allowed-root
Root outside configured allowedRoots
not-a-repo
Path is not inside a git work tree
git-missing
git binary not found on PATH
git-failed
git exited with an error (stderr tail capped)
timeout
git command exceeded gitTimeoutMs
cancelled
Caller aborted the request
param-invalid
Payload failed the contract guard
internal
Unexpected host failure / transport down (host unavailable)
The test suite needs a real git on PATH (git-service tests script a real repo under a temp dir). node_modules/@deepseek-ai/* are junctions to the read-only dsh checkout; vitest resolves dsh source through the alias map in vitest.config.ts (never the built module-loader bundles), and the client tests force a single React instance (see the comments in vitest.config.ts).
Known limitations (v1)
Explorer has no hidden-file reveal toggle, no multi-select, no virtualization.
Git supports the full working-tree loop: stage/unstage, commit (staged or "include all"), and discard (per-file or all untracked+unstaged) — but no diff CONTENT preview and no stash; no merge badge (contract lacks a parents field). Commit/discard are destructive and gated by the UI's confirm dialog; discard restores tracked files from HEAD and cleans untracked ones.
The dock owns the frame's right details column, so dsh's built-in tool-details viewer (click a tool call to inspect its input/output) is replaced by the sidebar. The details track only opens for a current NON-blank session on a wide-enough viewport (AppFrame gates both) — when it is closed the dock floats at the right edge instead of vanishing, and docks back in-flow once the column opens. Collapse shrinks it to a 56px tab rail (click a tab or the expand chevron to restore; Ctrl/Cmd+Shift+B toggles); without any current session the dock does not mount (dsh's native details behavior).
Open-file events are emitted but no editor consumes them yet.