Skip to content

fix(auth): follow central-manager's verification outcome on the verify-email page - #2030

Merged
dawsontoth merged 3 commits into
stagefrom
fix/1697-verify-email-outcomes
Oct 28, 2026
Merged

dawsontoth merged 3 commits into
stagefrom
fix/1697-verify-email-outcomes

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor
An owl postmaster at a forest crossroads points three woodland friends down different paths

⊙ Problem

HarperFast/central-manager#785 makes PUT /VerifyEmail/ with only the token replace the sign-up password and email a link to set a new one. Studio's verify page ignores the response and sends everyone to sign in with the old password, which fails. #785 can't deploy until Studio handles this, and Studio must still work with today's central-manager, which sends no outcome.

🤔 Is this the right first step?

Yes, as specified for step C1 of #1697: handle every outcome and send no newPassword. Asking for a password on this page is the second half. Splitting it keeps this change small and safe to ship before #785.

💡 Solution

The verify page now follows the response's outcome:

  • None (today) or password_set: toast and go to sign in, as before.
  • reset_email_sent, password_reset_required, already_verified or an unknown outcome: stay on the page and say how to get a working password.
  • 503: retried 3 times. Any verify still unanswered (503, 502, 504, no response) or sent offline gets Try again with the same link.
  • Each token is sent once per arrival, so StrictMode's second effect can't replace the first answer.
Screenshots: the new states (light theme, dev server, response answered locally)

reset_email_sent

Email verified, check your inbox to set your password

already_verified

Already verified, use the set-password link or Forgot Password

A 503 after three retries

Could not confirm the email was verified, with Try again and links to sign in and Forgot Password

Sent while offline

You're offline, with Try again and links to sign in and Forgot Password

🤔 Should already_verified stay on the page?

The suggested design toasted and went to sign in. But a reload, a second click, a mail scanner or a retry gets that answer after the first visit may have replaced the password, so the page stays and points at the set-password email. Keeping the first answer instead is #2029.

⚖️ Alternatives

  • Only picking a toast per outcome in the existing onSuccess: a toast is too brief to be the only notice that the password was replaced, and the new-link form can't retry the same link as a 503 asks.

🔧 Changes

🤔 Should automatic retries stay 503-only?

Only a 503 retries on its own, because central-manager says to retry it; a 502, 504 or no answer goes straight to Try again. Studio can't tell central-manager's 503 from a gateway's, so both get the 14 seconds of retries. #2027 tracks narrowing that.

🤔 Should the e2e drop the legacy branch after #785 ships?

It accepts either answer, so it passes on both sides of the deploy. Once #785 is everywhere, a central-manager that regressed to the old answer would still pass. Removing the sign-in branch then is a small follow-up.

