{
  "example": "import { Button, Logo, Text } from \"@godxjp/ui/general\";\nimport { AppShell, ErrorSurface, PageContainer, Sidebar } from \"@godxjp/ui/layout\";\n\n// 403 — APPLICATION mode: the body inside the shell the route ALREADY renders.\n// The sidebar/topbar/breadcrumb are preserved; the surface builds no chrome.\nexport function ForbiddenPage() {\n  return (\n    <AppShell sidebar={<Sidebar activeId=\"reports\" sections={sections} />}>\n      <PageContainer title=\"レポート\" breadcrumb={[{ label: \"ホーム\", to: \"/\" }, { label: \"レポート\" }]}>\n        <ErrorSurface\n          mode=\"application\"\n          status={403}\n          title={t(\"errors.403.title\")}\n          description={t(\"errors.403.description\")}\n          permission=\"reports.view\"\n          organization=\"株式会社ゴッドエックス\"\n          action={<Button onClick={goHome}>{t(\"errors.backHome\")}</Button>}\n        />\n      </PageContainer>\n    </AppShell>\n  );\n}\n\n// 503 — SYSTEM mode: the surface owns the page. No className, no min-h-dvh, no media query.\nexport function MaintenancePage() {\n  return (\n    <ErrorSurface\n      mode=\"system\"\n      status={503}\n      brand={<Logo glyph=\"G\" />}\n      title={t(\"errors.503.title\")}\n      description={t(\"errors.503.description\")}\n      maintenance={{\n        start: \"2026-08-02T18:00:00Z\",   // ISO-8601 instant, server-sent\n        end: \"2026-08-02T20:00:00Z\",\n        timeZone: \"Asia/Tokyo\",           // IANA — explicit, so SSR and client agree\n        progress: 40,                     // 0-100, server-sent (never from the client clock)\n      }}\n      footer={<Text size=\"xs\" tone=\"muted\">2026 GodX</Text>}\n      action={<Button onClick={reload}>{t(\"errors.reload\")}</Button>}\n    />\n  );\n}",
  "group": "layout",
  "importPath": "@godxjp/ui/layout",
  "name": "ErrorSurface",
  "props": [
    {
      "description": "The shell contract, not a skin. \"application\" (400/403/404) renders ONLY the surface block — put it in AppShell's children (usually inside a PageContainer) so the sidebar/topbar/breadcrumb stay mounted; it cannot build chrome, because nav data and the user menu are consumer-owned. \"system\" (500/503) renders the whole page: CenteredShell align=\"center\", so no consumer min-h-dvh / flex class / media query.",
      "name": "mode",
      "required": true,
      "type": "\"application\" | \"system\""
    },
    {
      "description": "The HTTP status (a NUMBER, not a string). It is the input that drives the default icon (TriangleAlert 400 · ShieldAlert 403 · SearchX 404 · ServerCrash 500 · Wrench 503) and tone (warning 400/403/503 · muted 404 · destructive 500). Also rendered as the compact tabular status code, announced as 'HTTP status 403' rather than the cardinal number. 400 is the malformed-request page — a route reached with parameters the server refuses to interpret (a bad id, a missing launch parameter) — and belongs in mode=\"application\" like 403/404.",
      "name": "status",
      "required": true,
      "type": "400 | 403 | 404 | 500 | 503"
    },
    {
      "description": "Headline. Consumer-owned copy from the APP's own t() — @godxjp/ui ships no product text.",
      "name": "title",
      "required": true,
      "type": "ReactNode"
    },
    {
      "description": "The ONE recovery action (a Button, or Button asChild wrapping a router Link). A single slot IS the enforcement: pass a fragment with two buttons and only the first renders, with a development-time console error. Support contact goes in `description`, never in a second CTA.",
      "name": "action",
      "required": true,
      "type": "ReactNode"
    },
    {
      "description": "Supporting sentence under the title. Put support-contact guidance here. Its measure is owned by --empty-state-description-max-width.",
      "name": "description",
      "type": "ReactNode"
    },
    {
      "description": "Support correlation id, rendered as a <dt>/<dd> metadata row with a localized 'Request ID' label and a mono/tabular value so it can be read out or copied accurately. Pass the bare id — never write it into `description` as prose.",
      "name": "requestId",
      "type": "string"
    },
    {
      "description": "The permission/role the viewer is missing (403). Pass the bare permission name ('reports.view'); the localized 'Required permission' label is the surface's.",
      "name": "permission",
      "type": "ReactNode"
    },
    {
      "description": "The organization/tenant the failed request was scoped to. Together with `permission` this is what distinguishes a wrong-workspace 403 from a missing-role 403.",
      "name": "organization",
      "type": "ReactNode"
    },
    {
      "description": "Planned-outage timing (503). `start`/`end` are ISO-8601 INSTANTS and `timeZone` an IANA id: the surface formats them with Intl.DateTimeFormat(locale).formatRange() and keeps the ISO value in <time dateTime>. NEVER pass a pre-formatted '18:00 - 20:00 JST' string. `progress` (0-100) adds a labelled Progress meter named via Intl.NumberFormat percent style; it is SERVER-SENT on purpose — deriving it from the client clock breaks SSR hydration.",
      "name": "maintenance",
      "type": "{ start: string; end?: string; timeZone?: string; progress?: number }"
    },
    {
      "description": "Override the status-derived icon. Use only when the product has a truer glyph for the failure, never to change perceived severity.",
      "name": "icon",
      "type": "ComponentType<{ className?: string }>"
    },
    {
      "description": "Override the status-derived tone (same union as EmptyState.tone). Defaults: 400/403/503 warning · 404 muted · 500 destructive.",
      "name": "tone",
      "type": "\"muted\" | \"info\" | \"success\" | \"warning\" | \"destructive\""
    },
    {
      "description": "Semantic heading level of `title`. Defaults to 2 in application mode (a PageContainer h1 sits above) and 1 in system mode (the surface IS the page). Choose it to keep the outline valid, never for size.",
      "name": "titleLevel",
      "type": "1 | 2 | 3 | 4"
    },
    {
      "description": "system mode ONLY — brand slot above the status code (a Logo). Ignored in application mode, where the shell already shows the brand.",
      "name": "brand",
      "type": "ReactNode"
    },
    {
      "description": "system mode ONLY — the page footer (contentinfo): copyright, status page link, locale switch.",
      "name": "footer",
      "type": "ReactNode"
    },
    {
      "defaultValue": "\"sm\"",
      "description": "system mode ONLY — measure of the centred column (the CenteredShell width tier).",
      "name": "width",
      "type": "\"sm\" | \"md\" | \"lg\""
    },
    {
      "description": "Root element id.",
      "name": "id",
      "type": "string"
    },
    {
      "description": "Root class override (rarely needed).",
      "name": "className",
      "type": "string"
    }
  ],
  "related": [
    "CenteredShell — the viewport-centred page shell that ErrorSurface renders internally for mode=\"system\". Use it directly only for a standalone surface that is NOT one of the four HTTP statuses.",
    "AppShell + PageContainer — what you wrap around a mode=\"application\" surface. ErrorSurface never builds them: nav sections and the user menu are consumer-owned data.",
    "EmptyState — the zero-state primitive ErrorSurface composes for its icon/title/description/action body. Use it directly for an empty LIST or an empty section, not for an HTTP exception page.",
    "AlertQueryError / humanError — inline, in-page query failure feedback. ErrorSurface is the whole PAGE; an inline alert is the right pick when the surrounding page still works.",
    "Progress — the meter ErrorSurface renders for maintenance.progress."
  ],
  "rules": [
    23,
    24
  ],
  "storyPath": "layout/error-surface/index.tsx",
  "tagline": "Package-owned semantic exception surface for 400 / 403 / 404 / 500 / 503. `mode` is the SHELL CONTRACT: \"application\" renders the body you put inside the AppShell the route already provides (chrome PRESERVED, never reconstructed); \"system\" owns the whole page via CenteredShell align=\"center\" (package-owned 1440/1024/390 geometry). Exactly one recovery action, plus semantic request-id / permission / organization / maintenance metadata slots.",
  "usage": [
    "DO use ErrorSurface for ANY 400 / 403 / 404 / 500 / 503 page. It is a real import from @godxjp/ui/layout — never hand-compose an error page from the auth CenteredShell + a generic Card, and never add a consumer-local `.canonical-auth-card`-style class (that workaround IS the regression).",
    "DO put mode=\"application\" INSIDE the shell the route already renders: <AppShell …><PageContainer …><ErrorSurface mode=\"application\" …/></PageContainer></AppShell>. The surface returns only its own block on purpose, so the sidebar/topbar/breadcrumb survive and the user can navigate away.",
    "DO use mode=\"system\" for 500/503 and pass NOTHING for geometry — the surface renders CenteredShell align=\"center\" itself. A className=\"min-h-dvh flex items-center\" is always wrong.",
    "DO pass ONE action. A second CTA is dropped with a development error; put 'contact support' in `description`.",
    "DO pass `maintenance` as ISO-8601 instants + an IANA timeZone and let Intl format it. A hand-built window string cannot localize, and a client-derived `progress` breaks SSR hydration.",
    "DO let `status` pick the icon and tone. Override them only for a truer product glyph — never to recolour a status into a different severity.",
    "DO NOT retune the geometry with a className: --error-surface-max-width, --error-surface-gap, --error-surface-padding-block(-compact), --error-surface-meta-* and --error-surface-progress-max-width are the knobs. The metadata divider defaults to `none` (rule #44); opt in with --error-surface-meta-border.",
    "DO NOT write the request id, the missing permission or the maintenance window into `description` as prose — each is a semantic <dt>/<dd> slot, and prose loses the label↔value relationship for a screen reader.",
    "DO NOT use the auth shape for an error page: it is the UNAUTHENTICATED root and imposes auth-card geometry."
  ],
  "useCases": [
    "Inertia/Laravel exception page (SCR-006): one Error.tsx receives `status` from Handler::render() and renders <ErrorSurface mode={status >= 500 ? \"system\" : \"application\"} status={status} … />, with Error.layout keeping the authenticated shell for 403/404 only.",
    "Forbidden report inside the console: <ErrorSurface mode=\"application\" status={403} permission=\"reports.view\" organization=\"Acme KK\" action={<Button asChild><Link href=\"/\">Back</Link></Button>} /> inside the existing AppShell + PageContainer.",
    "Planned maintenance page: <ErrorSurface mode=\"system\" status={503} maintenance={{ start, end, timeZone: \"Asia/Tokyo\", progress: 40 }} brand={<Logo glyph=\"G\" />} footer={…} />.",
    "Unexpected failure with a support correlation id: <ErrorSurface mode=\"system\" status={500} requestId=\"01J9Z0…\" action={<Button onClick={reload}>Reload</Button>} />."
  ]
}
