shell-redesign V1 attempt-3 Phase 3 BOUNCE-FIX follow-on: floating toggle + vellum rail + lighter icons

Founder direction 2026-05-06 — three refinements:

1. Collapsed Explorer: floating toggle, no whitespace column
   Previous: collapsed Explorer rendered as a 12px aside with bg-background;
   the whitespace column read as empty UI clutter.
   Fix: when collapsed, render ONLY a floating chevron-right button
   positioned fixed at left=3.5rem (just past the rail edge) +
   top=5.5rem. No aside container, no background column. Tabs and
   canvas reflow to fill (anchorLeft drops to 3rem = rail-only).
   data-explorer-collapsed='true' attribute on the floating button for
   runtime probing.

2. Rail with vellum surface
   Previous: rail used `bg-background` (solid paper-cream).
   Fix: rail now uses .vellum class (rgba(255,255,255,0.4) +
   backdrop-blur(8px)) — same translucent treatment as the
   topology-graph Explorer overlay. Paper-grid dots visible through
   the rail surface. Reads as a paper sheet layered over the canvas
   per universal-shell.md L48 sheets-of-vellum aesthetic.

3. Icons lighter line weight
   Previous: lucide icons rendered at default strokeWidth=2 (heavy
   line). Read as too dense against the vellum-translucent rail.
   Fix: strokeWidth=1.25 on all rail destination + chat icons + the
   Explorer toggle chevrons. Closer to 1px architectural drafting
   feel matching the 1px ghost border doctrine.
   - Widened RailIconSpec.icon ComponentType to accept strokeWidth.
   - Lighter hover bg (stone-200/60 instead of stone-200) so hover
     reads gentler against vellum.

Visual verification at /topology 1440x900:
- Rail: vellum-translucent; paper-grid dots show through; icons lighter.
- Explorer expanded: paper-grid dots show through; vellum overlay light.
- Click chevron-left: Explorer collapses; ONLY a floating chevron-right
  button visible at rail edge; no aside column; tabs reflow to anchor=3rem;
  canvas extends fully.
- Click floating chevron: Explorer re-expands.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-06 15:16:15 -07:00
1 parent ed08ff2376
commit bbe6250b97
2 files changed
+59 -37

No files matched your search

