dsh-sidebar-leap
v0.3.2
Published
DSH web plugin: a standalone per-turn Changes tab (host per-turn change index + client Changes view) that registers itself into dsh-better-sidebar through the ctx.betterSidebar service.
Readme
dsh-sidebar-leap
DSH web plugin: a standalone Changes / 变更 tab that shows, per conversation
turn, the files the model wrote or modified. It registers itself into
dsh-better-sidebar through
the ctx.betterSidebar service, and serves its own /sidebar-leap/api host
routes for the per-turn change index.
This plugin was extracted from dsh-better-sidebar's own Changes tab
(src/changes-index.ts + src/client/ChangesView.tsx + the diff helpers) into
a standalone package so the two can evolve independently. The core indexing
logic (createChangesIndex / collectTurnChanges) is migrated verbatim.
How it works
- Host half (
src/index.ts,src/changes-index.ts): an incremental per-turn produced-file index. It seeds from the session store's event log on first access, then follows the livesession/eventappend feed (deduped by seq).rebuildrefolds the index from the persisted event log (sessionPersistence.inspect), restoring history after adsh webrestart. Routes:POST /sidebar-leap/api/changes.listandPOST /sidebar-leap/api/changes.rebuild, behind the same browser-trust fence as the/apigateway. - Client half (
src/client/index.tsx,src/client/ChangesView.tsx): registers achangestab throughctx.betterSidebar.registerTab. The view pollschanges.listevery 2s while visible, renders one row per changed turn with file chips, and expands real per-change unified diffs inline (no git round-trip) usingbuildUnifiedDiff+DiffView.
Per-turn change model
Every turn keeps all applied mutations, one entry per applied edit / write
(TurnChange.edits), in application order — so a file edited several times in
one turn shows each changed spot as its own diff section. The before/after
text is the REAL changed region from the tool call's arguments (edit →
old_string/new_string, write → full content), NOT the clipped
meta.diffs hunks (which only carry 3 context lines and can never match the
actual file). Each chip also carries a git-style status badge:
+(Added) — every edit of the file was a pure insertion (oldText === null);M(Modified) — any edit ran against existing content;D(Deleted) — reserved: the DSH event log records no delete-tool event today, so nothing emits it yet.
The top all changed files summary shows each file's whole-file
comparison (the content before the conversation's first touch vs after its
last), reconstructed by replaying the mutation chain — write-led chains
forward, edit-led chains reversed from the file's current disk content
(src/mutations.ts). The host reads the file through node:fs/promises only
on the /sidebar-leap/api/changes.files route (never in the client bundle).
Right-clicking a file chip opens a context menu: open in the sidebar editor,
open in the OS file manager (reveal route), open in VS Code
(vscode://file/… through the host opener), and copy relative / absolute path.
The menu is a self-contained portal menu (src/client/context-menu.tsx), since
the DSH primitives Menu is not resolvable in this plugin's dependency tree;
the external opens go through this plugin's own fenced
/sidebar-leap/api/open.external route (src/open-external.ts, argv-only
platform openers — no shell).
When a turn has more than 8 changed files, the rest collapse behind a
clickable +N button that reveals all of them.
At the top of the tab a collapsible all changed files summary lists every
file the conversation produced (across all turns), each with its status badge
and the turns that touched it (POST /sidebar-leap/api/changes.files,
aggregated by summarizeFiles in src/changes-index.ts).
Install / mount
dsh-better-sidebar must be installed first (this plugin's client half
depends on its betterSidebar service):
dsh plugin --profile <name> add dsh-better-sidebar
dsh plugin --profile <name> add dsh-sidebar-leapThe client bundle is loaded automatically through the module table
(dsh.client declaration + exports["./client"]); no dsh web restart is
needed for client-half changes, but host-half changes (the routes / index) do
require a restart.
Development
pnpm install
pnpm exec tsc --noEmit # typecheck
pnpm test # vitest (changes.spec.ts + diff.spec.ts)
pnpm build # tsc -p tsconfig.build.json + tsdown → lib/Build output: lib/index.js (host half, ESM node), lib/client.js (browser
client bundle, CJS module-loader factory), lib/types/**/*.d.ts (declarations).
Peer dependency
dsh-better-sidebar is a peerDependency (optional): without it the plugin
loads but registers nothing (ctx.betterSidebar is undefined and the client
half returns early).
License
MIT
