Two failure modes of a sidebar render crash, fixed together:
1. Layout-push leak (root cause): the layout-push effect wrote
--dsh-sidebar-width/--dsh-sidebar-height on document.documentElement
without a cleanup, so ANY unmount of the Sidebar (error-boundary swap,
plugin disable, HMR) left the variables behind and layout.css kept
squeezing #root with a stale margin — "the sidebar cannot be hidden"
until a full reload. The effect now removes both properties on cleanup
(the CSS fallback 0px restores the layout).
2. Crash containment (UX): the root error boundary previously replaced the
WHOLE sidebar — toggle cluster included — with an error strip on any tab
crash. A new shared RenderBoundary (src/client/RenderBoundary.tsx) now
scopes containment per tab: a crashing viewer/editor shows a dismissible
strip inside its own pane while the toggle cluster, the other tabs, and
the panel stay alive. The descriptor is rendered as a real element
(createElement) so even top-level component throws land in the boundary,
not in TabContent's own render. The root boundary remains as the last
resort for Workbench/shell errors.
Tests (tests/sidebar-crash.spec.tsx, jsdom + real Sidebar/store harness):
- unmount clears both CSS variables (red before the fix: 307px leaked)
- a crashing tab is contained: strip in-pane, collapse button alive,
layout push still live
- the retry button recovers a tab whose crash has since been fixed
Full suite: 425 tests pass; tsc --noEmit clean.
node-pty@1.1.0 builds the spawn-helper executable only for OS=="mac"
(binding.gyp); linux uses forkpty directly and ships no helper at all.
The test's non-Windows premise was wrong for linux, and no candidate
exists there to assert on.
- checkout with fetch-depth: 0: the git log paging test needs the full
repository history (default shallow clone yields one commit)
- configure a global git identity on the runner: src/git.ts deliberately
never sets one and revert/cherry-pick rely on ambient config
- tests/unit.spec.ts: node-pty@1.1.0 ships a prebuilt spawn-helper only for
darwin/win32; on linux node-gyp compiles it into build/Release. Align the
assertion with ensureSpawnHelper's candidate list instead of hardcoding
the prebuilds/ path (the Linux test run was red for this reason)
The panel width lived in the per-session state (dsh-sidebar:v1:<id>), so a
drag in one conversation never carried to another: switching conversations
snapped the panel back to that session's own width. The width is a layout
preference, not session content, so it now persists under a cross-session
key (dsh-sidebar:v1:width):
- every persist writes the current width to the global key;
- loadState prefers the global width over a session's persisted one;
- a cache-hit session switch adopts the global width.
Backward compatible: with no global key present (first run / older data)
the behavior is unchanged. Adds a regression test covering drag-in-A ->
switch-to-B and drag-in-B -> switch-back-to-A.
The "add to conversation" selection popup was registered only for the
catch-all code viewer (viewerId === 'code'), so selecting text in the
CodeMirror editor of a markdown file produced no popup; insertion only
worked in markdown preview mode via the separate DOM-selection handler.
Extend the CodeMirror selection popup to the markdown editor
(viewerId === 'markdown') so line-exact selections can be injected into
the composer draft (\`\`\`rel/path:start-end\`\`\` payload), matching
preview-mode behavior and the code viewer.