feat(ui): autofocus login fields; keep verification code out of form history
The two-step e-mail login now drops the cursor on each step's primary field as it mounts — the e-mail field on load, the code field once the e-mail step advances — via a small `use:` action. Focusing fires each input's onfocus, which clears the readonly autofill guard, so the field is editable straight away. The code input now requests `autocomplete="one-time-code"` instead of `new-password`. The latter is a password-manager hint and does not stop Firefox saving the typed code to form history (it was offering the previous code back in a dropdown). `one-time-code` is the semantic token for a verification code; Firefox honours it specifically to keep the value out of form history (Mozilla bug 1547294). The e-mail field keeps `new-password` to fend off saved-login autofill. Tests: new Vitest cases assert autofocus on both steps and the code field's `one-time-code` token; a new Playwright case covers the same in Chromium and WebKit (Safari engine). Firefox form history is owner manual-QA — there is no Firefox project in the e2e matrix. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { tick } from "svelte";
|
||||
import { appScreen } from "$lib/app-nav.svelte";
|
||||
import {
|
||||
AuthError,
|
||||
@@ -22,11 +23,30 @@
|
||||
// fields and pops the Keychain suggester regardless. The classic
|
||||
// workaround is to render the input as `readonly` initially —
|
||||
// Safari does not autofill readonly fields — and drop the
|
||||
// attribute on the first user focus so typing still works. Once
|
||||
// dropped, the flag stays false for the rest of the page life.
|
||||
// attribute on the first focus (user-driven or via `focusOnMount`
|
||||
// below) so typing still works. Once dropped, the flag stays false
|
||||
// for the rest of the page life.
|
||||
let emailReadonly = $state(true);
|
||||
let codeReadonly = $state(true);
|
||||
|
||||
// Autofill intent differs per field. The e-mail input asks for
|
||||
// `new-password` to stop password managers injecting a saved login.
|
||||
// The code input asks for `one-time-code` (set on the element): it is
|
||||
// the semantic token for a verification code and the only reliable way
|
||||
// to keep Firefox from saving the code to form history and offering it
|
||||
// back in a dropdown — Firefox honours it specifically, while plain
|
||||
// `autocomplete="off"` is not respected here (Mozilla bug 1547294).
|
||||
|
||||
// Drop the cursor on the step's primary field as soon as it mounts so
|
||||
// the user can start typing immediately: the e-mail field on load, the
|
||||
// code field once the e-mail step advances. Deferring one tick lets the
|
||||
// field's own focus handler wire up first; firing it clears the
|
||||
// readonly autofill guard above, leaving the field editable. Mirrors
|
||||
// the focus pattern in `designer-science.svelte`.
|
||||
function focusOnMount(node: HTMLInputElement): void {
|
||||
void tick().then(() => node.focus());
|
||||
}
|
||||
|
||||
function describe(err: unknown): string {
|
||||
if (err instanceof AuthError) {
|
||||
return err.message;
|
||||
@@ -199,6 +219,7 @@
|
||||
spellcheck="false"
|
||||
readonly={emailReadonly}
|
||||
onfocus={() => (emailReadonly = false)}
|
||||
use:focusOnMount
|
||||
bind:value={email}
|
||||
disabled={pending}
|
||||
required
|
||||
@@ -228,12 +249,13 @@
|
||||
type="text"
|
||||
name="galaxy-login-code"
|
||||
inputmode="numeric"
|
||||
autocomplete="new-password"
|
||||
autocomplete="one-time-code"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
readonly={codeReadonly}
|
||||
onfocus={() => (codeReadonly = false)}
|
||||
use:focusOnMount
|
||||
bind:value={code}
|
||||
disabled={pending}
|
||||
required
|
||||
|
||||
Reference in New Issue
Block a user