| 1 | import { useCallback, useEffect, useRef, useState } from "react"; |
| 2 | import { app } from "./bridge"; |
| 3 | import { invalidateSharedQuery } from "./queryCoalesce"; |
| 4 | import type { ContextPanelInfo } from "./types"; |
| 5 | |
| 6 | // Overview snapshots have one owner and a trailing refresh: a burst's final |
| 7 | // usage event must eventually reach the panel even if no further event arrives. |
| 8 | export function useContextPanelSnapshot(tabId: string | undefined, sessionGen: number | undefined, |
| 9 | refreshKey: number | undefined, usageKey: string, usageSeq: number | undefined) { |
| 10 | const [snapshot, setSnapshot] = useState<{ tabId: string; sessionGen?: number; info: ContextPanelInfo }>(); |
| 11 | const requestSeq = useRef(0); |
| 12 | const lastRefreshTime = useRef(0); |
| 13 | const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined); |
| 14 | const refresh = useCallback(async () => { |
| 15 | if (!tabId) return; |
| 16 | clearTimeout(timer.current); |
| 17 | timer.current = undefined; |
| 18 | const seq = ++requestSeq.current; |
| 19 | lastRefreshTime.current = Date.now(); |
| 20 | // A usage/session boundary can change data inside the bridge's 200ms |
| 21 | // coalescing window. A new request must not adopt the previous snapshot. |
| 22 | invalidateSharedQuery("ContextPanel", [tabId]); |
| 23 | try { |
| 24 | const info = await app.ContextPanel(tabId); |
| 25 | if (requestSeq.current === seq) setSnapshot({ tabId, sessionGen, info }); |
| 26 | } catch { |
| 27 | /* Keep the last same-session snapshot while the bridge is unavailable. */ |
| 28 | } |
| 29 | }, [tabId, sessionGen]); |
| 30 | |
| 31 | useEffect(() => { |
| 32 | void refresh(); |
| 33 | return () => { |
| 34 | requestSeq.current++; |
| 35 | clearTimeout(timer.current); |
| 36 | timer.current = undefined; |
| 37 | }; |
| 38 | }, [refresh, refreshKey]); |
| 39 | |
| 40 | useEffect(() => { |
| 41 | if (!usageKey && !usageSeq) return; |
| 42 | const delay = Math.max(0, 1000 - (Date.now() - lastRefreshTime.current)); |
| 43 | timer.current = setTimeout(() => { void refresh(); }, delay); |
| 44 | return () => { |
| 45 | clearTimeout(timer.current); |
| 46 | timer.current = undefined; |
| 47 | }; |
| 48 | }, [usageKey, usageSeq, refresh]); |
| 49 | |
| 50 | return tabId && snapshot?.tabId === tabId && snapshot.sessionGen === sessionGen ? snapshot.info : null; |
| 51 | } |
| 52 |