Closer/docs
null 6330bf98ca fix(auth): don't mistake the sign-in stub document for a new user (C-AUTH-001)
Verified on a throwaway emulator with a real paired account: signing in on a new
device sent a fully set-up user to CREATE_PROFILE instead of RECOVERY. Not a rare
race — it happened on every run.

The previous commit guessed at the cause and was wrong: it assumed the profile
read *failed*, and retried on exceptions. Instrumenting the decision showed the
read succeeds and returns a document that exists with no fields at all:

  DIAG null=false nameBlank=true sexBlank=true couple=false -> create_profile
  DIAG null=false nameBlank=true sexBlank=true couple=false -> create_profile
  DIAG null=false nameBlank=false sexBlank=false sexLen=33 couple=true -> home

The first two land within a millisecond of sign-in; the real document arrives
215ms later. Sign-in registers the FCM token, and that merge-write materialises
users/{uid} before the profile has synced. Reading from the server does not dodge
it — Source.SERVER returns the same field-less document — so the read has to be
*trusted* before it is acted on, not just sourced differently.

resolveDestination now treats a field-less document as "ask again" rather than
"new user", retrying until one with real identity on it arrives; only that decides.
A read that never succeeds routes Home, which is non-destructive and routes to
Recovery on its own. New users are unaffected: every path that creates a user
writes email and createdAt before onboarding, and a document that genuinely stays
empty still falls through to profile setup.

This mattered because createUser is a whole-document set(), not a merge, and the
CreateProfile submit calls it: tapping through the screen that should never have
been shown overwrites the real displayName with the locked placeholder and drops
coupleId/partnerId, unpairing the couple.

Keeps getUserFromServer for the decision so an offline read throws rather than
answering from cache. Landmine written up as C-AUTH-001 — the general trap is that
exists() == true does not mean populated, and blank fields right after sign-in mean
"not synced yet".

Verified live end to end on the throwaway: sign-in now lands on Recovery, and a
Title-Cased phrase (previously rejected) unlocks to Home, "Connected with Ben".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 00:48:49 -05:00
..
brand feat(packs): add distinct pack artwork 2026-07-14 20:18:56 -05:00
crypto docs(crypto): key-storage migration design (design-only, gated) 2026-07-06 21:08:52 -05:00
qa docs: reconcile stale docs with project reality (Batch 1 doc hygiene) 2026-07-11 18:29:08 -05:00
release docs: reconcile stale docs with project reality (Batch 1 doc hygiene) 2026-07-11 18:29:08 -05:00
screenshots docs(readme): refresh ready-state home screenshots 2026-07-09 00:30:27 -05:00
store docs(store): refine banner card icon balance 2026-07-09 00:04:16 -05:00
CopyMigration.md docs: reconcile stale docs with project reality (Batch 1 doc hygiene) 2026-07-11 18:29:08 -05:00
Engineering_Reference_Manual.md fix(auth): don't mistake the sign-in stub document for a new user (C-AUTH-001) 2026-07-15 00:48:49 -05:00
NameChange.md docs: reconcile stale docs with project reality (Batch 1 doc hygiene) 2026-07-11 18:29:08 -05:00
copy-guide.md docs: brand identity, copy guide, release checklists, store assets, README 2026-06-21 17:56:46 -05:00
date-planning-roadmap.md docs: reconcile stale docs with project reality (Batch 1 doc hygiene) 2026-07-11 18:29:08 -05:00
standardization-review.md docs: reconcile stale docs with project reality (Batch 1 doc hygiene) 2026-07-11 18:29:08 -05:00