fix(web): keep focus in place after removing a media API key (#148)

Remove unmounts itself once clicked, so focus fell to the body and the
settings dialog pulled it back to its popup a frame later. Whatever field
the user (or a test) focused in that frame lost focus again; in CI the
cleared transcription URL was still sent on the next save. Remove now
moves focus to the key field before it disappears.


Claude-Session: https://claude.ai/code/session_01UfVneQG6488JGLiHYAc9RZ

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-05 06:16:36 +02:00 committed by GitHub
parent ca57ebda85
commit f3fb07e41f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 10 additions and 2 deletions

View File

@ -105,6 +105,7 @@ test("the deployment owner points media at their own servers", async ({ page },
await expect(comfyKey).toHaveAttribute("placeholder", "Saved"); await expect(comfyKey).toHaveAttribute("placeholder", "Saved");
await comfy.getByRole("button", { name: "Remove", exact: true }).click(); await comfy.getByRole("button", { name: "Remove", exact: true }).click();
await expect(comfyKey).toBeFocused();
await transcription.getByLabel("Server URL", { exact: true }).fill(""); await transcription.getByLabel("Server URL", { exact: true }).fill("");
await panel.getByRole("button", { name: "Save", exact: true }).click(); await panel.getByRole("button", { name: "Save", exact: true }).click();
await expect.poll(() => saved.length).toBe(2); await expect.poll(() => saved.length).toBe(2);

View File

@ -1,7 +1,7 @@
import { Trans, useLingui } from "@lingui/react/macro"; import { Trans, useLingui } from "@lingui/react/macro";
import type { MediaSettingsUpdate, MediaSettingsView } from "@rakazo/contracts"; import type { MediaSettingsUpdate, MediaSettingsView } from "@rakazo/contracts";
import { Button, Field, FieldLabel, Input, Textarea } from "@rakazo/ui-web"; import { Button, Field, FieldLabel, Input, Textarea } from "@rakazo/ui-web";
import { useEffect, useId, useState } from "react"; import { useEffect, useId, useRef, useState } from "react";
import { rpc } from "../lib/rpc"; import { rpc } from "../lib/rpc";
import { SuccessPop } from "./ai/primitives"; import { SuccessPop } from "./ai/primitives";
@ -28,6 +28,7 @@ function ApiKeyField({
}) { }) {
const { t } = useLingui(); const { t } = useLingui();
const id = useId(); const id = useId();
const inputRef = useRef<HTMLInputElement>(null);
const showSaved = saved && !draft.remove; const showSaved = saved && !draft.remove;
return ( return (
<Field className="mt-4"> <Field className="mt-4">
@ -41,13 +42,19 @@ function ApiKeyField({
variant="ghost" variant="ghost"
size="xs" size="xs"
disabled={disabled} disabled={disabled}
onClick={() => onChange({ value: "", remove: true })} onClick={() => {
// The button unmounts once clicked; hand focus to the field so the dialog
// does not pull it back to its popup a frame later.
inputRef.current?.focus();
onChange({ value: "", remove: true });
}}
> >
<Trans>Remove</Trans> <Trans>Remove</Trans>
</Button> </Button>
) : null} ) : null}
</div> </div>
<Input <Input
ref={inputRef}
id={id} id={id}
type="password" type="password"
autoComplete="new-password" autoComplete="new-password"