detail-page-shell-webrun.md01_define/output/spec.mdDemo wraps every detail/sub-page in one DetailPageShell (back-link + persona-accent icon +
h1 size="xl" weight="bold" + description). Web has no equivalent and hand-rolls each page header
inline, so the headers have drifted: size="2xl" weight="semibold", inconsistent back-link labels
("Back to dashboard" vs demo's "Back to vendor dashboard"), inconsistent back-link targets, and per-page
icon choices. This is the single most-repeated design discrepancy in the demo↔web parity audit — admin,
vendor and customer detail pages all hit it. It advances the Refine the Bridge initiative (Q2 2026
objective O1, vendor engagement): consistent page framing is the substrate every persona dashboard sits
on, and it can't be reached while each app frames pages its own way.
Promote demo's DetailPageShell into @sustentus/ui as one shared, persona-agnostic shell, then
adopt it on the web detail pages that currently hand-roll headers — header normalisation only, no
content change.
@sustentus/ui). A new DetailPageShell compound component whose header is the
demo standard: an optional back-link (ArrowLeft + label), an optional persona-accent icon, the
title as Typography as="h1" size="xl" weight="bold", an optional eyebrow and description, an
optional inline action slot, and a children content slot. Because the shared package can't depend
on either app's persona map or routing, the shell takes explicit props — backHref, backLabel,
icon, iconClassName (persona accent), title, eyebrow, description, action, children —
rather than deriving them from a persona id. Exported from the package barrel; covered by a Default
Storybook story.detail-page-shell.tsx becomes a thin wrapper that maps
its persona prop → the shared shell's explicit props (persona label → backLabel,
/dashboards/<persona> → backHref, persona accent → iconClassName). Demo call sites are unchanged;
there is now one visual source of truth.xl/bold), the
back-link label/target convention, and the persona-accent icon slot. The remaining per-page adoption
(and all per-page content fixes) ride on the later vendor/customer/admin parity stubs; this run
delivers the shell + the header standard and proves it on the foundation set.Heading scale (xl/bold) is fixed here as the single detail-page header standard, consistent with the
Typography direction settled in the design-system-reconciliation stub (stub 1).
DetailPageShell exists in @sustentus/ui, is exported from the package barrel, and
renders its title as Typography as="h1" size="xl" weight="bold" with optional back-link, eyebrow,
persona-accent icon, description, action slot and children.Default Storybook story in the @sustentus/ui Storybook.DetailPageShell re-implements over the shared shell (thin persona→props wrapper); demo
detail pages render unchanged from today (no visual regression).xl/bold) and back-link convention (ArrowLeft + "Back to <destination>" pointing at the
persona's dashboard home).pnpm lint, pnpm format:check and the web + storybook builds pass.@sustentus/ui as a shared persona-agnostic component, and web adoption this run covers the named
foundation set only. Editing this spec before ticking "Spec approved" steers Build; both are reversible
here.)02_build/output/notes.mdpackages/ui/src/compound/detail-page-shell.tsx (new): shared, persona-agnostic
DetailPageShell compound component. Header standard = optional back-link (ArrowLeft +
label), optional persona-accent icon, title as Typography as="h1" size="xl" weight="bold",
optional eyebrow + description, optional action slot, children. Takes explicit props
(backHref, backLabel, icon, eyebrow, description, action, className) rather than
deriving from a persona id, so the shared package depends on neither app's persona map nor
routing. Back-link is a plain <a> (framework-agnostic — works in Storybook and any app). Uses
only semantic tokens (text-muted-foreground/text-foreground). icon is a rendered
ReactNode (not a component type) — see the RSC-boundary note below.packages/ui/src/compound/index.ts: export the new shell from the barrel (→ @sustentus/ui).apps/storybook/src/compound/DetailPageShell.stories.tsx (new): the mandatory Default story,
all header props exposed via Controls.apps/demo/components/dashboard/detail-page-shell.tsx: re-implemented as a thin persona→props
wrapper over the shared shell (persona label → backLabel, /dashboards/<persona> → backHref,
persona accent → iconClassName). Demo call sites unchanged.apps/web/app/(app)/customer/{activity,invoices}/page.tsxapps/web/app/(app)/vendor/{retention,renewals,delivery,funnel,risk,pipeline,health,csat}/page.tsxapps/web/app/(app)/admin/{setup,integrations,users/invite}/page.tsx
Each hand-rolled inline header (was size="2xl" weight="semibold", ad-hoc back-link) is
replaced by the shared shell: heading scale now xl/bold, back-link normalised to
ArrowLeft + "Back to <persona> dashboard" pointing at the persona dashboard home (invite keeps
its natural "Back to users & roles" → /admin/users). Page padding preserved via className="p-6".DetailPageShell in @sustentus/ui, exported from the barrel, title as
Typography as="h1" size="xl" weight="bold" with optional back-link, eyebrow, persona-accent
icon, description, action slot and children — packages/ui/src/compound/detail-page-shell.tsx.Default Storybook story in the @sustentus/ui Storybook —
apps/storybook/src/compound/DetailPageShell.stories.tsx.DetailPageShell re-implements over the shared shell (thin persona→props wrapper);
demo call sites and layout unchanged (visually identical; back-link is now a full-nav anchor).xl/bold with the ArrowLeft + "Back to <destination>" convention.pnpm lint, pnpm format:check, web + storybook builds — delegated to CI + Vercel preview.main (PR #699 "Demo colour-token cleanup" also rewrote demo's
detail-page-shell.tsx): resolved by keeping the delegating wrapper — #699's goal (semantic
tokens over hardcoded greys) is already met by the shared shell, so nothing was lost.@sustentus/ui ships a "use client" banner (tsup), so DetailPageShell is a Client
Component. The first cut passed icon as an ElementType (a component function) from server
pages across the boundary, which Next.js forbids — demo (static) failed at build, web (dynamic)
failed at request. Fixed by making icon a rendered ReactNode: each caller (a server
component) now renders <Icon className="h-5 w-5 <accent>" /> and passes the element (the demo
wrapper still maps persona accent → the icon element, so demo call sites are unchanged). Dropped
the iconClassName prop — the accent is applied by the caller when rendering the element.<a> (not next/link) so @sustentus/ui stays
framework-agnostic (Storybook is Vite, not Next). Web/demo back-links therefore do a full
navigation rather than a soft client-side transition — an accepted trade-off for one shared shell.admin/integrations already used xl/bold; adopting the shell also adds the standard back-link
to /admin/dashboard it previously lacked (header-convention normalisation).text-red-500) — permitted in
apps/web; the demo path routes its accent through the shared token-safe persona accents.03_release/output/investor-update.mdWho it's for: Admin, vendor, and customer users What shipped: A shared page-header component brings every detail and sub-page onto one heading scale, back-link, and icon convention across the web app and the demo. Why it matters: Consistent framing sharpens dashboard usability — part of Refine the Bridge and our Q2 objective to keep onboarded vendors actively engaged.
Dig deeper: <merged-PR URL> · <changelog entry URL>
03_release/output/release.mdapps/docs/app/technical/packages/ui/page.mdx — added DetailPageShell to the component catalog (new exported compound component)ENABLE_CLAUDE_REVIEW off) → job skipped. Reviewed the
diff against CONVENTIONS.md here instead.type not interface, curly, named imports, no-unused)
verified by green Quality CI (pnpm lint --max-warnings=45, pnpm typecheck, pnpm format:check).Typography used for all text; the shared shell
uses only semantic tokens (text-muted-foreground/text-foreground).@sustentus/ui ships a "use client" banner), so passing icon as a component type crossed the
server→client boundary — demo failed at build, web at request. Fixed by making icon a rendered
ReactNode. Verified by green demo + web deploys.DetailPageShell in @sustentus/ui, barrel-exported, title Typography h1 xl bold with
optional back-link/eyebrow/icon/description/action/children — packages/ui/src/compound/detail-page-shell.tsx.Default Storybook story — apps/storybook/src/compound/DetailPageShell.stories.tsx (storybook deploy Ready).xl/bold with the ArrowLeft +
"Back to <destination>" convention.pnpm lint, pnpm format:check, web + storybook builds pass — green in CI + Vercel previews.