Estimate: 26m · Depends on: 1.0.5.2, 1.5.1
Ship the canonical AppLayout + Sidebar primitives in components/ui/. Data-agnostic — accept children + slot props, no knowledge of workspaces / projects / routes. The wiring to the (authed) layout lands in 1.5.3.
API:
<AppLayout topNav={...} sidebar={...}>{children}</AppLayout> — CSS-grid two-column layout (sidebar · main) at ≥md, single-column with the sidebar off-canvas at <md. Persists the collapsed state via the standard localStorage key (motir.shell.sidebar.collapsed — mirrors the 1.0.5.2 theme key naming).<Sidebar header={...} sections={[...]} footer={...} /> — each section is { id, label, items: [{ icon, label, href, kbd?, active? }] }. Renders nav semantics (<nav aria-label="Primary">), uses Radix's Collapsible for any section the consumer marks collapsible. Active item gets aria-current="page".<SidebarToggle /> — the collapse / expand button that reads + writes the same localStorage key. Lives in the sidebar footer for desktop, in the top-nav as a hamburger for mobile (the prop variant: 'footer' | 'hamburger' picks the affordance).<SidebarDrawer /> — the mobile off-canvas variant, a Radix Dialog.Root with a custom scrim + slide-in animation. Closes on route change (consume usePathname).Tokens: introduce 4 new --el-* tokens in app/globals.css per the 1.0.5.2 growth principle (minimal tokens, add as needed): --el-sidebar-bg, --el-sidebar-border, --el-sidebar-item-bg-hover, --el-sidebar-item-bg-active. Reference them in the new primitive's classes; document each in /docs/design-system.md.
/tokens specimen: add an "App shell" section to app/tokens/page.tsx rendering the full variant matrix — sidebar expanded, sidebar collapsed (via a local useState control on the page), sample sections with active and hover states. Per Story 1.0.5's convention every new primitive lands in /tokens with its variants.
Keyboard: ⌘\ (Mac) / Ctrl+\ (Win/Linux) toggles the sidebar's collapsed state. Use a single shared keyboard-shortcut hook (lib/hooks/useShortcut.ts) so 1.5.4 can register additional shortcuts consistently.
No data wiring in this Subtask — pure presentational primitives. The (authed) layout migration is 1.5.3.
components/ui/AppLayout.tsx + components/ui/Sidebar.tsx + components/ui/SidebarDrawer.tsx ship with the API above; all props strongly typed; forwardRef where the primitive wraps a focusable element.localStorage under motir.shell.sidebar.collapsed; reads the saved value via useSyncExternalStore + lazy useState initializer (React 19 set-state-in-effect rule, established in 1.0.5.2).md breakpoint the sidebar mounts as a SidebarDrawer (off-canvas Dialog); above md it mounts as a persistent column.⌘\ / Ctrl+\ toggles collapse; esc closes the mobile drawer; focus is trapped inside the drawer when open (Radix Dialog handles this); skip-link to #main rendered at the top of AppLayout.--el-* tokens added to app/globals.css under the existing tier-4 block; each documented in /docs/design-system.md.app/tokens/page.tsx grows an "App shell" section showing the primitive's variant matrix (expanded / collapsed / drawer).tests/components/app-layout.test.tsx cover: collapse toggle persistence, drawer open/close on route change, keyboard shortcut firing, skip-link target./tokens route screenshot regression (if any) still pass.motir-core/CLAUDE.md — 4-layer rule (auto-loaded)/design/shell/*.pen + PNG exports from 1.5.1 — the visual contractcomponents/ui/Dialog.tsx + Tooltip.tsx + Popover.tsx — primitives to compose fromlib/theme/ — the existing useSyncExternalStore + localStorage pattern (mirror it)app/tokens/page.tsx — the living-spec route to extend/docs/design-system.md — token + primitive documentation