From e820831aec748945fabd2f5c47d33350f252c50b Mon Sep 17 00:00:00 2001 From: officer Date: Wed, 19 Aug 2026 10:45:05 +0000 Subject: [PATCH] do not render the frame before its session exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Saving the connection showed an error that a refresh then cleared. The effect that re-mints the frame session re-ran, but `state` was left at 'ready' from the previous pass — so the iframe mounted immediately, before the cookie existed, and the first request inside it answered 401. By the time anyone refreshed, the mint had finished. A stale 'ready' is worse than a spinner: it renders something broken confidently. Also stays on the form after the URL-only step, where there is still a token to set. Bouncing to a frame that immediately sends you back is worse than not moving. --- web/GiteaConnection.tsx | 8 +++++++- web/GiteaFrame.tsx | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/web/GiteaConnection.tsx b/web/GiteaConnection.tsx index 8ca147c..65be216 100644 --- a/web/GiteaConnection.tsx +++ b/web/GiteaConnection.tsx @@ -81,10 +81,16 @@ export const GiteaConnection = ({ state, onSaved }: Props) => { setError(null); try { await put('/gitea/_config', state.isOwner === false ? { token } : { url, token }); + // ORDER MATTERS. `onSaved` re-reads the connection and re-mints the frame session; navigating + // first would swap to the frame while that is still in flight, and the frame would load against a + // session that does not exist yet. onSaved(); // Back to the frame. Saving from `/gitea/connection` otherwise leaves you looking at the form you // just submitted, which reads as nothing having happened. - navigate('/gitea'); + // + // Only when there is a frame to go back TO: after the URL-only step there is still a token to + // set, and bouncing to a frame that immediately sends you back is worse than staying put. + if (!needsUrl) navigate('/gitea'); } catch (err) { setError(err instanceof Error ? err.message : 'could not save'); } finally { diff --git a/web/GiteaFrame.tsx b/web/GiteaFrame.tsx index e8f8f4a..b210c09 100644 --- a/web/GiteaFrame.tsx +++ b/web/GiteaFrame.tsx @@ -38,6 +38,15 @@ export const GiteaFrame = () => { useEffect(() => { let cancelled = false; + // BACK TO 'opening' on every re-run, not just the first. + // + // Without this, saving the connection left `state` at 'ready' from the previous pass, so the iframe + // mounted immediately — before the session cookie for it had been minted. The first request inside + // the frame answered 401 and showed an error that a refresh then cleared, because by then the mint + // had finished. + // + // A stale 'ready' is worse than a spinner: it renders something broken confidently. + setState('opening'); // Not in a dependency array: `useClient()` returns a new object every render, so depending on `get` // or `post` would re-run this on every keystroke elsewhere in the app. void (async () => {