@flamingo-stack/openframe-frontend-core 0.0.279 → 0.0.280
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-776ZURDZ.cjs → chunk-23DPR7HU.cjs} +15 -15
- package/dist/{chunk-776ZURDZ.cjs.map → chunk-23DPR7HU.cjs.map} +1 -1
- package/dist/{chunk-7ECI5ZVO.js → chunk-5ZOWHCFN.js} +3 -3
- package/dist/{chunk-HR4CVAVH.cjs → chunk-6QZ656YE.cjs} +12 -10
- package/dist/chunk-6QZ656YE.cjs.map +1 -0
- package/dist/{chunk-Z53YSXDD.cjs → chunk-ADJKXYCZ.cjs} +303 -303
- package/dist/{chunk-Z53YSXDD.cjs.map → chunk-ADJKXYCZ.cjs.map} +1 -1
- package/dist/{chunk-7XUBAK5K.cjs → chunk-AUVMURC5.cjs} +12 -12
- package/dist/{chunk-7XUBAK5K.cjs.map → chunk-AUVMURC5.cjs.map} +1 -1
- package/dist/{chunk-R6SHY6ZQ.js → chunk-AWFHDRRT.js} +2 -2
- package/dist/{chunk-XBTQK32O.cjs → chunk-CBVCOXC5.cjs} +27 -27
- package/dist/{chunk-XBTQK32O.cjs.map → chunk-CBVCOXC5.cjs.map} +1 -1
- package/dist/{chunk-QP4H2JJN.cjs → chunk-IIS4LQEO.cjs} +38 -38
- package/dist/{chunk-QP4H2JJN.cjs.map → chunk-IIS4LQEO.cjs.map} +1 -1
- package/dist/{chunk-6ZMD3KBP.js → chunk-KLBB2J7W.js} +3 -3
- package/dist/{chunk-SZG56DA3.js → chunk-PE5AMPHQ.js} +3 -3
- package/dist/{chunk-AVLSICZO.js → chunk-QHTJTSHT.js} +2 -2
- package/dist/{chunk-7XVVRE53.cjs → chunk-U72ACGRR.cjs} +10 -10
- package/dist/{chunk-7XVVRE53.cjs.map → chunk-U72ACGRR.cjs.map} +1 -1
- package/dist/{chunk-RGCVYGA2.js → chunk-UV55CG7P.js} +12 -10
- package/dist/chunk-UV55CG7P.js.map +1 -0
- package/dist/{chunk-YK6FFIVG.js → chunk-Y2ZFRZ5Q.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/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/index.cjs +85 -85
- package/dist/components/index.js +6 -6
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/onboarding-guides/index.cjs +22 -22
- package/dist/components/onboarding-guides/index.js +2 -2
- package/dist/components/related-content/index.cjs +4 -4
- package/dist/components/related-content/index.js +3 -3
- package/dist/components/tickets/index.cjs +61 -61
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/components/ui/square-avatar.d.ts.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/src/components/ui/square-avatar.tsx +11 -9
- package/dist/chunk-HR4CVAVH.cjs.map +0 -1
- package/dist/chunk-RGCVYGA2.js.map +0 -1
- /package/dist/{chunk-7ECI5ZVO.js.map → chunk-5ZOWHCFN.js.map} +0 -0
- /package/dist/{chunk-R6SHY6ZQ.js.map → chunk-AWFHDRRT.js.map} +0 -0
- /package/dist/{chunk-6ZMD3KBP.js.map → chunk-KLBB2J7W.js.map} +0 -0
- /package/dist/{chunk-SZG56DA3.js.map → chunk-PE5AMPHQ.js.map} +0 -0
- /package/dist/{chunk-AVLSICZO.js.map → chunk-QHTJTSHT.js.map} +0 -0
- /package/dist/{chunk-YK6FFIVG.js.map → chunk-Y2ZFRZ5Q.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ui/hidden-tags-popup.tsx","../src/components/ui/tag.tsx","../src/components/ui/card.tsx","../src/components/ui/tabs.tsx","../src/components/ui/status-badge.tsx","../src/components/ui/simple-markdown-renderer.tsx","../src/utils/format.ts","../src/components/ui/square-avatar.tsx","../src/components/ui/actions-menu.tsx","../node_modules/@radix-ui/react-use-controllable-state/src/use-controllable-state.tsx","../node_modules/@radix-ui/react-use-controllable-state/src/use-controllable-state-reducer.tsx","../node_modules/@radix-ui/react-use-layout-effect/src/use-layout-effect.tsx","../src/utils/image-proxy.ts","../src/components/chat/utils/is-cross-origin-url.ts","../src/components/chat/utils/decide-new-tab.ts","../src/components/chat/utils/nav-anchor-props.ts","../src/components/chat/utils/chat-nav-resolution.ts","../src/components/chat/entity-cards/entity-author-card.tsx","../src/components/chat/entity-cards/blog-image-placeholder.tsx","../src/utils/fetch-priority.ts","../src/components/features/video.tsx","../src/components/layout/page-heading.tsx","../src/components/features/video-ratio-tabs.tsx","../src/components/features/video-bites-display.tsx","../src/components/features/entity-video-section.tsx","../src/components/chat/utils/compact-card-classes.ts","../src/components/chat/entity-cards/onboarding-guide-card.tsx","../src/components/chat/entity-cards/use-entity-card-link.ts","../src/components/chat/entity-cards/use-entity-card-placeholder.ts","../src/utils/source-icons.ts","../src/components/chat/utils/icon-registry.ts","../src/components/chat/utils/source-row-cta.ts","../src/components/ui/page-actions.tsx","../src/components/ui/entity-image.tsx","../src/components/layout/title-block.tsx","../src/components/layout/back-button.tsx","../src/components/layout/page-layout.tsx","../src/components/layout/article-detail-layout.tsx","../src/utils/content-href.ts","../src/components/ui/error-state.tsx","../src/components/features/entity-tag-badges.tsx","../src/components/features/mux-origins.ts","../src/components/features/use-video-warmup.ts","../src/components/features/captions-url.ts","../src/components/ui/search-input.tsx","../src/components/ui/filter-pill-row.tsx"],"sourcesContent":["\"use client\"\n\nimport { type CSSProperties, type ForwardedRef, type ReactNode, forwardRef } from \"react\"\nimport { XmarkCircleIcon } from \"../icons-v2-generated/signs-and-symbols/xmark-circle-icon\"\nimport { cn } from \"../../utils/cn\"\n\nexport interface HiddenTagItem {\n label: ReactNode\n value: unknown\n}\n\nexport interface HiddenTagsPopupProps {\n items: HiddenTagItem[]\n onRemove?: (value: unknown) => void\n disabled?: boolean\n className?: string\n style?: CSSProperties\n}\n\nexport const HiddenTagsPopup = forwardRef(function HiddenTagsPopup(\n { items, onRemove, disabled, className, style }: HiddenTagsPopupProps,\n ref: ForwardedRef<HTMLDivElement>,\n) {\n return (\n <div\n ref={ref}\n style={style}\n className={cn(\n // Base positioning is neutral (left-anchored); consumers override via\n // `style.left` (search-input, tag-search-input) or `className` (autocomplete).\n \"absolute top-full left-0 mt-1 z-50 min-w-[200px]\",\n \"bg-ods-card border border-ods-border rounded-[6px] shadow-lg\",\n \"animate-in fade-in-0 zoom-in-95 duration-150\",\n className,\n )}\n >\n {items.map((item) => (\n <div\n key={String(item.value)}\n className={cn(\n \"flex items-center justify-between gap-3 px-3 h-11 md:h-12\",\n \"border-b border-ods-border last:border-b-0\",\n )}\n >\n <span className=\"min-w-0 flex-1 text-h5 truncate uppercase text-ods-text-primary\" title={typeof item.label === 'string' ? item.label : undefined}>\n {item.label}\n </span>\n {!disabled && onRemove && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onRemove(item.value)\n }}\n className=\"shrink-0 text-ods-text-secondary hover:text-ods-text-primary transition-colors\"\n aria-label={`Remove ${String(item.label)}`}\n >\n <XmarkCircleIcon size={20} />\n </button>\n )}\n </div>\n ))}\n </div>\n )\n})\n","\"use client\"\n\nimport React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { XmarkCircleIcon } from \"../icons-v2-generated/signs-and-symbols/xmark-circle-icon\"\nimport { cn } from \"../../utils/cn\"\n\nconst tagVariants = cva(\n [\n \"text-h5 h-8 inline-flex items-center justify-center gap-[var(--spacing-system-xxs)] p-[var(--spacing-system-xsf)] rounded-md\",\n \"transition-colors duration-150\",\n ],\n {\n variants: {\n variant: {\n primary: [\n \"bg-[var(--ods-open-yellow-base)] text-[var(--ods-system-greys-black)]\",\n \"hover:bg-[var(--ods-open-yellow-hover)] active:bg-[var(--ods-open-yellow-action)]\",\n ],\n outline: [\n \"bg-[var(--ods-system-greys-black)] text-[var(--ods-system-greys-white)] border border-[var(--ods-system-greys-soft-grey)]\",\n \"hover:bg-[var(--ods-system-greys-black-hover)] hover:border-[var(--ods-system-greys-soft-grey-hover)]\",\n \"active:bg-[var(--ods-system-greys-black-action)] active:border-[var(--ods-system-greys-soft-grey-action)]\",\n ],\n success: [\n \"bg-[var(--ods-attention-green-success-secondary)] text-[var(--ods-attention-green-success)]\",\n \"hover:bg-[#385029] active:bg-[#425a33]\",\n ],\n warning: [\n \"bg-[var(--ods-attention-yellow-warning-secondary)] text-[var(--ods-attention-yellow-warning)]\",\n \"hover:bg-[#544729] active:bg-[#5e5133]\",\n ],\n error: [\n \"bg-[var(--ods-attention-red-error-secondary)] text-[var(--ods-attention-red-error)]\",\n \"hover:bg-[#542b2b] active:bg-[#5e3535]\",\n ],\n critical: [\n \"bg-[var(--ods-attention-red-error)] text-[var(--ods-attention-red-error-secondary)]\",\n \"hover:bg-[var(--ods-attention-red-error-hover)] active:bg-[var(--ods-attention-red-error-action)]\",\n ],\n grey: [\n \"bg-[var(--ods-system-greys-soft-grey)] text-[var(--ods-system-greys-grey)]\",\n \"hover:bg-[var(--ods-system-greys-soft-grey-hover)] active:bg-[var(--ods-system-greys-soft-grey-action)]\",\n ],\n // Matches the EntityTagBadges / StatusBadge tag skin (ods-card + ods-border,\n // mono uppercase) so the tag-editor chips render identically to the public\n // tag badges. Used for FilterChipData variant 'tag' (see search-input).\n badge: [\n \"bg-ods-card text-ods-text-primary border border-ods-border font-mono uppercase tracking-wide\",\n \"hover:border-ods-accent transition-colors\",\n ],\n },\n },\n defaultVariants: {\n variant: \"primary\",\n },\n }\n)\n\nconst disabledTagClasses = [\n \"bg-[var(--ods-system-greys-soft-grey)] text-[var(--ods-system-greys-grey)]\",\n \"border-transparent\",\n \"cursor-not-allowed\",\n \"pointer-events-none\",\n]\n\nexport interface TagProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof tagVariants> {\n label: React.ReactNode\n labelClassName?: string\n icon?: React.ReactNode\n onClose?: () => void\n disabled?: boolean\n}\n\nfunction Tag({\n label,\n variant,\n icon,\n onClose,\n className,\n labelClassName,\n disabled,\n ...props\n}: TagProps) {\n return (\n <div\n className={cn(\n tagVariants({ variant }),\n disabled && disabledTagClasses,\n className\n )}\n aria-disabled={disabled || undefined}\n {...props}\n >\n {icon && (\n <span className=\"flex items-center justify-center size-5 shrink-0\">\n {icon}\n </span>\n )}\n <span className={cn(\"truncate\", labelClassName)} title={typeof label === 'string' ? label : undefined}>{label}</span>\n {onClose && (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation()\n onClose()\n }}\n className={cn(\n \"flex items-center justify-center size-5 shrink-0 rounded-full opacity-70 transition-opacity\",\n disabled ? \"cursor-not-allowed\" : \"hover:opacity-100\"\n )}\n aria-label=\"Remove\"\n >\n <XmarkCircleIcon className=\"size-4\" />\n </button>\n )}\n </div>\n )\n}\n\nexport { Tag, tagVariants }\n","\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"../../utils/cn\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"rounded-lg border bg-card text-card-foreground shadow-sm\",\n className\n )}\n {...props}\n />\n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n {...props}\n />\n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"text-2xl font-semibold leading-none tracking-tight\",\n className\n )}\n {...props}\n />\n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex items-center p-6 pt-0\", className)}\n {...props}\n />\n))\nCardFooter.displayName = \"CardFooter\"\n\n// Unified horizontal card for homepage category section\ninterface CardHorizontalProps {\n icon: React.ReactNode;\n title: string;\n description: string;\n className?: string;\n borderLeft?: boolean;\n}\n\nexport function CardHorizontal({ icon, title, description, className = '', borderLeft = true }: CardHorizontalProps) {\n return (\n <div\n className={cn(\n 'w-full flex flex-row items-center gap-3 md:gap-4 bg-ods-card p-4 md:p-6 min-h-[80px]',\n borderLeft ? 'border-l border-ods-border' : '',\n className\n )}\n >\n <div className=\"w-5 h-5 flex-shrink-0\">{icon}</div>\n <div className=\"flex flex-col min-w-0\">\n <span className=\"font-medium text-sm md:text-sm text-ods-text-primary leading-tight mb-0.5 text-left\">{title}</span>\n <span className=\"text-xs md:text-sm text-ods-text-secondary leading-tight text-left\">{description}</span>\n </div>\n </div>\n );\n}\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }","\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \"../../utils/cn\"\n\nconst Tabs = TabsPrimitive.Root\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.List\n ref={ref}\n className={cn(\n \"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground\",\n className\n )}\n {...props}\n />\n))\nTabsList.displayName = TabsPrimitive.List.displayName\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\",\n className\n )}\n {...props}\n />\n))\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n \"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n className\n )}\n {...props}\n />\n))\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n","\"use client\"\n\nimport React from 'react';\nimport { cn } from '../../utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nconst statusBadgeVariants = cva(\n \"inline-flex items-center justify-center rounded font-mono font-medium uppercase tracking-wide\",\n {\n variants: {\n variant: {\n card: \"px-3 py-1.5 text-sm\",\n button: \"px-2 py-0.5 text-[10px] leading-none\",\n },\n colorScheme: {\n cyan: \"bg-[var(--ods-flamingo-cyan-base)] text-ods-text-on-accent\",\n pink: \"bg-[var(--ods-flamingo-pink-base)] text-ods-text-on-accent\",\n yellow: \"bg-[var(--ods-flamingo-yellow-base)] text-ods-text-on-accent border border-[var(--ods-system-greys-black)]\",\n green: \"bg-[var(--ods-flamingo-green-base)] text-ods-text-on-accent\",\n purple: \"bg-[var(--ods-flamingo-purple-base)] text-ods-text-on-accent\",\n success: \"bg-[var(--ods-attention-green-success-secondary)] text-[var(--ods-attention-green-success)]\",\n error: \"bg-[var(--ods-attention-red-error-secondary)] text-[var(--ods-attention-red-error)]\",\n warning: \"bg-[var(--ods-attention-yellow-warning-secondary)] text-[var(--ods-attention-yellow-warning)]\",\n default: \"bg-ods-bg-secondary text-ods-text-primary\",\n // Border-only variants (no background) - for task type badges\n accentBorder: \"bg-transparent border-2 text-ods-accent border-ods-accent\",\n errorBorder: \"bg-transparent border-2 text-[var(--ods-attention-red-error)] border-[var(--ods-attention-red-error)]\",\n whiteBorder: \"bg-transparent border-2 text-ods-text-primary border-ods-text-primary\",\n },\n },\n defaultVariants: {\n variant: \"card\",\n colorScheme: \"default\",\n },\n }\n);\n\nexport interface StatusBadgeProps\n extends React.HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof statusBadgeVariants> {\n text: string;\n /**\n * When true, renders `text` verbatim on a single line, bypassing the\n * default multi-word vertical-stack behavior used by `variant=\"button\"`.\n * Use this for compact inline contexts (e.g. chat-inline roadmap cards)\n * where the stamp-like stacked layout is undesirable.\n */\n singleLine?: boolean;\n}\n\nfunction StatusBadge({\n text,\n variant,\n colorScheme,\n className,\n singleLine,\n ...props\n}: StatusBadgeProps) {\n // Outer element is `<span>` so the badge is HTML-valid in any inline\n // context (e.g. inside a markdown `<p>` next to a compact chat card,\n // or inside an `<a>`). The `inline-flex` base class in\n // `statusBadgeVariants` keeps the layout identical to the previous\n // `<div>` outer — only the element name changed.\n //\n // Escape hatch: callers can pass `singleLine` to opt out of the\n // multi-word stacking applied for `variant=\"button\"`. This is needed\n // for compact inline contexts (chat-inline roadmap cards) where the\n // default stamp-like vertical stack (\"TO\" / \"DO\") breaks layout.\n const renderText = () => {\n if (singleLine) return text;\n if (variant === 'button' && text.includes(' ')) {\n const words = text.split(' ');\n return (\n <span className=\"flex flex-col items-center justify-center text-center gap-0\">\n {words.map((word, index) => (\n <span key={index} className=\"block\">{word}</span>\n ))}\n </span>\n );\n }\n return text;\n };\n\n return (\n <span\n className={cn(statusBadgeVariants({ variant, colorScheme }), className)}\n {...props}\n >\n {renderText()}\n </span>\n );\n}\n\nexport { StatusBadge, statusBadgeVariants };","\"use client\";\n\nimport React, { useEffect, useState, useRef, useMemo, useCallback, memo } from 'react';\nimport ReactMarkdown, { type Components, defaultUrlTransform } from 'react-markdown';\nimport type { PluggableList } from 'unified';\nimport remarkGfm from 'remark-gfm';\nimport remarkBreaks from 'remark-breaks';\nimport rehypeHighlight from 'rehype-highlight';\nimport rehypeRaw from 'rehype-raw';\nimport { visit } from 'unist-util-visit';\nimport Image from '../../embed-shims/next-image';\nimport { AlertCircleIcon } from '../icons-v2-generated';\nimport { cn } from '../../utils/cn';\n\n// ---------------------------------------------------------------------------\n// rehype HAST sanitizer — runs AFTER rehype-raw to strip XSS vectors\n// ---------------------------------------------------------------------------\n/**\n * Minimal HAST sanitizer. Runs AFTER `rehype-raw` (which parses raw HTML\n * embedded in markdown) and BEFORE `rehype-highlight`. Strips the\n * attack surfaces that rehype-raw leaves wide open:\n *\n * - `on*` event handlers (onerror, onload, onclick, …) on ANY element\n * - href / src / formaction / xlink:href / poster pointing at\n * `javascript:` (case + whitespace tolerant)\n * - `iframe srcdoc` (full-document XSS)\n * - `<script>`, `<style>`, `<noscript>`, `<noembed>` elements (drop)\n * - `data:` URIs on src-ish attrs (SVG-with-embedded-JS class of bug)\n *\n * Why custom (vs `rehype-sanitize`): the OSS-lib build environment\n * doesn't have `rehype-sanitize` in its `node_modules` (sandbox\n * restriction), but `unist-util-visit` is already a transitive dep of\n * `rehype-raw`. This plugin is ~60 lines, ships nothing new, and is\n * tighter than the default sanitize schema for our threat model\n * (we want LLM-emitted markdown to be safe; we don't need full HTML5\n * fidelity).\n *\n * The text-level `escapeUnknownHtmlTags` pre-pass below is still useful\n * for catching `<their>`-style accidental tag emissions that React 19\n * rejects, but it is NOT a security boundary. THIS is.\n */\nconst EVENT_HANDLER_ATTR_RE = /^on[a-z]+$/i\nconst JAVASCRIPT_URL_RE = /^[\\s\\x00-\\x1f]*javascript:/i\nconst DATA_URL_RE = /^[\\s\\x00-\\x1f]*data:/i\nconst URL_ATTRS = new Set([\n 'href',\n 'src',\n // `srcset` accepts `javascript:` on legacy browsers — same guard\n // as the canonical hast-util-sanitize default schema's attr set.\n // Multi-candidate scanning lives in `srcsetHasUnsafeCandidate` below;\n // a single-URL check would miss a malicious second candidate like\n // `\"https://safe.png 1x, javascript:alert(1) 2x\"`.\n 'srcset',\n 'formaction',\n 'xlink:href',\n 'poster',\n 'data',\n 'action',\n 'background',\n])\n\n/**\n * Returns true if any candidate in an `srcset` attribute has a dangerous\n * URL scheme. Per the HTML spec, srcset is a comma-separated list of\n * candidates; each candidate is `<url> <descriptor>?` (e.g.\n * `\"https://x/a.png 2x\"`). The single-URL `JAVASCRIPT_URL_RE.test(v)`\n * check only inspects the FIRST candidate — so a malicious second\n * candidate (`\"https://safe 1x, javascript:alert(1) 2x\"`) would slip\n * through. This helper splits on `,`, trims each candidate, takes the\n * leading whitespace-delimited token (the URL), and tests that.\n *\n * Splitting on every comma over-matches the rare-in-practice case of\n * commas inside URL paths. That's the correct error bias for a\n * sanitizer: over-strip is safe, under-strip is not.\n */\nfunction srcsetHasUnsafeCandidate(srcset: string): boolean {\n for (const candidate of srcset.split(',')) {\n const url = candidate.trim().split(/\\s+/)[0] ?? ''\n if (JAVASCRIPT_URL_RE.test(url) || DATA_URL_RE.test(url)) return true\n }\n return false\n}\n// Element-level drop list. The text-pre-pass `escapeUnknownHtmlTags`\n// already escapes any `<tag>` whose name isn't on its allow-list, so\n// `<object>`, `<embed>`, `<applet>`, `<base>`, `<meta>` normally never\n// reach `rehype-raw` as elements. Re-stripping at the HAST layer\n// removes the cross-layer dependency: this sanitizer is sufficient\n// on its own even if the text pre-pass is bypassed.\nconst STRIP_ELEMENTS = new Set([\n 'script',\n 'style',\n 'noscript',\n 'noembed',\n 'object',\n 'embed',\n 'applet',\n 'base',\n 'meta',\n])\n\nfunction rehypeStripUnsafe() {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (tree: any) => {\n visit(tree, 'element', (node: any, index: number | undefined, parent: any) => {\n const tag = String(node.tagName ?? '').toLowerCase()\n if (STRIP_ELEMENTS.has(tag)) {\n if (parent && typeof index === 'number') {\n parent.children.splice(index, 1)\n // Return the numeric index (`unist-util-visit` treats it as\n // `[CONTINUE, index]`) so the walker resumes at the slot the\n // removed node vacated. Don't return `[SKIP, index]`:\n // skip-descendants is meaningless for a node we just removed,\n // and SKIP+index conflates two signals.\n return index\n }\n // Root-level strip element (parent undefined). Rare, but\n // possible if `rehype-raw` ever lifts one. Neutralize in\n // place by clearing children + retagging as a blank span.\n node.children = []\n node.tagName = 'span'\n node.properties = {}\n return\n }\n if (!node.properties || typeof node.properties !== 'object') return\n for (const key of Object.keys(node.properties)) {\n // 1. event handlers\n if (EVENT_HANDLER_ATTR_RE.test(key)) {\n delete node.properties[key]\n continue\n }\n // 2. dangerous URL schemes on URL-bearing attrs. `srcset` is\n // multi-candidate so it routes through `srcsetHasUnsafeCandidate`\n // which inspects every URL in the list (not just the first).\n if (URL_ATTRS.has(key.toLowerCase())) {\n const raw = node.properties[key]\n const v = Array.isArray(raw) ? raw[0] : raw\n if (typeof v === 'string') {\n const unsafe =\n key.toLowerCase() === 'srcset'\n ? srcsetHasUnsafeCandidate(v)\n : JAVASCRIPT_URL_RE.test(v) || DATA_URL_RE.test(v)\n if (unsafe) {\n delete node.properties[key]\n continue\n }\n }\n }\n // 3. iframe srcdoc — full-document XSS vector\n if (tag === 'iframe' && key.toLowerCase() === 'srcdoc') {\n delete node.properties[key]\n }\n }\n })\n }\n}\n\n/**\n * URL transformer that extends react-markdown's default safe-protocol\n * allowlist with `card://` — the non-standard scheme `remarkCardLinks`\n * emits for inline chat-card markers.\n *\n * Without this, react-markdown 10's `defaultUrlTransform` strips the URL\n * to `\"\"` before the `<a>` component override runs (the override's\n * `href.startsWith('card://')` check then fails and the marker leaks\n * through as literal text). All other URL schemes still go through the\n * default sanitizer — `javascript:`, `vbscript:`, `data:` (non-image), etc.\n * remain blocked.\n *\n * Scope: `card://` is allowed ONLY for `href` attributes. If the LLM\n * accidentally emits `` the URL still goes through\n * `defaultUrlTransform` (which strips it to `\"\"`) so an `<img src=\"card://...\">`\n * never reaches the DOM — broken request avoided. Per v6.1 §B.2.4: \"the\n * `card://` scheme is internal — never network-fetched, never written to\n * attributes other than `href` for renderer dispatch.\"\n */\nfunction cardAwareUrlTransform(url: string, key: string): string {\n if (key === 'href' && typeof url === 'string' && url.startsWith('card://')) return url;\n return defaultUrlTransform(url);\n}\n\n// ---------------------------------------------------------------------------\n// LLM-output sanitizer — escape unknown HTML-style tags\n// ---------------------------------------------------------------------------\n/**\n * Tags `rehype-raw` is allowed to forward to React as-is. Anything\n * outside this set gets its angle brackets escaped so it renders as\n * plain text rather than reaching React as an unrecognized custom\n * element.\n *\n * Why this matters: chat output is LLM-generated markdown. An LLM that\n * accidentally wraps a word in angle brackets (\"share <their> settings\")\n * or echoes a system-prompt XML tag (\"the <ticket> element above\")\n * makes `rehype-raw` mint a `<their>` / `<ticket>` JSX element. React\n * 18 logs \"tag is unrecognized in this browser\" for every unknown tag;\n * React 19 throws on tags with reserved kebab-case forms. We pre-escape\n * to keep the renderer pristine without losing legitimate inline HTML\n * (details/summary, video, iframe, kbd, mark, etc.).\n *\n * The allow-list mirrors HTML5 + the elements the chat shell wires\n * component overrides for. Kept lower-case; matched case-insensitively.\n */\nconst SAFE_HTML_TAGS = new Set([\n // Block + inline text\n 'a', 'abbr', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'blockquote',\n 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'dd', 'del',\n 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer',\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'ins',\n 'kbd', 'li', 'main', 'mark', 'nav', 'ol', 'p', 'pre', 'q', 'rp', 'rt',\n 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary',\n 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'u',\n 'ul', 'var', 'wbr',\n // Media\n 'img', 'picture', 'source', 'audio', 'video', 'iframe', 'track',\n // Forms (rehype-raw allows them; mostly harmless for chat output)\n 'button', 'input', 'label', 'select', 'option', 'optgroup', 'textarea', 'form', 'fieldset', 'legend',\n])\n\n/**\n * Match an opening / closing HTML tag in markdown source. Captures:\n * 1 — optional `/` for closing tags\n * 2 — tag name (must start with a letter)\n * 3 — everything between the name and the closing `>` (attrs etc.)\n * 4 — optional `/` for void-element self-close\n */\n// ReDoS-safe shape (CodeQL polynomial-regex hardening):\n//\n// Every quantifier is hard-bounded so the engine has no possible\n// polynomial path. The original `[a-zA-Z0-9-]*` + `[^>]*?` admitted\n// O(n²) backtracking on `<A-----…` because both classes include `-`;\n// CodeQL flagged that as a polynomial regex. Bounding the tag-name\n// at 63 chars (covers every real HTML tag — `animateTransform` at 16\n// is the longest in practice) and the rest at 4096 chars (sane upper\n// bound for `<a href=\"…\">` attribute strings) means matching is\n// constant-time per tag, NOT proportional to input length.\n//\n// Lazy `{0,4096}?` is deliberately lazy (not greedy) so the trailing\n// `(\\/?)>` still captures the self-close `/` — greedy would swallow\n// the slash into the rest. Bounded lazy backtracking is constant-time\n// per match (capped at 4096 expansions), so the laziness is safe.\n//\n// Anything longer than 4159 chars per tag simply doesn't match —\n// falls through as plain text, the safe-degrade behavior for\n// HTML-in-markdown.\n//\n// Tag shapes still covered: `<a>`, `<a/>`, `<a class=\"x\">`,\n// `<a class=\"x\" />`, `</a>`, `<input type=\"text\" />`,\n// `<custom-element data-foo=\"bar\">`, `<animateTransform>`, etc.\nconst TAG_LIKE_REGEX = /<(\\/?)([a-zA-Z][a-zA-Z0-9-]{0,63})((?:\\s[^>]{0,4096}?)?)(\\/?)>/g\n\nfunction escapeUnknownHtmlTags(text: string): string {\n if (!text || text.indexOf('<') === -1) return text\n // Carve out fenced code blocks AND inline-backtick spans so we don't\n // corrupt `<their>` examples that legitimately live inside code. The\n // regex matches in priority order: triple-fence first (greediest),\n // then inline single-backtick. Each protected span is preserved\n // verbatim; everything between/around them runs through\n // `escapeOutsideFences`.\n const parts: string[] = []\n let cursor = 0\n // Triple-fence ``` … ``` OR single-backtick `…` (one line, no\n // embedded newlines). Backtick group is non-greedy and forbids\n // inner newlines per CommonMark inline-code semantics.\n const PROTECTED_SPAN_RE = /```[\\s\\S]*?```|`[^`\\n]+`/g\n let span: RegExpExecArray | null\n while ((span = PROTECTED_SPAN_RE.exec(text)) !== null) {\n if (span.index > cursor) {\n parts.push(escapeOutsideFences(text.slice(cursor, span.index)))\n }\n parts.push(span[0])\n cursor = span.index + span[0].length\n }\n if (cursor < text.length) {\n parts.push(escapeOutsideFences(text.slice(cursor)))\n }\n return parts.join('')\n}\n\nfunction escapeOutsideFences(segment: string): string {\n return segment.replace(TAG_LIKE_REGEX, (match, slash, tag, rest, selfClose) => {\n const lower = (tag as string).toLowerCase()\n if (SAFE_HTML_TAGS.has(lower)) return match\n // Unknown tag — escape so it renders as text instead of reaching\n // React as a custom element.\n return `<${slash}${tag}${rest}${selfClose}>`\n })\n}\n\n// ---------------------------------------------------------------------------\n// Mermaid styles (responsive)\n// ---------------------------------------------------------------------------\nconst mermaidStyles = `\n .mermaid-svg-container svg {\n max-width: 100% !important;\n height: auto !important;\n min-height: 200px;\n font-family: 'DM Sans', sans-serif !important;\n font-size: 14px !important;\n }\n @media (min-width: 1520px) {\n .mermaid-svg-container svg {\n max-width: 900px !important;\n max-height: 700px !important;\n min-height: 300px;\n font-size: 16px !important;\n }\n }\n @media (min-width: 768px) and (max-width: 1519px) {\n .mermaid-svg-container svg {\n max-width: 700px !important;\n max-height: 600px !important;\n min-height: 250px;\n font-size: 15px !important;\n }\n }\n @media (max-width: 767px) {\n .mermaid-svg-container svg {\n max-width: 90vw !important;\n max-height: 400px !important;\n min-height: 200px;\n font-size: 13px !important;\n }\n }\n .mermaid-svg-container svg[width] { width: 100% !important; }\n .mermaid-svg-container .node rect,\n .mermaid-svg-container .node circle,\n .mermaid-svg-container .node ellipse,\n .mermaid-svg-container .node polygon { stroke-width: 2px !important; }\n .mermaid-svg-container .edgePath path { stroke-width: 2px !important; }\n @media (min-width: 768px) {\n .mermaid-svg-container .node text,\n .mermaid-svg-container .edgeLabel text { font-size: 14px !important; }\n }\n @media (min-width: 1520px) {\n .mermaid-svg-container .node text,\n .mermaid-svg-container .edgeLabel text { font-size: 16px !important; }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// MermaidDiagram\n// ---------------------------------------------------------------------------\nconst MermaidDiagram: React.FC<{ chart: string }> = ({ chart }) => {\n const [svg, setSvg] = useState<string>('');\n const [error, setError] = useState<string>('');\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => { setMounted(true); }, []);\n\n useEffect(() => {\n const renderMermaid = async () => {\n try {\n setIsLoading(true);\n // @ts-ignore -- mermaid is an optional runtime dependency, dynamically imported by the consumer\n const { default: mermaid } = await import('mermaid');\n\n mermaid.initialize({\n startOnLoad: false,\n theme: 'dark' as const,\n themeVariables: {\n primaryColor: '#FFC008',\n primaryTextColor: '#FAFAFA',\n primaryBorderColor: '#3A3A3A',\n lineColor: '#888888',\n secondaryColor: '#212121',\n tertiaryColor: '#2A2A2A',\n background: 'transparent',\n mainBkg: 'transparent',\n secondBkg: 'transparent',\n tertiaryBkg: 'transparent',\n cScale0: '#FFC008', cScale1: '#4ECDC4', cScale2: '#45B7D1',\n cScale3: '#96CEB4', cScale4: '#FFEAA7', cScale5: '#DDA0DD',\n cScale6: '#98D8C8', cScale7: '#F7DC6F', cScale8: '#BB8FCE',\n cScale9: '#85C1E9',\n taskTextColor: '#FAFAFA',\n taskTextOutsideColor: '#FAFAFA',\n activeTaskTextColor: '#1A1A1A',\n nodeTextColor: '#FAFAFA',\n },\n flowchart: { useMaxWidth: true, htmlLabels: true, rankSpacing: 50, nodeSpacing: 30, curve: 'basis' },\n sequence: { useMaxWidth: true, width: 150 },\n pie: { useMaxWidth: true, useWidth: undefined },\n fontFamily: 'DM Sans, sans-serif',\n fontSize: 14,\n securityLevel: 'loose',\n });\n\n const { svg: renderedSvg } = await mermaid.render(`mermaid-${Date.now()}`, chart);\n setSvg(renderedSvg);\n setIsLoading(false);\n } catch (err) {\n console.error('Mermaid rendering error:', err);\n setError(`Failed to render diagram: ${err instanceof Error ? err.message : 'Unknown error'}`);\n setIsLoading(false);\n }\n };\n\n if (mounted) { renderMermaid(); }\n }, [chart, mounted]);\n\n if (error) {\n return (\n <div className=\"error-state bg-ods-card border border-ods-border rounded-lg p-6 my-6\">\n <div className=\"error-icon flex justify-center mb-4\">\n <AlertCircleIcon className=\"w-12 h-12 text-ods-error\" />\n </div>\n <div className=\"error-title text-center font-sans font-semibold text-lg text-ods-error mb-2\">\n Diagram Error\n </div>\n <div className=\"error-description text-center font-sans text-sm text-ods-text-secondary mb-4 break-words overflow-hidden max-w-full\">\n <div className=\"overflow-x-auto\">\n <pre className=\"whitespace-pre-wrap break-words text-xs\">{error}</pre>\n </div>\n </div>\n </div>\n );\n }\n\n if (isLoading || !svg) {\n return (\n <div className=\"skeleton-code bg-ods-card border border-ods-border rounded-lg p-6 min-h-[120px] flex items-center justify-center\">\n <div className=\"animate-pulse text-ods-text-tertiary font-sans\">\n {isLoading ? 'Loading diagram renderer...' : 'Rendering diagram...'}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"mermaid-container rounded-lg p-4 md:p-6 lg:p-8 my-6 overflow-x-auto bg-ods-card border border-ods-border\">\n <div className=\"flex justify-center items-center w-full min-h-[200px] md:min-h-[250px] lg:min-h-[300px]\">\n <div\n className=\"mermaid-svg-container w-full flex justify-center max-w-full\"\n style={{ fontSize: '14px' }}\n dangerouslySetInnerHTML={{\n __html: svg.replace(/<svg[^>]*>/, (match) =>\n match.replace(/width=\"[^\"]*\"/, 'width=\"100%\"').replace(/height=\"[^\"]*\"/, 'height=\"auto\"')\n ),\n }}\n />\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Utility: extract plain text from React children\n// ---------------------------------------------------------------------------\nfunction extractText(node: any): string {\n if (typeof node === 'string') return node;\n if (Array.isArray(node)) return node.map(extractText).join('');\n if (node?.props?.children) return extractText(node.props.children);\n return '';\n}\n\n// ---------------------------------------------------------------------------\n// Text size configuration\n// ---------------------------------------------------------------------------\nexport type TextSizeElement =\n | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n | 'p' | 'li' | 'blockquote' | 'code' | 'th' | 'td';\n\nexport type TextSizeClassMap = Partial<Record<TextSizeElement, string>>;\nexport type TextSizePreset = 'default' | 'compact' | 'large';\nexport type TextSizeConfig =\n | TextSizePreset\n | TextSizeClassMap\n | { preset: TextSizePreset; overrides: TextSizeClassMap };\n\nconst TEXT_SIZE_PRESETS: Record<TextSizePreset, Record<TextSizeElement, string>> = {\n default: {\n h1: 'text-heading-1',\n h2: 'text-heading-2',\n h3: 'text-2xl md:text-3xl',\n h4: 'text-xl',\n h5: 'text-lg md:text-xl',\n h6: 'text-base md:text-lg',\n p: 'md:text-h4 lg:text-h4',\n li: 'text-base md:text-lg',\n blockquote: 'text-lg',\n code: 'text-[14px]',\n th: 'text-xs md:text-sm',\n td: 'text-xs md:text-sm',\n },\n compact: {\n h1: 'text-heading-2',\n h2: 'text-heading-3',\n h3: 'text-xl md:text-2xl',\n h4: 'text-lg md:text-xl',\n h5: 'text-base md:text-lg',\n h6: 'text-sm md:text-base',\n p: 'text-base md:text-lg',\n li: 'text-base md:text-lg',\n blockquote: 'text-base md:text-lg',\n code: 'text-[13px]',\n th: 'text-xs md:text-sm',\n td: 'text-xs md:text-sm',\n },\n large: {\n h1: 'text-heading-1',\n h2: 'text-heading-1',\n h3: 'text-heading-2',\n h4: 'text-2xl md:text-3xl',\n h5: 'text-xl md:text-2xl',\n h6: 'text-lg md:text-xl',\n p: 'text-h3',\n li: 'text-lg md:text-xl',\n blockquote: 'text-xl md:text-2xl',\n code: 'text-[16px]',\n th: 'text-sm md:text-base',\n td: 'text-sm md:text-base',\n },\n};\n\nfunction resolveTextSizeConfig(config?: TextSizeConfig): Record<TextSizeElement, string> {\n const defaultSizes = TEXT_SIZE_PRESETS.default;\n if (!config) return defaultSizes;\n if (typeof config === 'string') return TEXT_SIZE_PRESETS[config];\n if ('preset' in config) return { ...TEXT_SIZE_PRESETS[config.preset], ...config.overrides };\n return { ...defaultSizes, ...config };\n}\n\n// ---------------------------------------------------------------------------\n// Resolved link result used by onResolveLink callback\n// ---------------------------------------------------------------------------\nexport interface ResolveLinkResult {\n success: boolean;\n resolvedPath?: string;\n type?: string;\n action?: string;\n error?: string;\n message?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\nexport interface SimpleMarkdownRendererProps {\n content: string;\n className?: string;\n /** Backend-provided heading IDs for deep-link anchors */\n sectionIds?: Array<{ id: string; title: string; level: number }>;\n /** When the page already has an H1, render markdown `#` as `<h2>` */\n demoteMarkdownH1ToH2?: boolean;\n /** List of broken link hrefs detected server-side (shown with [BROKEN] badge) */\n brokenLinks?: string[];\n /** Callback for internal (non-http, non-anchor) link clicks */\n onInternalLinkClick?: (path: string, options?: { expandFolder?: boolean; fromInternalLink?: boolean }) => void;\n /** Current documentation path — enables internal-link mode when set */\n currentPath?: string;\n /**\n * Resolve an internal link href to a navigation path.\n * Called when a user clicks an internal doc link.\n */\n onResolveLink?: (href: string, currentPath: string) => Promise<ResolveLinkResult>;\n /** Pre-process the raw markdown string before rendering (e.g. shortcode expansion) */\n preprocessContent?: (content: string) => string;\n /** Merge additional or override react-markdown component renderers */\n componentOverrides?: Partial<Components>;\n /**\n * Extra remark plugins appended after the built-in `remarkGfm` and\n * `remarkBreaks`. Used by chat consumers to inject `remarkCardLinks`,\n * which converts `[card://<type>:<id>]` markers into synthetic `link`\n * nodes; the host's `componentOverrides.a` then swaps those for whatever\n * inline entity-card component it provides.\n *\n * Each entry is either a Plugin reference or a [Plugin, options] tuple\n * — same shape as react-markdown's own `remarkPlugins` prop. Pass an\n * empty array (or omit) to keep the default plugin set.\n */\n additionalRemarkPlugins?: PluggableList;\n /**\n * Configure text sizing for all rendered elements.\n * - `\"default\"` — current behavior (large article-style typography)\n * - `\"compact\"` — smaller sizes for sidebars, cards, changelogs\n * - `\"large\"` — extra-large sizes for hero/landing content\n * - `{ p: \"text-sm\", h1: \"text-2xl\" }` — per-element overrides (merged onto default)\n * - `{ preset: \"compact\", overrides: { h1: \"text-heading-1\" } }` — preset + tweaks\n */\n textSize?: TextSizeConfig;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\nconst SimpleMarkdownRendererImpl: React.FC<SimpleMarkdownRendererProps> = ({\n content,\n className = \"\",\n sectionIds,\n demoteMarkdownH1ToH2 = false,\n brokenLinks = [],\n onInternalLinkClick,\n currentPath: propCurrentPath,\n onResolveLink,\n preprocessContent,\n componentOverrides,\n textSize,\n additionalRemarkPlugins,\n}) => {\n const idCountsRef = useRef<Record<string, number>>({});\n\n // ---- text sizes ----\n const textSizes = useMemo(() => resolveTextSizeConfig(textSize), [textSize]);\n\n // ---- section ID map ----\n const sectionIdMap = useMemo(() => {\n const map = new Map<string, string>();\n if (sectionIds) {\n sectionIds.forEach(section => {\n const cleanTitle = section.title\n .replace(/[\\u{1F300}-\\u{1F9FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu, '')\n .trim()\n .toLowerCase();\n map.set(section.title.toLowerCase(), section.id);\n map.set(cleanTitle, section.id);\n map.set(section.title, section.id);\n });\n }\n return map;\n }, [sectionIds]);\n\n // ---- heading ID generation ----\n const generateHeadingId = useCallback((text: string, level: number): string => {\n if (sectionIds && (level === 1 || level === 2)) {\n const variations = [\n text, text.toLowerCase(),\n text.replace(/[\\u{1F300}-\\u{1F9FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu, '').trim(),\n text.replace(/[\\u{1F300}-\\u{1F9FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu, '').trim().toLowerCase(),\n ];\n for (const v of variations) {\n const id = sectionIdMap.get(v);\n if (id) return id;\n }\n }\n const baseId = text\n .replace(/[\\u{1F300}-\\u{1F9FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu, '')\n .trim().toLowerCase()\n .replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-').replace(/^-+|-+$/g, '');\n const cleanId = baseId || `section-${Object.keys(idCountsRef.current).length + 1}`;\n if (idCountsRef.current[cleanId]) {\n idCountsRef.current[cleanId]++;\n return `${cleanId}-${idCountsRef.current[cleanId]}`;\n }\n idCountsRef.current[cleanId] = 1;\n return cleanId;\n }, [sectionIds, sectionIdMap]);\n\n // ---- preprocess ----\n // Run the optional caller-supplied transform first, then defensively\n // escape unknown HTML-style tags so an LLM that wrote `<their>` or\n // accidentally echoed a system-prompt XML tag like `<ticket>` doesn't\n // make `rehype-raw` hand React an unrecognized custom element (which\n // logs a noisy \"tag is unrecognized in this browser\" warning AND can\n // crash the SimpleMarkdownRenderer when the tag has a kebab-case\n // form React rejects outright). Allow-listed tags pass through\n // unchanged so legitimate inline HTML (details/summary, video,\n // iframe, etc.) still renders.\n const processedContent = useMemo(\n () => escapeUnknownHtmlTags(preprocessContent ? preprocessContent(content) : content),\n [preprocessContent, content],\n );\n\n // ---- heading factory ----\n const makeHeading = useCallback(\n (Tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6', level: number, headingClassName: string) =>\n ({ children }: any) => {\n const text = extractText(children);\n const effectiveLevel = Tag === 'h1' && demoteMarkdownH1ToH2 ? 2 : level;\n const id = generateHeadingId(text, effectiveLevel);\n const EffectiveTag = Tag === 'h1' && demoteMarkdownH1ToH2 ? 'h2' : Tag;\n return <EffectiveTag id={id} className={headingClassName}>{children}</EffectiveTag>;\n },\n [generateHeadingId, demoteMarkdownH1ToH2],\n );\n\n // ---- components ----\n const components: Components = useMemo(() => ({\n // --- code ---\n code: ({ node, inline, className: codeClassName, children, ...props }: any) => {\n const match = /language-(\\w+)/.exec(codeClassName || '');\n const language = match ? match[1] : '';\n\n if (!inline && language === 'mermaid') {\n return <MermaidDiagram chart={String(children).replace(/\\n$/, '')} />;\n }\n\n if (!inline && match) {\n return (\n <div className=\"code-block-container border rounded-lg my-6 overflow-hidden bg-ods-card border-ods-border\">\n <div className=\"code-header border-b px-4 py-2 bg-ods-card border-ods-border\">\n <span className=\"font-sans text-xs uppercase tracking-wide text-ods-text-tertiary\">\n {language || 'code'}\n </span>\n </div>\n <div className=\"p-4\">\n <pre className=\"overflow-x-auto\">\n <code\n className={cn(`language-${language} hljs`, textSizes.code)}\n style={{\n fontFamily: \"JetBrains Mono', 'SF Mono', Consolas, monospace\",\n background: 'transparent',\n color: 'var(--ods-text-primary)',\n }}\n {...props}\n >\n {children}\n </code>\n </pre>\n </div>\n </div>\n );\n }\n\n return (\n <code className=\"font-mono text-[0.9em] px-1.5 py-0.5 rounded border bg-ods-card text-ods-text-primary border-ods-border\" {...props}>\n {children}\n </code>\n );\n },\n\n // --- div (pass-through, overridable for embeds) ---\n div: ({ node, className: divClassName, children, ...props }: any) => (\n <div className={divClassName} {...props}>{children}</div>\n ),\n\n // --- blockquote ---\n blockquote: ({ children }: any) => (\n <blockquote className=\"border-l-4 border-ods-accent ml-0 pl-6 my-8 py-4 rounded-r-lg bg-ods-bg-secondary\">\n <div className={cn('font-sans leading-relaxed text-ods-text-secondary', textSizes.blockquote)}>\n {children}\n </div>\n </blockquote>\n ),\n\n // --- headings ---\n h1: makeHeading('h1', 1, cn('font-sans font-bold mt-8 mb-4 first:mt-0 text-ods-text-primary', textSizes.h1)),\n h2: makeHeading('h2', 2, cn('font-sans font-semibold mt-8 mb-4 pb-2 border-b text-ods-text-primary border-ods-border', textSizes.h2)),\n h3: makeHeading('h3', 3, cn('font-sans font-semibold mt-6 mb-3 text-ods-text-primary', textSizes.h3)),\n h4: makeHeading('h4', 4, cn('font-sans font-semibold mt-4 mb-2 text-ods-text-primary', textSizes.h4)),\n h5: makeHeading('h5', 5, cn('font-sans font-semibold mt-3 mb-2 text-ods-text-primary', textSizes.h5)),\n h6: makeHeading('h6', 6, cn('font-sans font-semibold mt-3 mb-1 text-ods-text-primary', textSizes.h6)),\n\n // --- paragraph ---\n p: ({ children }: any) => (\n <p className={cn('leading-relaxed mb-4 first:mt-0 last:mb-0 text-ods-text-primary', textSizes.p)}>\n {children}\n </p>\n ),\n\n // --- links ---\n a: ({ href, children, className: linkClassName }: any) => {\n const isBroken = brokenLinks.includes(href);\n const isInternalDocLink =\n propCurrentPath !== undefined &&\n propCurrentPath !== null &&\n href &&\n !href.startsWith('http') &&\n !href.startsWith('#');\n\n if (isBroken) {\n return (\n <span className=\"text-ods-accent cursor-not-allowed\">\n {children}\n <sup className=\"ml-1 text-xs font-bold text-ods-attention-red-error\">[BROKEN]</sup>\n </span>\n );\n }\n\n if (isInternalDocLink && onInternalLinkClick) {\n const currentPath = propCurrentPath ?? '';\n return (\n <span\n className=\"text-ods-accent no-underline relative transition-colors duration-200 hover:after:w-full after:content-[''] after:absolute after:w-0 after:h-0.5 after:-bottom-0.5 after:left-0 after:bg-ods-accent after:transition-all after:duration-300 cursor-pointer\"\n onClick={async (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (onResolveLink) {\n try {\n const result = await onResolveLink(href, currentPath);\n if (result.type === 'folder-no-readme' && result.action === 'expand_folder') {\n onInternalLinkClick(result.resolvedPath!, { expandFolder: true, fromInternalLink: true });\n } else if (result.type === 'not-found') {\n return;\n } else if (result.success && result.resolvedPath) {\n onInternalLinkClick(result.resolvedPath, { fromInternalLink: true });\n }\n } catch (error) {\n console.error('Error resolving link:', error);\n }\n } else {\n onInternalLinkClick(href, { fromInternalLink: true });\n }\n }}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n (e.currentTarget as HTMLElement).click();\n }\n }}\n >\n {children}\n </span>\n );\n }\n\n return (\n <a\n href={href}\n className={`text-ods-accent no-underline relative transition-colors duration-200 hover:after:w-full after:content-[''] after:absolute after:w-0 after:h-0.5 after:-bottom-0.5 after:left-0 after:bg-ods-accent after:transition-all after:duration-300 ${linkClassName || ''}`}\n target={href?.startsWith('http') ? '_blank' : undefined}\n rel={href?.startsWith('http') ? 'noopener noreferrer' : undefined}\n >\n {children}\n </a>\n );\n },\n\n // --- images ---\n // Inline content image renderer. Used by blog posts, docs, AND chat\n // messages (where users may attach screenshots / photos via the +\n // attachment button).\n //\n // Sizing rules (2025-2026 best practice — Claude.ai, ChatGPT,\n // iMessage, Slack, Discord inline image patterns):\n //\n // - CAP max-width at 400px so inline images don't blow out the\n // message column on wide panels. Click-to-expand opens a\n // full-resolution modal for users who need detail.\n // - CAP max-height at 400px so portrait-orientation images\n // don't dominate vertical space (a 1000x3000 phone screenshot\n // would otherwise push the next message off-screen).\n // - Small images render at NATURAL pixel size — a 64x64\n // thumbnail stays 64x64, not stretched to fill the column.\n // - `object-contain` preserves aspect ratio when both dimensions\n // are constrained (long landscape, tall portrait).\n //\n // Implementation: Next.js `<Image>` — the project's canonical\n // image primitive. Gives us:\n // - WebP/AVIF format conversion for modern browsers (smaller\n // bytes for the same visual quality).\n // - Responsive `srcset` via the `sizes` prop (browser picks the\n // right variant for the viewport).\n // - Automatic lazy-loading (`loading=\"lazy\"` by default, mid-\n // page images skipped until they near the viewport).\n // - Automatic `decoding=\"async\"` so image decode doesn't block\n // paint.\n //\n // `width={400} height={400}` props are REQUIRED by Next.js\n // `<Image>` (non-`fill` mode throws without them) but they're\n // effectively a CEILING here, not the display size — the CSS\n // overrides (`w-auto h-auto max-w-full max-h-[400px]`) drive\n // the actual rendered size. The inline `style={{ width: 'auto',\n // height: 'auto' }}` is belt-and-suspenders: Next.js Image sets\n // matching HTML `width`/`height` attributes on the rendered\n // `<img>` and inline style wins over both HTML attributes AND\n // utility classes regardless of CSS-specificity surprises.\n //\n // Layout reservation trade-off: until image bytes arrive, the\n // browser may reserve a placeholder box up to 400x400 (the props'\n // intrinsic-ratio hint). Once loaded, the box collapses to the\n // natural size if smaller. This is the standard Next.js Image\n // behavior across the codebase — accepted for the optimizer +\n // responsive-srcset benefits. Chat attachments hosted in side\n // panels see this only on first render of a fresh attachment\n // (cached re-renders pop in without a perceptible shift).\n img: ({ src, alt }: any) => {\n if (!src || typeof src !== 'string' || src.trim() === '') return null;\n return (\n <Image\n src={src}\n alt={alt ?? 'No image available'}\n width={400}\n height={400}\n sizes=\"(max-width: 400px) 100vw, 400px\"\n className=\"max-w-full max-h-[400px] w-auto h-auto rounded-lg object-contain\"\n style={{ width: 'auto', height: 'auto' }}\n />\n );\n },\n\n // --- lists ---\n ul: ({ children }: any) => (\n <ul className=\"list-disc list-outside my-4 ml-8 space-y-2 text-ods-text-primary\">{children}</ul>\n ),\n ol: ({ children }: any) => (\n <ol className=\"list-decimal list-outside my-4 ml-8 space-y-2 text-ods-text-primary\">{children}</ol>\n ),\n li: ({ children }: any) => (\n <li className={cn('leading-relaxed pl-2', textSizes.li)}>{children}</li>\n ),\n\n // --- tables ---\n table: ({ children }: any) => (\n <div className=\"table-container my-6 overflow-x-auto\">\n <div className=\"min-w-full border rounded-lg border-ods-border bg-ods-card\">\n <table className=\"w-full table-fixed md:table-auto\">{children}</table>\n </div>\n </div>\n ),\n thead: ({ children }: any) => (\n <thead className=\"bg-ods-bg-secondary\">{children}</thead>\n ),\n th: ({ children }: any) => (\n <th className={cn('px-2 md:px-4 py-3 text-left font-semibold text-ods-accent border-r last:border-r-0 break-words border-ods-border', textSizes.th)}>\n {children}\n </th>\n ),\n td: ({ children }: any) => (\n <td className={cn('px-2 md:px-4 py-3 border-r last:border-r-0 border-b break-words whitespace-normal text-ods-text-primary border-ods-border', textSizes.td)}>\n {children}\n </td>\n ),\n\n // --- horizontal rule ---\n hr: () => <hr className=\"border-0 border-t my-8 border-ods-border\" />,\n\n // --- merge overrides ---\n ...componentOverrides,\n }), [makeHeading, brokenLinks, propCurrentPath, onInternalLinkClick, onResolveLink, componentOverrides, textSizes]);\n\n return (\n <div className={`simple-markdown-renderer ${className}`}>\n <style dangerouslySetInnerHTML={{ __html: mermaidStyles }} />\n <div className=\"content-wrapper max-w-none break-words\">\n <article className=\"prose prose-lg max-w-none\">\n <ReactMarkdown\n remarkPlugins={[remarkGfm, remarkBreaks, ...(additionalRemarkPlugins ?? [])]}\n rehypePlugins={[\n // ORDER MATTERS: rehype-raw parses the raw HTML embedded\n // in the source markdown into HAST nodes; rehypeStripUnsafe\n // then walks the HAST tree and drops XSS vectors (on*\n // event handlers, javascript: URLs, script/style/iframe-srcdoc,\n // data: URIs). Reversing the order would have nothing to\n // sanitize (raw HTML would still be strings).\n rehypeRaw,\n rehypeStripUnsafe,\n [rehypeHighlight, { detect: true, ignoreMissing: true }],\n ]}\n urlTransform={cardAwareUrlTransform}\n components={components}\n >\n {processedContent}\n </ReactMarkdown>\n </article>\n </div>\n </div>\n );\n};\n\n/**\n * Memoized so a parent re-render with UNCHANGED props (same `content` string,\n * same memoized `componentOverrides`/plugins) does NOT re-parse the markdown.\n * Re-parsing rebuilds the entire react-markdown subtree, which RE-MOUNTS any\n * embedded inline entity cards — closing their open menus and re-triggering\n * their fetch on every chat re-render (streaming chunk AND scroll). With\n * stable props the renderer bails, so completed messages' cards stay mounted.\n */\nexport const SimpleMarkdownRenderer = memo(SimpleMarkdownRendererImpl);\n","/**\n * Utility functions for formatting data\n */\n\n/**\n * Format a date to a human-readable string\n * @param date - The date to format (Date object or ISO string)\n * @param options - Formatting options\n * @returns Formatted date string\n */\nexport function formatDate(\n date: Date | string,\n options: Intl.DateTimeFormatOptions = {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n },\n): string {\n const dateObj = typeof date === \"string\" ? new Date(date) : date\n \n // Check if the date is valid\n if (isNaN(dateObj.getTime())) {\n console.warn(\"Invalid date provided to formatDate:\", date)\n return \"Invalid Date\"\n }\n \n return dateObj.toLocaleDateString(\"en-US\", options)\n}\n\n/**\n * Format a number with thousands separators\n * @param num - The number to format\n * @returns Formatted number string\n */\nexport function formatNumber(num: number): string {\n return num.toLocaleString()\n}\n\n/**\n * Format a price with currency symbol\n * @param price - The price to format\n * @param currency - The currency code\n * @returns Formatted price string\n */\nexport function formatPrice(price: number, currency = \"USD\"): string {\n return new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency,\n }).format(price)\n}\n\n/**\n * Format bytes to a human-readable string (KB, MB, GB, etc.)\n * @param bytes - The number of bytes\n * @param decimals - Number of decimal places\n * @returns Formatted bytes string\n */\nexport function formatBytes(bytes: number, decimals = 2): string {\n if (bytes === 0) return \"0 Bytes\"\n\n const k = 1024\n const dm = decimals < 0 ? 0 : decimals\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"]\n\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n\n return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i]\n}\n\n/**\n * Compact bytes formatter using the single-letter unit `'B'` (not\n * `'Bytes'`). Used by upload-progress UIs where horizontal space is\n * tight and the longer \"Bytes\" string wraps. Tops out at `TB`.\n * @example formatBytesShort(0) → \"0 B\"; formatBytesShort(1536) → \"1.5 KB\"\n */\nexport function formatBytesShort(bytes: number): string {\n if (bytes === 0) return '0 B'\n const k = 1024\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`\n}\n\n/**\n * Format large numbers to abbreviated form (K, M, B) with no decimal points\n * @param num - The number to format\n * @returns Formatted number string (e.g., \"1K\", \"2M\", \"3B\")\n */\nexport function formatLargeNumber(num: number): string {\n if (num === 0) return \"0\"\n \n // Handle negative numbers\n const isNegative = num < 0\n const absNum = Math.abs(num)\n \n let result: string\n \n if (absNum >= 1_000_000_000) {\n // Billions\n result = `${Math.floor(absNum / 1_000_000_000)}B`\n } else if (absNum >= 1_000_000) {\n // Millions\n result = `${Math.floor(absNum / 1_000_000)}M`\n } else if (absNum >= 1_000) {\n // Thousands\n result = `${Math.floor(absNum / 1_000)}K`\n } else {\n // Less than 1000, show as-is\n result = Math.floor(absNum).toString()\n }\n \n return isNegative ? `-${result}` : result\n}\n\n/**\n * Abbreviate large numbers for compact display.\n * 1 200 → 1.2K , 15 000 → 15K , 2 000 000 → 2M\n * @param n Number to format\n */\nexport function formatAbbreviatedNumber(n: number): string {\n if (n >= 1_000_000_000) {\n const value = n / 1_000_000_000;\n return `${Number.isInteger(value) ? value.toFixed(0) : value.toFixed(1)}B`;\n }\n if (n >= 1_000_000) {\n const value = n / 1_000_000;\n return `${Number.isInteger(value) ? value.toFixed(0) : value.toFixed(1)}M`;\n }\n if (n >= 1_000) {\n const value = n / 1_000;\n return `${Number.isInteger(value) ? value.toFixed(0) : value.toFixed(1)}K`;\n }\n return n.toLocaleString();\n}\n\n/**\n * Two-letter uppercase initials from the FIRST + LAST word of a name.\n * Used by avatar-style fallbacks (SquareAvatar, EntityImage) where\n * \"John Michael Doe\" → \"JD\" reads better than \"JM\".\n *\n * Returns `''` for empty / whitespace-only input. Single-word names\n * return a single uppercase letter. Pure — same input always produces\n * the same output, no locale or timezone surface.\n */\nexport function getFirstLastInitials(name?: string | null): string {\n if (!name) return ''\n const words = name.trim().split(/\\s+/)\n if (words.length === 0 || !words[0]) return ''\n if (words.length === 1) return words[0].charAt(0).toUpperCase()\n return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase()\n}\n\n/**\n * Two-letter uppercase initials from the FIRST + SECOND word of a name.\n * Used as SquareAvatar / EntityImage / EntityAuthorCard fallback across\n * admin and public pages in both the lib and the hub. Handles empty\n * strings, all-whitespace input, and single-word names cleanly — always\n * returns at least one character so the fallback slot is never empty.\n *\n * Single source of truth: every \"first-letter of each word, uppercase,\n * max 2 chars\" computation across hub + lib MUST come through here.\n */\nexport function nameInitials(\n name: string | null | undefined,\n fallback: string = 'E',\n): string {\n const source = typeof name === 'string' ? name.trim() : ''\n const words = source.length > 0 ? source.split(/\\s+/) : []\n const letters = words\n .map((w) => w[0])\n .filter(Boolean)\n .slice(0, 2)\n .join('')\n return (letters || fallback).toUpperCase()\n}\n\n/**\n * Format seconds to MM:SS or HH:MM:SS format\n * Used for media durations (podcasts, videos)\n * Returns: \"MM:SS\" or \"HH:MM:SS\" if hours > 0\n */\nexport function formatDurationMMSS(seconds: number | null | undefined): string {\n if (!seconds) return '';\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = Math.floor(seconds % 60);\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${secs.toString().padStart(2, '0')}`;\n}\n\n/**\n * Format seconds to compact human-readable duration\n * Used for displaying duration in cards and headers\n * Returns: \"Xh Xm\" or \"X min\"\n */\nexport function formatDurationCompact(seconds: number | null | undefined): string {\n if (!seconds) return '';\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n if (hours > 0) {\n return `${hours}h ${minutes}m`;\n }\n return `${minutes} min`;\n}\n\n/**\n * Format time with optional timezone\n * Used for webinar and event times\n * Returns: \"10:30 AM EST\" or \"10:30 AM\"\n */\nexport function formatTimeWithTimezone(\n date: Date | string | null | undefined,\n timezone?: string | null\n): string {\n if (!date) return '';\n\n const dateObj = typeof date === 'string' ? new Date(date) : date;\n const timeStr = dateObj.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n });\n\n return timezone ? `${timeStr} ${timezone}` : timeStr;\n}\n\n/**\n * Calculate and format duration between two timestamps\n * Used for webinar durations\n * Returns: \"1h 30m\" or \"45m\"\n */\nexport function formatDurationFromRange(\n startAt: string | Date | null | undefined,\n endAt: string | Date | null | undefined\n): string {\n if (!startAt || !endAt) return '';\n\n const start = typeof startAt === 'string' ? new Date(startAt) : startAt;\n const end = typeof endAt === 'string' ? new Date(endAt) : endAt;\n const durationMs = end.getTime() - start.getTime();\n const minutes = Math.round(durationMs / 60000);\n\n if (minutes >= 60) {\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;\n }\n return `${minutes}m`;\n}\n\n/**\n * UTC-anchored date formatter — distinct from the local-rendering\n * `formatDate` above. RAG mappers + audit-style consumers want UTC anchor\n * so date-only strings (`'2024-08-15'`) don't drift across timezones, AND\n * a `'N/A'` (or caller-supplied) fallback for missing values so the LLM\n * never sees a blank.\n *\n * Accepts:\n * - ISO date or timestamp string (`'2024-08-15'` or `'2024-08-15T10:30:00Z'`)\n * - Numeric epoch milliseconds\n * - Numeric string (treated as epoch ms — Slack and GitHub timestamps\n * sometimes arrive that way)\n * - `null` / `undefined` / empty / unparseable → returns `options.fallback`\n *\n * Date-only strings (no `'T'`) get `T00:00:00Z` appended so they anchor to\n * UTC midnight rather than midnight in the runtime's local timezone.\n */\nexport interface FormatDateUTCOptions {\n /** Returned for null/undefined/empty/unparseable input. Defaults to 'N/A'. */\n fallback?: string\n /** 'UTC' (default) or 'local' — switch off the UTC anchor when audit\n * stability matters less than local relevance (chat-card timestamps). */\n timezone?: 'UTC' | 'local'\n}\n\nexport function formatDateUTC(\n value: string | number | null | undefined,\n options: FormatDateUTCOptions = {},\n): string {\n const { fallback = 'N/A', timezone = 'UTC' } = options\n\n if (value === null || value === undefined || value === '') return fallback\n\n let ms: number\n if (typeof value === 'number') {\n ms = value\n } else {\n // String input — first try to interpret as a numeric epoch (Slack/GitHub\n // sometimes arrive that way). Number('') is 0, but we already ruled out\n // empty strings above.\n const n = Number(value)\n if (Number.isFinite(n) && n > 0 && /^-?\\d+(\\.\\d+)?$/.test(value.trim())) {\n ms = n\n } else {\n // ISO string — for date-only forms, anchor to UTC midnight to avoid\n // timezone-offset drift (preserves the original RAG-mapper contract).\n ms = Date.parse(value.includes('T') ? value : value + 'T00:00:00Z')\n }\n }\n\n if (!Number.isFinite(ms)) return fallback\n\n return new Date(ms).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n timeZone: timezone === 'local' ? undefined : 'UTC',\n })\n}\n\n/**\n * Format a reporting-month value (`entry_month`) as \"Mon YYYY\" / \"Month YYYY\",\n * always anchored to UTC. THE single home for the \"What I Shipped\" month label —\n * both the lib card (`'short'`) and the hub detail page (`'long'`) call this, so\n * the React #418 UTC-pin convention lives in exactly one place. Returns `null`\n * for empty input (callers omit the label entirely).\n */\nexport function formatEntryMonthUTC(\n entryMonth: string | null | undefined,\n style: 'short' | 'long' = 'short',\n): string | null {\n if (!entryMonth) return null\n return new Date(entryMonth).toLocaleDateString('en-US', { month: style, year: 'numeric', timeZone: 'UTC' })\n}\n\n/**\n * Format a date string as `MM/DD/YYYY` for legal-document display\n * (privacy policy, terms of service). Locale-stable: always en-US.\n */\nexport function formatLegalDate(dateInput: string): string {\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).format(new Date(dateInput))\n}\n\n/**\n * Format a currency value as `$1,234`. Returns `'N/A'` for null/undefined.\n * USD-rounded (no cents). Used on KPI cards + investor pages.\n */\nexport function formatCurrency(value: number | null | undefined): string {\n if (value == null) return 'N/A'\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n maximumFractionDigits: 0,\n }).format(value)\n}\n\n/**\n * Format a percent value as `12.50%`. Returns `'N/A'` for null/undefined.\n */\nexport function formatPercent(value: number | null | undefined): string {\n if (value == null) return 'N/A'\n return `${value.toFixed(2)}%`\n}\n\n/**\n * Whole-dollar price (no cents) — `$1,234`. Configurable currency code.\n */\nexport function formatWholeDollars(price: number, currency = 'USD'): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n maximumFractionDigits: 0,\n }).format(price)\n}\n\n// =============================================================================\n// Metric Formatting (for KPI cards / investor updates)\n// =============================================================================\n\nexport type MetricFormat = 'number' | 'currency' | 'percentage' | 'months'\n\n/**\n * Polarity determines whether an increase is good or bad.\n * - 'positive': higher is better (revenue, users, MRR) → up = green, down = red\n * - 'negative': higher is worse (burn rate, churn, CAC) → up = red, down = green\n * - 'neutral': no judgment (headcount, runway) → always gray\n */\nexport type TrendPolarity = 'positive' | 'negative' | 'neutral'\n\n/**\n * Format a metric value with compact notation ($1.2M, 150K, 12 months).\n */\nexport function formatCompactMetric(\n value: number,\n format: MetricFormat = 'number',\n options?: { prefix?: string; suffix?: string },\n): string {\n if (value === 0 || value === null || value === undefined) {\n if (format === 'currency') return `${options?.prefix || '$'}0`\n if (format === 'percentage') return '0%'\n if (format === 'months') return `0 ${options?.suffix || 'months'}`\n return '0'\n }\n\n const absValue = Math.abs(value)\n const sign = value < 0 ? '-' : ''\n\n if (format === 'currency') {\n const prefix = options?.prefix || '$'\n const compact = (val: number, divisor: number, suffix: string) => {\n const divided = val / divisor\n const formatted = divided % 1 === 0 ? divided.toFixed(0) : divided.toFixed(1)\n return `${sign}${prefix}${formatted}${suffix}`\n }\n if (absValue >= 1_000_000_000) return compact(absValue, 1_000_000_000, 'B')\n if (absValue >= 1_000_000) return compact(absValue, 1_000_000, 'M')\n if (absValue >= 1_000) return `${sign}${prefix}${(absValue / 1_000).toFixed(0)}K`\n return `${sign}${prefix}${absValue.toLocaleString()}`\n }\n\n if (format === 'percentage') {\n return `${sign}${absValue}%`\n }\n\n if (format === 'months') {\n const rounded = Math.round(value * 10) / 10\n return `${rounded} ${options?.suffix || 'months'}`\n }\n\n if (absValue >= 1_000_000) return `${sign}${(absValue / 1_000_000).toFixed(1)}M`\n if (absValue >= 1_000) return `${sign}${(absValue / 1_000).toFixed(1)}K`\n return value.toLocaleString()\n}\n\n/**\n * Get ODS trend colors based on direction AND polarity. Single source\n * of truth for trend coloring across hub + lib.\n */\nexport function getTrendColors(\n direction: 'up' | 'down' | 'neutral',\n polarity: TrendPolarity = 'positive',\n): { textClass: string; badgeClass: string } {\n if (direction === 'neutral' || polarity === 'neutral') {\n return {\n textClass: 'text-ods-text-secondary',\n badgeClass: 'bg-ods-border text-ods-text-secondary',\n }\n }\n\n const isPositiveOutcome =\n (direction === 'up' && polarity === 'positive') ||\n (direction === 'down' && polarity === 'negative')\n\n if (isPositiveOutcome) {\n return {\n textClass: 'text-ods-success',\n badgeClass: 'bg-ods-success-secondary text-ods-success',\n }\n }\n\n return {\n textClass: 'text-ods-error',\n badgeClass: 'bg-ods-error-secondary text-ods-error',\n }\n}\n\n/**\n * Format a date range as `Apr 20, 2026 — Jul 20, 2026`. Used on review\n * cycle list/detail rows and summary headers.\n *\n * Accepts either full ISO timestamps or bare `YYYY-MM-DD` dates. Bare\n * dates are interpreted in the viewer's LOCAL timezone — otherwise\n * `\"2026-04-20\"` renders as `\"Apr 19\"` west of UTC. DB values for cycle\n * period are stored as plain dates, so local-tz parsing is correct.\n */\nexport function formatDateRange(\n start: string | null | undefined,\n end: string | null | undefined,\n): string {\n if (!start || !end) return ''\n const fmt = (s: string): string => {\n const bareMatch = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(s)\n const d = bareMatch\n ? new Date(Number(bareMatch[1]), Number(bareMatch[2]) - 1, Number(bareMatch[3]))\n : new Date(s)\n if (Number.isNaN(d.getTime())) return s\n return d.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n }\n return `${fmt(start)} — ${fmt(end)}`\n}\n\n/**\n * Format an ISO date string as `\"Jan 5, 2025 at 10:30 AM\"`. Used by\n * admin podcast/webinar cards where the display needs both the short\n * date AND wall-clock time in a single readable phrase.\n *\n * Uses `new Date(...).toLocale*` (NOT the TZ-safe split) because the\n * source columns store full timestamps + the wall-clock half MUST\n * render in the viewer's local timezone (a podcast scheduled \"10:30 AM\n * EST\" should display \"10:30 AM\" for the Eastern admin, \"7:30 AM\" for\n * the Pacific admin — viewer-local is the right semantics here, unlike\n * date-only fields).\n */\nexport function formatDateTimeAt(dateString: string): string {\n const date = new Date(dateString)\n const dateStr = date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n const timeStr = date.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n })\n return `${dateStr} at ${timeStr}`\n}\n\n/**\n * Format a duration measured in MILLISECONDS as a compact human-readable\n * string. Returns `\"0ms\"` for null/NaN/negative, then `Xms` → `X.Xs` →\n * `X.Xm` as the input grows. Used by job-runs / orchestrator dashboards\n * where elapsed milliseconds is the natural unit.\n *\n * Distinct from `formatDuration(seconds)` (verbose `\"X hours Y minutes\"`)\n * and `formatDurationMMSS(seconds)` (media timecode `MM:SS`).\n */\nexport function formatDurationFromMs(ms: number | null | undefined): string {\n if (!ms || isNaN(ms) || ms < 0) return '0ms'\n if (ms < 1000) return `${ms}ms`\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`\n return `${(ms / 60000).toFixed(1)}m`\n}\n\n/**\n * Format seconds to verbose human-readable duration: `\"X seconds\"`,\n * `\"X minutes\"`, `\"X hours Y minutes\"`. Use this for human-readable\n * spans; for media timecodes use `formatDurationMMSS`; for compact\n * media labels use `formatDurationCompact`; for elapsed milliseconds\n * use `formatDurationFromMs`.\n */\nexport function formatDuration(seconds: number): string {\n if (seconds < 60) return `${seconds} seconds`\n if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`\n\n const hours = Math.floor(seconds / 3600)\n const minutes = Math.floor((seconds % 3600) / 60)\n\n if (minutes === 0) return `${hours} hour${hours > 1 ? 's' : ''}`\n return `${hours} hour${hours > 1 ? 's' : ''} ${minutes} minute${minutes > 1 ? 's' : ''}`\n}\n\n// =============================================================================\n// Text formatting helpers (proper-case, HTML strip, bio cleanup)\n// =============================================================================\n\n/**\n * Format underscore-separated text into proper case.\n * `\"self_hosted\"` → `\"Self Hosted\"`\n * `\"open_source\"` → `\"Open Source\"`\n */\nexport function formatUnderscoreText(text: string): string {\n return text\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n}\n\n/**\n * Strip HTML tags and decode common HTML entities from a string.\n * Useful for cleaning API responses that contain HTML content.\n *\n * @example\n * stripHtml('<p>Hello <strong>World</strong></p>') // \"Hello World\"\n * stripHtml('Test & Example') // \"Test & Example\"\n */\nexport function stripHtml(html: string): string {\n // Iterative tag-strip — a single pass is bypass-able by inputs like\n // `<scr<script>ipt>`: stripping the inner `<script>` leaves the outer\n // `<script>` reassembled. Loop until the string is stable so multi-\n // character bypasses cannot survive (CodeQL\n // `js/incomplete-multi-character-sanitization`). `<[^<>]*>` rejects\n // `<` inside the tag body so each pass is itself ReDoS-safe (no\n // backtracking on `<<<<<...<>` inputs).\n let noTags = html\n let prev: string\n do {\n prev = noTags\n noTags = noTags.replace(/<[^<>]*>/g, '')\n } while (noTags !== prev)\n\n // Decode entities. `&` MUST come LAST so we don't double-decode\n // sequences like `&lt;` (which should render as the LITERAL text\n // `<`, not as `<`). All other named/numeric entities decode first;\n // only after those have been replaced do we collapse `&` → `&`.\n return noTags\n .replace(/ /g, ' ')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/&/g, '&')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/**\n * Display label for the openmsp `vendors.classification` enum.\n * Falls back to proper-cased underscore split for values not in the\n * curated mapping.\n */\nexport function formatClassification(classification: string): string {\n const customMappings: Record<string, string> = {\n openframe_selected: 'OpenFrame Selected',\n }\n return customMappings[classification] || formatUnderscoreText(classification)\n}\n\n/**\n * Display label for the openmsp `vendors.pricing_model` enum.\n * Falls back to proper-cased underscore split for values not in the\n * curated mapping.\n */\nexport function formatPricingModel(pricingModel: string): string {\n const customMappings: Record<string, string> = {\n one_time: 'One-time Purchase',\n self_hosted: 'Self-hosted',\n }\n return customMappings[pricingModel] || formatUnderscoreText(pricingModel)\n}\n\n/**\n * Format a bio/about text from the profiles table for display.\n * Handles HTML content (e.g. `<p>` tags from rich text editors),\n * plain text passthrough, and null/undefined values.\n */\nexport function formatBioText(\n aboutHtml: string | null | undefined,\n fallback: string = '',\n): string {\n if (!aboutHtml || !aboutHtml.trim()) return fallback\n\n if (aboutHtml.includes('<p')) {\n // `<p[^<>]*>` rejects `<` inside the tag so the automaton can't\n // backtrack on `<<<<<...<p>` inputs (ReDoS class fix vs. `<p[^>]*>`).\n const paragraphs = aboutHtml\n .split(/<p[^<>]*>/)\n .slice(1)\n .map((part) => part.split('</p>')[0])\n .map((text) => stripHtml(text).trim())\n .filter((text) => text.length > 0)\n\n if (paragraphs.length > 0) return paragraphs.join(' ')\n }\n\n return stripHtml(aboutHtml).trim() || fallback\n}\n","\"use client\"\n\nimport * as React from \"react\";\nimport Image from \"../../embed-shims/next-image\";\nimport { cn } from \"../../utils/cn\";\nimport { getFirstLastInitials } from \"../../utils/format\";\n\ninterface SquareAvatarProps extends React.HTMLAttributes<HTMLDivElement> {\n src?: string;\n alt?: string;\n size?: 'sm' | 'md' | 'lg' | 'xl';\n fallback?: string;\n variant?: 'square' | 'round';\n /** Override the initials-fallback styling (font size/color). Merged over the\n * defaults (`text-xs font-medium text-ods-text-primary`) via tailwind-merge,\n * so callers can shrink/recolor the initials for compact avatars. */\n initialsClassName?: string;\n}\n\nconst SquareAvatar = React.memo(React.forwardRef<HTMLDivElement, SquareAvatarProps>(\n ({ className, src, alt, size = 'md', fallback, variant = 'square', initialsClassName, ...props }, ref) => {\n const sizeClasses = {\n sm: 'h-8 w-8',\n md: 'h-10 w-10',\n lg: 'h-12 w-12',\n xl: 'h-16 w-16'\n };\n\n const sizePx = {\n sm: 32,\n md: 40,\n lg: 48,\n xl: 64\n };\n\n const variantClasses = {\n square: 'rounded-md',\n round: 'rounded-full'\n };\n\n return (\n <div\n className={cn(\n \"relative flex items-center justify-center shrink-0 overflow-hidden border border-ods-border bg-ods-bg\",\n sizeClasses[size],\n variantClasses[variant],\n className\n )}\n ref={ref}\n {...props}\n >\n <div className={cn(\n // Initials default to `--color-text-primary` (the old\n // `text-ods-text-primary` value) so they stay readable on the default\n // `bg-ods-bg` AND on the brand accent fills (`bg-ods-flamingo-pink`\n // for the current user, `bg-ods-flamingo-cyan` for Mingo). The color\n // resolves through `--ods-avatar-initials` with that fallback, so a\n // host themed with a custom avatar fill can override the var with a\n // contrast-correct value (e.g. `getReadableTextColor(accent)`) WITHOUT\n // regressing any avatar that leaves the var unset. A caller passing\n // its own `initialsClassName` text color still wins (tailwind-merge\n // keeps the later class).\n 'flex items-center justify-center text-xs font-medium text-[color:var(--ods-avatar-initials,var(--color-text-primary))]',\n initialsClassName,\n src && 'hidden'\n )}>\n {getFirstLastInitials(fallback || alt) || '?'}\n </div>\n {src && (\n <Image\n className=\"absolute inset-0 h-full w-full object-cover\"\n src={src}\n alt={alt || ''}\n width={sizePx[size]}\n height={sizePx[size]}\n onError={(e) => {\n e.currentTarget.style.display = 'none';\n const el = e.currentTarget.previousElementSibling as HTMLElement;\n if (el) el.classList.remove('hidden');\n }}\n />\n )}\n </div>\n )\n }\n))\nSquareAvatar.displayName = \"SquareAvatar\"\n\nexport { SquareAvatar };\n","\"use client\";\n\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { Check } from \"lucide-react\";\nimport Link from \"../../embed-shims/next-link\";\nimport React, { useCallback } from \"react\";\nimport { Chevron02RightIcon, Ellipsis01Icon } from \"../icons-v2-generated\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./button\";\nimport {\n\tDropdownMenu,\n\tDropdownMenuContent,\n\tDropdownMenuTrigger,\n} from \"./dropdown-menu\";\n\nexport interface ActionsMenuItemIconAction {\n\ticon: React.ReactNode;\n\t\"aria-label\": string;\n\tonClick?: () => void;\n\thref?: string;\n\topenInNewTab?: boolean;\n\tdisabled?: boolean;\n}\n\nexport interface ActionsMenuItem {\n\tid: string;\n\tlabel: string;\n\ticon?: React.ReactNode;\n\tonClick?: () => void;\n\tdisabled?: boolean;\n\ttype?: \"item\" | \"checkbox\" | \"submenu\" | \"separator\";\n\tchecked?: boolean;\n\t/**\n\t * Keep the dropdown open after this item is clicked instead of closing it\n\t * (e.g. multi-select add). Only affects ActionsMenuDropdown; `checkbox` and\n\t * `submenu` items always keep the menu open regardless. Defaults to closing.\n\t */\n\tcloseOnSelect?: boolean;\n\tsubmenu?: ActionsMenuItem[];\n\t/** Render the row in the error/destructive color (label + icon). */\n\tdanger?: boolean;\n\t/** Optional URL for navigation items */\n\thref?: string;\n\t/**\n\t * Optional secondary action — a 40px-wide button on the right of the row\n\t * with a vertical divider. The main row keeps its primary click target;\n\t * the secondary is independently clickable (e.g. \"open in new tab\").\n\t */\n\ticonAction?: ActionsMenuItemIconAction;\n}\n\nexport interface ActionsMenuGroup {\n\tid?: string;\n\titems: ActionsMenuItem[];\n\tseparator?: boolean;\n}\n\nexport interface ActionsMenuProps {\n\tgroups: ActionsMenuGroup[];\n\tclassName?: string;\n\tonItemClick?: (item: ActionsMenuItem) => void;\n}\n\ninterface MenuItemProps {\n\titem: ActionsMenuItem;\n\tonItemClick?: (item: ActionsMenuItem) => void;\n}\n\nconst ROW_CLASSES =\n\t\"flex flex-1 min-w-0 items-center gap-[var(--spacing-system-xsf)] p-[var(--spacing-system-s)] cursor-pointer transition-colors bg-ods-bg outline-none\";\nconst WRAPPER_CLASSES =\n\t\"relative flex items-stretch border-b border-ods-border last:border-b-0\";\n\nconst SECONDARY_ACTION_CLASSES =\n\t\"flex p-[var(--spacing-system-s)] shrink-0 items-center justify-center self-stretch border-l border-ods-border transition-colors hover:bg-ods-bg-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus [&_svg]:w-4 [&_svg]:h-4 md:[&_svg]:w-6 md:[&_svg]:h-6\";\n\nconst SecondaryAction: React.FC<{ action: ActionsMenuItemIconAction }> = ({ action }) => {\n\tconst handleClick = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\te.stopPropagation();\n\t\t\tif (action.disabled) {\n\t\t\t\te.preventDefault();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\taction.onClick?.();\n\t\t},\n\t\t[action],\n\t);\n\n\tconst classes = cn(\n\t\tSECONDARY_ACTION_CLASSES,\n\t\taction.disabled && \"cursor-not-allowed opacity-60 pointer-events-none\",\n\t);\n\n\tif (action.href) {\n\t\treturn (\n\t\t\t<Link\n\t\t\t\thref={action.href}\n\t\t\t\tprefetch={false}\n\t\t\t\ttarget={action.openInNewTab ? \"_blank\" : undefined}\n\t\t\t\trel={action.openInNewTab ? \"noopener noreferrer\" : undefined}\n\t\t\t\taria-label={action[\"aria-label\"]}\n\t\t\t\taria-disabled={action.disabled || undefined}\n\t\t\t\ttabIndex={action.disabled ? -1 : undefined}\n\t\t\t\tclassName={classes}\n\t\t\t\tonClick={handleClick}\n\t\t\t>\n\t\t\t\t{action.icon}\n\t\t\t</Link>\n\t\t);\n\t}\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\taria-label={action[\"aria-label\"]}\n\t\t\tdisabled={action.disabled}\n\t\t\tclassName={classes}\n\t\t\tonClick={handleClick}\n\t\t>\n\t\t\t{action.icon}\n\t\t</button>\n\t);\n};\n\nconst MenuItem: React.FC<MenuItemProps> = ({ item, onItemClick }) => {\n\tconst activate = useCallback(() => {\n\t\tif (item.disabled) return;\n\t\tif (item.type === \"checkbox\") {\n\t\t\titem.onClick?.();\n\t\t\tonItemClick?.(item);\n\t\t\treturn;\n\t\t}\n\t\tif (item.type === \"submenu\") return;\n\t\titem.onClick?.();\n\t\tonItemClick?.(item);\n\t}, [item, onItemClick]);\n\n\tconst handleClick = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tactivate();\n\t\t},\n\t\t[activate],\n\t);\n\n\tconst handleKeyDown = useCallback(\n\t\t(e: React.KeyboardEvent) => {\n\t\t\tif (e.key !== \"Enter\" && e.key !== \" \") return;\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tactivate();\n\t\t},\n\t\t[activate],\n\t);\n\n\tconst handleLinkClick = useCallback(\n\t\t(e: React.MouseEvent<HTMLAnchorElement>) => {\n\t\t\tif (item.disabled) {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\titem.onClick?.();\n\t\t\tonItemClick?.(item);\n\t\t},\n\t\t[item, onItemClick],\n\t);\n\n\tif (item.type === \"separator\") {\n\t\treturn <div className=\"bg-ods-system-greys-soft-grey h-1 w-full\" />;\n\t}\n\n\tconst itemClasses = cn(\n\t\tROW_CLASSES,\n\t\titem.disabled\n\t\t\t? \"text-ods-text-secondary cursor-not-allowed pointer-events-none opacity-60\"\n\t\t\t: \"text-ods-text-primary hover:bg-ods-bg-hover\",\n\t);\n\n\tconst subTriggerClasses = cn(\n\t\titemClasses,\n\t\t\"data-[state=open]:bg-ods-bg-active focus:bg-ods-bg-hover\",\n\t);\n\n\tconst renderAsLink =\n\t\t!!item.href && item.type !== \"submenu\" && item.type !== \"checkbox\";\n\n\tconst rowContent = (\n\t\t<>\n\t\t\t{item.icon && (\n\t\t\t\t<div\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"w-4 h-4 md:w-6 md:h-6 flex-shrink-0 flex items-center justify-center\",\n\t\t\t\t\t\titem.danger && \"text-ods-error\",\n\t\t\t\t\t\titem.disabled && \"opacity-50\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{item.icon}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t<span\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex-1 text-h4 font-medium leading-6\",\n\t\t\t\t\titem.disabled\n\t\t\t\t\t\t? \"text-ods-text-secondary\"\n\t\t\t\t\t\t: item.danger\n\t\t\t\t\t\t\t? \"text-ods-error\"\n\t\t\t\t\t\t\t: \"text-ods-text-primary\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{item.label}\n\t\t\t</span>\n\n\t\t\t{item.type === \"checkbox\" && (\n\t\t\t\t<div\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"w-4 h-4 md:w-6 md:h-6 flex items-center justify-center rounded-md transition-colors\",\n\t\t\t\t\t\titem.checked\n\t\t\t\t\t\t\t? \"bg-ods-accent\"\n\t\t\t\t\t\t\t: \"border-2 border-ods-border bg-transparent\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{item.checked && (\n\t\t\t\t\t\t<Check className=\"w-3 h-3 md:w-4 md:h-4 text-ods-text-on-accent\" strokeWidth={3} />\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{item.type === \"submenu\" && (\n\t\t\t\t<Chevron02RightIcon className=\"w-4 h-4 md:w-6 md:h-6 text-ods-text-secondary\" />\n\t\t\t)}\n\t\t</>\n\t);\n\n\tif (renderAsLink && item.href) {\n\t\treturn (\n\t\t\t<div className={WRAPPER_CLASSES}>\n\t\t\t\t<Link\n\t\t\t\t\thref={item.href}\n\t\t\t\t\tprefetch={false}\n\t\t\t\t\tclassName={itemClasses}\n\t\t\t\t\tonClick={handleLinkClick}\n\t\t\t\t\taria-disabled={item.disabled}\n\t\t\t\t\ttabIndex={item.disabled ? -1 : undefined}\n\t\t\t\t>\n\t\t\t\t\t{rowContent}\n\t\t\t\t</Link>\n\t\t\t\t{item.iconAction && <SecondaryAction action={item.iconAction} />}\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (item.type === \"submenu\" && item.submenu) {\n\t\treturn (\n\t\t\t<div className={WRAPPER_CLASSES}>\n\t\t\t\t<DropdownMenuPrimitive.Sub>\n\t\t\t\t\t<DropdownMenuPrimitive.SubTrigger\n\t\t\t\t\t\tdisabled={item.disabled}\n\t\t\t\t\t\tclassName={subTriggerClasses}\n\t\t\t\t\t>\n\t\t\t\t\t\t{rowContent}\n\t\t\t\t\t</DropdownMenuPrimitive.SubTrigger>\n\t\t\t\t\t<DropdownMenuPrimitive.Portal>\n\t\t\t\t\t\t<DropdownMenuPrimitive.SubContent\n\t\t\t\t\t\t\tsideOffset={4}\n\t\t\t\t\t\t\tclassName=\"z-[1500] min-w-[256px] max-h-[var(--radix-popper-available-height)] bg-ods-bg border border-ods-border rounded-md shadow-xl overflow-y-auto p-0\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{item.submenu.map((subItem, index) => (\n\t\t\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\t\t\tkey={subItem.id || index}\n\t\t\t\t\t\t\t\t\titem={subItem}\n\t\t\t\t\t\t\t\t\tonItemClick={onItemClick}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</DropdownMenuPrimitive.SubContent>\n\t\t\t\t\t</DropdownMenuPrimitive.Portal>\n\t\t\t\t</DropdownMenuPrimitive.Sub>\n\t\t\t\t{item.iconAction && <SecondaryAction action={item.iconAction} />}\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className={WRAPPER_CLASSES}>\n\t\t\t<div\n\t\t\t\trole=\"menuitem\"\n\t\t\t\ttabIndex={item.disabled ? -1 : 0}\n\t\t\t\taria-disabled={item.disabled}\n\t\t\t\tclassName={itemClasses}\n\t\t\t\tonClick={handleClick}\n\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t>\n\t\t\t\t{rowContent}\n\t\t\t</div>\n\t\t\t{item.iconAction && <SecondaryAction action={item.iconAction} />}\n\t\t</div>\n\t);\n};\n\nconst GroupSeparator: React.FC = () => (\n\t<div className=\"bg-ods-bg-surface h-[3px] w-full\" />\n);\n\nexport const ActionsMenu: React.FC<ActionsMenuProps> = ({\n\tgroups,\n\tclassName = \"\",\n\tonItemClick,\n}) => {\n\treturn (\n\t\t<div\n\t\t\tclassName={`relative min-w-[256px] max-h-[var(--radix-popper-available-height)] bg-ods-bg border border-ods-border rounded-md shadow-lg overflow-y-auto ${className}`}\n\t\t>\n\t\t\t{groups.map((group, groupIndex) => {\n\t\t\t\tconst groupKey = group.id || group.items.map((i) => i.id).join(\"|\");\n\t\t\t\treturn (\n\t\t\t\t\t<React.Fragment key={groupKey}>\n\t\t\t\t\t\t{group.items.map((item, itemIndex) => (\n\t\t\t\t\t\t\t<MenuItem\n\t\t\t\t\t\t\t\tkey={item.id || `${groupKey}-${itemIndex}`}\n\t\t\t\t\t\t\t\titem={item}\n\t\t\t\t\t\t\t\tonItemClick={onItemClick}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t{group.separator && groupIndex < groups.length - 1 && (\n\t\t\t\t\t\t\t<GroupSeparator />\n\t\t\t\t\t\t)}\n\t\t\t\t\t</React.Fragment>\n\t\t\t\t);\n\t\t\t})}\n\t\t</div>\n\t);\n};\n\nexport interface ActionsMenuDropdownProps extends ActionsMenuProps {\n\ttrigger?: React.ReactNode;\n\t/** Replace the entire default trigger button. When set, rendered directly as the DropdownMenuTrigger child. */\n\tcustomTrigger?: React.ReactNode;\n\ttriggerAriaLabel?: string;\n\ttriggerClassName?: string;\n\tcontentClassName?: string;\n\talign?: \"start\" | \"center\" | \"end\";\n\tside?: \"top\" | \"right\" | \"bottom\" | \"left\";\n\tsideOffset?: number;\n\t/** Controlled open state. Pair with `onOpenChange`. Uncontrolled by default. */\n\topen?: boolean;\n\t/** Open-state change handler (also fires when an item closes the menu). */\n\tonOpenChange?: (open: boolean) => void;\n\t/** Forwarded to the dropdown content — e.g. `e.preventDefault()` to stop\n\t * Radix returning focus (and its focus ring) to the trigger on close. */\n\tonCloseAutoFocus?: (event: Event) => void;\n}\n\nexport const ActionsMenuDropdown: React.FC<ActionsMenuDropdownProps> = ({\n\tgroups,\n\tonItemClick,\n\tclassName,\n\ttrigger,\n\tcustomTrigger,\n\ttriggerAriaLabel = \"More actions\",\n\ttriggerClassName,\n\tcontentClassName,\n\talign = \"end\",\n\tside = \"bottom\",\n\tsideOffset = 6,\n\topen: openProp,\n\tonOpenChange,\n\tonCloseAutoFocus,\n}) => {\n\tconst [open = false, setOpen] = useControllableState({\n\t\tprop: openProp,\n\t\tdefaultProp: false,\n\t\tonChange: onOpenChange,\n\t});\n\n\tconst handleItemClick = useCallback(\n\t\t(item: ActionsMenuItem) => {\n\t\t\tonItemClick?.(item);\n\t\t\tif (\n\t\t\t\titem.type !== \"checkbox\" &&\n\t\t\t\titem.type !== \"submenu\" &&\n\t\t\t\titem.closeOnSelect !== false\n\t\t\t) {\n\t\t\t\tsetOpen(false);\n\t\t\t}\n\t\t},\n\t\t[onItemClick, setOpen],\n\t);\n\n\treturn (\n\t\t<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>\n\t\t\t<DropdownMenuTrigger asChild>\n\t\t\t\t{customTrigger ?? (\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\taria-label={triggerAriaLabel}\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\ttriggerClassName ||\n\t\t\t\t\t\t\t\"bg-ods-card border-ods-border hover:bg-ods-bg-hover flex items-center justify-center focus-visible:ring-0\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\tleftIcon={\n\t\t\t\t\t\t\ttrigger ?? (\n\t\t\t\t\t\t\t\t<Ellipsis01Icon size={24} className=\"text-ods-text-primary\" />\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</DropdownMenuTrigger>\n\t\t\t<DropdownMenuContent\n\t\t\t\talign={align}\n\t\t\t\tside={side}\n\t\t\t\tsideOffset={sideOffset}\n\t\t\t\tonCloseAutoFocus={onCloseAutoFocus}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"p-0 border-0 bg-transparent shadow-none overflow-visible\",\n\t\t\t\t\tcontentClassName,\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<ActionsMenu\n\t\t\t\t\tgroups={groups}\n\t\t\t\t\tonItemClick={handleItemClick}\n\t\t\t\t\tclassName={className}\n\t\t\t\t/>\n\t\t\t</DropdownMenuContent>\n\t\t</DropdownMenu>\n\t);\n};\n\nexport default ActionsMenu;\n","import * as React from 'react';\nimport { useLayoutEffect } from '@radix-ui/react-use-layout-effect';\n\n// Prevent bundlers from trying to optimize the import\nconst useInsertionEffect: typeof useLayoutEffect =\n (React as any)[' useInsertionEffect '.trim().toString()] || useLayoutEffect;\n\ntype ChangeHandler<T> = (state: T) => void;\ntype SetStateFn<T> = React.Dispatch<React.SetStateAction<T>>;\n\ninterface UseControllableStateParams<T> {\n prop?: T | undefined;\n defaultProp: T;\n onChange?: ChangeHandler<T>;\n caller?: string;\n}\n\nexport function useControllableState<T>({\n prop,\n defaultProp,\n onChange = () => {},\n caller,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({\n defaultProp,\n onChange,\n });\n const isControlled = prop !== undefined;\n const value = isControlled ? prop : uncontrolledProp;\n\n // OK to disable conditionally calling hooks here because they will always run\n // consistently in the same environment. Bundlers should be able to remove the\n // code block entirely in production.\n /* eslint-disable react-hooks/rules-of-hooks */\n if (process.env.NODE_ENV !== 'production') {\n const isControlledRef = React.useRef(prop !== undefined);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? 'controlled' : 'uncontrolled';\n const to = isControlled ? 'controlled' : 'uncontrolled';\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`,\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n /* eslint-enable react-hooks/rules-of-hooks */\n\n const setValue = React.useCallback<SetStateFn<T>>(\n (nextValue) => {\n if (isControlled) {\n const value = isFunction(nextValue) ? nextValue(prop) : nextValue;\n if (value !== prop) {\n onChangeRef.current?.(value);\n }\n } else {\n setUncontrolledProp(nextValue);\n }\n },\n [isControlled, prop, setUncontrolledProp, onChangeRef],\n );\n\n return [value, setValue];\n}\n\nfunction useUncontrolledState<T>({\n defaultProp,\n onChange,\n}: Omit<UseControllableStateParams<T>, 'prop'>): [\n Value: T,\n setValue: React.Dispatch<React.SetStateAction<T>>,\n OnChangeRef: React.RefObject<ChangeHandler<T> | undefined>,\n] {\n const [value, setValue] = React.useState(defaultProp);\n const prevValueRef = React.useRef(value);\n\n const onChangeRef = React.useRef(onChange);\n useInsertionEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n\n React.useEffect(() => {\n if (prevValueRef.current !== value) {\n onChangeRef.current?.(value);\n prevValueRef.current = value;\n }\n }, [value, prevValueRef]);\n\n return [value, setValue, onChangeRef];\n}\n\nfunction isFunction(value: unknown): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n","import * as React from 'react';\nimport { useEffectEvent } from '@radix-ui/react-use-effect-event';\n\ntype ChangeHandler<T> = (state: T) => void;\n\ninterface UseControllableStateParams<T> {\n prop: T | undefined;\n defaultProp: T;\n onChange: ChangeHandler<T> | undefined;\n caller: string;\n}\n\ninterface AnyAction {\n type: string;\n}\n\nconst SYNC_STATE = Symbol('RADIX:SYNC_STATE');\n\ninterface SyncStateAction<T> {\n type: typeof SYNC_STATE;\n state: T;\n}\n\nexport function useControllableStateReducer<T, S extends {}, A extends AnyAction>(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams<T>,\n initialState: S,\n): [S & { state: T }, React.Dispatch<A>];\n\nexport function useControllableStateReducer<T, S extends {}, I, A extends AnyAction>(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams<T>,\n initialArg: I,\n init: (i: I & { state: T }) => S,\n): [S & { state: T }, React.Dispatch<A>];\n\nexport function useControllableStateReducer<T, S extends {}, A extends AnyAction>(\n reducer: (prevState: S & { state: T }, action: A) => S & { state: T },\n userArgs: UseControllableStateParams<T>,\n initialArg: any,\n init?: (i: any) => Omit<S, 'state'>,\n): [S & { state: T }, React.Dispatch<A>] {\n const { prop: controlledState, defaultProp, onChange: onChangeProp, caller } = userArgs;\n const isControlled = controlledState !== undefined;\n\n const onChange = useEffectEvent(onChangeProp);\n\n // OK to disable conditionally calling hooks here because they will always run\n // consistently in the same environment. Bundlers should be able to remove the\n // code block entirely in production.\n /* eslint-disable react-hooks/rules-of-hooks */\n if (process.env.NODE_ENV !== 'production') {\n const isControlledRef = React.useRef(controlledState !== undefined);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? 'controlled' : 'uncontrolled';\n const to = isControlled ? 'controlled' : 'uncontrolled';\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`,\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n /* eslint-enable react-hooks/rules-of-hooks */\n\n type InternalState = S & { state: T };\n const args: [InternalState] = [{ ...initialArg, state: defaultProp }];\n if (init) {\n // @ts-expect-error\n args.push(init);\n }\n\n const [internalState, dispatch] = React.useReducer(\n (state: InternalState, action: A | SyncStateAction<T>): InternalState => {\n if (action.type === SYNC_STATE) {\n return { ...state, state: action.state };\n }\n\n const next = reducer(state, action);\n if (isControlled && !Object.is(next.state, state.state)) {\n onChange(next.state);\n }\n return next;\n },\n ...args,\n );\n\n const uncontrolledState = internalState.state;\n const prevValueRef = React.useRef(uncontrolledState);\n React.useEffect(() => {\n if (prevValueRef.current !== uncontrolledState) {\n prevValueRef.current = uncontrolledState;\n if (!isControlled) {\n onChange(uncontrolledState);\n }\n }\n }, [uncontrolledState, prevValueRef, isControlled]);\n\n const state = React.useMemo(() => {\n const isControlled = controlledState !== undefined;\n if (isControlled) {\n return { ...internalState, state: controlledState };\n }\n\n return internalState;\n }, [internalState, controlledState]);\n\n React.useEffect(() => {\n // Sync internal state for controlled components so that reducer is called\n // with the correct state values\n if (isControlled && !Object.is(controlledState, internalState.state)) {\n dispatch({ type: SYNC_STATE, state: controlledState });\n }\n }, [controlledState, internalState.state, isControlled]);\n\n return [state, dispatch as React.Dispatch<A>];\n}\n","import * as React from 'react';\n\n/**\n * On the server, React emits a warning when calling `useLayoutEffect`.\n * This is because neither `useLayoutEffect` nor `useEffect` run on the server.\n * We use this safe version which suppresses the warning by replacing it with a noop on the server.\n *\n * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect\n */\nconst useLayoutEffect = globalThis?.document ? React.useLayoutEffect : () => {};\n\nexport { useLayoutEffect };\n","/**\n * Pure image-proxy URL builder + helpers.\n *\n * Lib-side replacement for the hub's `lib/utils/image-proxy.ts`. The\n * hub used to hardcode `/api/image-proxy` + an `openmsp.ai` skip-domain;\n * this version takes BOTH as parameters so embedded apps (and other\n * platforms that host the lib) can wire their own proxy prefix + skip\n * list at the runtime layer.\n *\n * Pure function — no side effects, no env reads. Callers thread the\n * proxy config through (typically from `ChatRuntime.endpoints.imageProxyUrlPrefix`).\n */\n\nexport type GetProxiedImageUrlOptions = {\n /**\n * URL prefix for the image proxy (`<prefix>?url=<encoded>`). When unset,\n * `getProxiedImageUrl` returns the original URL unchanged — relative\n * URLs always pass through. Hub default: `/api/image-proxy`.\n */\n proxyPrefix?: string;\n /**\n * Domains that should bypass the proxy (e.g. own-CDN hosts whose\n * `Content-Type` is reliable and that already serve CORS-permitting\n * headers). Matched as `imageUrl.includes(domain)` so subdomains\n * inherit. Default: `[]`.\n */\n skipDomains?: string[];\n /**\n * Return the original `https://` URL so the browser loads it directly\n * (no proxy). Use when upstream `Content-Type` breaks the proxy (common\n * with SVG on some CDNs) or you want the origin to see the client\n * request. HTTP stays proxied (mixed content / legacy).\n */\n directHttps?: boolean;\n};\n\n/**\n * Resolve an external image URL through (or around) the image proxy.\n *\n * Resolution order:\n * 1. `imageUrl` already contains `proxyPrefix` → return unchanged\n * (self-skip — prevents double-wrap).\n * 2. `directHttps` set AND `imageUrl` starts with `https://` → return\n * unchanged.\n * 3. `imageUrl` doesn't start with `http://` or `https://` → return\n * unchanged (relative URLs pass through).\n * 4. `proxyPrefix` unset → return unchanged.\n * 5. `skipDomains` matches the URL's host → return unchanged.\n * 6. Else return `<proxyPrefix>?url=<encoded>`.\n */\nexport function getProxiedImageUrl(\n imageUrl: string | null,\n options?: GetProxiedImageUrlOptions,\n): string | null {\n if (!imageUrl) return null;\n\n const proxyPrefix = options?.proxyPrefix;\n const skipDomains = options?.skipDomains ?? [];\n const directHttps = options?.directHttps ?? false;\n\n // (1) Self-skip — already proxied. Check this BEFORE the http/https\n // gate so an absolute proxy URL passed in (e.g. `https://hub.example/api/image-proxy?url=…`)\n // is treated as already-proxied even though it starts with https://.\n if (proxyPrefix && imageUrl.includes(proxyPrefix)) {\n return imageUrl;\n }\n\n // (3) Relative URLs / data: / blob: — return as-is.\n if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://')) {\n return imageUrl;\n }\n\n // (2) Direct-https opt-out — only applies to https (http stays proxied).\n if (directHttps && imageUrl.startsWith('https://')) {\n return imageUrl;\n }\n\n // (4) No proxy configured — return as-is.\n if (!proxyPrefix) {\n return imageUrl;\n }\n\n // (5) Skip-list match.\n for (const domain of skipDomains) {\n if (imageUrl.includes(domain)) {\n return imageUrl;\n }\n }\n\n // (6) Proxy.\n return `${proxyPrefix}?url=${encodeURIComponent(imageUrl)}`;\n}\n\n/**\n * Heuristic: URL path looks like an SVG. Useful with `{ directHttps: true }`\n * when only SVGs misbehave through the proxy; raster images can stay\n * proxied if you prefer.\n */\nexport function urlPathLooksLikeSvg(imageUrl: string): boolean {\n try {\n return /\\.svg$/i.test(new URL(imageUrl).pathname);\n } catch {\n return /\\.svg(\\?|#|$)/i.test(imageUrl);\n }\n}\n\n/**\n * Check if an image URL needs to be proxied. `proxyPrefix` is the same\n * value passed to `getProxiedImageUrl` — used to short-circuit the\n * self-skip case.\n */\nexport function shouldProxyImage(\n imageUrl: string | null,\n proxyPrefix?: string,\n): boolean {\n if (!imageUrl) return false;\n if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://')) return false;\n if (proxyPrefix && imageUrl.includes(proxyPrefix)) return false;\n return true;\n}\n\n/**\n * Generate a responsive `sizes` attribute for `<img>` / `<Image>` tags.\n * Pure utility — kept here for backwards-compatibility with the previous\n * stub.\n */\nexport function generateImageSizes(_url: string): string {\n return `(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw`;\n}\n","/**\n * Pure utility — true iff `url` has an explicit host (i.e. would resolve\n * to a different origin than where the consumer is running). Used by\n * chat nav decisions and the extracted `decideNewTab` helper.\n *\n * SSR-deterministic: any URL with an explicit host (http://, https://,\n * or protocol-relative //) is treated as cross-origin. By convention\n * same-origin same-app navigation uses RELATIVE paths; an absolute URL\n * with a host is external.\n *\n * Why deterministic: an earlier `window`-dependent implementation\n * returned different values during SSR (false — no window) vs client\n * hydration (true — origin differs), causing React hydration mismatches\n * on every anchor that fell into the no-targetPlatform fallback (e.g.\n * footer \"Support\" links to flamingo.run/support). Same-platform\n * absolute URLs (rare — they only happen when a composer like\n * `buildContentURL` returns an absolute URL for a non-current platform)\n * flow through `decideNewTab`'s PLATFORM branch first (which uses\n * `targetPlatform !== currentSource`) and never reach this fallback.\n */\nexport function isCrossOriginUrl(url: string | null | undefined): boolean {\n if (!url) return false\n // Relative URL — by definition same origin.\n if (url.startsWith('/') && !url.startsWith('//')) return false\n // Anything else — absolute URL (http(s)://) or protocol-relative (//)\n // — is treated as cross-origin.\n return true\n}\n","/**\n * Pure new-tab decision helper.\n *\n * Same logic as the hub's prior inline `decideNewTab`, but parameterized:\n * - `currentSource` is passed by caller (was `currentPlatform()` reads\n * inside the body). Hub callers pass `currentPlatform()`; lib chip\n * code passes `runtime.source ?? ''`.\n *\n * BRANCH PRIORITY:\n * 1. `runtimeMode === 'embed'` → always new-tab (embed-mode short-circuit)\n * 2. `openIn === 'new-tab'` or `'same-tab'` — explicit caller override\n * 3. `alwaysNewTab === true` (legacy synonym for `openIn: 'new-tab'`)\n * 4. `targetPlatform` defined AND `currentSource` non-empty →\n * platform comparison (cross-app → new tab, same-app → same tab)\n * 5. Legacy fallback: `isCrossOriginUrl(href)` — origin compare. Works\n * in prod (distinct hosts) but degrades to \"always same-tab\" in dev.\n *\n * No call site implements its own variant of the rule.\n */\n\nimport { isCrossOriginUrl } from './is-cross-origin-url'\n\nexport type NavSurface = 'useNavLink' | 'useUnifiedNav'\nexport type RuntimeMode = 'host' | 'embed'\n\nexport interface DecideNewTabInput {\n href: string | null | undefined\n targetPlatform?: string | null\n openIn?: 'new-tab' | 'same-tab'\n alwaysNewTab?: boolean\n /** Caller tag — labels the decision in any external trace logging. */\n surface?: NavSurface\n /** Optional chat-runtime mode. When omitted OR set to `'host'`,\n * behavior is byte-equivalent to the legacy logic. Only `'embed'`\n * triggers the new priority-1 branch that forces new-tab. */\n runtimeMode?: RuntimeMode\n /** Current chat source / platform identifier — caller-threaded.\n * Empty string disables the platform-comparison branch (falls\n * through to the origin-compare fallback). */\n currentSource: string\n}\n\nexport function decideNewTab(input: DecideNewTabInput): boolean {\n const { href, targetPlatform, openIn, alwaysNewTab, runtimeMode, currentSource } = input\n\n // Priority #1: embed-mode trumps every other branch.\n if (runtimeMode === 'embed') return true\n if (openIn === 'new-tab') return true\n if (alwaysNewTab === true) return true\n if (openIn === 'same-tab') return false\n // `currentSource` falsy guard: if empty, fall through to origin check.\n if (targetPlatform !== undefined && targetPlatform !== null && currentSource) {\n return targetPlatform !== currentSource\n }\n if (!href) return false\n return isCrossOriginUrl(href)\n}\n","/**\n * Pure helpers for the new-tab decision + anchor attribute pair used\n * by chat-rendered links.\n *\n * After the navigation unification (ChatCardLoader pre-resolves\n * `chatRef.url` via `resolveSourceRowCTA` + `resolveHrefForRuntime`,\n * and `ChatCardNavWrap` handles every primary click via `onClickCapture`\n * → `handleChatNavClick`), the per-card wrapper only needs\n * `{href, target, rel}` on its inner `<a>` so modifier-click /\n * hover-preview / copy-link work natively.\n *\n * `computeIsNewTab` is the SINGLE source of the new-tab rule across\n * inline cards, source chips, and `NavLinkAnchorViaRuntime`. Do NOT\n * inline the `runtime.navigation.mode === 'embed'` short-circuit + the\n * `decideNewTab` fallback chain anywhere else.\n *\n * Rules:\n * - `embed` mode → ALWAYS new-tab. The chat lives on the embedder\n * origin; content lives on the hub. Same-tab nav would leave the\n * embedder entirely.\n * - `host` mode → defer to the runtime's `decideNewTab` callback\n * (cross-platform → new-tab) or the lib's default.\n */\n\nimport type { ChatRuntime } from '../../../contexts/chat-runtime-context'\nimport { decideNewTab as libDecideNewTab } from './decide-new-tab'\n\nexport function computeIsNewTab(\n runtime: ChatRuntime,\n href: string | null | undefined,\n targetPlatform: string | null,\n): boolean {\n if (!href) return false\n if (runtime.navigation.mode === 'embed') return true\n return (\n runtime.navigation.decideNewTab?.({ href, targetPlatform }) ??\n libDecideNewTab({\n href,\n targetPlatform,\n currentSource: runtime.source ?? '',\n })\n )\n}\n\n/**\n * Returns the `target` + `rel` attribute pair for an inner `<a>` based\n * on the new-tab decision. Spread directly into JSX:\n * <a href={href} {...newTabAnchorAttrs(isNewTab)} />\n *\n * Single helper so chat anchors stay consistent — new-tab links always\n * pair `_blank` with `noopener noreferrer` (defense against window.opener\n * tab-nabbing) and same-tab links don't render either attribute.\n */\nexport function newTabAnchorAttrs(isNewTab: boolean): {\n target?: '_blank'\n rel?: 'noopener noreferrer'\n} {\n return isNewTab\n ? { target: '_blank' as const, rel: 'noopener noreferrer' as const }\n : {}\n}\n\n/**\n * Combine `href` + new-tab attrs into a card's `anchorProps` slot.\n * Returns `undefined` (not a falsy object) when there's no URL, so card\n * components can branch on `anchorProps != null` to render either a\n * clickable `<a>` or a static `<span>`.\n *\n * Replaces the repeated inline\n * `chatRef.url ? { href: chatRef.url, ...newTabAnchorAttrs(isNewTab) } : undefined`\n * pattern across the 7 dispatcher wrappers.\n */\nexport function buildAnchorProps(\n href: string | null | undefined,\n isNewTab: boolean,\n): { href: string; target?: '_blank'; rel?: 'noopener noreferrer' } | undefined {\n return href ? { href, ...newTabAnchorAttrs(isNewTab) } : undefined\n}\n","'use client'\n\n/**\n * Embed-mode navigation resolver + shared click-handling primitives.\n *\n * Used by the lib chat panel's chip + card click handlers to force\n * new-tab + absolutize against the embedder-supplied content origin.\n *\n * Refactor from hub origin (D5): inferTargetPlatformFromHref +\n * getPlatformDomain dropped. Caller threads `targetPlatform` directly.\n * Relative URLs without a defaultContentOrigin emit a dev warning\n * and resolve against window.location.origin (likely wrong in embed\n * mode; embedder should set defaultContentOrigin).\n */\n\nimport type { ChatRuntime } from '../../../contexts/chat-runtime-context'\n\n/** Arguments accepted by `window.open` for a new tab — keep `noopener`\n * + `noreferrer` to prevent the opened page from accessing\n * window.opener (reverse-tabnabbing class). */\nexport const NEW_TAB_FEATURES = 'noopener,noreferrer'\n\n/** Modifier-click predicate — matches the existing useNavLink branch\n * (cmd/ctrl/shift/alt OR non-primary mouse button). */\nexport function isModifierClick(e: {\n button?: number\n metaKey?: boolean\n ctrlKey?: boolean\n shiftKey?: boolean\n altKey?: boolean\n}): boolean {\n return (\n e.button !== 0 ||\n !!e.metaKey ||\n !!e.ctrlKey ||\n !!e.shiftKey ||\n !!e.altKey\n )\n}\n\n/** Strip the origin from a same-origin absolute URL, returning a relative\n * path the host's router will treat as in-app navigation. Pass-through\n * for already-relative URLs and for cross-origin URLs. */\nexport function stripSameOriginToPath(href: string): string {\n if (!href.startsWith('http')) return href\n try {\n const u = new URL(href)\n return u.pathname + u.search + u.hash\n } catch {\n return href\n }\n}\n\nexport interface ExternalNavResolution {\n /** Absolutized href — relative paths are joined with\n * `runtime.navigation.defaultContentOrigin` (embed contract). */\n href: string\n /** Synchronous opener. MUST be invoked inline from a user gesture for\n * popup-blocker compatibility (Safari/Firefox). Honors\n * runtime.navigation.openExternal override, else falls back to\n * window.open(href, '_blank', 'noopener,noreferrer'). */\n open: () => void\n}\n\n/**\n * Pre-resolve an href against the embed-mode `defaultContentOrigin`.\n *\n * In `embed` mode every chat-rendered anchor MUST point at an absolute\n * URL on the hub origin (not the embedder origin). Without\n * pre-resolution, relative paths like `/knowledge-base/foo.md` render\n * as `<a href=\"/knowledge-base/...\">` — the click handler intercepts\n * primary-button clicks correctly, but modifier-click (cmd/ctrl/middle/\n * right) bypasses the handler and opens `<embedder-origin>/...` → 404.\n * Pre-resolution at render time makes every browser-native navigation\n * path (hover preview, status bar, copy-link, new-tab via modifier)\n * land on the hub correctly.\n *\n * In `host` mode (the hub itself) the href is returned unchanged —\n * relative paths resolve naturally against the hub's own origin.\n *\n * Absolute URLs and protocol-relative URLs are returned unchanged in\n * both modes.\n */\nexport function resolveHrefForRuntime(\n href: string,\n runtime: ChatRuntime,\n): string {\n if (runtime.navigation.mode !== 'embed') return href\n if (href.startsWith('http')) return href\n if (href.startsWith('//')) return href\n const origin = runtime.navigation.defaultContentOrigin\n if (!origin) return href\n return origin.replace(/\\/+$/, '') + (href.startsWith('/') ? href : '/' + href)\n}\n\n/**\n * Resolve external navigation in embed mode.\n *\n * Browser-only: called from React event handlers (onClick, imperative\n * click handlers) after mount. No SSR-reachable paths.\n */\nexport function resolveExternalNavigation(args: {\n href: string\n targetPlatform: string | null | undefined\n runtime: ChatRuntime\n}): ExternalNavResolution {\n let abs = args.href\n\n // Defense: protocol-relative URLs (`//evil.com/x`) are NOT same-origin —\n // browsers treat them as cross-origin absolute. Without this guard,\n // a malicious href like `//evil.com/x` would skip the `startsWith('http')`\n // check and reach the origin-concat branch below, producing a malformed\n // URL that some browsers normalize back to `https://evil.com/x`\n // (open-redirect class).\n if (abs.startsWith('//')) abs = window.location.protocol + abs\n\n if (!abs.startsWith('http')) {\n if (args.runtime.navigation.defaultContentOrigin) {\n // Strip trailing slashes via a loop, not `/\\/+$/` — CodeQL flags\n // the greedy `+` as polynomial-redos even with the `$` anchor.\n // The loop is unambiguously O(n) in slash count.\n let origin = args.runtime.navigation.defaultContentOrigin\n while (origin.endsWith('/')) origin = origin.slice(0, -1)\n abs = origin + (abs.startsWith('/') ? abs : '/' + abs)\n } else if (\n args.runtime.navigation.mode === 'embed' &&\n process.env.NODE_ENV !== 'production'\n ) {\n console.warn(\n '[chat-nav-resolution] relative href in embed mode with no ' +\n 'defaultContentOrigin set on runtime.navigation — link will resolve ' +\n 'against the embedder origin.',\n { href: abs, targetPlatform: args.targetPlatform },\n )\n }\n }\n\n const open = () => {\n // Explicit if/else (NOT `??`) — `??` only short-circuits on\n // null/undefined LHS, but `openExternal?.(href)` returns void on a\n // successful call, which `??` treats as undefined and re-fires\n // window.open — double-open bug.\n if (args.runtime.navigation.openExternal) {\n args.runtime.navigation.openExternal(abs)\n } else {\n window.open(abs, '_blank', NEW_TAB_FEATURES)\n }\n }\n\n return { href: abs, open }\n}\n","import Link from '../../../embed-shims/next-link'\nimport type { EntityAuthor } from '../../../types/entity-author'\nimport { SquareAvatar } from '../../ui/square-avatar'\nimport { formatDate, nameInitials } from '../../../utils/format'\n\n/**\n * Author + publication metadata, rendered as the SAME 2-or-N-cell grid pattern\n * the release detail page uses.\n *\n * Each cell sits inside a single bordered/rounded container, separated by\n * `border-r` dividers — byte-identical visual contract to the release page.\n *\n * Cell taxonomy:\n * - `<EntityMetadataValueCell>` — top: large value (`text-h4` uppercase),\n * bottom: small label (`DM_Sans` 14px secondary). Used for Type / Status\n * / Date.\n * - `<EntityMetadataAuthorCell>` — `<SquareAvatar>` + name + label. Used\n * for the Author column.\n *\n * `<EntityAuthorCard>` is the convenience composer for the common\n * \"Published + Author\" pair. Pass `publishedAt` to get both cells; omit it\n * to get just the Author card (single-cell mode used by surfaces with no\n * separate publication-date concept).\n *\n * Renders null when `author` is null/empty unless `renderEmptyAuthor` is set.\n */\n/**\n * Documented empty-author placeholder for CHAT CARD surfaces (\"—\" name,\n * \"Unknown\" role). The release detail page deliberately keeps its legacy\n * empty rendering instead (\"Unknown Author\"/\"Author\" via a null-field stub\n * — see release-detail-page.tsx) — two documented empty styles, one cell.\n */\nexport const EMPTY_AUTHOR_PLACEHOLDER = {\n full_name: '—',\n avatar_url: null,\n job_title: 'Unknown',\n} as const\n\nexport interface EntityAuthorCardProps {\n author: EntityAuthor | null | undefined\n /** Role label rendered under the name. Defaults to \"Author\". Override\n * to e.g. \"Presenter\" / \"Contributor\" if semantics differ. */\n roleLabel?: string\n /** Link target for the author name (e.g. the public author page). The\n * HOST computes it (route availability is host knowledge) — absent ⇒\n * plain text, exactly the prior render. */\n authorHref?: string\n /** Optional publication date. When provided, the component renders as a\n * 2-cell grid (Published | Author). When omitted, only the Author cell\n * renders inside the same bordered container. */\n publishedAt?: string | Date | null\n /** Label for the Published cell. Defaults to \"Published\". */\n publishedLabel?: string\n /**\n * Extra value cells inserted into the metadata grid BEFORE the\n * Published cell. Each item renders as an `<EntityMetadataValueCell>`\n * with a large `text-h4` value and a small `DM_Sans` 14px label.\n *\n * Use for entity-specific labels (e.g. onboarding-guide section/step,\n * webinar host, customer-interview customer). The grid auto-sizes via\n * `grid-cols-N`.\n */\n extraCells?: Array<{ value: string; label: string; uppercase?: boolean }>\n /** When true, render the author cell even when `author?.full_name` is\n * missing — using the `EMPTY_AUTHOR_PLACEHOLDER` shape above. Used by\n * catalog grids that must keep a fixed shape so skeleton alignment\n * holds. Defaults to false. */\n renderEmptyAuthor?: boolean\n className?: string\n}\n\n\n/**\n * Single value cell — top: large `text-h4` value (uppercase), bottom: small\n * `DM_Sans` 14px secondary label.\n */\nexport function EntityMetadataValueCell({\n value,\n label,\n className,\n uppercase = true,\n}: {\n value: string\n label: string\n className?: string\n uppercase?: boolean\n}) {\n return (\n <div className={`bg-ods-card p-4 flex flex-col gap-3 ${className ?? ''}`}>\n <div className=\"flex flex-col gap-0\">\n <p className=\"text-h4 text-ods-text-primary\">\n {uppercase ? value.toLocaleUpperCase() : value}\n </p>\n <p className=\"font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary\">\n {label}\n </p>\n </div>\n </div>\n )\n}\n\n/**\n * Author cell — `<SquareAvatar>` + name + role label. The containing border /\n * divider is the caller's responsibility — this is just the cell content.\n */\nexport function EntityMetadataAuthorCell({\n author,\n roleLabel = 'Author',\n authorHref,\n className,\n}: {\n author: NonNullable<EntityAuthorCardProps['author']>\n roleLabel?: string\n authorHref?: string\n className?: string\n}) {\n // Trimmed real name gates the LINK — a placeholder/unknown label must\n // never render as a clickable author (an authorHref passed alongside an\n // empty name would link \"Unknown Author\").\n const trimmedName = typeof author.full_name === 'string' ? author.full_name.trim() : ''\n const fullName = trimmedName || 'Unknown Author'\n return (\n <div className={`bg-ods-card p-4 flex items-center gap-3 ${className ?? ''}`}>\n <SquareAvatar\n src={author.avatar_url || ''}\n alt={fullName}\n fallback={nameInitials(fullName, '')}\n size=\"md\"\n variant=\"round\"\n />\n <div className=\"flex flex-col gap-0 flex-1 min-w-0\">\n {/* title = full-name tooltip on truncation (carried over from the\n release page's cell when it adopted this shared one). */}\n <p className=\"text-h3 tracking-[-0.36px] text-ods-text-primary truncate\" title={fullName}>\n {authorHref && trimmedName ? (\n <Link href={authorHref} className=\"hover:text-ods-accent transition-colors\">\n {fullName}\n </Link>\n ) : (\n fullName\n )}\n </p>\n <p className=\"font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary\">\n {author.job_title || roleLabel}\n </p>\n </div>\n </div>\n )\n}\n\nexport function EntityAuthorCard({\n author,\n roleLabel = 'Author',\n authorHref,\n publishedAt,\n publishedLabel = 'Published',\n extraCells,\n renderEmptyAuthor = false,\n className,\n}: EntityAuthorCardProps) {\n const hasAuthor = !!author?.full_name\n if (!hasAuthor && !renderEmptyAuthor) return null\n const effectiveAuthor = hasAuthor ? (author as NonNullable<EntityAuthorCardProps['author']>) : EMPTY_AUTHOR_PLACEHOLDER\n\n // Resolve the date label. `formatDate` returns \"Invalid Date\" for\n // unparseable inputs; collapse that to empty so the cell is hidden, not\n // literal text.\n const formatted = publishedAt ? formatDate(publishedAt as Date | string) : ''\n const dateLabel = formatted === 'Invalid Date' ? '' : formatted\n\n const showDateCell = !!dateLabel\n const extras = extraCells ?? []\n // Total cell count = extras + (optional Published) + Author. Map to\n // an explicit tailwind `md:grid-cols-N` class string so JIT picks it\n // up at build time.\n const totalCells = extras.length + (showDateCell ? 1 : 0) + 1\n const gridColsClass =\n totalCells >= 4 ? 'md:grid-cols-4'\n : totalCells === 3 ? 'md:grid-cols-3'\n : totalCells === 2 ? 'md:grid-cols-2'\n : 'md:grid-cols-1'\n\n // Helper — every cell EXCEPT the last needs the dividers (bottom on\n // mobile, right on desktop). The last cell (Author) closes the grid\n // without a trailing divider so the rounded corner stays clean.\n const dividerClass = 'border-b md:border-b-0 md:border-r border-ods-border'\n\n return (\n <div\n className={`grid grid-cols-1 ${gridColsClass} border border-ods-border rounded-md overflow-hidden w-full ${className ?? ''}`}\n >\n {extras.map((cell, i) => (\n <EntityMetadataValueCell\n key={`${cell.label}-${i}`}\n value={cell.value}\n label={cell.label}\n uppercase={cell.uppercase ?? true}\n className={dividerClass}\n />\n ))}\n {showDateCell && (\n <EntityMetadataValueCell\n value={dateLabel}\n label={publishedLabel}\n uppercase={false}\n className={dividerClass}\n />\n )}\n <EntityMetadataAuthorCell author={effectiveAuthor} roleLabel={roleLabel} authorHref={authorHref} />\n </div>\n )\n}\n","'use client'\n\nimport React from 'react'\n\ninterface BlogImagePlaceholderProps {\n /** Cover-image URL. The hub passes a `useOgPlaceholder(title, category)`\n * result; embedders pass their own pre-resolved URL. When null, the\n * component renders nothing. */\n imageUrl: string | null\n /** Used for the `alt` attribute. */\n title: string\n className?: string\n}\n\n/**\n * Pure presentation wrapper for a cover-image / OG-placeholder fallback.\n *\n * Outer must be inline-content-model so this placeholder is HTML-valid\n * when rendered inside a markdown `<p>` (e.g. via the chat shell's\n * compact `BlogCard` fallback). `<span className=\"block\">` keeps the\n * same visual behavior as the prior `<div>` while satisfying the\n * phrasing-content constraint of its parent.\n *\n * If the `imageUrl` itself 404s (cold cache, transient failure), the\n * `onError` handler hides the broken-image icon so the parent's\n * `bg-ods-bg` shows through cleanly. Same recovery pattern every\n * cover-image render path uses.\n */\nexport function BlogImagePlaceholder({\n imageUrl,\n title,\n className = '',\n}: BlogImagePlaceholderProps) {\n if (!imageUrl) return null\n\n return (\n <span className={`relative block w-full h-full overflow-hidden bg-ods-bg ${className}`}>\n {/* eslint-disable-next-line @next/next/no-img-element -- this is a\n dynamically-generated placeholder image with a query string;\n next/image's loader configuration adds nothing here. */}\n <img\n src={imageUrl}\n alt={`Cover image for ${title}`}\n className=\"block w-full h-full object-contain\"\n loading=\"lazy\"\n onError={(e) => {\n (e.currentTarget as HTMLImageElement).style.display = 'none'\n }}\n />\n </span>\n )\n}\n","import React from 'react';\n\n/**\n * `fetchPriorityProp(priority)` — build the right shape for the `<img>` /\n * `<iframe>` fetch-priority hint based on the React major version detected\n * at module load.\n *\n * React 18 only recognizes the lowercase HTML attribute `fetchpriority`\n * and emits a console warning when it sees the camelCase prop. React 19+\n * recognizes the camelCase prop `fetchPriority` (matching the DOM's IDL\n * property) and warns when it sees the lowercase form.\n *\n * Spread the returned object into the element so the rendered DOM ends\n * up with the canonical lowercase `fetchpriority` attribute either way,\n * with zero console warnings on both React versions.\n *\n * Accepts either a boolean (for the simple \"high vs low\" hint at LCP\n * candidates) or an explicit HTML `fetchpriority` value (`'high' | 'low'\n * | 'auto'`) for call sites that always want one specific value.\n */\nconst REACT_MAJOR = parseInt((React.version || '0').split('.')[0], 10);\nconst USE_CAMEL_CASE_FETCH_PRIORITY = REACT_MAJOR >= 19;\n\nexport type FetchPriorityValue = 'high' | 'low' | 'auto';\n\nexport function fetchPriorityProp(\n priority: boolean | FetchPriorityValue | undefined,\n): Record<string, string> {\n // `undefined` → `'auto'` so callers can spread `{...fetchPriorityProp(maybeBool)}`\n // without a separate null-check at every callsite. `'auto'` is the\n // DOM default and emits no warning on either React major.\n const value: FetchPriorityValue =\n priority === undefined\n ? 'auto'\n : typeof priority === 'boolean'\n ? priority ? 'high' : 'low'\n : priority;\n return USE_CAMEL_CASE_FETCH_PRIORITY\n ? { fetchPriority: value }\n : { fetchpriority: value };\n}\n","\"use client\";\n\n/**\n * <Video> — single source of truth for every public video surface\n * across every Flamingo platform consumer of this lib.\n *\n * One component, three sources, three layouts. Replaces and deletes\n * the previous lib primitives:\n *\n * - `<VideoPlayer>` (react-player wrapper, ~900 LOC custom controls)\n * - `<YouTubeEmbed>` (separate lite-youtube facade)\n *\n * Routing (`kind` discriminant, default `'auto'`):\n *\n * kind=\"youtube\" → inline lite-youtube facade (poster + click→iframe)\n * kind=\"file\" → <MuxPlayer> (HLS + MP4 + Mux Data + CMCD all in one)\n * kind=\"auto\" → strict URL parse:\n * bare 11-char id → youtube\n * YouTube hostname → youtube\n * anything else → file\n *\n * `<MuxPlayer>` handles both `.m3u8` (HLS via hls.js, native on Safari)\n * AND plain `.mp4` (uses the underlying `<video>` element). One component,\n * both paths — no internal \"HLS vs MP4\" branch needed. Captions are\n * rendered as native `<track>` children when `captionsUrl` is passed.\n *\n * Layouts:\n * layout=\"centered\" → max-w-3xl centered wrapper. Detail-page surface.\n * layout=\"fill\" → absolute inset-0 w-full h-full. Carousel slides.\n * layout=\"native\" → intrinsic aspect ratio. Bites grid, blog cards.\n */\n\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport MuxPlayer from '@mux/mux-player-react';\nimport { PlayIcon } from '../icons-v2-generated/media-playback/play-icon';\nimport { fetchPriorityProp } from '../../utils/fetch-priority';\n\n// =============================================================================\n// Suppress Google Cast SDK loading (CSP-friendly)\n// =============================================================================\n//\n// Why: MuxPlayer is built on `media-chrome`, which is built on `castable-video`.\n// `castable-video`'s `loadCastFramework()` UNCONDITIONALLY injects a\n// `<script src=\"https://www.gstatic.com/cv/.../cast_sender.js?loadCastFramework=1\">`\n// whenever Chrome is detected. That script then internally loads further\n// scripts from `http://www.gstatic.com/eureka/clank/...` and\n// `http://www.gstatic.com/cast/sdk/libs/...` — over **HTTP, not HTTPS** —\n// which can never pass any reasonable CSP. Result: every video render\n// emits 3+ \"Loading the script ... violates CSP\" errors in the browser\n// console.\n//\n// The loader has a single early-exit: `if (globalThis.chrome?.cast)\n// return;`. So we make `chrome.cast` truthy (with `isAvailable: false`\n// so existing apps that consult that flag still see \"no cast\") BEFORE\n// MuxPlayer's `castable-mixin` runs. Module-level code in this file\n// executes during the import that brings `MuxPlayer` into the bundle —\n// safely before any instance mounts.\n//\n// We don't use Chromecast anywhere in the hub. If we ever do, replace\n// this block with explicit cast initialization at the call site.\nif (typeof window !== 'undefined') {\n const w = window as unknown as { chrome?: { cast?: unknown } };\n if (!w.chrome?.cast) {\n w.chrome = { ...(w.chrome ?? {}), cast: { isAvailable: false } };\n }\n}\n\n// =============================================================================\n// Suppress benign \"Media Chrome: No style sheet found ...\" warning.\n// =============================================================================\n//\n// Why: MuxPlayer's UI is built on `media-chrome`, which uses custom\n// elements with shadow-DOM `<style>` tags. `media-chrome`'s internal\n// `insertCSSRule()` helper queries `style.sheet` during the element's\n// `connectedCallback`. When the player mounts inside a React portal\n// (the chat panel renders `<Video>` inside Radix's `<Dialog.Portal>`),\n// the element is moved across DOM trees BEFORE the browser hydrates\n// the shadow `<style>`'s `.sheet`. The helper then logs:\n// \"Media Chrome: No style sheet found on style tag of #shadow-root (open)\"\n// and returns a no-op style shim. The warning is purely cosmetic —\n// playback, controls, and theming all render correctly because the\n// shadow stylesheet does hydrate on the next paint.\n//\n// Until upstream `media-chrome` either retries on the next microtask\n// or downgrades the warning (tracked in their issue tracker), patch\n// `console.warn` once at module load to drop only THIS exact string.\n// All other `console.warn` calls pass through unchanged.\n//\n// Why patch instead of opting out: `@mux/mux-player-react` exposes no\n// prop to disable internal style queries, importing CSS explicitly\n// doesn't seed the shadow-root stylesheets (they're per-element), and\n// the warning fires before any consumer can intercept the element.\nif (typeof window !== 'undefined' && typeof console !== 'undefined') {\n const w = window as unknown as { __MEDIA_CHROME_WARN_PATCHED__?: boolean };\n if (!w.__MEDIA_CHROME_WARN_PATCHED__) {\n w.__MEDIA_CHROME_WARN_PATCHED__ = true;\n const MEDIA_CHROME_NO_STYLESHEET_PREFIX = 'Media Chrome: No style sheet found on style tag of';\n const originalWarn = console.warn.bind(console);\n console.warn = (...args: unknown[]): void => {\n if (typeof args[0] === 'string' && args[0].startsWith(MEDIA_CHROME_NO_STYLESHEET_PREFIX)) {\n return;\n }\n originalWarn(...args);\n };\n }\n}\n\n// =============================================================================\n// URL classifiers (private — `<Video>` is the only consumer)\n// =============================================================================\n\nconst YT_HOSTS = new Set([\n 'youtube.com', 'www.youtube.com', 'm.youtube.com',\n 'youtu.be',\n 'youtube-nocookie.com', 'www.youtube-nocookie.com',\n]);\n\n/** Strict YouTube URL detection — parses the URL and checks the hostname. */\nfunction isYouTubeUrl(url: string): boolean {\n try {\n return YT_HOSTS.has(new URL(url, 'http://placeholder.local').hostname.toLowerCase());\n } catch {\n return false;\n }\n}\n\n// `youtube.com/(embed|v|shorts)/<id>` — anchored, no `.*`, ReDoS-safe.\nconst YT_PATH_RE = /^\\/(?:embed|v|shorts)\\/([^/]+)\\/?$/;\n\n// Bare 11-char YouTube id — base62 alphabet `[A-Za-z0-9_-]`.\n// Anchored, no `.*`, ReDoS-safe; rejects anything that contains `/` or `:`.\nconst BARE_YT_ID_RE = /^[A-Za-z0-9_-]{11}$/;\n\n/**\n * Extract the YouTube video id from any common URL shape OR from a\n * bare 11-char id (carousels and some admin shapes pass that form\n * directly).\n *\n * Uses strict `URL` parsing + an anchored pathname regex — NOT the\n * legacy `.*v=` pattern that CodeQL flagged for polynomial-time\n * backtracking on adversarial input like `youtube.com/watch?`\n * repeated N times. Bare-id fallback uses an anchored character-class\n * regex so it can never ReDoS either.\n *\n * Exported so admin tooling and carousel thumbnail logic can validate\n * a URL without rendering the full `<Video>` component.\n */\nexport function extractYouTubeId(url: string): string | null {\n if (!url) return null;\n // Bare-id form first — `new URL('dQw4w9WgXcQ')` throws, so this MUST\n // run before the URL parse. The 11-char anchored regex rejects URLs\n // (which always contain `:` or `/`).\n if (BARE_YT_ID_RE.test(url)) return url;\n let u: URL;\n // Match `isYouTubeUrl`'s relative-safe parsing — a base URL with a\n // placeholder origin lets us handle protocol-relative inputs (`//youtube.com/...`)\n // and protocol-less inputs (`youtube.com/...`) without throwing.\n try { u = new URL(url, 'http://placeholder.local'); } catch { return null; }\n if (!YT_HOSTS.has(u.hostname.toLowerCase())) return null;\n // `youtu.be/<id>` — id is the first non-empty path segment.\n if (u.hostname.toLowerCase().endsWith('youtu.be')) {\n return u.pathname.split('/').filter(Boolean)[0] ?? null;\n }\n // `youtube.com/watch?v=<id>` — query parameter.\n const v = u.searchParams.get('v');\n if (v) return v;\n // `youtube.com/(embed|v|shorts)/<id>` — anchored pathname match.\n const m = u.pathname.match(YT_PATH_RE);\n return m ? m[1] : null;\n}\n\n// =============================================================================\n// Props\n// =============================================================================\n\nexport type VideoLayout = 'centered' | 'fill' | 'native';\n\ninterface VideoCommonProps {\n /** Layout wrapper. Detail pages pass `\"centered\"`. Default `\"native\"`. */\n layout?: VideoLayout;\n /** Poster / thumbnail. */\n poster?: string | null;\n /** Mute by default — for autoplay carousels. */\n muted?: boolean;\n /** LCP hint — YouTube facade poster gets `fetchpriority=\"high\"`. */\n priority?: boolean;\n /** Tailwind classes applied to the underlying player root. */\n className?: string;\n /** Accessible label (used as YT facade title; ignored for file branch). */\n title?: string;\n /**\n * YouTube-only: hide YT player chrome (controls, info, fullscreen, related\n * videos, keyboard shortcuts). Used for marketing/landing-page embeds that\n * want a minimal look. No-op for file (MP4/HLS) branches.\n */\n minimalControls?: boolean;\n}\n\ninterface VideoFileProps extends VideoCommonProps {\n kind: 'file';\n url: string;\n /**\n * SRT raw content. Deprecated: pass `captionsUrl` (VTT) instead.\n * Native `<track>` requires a URL; raw SRT can't be rendered without\n * a custom overlay. Setting this without `captionsUrl` is a no-op\n * with a dev warning.\n */\n srtContent?: string | null;\n /** HTTPS URL to a VTT captions file. Rendered as a native `<track>`. */\n captionsUrl?: string | null;\n}\n\ninterface VideoYouTubeProps extends VideoCommonProps {\n kind: 'youtube';\n /** Either a full YT URL or just the video id. */\n url: string;\n}\n\ninterface VideoAutoProps extends VideoCommonProps {\n kind?: 'auto';\n url: string;\n srtContent?: string | null;\n captionsUrl?: string | null;\n}\n\nexport type VideoProps = VideoFileProps | VideoYouTubeProps | VideoAutoProps;\n\n// =============================================================================\n// Component\n// =============================================================================\n\nexport function Video(props: VideoProps): React.ReactElement | null {\n const url = props.url;\n if (!url) return null;\n\n const effectiveKind = resolveKind(props, url);\n const layout = props.layout ?? 'native';\n\n const inner =\n effectiveKind === 'youtube' ? (\n <YouTubeFacade\n url={url}\n title={props.title}\n priority={props.priority}\n className={props.className}\n minimalControls={props.minimalControls}\n />\n ) : (\n <FilePlayer\n url={url}\n poster={props.poster}\n muted={props.muted}\n srtContent={'srtContent' in props ? props.srtContent : null}\n captionsUrl={'captionsUrl' in props ? props.captionsUrl : null}\n className={props.className}\n />\n );\n\n return wrapWithLayout(inner, layout);\n}\n\n// =============================================================================\n// Internals — never imported by call sites; `<Video>` is the only entry.\n// =============================================================================\n\n/**\n * Resolve the rendering branch. `'auto'` (or no `kind`) inspects the URL:\n * YouTube host (or a bare 11-char video id) → 'youtube', anything else →\n * 'file' (HLS / MP4 both handled by MuxPlayer). Type-safe — no `as` casts.\n */\nfunction resolveKind(props: VideoProps, url: string): 'youtube' | 'file' {\n if ('kind' in props) {\n if (props.kind === 'youtube') return 'youtube';\n if (props.kind === 'file') return 'file';\n // kind === 'auto' falls through to URL-based detection\n }\n // Bare 11-char YouTube id — use the same anchored regex as\n // `extractYouTubeId` so both code paths agree on which strings are\n // bare ids vs. URLs (avoids the regression where `videos/clip`\n // length-11 strings were mis-routed to YouTube).\n if (BARE_YT_ID_RE.test(url)) return 'youtube';\n return isYouTubeUrl(url) ? 'youtube' : 'file';\n}\n\nfunction wrapWithLayout(\n inner: React.ReactElement,\n layout: VideoLayout,\n): React.ReactElement {\n switch (layout) {\n case 'centered':\n // `aspect-video` (16:9) reserves the box from first paint so MuxPlayer\n // doesn't flicker tiny→full while video metadata loads. Both branches\n // are sized to fill 100% of this container (MuxPlayer via `style`,\n // YouTube facade via internal `paddingBottom: 56.25%` which compounds\n // harmlessly inside an already-16:9 box). `rounded-lg overflow-hidden\n // border border-ods-border` is on the wrapper (not the inner) so BOTH\n // branches end up with the same rounded card look — YouTube's facade\n // also paints its own internal rounded-lg on the button + iframe,\n // matching the outer radius (so the corners stay sharp through the\n // poster → iframe transition); MuxPlayer renders flat and inherits\n // the wrapper's rounded corners via `overflow-hidden` clipping.\n return (\n <div className=\"flex justify-center w-full\">\n <div className=\"w-full max-w-3xl aspect-video rounded-lg overflow-hidden border border-ods-border\">{inner}</div>\n </div>\n );\n case 'fill':\n return <div className=\"absolute inset-0 w-full h-full\">{inner}</div>;\n case 'native':\n default:\n // `native` callers (LazyBite in `<VideoBitesDisplay>`, blog cards) are\n // expected to provide their own aspect-ratio container so the layout\n // primitive doesn't override portrait/square/landscape bites with 16:9.\n return inner;\n }\n}\n\n// -----------------------------------------------------------------------------\n// File branch — MuxPlayer (handles both .m3u8 HLS and plain .mp4)\n// -----------------------------------------------------------------------------\n\ninterface FilePlayerProps {\n url: string;\n poster?: string | null;\n muted?: boolean;\n srtContent?: string | null;\n captionsUrl?: string | null;\n className?: string;\n}\n\nfunction FilePlayer({\n url,\n poster,\n muted,\n srtContent,\n captionsUrl,\n className,\n}: FilePlayerProps): React.ReactElement {\n // Raw SRT text is unusable without a custom overlay — and we just deleted\n // the 900-LOC custom-controls layer that owned that overlay. Consumers\n // pass `captionsUrl` (the API-side VTT conversion) alongside `srtContent`\n // anyway. Warn in dev if the deprecated prop is the only one supplied\n // so a single-prop call site doesn't silently lose captions.\n if (process.env.NODE_ENV !== 'production' && srtContent && !captionsUrl) {\n // eslint-disable-next-line no-console\n console.warn(\n '[Video] srtContent supplied without captionsUrl — captions will not render. ' +\n 'Pass captionsUrl (the VTT URL) instead; raw SRT text overlays are no longer supported.',\n );\n }\n\n return (\n <MuxPlayer\n src={url}\n poster={poster || undefined}\n streamType=\"on-demand\"\n playsInline\n muted={muted}\n preferCmcd=\"header\"\n // MuxPlayer's built-in default is `#fa50b5` (Mux brand pink) — when\n // its `--media-accent-color` resolves to nothing the player falls\n // through to that hardcoded pink. The `var(--ods-accent,\n // var(--color-accent-primary))` chain hits the platform-aware\n // ODS token first, then the semantic accent alias if `--ods-accent`\n // is ever undefined on a `data-app-type` we haven't themed yet.\n // NEVER let Mux pink leak onto a non-Flamingo platform.\n accentColor=\"var(--ods-accent, var(--color-accent-primary))\"\n className={className}\n // Fill the wrapping aspect-ratio container instead of MuxPlayer's\n // intrinsic size. Without this, MuxPlayer renders at its default\n // dimensions before video metadata loads, then grows to its\n // metadata-derived size — that's the \"starts super small and\n // flickers and grows\" CLS we're killing. With `aspect-video` on\n // the centered wrapper and `width/height: 100%` here, the box is\n // 16:9 from first paint and stays put.\n style={{ width: '100%', height: '100%' }}\n >\n {captionsUrl ? (\n <track\n kind=\"captions\"\n src={captionsUrl}\n srcLang=\"en\"\n label=\"English\"\n default\n />\n ) : null}\n </MuxPlayer>\n );\n}\n\n// -----------------------------------------------------------------------------\n// YouTube facade — inlined lite-youtube-embed pattern\n// -----------------------------------------------------------------------------\n\ninterface YouTubeFacadeProps {\n url: string;\n title?: string;\n priority?: boolean;\n className?: string;\n minimalControls?: boolean;\n}\n\nfunction YouTubeFacade({\n url,\n title = 'YouTube Video',\n priority,\n className,\n minimalControls,\n}: YouTubeFacadeProps): React.ReactElement | null {\n // `extractYouTubeId` handles both bare 11-char ids AND full URLs in a\n // single call site, so the resolution logic lives in exactly one place.\n const videoId = extractYouTubeId(url);\n if (!videoId) return null;\n\n return <YouTubeFacadeInner videoId={videoId} title={title} priority={priority} className={className} minimalControls={minimalControls} />;\n}\n\ninterface YouTubeFacadeInnerProps {\n videoId: string;\n title: string;\n priority?: boolean;\n className?: string;\n minimalControls?: boolean;\n}\n\nconst YT_NOCOOKIE_ORIGIN = 'https://www.youtube-nocookie.com';\n\n// YouTube IFrame Player API state codes — documented integers.\n// https://developers.google.com/youtube/iframe_api_reference#Playback_status\nconst YT_STATE_ENDED = 0;\nconst YT_STATE_PLAYING = 1;\n\n// Sub-second delay before we blur the iframe after PLAYING. Zero would\n// cancel YouTube's mount-time \"controls visible\" intro flash entirely\n// (jarring); ~1s lets the user briefly see playback started, then we\n// kick YouTube's internal idle timer by removing DOM focus from the\n// iframe. Net result: controls fade ~1s after playback begins,\n// matching the user-locked target.\nconst YT_PLAYING_BLUR_DELAY_MS = 1000;\n\ninterface YouTubeInfoDeliveryMessage {\n event?: string;\n info?: { playerState?: number };\n}\n\nfunction YouTubeFacadeInner({\n videoId,\n title,\n priority,\n className,\n minimalControls,\n}: YouTubeFacadeInnerProps): React.ReactElement {\n const [activated, setActivated] = useState(false);\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n\n // Embed URL + poster URLs only change when `videoId` or `minimalControls`\n // do — memoize so we don't rebuild URLSearchParams on every render.\n //\n // `enablejsapi=1` opens the postMessage state channel we subscribe to\n // below — without it, YouTube ignores `event:listening` messages and\n // we can't detect PLAYING / ENDED to drive the auto-hide accelerator.\n //\n // `origin=<parent-page-origin>` is REQUIRED when `enablejsapi=1` is set.\n // Without it, the YouTube widget inside the iframe defaults its\n // `postMessage` targetOrigin to its OWN origin (`youtube-nocookie.com`)\n // when emitting state-change events back to the parent. The browser\n // then drops every message and logs:\n // \"Failed to execute 'postMessage' on 'DOMWindow': The target origin\n // provided ('https://www.youtube-nocookie.com') does not match the\n // recipient window's origin ('https://www.<our-site>')\"\n // Setting `origin` tells the widget the real parent host so it sends\n // with the matching targetOrigin. Documented in YouTube's IFrame Player\n // API reference (developers.google.com/youtube/iframe_api_reference).\n // SSR-safe: the URL is rebuilt client-side in the same useMemo on\n // hydration when `window` becomes available; the first SSR pass emits\n // the URL without `origin` (no jsapi traffic yet — no iframe mounted).\n const { embedUrl, posterJpg, posterWebp } = useMemo(() => {\n const params = new URLSearchParams({\n autoplay: '1',\n rel: '0',\n modestbranding: '1',\n playsinline: '1',\n enablejsapi: '1',\n });\n if (typeof window !== 'undefined') {\n params.set('origin', window.location.origin);\n }\n if (minimalControls) {\n params.set('controls', '0');\n params.set('fs', '0');\n params.set('iv_load_policy', '3');\n params.set('cc_load_policy', '0');\n params.set('disablekb', '1');\n }\n return {\n embedUrl: `${YT_NOCOOKIE_ORIGIN}/embed/${videoId}?${params.toString()}`,\n posterJpg: `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`,\n posterWebp: `https://i.ytimg.com/vi_webp/${videoId}/mqdefault.webp`,\n };\n }, [videoId, minimalControls]);\n\n // ---------------------------------------------------------------------------\n // YouTube control-fade accelerator (user-locked target: ~1s).\n //\n // YouTube's native idle timer for the bottom control bar is 5–10s when\n // the iframe holds DOM focus. The IFrame Player API has no public\n // `hideControls` command (full `func` list verified — none expose\n // visibility). The one legal lever from outside the iframe is\n // `iframe.blur()`, which kicks YouTube into its post-focus idle path\n // (~2s minimum).\n //\n // Subscribe to YouTube's state channel via the documented lite-mode\n // postMessage handshake — no full `iframe_api.js` library needed:\n // <https://developers.google.com/youtube/iframe_api_reference>.\n //\n // - PLAYING (1) arrives once autoplay kicks in. Wait ~1s (so the user\n // briefly sees that the player started), then blur the iframe so\n // YouTube's idle timer fires immediately.\n //\n // - ENDED (0) → tear down the iframe. Playback is already over so\n // there's nothing to interrupt, and removing the iframe kills the\n // residual \"More videos\" suggestion grid YouTube leaves on screen.\n //\n // PAUSED (2) is deliberately unhandled — the user paused on purpose,\n // leave YouTube's UI alone so they can resume.\n //\n // Playback is NEVER stopped by anything in this facade. Outside-click,\n // Escape, tab-switch — all no-ops. The only state flip is on natural\n // end-of-video (ENDED).\n // ---------------------------------------------------------------------------\n useEffect(() => {\n if (!activated) return;\n const iframe = iframeRef.current;\n if (!iframe) return;\n\n function subscribe() {\n iframe?.contentWindow?.postMessage(\n '{\"event\":\"listening\"}',\n YT_NOCOOKIE_ORIGIN,\n );\n }\n\n iframe.addEventListener('load', subscribe);\n subscribe();\n\n let blurTimer: ReturnType<typeof setTimeout> | null = null;\n\n function handleMessage(event: MessageEvent) {\n if (event.origin !== YT_NOCOOKIE_ORIGIN) return;\n if (typeof event.data !== 'string') return;\n let payload: YouTubeInfoDeliveryMessage | null = null;\n try {\n payload = JSON.parse(event.data);\n } catch {\n return;\n }\n if (!payload || payload.event !== 'infoDelivery') return;\n const state = payload.info?.playerState;\n if (typeof state !== 'number') return;\n\n if (state === YT_STATE_PLAYING) {\n if (blurTimer !== null) return;\n blurTimer = setTimeout(() => {\n blurTimer = null;\n iframeRef.current?.blur();\n }, YT_PLAYING_BLUR_DELAY_MS);\n return;\n }\n if (state === YT_STATE_ENDED) {\n setActivated(false);\n }\n }\n\n window.addEventListener('message', handleMessage);\n return () => {\n iframe.removeEventListener('load', subscribe);\n window.removeEventListener('message', handleMessage);\n if (blurTimer !== null) clearTimeout(blurTimer);\n };\n }, [activated]);\n\n const wrapperClass = `relative w-full ${className ?? ''}`;\n const wrapperStyle = { paddingBottom: '56.25%' as const };\n\n if (activated) {\n return (\n <div className={wrapperClass} style={wrapperStyle}>\n <iframe\n ref={iframeRef}\n src={embedUrl}\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n title={title}\n className=\"absolute inset-0 w-full h-full border-0 rounded-lg\"\n />\n </div>\n );\n }\n\n return (\n <div className={wrapperClass} style={wrapperStyle}>\n <button\n type=\"button\"\n aria-label={`Play: ${title}`}\n onClick={() => setActivated(true)}\n className=\"group absolute inset-0 p-0 m-0 border border-ods-border rounded-lg overflow-hidden bg-ods-card cursor-pointer\"\n >\n <picture>\n <source type=\"image/webp\" srcSet={posterWebp} />\n <img\n src={posterJpg}\n alt={title}\n loading=\"lazy\"\n // React 18 wants lowercase (`fetchpriority` DOM attribute);\n // React 19 wants camelCase (`fetchPriority` prop). Detect at\n // module load and spread the right shape so both runtimes\n // render the DOM attribute cleanly with no console warnings.\n {...fetchPriorityProp(priority)}\n decoding={priority ? 'sync' : 'async'}\n className=\"absolute inset-0 w-full h-full object-cover\"\n />\n </picture>\n <div className=\"absolute inset-0 flex items-center justify-center bg-ods-bg-inverse bg-opacity-20 transition-opacity duration-200 group-hover:bg-opacity-30\">\n <span className=\"flex items-center justify-center w-16 h-16 rounded-full bg-ods-accent text-ods-text-on-accent shadow-lg transition-transform duration-200 group-hover:scale-110\">\n <PlayIcon size={24} color=\"currentColor\" className=\"ml-1\" />\n </span>\n </div>\n </button>\n </div>\n );\n}\n","import type { ReactNode } from 'react'\n\n/**\n * THE page-title style — the ODS `text-h1` token (`--font-size-h1-title` =\n * 40 / 48 / 56px, Azeret Mono semibold), which already owns the responsive\n * letter-spacing (-0.02em). Mirrors the global `h1 { @apply text-h1 }` base, so\n * every page heading is the SAME size across the lib and every consuming app.\n *\n * Single source of truth — do NOT hardcode a px size or re-assert the token's\n * tracking; render `<PageHeading>` (or, for the rare non-h1 caller, apply\n * `PAGE_HEADING_CLASS`).\n */\nexport const PAGE_HEADING_CLASS = 'text-h1 text-ods-text-primary'\n\n/**\n * THE section-heading style — the ODS `text-h2` sub-title token\n * (`--font-size-h2-sub-title` = 24 / 32 / 32px), one step below the page\n * title so a section `<h2>`/`<h3>` is always visually distinguishable from\n * the page's `<h1>`. Same single-source rule as PAGE_HEADING_CLASS: never\n * hardcode a px ramp or re-assert the token's tracking on a section heading.\n */\nexport const SECTION_HEADING_CLASS = 'text-h2 text-ods-text-primary'\n\nconst DESCRIPTION_CLASS =\n \"mt-6 max-w-[640px] font-['DM_Sans'] text-[16px] md:text-[18px] leading-[24px] md:leading-[28px] text-ods-text-secondary\"\n\nexport interface PageHeadingProps {\n /** Heading content — plain text or nodes (e.g. an accent <span>). */\n children: ReactNode\n /**\n * Heading level. Defaults to 'h1' (exactly one per page). Pass 'h2' where the\n * page already renders an <h1> above (e.g. a hero/featured item).\n */\n as?: 'h1' | 'h2'\n /** Optional supporting copy rendered as a <p> beneath the heading. */\n description?: ReactNode\n /** Extra classes merged onto the heading (margins, width, truncate, etc.). */\n className?: string\n /** Extra classes merged onto the description <p>. */\n descriptionClassName?: string\n}\n\n/**\n * Unified page heading. Renders the canonical page-title <h1> (or <h2>) plus an\n * optional description, so every page shares one consistent style instead of\n * duplicating the class string. Layout (PageContainer, margins, surrounding\n * sections) stays with the caller — this owns only the heading + description.\n */\nexport function PageHeading({\n children,\n as: Tag = 'h1',\n description,\n className,\n descriptionClassName,\n}: PageHeadingProps) {\n const headingClass = className ? `${PAGE_HEADING_CLASS} ${className}` : PAGE_HEADING_CLASS\n const descClass = descriptionClassName ? `${DESCRIPTION_CLASS} ${descriptionClassName}` : DESCRIPTION_CLASS\n // `description` is a ReactNode, so `description={cond && '...'}` can pass a\n // boolean `false` — exclude it (and empty string) so we never render an empty\n // <p> that adds phantom vertical gap beneath the heading.\n const hasDescription =\n description != null && description !== '' && typeof description !== 'boolean'\n return (\n <>\n <Tag className={headingClass}>{children}</Tag>\n {hasDescription && <p className={descClass}>{description}</p>}\n </>\n )\n}\n","\"use client\";\n\n/**\n * Aspect-ratio tab + grouping primitives for video grids.\n *\n * Used by `<VideoBitesDisplay>` and by app-level admin editors\n * (VideoBitesEditor, VideoLibraryGrid) so portrait / square / landscape\n * clips render in cohesive groups.\n *\n * Why these live here in the lib (not the hub): `<VideoBitesDisplay>`\n * was moved into the lib for SSoT, and it depends on these primitives.\n * The hub's admin editors re-export from here.\n */\n\nimport {\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from '../ui/tabs';\nimport type { VideoTeaser } from '../../types/video-processing';\n\n/**\n * Vizard clip extraction aspect ratios. Mirrors `lib/types/aspect-ratio.ts`\n * in the hub — narrow on purpose. If the hub's `VizardAspectRatio` adds a\n * value, mirror it here.\n */\nexport type VizardAspectRatio = '9:16' | '16:9' | '1:1';\n\n/**\n * Extended VideoTeaser with aspect_ratio metadata from Vizard.\n * The lib `VideoTeaser` is the canonical type but doesn't include\n * `aspect_ratio` (stored in JSONB, preserved through spreads).\n * Import this when you need the ratio at compile time.\n */\nexport interface VideoTeaserWithRatio extends VideoTeaser {\n aspect_ratio?: VizardAspectRatio;\n confidence?: number;\n viral_reason?: string;\n start_time_ms?: number;\n end_time_ms?: number;\n}\n\n/** Ratio category used for grid layout and tab grouping. */\nexport type RatioCategory = 'portrait' | 'square' | 'landscape';\n\n// Shared tab trigger class — matches `<EntityVideoSection>`'s pattern.\nconst TAB_TRIGGER_CLASS =\n 'rounded-none border-b-2 border-transparent data-[state=active]:border-ods-accent data-[state=active]:bg-transparent data-[state=active]:shadow-none px-4 py-2 text-sm text-ods-text-secondary data-[state=active]:text-ods-text-primary';\n\n/** Grid class for each aspect ratio (admin editors — narrower columns). */\nexport const RATIO_GRID_CLASS: Record<RatioCategory, string> = {\n portrait: 'grid grid-cols-2 md:grid-cols-3 gap-4',\n square: 'grid grid-cols-2 md:grid-cols-3 gap-4',\n landscape: 'grid grid-cols-1 md:grid-cols-2 gap-4',\n};\n\n/** Grid class for public display (wider grids on detail pages). */\nexport const RATIO_DISPLAY_GRID_CLASS: Record<RatioCategory, string> = {\n portrait: 'grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4',\n square: 'grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4',\n landscape: 'grid grid-cols-1 md:grid-cols-2 gap-6',\n};\n\nconst RATIO_TAB_CONFIG: { key: RatioCategory; label: string }[] = [\n { key: 'portrait', label: 'Portrait 9:16' },\n { key: 'square', label: 'Square 1:1' },\n { key: 'landscape', label: 'Landscape 16:9' },\n];\n\ninterface RatioTabsProps {\n groups: Record<RatioCategory, { count: number; render: () => React.ReactNode }>;\n defaultTab?: RatioCategory;\n className?: string;\n}\n\n/**\n * RatioTabs — shared aspect-ratio tab wrapper.\n *\n * Only renders tabs that have content. `forceMount` + `data-[state=inactive]:hidden`\n * keeps inactive tabs in the DOM so switching back doesn't scroll-jump.\n */\nexport function RatioTabs({\n groups,\n defaultTab,\n className = '',\n}: RatioTabsProps) {\n const activeTabs = RATIO_TAB_CONFIG.filter(t => groups[t.key].count > 0);\n\n // If only one tab has content, don't show tabs.\n if (activeTabs.length <= 1) {\n const active = activeTabs[0];\n return active ? <>{groups[active.key].render()}</> : null;\n }\n\n const firstTab =\n defaultTab && groups[defaultTab].count > 0 ? defaultTab : activeTabs[0].key;\n\n return (\n <Tabs defaultValue={firstTab} className={`w-full ${className}`}>\n <TabsList className=\"inline-flex justify-start rounded-none bg-transparent h-auto p-0 gap-0 mb-2\">\n {activeTabs.map(t => (\n <TabsTrigger key={t.key} value={t.key} className={TAB_TRIGGER_CLASS}>\n {t.label} ({groups[t.key].count})\n </TabsTrigger>\n ))}\n </TabsList>\n {activeTabs.map(t => (\n <TabsContent\n key={t.key}\n value={t.key}\n forceMount\n className=\"data-[state=inactive]:hidden\"\n >\n {groups[t.key].render()}\n </TabsContent>\n ))}\n </Tabs>\n );\n}\n\n/**\n * Detect aspect ratio from a Vizard ratio string, falling back to\n * inferring from width/height if the string is missing or unknown.\n * Default: portrait (`'9:16'`).\n */\nexport function detectAspectRatio(\n ratioString?: string,\n width?: number,\n height?: number,\n): VizardAspectRatio {\n if (ratioString === '16:9') return '16:9';\n if (ratioString === '1:1') return '1:1';\n if (ratioString === '9:16') return '9:16';\n if (width && height) {\n if (Math.abs(width - height) < Math.min(width, height) * 0.1) return '1:1';\n if (width > height) return '16:9';\n }\n return '9:16';\n}\n\n/** Map a `VizardAspectRatio` to its `RatioCategory` for grouping. */\nexport function ratioToCategory(ratio: VizardAspectRatio): RatioCategory {\n if (ratio === '16:9') return 'landscape';\n if (ratio === '1:1') return 'square';\n return 'portrait';\n}\n\n/**\n * Group items by aspect ratio into portrait / square / landscape buckets.\n * `hasMultiple` is true when 2+ buckets are non-empty (drives tab vs. flat\n * grid rendering downstream).\n */\nexport function groupByAspectRatio<T>(\n items: T[],\n getAspectRatio: (item: T) => VizardAspectRatio,\n): {\n portrait: T[];\n square: T[];\n landscape: T[];\n hasMultiple: boolean;\n} {\n const portrait: T[] = [];\n const square: T[] = [];\n const landscape: T[] = [];\n for (const item of items) {\n const cat = ratioToCategory(getAspectRatio(item));\n if (cat === 'landscape') landscape.push(item);\n else if (cat === 'square') square.push(item);\n else portrait.push(item);\n }\n const filled = [portrait, square, landscape].filter(a => a.length > 0).length;\n return { portrait, square, landscape, hasMultiple: filled > 1 };\n}\n","\"use client\";\n\nimport React, { useMemo } from 'react';\nimport { Card } from '../ui/card';\nimport { useNearViewport } from '../../hooks/use-near-viewport';\nimport type { VideoTeaser } from '../../types/video-processing';\nimport { Video } from './video';\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading';\nimport {\n RatioTabs,\n groupByAspectRatio,\n detectAspectRatio,\n ratioToCategory,\n RATIO_DISPLAY_GRID_CLASS,\n type VideoTeaserWithRatio,\n type RatioCategory,\n} from './video-ratio-tabs';\n\n/**\n * <VideoBitesDisplay> — public grid of short video bites grouped by\n * aspect ratio.\n *\n * Goes through `<Video>` for every clip — bites are no longer carved\n * out to raw `<VideoPlayer>`. The previous carve-out existed because\n * react-player's hls.js bundle was ~80KB and short clips didn't need\n * adaptive bitrate. With MuxPlayer, the player loads its HLS engine\n * lazily — for a plain MP4 source it's a thin shell around `<video>`,\n * so the carve-out costs more in complexity than it saves in bytes.\n *\n * `LazyBite` defers mount until the wrapper enters the IO `500px`\n * margin so off-screen bites don't even render their player. Same\n * `useNearViewport` singleton used everywhere else in the lib.\n */\n\n// =============================================================================\n// LazyBite — viewport-gated wrapper for video bite cards\n// =============================================================================\n\n/** Aspect-ratio-aware placeholder prevents CLS across the grid. */\nconst RATIO_TO_CSS_ASPECT: Record<RatioCategory, string> = {\n portrait: '9 / 16',\n square: '1 / 1',\n landscape: '16 / 9',\n};\n\ninterface LazyBiteProps {\n /** Aspect ratio of the wrapped bite — placeholder keeps layout stable. */\n ratio: RatioCategory;\n /** Card rendered once the wrapper enters (within `500px` of) the viewport. */\n children: React.ReactNode;\n}\n\n/**\n * Defers mounting an off-screen video bite until it scrolls within\n * ~500px of the viewport. Renders an aspect-ratio-matched placeholder\n * beforehand so layout stays stable.\n *\n * Placeholder bg matches the wrapped `<Card>` background (`bg-ods-card`)\n * so the swap-in is visually seamless and avoids a flash on hydration.\n */\nfunction LazyBite({ ratio, children }: LazyBiteProps) {\n const { ref, isNear } = useNearViewport<HTMLDivElement>('500px');\n\n return (\n <div ref={ref} style={{ aspectRatio: RATIO_TO_CSS_ASPECT[ratio] }}>\n {isNear ? children : <div className=\"w-full h-full bg-ods-card rounded-md\" />}\n </div>\n );\n}\n\n// =============================================================================\n// Public component\n// =============================================================================\n\nexport interface VideoBitesDisplayProps {\n /** Array of video bites/teasers to display. */\n bites: VideoTeaser[];\n /** Title for the section. */\n title?: string;\n /** Whether to filter to only show published bites. Default `true`. */\n filterPublished?: boolean;\n /** Whether to show the title section heading. Default `true`. */\n showTitle?: boolean;\n}\n\n/**\n * Unified video-bites grid.\n *\n * Groups by aspect ratio when multiple ratios are present, otherwise\n * renders a flat grid. Each bite mounts lazily via `LazyBite` so\n * off-screen players don't cost the page.\n */\nexport function VideoBitesDisplay({\n bites,\n title = 'Video Highlights',\n filterPublished = true,\n showTitle = true,\n}: VideoBitesDisplayProps) {\n const grouped = useMemo(() => {\n const filtered = filterPublished ? bites.filter(b => b.published) : bites;\n\n const sorted = [...filtered].sort((a, b) => {\n if (!a.created_at && !b.created_at) return 0;\n if (!a.created_at) return 1;\n if (!b.created_at) return -1;\n return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();\n });\n\n return groupByAspectRatio(sorted, b =>\n detectAspectRatio((b as VideoTeaserWithRatio).aspect_ratio),\n );\n }, [bites, filterPublished]);\n\n const totalCount = grouped.portrait.length + grouped.square.length + grouped.landscape.length;\n if (totalCount === 0) return null;\n\n return (\n <div className=\"flex flex-col gap-6 w-full min-w-0\">\n {showTitle && (\n <h2 className={`${SECTION_HEADING_CLASS} break-words`}>\n {title}\n </h2>\n )}\n\n {grouped.hasMultiple ? (\n <RatioTabs\n groups={{\n portrait: {\n count: grouped.portrait.length,\n render: () => <BiteGrid bites={grouped.portrait} ratio=\"portrait\" />,\n },\n square: {\n count: grouped.square.length,\n render: () => <BiteGrid bites={grouped.square} ratio=\"square\" />,\n },\n landscape: {\n count: grouped.landscape.length,\n render: () => <BiteGrid bites={grouped.landscape} ratio=\"landscape\" />,\n },\n }}\n />\n ) : (\n <BiteGrid\n bites={\n grouped.portrait.length > 0\n ? grouped.portrait\n : grouped.square.length > 0\n ? grouped.square\n : grouped.landscape\n }\n ratio={\n grouped.portrait.length > 0\n ? 'portrait'\n : grouped.square.length > 0\n ? 'square'\n : 'landscape'\n }\n />\n )}\n </div>\n );\n}\n\n// =============================================================================\n// Internals\n// =============================================================================\n\n/**\n * Renders a grid of bite cards with ratio-appropriate column layout.\n * Each card is wrapped in `LazyBite` so off-screen bites don't mount\n * their player until they scroll near the viewport.\n */\nfunction BiteGrid({ bites, ratio }: { bites: VideoTeaser[]; ratio: RatioCategory }) {\n return (\n <div className={RATIO_DISPLAY_GRID_CLASS[ratio]}>\n {bites.map((bite, index) => (\n <LazyBite key={bite.url || index} ratio={ratio}>\n <VideoBiteCard url={bite.url} title={bite.title} thumbnailUrl={bite.thumbnail_url} />\n </LazyBite>\n ))}\n </div>\n );\n}\n\ninterface VideoBiteCardProps {\n url: string;\n title?: string | null;\n thumbnailUrl?: string | null;\n}\n\n/**\n * Individual bite card — routes through `<Video>` so the SSoT player\n * is the only video primitive in the lib.\n *\n * Layout: `LazyBite` sets the OUTER `aspectRatio` (portrait/square/landscape),\n * but `<Card>` between LazyBite and `<Video>` has no intrinsic height, so\n * we wrap `<Video>` in `layout=\"fill\"` + an explicit `relative` parent so\n * the player fills the bite's aspect box from first paint. Otherwise\n * MuxPlayer renders at its intrinsic default size and grows once metadata\n * loads — the same CLS that hits the centered layout.\n */\nfunction VideoBiteCard({ url, title, thumbnailUrl }: VideoBiteCardProps) {\n return (\n <Card className=\"overflow-hidden border border-ods-border bg-ods-card hover:border-ods-accent transition-colors flex flex-col h-full\">\n <div className=\"relative flex-1 min-h-0\">\n <Video url={url} poster={thumbnailUrl || undefined} layout=\"fill\" />\n </div>\n {title && (\n <div className=\"p-4\">\n <p className=\"text-h4 text-ods-text-primary line-clamp-2\" title={title}>{title}</p>\n </div>\n )}\n </Card>\n );\n}\n\nexport { VideoBiteCard };\n","\"use client\";\n\nimport { ComponentType } from 'react';\nimport {\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from '../ui/tabs';\nimport type { VideoTeaser } from '../../types/video-processing';\nimport { Video } from './video';\nimport { VideoBitesDisplay } from './video-bites-display';\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading';\n\n/**\n * <EntityVideoSection> — public detail-page video block.\n *\n * Tabbed Full Video / Highlights when both exist, plus optional\n * markdown summary + video bites grid. The actual video rendering\n * (YouTube facade, Mux HLS, MP4 fallback) is delegated to `<Video>` —\n * the single source of truth.\n *\n * YouTube takes precedence over the uploaded video when both\n * `youtubeUrl` and `mainVideoUrl` are present. That precedence is\n * resolved here (in the section wrapper) rather than inside `<Video>`,\n * so the underlying primitive stays single-source-per-render.\n */\n\ninterface MarkdownRendererProps {\n content: string;\n}\n\nexport interface EntityVideoSectionProps {\n /** Main uploaded video URL. */\n mainVideoUrl?: string | null;\n /** YouTube URL (takes priority over `mainVideoUrl` for display). */\n youtubeUrl?: string | null;\n /** AI-generated highlight video URL. */\n highlightVideoUrl?: string | null;\n /** Thumbnail for highlight video. */\n highlightVideoThumbnail?: string | null;\n /** Poster/thumbnail for main video. */\n mainVideoPoster?: string | null;\n /** Title for YouTube embed. */\n title?: string;\n /** AI-generated video summary (markdown). */\n videoSummary?: string | null;\n /** Video bites/teasers array. */\n videoBites?: VideoTeaser[];\n /** Title for the video bites section. */\n bitesTitle?: string;\n /** Whether to filter bites to published only. */\n filterPublishedBites?: boolean;\n /** Markdown renderer component injected by the host app. */\n MarkdownRenderer?: ComponentType<MarkdownRendererProps>;\n /**\n * Raw SRT content. Deprecated — pass `captionsUrl` instead.\n * Forwarded to `<Video>` for the dev-only warning.\n */\n srtContent?: string | null;\n /** HTTPS URL to a VTT captions file (rendered as native `<track>`). */\n captionsUrl?: string | null;\n /** LCP hint — when true, the full-video tab's poster eager-loads. */\n priority?: boolean;\n}\n\nexport function EntityVideoSection({\n mainVideoUrl,\n youtubeUrl,\n highlightVideoUrl,\n highlightVideoThumbnail,\n mainVideoPoster,\n title = 'Video',\n videoSummary,\n videoBites,\n bitesTitle = 'Video Highlights',\n filterPublishedBites = true,\n MarkdownRenderer,\n srtContent,\n captionsUrl,\n priority = false,\n}: EntityVideoSectionProps) {\n const hasFullVideo = !!(youtubeUrl || mainVideoUrl);\n const hasHighlight = !!highlightVideoUrl;\n const hasVideo = hasFullVideo || hasHighlight;\n\n if (!hasVideo && !videoSummary && (!videoBites || videoBites.length === 0)) {\n return null;\n }\n\n // YouTube wins when both URLs are present.\n const fullVideoUrl = youtubeUrl || mainVideoUrl || null;\n const fullVideoKind: 'youtube' | 'auto' = youtubeUrl ? 'youtube' : 'auto';\n\n return (\n <>\n {hasVideo &&\n (hasFullVideo && hasHighlight ? (\n <Tabs defaultValue=\"full-video\" className=\"w-full\">\n <TabsList className=\"inline-flex justify-start rounded-none bg-transparent h-auto p-0 gap-0\">\n <TabsTrigger\n value=\"full-video\"\n className=\"rounded-none border-b-2 border-transparent data-[state=active]:border-ods-accent data-[state=active]:bg-transparent data-[state=active]:shadow-none px-4 md:px-6 py-3 text-ods-text-secondary data-[state=active]:text-ods-text-primary\"\n >\n Full Video\n </TabsTrigger>\n <TabsTrigger\n value=\"highlights\"\n className=\"rounded-none border-b-2 border-transparent data-[state=active]:border-ods-accent data-[state=active]:bg-transparent data-[state=active]:shadow-none px-4 md:px-6 py-3 text-ods-text-secondary data-[state=active]:text-ods-text-primary\"\n >\n Highlights\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"full-video\" className=\"mt-4\">\n <Video\n kind={fullVideoKind}\n url={fullVideoUrl!}\n poster={mainVideoPoster}\n title={title}\n srtContent={srtContent}\n captionsUrl={captionsUrl}\n layout=\"centered\"\n priority={priority}\n />\n </TabsContent>\n\n <TabsContent value=\"highlights\" className=\"mt-4\">\n <Video\n url={highlightVideoUrl!}\n poster={highlightVideoThumbnail}\n layout=\"centered\"\n />\n </TabsContent>\n </Tabs>\n ) : hasFullVideo ? (\n <Video\n kind={fullVideoKind}\n url={fullVideoUrl!}\n poster={mainVideoPoster}\n title={title}\n srtContent={srtContent}\n captionsUrl={captionsUrl}\n layout=\"centered\"\n priority={priority}\n />\n ) : (\n <Video\n url={highlightVideoUrl!}\n poster={highlightVideoThumbnail}\n layout=\"centered\"\n priority={priority}\n />\n ))}\n\n {videoSummary && MarkdownRenderer && (\n <div className=\"flex flex-col gap-6 w-full min-w-0\">\n <h2 className={`${SECTION_HEADING_CLASS} break-words`}>\n Summary\n </h2>\n <div className=\"text-h4 text-ods-text-primary break-words overflow-hidden\">\n <MarkdownRenderer content={videoSummary} />\n </div>\n </div>\n )}\n\n {videoBites && videoBites.length > 0 && (\n <VideoBitesDisplay\n bites={videoBites}\n title={bitesTitle}\n filterPublished={filterPublishedBites}\n />\n )}\n </>\n );\n}\n","/**\n * Compact-card class constants — single source of truth for the chat-inline\n * `size='sm'` card frame.\n *\n * Lifted from the hub's `components/shared/compact-card/compact-card-classes.ts`\n * so the pure-presentation cards in `entity-cards/` can stop importing from\n * the hub tree. Every compact card across the chat shell shares the SAME\n * outer frame, skeleton frame, image slot, icon slot, text column, and\n * per-line typography.\n *\n * Card-specific variation (Play overlay on podcasts, #N pill on investor\n * updates, status badge on roadmap) lives INSIDE the card next to its\n * other type-specific JSX; the frame stays uniform across types.\n */\n\n/** Loaded compact card outer frame for INTERACTIVE state — anchor wrap\n * with hover/transition affordance. Use ONLY when the card resolves\n * to a real, safe href; the user expects a clickable feel. */\nexport const COMPACT_CARD_OUTER =\n 'my-1.5 flex items-start gap-3 w-full p-2 rounded-lg border border-ods-border bg-ods-card no-underline transition-colors hover:border-ods-text-secondary/40'\n\n/** Loaded compact card outer frame for NON-INTERACTIVE state — span wrap\n * used when `safeHref()` rejects the chat ref's url. */\nexport const COMPACT_CARD_OUTER_STATIC =\n 'my-1.5 flex items-start gap-3 w-full p-2 rounded-lg border border-ods-border bg-ods-card no-underline cursor-default'\n\n/** Skeleton outer frame — same dimensions as loaded, plus `animate-pulse`. */\nexport const COMPACT_CARD_SKELETON_OUTER =\n 'my-1.5 flex w-full animate-pulse items-start gap-3 rounded-lg border border-ods-border bg-ods-card p-2'\n\n/** 56×56 image slot — for cards with a cover image. */\nexport const COMPACT_CARD_IMAGE_SLOT =\n 'relative block shrink-0 self-start w-14 h-14 aspect-square overflow-hidden rounded-md bg-ods-bg'\n\n/** 56×56 placeholder slot for skeletons. */\nexport const COMPACT_CARD_SKELETON_IMAGE_SLOT =\n 'block h-14 w-14 aspect-square shrink-0 self-start rounded-md bg-ods-bg'\n\n/** 56×56 icon slot — for cards without a cover image. */\nexport const COMPACT_CARD_ICON_SLOT =\n 'flex h-14 w-14 aspect-square shrink-0 self-start items-center justify-center rounded-md bg-ods-bg text-ods-accent'\n\n/** Text column wrapper — explicit 56px height with `flex flex-col`. */\nexport const COMPACT_CARD_TEXT_COL =\n 'flex min-w-0 flex-1 flex-col gap-0.5 min-h-14'\n\n/** Title row container — fixed 20px (h-5) to match `text-sm leading-5`. */\nexport const COMPACT_CARD_TITLE_ROW = 'flex items-center min-w-0 h-5'\n\n/** Subtitle / summary row container — fixed 16px (h-4). */\nexport const COMPACT_CARD_META_ROW_BOX = 'flex items-center min-w-0 h-4'\n\n/** Title text — bold 14px on a 20px line. */\nexport const COMPACT_CARD_TITLE =\n 'truncate text-sm font-semibold leading-5 text-ods-text-primary'\n\n/** Subtitle text — 11px on a 16px line. */\nexport const COMPACT_CARD_SUBTITLE =\n 'truncate text-[11px] leading-4 text-ods-text-secondary'\n\n/** Summary text — same metrics as subtitle, dropped to 80% opacity. */\nexport const COMPACT_CARD_SUMMARY =\n 'truncate text-[11px] leading-4 text-ods-text-secondary/80'\n\n/** Meta-row variant — multi-part 11px metadata. */\nexport const COMPACT_CARD_META_ROW =\n 'flex items-center gap-1.5 min-w-0 text-[11px] leading-4 text-ods-text-secondary'\n\n/** Non-breaking space — used as a fallback child so the row's height stays 16px. */\nexport const COMPACT_CARD_ROW_FILLER = ' '\n\n/** Schemes allowed in the outer `<a href>` of a compact card. */\nconst SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:']\n\n/** Return the input url ONLY if it's safe to drop into an `<a href>`:\n * the scheme must be http / https / mailto, or the URL must be a\n * same-origin path (`/blog/foo`). Otherwise returns `null`. */\nexport function safeHref(url: string | null | undefined): string | null {\n if (!url) return null\n // Defense in depth: control chars + zero-width / line-separator chars.\n if (/[\\u0000-\\u001f\\u007f\\u200b-\\u200d\\u2028\\u2029\\ufeff]/.test(url)) return null\n const trimmed = url.trim()\n if (!trimmed) return null\n // Pure same-origin path.\n if (trimmed.startsWith('/') && !trimmed.startsWith('//')) return trimmed\n if (trimmed.startsWith('#')) return trimmed\n // Reject bare scheme-only inputs.\n if (/^[a-z][a-z0-9+.-]*:$/i.test(trimmed)) return null\n try {\n const parsed = new URL(trimmed, 'https://_safehref_base.invalid')\n if (!SAFE_URL_SCHEMES.includes(parsed.protocol)) return null\n if ((parsed.protocol === 'https:' || parsed.protocol === 'http:') && !parsed.hostname) return null\n return trimmed\n } catch {\n return null\n }\n}\n","'use client'\n\n/**\n * OnboardingGuideCard (pure presentation + runtime-derived link attrs).\n *\n * Three variants:\n * - `catalog`: rich detail card (hero + author grid) for the public catalog\n * page.\n * - `default`: horizontal step-numbered card for \"More in {section}\" rail.\n * - `sm`: compact horizontal card for chat-inline rendering.\n *\n * Link semantics: the card derives `target`/`rel` from `ChatRuntime.navigation\n * .decideNewTab` (hub-wired via `HubRuntimeProvider`) and the placeholder\n * image from `ChatRuntime.resolvePlaceholderUrl`. Explicit `target` / `rel`\n * / `placeholderUrl` props always WIN — chat dispatch and tests can\n * pre-resolve. No runtime mounted → same-tab + no placeholder.\n */\n\nimport React from 'react'\nimport Image from '../../../embed-shims/next-image'\nimport Link from '../../../embed-shims/next-link'\nimport { Clock, ExternalLink, GraduationCap, Play } from 'lucide-react'\nimport { cn } from '../../../utils/cn'\nimport { BlogImagePlaceholder } from './blog-image-placeholder'\nimport { EntityAuthorCard } from './entity-author-card'\nimport { formatDurationMMSS } from '../../../utils/format'\nimport type { OnboardingGuide } from '../types/entities/onboarding-guide'\nimport { useEntityCardLink } from './use-entity-card-link'\nimport { useEntityCardPlaceholder } from './use-entity-card-placeholder'\nimport {\n COMPACT_CARD_OUTER,\n COMPACT_CARD_IMAGE_SLOT,\n COMPACT_CARD_SKELETON_IMAGE_SLOT,\n COMPACT_CARD_SKELETON_OUTER,\n COMPACT_CARD_TEXT_COL,\n COMPACT_CARD_TITLE_ROW,\n COMPACT_CARD_TITLE,\n COMPACT_CARD_META_ROW_BOX,\n COMPACT_CARD_SUMMARY,\n COMPACT_CARD_ROW_FILLER,\n} from '../utils/compact-card-classes'\n\nexport interface OnboardingGuideCardProps {\n guide: OnboardingGuide\n /** Detail URL resolved by the caller. */\n href: string\n /** When `_blank`, opens in a new tab. Set by chat dispatch via\n * `computeIsNewTab`. Defaults to same-tab. */\n target?: '_blank'\n rel?: 'noopener noreferrer'\n targetPlatform?: string | null\n /** OG placeholder URL used by the catalog + sm variants when no cover. */\n placeholderUrl?: string | null\n size?: 'catalog' | 'default' | 'sm'\n className?: string\n}\n\n/** Markdown source → clean one-line preview prose. The guide cards preview\n * `video_summary || content`, and `content` is raw markdown — without this,\n * the clamped summary shows literal `**bold**` / `## heading` noise. */\nfunction stripMarkdownPreview(text: string): string {\n return text\n .replace(/```[\\s\\S]*?```/g, ' ')\n .replace(/^#{1,6}\\s+/gm, '')\n .replace(/!?\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/[*_~`>]/g, '')\n .replace(/^\\s*[-+]\\s+/gm, '')\n .replace(/-{3,}/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n // Hard cap — line-clamp is the visual truncation, but a CSS-order\n // conflict (display utilities vs -webkit-box) once silently killed it\n // and dumped whole guides into the card. Never ship more than ~2 lines\n // of source text regardless of CSS.\n .replace(/^([\\s\\S]{240})[\\s\\S]+$/, '$1…')\n}\n\nconst HORIZONTAL_SIZE_TOKENS = {\n default: {\n padding: 'p-4',\n step: 'w-8 h-8 text-sm',\n title: 'text-h5',\n summaryClamp: 'line-clamp-2',\n },\n} as const\n\nexport function OnboardingGuideCardSkeleton({ size = 'default' }: { size?: 'catalog' | 'default' | 'sm' }) {\n if (size === 'catalog') {\n return (\n <div className=\"bg-ods-system-greys-black border border-ods-border rounded-lg overflow-hidden flex flex-col p-6 gap-4 animate-pulse\">\n <div className=\"flex flex-col md:flex-row gap-4 md:gap-6\">\n <div className=\"w-full md:w-[256px] aspect-[1200/630] bg-ods-border rounded-lg flex-shrink-0\" />\n <div className=\"flex-1 min-w-0 flex flex-col\">\n <div className=\"min-h-[60px] md:min-h-[72px] flex flex-col gap-1.5 justify-start mb-3\">\n <div className=\"h-[25px] md:h-[30px] w-3/4 bg-ods-border rounded\" />\n <div className=\"h-[25px] md:h-[30px] w-1/2 bg-ods-border rounded\" />\n </div>\n <div className=\"min-h-[46px] md:min-h-[52px] flex flex-col gap-2 justify-start\">\n <div className=\"h-3 w-full bg-ods-border/70 rounded\" />\n <div className=\"h-3 w-5/6 bg-ods-border/70 rounded\" />\n </div>\n </div>\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-3 border border-ods-border rounded-md overflow-hidden w-full\">\n {[0, 1].map((i) => (\n <div\n key={`cell-${i}`}\n className=\"bg-ods-card p-4 flex flex-col gap-3 border-b md:border-b-0 md:border-r border-ods-border\"\n >\n <div className=\"flex flex-col gap-2\">\n <div className=\"h-6 w-32 bg-ods-bg rounded\" />\n <div className=\"h-3 w-20 bg-ods-bg/60 rounded\" />\n </div>\n </div>\n ))}\n <div className=\"bg-ods-card p-4 flex items-center gap-3\">\n <div className=\"h-10 w-10 rounded-full bg-ods-bg shrink-0\" />\n <div className=\"flex flex-col gap-2 flex-1 min-w-0\">\n <div className=\"h-4 w-3/4 bg-ods-bg rounded\" />\n <div className=\"h-3 w-1/2 bg-ods-bg/60 rounded\" />\n </div>\n </div>\n </div>\n </div>\n )\n }\n if (size === 'sm') {\n return (\n <span className={COMPACT_CARD_SKELETON_OUTER}>\n <span className={COMPACT_CARD_SKELETON_IMAGE_SLOT} />\n <span className={COMPACT_CARD_TEXT_COL}>\n <span className={COMPACT_CARD_TITLE_ROW}>\n <span className=\"h-3.5 w-3/5 rounded bg-ods-bg\" />\n </span>\n <span className={COMPACT_CARD_META_ROW_BOX}>\n <span className=\"h-3 w-2/5 rounded bg-ods-bg/70\" />\n </span>\n <span className={COMPACT_CARD_META_ROW_BOX}>\n <span className=\"h-3 w-11/12 rounded bg-ods-bg/40\" />\n </span>\n </span>\n <span className=\"flex shrink-0 items-center self-start h-5\">\n <span className=\"h-3.5 w-3.5 rounded bg-ods-bg\" />\n </span>\n </span>\n )\n }\n const t = HORIZONTAL_SIZE_TOKENS.default\n return (\n <span\n className={`flex items-start gap-3 rounded-md border border-ods-border bg-ods-card ${t.padding} animate-pulse`}\n >\n <span className={`shrink-0 inline-flex items-center justify-center rounded-full bg-ods-bg ${t.step}`} />\n <span className=\"flex flex-col gap-2 flex-1 min-w-0\">\n <span className=\"block h-4 w-2/3 rounded bg-ods-bg\" />\n <span className=\"block h-3 w-1/3 rounded bg-ods-bg/70\" />\n <span className=\"block h-3 w-full rounded bg-ods-bg/40\" />\n </span>\n </span>\n )\n}\n\nexport function OnboardingGuideCard({\n guide,\n href,\n target: targetProp,\n rel: relProp,\n targetPlatform,\n placeholderUrl: placeholderUrlProp,\n size = 'default',\n className,\n}: OnboardingGuideCardProps) {\n const { target, rel } = useEntityCardLink({\n href,\n targetPlatform,\n target: targetProp,\n rel: relProp,\n })\n const placeholderUrl = useEntityCardPlaceholder({\n title: guide.title,\n placeholderUrl: placeholderUrlProp,\n aspect: size === 'sm' ? 'square' : 'wide',\n })\n\n if (size === 'catalog') {\n const coverImage =\n guide.featured_image ||\n guide.main_video_thumbnail ||\n guide.og_image_url ||\n null\n const hasVideoCover = !!(guide.main_video_thumbnail || guide.highlight_video_thumbnail)\n const stepLabel =\n typeof guide.step_order === 'number'\n ? String(guide.step_order).padStart(2, '0')\n : '—'\n const durationLabel =\n typeof guide.highlight_video_duration_ms === 'number' && guide.highlight_video_duration_ms > 0\n ? formatDurationMMSS(Math.floor(guide.highlight_video_duration_ms / 1000))\n : ''\n\n return (\n <Link\n href={href}\n target={target}\n rel={rel}\n prefetch={false}\n className={cn(\n 'group block no-underline bg-ods-system-greys-black',\n 'border border-ods-border rounded-lg overflow-hidden',\n 'transition-all duration-300 ease-out',\n 'transform hover:translate-y-[-2px]',\n 'hover:border-ods-accent hover:shadow-lg hover:shadow-ods-accent/[0.08]',\n className,\n )}\n aria-label={`Open ${guide.title}`}\n >\n <div className=\"flex flex-col p-6 gap-4\">\n <div className=\"flex flex-col md:flex-row gap-4 md:gap-6\">\n <div className=\"w-full md:w-[256px] flex-shrink-0\">\n <div className=\"relative rounded-lg overflow-hidden w-full aspect-[1200/630] bg-ods-bg\">\n {coverImage ? (\n <Image\n src={coverImage}\n alt={guide.title}\n fill\n sizes=\"(max-width: 768px) 100vw, 256px\"\n className=\"object-cover\"\n unoptimized\n />\n ) : (\n <BlogImagePlaceholder\n title={guide.title}\n imageUrl={placeholderUrl ?? null}\n className=\"absolute inset-0\"\n />\n )}\n {hasVideoCover && coverImage && (\n <span className=\"absolute inset-0 flex items-center justify-center bg-black/30\">\n <Play className=\"w-10 h-10 text-white\" fill=\"white\" />\n </span>\n )}\n {durationLabel && (\n <span className=\"absolute bottom-2 right-2 inline-flex items-center gap-1 px-2 py-1 rounded bg-black/60 text-white text-xs font-medium font-mono\">\n <Clock className=\"w-3 h-3\" />\n {durationLabel}\n </span>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-w-0 flex flex-col\">\n <div className=\"min-h-[60px] md:min-h-[72px] flex items-start mb-3\">\n <h3 className=\"font-['Azeret_Mono'] font-semibold text-xl md:text-2xl text-ods-text-primary leading-tight line-clamp-2\">\n {guide.title}\n </h3>\n </div>\n <div className=\"min-h-[46px] md:min-h-[52px]\">\n <p className=\"font-['DM_Sans'] text-sm md:text-base text-ods-text-secondary leading-relaxed line-clamp-2\">\n {stripMarkdownPreview(guide.video_summary || guide.content || '')}\n </p>\n </div>\n </div>\n </div>\n\n <EntityAuthorCard\n author={guide.author}\n publishedAt={guide.published_at}\n renderEmptyAuthor\n extraCells={[\n {\n value: `${guide.section} · Step ${stepLabel}`,\n label: 'Section',\n uppercase: false,\n },\n ]}\n />\n </div>\n </Link>\n )\n }\n\n if (size === 'sm') {\n const coverImage = guide.featured_image || guide.main_video_thumbnail || guide.og_image_url || null\n const compactCover = coverImage || placeholderUrl || null\n const hasVideoCover = !guide.featured_image && !!guide.main_video_thumbnail\n const summary = stripMarkdownPreview(guide.video_summary || guide.content || '')\n const author = guide.author?.full_name?.trim() || ''\n const subtitleParts = [\n `Step ${guide.step_order}`,\n guide.section,\n author,\n ].filter((s): s is string => typeof s === 'string' && s.length > 0)\n return (\n <Link href={href} target={target} rel={rel} prefetch={false} className={cn(COMPACT_CARD_OUTER, className)}>\n <span className={COMPACT_CARD_IMAGE_SLOT}>\n {compactCover ? (\n <Image\n src={compactCover}\n alt={guide.title}\n fill\n sizes=\"56px\"\n className=\"object-contain\"\n unoptimized\n />\n ) : (\n <span className=\"flex h-full w-full items-center justify-center text-ods-accent\">\n <GraduationCap className=\"w-4 h-4\" />\n </span>\n )}\n {hasVideoCover && compactCover && (\n <span className=\"absolute inset-0 flex items-center justify-center bg-black/30\">\n <Play className=\"h-4 w-4 text-white\" fill=\"white\" />\n </span>\n )}\n </span>\n <span className={COMPACT_CARD_TEXT_COL}>\n <span className={COMPACT_CARD_TITLE_ROW}>\n <span className={cn(COMPACT_CARD_TITLE, \"font-['Azeret_Mono']\")}>\n {guide.title}\n </span>\n </span>\n <span className={COMPACT_CARD_META_ROW_BOX}>\n <span className=\"truncate text-[11px] leading-4 text-[var(--color-accent-primary)]\">\n {subtitleParts.join(' · ')}\n </span>\n </span>\n <span className={COMPACT_CARD_META_ROW_BOX}>\n <span className={COMPACT_CARD_SUMMARY}>\n {summary || COMPACT_CARD_ROW_FILLER}\n </span>\n </span>\n </span>\n <span className=\"flex shrink-0 items-center self-start h-5 text-ods-text-secondary\">\n <ExternalLink className=\"w-3.5 h-3.5\" />\n </span>\n </Link>\n )\n }\n\n // size === 'default' — horizontal step-numbered card for related-rail.\n const t = HORIZONTAL_SIZE_TOKENS.default\n const summary = stripMarkdownPreview(guide.video_summary || guide.content || '')\n\n return (\n <Link\n href={href}\n target={target}\n rel={rel}\n prefetch={false}\n className={cn(\n `flex items-start gap-3 rounded-md border border-ods-border bg-ods-card hover:border-ods-accent transition-colors ${t.padding}`,\n className,\n )}\n >\n <span\n className={`shrink-0 inline-flex items-center justify-center rounded-full bg-ods-accent/10 text-ods-accent font-semibold ${t.step}`}\n aria-hidden=\"true\"\n >\n {guide.step_order}\n </span>\n <span className=\"flex flex-col gap-0.5 flex-1 min-w-0\">\n <span className={`block ${t.title} text-ods-text-primary truncate`}>{guide.title}</span>\n <span className=\"inline-flex items-center gap-1 font-['DM_Sans'] text-[12px] leading-[16px] text-ods-text-secondary\">\n <GraduationCap className=\"h-3 w-3 shrink-0\" />\n <span className=\"truncate\">{guide.section}</span>\n </span>\n {summary && (\n <span className={`font-['DM_Sans'] text-[14px] leading-[20px] text-ods-text-secondary ${t.summaryClamp}`}>\n {summary}\n </span>\n )}\n </span>\n </Link>\n )\n}\n","'use client'\n\n/**\n * Shared link-attribute resolver for every entity card.\n *\n * Pure-presentation cards (BlogCard, OnboardingGuideCard, etc.)\n * historically required callers to pre-compute `target`/`rel` via a\n * hub-side `useNavLink` wrapper because the same-tab-vs-new-tab\n * decision depends on `currentPlatform()` and the cross-platform\n * URL topology — neither of which the lib knows.\n *\n * This hook moves that decision INTO the card via the\n * `ChatRuntime.navigation.decideNewTab` callback already wired by\n * `HubRuntimeProvider` (and overridable per-embedder). The wrapping\n * `*-card-item.tsx` files in the hub become unnecessary — every card\n * derives its own `target`/`rel` from runtime.\n *\n * Backwards compat: explicit `target` / `rel` props always WIN over\n * runtime-derived values. Chat dispatcher callsites that already pass\n * pre-resolved attributes are unaffected.\n *\n * No runtime mounted? Returns `{ target: undefined, rel: undefined }`\n * (same-tab) — matches the documented embed-shim fallback.\n */\n\nimport { useMemo } from 'react'\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context'\nimport { computeIsNewTab } from '../utils/nav-anchor-props'\n\nexport interface UseEntityCardLinkArgs {\n href: string\n targetPlatform?: string | null\n /** Explicit override. When set, runtime decision is skipped. */\n target?: '_blank'\n /** Explicit override. When set, runtime decision is skipped. */\n rel?: 'noopener noreferrer'\n}\n\nexport interface EntityCardLinkProps {\n target: '_blank' | undefined\n rel: 'noopener noreferrer' | undefined\n}\n\nexport function useEntityCardLink({\n href,\n targetPlatform,\n target,\n rel,\n}: UseEntityCardLinkArgs): EntityCardLinkProps {\n const runtime = useChatRuntime()\n return useMemo(() => {\n // Explicit prop wins — preserves the chat-dispatcher path that\n // pre-computes attrs from `computeIsNewTab`. When `target='_blank'`\n // is passed without `rel`, auto-pair with `noopener noreferrer`\n // to close the tabnabbing vector (window.opener access from the\n // new tab back to the parent).\n if (target !== undefined || rel !== undefined) {\n const safeRel =\n rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined)\n return { target, rel: safeRel }\n }\n // Use the UNIFIED new-tab decision so the rendered anchor's target/rel ALWAYS\n // agrees with the click handler (executeNavigation also calls computeIsNewTab) —\n // including the embed-mode short-circuit + the origin/platform fallback.\n // Previously this used `decideNewTab ?? false`, which diverged from the click\n // when decideNewTab was unwired (embed mode rendered a same-tab anchor that\n // clicked into a new tab). Hub behavior is unchanged: it wires decideNewTab,\n // which computeIsNewTab consumes identically.\n const newTab = runtime ? computeIsNewTab(runtime, href, targetPlatform ?? null) : false\n return newTab\n ? { target: '_blank' as const, rel: 'noopener noreferrer' as const }\n : { target: undefined, rel: undefined }\n }, [target, rel, href, targetPlatform, runtime])\n}\n","'use client'\n\n/**\n * Shared OG-placeholder resolver for every entity card.\n *\n * Pure-presentation cards historically required callers to\n * pre-compute `placeholderUrl` via a hub-side `useOgPlaceholder`\n * wrapper that injected the hub's `buildOgPlaceholderUrl` (resolves\n * CSS-var ODS colors to hex via the static map).\n *\n * This hook moves that resolution INTO the card via the\n * `ChatRuntime.resolvePlaceholderUrl` callback. Embedders that don't\n * wire the callback get no placeholder (the card's empty-state path\n * activates) — same fallback semantics as before.\n *\n * Backwards compat: explicit `placeholderUrl` prop ALWAYS wins over\n * runtime-derived value. Callers that pre-resolve (chat dispatch,\n * tests) are unaffected.\n */\n\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context'\nimport { useOgPlaceholder } from '../../../hooks/use-og-placeholder'\n\nexport interface UseEntityCardPlaceholderArgs {\n /** Entity title — used as the placeholder label. */\n title: string | undefined | null\n /** Explicit override. When set, runtime resolver is skipped. */\n placeholderUrl?: string | null\n /** Site name shown under the title. Optional. */\n siteName?: string\n /** Output aspect ratio. `'wide'` (default) for catalog cards,\n * `'square'` for compact chat-inline cards. */\n aspect?: 'wide' | 'square'\n}\n\nconst NO_OP_BUILDER = () => ''\n\nexport function useEntityCardPlaceholder({\n title,\n placeholderUrl,\n siteName = '',\n aspect = 'wide',\n}: UseEntityCardPlaceholderArgs): string | null {\n const runtime = useChatRuntime()\n const builder = runtime?.resolvePlaceholderUrl ?? NO_OP_BUILDER\n const enabled = placeholderUrl === undefined && !!runtime?.resolvePlaceholderUrl\n const derived = useOgPlaceholder(builder, title, siteName, enabled, aspect)\n // Explicit prop (including explicit null) wins; `undefined` falls back to derived.\n return placeholderUrl !== undefined ? placeholderUrl : derived\n}\n","/**\n * Per-source icon-name + label lookup. Keyed by `RagTableConfig.id` (the same\n * id used in `chat_source_rag_tables.rag_table_id` and on each\n * `ChatRef.sourceRepo`).\n *\n * Server-safe — lives in `src/utils/` (no `'use client'` banner) so\n * hub server-side rag-mappers + chat route handlers can call\n * `getSourceLabel()` / `getSourceIconName()` directly. The client-side\n * chat barrel re-exports from here for ergonomic client imports.\n *\n * Used by SOURCE-LEVEL chip surfaces:\n * - The chip strip below an assistant message.\n * - The tracking-row source glyph on inline entity cards.\n *\n * Why a small map here instead of a column on RAG_TABLE_CONFIGS:\n * - The icon NAME is a small string; the resolver (icon-name → React\n * component) lives in `src/components/chat/utils/icon-registry.ts`\n * where it can stay client-only. Server-side consumers stay\n * icon-component-free.\n * - The mapping is intrinsic to the TABLE, not per-platform — same\n * entry serves every (platform, source) binding.\n *\n * Per-table admin-UI bucket id (`SOURCE_CATEGORIES_BY_TABLE`) stays\n * hub-side because it depends on the hub's `RagSourceCategoryId` enum.\n */\n\n/** Map RagTableConfig.id → icon_name (resolvable via `getIconComponent`). */\nexport const SOURCE_ICON_NAMES: Record<string, string> = {\n // Doc tables\n 'openframe-docs': 'openframe',\n 'data-room-docs': 'shield',\n\n // CMS / programs\n 'blog-posts': 'newspaper',\n 'product-releases': 'rocket',\n 'case-studies': 'briefcase',\n 'onboarding-guides': 'graduation-cap',\n webinars: 'video',\n events: 'calendar',\n podcasts: 'headphones',\n 'customer-interviews': 'users',\n\n // Financials\n 'investor-updates': 'mail',\n 'financial-kpis': 'activity',\n 'financial-cap-table': 'table',\n 'financial-pnl': 'trending-up',\n 'financial-balance-sheet': 'dollar-sign',\n 'financial-cash-flow': 'banknote',\n\n // ClickUp\n 'clickup-roadmap': 'clickup',\n 'clickup-delivery': 'clickup',\n 'clickup-tasks-internal': 'clickup',\n\n // GitHub\n 'github-commits': 'github',\n 'github-pull-requests': 'github',\n 'github-pr-reviews': 'github',\n 'github-commits-public': 'github',\n 'github-pull-requests-public': 'github',\n 'github-pr-reviews-public': 'github',\n\n // HubSpot\n 'hubspot-tickets': 'hubspot',\n 'hubspot-tickets-anon': 'hubspot',\n 'hubspot-tickets-self': 'hubspot',\n\n // Communications\n 'slack-messages': 'slack',\n}\n\n/** Lookup an icon name by RagTableConfig.id. Returns undefined when\n * unknown so callers can decide whether to fall back to documentType\n * or to the generic FileText glyph in the icon registry. */\nexport function getSourceIconName(tableId: string | null | undefined): string | undefined {\n if (!tableId) return undefined\n return SOURCE_ICON_NAMES[tableId]\n}\n\n/**\n * Per-table display LABEL — used by SERVER-SIDE chip-strip rendering\n * (the row's \"Customer Interviews (8 records)\" chip below an assistant\n * message).\n *\n * The chip GRID + autocomplete DROPDOWN read their command label\n * from the DB (per-source `chat_admin_slash_commands.label`) — those\n * surfaces can carry per-source labels (e.g. \"OpenFrame Commits\" vs\n * \"GitHub Commits\" for the same table). This map is the TABLE-LEVEL\n * label used by sub-message chips where a SINGLE label per table is\n * sufficient regardless of which source's chat is rendering.\n */\nexport const SOURCE_LABELS_BY_TABLE: Record<string, string> = {\n // Doc tables\n 'openframe-docs': 'OpenFrame Docs',\n 'data-room-docs': 'Data Room',\n\n // CMS / programs\n 'blog-posts': 'Blog Posts',\n 'product-releases': 'Product Releases',\n 'case-studies': 'Case Studies',\n 'onboarding-guides': 'Onboarding Guides',\n webinars: 'Webinars',\n events: 'Events',\n podcasts: 'Podcasts',\n 'customer-interviews': 'Customer Interviews',\n\n // Financials\n 'investor-updates': 'Investor Updates',\n 'financial-kpis': 'Financial KPIs',\n 'financial-cap-table': 'Cap Table',\n 'financial-pnl': 'Profit & Loss',\n 'financial-balance-sheet': 'Balance Sheet',\n 'financial-cash-flow': 'Cash Flow',\n\n // ClickUp\n 'clickup-roadmap': 'ClickUp Roadmap',\n 'clickup-delivery': 'ClickUp Delivery',\n 'clickup-tasks-internal': 'ClickUp Tasks',\n\n // GitHub\n 'github-commits': 'GitHub Commits',\n 'github-pull-requests': 'GitHub Pull Requests',\n 'github-pr-reviews': 'GitHub PR Reviews',\n 'github-commits-public': 'OpenFrame Commits',\n 'github-pull-requests-public': 'OpenFrame Pull Requests',\n 'github-pr-reviews-public': 'OpenFrame PR Reviews',\n\n // HubSpot\n 'hubspot-tickets': 'HubSpot Tickets',\n 'hubspot-tickets-anon': 'Known Issues',\n 'hubspot-tickets-self': 'My Tickets',\n\n // Communications\n 'slack-messages': 'OpenMSP Community',\n}\n\n/** Lookup a human-readable label by RagTableConfig.id. Falls back\n * to the raw id (chip text becomes the table slug — visible bug\n * that prompts an entry to land in `SOURCE_LABELS_BY_TABLE`). */\nexport function getSourceLabel(tableId: string): string {\n return SOURCE_LABELS_BY_TABLE[tableId] ?? tableId\n}\n\n/**\n * Default `documentType → RagTableConfig.id` reverse map.\n *\n * Mirrors the canonical lookup the hub registers via\n * `lib/config/rag-table-config.ts:tableIdForDocumentType()` — same vocabulary\n * the LLM emits inside `[card://<type>:<id>]` markers, same tableIds the\n * retrieval layer routes by, and same set of keys covered by\n * `SOURCE_ICON_NAMES` / `SOURCE_LABELS_BY_TABLE` above.\n *\n * Server-safe (no `'use client'` banner), keyed by string for forward\n * compatibility — adding a new RAG table is a single-line edit here so\n * lib-based embedders pick it up without re-publishing.\n *\n * Embedders that need a CUSTOM mapping (e.g. polymorphic types whose\n * tableId depends on per-tenant config) still pass their own\n * `tableIdForDocumentType` callback to `useEmbeddedChat`; this default\n * only applies when no callback is supplied.\n *\n * Inverse mapping audited 2026-05-24 against the hub registry — every\n * entry in `RAG_TABLE_CONFIGS` with a `documentType` is represented.\n */\nexport const DEFAULT_DOCUMENT_TYPE_TO_TABLE_ID: Record<string, string> = {\n // Doc tables\n markdown: 'openframe-docs',\n data_room_doc: 'data-room-docs',\n\n // CMS / programs\n blog_post: 'blog-posts',\n product_release: 'product-releases',\n case_study: 'case-studies',\n onboarding_guide: 'onboarding-guides',\n webinar: 'webinars',\n event: 'events',\n podcast: 'podcasts',\n customer_interview: 'customer-interviews',\n\n // Financials\n investor_update: 'investor-updates',\n financial_kpi: 'financial-kpis',\n cap_table: 'financial-cap-table',\n profit_loss: 'financial-pnl',\n balance_sheet: 'financial-balance-sheet',\n cash_flow: 'financial-cash-flow',\n\n // ClickUp\n roadmap_item: 'clickup-roadmap',\n delivery_item: 'clickup-delivery',\n internal_task: 'clickup-tasks-internal',\n\n // GitHub\n github_commit: 'github-commits',\n github_pull_request: 'github-pull-requests',\n github_pr_review: 'github-pr-reviews',\n github_commit_public: 'github-commits-public',\n github_pull_request_public: 'github-pull-requests-public',\n github_pr_review_public: 'github-pr-reviews-public',\n\n // HubSpot\n hubspot_ticket: 'hubspot-tickets',\n hubspot_ticket_anon: 'hubspot-tickets-anon',\n hubspot_ticket_self: 'hubspot-tickets-self',\n\n // Communications\n slack_message: 'slack-messages',\n}\n\n/**\n * Default `tableIdForDocumentType` resolver used by `useEmbeddedChat` when\n * the caller didn't pass an explicit callback. Returns `null` for\n * unrecognized document types so downstream `discussRef` / `displayRef`\n * still short-circuits gracefully on stale or custom types.\n */\nexport function defaultTableIdForDocumentType(documentType: string): string | null {\n return DEFAULT_DOCUMENT_TYPE_TO_TABLE_ID[documentType] ?? null\n}\n","/**\n * Icon registry — string `icon_name` → React component.\n *\n * SINGLE SOURCE OF TRUTH (no, really this time) for every icon-name →\n * React-component lookup across the lib + the hub. Two DB columns feed\n * in with DIFFERENT conventions:\n *\n * - `chat_admin_slash_commands.icon_name` — kebab-case (`'rocket'`,\n * `'hubspot'`, `'github'`, `'dollar-sign'`).\n * - `social_platforms.icon_name` — PascalCase component names\n * (`'LinkedInIcon'`, `'XLogo'`, `'YouTubeIcon'`, `'Github'`).\n *\n * The registry stores entries under kebab-case keys (URL/DB-friendly,\n * shell-safe, matches `lucide-react`'s own file-naming convention).\n * The PascalCase variants come in via `normalizeIconKey()`, which maps\n * known PascalCase aliases → kebab-case before the lookup runs.\n *\n * Adding a new icon: import the component below, add a `'name': Component`\n * entry in `ICON_REGISTRY`, and (if the same icon also flows in from a DB\n * column that uses PascalCase) add a `PascalName → 'name'` row to\n * `PASCAL_TO_KEBAB_ALIASES`.\n *\n * Fallback: an unknown / null icon_name resolves to `FileText` so a chip\n * never renders without an icon.\n */\n\nimport { createElement, type ComponentType, type ReactNode } from 'react'\nimport {\n Activity,\n Banknote,\n Bell,\n BookOpen,\n Box,\n Briefcase,\n Calendar,\n CheckSquare,\n DollarSign,\n Facebook,\n FileText,\n Github,\n Globe,\n GraduationCap,\n Headphones,\n Info,\n Instagram,\n Mail,\n Megaphone,\n MessageCircle,\n MessageSquare,\n Newspaper,\n Package,\n PenSquare,\n Rocket,\n Search,\n Send,\n Shield,\n Star,\n TableProperties,\n TrendingUp,\n Twitter,\n Users,\n Video,\n Youtube,\n} from 'lucide-react'\n// Brand icons — lib-local exports.\nimport {\n SlackIcon,\n GitHubIcon,\n ClickUpIcon,\n HubspotIcon,\n LinkedInIcon,\n FacebookIcon,\n InstagramIcon,\n YouTubeIcon,\n XLogo,\n OpenFrameLogo as RawOpenFrameLogo,\n} from '../../icons'\n\n// Wrapper so the OpenFrame logo's outer frame inherits the parent's text\n// color instead of its hardcoded default — that default is invisible on\n// dark chat surfaces. The white squares stay white (visible on dark).\n// With this, the parent's `text-ods-text-primary` propagates via\n// `currentColor` and the frame renders in the same primary text color\n// as the lucide icons next to it. Uses `createElement` (not JSX) so this\n// stays a `.ts` file. Accepts `color` for shape-compat with the registry's\n// IconComponent type, but ignores it — the logo's outer frame is wired\n// to `currentColor` via the upperPathColor pass-through.\nconst OpenFrameLogoIcon: ComponentType<{ className?: string; color?: string }> = ({ className }) =>\n createElement(RawOpenFrameLogo, { className, upperPathColor: 'currentColor' })\n\n/**\n * Loose icon-component shape: every entry accepts `className`; `color` is\n * optional (lucide + most brand icons accept it; OpenFrameLogo doesn't,\n * but `color` is optional so passing `undefined` is a no-op). Loose enough\n * to accept both lucide's `LucideIcon` and our brand SVG components.\n */\ntype IconComponent = ComponentType<{ className?: string; color?: string }>\n\n/**\n * The kebab-case registry. ALL lookups go through here; PascalCase\n * aliases route through `normalizeIconKey()` first.\n */\nexport const ICON_REGISTRY: Record<string, IconComponent> = {\n // lucide-react (kebab-case)\n activity: Activity,\n banknote: Banknote,\n bell: Bell,\n 'book-open': BookOpen,\n box: Box,\n briefcase: Briefcase,\n calendar: Calendar,\n 'check-square': CheckSquare,\n 'dollar-sign': DollarSign,\n // For brand-vs-lucide variants the kebab key is the BRAND (most-common\n // social-platform intent); `'<name>-lucide'` carries the lucide outline.\n facebook: FacebookIcon,\n 'facebook-lucide': Facebook,\n 'file-text': FileText,\n // `'github-lucide'` is the lucide outline glyph (used by\n // `social_platforms.icon_name='Github'`); `'github'` is the brand icon\n // (used by `chat_admin_slash_commands.icon_name='github'`). The PascalCase\n // alias `'Github' → 'github-lucide'` routes the lucide glyph for\n // social-platform DB rows; `'GitHubIcon' → 'github'` keeps brand routing\n // consistent for callers that store the PascalCase component name.\n github: GitHubIcon,\n 'github-lucide': Github,\n globe: Globe,\n 'graduation-cap': GraduationCap,\n headphones: Headphones,\n info: Info,\n instagram: InstagramIcon,\n 'instagram-lucide': Instagram,\n mail: Mail,\n megaphone: Megaphone,\n 'message-circle': MessageCircle,\n 'message-square': MessageSquare,\n newspaper: Newspaper,\n package: Package,\n 'pen-square': PenSquare,\n rocket: Rocket,\n search: Search,\n send: Send,\n shield: Shield,\n star: Star,\n table: TableProperties,\n 'trending-up': TrendingUp,\n twitter: Twitter,\n users: Users,\n video: Video,\n youtube: YouTubeIcon,\n 'youtube-lucide': Youtube,\n // brand-only icons (no lucide variant in current use)\n slack: SlackIcon,\n clickup: ClickUpIcon,\n hubspot: HubspotIcon,\n linkedin: LinkedInIcon,\n x: XLogo,\n openframe: OpenFrameLogoIcon,\n}\n\n/**\n * PascalCase → kebab-case alias table for DB columns that store icon\n * names as component identifiers (currently `social_platforms.icon_name`).\n * Keep these in sync with the actual rows in those tables.\n */\nconst PASCAL_TO_KEBAB_ALIASES: Record<string, string> = {\n // Lucide PascalCase names → kebab equivalents\n Activity: 'activity',\n Banknote: 'banknote',\n Bell: 'bell',\n BookOpen: 'book-open',\n Box: 'box',\n Briefcase: 'briefcase',\n Calendar: 'calendar',\n CheckSquare: 'check-square',\n DollarSign: 'dollar-sign',\n // Lucide PascalCase → the `-lucide` kebab variant (kebab default is the\n // brand icon for the social-platform names below).\n Facebook: 'facebook-lucide',\n FileText: 'file-text',\n Github: 'github-lucide',\n Globe: 'globe',\n GraduationCap: 'graduation-cap',\n Headphones: 'headphones',\n Info: 'info',\n Instagram: 'instagram-lucide',\n Mail: 'mail',\n Megaphone: 'megaphone',\n MessageCircle: 'message-circle',\n MessageSquare: 'message-square',\n Newspaper: 'newspaper',\n Package: 'package',\n PenSquare: 'pen-square',\n Rocket: 'rocket',\n Search: 'search',\n Send: 'send',\n Shield: 'shield',\n Star: 'star',\n TableProperties: 'table',\n TrendingUp: 'trending-up',\n Twitter: 'twitter',\n Users: 'users',\n Video: 'video',\n Youtube: 'youtube-lucide',\n // Brand-icon PascalCase exports → kebab (kebab default is the brand)\n SlackIcon: 'slack',\n GitHubIcon: 'github',\n ClickUpIcon: 'clickup',\n HubspotIcon: 'hubspot',\n LinkedInIcon: 'linkedin',\n FacebookIcon: 'facebook',\n InstagramIcon: 'instagram',\n YouTubeIcon: 'youtube',\n XLogo: 'x',\n OpenFrameLogo: 'openframe',\n}\n\n/**\n * Normalize an icon key to the registry's canonical kebab-case form.\n * Accepts PascalCase variants (from `social_platforms.icon_name`-style\n * columns) and passes kebab-case keys through unchanged.\n */\nexport function normalizeIconKey(key: string): string {\n return PASCAL_TO_KEBAB_ALIASES[key] ?? key\n}\n\n/**\n * Resolve an `icon_name` to its React component. Accepts both PascalCase\n * and kebab-case keys (normalizes PascalCase first). Unknown / null /\n * undefined names fall back to `FileText` — chips always render with\n * SOMETHING rather than a missing glyph. The fallback is intentional:\n * a typo in a new admin-authored row shouldn't crash render.\n */\nexport function getIconComponent(\n iconName: string | null | undefined,\n): ComponentType<{ className?: string; color?: string }> {\n if (!iconName) return FileText\n return ICON_REGISTRY[normalizeIconKey(iconName)] ?? FileText\n}\n\n// ---------------------------------------------------------------------------\n// Sized-icon rendering helper (migrated from `src/utils/dynamic-icons.tsx`)\n// ---------------------------------------------------------------------------\n\nexport type DynamicIconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\nconst SIZE_CLASSES: Record<DynamicIconSize, string> = {\n xs: 'w-3 h-3', // 12px\n sm: 'w-4 h-4', // 16px\n md: 'w-6 h-6', // 24px\n lg: 'w-8 h-8', // 32px\n xl: 'w-12 h-12', // 48px\n}\n\n/**\n * Optional per-icon color overrides. Keys are kebab-case (canonical\n * registry form) — pass any incoming key through `normalizeIconKey()`\n * before lookup.\n */\nconst ICON_COLORS: Record<string, { color?: string; fill?: string }> = {\n linkedin: { color: '#0A66C2' },\n facebook: { color: '#1877F2' },\n youtube: { color: '#FF0000' },\n}\n\n/**\n * Render an icon by name with a size preset + optional className.\n * Replaces the old `src/utils/dynamic-icons.tsx#getDynamicIcon`. Accepts\n * both PascalCase and kebab-case keys (via `normalizeIconKey()`).\n */\nexport function getDynamicIcon(\n iconName: string | undefined | null,\n size: DynamicIconSize = 'md',\n className?: string,\n): ReactNode {\n if (!iconName) {\n console.warn('[getDynamicIcon] No iconName provided, using Globe fallback')\n return createElement(Globe, { className: SIZE_CLASSES[size] })\n }\n\n const sizeClass = SIZE_CLASSES[size]\n const finalClassName = className ? `${sizeClass} ${className}` : sizeClass\n\n const canonicalKey = normalizeIconKey(iconName)\n const IconComponent = ICON_REGISTRY[canonicalKey]\n\n if (IconComponent) {\n const colorConfig = ICON_COLORS[canonicalKey] || {}\n return createElement(IconComponent, { className: finalClassName, ...colorConfig })\n }\n\n console.error(\n `[getDynamicIcon] Icon NOT found in registry: \"${iconName}\" (normalized to \"${canonicalKey}\"). Available icons:`,\n Object.keys(ICON_REGISTRY),\n )\n return createElement(Globe, { className: finalClassName })\n}\n","/**\n * Single source of truth for \"what does clicking THIS row do?\" across\n * the chat-adjacent surfaces (source chips, inline cards, search results).\n *\n * Lib-side refactor of the hub's `lib/utils/source-row-cta.ts`. Drops:\n * - `currentPlatform()` — host injects via `ctx.currentPlatform`\n * - `traceCompose()` logging — lib stays log-free; hosts can wrap\n * - `tableIdForDocumentType` — host injects via `ctx.tableIdForDocumentType`\n *\n * The contract:\n * - `icon` + `iconLabel` — for visual + a11y. Sourced from\n * `SOURCE_ICON_NAMES` keyed by the row's `sourceRepo` (config id),\n * resolved to a React component via `ICON_REGISTRY`.\n * - `href` — destination URL. `null` means \"no public viewer; Ask is\n * the only action\". Honors the inline card / chip / search bar's\n * existing fallback chain (externalUrl → in-app path → null) and\n * also runs `safeHref()` on the final value so a hostile RAG row\n * can't land `javascript:` / `data:` in the DOM.\n * - `askable` — whether the Ask drill-in flow can fire. Requires a\n * non-empty `id` AND a `documentType`.\n * - `chatRef` — pre-synthesized `ChatRef` for the Ask handler.\n */\n\nimport React from 'react'\nimport { FileText } from 'lucide-react'\nimport { getSourceIconName } from './source-icons'\nimport { getIconComponent } from './icon-registry'\nimport { getBaseUrl } from '../../../utils/cn'\nimport { safeHref } from './compact-card-classes'\nimport type { ChatRef } from '../chat-ref.types'\nimport type { ComposeContentUrl } from '../../../utils/content-href'\n\n/** Path sanitization — keep alphanumerics, slash, dash, dot, underscore;\n * strip everything else. Defends against a hostile mapper returning a\n * path with `..` traversal or query-string injection. */\nfunction sanitizePath(path: string): string {\n return (path || '').replace(/[^a-zA-Z0-9/_\\-.]/g, '')\n}\n\nexport interface SourceRowInput {\n /** RagTableConfig.id (e.g. 'blog-posts', 'financial-cap-table'). Drives\n * icon resolution via `SOURCE_ICON_NAMES` + `ICON_REGISTRY`. */\n sourceRepo?: string | null\n /** The config's `documentType` (e.g. 'blog_post', 'cap_table'). Drives\n * the Ask drill-in's `ChatRef.type`. */\n documentType?: string | null\n /** Primary-key value of the row (config.primaryKey). Required for Ask. */\n id?: string | null\n /** Display title. */\n title: string\n /** Resolved public URL when the row HAS a viewer. `null`/undefined when\n * the row has no public destination — surface decides Ask-only behavior. */\n externalUrl?: string | null\n /** Platform that owns `externalUrl`. */\n targetPlatform?: string | null\n /** In-app navigator path (e.g. `legal/fundraising/.../safe`). */\n path?: string | null\n}\n\nexport interface SourceRowContext {\n /** Chat shell's base route — `/data-room`, `/knowledge-base`, etc. */\n baseRoute?: string\n /** Cross-platform target for the chip / card click. */\n chipBasePlatform?: string\n /**\n * Host-supplied current-platform identifier. Used as the\n * `targetPlatform` for path-based fallback URLs that point to the\n * host's own surface. When omitted, path-based fallback rows get a\n * `null` targetPlatform — consumers fall back to their legacy\n * origin-check.\n */\n currentPlatform?: string | null\n /**\n * Host-supplied reverse map: documentType → tableId. Used to derive\n * `sourceRepo` from `documentType` when the caller didn't supply it\n * (inline cards know `ref.type` but not `sourceRepo`). When omitted,\n * the resolver falls back to the documentType reverse-map miss and\n * keeps the FileText icon.\n */\n tableIdForDocumentType?: (documentType: string) => string | null\n /**\n * Host-supplied unified content-href resolver (`runtime.composeContentUrl`).\n * When provided, entity rows WITH a public `externalUrl` resolve their href\n * through it — so the host's `hostedTypes` / `overrides` route a chat card to\n * the SAME destination as the equivalent page-view card (in-app for hosted\n * types, the hub URL otherwise). When omitted, the row's `externalUrl` is used\n * verbatim (legacy).\n */\n composeContentUrl?: ComposeContentUrl\n /**\n * Host-supplied per-`documentType` doc-viewer targets — the UNIFIED, DYNAMIC\n * replacement for the single `chipBasePlatform`. Maps a doc-table documentType\n * (`'markdown'`, `'data_room_doc'`, …) → the platform whose PUBLIC doc viewer\n * hosts it + that viewer's base path. A doc chip with no `externalUrl` resolves\n * to `getBaseUrl(platform)/<basePath>/<path>` PER ROW — so a chat mixing several\n * doc sources sends EACH to its own home (markdown→flamingo/knowledge-base,\n * data_room_doc→company-hub/data-room) instead of one static fallback for all.\n * Wins over `chipBasePlatform` when a row's documentType has an entry.\n */\n docPlatformTargets?: Record<string, { platform: string; basePath: string }>\n}\n\nexport interface SourceRowCTA {\n /** Source icon component. Always defined — falls back to `FileText`. */\n icon: React.ComponentType<{ className?: string }>\n /** Human-readable label for the icon. */\n iconLabel: string\n /** Resolved destination URL. `null` when the row has no openable\n * target. Already passed through `safeHref()`. */\n href: string | null\n /** Platform that owns the destination. `null` when the row has no\n * openable target OR the destination is external/unknown. */\n targetPlatform: string | null\n /** When true, the surface can fire the Ask drill-in flow for this row. */\n askable: boolean\n /** Pre-synthesized `ChatRef` for the Ask handler. `null` when not askable. */\n chatRef: ChatRef | null\n}\n\n/**\n * Build a `SourceRowContext` from a `ChatRuntime` + the surface's `baseRoute` /\n * `chipBasePlatform`. The runtime-derived fields (`currentPlatform`, `composeContentUrl`,\n * `docPlatformTargets`) are wired identically at every `resolveSourceRowCTA` call site\n * (source chips + inline cards), so they live here once — adding a ctx field is then a\n * one-line change instead of editing all three call sites.\n */\nexport function sourceRowCtxFromRuntime(\n runtime: {\n source?: string\n composeContentUrl?: ComposeContentUrl\n docPlatformTargets?: SourceRowContext['docPlatformTargets']\n },\n surface: { baseRoute?: string; chipBasePlatform?: string } = {},\n): SourceRowContext {\n return {\n currentPlatform: runtime.source ?? null,\n composeContentUrl: runtime.composeContentUrl,\n docPlatformTargets: runtime.docPlatformTargets,\n baseRoute: surface.baseRoute,\n chipBasePlatform: surface.chipBasePlatform,\n }\n}\n\n/**\n * Pure icon-resolution helper — `sourceRepo` (RagTableConfig.id) →\n * icon_name via `SOURCE_ICON_NAMES`, then icon_name → React component\n * via `ICON_REGISTRY`.\n */\nfunction pickSourceIcon(sourceRepo: string | null, documentType: string | null | undefined) {\n const iconName = sourceRepo ? getSourceIconName(sourceRepo) : undefined\n const icon = iconName ? getIconComponent(iconName) : FileText\n const iconLabel = documentType ?? 'Source'\n return { icon, iconLabel }\n}\n\n/**\n * Doc-table documentTypes — rows that carry an in-app `path` (not an entity\n * `externalUrl`) and resolve to a doc viewer. The SAME set an embedder keys its\n * `docPlatformTargets` map by (markdown = product docs, data_room_doc = data room),\n * declared once so the two can't silently diverge.\n */\nexport const DOC_TABLE_TYPES = ['markdown', 'data_room_doc'] as const\n\n/**\n * Only doc-table rows (DOC_TABLE_TYPES) fall back to doc-viewer navigation when no\n * `externalUrl` is set; the in-app/per-platform viewer can render them. Entity-table\n * rows MUST come with an explicit `externalUrl` from the mapper.\n */\nfunction shouldFallbackToPathNav(row: SourceRowInput): boolean {\n return !!row.documentType && (DOC_TABLE_TYPES as readonly string[]).includes(row.documentType)\n}\n\n/**\n * Resolve the CTA contract for a row. Pure function — no DOM, no fetch,\n * no logging. Call from any surface (chip, card, search result) with the\n * same row shape and get the same answer back.\n */\nexport function resolveSourceRowCTA(\n row: SourceRowInput,\n ctx: SourceRowContext = {},\n): SourceRowCTA {\n // Derive sourceRepo from documentType when the caller didn't supply\n // it. Inline cards know `ref.type` (documentType) but not `sourceRepo`;\n // chips and search results have sourceRepo directly. The host supplies\n // the reverse map via `ctx.tableIdForDocumentType`.\n const sourceRepo = row.sourceRepo\n ?? (row.documentType && ctx.tableIdForDocumentType ? ctx.tableIdForDocumentType(row.documentType) : null)\n ?? null\n\n const { icon, iconLabel } = pickSourceIcon(sourceRepo, row.documentType)\n\n // URL resolution. `idValue` (row primary key) is shared with the Ask\n // drill-in check below.\n let href: string | null = null\n let targetPlatform: string | null = null\n const idValue = (row.id ?? '').trim()\n if (row.path && shouldFallbackToPathNav(row) && !row.externalUrl) {\n // Doc-table (markdown / data-room PDF) with no public URL. Resolve where its\n // viewer lives, in priority order (NOT a content type the composeContentUrl\n // seam knows — docs carry a tree `path`, not a slug/id):\n // 1. docPlatformTargets[documentType] — per-doc-type cross-platform target.\n // The UNIFIED, DYNAMIC path: a chat mixing multiple doc sources routes\n // EACH row to its own home (markdown→flamingo, data_room_doc→company-hub).\n // 2. chipBasePlatform — legacy SINGLE cross-platform fallback (one platform\n // for every doc; only safe when a surface sees just one doc source).\n // 3. baseRoute — the host serves the doc viewer in-app → relative path nav.\n // else → null (no viewer configured → Ask-only).\n const safePath = sanitizePath(row.path)\n if (safePath) {\n const docTarget = row.documentType ? ctx.docPlatformTargets?.[row.documentType] : undefined\n if (docTarget) {\n // Trim leading/trailing (and collapse empty) segments WITHOUT a regex — the\n // slash-stripping regex `/^\\/+|\\/+$/g` tripped CodeQL's js/polynomial-redos (high)\n // since `\\/+$` backtracks on inputs with many '/'. split/filter/join is linear.\n const seg = docTarget.basePath.split('/').filter(Boolean).join('/')\n const base = `${getBaseUrl(docTarget.platform)}${seg ? `/${seg}` : ''}/`\n href = safeHref(new URL(safePath, base).toString()) ?? null\n targetPlatform = docTarget.platform\n } else if (ctx.chipBasePlatform) {\n const base = `${getBaseUrl(ctx.chipBasePlatform)}/knowledge-base/`\n href = safeHref(new URL(safePath, base).toString()) ?? null\n targetPlatform = ctx.chipBasePlatform\n } else if (ctx.baseRoute) {\n const synthetic = `https://_internal_.local${ctx.baseRoute.startsWith('/') ? ctx.baseRoute : '/' + ctx.baseRoute}/`\n const absolute = new URL(safePath, synthetic).toString()\n href = safeHref(absolute.replace('https://_internal_.local', '')) ?? null\n targetPlatform = ctx.currentPlatform ?? null\n }\n }\n } else if (ctx.composeContentUrl && row.documentType && row.externalUrl) {\n // Entity content WITH a public viewer → the unified content-href seam. The\n // host's hostedTypes/overrides route hosted types in-app (slug relativized\n // from the externalUrl) and everything else to the externalUrl verbatim —\n // the SAME resolver the page-view cards use, so chat + page links match.\n const composed = ctx.composeContentUrl({\n type: row.documentType,\n identifier: idValue,\n externalUrl: row.externalUrl,\n targetPlatform: row.targetPlatform ?? null,\n })\n href = composed.href ? (safeHref(composed.href) ?? null) : null\n targetPlatform = composed.targetPlatform\n } else if (row.externalUrl) {\n // No composer wired → legacy: the RAG externalUrl verbatim.\n href = safeHref(row.externalUrl) ?? null\n targetPlatform = row.targetPlatform ?? null\n }\n\n // Ask drill-in viability. (`idValue` computed above.)\n const askable = !!(idValue && row.documentType)\n const chatRef: ChatRef | null = askable\n ? ({\n type: row.documentType!,\n ...(sourceRepo ? { sourceRepo } : {}),\n id: idValue,\n title: row.title,\n url: href,\n targetPlatform,\n // Carry `path` so a downstream inline card (rendered from this\n // chatRef on a subsequent turn) gets the same doc-tree-swap\n // routing the chip already used. Without it, the Ask drill-in\n // would resolve to a same-tab full-page nav even when an in-app\n // path is available.\n ...(row.path ? { metadata: { path: row.path } } : {}),\n } as ChatRef)\n : null\n\n return { icon, iconLabel, href, targetPlatform, askable, chatRef }\n}\n\n/**\n * Convenience helper for callers that just need the source icon —\n * inline cards' tracking strip + the search-bar dropdown row, today.\n *\n * Single source of truth: every consumer (chips, search results,\n * inline cards) routes through `SOURCE_ICON_NAMES` + `ICON_REGISTRY`.\n *\n * Accepts a string for backward-compatibility with the single-arg call\n * shape — treated as `documentType`.\n */\nexport function resolveSourceIcon(\n input: { sourceRepo?: string | null; documentType?: string | null } | string | null | undefined,\n ctx: { tableIdForDocumentType?: (documentType: string) => string | null } = {},\n): {\n Icon: React.ComponentType<{ className?: string }>\n label: string\n} {\n if (!input) return { Icon: FileText, label: 'Source' }\n const { sourceRepo, documentType } = typeof input === 'string'\n ? { sourceRepo: null, documentType: input }\n : input\n if (!sourceRepo && !documentType) return { Icon: FileText, label: 'Source' }\n const resolvedRepo = sourceRepo\n ?? (documentType && ctx.tableIdForDocumentType ? ctx.tableIdForDocumentType(documentType) : null)\n ?? null\n const { icon, iconLabel } = pickSourceIcon(resolvedRepo, documentType)\n return { Icon: icon, label: iconLabel }\n}\n","'use client'\n\nimport React from 'react'\nimport { cn } from '../../utils/cn'\nimport { Chevron02DownIcon } from '../icons-v2-generated'\nimport { ActionsMenuDropdown, type ActionsMenuGroup, type ActionsMenuItem } from './actions-menu'\nimport type { ButtonProps, SplitButtonIconAction } from './button'\nimport { Button, SplitButton } from './button'\n\nexport type PageActionButton = {\n /** Button label. Omit to render an icon-only button. */\n label?: string\n /** Accessible name. Required for icon-only buttons (when `label` is omitted). */\n ariaLabel?: string\n /** Click handler. Optional when `href` or `submenu` is provided. */\n onClick?: () => void\n icon?: React.ReactNode\n variant?: ButtonProps['variant']\n disabled?: boolean\n /**\n * For SplitButton actions (when `iconAction` is set): disables only the main\n * half. Combine with `iconAction.disabled` for icon-only disable. Ignored\n * for non-SplitButton actions.\n */\n mainDisabled?: boolean\n loading?: boolean\n /** Show action only on mobile (below md). Default: visible on all screens. */\n showOnlyMobile?: boolean\n /**\n * Render the desktop button as icon-only (label hidden, icon centered). The full\n * label still appears in the mobile \"...\" dropdown. The desktop icon is forced to\n * `text-ods-text-primary`; the mobile row keeps the caller-provided icon color.\n */\n iconOnlyOnDesktop?: boolean\n /** Render as a link (next/link). Mutually exclusive with `submenu`. */\n href?: string\n /** Forwarded to next/link's prefetch. Only applies when `href` is set. */\n prefetch?: boolean\n /** Open link in a new tab. Only applies when `href` is set. */\n openInNewTab?: boolean\n /**\n * Render the action as a `SplitButton` (two independent click targets).\n * The main half runs `onClick`/`href`; the icon half runs its own action.\n * Mutually exclusive with `submenu`.\n */\n iconAction?: SplitButtonIconAction\n /**\n * Render a button with a chevron that opens a dropdown. The whole button is\n * a single click target — clicking anywhere opens the menu.\n * Mutually exclusive with `iconAction` and `href`/`onClick`.\n */\n submenu?: ActionsMenuItem[]\n}\n\nfunction actionKey(action: PageActionButton, idx: number) {\n return `${action.label ?? action.ariaLabel ?? 'action'}-${idx}`\n}\n\nfunction actionToMenuItems(action: PageActionButton, idx: number): ActionsMenuItem[] {\n if (action.submenu && action.submenu.length > 0) {\n // When a split-button action collapses into the merged mobile \"...\" menu,\n // its chevron disappears and its children become sibling rows. Prefix\n // each child with the parent label.\n if (!action.label) return action.submenu\n return action.submenu.map(item => ({\n ...item,\n label: `${action.label} (${item.label})`,\n }))\n }\n\n if (!action.label) return []\n return [{\n id: `action-${idx}`,\n label: action.label,\n icon: action.icon,\n onClick: action.onClick,\n disabled: action.disabled,\n href: action.href,\n iconAction: action.iconAction\n ? {\n icon: action.iconAction.icon,\n 'aria-label': action.iconAction['aria-label'],\n onClick: action.iconAction.onClick as (() => void) | undefined,\n href: action.iconAction.href,\n openInNewTab: action.iconAction.openInNewTab,\n disabled: action.iconAction.disabled,\n }\n : undefined,\n }]\n}\n\ninterface RenderOptions {\n /** Force the rendered button to be icon-only (label hidden). */\n iconOnly?: boolean\n /** Stretch the button to fill flex parent (used in mobile bottom bar). */\n fullWidth?: boolean\n}\n\nfunction renderActionButton(action: PageActionButton, opts: RenderOptions = {}): React.ReactNode {\n // Two-target SplitButton — primary action + secondary icon action.\n if (action.iconAction) {\n return (\n <SplitButton\n variant={action.variant ?? undefined}\n href={action.href}\n prefetch={action.prefetch}\n openInNewTab={action.openInNewTab}\n onClick={action.onClick}\n disabled={action.disabled}\n mainDisabled={action.mainDisabled}\n leftIcon={action.icon}\n fullWidth={opts.fullWidth}\n iconAction={action.iconAction}\n >\n {action.label}\n </SplitButton>\n )\n }\n\n // Submenu — single click target with a trailing chevron divider.\n if (action.submenu && action.submenu.length > 0) {\n return (\n <ActionsMenuDropdown\n groups={[{ items: action.submenu }]}\n customTrigger={\n <Button\n variant=\"outline\"\n disabled={action.disabled}\n loading={action.loading}\n leftIcon={action.icon}\n splitIcon={<Chevron02DownIcon className=\"h-4 w-4\" />}\n className={opts.fullWidth ? 'flex-1' : undefined}\n >\n {action.label}\n </Button>\n }\n />\n )\n }\n\n // Icon-only button (no label, or explicitly icon-only on desktop).\n const isIconOnly = opts.iconOnly || !action.label || action.iconOnlyOnDesktop\n if (isIconOnly) {\n const iconNode = action.iconOnlyOnDesktop\n ? <span className=\"inline-flex [&_svg]:!text-ods-text-primary\">{action.icon}</span>\n : action.icon\n return (\n <Button\n variant={action.variant}\n size=\"icon\"\n href={action.href}\n prefetch={action.prefetch}\n openInNewTab={action.openInNewTab}\n onClick={action.onClick}\n disabled={action.disabled}\n loading={action.loading}\n leftIcon={iconNode}\n aria-label={action.label ?? action.ariaLabel}\n />\n )\n }\n\n // Default labeled button.\n return (\n <Button\n variant={action.variant}\n href={action.href}\n prefetch={action.prefetch}\n openInNewTab={action.openInNewTab}\n onClick={action.onClick}\n disabled={action.disabled}\n loading={action.loading}\n leftIcon={action.icon}\n className={opts.fullWidth ? 'flex-1' : undefined}\n >\n {action.label}\n </Button>\n )\n}\n\nexport interface PageActionsProps {\n variant?: 'icon-buttons' | 'primary-buttons' | 'menu-primary'\n actions: PageActionButton[]\n menuActions?: ActionsMenuGroup[]\n /**\n * Desktop-only slot rendered before the action buttons (e.g. a `TabSelector`\n * for view-mode toggles). Hidden on mobile and never merged into the \"…\" menu.\n * Honored by the `icon-buttons` and `menu-primary` variants.\n */\n selector?: React.ReactNode\n className?: string\n}\n\nconst ACTIONS_GAP = 'gap-[var(--spacing-system-xs)]'\n\nexport function PageActions({\n variant = 'icon-buttons',\n actions,\n menuActions,\n selector,\n className,\n}: PageActionsProps) {\n if (variant === 'icon-buttons') {\n return <IconButtonsVariant actions={actions} menuActions={menuActions} selector={selector} className={className} />\n }\n\n if (variant === 'menu-primary') {\n return <MenuPrimaryVariant actions={actions} menuActions={menuActions || []} selector={selector} className={className} />\n }\n\n return <PrimaryButtonsVariant actions={actions} className={className} />\n}\n\nfunction IconButtonsVariant({\n actions,\n menuActions,\n selector,\n className,\n}: {\n actions: PageActionButton[]\n menuActions?: ActionsMenuGroup[]\n selector?: React.ReactNode\n className?: string\n}) {\n const desktopActions = actions.filter(a => !a.showOnlyMobile)\n const hasMenuActions = !!menuActions && menuActions.some(g => g.items.length > 0)\n\n const isSingleAction = actions.length === 1 && !actions[0].submenu?.length\n const singleAction = isSingleAction ? actions[0] : null\n const useSingleActionMobile = isSingleAction && !hasMenuActions\n\n return (\n <>\n {/* Desktop: every action as an icon button + optional overflow menu */}\n <div className={cn('hidden md:flex items-center', ACTIONS_GAP, className)}>\n {selector}\n {desktopActions.map((action, idx) => (\n <React.Fragment key={actionKey(action, idx)}>\n {renderActionButton(action)}\n </React.Fragment>\n ))}\n {hasMenuActions && <ActionsMenuDropdown groups={menuActions} />}\n </div>\n\n {/* Mobile: single icon button OR all actions merged into one \"...\" menu */}\n <div className={cn('flex md:hidden', className)}>\n {useSingleActionMobile && singleAction ? (\n renderActionButton(singleAction, { iconOnly: true })\n ) : (\n <ActionsMenuDropdown\n groups={[\n { items: actions.flatMap(actionToMenuItems) },\n ...(menuActions ?? []),\n ]}\n />\n )}\n </div>\n </>\n )\n}\n\n/**\n * Primary buttons variant — primary + outline buttons on desktop,\n * fixed bottom bar on mobile.\n */\nfunction PrimaryButtonsVariant({\n actions,\n className,\n}: {\n actions: PageActionButton[]\n className?: string\n}) {\n // Sort: outline first, accent last (rightmost on desktop).\n const sortedActions = [...actions].sort((a, b) => {\n if (a.variant === 'accent' && b.variant !== 'accent') return 1\n if (a.variant !== 'accent' && b.variant === 'accent') return -1\n return 0\n })\n\n const desktopActions = sortedActions.filter(a => !a.showOnlyMobile)\n\n return (\n <>\n <div className={cn('hidden md:flex items-center', ACTIONS_GAP, className)}>\n {desktopActions.map((action, idx) => (\n <React.Fragment key={`desktop-${actionKey(action, idx)}`}>\n {renderActionButton(action)}\n </React.Fragment>\n ))}\n </div>\n\n <MobileBottomActions actions={sortedActions} />\n </>\n )\n}\n\n/**\n * Menu + primary variant — \"...\" menu + primary buttons on desktop,\n * all actions merged into a single \"...\" menu on mobile.\n */\nfunction MenuPrimaryVariant({\n actions,\n menuActions,\n selector,\n className,\n}: {\n actions: PageActionButton[]\n menuActions: ActionsMenuGroup[]\n selector?: React.ReactNode\n className?: string\n}) {\n const desktopActions = actions.filter(a => !a.showOnlyMobile)\n const hasMenuActions = menuActions.some(g => g.items.length > 0)\n\n return (\n <>\n <div className={cn('hidden md:flex items-center', ACTIONS_GAP, className)}>\n {selector}\n {hasMenuActions && <ActionsMenuDropdown groups={menuActions} />}\n {desktopActions.map((action, idx) => (\n <React.Fragment key={`desktop-${actionKey(action, idx)}`}>\n {renderActionButton({ ...action, variant: action.variant || 'accent' })}\n </React.Fragment>\n ))}\n </div>\n\n <div className={cn('flex md:hidden', className)}>\n <ActionsMenuDropdown\n groups={[\n { items: actions.flatMap(actionToMenuItems) },\n ...menuActions,\n ]}\n />\n </div>\n </>\n )\n}\n\nfunction MobileBottomActions({ actions }: { actions: PageActionButton[] }) {\n return (\n <div className={cn(\n 'fixed md:hidden bottom-0 left-0 right-0 z-50',\n 'bg-ods-card border-t border-ods-border',\n 'flex items-start pt-6 pb-6 px-6',\n ACTIONS_GAP,\n )}>\n {actions.map((action, idx) => (\n <React.Fragment key={`mobile-${actionKey(action, idx)}`}>\n {renderActionButton(action, { fullWidth: !!action.label })}\n </React.Fragment>\n ))}\n </div>\n )\n}\n\nexport function usePageActionsBottomPadding(variant: PageActionsProps['variant']) {\n return variant === 'primary-buttons' || variant === 'menu-primary' ? 'pb-40 md:pb-0' : ''\n}\n\nexport default PageActions\n","'use client'\n\nimport React from 'react'\nimport { cn } from '../../utils/cn'\nimport { getFirstLastInitials } from '../../utils/format'\n\nexport interface EntityImageProps {\n src?: string | null\n alt?: string\n /** Overrides the initials source. Defaults to `alt`. */\n fallbackText?: string\n className?: string\n}\n\nexport function EntityImage({ src, alt, fallbackText, className }: EntityImageProps) {\n const [imageFailed, setImageFailed] = React.useState(false)\n\n React.useEffect(() => {\n setImageFailed(false)\n }, [src])\n\n const showFallback = imageFailed || !src\n const initials = getFirstLastInitials(fallbackText ?? alt)\n\n if (showFallback) {\n return (\n <div\n aria-label={alt}\n className={cn(\n 'size-[52px] md:size-[60px] shrink-0 rounded-md border border-ods-border bg-ods-bg flex items-center justify-center text-ods-text-secondary text-h4 select-none',\n className,\n )}\n >\n {initials || '?'}\n </div>\n )\n }\n\n return (\n <img\n src={src ?? undefined}\n alt={alt ?? ''}\n onError={() => setImageFailed(true)}\n className={cn(\n 'size-[52px] md:size-[60px] shrink-0 rounded-md border border-ods-border object-contain',\n className,\n )}\n />\n )\n}\n","'use client'\n\nimport React from 'react'\nimport { cn } from '../../utils/cn'\nimport type { ActionsMenuGroup } from '../ui/actions-menu'\nimport { EntityImage } from '../ui/entity-image'\nimport { PageActions, type PageActionButton } from '../ui/page-actions'\nimport { BackButton } from './back-button'\n\nexport interface TitleBlockProps {\n title?: string\n subtitle?: string\n image?: { src: string; alt?: string }\n backButton?: { label?: string; onClick: () => void }\n actions?: PageActionButton[]\n actionsVariant?: 'icon-buttons' | 'primary-buttons' | 'menu-primary'\n menuActions?: ActionsMenuGroup[]\n /** Desktop-only slot (e.g. a `TabSelector`) rendered with the actions. Hidden on mobile. */\n selector?: React.ReactNode\n /**\n * Visual variant.\n * - `plain` (default): transparent background, no border.\n * - `card`: card background, border, and padding on mobile only — collapses to plain on md+.\n */\n variant?: 'plain' | 'card'\n className?: string\n}\n\nexport function TitleBlock({\n title,\n subtitle,\n image,\n backButton,\n actions,\n actionsVariant = 'icon-buttons',\n menuActions,\n selector,\n variant = 'plain',\n className,\n}: TitleBlockProps) {\n const hasActions = actions && actions.length > 0\n const hasMenuActions = !!menuActions && menuActions.some(g => g.items.length > 0)\n\n return (\n <div\n className={cn(\n 'flex items-end justify-between gap-[var(--spacing-system-m)]',\n 'md:flex-col md:items-start md:justify-start lg:flex-row lg:items-end lg:justify-between',\n 'pt-[var(--spacing-system-l)]',\n variant === 'card'\n ? cn(\n 'bg-ods-card border-b border-ods-border',\n 'px-[var(--spacing-system-l)] pb-[var(--spacing-system-l)]',\n 'md:bg-transparent md:border-b-0',\n 'md:px-0 md:pb-0',\n 'md:mb-[var(--spacing-system-l)]',\n )\n : 'mb-[var(--spacing-system-l)]',\n className,\n )}\n >\n <div className=\"flex flex-col gap-[var(--spacing-system-xs)] flex-1 min-w-0\">\n {backButton && (\n <BackButton\n onClick={backButton.onClick}\n label={backButton.label}\n className=\"hidden md:inline-flex\"\n />\n )}\n {(image || subtitle) ? (\n <div className=\"flex items-center gap-[var(--spacing-system-m)] min-w-0 w-full\">\n {image && (\n <EntityImage\n src={image.src}\n alt={image.alt}\n fallbackText={image.alt || title}\n />\n )}\n <div className=\"flex flex-col justify-center min-w-0 flex-1\">\n {title && (\n <h1 className=\"text-h2 text-ods-text-primary truncate\" title={title}>{title}</h1>\n )}\n {subtitle && (\n <p className=\"text-h6 text-ods-text-secondary truncate\" title={subtitle}>{subtitle}</p>\n )}\n </div>\n </div>\n ) : (\n title && <h1 className=\"text-h2 text-ods-text-primary\">{title}</h1>\n )}\n </div>\n\n {(hasActions || hasMenuActions || selector) && (\n <div className=\"flex gap-2 items-center shrink-0\">\n <PageActions\n variant={actionsVariant}\n actions={actions ?? []}\n menuActions={menuActions}\n selector={selector}\n />\n </div>\n )}\n </div>\n )\n}\n\nexport default TitleBlock\n","'use client'\n\nimport React from 'react'\nimport { cn } from '../../utils/cn'\nimport { Chevron02LeftIcon } from '../icons-v2-generated/arrows/chevron-02-left-icon'\n\nexport interface BackButtonProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {\n label?: string\n onClick?: React.MouseEventHandler<HTMLButtonElement>\n}\n\nexport function BackButton({ label = 'Back', className, type = 'button', ...props }: BackButtonProps) {\n return (\n <button\n type={type}\n className={cn(\n 'group inline-flex items-center justify-center self-start rounded-md',\n 'gap-[var(--spacing-system-xsf)] py-[var(--spacing-system-sf)]',\n 'text-ods-text-secondary hover:text-ods-text-primary',\n 'transition-colors duration-200',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus',\n className,\n )}\n {...props}\n >\n <Chevron02LeftIcon className=\"size-6 shrink-0\" />\n <span className=\"text-h4\">{label}</span>\n </button>\n )\n}\n\nexport default BackButton\n","'use client'\n\nimport React from 'react'\nimport { cn } from '../../utils/cn'\nimport type { ActionsMenuGroup } from '../ui/actions-menu'\nimport { type PageActionButton } from '../ui/page-actions'\nimport { TitleBlock } from './title-block'\n\nexport interface PageLayoutProps {\n children: React.ReactNode\n title?: string\n subtitle?: string\n image?: { src: string; alt?: string }\n backButton?: { label?: string; onClick: () => void }\n actions?: PageActionButton[]\n actionsVariant?: 'icon-buttons' | 'primary-buttons' | 'menu-primary'\n menuActions?: ActionsMenuGroup[]\n /** Desktop-only slot (e.g. a `TabSelector`) rendered with the actions. Hidden on mobile. */\n selector?: React.ReactNode\n /** Header visual variant. `card` adds a card background, border, and padding on mobile. */\n headerVariant?: 'plain' | 'card'\n className?: string\n contentClassName?: string\n showHeader?: boolean\n}\n\n/**\n * Page layout container with consistent spacing, header, and actions.\n *\n * Uses `--spacing-system-l` as the gap between sections.\n */\nexport function PageLayout({\n children,\n title,\n subtitle,\n image,\n backButton,\n actions,\n actionsVariant = 'icon-buttons',\n menuActions,\n selector,\n headerVariant,\n className,\n contentClassName,\n showHeader = true,\n}: PageLayoutProps) {\n const hasActions = actions && actions.length > 0\n const needsBottomPadding = hasActions && actionsVariant === 'primary-buttons'\n const hasHeader = showHeader && (title || subtitle || image || backButton || hasActions || selector)\n\n return (\n <div className={cn('flex flex-col w-full', className)}>\n {hasHeader && (\n <TitleBlock\n title={title}\n subtitle={subtitle}\n image={image}\n backButton={backButton}\n actions={actions}\n actionsVariant={actionsVariant}\n menuActions={menuActions}\n selector={selector}\n variant={headerVariant}\n />\n )}\n\n <div className={cn('flex flex-col flex-1 gap-[var(--spacing-system-l)]', needsBottomPadding && 'pb-28 md:pb-0', contentClassName)}>\n {children}\n </div>\n </div>\n )\n}\n\nexport type { PageActionButton } from '../ui/page-actions'\nexport { TitleBlock } from './title-block'\nexport type { TitleBlockProps } from './title-block'\nexport default PageLayout\n","import React from 'react';\nimport { cn } from '../../utils/cn';\n\n/**\n * Unified page layout components.\n *\n * PageShell: full-width <main> for list pages, dashboards, and other wide\n * layouts (max-w-[1920px]) — bg + min-height + centered, max-width content box.\n * ArticleDetailLayout: the same shell constrained to a readable article width\n * (max-w-[1280px]) — blog posts, release notes, case studies, etc.\n * Child components render content only; no layout wrappers needed.\n *\n * Outer padding is driven by CSS custom properties (see\n * `styles/ods-page-shell.css`), so an embedder matches its host grid by setting\n * the `--page-shell-*` vars on ANY ancestor — no prop-threading through the lib\n * wrappers (DevSectionPage / LegalDocumentPage / ReleaseDetailPage / …), no\n * imperative global, no context. The cascade scopes each override to its own\n * subtree. Per-instance escape hatch: `contentClassName` (Tailwind padding\n * utilities there win over the class defaults).\n */\n\ninterface LayoutProps {\n children: React.ReactNode;\n /** JSON-LD schema script elements (breadcrumbs, article schema, etc.) */\n schemas?: React.ReactNode;\n /** Per-instance class override on the content box (wins over the var defaults). */\n contentClassName?: string;\n}\n\n/**\n * Full-width page shell for list pages, dashboards, and other wide layouts.\n * Max width: 1920px.\n */\nexport function PageShell({ children, schemas, contentClassName }: LayoutProps) {\n return (\n <main className=\"bg-ods-bg min-h-screen\">\n {schemas}\n <div className={cn('page-shell-content max-w-[1920px] mx-auto', contentClassName)}>\n {children}\n </div>\n </main>\n );\n}\n\n/**\n * Constrained layout for article/detail pages (max-w-[1280px]) — readable\n * content width for blog posts, release notes, case studies, investor updates.\n * Fixed hub padding (`px-6 md:px-20 py-6 md:py-10`); pass `contentClassName` to\n * override per instance. (PageShell's `--page-shell-*` var system does NOT apply\n * here — this layout keeps its own fixed spacing.)\n */\nexport function ArticleDetailLayout({ children, schemas, contentClassName }: LayoutProps) {\n return (\n <main className=\"bg-ods-bg min-h-screen\">\n {schemas}\n <div className={cn('max-w-[1280px] mx-auto px-6 md:px-20 py-6 md:py-10', contentClassName)}>\n {children}\n </div>\n </main>\n );\n}\n","/**\n * The SINGLE content-href authority for every embeddable surface — the\n * `ChatRuntime.composeContentUrl` seam. Page views (onboarding catalog/detail,\n * product releases) AND chat surfaces (entity cards, source chips, search\n * results) all resolve a content link through this one function, so a given\n * content type lands in the SAME place no matter where it's rendered.\n *\n * ## Why this exists\n *\n * Before unification there were two link builders: `composeContentUrl` (pages)\n * and `resolveSourceRowCTA` (chat), and only the former honored the embedder's\n * in-app routing config — so the same `product_release` soft-navigated in-app on\n * the releases page but opened OUT to the hub as a chat card. Now `resolveSource-\n * RowCTA` delegates its href to this seam, so one config (`hostedTypes` /\n * `overrides`) governs internal-vs-external for pages and chat alike.\n *\n * `makeComposeContentUrl` builds the embedder default from a small config: the\n * set of types THIS host serves in-app (→ relative href, soft-navigates) vs.\n * everything else (→ the canonical hub URL, opens out). The hub wires its own\n * composer (`composeContentUrlFromPlatforms` — cross-platform topology) to the\n * same seam. Pure + server-safe (no React, no browser APIs).\n *\n * composeContentUrl: makeComposeContentUrl({\n * hostedTypes: new Set(['onboarding_guide', 'product_release']),\n * contentOrigin: VITE_HUB_ORIGIN,\n * })\n */\n\n/**\n * Type → in-app route suffix. The public-hostable subset of the hub's\n * `PUBLIC_URL_PATHS` (`lib/utils/content-url-builder.ts`); the hub keeps\n * its own copy, this is the embedder default and must stay in sync. (The\n * cross-repo boundary makes a shared import impossible; the lib test pins this\n * constant against a literal copy of itself, so it does NOT detect hub-side\n * drift — values match today and are kept aligned by hand.)\n */\nexport const DEFAULT_CONTENT_SUFFIXES: Record<string, string> = {\n onboarding_guide: 'onboarding-guides',\n product_release: 'releases',\n blog_post: 'blog',\n case_study: 'case-studies',\n customer_interview: 'interviews',\n investor_update: 'investor-updates',\n webinar: 'webinars',\n podcast: 'podcasts',\n event: 'events',\n // Author profile pages (`/authors/<slug>`) — identified by the profile\n // slug. Mirrors the hub's `PUBLIC_URL_PATHS.author`, which derives from\n // `AUTHORS_PATH` in the hub's `lib/utils/breadcrumbs.ts` (the hub-side\n // SSOT for the segment).\n author: 'authors',\n}\n\n/** Input to the unified content-href seam. ONE object covers both callers:\n * page views pass `type` + `identifier` (the slug); chat rows pass `type` +\n * `identifier` (the primary-key id) + `externalUrl` (the canonical hub URL,\n * from which the slug is recovered for in-app routing). */\nexport interface ComposeContentUrlInput {\n /** The content's documentType (e.g. `'product_release'`, `'blog_post'`). */\n type: string\n /** Content identifier. Page views pass the slug; chat rows pass the\n * primary-key id (the slug is recovered from `externalUrl` when hosted). */\n identifier: string\n /** Hydrated platform junction (page views). Unused by the embedder default\n * — `hostedTypes` membership decides in-vs-out — but threaded for the hub\n * composer's cross-platform topology. */\n platforms?: Array<{ name?: string }>\n /** The canonical hub URL when the caller already has it (chat entity rows\n * carry it from the RAG mapper). Hosted types relativize it to an in-app\n * path; non-hosted types use it verbatim (authoritative). Absent for pages. */\n externalUrl?: string | null\n /** Platform that owns `externalUrl` (chat rows). Passed through on the\n * non-hosted branch. */\n targetPlatform?: string | null\n}\n\nexport interface ContentHrefOptions {\n /** Types THIS host serves in-app → relative href (soft-nav). Everything\n * else resolves to the row's `externalUrl` / `contentOrigin` (opens out). */\n hostedTypes: ReadonlySet<string>\n /** Hub origin for non-hosted types with no `externalUrl` (e.g.\n * `https://openframe.app`). */\n contentOrigin: string\n /** Per-type route suffix. Defaults to {@link DEFAULT_CONTENT_SUFFIXES}. */\n suffixes?: Record<string, string>\n /** Per-type full override — wins over the suffix logic. Receives the same\n * `identifier` the seam was called with. */\n overrides?: Record<string, (identifier: string) => { href: string; targetPlatform: string | null }>\n}\n\n/** The unified `composeContentUrl` seam shape on `ChatRuntime`. ALWAYS returns\n * a tuple (never null) — the seam type is non-nullable and callers read\n * `.href` unconditionally. */\nexport type ComposeContentUrl = (\n input: ComposeContentUrlInput,\n) => { href: string; targetPlatform: string | null }\n\n/** Last non-empty path segment of a URL (relative or absolute) — the content\n * slug of a canonical detail URL like `https://hub/releases/my-release`.\n * Returns `null` on a malformed URL or empty path. */\nfunction lastPathSegment(url: string): string | null {\n try {\n // Resolve against a dummy base so relative inputs parse too.\n const pathname = new URL(url, 'https://_.local').pathname\n const segs = pathname.split('/').filter(Boolean)\n return segs.length > 0 ? segs[segs.length - 1] : null\n } catch {\n return null\n }\n}\n\n/**\n * Build the embedder's `composeContentUrl` for the unified seam.\n *\n * Resolution order (the merged rule used by BOTH page views and chat cards):\n * 1. `overrides[type]` — explicit per-type href.\n * 2. `hostedTypes.has(type)` → relative `/<suffix>/<slug>` (in-app, soft-nav).\n * Chat rows carry the hub URL not the slug, so the slug is recovered from\n * `externalUrl`; page views pass the slug as `identifier`.\n * 3. `externalUrl` present → use it verbatim (RAG-authoritative hub URL; the\n * chat non-hosted case).\n * 4. else → `${contentOrigin}/<suffix>/<identifier>` (page-view non-hosted).\n *\n * In-vs-out is decided by `hostedTypes` membership — NOT platform equality,\n * since an embedder has a free-form `source`. The `platforms` arg is part of the\n * seam signature but unused here.\n */\nexport function makeComposeContentUrl(opts: ContentHrefOptions): ComposeContentUrl {\n const suffixes = opts.suffixes ?? DEFAULT_CONTENT_SUFFIXES\n return ({ type, identifier, externalUrl, targetPlatform }) => {\n const override =\n opts.overrides && Object.prototype.hasOwnProperty.call(opts.overrides, type)\n ? opts.overrides[type]\n : undefined\n if (override) return override(identifier)\n\n const seg =\n (Object.prototype.hasOwnProperty.call(suffixes, type) ? suffixes[type] : undefined) ?? type\n\n if (opts.hostedTypes.has(type)) {\n // In-app (soft-nav). Recover the slug from the hub URL for chat rows;\n // page views already pass the slug as `identifier`. Guard: if the recovered\n // segment is the type's OWN suffix (e.g. a malformed/list externalUrl like\n // `https://hub/releases/` → `'releases'`), fall back to `identifier` instead\n // of emitting a nonsensical `/releases/releases`.\n const recovered = externalUrl ? lastPathSegment(externalUrl) : null\n const slug = recovered && recovered !== seg ? recovered : identifier\n return { href: `/${seg}/${slug}`, targetPlatform: null }\n }\n // Not hosted → opens out. Prefer the RAG-authoritative `externalUrl` (chat);\n // else compose against the hub origin (page views with no externalUrl).\n return externalUrl\n ? { href: externalUrl, targetPlatform: targetPlatform ?? null }\n : { href: `${opts.contentOrigin}/${seg}/${identifier}`, targetPlatform: null }\n }\n}\n\n/**\n * Default href shape when `runtime.composeContentUrl` is NOT wired\n * (single-platform embedders without cross-platform topology). Shared by\n * every catalog/detail view that composes a content link (onboarding guides,\n * product releases, …) so the no-composer fallback has ONE source — both\n * views pass their `basePath`-derived shape through here.\n */\nexport function buildDefaultHref(\n basePath: string,\n slug: string,\n): { href: string; targetPlatform: string | null } {\n return { href: `${basePath}/${slug}`, targetPlatform: null }\n}\n\n/**\n * Resolve a content link via the host's `composeContentUrl` when wired, else the\n * same-origin `buildDefaultHref` fallback — the exact ternary every catalog/detail page\n * VIEW repeated. Centralizes the `composeContentUrl` input shape + the fallback in one\n * place so a future input-shape change lands once, not per view.\n */\nexport function resolveContentHref(\n composeContentUrl: ComposeContentUrl | undefined,\n args: { type: string; slug: string; basePath: string; platforms?: ComposeContentUrlInput['platforms'] },\n): { href: string; targetPlatform: string | null } {\n return composeContentUrl\n ? composeContentUrl({ type: args.type, identifier: args.slug, platforms: args.platforms })\n : buildDefaultHref(args.basePath, args.slug)\n}\n","\"use client\"\n\nimport React from 'react'\nimport { AlertTriangle, RefreshCw, Home } from 'lucide-react'\nimport { Button } from './button'\nimport { cn } from '../../utils/cn'\n\ninterface ErrorStateProps {\n title?: string\n message: string\n variant?: 'error' | 'warning' | 'info'\n showIcon?: boolean\n showRetry?: boolean\n showHome?: boolean\n onRetry?: () => void\n onHome?: () => void\n className?: string\n containerClassName?: string\n}\n\nexport function ErrorState({\n title = 'Error',\n message,\n variant = 'error',\n showIcon = true,\n showRetry = false,\n showHome = false,\n onRetry,\n onHome,\n className,\n containerClassName\n}: ErrorStateProps) {\n const getVariantStyles = () => {\n switch (variant) {\n case 'error':\n return {\n bg: 'bg-ods-attention-red-error/20',\n border: 'border-ods-attention-red-error',\n text: 'text-ods-attention-red-error',\n icon: 'text-ods-attention-red-error'\n }\n case 'warning':\n return {\n bg: 'bg-ods-attention-yellow-warning/20',\n border: 'border-ods-attention-yellow-warning',\n text: 'text-ods-attention-yellow-warning',\n icon: 'text-ods-attention-yellow-warning'\n }\n case 'info':\n return {\n bg: 'bg-ods-bg-surface',\n border: 'border-ods-border',\n text: 'text-ods-text-secondary',\n icon: 'text-ods-text-secondary'\n }\n }\n }\n\n const styles = getVariantStyles()\n\n return (\n <div className={cn(\"p-6\", containerClassName)}>\n <div className={cn(\n \"rounded-lg p-4 border\",\n styles.bg,\n styles.border,\n className\n )}>\n <div className=\"flex items-start gap-3\">\n {showIcon && (\n <AlertTriangle className={cn(\"h-5 w-5 mt-0.5 flex-shrink-0\", styles.icon)} />\n )}\n <div className=\"flex-1\">\n <h3 className={cn(\"font-semibold mb-1\", styles.text)}>\n {title}\n </h3>\n <p className={cn(\"text-sm\", styles.text)}>\n {message}\n </p>\n {(showRetry || showHome) && (\n <div className=\"flex gap-2 mt-3\">\n {showRetry && onRetry && (\n <Button\n onClick={onRetry}\n variant=\"outline\"\n size=\"small-legacy\"\n className=\"h-8\"\n leftIcon={<RefreshCw className=\"h-4 w-4\" />}\n >\n Try Again\n </Button>\n )}\n {showHome && onHome && (\n <Button\n onClick={onHome}\n variant=\"outline\"\n size=\"small-legacy\"\n className=\"h-8\"\n leftIcon={<Home className=\"h-4 w-4\" />}\n >\n Go Home\n </Button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// Convenience components for common error scenarios\nexport function PageError({ message, onRetry, onHome }: { message: string; onRetry?: () => void; onHome?: () => void }) {\n return (\n <ErrorState\n title=\"Page Error\"\n message={message}\n variant=\"error\"\n showRetry={!!onRetry}\n showHome={!!onHome}\n onRetry={onRetry}\n onHome={onHome}\n />\n )\n}\n\nexport function LoadError({ message, onRetry }: { message: string; onRetry?: () => void }) {\n return (\n <ErrorState\n title=\"Loading Error\"\n message={message}\n variant=\"error\"\n showRetry={!!onRetry}\n onRetry={onRetry}\n />\n )\n}\n\nexport function NotFoundError({ message = \"The requested item was not found\", onHome }: { message?: string; onHome?: () => void }) {\n return (\n <ErrorState\n title=\"Not Found\"\n message={message}\n variant=\"warning\"\n showHome={!!onHome}\n onHome={onHome}\n />\n )\n}","import Link from 'next/link'\nimport { StatusBadge } from '../ui/status-badge'\nimport type { TagAssoc } from '../../types/blog'\n\ninterface EntityTagBadgesProps {\n /** Flat `<entity>_tags[]` association array (from entity_tags hydration). */\n tags?: TagAssoc[] | null\n /**\n * When set, each badge links to `${basePath}?tags=${slug}` (clickable, SPA nav\n * via next/link). Omit for a non-interactive display (badges with no slug also\n * render non-interactive even when basePath is set).\n */\n basePath?: string\n /** Cap visible badges; the remainder collapse into a \"+N\" badge. */\n max?: number\n /** Extra classes on the wrapper row. */\n className?: string\n}\n\n// The ONE tag-badge skin (OpenFrame design): rounded-rect, font-mono uppercase\n// StatusBadge on ods-card + ods-border. Clickable badges add the accent hover.\n// Exported so other tag-shaped chips (e.g. FaqAccordion's section badge)\n// render the IDENTICAL skin without re-declaring it.\nexport const TAG_BADGE_CLASS = 'bg-ods-card border border-ods-border'\nconst BADGE_CLASS = TAG_BADGE_CLASS\n\n/**\n * THE single tag-badge renderer for the whole product (hub + lib). Renders the\n * OpenFrame `StatusBadge` chip skin for a flat `<entity>_tags[]` array, optionally\n * clickable (links each tag to its filtered list via `basePath`). The hub's\n * `EntityTagList` delegates here so there is exactly one tag-display design.\n * Renders nothing when there are no tags.\n */\nexport function EntityTagBadges({ tags, basePath, max, className }: EntityTagBadgesProps) {\n const items = (tags || []).filter((t): t is TagAssoc => !!t && !!t.name)\n if (items.length === 0) return null\n\n // Only null/undefined means \"no cap\"; max={0} shows none, negatives clamp to 0.\n const visibleLimit = max == null ? items.length : Math.max(0, max)\n const shown = items.slice(0, visibleLimit)\n const overflow = items.length - shown.length\n\n return (\n <div className={`flex flex-wrap items-center gap-2 w-full ${className || ''}`}>\n {shown.map((tag) => {\n const key = tag.tag_id ?? tag.id ?? tag.slug ?? tag.name\n const label = (tag.name || '').toUpperCase()\n if (basePath && tag.slug) {\n return (\n <Link key={key} href={`${basePath}?tags=${tag.slug}`} className=\"inline-flex\">\n <StatusBadge\n text={label}\n variant=\"card\"\n className={`${BADGE_CLASS} cursor-pointer transition-colors hover:border-ods-accent`}\n />\n </Link>\n )\n }\n return <StatusBadge key={key} text={label} variant=\"card\" className={BADGE_CLASS} />\n })}\n {overflow > 0 && (\n <StatusBadge text={`+${overflow}`} variant=\"card\" className={`${BADGE_CLASS} text-ods-text-secondary`} />\n )}\n </div>\n )\n}\n","/**\n * Mux CDN origin constants — single source of truth, server-safe.\n *\n * Lives in its own NON-`'use client'` module so server-side hub\n * modules (webhook handlers, URL builders, hostname comparisons in\n * `lib/config/mux-config.ts`) can import these strings without\n * tripping Next.js's client-reference poisoning. Re-exported from\n * `use-video-warmup.ts` for backward-compat with client-side callers.\n *\n * Bug history (2026-05-29): when these constants lived in\n * `use-video-warmup.ts` (which is `'use client'`), the hub's\n * server-side `new URL(MUX_STREAM_ORIGIN).hostname` evaluation crashed\n * at Vercel build with `TypeError: Invalid URL` — Next.js had\n * replaced the constant with a client-function stub that throws\n * \"Attempted to call ... from the server\" when stringified. Splitting\n * the constants into this module restores the server-safe path.\n *\n * These hostnames are part of Mux's public API contract and are\n * stable. A future change to the Mux CDN architecture (extremely\n * unlikely) would be a single-line edit here.\n */\n\n/** HLS playback (`/{playback_id}.m3u8` + segments + per-asset MP4 renditions). */\nexport const MUX_STREAM_ORIGIN = 'https://stream.mux.com'\n\n/** Server-generated thumbnails (`/{playback_id}/thumbnail.jpg`). */\nexport const MUX_IMAGE_ORIGIN = 'https://image.mux.com'\n","'use client'\n\n/**\n * `useVideoWarmup` — single-source-of-truth hook for warming the\n * network path to a public entity's main video so click→first-frame\n * lands in sub-second on Fast 4G.\n *\n * Behavior:\n *\n * 1. **Preconnect on every render** (`ReactDOM.preconnect`) — buys\n * the TCP / TLS handshakes to the video-bearing origins. React\n * 19 de-dupes identical preconnects, so this is safe to call\n * on every render.\n *\n * 2. **Preload the video bytes** (`<link rel=\"preload\" as=\"video\">`)\n * ONLY when:\n * - the consumer's container scrolls within `nearMargin` of\n * the viewport (gated via the lib's IO singleton hook), AND\n * - `navigator.connection?.saveData !== true`, AND\n * - the URL is on the Supabase storage origin (Mux HLS warms\n * via its own manifest fetch when MuxPlayer mounts; YouTube\n * has its own origin pool, no preload benefit).\n *\n * Origin configuration:\n * - Mux origins (`stream.mux.com` / `image.mux.com`) are public\n * Mux CDN hostnames and stable across the Mux API contract —\n * hardcoded here.\n * - Supabase storage origin varies per-deployment (different\n * project per env). Threaded via the `supabaseStorageOrigin`\n * argument so the lib stays env-agnostic; hub callers pass\n * `getSupabaseStorageOrigin()` from their env config, or read\n * it from `ChatRuntime.endpoints.supabaseStorageOrigin`.\n *\n * Lifted from hub `hooks/use-video-warmup.ts`. The Mux constants\n * and the IO-gated preload semantics are byte-equivalent.\n */\n\nimport { useEffect } from 'react'\nimport ReactDOM from 'react-dom'\nimport { useNearViewport } from '../../hooks/use-near-viewport'\nimport { useChatRuntime } from '../../contexts/chat-runtime-context'\n// Re-export from the server-safe `mux-origins.ts` module so the\n// constants are NOT bound to this `'use client'` file. See the\n// JSDoc in `mux-origins.ts` for the bug history. Backward-compat:\n// existing imports that read `MUX_STREAM_ORIGIN` from\n// `@flamingo-stack/openframe-frontend-core/components/features`\n// continue to resolve through this re-export.\nexport { MUX_STREAM_ORIGIN, MUX_IMAGE_ORIGIN } from './mux-origins'\nimport { MUX_STREAM_ORIGIN, MUX_IMAGE_ORIGIN } from './mux-origins'\n\n/**\n * Preconnect-only variant — fires the three video-bearing origin\n * preconnects (Supabase Storage + Mux stream + Mux image) without\n * setting up the IntersectionObserver subscription or the preload\n * `<link>` injection.\n *\n * Use this when the consumer can't attach a `ref` to the video\n * container (e.g. release detail page, which delegates the player\n * render to a sibling component). Calling the full `useVideoWarmup`\n * from there would subscribe to a never-mounted ref and ship dead\n * preload machinery in the bundle.\n *\n * For consumers that own the video container, use `useVideoWarmup`\n * (which composes this hook + the IO-gated preload step).\n *\n * Reads `supabaseStorageOrigin` from `ChatRuntime.endpoints` by\n * default — callers in hosts that mount `HubRuntimeProvider` (or\n * any equivalent provider that wires the field) get the origin\n * automatically. The explicit `supabaseStorageOrigin` argument\n * overrides the runtime value when set.\n */\nexport function useVideoOriginPreconnect({\n supabaseStorageOrigin,\n}: { supabaseStorageOrigin?: string } = {}): void {\n const runtime = useChatRuntime()\n const resolvedOrigin =\n supabaseStorageOrigin ?? runtime?.endpoints.supabaseStorageOrigin\n try {\n ReactDOM.preconnect(MUX_STREAM_ORIGIN, { crossOrigin: 'anonymous' })\n ReactDOM.preconnect(MUX_IMAGE_ORIGIN, { crossOrigin: 'anonymous' })\n if (resolvedOrigin) {\n ReactDOM.preconnect(resolvedOrigin, { crossOrigin: 'anonymous' })\n }\n } catch (err) {\n if (process.env.NODE_ENV !== 'production') {\n // eslint-disable-next-line no-console\n console.warn('[useVideoOriginPreconnect] preconnect failed:', err)\n }\n }\n}\n\ninterface UseVideoWarmupOptions {\n /**\n * Effective video URL the page renders. Pass null/undefined when\n * there's no video yet (the hook still preconnects). Only URLs on\n * `supabaseStorageOrigin` are preloaded — Mux HLS and YouTube are\n * no-ops on the preload side.\n */\n videoUrl?: string | null\n /**\n * Supabase storage origin (e.g. `https://xyz.supabase.co`). When\n * omitted, falls back to `ChatRuntime.endpoints.supabaseStorageOrigin`\n * — hosts that mount `HubRuntimeProvider` (or any equivalent\n * provider) get the origin automatically. When neither is set, the\n * preload step is skipped (preconnect to Mux still fires).\n */\n supabaseStorageOrigin?: string\n /**\n * IO root margin gate for the preload step. Default `'1000px'` —\n * about one viewport's worth of lookahead on desktop.\n */\n nearMargin?: string\n}\n\nexport interface UseVideoWarmupResult<T extends Element = HTMLDivElement> {\n ref: (node: T | null) => void\n isNear: boolean\n}\n\nexport function useVideoWarmup<T extends Element = HTMLDivElement>({\n videoUrl,\n supabaseStorageOrigin,\n nearMargin = '1000px',\n}: UseVideoWarmupOptions = {}): UseVideoWarmupResult<T> {\n // Resolve origin once — runtime fallback so callers in hosts that\n // mount `HubRuntimeProvider` don't need to thread it themselves.\n const runtime = useChatRuntime()\n const resolvedOrigin =\n supabaseStorageOrigin ?? runtime?.endpoints.supabaseStorageOrigin\n\n // Preconnect on every render — React 19 dedupes. Delegates to the\n // shared preconnect-only variant so the origin list is a single\n // source of truth.\n useVideoOriginPreconnect({ supabaseStorageOrigin: resolvedOrigin })\n\n const { ref, isNear } = useNearViewport<T>(nearMargin)\n\n useEffect(() => {\n if (!isNear || !videoUrl || !resolvedOrigin) return\n\n // Save-Data gate — metered connections skip preload.\n type Connection = { saveData?: boolean }\n const conn = (navigator as Navigator & { connection?: Connection }).connection\n if (conn?.saveData === true) return\n\n // Origin gate: only preload Supabase-hosted MP4s. Mux HLS warms\n // via the manifest fetch when MuxPlayer mounts; YouTube has no\n // preload benefit.\n let videoOrigin: string\n try {\n videoOrigin = new URL(videoUrl, 'http://placeholder.local').origin\n } catch {\n return\n }\n if (videoOrigin !== resolvedOrigin) return\n\n const link = document.createElement('link')\n link.rel = 'preload'\n link.as = 'video'\n link.href = videoUrl\n link.crossOrigin = 'anonymous'\n // `fetchPriority='low'` matches the plan — the hint should not\n // steal network from the LCP image; the click→first-frame win is\n // in milliseconds, not the first paint.\n if ('fetchPriority' in link) {\n ;(link as HTMLLinkElement & { fetchPriority?: string }).fetchPriority = 'low'\n }\n document.head.appendChild(link)\n\n return () => {\n link.remove()\n }\n }, [isNear, videoUrl, resolvedOrigin])\n\n return { ref, isNear }\n}\n","/**\n * Build the captions API URL for a video entity.\n *\n * Returns the HTTPS URL to the `/api/captions/[entityType]/[entityId]` endpoint\n * which serves VTT content for iOS native fullscreen subtitles.\n * Returns undefined if entity has no srt_content.\n *\n * Cache-busting hash derived from the srt_content length so iOS Safari\n * fetches fresh VTT when subtitles are regenerated (Safari aggressively caches\n * <track> src URLs even with short Cache-Control max-age).\n *\n * Lifted from hub `lib/utils/captions-url.ts`. The hub's hard-coded\n * `VideoEnabledEntityType` enum is widened to `string` here — embedders\n * pass whatever entity-type discriminator their reverse-proxied\n * `/api/captions/...` route expects.\n */\nexport function getCaptionsUrl(\n entityType: string,\n entityId: string | number,\n srtContent?: string | null,\n): string | undefined {\n if (!srtContent) return undefined\n const hash = `${srtContent.length}-${srtContent.slice(0, 8).replace(/\\s/g, '')}`\n return `/api/captions/${entityType}/${entityId}?v=${hash}`\n}\n","\"use client\"\n\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\nimport * as React from \"react\"\nimport { cn } from \"../../utils/cn\"\nimport { useDebounce } from \"../../hooks/ui/use-debounce\"\nimport { useAutoLimitTags } from \"../../hooks/ui/use-auto-limit-tags\"\nimport { SearchIcon } from \"../icons-v2-generated\"\nimport { XmarkCircleIcon } from \"../icons-v2-generated/signs-and-symbols/xmark-circle-icon\"\nimport { Tag } from \"./tag\"\nimport { HiddenTagsPopup } from \"./hidden-tags-popup\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SearchResult {\n id: string\n title: string\n description?: string\n path?: string\n type?: string\n icon?: React.ReactNode\n metadata?: Record<string, unknown>\n}\n\nexport interface FilterChipData {\n id: string\n label: string\n variant?: \"selected\" | \"category\" | \"subcategory\" | \"tag\"\n}\n\nexport interface SearchInputProps {\n /** Placeholder text shown in the input */\n placeholder?: string\n /** Controlled value */\n value?: string\n /** Default value for uncontrolled mode */\n defaultValue?: string\n /** Called when input value changes (raw, not debounced) */\n onChange?: (value: string) => void\n /** Called when user presses Enter */\n onSubmit?: (value: string) => void\n /** Search results to display in the dropdown */\n results?: SearchResult[]\n /** Whether results are loading */\n isLoading?: boolean\n /** Called when a result row is selected.\n *\n * `modifiers` carries the click event's modifier-key state when the\n * user picked the row via mouse — pass through so the consumer can\n * honor cmd/ctrl/shift/middle-click for background-tab navigation\n * (the row is a `<div role=\"option\">` rather than an `<a>`, so the\n * browser doesn't background-tab natively even with `target=\"_blank\"`).\n * Empty `{}` when the row was selected via keyboard Enter. */\n onResultSelect?: (\n result: SearchResult,\n modifiers?: { metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean; altKey?: boolean; button?: number },\n ) => void\n /** Debounce delay in ms. 0 disables debounce. Default 300 */\n debounceMs?: number\n /** Custom renderer for a single result row */\n renderResult?: (result: SearchResult, isHighlighted: boolean) => React.ReactNode\n /** Group results by a key derived from each result */\n groupBy?: (result: SearchResult) => string\n /** Text shown when query meets minQueryLength but no results */\n emptyResultsText?: string\n /** Force-control dropdown visibility. Default: auto */\n showDropdown?: boolean\n /** Filter chips rendered inline before the input */\n filterChips?: FilterChipData[]\n /** Called when a filter chip is removed */\n onFilterRemove?: (id: string) => void\n /** Element rendered before the input. Default: SearchIcon */\n startAdornment?: React.ReactNode\n /** Element rendered after the input */\n endAdornment?: React.ReactNode\n /** Extra class names for the outer container */\n className?: string\n /** Extra class names for the dropdown */\n dropdownClassName?: string\n /** Minimum characters before showing results. Default 2 */\n minQueryLength?: number\n /** Maximum visible filter chips. \"auto\" measures available width. Default \"auto\" */\n limitTags?: number | \"auto\"\n /** Custom render for the \"+N\" overflow text */\n getLimitTagsText?: (more: number) => React.ReactNode\n}\n\n// ---------------------------------------------------------------------------\n// Shared styles (consistent with Autocomplete / Input)\n// ---------------------------------------------------------------------------\n\nconst containerStyles = cn(\n // Layout & spacing — matches lib Input component\n \"flex items-center gap-2 rounded-[6px] border px-3 h-11 md:h-12 cursor-text\",\n \"has-[:focus-visible]:outline-none\",\n \"group\",\n \"transition-colors duration-200\",\n // Theme palette — matches lib Input component\n \"bg-ods-card border-ods-border has-[:focus]:border-ods-accent\"\n)\n\nconst innerInputStyles = cn(\n \"flex-1 min-w-[60px] bg-transparent border-none outline-none\",\n \"text-h4\",\n \"text-ods-text-primary placeholder:text-ods-text-secondary\",\n \"disabled:cursor-not-allowed\",\n \"touch-manipulation\"\n)\n\n// ---------------------------------------------------------------------------\n// Helper: chip variant → Tag variant mapping\n// ---------------------------------------------------------------------------\n\nfunction chipVariantToTagVariant(variant?: FilterChipData[\"variant\"]): \"primary\" | \"outline\" | \"badge\" {\n switch (variant) {\n case \"selected\":\n return \"primary\"\n // Content tags render with the unified badge skin (ods-card + ods-border,\n // mono uppercase) — identical to the public EntityTagBadges display.\n case \"tag\":\n return \"badge\"\n case \"category\":\n case \"subcategory\":\n default:\n return \"outline\"\n }\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function SearchInput({\n placeholder = \"Search...\",\n value,\n defaultValue = \"\",\n onChange,\n onSubmit,\n results = [],\n isLoading = false,\n onResultSelect,\n debounceMs = 300,\n renderResult,\n groupBy,\n emptyResultsText = \"No results found\",\n showDropdown: showDropdownProp,\n filterChips = [],\n onFilterRemove,\n startAdornment,\n endAdornment,\n className,\n dropdownClassName,\n minQueryLength = 2,\n limitTags = \"auto\",\n getLimitTagsText = (more: number) => `+${more}`,\n}: SearchInputProps) {\n // ---- Controlled / uncontrolled ----\n const [internalValue, setInternalValue] = React.useState(defaultValue)\n const currentValue = onChange ? (value ?? \"\") : internalValue\n\n // ---- Debounce ----\n const debouncedValue = useDebounce(currentValue, debounceMs)\n\n // ---- Popover state ----\n const [isOpen, setIsOpen] = React.useState(false)\n const [highlightedIndex, setHighlightedIndex] = React.useState(-1)\n\n const containerRef = React.useRef<HTMLDivElement>(null)\n\n // ---- Auto-limit tags ----\n const currentPlaceholder = filterChips.length > 0 ? \"Add filter...\" : placeholder\n\n const {\n visibleCount: rawVisibleCount, middleRef, measureRef, textMeasureRef, badgeRef, inputRef,\n } = useAutoLimitTags({\n count: filterChips.length,\n limitTags,\n // When chips exist, pass empty placeholder so the hook only reserves input minWidth,\n // not the full placeholder text width — gives more room for chips on narrow screens\n placeholder: filterChips.length > 0 ? \"\" : placeholder,\n })\n\n // Always show at least 1 chip when chips exist (industry standard: Gmail, MUI, Ant Design)\n const visibleCount = filterChips.length > 0 ? Math.max(1, rawVisibleCount) : rawVisibleCount\n\n // ---- Hidden tags popup ----\n const hiddenTagsRef = React.useRef<HTMLDivElement>(null)\n const hiddenTagsPopupRef = React.useRef<HTMLDivElement>(null)\n const [showHiddenTags, setShowHiddenTags] = React.useState(false)\n\n React.useEffect(() => {\n if (!showHiddenTags) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node\n if (!hiddenTagsRef.current?.contains(target) && !hiddenTagsPopupRef.current?.contains(target)) {\n setShowHiddenTags(false)\n }\n }\n document.addEventListener(\"mousedown\", handleClick)\n return () => document.removeEventListener(\"mousedown\", handleClick)\n }, [showHiddenTags])\n\n // ---- Derived chip slicing ----\n const hiddenCount = filterChips.length - visibleCount\n const visibleChips = filterChips.slice(0, visibleCount)\n const hiddenChips = filterChips.slice(visibleCount)\n\n // ---- Derive flat list (possibly grouped) ----\n const { flatResults, groups } = React.useMemo(() => {\n if (!groupBy) return { flatResults: results, groups: null }\n\n const grouped = new Map<string, SearchResult[]>()\n for (const r of results) {\n const key = groupBy(r)\n const arr = grouped.get(key)\n if (arr) {\n arr.push(r)\n } else {\n grouped.set(key, [r])\n }\n }\n return { flatResults: results, groups: grouped }\n }, [results, groupBy])\n\n // ---- Auto-show logic ----\n const meetsMinQuery = debouncedValue.length >= minQueryLength\n const autoShow = meetsMinQuery\n const dropdownVisible = showDropdownProp ?? (isOpen && autoShow)\n\n // ---- Reset highlight when results change ----\n React.useEffect(() => {\n setHighlightedIndex(-1)\n }, [flatResults.length])\n\n // ---- Handlers ----\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newVal = e.target.value\n if (onChange) {\n onChange(newVal)\n } else {\n setInternalValue(newVal)\n }\n if (!isOpen) setIsOpen(true)\n setHighlightedIndex(-1)\n }\n\n const handleClear = (e: React.MouseEvent) => {\n e.stopPropagation()\n e.preventDefault()\n if (onChange) {\n onChange(\"\")\n } else {\n setInternalValue(\"\")\n }\n inputRef.current?.focus()\n }\n\n const handleResultClick = (\n result: SearchResult,\n e?: React.MouseEvent,\n ) => {\n onResultSelect?.(\n result,\n e\n ? {\n metaKey: e.metaKey,\n ctrlKey: e.ctrlKey,\n shiftKey: e.shiftKey,\n altKey: e.altKey,\n button: e.button,\n }\n : undefined,\n )\n setIsOpen(false)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault()\n if (!isOpen) setIsOpen(true)\n setHighlightedIndex((prev) =>\n prev < flatResults.length - 1 ? prev + 1 : 0\n )\n break\n case \"ArrowUp\":\n e.preventDefault()\n setHighlightedIndex((prev) =>\n prev > 0 ? prev - 1 : flatResults.length - 1\n )\n break\n case \"Enter\":\n e.preventDefault()\n if (highlightedIndex >= 0 && flatResults[highlightedIndex]) {\n handleResultClick(flatResults[highlightedIndex])\n } else {\n onSubmit?.(currentValue)\n }\n break\n case \"Escape\":\n setIsOpen(false)\n setHighlightedIndex(-1)\n break\n case \"Backspace\":\n if (!currentValue && filterChips.length > 0 && onFilterRemove) {\n onFilterRemove(filterChips[filterChips.length - 1].id)\n }\n break\n }\n }\n\n const handleOpenChange = (open: boolean) => {\n setIsOpen(open)\n }\n\n // ---- Default result renderer ----\n const defaultRenderResult = (result: SearchResult, isHighlighted: boolean) => (\n <div className=\"flex items-center gap-3 w-full min-w-0\">\n {result.icon && (\n <span className=\"flex-shrink-0 text-ods-text-secondary [&_svg]:size-4\">\n {result.icon}\n </span>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className={cn(\n \"text-sm font-medium leading-5 truncate\",\n isHighlighted ? \"text-ods-accent\" : \"text-ods-text-primary\"\n )} title={result.title}>\n {result.title}\n </div>\n {result.description && (\n <div className=\"text-xs leading-4 text-ods-text-secondary truncate mt-0.5\" title={result.description}>\n {result.description}\n </div>\n )}\n </div>\n {result.type && (\n <span className=\"flex-shrink-0 text-[11px] font-medium text-ods-text-muted uppercase tracking-wider\">\n {result.type}\n </span>\n )}\n </div>\n )\n\n // ---- Render a result row ----\n const renderRow = (result: SearchResult, index: number) => {\n const isHighlighted = index === highlightedIndex\n return (\n <div\n key={result.id}\n role=\"option\"\n aria-selected={isHighlighted}\n className={cn(\n \"flex items-center min-h-10 px-3 cursor-pointer transition-colors border-b border-ods-border last:border-b-0\",\n isHighlighted && \"bg-ods-bg-hover\",\n !isHighlighted && \"hover:bg-ods-bg-hover\"\n )}\n onClick={(e) => handleResultClick(result, e)}\n onMouseEnter={() => setHighlightedIndex(index)}\n >\n {renderResult ? renderResult(result, isHighlighted) : defaultRenderResult(result, isHighlighted)}\n </div>\n )\n }\n\n // ---- Dropdown content ----\n const renderDropdownContent = () => {\n if (isLoading) {\n return (\n <div className=\"px-4 py-3 text-ods-text-secondary text-[14px]\">\n Loading...\n </div>\n )\n }\n\n if (flatResults.length === 0) {\n return (\n <div className=\"px-4 py-3 text-ods-text-secondary text-[14px]\">\n {emptyResultsText}\n </div>\n )\n }\n\n if (groups) {\n let globalIndex = 0\n return Array.from(groups.entries()).map(([groupLabel, groupResults]) => (\n <div key={groupLabel}>\n <div className=\"px-4 py-2 text-[12px] font-semibold text-ods-text-secondary uppercase tracking-wide bg-ods-bg\">\n {groupLabel}\n </div>\n {groupResults.map((result) => {\n const idx = globalIndex++\n return renderRow(result, idx)\n })}\n </div>\n ))\n }\n\n return flatResults.map((result, index) => renderRow(result, index))\n }\n\n // ---- Determine if we have a value worth clearing ----\n const hasValue = currentValue.length > 0\n\n return (\n <div className={cn(\"relative\", className)} ref={containerRef}>\n <PopoverPrimitive.Root open={dropdownVisible} onOpenChange={handleOpenChange} modal={false}>\n <PopoverPrimitive.Anchor asChild>\n <div\n className={cn(\n containerStyles,\n \"hover:bg-ods-bg-hover hover:border-ods-border-hover active:bg-ods-bg-active active:border-ods-border-active\",\n dropdownVisible && \"!border-ods-accent\"\n )}\n onClick={() => {\n inputRef.current?.focus()\n setIsOpen(true)\n }}\n >\n {/* Start Adornment — pinned left, shrink-0 */}\n <span className=\"flex-shrink-0 text-ods-text-secondary transition-colors duration-200 group-has-[:focus]:text-ods-accent [&_svg]:size-4 md:[&_svg]:size-6\">\n {startAdornment !== undefined ? startAdornment : <SearchIcon />}\n </span>\n\n {/* Middle zone: chips + input — overflow hidden, single line */}\n <div ref={middleRef} className=\"flex-1 flex items-center gap-2 min-w-0 overflow-hidden\">\n {/* Visible filter chips */}\n {visibleChips.map((chip) => (\n <Tag\n key={chip.id}\n variant={chipVariantToTagVariant(chip.variant)}\n label={chip.label}\n labelClassName=\"truncate max-w-[120px]\"\n onClose={onFilterRemove ? () => onFilterRemove(chip.id) : undefined}\n />\n ))}\n\n {/* \"+N\" overflow badge */}\n {hiddenCount > 0 && (\n <div ref={hiddenTagsRef} className=\"shrink-0\">\n <button\n ref={badgeRef}\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setShowHiddenTags((prev) => !prev)\n }}\n className={cn(\n \"flex items-center h-8 px-2\",\n \"bg-ods-card border border-ods-border rounded-[6px]\",\n \"font-mono text-[14px] font-medium leading-5 text-ods-text-secondary uppercase tracking-[-0.28px]\",\n \"hover:bg-ods-bg-hover transition-colors cursor-pointer\",\n )}\n aria-label={`${hiddenCount} more selected filters`}\n >\n {getLimitTagsText(hiddenCount)}\n </button>\n </div>\n )}\n\n {/* Input */}\n <input\n ref={inputRef}\n type=\"text\"\n value={currentValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onFocus={() => {\n setIsOpen(true)\n setShowHiddenTags(false)\n }}\n placeholder={currentPlaceholder}\n className={innerInputStyles}\n />\n </div>\n\n {/* End adornment / Clear — pinned right, shrink-0 */}\n <div className=\"flex items-center gap-1 shrink-0 ml-auto\">\n {hasValue && (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"flex items-center justify-center hover:opacity-70 transition-opacity\"\n aria-label=\"Clear search\"\n >\n <XmarkCircleIcon className=\"text-ods-text-secondary\" size={24} />\n </button>\n )}\n {endAdornment}\n </div>\n </div>\n </PopoverPrimitive.Anchor>\n\n <PopoverPrimitive.Content\n className={cn(\n \"z-50 w-[var(--radix-popover-trigger-width)] mt-1\",\n \"bg-ods-card border border-ods-border rounded-[6px] overflow-hidden shadow-lg\",\n \"data-[state=open]:animate-in data-[state=closed]:animate-out\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2\",\n dropdownClassName\n )}\n sideOffset={4}\n align=\"start\"\n onOpenAutoFocus={(e) => {\n e.preventDefault()\n inputRef.current?.focus()\n }}\n onInteractOutside={(e) => {\n if (containerRef.current?.contains(e.target as Node)) {\n e.preventDefault()\n }\n }}\n >\n <ScrollAreaPrimitive.Root className=\"overflow-hidden\">\n <ScrollAreaPrimitive.Viewport className=\"max-h-[320px] w-full\">\n <div role=\"listbox\">\n {renderDropdownContent()}\n </div>\n </ScrollAreaPrimitive.Viewport>\n <ScrollAreaPrimitive.Scrollbar className=\"hidden\" orientation=\"vertical\">\n <ScrollAreaPrimitive.Thumb />\n </ScrollAreaPrimitive.Scrollbar>\n </ScrollAreaPrimitive.Root>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Root>\n\n {/* Hidden tags popup — outside overflow-hidden, positioned under badge */}\n {showHiddenTags && hiddenCount > 0 && (\n <HiddenTagsPopup\n ref={hiddenTagsPopupRef}\n items={hiddenChips.map(chip => ({ label: chip.label, value: chip.id }))}\n style={{\n left: badgeRef.current\n ? badgeRef.current.getBoundingClientRect().left -\n (containerRef.current?.getBoundingClientRect().left ?? 0)\n : 0,\n }}\n onRemove={(value) => {\n onFilterRemove?.(value as string)\n if (hiddenCount <= 1) setShowHiddenTags(false)\n }}\n />\n )}\n\n {/* Off-screen measurement: placeholder text width — fixed positioning avoids scroll contribution */}\n <span\n ref={textMeasureRef}\n aria-hidden=\"true\"\n className=\"fixed -left-[9999px] top-0 pointer-events-none whitespace-nowrap text-ods-text-primary\"\n >\n {currentPlaceholder}\n </span>\n\n {/* Off-screen measurement: all chip widths */}\n <div\n ref={measureRef}\n aria-hidden=\"true\"\n className=\"fixed -left-[9999px] top-0 flex gap-2 pointer-events-none\"\n >\n {filterChips.map((chip) => (\n <Tag\n key={`m-${chip.id}`}\n variant={chipVariantToTagVariant(chip.variant)}\n label={chip.label}\n labelClassName=\"truncate max-w-[120px]\"\n onClose={() => {}}\n />\n ))}\n </div>\n </div>\n )\n}\n\nexport default SearchInput\n","'use client'\n\n/**\n * Shared filter section wrapper with responsive layout.\n *\n * Provides the consistent card container for any filter row (status,\n * platform, section, etc.) Single row on desktop with flex-wrap,\n * stacks naturally on mobile.\n *\n * Lifted from hub `components/admin/shared/filter-section.tsx` so\n * lib catalog views can render their own filter pills without an\n * injected slot.\n */\n\nimport { Filter } from 'lucide-react'\nimport { Button } from './button'\n\nexport interface FilterPillRowOption {\n value: string\n label: string\n}\n\nexport interface FilterPillRowProps {\n /** Label shown next to the filter icon, e.g. \"Section\", \"Platform\". */\n label: string\n /** The currently selected filter value. */\n selectedValue: string\n /** Callback when a filter option is selected. */\n onValueChange: (value: string) => void\n /** Available filter options. */\n options: FilterPillRowOption[]\n /** Optional count label, e.g. \"Showing 1-10 of 42 items\". */\n countLabel?: string\n /** Optional children to render instead of options (for custom filter content). */\n children?: React.ReactNode\n}\n\nexport function FilterPillRow({\n label,\n selectedValue,\n onValueChange,\n options,\n countLabel,\n children,\n}: FilterPillRowProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-3 p-4 bg-ods-card border border-ods-border rounded-lg\">\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-ods-accent\" />\n <span className=\"text-h5 text-ods-text-secondary\">{label}</span>\n </div>\n {children ||\n options.map((opt) => (\n <Button\n key={opt.value}\n type=\"button\"\n variant={selectedValue === opt.value ? 'accent' : 'outline'}\n size=\"small-legacy\"\n onClick={() => onValueChange(opt.value)}\n className=\"text-h3\"\n >\n {opt.label}\n </Button>\n ))}\n {countLabel && (\n <div className=\"ml-auto text-[12px] font-['DM_Sans'] text-ods-text-secondary shrink-0\">\n {countLabel}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["HiddenTagsPopup","jsx","jsxs","jsx","jsxs","React","jsx","cva","jsx","jsx","jsxs","Tag","React","jsx","jsxs","React","React","useLayoutEffect","React","React","value","React","useCallback","jsx","jsxs","useCallback","React","jsx","jsxs","jsx","React","useEffect","useMemo","useRef","useState","jsx","jsxs","useState","useRef","useMemo","useEffect","Fragment","jsx","jsxs","Tag","Fragment","jsx","jsxs","useMemo","jsx","jsxs","useMemo","Fragment","jsx","jsxs","useMemo","useMemo","jsx","jsxs","summary","GraduationCap","Video","GraduationCap","Video","FileText","FileText","React","Fragment","jsx","jsxs","React","React","jsx","React","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","useEffect","useEffect","React","jsx","jsxs","value","jsx","jsxs"]}
|