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 () => {