From 8a152f5b635b5168bf22b68e1c5b5c53db666a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 31 Aug 2026 02:08:54 +0100 Subject: [PATCH] =?UTF-8?q?the=20preview=20hung=20on=20"Connecting?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `useClient()` rebuilds its verbs on every render, so `get` has a new identity each time. It was in the effect's dependency list, so the effect re-ran on every render — and its first act is setReady(false), which renders again, which builds another `get`, which re-runs the effect. The frame never mounted. Through a ref instead, with the effect depending only on the port. This trap is documented in CLAUDE.md and it is the same one that silently disabled every playback report in the Jellyfin player. The platform's own Preview panel sidesteps it by depending on neither the client nor the URL, which is what this now does. Co-Authored-By: Claude Opus 5 (1M context) --- web/sections/LayoutPanel.tsx | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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.'