{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-DP2L7US5.cjs","../src/hooks/use-self-fetch.ts"],"names":[],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACA;ACHA,8BAAgF;AAyCzE,SAAS,YAAA,CACd,GAAA,EACA,OAAA,EACuB;AACvB,EAAA,MAAM,YAAA,kBAAc,OAAA,2BAAS,aAAA;AAC7B,EAAA,MAAM,2BAAA,kBAA6B,OAAA,6BAAS,4BAAA;AAC5C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,EAAA,EAAI,6BAAA,iBAAmB,WAAA,UAAe,MAAI,CAAA;AAC9D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,EAAA,EAAI,6BAAA,YAAS,IAAgB,KAAA,EAAA,GAAa,IAAA,IAAQ,IAAI,CAAA;AACpF,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,EAAA,EAAI,6BAAA,KAAc,CAAA;AACxC,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,EAAA,EAAI,6BAAA,CAAU,CAAA;AAI5C,EAAA,MAAM,UAAA,EAAY,2BAAA,YAAe,IAAgB,KAAA,EAAA,EAAY,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,CAAC,CAAA;AAM3E,EAAA,MAAM,WAAA,EAAa,2BAAA,YAAsB,IAAgB,KAAA,EAAA,EAAY,IAAA,EAAM,IAAI,CAAA;AAK/E,EAAA,8BAAA,CAAU,EAAA,GAAM;AACd,IAAA,GAAA,CAAI,YAAA,IAAgB,KAAA,CAAA,EAAW,OAAA,CAAQ,WAAW,CAAA;AAAA,EACpD,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,8BAAA,CAAU,EAAA,GAAM;AACd,IAAA,GAAA,CAAI,IAAA,IAAQ,IAAA,EAAM;AAChB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,MAAA;AAAA,IACF;AAGA,IAAA,GAAA,CAAI,UAAA,CAAW,QAAA,IAAY,GAAA,EAAK,MAAA;AAChC,IAAA,MAAM,KAAA,EAAO,IAAI,eAAA,CAAgB,CAAA;AACjC,IAAA,IAAI,UAAA,EAAY,KAAA;AAChB,IAAA,MAAA,SAAe,IAAA,CAAA,EAAO;AACpB,MAAA,IAAI;AACF,QAAA,YAAA,CAAa,IAAI,CAAA;AACjB,QAAA,QAAA,CAAS,KAAK,CAAA;AACd,QAAA,MAAM,IAAA,EAAM,MAAM,4CAAA,GAAa,EAAe,EAAE,MAAA,EAAQ,IAAA,CAAK,OAAO,CAAC,CAAA;AACrE,QAAA,GAAA,CAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAC7D,QAAA,MAAM,KAAA,EAAQ,MAAM,GAAA,CAAI,IAAA,CAAK,CAAA;AAC7B,QAAA,GAAA,CAAI,CAAC,SAAA,EAAW;AACd,UAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,UAAA,UAAA,CAAW,QAAA,EAAU,GAAA;AACrB,UAAA,SAAA,CAAU,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,CAAA;AAAA,QAC/B;AAAA,MACF,EAAA,MAAA,CAAS,GAAA,EAAK;AAMZ,QAAA,GAAA,CAAI,UAAA,mBAAc,GAAA,6BAAe,OAAA,IAAS,YAAA,EAAc,MAAA;AACxD,QAAA,QAAA,CAAS,IAAI,CAAA;AACb,QAAA,OAAA,CAAQ,KAAA,CAAM,eAAA,EAAiB,GAAA,EAAK,GAAG,CAAA;AAAA,MACzC,EAAA,QAAE;AACA,QAAA,GAAA,CAAI,CAAC,SAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAAA,MACpC;AAAA,IACF;AACA,IAAA,IAAA,CAAK,CAAA;AACL,IAAA,OAAO,CAAA,EAAA,GAAM;AACX,MAAA,UAAA,EAAY,IAAA;AACZ,MAAA,IAAA,CAAK,KAAA,CAAM,CAAA;AAAA,IACb,CAAA;AAAA,EAEF,CAAA,EAAG,CAAC,GAAA,EAAK,SAAS,CAAC,CAAA;AAInB,EAAA,MAAM,YAAA,EAAc,CAAA,EAAA,GAAM;AACxB,IAAA,UAAA,CAAW,QAAA,EAAU,IAAA;AACrB,IAAA,YAAA,CAAa,CAAC,CAAA,EAAA,GAAM,EAAA,EAAI,CAAC,CAAA;AAAA,EAC3B,CAAA;AAKA,EAAA,8BAAA,CAAU,EAAA,GAAM;AACd,IAAA,GAAA,CAAI,CAAC,2BAAA,GAA8B,IAAA,IAAQ,IAAA,EAAM,MAAA;AACjD,IAAA,MAAM,UAAA,EAAY,CAAA,EAAA,GAAM;AACtB,MAAA,GAAA,CAAI,QAAA,CAAS,gBAAA,IAAoB,SAAA,EAAW,MAAA;AAC5C,MAAA,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,SAAA,CAAU,QAAA,EAAU,0BAAA,EAA4B,MAAA;AACjE,MAAA,WAAA,CAAY,CAAA;AAAA,IACd,CAAA;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,kBAAA,EAAoB,SAAS,CAAA;AACvD,IAAA,OAAO,CAAA,EAAA,GAAM,QAAA,CAAS,mBAAA,CAAoB,kBAAA,EAAoB,SAAS,CAAA;AAAA,EACzE,CAAA,EAAG,CAAC,GAAA,EAAK,0BAA0B,CAAC,CAAA;AAEpC,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA;AAAA,IAEA,MAAA,EAAQ;AAAA,EACV,CAAA;AACF;ADnEA;AACA;AACE;AACF,oCAAC","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-DP2L7US5.cjs","sourcesContent":[null,"'use client'\n\nimport { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react'\nimport { contentFetch } from '../utils/embed-content-fetch'\n\nexport interface UseSelfFetchResult<T> {\n  data: T | null\n  /** Imperatively patch the fetched data (e.g. optimistic vote updates). */\n  setData: Dispatch<SetStateAction<T | null>>\n  isLoading: boolean\n  error: boolean\n  /** Re-run the fetch (error-retry affordance). */\n  reload: () => void\n}\n\n/**\n * The single source for the 4 self-fetching content views\n * (`ProductReleasesView`, `RoadmapView`, the onboarding catalog/detail). GETs a\n * configured `url` into component state with **plain `fetch` + `useEffect`** —\n * deliberately NO react-query, so embedders don't need a QueryClient. This is\n * the same technology choice the shipped `DeliveryLists` makes; that component\n * predates this hook and still hand-rolls the loop (a future pass can migrate\n * it onto a two-url variant of this hook).\n *\n * Behaviour:\n *   - `url = null` → fetching is DISABLED (controlled / SSR mode, or missing\n *     config); returns `initialData ?? null` and never fetches.\n *   - `initialData` provided → hydrates immediately and SKIPS the first fetch\n *     (so the hub's server-rendered data isn't re-fetched on mount); later\n *     `url` changes still re-fetch.\n *   - a `cancelled` guard ensures an in-flight response from a STALE `url`\n *     (e.g. a fast pagination / section toggle) can't overwrite a newer one.\n *   - `reload()` bumps an internal key to retry after an error.\n *   - `revalidateOnVisibleAfterMs` (opt-in) → re-fetch when the tab becomes\n *     visible AND the held data is older than the threshold. Event-driven\n *     freshness with ZERO background requests — the replacement for\n *     setInterval polling. Data age counts from the last completed fetch, or\n *     from mount when `initialData` seeded it (an SSR seed is milliseconds\n *     old — without that, the first refocus would always refire).\n *\n * Re-fetches whenever `url` changes, so callers fold all query params INTO the\n * url string (the url IS the cache key).\n */\nexport function useSelfFetch<T>(\n  url: string | null,\n  options?: { initialData?: T; revalidateOnVisibleAfterMs?: number },\n): UseSelfFetchResult<T> {\n  const initialData = options?.initialData\n  const revalidateOnVisibleAfterMs = options?.revalidateOnVisibleAfterMs\n  const [data, setData] = useState<T | null>(initialData ?? null)\n  const [isLoading, setIsLoading] = useState(initialData === undefined && url !== null)\n  const [error, setError] = useState(false)\n  const [reloadKey, setReloadKey] = useState(0)\n  // When the held data was obtained: seeded at mount for SSR-hydrated data,\n  // stamped after each completed fetch. Drives the visibility revalidation age\n  // check. 0 = nothing held yet (first visible event may fetch).\n  const dataAtRef = useRef<number>(initialData !== undefined ? Date.now() : 0)\n  // The url whose data we currently hold — seeded from SSR `initialData`, then set after\n  // each completed fetch. The effect skips the fetch when this equals `url` (so server-\n  // rendered data isn't re-fetched on mount). A VALUE compare (not a one-shot flag) so the\n  // SSR-hydration skip survives React 18 StrictMode's mount→unmount→remount in dev;\n  // `reload()` nulls it to force a re-fetch of the same url.\n  const dataUrlRef = useRef<string | null>(initialData !== undefined ? url : null)\n\n  // Re-sync when a CONTROLLED `initialData` changes (e.g. the host navigates\n  // between detail slugs without remounting). No-op in self-fetch mode, where\n  // `initialData` is `undefined`.\n  useEffect(() => {\n    if (initialData !== undefined) setData(initialData)\n  }, [initialData])\n\n  useEffect(() => {\n    if (url === null) {\n      setIsLoading(false)\n      return\n    }\n    // Already hold data for this exact url (SSR-hydrated, or a prior completed fetch) →\n    // skip. `reload()` clears `dataUrlRef` so a retry of the same url still fetches.\n    if (dataUrlRef.current === url) return\n    const ctrl = new AbortController()\n    let cancelled = false\n    async function load() {\n      try {\n        setIsLoading(true)\n        setError(false)\n        const res = await contentFetch(url as string, { signal: ctrl.signal })\n        if (!res.ok) throw new Error(`Request failed (${res.status})`)\n        const json = (await res.json()) as T\n        if (!cancelled) {\n          setData(json)\n          dataUrlRef.current = url // remember the url we now hold data for\n          dataAtRef.current = Date.now()\n        }\n      } catch (err) {\n        // AbortError on cleanup (unmount / stale-url change / React StrictMode's dev\n        // double-invoke) is EXPECTED — the request was intentionally aborted. Aborting\n        // also means the orphaned StrictMode fetch shows as \"cancelled\" instead of\n        // completing as a wasted duplicate (parity with useChatIdentity). Only real\n        // failures fall through to the error state + console.\n        if (cancelled || (err as Error)?.name === 'AbortError') return\n        setError(true)\n        console.error('useSelfFetch:', url, err)\n      } finally {\n        if (!cancelled) setIsLoading(false)\n      }\n    }\n    load()\n    return () => {\n      cancelled = true\n      ctrl.abort()\n    }\n    // `url` folds in every query param; `reloadKey` drives retry.\n  }, [url, reloadKey])\n\n  // ONE force-refetch mechanic shared by reload() and the visibility\n  // revalidation: clear the held-url skip, bump the effect key.\n  const forceReload = () => {\n    dataUrlRef.current = null\n    setReloadKey((k) => k + 1)\n  }\n\n  // Opt-in visibility-driven revalidation. Registered only when the option is\n  // set and fetching is enabled; SSR-safe (effects don't run on the server)\n  // and StrictMode-safe (idempotent listener, cleaned up per mount cycle).\n  useEffect(() => {\n    if (!revalidateOnVisibleAfterMs || url === null) return\n    const onVisible = () => {\n      if (document.visibilityState !== 'visible') return\n      if (Date.now() - dataAtRef.current < revalidateOnVisibleAfterMs) return\n      forceReload()\n    }\n    document.addEventListener('visibilitychange', onVisible)\n    return () => document.removeEventListener('visibilitychange', onVisible)\n  }, [url, revalidateOnVisibleAfterMs])\n\n  return {\n    data,\n    setData,\n    isLoading,\n    error,\n    // Force a re-fetch of the current url (error-retry affordance).\n    reload: forceReload,\n  }\n}\n"]}