Skip to Content

← All archived runs

Run: detail-page-shell-web

run.md

Run: detail-page-shell-web

  • branch: claude/detail-page-shell-web-qt83vr
  • pr: #700

01_define/output/spec.md

Spec: Shared DetailPageShell in web

  • slug: detail-page-shell-web
  • personas: Admin, Vendor, Customer (every persona with detail/sub-pages)
  • touches: packages/ui/src/compound (new shell), packages/ui/src/index.ts, apps/demo/components/dashboard/detail-page-shell.tsx (thin wrapper), apps/web/app/(app)/{customer,vendor,admin}/** (header adoption), apps/storybook (Default story)
  • complexity: standard

Problem

Demo 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.

Proposed change

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.

  • Shared shell (@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 propsbackHref, 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.
  • Demo keeps its API. Demo's existing 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.
  • Web adopts the shell (foundation set). Replace the hand-rolled inline headers with the shared shell on the pages the stub names — customer activity and invoices, the vendor detail pages, and a small set of admin config/detail pages — standardising the heading scale (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).

Acceptance criteria

  • A shared 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.
  • The shared shell has a Default Storybook story in the @sustentus/ui Storybook.
  • Demo's DetailPageShell re-implements over the shared shell (thin persona→props wrapper); demo detail pages render unchanged from today (no visual regression).
  • The named web detail pages (customer activity, customer invoices, the vendor detail pages, the named admin config/detail pages) use the shared shell; their headers now match demo's heading scale (xl/bold) and back-link convention (ArrowLeft + "Back to <destination>" pointing at the persona's dashboard home).
  • Those web pages render with no content loss — only header normalisation (title, back-link, icon, description); the page body/data below the header is unchanged.
  • pnpm lint, pnpm format:check and the web + storybook builds pass.

Out of scope

  • Per-page content fixes on any detail page (vendor/customer/admin parity stubs own those).
  • Adopting the shell on web detail pages beyond the named foundation set (later persona-parity stubs).
  • Metric-tile / Badge / Card reconciliation (design-system-reconciliation stub 1).
  • Demo colour-token cleanup (demo-token-cleanup stub 3).
  • Any change to web routing or the persona/role model.

Open questions

  • none. (Two decisions were offered to the user and left to the stub's stated defaults: shell lives in @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.md

Build notes: detail-page-shell-web

  • commits: feat: detail-page-shell-web — shared DetailPageShell + web/demo/storybook adoption

What changed

  • packages/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.
  • Web adoption (foundation set — header normalisation only, no body/content change):
    • apps/web/app/(app)/customer/{activity,invoices}/page.tsx
    • apps/web/app/(app)/vendor/{retention,renewals,delivery,funnel,risk,pipeline,health,csat}/page.tsx
    • apps/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".

Acceptance criteria status

  • Shared 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 childrenpackages/ui/src/compound/detail-page-shell.tsx.
  • Default Storybook story in the @sustentus/ui Storybook — apps/storybook/src/compound/DetailPageShell.stories.tsx.
  • Demo's 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).
  • Named web detail pages use the shared shell — customer activity/invoices, the eight vendor metric detail pages, and the named admin config/detail pages (setup, integrations, invite); headers now xl/bold with the ArrowLeft + "Back to <destination>" convention.
  • Those web pages render with no content loss — only the header block was swapped; the body/data below is byte-for-byte the same children.
  • pnpm lint, pnpm format:check, web + storybook builds — delegated to CI + Vercel preview.

Verify result

  • mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here. Release reads them back from the PR's check runs.

Post-open fixes

  • Merge conflict with 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.
  • RSC server→client boundary bug (demo deploy failed; web detail pages hit the Next.js error page). @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.

Notes for review

  • The shared shell deliberately uses a plain <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).
  • Web icon accents keep their existing raw-palette classes (e.g. 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.md

Detail pages now share one consistent header across the platform

Who 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.md

Release: detail-page-shell-web

  • pr: #700 (https://github.com/sustentus/sustentus/pull/700) · merged: pending (Ready-to-merge ticked)
  • CI: green — Quality Project (lint · typecheck · format) success, preview DB migration success, all Vercel deploys (web, demo, marketing, storybook) Ready
  • technical docs: apps/docs/app/technical/packages/ui/page.mdx — added DetailPageShell to the component catalog (new exported compound component)
  • business docs: no business docs impact — header normalisation only; no persona capability, route, or journey step changed
  • release notes: investor-only — no end-user changelog (visual consistency refinement, no capability or data change). Investor draft in this PR.
  • sent: pending (filled after merge)

Review summary

  • Claude Code Review CI action is disabled (ENABLE_CLAUDE_REVIEW off) → job skipped. Reviewed the diff against CONVENTIONS.md here instead.
  • Mechanical conventions (arrow functions, type not interface, curly, named imports, no-unused) verified by green Quality CI (pnpm lint --max-warnings=45, pnpm typecheck, pnpm format:check).
  • Copy/style: sentence-case titles and back-links; Typography used for all text; the shared shell uses only semantic tokens (text-muted-foreground/text-foreground).
  • One real defect found and fixed during Build follow-up: the shared shell is a Client Component (@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.
  • No open review threads. No cleanup outstanding.

Acceptance check (vs spec)

  • Shared 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).
  • Demo shell re-implements over the shared shell (thin persona→props wrapper); demo detail pages unchanged (demo deploy Ready, no visual regression).
  • Named web detail pages (customer activity/invoices, the 8 vendor metric pages, admin setup/integrations/invite) use the shared shell; headers now xl/bold with the ArrowLeft + "Back to <destination>" convention.
  • No content loss — only the header block changed; body/data below is the same children.
  • pnpm lint, pnpm format:check, web + storybook builds pass — green in CI + Vercel previews.