@flamingo-stack/openframe-frontend-core 0.0.380 → 0.0.381-snapshot.20260708122906
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-U5WZLNNP.js → chunk-23PZFAET.js} +4 -4
- package/dist/{chunk-AIWSELEM.cjs → chunk-32YONDMH.cjs} +30 -30
- package/dist/{chunk-AIWSELEM.cjs.map → chunk-32YONDMH.cjs.map} +1 -1
- package/dist/{chunk-QQJJJSYG.js → chunk-3GGIVFSI.js} +3 -3
- package/dist/{chunk-WE6PHQMR.js → chunk-4A4DA7OX.js} +2 -2
- package/dist/{chunk-ZM6IITQ6.cjs → chunk-7IQK3Y3G.cjs} +25 -25
- package/dist/{chunk-ZM6IITQ6.cjs.map → chunk-7IQK3Y3G.cjs.map} +1 -1
- package/dist/{chunk-NZ7UWZDX.cjs → chunk-AS6FET7G.cjs} +19 -19
- package/dist/{chunk-NZ7UWZDX.cjs.map → chunk-AS6FET7G.cjs.map} +1 -1
- package/dist/{chunk-T3RWEEML.js → chunk-DQJCWNRT.js} +3 -3
- package/dist/{chunk-RHBCKJVP.cjs → chunk-EE26RXMG.cjs} +12 -12
- package/dist/{chunk-RHBCKJVP.cjs.map → chunk-EE26RXMG.cjs.map} +1 -1
- package/dist/{chunk-PRA7467B.js → chunk-GZ5N76H2.js} +4 -4
- package/dist/{chunk-N4PA44BJ.js → chunk-GZIHB6TH.js} +90 -99
- package/dist/{chunk-N4PA44BJ.js.map → chunk-GZIHB6TH.js.map} +1 -1
- package/dist/{chunk-NC53B5ZJ.js → chunk-HDWY2SXF.js} +4 -4
- package/dist/{chunk-2D7HO6RK.cjs → chunk-IENK6OY5.cjs} +5 -5
- package/dist/{chunk-2D7HO6RK.cjs.map → chunk-IENK6OY5.cjs.map} +1 -1
- package/dist/{chunk-752ULO73.cjs → chunk-JQT7PD6Z.cjs} +57 -57
- package/dist/{chunk-752ULO73.cjs.map → chunk-JQT7PD6Z.cjs.map} +1 -1
- package/dist/{chunk-EQO6DKVS.js → chunk-K5P6XKIT.js} +2 -2
- package/dist/{chunk-EASN3OWV.cjs → chunk-L3DRZ2WE.cjs} +82 -82
- package/dist/{chunk-EASN3OWV.cjs.map → chunk-L3DRZ2WE.cjs.map} +1 -1
- package/dist/{chunk-QM4UDTZ3.js → chunk-LIK575RM.js} +2 -2
- package/dist/{chunk-FVKRKWAT.js → chunk-LRA7FGQC.js} +5 -5
- package/dist/{chunk-3Y4XNSHE.js → chunk-MGDDM2UI.js} +3 -3
- package/dist/{chunk-QJGRP2YE.cjs → chunk-NYIZVI4I.cjs} +7 -4
- package/dist/chunk-NYIZVI4I.cjs.map +1 -0
- package/dist/{chunk-CTEN4ADV.js → chunk-PI3XFKEW.js} +2 -2
- package/dist/{chunk-2MYFKIPD.cjs → chunk-QI3PW4F7.cjs} +12 -12
- package/dist/{chunk-2MYFKIPD.cjs.map → chunk-QI3PW4F7.cjs.map} +1 -1
- package/dist/{chunk-5YJJLD66.cjs → chunk-QQAHEWSR.cjs} +31 -31
- package/dist/{chunk-5YJJLD66.cjs.map → chunk-QQAHEWSR.cjs.map} +1 -1
- package/dist/{chunk-2RQKK2BY.cjs → chunk-SZSBHBXE.cjs} +838 -847
- package/dist/chunk-SZSBHBXE.cjs.map +1 -0
- package/dist/{chunk-AI5X5JTD.js → chunk-X2FPWS3P.js} +7 -4
- package/dist/chunk-X2FPWS3P.js.map +1 -0
- package/dist/{chunk-4U3AHQTQ.cjs → chunk-XHCRZDM6.cjs} +37 -37
- package/dist/{chunk-4U3AHQTQ.cjs.map → chunk-XHCRZDM6.cjs.map} +1 -1
- package/dist/{chunk-NP2BBKUM.cjs → chunk-Z56MCQ6X.cjs} +9 -9
- package/dist/{chunk-NP2BBKUM.cjs.map → chunk-Z56MCQ6X.cjs.map} +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +3 -3
- package/dist/components/chat/index.js +2 -2
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +6 -6
- package/dist/components/docs/index.js +5 -5
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/auth/accept-invitation-form.d.ts +3 -9
- package/dist/components/features/auth/accept-invitation-form.d.ts.map +1 -1
- package/dist/components/features/auth/create-organization-form.d.ts +6 -1
- package/dist/components/features/auth/create-organization-form.d.ts.map +1 -1
- package/dist/components/features/auth/invite-link-invalid-modal.d.ts +3 -2
- package/dist/components/features/auth/invite-link-invalid-modal.d.ts.map +1 -1
- package/dist/components/features/auth/password-reset-form.d.ts +3 -1
- package/dist/components/features/auth/password-reset-form.d.ts.map +1 -1
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +21 -21
- package/dist/components/help-center-pages/index.js +12 -12
- package/dist/components/index.cjs +144 -144
- package/dist/components/index.js +10 -10
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/related-content/index.cjs +4 -4
- package/dist/components/related-content/index.js +3 -3
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/field-wrapper.d.ts +3 -3
- package/dist/components/ui/field-wrapper.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -50
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/components/ui/input.d.ts +4 -3
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/status-badge.d.ts +1 -1
- package/dist/components/ui/tag.d.ts +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/src/components/features/auth/accept-invitation-form.tsx +4 -26
- package/src/components/features/auth/create-organization-form.tsx +5 -1
- package/src/components/features/auth/invite-link-invalid-modal.tsx +6 -4
- package/src/components/features/auth/password-reset-form.tsx +7 -0
- package/src/components/ui/field-wrapper.tsx +5 -3
- package/src/components/ui/input.tsx +8 -5
- package/src/stories/auth/AcceptInvitation.stories.tsx +4 -16
- package/src/stories/auth/PasswordReset.stories.tsx +1 -0
- package/dist/chunk-2RQKK2BY.cjs.map +0 -1
- package/dist/chunk-AI5X5JTD.js.map +0 -1
- package/dist/chunk-QJGRP2YE.cjs.map +0 -1
- /package/dist/{chunk-U5WZLNNP.js.map → chunk-23PZFAET.js.map} +0 -0
- /package/dist/{chunk-QQJJJSYG.js.map → chunk-3GGIVFSI.js.map} +0 -0
- /package/dist/{chunk-WE6PHQMR.js.map → chunk-4A4DA7OX.js.map} +0 -0
- /package/dist/{chunk-T3RWEEML.js.map → chunk-DQJCWNRT.js.map} +0 -0
- /package/dist/{chunk-PRA7467B.js.map → chunk-GZ5N76H2.js.map} +0 -0
- /package/dist/{chunk-NC53B5ZJ.js.map → chunk-HDWY2SXF.js.map} +0 -0
- /package/dist/{chunk-EQO6DKVS.js.map → chunk-K5P6XKIT.js.map} +0 -0
- /package/dist/{chunk-QM4UDTZ3.js.map → chunk-LIK575RM.js.map} +0 -0
- /package/dist/{chunk-FVKRKWAT.js.map → chunk-LRA7FGQC.js.map} +0 -0
- /package/dist/{chunk-3Y4XNSHE.js.map → chunk-MGDDM2UI.js.map} +0 -0
- /package/dist/{chunk-CTEN4ADV.js.map → chunk-PI3XFKEW.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-4U3AHQTQ.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-4U3AHQTQ.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 { 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 '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 '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-2xl font-bold 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-2xl font-bold 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=\"font-['Azeret_Mono'] text-[14px] font-semibold uppercase text-ods-text-secondary tracking-wider\">\n {getContentRefLabelOrTitleCase(type)}\n </h3>\n }\n />\n ))}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-XHCRZDM6.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-XHCRZDM6.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 { 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 '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 '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-2xl font-bold 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-2xl font-bold 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=\"font-['Azeret_Mono'] text-[14px] font-semibold uppercase text-ods-text-secondary tracking-wider\">\n {getContentRefLabelOrTitleCase(type)}\n </h3>\n }\n />\n ))}\n </div>\n );\n}\n"]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
var
|
|
9
|
+
var _chunkSZSBHBXEcjs = require('./chunk-SZSBHBXE.cjs');
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
@@ -34,7 +34,7 @@ function DocSearchResultRow({
|
|
|
34
34
|
}) {
|
|
35
35
|
const docType = _optionalChain([result, 'access', _ => _.metadata, 'optionalAccess', _2 => _2.documentType]) || void 0;
|
|
36
36
|
const sourceRepo = _optionalChain([result, 'access', _3 => _3.metadata, 'optionalAccess', _4 => _4.sourceRepo]) || void 0;
|
|
37
|
-
const { Icon: SourceIcon, label: iconLabel } =
|
|
37
|
+
const { Icon: SourceIcon, label: iconLabel } = _chunkSZSBHBXEcjs.resolveSourceIcon.call(void 0, {
|
|
38
38
|
sourceRepo,
|
|
39
39
|
documentType: docType
|
|
40
40
|
});
|
|
@@ -77,7 +77,7 @@ function DocSearchBar({
|
|
|
77
77
|
renderResult
|
|
78
78
|
}) {
|
|
79
79
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
80
|
-
|
|
80
|
+
_chunkSZSBHBXEcjs.SearchInput,
|
|
81
81
|
{
|
|
82
82
|
placeholder,
|
|
83
83
|
value: query,
|
|
@@ -186,7 +186,7 @@ function resolveSearchResultAction(result, source, runtimeMode) {
|
|
|
186
186
|
const externalUrl = meta.externalUrl;
|
|
187
187
|
if (externalUrl) {
|
|
188
188
|
const targetPlatform = meta.targetPlatform;
|
|
189
|
-
const isNewTab =
|
|
189
|
+
const isNewTab = _chunkSZSBHBXEcjs.decideNewTab.call(void 0, {
|
|
190
190
|
href: externalUrl,
|
|
191
191
|
targetPlatform,
|
|
192
192
|
surface: "useUnifiedNav",
|
|
@@ -288,7 +288,7 @@ function useDocSearch(config) {
|
|
|
288
288
|
if (_optionalChain([runtime, 'optionalAccess', _14 => _14.navigation, 'access', _15 => _15.mode]) === "embed") {
|
|
289
289
|
setKeepOpen(true);
|
|
290
290
|
const targetPlatform = _nullishCoalesce(_optionalChain([result, 'access', _16 => _16.metadata, 'optionalAccess', _17 => _17.targetPlatform]), () => ( null));
|
|
291
|
-
|
|
291
|
+
_chunkSZSBHBXEcjs.resolveExternalNavigation.call(void 0, {
|
|
292
292
|
href: action.href,
|
|
293
293
|
targetPlatform,
|
|
294
294
|
runtime
|
|
@@ -297,18 +297,18 @@ function useDocSearch(config) {
|
|
|
297
297
|
}
|
|
298
298
|
if (wantsNewTab) {
|
|
299
299
|
setKeepOpen(true);
|
|
300
|
-
window.open(action.href, "_blank",
|
|
300
|
+
window.open(action.href, "_blank", _chunkSZSBHBXEcjs.NEW_TAB_FEATURES);
|
|
301
301
|
return;
|
|
302
302
|
}
|
|
303
303
|
setKeepOpen(false);
|
|
304
304
|
const path = baseRoute && action.href.startsWith(`${baseRoute}/`) ? action.href.slice(baseRoute.length + 1) : null;
|
|
305
305
|
if (path && _optionalChain([onInPageSwap, 'optionalCall', _18 => _18(path)])) return;
|
|
306
|
-
router.push(
|
|
306
|
+
router.push(_chunkSZSBHBXEcjs.stripSameOriginToPath.call(void 0, action.href));
|
|
307
307
|
return;
|
|
308
308
|
}
|
|
309
309
|
case "navigate-new-tab":
|
|
310
310
|
setKeepOpen(true);
|
|
311
|
-
window.open(action.href, "_blank",
|
|
311
|
+
window.open(action.href, "_blank", _chunkSZSBHBXEcjs.NEW_TAB_FEATURES);
|
|
312
312
|
return;
|
|
313
313
|
case "ask-ai":
|
|
314
314
|
setKeepOpen(false);
|
|
@@ -347,4 +347,4 @@ function useDocSearch(config) {
|
|
|
347
347
|
|
|
348
348
|
|
|
349
349
|
exports.formatRelativePath = formatRelativePath; exports.DocSearchResultRow = DocSearchResultRow; exports.DocSearchBar = DocSearchBar; exports.mapDocSearchResults = mapDocSearchResults; exports.resolveSearchResultAction = resolveSearchResultAction; exports.useDocSearch = useDocSearch;
|
|
350
|
-
//# sourceMappingURL=chunk-
|
|
350
|
+
//# sourceMappingURL=chunk-Z56MCQ6X.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-NP2BBKUM.cjs","../src/components/shared/doc-search/format-relative-path.ts","../src/components/shared/doc-search/doc-search-result-row.tsx","../src/components/shared/doc-search/doc-search-bar.tsx","../src/components/shared/doc-search/map-doc-search-results.ts","../src/components/shared/doc-search/resolve-search-result-action.ts","../src/components/shared/doc-search/use-doc-search.ts"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-NP2BBKUM.cjs","sourcesContent":[null,"/**\n * Format a full document path as a breadcrumb trail.\n * Shows parent folders only (excludes the last segment / filename).\n *\n * @example\n * formatRelativePath('openframe-oss-tenant/architecture/api-controllers.md')\n * // → 'Openframe oss tenant / Architecture'\n */\nexport function formatRelativePath(fullPath: string): string {\n if (!fullPath) return ''\n const segments = fullPath.replace(/\\.md$/, '').split('/')\n // Show only parent path (exclude the filename itself since the title already shows it)\n const parentSegments = segments.length > 1 ? segments.slice(0, -1) : segments\n return parentSegments\n .map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1).replace(/-/g, ' '))\n .join(' / ')\n}\n","'use client'\n\n/**\n * Single row in the `<SearchInput>` dropdown — the standard layout\n * used by every doc-search-backed surface (company-hub data-room\n * search bar, onboarding-guide catalog search, …). Single source of\n * truth for the row appearance so search dropdowns are visually\n * identical everywhere.\n *\n * Resolves the source icon via the same `resolveSourceIcon()`\n * registry the inline chat-card refs use, so a row pointing at e.g.\n * an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the\n * chat card surfaces — no cross-surface drift.\n */\n\nimport { resolveSourceIcon } from '../../chat/utils/source-row-cta'\nimport { formatRelativePath } from './format-relative-path'\n\n/**\n * Minimal result shape this row renders. Compatible with any\n * doc-search hook whose result type exposes `{ title?, path?,\n * metadata? }`. The two hub consumers (onboarding-guide catalog,\n * data-room sidebar) both satisfy this shape via their\n * `useDocSearch` hook result.\n */\nexport interface DocSearchResultRowEntry {\n title?: string\n path?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface DocSearchResultRowProps {\n result: DocSearchResultRowEntry\n isHighlighted: boolean\n}\n\nexport function DocSearchResultRow({\n result,\n isHighlighted,\n}: DocSearchResultRowProps) {\n const docType = (result.metadata?.documentType as string) || undefined\n const sourceRepo = (result.metadata?.sourceRepo as string) || undefined\n const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({\n sourceRepo,\n documentType: docType,\n })\n const isGroup = result.metadata?.isGroup as boolean | undefined\n\n return (\n <div className=\"flex items-center gap-3 w-full min-w-0\">\n <span\n className=\"flex-shrink-0 text-ods-text-secondary\"\n title={iconLabel}\n >\n <SourceIcon className=\"size-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div\n className={`text-sm font-medium leading-5 truncate ${\n isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'\n }`}\n >\n {result.title || result.path}\n </div>\n {!isGroup && result.path?.includes('/') && (\n <div className=\"text-xs leading-4 text-ods-text-secondary truncate mt-0.5\">\n {formatRelativePath(result.path)}\n </div>\n )}\n </div>\n </div>\n )\n}\n","'use client'\n\n/**\n * `<DocSearchBar>` — the canonical RAG-search dropdown surface.\n *\n * Mounted by every doc-search consumer (data-room sidebar, onboarding-\n * guide catalog, and any future surface that needs typeahead against\n * `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard\n * `<DocSearchResultRow>` so the dropdown looks identical everywhere.\n *\n * ## Why a presentation component, not a \"search bar that owns its\n * own hook\"\n *\n * The data-fetching hook (`useDocSearch`) lives hub-side because it\n * depends on hub-only context (`useDocNavigation`, the rag-table-\n * config registry, the hub's `decideNewTab` helper). Moving the hook\n * would cascade ~5 more file migrations into the lib.\n *\n * Instead, the hook stays hub-side and callers pass its result into\n * this component as plain props. Both consumers shrink to ~5 lines.\n */\n\nimport type { ReactNode } from 'react'\nimport { SearchInput, type SearchResult } from '../../ui/search-input'\nimport { DocSearchResultRow } from './doc-search-result-row'\n\nexport interface DocSearchBarProps {\n placeholder: string\n query: string\n onQueryChange: (value: string) => void\n /** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`\n * shape directly so callers don't translate. */\n results: SearchResult[]\n isLoading: boolean\n /** Result selection handler. Mirrors `<SearchInput>` — the second\n * `modifiers` argument is preserved so cmd-click / shift-click on\n * a result row still forces new-tab behavior. Hub `useDocSearch`\n * reads these to short-circuit to `window.open()`. */\n onResultSelect: (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean\n ctrlKey?: boolean\n shiftKey?: boolean\n altKey?: boolean\n button?: number\n },\n ) => void\n /** Lets the caller's hook force the dropdown open after a recent\n * internal action (e.g. result navigation). `undefined` falls back\n * to `<SearchInput>`'s built-in focus/hover heuristics. */\n showDropdown?: boolean\n /** Defaults to 2 — matches the existing data-room and onboarding-\n * guide consumers. Override only if a surface needs different\n * typeahead semantics. */\n minQueryLength?: number\n /** Defaults to 0 — both existing consumers debounce inside the\n * hook, not the input. */\n debounceMs?: number\n className?: string\n /** Optional row-renderer override. Defaults to the lib's standard\n * `<DocSearchResultRow>` (source icon + title + path breadcrumb).\n * Override only when a surface needs custom row chrome. */\n renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode\n}\n\nexport function DocSearchBar({\n placeholder,\n query,\n onQueryChange,\n results,\n isLoading,\n onResultSelect,\n showDropdown,\n minQueryLength = 2,\n debounceMs = 0,\n className = 'w-full',\n renderResult,\n}: DocSearchBarProps) {\n return (\n <SearchInput\n placeholder={placeholder}\n value={query}\n onChange={onQueryChange}\n results={results}\n isLoading={isLoading}\n onResultSelect={onResultSelect}\n showDropdown={showDropdown || undefined}\n debounceMs={debounceMs}\n minQueryLength={minQueryLength}\n className={className}\n renderResult={\n renderResult ??\n ((result, isHighlighted) => (\n <DocSearchResultRow result={result} isHighlighted={isHighlighted} />\n ))\n }\n />\n )\n}\n","/**\n * Map RAG `/api/docs/search` wire results into the `<DocSearchBar>`\n * dropdown's row shape, collapsing entity-table rows into grouped\n * results so the dropdown lists ONE \"Cap Table (12 records)\" row\n * instead of 12 individual rows.\n *\n * Pure transform — no telemetry, no navigation, no React deps. Lifted\n * from the hub's `hooks/use-docs.ts:mapDocSearchResults` (the hub's\n * `traceCompose` call was hub-only telemetry and is intentionally\n * dropped — callers that want logging can wrap this helper).\n */\n\nimport type { SearchResult } from '../../ui/search-input'\nimport type { DocSearchResult } from './types'\n\n/** Source repos that should be collapsed into grouped results in the search bar.\n * Only financial tables (all rows link to the same admin page).\n * Content tables (blog, webinar, podcast, etc.) stay individual since each has a unique URL. */\nconst SEARCH_GROUP_REPOS = new Set([\n 'financial-cap-table',\n 'financial-kpis',\n 'financial-pnl',\n 'financial-balance-sheet',\n 'financial-cash-flow',\n])\n\nconst ENTITY_LABELS: Record<string, string> = {\n 'financial-cap-table': 'Cap Table',\n 'financial-kpis': 'Financial KPIs',\n 'financial-pnl': 'Profit & Loss',\n 'financial-balance-sheet': 'Balance Sheets',\n 'financial-cash-flow': 'Cash Flow',\n 'blog-posts': 'Blog Posts',\n 'product-releases': 'Product Releases',\n 'case-studies': 'Case Studies',\n webinars: 'Webinars',\n events: 'Events',\n podcasts: 'Podcasts',\n}\n\nexport function mapDocSearchResults(docs: DocSearchResult[]): SearchResult[] {\n const entityGroups = new Map<string, DocSearchResult[]>()\n // Track insertion order — groups appear where the FIRST row of that\n // repo appeared in the response.\n const order: Array<\n { type: 'entity'; repo: string } | { type: 'doc'; doc: DocSearchResult }\n > = []\n const seenRepos = new Set<string>()\n\n for (const doc of docs) {\n if (doc.sourceRepo && SEARCH_GROUP_REPOS.has(doc.sourceRepo)) {\n const group = entityGroups.get(doc.sourceRepo) || []\n group.push(doc)\n entityGroups.set(doc.sourceRepo, group)\n if (!seenRepos.has(doc.sourceRepo)) {\n seenRepos.add(doc.sourceRepo)\n order.push({ type: 'entity', repo: doc.sourceRepo })\n }\n } else {\n order.push({ type: 'doc', doc })\n }\n }\n\n const results: SearchResult[] = []\n for (const entry of order) {\n if (entry.type === 'entity') {\n const rows = entityGroups.get(entry.repo)!\n const label = ENTITY_LABELS[entry.repo] || entry.repo\n results.push({\n id: `group-${entry.repo}`,\n title: `${label} (${rows.length} ${rows.length === 1 ? 'record' : 'records'})`,\n path: rows[0].path,\n type: 'file',\n metadata: {\n documentType: rows[0].documentType,\n externalUrl: rows[0].externalUrl,\n sourceRepo: entry.repo,\n id: rows[0].entityId,\n isGroup: true,\n items: rows.map((r) => ({\n name: r.name,\n externalUrl: r.externalUrl,\n id: r.entityId,\n sourceRepo: r.sourceRepo,\n documentType: r.documentType,\n })),\n },\n })\n } else {\n const doc = entry.doc\n const isNonMarkdown = doc.documentType && doc.documentType !== 'markdown'\n results.push({\n id: doc.path,\n title: doc.name,\n description: isNonMarkdown ? doc.name : doc.snippet,\n path: doc.path,\n type: doc.type,\n metadata: {\n matchType: doc.matchType,\n ...(doc.documentType ? { documentType: doc.documentType } : {}),\n ...(doc.externalUrl ? { externalUrl: doc.externalUrl } : {}),\n ...(doc.targetPlatform != null\n ? { targetPlatform: doc.targetPlatform }\n : {}),\n ...(doc.sourceRepo ? { sourceRepo: doc.sourceRepo } : {}),\n ...(doc.entityId ? { id: doc.entityId } : {}),\n },\n })\n }\n }\n\n return results\n}\n","/**\n * Resolve what should happen when the user picks a search result.\n * Returns one of five typed actions so the caller is a single switch.\n *\n * Resolution order:\n * 1. `externalUrl` present → use `decideNewTab` to choose same-tab vs\n * new-tab against the row's `targetPlatform`.\n * 2. Row has `id` + `sourceRepo` + `documentType` → synth an Ask-AI\n * action (entity drill-in via primary key, no URL).\n * 3. Row has only `path` → legacy navigation fallback.\n * 4. Nothing actionable → noop.\n *\n * Lifted from the hub's `hooks/use-docs.ts:resolveSearchResultAction`.\n * Pure — no React, no telemetry.\n */\n\nimport type { SearchResult } from '../../ui/search-input'\nimport type { ChatRef } from '../../chat/chat-ref.types'\nimport { decideNewTab } from '../../chat/utils/decide-new-tab'\n\nexport type SearchResultAction =\n | { kind: 'navigate-same-tab'; href: string }\n | { kind: 'navigate-new-tab'; href: string }\n | { kind: 'ask-ai'; detail: { source: string; ref: ChatRef } }\n | { kind: 'route'; path: string }\n | { kind: 'noop' }\n\nexport function resolveSearchResultAction(\n result: SearchResult,\n source: string,\n runtimeMode?: 'host' | 'embed',\n): SearchResultAction {\n const meta = result.metadata ?? {}\n const externalUrl = meta.externalUrl as string | undefined\n if (externalUrl) {\n // Same pure helper `useNavLink` and `useUnifiedNav` call — single\n // decision rule across cards, chips, and autocomplete rows. Thread\n // the caller's `source` as `currentSource` so the platform-vs-\n // platform comparison matches the hub's pre-migration behavior.\n const targetPlatform = meta.targetPlatform as string | null | undefined\n const isNewTab = decideNewTab({\n href: externalUrl,\n targetPlatform,\n surface: 'useUnifiedNav',\n runtimeMode,\n currentSource: source,\n })\n return isNewTab\n ? { kind: 'navigate-new-tab', href: externalUrl }\n : { kind: 'navigate-same-tab', href: externalUrl }\n }\n const rowId = meta.id as string | undefined\n const sourceRepo = meta.sourceRepo as string | undefined\n const documentType = meta.documentType as string | undefined\n if (rowId && sourceRepo && documentType) {\n return {\n kind: 'ask-ai',\n detail: {\n source,\n ref: { type: documentType, id: rowId, title: result.title, url: null },\n },\n }\n }\n if (result.path) {\n return { kind: 'route', path: result.path }\n }\n return { kind: 'noop' }\n}\n","'use client'\n\n/**\n * `useDocSearch` — debounced RAG-search hook against `/api/docs/search`.\n *\n * Pure fetch + navigation glue. Embedders can mount this directly\n * (any host with a reverse-proxy that exposes `/api/docs/search` will\n * work). Hub callers wire it into the lib `<DocSearchBar>` for the\n * canonical typeahead dropdown.\n *\n * ## What moved from hub to lib\n *\n * Lifted from `multi-platform-hub/hooks/use-docs.ts:useDocSearch`. Two\n * hub-only concerns are now optional injection points instead of\n * direct imports:\n *\n * - `useDocNavigation()` (hub's in-page doc-tree swap) → optional\n * `onInPageSwap?: (path: string) => boolean` config callback. When\n * present and returns true, the hook treats a same-origin result\n * click as \"handled in-page\"; when absent or returns false, the\n * hook falls back to `onNavigate(path)` (`router.push` on hub,\n * `window.location.assign` on bare embedders).\n * - `traceCompose` (hub-only telemetry) → dropped. The lib has no\n * equivalent runtime-context yet; bring it back when there is one.\n *\n * Everything else (debounce, `useChatRuntime` for embed-mode short-\n * circuit, embed-shim router, the action-resolver + result-mapper) is\n * now lib-resident.\n */\n\nimport { useState, useEffect, useCallback } from 'react'\nimport { useRouter } from '../../../embed-shims'\nimport { useDebounce } from '../../../hooks/ui/use-debounce'\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context'\nimport { contentFetch } from '../../../utils/embed-content-fetch'\nimport type { SearchResult } from '../../ui/search-input'\nimport {\n resolveExternalNavigation,\n stripSameOriginToPath,\n NEW_TAB_FEATURES,\n} from '../../chat/utils/chat-nav-resolution'\nimport type { DocSearchResult } from './types'\nimport { mapDocSearchResults } from './map-doc-search-results'\nimport { resolveSearchResultAction } from './resolve-search-result-action'\n\nexport interface UseDocSearchConfig {\n /** Discriminator passed to `/api/docs/search?source=` (e.g.\n * `'openframe'`). Embedders set it to whatever discriminator their\n * reverse-proxy expects. */\n source: string\n /** Base route prefix this search lives under (e.g. `'/onboarding-guides'`).\n * When a result's href starts with `${baseRoute}/`, the hook\n * attempts the optional in-page swap path before falling through\n * to a full nav. */\n baseRoute: string\n /** Imperative navigation fallback. Called when no override\n * (in-page swap, new-tab) applies. Hub callers pass\n * `(path) => router.push(path)`; embedders pass an equivalent. */\n onNavigate: (path: string) => void\n /** Optional `RagTableConfig.id` list to narrow the search to specific\n * tables (e.g. `['onboarding-guides']`). Forwarded to\n * `/api/docs/search?tableIds=…` which intersects with the source's\n * standing set. */\n tableIds?: string[]\n /** Optional in-page swap callback. When the result's href is under\n * `baseRoute` AND this callback returns true, the hook treats the\n * click as handled in-page (no router push). Hub's\n * `<DocumentationSection>` wires this to\n * `useDocNavigation().navigate(path)`. */\n onInPageSwap?: (path: string) => boolean\n /** Optional endpoint override. Defaults to `'/api/docs/search'`\n * (the hub's reverse-proxy route). Embedders with a different\n * path can override. */\n searchEndpoint?: string\n}\n\nexport function useDocSearch(config: UseDocSearchConfig) {\n const {\n source,\n baseRoute,\n onNavigate,\n tableIds,\n onInPageSwap,\n searchEndpoint,\n } = config\n const tableIdsKey = tableIds && tableIds.length > 0 ? tableIds.join(',') : ''\n\n const router = useRouter()\n // Optional chat-runtime read — when present and mode='embed' the\n // search-result row click short-circuits to a new-tab open against\n // the absolutized URL. Null/host preserves today's behavior.\n // Also used as the proxy-prefix fallback for `searchEndpoint`, matching\n // how tickets resolves `findTicketUrl`.\n const runtime = useChatRuntime()\n const resolvedSearchEndpoint =\n searchEndpoint ?? runtime?.endpoints.docsSearchUrl ?? '/api/docs/search'\n\n const [query, setQuery] = useState('')\n const [results, setResults] = useState<SearchResult[]>([])\n const [isFetching, setIsFetching] = useState(false)\n const debouncedQuery = useDebounce(query, 300)\n\n useEffect(() => {\n if (!debouncedQuery || debouncedQuery.trim().length < 2) {\n setResults([])\n setIsFetching(false)\n return\n }\n\n let cancelled = false\n\n async function fetchResults() {\n setIsFetching(true)\n try {\n const params = new URLSearchParams({\n q: debouncedQuery,\n source,\n limit: '10',\n })\n if (tableIdsKey) params.set('tableIds', tableIdsKey)\n\n const response = await contentFetch(`${resolvedSearchEndpoint}?${params.toString()}`)\n if (!response.ok) {\n throw new Error(`Search request failed: ${response.status}`)\n }\n\n const json = await response.json()\n\n if (!cancelled && json.success && Array.isArray(json.data)) {\n const mapped = mapDocSearchResults(json.data as DocSearchResult[])\n setResults(mapped)\n }\n } catch (error) {\n console.error('Doc search error:', error)\n if (!cancelled) {\n setResults([])\n }\n } finally {\n if (!cancelled) {\n setIsFetching(false)\n }\n }\n }\n\n fetchResults()\n\n return () => {\n cancelled = true\n }\n }, [debouncedQuery, source, tableIdsKey, resolvedSearchEndpoint])\n\n // Derived loading state — single source of truth for \"should the\n // dropdown show 'Loading...' instead of 'No results found'\":\n const isLoading =\n query.trim().length >= 2 && (query !== debouncedQuery || isFetching)\n\n // Track whether dropdown should stay open (external link opened in new tab).\n const [keepOpen, setKeepOpen] = useState(false)\n\n const handleResultSelect = useCallback(\n (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean\n ctrlKey?: boolean\n shiftKey?: boolean\n altKey?: boolean\n button?: number\n },\n ) => {\n const action = resolveSearchResultAction(\n result,\n source,\n runtime?.navigation.mode,\n )\n // Modifier / non-primary mouse click → force new tab regardless of\n // same-tab/new-tab decision. The dropdown row is a `<div>`, not an\n // `<a target=\"_blank\">`, so the browser doesn't background-tab\n // natively on cmd-click. Honor it explicitly here for parity with\n // the anchor-based surfaces (cards, chips, related-content). Plain\n // Enter from the keyboard passes `modifiers === undefined`.\n const wantsNewTab =\n modifiers &&\n (modifiers.metaKey ||\n modifiers.ctrlKey ||\n modifiers.shiftKey ||\n modifiers.altKey ||\n (typeof modifiers.button === 'number' && modifiers.button !== 0))\n switch (action.kind) {\n case 'navigate-same-tab': {\n // Embed-mode short-circuit — autocomplete row clicked while\n // the chat panel is hosted inside an embedding app.\n if (runtime?.navigation.mode === 'embed') {\n setKeepOpen(true)\n const targetPlatform =\n (result.metadata?.targetPlatform as string | null | undefined) ?? null\n resolveExternalNavigation({\n href: action.href,\n targetPlatform,\n runtime,\n }).open()\n return\n }\n if (wantsNewTab) {\n setKeepOpen(true)\n window.open(action.href, '_blank', NEW_TAB_FEATURES)\n return\n }\n // Same-origin click:\n // 1. If the href is under the current doc-tree's baseRoute AND\n // an `onInPageSwap` callback is wired AND returns true →\n // consider in-page swap handled.\n // 2. Otherwise → embed-shim `router.push()` (soft RSC nav on\n // Next.js hosts, window.location.assign on bare hosts).\n setKeepOpen(false)\n const path =\n baseRoute && action.href.startsWith(`${baseRoute}/`)\n ? action.href.slice(baseRoute.length + 1)\n : null\n if (path && onInPageSwap?.(path)) return\n router.push(stripSameOriginToPath(action.href))\n return\n }\n case 'navigate-new-tab':\n // Cross-origin (e.g. clicking a flamingo.run release from\n // product-hub) — open in a new tab. Keep dropdown open so the\n // user can pick another result without re-searching.\n setKeepOpen(true)\n window.open(action.href, '_blank', NEW_TAB_FEATURES)\n return\n case 'ask-ai':\n // Row is searchable-but-not-openable (cap_table positions,\n // financial-kpi snapshots, anything backed by\n // `resolveUrl: () => null`). Dispatch a CustomEvent that\n // GlobalAskAI listens for — opens chat + drills via\n // `entityIdFilter` (primary-key only, same as inline-card Ask).\n setKeepOpen(false)\n window.dispatchEvent(\n new CustomEvent('ask-ai:open-with-ref', { detail: action.detail }),\n )\n return\n case 'route':\n // Final fallback: legacy navigation by path. Hits when a row\n // has neither URL nor pk metadata — a mapper/API regression.\n setKeepOpen(false)\n onNavigate(action.path)\n return\n case 'noop':\n return\n }\n },\n [onNavigate, source, baseRoute, router, onInPageSwap, runtime],\n )\n\n // Reset keepOpen when query changes.\n useEffect(() => {\n setKeepOpen(false)\n }, [query])\n\n return {\n query,\n setQuery,\n results,\n isLoading,\n handleResultSelect,\n keepDropdownOpen: keepOpen,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-Z56MCQ6X.cjs","../src/components/shared/doc-search/format-relative-path.ts","../src/components/shared/doc-search/doc-search-result-row.tsx","../src/components/shared/doc-search/doc-search-bar.tsx","../src/components/shared/doc-search/map-doc-search-results.ts","../src/components/shared/doc-search/resolve-search-result-action.ts","../src/components/shared/doc-search/use-doc-search.ts"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-Z56MCQ6X.cjs","sourcesContent":[null,"/**\n * Format a full document path as a breadcrumb trail.\n * Shows parent folders only (excludes the last segment / filename).\n *\n * @example\n * formatRelativePath('openframe-oss-tenant/architecture/api-controllers.md')\n * // → 'Openframe oss tenant / Architecture'\n */\nexport function formatRelativePath(fullPath: string): string {\n if (!fullPath) return ''\n const segments = fullPath.replace(/\\.md$/, '').split('/')\n // Show only parent path (exclude the filename itself since the title already shows it)\n const parentSegments = segments.length > 1 ? segments.slice(0, -1) : segments\n return parentSegments\n .map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1).replace(/-/g, ' '))\n .join(' / ')\n}\n","'use client'\n\n/**\n * Single row in the `<SearchInput>` dropdown — the standard layout\n * used by every doc-search-backed surface (company-hub data-room\n * search bar, onboarding-guide catalog search, …). Single source of\n * truth for the row appearance so search dropdowns are visually\n * identical everywhere.\n *\n * Resolves the source icon via the same `resolveSourceIcon()`\n * registry the inline chat-card refs use, so a row pointing at e.g.\n * an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the\n * chat card surfaces — no cross-surface drift.\n */\n\nimport { resolveSourceIcon } from '../../chat/utils/source-row-cta'\nimport { formatRelativePath } from './format-relative-path'\n\n/**\n * Minimal result shape this row renders. Compatible with any\n * doc-search hook whose result type exposes `{ title?, path?,\n * metadata? }`. The two hub consumers (onboarding-guide catalog,\n * data-room sidebar) both satisfy this shape via their\n * `useDocSearch` hook result.\n */\nexport interface DocSearchResultRowEntry {\n title?: string\n path?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface DocSearchResultRowProps {\n result: DocSearchResultRowEntry\n isHighlighted: boolean\n}\n\nexport function DocSearchResultRow({\n result,\n isHighlighted,\n}: DocSearchResultRowProps) {\n const docType = (result.metadata?.documentType as string) || undefined\n const sourceRepo = (result.metadata?.sourceRepo as string) || undefined\n const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({\n sourceRepo,\n documentType: docType,\n })\n const isGroup = result.metadata?.isGroup as boolean | undefined\n\n return (\n <div className=\"flex items-center gap-3 w-full min-w-0\">\n <span\n className=\"flex-shrink-0 text-ods-text-secondary\"\n title={iconLabel}\n >\n <SourceIcon className=\"size-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div\n className={`text-sm font-medium leading-5 truncate ${\n isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'\n }`}\n >\n {result.title || result.path}\n </div>\n {!isGroup && result.path?.includes('/') && (\n <div className=\"text-xs leading-4 text-ods-text-secondary truncate mt-0.5\">\n {formatRelativePath(result.path)}\n </div>\n )}\n </div>\n </div>\n )\n}\n","'use client'\n\n/**\n * `<DocSearchBar>` — the canonical RAG-search dropdown surface.\n *\n * Mounted by every doc-search consumer (data-room sidebar, onboarding-\n * guide catalog, and any future surface that needs typeahead against\n * `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard\n * `<DocSearchResultRow>` so the dropdown looks identical everywhere.\n *\n * ## Why a presentation component, not a \"search bar that owns its\n * own hook\"\n *\n * The data-fetching hook (`useDocSearch`) lives hub-side because it\n * depends on hub-only context (`useDocNavigation`, the rag-table-\n * config registry, the hub's `decideNewTab` helper). Moving the hook\n * would cascade ~5 more file migrations into the lib.\n *\n * Instead, the hook stays hub-side and callers pass its result into\n * this component as plain props. Both consumers shrink to ~5 lines.\n */\n\nimport type { ReactNode } from 'react'\nimport { SearchInput, type SearchResult } from '../../ui/search-input'\nimport { DocSearchResultRow } from './doc-search-result-row'\n\nexport interface DocSearchBarProps {\n placeholder: string\n query: string\n onQueryChange: (value: string) => void\n /** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`\n * shape directly so callers don't translate. */\n results: SearchResult[]\n isLoading: boolean\n /** Result selection handler. Mirrors `<SearchInput>` — the second\n * `modifiers` argument is preserved so cmd-click / shift-click on\n * a result row still forces new-tab behavior. Hub `useDocSearch`\n * reads these to short-circuit to `window.open()`. */\n onResultSelect: (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean\n ctrlKey?: boolean\n shiftKey?: boolean\n altKey?: boolean\n button?: number\n },\n ) => void\n /** Lets the caller's hook force the dropdown open after a recent\n * internal action (e.g. result navigation). `undefined` falls back\n * to `<SearchInput>`'s built-in focus/hover heuristics. */\n showDropdown?: boolean\n /** Defaults to 2 — matches the existing data-room and onboarding-\n * guide consumers. Override only if a surface needs different\n * typeahead semantics. */\n minQueryLength?: number\n /** Defaults to 0 — both existing consumers debounce inside the\n * hook, not the input. */\n debounceMs?: number\n className?: string\n /** Optional row-renderer override. Defaults to the lib's standard\n * `<DocSearchResultRow>` (source icon + title + path breadcrumb).\n * Override only when a surface needs custom row chrome. */\n renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode\n}\n\nexport function DocSearchBar({\n placeholder,\n query,\n onQueryChange,\n results,\n isLoading,\n onResultSelect,\n showDropdown,\n minQueryLength = 2,\n debounceMs = 0,\n className = 'w-full',\n renderResult,\n}: DocSearchBarProps) {\n return (\n <SearchInput\n placeholder={placeholder}\n value={query}\n onChange={onQueryChange}\n results={results}\n isLoading={isLoading}\n onResultSelect={onResultSelect}\n showDropdown={showDropdown || undefined}\n debounceMs={debounceMs}\n minQueryLength={minQueryLength}\n className={className}\n renderResult={\n renderResult ??\n ((result, isHighlighted) => (\n <DocSearchResultRow result={result} isHighlighted={isHighlighted} />\n ))\n }\n />\n )\n}\n","/**\n * Map RAG `/api/docs/search` wire results into the `<DocSearchBar>`\n * dropdown's row shape, collapsing entity-table rows into grouped\n * results so the dropdown lists ONE \"Cap Table (12 records)\" row\n * instead of 12 individual rows.\n *\n * Pure transform — no telemetry, no navigation, no React deps. Lifted\n * from the hub's `hooks/use-docs.ts:mapDocSearchResults` (the hub's\n * `traceCompose` call was hub-only telemetry and is intentionally\n * dropped — callers that want logging can wrap this helper).\n */\n\nimport type { SearchResult } from '../../ui/search-input'\nimport type { DocSearchResult } from './types'\n\n/** Source repos that should be collapsed into grouped results in the search bar.\n * Only financial tables (all rows link to the same admin page).\n * Content tables (blog, webinar, podcast, etc.) stay individual since each has a unique URL. */\nconst SEARCH_GROUP_REPOS = new Set([\n 'financial-cap-table',\n 'financial-kpis',\n 'financial-pnl',\n 'financial-balance-sheet',\n 'financial-cash-flow',\n])\n\nconst ENTITY_LABELS: Record<string, string> = {\n 'financial-cap-table': 'Cap Table',\n 'financial-kpis': 'Financial KPIs',\n 'financial-pnl': 'Profit & Loss',\n 'financial-balance-sheet': 'Balance Sheets',\n 'financial-cash-flow': 'Cash Flow',\n 'blog-posts': 'Blog Posts',\n 'product-releases': 'Product Releases',\n 'case-studies': 'Case Studies',\n webinars: 'Webinars',\n events: 'Events',\n podcasts: 'Podcasts',\n}\n\nexport function mapDocSearchResults(docs: DocSearchResult[]): SearchResult[] {\n const entityGroups = new Map<string, DocSearchResult[]>()\n // Track insertion order — groups appear where the FIRST row of that\n // repo appeared in the response.\n const order: Array<\n { type: 'entity'; repo: string } | { type: 'doc'; doc: DocSearchResult }\n > = []\n const seenRepos = new Set<string>()\n\n for (const doc of docs) {\n if (doc.sourceRepo && SEARCH_GROUP_REPOS.has(doc.sourceRepo)) {\n const group = entityGroups.get(doc.sourceRepo) || []\n group.push(doc)\n entityGroups.set(doc.sourceRepo, group)\n if (!seenRepos.has(doc.sourceRepo)) {\n seenRepos.add(doc.sourceRepo)\n order.push({ type: 'entity', repo: doc.sourceRepo })\n }\n } else {\n order.push({ type: 'doc', doc })\n }\n }\n\n const results: SearchResult[] = []\n for (const entry of order) {\n if (entry.type === 'entity') {\n const rows = entityGroups.get(entry.repo)!\n const label = ENTITY_LABELS[entry.repo] || entry.repo\n results.push({\n id: `group-${entry.repo}`,\n title: `${label} (${rows.length} ${rows.length === 1 ? 'record' : 'records'})`,\n path: rows[0].path,\n type: 'file',\n metadata: {\n documentType: rows[0].documentType,\n externalUrl: rows[0].externalUrl,\n sourceRepo: entry.repo,\n id: rows[0].entityId,\n isGroup: true,\n items: rows.map((r) => ({\n name: r.name,\n externalUrl: r.externalUrl,\n id: r.entityId,\n sourceRepo: r.sourceRepo,\n documentType: r.documentType,\n })),\n },\n })\n } else {\n const doc = entry.doc\n const isNonMarkdown = doc.documentType && doc.documentType !== 'markdown'\n results.push({\n id: doc.path,\n title: doc.name,\n description: isNonMarkdown ? doc.name : doc.snippet,\n path: doc.path,\n type: doc.type,\n metadata: {\n matchType: doc.matchType,\n ...(doc.documentType ? { documentType: doc.documentType } : {}),\n ...(doc.externalUrl ? { externalUrl: doc.externalUrl } : {}),\n ...(doc.targetPlatform != null\n ? { targetPlatform: doc.targetPlatform }\n : {}),\n ...(doc.sourceRepo ? { sourceRepo: doc.sourceRepo } : {}),\n ...(doc.entityId ? { id: doc.entityId } : {}),\n },\n })\n }\n }\n\n return results\n}\n","/**\n * Resolve what should happen when the user picks a search result.\n * Returns one of five typed actions so the caller is a single switch.\n *\n * Resolution order:\n * 1. `externalUrl` present → use `decideNewTab` to choose same-tab vs\n * new-tab against the row's `targetPlatform`.\n * 2. Row has `id` + `sourceRepo` + `documentType` → synth an Ask-AI\n * action (entity drill-in via primary key, no URL).\n * 3. Row has only `path` → legacy navigation fallback.\n * 4. Nothing actionable → noop.\n *\n * Lifted from the hub's `hooks/use-docs.ts:resolveSearchResultAction`.\n * Pure — no React, no telemetry.\n */\n\nimport type { SearchResult } from '../../ui/search-input'\nimport type { ChatRef } from '../../chat/chat-ref.types'\nimport { decideNewTab } from '../../chat/utils/decide-new-tab'\n\nexport type SearchResultAction =\n | { kind: 'navigate-same-tab'; href: string }\n | { kind: 'navigate-new-tab'; href: string }\n | { kind: 'ask-ai'; detail: { source: string; ref: ChatRef } }\n | { kind: 'route'; path: string }\n | { kind: 'noop' }\n\nexport function resolveSearchResultAction(\n result: SearchResult,\n source: string,\n runtimeMode?: 'host' | 'embed',\n): SearchResultAction {\n const meta = result.metadata ?? {}\n const externalUrl = meta.externalUrl as string | undefined\n if (externalUrl) {\n // Same pure helper `useNavLink` and `useUnifiedNav` call — single\n // decision rule across cards, chips, and autocomplete rows. Thread\n // the caller's `source` as `currentSource` so the platform-vs-\n // platform comparison matches the hub's pre-migration behavior.\n const targetPlatform = meta.targetPlatform as string | null | undefined\n const isNewTab = decideNewTab({\n href: externalUrl,\n targetPlatform,\n surface: 'useUnifiedNav',\n runtimeMode,\n currentSource: source,\n })\n return isNewTab\n ? { kind: 'navigate-new-tab', href: externalUrl }\n : { kind: 'navigate-same-tab', href: externalUrl }\n }\n const rowId = meta.id as string | undefined\n const sourceRepo = meta.sourceRepo as string | undefined\n const documentType = meta.documentType as string | undefined\n if (rowId && sourceRepo && documentType) {\n return {\n kind: 'ask-ai',\n detail: {\n source,\n ref: { type: documentType, id: rowId, title: result.title, url: null },\n },\n }\n }\n if (result.path) {\n return { kind: 'route', path: result.path }\n }\n return { kind: 'noop' }\n}\n","'use client'\n\n/**\n * `useDocSearch` — debounced RAG-search hook against `/api/docs/search`.\n *\n * Pure fetch + navigation glue. Embedders can mount this directly\n * (any host with a reverse-proxy that exposes `/api/docs/search` will\n * work). Hub callers wire it into the lib `<DocSearchBar>` for the\n * canonical typeahead dropdown.\n *\n * ## What moved from hub to lib\n *\n * Lifted from `multi-platform-hub/hooks/use-docs.ts:useDocSearch`. Two\n * hub-only concerns are now optional injection points instead of\n * direct imports:\n *\n * - `useDocNavigation()` (hub's in-page doc-tree swap) → optional\n * `onInPageSwap?: (path: string) => boolean` config callback. When\n * present and returns true, the hook treats a same-origin result\n * click as \"handled in-page\"; when absent or returns false, the\n * hook falls back to `onNavigate(path)` (`router.push` on hub,\n * `window.location.assign` on bare embedders).\n * - `traceCompose` (hub-only telemetry) → dropped. The lib has no\n * equivalent runtime-context yet; bring it back when there is one.\n *\n * Everything else (debounce, `useChatRuntime` for embed-mode short-\n * circuit, embed-shim router, the action-resolver + result-mapper) is\n * now lib-resident.\n */\n\nimport { useState, useEffect, useCallback } from 'react'\nimport { useRouter } from '../../../embed-shims'\nimport { useDebounce } from '../../../hooks/ui/use-debounce'\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context'\nimport { contentFetch } from '../../../utils/embed-content-fetch'\nimport type { SearchResult } from '../../ui/search-input'\nimport {\n resolveExternalNavigation,\n stripSameOriginToPath,\n NEW_TAB_FEATURES,\n} from '../../chat/utils/chat-nav-resolution'\nimport type { DocSearchResult } from './types'\nimport { mapDocSearchResults } from './map-doc-search-results'\nimport { resolveSearchResultAction } from './resolve-search-result-action'\n\nexport interface UseDocSearchConfig {\n /** Discriminator passed to `/api/docs/search?source=` (e.g.\n * `'openframe'`). Embedders set it to whatever discriminator their\n * reverse-proxy expects. */\n source: string\n /** Base route prefix this search lives under (e.g. `'/onboarding-guides'`).\n * When a result's href starts with `${baseRoute}/`, the hook\n * attempts the optional in-page swap path before falling through\n * to a full nav. */\n baseRoute: string\n /** Imperative navigation fallback. Called when no override\n * (in-page swap, new-tab) applies. Hub callers pass\n * `(path) => router.push(path)`; embedders pass an equivalent. */\n onNavigate: (path: string) => void\n /** Optional `RagTableConfig.id` list to narrow the search to specific\n * tables (e.g. `['onboarding-guides']`). Forwarded to\n * `/api/docs/search?tableIds=…` which intersects with the source's\n * standing set. */\n tableIds?: string[]\n /** Optional in-page swap callback. When the result's href is under\n * `baseRoute` AND this callback returns true, the hook treats the\n * click as handled in-page (no router push). Hub's\n * `<DocumentationSection>` wires this to\n * `useDocNavigation().navigate(path)`. */\n onInPageSwap?: (path: string) => boolean\n /** Optional endpoint override. Defaults to `'/api/docs/search'`\n * (the hub's reverse-proxy route). Embedders with a different\n * path can override. */\n searchEndpoint?: string\n}\n\nexport function useDocSearch(config: UseDocSearchConfig) {\n const {\n source,\n baseRoute,\n onNavigate,\n tableIds,\n onInPageSwap,\n searchEndpoint,\n } = config\n const tableIdsKey = tableIds && tableIds.length > 0 ? tableIds.join(',') : ''\n\n const router = useRouter()\n // Optional chat-runtime read — when present and mode='embed' the\n // search-result row click short-circuits to a new-tab open against\n // the absolutized URL. Null/host preserves today's behavior.\n // Also used as the proxy-prefix fallback for `searchEndpoint`, matching\n // how tickets resolves `findTicketUrl`.\n const runtime = useChatRuntime()\n const resolvedSearchEndpoint =\n searchEndpoint ?? runtime?.endpoints.docsSearchUrl ?? '/api/docs/search'\n\n const [query, setQuery] = useState('')\n const [results, setResults] = useState<SearchResult[]>([])\n const [isFetching, setIsFetching] = useState(false)\n const debouncedQuery = useDebounce(query, 300)\n\n useEffect(() => {\n if (!debouncedQuery || debouncedQuery.trim().length < 2) {\n setResults([])\n setIsFetching(false)\n return\n }\n\n let cancelled = false\n\n async function fetchResults() {\n setIsFetching(true)\n try {\n const params = new URLSearchParams({\n q: debouncedQuery,\n source,\n limit: '10',\n })\n if (tableIdsKey) params.set('tableIds', tableIdsKey)\n\n const response = await contentFetch(`${resolvedSearchEndpoint}?${params.toString()}`)\n if (!response.ok) {\n throw new Error(`Search request failed: ${response.status}`)\n }\n\n const json = await response.json()\n\n if (!cancelled && json.success && Array.isArray(json.data)) {\n const mapped = mapDocSearchResults(json.data as DocSearchResult[])\n setResults(mapped)\n }\n } catch (error) {\n console.error('Doc search error:', error)\n if (!cancelled) {\n setResults([])\n }\n } finally {\n if (!cancelled) {\n setIsFetching(false)\n }\n }\n }\n\n fetchResults()\n\n return () => {\n cancelled = true\n }\n }, [debouncedQuery, source, tableIdsKey, resolvedSearchEndpoint])\n\n // Derived loading state — single source of truth for \"should the\n // dropdown show 'Loading...' instead of 'No results found'\":\n const isLoading =\n query.trim().length >= 2 && (query !== debouncedQuery || isFetching)\n\n // Track whether dropdown should stay open (external link opened in new tab).\n const [keepOpen, setKeepOpen] = useState(false)\n\n const handleResultSelect = useCallback(\n (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean\n ctrlKey?: boolean\n shiftKey?: boolean\n altKey?: boolean\n button?: number\n },\n ) => {\n const action = resolveSearchResultAction(\n result,\n source,\n runtime?.navigation.mode,\n )\n // Modifier / non-primary mouse click → force new tab regardless of\n // same-tab/new-tab decision. The dropdown row is a `<div>`, not an\n // `<a target=\"_blank\">`, so the browser doesn't background-tab\n // natively on cmd-click. Honor it explicitly here for parity with\n // the anchor-based surfaces (cards, chips, related-content). Plain\n // Enter from the keyboard passes `modifiers === undefined`.\n const wantsNewTab =\n modifiers &&\n (modifiers.metaKey ||\n modifiers.ctrlKey ||\n modifiers.shiftKey ||\n modifiers.altKey ||\n (typeof modifiers.button === 'number' && modifiers.button !== 0))\n switch (action.kind) {\n case 'navigate-same-tab': {\n // Embed-mode short-circuit — autocomplete row clicked while\n // the chat panel is hosted inside an embedding app.\n if (runtime?.navigation.mode === 'embed') {\n setKeepOpen(true)\n const targetPlatform =\n (result.metadata?.targetPlatform as string | null | undefined) ?? null\n resolveExternalNavigation({\n href: action.href,\n targetPlatform,\n runtime,\n }).open()\n return\n }\n if (wantsNewTab) {\n setKeepOpen(true)\n window.open(action.href, '_blank', NEW_TAB_FEATURES)\n return\n }\n // Same-origin click:\n // 1. If the href is under the current doc-tree's baseRoute AND\n // an `onInPageSwap` callback is wired AND returns true →\n // consider in-page swap handled.\n // 2. Otherwise → embed-shim `router.push()` (soft RSC nav on\n // Next.js hosts, window.location.assign on bare hosts).\n setKeepOpen(false)\n const path =\n baseRoute && action.href.startsWith(`${baseRoute}/`)\n ? action.href.slice(baseRoute.length + 1)\n : null\n if (path && onInPageSwap?.(path)) return\n router.push(stripSameOriginToPath(action.href))\n return\n }\n case 'navigate-new-tab':\n // Cross-origin (e.g. clicking a flamingo.run release from\n // product-hub) — open in a new tab. Keep dropdown open so the\n // user can pick another result without re-searching.\n setKeepOpen(true)\n window.open(action.href, '_blank', NEW_TAB_FEATURES)\n return\n case 'ask-ai':\n // Row is searchable-but-not-openable (cap_table positions,\n // financial-kpi snapshots, anything backed by\n // `resolveUrl: () => null`). Dispatch a CustomEvent that\n // GlobalAskAI listens for — opens chat + drills via\n // `entityIdFilter` (primary-key only, same as inline-card Ask).\n setKeepOpen(false)\n window.dispatchEvent(\n new CustomEvent('ask-ai:open-with-ref', { detail: action.detail }),\n )\n return\n case 'route':\n // Final fallback: legacy navigation by path. Hits when a row\n // has neither URL nor pk metadata — a mapper/API regression.\n setKeepOpen(false)\n onNavigate(action.path)\n return\n case 'noop':\n return\n }\n },\n [onNavigate, source, baseRoute, router, onInPageSwap, runtime],\n )\n\n // Reset keepOpen when query changes.\n useEffect(() => {\n setKeepOpen(false)\n }, [query])\n\n return {\n query,\n setQuery,\n results,\n isLoading,\n handleResultSelect,\n keepDropdownOpen: keepOpen,\n }\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
3
|
declare const badgeVariants: (props?: ({
|
|
4
|
-
variant?: "
|
|
4
|
+
variant?: "success" | "default" | "outline" | "destructive" | "secondary" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
|
|
7
7
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunk32YONDMHcjs = require('../../chunk-32YONDMH.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunkSZSBHBXEcjs = require('../../chunk-SZSBHBXE.cjs');
|
|
9
9
|
require('../../chunk-743FCO64.cjs');
|
|
10
10
|
require('../../chunk-BGD345ZV.cjs');
|
|
11
11
|
require('../../chunk-3KEBI6TP.cjs');
|
|
12
12
|
require('../../chunk-BZFW3FOF.cjs');
|
|
13
13
|
require('../../chunk-G7UE6RKV.cjs');
|
|
14
14
|
require('../../chunk-XL4V2PYG.cjs');
|
|
15
|
-
require('../../chunk-
|
|
15
|
+
require('../../chunk-NYIZVI4I.cjs');
|
|
16
16
|
require('../../chunk-WZW7C7TF.cjs');
|
|
17
17
|
require('../../chunk-WBR7H6E3.cjs');
|
|
18
18
|
|
|
@@ -71,9 +71,9 @@ function ShareExperienceSection({
|
|
|
71
71
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "text-h2", children: howItWorksTitle }),
|
|
72
72
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-h4", children: howItWorksBody })
|
|
73
73
|
] }),
|
|
74
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
74
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkSZSBHBXEcjs.BenefitCardGrid, { columns: 4, children: [
|
|
75
75
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
76
|
-
|
|
76
|
+
_chunkSZSBHBXEcjs.BenefitCard,
|
|
77
77
|
{
|
|
78
78
|
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bg-ods-background border border-ods-border rounded-md p-2 w-12 h-12 flex items-center justify-center", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWNPMLIHcjs.G2Icon, { width: 24, height: 24 }) }),
|
|
79
79
|
title: "G2",
|
|
@@ -82,7 +82,7 @@ function ShareExperienceSection({
|
|
|
82
82
|
}
|
|
83
83
|
),
|
|
84
84
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
85
|
-
|
|
85
|
+
_chunkSZSBHBXEcjs.BenefitCard,
|
|
86
86
|
{
|
|
87
87
|
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bg-ods-background border border-ods-border rounded-md p-2 w-12 h-12 flex items-center justify-center", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWNPMLIHcjs.CapterraIcon, { width: 24, height: 24 }) }),
|
|
88
88
|
title: "Capterra",
|
|
@@ -91,7 +91,7 @@ function ShareExperienceSection({
|
|
|
91
91
|
}
|
|
92
92
|
),
|
|
93
93
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
94
|
-
|
|
94
|
+
_chunkSZSBHBXEcjs.BenefitCard,
|
|
95
95
|
{
|
|
96
96
|
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bg-ods-background border border-ods-border rounded-md p-2 w-12 h-12 flex items-center justify-center", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWNPMLIHcjs.TrustpilotIcon, { width: 24, height: 24 }) }),
|
|
97
97
|
title: "TrustPilot",
|
|
@@ -100,7 +100,7 @@ function ShareExperienceSection({
|
|
|
100
100
|
}
|
|
101
101
|
),
|
|
102
102
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
103
|
-
|
|
103
|
+
_chunkSZSBHBXEcjs.BenefitCard,
|
|
104
104
|
{
|
|
105
105
|
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bg-ods-background border border-ods-border rounded-md p-2 w-12 h-12 flex items-center justify-center", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWNPMLIHcjs.GetAppIcon, { width: 24, height: 24 }) }),
|
|
106
106
|
title: "GetApp",
|
|
@@ -110,7 +110,7 @@ function ShareExperienceSection({
|
|
|
110
110
|
)
|
|
111
111
|
] }),
|
|
112
112
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
113
|
-
|
|
113
|
+
_chunk32YONDMHcjs.ContactForm,
|
|
114
114
|
{
|
|
115
115
|
...DEFAULT_CONTACT_FORM_PROPS,
|
|
116
116
|
...contactFormProps
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
ContactForm
|
|
4
|
-
} from "../../chunk-
|
|
4
|
+
} from "../../chunk-MGDDM2UI.js";
|
|
5
5
|
import {
|
|
6
6
|
BenefitCard,
|
|
7
7
|
BenefitCardGrid
|
|
8
|
-
} from "../../chunk-
|
|
8
|
+
} from "../../chunk-GZIHB6TH.js";
|
|
9
9
|
import "../../chunk-KQESCGVG.js";
|
|
10
10
|
import "../../chunk-OYGFWWLQ.js";
|
|
11
11
|
import "../../chunk-NA7XJRWB.js";
|
|
12
12
|
import "../../chunk-EL5YVPD5.js";
|
|
13
13
|
import "../../chunk-PLJLE4A4.js";
|
|
14
14
|
import "../../chunk-LXC6P2EO.js";
|
|
15
|
-
import "../../chunk-
|
|
15
|
+
import "../../chunk-X2FPWS3P.js";
|
|
16
16
|
import "../../chunk-6U3IUD57.js";
|
|
17
17
|
import "../../chunk-OHPI2HRK.js";
|
|
18
18
|
import {
|