+18 -11
View File
@@ -116,7 +116,8 @@ interface RailIconSpec {
id: string;
label: string;
to: string;
icon: ComponentType<{ className?: string }>;
// lucide-react icons accept SVG props (strokeWidth, color, size, etc).
icon: ComponentType<{ className?: string; strokeWidth?: number | string }>;
/** Path prefix used for active-state matching. */
activeWhen: (pathname: string) => boolean;
testId: string;
@@ -247,10 +248,13 @@ function Rail({
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-stone-900 focus-visible:outline-offset-2",
active
? "bg-stone-900 text-stone-50"
: "text-stone-700 hover:bg-stone-200 hover:text-stone-900",
: "text-stone-700 hover:bg-stone-200/60 hover:text-stone-900",
)}
>
<Icon className="h-5 w-5" />
{/* Lighter icon line weight (founder direction 2026-05-06):
stroke-width 1.25 (default lucide is 2) for an architectural
drafting feel that matches the 1px ghost border doctrine. */}
<Icon className="h-5 w-5" strokeWidth={1.25} />
{active && (
<span
aria-hidden="true"
@@ -268,7 +272,11 @@ function Rail({
className={cn(
// V1 border weight doctrine (universal-shell.md L39–L48):
// 1px outline-variant ghost line for inter-region edges.
"bg-background border-outline-variant flex shrink-0",
// Vellum surface (founder direction 2026-05-06): same translucent
// treatment as the topology-graph Explorer overlay, so the rail
// reads as a paper sheet layered over the canvas (sheets-of-vellum
// aesthetic per universal-shell.md L48).
"vellum border-outline-variant flex shrink-0",
vertical
? "w-12 flex-col items-center border-r py-2 gap-1"
: "w-full flex-row items-center border-b px-2 gap-1 overflow-x-auto",
@@ -395,13 +403,12 @@ function AppShellInner({ children }: AppShellProps) {
// - overlay: padding = 0 (content extends behind translucent explorer);
// tab bar is sticky/positioned at left=anchor independently.
// 21rem = rail (3rem) + explorer (18rem).
// 21rem (rail 3 + explorer 18) when explorer fully open; 6rem
// (rail 3 + collapsed-explorer-stub 3) when explorer collapsed; 3rem
// (rail only) when no explorer for the destination.
const explorerAnchorLeft = isWideLayout && explorerVisible
? desktopExplorerOpen
? "21rem"
: "6rem"
// 21rem (rail 3 + explorer 18) when explorer fully open. When
// collapsed: 3rem (rail only) — the floating chevron toggle floats
// over the canvas and doesn't claim layout space. When no explorer
// for the destination: 3rem (rail only).
const explorerAnchorLeft = isWideLayout && explorerVisible && desktopExplorerOpen
? "21rem"
: "3rem";
const workspaceLeftOffset = isWideLayout
? isTopologyOverlay
+41 -26
View File
@@ -587,12 +587,37 @@ export function Explorer({
// vellum-heavy (70%) was too dense.
const isOverlay = overlayMode === "overlay";
const isCollapsed = desktopMode === "hidden";
// When collapsed at desktop: render ONLY a floating toggle button
// at left=rail-edge (no aside container behind it). The Explorer
// surface tree is unmounted; the canvas + tabs reflow to fill the
// freed width. Founder direction 2026-05-06.
if (isCollapsed) {
return (
<button
type="button"
data-testid="explorer-edge-toggle"
data-explorer-collapsed="true"
aria-label="Expand explorer"
onClick={onDesktopToggle}
className={cn(
"hidden lg:flex fixed top-[5.5rem] left-[3.5rem] z-30 h-8 w-8 items-center justify-center",
"rounded-full border border-outline-variant bg-background/90 text-stone-700",
"shadow-[0_2px_8px_rgba(41,37,36,0.08)] backdrop-blur-sm transition-colors",
"hover:bg-stone-100 hover:text-stone-900",
)}
>
<ChevronRight className="h-4 w-4" strokeWidth={1.5} />
</button>
);
}
return (
<aside
data-testid="explorer"
data-surface={surface}
data-explorer-mode={overlayMode}
data-explorer-collapsed={isCollapsed}
data-explorer-collapsed="false"
className={cn(
// V1 border weight doctrine (universal-shell.md L39–L48):
// 1px outline-variant ghost line for inter-region edges.
@@ -606,44 +631,34 @@ export function Explorer({
open ? "translate-x-0" : "-translate-x-full",
// Desktop (>=lg): persistent column at 280px (lg:w-72) per
// universal-shell.md L34. Positioned absolutely after the 48px
// rail. When collapsed: thin 12px column with just the
// chevron-right re-open toggle visible (founder direction
// 2026-05-06 — no way to expand otherwise without page refresh).
desktopMode === "full" && "lg:absolute lg:top-0 lg:bottom-0 lg:left-12 lg:w-72 lg:max-w-none lg:translate-x-0",
desktopMode === "hidden" && "lg:absolute lg:top-0 lg:bottom-0 lg:left-12 lg:w-12 lg:max-w-none lg:translate-x-0 bg-background",
// rail.
"lg:absolute lg:top-0 lg:bottom-0 lg:left-12 lg:w-72 lg:max-w-none lg:translate-x-0",
)}
>
<div className="relative flex h-full w-full flex-col">
{/* Collapse / expand toggle — always reachable. When the
explorer is collapsed (desktopMode='hidden'), the entire
aside shrinks to a 12px column showing only this toggle so
the user can re-expand without refreshing. Founder direction
2026-05-06. */}
<button
type="button"
data-testid="explorer-edge-toggle"
aria-label={isCollapsed ? "Expand explorer" : "Collapse explorer"}
aria-label="Collapse explorer"
onClick={onDesktopToggle}
className={cn(
"hidden lg:flex absolute z-10 h-8 w-8 items-center justify-center rounded-full border border-stone-300 bg-background/90 text-stone-700",
"hidden lg:flex absolute z-10 h-8 w-8 items-center justify-center rounded-full border border-outline-variant bg-background/90 text-stone-700",
"shadow-[0_2px_8px_rgba(41,37,36,0.08)] backdrop-blur-sm transition-colors hover:bg-stone-100 hover:text-stone-900",
isCollapsed ? "left-1/2 top-3 -translate-x-1/2" : "right-2 top-3",
"right-2 top-3",
)}
>
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
<ChevronLeft className="h-4 w-4" strokeWidth={1.5} />
</button>
{!isCollapsed && (
<SurfaceBody
surface={surface}
rigs={rigs}
psMap={psMap}
selection={selection}
onSelect={onSelect}
onClose={onClose}
currentRigId={currentRigId}
/>
)}
<SurfaceBody
surface={surface}
rigs={rigs}
psMap={psMap}
selection={selection}
onSelect={onSelect}
onClose={onClose}
currentRigId={currentRigId}
/>
</div>
</aside>
);