ADR-0095: One DetailPage host owns the detail-page skeleton

Status: Accepted (2026-07-16) · Owner: Gera Plan: docs/planning/detail-page-host.md · Recipe: docs/architecture/detail-page.md

Context

Every entity detail page (work order, renewal, tenant, vendor, unit, prospect, turnover) shares one skeleton: a PageBody, a BackLink above the header, the standard 2/3 + 1/3 grid, and loading/error twins. Before Wave 2, each page hand-rolled that skeleton (~5,200 lines across six clients), so the loading twin could — and did — drift from the loaded view (stale labels, destination flashes, an aria live region wrapping the back link). Wave 1 (back-button-body-standard.md) standardized the affordance; the skeleton itself remained N implementations.

Decision

src/components/feature/detail/DetailPage.tsx is THE host every detail page branches from ("a work-order detail page is a branch of DetailPage" — Gera). It owns: the back affordance (full BackLink contract, label rule enforced by the primitive), the header slot, the optional-sidebar grid, and the loading/error branches rendered from the SAME props as the loaded view — so loading-twin parity is structural, not a convention a guard has to chase. Per-page ghost customization goes through ghost slots, never a fork. DetailPageShell was renamed/reshaped into it (no shim, no parallel path). Pages whose layout is genuinely not a detail skeleton (the property HUB page) do not adopt — forced uniformity is drift in the other direction.

Alternatives considered

Consequences