Skip to Content

← All archived runs

Run: console-shell-sidebar

run.md

Run: console-shell-sidebar

  • branch: claude/stoic-meitner-qi74ow
  • pr: #906

02_define/output/spec.md

Spec: Console sidebar shell — navigation, theming and responsive foundation

  • slug: console-shell-sidebar
  • personas: Admin (Sustentus staff), Partner
  • touches: apps/console/app/(console)/layout.tsx, apps/console/components/, apps/console/app/globals.css
  • complexity: standard

Problem

The console shell is a row of text links in a narrow centred max-w-5xl column: it reads as an unstyled scaffold to the partners Sustentus invites in, crowds as links accumulate (Email delivery already hides behind a staff check partly for space), squeezes data-heavy pages, and surfaces none of the theming next-themes already wires. Partners judge Sustentus by this surface, so shell credibility advances Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners — under the Scale the Bridge initiative. Every other stub in the console-ui-polish batch restyles pages inside this frame, so the frame moves first.

Proposed change

Replace the top-nav header in the (console) layout with a sidebar shell, derived from apps/web's dashboard shell patterns (apps/web/components/sidebar/app-sidebar.tsx, built on the Sidebar primitives @sustentus/ui already exports) and the brand-guidelines skill — Jamie's ruling (2026-08-27): no external reference design exists; those two sources are the truth. This run is visual/UX only; the shell keeps exactly today's contents and behaviour:

  • Same five destinations, same gating. Home /, Tenants /tenants, Activity /activity, and — for staff viewers only, via the same isConsoleStaff(viewer.access) check — Access /access and Email delivery /notifications/delivery. Nothing is added, removed or re-gated.
  • Active state by section. Nested tenant routes (/tenants/new, /tenants/[tenantId], /tenants/deleted/**) highlight Tenants; /notifications/delivery highlights Email delivery; Home is active on / exactly (never as a prefix). /notifications (the see-all page) has no nav item today and gets none — it stays reachable through the bell, with no active nav item.
  • Notifications and account untouched in behaviour. The layout remains the server component that calls requireViewer() and readShellSummary(viewer); the bell stays a client shell around the server-rendered preview list, with See all and the mark-all-read action; the Clerk UserButton stays. The layout comment on App Router not re-rendering shared layouts on client navigation (notification freshness) is kept verbatim, and the behaviour with it.
  • Theme toggle. A visible toggle in the shell switches light/dark (system stays available — the existing provider default), persisting across navigation and reloads via the already-wired next-themes.
  • Responsive collapse. At tablet/mobile widths the sidebar collapses to an accessible drawer or icon rail (either is sanctioned — Build picks within the apps/web-derived pattern). The bell, theme toggle and UserButton remain reachable at every width; placement within the shell follows the apps/web pattern at Build's discretion.
  • Freed width. The content area drops the centred max-w-5xl cap: pages span the width the sidebar leaves, with consistent padding, so data pages stop being squeezed.
  • Brand baseline. The shell chrome and globals.css establish the brand typography/spacing/ colour baseline (per the brand-guidelines skill) that surface stubs 2–4 build on — correct in both light and dark themes.

Context budget: read the console shell source files (layout, providers, route list) in lieu of technical/architecture/repository-structure to pin exact behaviour; no scope.md exists for this batch — rulings trace to the intake breakdown (Jamie, 2026-08-27).

Acceptance criteria

  • Every (console) route renders inside the sidebar shell, and the nav marks the current section active: nested tenant routes highlight Tenants, /notifications/delivery highlights Email delivery, Home is active on / exactly — no route loses its place in the nav.
  • A partner viewer sees Home, Tenants and Activity only; a staff viewer additionally sees Access and Email delivery — the same isConsoleStaff(viewer.access) gating as today, only presentation moved.
  • The notification bell works exactly as before from the new shell: unread count, server-rendered preview, See all to /notifications, Mark all read shown only when unread count is above zero; the layout's data reads and its freshness comment are unchanged.
  • A theme toggle in the shell switches dark/light and the choice persists across client-side navigation and a full reload.
  • At tablet and phone widths the sidebar collapses to a keyboard-accessible drawer or icon rail; the bell, theme toggle and UserButton stay reachable; no (console) page shows a horizontal scrollbar caused by the shell itself.
  • The (console) content area no longer caps pages at the centred max-w-5xl column — pages use the width the sidebar frees, with consistent padding.
  • The shell chrome renders with the brand palette and typography from the brand-guidelines skill, correct in both light and dark themes.

Out of scope

  • Restyling the pages inside the shell — stubs 2–4 (console-dashboard-activity-polish, console-tenants-polish, console-auth-access-polish).
  • Adding, removing or re-gating any navigation destination; /notifications stays out of the nav (reached via the bell), and gating semantics are untouched.
  • Any change to proxy.ts, requireViewer/requireStaff, the console-access marker, or the layout's notification read (readShellSummary) and its freshness caveat.
  • The (auth) and no-access surfaces — stub 4's first-impression pass.
  • Changes to packages/ui — the Sidebar primitives it already exports are consumed as-is.

Open questions

  • none — (non-blocking note) drawer vs icon rail and the exact placement of bell/toggle/UserButton inside the shell are Build's to pick within the apps/web-derived pattern; the reachability and accessibility criteria above are the requirement.

03_build/output/notes.md

Build notes: console-shell-sidebar

  • commits: feat: console-shell-sidebar — replace the top nav with a sidebar shell (code), chore: console-shell-sidebar — build notes (this file)
  • ci: settled via ci-status.sh on the pushed head after this push — Build hands off only on GREEN; the PR's checks on the head commit are the durable record.

What changed

  • apps/console/lib/console-nav.ts (new): the nav model as pure data + predicates — the same five destinations with the same staffOnly gating, visibleNavItems(staff), and isNavActive (Home exact-match; section prefixes; /notifications deliberately owned by no item). The gating rationale comments from the old top nav moved here with the items.
  • apps/console/lib/console-nav.test.ts (new): unit tests written from the acceptance criteria — partner vs staff visibility, nested tenant routes under Tenants, /notifications/delivery under Email delivery, Home never active as a prefix, see-all page active nowhere.
  • apps/console/components/console-sidebar.tsx (new, client): the shell frame on the @sustentus/ui Sidebar primitives (collapsible="icon"), derived from apps/web/components/sidebar/app-sidebar.tsx — wordmark header linking to /, nav menu with active states and icon-mode tooltips, theme toggle in the footer (hidden on the icon rail; the mobile drawer always shows it).
  • apps/console/components/theme-toggle.tsx (new, client): the system/light/dark segmented control derived from apps/web's ThemeSettings, restyled onto the sidebar tokens, with a mounted guard so SSR doesn't hydrate against the stored theme.
  • apps/console/app/(console)/layout.tsx: data reads (requireViewer, readShellSummary) and every comment kept verbatim; render swapped to SidebarProviderConsoleSidebar + SidebarInset with a slim top bar (SidebarTrigger + the unchanged bell composition + UserButton) and a full-width scroll region (px-6 py-10, no max-w-5xl, overflow-x-hidden so the shell never causes a horizontal scrollbar).
  • apps/console/app/globals.css: untouched@sustentus/ui/globals.css already declares @source "..", so the sidebar primitive's classes compile, and the brand/sidebar tokens (light + dark) are the shared ones. Nothing console-specific was needed.

Context budget: within the Inputs table — the extra reads were the derivation sources the spec names (apps/web shell + packages/ui/src/base/sidebar.tsx for the primitive's API) plus the brand-guidelines skill; the architecture docs pages were not needed.

Acceptance criteria status

  • Shell + active states — all (console) routes render through the rewritten layout; isNavActive maps nested tenant routes → Tenants, /notifications/delivery → Email delivery, Home exact-only (unit-tested).
  • Gating unchanged — visibleNavItems(staff) with the same isConsoleStaff(viewer.access) input; partner sees Home/Tenants/Activity (unit-tested).
  • Bell unchanged — NotificationBell/NotificationList composition, See all, and the unread-gated Mark all read are byte-for-byte the old markup in the new top bar; reads and freshness comment untouched.
  • Theme toggle — visible in the sidebar footer; next-themes persists across navigation and reloads (provider config unchanged).
  • Responsive collapse — below 768px the primitive renders an offcanvas drawer (focus trapped, keyboard accessible) opened by the always-visible top-bar trigger; at ≥768px the trigger collapses to the icon rail. Bell + UserButton live in the top bar at every width; the toggle is in the drawer/expanded sidebar. Shell clamps overflow-x.
  • Freed width — content region is w-full px-6 py-10; the max-w-5xl cap is gone.
  • Brand baseline — shell chrome renders entirely from the shared sidebar/semantic tokens (brand purple on the wordmark mark and active accents), correct in both themes.

Notes for Verify

  • Smoke the tablet boundary: at exactly 768px the sidebar is the desktop one (expanded until collapsed via the trigger); below 768px it is the drawer. This is the apps/web-shipped behaviour the ruling derives from — flag it in conversation if the boundary feels wrong on the preview.
  • On the desktop icon rail the theme toggle is hidden (expand to reach it); the mobile drawer always shows it. Sanctioned by the spec's non-blocking note, but worth a look on the preview.
  • The Quality workflow's Run tests step now runs console-nav.test.ts — first tests in this app were already present (console-roles, subscription-labels), so no config change was needed.

04_verify/output/verify.md

Verify: console-shell-sidebar

  • ci: GREEN — established by ci-status.sh at stage start (ccb0cf1) and re-established after this stage's final push (the head carrying this file; SHA named in the gate hand-off, and the PR's checks are the durable record).
  • previews smoked: tenant-management preview built and serving for this head, but behind Vercel deployment protection (302 → Vercel SSO) — the agent could not reach even the sign-in page, so every visual line below is operator-side. All eight Vercel projects built for this commit; none skipped.
  • production-readiness: not required — the diff is five apps/console UI files (layout, two components, nav model + test); no database, payments, env vars, or migrations. proxy.ts, requireViewer/requireStaff and the console-access marker are untouched.
  • code-review: triaged the CI conventions review (Review diff against CONVENTIONS.md, pass) — zero review threads posted. Own adversarial pass over the diff found one accessibility finding (below), fixed on branch.
  • security-review: not required — no auth mechanics, payments, PII, or route-policy changes; the staff boolean is the same isConsoleStaff(viewer.access) read as before, consumed for presentation only.
  • playwright: TODO — manual DoD smoke performed instead (signed-in half is the operator's).

DoD smoke (each line says who verified it)

Agent-traced in the diff and pinned by the unit tier (console-nav.test.ts, green in Quality):

  • Active-state mapping — nested tenant routes → Tenants, /notifications/delivery → Email delivery, Home exact-only, /notifications owned by nothing (agent: unit tests + trace).
  • Gating unchanged — visibleNavItems(staff) filters the same two staff-only entries; partner sees Home/Tenants/Activity (agent: unit tests + trace).
  • Bell composition byte-identical to the old header markup; layout data reads and freshness comment unchanged (agent: diff).
  • Width cap removed — max-w-5xl gone; content region w-full px-6 py-10, shell clamps overflow-x (agent: diff).
  • Shell chrome renders only shared sidebar/semantic tokens (light + dark defined in @sustentus/ui/globals.css); toggle persists via next-themes localStorage (agent: trace).

Operator-demonstrated on the preview (Vercel-authenticated; confirm at the gate — the gate must not pass on unverified lines):

  • Every (console) route renders in the shell with the right active item while clicking through Home → Tenants (incl. a tenant detail, new, deleted) → Activity → Access → Email delivery (operator)
  • Partner viewer sees three destinations only; staff sees five (operator)
  • Bell: unread count, preview list, See all, Mark all read (only when unread > 0) behave as before (operator)
  • Theme toggle switches dark/light, survives navigation and a full reload; both themes look right (operator)
  • At tablet (≥768px: expanded sidebar, trigger collapses to icon rail) and phone (<768px: offcanvas drawer via the top-bar trigger) widths: keyboard-accessible, bell/toggle/ UserButton reachable, no horizontal scrollbar from the shell (operator)
  • Pages use the freed width with consistent padding (operator)
  • auth: staff and partner sign in via Clerk and reach the console (operator)
  • payments: not touched (agent: diff)
  • notifications: none expected — presentation-only change to the bell's frame; the read model and mark actions are untouched (agent: diff)

Walkthrough clip: optional visibility — internal console surface (staff + partners), not an owner-facing product surface; a #build clip is still worth it for a visible revamp. Operator's call while demonstrating.

Findings & cleanup

  • Theme toggle claimed role="radiogroup"/role="radio" without the ARIA radio keyboard contract (arrow-key roving focus) — announced as radios, operated as buttons. Fixed on branch: role="group" + aria-pressed toggle buttons (this stage's commit).
  • Carried from build notes for the operator's eye: the 768px boundary is the apps/web-shipped behaviour (expanded sidebar at exactly 768px until collapsed); the theme toggle hides on the desktop icon rail (expand to reach it; the drawer always shows it). Both sanctioned by the spec — flag in conversation if they feel wrong on the preview.
  • CI conventions review: no findings.

Context budget: within the Inputs table; extras were one unauthenticated curl of the console preview (agent smoke) and the theme-toggle re-read for the fix.

05_ship/output/changelog.md


title: A proper sidebar for the tenant management console date: 2026-08-28T00:00:00Z personas: [admin] slug: console-shell-sidebar pr: https://github.com/sustentus/sustentus/pull/906

A proper sidebar for the tenant management console

The console's navigation was a row of small text links across the top of a narrow centred column — easy to miss, cramped as pages were added, and squeezing wide tenant tables into a third of the screen. The frame has been rebuilt around a sidebar.

Everything is where it was. The same destinations — the dashboard, tenants, activity, and for the Sustentus team access and email delivery — now sit in a sidebar on the left, each with an icon and a clear highlight on the section you are in. A partner still sees exactly the entries they saw before, and the notification bell works exactly as it did, from the bar at the top of every page.

Pick your own appearance. A switch at the bottom of the sidebar moves the console between light, dark, and following your device, and the choice sticks — across pages and across visits.

Pages get the full width. The old centred column capped every page at the same narrow band. Content now uses the room the screen actually has, which the tenant directory and activity feed in particular were waiting for.

It travels well. On a tablet the sidebar collapses to an icon rail; on a phone it becomes a drawer you open from the top bar. Nothing scrolls sideways, and the bell and your account stay within reach at every size.

This is the frame only — the pages inside it are being refreshed one surface at a time, and those changes will land here as they ship.

05_ship/output/investor-update.md

The tenant console now looks the part

Who it's for: Sustentus staff and vendor partners What shipped: The tenant management console swapped its cramped top links for a branded sidebar — clear navigation, a dark mode switch, and full-width pages on any screen size. Why it matters: Partners judge Sustentus by this surface — polish that serves Establish Product-Market Fit with Vendor Partners, under Scale the Bridge.

Dig deeper: https://github.com/sustentus/sustentus/pull/906 · https://help.sustentus.com/changelog/2026-08-28-console-shell-sidebar

05_ship/output/release.md

Ship: console-shell-sidebar

  • pr: #906 · merge: authorised — Ready to merge ticked by Jamie; this commit rides the squash
  • CI: settled via ci-status.sh after this stage's last push — GREEN is the only verdict the merge proceeds on; the SHA is named in the ship report and the PR's checks are the durable record
  • technical docs: no technical docs impact — technical/applications/console describes routes, access model and data, all unchanged; its two shell mentions ("bell in the shell header", "read in the shell's server component") remain accurate for the sidebar shell
  • business docs: no business docs impact — the console is not a product seam (no feature-role-matrix or service-journey entry; settled at the intake cut)
  • release notes: both — changelog entry 2026-08-28-console-shell-sidebar (in this PR) + ship note below
  • sent: nothing sent at commit time — the ship note rides this merge and ship-note.yaml dispatches it to #product-update when the PR merges
  • close-out: intent — archive this run to apps/docs/archive/pipeline-runs/; the console-ui-polish epic stays live with 3 stubs still in flight (console-dashboard-activity-polish, console-tenants-polish, console-auth-access-polish)

Acceptance check (vs spec)

  • Sidebar shell on every (console) route with correct active states — unit-tested (console-nav.test.ts) and traced in Verify; demonstrated on the preview at the Verify gate (operator)
  • Staff/partner gating unchanged, presentation only — unit-tested + diff trace (Verify)
  • Notification bell byte-identical behaviour from the new shell — diff trace (Verify); preview (operator)
  • Theme toggle switches dark/light and persists — next-themes persistence traced (Verify); preview (operator)
  • Tablet/phone collapse, reachability, no shell-caused horizontal scrollbar — primitive behaviour traced (Verify); preview (operator)
  • max-w-5xl cap removed; full-width content with consistent padding — diff trace (Verify)
  • Brand palette/typography in both themes — shared-token trace (Verify); preview (operator)