diff --git a/web/sections/LayoutPanel.tsx b/web/sections/LayoutPanel.tsx index 15b33c1..157feae 100644 --- a/web/sections/LayoutPanel.tsx +++ b/web/sections/LayoutPanel.tsx @@ -490,7 +490,21 @@ const LinkRow = ({ * says so in its own comments. */ const LayoutPreview = ({ port }: { port: number | null }) => { - const { get } = useClient(); + const client = useClient(); + /* + Through a ref, and this is the whole reason the preview hung. + + `useClient()` rebuilds its verbs on EVERY render, so `get` has a new identity each time. With it in + the dependency list the effect re-ran on every render, and its first act is `setReady(false)` — + which renders again, which makes another `get`, which re-runs the effect. The frame never mounted + and the panel said "Connecting…" forever. + + It is a known trap here: the same thing silently disabled every playback report in the Jellyfin + player. The platform's own Preview panel avoids it by depending on neither the client nor the URL. + */ + const clientRef = useRef(client); + clientRef.current = client; + const [ready, setReady] = useState(false); const [failed, setFailed] = useState(null); @@ -499,13 +513,14 @@ const LayoutPreview = ({ port }: { port: number | null }) => { let alive = true; setReady(false); setFailed(null); - void get<{ ok: boolean }>('/dev-proxy/session') + void clientRef.current + .get<{ ok: boolean }>('/dev-proxy/session') .then(() => alive && setReady(true)) .catch((failure: unknown) => alive && setFailed(readableError(failure))); return () => { alive = false; }; - }, [port, get]); + }, [port]); const message = !port ? 'No development server is configured — set its port under Administration.'