Skip to content

ADR-0004 — Three Page Layout Wrappers

Every page MUST use WorkspacePage, StandardPage, or ControlPage. Three sharp tools beat one Swiss Army knife. AppBreadcrumb mounted consistently; rail-to-rail layout enforced.

FieldValue
Status🟢 Accepted
Date2026-03-16
Deciders@aphaiboon
Related ADRs(none — design system decisions are typically standalone)

By early 2026, CMMD had ~40 top-level pages. Each was built ad hoc — some used <UnifiedHeader> directly, some wrapped their own headers, some used a deprecated zone system (setZ3/Z4/Z5). Visual inconsistency was creeping in: different gutters per page, different breadcrumb behavior, different empty states, different loading spinners.

The “rail-to-rail” layout principle (both vertical rails defined by the unified header at px-4 md:px-6) was being broken by half the pages adding their own max-w-* or mx-auto. This collapsed content away from the rails on wide screens, creating a “narrow column floating in the middle of the screen” look that conflicts with our Linear/Notion/Stripe minimalism reference (see Design System).

We needed to consolidate to a small fixed set of layout shapes that:

  • Enforce rail-to-rail
  • Mount AppBreadcrumb consistently
  • Standardize empty + loading states
  • Cover all real use cases without needing escape hatches

Every new page MUST use one of three wrappers from @/components/layout/page-wrappers:

WrapperWhenExamples
WorkspacePageSplit-view apps with custom panels/sidebarsChat, Comms, Tasks
StandardPage (default)Header + content gridApp Store / Marketplace, Admin, Projects, Brain
ControlPageSettings / configuration forms (narrow centered max-w-4xl)Settings, Billing

StandardPage.Content provides w-full px-4 md:px-6 automatically. The UnifiedHeader Z3/Z4/Z5 zones are deprecatedsetZ3/Z4/Z5 is banned in new pages.

→ Full implementation rules: Page Layout Wrappers standard

Option A — One mega-wrapper with config props

Section titled “Option A — One mega-wrapper with config props”
<Page mode="workspace" hasSidebar={true} narrow={false} />
  • ✅ Single component to learn
  • ❌ Config combinations explode; many are invalid (e.g. ControlPage should always be narrow)
  • Rejected: Three sharp tools beat one Swiss Army knife.

Option B — Compound components only (<Page.Header>, <Page.Body>, …)

Section titled “Option B — Compound components only (<Page.Header>, <Page.Body>, …)”
  • ✅ Maximum flexibility
  • ❌ Doesn’t enforce the layout choice — every page can compose differently
  • ❌ Doesn’t solve the original problem (visual inconsistency)
  • Rejected: Flexibility was the problem, not the solution

Option C — Style guide alone (no wrappers)

Section titled “Option C — Style guide alone (no wrappers)”
  • Was the status quo. Didn’t work.
  • ✅ Three layout shapes cover 100% of real use cases (verified by porting all existing pages)
  • ✅ AppBreadcrumb always mounted in the right slot — consistent navigation
  • ✅ Rail-to-rail enforced — StandardPage.Content does the padding
  • ✅ Single place to update layout-level behavior (e.g. adding right rail for Sidekick)
  • ✅ Easier code review — “which wrapper?” is the first question
  • ⚠️ Custom-shaped pages need to live within one of the three shells; very unusual layouts may need an additional wrapper later
  • ⚠️ ControlPage is the only place max-w-* is allowed on outer wrapper — slight rule inconsistency
  • 🟡 New engineers building their own wrapper instead of using these — mitigation: code review + ESLint custom rule planned
  • Build the three wrappers — 2026-03-16
  • Migrate existing pages — 2026-03-16 to 2026-04-01
  • Deprecate setZ3/Z4/Z5 zones, document migration path — 2026-04
  • Add rule to CLAUDE.md (#13) — 2026-04
  • Plan written at docs/plans/2026-03-16-page-layout-standard.md

Next review: When we ship our 100th distinct page OR if a recurring layout need can’t be served by any of the three.