Skip to content

fix(auth): complete CLI login for already-authed users + login CTA on account_exists claim#220

Merged
mastermanas805 merged 2 commits into
mainfrom
fix/web-cli-login-and-claim-recovery
Jun 10, 2026
Merged

fix(auth): complete CLI login for already-authed users + login CTA on account_exists claim#220
mastermanas805 merged 2 commits into
mainfrom
fix/web-cli-login-and-claim-recovery

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Fixes two funnel-blocking UI gaps found in a live cross-interface journey test. One PR, both bugs.

BUG 1 (HIGH) — instant login cannot complete for an already-signed-in user

When a developer who is already authenticated runs instant login, the CLI opens /login?cli_session=<id> and polls. An already-authed user lands directly on LoginPage with a live token and never takes the OAuth/magic-link return path (LoginCallbackPage) that fires completeCliSession — so POST /auth/cli/<id>/complete never fires and the CLI polls forever.

Fix (src/pages/LoginPage.tsx): a mount effect that, when both getToken() returns a token and a cli_session param is present, fires the same completeCliSession(cliSessionId) path used by LoginCallbackPage (reused, not duplicated), then renders a "✓ Your CLI session is approved — return to your terminal" confirmation instead of the sign-in form. A completion failure surfaces a non-blocking note (still signed in; re-run instant login) — never hard-blocks. The fresh-OAuth/magic-link cli-completion in LoginCallbackPage (web #216 / D2) is untouched and still verified by its existing tests.

BUG 2 (MED) — account_exists (409) claim error had no way to log in

On the claim page, claiming with an email that already has an account returns 409 account_exists and the UI said "log in first" but rendered no login control.

Fix (src/pages/ClaimPage.tsx): the account_exists error branch now renders a "Log in to claim" CTA routing to /login?next=<claim path with token> so the claim resumes after login. Detection keys on the error code (not status) because already_claimed is also 409 (api/internal/handlers/onboarding.go) and is NOT login-recoverable. The claim is still correctly refused for an existing account — this only adds the recovery control.

Tests

  • LoginPage.test.tsx — already-authed + cli_session fires completion + shows confirmation; no-op when unauthed; no-op without param; non-blocking failure note.
  • ClaimPage.test.tsx — account_exists renders the CTA carrying the token through next=; NOT rendered on already_claimed (also 409) or plain errors.
  • e2e/funnel-recovery.spec.ts — Playwright D2-authed completion + failure note + account_exists CTA (mocked VITE_NO_PROXY=1 mode).
  • Local npm run gate green (tsc + build incl. prerender + 1267 vitest). diff-cover patch coverage 100% (18 lines, 0 missing).

Verification: CLI-login fix verified in code + tests; live-authed verification pending (needs a real session). The claim-page CTA renders in the served bundle and is verifiable post-deploy.

🤖 Generated with Claude Code

…A on account_exists claim

Two funnel-blocking UI gaps found in a live cross-interface journey test:

BUG 1 — `instant login` (CLI device-flow) could not complete for an
already-signed-in user. The CLI opens /login?cli_session=<id> and polls,
but an already-authed user lands directly on LoginPage with a live token
and never takes the OAuth/magic-link callback path that fires
completeCliSession — so the device flow never completed and the CLI
polled forever. LoginPage now fires the SAME completion path
(completeCliSession, reused from LoginCallbackPage — no duplicated fetch)
on mount when both getToken() and a cli_session param are present, then
shows a "return to your terminal" confirmation instead of the sign-in
form. A completion failure surfaces a non-blocking note (still signed in,
re-run `instant login`).

BUG 2 — the account_exists (409) claim error told the user to "log in
first" with no control to do so. ClaimPage now renders a "Log in to
claim" CTA on that specific error (keyed on error code, not status, since
already_claimed is also 409) routing to /login?next=<claim path with
token> so the claim resumes after sign-in. The claim is still correctly
refused for an existing account — this only adds the recovery control.

Tests: LoginPage already-authed + cli_session completion (fired / no-op
when unauthed / no-op without param / non-blocking failure); ClaimPage
account_exists CTA (renders + carries token, not on already_claimed/plain
errors). Playwright D2-authed + account_exists CTA specs in
funnel-recovery.spec.ts. Patch coverage 100% (diff-cover).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mastermanas805 mastermanas805 enabled auto-merge (squash) June 10, 2026 20:24
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
dist/assets/index-BF276kaL.js 0 B (-100% 🔽)
dist/assets/index-DhCc6Wh3.css 6.14 KB (0%)
dist/assets/index-oCnebW5x.js 162.97 KB (+100% 🔺)

…re 100% patch coverage

The mount effect double-invoked under React StrictMode, firing
completeCliSession twice (Playwright caught it: count=2 locally, racy 0 in
CI). Guard with a fire-once ref and drop the per-pass `cancelled` flag —
under StrictMode the first pass's cleanup fired before the async resolved,
so the cancelled-gate (with the ref blocking the second pass) dropped the
result and never rendered the confirmation. A setState after unmount is a
no-op in React 19, so firing once and always committing is correct.

Also split the pre-existing PAT-error ternary into single-line statements
so v8 emits per-line DA markers (its multi-line-ternary continuation lines
carry no marker), restoring diff-cover to 100% patch coverage after the
insertion shifted those lines into the diff window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mastermanas805 mastermanas805 merged commit 8e552e9 into main Jun 10, 2026
19 checks passed
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.

1 participant