fix: sidebar crash #31 — layout-push CSS-variable leak + per-tab error containment

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.
This commit is contained in:
Menghuan1918
2026-08-14 18:29:31 +08:00
parent 28d20a46c0
commit 9aeb0464ce
5 changed files with 252 additions and 40 deletions
+16
View File
@@ -1370,6 +1370,22 @@
color: var(--dsw-alias-label-primary);
}
/* The PER-TAB error strip: fills one tab's content area (unlike
.boundaryError, the full-height fixed rail for shell-level crashes), so a
crashing viewer/editor never hides the toggle cluster or the other tabs. */
.tabBoundaryError {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-start;
padding: 12px 16px;
overflow: auto;
font: var(--dsw-font-xxs-12);
color: var(--dsw-alias-state-error-primary);
}
/* ── Git ───────────────────────────────────────────────────────────────── */
.git {