DSH 0.1.5 owns the right column (its native Sidebar hosts every tab type
this plugin registers), so the plugin's own right panel, its width drag /
new-session seed prefs, and the free-window feature are gone. The bottom
workbench is the only plugin-owned surface left, and its expand/collapse
control is registered into DSH's session-header utilities list slot (the
header's corner seat belongs to the native sidebar's own control).
- state: one `bottomSplits` tree; `makeDefaultState()` takes no arguments;
`toggleBottomPanel` / `openTabInBottomPane` are the only landing
reducers; `setBottomHeight` and `sanitizeState` share the
`CONVERSATION_MIN` cap (a persisted tall height no longer snaps on the
first drag).
- service: every landing in the workbench (creation or focus) expands it;
`SIDEBAR_FEATURES` drops `'floatWindows'`; the open/close/activate tab
pools cover the single tree.
- shell: `Sidebar.tsx` renders only the bottom workbench; the toggle lives
in `sidebar/bottom-toggle.tsx`; layout-push pushes height only (no width,
no `--dsh-sidebar-width`).
- prefs: `openByDefault` / `defaultWidthPercent` / `changesDiffFloat`
removed from the schema, defaults, client parser, settings page, and all
20 dictionaries; the retired keys in stored documents are ignored.
- tests: free-window specs deleted, panel/float assertions dropped,
remaining suites adapted (124 files / 1287 tests); the mount lane asserts
the header toggle and sweeps the native sidebar's guide page.
- docs: README / README_EN / guide §0,§11 / AGENTS test map.
TabBar's pin icon now shows for both virtual pinned tabs (injected
from other sessions) AND the home session's own pinned terminal tabs
(those with tab.pin !== undefined). Previously only virtual tabs got
the icon — the home tab looked like a regular terminal despite being
pinned.
M2 of pinned terminals (v0.17.0):
- TabBar: onPinTab prop (optional, hidden when unset for legacy callers)
- Terminal tabs get a 'Pin ▸' submenu (workspace/global) or 'Unpin' row
- UI vs Agent label distinction via isAgentTabId
- Non-terminal tabs get no pin entry (5-item legacy menu preserved)
- WorkbenchActions.pinTab threaded through split-pane LeafView
- Sidebar.onPinTab wires store.reduce(setTabPin) with cwd snapshot
- 12 new i18n keys synced across all 19 language dictionaries
Right-clicking a tab now takes over the browser menu and shows four
close operations scoped to the current pane:
- Close
- Close Other Tabs
- Close Tabs to the Left
- Close Tabs to the Right
Items gray out when nothing is eligible (single tab / strip ends).
Bulk closes reuse the per-tab onClose path, so descriptor onClose
callbacks and terminal pty release still run for every close. Labels
are i18n'd (zh/en).
The close settled on auxclick delivery, which the browser redirects to the
nearest common ancestor of the press/release targets when they differ — any
drift, middle-button autoscroll scroll, or tab-list reflow between press and
release swallowed the close (the strip hides its scrollbar, so Chrome's
autoscroll indicator is inert here and only added confusion).
Now the press target is recorded on middle mousedown (preventDefaulted to
disarm autoscroll at the source) and the close settles on the first middle
mouseup OVER that same tab — release-position semantics matching VS Code
(microsoft/vscode#101028) and user expectations for Chrome tabs
(crbug/40679924): pressing on a tab and releasing elsewhere cancels.
Left-button activate/drag and wheel scrolling are untouched
(tests/tab-bar-middle-click.spec.tsx).
- 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
- markdown tabs open in rendered preview with a 预览/编辑 segmented toggle;
edit mode uses the CodeMirror source editor (markdown highlighting), the
preview renders the live unsaved draft, and the editor stays mounted while
previewing so edits survive the toggle.
- an empty pane shows openable-type cards (explorer / git / terminal, the
same options as the + menu, terminal dimmed at the cap) instead of the
bare 'this pane is empty' message; clicking a card opens the tab.
- the + button sits immediately right of the last tab (sticky at the
scrollport's right edge when the tab row overflows, so it stays
reachable).
- The per-session terminal quota counted exited zombie handles (shells
closed or dropped on hosts without the close-frame protocol), so enough
abandoned terminals permanently blocked new ones — the 'disconnected,
reconnecting' loop. open() now sweeps the session's exited handles before
the quota check, and the running instance's leftovers disappear on the
next restart (disposeAll).
- Dragging a tab onto the strip's background fired the drop TWICE: the
TabBar handled it (merge into this pane) and the event bubbled to the
pane root, which ran its edge-zone handler again — producing two splits /
a duplicated instance (up+down). The strip now stops propagation for
drag-over and drop.
- Tests: +1 real-pty smoke (exited handle swept, new tab spawns under a
quota of 1).
- Root cause of the endless 'disconnected, reconnecting' banner: pnpm
strips the executable bit from node-pty's prebuilt spawn-helper, so every
spawn failed with posix_spawnp failed and the client retried forever.
ensureSpawnHelper() restores 0755 at plugin activation (mirrors
dsh-pty-local's postinstall; works for link installs too). Verified
against the live GUI: terminal now connects and echoes.
- TerminalView stops auto-reconnecting on a server refusal (close 1011 with
a reason): shows the reason plus a manual retry instead of spinning.
- defaultShell() also guards an empty SHELL.
- New sessions open at half the viewport width; width clamping now allows
filling the window, and a header fullscreen button expands the sidebar to
the full viewport (toggle restores half).
- Middle-click on a tab closes it (auxclick button 1, autoscroll
suppressed).
- Tests: +2 (shell fallback, spawn-helper exec bit idempotent).
- Split panes by dragging a tab to a pane edge (left/right/up/down zones
with a drop overlay) or into the center to merge; the toolbar split
buttons are gone (split-pane.tsx, state.ts moveTabToEdge/insertLeafAt).
- The header close button is now a collapse button; the expand button on
the right edge stays visible whenever the panel is collapsed, so the
sidebar can always be reopened (Sidebar.tsx, locales collapse/expand).
- Styles now follow the DSH web-styling rules exactly: --dsw-alias-*
semantic colors (state-error/warn tokens), --dsw-font-* typography roles
(size + line-height paired), --dsw-shadow-lv* elevation, no color
literals or static palette values (sidebar.module.css).
- Tests: +3 for moveTabToEdge (edge split, center merge, self reorder).
Host half (src/index.ts): /sidebar/api JSON API (session cwd, fs tree/read/
write, git status/diff/stage/commit/branch/log/show), /sidebar/file media
route, /sidebar/ws/terminal WebSocket over node-pty; routes share the /api
trust fence (Host-header loopback or connection.trustedHosts).
Client half (src/client): portal-mounted right panel with a per-session
split-pane workbench (recursive row/col splits, draggable dividers, tab
drag-move), tab bar with max-width 160px + plus-menu (explorer/git/terminal
multi-open rules), lazy file explorer rooted at the session cwd, editable
text editor (dirty dot, Ctrl/Cmd+S), image + Markdown preview, xterm
terminal with reconnect, and a VSCode-style git panel. Per-session state
persists to localStorage (dsh-sidebar:v1:<sessionId>). Intercepts the chat
turn-tail produced-files row (priority -1 chain entry) to open files in the
sidebar. UI styled with the dsh --dsw-* theme tokens and ui-primitives.
Build: tsdown emits the host lib plus the __ModuleLoader__ client bundle
(externals = module table words; xterm/css inlined). Tests: 19 unit + smoke
specs (pure state/tree/git parsers + real git/fs integration).