{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-3IXDZI3R.cjs","../src/hooks/use-self-fetch.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-3IXDZI3R.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  // The mount timestamp comes from a lazy `useState` initialiser — a\n  // `useRef(Date.now())` argument is re-evaluated on every render.\n  const [mountedAt] = useState(() => Date.now());\n  const dataAtRef = useRef<number>(initialData !== undefined ? mountedAt : 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  // Both resets below are prop-driven, so they use React's \"adjust state while\n  // rendering\" pattern rather than an effect. From an effect each one commits a\n  // frame showing the superseded data (or a spinner that will never resolve)\n  // and then immediately re-renders to correct it; done here the wrong frame\n  // never reaches the screen.\n  const [syncedProps, setSyncedProps] = useState({ url, initialData });\n  if (syncedProps.url !== url || syncedProps.initialData !== initialData) {\n    setSyncedProps({ url, initialData });\n    // A CONTROLLED `initialData` changed (e.g. the host navigates between\n    // detail slugs without remounting). No-op in self-fetch mode, where\n    // `initialData` is `undefined`.\n    if (syncedProps.initialData !== initialData && initialData !== undefined) {\n      setData(initialData);\n    }\n    // Fetching just got disabled — nothing will land to clear the spinner.\n    if (syncedProps.url !== url && url === null) {\n      setIsLoading(false);\n    }\n  }\n\n  useEffect(() => {\n    if (url === null) return undefined;\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 undefined;\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    // Never rejects — try/catch/finally, every write gated on `cancelled`.\n    void 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 undefined;\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"]}