- Remove every box-shadow; panel uses the native sidebar fill surface
(--dsw-specific-sidebar-fill) with a hairline border, the collapse
control becomes the native 36px circular rail button, the active tab
uses the native interactive-bg-active fill, and rows/typography follow
the native 34px/14px session-row recipe.
- New 'Side card' settings section in the DSH Settings shell: open-by-
default for new conversations (boolean) and default width share as a
percent of the window (20-60). New sessions seed from these prefs;
existing per-session layouts stay untouched.
- The settings namespace is read/written through the plugin's own fenced
/sidebar routes calling the settings seam in-process: the DSH settings
RPC domain only serves allowlisted namespaces to configuration clients,
so third-party namespaces cannot use connection.api.settings (reads are
silently filtered, writes refused with 'not exposed'). The seam still
persists to the user settings document with revision-guarded writes.
- README leads with a self-contained prompt the user pastes into DSH:
clone URL, build, profile registration, cordis mount row, restart
- manual install/update section kept as the no-prompt alternative
- platform support section documents win32/linux/macOS adaptation and
the node-pty build-tool requirements
- fs-tree joins listed paths with the platform separator (Windows rows
previously leaked mixed separators)
- media route containment check goes through isWithin(): separator-
tolerant and case-insensitive on win32, so case-mismatched Windows
paths are not misclassified
- relativeTo compares case-insensitively (Windows / case-insensitive
macOS volumes) while keeping the caller's own casing in the output
- tests: platform-conditional path assertions, os.tmpdir() instead of
/tmp, isWithin/relativeTo/resolveSidebarPath win32 coverage
- explorer row menu gains a download action (files only); the host route
serves raw bytes with Content-Disposition: attachment, binary-safe
- editor enables line numbers
- state: side-store per conversation, injected as a prop instead of a module singleton
- host: schemastery Config (read/media/list/terminal limits) resolved from cordis config
- shape: types/exports/peerDependencies for the web profile + plugin-shape spec
- fix: split divider emits incremental deltas so drags track the cursor 1:1
- docs: README refresh
Restructures the README for readability: a one-line pitch plus "why use
it" bullets, emoji-grouped feature sections, a keyboard-shortcut table,
and a quick-start flow. Adds the recently shipped capabilities to the
feature map (tab/+ menu icons, the @-reference row button, right-click
copy relative/absolute path, persisted-state self-healing) and documents
the split-id de-duplication under architecture.
The uid counter is module-global and resets on every reload, while the
persisted split tree keeps its old ids (pane:1, split:1, ...). A split
created AFTER a reload minted a fresh "pane:1" beside the persisted
"pane:1"; mapLeaf then visited BOTH leaves, so every open (explorer click,
produced-files chip) landed the tab in both panes of a top-bottom split.
- loadState seeds the counter past the raw persisted ids before sanitize
- setSession re-seeds on cache hits (another session's smaller max must
not leave the counter below this session's ids)
- sanitize re-ids stale duplicate pane/split ids (heals states already
corrupted) and follows the activePane rename
- openTab falls back to the first pane when the active pane is gone
instead of silently dropping the open
Unit tests cover the re-id heal and the stale-activePane fallback;
verified end-to-end in headless Chrome: a state with two "pane:1" leaves
loads healed and a file click lands in exactly one pane.
Cordis only grants property reads for services declared in the plugin's
inject list; every other read throws "cannot get property X without
inject" (the app's own plugins read 'conversation' through ctx.get for
this reason). referenceInChat now resolves the conversation service lazily
via ctx.get('conversation') and no-ops when absent. Verified end-to-end in
headless Chrome: clicking the @file button on root/dir/file rows now
appends the at-relative-path mention to the composer draft with no
console errors.
The new useCallback was declared after the early return for the no-session
state, so the first render (no session) ran fewer hooks than the follow-up
render once a session arrived — React error #310, boundary strip on load.
Moved the hook above the early return (with a sessionId guard) and verified
headlessly: the error no longer appears and the panel renders fully.
- Tabs, the + menu, and empty-pane cards carry per-type icons (folder /
branch / terminal / code file); a terminal glyph joins the icon set
- Hovering an explorer row reveals an @文件 button at the far right that
appends @<relative path> to the composer draft (session-scoped input
service, space-separated append)
- Right-clicking a row opens a context menu: copy relative / absolute
path (writeClipboard + transient in-row "copied" label)
- Rows become div role=button (Enter/Space) to host the inner button;
relativeTo helper unit-tested
Explorer rows become the app's tree-row cell — 34px, radius 8, inset by
the scroll padding, 22px indent step, and the session-row mount fade —
and git rows get the same cell plus the proper selected fill
(interactive-bg-active). Icon buttons switch to the app's 28px circle
pattern (ui-workspace), headers settle at the 36px section rhythm, and
keyboard focus gets a visible ring everywhere per the styling policy,
with a prefers-reduced-motion block turning off the slides and fades.