{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-QPAPUIF3.cjs","../src/components/related-content/related-content-section.tsx","../src/components/related-content/card-registry.tsx"],"names":["jsx","buildListUrl"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-QPAPUIF3.cjs","sourcesContent":[null,"'use client';\n\n/**\n * RelatedContentSection\n *\n * Renders content references grouped by type using the canonical card\n * components. MOVED from the hub (`components/shared/related-content-card.tsx`)\n * so any consuming app can embed it; the hub keeps a thin wrapper that\n * pre-binds its host-specific injections (nav hook, URL recomposition,\n * program configs, admin campaign card).\n *\n * THREE data modes (precedence top-down):\n *   1. CONTROLLED — `contentRefs` provided (even `[]`): render exactly those\n *      refs, no suggestion fetch (the original investor-update behavior).\n *   2. SUGGESTION — `entityType` + `entityId` provided: self-fetch\n *      `GET {apiBaseUrl}/api/related-content?entityType&entityId[&count][&excludeTypes]`\n *      (the generic 5-tier engine's second web service). `minResults` maps to\n *      `count`; absent → param not sent (server default applies). Each ref\n *      carries a `reason` (data-only — never rendered, matching the\n *      FaqSection/FaqWithReason precedent).\n *   3. SSR-HYDRATED suggestion — also pass `initialItems` (the server page\n *      called the engine directly); the first client fetch is skipped per the\n *      `useSelfFetch` initialData contract.\n *\n * Group layout (list vs grid) + card size (lg vs default) come from\n * `CONTENT_REF_GROUPS` in `../../utils/content-ref-groups` — single source of\n * truth, no per-type logic in this file. Skeletons come from\n * `renderSkeletonForType` so the placeholder height matches the loaded card\n * exactly (zero layout shift on resolve).\n *\n * One API call per content type via the shared list-URL builder\n * (`buildListUrl` — injectable; defaults to the lib's byte-parity-tested\n * builder prefixed with `apiBaseUrl`). Fetching uses `useSelfFetch` (plain\n * fetch, NO react-query) so third-party embedders need no QueryClientProvider;\n * cards are imported via DEEP module paths (not the chat barrel) so this\n * chunk never reaches `@tanstack/react-query`.\n *\n * The per-type card + skeleton dispatch lives in `./card-registry`\n * (`RELATED_CARD_REGISTRY`): ONE entry per content type, each carrying that\n * type's own row type and the `unknown` → row decode, so a fetched row reaches\n * its card concretely typed. This file only picks the entry and builds the\n * shared render context.\n *\n * LOCKSTEP NOTE: that registry is the SIZED sibling of the chat-side\n * `CHAT_CARD_REGISTRY` (`../chat/entity-cards/dispatch.tsx`), which renders\n * compact `size='sm'` cards wired to the chat runtime. Two dispatchers by\n * design — when registering a new fetch-mode content type, add it BOTH there\n * and in `./card-registry` (card + skeleton + list URL).\n */\n\nimport type React from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useSelfFetch } from '../../hooks/use-self-fetch';\nimport type { ContentRef, ContentRefWithReason } from '../../types/content-ref';\nimport {\n  CONTENT_REF_GROUPS,\n  getContentRefLabelOrTitleCase,\n  orderContentRefTypes,\n  type ContentRefGroupConfig,\n} from '../../utils/content-ref-groups';\nimport { extractItems, extractItemId } from '../../utils/extract-items';\nimport { buildListUrl as libBuildListUrl, canonicalContentRefType } from '../../utils/list-url';\nimport { pageCount } from '../../utils/search-params';\nimport { buildSuggestionUrl } from '../../utils/suggestion-url';\n// Type-only — erased at build, no runtime dependency on the dispatch module.\nimport type { ChatCardDispatchExtras } from '../chat/entity-cards/dispatch';\nimport { decideNewTab } from '../chat/utils/decide-new-tab';\nimport { Pagination } from '../pagination';\n// The per-type card dispatch — one registry entry per content type, each\n// carrying its OWN row type — plus the DEEP card imports that keep this\n// chunk's source graph free of @tanstack/react-query (the `../chat` barrel\n// reaches it via embeddable-chat; deep paths don't). `CardSize` and\n// `CardLinkAnchorProps` are declared alongside the registry, its primary\n// consumer, and re-exported below so the subpath barrel is unchanged.\nimport {\n  RELATED_CARD_REGISTRY,\n  rowString,\n  type CardLinkAnchorProps,\n  type CardSize,\n  type RelatedCardRegistryEntry,\n} from './card-registry';\n\nexport type { CardLinkAnchorProps };\n\n/** Render-prop component injection for the navigation decision — keeps hook\n *  calls legal (hooks live INSIDE the injected component; `CardForType`\n *  itself calls zero hooks). The hub injects a `useNavLink`-backed provider;\n *  the default is hook-free (pure `decideNewTab`). MUST be defined at module\n *  scope by hosts — an inline arrow would remount every card each render. */\nexport interface CardLinkProviderProps {\n  href: string | null;\n  targetPlatform: string | null;\n  children: (linkProps: CardLinkAnchorProps | null) => React.ReactElement | null;\n}\nexport type CardLinkProvider = React.ComponentType<CardLinkProviderProps>;\n\n/** Default link provider for standalone embeds: relative/-same-origin hrefs\n *  stay same-tab, cross-origin pops a new tab (pure `decideNewTab` with no\n *  platform context — `currentSource: ''` falls through to the origin\n *  check). No router integration, no hooks. */\nfunction DefaultLinkPropsProvider({\n  href,\n  targetPlatform,\n  children,\n}: CardLinkProviderProps): React.ReactElement | null {\n  if (!href) return children(null);\n  const newTab = decideNewTab({ href, targetPlatform, currentSource: '' });\n  return children(newTab ? { href, target: '_blank', rel: 'noopener noreferrer' } : { href });\n}\n\n/** Default href resolution: trust the ref's stored url/targetPlatform as the\n *  API composed them. The hub overrides this with its `buildContentURL`\n *  re-composition so dev gets localhost and prod gets platform domains. */\nfunction defaultResolveHref(ref: ContentRef): { href: string | null; targetPlatform: string | null } {\n  return { href: ref.url || null, targetPlatform: ref.targetPlatform ?? null };\n}\n\n/** Host-injected renderer pair for the admin-only `marketing_campaign` type.\n *  Absent (every non-hub embed) → the type renders nothing (its list URL\n *  hits `/api/admin`, unreachable outside the hub anyway). */\nexport interface AdminCampaignCardSlot {\n  /**\n   * The host's own campaign card. Its `campaign` prop is a HOST row type this\n   * lib cannot name (the hub's `MarketingCampaign`), and React props are\n   * CONTRAVARIANT, so this is the one seam in the rail with no honest type.\n   * All three alternatives were compiled against the hub's real\n   * `CampaignCardAdmin`:\n   *\n   *   - `unknown` — the host can no longer inject: TS2322, \"Type\n   *     '{ campaign: unknown }' is not assignable to\n   *     '{ campaign: MarketingCampaign }'\".\n   *   - `never` — every host component assigns, but the rail can no longer\n   *     hand it a row: TS2769, \"Type 'unknown' is not assignable to 'never'\".\n   *   - a generic on the slot's OWNER (`AdminCampaignCardSlot<TCampaign>`\n   *     threaded through `RelatedContentSectionProps`) — inference from the\n   *     host's card works, but the render still fails: TS2769, \"'TCampaign'\n   *     could be instantiated with an arbitrary type\". The rail holds an\n   *     unvalidated row; only the HOST knows how to narrow it.\n   *\n   * Typing this means changing the injection contract (a host-supplied\n   * `render(row: unknown)` closure instead of a component), which is a\n   * breaking change for every embedder that passes `{ Card, Skeleton }`.\n   * Everything the rail renders ITSELF now goes through the per-type\n   * `RELATED_CARD_REGISTRY`; this is the only `any` left.\n   */\n  Card: React.ComponentType<{ campaign: any }>;\n  Skeleton: React.ComponentType<{ size?: 'default' | 'sm' }>;\n}\n\n/**\n * Per-type skeleton dispatch — returns the SAME colocated skeleton the\n * resolved card renders, sized to match (zero layout shift on resolve). One\n * lookup through `RELATED_CARD_REGISTRY`, exactly the way the chat-side\n * `CHAT_CARD_REGISTRY` does it via `entry.skeleton()`.\n *\n * `marketing_campaign` is the one type with no registry entry: BOTH its card\n * and its skeleton are host-injected (`AdminCampaignCardSlot`), so there is no\n * lib component to register.\n */\nfunction renderSkeletonForType(\n  type: string,\n  size: CardSize,\n  adminCampaignCard?: AdminCampaignCardSlot,\n): React.ReactNode {\n  if (type === 'marketing_campaign') {\n    return adminCampaignCard ? <adminCampaignCard.Skeleton size={size === 'sm' ? 'sm' : 'default'} /> : null;\n  }\n  const entry: RelatedCardRegistryEntry | undefined = RELATED_CARD_REGISTRY[type];\n  return entry ? entry.skeleton(size) : null;\n}\n\n/**\n * Per-type card dispatch — ONE lookup through `RELATED_CARD_REGISTRY`, whose\n * entries each pair a skeleton with a card renderer that closes over that\n * type's OWN row type. Sized cards (`'lg'` / `'default'`) are unique to this\n * rail; the chat dispatcher only renders `'sm'`.\n *\n * PURE FUNCTION COMPONENT WITH ZERO HOOK CALLS: the placeholder comes from a\n * plain `extras.buildOgPlaceholderUrl` call (the chat `dispatch.tsx`\n * pattern) and the anchor-prop bundle arrives via the `LinkProvider`\n * render-prop from the parent — so per-card hook legality is owned by the\n * injected provider component, not by this dispatcher.\n *\n * `href` comes from the host's `resolveHref(ref)` (hub: live\n * `buildContentURL` recomposition; default: the ref's stored url).\n */\nfunction CardForType({\n  type,\n  item,\n  size,\n  href,\n  targetPlatform,\n  linkProps,\n  extras,\n  adminCampaignCard,\n}: {\n  type: string;\n  /** One fetched row, still unvalidated. Its shape is decided at RUNTIME by\n   *  `type` (each group fetches from its own per-type list API), so it stays\n   *  `unknown` right up to the registry entry for that type — which owns the\n   *  `unknown` → row-type decode and hands its card a concretely typed row. */\n  item: unknown;\n  size: CardSize;\n  href: string;\n  targetPlatform: string | null;\n  linkProps: CardLinkAnchorProps | null;\n  extras?: ChatCardDispatchExtras;\n  adminCampaignCard?: AdminCampaignCardSlot;\n}): React.ReactNode {\n  // Host-injected admin slot — the only type with no registry entry, because\n  // both its card and its skeleton come from the host (see\n  // `AdminCampaignCardSlot`). Handled before the lookup so the registry stays\n  // a pure lib-card table.\n  if (type === 'marketing_campaign') {\n    return adminCampaignCard ? <adminCampaignCard.Card campaign={item} /> : null;\n  }\n\n  const entry: RelatedCardRegistryEntry | undefined = RELATED_CARD_REGISTRY[type];\n  if (!entry) return null;\n\n  return entry.render(item, {\n    size,\n    // Most card variants accept only `{default, sm}`. `'lg'` collapses to\n    // `'default'` for those. ProductReleaseCard uses its own lg/sm pair.\n    legacySize: size === 'sm' ? 'sm' : 'default',\n    href,\n    targetPlatform,\n    linkProps,\n    // Top-level target/rel for cards that take them as separate props\n    // (BlogCard, CaseStudyCard, …). ProductReleaseCard takes the bundle as a\n    // single `anchorProps={...}` and uses `linkProps` directly. When the host\n    // didn't surface a URL, `linkProps` is null and the card stays in\n    // non-anchor mode.\n    anchorAttrs: linkProps ? { target: linkProps.target, rel: linkProps.rel } : {},\n    // OG placeholder URL — injected into the pure-presentation cards so they\n    // render a branded fallback when the row's featured image is null. Plain\n    // function call (NOT a hook). Title is the universal field across all card\n    // row shapes used here.\n    // `item?.title as string | undefined` claimed a string without checking one:\n    // a numeric title reached `buildOgPlaceholderUrl` as a number. `rowString`\n    // makes the claim true.\n    placeholderUrl: extras?.buildOgPlaceholderUrl?.(rowString(item, 'title') ?? '') ?? undefined,\n    extras,\n  });\n}\n\n// =============================================================================\n// Fetch all items for a type in ONE server-sorted call, via the injectable\n// list-URL builder. `useSelfFetch` (URL = cache key) replaces the hub's old\n// react-query usage: `enabled` ≙ `url === null`, `!res.ok`/network error ≙\n// `error → items null → group renders nothing`. Accepted deltas vs\n// react-query: no retry/backoff, no focus refetch, no cross-mount cache.\n// =============================================================================\n\nfunction useGroupItems(type: string, refs: ContentRef[], buildUrl: (type: string, ids: string[]) => string | null) {\n  const ids = refs.map(r => r.id);\n  const url = ids.length > 0 ? buildUrl(type, ids) : null;\n  const { data, isLoading } = useSelfFetch<unknown>(url);\n  const items = data != null ? extractItems(data) : null;\n  return { items, isLoading };\n}\n\n// =============================================================================\n// Per-group renderer — one API call, server-sorted, then render cards via the\n// dispatcher with per-type skeletons + per-type layout from CONTENT_REF_GROUPS.\n// =============================================================================\n\n/** Map columns prop → tailwind grid class. Only consulted for grid-layout\n *  groups; list-layout groups stack vertically. */\nfunction gridClassFor(columns: 2 | 3): string {\n  return columns === 3\n    ? 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6'\n    : 'grid grid-cols-1 sm:grid-cols-2 gap-6';\n}\n\n/** Resolve the group config for a type, falling back to a grid layout with\n *  the default card size for unregistered types so the section still renders\n *  rather than silently dropping them. The `label` field on the fallback is\n *  intentionally a placeholder — the section heading goes through\n *  `getContentRefLabelOrTitleCase(type)` instead so cross-surface labels\n *  stay consistent between this rail and the investor-email builder. */\nfunction resolveGroupConfig(type: string): ContentRefGroupConfig {\n  return (\n    CONTENT_REF_GROUPS[type] ?? {\n      label: type,\n      order: 999,\n      layout: 'grid',\n      gridSize: 'default',\n    }\n  );\n}\n\n/** Items per page within one type group. Groups larger than this paginate\n *  with the standard Pagination control (NO nested scrolling — a bounded\n *  scrollbox inside the page traps wheel events and hides the sections\n *  below it). MUST stay at or above the largest suggestion fill\n *  (RELATED_SAME_TYPE_COUNT in the hub's lib/constants/suggestions.ts) so\n *  current rails never paginate — only genuinely big groups (author pages)\n *  do. Exported through the subpath barrel for the hub's module-load\n *  assertion of that relation (entity-suggestion-sections.tsx). */\nexport const GROUP_PAGE_SIZE = 12;\n\nfunction ContentGroup({\n  type,\n  refs,\n  columns,\n  buildUrl,\n  resolveHref,\n  LinkProvider,\n  extras,\n  adminCampaignCard,\n  heading,\n}: {\n  type: string;\n  refs: ContentRef[];\n  columns: 2 | 3;\n  buildUrl: (type: string, ids: string[]) => string | null;\n  resolveHref: (ref: ContentRef) => { href: string | null; targetPlatform: string | null };\n  LinkProvider: CardLinkProvider;\n  extras?: ChatCardDispatchExtras;\n  adminCampaignCard?: AdminCampaignCardSlot;\n  /** Group heading, rendered INSIDE the group so a group that resolves to\n   *  nothing (fetch miss / unsupported type / missing program config) drops\n   *  its heading too — no orphaned titles. */\n  heading: React.ReactNode;\n}) {\n  const { items, isLoading } = useGroupItems(type, refs, buildUrl);\n  const config = resolveGroupConfig(type);\n  const isListLayout = config.layout === 'list';\n  const cardSize = config.gridSize;\n\n  // Per-group pagination for big groups (author pages): GROUP_PAGE_SIZE items\n  // per page with the standard Pagination control below the group. Client-side\n  // slicing — useGroupItems already fetched every row in one batched call, so\n  // page flips are instant. Hooks live above every early return (file\n  // convention). Page is clamped so a shrinking refs array (suggestion\n  // refetch) can never strand the view past the last page, and RESET when the\n  // ref set actually changes (shrink→grow must not return to a stale page).\n  const [page, setPage] = useState(1);\n  const refsKey = refs.map(r => r.id).join('|');\n  const prevRefsKeyRef = useRef(refsKey);\n  useEffect(() => {\n    if (prevRefsKeyRef.current !== refsKey) {\n      prevRefsKeyRef.current = refsKey;\n      setPage(1);\n    }\n  }, [refsKey]);\n  const totalGroupPages = pageCount(refs.length, GROUP_PAGE_SIZE);\n  const safePage = Math.min(page, totalGroupPages);\n  const visibleGroupRefs =\n    refs.length > GROUP_PAGE_SIZE ? refs.slice((safePage - 1) * GROUP_PAGE_SIZE, safePage * GROUP_PAGE_SIZE) : refs;\n  const groupPagination =\n    totalGroupPages > 1 ? (\n      <Pagination currentPage={safePage} totalPages={totalGroupPages} onPageChange={setPage} />\n    ) : null;\n\n  // Skeleton gate: `isLoading && !items` — SSR HTML and the client's first\n  // paint render identical skeletons (useSelfFetch starts isLoading=true on\n  // both sides), and once items exist they are never replaced by skeletons.\n  if (isLoading && !items) {\n    const skeletons = visibleGroupRefs.map(r => (\n      <div key={r.id}>{renderSkeletonForType(type, cardSize, adminCampaignCard)}</div>\n    ));\n    return (\n      <div className=\"space-y-4\">\n        {heading}\n        {isListLayout ? (\n          <div className=\"space-y-4\">{skeletons}</div>\n        ) : (\n          <div className={gridClassFor(columns)}>{skeletons}</div>\n        )}\n      </div>\n    );\n  }\n\n  if (!items || items.length === 0) return null;\n\n  // Index fetched rows by id, then render in REF order — refs carry the\n  // intended sequence (suggestion mode: the engine's tier order, so\n  // same-platform/tag-matched items lead; controlled mode: the curated\n  // display_order). The list APIs return rows date-sorted, which would\n  // otherwise scramble that ordering (same-platform items sinking below\n  // newer cross-platform ones).\n  // Shared extractor (NOT raw `.id`) — some API shapes key items differently\n  // (e.g. external_id types); raw access would silently drop valid items.\n  const rowKey = (it: unknown): string => {\n    const extracted = extractItemId(type, it);\n    if (extracted !== null) return extracted;\n    // Fallback for shapes `extractItemId` doesn't recognise — same\n    // `String(it?.id)` the untyped version produced.\n    return String(typeof it === 'object' && it !== null ? (it as { id?: unknown }).id : undefined);\n  };\n  const itemById = new Map(items.map(it => [rowKey(it), it]));\n\n  const cards = visibleGroupRefs\n    .map(contentRef => {\n      const itemId = String(contentRef.id);\n      const item = itemById.get(itemId);\n      if (!item) return null;\n      // Re-compose the URL via the host's resolver (hub: buildContentURL so\n      // dev gets localhost and prod the right platform domain; default: the\n      // ref's stored url as the API composed it).\n      const resolved = resolveHref(contentRef);\n      const href = resolved.href ?? '';\n      const targetPlatform = resolved.targetPlatform ?? contentRef.targetPlatform ?? null;\n      return (\n        <div key={itemId}>\n          <LinkProvider href={href || null} targetPlatform={targetPlatform}>\n            {linkProps => (\n              <CardForType\n                type={type}\n                item={item}\n                size={cardSize}\n                href={href}\n                targetPlatform={targetPlatform}\n                linkProps={linkProps}\n                extras={extras}\n                adminCampaignCard={adminCampaignCard}\n              />\n            )}\n          </LinkProvider>\n        </div>\n      );\n    })\n    .filter(Boolean);\n\n  if (cards.length === 0) {\n    // Current PAGE resolved zero cards (rows deleted between the ref fetch\n    // and the group fetch, or a stricter list-API gate dropped them). When a\n    // pager exists the user must keep the controls to navigate back —\n    // dropping the whole group would strand them. A genuinely empty group\n    // (no pager) still vanishes with its heading.\n    if (groupPagination) {\n      return (\n        <div className=\"space-y-4\">\n          {heading}\n          {groupPagination}\n        </div>\n      );\n    }\n    return null;\n  }\n\n  return (\n    <div className=\"space-y-4\">\n      {heading}\n      {isListLayout ? <div className=\"space-y-4\">{cards}</div> : <div className={gridClassFor(columns)}>{cards}</div>}\n      {groupPagination}\n    </div>\n  );\n}\n\n// =============================================================================\n// Main component\n// =============================================================================\n\ninterface RelatedContentResponse {\n  refs: ContentRefWithReason[];\n}\n\nexport interface RelatedContentSectionProps {\n  /** CONTROLLED mode (the original behavior). When defined — even `[]` — no\n   *  suggestion fetch runs and exactly these refs render. */\n  contentRefs?: ContentRef[];\n  /** SUGGESTION mode (with `entityId`): self-fetch suggestions for this host\n   *  entity from `{apiBaseUrl}/api/related-content`. Ignored when\n   *  `contentRefs` is provided. */\n  entityType?: string;\n  entityId?: number | string;\n  /** AUTHOR mode: self-fetch ALL published content authored by this profile\n   *  from `{apiBaseUrl}/api/related-content?authorId=…` (grouped per type,\n   *  endless within each group). Ignored when `contentRefs` is provided;\n   *  takes precedence over the entityType/entityId suggestion scope.\n   *  SSR-hydrate via `initialItems`, same as suggestion mode. */\n  authorId?: string;\n  /** Maps to the suggestion API's `count` param — the PER-TYPE fill target\n   *  for every candidate type EXCEPT the host's own. Absent → param not sent\n   *  (server default applies). */\n  minResults?: number;\n  /** Maps to the suggestion API's `sameTypeCount` param — the budget for the\n   *  candidate type MATCHING the host's own `entityType` (same-type boost:\n   *  a blog post's rail leads with more blog posts). Absent → param not\n   *  sent (host's type uses the server's `count`). */\n  sameTypeMinResults?: number;\n  /** SSR hydrate for suggestion mode — the server page ran the engine and\n   *  drills the refs here; the first client fetch is skipped (useSelfFetch\n   *  initialData contract). */\n  initialItems?: ContentRefWithReason[];\n  /** Section title (default: \"Related Content\") */\n  title?: string;\n  /**\n   * Grid columns at desktop. 2 = denser cards / wider summary (original\n   * investor-update layout); 3 = more cards per row for dashboards.\n   * Only consulted for grid-layout groups. Default: 2.\n   */\n  columns?: 2 | 3;\n  /**\n   * ContentRef.type values to exclude. Honored in ALL modes — controlled\n   * mode post-filters (original behavior); suggestion mode ALSO forwards the\n   * list verbatim as the API's `excludeTypes=` param so excluded types never\n   * consume engine fill slots (`minResults` stays honored). The subtraction\n   * happens SERVER-side — this component never mirrors the hub's candidate\n   * list.\n   */\n  excludeTypes?: string[];\n  /**\n   * SUGGESTION-mode allow-list (rail vocabulary): which content types\n   * participate in this rail. Sent verbatim as the API's `types=` param —\n   * the SERVER intersects it with its own allowed candidate set, and\n   * platform policy gates (e.g. internal-only types) ALWAYS win: the client\n   * cannot request its way past them. Absent → all server-side candidates.\n   */\n  includeTypes?: string[];\n  /** Fetch-URL prefix for third-party embeds / reverse proxies\n   *  ('' = same-origin). Applied to BOTH the suggestion fetch and the\n   *  default per-group list fetches. */\n  apiBaseUrl?: string;\n  /** Host injection bundle — REUSES the chat dispatcher's\n   *  `ChatCardDispatchExtras` (programConfigs, buildOgPlaceholderUrl,\n   *  buildProductReleaseCardProps override). Program groups render nothing\n   *  when their config is absent. */\n  extras?: ChatCardDispatchExtras;\n  /** Hub injects its `buildContentURL` recomposition; default uses the\n   *  ref's stored `url`/`targetPlatform` as the API composed them. */\n  resolveHref?: (ref: ContentRef) => { href: string | null; targetPlatform: string | null };\n  /** Hub injects its registry-driven entity-list-api builder; default = the\n   *  lib's `buildListUrl(type, ids, apiBaseUrl)`. */\n  buildListUrl?: (type: string, ids: string[]) => string | null;\n  /** Hub injects a `useNavLink`-backed render-prop provider; default = pure\n   *  anchor via `decideNewTab`. MUST be a module-scope component. */\n  LinkProvider?: CardLinkProvider;\n  /** Renderer pair for the admin-only `marketing_campaign` type. Absent →\n   *  the type renders nothing. */\n  adminCampaignCard?: AdminCampaignCardSlot;\n  /** When true, render the section shell (title + an empty-state line) even\n   *  with ZERO refs, instead of returning null. Default false (the original\n   *  behavior — empty rail = no shell). Opt-in per host page (e.g. people-hub's\n   *  \"What I Shipped\", where the section should always be present). */\n  showWhenEmpty?: boolean;\n  /** Empty-state copy shown under the title when `showWhenEmpty` and no refs.\n   *  Default: \"No related content yet.\" */\n  emptyStateText?: string;\n  /** Custom empty-state node (e.g. a hub `<EmptyState/>`) rendered under the\n   *  title when `showWhenEmpty` and there are no refs — overrides\n   *  `emptyStateText`. Lets a host match its canonical empty state. */\n  emptyState?: React.ReactNode;\n}\n\nexport function RelatedContentSection({\n  contentRefs,\n  entityType,\n  entityId,\n  authorId,\n  minResults,\n  sameTypeMinResults,\n  includeTypes,\n  initialItems,\n  title = 'Related Content',\n  columns = 2,\n  excludeTypes,\n  apiBaseUrl = '',\n  extras,\n  resolveHref = defaultResolveHref,\n  buildListUrl,\n  LinkProvider = DefaultLinkPropsProvider,\n  adminCampaignCard,\n  showWhenEmpty = false,\n  emptyStateText = 'No related content yet.',\n  emptyState,\n}: RelatedContentSectionProps) {\n  // ── Hooks above EVERY early return (the original `if (!contentRefs.length)\n  // return null` guard moved below them). ──\n\n  // Suggestion-mode fetch URL — null in controlled mode (contentRefs defined,\n  // even []) or when the entity scope is incomplete.\n  // `includeTypes: []` is an explicit \"nothing participates\" — skip the fetch\n  // entirely (an empty-string `types=` param would be dropped by the URL\n  // builder and read server-side as \"all candidates\") AND ignore SSR refs.\n  const suggestionsDisabled = includeTypes?.length === 0;\n  // Shared type-filter params — one spelling for both fetch modes so a future\n  // normalization (trim/dedupe) can't diverge between them.\n  const typeFilterParams = {\n    types: includeTypes !== undefined ? includeTypes.join(',') : undefined,\n    excludeTypes: excludeTypes && excludeTypes.length > 0 ? excludeTypes.join(',') : undefined,\n  };\n  // AUTHOR mode beats suggestion mode: when `authorId` is set the rail lists\n  // everything that profile authored (the server returns ALL, no count).\n  const authorUrl =\n    contentRefs === undefined && authorId && !suggestionsDisabled\n      ? buildSuggestionUrl('/api/related-content', {\n          apiBaseUrl,\n          extraParams: { authorId, ...typeFilterParams },\n        })\n      : null;\n  const suggestUrl =\n    authorUrl ??\n    (contentRefs === undefined &&\n    entityType &&\n    entityId !== undefined &&\n    entityId !== null &&\n    entityId !== '' &&\n    !suggestionsDisabled\n      ? buildSuggestionUrl('/api/related-content', {\n          apiBaseUrl,\n          entityType,\n          entityId,\n          count: minResults,\n          extraParams: {\n            sameTypeCount: sameTypeMinResults !== undefined ? String(sameTypeMinResults) : undefined,\n            ...typeFilterParams,\n          },\n        })\n      : null);\n  // Memoize the initialData wrapper — useSelfFetch re-syncs on [initialData],\n  // and a fresh per-render object would loop setState under re-rendering\n  // parents (the latent FaqSection bug, fixed there in the same change).\n  const initialData = useMemo<RelatedContentResponse | undefined>(\n    // An explicitly disabled rail (includeTypes: []) must ignore SSR-hydrated\n    // refs too — otherwise useSelfFetch(null, {initialData}) keeps serving\n    // initialItems and the \"nothing participates\" contract silently breaks.\n    () => (!suggestionsDisabled && initialItems ? { refs: initialItems } : undefined),\n    [initialItems, suggestionsDisabled],\n  );\n  const { data, isLoading } = useSelfFetch<RelatedContentResponse>(suggestUrl, { initialData });\n\n  // Default group fetcher: the lib's byte-parity-tested builder, prefixed for\n  // embeds. Memoized so group-fetch URLs stay value-stable across renders.\n  const effectiveBuildListUrl = useMemo(\n    () => buildListUrl ?? ((type: string, ids: string[]) => libBuildListUrl(type, ids, apiBaseUrl)),\n    [buildListUrl, apiBaseUrl],\n  );\n\n  const refs: ContentRef[] = contentRefs ?? data?.refs ?? [];\n\n  // Per-consumer type gating — drops refs whose `type` is in the exclude\n  // list. In suggestion mode the server already subtracted these (the param\n  // is forwarded above); the client filter stays as an idempotent guard and\n  // IS the mechanism in controlled mode (original behavior).\n  const exclude = new Set(excludeTypes || []);\n  const visibleRefs = exclude.size > 0 ? refs.filter(r => !exclude.has(r.type)) : refs;\n  // Zero refs (still loading in suggestion mode, or genuinely empty). Default:\n  // no empty shell. Opt-in (`showWhenEmpty`): render the title + an empty-state\n  // line so the section is always present (e.g. people-hub \"What I Shipped\").\n  if (!visibleRefs.length) {\n    if (!showWhenEmpty) return null; // non-showWhenEmpty consumers stay blank (unchanged)\n    // Client-fetch loading (author/suggestion mode, no SSR initialItems): render a\n    // SKELETON grid — reserves height + matches the rest of the app's loading, so\n    // there's no blank-then-pop jump (the prior `return null` collapsed the tab to\n    // zero height during the fetch). SSR controlled mode has isLoading=false → it\n    // skips straight to the empty state below. Skeleton type = the requested rail\n    // type (author mode passes a single `includeTypes`).\n    if (isLoading) {\n      const skeletonType = includeTypes?.[0] ?? entityType ?? 'blog_post_existing';\n      return (\n        <div className=\"space-y-8\">\n          <h2 className=\"text-ods-text-primary text-h2\">{title}</h2>\n          <div className={gridClassFor(columns)}>\n            {Array.from({ length: 3 }).map((_, i) => (\n              <div key={i}>{renderSkeletonForType(skeletonType, 'default', adminCampaignCard)}</div>\n            ))}\n          </div>\n        </div>\n      );\n    }\n    return (\n      <div className=\"space-y-8\">\n        <h2 className=\"text-2xl font-bold text-ods-text-primary\">{title}</h2>\n        {emptyState ?? <p className=\"text-ods-text-secondary\">{emptyStateText}</p>}\n      </div>\n    );\n  }\n\n  const grouped: Record<string, ContentRef[]> = {};\n  for (const ref of visibleRefs) {\n    if (!grouped[ref.type]) grouped[ref.type] = [];\n    grouped[ref.type].push(ref);\n  }\n\n  // Registered types in CONTENT_REF_GROUPS order, then any unregistered\n  // types appended (same shape the investor-email builder uses — both\n  // consume `orderContentRefTypes` so cross-surface ordering matches).\n  // SAME-TYPE FIRST: when a host entityType is known (suggestion / SSR\n  // modes), its own content-type group is hoisted to the top — a blog\n  // post's rail leads with blog posts. Rail group keys are compared via\n  // the shared alias canonicalizer (blog_post_existing ↔ blog_post).\n  let orderedTypes = orderContentRefTypes(Object.keys(grouped));\n  if (entityType) {\n    // Canonicalize BOTH sides — hosts pass registry vocab ('blog_post') but\n    // rail-vocab aliases ('blog_post_existing') are also legal inputs; a raw\n    // comparison would silently lose the same-type-first hoist for aliases.\n    const canonicalEntityType = canonicalContentRefType(entityType);\n    const sameType = orderedTypes.filter(t => canonicalContentRefType(t) === canonicalEntityType);\n    if (sameType.length > 0) {\n      orderedTypes = [...sameType, ...orderedTypes.filter(t => canonicalContentRefType(t) !== canonicalEntityType)];\n    }\n  }\n\n  return (\n    <div className=\"space-y-8\">\n      <h2 className=\"text-ods-text-primary text-h2\">{title}</h2>\n      {orderedTypes.map(type => (\n        <ContentGroup\n          key={type}\n          type={type}\n          refs={grouped[type]}\n          columns={columns}\n          buildUrl={effectiveBuildListUrl}\n          resolveHref={resolveHref}\n          LinkProvider={LinkProvider}\n          extras={extras}\n          adminCampaignCard={adminCampaignCard}\n          heading={\n            <h3 className=\"font-semibold text-ods-text-secondary text-h5\">{getContentRefLabelOrTitleCase(type)}</h3>\n          }\n        />\n      ))}\n    </div>\n  );\n}\n","'use client';\n\n/**\n * Per-type card registry for the related-content rail — the SIZED sibling of\n * the chat side's `CHAT_CARD_REGISTRY` (`../chat/entity-cards/dispatch.tsx`),\n * and built to the same shape: ONE entry per content type, carrying that\n * type's skeleton and its card renderer, dispatched once from\n * `related-content-section.tsx`.\n *\n * WHY THIS FILE EXISTS\n * --------------------\n * The rail fetches one row per ref from that type's public list API. The row\n * arrives as `unknown` (`useSelfFetch<unknown>` → `extractItems`), and its\n * SHAPE is decided at runtime by the ref's `type`. The rail used to bridge\n * that with a single `item: any` threaded through a ten-branch switch, so\n * every `prop={item}` forward was an unchecked promise: a row missing a field\n * reached a card that declared it required, and the card rendered the miss\n * (an empty heading, a version pill reading \"vundefined\").\n *\n * Each entry here closes over its OWN row type instead. `cardEntry<Row>` pairs\n * a `decode` (runtime `unknown` → `Row`) with a `render` that receives `Row`,\n * and erases `Row` from the stored entry — so the registry is homogeneous\n * (`Record<string, RelatedCardRegistryEntry>`) while every branch stays\n * concretely typed. No `any`, no assertions into card row types.\n *\n * ON THE `*_ROW_DEFAULTS` CONSTANTS\n * ---------------------------------\n * Each card declares the FULL entity type as its row prop (`BlogPostSummary`,\n * `CaseStudy`, …) while reading only a handful of fields off it, and the list\n * APIs return projections rather than whole rows. A decoder therefore reads\n * the card's read set from the row and takes every other required field from\n * a `*_ROW_DEFAULTS` literal. Those placeholders are inert BY CONSTRUCTION —\n * the decoded row goes nowhere but its own card, and the card never reads\n * them. They are annotated with the entity type on purpose: a new required\n * field on `CaseStudy` surfaces as a compile error here instead of silently\n * reaching a card as `undefined`, which is the exact failure this file\n * replaces.\n *\n * ADDING A TYPE: one entry below (skeleton + decode + render), plus the chat\n * side's `CHAT_CARD_REGISTRY` and the list-URL builder — see the LOCKSTEP note\n * in `related-content-section.tsx`.\n */\n\nimport type React from 'react';\nimport type { BlogPostSummary } from '../../types/blog';\nimport type { CaseStudy } from '../../types/case-study';\nimport type { CustomerInterview } from '../../types/customer-interview';\nimport type { EntityAuthor } from '../../types/entity-author';\nimport type { MSP } from '../../types/stack';\nimport type { UserProfile } from '../../types/user';\n// DEEP card imports — NOT the `../chat` barrel. See the import note in\n// `related-content-section.tsx`: the barrel statically reaches\n// @tanstack/react-query, deep paths keep this module's source graph free of it.\nimport { BlogCard, BlogCardSkeleton } from '../chat/entity-cards/blog-card';\nimport { CaseStudyCard, CaseStudyCardSkeleton } from '../chat/entity-cards/case-study-card';\nimport { CustomerInterviewCard, CustomerInterviewCardSkeleton } from '../chat/entity-cards/customer-interview-card';\n// Type-only — erased at build, no runtime dependency on the dispatch module.\nimport type { ChatCardDispatchExtras } from '../chat/entity-cards/dispatch';\nimport { HowIWorkCard, HowIWorkCardSkeleton, type HowIWorkCardData } from '../chat/entity-cards/how-i-work-card';\nimport { InvestorUpdateCard, InvestorUpdateCardSkeleton } from '../chat/entity-cards/investor-update-card';\nimport { OnboardingGuideCard, OnboardingGuideCardSkeleton } from '../chat/entity-cards/onboarding-guide-card';\nimport { ProductReleaseCard, ProductReleaseCardSkeleton } from '../chat/entity-cards/product-release-card';\nimport { buildProductReleaseCardProps } from '../chat/entity-cards/product-release-card-defaults';\nimport { ProgramCard, ProgramCardSkeleton } from '../chat/entity-cards/program-card';\nimport { RoadmapCard, RoadmapCardSkeleton } from '../chat/entity-cards/roadmap-card';\nimport {\n  WhatIShippedCard,\n  WhatIShippedCardSkeleton,\n  type WhatIShippedCardData,\n} from '../chat/entity-cards/what-i-shipped-card';\nimport type { InvestorUpdate } from '../chat/types/entities/investor-update';\nimport type { OnboardingGuide } from '../chat/types/entities/onboarding-guide';\nimport type { BaseProgramItem, ProgramHost } from '../chat/types/entities/program-types';\nimport type { RoadmapItem } from '../chat/types/entities/roadmap-item';\n\nexport type CardSize = 'lg' | 'default' | 'sm';\n\n/** Anchor prop bundle the per-card link surface receives — same shape the\n *  hub's `useNavLink` returns and the chat dispatcher's anchor builders\n *  produce. `null` = non-anchor mode (no URL). */\nexport interface CardLinkAnchorProps {\n  href: string;\n  target?: '_blank';\n  rel?: 'noopener noreferrer';\n  onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;\n}\n\n// =============================================================================\n// Row reading — the ONE place a runtime row is inspected.\n// =============================================================================\n\n/**\n * View an unvalidated row as a keyed bag. This claims nothing about the row's\n * SHAPE — every read below still checks the value's type — it only says \"this\n * is an object I may look keys up on\". A non-object row (null, a string, a\n * number) collapses to `{}`, so every decoder degrades to its defaults instead\n * of throwing.\n */\nfunction asRecord(value: unknown): Record<string, unknown> {\n  return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {};\n}\n\n/** A STRING field, or `undefined` when missing or wrong-typed. */\nexport function rowString(row: unknown, key: string): string | undefined {\n  const value = asRecord(row)[key];\n  return typeof value === 'string' ? value : undefined;\n}\n\nfunction str(row: Record<string, unknown>, key: string): string {\n  const value = row[key];\n  return typeof value === 'string' ? value : '';\n}\n\nfunction strOrNull(row: Record<string, unknown>, key: string): string | null {\n  const value = row[key];\n  return typeof value === 'string' ? value : null;\n}\n\nfunction numOrNull(row: Record<string, unknown>, key: string): number | null {\n  const value = row[key];\n  return typeof value === 'number' ? value : null;\n}\n\n/** `Array.isArray` narrows `unknown` to `any[]`; annotating the result keeps\n *  the elements `unknown` so nothing downstream inherits `any`. Same guard the\n *  shared `extractItems` normalizer uses. */\nfunction arrayOf(value: unknown): unknown[] {\n  const list: unknown[] = Array.isArray(value) ? value : [];\n  return list;\n}\n\nfunction stringList(value: unknown): string[] {\n  return arrayOf(value).filter((entry): entry is string => typeof entry === 'string');\n}\n\n// =============================================================================\n// Shared nested-shape decoders (joins hydrated onto several row types).\n// =============================================================================\n\n/** `{ name, slug }` tag / category joins. Rows that carry only `name` (the\n *  chat-side row shape) keep their label and get an empty slug — the cards\n *  read `name` and nothing else. */\nfunction namedSlugList(value: unknown): { name: string; slug: string }[] {\n  const out: { name: string; slug: string }[] = [];\n  for (const entry of arrayOf(value)) {\n    const record = asRecord(entry);\n    const name = strOrNull(record, 'name');\n    if (name === null) continue;\n    out.push({ name, slug: str(record, 'slug') });\n  }\n  return out;\n}\n\nconst USER_PROFILE_DEFAULTS: UserProfile = {\n  id: '',\n  full_name: null,\n  email: '',\n  avatar_url: null,\n  job_title: null,\n  company: null,\n  bio: null,\n  employee_id: null,\n  department_id: null,\n  department: null,\n  role: 'user',\n  msp_id: null,\n  created_at: '',\n  updated_at: '',\n};\n\n/** The customer/author join on case studies + customer interviews. The cards\n *  read `full_name`, `avatar_url` and `job_title`; absent join → `undefined`,\n *  which is what those cards already fall back on. */\nfunction decodeUserProfile(value: unknown): UserProfile | undefined {\n  if (typeof value !== 'object' || value === null) return undefined;\n  const record = asRecord(value);\n  return {\n    ...USER_PROFILE_DEFAULTS,\n    id: str(record, 'id'),\n    full_name: strOrNull(record, 'full_name'),\n    avatar_url: strOrNull(record, 'avatar_url'),\n    job_title: strOrNull(record, 'job_title'),\n  };\n}\n\nconst MSP_DEFAULTS: MSP = { id: '', name: '', seat_count: 0, created_at: '', updated_at: '' };\n\n/** The MSP join — cards read `name` and `icon_url`. */\nfunction decodeMsp(value: unknown): MSP | undefined {\n  if (typeof value !== 'object' || value === null) return undefined;\n  const record = asRecord(value);\n  return {\n    ...MSP_DEFAULTS,\n    id: str(record, 'id'),\n    name: str(record, 'name'),\n    icon_url: rowString(record, 'icon_url'),\n  };\n}\n\n/** The shared hydrated-author shape (`EntityAuthor`) — only `full_name` and\n *  `avatar_url` are required, so this needs no defaults literal. */\nfunction decodeEntityAuthor(value: unknown): EntityAuthor | undefined {\n  if (typeof value !== 'object' || value === null) return undefined;\n  const record = asRecord(value);\n  return {\n    id: rowString(record, 'id'),\n    full_name: strOrNull(record, 'full_name'),\n    avatar_url: strOrNull(record, 'avatar_url'),\n    job_title: strOrNull(record, 'job_title'),\n    about: strOrNull(record, 'about'),\n    slug: rowString(record, 'slug'),\n  };\n}\n\n/** Program hosts / speakers. `name` is the only required field; an entry\n *  without one is dropped rather than rendered as a nameless avatar. */\nfunction decodeProgramHosts(value: unknown): ProgramHost[] | null {\n  if (value == null) return null;\n  const out: ProgramHost[] = [];\n  for (const entry of arrayOf(value)) {\n    const record = asRecord(entry);\n    const name = strOrNull(record, 'name');\n    if (name === null) continue;\n    out.push({\n      name,\n      id: rowString(record, 'id'),\n      email: rowString(record, 'email'),\n      avatar_url: rowString(record, 'avatar_url'),\n      bio: rowString(record, 'bio'),\n      role: rowString(record, 'role'),\n    });\n  }\n  return out;\n}\n\n// =============================================================================\n// Per-type row decoders. Each reads the fields ITS card renders; every other\n// required field comes from the type-annotated defaults literal above it.\n// =============================================================================\n\nfunction decodeBlogRow(row: unknown): BlogPostSummary {\n  const record = asRecord(row);\n  return {\n    id: numOrNull(record, 'id') ?? 0,\n    title: str(record, 'title'),\n    slug: str(record, 'slug'),\n    summary: strOrNull(record, 'summary'),\n    featured_image: strOrNull(record, 'featured_image'),\n    published_at: strOrNull(record, 'published_at'),\n    author_name: strOrNull(record, 'author_name'),\n    author_avatar: strOrNull(record, 'author_avatar'),\n    categories: namedSlugList(record.categories),\n    tags: namedSlugList(record.tags),\n    view_count: numOrNull(record, 'view_count') ?? undefined,\n  };\n}\n\nconst CASE_STUDY_ROW_DEFAULTS: CaseStudy = {\n  id: 0,\n  title: '',\n  slug: '',\n  summary: null,\n  featured_image: null,\n  user_id: null,\n  challenge: null,\n  solution: null,\n  results: null,\n  testimonial_video_url: null,\n  main_video_url: null,\n  video_source_type: null,\n  video_source: null,\n  video_bites: [],\n  customer_interview_id: null,\n  seo_title: null,\n  seo_description: null,\n  seo_keywords: null,\n  og_image_url: null,\n  status: 'published',\n  published_at: null,\n  author_id: null,\n  created_at: '',\n  updated_at: '',\n  view_count: 0,\n};\n\nfunction decodeCaseStudyRow(row: unknown): CaseStudy {\n  const record = asRecord(row);\n  return {\n    ...CASE_STUDY_ROW_DEFAULTS,\n    id: numOrNull(record, 'id') ?? 0,\n    title: str(record, 'title'),\n    summary: strOrNull(record, 'summary'),\n    featured_image: strOrNull(record, 'featured_image'),\n    msp: decodeMsp(record.msp),\n    user: decodeUserProfile(record.user),\n  };\n}\n\nconst CUSTOMER_INTERVIEW_ROW_DEFAULTS: CustomerInterview = {\n  id: 0,\n  title: '',\n  slug: '',\n  video_summary: null,\n  transcript: null,\n  user_id: null,\n  main_video_url: null,\n  teasers: [],\n  case_study_id: null,\n  seo_title: null,\n  seo_description: null,\n  seo_keywords: null,\n  og_image_url: null,\n  featured_image: null,\n  status: 'completed',\n  completed_at: null,\n  author_id: null,\n  custom_instructions: null,\n  created_at: '',\n  updated_at: '',\n  view_count: 0,\n};\n\nfunction decodeCustomerInterviewRow(row: unknown): CustomerInterview {\n  const record = asRecord(row);\n  return {\n    ...CUSTOMER_INTERVIEW_ROW_DEFAULTS,\n    id: numOrNull(record, 'id') ?? 0,\n    title: str(record, 'title'),\n    video_summary: strOrNull(record, 'video_summary'),\n    main_video_url: strOrNull(record, 'main_video_url'),\n    featured_image: strOrNull(record, 'featured_image'),\n    msp: decodeMsp(record.msp),\n    user: decodeUserProfile(record.user),\n  };\n}\n\nconst INVESTOR_UPDATE_ROW_DEFAULTS: InvestorUpdate = {\n  id: '',\n  title: '',\n  slug: '',\n  update_number: null,\n  period_start: null,\n  period_end: null,\n  platform_id: null,\n  content: null,\n  video_summary: null,\n  transcript: null,\n  main_video_url: null,\n  highlight_video_url: null,\n  highlight_video_thumbnail: null,\n  main_video_thumbnail: null,\n  video_bites: [],\n  featured_image: null,\n  strategic_update: null,\n  financials: {},\n  metrics_snapshot: {},\n  content_refs: [],\n  highlights: null,\n  section_visibility: {},\n  status: 'published',\n  published_at: null,\n  author_id: null,\n  seo_title: null,\n  seo_description: null,\n  seo_keywords: null,\n  og_image_url: null,\n  hubspot_email_id: null,\n  custom_instructions: null,\n  created_at: '',\n  updated_at: '',\n  created_by: null,\n  updated_by: null,\n};\n\nfunction decodeInvestorUpdateRow(row: unknown): InvestorUpdate {\n  const record = asRecord(row);\n  return {\n    ...INVESTOR_UPDATE_ROW_DEFAULTS,\n    id: str(record, 'id'),\n    title: str(record, 'title'),\n    update_number: numOrNull(record, 'update_number'),\n    period_start: strOrNull(record, 'period_start'),\n    period_end: strOrNull(record, 'period_end'),\n    content: strOrNull(record, 'content'),\n    strategic_update: strOrNull(record, 'strategic_update'),\n    featured_image: strOrNull(record, 'featured_image'),\n  };\n}\n\nconst ONBOARDING_GUIDE_ROW_DEFAULTS: OnboardingGuide = {\n  id: '',\n  title: '',\n  slug: '',\n  section: '',\n  step_order: 0,\n  section_order: 0,\n  content: null,\n  video_summary: null,\n  transcript: null,\n  transcript_words_data: null,\n  srt_content: null,\n  ai_transcript_formatted: null,\n  main_video_url: null,\n  main_video_thumbnail: null,\n  youtube_url: null,\n  highlight_video_url: null,\n  highlight_video_thumbnail: null,\n  highlight_video_duration_ms: null,\n  highlight_video_source: null,\n  video_bites: [],\n  featured_image: null,\n  og_image_url: null,\n  seo_title: null,\n  seo_description: null,\n  seo_keywords: null,\n  status: 'published',\n  published_at: null,\n  author_id: null,\n  custom_instructions: null,\n  config: null,\n  ai_effort_score: null,\n  created_at: '',\n  updated_at: '',\n  created_by: null,\n  updated_by: null,\n};\n\nfunction decodeOnboardingGuideRow(row: unknown): OnboardingGuide {\n  const record = asRecord(row);\n  return {\n    ...ONBOARDING_GUIDE_ROW_DEFAULTS,\n    id: str(record, 'id'),\n    title: str(record, 'title'),\n    section: str(record, 'section'),\n    step_order: numOrNull(record, 'step_order') ?? 0,\n    content: strOrNull(record, 'content'),\n    video_summary: strOrNull(record, 'video_summary'),\n    featured_image: strOrNull(record, 'featured_image'),\n    og_image_url: strOrNull(record, 'og_image_url'),\n    main_video_thumbnail: strOrNull(record, 'main_video_thumbnail'),\n    highlight_video_thumbnail: strOrNull(record, 'highlight_video_thumbnail'),\n    highlight_video_duration_ms: numOrNull(record, 'highlight_video_duration_ms'),\n    author: decodeEntityAuthor(record.author),\n  };\n}\n\n/**\n * `RoadmapItem` is the one row type the card reads WHOLE, so this decoder\n * carries no defaults literal — every field is read off the row.\n *\n * `upvotes` / `downvotes` / `quarter` are required `number`/`string` props the\n * card renders directly. Read off an untyped row they were `any`, so a row\n * without them rendered the miss (a blank vote count). They now fall back to\n * `0` / `''`, matching how the card renders a genuinely zero-vote item.\n */\nfunction decodeRoadmapRow(row: unknown): RoadmapItem {\n  const record = asRecord(row);\n  const assignees: NonNullable<RoadmapItem['assignees']> = [];\n  for (const entry of arrayOf(record.assignees)) {\n    const assignee = asRecord(entry);\n    assignees.push({\n      id: numOrNull(assignee, 'id') ?? 0,\n      name: strOrNull(assignee, 'name'),\n      avatarUrl: strOrNull(assignee, 'avatarUrl'),\n    });\n  }\n  return {\n    id: str(record, 'id'),\n    title: str(record, 'title'),\n    description: str(record, 'description'),\n    status: str(record, 'status'),\n    statusColor: str(record, 'statusColor'),\n    icon: strOrNull(record, 'icon'),\n    figmaUrl: strOrNull(record, 'figmaUrl'),\n    screenshots: stringList(record.screenshots),\n    targetVersion: strOrNull(record, 'targetVersion'),\n    upvotes: numOrNull(record, 'upvotes') ?? 0,\n    downvotes: numOrNull(record, 'downvotes') ?? 0,\n    quarter: str(record, 'quarter'),\n    clickupUrl: str(record, 'clickupUrl'),\n    customItemId: numOrNull(record, 'customItemId'),\n    assignees,\n  };\n}\n\n/**\n * Program rows (podcast / webinar / event) keep their EXTRA columns.\n *\n * `<ProgramCard>` is generic over the item, and the config the rail threads in\n * is an `AnyProgramConfig` — `ProgramConfig<BaseProgramItem &\n * Record<string, unknown>>` — precisely so per-program columns ride along. The\n * card branches on `'duration_seconds' in item` / `'location_name' in item` /\n * `'start_at' in item`, so dropping the unrecognised columns would silently\n * strip a podcast's runtime and an event's venue. The raw row is spread first\n * and the base fields are decoded over it.\n */\nfunction decodeProgramRow(row: unknown): BaseProgramItem & Record<string, unknown> {\n  const record = asRecord(row);\n  return {\n    ...record,\n    id: str(record, 'id'),\n    title: str(record, 'title'),\n    description: strOrNull(record, 'description'),\n    cover_url: strOrNull(record, 'cover_url'),\n    date: str(record, 'date'),\n    external_url: strOrNull(record, 'external_url'),\n    hosts: decodeProgramHosts(record.hosts),\n  };\n}\n\n/** The two employee-entry cards already declare MINIMAL row shapes\n *  (`EmployeeEntryCardData` + one date column each), so their decoders read\n *  exactly the card's fields with nothing left over. */\nfunction decodeEmployeeEntryRow(row: unknown): WhatIShippedCardData & HowIWorkCardData {\n  const record = asRecord(row);\n  return {\n    title: strOrNull(record, 'title'),\n    summary: strOrNull(record, 'summary'),\n    status: strOrNull(record, 'status'),\n    featured_image: strOrNull(record, 'featured_image'),\n    main_video_thumbnail: strOrNull(record, 'main_video_thumbnail'),\n    author: decodeEntityAuthor(record.author),\n    entry_month: strOrNull(record, 'entry_month'),\n    session_date: strOrNull(record, 'session_date'),\n    discipline: strOrNull(record, 'discipline'),\n  };\n}\n\n// =============================================================================\n// Registry\n// =============================================================================\n\n/** Everything a card branch needs beyond its own row — computed once per card\n *  by the rail's dispatcher and shared by every entry. */\nexport interface RelatedCardContext {\n  /** Card density for this group, from `CONTENT_REF_GROUPS`. */\n  size: CardSize;\n  /** `size` with `'lg'` collapsed to `'default'` — most card variants accept\n   *  only that pair. */\n  legacySize: 'default' | 'sm';\n  href: string;\n  targetPlatform: string | null;\n  /** `null` = the host surfaced no URL; the card stays in non-anchor mode. */\n  linkProps: CardLinkAnchorProps | null;\n  /** `{ target, rel }` for the cards that take them as separate props. */\n  anchorAttrs: Pick<CardLinkAnchorProps, 'target' | 'rel'>;\n  /** Branded OG fallback for cards whose row has no featured image. */\n  placeholderUrl?: string;\n  extras?: ChatCardDispatchExtras;\n}\n\n/** One registered content type. `Row` is erased here — `cardEntry` below is\n *  what keeps each branch's row type concrete. */\nexport interface RelatedCardRegistryEntry {\n  /** Sized to match the resolved card exactly (zero layout shift). */\n  skeleton: (size: CardSize) => React.ReactNode;\n  render: (row: unknown, ctx: RelatedCardContext) => React.ReactNode;\n}\n\n/**\n * Register one type with its OWN row type.\n *\n * `Row` is bound by the definition object and never escapes: `render` is\n * handed a decoded `Row`, and the stored entry exposes only\n * `(row: unknown) => ReactNode`. That is what lets a single registry hold ten\n * heterogeneous row shapes without a shared supertype — the job `item: any`\n * used to do, minus the promise it could not keep.\n */\nfunction cardEntry<Row>(definition: {\n  skeleton: (size: CardSize) => React.ReactNode;\n  decode: (row: unknown) => Row;\n  render: (row: Row, ctx: RelatedCardContext) => React.ReactNode;\n}): RelatedCardRegistryEntry {\n  return {\n    skeleton: definition.skeleton,\n    render: (row, ctx) => definition.render(definition.decode(row), ctx),\n  };\n}\n\n/** Podcast / webinar / event share one card + one skeleton and differ only by\n *  which host-supplied `ProgramConfig` they render with. A group whose config\n *  the host didn't supply renders nothing (the skeleton still reserves its\n *  height, so a rail that never resolves collapses instead of flickering). */\nfunction programEntry(configKey: 'podcast' | 'webinar' | 'event'): RelatedCardRegistryEntry {\n  return cardEntry({\n    skeleton: size => <ProgramCardSkeleton size={size === 'sm' ? 'sm' : 'default'} />,\n    decode: decodeProgramRow,\n    render: (item, ctx) => {\n      const config = ctx.extras?.programConfigs?.[configKey];\n      if (!config) return null;\n      return (\n        <ProgramCard\n          config={config}\n          item={item}\n          size={ctx.legacySize}\n          href={ctx.href}\n          targetPlatform={ctx.targetPlatform}\n          placeholderUrl={ctx.placeholderUrl}\n          {...ctx.anchorAttrs}\n        />\n      );\n    },\n  });\n}\n\n/** The three ClickUp-backed types render the same card, distinguished by\n *  `cardType` (drives the compact icon slot). */\nfunction roadmapEntry(cardType: 'roadmap_item' | 'delivery_item' | 'internal_task'): RelatedCardRegistryEntry {\n  return cardEntry({\n    skeleton: size => <RoadmapCardSkeleton size={size === 'sm' ? 'sm' : 'default'} />,\n    decode: decodeRoadmapRow,\n    render: (item, ctx) => (\n      <RoadmapCard\n        item={item}\n        href={ctx.href}\n        targetPlatform={ctx.targetPlatform}\n        userVote={null}\n        onVote={() => {}}\n        size={ctx.legacySize}\n        cardType={cardType}\n        {...ctx.anchorAttrs}\n      />\n    ),\n  });\n}\n\nexport const RELATED_CARD_REGISTRY: Record<string, RelatedCardRegistryEntry> = {\n  blog_post_existing: cardEntry({\n    skeleton: size => <BlogCardSkeleton size={size === 'sm' ? 'sm' : 'default'} />,\n    decode: decodeBlogRow,\n    render: (post, ctx) => (\n      <BlogCard\n        post={post}\n        size={ctx.legacySize}\n        href={ctx.href}\n        targetPlatform={ctx.targetPlatform}\n        placeholderUrl={ctx.placeholderUrl}\n        {...ctx.anchorAttrs}\n      />\n    ),\n  }),\n\n  case_study: cardEntry({\n    skeleton: size => <CaseStudyCardSkeleton size={size === 'sm' ? 'sm' : 'default'} />,\n    decode: decodeCaseStudyRow,\n    render: (study, ctx) => (\n      <CaseStudyCard\n        study={study}\n        size={ctx.legacySize}\n        href={ctx.href}\n        targetPlatform={ctx.targetPlatform}\n        placeholderUrl={ctx.placeholderUrl}\n        {...ctx.anchorAttrs}\n      />\n    ),\n  }),\n\n  customer_interview: cardEntry({\n    skeleton: size => <CustomerInterviewCardSkeleton size={size === 'sm' ? 'sm' : 'default'} />,\n    decode: decodeCustomerInterviewRow,\n    render: (interview, ctx) => (\n      <CustomerInterviewCard\n        interview={interview}\n        size={ctx.legacySize}\n        href={ctx.href}\n        targetPlatform={ctx.targetPlatform}\n        placeholderUrl={ctx.placeholderUrl}\n        {...ctx.anchorAttrs}\n      />\n    ),\n  }),\n\n  /**\n   * Product releases derive their whole prop bundle through a HOST seam\n   * (`extras.buildProductReleaseCardProps`, defaulting to the lib builder)\n   * which already takes `unknown` — so this entry's row type is the bare\n   * record, and the three props the rail sets itself are read off it.\n   */\n  product_release: cardEntry({\n    skeleton: size => <ProductReleaseCardSkeleton size={size === 'sm' ? 'sm' : 'lg'} />,\n    decode: asRecord,\n    render: (release, ctx) => {\n      const buildReleaseProps = ctx.extras?.buildProductReleaseCardProps ?? buildProductReleaseCardProps;\n      const releaseProps = buildReleaseProps(release);\n      return (\n        <ProductReleaseCard\n          size={ctx.size === 'sm' ? 'sm' : 'lg'}\n          // `title` and `version` are REQUIRED `string` props. Read off an\n          // untyped row they were `any`, so a release row missing either one\n          // rendered an empty heading and a version pill reading \"vundefined\".\n          // `summary` is `string | null | undefined` and the card renders\n          // `summary ?? ''`, so a dropped one is indistinguishable from null.\n          title={str(release, 'title')}\n          summary={rowString(release, 'summary')}\n          version={str(release, 'version')}\n          {...releaseProps}\n          // The card wraps in `<a {...anchorProps}>` ONLY when\n          // `anchorProps.href` is set — `undefined` (not an empty object)\n          // keeps it in non-anchor mode instead of rendering a dead <a>.\n          anchorProps={ctx.linkProps ?? undefined}\n        />\n      );\n    },\n  }),\n\n  podcast: programEntry('podcast'),\n  webinar: programEntry('webinar'),\n  event: programEntry('event'),\n\n  investor_update: cardEntry({\n    skeleton: size => <InvestorUpdateCardSkeleton size={size === 'sm' ? 'sm' : 'default'} />,\n    decode: decodeInvestorUpdateRow,\n    render: (update, ctx) => (\n      <InvestorUpdateCard\n        update={update}\n        size={ctx.legacySize}\n        href={ctx.href}\n        targetPlatform={ctx.targetPlatform}\n        placeholderUrl={ctx.placeholderUrl}\n        {...ctx.anchorAttrs}\n      />\n    ),\n  }),\n\n  /** The rich CATALOG variant (hero + author grid, clamped description) — the\n   *  step-numbered 'default' variant is for the guide detail page's \"More in\n   *  section\" rail, not this full-width row. */\n  onboarding_guide: cardEntry({\n    skeleton: size => <OnboardingGuideCardSkeleton size={size === 'sm' ? 'sm' : 'catalog'} />,\n    decode: decodeOnboardingGuideRow,\n    render: (guide, ctx) => (\n      <OnboardingGuideCard\n        guide={guide}\n        size={ctx.size === 'sm' ? 'sm' : 'catalog'}\n        href={ctx.href}\n        targetPlatform={ctx.targetPlatform}\n        placeholderUrl={ctx.placeholderUrl}\n        {...ctx.anchorAttrs}\n      />\n    ),\n  }),\n\n  /** THE single What I Shipped card — same lib component the people-hub\n   *  dashboard renders. `anchorProps` makes the whole card a click-through\n   *  (the rail is read-only — no owner actions). Only pass it with a REAL\n   *  href: an object with `href: undefined` is still truthy and would wrap the\n   *  card in a dead <a>. */\n  what_i_shipped: cardEntry({\n    skeleton: () => <WhatIShippedCardSkeleton />,\n    decode: decodeEmployeeEntryRow,\n    render: (entry, ctx) => (\n      <WhatIShippedCard\n        entry={entry}\n        placeholderUrl={ctx.placeholderUrl}\n        anchorProps={ctx.linkProps ?? (ctx.href ? { href: ctx.href, ...ctx.anchorAttrs } : undefined)}\n      />\n    ),\n  }),\n\n  /** Same shared employee-entry shape + anchor contract as What I Shipped. */\n  how_i_work: cardEntry({\n    skeleton: () => <HowIWorkCardSkeleton />,\n    decode: decodeEmployeeEntryRow,\n    render: (entry, ctx) => (\n      <HowIWorkCard\n        entry={entry}\n        placeholderUrl={ctx.placeholderUrl}\n        anchorProps={ctx.linkProps ?? (ctx.href ? { href: ctx.href, ...ctx.anchorAttrs } : undefined)}\n      />\n    ),\n  }),\n\n  roadmap_item: roadmapEntry('roadmap_item'),\n  delivery_item: roadmapEntry('delivery_item'),\n  internal_task: roadmapEntry('internal_task'),\n};\n"]}