Skip to content

Phone input +1 prefix (#777) - Manual Test Plan โ€‹

FieldValue
Branch / PRtest/144-block-sever-e2e / #778
EnvironmentDeployed dev (dev.ourlantern.app) post-merge, or local npm run dev (web :5173)
Build flags / confignone (behavior is default-on; no flag)
Build (commit)see PR head
Testeroperator
Date2026-08-03

What changed โ€‹

Every standard phone entry now shows a fixed, visible +1 prefix and accepts only the local number (formatted 619 555 0001). (The AppealFlow appeal-entry input is intentionally out of scope: a separate moderation flow through appealService, tracked separately.) The +1 is owned by the app; submit is gated on a full 10-digit number so no account can be created (and no lookup fired) under a malformed or partial number. Converted screens: PhonePinSignup, PhonePinLogin, AdminSignup, PhoneMigrationFlow, ForgotPassphraseModal (default neutral-900 style) and LockScreen, MerchantSignup (translucent white/5 style, via the component's className surface override). PhoneReclaimFlow has no phone input (it only displays a passed-in number). MerchantSignup previously shipped the raw typed string to the server; it now sends a normalized +1XXXXXXXXXX.

Summary โ€‹

#ScenarioResultNotes
1Signup: type a local number, +1 visible, format-as-typed[ ] pass [ ] fail [ ] blocked
2Signup: button disabled until 10 digits (no garbage submit)[ ] pass [ ] fail [ ] blocked
3Signup: paste a full +1 (619) 555-0001 normalizes to local[ ] pass [ ] fail [ ] blocked
4Signup: account created under a clean +1XXXXXXXXXX[ ] pass [ ] fail [ ] blocked
5Signup: admin number goes through the same flow[ ] pass [ ] fail [ ] blocked
6Login: sign in with an existing account works[ ] pass [ ] fail [ ] blocked
7Login: button disabled until 10 digits[ ] pass [ ] fail [ ] blocked
8Lock screen: +1 input on the white/5 surface unlocks[ ] pass [ ] fail [ ] blocked
9Phone migration: linked E.164 number pre-fills as local + editable[ ] pass [ ] fail [ ] blocked
10Forgot-passphrase recovery: +1 input + disabled-until-10[ ] pass [ ] fail [ ] blocked
11Merchant signup: +1 input, ships normalized +1 number[ ] pass [ ] fail [ ] blocked

Setup / preconditions โ€‹

  • Use a Prelude sandbox test number (+16195550001..0005, OTP 123456) for any signup, never a real number. See docs/engineering/testing/HEADLESS_E2E.md.
  • A fresh signup needs an invite (?invite=...); mint one with node tooling/e2e/onboard.mjs or from the admin portal.
  • Data to verify lives in lantern-app-dev Firestore. A newly created account's stored number is derivable via its phoneHash; the reliable check is that the OTP send/verify used the exact +1XXXXXXXXXX string (network tab -> /auth/phone/send-otp body) and no account exists under any non-+1 string.
  • Reset a test number between runs: node --env-file=.env.local tooling/scripts/otp-local-test.mjs reset +16195550001 (dev only).

Scenarios โ€‹

1. Signup: visible +1 and format-as-typed โ€‹

  • Goal: the user types only the local number and sees it formatted.
  • Steps:
    1. Open #/signup?invite=<token>.
    2. In the phone field, type 6195550001.
  • Expected: a fixed +1 sits to the left of the field; the typed value renders as 619 555 0001; no way to edit or delete the +1.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:   (screenshot)
Follow-up:

2. Signup: button disabled until 10 digits โ€‹

  • Goal: a partial/malformed number cannot be submitted.
  • Steps:
    1. On signup step 1, enter a valid birth date (18+).
    2. Type only 619555 in the phone field.
    3. Observe "Send Verification Code".
    4. Complete the number to 6195550001.
  • Expected: the button is disabled at 6 digits and enables only at 10. Typing letters or symbols does nothing; pasting more than 10 digits caps at 10.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

3. Signup: paste normalization โ€‹

  • Goal: pasting a fully-formatted number lands on the right local digits.
  • Steps:
    1. Paste +1 (619) 555-0001 into the phone field.
    2. Then separately paste 16195550001.
  • Expected: both render as 619 555 0001 (the pasted +1 / leading 1 is absorbed, not doubled).
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

4. Signup: clean account created + step-2 display โ€‹

  • Goal: a completed signup creates an account under a clean +1 number.
  • Steps:
    1. Enter 6195550001 + birth date, tap "Send Verification Code".
    2. On step 2, read the "code sent to" line.
    3. Enter OTP 123456, finish signup (PIN + terms).
    4. Check the network tab for the /auth/phone/send-otp (and verify-otp) request body.
  • Expected: step 2 shows +16195550001 (not 6195550001). The OTP requests carry phone: "+16195550001". The account is created and signs in.
  • Verify (data): no users doc / auth_lookup row exists under any non-+1 variant of the number.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:   (network request body + screenshot)
Follow-up:

5. Signup: admin number through the same flow โ€‹

  • Goal: an existing admin signing up for an app account still works (the bridge runs through this same step-1 field).
  • Preconditions: a phone number that is registered as an admin (admin portal), used with a valid invite.
  • Steps:
    1. Open signup with an invite, enter the admin's local number, tap "Send Verification Code".
  • Expected: the "Admin account detected" panel appears (linking to the existing admin account), exactly as before. No difference from the free-text input other than the +1 affix.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

6. Login: existing account signs in โ€‹

  • Goal: login still works end to end.
  • Steps:
    1. Open #/login, type the local number of an existing test account.
    2. Enter its PIN, tap "Sign In".
  • Expected: the +1 affix shows; sign-in completes and lands in the app. The lookup request carries the +1XXXXXXXXXX number.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

7. Login: button disabled until 10 digits โ€‹

  • Goal: an incomplete number cannot trigger a lookup.
  • Steps:
    1. On #/login, type 12 in the phone field, enter any 6-digit PIN.
    2. Observe "Sign In".
  • Expected: "Sign In" is disabled; no /auth/phone/lookup request fires (network tab empty for it).
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

8. Lock screen: +1 input unlocks โ€‹

  • Goal: the lock-screen phone+PIN unlock works with the new input on its translucent surface.
  • Preconditions: a signed-in account that has been locked (background the app / trigger the lock).
  • Steps:
    1. On the lock screen, enter your local number in the phone field, then your PIN.
    2. Tap Unlock.
  • Expected: the +1 affix shows on the white/5 field (matching the lock-screen look); Unlock is disabled until 10 digits; unlocking succeeds and re-derives the key (no full sign-out).
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

9. Phone migration: linked number pre-fills as local โ€‹

  • Goal: an account migrating with an already-linked phone sees it correctly.
  • Preconditions: an account whose user.phoneNumber is already set (E.164).
  • Steps:
    1. Reach the phone-migration flow.
  • Expected: the phone field shows the +1 affix with the linked number rendered as local digits (619 555 0001), editable, not a raw +16195550001. Submit works and normalizes to the correct +1 number.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

10. Forgot-passphrase recovery: +1 input โ€‹

  • Goal: the recovery phone input matches the new behavior.
  • Steps:
    1. Open "Forgot your PIN / passphrase" recovery.
    2. Enter a partial number, then a full 10-digit one.
  • Expected: +1 affix visible; "Recover Account" is disabled until 10 digits; recovery proceeds against the normalized +1 number.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

11. Merchant signup: normalized +1 shipped โ€‹

  • Goal: the merchant application form uses the +1 input and sends a clean number.
  • Steps:
    1. Open #/merchant/signup, fill the form, enter a local number in Phone Number.
    2. Submit and watch the network tab for the application POST.
  • Expected: +1 affix on the white/5 field; an incomplete number triggers the "fill in all required fields" error (no submit); the submitted application body carries phoneNumber: "+1XXXXXXXXXX", not the raw typed string.
  • Verify (data/network): the merchant-application request body's phoneNumber is E.164.
Result:    [ ] pass   [ ] fail   [ ] blocked
Actual:
Evidence:
Follow-up:

Notes โ€‹

Built with VitePress