Skip to content

Reserve the Chief of Staff two-pane shell with a PageSkeleton split layout - #4244

Merged
atomantic merged 2 commits into
mainfrom
claim/issue-4144
Aug 15, 2026
Merged

Reserve the Chief of Staff two-pane shell with a PageSkeleton split layout#4244
atomantic merged 2 commits into
mainfrom
claim/issue-4144

Conversation

@atomantic

@atomantic atomantic commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Summary

/cos is an isFullWidth route, so Layout hands it a bare relative overflow-hidden <main>. Its loading guard rendered a centered BrailleSpinner inside an h-64 box, which reserved none of the loaded shell — the whole two-pane page (320px agent rail + tab/content pane) jumped into place on first paint.

PageSkeleton had no two-pane mode (layout only modelled stack/grid), so this takes option 1 from the issue and adds one rather than hand-rolling a page-local skeleton:

  • PageSkeleton gains layout="split" — a two-pane shell that owns the whole page: a fixed-width side rail beside a flexible main pane that owns the scroll under fullHeight. Because it owns the shell it ignores header (a two-pane page's title lives inside a pane) and renders the tab strip inside the main pane, where a two-pane page actually puts it. Knobs mirror the loaded layout: sideCollapsed (desktop rail collapsed to a zero-width track while the mobile band stays), splitColsClass (grid tracks, defaulted off sideCollapsed so a collapsed rail can't reserve 320px of nothing), sideClassName, sideHero (avatar/portrait circle), sideBlocks + sideBlockColsClass (nav list at 1 col, stat grid at 2). Below lg the rail stacks above the main pane, same as the loaded page, so the mobile band is reserved too.
  • Chief of Staff uses it. desktopPanelCollapsed is read from localStorage before the first fetch resolves, so the skeleton already holds the right rail width (lg:grid-cols-[320px_1fr] or lg:grid-cols-[0px_1fr]) and reserves the 2×2 stat grid, the avatar, and the TABS.length tab strip.
  • Small DRY pass: the stacked-card block is extracted once (stackedCards) and shared by the stack and split bodies, and the stack/grid body tree is now built after the split early return so a two-pane page doesn't allocate a tree it never renders.

Scoped to Chief of Staff and the shared primitive it needed — the sub-region spinners in #4147 are deliberately left alone.

Test plan

  • client/src/components/ui/PageSkeleton.test.jsx — new layout="split" block: reserves the two-pane tracks and drops the stack sidebar; collapses the rail track by default under sideCollapsed; takes a caller override over that derived default; renders the tab strip inside the main pane (asserted by locating the strip's pane and finding the cards in it); reserves hero + rail blocks in the requested column count; omits both when not requested; sideCollapsed keeps a zero-width desktop track plus the lg:hidden mobile band; h-full and the main pane's overflow-y-auto only under fullHeight; bodyClassName applies only when padded.
  • client/src/pages/ChiefOfStaff.test.jsx — new guard holds the first getCosStatus fetch open and asserts the busy region is the two-pane grid (aria-busy, aria-label="Loading Chief of Staff", lg:grid-cols-[320px_1fr], h-full) and that the old h-64 spinner shell is gone. jsdom does no layout, so the guard pins the structure that reserves the dimensions.
  • Full client suite: cd client && npx vitest run → 642 files / 7816 tests passing.
  • npx biome lint --error-on-warnings clean on the four changed files.

Closes #4144

…plit layout (#4144)

The /cos route is full-width, so its <main> is a bare relative overflow-hidden
box. The old loading guard rendered a centered BrailleSpinner in an h-64 div,
which reserved none of the loaded 320px-rail + chat-pane grid — the entire page
jumped into place on first paint.

Adds a layout="split" mode to PageSkeleton (fixed side rail, flexible main pane
that owns the scroll, tab strip inside the main pane) so any two-pane view can
reuse it, and points Chief of Staff at it. desktopPanelCollapsed already comes
from localStorage, so the skeleton holds the correct rail width on first paint.
…apsed rail track (#4144)

Review findings on the new PageSkeleton split layout:

- the fullHeight root is overflow-hidden, so the main pane needs
  overflow-y-auto or the reserved cards clip instead of scrolling (the
  loaded Chief of Staff content panel scrolls, so the skeleton must too);
- splitColsClass now defaults off sideCollapsed, so a collapsed rail cannot
  reserve a 320px column of nothing when the caller omits it — which lets
  Chief of Staff drop its own ternary;
- body is built after the split early return so a two-pane page no longer
  allocates the stack/grid tree it never renders.
@atomantic
atomantic merged commit 66e8b10 into main Aug 15, 2026
7 checks passed
@atomantic
atomantic deleted the claim/issue-4144 branch August 15, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chief of Staff page needs a dimension-reserving loading skeleton

1 participant