Repository navigation
Fix login-screen UX: resend countdown, validation, demo-code mask, phone layout (T3: AUTH-03, 04, 05, 09, 10, 11, 12, 14, VS-02, AX-06) - #210
Merged
Conversation
…one layout (T3) Resend is disabled with a countdown after each code is sent, kept in sessionStorage per address so a reload of /login keeps it. A 429 that names a wait says how long. Sending to the same address again within the wait opens the code step without a request. Each send clears old errors and notices. Sending shows a spinner and, after 4 s, says it can take a few seconds; one click makes one request, and verify and resend use the address the code went to. The forms are noValidate, so only the app's localized messages show. An empty email says it is required, and an over-long one says so instead of being cut. The demo-code field formats input as DEMO-XXXX-XXXX and gives a format error. On phones the card starts near the top. /login, RequireAuth's loading view and the auth callback views each render one <main>. Fixes AUTH-04, AUTH-05, AUTH-09, AUTH-10, AUTH-11, AUTH-12, VS-02, AX-06. AUTH-03 and AUTH-14 were already fixed on main; a verify recipe now covers both on the local stack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedEnable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Next included review available in 32 minutes. View limit details
Note 🎁 Summarized by CodeRabbit Free
Comment |
This was referenced Oct 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Login-screen fixes from the 22 Oct QA report (batch T3), built on T1 (#194) and T2 (#202). There are no migrations, no Edge Function changes and no Supabase settings changes. Merging deploys the frontend only.
Issues
AUTH-03, wrong code reported as expired. Main already had this fix before the batch started. #184 (b23b5d7) changed
login.errors.codeExpiredto "That code is wrong or has expired. Check it or send a new one." in all three languages, andsrc/test/auth/login-otp.test.tsxcovers theotp_expiredmapping. b23b5d7 is an ancestor of the QA build d749051, so the prod log entry at 15:57 came from an older deploy. This PR adds a comment at the mapping and a recipe step that sends a wrong code to the real local Auth (403otp_expired) and checks the message. The digits-only filter is unchanged.AUTH-04, Resend right away hits the rate limit. After each send, Resend is disabled and counts down ("Resend in 1:00", "0:59" and so on). The deadline is stored in sessionStorage under the trimmed, lowercased address, so a reload of /login keeps it. Sending to the same address again before it runs out opens the code step with "We sent a code to this address a moment ago…" and makes no request. When Auth refuses with "you can only request this after N seconds", the alert says "Too many attempts. Try again in 0:42." once and the button counts down from N. The hourly email cap names no wait. In that case the alert keeps the generic message and the button waits a minute. The PKCE verifier code is untouched.
AUTH-05, stale errors. Each send, resend, demo sign-in and "Use a different email" clears the old field error, the failed-link notice and the session-expired notice. A resend says "New code sent".
AUTH-09, no progress feedback. Send, Resend, Verify and the demo sign-in show a spinner. After 4 seconds a status line says "This can take a few seconds." A ref blocks a second request while one is running; before this, Enter in the email field could send another. The email field is disabled during a send, and verify and resend use the address the code went to.
AUTH-10, browser bubble next to the app error. All three forms are
noValidate.type="email",autocompleteand the OTPinputModestay.AUTH-11, empty and over-long email. Empty input says "Email is required." Input over 254 characters says the address is too long. The field no longer has
maxLength, so nothing is cut.AUTH-12, demo code format. The field formats typed and pasted input as
DEMO-XXXX-XXXX. It uppercases, drops anything that isn't a letter or digit, and adds the prefix and dashes, soab12cd34becomesDEMO-AB12-CD34. The caret stays after the character it followed, and Backspace over a trailing dash works. The input haspattern,autoCapitalize="characters"and no spellcheck. An incomplete code says "Demo codes look like DEMO-AB12-CD34." and an empty one says "Enter your demo code." A well-formed unknown code still gets "Demo code not found." from the server. No real demo codes were created or used; the recipes mint and delete local ones.AUTH-14, signed-out deep links. Current main already redirects. RequireAuth sent signed-out /app routes to /login with
state.fromat d749051 too, and T2 carries the deep link through Google and the email link as?next=. A probe of signed-out/app/generatebefore this change showed URL/loginandhistory.state.usr.from.pathname = "/app/generate". I couldn't reproduce an in-place card. What was missing was the<main>landmark (AX-06 below). The new recipeauth-deep-link-email-codeproves the round trip on the local stack. It goes from /app/generate to /login, sends a code, enters a wrong one, resends after the countdown, signs in, and lands back on /app/generate. T2's per-account landing (AUTH-06) is unchanged, andauth-switch-accountstill passes.VS-02, gap above the card on phones. Below 768 px (
md) the page starts at the top with 16 px padding. From 768 px up it stays centred as before. The top of the back link moved as follows (EN; HU shows the same pattern): 375 px from 133 to 16, 360 px from 97 to 16. At 768 px it stays at 249 and at 1280 px at 137.AX-06, no
<main>on signed-out views. /login, RequireAuth's loading view and both AuthCallbackPage views now render one<main>. The callback's timed-out view gets an<h1>. RequireAuth's spinner has arole="status"wrapper with a hidden "Loading" label. The callback spinner'srole="status"moved to an inner element so it doesn't replace the landmark role. The repo has no axe dependency, so I checked with role queries: /login and signed-out /app/generate (which redirects to /login) have exactly onemainand oneh1, in both the recipe and a Playwright probe at four widths. The route's own Suspense skeleton, shown while the /login chunk loads, still has no<main>. That was already the case on main and is not changed here.Verification
GitHub Actions minutes are used up, so CI did not run. Every result below is local.
npm run verifypassed in full on this PR's head (4f1d0c4, rebased onto b0b2ae7):src/test/auth/login-form.test.tsxcovers the resend countdown with fake timers across a remount, the 429 wait at both the email step and the resend step,noValidate, the empty and over-long email errors, the demo-code mask and caret, andauthRetryAfterSeconds.test:sw: all offline checks true.I ran the local stack (
verify.sh up, fake provider, local Supabase with Mailpit, never prod mail) on 6c4a08d6. That is this commit before the rebase; the rebase only brought in unrelated T4, T9 and live-stream changes and one README conflict. Results on desktop-chromium and mobile-chromium:recipes/auth.spec.ts: 5 of 5 passed on each. These wereauth-email-otp,auth-session-expired,auth-email-link(now waits out the countdown before its 429 resend), the newauth-deep-link-email-code, andauth-switch-account.e2e/full-stack/auth.spec.ts: 3 of 3 passed on each, including the unknown demo code going through the mask to the real function.Then
verify.sh downreleased the lock.Screenshots of /login at 375 px in EN and HU, before and after, plus 360, 768 and 1280 px, were taken locally and shown in the T3 thread; they are not uploaded here. The plain dev server only rendered pages; sign-in ran only against the local stack.
Review rounds
Codex was rate-limited, so each round was one fresh-context Claude subagent (
claude-opus-5-5onclaudeAgent_cpa) reading the full diff against main.Round 1 found one Medium. The email field stayed editable during a send, and the countdown, resend and verify read the live field instead of the address the code went to. I fixed it with a
sentTostate, and the field is now disabled while sending. The Lows and Nits I fixed:<main>had no content.sm(640 px) instead ofmd.I kept the
noValidateattribute test because the brief asks for one.Round 2 checked every round-1 fix and found nothing Medium or above. I fixed these:
nextvariable.Left as is:
authStatusstays aterrorwith no message after a wait ends. The reviewer confirmed this is harmless.Needs a real browser
next, which this PR doesn't change.autoCapitalizeon the demo field, and one-time-code autofill.max_frequency. If it is above 60 seconds, a resend after the client's minute gets a 429 that names the wait, and the UI shows it. Local is 1 second.Deploy notes
Migrations: none. Edge Functions to deploy: none.
🤖 Generated with Claude Code