{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "section-boundary",
  "type": "registry:ui",
  "title": "Section Boundary",
  "description": "The \"stream per section\" primitive. Fuses React Suspense + a class-based ErrorBoundary into one component so a template can render section-by-section with per-section skeleton + per-section error fallback. The page paints whatever's ready; slow / failed…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "foundation",
    "primitive"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/skeleton.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/section-boundary.tsx",
      "target": "components/ui/section-boundary.tsx",
      "type": "registry:ui",
      "content": "'use client';\n\nimport * as React from 'react';\n\n// @interlace/section-boundary v1.0.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/section-boundary\n// What changed since: https://ds.interlace.tools/c/section-boundary#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — SectionBoundary\n *\n * The \"stream per section\" primitive. Fuses React Suspense + a class-based\n * ErrorBoundary into one component so a template can render\n * section-by-section with per-section skeleton + per-section error\n * fallback. The page paints whatever's ready; slow / failed sections\n * degrade in place without blocking the rest.\n *\n *   <ArticleTemplate>\n *     <SectionBoundary name=\"article-header\">       ← header streams\n *       <ArticleHeader articleId={id} />            ← async RSC inside\n *     </SectionBoundary>\n *     <SectionBoundary name=\"article-body\">         ← body streams\n *       <ArticleBody articleId={id} />              ← async RSC inside\n *     </SectionBoundary>\n *     <SectionBoundary name=\"article-related\">      ← related streams\n *       <RelatedPosts articleId={id} />             ← async RSC inside\n *     </SectionBoundary>\n *   </ArticleTemplate>\n *\n * Each `<SectionBoundary>` declares ITS OWN suspense + error boundary so\n * a slow `<RelatedPosts>` doesn't block `<ArticleBody>` from painting.\n * Without this, React promotes the suspense up to the nearest ancestor\n * boundary — typically the page root — and the entire page goes blank\n * until the slowest data source resolves.\n *\n * ## Anatomy\n *\n *   <section data-slot=\"section-boundary\" data-name=\"…\" data-min-viewport=\"320\">\n *     <Suspense fallback={skeleton}>\n *       <ErrorBoundary fallback={error}>\n *         {children}\n *       </ErrorBoundary>\n *     </Suspense>\n *   </section>\n *\n * ## MIN_VIEWPORT — 320\n *\n * Inherits whatever children render; the section wrapper itself is just a\n * flexible block container with the standard `data-min-viewport`.\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends native el                | `React.ComponentProps<'section'>` + section-boundary props  |\n * | R6   | data-slot on root                | `data-slot=\"section-boundary\"` + `data-name`                |\n * | R7   | className merged + ...rest       | `cn(className)` + `{...props}` on <section>                 |\n * | R8   | No `isXxx`; required `name`      | `name` required (telemetry surface) — not a boolean         |\n * | R10  | Composition seam (fallbacks)     | `skeleton` + `error` slots accept any ReactNode             |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}` + exported const |\n * | R18  | Tailwind only                    | Zero inline `style`; cn() pass-through                      |\n * | R19  | Tokens only                      | (n/a — primitive renders no visual chrome itself)           |\n * | R20  | AA contrast                      | (delegated to fallback children — Skeleton + ErrorState own AA) |\n * | R25  | Client component                 | Suspense + ErrorBoundary require client tier                |\n * | R26  | A11y                             | role=\"region\" + aria-label={name} so screen readers can land on each section |\n *\n * Out of scope: this primitive does NOT manage retries, telemetry,\n * timeout-driven fallbacks, or progressive enhancement of failed sections.\n * Those belong in a sibling `<RetryBoundary>` / consumer-app instrumentation.\n */\n\nimport { cn } from '@/lib/utils';\nimport { Skeleton, type SkeletonVariant } from '@/components/ui/skeleton';\n\nexport const MIN_VIEWPORT = 320 as const;\n\ninterface SectionBoundaryProps extends React.ComponentProps<'section'> {\n  /**\n   * Telemetry-grade name for this section (\"article-header\",\n   * \"registry-item-variants\"). Required so error reporters / Sentry\n   * breadcrumbs / a11y screen-reader announcements have a stable handle.\n   *\n   * Also projected to the DOM as `data-name` so playwright E2E + manual\n   * QA can `await page.locator('[data-slot=\"section-boundary\"][data-name=\"article-header\"]')`\n   * without coupling to className.\n   */\n  name: string;\n  /**\n   * Loading-state UI surfaced while children suspend. Defaults to a\n   * generic full-width `<Skeleton variant=\"card\" />`. Pass a\n   * shape-matched variant via `skeletonVariant` for a one-prop swap, or\n   * pass an arbitrary ReactNode via `skeleton` for full control.\n   */\n  skeleton?: React.ReactNode;\n  /** Shortcut for the common case — picks a `<Skeleton variant>` shape. */\n  skeletonVariant?: SkeletonVariant;\n  /**\n   * Error-state UI surfaced when children (or any descendant) throws.\n   * Defaults to a minimal `<p role=\"alert\">Section failed to load.</p>`\n   * styled with `text-destructive`. Pass a React node — including a\n   * `<button onClick={retry}>` — to give the user a recovery path.\n   *\n   * NOTE: rendering a different fallback after recovery requires a\n   * remount (the boundary's error state is one-shot). For retry-on-click\n   * patterns, wrap children in a key-based forced remount.\n   */\n  error?: React.ReactNode;\n  children: React.ReactNode;\n}\n\nfunction SectionBoundary({\n  name,\n  skeleton,\n  skeletonVariant,\n  error,\n  children,\n  className,\n  ...props\n}: SectionBoundaryProps) {\n  const fallback =\n    skeleton ?? <Skeleton variant={skeletonVariant ?? 'card'} />;\n  const errorFallback = error ?? (\n    <p\n      role=\"alert\"\n      className=\"text-destructive font-body text-ui p-md\"\n    >\n      Section failed to load.\n    </p>\n  );\n\n  return (\n    <section\n      data-slot=\"section-boundary\"\n      data-name={name}\n      data-min-viewport={String(MIN_VIEWPORT)}\n      role=\"region\"\n      aria-label={name}\n      className={cn('contents', className)}\n      {...props}\n    >\n      <SectionErrorBoundary fallback={errorFallback} name={name}>\n        <React.Suspense fallback={fallback}>{children}</React.Suspense>\n      </SectionErrorBoundary>\n    </section>\n  );\n}\nSectionBoundary.displayName = 'SectionBoundary';\n\n/* ─────────────────────────────────────────────────────────────────\n * SectionErrorBoundary — a minimal class-based boundary scoped to the\n * SectionBoundary primitive. React 19 still has no functional error\n * boundary (componentDidCatch is class-only); we inline a private one\n * here rather than pulling react-error-boundary as a dep because we\n * own a fixed UX (single fallback, no retry slot — that's a follow-up\n * primitive). Class + small surface = no upkeep cost.\n * ──────────────────────────────────────────────────────────────── */\ninterface SectionErrorBoundaryProps {\n  fallback: React.ReactNode;\n  name: string;\n  children: React.ReactNode;\n}\n\ninterface SectionErrorBoundaryState {\n  hasError: boolean;\n}\n\nclass SectionErrorBoundary extends React.Component<\n  SectionErrorBoundaryProps,\n  SectionErrorBoundaryState\n> {\n  state: SectionErrorBoundaryState = { hasError: false };\n\n  static getDerivedStateFromError(): SectionErrorBoundaryState {\n    return { hasError: true };\n  }\n\n  componentDidCatch(error: Error, info: React.ErrorInfo): void {\n    // Surface for consumer telemetry — leave a breadcrumb that's\n    // greppable. Consumers wanting structured reporting can wrap their\n    // app in their own ErrorBoundary higher up; our boundary doesn't\n    // swallow the throw, the React error reporting still fires too.\n    // eslint-disable-next-line no-console\n    console.error(\n      `[SectionBoundary \"${this.props.name}\"] section failed to render`,\n      error,\n      info.componentStack,\n    );\n  }\n\n  render(): React.ReactNode {\n    if (this.state.hasError) return this.props.fallback;\n    return this.props.children;\n  }\n}\n\nexport { SectionBoundary };\nexport type { SectionBoundaryProps };\n"
    }
  ],
  "meta": {
    "tier": "primitive",
    "client": true,
    "minViewport": 320,
    "loading": false,
    "version": "1.0.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/section-boundary\n\nInstalled to `components/ui/section-boundary.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/section-boundary';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/section-boundary\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