✅ Verification

  • vitest run: 590 files, 6,094 tests pass. tsc -b, oxlint, dprint check and e2e tsc --noEmit are clean.
  • Mutation check: removing any guard this adds (once per arrival, late answer, retry, toast suppression, RUM report, string-only token, offline in the predicate, the offline copy, the panel's links), narrowing Try again to 503, sending already_verified to sign in, or reporting a refusal twice turns a test red.
  • Browser (dev server under StrictMode, PUT /VerifyEmail/ answered locally): one PUT per token; each view renders; four 503s led to Try again with no toast; a 503 then already_verified explained the repeat; an offline send showed the offline copy, and Try again after reconnecting resent the same token.
  • The @roundtrip e2e did not run: it needs Mailosaur credentials and the dev environment, which this machine lacks. It typechecks and lists.
  • The first commit went in with --no-verify: at machine load 30–90 the husky hook's unbounded vitest run segfaulted once and once failed entityAuthCheckScope.test.ts (untouched; passes alone). Its steps passed by hand. The two follow-up commits ran the full hook.

🤖 Generated by Claude Code (Claude Opus 5.5); posted via @dawsontoth.

Related PRs: none found (partial scan: issue #2026 search truncated at 20 of 82)
Complexity: medium

Deferred findings

Review-Coverage: authored=claude; ran=gemini,codex,cursor-composer,cursor-grok; pending=cursor-grok; late=cursor-grok:pending; adjudicated=domain; blocked=cursor-muse(failed); declined=cursor-kimi,muse; rounds=7; full=3 @ 8b76766

Review-Attention: deep ~30m (sensitive: VerifyEmail.tsx, useVerifyEmail.ts; decisions: do-less-alternative, offline-in-unconfirmed-predicate, reload-copy-vs-persisted-outcome, hook-level-503-retry, e2e-accepts-either-cm) @ 8b76766

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the email verification flow to support a new central-manager behavior where the sign-up password is replaced and a set-password email is sent upon verification. It updates the VerifyEmail component and its hook to handle various verification outcomes (such as reset_email_sent and already_verified), implements automatic retries for 503 errors, and adds comprehensive unit and E2E tests. Feedback on the changes suggests correcting the generic type parameter on the apiClient.put call in useVerifyEmail.ts to ensure correct typing, and forwarding all arguments (error, variables, context) to the parent onError handler in the mutation hook to prevent potential runtime issues.

Comment thread src/features/auth/hooks/useVerifyEmail.ts
Comment thread src/features/auth/hooks/useVerifyEmail.ts
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 82.08% 16683 / 20324
🔵 Statements 81.97% 17704 / 21598
🔵 Functions 77.49% 4534 / 5851
🔵 Branches 76.95% 13280 / 17257
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/auth/VerifyEmail.tsx 90.56% 97.05% 77.77% 90.56% 47-56, 208
src/features/auth/hooks/useVerifyEmail.ts 100% 100% 100% 100%
Generated in workflow #2478 for commit 892dc0f by the Vitest Coverage Report Action

@dawsontoth
dawsontoth marked this pull request as ready for review October 27, 2026 20:53
@dawsontoth
dawsontoth requested a review from a team as a code owner October 27, 2026 20:53

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Reviewed with Muse

Comment thread src/features/auth/VerifyEmail.tsx
Comment thread src/features/auth/VerifyEmail.tsx Outdated
dawsontoth and others added 3 commits October 28, 2026 14:43
…y-email page

central-manager#785 makes a token-only `PUT /VerifyEmail/` replace the sign-up
password and email a set-password link, and reports what it did in a new
`outcome` field. The page ignored the response and sent everyone to sign in
with a password that no longer works.

The page now reads `outcome`. No outcome (today's central-manager) and
`password_set` go to sign in as before. `reset_email_sent`,
`password_reset_required`, `already_verified` and any outcome it doesn't
know stay on the page and say how to get a working password;
`already_verified` is what a reload, a second click or a retried request
answers, after the first visit may already have replaced the password.

A 503 is retried three times with backoff. Any verify left without an answer
(503, a gateway's 502/504, no response) then gets "Try again" with the same
link instead of the request-a-new-link form, in copy that claims no outcome;
repeating it is safe, since one that landed answers `already_verified`. Each
token is submitted once per arrival, so StrictMode's second effect can't
replace the first answer, and a late answer for a token the route has
dropped is ignored.

The signup round-trip e2e follows either answer, so it passes before and
after central-manager#785 deploys.

Part of #1697.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… out

A verify sent while the browser was offline fell back to the new-link
form and an offline toast, so reconnecting offered no way to retry the
link short of reloading. It now gets the retry panel with the offline
copy, stays out of the toast and RUM like the other unconfirmed verifies,
and Try again resends the same token once back online.

The retry panel also gains the Back to sign in and Forgot password links
the verified views already had, so a persistent 502/504 is not a dead end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving email verification to the page-shown offline cases dropped it from
the shared test that pinned an online refusal's toast description and its
single RUM report; the hook's own 401/403 test now asserts both. Also
rewords the onError comment, which no longer fit the offline case, and a
mail helper comment that named a central-manager function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dawsontoth
dawsontoth force-pushed the fix/1697-verify-email-outcomes branch from 8b76766 to 892dc0f Compare October 28, 2026 18:43
@dawsontoth
dawsontoth added this pull request to the merge queue Oct 28, 2026
Merged via the queue into stage with commit d2275f7 Oct 28, 2026
2 checks passed
@dawsontoth
dawsontoth deleted the fix/1697-verify-email-outcomes branch October 28, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants