From 8125363e0b4907b98c349298411120781a8aa954 Mon Sep 17 00:00:00 2001 From: officer Date: Wed, 19 Aug 2026 10:32:59 +0000 Subject: [PATCH] a real form, and the URL step no longer demands a token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things reported from a production instance. The URL step was refused with "an access token is required the first time" — a check that predates splitting the screen into two steps, and which made the first step impossible to complete. Naming the instance is now its own step; a member still needs a token, since setting one is all they do. And the fields were a div, so pressing Enter did nothing and the button was the only way to submit. That is a keyboard trap wearing a layout. Now a
with onSubmit, with the other two buttons marked type="button" so they do not submit it. 19 panel files across the platform have the same problem. Recorded in the platform's TODO.md rather than swept blindly — a search box has no submit, and a multi-field editor wants the useForm hook. Also dropped the hint under the URL field, which repeated the sentence directly above it. --- sidecar/index.ts | 9 ++++++++- web/GiteaConnection.tsx | 23 ++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/sidecar/index.ts b/sidecar/index.ts index 0543c90..3a1b7d6 100644 --- a/sidecar/index.ts +++ b/sidecar/index.ts @@ -198,7 +198,14 @@ async function handleConfig(req: Request, userId: number): Promise { } const existing = await getServiceConnection(userId, 'gitea'); - if (!token && !existing?.hasSecret) return bad('an access token is required the first time'); + // NOT required on the first save any more. Naming the instance is its own step — the owner says + // where Gitea is, and the token is asked for (or found in tea's config) afterwards. This check + // predates that split and refused the URL step outright. + // + // A member is different: they set nothing but a token, so an empty save from them means nothing. + if (!owner && !token && !(await readTeaLogin(userId, base))?.hasToken) { + return bad('an access token is required'); + } // Probe with the credentials as they WILL be, so a save either stores something that works or // fails with the reason. Storing first and discovering later makes every downstream screen fail diff --git a/web/GiteaConnection.tsx b/web/GiteaConnection.tsx index 97a7564..8ca147c 100644 --- a/web/GiteaConnection.tsx +++ b/web/GiteaConnection.tsx @@ -75,7 +75,8 @@ export const GiteaConnection = ({ state, onSaved }: Props) => { // Members skip step one entirely: the instance is the owner's. const needsUrl = state.isOwner !== false && !state.instanceConfigured; - const save = async () => { + const save = async (ev?: React.FormEvent) => { + ev?.preventDefault(); setBusy(true); setError(null); try { @@ -92,7 +93,10 @@ export const GiteaConnection = ({ state, onSaved }: Props) => { }; return ( -
+ // A real , so Enter submits. Typing a URL and pressing return is what anyone does, and a div + // full of inputs silently swallows it — the button becomes the only way in, which is a keyboard trap + // wearing a layout. +

{state.unreadable ? 'Re-enter your token' : 'Connect Gitea'}

@@ -110,10 +114,6 @@ export const GiteaConnection = ({ state, onSaved }: Props) => { )} @@ -157,26 +157,23 @@ export const GiteaConnection = ({ state, onSaved }: Props) => { {error &&

{error}

}
- {/* Only offered when there is something to test. */} {state.tea?.hasToken ? ( - ) : null} {/* Only once there is something to go back TO. */} {state.configured && !state.unreadable ? ( - ) : null}
-
+ ); };