console-shell-sidebarrun.md02_define/output/spec.mdThe 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.
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:
/, 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./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.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.next-themes.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.max-w-5xl cap: pages span the width the
sidebar leaves, with consistent padding, so data pages stop being squeezed.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).
(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.isConsoleStaff(viewer.access) gating as today, only
presentation moved./notifications, Mark all read shown only when unread
count is above zero; the layout's data reads and its freshness comment are unchanged.UserButton stay reachable; no (console) page shows a
horizontal scrollbar caused by the shell itself.(console) content area no longer caps pages at the centred max-w-5xl column — pages
use the width the sidebar frees, with consistent padding.brand-guidelines
skill, correct in both light and dark themes.console-dashboard-activity-polish,
console-tenants-polish, console-auth-access-polish)./notifications stays out of the nav
(reached via the bell), and gating semantics are untouched.proxy.ts, requireViewer/requireStaff, the console-access marker, or the
layout's notification read (readShellSummary) and its freshness caveat.(auth) and no-access surfaces — stub 4's first-impression pass.packages/ui — the Sidebar primitives it already exports are consumed as-is.apps/web-derived pattern; the reachability and
accessibility criteria above are the requirement.03_build/output/notes.mdfeat: console-shell-sidebar — replace the top nav with a sidebar shell (code),
chore: console-shell-sidebar — build notes (this file)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.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 SidebarProvider → ConsoleSidebar +
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.
(console) routes render through the rewritten layout;
isNavActive maps nested tenant routes → Tenants, /notifications/delivery → Email
delivery, Home exact-only (unit-tested).visibleNavItems(staff) with the same
isConsoleStaff(viewer.access) input; partner sees Home/Tenants/Activity (unit-tested).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.w-full px-6 py-10; the max-w-5xl cap is gone.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.mdci-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).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.Review diff against CONVENTIONS.md, pass) —
zero review threads posted. Own adversarial pass over the diff found one accessibility finding
(below), fixed on branch.staff boolean is the same isConsoleStaff(viewer.access) read as before, consumed for
presentation only.Agent-traced in the diff and pinned by the unit tier (console-nav.test.ts, green in Quality):
/notifications/delivery → Email
delivery, Home exact-only, /notifications owned by nothing (agent: unit tests + trace).visibleNavItems(staff) filters the same two staff-only entries;
partner sees Home/Tenants/Activity (agent: unit tests + trace).max-w-5xl gone; content region w-full px-6 py-10, shell clamps
overflow-x (agent: diff).@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):
(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)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.
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).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.mdThe 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.mdWho 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.mdci-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
recordtechnical/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 shell2026-08-28-console-shell-sidebar (in this PR) + ship
note belowship-note.yaml
dispatches it to #product-update when the PR mergesapps/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)(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)max-w-5xl cap removed; full-width content with consistent padding — diff trace (Verify)