{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-XGNTWDW4.cjs","../src/components/related-content/related-content-section.tsx"],"names":["buildListUrl"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-XGNTWDW4.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 * LOCKSTEP NOTE: this file's per-type card/skeleton dispatch is the SIZED\n * sibling of the chat-side `CHAT_CARD_REGISTRY` (`../chat/entity-cards/\n * dispatch.tsx`), which renders compact `size='sm'` cards wired to the chat\n * runtime. Two dispatchers by design — when registering a new fetch-mode\n * content type, add it BOTH there and here (cards + skeleton + list URL).\n */\n\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n  CONTENT_REF_GROUPS,\n  getContentRefLabelOrTitleCase,\n  orderContentRefTypes,\n  type ContentRefGroupConfig,\n} from '../../utils/content-ref-groups';\nimport type { ContentRef, ContentRefWithReason } from '../../types/content-ref';\nimport { useSelfFetch } from '../../hooks/use-self-fetch';\nimport { Pagination } from '../pagination';\nimport { extractItems, extractItemId } from '../../utils/extract-items';\nimport { buildListUrl as libBuildListUrl, canonicalContentRefType } from '../../utils/list-url';\nimport { buildSuggestionUrl } from '../../utils/suggestion-url';\nimport { decideNewTab } from '../chat/utils/decide-new-tab';\n// DEEP card imports — NOT the `../chat` barrel (the barrel statically reaches\n// @tanstack/react-query via embeddable-chat + its hooks). Deep paths keep this\n// component's SOURCE graph react-query-free. Note: tsup's shared-chunk\n// splitting may still colocate the cards with chat hooks in one dist chunk\n// (react-query is a required peerDep, so resolution always succeeds) — the\n// guarantee that matters here is the RUNTIME one: nothing on this path ever\n// instantiates a QueryClient, so embedders need NO QueryClientProvider.\nimport { BlogCard, BlogCardSkeleton } from '../chat/entity-cards/blog-card';\nimport { WhatIShippedCard, WhatIShippedCardSkeleton } from '../chat/entity-cards/what-i-shipped-card';\nimport { HowIWorkCard, HowIWorkCardSkeleton } from '../chat/entity-cards/how-i-work-card';\nimport { CaseStudyCard, CaseStudyCardSkeleton } from '../chat/entity-cards/case-study-card';\nimport { CustomerInterviewCard, CustomerInterviewCardSkeleton } from '../chat/entity-cards/customer-interview-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 { InvestorUpdateCard, InvestorUpdateCardSkeleton } from '../chat/entity-cards/investor-update-card';\nimport { OnboardingGuideCard, OnboardingGuideCardSkeleton } from '../chat/entity-cards/onboarding-guide-card';\nimport { RoadmapCard, RoadmapCardSkeleton } from '../chat/entity-cards/roadmap-card';\n// Type-only — erased at build, no runtime dependency on the dispatch module.\nimport type { ChatCardDispatchExtras } from '../chat/entity-cards/dispatch';\n\ntype 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/** 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({ href, targetPlatform, children }: CardLinkProviderProps): React.ReactElement | null {\n  if (!href) return children(null);\n  const newTab = decideNewTab({ href, targetPlatform, currentSource: '' });\n  return children(\n    newTab\n      ? { href, target: '_blank', rel: 'noopener noreferrer' }\n      : { href },\n  );\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  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).\n * The chat-side `CHAT_CARD_REGISTRY` already does this via\n * `entry.skeleton()`; this surface exposes the same discipline to the\n * related-content rail.\n */\nfunction renderSkeletonForType(\n  type: string,\n  size: CardSize,\n  adminCampaignCard?: AdminCampaignCardSlot,\n): React.ReactNode {\n  // Most card skeletons accept only `{default, sm}`. `'lg'` collapses to\n  // `'default'`. ProductReleaseCardSkeleton uses lg/sm pair.\n  const legacySize: 'default' | 'sm' = size === 'sm' ? 'sm' : 'default';\n  switch (type) {\n    case 'blog_post_existing':\n      return <BlogCardSkeleton size={legacySize} />;\n    case 'case_study':\n      return <CaseStudyCardSkeleton size={legacySize} />;\n    case 'customer_interview':\n      return <CustomerInterviewCardSkeleton size={legacySize} />;\n    case 'product_release':\n      return <ProductReleaseCardSkeleton size={size === 'sm' ? 'sm' : 'lg'} />;\n    case 'podcast':\n    case 'webinar':\n    case 'event':\n      return <ProgramCardSkeleton size={legacySize} />;\n    case 'investor_update':\n      return <InvestorUpdateCardSkeleton size={legacySize} />;\n    case 'onboarding_guide':\n      // The rich catalog variant (hero + author grid, clamped description) —\n      // the step-numbered 'default' variant is for the guide detail page's\n      // \"More in section\" rail, not this full-width row.\n      return <OnboardingGuideCardSkeleton size={size === 'sm' ? 'sm' : 'catalog'} />;\n    case 'what_i_shipped':\n      // Matches the WhatIShippedCard (AdminContentCard 3:2) shape.\n      return <WhatIShippedCardSkeleton />;\n    case 'how_i_work':\n      // Same shared employee-entry shape as What I Shipped.\n      return <HowIWorkCardSkeleton />;\n    case 'marketing_campaign':\n      return adminCampaignCard ? <adminCampaignCard.Skeleton size={legacySize} /> : null;\n    case 'roadmap_item':\n    case 'delivery_item':\n    case 'internal_task':\n      return <RoadmapCardSkeleton size={legacySize} />;\n    default:\n      return null;\n  }\n}\n\n/**\n * Per-type card dispatch — renders the right card with the right size.\n * Sized cards (`'lg'` / `'default'`) are unique to this rail — the chat\n * dispatcher only renders `'sm'`, so we go directly through the per-type\n * cards here.\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 switch.\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  item: any;\n  size: CardSize;\n  href: string;\n  targetPlatform: string | null;\n  linkProps: CardLinkAnchorProps | null;\n  extras?: ChatCardDispatchExtras;\n  adminCampaignCard?: AdminCampaignCardSlot;\n}): React.ReactNode {\n  // Most card variants accept only `{default, sm}`. `'lg'` collapses to\n  // `'default'` for those. ProductReleaseCard uses its own lg/sm pair.\n  const legacySize: 'default' | 'sm' = size === 'sm' ? 'sm' : 'default';\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  // item shapes used here.\n  const placeholderUrl =\n    extras?.buildOgPlaceholderUrl?.((item?.title as string | undefined) ?? '') ?? undefined;\n\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  const anchorAttrs: Pick<CardLinkAnchorProps, 'target' | 'rel'> = linkProps\n    ? { target: linkProps.target, rel: linkProps.rel }\n    : {};\n\n  switch (type) {\n    case 'blog_post_existing':\n      return <BlogCard post={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n    case 'case_study':\n      return <CaseStudyCard study={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n    case 'customer_interview':\n      return <CustomerInterviewCard interview={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n    case 'product_release': {\n      // Anchor-prop pattern: build product-release lg-variant props from the\n      // shared `buildProductReleaseCardProps` so this rail and the /releases\n      // catalog page render byte-identically. The card wraps in\n      // `<a {...anchorProps}>` ONLY when `anchorProps.href` is set — pass\n      // `undefined` (not an empty object) when href is empty so the card\n      // stays in non-anchor mode without rendering a dead <a> tag.\n      const releaseSize = size === 'sm' ? 'sm' : 'lg';\n      const buildReleaseProps = extras?.buildProductReleaseCardProps ?? buildProductReleaseCardProps;\n      const releaseProps = buildReleaseProps(item);\n      return (\n        <ProductReleaseCard\n          size={releaseSize}\n          title={item.title}\n          summary={item.summary}\n          version={item.version}\n          {...releaseProps}\n          anchorProps={linkProps ?? undefined}\n        />\n      );\n    }\n    case 'podcast':\n      return extras?.programConfigs?.podcast\n        ? <ProgramCard config={extras.programConfigs.podcast} item={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />\n        : null;\n    case 'webinar':\n      return extras?.programConfigs?.webinar\n        ? <ProgramCard config={extras.programConfigs.webinar} item={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />\n        : null;\n    case 'event':\n      return extras?.programConfigs?.event\n        ? <ProgramCard config={extras.programConfigs.event} item={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />\n        : null;\n    case 'investor_update':\n      return <InvestorUpdateCard update={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n    case 'onboarding_guide':\n      // Catalog variant (see skeleton note) — full-width rich card with a\n      // line-clamped description instead of the step-numbered rail card.\n      return <OnboardingGuideCard guide={item} size={size === 'sm' ? 'sm' : 'catalog'} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n    case 'what_i_shipped':\n      // THE single What I Shipped card — same lib component the people-hub\n      // dashboard renders, so the card is identical in the rail and the\n      // dashboard. `anchorProps` makes the whole card a click-through link\n      // (rail is read-only — no owner actions).\n      return (\n        <WhatIShippedCard\n          entry={item}\n          placeholderUrl={placeholderUrl}\n          // Only pass anchorProps when there's a REAL href — a fallback object\n          // with `href: undefined` is still truthy and would make WhatIShippedCard\n          // wrap the card in a dead <a> (no URL). Mirrors the ProductReleaseCard\n          // `linkProps ?? undefined` pattern above.\n          anchorProps={\n            linkProps ??\n            (href ? ({ href, ...anchorAttrs } as React.AnchorHTMLAttributes<HTMLAnchorElement>) : undefined)\n          }\n        />\n      );\n    case 'how_i_work':\n      // THE single How I Work card — same lib component the people-hub dashboard\n      // renders. Same `anchorProps` contract as What I Shipped above (a real\n      // href only; a fallback object would wrap the card in a dead <a>).\n      return (\n        <HowIWorkCard\n          entry={item}\n          placeholderUrl={placeholderUrl}\n          anchorProps={\n            linkProps ??\n            (href ? ({ href, ...anchorAttrs } as React.AnchorHTMLAttributes<HTMLAnchorElement>) : undefined)\n          }\n        />\n      );\n    case 'marketing_campaign':\n      return adminCampaignCard ? <adminCampaignCard.Card campaign={item} /> : null;\n    case 'roadmap_item':\n    case 'delivery_item':\n    case 'internal_task':\n      return (\n        <RoadmapCard\n          item={item}\n          href={href ?? ''}\n          targetPlatform={targetPlatform}\n          userVote={null}\n          onVote={() => {}}\n          size={legacySize}\n          cardType={type as 'roadmap_item' | 'delivery_item' | 'internal_task'}\n          {...anchorAttrs}\n        />\n      );\n    default:\n      return null;\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(\n  type: string,\n  refs: ContentRef[],\n  buildUrl: (type: string, ids: string[]) => string | null,\n) {\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 CONTENT_REF_GROUPS[type] ?? {\n    label: type,\n    order: 999,\n    layout: 'grid',\n    gridSize: 'default',\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 = Math.max(1, Math.ceil(refs.length / GROUP_PAGE_SIZE));\n  const safePage = Math.min(page, totalGroupPages);\n  const visibleGroupRefs =\n    refs.length > GROUP_PAGE_SIZE\n      ? refs.slice((safePage - 1) * GROUP_PAGE_SIZE, safePage * GROUP_PAGE_SIZE)\n      : 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 itemById = new Map(\n    (items as any[]).map((it) => [extractItemId(type, it) ?? String((it as any)?.id), it]),\n  );\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 ? (\n        <div className=\"space-y-4\">{cards}</div>\n      ) : (\n        <div className={gridClassFor(columns)}>{cards}</div>\n      )}\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-h2 text-ods-text-primary\">{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-h2 text-ods-text-primary\">{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=\"text-h5 font-semibold text-ods-text-secondary\">\n              {getContentRefLabelOrTitleCase(type)}\n            </h3>\n          }\n        />\n      ))}\n    </div>\n  );\n}\n"]}