@flamingo-stack/openframe-frontend-core 0.0.519 → 0.0.520

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.
Files changed (93) hide show
  1. package/dist/{chunk-QQZWLTYU.cjs → chunk-2HL3BXWV.cjs} +4 -4
  2. package/dist/{chunk-QQZWLTYU.cjs.map → chunk-2HL3BXWV.cjs.map} +1 -1
  3. package/dist/{chunk-HWA3NZPK.js → chunk-2Y4REKE5.js} +2 -2
  4. package/dist/{chunk-ZGGI4XMF.cjs → chunk-37OBDW56.cjs} +9 -9
  5. package/dist/{chunk-ZGGI4XMF.cjs.map → chunk-37OBDW56.cjs.map} +1 -1
  6. package/dist/{chunk-QEGGWVFV.cjs → chunk-4NEP3KY7.cjs} +26 -26
  7. package/dist/{chunk-QEGGWVFV.cjs.map → chunk-4NEP3KY7.cjs.map} +1 -1
  8. package/dist/{chunk-HVXGSQ4V.js → chunk-5PT2RQRU.js} +6 -6
  9. package/dist/{chunk-6FLRFCK4.cjs → chunk-5ZQKEGL3.cjs} +870 -796
  10. package/dist/chunk-5ZQKEGL3.cjs.map +1 -0
  11. package/dist/{chunk-ZA5PFNHR.js → chunk-AQHR5D7X.js} +324 -250
  12. package/dist/chunk-AQHR5D7X.js.map +1 -0
  13. package/dist/{chunk-BUZOZOQ2.js → chunk-DMITCSFH.js} +2 -2
  14. package/dist/{chunk-VH7BOHRG.js → chunk-HI3TQS4D.js} +2 -2
  15. package/dist/{chunk-S4OAWVKF.js → chunk-JKICCXLC.js} +2 -2
  16. package/dist/{chunk-QGLEI6T2.cjs → chunk-JZC5BBAF.cjs} +5 -5
  17. package/dist/{chunk-QGLEI6T2.cjs.map → chunk-JZC5BBAF.cjs.map} +1 -1
  18. package/dist/{chunk-JMGVYERE.js → chunk-LB66ECD7.js} +2 -2
  19. package/dist/{chunk-MEWHQVOU.js → chunk-P7AQ5XKO.js} +2 -2
  20. package/dist/{chunk-5PMRHRMM.cjs → chunk-PA6L55NF.cjs} +14 -14
  21. package/dist/{chunk-5PMRHRMM.cjs.map → chunk-PA6L55NF.cjs.map} +1 -1
  22. package/dist/{chunk-YACGPO7I.js → chunk-QICUWGET.js} +2 -2
  23. package/dist/{chunk-OBJSXQ67.cjs → chunk-R3Y65KPT.cjs} +3 -3
  24. package/dist/{chunk-OBJSXQ67.cjs.map → chunk-R3Y65KPT.cjs.map} +1 -1
  25. package/dist/{chunk-FWCENA3G.cjs → chunk-RRCSGTT5.cjs} +31 -31
  26. package/dist/{chunk-FWCENA3G.cjs.map → chunk-RRCSGTT5.cjs.map} +1 -1
  27. package/dist/{chunk-SAPKMN4W.js → chunk-RT4IGTJ6.js} +2 -2
  28. package/dist/{chunk-UBT2KBK7.js → chunk-RTJ6Q7JA.js} +5 -5
  29. package/dist/{chunk-KCDKQXE5.cjs → chunk-S5HO5E6R.cjs} +7 -7
  30. package/dist/{chunk-KCDKQXE5.cjs.map → chunk-S5HO5E6R.cjs.map} +1 -1
  31. package/dist/{chunk-CCK2MFIQ.cjs → chunk-SVYXGHJG.cjs} +26 -26
  32. package/dist/{chunk-CCK2MFIQ.cjs.map → chunk-SVYXGHJG.cjs.map} +1 -1
  33. package/dist/{chunk-BFCBR277.cjs → chunk-TRIU3RJD.cjs} +11 -11
  34. package/dist/{chunk-BFCBR277.cjs.map → chunk-TRIU3RJD.cjs.map} +1 -1
  35. package/dist/{chunk-GLFQSFYV.cjs → chunk-UDPOAWKH.cjs} +61 -61
  36. package/dist/{chunk-GLFQSFYV.cjs.map → chunk-UDPOAWKH.cjs.map} +1 -1
  37. package/dist/{chunk-CRJRVUWB.js → chunk-UOMUR62H.js} +2 -2
  38. package/dist/{chunk-OQHHJDUB.js → chunk-W3UAL5GU.js} +4 -4
  39. package/dist/{chunk-6WQN7NLD.js → chunk-Y43UWXF7.js} +4 -4
  40. package/dist/{chunk-WEPJKF65.cjs → chunk-YD7JM53S.cjs} +37 -37
  41. package/dist/{chunk-WEPJKF65.cjs.map → chunk-YD7JM53S.cjs.map} +1 -1
  42. package/dist/{chunk-V3MAG3G5.cjs → chunk-YPHKO6C5.cjs} +87 -87
  43. package/dist/{chunk-V3MAG3G5.cjs.map → chunk-YPHKO6C5.cjs.map} +1 -1
  44. package/dist/components/case-studies/index.cjs +8 -8
  45. package/dist/components/case-studies/index.js +2 -2
  46. package/dist/components/chat/index.cjs +2 -2
  47. package/dist/components/chat/index.js +1 -1
  48. package/dist/components/contact/index.cjs +3 -3
  49. package/dist/components/contact/index.js +2 -2
  50. package/dist/components/docs/index.cjs +5 -5
  51. package/dist/components/docs/index.js +4 -4
  52. package/dist/components/embeds/index.cjs +3 -3
  53. package/dist/components/embeds/index.js +2 -2
  54. package/dist/components/faq/index.cjs +3 -3
  55. package/dist/components/faq/index.js +2 -2
  56. package/dist/components/features/index.cjs +2 -2
  57. package/dist/components/features/index.js +1 -1
  58. package/dist/components/help-center-pages/index.cjs +22 -22
  59. package/dist/components/help-center-pages/index.js +13 -13
  60. package/dist/components/index.cjs +132 -132
  61. package/dist/components/index.js +11 -11
  62. package/dist/components/meeting-scheduler/index.cjs +34 -34
  63. package/dist/components/meeting-scheduler/index.js +3 -3
  64. package/dist/components/navigation/index.cjs +2 -2
  65. package/dist/components/navigation/index.js +1 -1
  66. package/dist/components/onboarding-guides/index.cjs +5 -5
  67. package/dist/components/onboarding-guides/index.js +4 -4
  68. package/dist/components/related-content/index.cjs +3 -3
  69. package/dist/components/related-content/index.js +2 -2
  70. package/dist/components/tickets/index.cjs +6 -6
  71. package/dist/components/tickets/index.js +5 -5
  72. package/dist/components/ui/index.cjs +2 -2
  73. package/dist/components/ui/index.js +1 -1
  74. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  75. package/dist/index.cjs +2 -2
  76. package/dist/index.js +1 -1
  77. package/package.json +1 -1
  78. package/src/components/ui/modal-v2.tsx +95 -1
  79. package/dist/chunk-6FLRFCK4.cjs.map +0 -1
  80. package/dist/chunk-ZA5PFNHR.js.map +0 -1
  81. /package/dist/{chunk-HWA3NZPK.js.map → chunk-2Y4REKE5.js.map} +0 -0
  82. /package/dist/{chunk-HVXGSQ4V.js.map → chunk-5PT2RQRU.js.map} +0 -0
  83. /package/dist/{chunk-BUZOZOQ2.js.map → chunk-DMITCSFH.js.map} +0 -0
  84. /package/dist/{chunk-VH7BOHRG.js.map → chunk-HI3TQS4D.js.map} +0 -0
  85. /package/dist/{chunk-S4OAWVKF.js.map → chunk-JKICCXLC.js.map} +0 -0
  86. /package/dist/{chunk-JMGVYERE.js.map → chunk-LB66ECD7.js.map} +0 -0
  87. /package/dist/{chunk-MEWHQVOU.js.map → chunk-P7AQ5XKO.js.map} +0 -0
  88. /package/dist/{chunk-YACGPO7I.js.map → chunk-QICUWGET.js.map} +0 -0
  89. /package/dist/{chunk-SAPKMN4W.js.map → chunk-RT4IGTJ6.js.map} +0 -0
  90. /package/dist/{chunk-UBT2KBK7.js.map → chunk-RTJ6Q7JA.js.map} +0 -0
  91. /package/dist/{chunk-CRJRVUWB.js.map → chunk-UOMUR62H.js.map} +0 -0
  92. /package/dist/{chunk-OQHHJDUB.js.map → chunk-W3UAL5GU.js.map} +0 -0
  93. /package/dist/{chunk-6WQN7NLD.js.map → chunk-Y43UWXF7.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BFCBR277.cjs","../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts","../src/components/faq/faq-document-page.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BFCBR277.cjs","sourcesContent":[null,"\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { Faq } from '../../types/faq'\nimport { FaqAccordion, type FaqItem } from '../faq-accordion'\nimport { useSelfFetch } from '../../hooks/use-self-fetch'\nimport { buildSuggestionUrl } from '../../utils/suggestion-url'\nimport { serializeJsonLd } from '../../utils/common'\nimport { useScrollToHash } from '../../hooks/use-scroll-to-hash'\nimport { navigateSamePageHash, STICKY_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav'\nimport { faqSectionSlug, parseFaqHash, type FaqHashTarget } from '../../utils/faq-anchor'\nimport { cn } from '../../utils/cn'\nimport { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld'\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading'\n\nexport interface FaqSectionProps {\n /**\n * SSR hydrate. When provided, the hook skips the first client fetch (per\n * useSelfFetch contract). The consuming server page resolves FAQs then drills\n * them into this prop — the lib never re-fetches what the host already gated on.\n */\n initialFaqs?: Faq[]\n /** Both required together for entity-attached FAQs; partial → bare /api/faqs. */\n entityType?: string\n entityId?: number | string\n /**\n * Heading node above the grouped list. `undefined` → default\n * `<h2>`\"Frequently Asked Questions\". `null` → no heading (the host page\n * owns the `<h1>`, as the standalone /faqs surface does). A React node lets a\n * platform drill its own <PageHeading> without the lib referencing platform\n * state. Also drives category nesting so the document outline stays correct:\n * `null` → categories render `<h2>` (directly under the page `<h1>`);\n * otherwise categories render `<h3>` beneath this heading.\n */\n heading?: React.ReactNode | null\n /** Inject FAQPage schema.org JSON-LD as a <script>. Off by default so embeds\n * don't emit duplicate schema. */\n emitJsonLd?: boolean\n /** Overrides for the JSON-LD's name/description/url. */\n jsonLd?: FaqSchemaOptions\n className?: string\n /** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param\n * not sent (server default applies). */\n minResults?: number\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin relative). */\n apiBaseUrl?: string\n}\n\nconst DEFAULT_HEADING_TEXT = 'Frequently Asked Questions'\n\n/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)\n * — byte-identical to the historical `buildFaqsUrl` output when\n * `minResults`/`apiBaseUrl` are absent. */\nfunction buildFaqsUrl(\n entityType?: string,\n entityId?: number | string,\n minResults?: number,\n apiBaseUrl = '',\n): string {\n return buildSuggestionUrl('/api/faqs', { apiBaseUrl, entityType, entityId, count: minResults })\n}\n\ninterface FaqGroup {\n /** null → the uncategorized bucket: no heading, no jump pill, rendered last. */\n section: string | null\n slug: string | null\n items: FaqItem[]\n}\n\n/** Group FAQs by `faq.section`, preserving the server's first-seen\n * (display_order) order for BOTH the section order and the rows within each\n * section. The uncategorized bucket (blank/missing section) sinks to the end\n * since it renders without a heading. Items carry NO badge here — the `<h2>`\n * IS the category, so a per-row chip would be redundant. */\nfunction groupFaqsBySection(faqs: Faq[]): FaqGroup[] {\n const order: string[] = []\n const byName = new Map<string, FaqGroup>()\n let uncategorized: FaqGroup | null = null\n for (const faq of faqs) {\n const item: FaqItem = { id: faq.id, question: faq.question, answer: faq.answer }\n const name = faq.section?.trim()\n if (!name) {\n if (!uncategorized) uncategorized = { section: null, slug: null, items: [] }\n uncategorized.items.push(item)\n continue\n }\n let group = byName.get(name)\n if (!group) {\n group = { section: name, slug: faqSectionSlug(name), items: [] }\n byName.set(name, group)\n order.push(name)\n }\n group.items.push(item)\n }\n const groups = order.map((name) => byName.get(name)!)\n if (uncategorized) groups.push(uncategorized)\n return groups\n}\n\n\n/** Map key for the uncategorized bucket — `group.slug` is null for it, so\n * every per-group map (default-open ids, accordion keys) uses this sentinel\n * to keep the lookup typed. */\nconst UNCATEGORIZED_KEY = '__uncategorized__'\nconst groupKey = (g: FaqGroup): string => g.slug ?? UNCATEGORIZED_KEY\n\n/**\n * Grouped FAQ layout: a category jump-nav above stacked `<h2>` category\n * sections (each its own accordion). Isolated into its own component so the\n * scroll-spy hooks only mount in grouped mode — `FaqSection`'s own hooks stay\n * unconditional.\n *\n * The pills are real `<a href=\"#slug\">` anchors (crawlable in-page links,\n * deep-linkable, work without JS); the click handler upgrades the jump to the\n * cancellation-proof `scrollElementIntoView` tween and syncs the URL hash.\n */\nfunction GroupedFaqList({\n groups,\n categoryHeadingAs,\n}: {\n groups: FaqGroup[]\n /** Heading tag for each category, so the document outline nests correctly\n * under whatever owns the heading above this block: `h2` on the standalone\n * page (the page owns the `<h1>`), `h3` beneath an embed's `<h2>` title.\n * The VISUAL is `SECTION_HEADING_CLASS` either way, so categories look\n * identical on every surface. */\n categoryHeadingAs: 'h2' | 'h3'\n}) {\n const CategoryHeading = categoryHeadingAs\n const navGroups = useMemo(() => groups.filter((g) => g.slug), [groups])\n const [activeSlug, setActiveSlug] = useState<string | null>(navGroups[0]?.slug ?? null)\n // Identity-stable key for the section set so the observer re-binds only when\n // the categories actually change (not on every parent re-render).\n const slugKey = navGroups.map((g) => g.slug).join('|')\n\n // Scroll-spy: mark the pill for the category currently at the top of the\n // viewport. rootMargin drops the trigger line just below the sticky header\n // and ignores the bottom ~55% so \"active\" is the section being read, not the\n // next one peeking in.\n useEffect(() => {\n if (navGroups.length < 2) return\n const tops = new Map<string, number>()\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id\n if (entry.isIntersecting) tops.set(id, entry.boundingClientRect.top)\n else tops.delete(id)\n }\n let bestId: string | null = null\n let bestTop = Number.POSITIVE_INFINITY\n for (const [id, top] of tops) {\n if (top < bestTop) {\n bestTop = top\n bestId = id\n }\n }\n if (bestId) setActiveSlug(bestId)\n },\n { rootMargin: `-${STICKY_HEADER_OFFSET_PX}px 0px -55% 0px`, threshold: 0 },\n )\n for (const group of navGroups) {\n const el = group.slug ? document.getElementById(group.slug) : null\n if (el) observer.observe(el)\n }\n return () => observer.disconnect()\n // slugKey encodes the section set; re-observe only when it changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slugKey])\n\n // ─── Hash dispatch — `/faqs#faq-item-<id>` or `/faqs#faq-<section-slug>` ──\n // Tracks the current hash so:\n // 1. an item-kind hash seeds `defaultOpenIds` on the matching accordion\n // (auto-expands the cited question);\n // 2. either kind triggers the cancellation-proof tween scroll with the\n // sticky-header offset (native browser hash scroll runs once, ignores\n // our offset — re-running the tween puts the target in the right spot).\n // Listens to `hashchange` so back/forward replays the same behavior. SSR-\n // safe: initial null state matches the server render; the first effect\n // tick on the client updates it.\n const [hashTarget, setHashTarget] = useState<FaqHashTarget | null>(null)\n useEffect(() => {\n const refresh = () => setHashTarget(parseFaqHash(window.location.hash))\n refresh()\n window.addEventListener('hashchange', refresh)\n return () => window.removeEventListener('hashchange', refresh)\n }, [])\n\n // Per-group default-open set when the hash points at an item. The map key\n // matches `groupKey(group)` so the render-time lookup is O(1) per group.\n const defaultOpenByGroupKey = useMemo(() => {\n if (hashTarget?.kind !== 'item') return null\n const targetId = hashTarget.rawId\n const result = new Map<string, (string | number)[]>()\n for (const group of groups) {\n const hit = group.items.find((i) => String(i.id) === targetId)\n if (hit) result.set(groupKey(group), [hit.id])\n }\n return result.size > 0 ? result : null\n }, [groups, hashTarget])\n\n // Accordion is uncontrolled — `defaultOpenIds` is only consumed at mount,\n // so a new item hash needs a remount to honor it. Keying off the item-id\n // suffix triggers exactly the remount we need (and stays stable when the\n // hash points at a section, so category navigation never disturbs the\n // accordion's open state).\n const accordionKeySuffix =\n hashTarget?.kind === 'item' ? `item:${hashTarget.rawId}` : 'default'\n\n // Unified scroll-to-hash: rAF-polls for the target row (outlasts the SWR self-fetch\n // + Radix accordion expand) and re-fires on `hashchange` — including the synthetic\n // event `navigateSamePageHash` dispatches for a SOFT same-page chat-card nav (a\n // host-router `pushState` fires no `hashchange`). The ready-dep keys off BOTH the\n // loaded `groups` (so a deep-link / refresh scrolls once the self-fetched list\n // mounts) AND `hashTarget` (so a soft nav re-runs the scroll AFTER the re-render\n // that remounts + opens the cited row, landing on its FINAL expanded position\n // instead of racing the uncontrolled accordion's key-remount — the bare-`hashchange`\n // listener alone fires before the remount and lands on the stale closed position).\n const scrollDep =\n `${groups.length}|` +\n (hashTarget?.kind === 'item'\n ? `item:${hashTarget.rawId}`\n : hashTarget?.kind === 'section'\n ? `section:${hashTarget.slug}`\n : 'none')\n useScrollToHash(scrollDep, { headerOffset: STICKY_HEADER_OFFSET_PX })\n\n // A section hash also lights up its category pill; item hashes leave the pills\n // untouched so deep-linking a question never disturbs scroll-spy state.\n useEffect(() => {\n if (hashTarget?.kind === 'section') setActiveSlug(hashTarget.slug)\n }, [hashTarget])\n\n // Category pill click. `navigateSamePageHash` owns the entire transition:\n // replaceState → synthetic `hashchange` → `scrollElementIntoView` tween\n // with `STICKY_HEADER_OFFSET_PX` so the section heading lands BELOW the\n // sticky category nav on the FIRST tween (covers the same-target\n // re-click case, where the `hashTarget` effect at L214 is a no-op\n // because the state reference is equal). For DIFFERENT-target clicks\n // the helper's synthetic `hashchange` re-fires that effect, which\n // re-scrolls with the same offset and cancels this tween (singleton)\n // — both paths land at the same position. The effect is still\n // required for back/forward + direct URL edits, where the helper\n // isn't in the call chain.\n //\n // `history: 'replace'` matches the pre-helper behavior: category pills\n // are a TOC, not a navigation step, so the Back button leaves the\n // FAQ page in one step regardless of how many categories the user\n // clicked through.\n const handleJump = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>, slug: string) => {\n e.preventDefault()\n navigateSamePageHash('#' + slug, {\n headerOffset: STICKY_HEADER_OFFSET_PX,\n history: 'replace',\n })\n },\n [],\n )\n\n return (\n <div className=\"space-y-8\">\n {navGroups.length > 1 && (\n <nav aria-label=\"FAQ categories\" className=\"flex flex-wrap gap-2\">\n {navGroups.map((group) => {\n const isActive = group.slug === activeSlug\n return (\n <a\n key={group.slug}\n href={`#${group.slug}`}\n aria-current={isActive ? 'true' : undefined}\n onClick={(e) => handleJump(e, group.slug as string)}\n className={cn(\n \"rounded-full border px-4 py-2 text-h6 transition-colors\",\n isActive\n ? 'border-ods-text-primary bg-ods-card text-ods-text-primary'\n : 'border-ods-border bg-ods-card text-ods-text-secondary hover:border-ods-text-secondary hover:text-ods-text-primary',\n )}\n >\n {group.section}\n </a>\n )\n })}\n </nav>\n )}\n <div className=\"space-y-10\">\n {groups.map((group) => {\n const key = groupKey(group)\n return (\n <section\n key={key}\n id={group.slug ?? undefined}\n className=\"scroll-mt-24 space-y-4\"\n >\n {group.section && (\n <CategoryHeading className={SECTION_HEADING_CLASS}>{group.section}</CategoryHeading>\n )}\n <FaqAccordion\n // Re-key on item-hash changes so the remount picks up the new\n // `defaultOpenIds` (the accordion is uncontrolled). Stable for\n // section hashes — category navigation doesn't disturb state.\n key={`${key}:${accordionKeySuffix}`}\n items={group.items}\n defaultOpenIds={defaultOpenByGroupKey?.get(key)}\n />\n </section>\n )\n })}\n </div>\n </div>\n )\n}\n\nfunction FaqSkeleton() {\n return (\n <div className=\"space-y-8 animate-pulse\">\n <div className=\"h-12 md:h-14 w-2/3 rounded bg-ods-border\" />\n <div className=\"rounded-md border border-ods-border overflow-hidden bg-transparent divide-y divide-ods-border w-full\">\n {Array.from({ length: 8 }).map((_, idx) => (\n <div key={idx} className=\"flex items-center justify-between gap-6 md:gap-10 px-6 py-4\">\n <div className=\"h-6 w-5/6 rounded bg-ods-border\" />\n <div className=\"size-6 rounded bg-ods-border\" />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\ninterface FaqsResponse {\n faqs: Faq[]\n}\n\n/**\n * The FAQ display surface — ONE rendering on every host: the list grouped by\n * `faq.section` (each category a heading + its own accordion, with a category\n * jump-nav once there are 2+ categories). There is no flat/ungrouped mode and\n * no page-vs-embedded shell fork; the standalone /faqs page and every embed\n * render through this single path, so they cannot drift.\n *\n * - Standalone /faqs page: pass `initialFaqs` (SSR) + `heading={null}` (the\n * page owns the <h1>) + `emitJsonLd` with `jsonLd` overrides for SEO.\n * - Per-entity embed: pass `entityType` + `entityId` (no `initialFaqs`); the\n * hook self-fetches `GET /api/faqs`, and `heading` is this block's own <h2>.\n *\n * CONTRACT: the consuming app MUST implement `GET /api/faqs`. On a fetch error\n * (or zero FAQs) the component renders nothing so the host page isn't\n * disfigured. The host always supplies the page shell — this renders a bare\n * <section>.\n */\nexport function FaqSection({\n initialFaqs,\n entityType,\n entityId,\n heading,\n emitJsonLd = false,\n jsonLd,\n className,\n minResults,\n apiBaseUrl = '',\n}: FaqSectionProps) {\n const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl)\n // Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render\n // wrapper object would setState-loop under re-rendering parents.\n const initialData = useMemo<FaqsResponse | undefined>(\n () => (initialFaqs ? { faqs: initialFaqs } : undefined),\n [initialFaqs],\n )\n const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData })\n\n const faqs = data?.faqs ?? []\n // Grouped before the early returns so the hook order stays stable.\n const groups = useMemo(() => (faqs.length > 0 ? groupFaqsBySection(faqs) : []), [faqs])\n\n // `undefined` → default <h2> title; `null` → the host page owns the <h1>, so\n // no title renders here. `heading === null` also makes the category headings\n // <h2> (directly under the page <h1>); otherwise they nest as <h3>.\n const headingNode =\n heading === undefined ? <h2 className={SECTION_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h2> : heading\n\n // Degrade silently — never show an error banner or an empty section shell\n // where FAQs would be (host pages and the standalone surface both rely on it).\n if (error) return null\n if (!isLoading && faqs.length === 0) return null\n if (isLoading && faqs.length === 0) {\n return (\n <div className={className}>\n <FaqSkeleton />\n </div>\n )\n }\n\n const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null\n\n return (\n <>\n <section className={className ?? 'space-y-10'}>\n {headingNode}\n <GroupedFaqList groups={groups} categoryHeadingAs={heading === null ? 'h2' : 'h3'} />\n </section>\n {schema && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // serializeJsonLd, NOT raw JSON.stringify — FAQ answers are\n // admin-entered; an embedded \"</script>\" must not break the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }}\n />\n )}\n </>\n )\n}\n","\"use client\"\n\nimport React, { useRef, useState, useEffect, useCallback } from 'react'\nimport { Chevron02DownIcon } from './icons-v2-generated/arrows/chevron-02-down-icon'\nimport { cn } from \"../utils/cn\"\nimport { faqItemAnchor } from \"../utils/faq-anchor\"\n\nexport interface FaqItem {\n id: number | string\n question: string\n answer: string\n}\n\ninterface FaqAccordionProps {\n items: FaqItem[]\n defaultOpenIds?: (number | string)[]\n}\n\n// Utility to measure scrollHeight outside render cycle\nconst useMeasuredHeight = (isOpen: boolean) => {\n const ref = useRef<HTMLDivElement | null>(null)\n const [maxHeight, setMaxHeight] = useState<string>('0px')\n\n const measure = useCallback(() => {\n if (ref.current) {\n const height = ref.current.scrollHeight\n setMaxHeight(`${height}px`)\n }\n }, [])\n\n // Update height only when section is open\n useEffect(() => {\n if (isOpen) {\n measure()\n } else {\n setMaxHeight('0px')\n }\n }, [isOpen, measure])\n\n return { ref, maxHeight }\n}\n\nexport function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps) {\n const [openSet, setOpenSet] = useState<Set<string | number>>(new Set(defaultOpenIds))\n\n const toggle = (id: string | number) => {\n setOpenSet(prev => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"rounded-md border border-ods-border divide-y divide-ods-border bg-transparent overflow-hidden\">\n {items.map(item => {\n const isOpen = openSet.has(item.id)\n const { ref, maxHeight } = useMeasuredHeight(isOpen)\n\n return (\n <div\n key={item.id}\n // Per-row anchor — chat citation chips (`/faqs#faq-item-<id>`) land\n // here via native browser hash scroll AND via `FaqSection`'s tween\n // dispatch. `scroll-mt-24` keeps the row header below the 96px\n // sticky nav offset (matches `<section>`'s scroll-margin for\n // category anchors).\n id={faqItemAnchor(item.id)}\n className=\"scroll-mt-24 transition-colors hover:bg-ods-bg-hover\"\n >\n {/* Header */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => toggle(item.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggle(item.id);\n }\n }}\n aria-expanded={isOpen}\n className=\"flex w-full items-center gap-6 md:gap-10 px-6 py-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ods-focus transition-colors cursor-pointer\"\n >\n <h3 className=\"flex-1 min-w-0 break-words\">\n {item.question}\n </h3>\n <Chevron02DownIcon\n aria-hidden=\"true\"\n size={24}\n className={cn(\n 'shrink-0 text-ods-text-primary transition-transform duration-300',\n isOpen && 'rotate-180',\n )}\n />\n </div>\n {/* Content wrapper with max-height animation */}\n <div\n style={{ maxHeight, transition: 'max-height 0.35s ease-in-out, opacity 0.35s ease-in-out', opacity: isOpen ? 1 : 0 }}\n className=\"overflow-hidden\"\n >\n {/* break-words: FAQ answers render as plain text, so a long URL or\n token has no wrap opportunity — and the parent is overflow-hidden,\n which would CLIP it past the viewport on mobile. Mirrors the\n markdown-renderer overflow-wrap fix. */}\n <div ref={ref} className=\"px-6 pb-4 text-ods-text-primary text-h4 break-words\">\n {item.answer}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n )\n} ","/**\n * Pure FAQ JSON-LD builder. No React, no client-only deps — safe to import from\n * Server Components via the `./components/faq` subpath export. (Do NOT import\n * through the root `./components` barrel — that barrel is `\"use client\"` and\n * dragging it into a Server Component would force the client graph into the\n * server.)\n *\n * The hub used to harcode `name`/`description` here for OpenMSP; in the lib we\n * accept overrides so every embedder can supply its own platform branding.\n */\nimport type { Faq } from '../../types/faq'\n\nexport interface FaqSchemaOptions {\n name?: string\n description?: string\n url?: string\n}\n\nconst DEFAULT_NAME = 'Frequently Asked Questions'\nconst DEFAULT_DESCRIPTION =\n 'Answers to common questions.'\n\nexport function baseFaqSchema(opts: FaqSchemaOptions = {}) {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n name: opts.name ?? DEFAULT_NAME,\n description: opts.description ?? DEFAULT_DESCRIPTION,\n ...(opts.url ? { url: opts.url } : {}),\n } as const\n}\n\nexport function buildFaqJsonLdFromFaqs(faqs: Faq[], opts: FaqSchemaOptions = {}) {\n return {\n ...baseFaqSchema(opts),\n mainEntity: faqs.map((faq) => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n } as const\n}\n","'use client';\n\n/**\n * FaqDocumentPage — the full `/faqs` page with chrome, so embedders drop in\n * ONE component instead of hand-assembling `PageShell` + `PageLayout` around\n * the bare `<FaqSection>`. Mirrors `LegalDocumentPage` / `DevSectionPage`: the\n * page-level layout lives in the lib, the host page is a thin wrapper that\n * passes only config + the SSR-resolved data.\n *\n * HERO: the title + subtitle route through the canonical (frozen) `PageLayout`\n * `TitleBlock` with `titleSize=\"h1\"` — the unified Help Center header (the\n * `TitleBlock` element is always an `<h1>`; `titleSize` only sets its typography,\n * here `text-h1`). The page owns that `<h1>`, so `<FaqSection heading={null}>`\n * nests its category headings as `<h2>`/`<h3>` (the SEO-recommended FAQ document\n * outline).\n *\n * DATA: pass `initialFaqs` (SSR-resolved, platform-scoped) so `FaqSection`\n * skips its client self-fetch — the standalone page's static platform-only\n * contract. Omit it for embeds that want the self-fetching `/api/faqs`\n * suggestion-fill instead.\n */\n\nimport type { Faq } from '../../types/faq';\nimport { PageShell, PageLayout } from '../ui';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { FaqSection } from './faq-section';\nimport type { FaqSchemaOptions } from './json-ld';\n\nexport interface FaqDocumentPageProps {\n /** Page title (frozen `PageLayout` `TitleBlock`). Default \"Frequently Asked Questions\". */\n title?: string;\n /** Subtitle under the title. */\n subtitle?: string;\n /** Back-button config — same pattern as `DevSectionPage` / `LegalDocumentPage`.\n * Pass `false` to hide. Default `{ label: 'Back to home', href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default\n * true. Pass false when the host layout already provides the page container\n * (e.g. openframe-frontend's `AppLayout` `<main>`) — only the padding box\n * renders, avoiding a nested `<main>`. */\n shell?: boolean;\n /** SSR-hydrate `FaqSection` (skips the client fetch — the platform-only\n * contract for the standalone page). Omit for self-fetching embeds. */\n initialFaqs?: Faq[];\n /** Emit FAQPage schema.org JSON-LD (forwarded to `FaqSection`). Off by\n * default so embeds don't emit duplicate schema. */\n emitJsonLd?: boolean;\n /** JSON-LD name/description/url overrides (forwarded to `FaqSection`). */\n jsonLd?: FaqSchemaOptions;\n /** Base URL `FaqSection` appends `/api/faqs` to (reverse-proxy embedders). */\n apiBaseUrl?: string;\n /** Optional entity scoping forwarded to `FaqSection`. */\n entityType?: string;\n entityId?: number | string;\n /** Minimum FAQ count before the section renders (forwarded to `FaqSection`). */\n minResults?: number;\n}\n\nexport function FaqDocumentPage({\n title = 'Frequently Asked Questions',\n subtitle,\n backButton,\n initialFaqs,\n emitJsonLd,\n jsonLd,\n apiBaseUrl,\n entityType,\n entityId,\n minResults,\n shell = true,\n}: FaqDocumentPageProps) {\n const router = useRouter();\n\n // Back-button config — mirrors LegalDocumentPage / DevSectionPage. Hide\n // entirely when the caller passes `false` (embed-mode where the host owns\n // nav chrome).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n\n const inner = (\n <PageLayout title={title} subtitle={subtitle} backButton={backCfg} titleSize=\"h1\" titleWrap>\n <FaqSection\n initialFaqs={initialFaqs}\n heading={null}\n emitJsonLd={emitJsonLd}\n jsonLd={jsonLd}\n apiBaseUrl={apiBaseUrl}\n entityType={entityType}\n entityId={entityId}\n minResults={minResults}\n />\n </PageLayout>\n );\n\n return shell ? <PageShell>{inner}</PageShell> : <div className=\"page-shell-content\">{inner}</div>;\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-TRIU3RJD.cjs","../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts","../src/components/faq/faq-document-page.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-TRIU3RJD.cjs","sourcesContent":[null,"\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { Faq } from '../../types/faq'\nimport { FaqAccordion, type FaqItem } from '../faq-accordion'\nimport { useSelfFetch } from '../../hooks/use-self-fetch'\nimport { buildSuggestionUrl } from '../../utils/suggestion-url'\nimport { serializeJsonLd } from '../../utils/common'\nimport { useScrollToHash } from '../../hooks/use-scroll-to-hash'\nimport { navigateSamePageHash, STICKY_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav'\nimport { faqSectionSlug, parseFaqHash, type FaqHashTarget } from '../../utils/faq-anchor'\nimport { cn } from '../../utils/cn'\nimport { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld'\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading'\n\nexport interface FaqSectionProps {\n /**\n * SSR hydrate. When provided, the hook skips the first client fetch (per\n * useSelfFetch contract). The consuming server page resolves FAQs then drills\n * them into this prop — the lib never re-fetches what the host already gated on.\n */\n initialFaqs?: Faq[]\n /** Both required together for entity-attached FAQs; partial → bare /api/faqs. */\n entityType?: string\n entityId?: number | string\n /**\n * Heading node above the grouped list. `undefined` → default\n * `<h2>`\"Frequently Asked Questions\". `null` → no heading (the host page\n * owns the `<h1>`, as the standalone /faqs surface does). A React node lets a\n * platform drill its own <PageHeading> without the lib referencing platform\n * state. Also drives category nesting so the document outline stays correct:\n * `null` → categories render `<h2>` (directly under the page `<h1>`);\n * otherwise categories render `<h3>` beneath this heading.\n */\n heading?: React.ReactNode | null\n /** Inject FAQPage schema.org JSON-LD as a <script>. Off by default so embeds\n * don't emit duplicate schema. */\n emitJsonLd?: boolean\n /** Overrides for the JSON-LD's name/description/url. */\n jsonLd?: FaqSchemaOptions\n className?: string\n /** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param\n * not sent (server default applies). */\n minResults?: number\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin relative). */\n apiBaseUrl?: string\n}\n\nconst DEFAULT_HEADING_TEXT = 'Frequently Asked Questions'\n\n/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)\n * — byte-identical to the historical `buildFaqsUrl` output when\n * `minResults`/`apiBaseUrl` are absent. */\nfunction buildFaqsUrl(\n entityType?: string,\n entityId?: number | string,\n minResults?: number,\n apiBaseUrl = '',\n): string {\n return buildSuggestionUrl('/api/faqs', { apiBaseUrl, entityType, entityId, count: minResults })\n}\n\ninterface FaqGroup {\n /** null → the uncategorized bucket: no heading, no jump pill, rendered last. */\n section: string | null\n slug: string | null\n items: FaqItem[]\n}\n\n/** Group FAQs by `faq.section`, preserving the server's first-seen\n * (display_order) order for BOTH the section order and the rows within each\n * section. The uncategorized bucket (blank/missing section) sinks to the end\n * since it renders without a heading. Items carry NO badge here — the `<h2>`\n * IS the category, so a per-row chip would be redundant. */\nfunction groupFaqsBySection(faqs: Faq[]): FaqGroup[] {\n const order: string[] = []\n const byName = new Map<string, FaqGroup>()\n let uncategorized: FaqGroup | null = null\n for (const faq of faqs) {\n const item: FaqItem = { id: faq.id, question: faq.question, answer: faq.answer }\n const name = faq.section?.trim()\n if (!name) {\n if (!uncategorized) uncategorized = { section: null, slug: null, items: [] }\n uncategorized.items.push(item)\n continue\n }\n let group = byName.get(name)\n if (!group) {\n group = { section: name, slug: faqSectionSlug(name), items: [] }\n byName.set(name, group)\n order.push(name)\n }\n group.items.push(item)\n }\n const groups = order.map((name) => byName.get(name)!)\n if (uncategorized) groups.push(uncategorized)\n return groups\n}\n\n\n/** Map key for the uncategorized bucket — `group.slug` is null for it, so\n * every per-group map (default-open ids, accordion keys) uses this sentinel\n * to keep the lookup typed. */\nconst UNCATEGORIZED_KEY = '__uncategorized__'\nconst groupKey = (g: FaqGroup): string => g.slug ?? UNCATEGORIZED_KEY\n\n/**\n * Grouped FAQ layout: a category jump-nav above stacked `<h2>` category\n * sections (each its own accordion). Isolated into its own component so the\n * scroll-spy hooks only mount in grouped mode — `FaqSection`'s own hooks stay\n * unconditional.\n *\n * The pills are real `<a href=\"#slug\">` anchors (crawlable in-page links,\n * deep-linkable, work without JS); the click handler upgrades the jump to the\n * cancellation-proof `scrollElementIntoView` tween and syncs the URL hash.\n */\nfunction GroupedFaqList({\n groups,\n categoryHeadingAs,\n}: {\n groups: FaqGroup[]\n /** Heading tag for each category, so the document outline nests correctly\n * under whatever owns the heading above this block: `h2` on the standalone\n * page (the page owns the `<h1>`), `h3` beneath an embed's `<h2>` title.\n * The VISUAL is `SECTION_HEADING_CLASS` either way, so categories look\n * identical on every surface. */\n categoryHeadingAs: 'h2' | 'h3'\n}) {\n const CategoryHeading = categoryHeadingAs\n const navGroups = useMemo(() => groups.filter((g) => g.slug), [groups])\n const [activeSlug, setActiveSlug] = useState<string | null>(navGroups[0]?.slug ?? null)\n // Identity-stable key for the section set so the observer re-binds only when\n // the categories actually change (not on every parent re-render).\n const slugKey = navGroups.map((g) => g.slug).join('|')\n\n // Scroll-spy: mark the pill for the category currently at the top of the\n // viewport. rootMargin drops the trigger line just below the sticky header\n // and ignores the bottom ~55% so \"active\" is the section being read, not the\n // next one peeking in.\n useEffect(() => {\n if (navGroups.length < 2) return\n const tops = new Map<string, number>()\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id\n if (entry.isIntersecting) tops.set(id, entry.boundingClientRect.top)\n else tops.delete(id)\n }\n let bestId: string | null = null\n let bestTop = Number.POSITIVE_INFINITY\n for (const [id, top] of tops) {\n if (top < bestTop) {\n bestTop = top\n bestId = id\n }\n }\n if (bestId) setActiveSlug(bestId)\n },\n { rootMargin: `-${STICKY_HEADER_OFFSET_PX}px 0px -55% 0px`, threshold: 0 },\n )\n for (const group of navGroups) {\n const el = group.slug ? document.getElementById(group.slug) : null\n if (el) observer.observe(el)\n }\n return () => observer.disconnect()\n // slugKey encodes the section set; re-observe only when it changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slugKey])\n\n // ─── Hash dispatch — `/faqs#faq-item-<id>` or `/faqs#faq-<section-slug>` ──\n // Tracks the current hash so:\n // 1. an item-kind hash seeds `defaultOpenIds` on the matching accordion\n // (auto-expands the cited question);\n // 2. either kind triggers the cancellation-proof tween scroll with the\n // sticky-header offset (native browser hash scroll runs once, ignores\n // our offset — re-running the tween puts the target in the right spot).\n // Listens to `hashchange` so back/forward replays the same behavior. SSR-\n // safe: initial null state matches the server render; the first effect\n // tick on the client updates it.\n const [hashTarget, setHashTarget] = useState<FaqHashTarget | null>(null)\n useEffect(() => {\n const refresh = () => setHashTarget(parseFaqHash(window.location.hash))\n refresh()\n window.addEventListener('hashchange', refresh)\n return () => window.removeEventListener('hashchange', refresh)\n }, [])\n\n // Per-group default-open set when the hash points at an item. The map key\n // matches `groupKey(group)` so the render-time lookup is O(1) per group.\n const defaultOpenByGroupKey = useMemo(() => {\n if (hashTarget?.kind !== 'item') return null\n const targetId = hashTarget.rawId\n const result = new Map<string, (string | number)[]>()\n for (const group of groups) {\n const hit = group.items.find((i) => String(i.id) === targetId)\n if (hit) result.set(groupKey(group), [hit.id])\n }\n return result.size > 0 ? result : null\n }, [groups, hashTarget])\n\n // Accordion is uncontrolled — `defaultOpenIds` is only consumed at mount,\n // so a new item hash needs a remount to honor it. Keying off the item-id\n // suffix triggers exactly the remount we need (and stays stable when the\n // hash points at a section, so category navigation never disturbs the\n // accordion's open state).\n const accordionKeySuffix =\n hashTarget?.kind === 'item' ? `item:${hashTarget.rawId}` : 'default'\n\n // Unified scroll-to-hash: rAF-polls for the target row (outlasts the SWR self-fetch\n // + Radix accordion expand) and re-fires on `hashchange` — including the synthetic\n // event `navigateSamePageHash` dispatches for a SOFT same-page chat-card nav (a\n // host-router `pushState` fires no `hashchange`). The ready-dep keys off BOTH the\n // loaded `groups` (so a deep-link / refresh scrolls once the self-fetched list\n // mounts) AND `hashTarget` (so a soft nav re-runs the scroll AFTER the re-render\n // that remounts + opens the cited row, landing on its FINAL expanded position\n // instead of racing the uncontrolled accordion's key-remount — the bare-`hashchange`\n // listener alone fires before the remount and lands on the stale closed position).\n const scrollDep =\n `${groups.length}|` +\n (hashTarget?.kind === 'item'\n ? `item:${hashTarget.rawId}`\n : hashTarget?.kind === 'section'\n ? `section:${hashTarget.slug}`\n : 'none')\n useScrollToHash(scrollDep, { headerOffset: STICKY_HEADER_OFFSET_PX })\n\n // A section hash also lights up its category pill; item hashes leave the pills\n // untouched so deep-linking a question never disturbs scroll-spy state.\n useEffect(() => {\n if (hashTarget?.kind === 'section') setActiveSlug(hashTarget.slug)\n }, [hashTarget])\n\n // Category pill click. `navigateSamePageHash` owns the entire transition:\n // replaceState → synthetic `hashchange` → `scrollElementIntoView` tween\n // with `STICKY_HEADER_OFFSET_PX` so the section heading lands BELOW the\n // sticky category nav on the FIRST tween (covers the same-target\n // re-click case, where the `hashTarget` effect at L214 is a no-op\n // because the state reference is equal). For DIFFERENT-target clicks\n // the helper's synthetic `hashchange` re-fires that effect, which\n // re-scrolls with the same offset and cancels this tween (singleton)\n // — both paths land at the same position. The effect is still\n // required for back/forward + direct URL edits, where the helper\n // isn't in the call chain.\n //\n // `history: 'replace'` matches the pre-helper behavior: category pills\n // are a TOC, not a navigation step, so the Back button leaves the\n // FAQ page in one step regardless of how many categories the user\n // clicked through.\n const handleJump = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>, slug: string) => {\n e.preventDefault()\n navigateSamePageHash('#' + slug, {\n headerOffset: STICKY_HEADER_OFFSET_PX,\n history: 'replace',\n })\n },\n [],\n )\n\n return (\n <div className=\"space-y-8\">\n {navGroups.length > 1 && (\n <nav aria-label=\"FAQ categories\" className=\"flex flex-wrap gap-2\">\n {navGroups.map((group) => {\n const isActive = group.slug === activeSlug\n return (\n <a\n key={group.slug}\n href={`#${group.slug}`}\n aria-current={isActive ? 'true' : undefined}\n onClick={(e) => handleJump(e, group.slug as string)}\n className={cn(\n \"rounded-full border px-4 py-2 text-h6 transition-colors\",\n isActive\n ? 'border-ods-text-primary bg-ods-card text-ods-text-primary'\n : 'border-ods-border bg-ods-card text-ods-text-secondary hover:border-ods-text-secondary hover:text-ods-text-primary',\n )}\n >\n {group.section}\n </a>\n )\n })}\n </nav>\n )}\n <div className=\"space-y-10\">\n {groups.map((group) => {\n const key = groupKey(group)\n return (\n <section\n key={key}\n id={group.slug ?? undefined}\n className=\"scroll-mt-24 space-y-4\"\n >\n {group.section && (\n <CategoryHeading className={SECTION_HEADING_CLASS}>{group.section}</CategoryHeading>\n )}\n <FaqAccordion\n // Re-key on item-hash changes so the remount picks up the new\n // `defaultOpenIds` (the accordion is uncontrolled). Stable for\n // section hashes — category navigation doesn't disturb state.\n key={`${key}:${accordionKeySuffix}`}\n items={group.items}\n defaultOpenIds={defaultOpenByGroupKey?.get(key)}\n />\n </section>\n )\n })}\n </div>\n </div>\n )\n}\n\nfunction FaqSkeleton() {\n return (\n <div className=\"space-y-8 animate-pulse\">\n <div className=\"h-12 md:h-14 w-2/3 rounded bg-ods-border\" />\n <div className=\"rounded-md border border-ods-border overflow-hidden bg-transparent divide-y divide-ods-border w-full\">\n {Array.from({ length: 8 }).map((_, idx) => (\n <div key={idx} className=\"flex items-center justify-between gap-6 md:gap-10 px-6 py-4\">\n <div className=\"h-6 w-5/6 rounded bg-ods-border\" />\n <div className=\"size-6 rounded bg-ods-border\" />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\ninterface FaqsResponse {\n faqs: Faq[]\n}\n\n/**\n * The FAQ display surface — ONE rendering on every host: the list grouped by\n * `faq.section` (each category a heading + its own accordion, with a category\n * jump-nav once there are 2+ categories). There is no flat/ungrouped mode and\n * no page-vs-embedded shell fork; the standalone /faqs page and every embed\n * render through this single path, so they cannot drift.\n *\n * - Standalone /faqs page: pass `initialFaqs` (SSR) + `heading={null}` (the\n * page owns the <h1>) + `emitJsonLd` with `jsonLd` overrides for SEO.\n * - Per-entity embed: pass `entityType` + `entityId` (no `initialFaqs`); the\n * hook self-fetches `GET /api/faqs`, and `heading` is this block's own <h2>.\n *\n * CONTRACT: the consuming app MUST implement `GET /api/faqs`. On a fetch error\n * (or zero FAQs) the component renders nothing so the host page isn't\n * disfigured. The host always supplies the page shell — this renders a bare\n * <section>.\n */\nexport function FaqSection({\n initialFaqs,\n entityType,\n entityId,\n heading,\n emitJsonLd = false,\n jsonLd,\n className,\n minResults,\n apiBaseUrl = '',\n}: FaqSectionProps) {\n const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl)\n // Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render\n // wrapper object would setState-loop under re-rendering parents.\n const initialData = useMemo<FaqsResponse | undefined>(\n () => (initialFaqs ? { faqs: initialFaqs } : undefined),\n [initialFaqs],\n )\n const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData })\n\n const faqs = data?.faqs ?? []\n // Grouped before the early returns so the hook order stays stable.\n const groups = useMemo(() => (faqs.length > 0 ? groupFaqsBySection(faqs) : []), [faqs])\n\n // `undefined` → default <h2> title; `null` → the host page owns the <h1>, so\n // no title renders here. `heading === null` also makes the category headings\n // <h2> (directly under the page <h1>); otherwise they nest as <h3>.\n const headingNode =\n heading === undefined ? <h2 className={SECTION_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h2> : heading\n\n // Degrade silently — never show an error banner or an empty section shell\n // where FAQs would be (host pages and the standalone surface both rely on it).\n if (error) return null\n if (!isLoading && faqs.length === 0) return null\n if (isLoading && faqs.length === 0) {\n return (\n <div className={className}>\n <FaqSkeleton />\n </div>\n )\n }\n\n const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null\n\n return (\n <>\n <section className={className ?? 'space-y-10'}>\n {headingNode}\n <GroupedFaqList groups={groups} categoryHeadingAs={heading === null ? 'h2' : 'h3'} />\n </section>\n {schema && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // serializeJsonLd, NOT raw JSON.stringify — FAQ answers are\n // admin-entered; an embedded \"</script>\" must not break the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }}\n />\n )}\n </>\n )\n}\n","\"use client\"\n\nimport React, { useRef, useState, useEffect, useCallback } from 'react'\nimport { Chevron02DownIcon } from './icons-v2-generated/arrows/chevron-02-down-icon'\nimport { cn } from \"../utils/cn\"\nimport { faqItemAnchor } from \"../utils/faq-anchor\"\n\nexport interface FaqItem {\n id: number | string\n question: string\n answer: string\n}\n\ninterface FaqAccordionProps {\n items: FaqItem[]\n defaultOpenIds?: (number | string)[]\n}\n\n// Utility to measure scrollHeight outside render cycle\nconst useMeasuredHeight = (isOpen: boolean) => {\n const ref = useRef<HTMLDivElement | null>(null)\n const [maxHeight, setMaxHeight] = useState<string>('0px')\n\n const measure = useCallback(() => {\n if (ref.current) {\n const height = ref.current.scrollHeight\n setMaxHeight(`${height}px`)\n }\n }, [])\n\n // Update height only when section is open\n useEffect(() => {\n if (isOpen) {\n measure()\n } else {\n setMaxHeight('0px')\n }\n }, [isOpen, measure])\n\n return { ref, maxHeight }\n}\n\nexport function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps) {\n const [openSet, setOpenSet] = useState<Set<string | number>>(new Set(defaultOpenIds))\n\n const toggle = (id: string | number) => {\n setOpenSet(prev => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"rounded-md border border-ods-border divide-y divide-ods-border bg-transparent overflow-hidden\">\n {items.map(item => {\n const isOpen = openSet.has(item.id)\n const { ref, maxHeight } = useMeasuredHeight(isOpen)\n\n return (\n <div\n key={item.id}\n // Per-row anchor — chat citation chips (`/faqs#faq-item-<id>`) land\n // here via native browser hash scroll AND via `FaqSection`'s tween\n // dispatch. `scroll-mt-24` keeps the row header below the 96px\n // sticky nav offset (matches `<section>`'s scroll-margin for\n // category anchors).\n id={faqItemAnchor(item.id)}\n className=\"scroll-mt-24 transition-colors hover:bg-ods-bg-hover\"\n >\n {/* Header */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => toggle(item.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggle(item.id);\n }\n }}\n aria-expanded={isOpen}\n className=\"flex w-full items-center gap-6 md:gap-10 px-6 py-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ods-focus transition-colors cursor-pointer\"\n >\n <h3 className=\"flex-1 min-w-0 break-words\">\n {item.question}\n </h3>\n <Chevron02DownIcon\n aria-hidden=\"true\"\n size={24}\n className={cn(\n 'shrink-0 text-ods-text-primary transition-transform duration-300',\n isOpen && 'rotate-180',\n )}\n />\n </div>\n {/* Content wrapper with max-height animation */}\n <div\n style={{ maxHeight, transition: 'max-height 0.35s ease-in-out, opacity 0.35s ease-in-out', opacity: isOpen ? 1 : 0 }}\n className=\"overflow-hidden\"\n >\n {/* break-words: FAQ answers render as plain text, so a long URL or\n token has no wrap opportunity — and the parent is overflow-hidden,\n which would CLIP it past the viewport on mobile. Mirrors the\n markdown-renderer overflow-wrap fix. */}\n <div ref={ref} className=\"px-6 pb-4 text-ods-text-primary text-h4 break-words\">\n {item.answer}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n )\n} ","/**\n * Pure FAQ JSON-LD builder. No React, no client-only deps — safe to import from\n * Server Components via the `./components/faq` subpath export. (Do NOT import\n * through the root `./components` barrel — that barrel is `\"use client\"` and\n * dragging it into a Server Component would force the client graph into the\n * server.)\n *\n * The hub used to harcode `name`/`description` here for OpenMSP; in the lib we\n * accept overrides so every embedder can supply its own platform branding.\n */\nimport type { Faq } from '../../types/faq'\n\nexport interface FaqSchemaOptions {\n name?: string\n description?: string\n url?: string\n}\n\nconst DEFAULT_NAME = 'Frequently Asked Questions'\nconst DEFAULT_DESCRIPTION =\n 'Answers to common questions.'\n\nexport function baseFaqSchema(opts: FaqSchemaOptions = {}) {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n name: opts.name ?? DEFAULT_NAME,\n description: opts.description ?? DEFAULT_DESCRIPTION,\n ...(opts.url ? { url: opts.url } : {}),\n } as const\n}\n\nexport function buildFaqJsonLdFromFaqs(faqs: Faq[], opts: FaqSchemaOptions = {}) {\n return {\n ...baseFaqSchema(opts),\n mainEntity: faqs.map((faq) => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n } as const\n}\n","'use client';\n\n/**\n * FaqDocumentPage — the full `/faqs` page with chrome, so embedders drop in\n * ONE component instead of hand-assembling `PageShell` + `PageLayout` around\n * the bare `<FaqSection>`. Mirrors `LegalDocumentPage` / `DevSectionPage`: the\n * page-level layout lives in the lib, the host page is a thin wrapper that\n * passes only config + the SSR-resolved data.\n *\n * HERO: the title + subtitle route through the canonical (frozen) `PageLayout`\n * `TitleBlock` with `titleSize=\"h1\"` — the unified Help Center header (the\n * `TitleBlock` element is always an `<h1>`; `titleSize` only sets its typography,\n * here `text-h1`). The page owns that `<h1>`, so `<FaqSection heading={null}>`\n * nests its category headings as `<h2>`/`<h3>` (the SEO-recommended FAQ document\n * outline).\n *\n * DATA: pass `initialFaqs` (SSR-resolved, platform-scoped) so `FaqSection`\n * skips its client self-fetch — the standalone page's static platform-only\n * contract. Omit it for embeds that want the self-fetching `/api/faqs`\n * suggestion-fill instead.\n */\n\nimport type { Faq } from '../../types/faq';\nimport { PageShell, PageLayout } from '../ui';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { FaqSection } from './faq-section';\nimport type { FaqSchemaOptions } from './json-ld';\n\nexport interface FaqDocumentPageProps {\n /** Page title (frozen `PageLayout` `TitleBlock`). Default \"Frequently Asked Questions\". */\n title?: string;\n /** Subtitle under the title. */\n subtitle?: string;\n /** Back-button config — same pattern as `DevSectionPage` / `LegalDocumentPage`.\n * Pass `false` to hide. Default `{ label: 'Back to home', href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default\n * true. Pass false when the host layout already provides the page container\n * (e.g. openframe-frontend's `AppLayout` `<main>`) — only the padding box\n * renders, avoiding a nested `<main>`. */\n shell?: boolean;\n /** SSR-hydrate `FaqSection` (skips the client fetch — the platform-only\n * contract for the standalone page). Omit for self-fetching embeds. */\n initialFaqs?: Faq[];\n /** Emit FAQPage schema.org JSON-LD (forwarded to `FaqSection`). Off by\n * default so embeds don't emit duplicate schema. */\n emitJsonLd?: boolean;\n /** JSON-LD name/description/url overrides (forwarded to `FaqSection`). */\n jsonLd?: FaqSchemaOptions;\n /** Base URL `FaqSection` appends `/api/faqs` to (reverse-proxy embedders). */\n apiBaseUrl?: string;\n /** Optional entity scoping forwarded to `FaqSection`. */\n entityType?: string;\n entityId?: number | string;\n /** Minimum FAQ count before the section renders (forwarded to `FaqSection`). */\n minResults?: number;\n}\n\nexport function FaqDocumentPage({\n title = 'Frequently Asked Questions',\n subtitle,\n backButton,\n initialFaqs,\n emitJsonLd,\n jsonLd,\n apiBaseUrl,\n entityType,\n entityId,\n minResults,\n shell = true,\n}: FaqDocumentPageProps) {\n const router = useRouter();\n\n // Back-button config — mirrors LegalDocumentPage / DevSectionPage. Hide\n // entirely when the caller passes `false` (embed-mode where the host owns\n // nav chrome).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n\n const inner = (\n <PageLayout title={title} subtitle={subtitle} backButton={backCfg} titleSize=\"h1\" titleWrap>\n <FaqSection\n initialFaqs={initialFaqs}\n heading={null}\n emitJsonLd={emitJsonLd}\n jsonLd={jsonLd}\n apiBaseUrl={apiBaseUrl}\n entityType={entityType}\n entityId={entityId}\n minResults={minResults}\n />\n </PageLayout>\n );\n\n return shell ? <PageShell>{inner}</PageShell> : <div className=\"page-shell-content\">{inner}</div>;\n}\n"]}
@@ -1,16 +1,16 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
3
 
4
- var _chunkOBJSXQ67cjs = require('./chunk-OBJSXQ67.cjs');
4
+ var _chunkR3Y65KPTcjs = require('./chunk-R3Y65KPT.cjs');
5
5
 
6
6
 
7
- var _chunk5PMRHRMMcjs = require('./chunk-5PMRHRMM.cjs');
7
+ var _chunkPA6L55NFcjs = require('./chunk-PA6L55NF.cjs');
8
8
 
9
9
 
10
- var _chunkQQZWLTYUcjs = require('./chunk-QQZWLTYU.cjs');
10
+ var _chunk2HL3BXWVcjs = require('./chunk-2HL3BXWV.cjs');
11
11
 
12
12
 
13
- var _chunkQGLEI6T2cjs = require('./chunk-QGLEI6T2.cjs');
13
+ var _chunkJZC5BBAFcjs = require('./chunk-JZC5BBAF.cjs');
14
14
 
15
15
 
16
16
  var _chunkGYONSC3Ucjs = require('./chunk-GYONSC3U.cjs');
@@ -45,7 +45,7 @@ var _chunkGYONSC3Ucjs = require('./chunk-GYONSC3U.cjs');
45
45
 
46
46
 
47
47
 
48
- var _chunk6FLRFCK4cjs = require('./chunk-6FLRFCK4.cjs');
48
+ var _chunk5ZQKEGL3cjs = require('./chunk-5ZQKEGL3.cjs');
49
49
 
50
50
 
51
51
 
@@ -76,8 +76,8 @@ var _chunkKI4EGKNVcjs = require('./chunk-KI4EGKNV.cjs');
76
76
  _chunkKI4EGKNVcjs.init_cn.call(void 0, );
77
77
  var _jsxruntime = require('react/jsx-runtime');
78
78
  var DEFAULT_ENDPOINT = "/api/releases";
79
- var DEFAULT_SEARCH_PARAM_KEY = _chunk6FLRFCK4cjs.DEV_SECTION_PARAM_KEYS.search;
80
- var DEFAULT_STATUS_PARAM_KEY = _chunk6FLRFCK4cjs.DEV_SECTION_PARAM_KEYS.releaseStatus;
79
+ var DEFAULT_SEARCH_PARAM_KEY = _chunk5ZQKEGL3cjs.DEV_SECTION_PARAM_KEYS.search;
80
+ var DEFAULT_STATUS_PARAM_KEY = _chunk5ZQKEGL3cjs.DEV_SECTION_PARAM_KEYS.releaseStatus;
81
81
  var DEFAULT_PAGE_PARAM_KEY = "page";
82
82
  function ReleaseRow({
83
83
  release,
@@ -86,17 +86,17 @@ function ReleaseRow({
86
86
  }) {
87
87
  const runtime = _chunk7OPWSWEJcjs.useChatRuntime.call(void 0, );
88
88
  const router = _chunkSSJNN5BScjs.useRouter.call(void 0, );
89
- const cta = _chunk6FLRFCK4cjs.resolveContentHref.call(void 0, _optionalChain([runtime, 'optionalAccess', _2 => _2.composeContentUrl]), {
89
+ const cta = _chunk5ZQKEGL3cjs.resolveContentHref.call(void 0, _optionalChain([runtime, 'optionalAccess', _2 => _2.composeContentUrl]), {
90
90
  type: "product_release",
91
91
  slug: release.slug,
92
92
  basePath,
93
93
  platforms: release.product_release_platforms
94
94
  });
95
- const { target, rel } = _chunk6FLRFCK4cjs.useEntityCardLink.call(void 0, { href: cta.href, targetPlatform: cta.targetPlatform });
95
+ const { target, rel } = _chunk5ZQKEGL3cjs.useEntityCardLink.call(void 0, { href: cta.href, targetPlatform: cta.targetPlatform });
96
96
  const onClick = (e) => {
97
- if (e.defaultPrevented || _chunk6FLRFCK4cjs.isModifierClick.call(void 0, e) || target === "_blank") return;
97
+ if (e.defaultPrevented || _chunk5ZQKEGL3cjs.isModifierClick.call(void 0, e) || target === "_blank") return;
98
98
  e.preventDefault();
99
- _chunk6FLRFCK4cjs.executeNavigationImperative.call(void 0, {
99
+ _chunk5ZQKEGL3cjs.executeNavigationImperative.call(void 0, {
100
100
  runtime,
101
101
  href: cta.href,
102
102
  targetPlatform: cta.targetPlatform,
@@ -104,7 +104,7 @@ function ReleaseRow({
104
104
  });
105
105
  };
106
106
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
107
- _chunk6FLRFCK4cjs.ProductReleaseCard,
107
+ _chunk5ZQKEGL3cjs.ProductReleaseCard,
108
108
  {
109
109
  size: "lg",
110
110
  title: release.title,
@@ -120,7 +120,7 @@ function ProductReleasesView({
120
120
  initialData,
121
121
  itemsPerPage = 5,
122
122
  basePath = "/releases",
123
- buildCardProps = _chunk6FLRFCK4cjs.buildProductReleaseCardProps,
123
+ buildCardProps = _chunk5ZQKEGL3cjs.buildProductReleaseCardProps,
124
124
  searchParamKey = DEFAULT_SEARCH_PARAM_KEY,
125
125
  statusParamKey = DEFAULT_STATUS_PARAM_KEY,
126
126
  pageParamKey = DEFAULT_PAGE_PARAM_KEY,
@@ -158,10 +158,10 @@ function ProductReleasesView({
158
158
  router.replace(`${pathname}?${params.toString()}`, { scroll: false });
159
159
  };
160
160
  if (error) {
161
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _chunkKI4EGKNVcjs.cn.call(void 0, "w-full", className), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.LoadError, { message: "Failed to load releases.", onRetry: reload }) });
161
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _chunkKI4EGKNVcjs.cn.call(void 0, "w-full", className), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.LoadError, { message: "Failed to load releases.", onRetry: reload }) });
162
162
  }
163
163
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _chunkKI4EGKNVcjs.cn.call(void 0, "w-full flex flex-col gap-[40px]", className), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "min-h-[600px]", children: showEmpty ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-[600px] flex items-center justify-center", children: hasActiveFilters ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
164
- _chunkQQZWLTYUcjs.EmptyState,
164
+ _chunk2HL3BXWVcjs.EmptyState,
165
165
  {
166
166
  type: "search",
167
167
  title: "No releases found",
@@ -171,7 +171,7 @@ function ProductReleasesView({
171
171
  onCtaClick: resetFilters
172
172
  }
173
173
  ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
174
- _chunkQQZWLTYUcjs.EmptyState,
174
+ _chunk2HL3BXWVcjs.EmptyState,
175
175
  {
176
176
  type: "generic",
177
177
  title: "No releases available",
@@ -186,13 +186,13 @@ function ProductReleasesView({
186
186
  "div",
187
187
  {
188
188
  style: { visibility: isLoading || hasData ? "visible" : "hidden" },
189
- children: isLoading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.ProductReleaseCardSkeleton, { size: "lg" }) : release ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ReleaseRow, { release, basePath, buildCardProps }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.ProductReleaseCardSkeleton, { size: "lg" })
189
+ children: isLoading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.ProductReleaseCardSkeleton, { size: "lg" }) : release ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ReleaseRow, { release, basePath, buildCardProps }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.ProductReleaseCardSkeleton, { size: "lg" })
190
190
  },
191
191
  _nullishCoalesce(_optionalChain([release, 'optionalAccess', _5 => _5.id]), () => ( `slot-${i}`))
192
192
  );
193
193
  }) }),
194
194
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mt-6 md:mt-8 flex justify-center", children: isLoading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-12 m-3 w-64" }) : releases.length > 0 && totalPages > 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
195
- _chunkOBJSXQ67cjs.PersistentPaginationWrapper,
195
+ _chunkR3Y65KPTcjs.PersistentPaginationWrapper,
196
196
  {
197
197
  isLoading: false,
198
198
  currentPage,
@@ -234,10 +234,10 @@ function MediaGalleryStrip({ items, className }) {
234
234
  mediaItem.id || index
235
235
  );
236
236
  }
237
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: tileClass, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.Video, { url: mediaItem.media_url, layout: "native" }) }, mediaItem.id || index);
237
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: tileClass, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.Video, { url: mediaItem.media_url, layout: "native" }) }, mediaItem.id || index);
238
238
  }) }),
