From d5f6e571ae8b35b567e3217cf8ca36cf52bf2470 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:17:02 +0000 Subject: [PATCH 1/3] Fix hosted auth UX: autofocus, OTP paste, and email persistence across tabs Co-Authored-By: mantra --- .../src/hosted-components/auth/auth-page.tsx | 13 ++++++++---- .../auth/forms/credential-sign-in.tsx | 9 +++++++-- .../auth/forms/credential-sign-up.tsx | 6 +++++- .../auth/forms/magic-link-sign-in.tsx | 20 ++++++++++++++++--- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/apps/hosted-components/src/hosted-components/auth/auth-page.tsx b/apps/hosted-components/src/hosted-components/auth/auth-page.tsx index 3a5fbb987..280351658 100644 --- a/apps/hosted-components/src/hosted-components/auth/auth-page.tsx +++ b/apps/hosted-components/src/hosted-components/auth/auth-page.tsx @@ -92,6 +92,11 @@ function HostedAuthPageInner(props: { const projectFromHook = app.useProject(); const project: AuthProject = props.mockProject ?? projectFromHook; + // Lifted so the typed email survives switching between the "Email" (magic link) and + // "Email & Password" (credential) tabs — the tab content is unmounted when inactive, + // so keeping the email here (instead of inside each form) both persists and shares it. + const [email, setEmail] = useState(""); + if (props.automaticRedirect && user != null && props.mockProject == null) { return ( }> @@ -165,16 +170,16 @@ function HostedAuthPageInner(props: { Email & Password - + - {props.type === "sign-up" ? : } + {props.type === "sign-up" ? : } ) : project.config.credentialEnabled ? ( - props.type === "sign-up" ? : + props.type === "sign-up" ? : ) : project.config.magicLinkEnabled ? ( - + ) : !(hasOAuthProviders || hasPasskey) ? (

No authentication method enabled.

) : null} diff --git a/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-in.tsx b/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-in.tsx index 04f4b79c5..06fd0587d 100644 --- a/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-in.tsx +++ b/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-in.tsx @@ -7,9 +7,13 @@ import { Button, Input, Label, PasswordInput } from "~/components/ui"; import { FormWarningText } from "../supporting/form-elements"; import { isValidEmail } from "../supporting/utils"; -export function CredentialSignIn() { +export function CredentialSignIn(props: { + email: string, + onEmailChange: (email: string) => void, +}) { const app = useStackApp(); - const [email, setEmail] = useState(""); + const email = props.email; + const setEmail = props.onEmailChange; const [password, setPassword] = useState(""); const [emailError, setEmailError] = useState(null); const [passwordError, setPasswordError] = useState(null); @@ -52,6 +56,7 @@ export function CredentialSignIn() { id="email" type="email" autoComplete="email" + autoFocus className="h-10 rounded-xl border-border bg-background" value={email} onChange={(event) => { diff --git a/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-up.tsx b/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-up.tsx index efdf65388..7dabf52cb 100644 --- a/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-up.tsx +++ b/apps/hosted-components/src/hosted-components/auth/forms/credential-sign-up.tsx @@ -10,9 +10,12 @@ import { isValidEmail } from "../supporting/utils"; export function CredentialSignUp(props: { noPasswordRepeat?: boolean, + email: string, + onEmailChange: (email: string) => void, }) { const app = useStackApp(); - const [email, setEmail] = useState(""); + const email = props.email; + const setEmail = props.onEmailChange; const [password, setPassword] = useState(""); const [passwordRepeat, setPasswordRepeat] = useState(""); const [emailError, setEmailError] = useState(null); @@ -67,6 +70,7 @@ export function CredentialSignUp(props: { id="email" type="email" autoComplete="email" + autoFocus className="h-10 rounded-xl border-border bg-background" value={email} onChange={(event) => { diff --git a/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx b/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx index 9f03bdfa4..d4cab2b66 100644 --- a/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx +++ b/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx @@ -1,7 +1,7 @@ import { KnownErrors } from "@hexclave/shared"; import { useStackApp } from "@hexclave/react"; import { runAsynchronouslyWithAlert } from "@hexclave/shared/dist/utils/promises"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Button, @@ -24,6 +24,13 @@ function MagicLinkOtp(props: { const [otp, setOtp] = useState(""); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); + const otpInputRef = useRef(null); + + // Focus the (visually hidden) OTP input on mount so the user can type or paste the + // code immediately without having to click on the boxes first. + useEffect(() => { + otpInputRef.current?.focus(); + }, []); useEffect(() => { if (otp.length !== 6 || submitting) { @@ -56,9 +63,11 @@ function MagicLinkOtp(props: {
Enter the code from your email setOtp(value.toUpperCase())} @@ -79,9 +88,13 @@ function MagicLinkOtp(props: { ); } -export function MagicLinkSignIn() { +export function MagicLinkSignIn(props: { + email: string, + onEmailChange: (email: string) => void, +}) { const app = useStackApp(); - const [email, setEmail] = useState(""); + const email = props.email; + const setEmail = props.onEmailChange; const [emailError, setEmailError] = useState(null); const [nonce, setNonce] = useState(null); const [loading, setLoading] = useState(false); @@ -130,6 +143,7 @@ export function MagicLinkSignIn() { id="email" type="email" autoComplete="email" + autoFocus className="h-10 rounded-xl border-border bg-background" value={email} onChange={(event) => { From aeb1304823d62f9512dbbdd9ea9ca63e55b76744 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:32:20 +0000 Subject: [PATCH 2/3] Drop autoComplete=one-time-code to avoid OTP autofill state desync Co-Authored-By: mantra --- .../src/hosted-components/auth/forms/magic-link-sign-in.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx b/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx index d4cab2b66..600754ebe 100644 --- a/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx +++ b/apps/hosted-components/src/hosted-components/auth/forms/magic-link-sign-in.tsx @@ -67,7 +67,6 @@ function MagicLinkOtp(props: { maxLength={6} type="text" inputMode="text" - autoComplete="one-time-code" pattern="^[a-zA-Z0-9]+$" value={otp} onChange={(value) => setOtp(value.toUpperCase())} From 3c48d810cb892164ef187b34263a813fd2b2052d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 06:05:03 +0000 Subject: [PATCH 3/3] Re-trigger CI (E2E mock runner hung >3h, unrelated to change) Co-Authored-By: mantra