wrap inputs in real forms, so enter submits instead of doing nothing

24 panel and screen files had their fields sitting in a <div> instead of a
<form> — typing a value and pressing Enter did nothing, and the button was
the only way in. Fixed the sites that are a real form, a single field with
an obvious action, or a multi-field editor; left search/filter boxes and a
few genuinely ambiguous sites alone (see TODO.md for the full breakdown).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 10:51:58 +00:00
co-authored by Claude Opus 5
parent 319afe2c5d
commit 994de45296
24 changed files with 510 additions and 408 deletions
@@ -226,6 +226,23 @@ export const AIHarnessesSection = () => {
}
};
const submitLocalUrl = (ev?: React.FormEvent) => {
ev?.preventDefault();
if (probeState.step === 'url' && localUrl.trim()) handleProbe();
};
const submitAuth = (ev?: React.FormEvent) => {
ev?.preventDefault();
if (probeState.step !== 'auth') return;
const ready = probeState.probe.authType === 'basic' ? authUsername && authPassword : authApiKey;
if (ready) handleAuthSubmit(probeState.probe);
};
const submitApiKey = (ev: React.FormEvent, providerId: string) => {
ev.preventDefault();
if (keyInputs[providerId]) saveApiKey(providerId);
};
const removeLocalProvider = async (id: string) => {
await client.delete(`/server-settings/chat-providers/local-providers/${id}`);
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_PROVIDERS'] });
@@ -291,57 +308,60 @@ export const AIHarnessesSection = () => {
{addingLocal ? (
<div className="flex flex-col gap-2 max-w-sm">
{/* Step 1: Name + URL inputs stacked */}
<Input
type="text"
className="h-8 text-xs"
placeholder="Name (e.g. My Ollama)"
value={localName}
onChange={(ev) => setLocalName(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Escape') resetLocalForm();
}}
disabled={probeState.step !== 'url'}
autoFocus
/>
<Input
type="url"
className="h-8 text-xs"
placeholder="http://localhost:11434"
value={localUrl}
onChange={(ev) => setLocalUrl(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Escape') resetLocalForm();
if (ev.key === 'Enter' && localUrl.trim() && probeState.step === 'url') handleProbe();
}}
disabled={probeState.step !== 'url'}
/>
<div className="flex items-center gap-2">
{probeState.step === 'url' && (
<Button
<form onSubmit={submitLocalUrl} className="contents">
<Input
type="text"
className="h-8 text-xs"
placeholder="Name (e.g. My Ollama)"
value={localName}
onChange={(ev) => setLocalName(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Escape') resetLocalForm();
}}
disabled={probeState.step !== 'url'}
autoFocus
/>
<Input
type="url"
className="h-8 text-xs"
placeholder="http://localhost:11434"
value={localUrl}
onChange={(ev) => setLocalUrl(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Escape') resetLocalForm();
}}
disabled={probeState.step !== 'url'}
/>
<div className="flex items-center gap-2">
{probeState.step === 'url' && (
<Button
type="submit"
size="sm"
className="h-7 text-xs bg-duck-dark text-white hover:bg-duck-dark/90 dark:bg-foreground dark:text-background dark:hover:bg-foreground/90 cursor-pointer disabled:opacity-40"
disabled={!localUrl.trim()}
>
Connect
</Button>
)}
{(probeState.step === 'probing' || probeState.step === 'saving') && (
<Loader2 className="h-4 w-4 animate-spin text-duck-teal shrink-0" />
)}
<button
type="button"
size="sm"
className="h-7 text-xs bg-duck-dark text-white hover:bg-duck-dark/90 dark:bg-foreground dark:text-background dark:hover:bg-foreground/90 cursor-pointer disabled:opacity-40"
disabled={!localUrl.trim()}
onClick={() => handleProbe()}
onClick={resetLocalForm}
className="text-duck-dark/40 hover:text-duck-dark/70 dark:text-foreground/40 dark:hover:text-foreground/70 cursor-pointer transition-colors text-xs"
>
Connect
</Button>
)}
{(probeState.step === 'probing' || probeState.step === 'saving') && (
<Loader2 className="h-4 w-4 animate-spin text-duck-teal shrink-0" />
)}
<button
type="button"
onClick={resetLocalForm}
className="text-duck-dark/40 hover:text-duck-dark/70 dark:text-foreground/40 dark:hover:text-foreground/70 cursor-pointer transition-colors text-xs"
>
Cancel
</button>
</div>
Cancel
</button>
</div>
</form>
{/* Step 2: Auth form (if needed) */}
{probeState.step === 'auth' && (
<div className="flex flex-col gap-2 rounded-md border border-duck-dark/10 dark:border-foreground/10 p-3">
<form
onSubmit={submitAuth}
className="flex flex-col gap-2 rounded-md border border-duck-dark/10 dark:border-foreground/10 p-3"
>
<span className="text-duck-dark/70 dark:text-foreground/70 font-medium">
{probeState.probe.name} requires authentication
</span>
@@ -361,17 +381,12 @@ export const AIHarnessesSection = () => {
placeholder="Password"
value={authPassword}
onChange={(ev) => setAuthPassword(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Enter' && authUsername && authPassword)
handleAuthSubmit(probeState.probe);
}}
/>
<Button
type="button"
type="submit"
size="sm"
className="h-7 text-xs w-fit bg-duck-dark text-white hover:bg-duck-dark/90 dark:bg-foreground dark:text-background dark:hover:bg-foreground/90 cursor-pointer disabled:opacity-40"
disabled={!authUsername || !authPassword}
onClick={() => handleAuthSubmit(probeState.probe)}
>
Authenticate
</Button>
@@ -384,23 +399,19 @@ export const AIHarnessesSection = () => {
placeholder="API Key"
value={authApiKey}
onChange={(ev) => setAuthApiKey(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Enter' && authApiKey) handleAuthSubmit(probeState.probe);
}}
autoFocus
/>
<Button
type="button"
type="submit"
size="sm"
className="h-7 text-xs w-fit bg-duck-dark text-white hover:bg-duck-dark/90 dark:bg-foreground dark:text-background dark:hover:bg-foreground/90 cursor-pointer disabled:opacity-40"
disabled={!authApiKey}
onClick={() => handleAuthSubmit(probeState.probe)}
>
Authenticate
</Button>
</>
)}
</div>
</form>
)}
</div>
) : (
@@ -460,7 +471,10 @@ export const AIHarnessesSection = () => {
</button>
</div>
{editingProvider === provider.key && (
<div className="flex items-center gap-2 pl-4">
<form
onSubmit={(ev) => submitApiKey(ev, provider.providerId)}
className="flex items-center gap-2 pl-4"
>
<Input
type="password"
className="h-7 text-xs flex-1"
@@ -470,15 +484,13 @@ export const AIHarnessesSection = () => {
setKeyInputs((prev) => ({ ...prev, [provider.providerId]: ev.target.value }))
}
onKeyDown={(ev) => {
if (ev.key === 'Enter' && keyInputs[provider.providerId]) saveApiKey(provider.providerId);
if (ev.key === 'Escape') setEditingProvider(null);
}}
autoFocus
/>
<button
type="button"
type="submit"
disabled={keyInputs[provider.providerId] === undefined || savingKey === provider.providerId}
onClick={() => saveApiKey(provider.providerId)}
className="shrink-0 p-1 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30 disabled:cursor-default"
title="Save key"
>
@@ -491,7 +503,7 @@ export const AIHarnessesSection = () => {
>
Cancel
</button>
</div>
</form>
)}
</div>
))}
@@ -501,7 +513,11 @@ export const AIHarnessesSection = () => {
if (!provider || connectedProviders.some((cp) => cp.providerId === provider.providerId))
return null;
return (
<div key={provider.providerId} className="flex items-center gap-2">
<form
key={provider.providerId}
onSubmit={(ev) => submitApiKey(ev, provider.providerId)}
className="flex items-center gap-2"
>
<label className="w-36 text-duck-dark/70 dark:text-foreground/70 shrink-0 truncate font-medium text-[11px]">
{provider.key}
</label>
@@ -514,15 +530,13 @@ export const AIHarnessesSection = () => {
setKeyInputs((prev) => ({ ...prev, [provider.providerId]: ev.target.value }))
}
onKeyDown={(ev) => {
if (ev.key === 'Enter' && keyInputs[provider.providerId]) saveApiKey(provider.providerId);
if (ev.key === 'Escape') setEditingProvider(null);
}}
autoFocus
/>
<button
type="button"
type="submit"
disabled={keyInputs[provider.providerId] === undefined || savingKey === provider.providerId}
onClick={() => saveApiKey(provider.providerId)}
className="shrink-0 p-1 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30 disabled:cursor-default"
title="Save key"
>
@@ -535,7 +549,7 @@ export const AIHarnessesSection = () => {
>
Cancel
</button>
</div>
</form>
);
})()}
{unconnectedProviders.length > 0 && (