239
239
  galleryImages.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
240
- _chunk6FLRFCK4cjs.ImageGalleryModal,
240
+ _chunk5ZQKEGL3cjs.ImageGalleryModal,
241
241
  {
242
242
  images: galleryImages,
243
243
  isOpen: galleryOpen,
@@ -254,7 +254,7 @@ _chunkSSJNN5BScjs.init_next_navigation.call(void 0, );
254
254
 
255
255
  var _lucidereact = require('lucide-react');
256
256
 
257
- var DefaultMarkdownRenderer = _chunk6FLRFCK4cjs.RichMarkdownRenderer;
257
+ var DefaultMarkdownRenderer = _chunk5ZQKEGL3cjs.RichMarkdownRenderer;
258
258
  function ReleaseDetailPage({
259
259
  authorHref,
260
260
  slug,
@@ -271,7 +271,7 @@ function ReleaseDetailPage({
271
271
  shell = true
272
272
  }) {
273
273
  const router = _chunkSSJNN5BScjs.useRouter.call(void 0, );
274
- const renderShell = (node) => shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.PageShell, { children: node }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "page-shell-content", children: node });
274
+ const renderShell = (node) => shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.PageShell, { children: node }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "page-shell-content", children: node });
275
275
  const { data: fetchedRelease, error, isLoading } = useRelease(initialData ? void 0 : slug);
