import { useCallback, useRef } from 'react'; import { defineChannel } from 'hooks/usePanelChannel'; /** * "Something on the fofos site changed — anyone reading it should read it again." * * ── Why a channel and not the URL ── * * `docs/navigation-audit.md` draws the line: anything ADDRESSABLE goes in the URL, and a channel is * only for a signal. Nobody wants to link to "the moment after the connection was saved", and there is * nothing to select — this is the second kind. * * ── The nonce is a plain counter, and that is not an accident ── * * Copied deliberately from `plugins/proxy-manager/web/channel.ts`, which records why. * `setSignal(n => n + 1)` is WRONG: the functional setter applies against the value captured at * render, so two bumps inside one render window both compute `snapshot + 1` and the second silently * overwrites the first — nobody re-reads. `Date.now()` has the same flaw in miniature, two bumps in * one millisecond being one number. A module counter never reads React state, so it cannot be wrong * however often it is called. */ const useFofosChangedChannel = defineChannel('fofos-admin:changed', 0); let nonce = 0; export const useFofosChanged = () => { const [signal, setSignal] = useFofosChangedChannel(); // The setter is a fresh closure every render and this `bump` goes into dependency lists, so it is // read through a ref — stable identity without freezing the first render's setter. const setRef = useRef(setSignal); setRef.current = setSignal; const bump = useCallback(() => setRef.current(++nonce), []); return [signal, bump] as const; };