Two behavioral fixes for the collapsible sidebar so collapsing/expanding is
instant and the icons never move between states:
- Instant, not animated: drop the `transition-[width]` on the SidebarShell
panel so the rail/expanded widths snap with no sliding motion.
- Icons must not move (1:1 across states). Three drift sources fixed:
1. SidebarSection: the collapsed-rail header (a thin divider) was ~13px tall
vs the ~36px expanded header, shifting every icon below it. The rail header
now mirrors the expanded footprint (`px-3 py-1.5` + `min-h-6`).
2. Row labels used `sr-only`, which is `position:absolute` and removes the
label's line-height from flow, so each rail row was ~3-4px shorter than
expanded and the offset accumulated down the list. Labels are now kept in
flow but zero-width/clipped/transparent (SIDEBAR_RAIL_HIDDEN_LABEL), so the
row stays exactly as tall as expanded while remaining in the a11y tree.
3. The rail wrapped each NavLink directly in `TooltipTrigger asChild`; Radix
Slot drops React Router's *function* className, stripping `flex` off the
<a> (it rendered as a block) so the in-flow label stacked under the icon.
The tooltip now anchors to a plain wrapper element, keeping the <a> flex.
Verified by rendering the real Sidebar (new SidebarIconAlignment story) and
measuring every nav/project/agent icon's bounding box in both states: dX=0 and
dY within ±1px (rounding); a red/green overlay shows the icon glyphs perfectly
superimposed. Tests updated accordingly (56 sidebar tests pass).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Task 2 of the collapsible sidebar plan (PAP-10638, plan §4/§5/§6).
Replace ResizableSidebarPane with a SidebarShell that renders two layers:
- an in-flow spacer reserving `reservedWidth` (rail when collapsed, expanded
width otherwise) so content to the right reflows only on pin, never on peek;
- an absolutely-positioned panel (`inset-y-0 left-0`) of `panelWidth` (rail at
rest, expanded while peeking) that overlays content with raised z-index +
shadow-lg + bg-background + 1px border when wider than the spacer.
Unified width function (one code path for every pinned state):
reservedWidth = collapsed ? SIDEBAR_RAIL_WIDTH : expandedWidth
panelWidth = (collapsed && !peeking) ? SIDEBAR_RAIL_WIDTH : expandedWidth
overlay when panelWidth > reservedWidth
SIDEBAR_RAIL_WIDTH=56 (ends just past the icon). The drag resize handle is
disabled while collapsed; only the expanded width is persisted. The existing
`transition-[width] duration-100 ease-out` is reused; `motion-reduce` drops it.
Wire Layout: the shell wraps the whole sidebar column (nav + account row) so
the entire sidebar peeks as one overlay. Peek triggers live in Layout —
onMouseEnter (50ms open), onMouseLeave (120ms debounce close), focus-within
(immediate open), close on route change and Escape; pointer is never trapped.
Context already gates effective `peeking` to desktop + collapsed + hover-capable
pointers. Mobile drawer path untouched.
Icon-alignment guarantee: item padding/gap/icon markup are unchanged; collapse
is a pure pane-width animation + overflow-hidden label clipping. Verified in a
headless browser that the first nav icon's offset is pixel-identical (24,16)
across expanded/collapsed/peek, that peek does not reflow content (spacer stays
56px) while the panel overlaps content with a shadow.
Tests cover width split, rail collapse, overlay-on-peek styling, handle hidden
when collapsed, and the reduced-motion path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>