276
276
  const release = initialData || fetchedRelease;
277
277
  const showBackButton = backButton !== false;
@@ -315,7 +315,7 @@ function ReleaseDetailPage({
315
315
  return renderShell(
316
316
  // Match the loaded page's top offset (TitleBlock's
317
317
  // `pt-[var(--spacing-system-l)]`) so content doesn't jump on load.
318
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pt-[var(--spacing-system-l)]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQGLEI6T2cjs.DetailPageSkeleton, { bare: true, metadataColumns: 4, showImageGallery: true }) })
318
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pt-[var(--spacing-system-l)]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJZC5BBAFcjs.DetailPageSkeleton, { bare: true, metadataColumns: 4, showImageGallery: true }) })
319
319
  );
320
320
  }
321
321
  if (error || !release) {
@@ -351,7 +351,7 @@ function ReleaseDetailPage({
351
351
  const improvements = release.improvements;
352
352
  return renderShell(
353
353
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
354
- _chunk6FLRFCK4cjs.PageLayout,
354
+ _chunk5ZQKEGL3cjs.PageLayout,
355
355
  {
356
356
  title: releaseTitle,
357
357
  subtitle: `Version: ${releaseVersion}`,
@@ -360,7 +360,7 @@ function ReleaseDetailPage({
360
360
  backButton: showBackButton ? { label: backLabel, onClick: () => router.push(backHref) } : void 0,
361
361
  children: [
362
362
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-6 md:space-y-8", children: [
363
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.EntityTagBadges, { tags: release.product_release_tags }),
363
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.EntityTagBadges, { tags: release.product_release_tags }),
364
364
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid grid-cols-1 md:grid-cols-4 border border-ods-border rounded-md overflow-hidden w-full", children: [
365
365
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bg-ods-card border-b md:border-b-0 md:border-r border-ods-border p-4 flex flex-col gap-3", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-0", children: [
366
366
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-h4 text-ods-text-primary", children: releaseType.toLocaleUpperCase() }),
@@ -371,11 +371,11 @@ function ReleaseDetailPage({
371
371
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-h6 text-ods-text-secondary", children: "Release Status" })
372
372
  ] }) }),
373
373
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bg-ods-card border-b md:border-b-0 md:border-r border-ods-border p-4 flex flex-col gap-3", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-0", children: [
374
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-h4 text-ods-text-primary", children: _chunk6FLRFCK4cjs.formatReleaseDate.call(void 0, releaseDate) }),
374
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-h4 text-ods-text-primary", children: _chunk5ZQKEGL3cjs.formatReleaseDate.call(void 0, releaseDate) }),
375
375
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-h6 text-ods-text-secondary", children: "Release Date" })
376
376
  ] }) }),
