fix(ui): poll for out-of-band email confirmation #180

Merged
developer merged 1 commits from feature/email-confirm-poll-fallback into development 2026-07-05 12:20:38 +00:00
Owner

Stacked on #179 (branched off it — both touch Profile.svelte, which would conflict as independent branches). Until #179 merges, this PR's diff also shows its commit c1805e5; review the second commit fb7490f here. Merge #179 first, then this.

Root cause (systematic-debugging)

An add-email confirmation can complete out of band — the recipient taps the one-tap link in the email, which confirms in another browser/session. The backend already publishes a notify profile re-fetch signal for exactly this (handlers_auth.go handleEmailConfirmLink), and the client already re-fetches on it (app.svelte.ts).

But the live stream is single-shot with no replay: a Mini App backgrounded while the user is in their mail app tapping the link drops the stream and misses the event (the gateway hub has no subscriber to deliver to), and the reconnect on foreground does not re-sync — so the open code form stayed until a manual reload. On Telegram Desktop the app is never backgrounded, so the push works and there is no bug there.

Fix (client-only fallback — the push half already exists)

While an add-email confirmation is pending (a code was sent, no email yet), poll profile.get on a 4s interval and on foreground regain until the address lands; the $effect stops the moment the email appears. The live push still updates instantly when foregrounded — this only covers the backgrounded-miss gap. No backend/proto/fbs/wire change (the profile event kind already exists).

Tests

New mock e2e (social.spec.ts) attaches the email without emitting a live event (new window.__mock.clearEmail / confirmEmailOutOfBand seams), so it exercises the poll, not the push, and asserts the code form collapses into the email row. Verified failing pre-fix, green post-fix.

Verification (local)

  • pnpm check — 0 errors / 0 warnings
  • pnpm test:unit — 396 passed
  • pnpm test:e2e190 passed (Chromium + WebKit), incl. the new test on both
  • pnpm build — ok

Docs: ARCHITECTURE.md §10 notes the single-shot gap + the poll fallback.

**Stacked on #179** (branched off it — both touch `Profile.svelte`, which would conflict as independent branches). Until #179 merges, this PR's diff also shows its commit `c1805e5`; review the second commit `fb7490f` here. **Merge #179 first**, then this. ## Root cause (systematic-debugging) An add-email confirmation can complete **out of band** — the recipient taps the one-tap link in the email, which confirms in another browser/session. The backend **already** publishes a `notify` `profile` re-fetch signal for exactly this (`handlers_auth.go` `handleEmailConfirmLink`), and the client already re-fetches on it (`app.svelte.ts`). But the live stream is **single-shot with no replay**: a Mini App backgrounded while the user is in their mail app tapping the link drops the stream and misses the event (the gateway hub has no subscriber to deliver to), and the reconnect on foreground does **not** re-sync — so the open code form stayed until a manual reload. On Telegram **Desktop** the app is never backgrounded, so the push works and there is no bug there. ## Fix (client-only fallback — the push half already exists) While an add-email confirmation is pending (a code was sent, no email yet), poll `profile.get` on a 4s interval **and on foreground regain** until the address lands; the `$effect` stops the moment the email appears. The live push still updates instantly when foregrounded — this only covers the backgrounded-miss gap. No backend/proto/fbs/wire change (the `profile` event kind already exists). ## Tests New mock e2e (`social.spec.ts`) attaches the email **without** emitting a live event (new `window.__mock.clearEmail` / `confirmEmailOutOfBand` seams), so it exercises the **poll**, not the push, and asserts the code form collapses into the email row. Verified failing pre-fix, green post-fix. ## Verification (local) - `pnpm check` — 0 errors / 0 warnings - `pnpm test:unit` — 396 passed - `pnpm test:e2e` — **190 passed** (Chromium + WebKit), incl. the new test on both - `pnpm build` — ok Docs: `ARCHITECTURE.md` §10 notes the single-shot gap + the poll fallback.
developer added 2 commits 2026-07-05 11:59:27 +00:00
feat(ui): hide current-host sign-in row in profile
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m6s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m40s
c1805e5b7c
Inside a Telegram/VK Mini App the host provider is auto-linked. Once the
player also linked an email, `canUnlink` turned true and the "Unlink"
control appeared on the host platform's own row — letting them unlink the
very platform they are signed in through, which is meaningless.

Gate the Telegram row on `!insideTelegram()` and the VK row on
`!insideVK()`, reusing the runtime host detectors that already gate the
"link" buttons. Symmetric: inside TG only the TG row is hidden (the VK row
still shows, since VK is not the current host), and vice versa. The web and
native builds are unchanged (both detectors are false there); the backend
is untouched — this is a UI display gate, and `linkUnlink` still refuses to
remove the last identity.

Docs: FUNCTIONAL.md (+_ru mirror).
fix(ui): poll for out-of-band email confirmation
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
fb7490f1df
An email link/code can be confirmed out of band: the recipient taps the
one-tap link in the email, which confirms in another browser/session. The
backend already publishes a `notify` `profile` re-fetch signal for this
(handlers_auth.go handleEmailConfirmLink), and the client re-fetches on it.

But the live stream is single-shot with no replay: a Mini App backgrounded
while the user is in their mail app tapping the link drops the stream and
misses the event (the gateway hub has no subscriber to deliver to), and the
reconnect on foreground does not re-sync — so the open code form stayed
until a manual reload. On Telegram Desktop the app is never backgrounded,
so the push works and there was no bug.

Add a client-side fallback: while an add-email confirmation is pending
(a code was sent, no email yet), poll `profile.get` on a 4s interval and on
foreground regain until the address lands; the effect stops as soon as the
email appears. The live push still updates instantly when foregrounded —
this only covers the backgrounded-miss gap.

Tests: a mock e2e attaches the email WITHOUT emitting a live event (new
window.__mock.clearEmail / confirmEmailOutOfBand seams), so it exercises the
poll, not the push, and asserts the code form collapses into the email row.
Docs: ARCHITECTURE.md §10 notes the single-shot gap + the poll fallback.
owner approved these changes 2026-07-05 12:16:50 +00:00
developer merged commit 16349f5ad9 into development 2026-07-05 12:20:38 +00:00
developer deleted branch feature/email-confirm-poll-fallback 2026-07-05 12:20:38 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#180