377
377
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
378
- _chunk6FLRFCK4cjs.EntityMetadataAuthorCell,
378
+ _chunk5ZQKEGL3cjs.EntityMetadataAuthorCell,
379
379
  {
380
380
  author: _nullishCoalesce(author, () => ( { full_name: null, avatar_url: null })),
381
381
  authorHref: _optionalChain([author, 'optionalAccess', _6 => _6.full_name]) ? authorHref : void 0
@@ -393,16 +393,16 @@ function ReleaseDetailPage({
393
393
  highlightVideoThumbnail,
394
394
  title: releaseTitle,
395
395
  videoBites,
396
- bitesTitle: _chunk6FLRFCK4cjs.DEFAULT_VIDEO_BITES_TITLE,
396
+ bitesTitle: _chunk5ZQKEGL3cjs.DEFAULT_VIDEO_BITES_TITLE,
397
397
  filterPublishedBites: true,
398
- bitesProfile: _chunk6FLRFCK4cjs.toStripProfile.call(void 0, author),
398
+ bitesProfile: _chunk5ZQKEGL3cjs.toStripProfile.call(void 0, author),
399
399
  bitesHref: typeof release.slug === "string" ? `/releases/${release.slug}` : void 0,
400
400
  srtContent: _optionalChain([release, 'optionalAccess', _7 => _7.srt_content]),
401
401
  captionsUrl: _optionalChain([release, 'optionalAccess', _8 => _8.captionsUrl])
402
402
  }
403
403
  ) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
404
404
  youtubeUrl && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
405
- _chunk6FLRFCK4cjs.Video,
405
+ _chunk5ZQKEGL3cjs.Video,
406
406
  {
407
407
  kind: "youtube",
408
408
  url: youtubeUrl,
@@ -411,7 +411,7 @@ function ReleaseDetailPage({
411
411
  }
412
412
  ),
413
413
  !youtubeUrl && mainVideoUrl && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
414
- _chunk6FLRFCK4cjs.Video,
414
+ _chunk5ZQKEGL3cjs.Video,
415
415
  {
416
416
  url: mainVideoUrl,
417
417
  srtContent: _optionalChain([release, 'optionalAccess', _9 => _9.srt_content]),
@@ -420,7 +420,7 @@ function ReleaseDetailPage({
420
420
  }
421
421
  ),
422
422
  highlightVideoUrl && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
423
- _chunk6FLRFCK4cjs.Video,
423
+ _chunk5ZQKEGL3cjs.Video,
424
424
  {
425
425
  url: highlightVideoUrl,
426
426
  poster: highlightVideoThumbnail,
@@ -429,7 +429,7 @@ function ReleaseDetailPage({
429
429
  )
430
430
  ] }),
431
431
  releaseContent && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "text-h4 text-ods-text-primary", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, MarkdownRenderer, { content: releaseContent }) }),
432
- hasBreakingChanges && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.Card, { className: "border-ods-error bg-ods-error/10", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.CardContent, { className: "p-6", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-3", children: [
432
+ hasBreakingChanges && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.Card, { className: "border-ods-error bg-ods-error/10", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.CardContent, { className: "p-6", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-3", children: [
433
433
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.AlertTriangle, { className: "h-6 w-6 text-ods-error" }),
434
434
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
435
435
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "text-h3 text-ods-error", children: "Breaking Changes" }),
@@ -437,7 +437,7 @@ function ReleaseDetailPage({
437
437
  ] })
438
438
  ] }) }) }),
439
439
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
440
- _chunk6FLRFCK4cjs.ReleaseChangelogSection,
440
+ _chunk5ZQKEGL3cjs.ReleaseChangelogSection,
441
441
  {
442
442
  title: "Breaking Changes",
443
443
  entries: breakingChanges || [],
@@ -448,7 +448,7 @@ function ReleaseDetailPage({
448
448
  }
449
449
  ),
450
450
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
451
- _chunk6FLRFCK4cjs.ReleaseChangelogSection,
451
+ _chunk5ZQKEGL3cjs.ReleaseChangelogSection,
452
452
  {
453
453
  title: "Features Added",
454
454
  entries: featuresAdded || [],
@@ -458,7 +458,7 @@ function ReleaseDetailPage({
458
458
  }
459
459
  ),
460
460
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
461
- _chunk6FLRFCK4cjs.ReleaseChangelogSection,
461
+ _chunk5ZQKEGL3cjs.ReleaseChangelogSection,
462
462
  {
463
463
  title: "Bugs Fixed",
464
464
  entries: bugFixed || [],
@@ -468,7 +468,7 @@ function ReleaseDetailPage({
468
468
  }
469
469
  ),
470
470
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
471
- _chunk6FLRFCK4cjs.ReleaseChangelogSection,
471
+ _chunk5ZQKEGL3cjs.ReleaseChangelogSection,
472
472
  {
473
473
  title: "Improvements",
474
474
  entries: improvements || [],
@@ -499,7 +499,7 @@ function ReleaseDetailPage({
499
499
  (() => {
500
500
  const deliveryCount = deliveryData ? deliveryData.completed.length + deliveryData.inProgress.length : 0;
501
501
  return deliveryLoading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DeliverySection, { data: deliveryData, isLoading: deliveryLoading }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
502
- _chunk6FLRFCK4cjs.FadePreview,
502
+ _chunk5ZQKEGL3cjs.FadePreview,
503
503
  {
504
504
  hiddenCount: deliveryCount - 1,
505
505
  collapsedHeight: 300,
@@ -511,7 +511,7 @@ function ReleaseDetailPage({
511
511
  ] }),
512
512
  (_optionalChain([githubReleases, 'optionalAccess', _11 => _11.length]) || _optionalChain([knowledgeBaseLinks, 'optionalAccess', _12 => _12.length]) || migrationGuideUrl || documentationUrl) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-1 w-full", children: [
513
513
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-h5 tracking-[-0.28px] text-ods-text-secondary", children: "Related Links" }),
514
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.Card, { className: "bg-ods-card border-ods-border p-6", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-4", children: [
514
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.Card, { className: "bg-ods-card border-ods-border p-6", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-4", children: [
515
515
  githubReleases && githubReleases.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: githubReleases.map((ghRelease) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-start gap-1", children: [
516
516
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3M2MFP2Ycjs.GitHubIcon, { className: "shrink-0", width: 24, height: 24, color: "var(--color-text-secondary)" }),
517
517
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-h4 text-ods-text-primary", children: "Github Release" }),
@@ -585,7 +585,7 @@ function ReleaseDetailPage({
585
585
  // src/components/shared/product-release/release-detail-skeleton.tsx
586
586
 
587
587
  function ReleaseDetailSkeleton() {
588
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQGLEI6T2cjs.DetailPageSkeleton, { metadataColumns: 4, showImageGallery: true });
588
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJZC5BBAFcjs.DetailPageSkeleton, { metadataColumns: 4, showImageGallery: true });
589
589
  }
590
590
 
591
591
  // src/components/shared/roadmap/use-roadmap-voting.ts
@@ -746,10 +746,10 @@ function RoadmapGridSingle({
746
746
  // `useScrollToHash` in `roadmap-view.tsx` finds the card by id
747
747
  // and scrolls.
748
748
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
749
- _chunk6FLRFCK4cjs.RoadmapCard,
749
+ _chunk5ZQKEGL3cjs.RoadmapCard,
750
750
  {
751
751
  item,
752
- id: _chunk6FLRFCK4cjs.devSectionAnchorId.call(void 0, "roadmap", item.id),
752
+ id: _chunk5ZQKEGL3cjs.devSectionAnchorId.call(void 0, "roadmap", item.id),
753
753
  userVote: getVote(item.id),
754
754
  onVote: (voteType) => onVote(item.id, voteType),
755
755
  isVoting: votingTasks.has(item.id)
@@ -829,7 +829,7 @@ function RoadmapGrid({
829
829
  }, [hasActiveFilters, sortedQuartersKey, isInitialized, quartersToKeepClosed]);
830
830
  if (items.length === 0) {
831
831
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
832
- _chunkQQZWLTYUcjs.EmptyState,
832
+ _chunk2HL3BXWVcjs.EmptyState,
833
833
  {
834
834
  type: "generic",
835
835
  title: "No roadmap items",
@@ -850,7 +850,7 @@ function RoadmapGrid({
850
850
  );
851
851
  }
852
852
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
853
- _chunk6FLRFCK4cjs.Accordion,
853
+ _chunk5ZQKEGL3cjs.Accordion,
854
854
  {
855
855
  type: "multiple",
856
856
  value: expandedQuarters,
@@ -860,13 +860,13 @@ function RoadmapGrid({
860
860
  const itemCount = _optionalChain([itemsByQuarter, 'access', _21 => _21[quarter], 'optionalAccess', _22 => _22.length]) || 0;
861
861
  const isExpanded = expandedQuarters.includes(quarter);
862
862
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
863
- _chunk6FLRFCK4cjs.AccordionItem,
863
+ _chunk5ZQKEGL3cjs.AccordionItem,
864
864
  {
865
865
  value: quarter,
866
866
  id: `quarter-${quarter.replace(/\s+/g, "-").toLowerCase()}`,
867
867
  className: "border-0",
868
868
  children: [
869
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.AccordionTrigger, { className: "w-full p-0 hover:no-underline [&>svg]:h-5 [&>svg]:w-5 [&>svg]:text-ods-text-secondary [&>svg]:ml-auto [&>svg]:shrink-0", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-3", children: [
869
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.AccordionTrigger, { className: "w-full p-0 hover:no-underline [&>svg]:h-5 [&>svg]:w-5 [&>svg]:text-ods-text-secondary [&>svg]:ml-auto [&>svg]:shrink-0", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-3", children: [
870
870
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
871
871
  "h3",
872
872
  {
@@ -896,7 +896,7 @@ function RoadmapGrid({
896
896
  }
897
897
  )
898
898
  ] }) }),
899
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.AccordionContent, { className: "pt-4 pb-0 overflow-hidden data-[state=closed]:animate-none data-[state=open]:animate-none", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
899
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.AccordionContent, { className: "pt-4 pb-0 overflow-hidden data-[state=closed]:animate-none data-[state=open]:animate-none", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
900
900
  RoadmapGridSingle,
901
901
  {
902
902
  items: itemsByQuarter[quarter],
@@ -947,8 +947,8 @@ function RoadmapGridSkeleton({ count = 4, showLeftMargin = true }) {
947
947
 
948
948
 
949
949
  var DEFAULT_ENDPOINT2 = "/api/roadmap";
950
- var DEFAULT_SEARCH_PARAM_KEY2 = _chunk6FLRFCK4cjs.DEV_SECTION_PARAM_KEYS.search;
951
- var DEFAULT_STATUS_PARAM_KEY2 = _chunk6FLRFCK4cjs.DEV_SECTION_PARAM_KEYS.status;
950
+ var DEFAULT_SEARCH_PARAM_KEY2 = _chunk5ZQKEGL3cjs.DEV_SECTION_PARAM_KEYS.search;
951
+ var DEFAULT_STATUS_PARAM_KEY2 = _chunk5ZQKEGL3cjs.DEV_SECTION_PARAM_KEYS.status;
952
952
  function RoadmapView({
953
953
  endpoint = DEFAULT_ENDPOINT2,
954
954
  initialItems,
@@ -974,7 +974,7 @@ function RoadmapView({
974
974
  const items = _nullishCoalesce(_optionalChain([data, 'optionalAccess', _23 => _23.items]), () => ( []));
975
975
  _chunkSXLEPNY2cjs.useScrollToHash.call(void 0, data, { headerOffset: _chunkSXLEPNY2cjs.STICKY_HEADER_OFFSET_PX });
976
976
  if (error) {
977
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.LoadError, { message: "Failed to load roadmap.", onRetry: reload });
977
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.LoadError, { message: "Failed to load roadmap.", onRetry: reload });
978
978
  }
979
979
  if (isLoading && !data) {
980
980
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RoadmapGridSkeleton, { showLeftMargin });
@@ -1031,7 +1031,7 @@ function DeliveryTable({ items, isLoading = false }) {
1031
1031
  "div",
1032
1032
  {
1033
1033
  className: "border-b border-ods-border last:border-b-0",
1034
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5PMRHRMMcjs.DeliveryRow, { item, id: _chunk6FLRFCK4cjs.devSectionAnchorId.call(void 0, "delivery", item.id) })
1034
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPA6L55NFcjs.DeliveryRow, { item, id: _chunk5ZQKEGL3cjs.devSectionAnchorId.call(void 0, "delivery", item.id) })
1035
1035
  },
1036
1036
  item.id
1037
1037
  )
@@ -1043,8 +1043,8 @@ function DeliveryTable({ items, isLoading = false }) {
1043
1043
 
1044
1044
  var DEFAULT_COMPLETED_ENDPOINT = "/api/delivery/completed";
1045
1045
  var DEFAULT_IN_PROGRESS_ENDPOINT = "/api/delivery/in-progress";
1046
- var DEFAULT_SEARCH_PARAM_KEY3 = _chunk6FLRFCK4cjs.DEV_SECTION_PARAM_KEYS.search;
1047
- var DEFAULT_TASK_TYPE_PARAM_KEY = _chunk6FLRFCK4cjs.DEV_SECTION_PARAM_KEYS.deliveryTaskType;
1046
+ var DEFAULT_SEARCH_PARAM_KEY3 = _chunk5ZQKEGL3cjs.DEV_SECTION_PARAM_KEYS.search;
1047
+ var DEFAULT_TASK_TYPE_PARAM_KEY = _chunk5ZQKEGL3cjs.DEV_SECTION_PARAM_KEYS.deliveryTaskType;
1048
1048
  function DeliveryLists({
1049
1049
  completedApiEndpoint = DEFAULT_COMPLETED_ENDPOINT,
1050
1050
  inProgressApiEndpoint = DEFAULT_IN_PROGRESS_ENDPOINT,
@@ -1105,11 +1105,11 @@ function DeliveryLists({
1105
1105
  const hasActiveFilters = searchQuery !== "" || taskTypeFilter !== "all";
1106
1106
  const hasResults = showCompleted && filteredCompleted.length > 0 || showInProgress && filteredInProgress.length > 0;
1107
1107
  if (error) {
1108
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.LoadError, { message: error, onRetry: () => window.location.reload() }) });
1108
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.LoadError, { message: error, onRetry: () => window.location.reload() }) });
1109
1109
  }
1110
1110
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "w-full flex flex-col gap-[40px]", children: [
1111
1111
  !isLoading && !hasResults && (hasActiveFilters ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1112
- _chunkQQZWLTYUcjs.EmptyState,
1112
+ _chunk2HL3BXWVcjs.EmptyState,
1113
1113
  {
1114
1114
  type: "search",
1115
1115
  title: "No tasks found",
@@ -1124,7 +1124,7 @@ function DeliveryLists({
1124
1124
  }
1125
1125
  }
1126
1126
  ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1127
- _chunkQQZWLTYUcjs.EmptyState,
1127
+ _chunk2HL3BXWVcjs.EmptyState,
1128
1128
  {
1129
1129
  type: "generic",
1130
1130
  title: "No tasks available",
@@ -1226,7 +1226,7 @@ function LegalDocumentPage({
1226
1226
  initialData = null,
1227
1227
  initialLastUpdatedLabel = null,
1228
1228
  apiEndpoint,
1229
- MarkdownRenderer = _chunk6FLRFCK4cjs.RichMarkdownRenderer,
1229
+ MarkdownRenderer = _chunk5ZQKEGL3cjs.RichMarkdownRenderer,
1230
1230
  backButton,
1231
1231
  shell = true
1232
1232
  }) {
@@ -1236,10 +1236,10 @@ function LegalDocumentPage({
1236
1236
  label: _nullishCoalesce(_optionalChain([backButton, 'optionalAccess', _26 => _26.label]), () => ( "Back to home")),
1237
1237
  onClick: () => router.push(_nullishCoalesce(_optionalChain([backButton, 'optionalAccess', _27 => _27.href]), () => ( "/")))
1238
1238
  };
1239
- const fallbackLastUpdatedLabel = _optionalChain([data, 'optionalAccess', _28 => _28.lastSynced]) != null ? _chunk6FLRFCK4cjs.formatLegalDate.call(void 0, data.lastSynced) : null;
1239
+ const fallbackLastUpdatedLabel = _optionalChain([data, 'optionalAccess', _28 => _28.lastSynced]) != null ? _chunk5ZQKEGL3cjs.formatLegalDate.call(void 0, data.lastSynced) : null;
1240
1240
  const effectiveLastUpdatedLabel = _nullishCoalesce(initialLastUpdatedLabel, () => ( fallbackLastUpdatedLabel));
1241
1241
  const subtitle = effectiveLastUpdatedLabel ? `Last Updated: ${effectiveLastUpdatedLabel}` : fallbackDescription;
1242
- const inner = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk6FLRFCK4cjs.PageLayout, { title, subtitle, backButton: backCfg, titleSize: "h1", titleWrap: true, children: [
1242
+ const inner = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk5ZQKEGL3cjs.PageLayout, { title, subtitle, backButton: backCfg, titleSize: "h1", titleWrap: true, children: [
1243
1243
  _optionalChain([data, 'optionalAccess', _29 => _29.sourceFile]) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { className: "text-h6 text-ods-text-secondary opacity-75", children: [
1244
1244
  "Source: ",
1245
1245
  data.sourceFile
@@ -1286,7 +1286,7 @@ function LegalDocumentPage({
1286
1286
  ] })
1287
1287
  ] }) }) }) }) })
1288
1288
  ] });
1289
- return shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6FLRFCK4cjs.PageShell, { children: inner }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "page-shell-content", children: inner });
1289
+ return shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5ZQKEGL3cjs.PageShell, { children: inner }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "page-shell-content", children: inner });
1290
1290
  }
1291
1291
 
1292
1292
 
@@ -1303,4 +1303,4 @@ function LegalDocumentPage({
1303
1303
 
1304
1304
 
1305
1305
  exports.ProductReleasesView = ProductReleasesView; exports.MediaGalleryStrip = MediaGalleryStrip; exports.ReleaseDetailPage = ReleaseDetailPage; exports.ReleaseDetailSkeleton = ReleaseDetailSkeleton; exports.useRoadmapVoting = useRoadmapVoting; exports.RoadmapGrid = RoadmapGrid; exports.RoadmapGridSkeleton = RoadmapGridSkeleton; exports.RoadmapView = RoadmapView; exports.DeliveryTable = DeliveryTable; exports.DeliveryLists = DeliveryLists; exports.useLegalDocs = useLegalDocs; exports.LegalDocumentPage = LegalDocumentPage;
1306
- //# sourceMappingURL=chunk-GLFQSFYV.cjs.map
1306
+ //# sourceMappingURL=chunk-UDPOAWKH.cjs.map