diff --git a/README.md b/README.md index 4a5a9db..17a857c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ link's build current automatically. - [Architecture](docs/ARCHITECTURE.md) — how the pieces fit, selection, reminders, security - [Roadmap](docs/ROADMAP.md) — shipped phases and what's next - [Backend](backend/README.md) — API endpoints, auth, running locally +- [SMTP delivery setup](docs/EMAIL_SETUP.md) — deferred provider setup and delivery checks - [Deployment](mobile/DEPLOYMENT.md) — EAS builds, OTA updates, release runbook ## Contributing diff --git a/backend/README.md b/backend/README.md index 2b97c4c..9634c52 100644 --- a/backend/README.md +++ b/backend/README.md @@ -106,6 +106,12 @@ confusion attacks, both of which are covered by tests. `user_id` is taken from the verified token's `sub` claim and from nowhere else. No endpoint accepts a user id as a parameter. +Supabase Auth sends account emails through its configured provider. See +[SMTP delivery setup](../docs/EMAIL_SETUP.md) for the deferred provider preparation +and real-inbox checks. Branded templates are delivered independently in +[PR #188](https://github.com/Coding-Moves/one-concept/pull/188). Repository changes +do not configure SMTP or publish templates in Supabase. + ## Content generation Gemini writes lessons. It does **not** choose subjects. diff --git a/docs/CODEBASE_MAP.md b/docs/CODEBASE_MAP.md index 7eab610..789eebd 100644 --- a/docs/CODEBASE_MAP.md +++ b/docs/CODEBASE_MAP.md @@ -17,6 +17,7 @@ learned history, streaks, likes, saved concepts, and push reminders. | Content engine | `backend/app/services/generation.py`, `pool.py`, `prefetch.py`; Gemini lessons from a curated backlog. | | Operations | `.github/workflows/`, `backend/railway.json`, `backend/Dockerfile`, `mobile/eas.json`, `mobile/app.config.js`. | | Documentation | Root `README.md`, `RELEASING.md`, `CONTRIBUTING.md`, `docs/ARCHITECTURE.md`, `docs/ROADMAP.md`, and the backend/mobile guides. | +| SMTP delivery setup | `docs/EMAIL_SETUP.md` retains deferred provider configuration and real-inbox acceptance. The branded HTML and its installation guide are separate in [PR #188](https://github.com/Coding-Moves/one-concept/pull/188). | | Agent guidance | Root `AGENTS.md`; `mobile/AGENTS.md` adds Expo documentation requirements and `mobile/CLAUDE.md` references it. | ## Mobile navigation and presentation diff --git a/docs/EMAIL_SETUP.md b/docs/EMAIL_SETUP.md new file mode 100644 index 0000000..d181b8d --- /dev/null +++ b/docs/EMAIL_SETUP.md @@ -0,0 +1,226 @@ +# SMTP delivery setup (deferred) + +This draft prepares the delivery configuration for [#152](https://github.com/Coding-Moves/one-concept/issues/152) +and [#171](https://github.com/Coding-Moves/one-concept/issues/171). +**Status: SMTP activation and real inbox delivery remain unverified.** +Keep [PR #187](https://github.com/Coding-Moves/one-concept/pull/187) in draft. +The owner has a $0 budget and is not purchasing a domain now. + +The branded HTML and installation guide are a separate, non-draft change in +[PR #188](https://github.com/Coding-Moves/one-concept/pull/188). Those templates +work with the project's configured sender and do not depend on this draft or +on buying a domain. This branch retains the provider preparation only. + +The owner previously chose an existing Gmail account for initial Supabase SMTP; +actual authentication and inbox delivery still need verification. Supabase's +built-in sender remains limited to project-team addresses and currently two +emails per hour; customizing the HTML does not remove those restrictions. +[Supabase SMTP documentation](https://supabase.com/docs/guides/auth/auth-smtp). + +The app already asks Supabase Auth to send signup and password-recovery emails. +Supabase owns verification tokens and sends through its configured SMTP provider; +FastAPI serves the existing confirmation/reset pages. No app release installs +SMTP settings or email templates. Daily reminders remain push notifications. + +## Optional Resend setup + +Steps 1–4 below preserve the original Resend preparation. They are not required +for the owner's current Gmail setup. The later redirect, template, and acceptance +steps apply to either provider. + +The owner has set a **$0 budget, with no domain purchase**. Complete the numbered +steps below only using free services. If registration, identity verification, +or DNS requires payment, stop; no charge is authorized. Production activation +remains pending until the owner controls a suitable domain. +You do not need website hosting, a paid mailbox, or a Supabase custom API domain +to send these emails. A mailbox is needed separately only if you want to receive +replies at the sending address. + +[Resend's free sending plan](https://resend.com/pricing) currently allows 3,000 +emails per month, at most 100 per day (checked 2026-09-12). These are emails, not +users: signups, resets, and repeat requests all share the allowance. Stay on Free +and recheck its limits before a public launch. Domain ownership is a prerequisite +for [real-user delivery](https://resend.com/docs/dashboard/domains/introduction); +the testing sender is not a replacement for your own domain. + +### 1. Obtain a free domain with DNS control and create the Resend account + +One candidate is [.pp.ua through NIC.UA](https://nic.ua/en/domains/.pp.ua), which +currently lists free registration and renewal. Its +[DNS hosting is free for domains registered there](https://nic.ua/en/domains/nameservers). +This is a candidate, not a verified deployment: name availability, account +eligibility, activation, Resend acceptance, and inbox delivery still need checks. + +Before choosing it, read these conditions: NIC.UA requires a linked payment card +for identification and phone/SMS or Telegram activation; .pp.ua registrant contact +data is public. The owner must decide whether those terms are acceptable and +complete registration directly. Do not register or publish contact information +on the owner's behalf. See [activation requirements](https://nic.ua/en/knowledge-base/how-to-activate-pp-ua-domain) +and [public registration data](https://nic.ua/en/knowledge-base/pp-ua-domains-restrictions). + +If acceptable, search for an available project name such as `oneconcept.pp.ua` +(availability is not established), verify a zero order total, complete the +registrar's activation steps, and enable its free DNS service. Complete activation +within the registrar's stated window. Free domains still need timely renewal +and reactivation; record the expiry privately. An expired domain may require a +paid restoration, which is outside this budget. + +Then create your account at [Resend](https://resend.com/signup) and select Free. +If the free domain cannot be activated or accepted by Resend, keep this setup +pending instead of buying an alternative or treating `onboarding@resend.dev` as +production. That [test sender only reaches your own Resend account address](https://resend.com/docs/knowledge-base/403-error-resend-dev-domain). + +The examples below use `example.com`. Replace it with your activated domain. +We will use **`auth.example.com`** as the sending domain and +**`noreply@auth.example.com`** as the sender. The `auth` subdomain does not require +a separate registration. It can coexist with a future website/API domain. + +### 2. Verify the sending domain + +In **Resend → Domains → Add domain**, enter `auth.example.com`. +In your registrar's DNS panel, copy the exact record type, name, value, and +priority that Resend provides for sending verification (DKIM and SPF/return-path +records). Values depend on the domain and region; do not copy somebody else's +DNS values. Some DNS panels append your root domain automatically, so check the +resulting full record name before saving. + +Enable sending; receiving is not required. An MX record requested for the sending +return-path belongs at the exact name Resend specifies, not automatically at the +root of your domain. Wait until Resend marks sending **Verified**. Follow +[Resend's DMARC guide](https://resend.com/docs/dashboard/domains/dmarc) for the +sending domain as well; if a record already exists, edit it instead of adding +a second DMARC policy. An initial monitoring policy can be tightened after +verifying legitimate mail; use a report address only if you actually monitor it. + +In that Resend domain's settings, leave **click tracking and open tracking off**. +Supabase verification links must reach Supabase unchanged. See +[Supabase's email tracking guidance](https://supabase.com/docs/guides/auth/auth-email-templates#email-tracking). + +### 3. Create a dedicated sending key + +In **Resend → API Keys**, create a key named `one-concept-supabase-auth` with +**Sending access**, limited to the verified sending domain. Copy it to your +password manager and use it only as the SMTP password in the next step. Resend +documents [sending-only, domain-scoped keys](https://resend.com/docs/dashboard/api-keys/introduction). + +Do not put this key in a template, Git, a PR, the mobile app, or a chat message. +No new Railway or `EXPO_PUBLIC_*` environment variable is needed. + +### 4. Configure Supabase custom SMTP + +First open the **existing One Concept Supabase project**. Inspect whether custom +SMTP or a Send Email Auth Hook is already configured; record the non-secret +settings and save the current templates privately before changing anything. +An existing Send Email hook can handle delivery instead of SMTP; resolve which +delivery path is active before switching it. + +Go to **Authentication → Email → SMTP Settings** (some dashboard versions group +this under Notifications). Enable custom SMTP and enter: + +| Field | Value | +| --- | --- | +| Sender name | `One Concept — by Coding Moves` | +| Sender email | `noreply@auth.example.com` with your actual verified domain | +| SMTP host | `smtp.resend.com` | +| SMTP port | `465` (TLS) | +| SMTP username | `resend` | +| SMTP password | The dedicated Resend API key from step 3 | + +Save. These values follow [Resend's Supabase SMTP guide](https://resend.com/docs/send-with-supabase-smtp). +Settings take effect in Supabase immediately; no APK or backend deployment is +required. Keep email confirmation enabled. + +Under **Authentication → Rate Limits**, inspect the email-sending limit. +Supabase documents an initial custom-SMTP limit of **30 emails/hour**, which is +configurable. Keep that conservative starting value for initial testing; raising +it does not raise Resend's 100/day and 3,000/month limits. Custom SMTP removes +the built-in sender's restrictions, not all limits. +[Supabase SMTP limits](https://supabase.com/docs/guides/auth/auth-smtp). + +### 5. Preserve the app's existing redirect URLs + +Find the app's current `EXPO_PUBLIC_API_BASE_URL` in the EAS environment settings +or the existing deployment configuration. Call that HTTPS origin `API_BASE` +below, with no trailing slash. This is the current backend address, **not** the +new email-sending domain; setting up email does not move the backend. + +In **Supabase → Authentication → URL Configuration**, verify: + +| Setting | Expected value | +| --- | --- | +| Site URL | `API_BASE/confirmed` | +| Redirect URL allow list | Contains `API_BASE/confirmed` and `API_BASE/reset-password` | + +Preserve other legitimate existing redirects. The signup call currently relies +on Site URL; `resetPasswordForEmail` explicitly requests `/reset-password`. +Keep the project's existing Supabase URL and anon/publishable key unchanged. +Do not replace the email action link with either landing-page URL: the user must +first pass through Supabase's token verification endpoint. + +### 6. Install the separately reviewed templates + +The three branded HTML files and their provider-independent installation guide +are delivered in [PR #188](https://github.com/Coding-Moves/one-concept/pull/188). +Use that PR's `docs/EMAIL_TEMPLATES.md` for subjects, raw source, confirmation +placeholders, and the password-changed notification toggle. This delivery draft +contains no template files. The template PR can merge before provider setup; +merging either PR does not install templates in Supabase. + +### 7. Verify real delivery before completing either issue + +Use addresses you control, including one outside your Supabase organization's +team. Never publish passwords, full verification URLs, tokens, SMTP keys, or +private recipient details as evidence. Record the time, provider, redacted +delivery status, and outcome in the PR's activation checklist. + +- [ ] **Signup:** create a fresh test account in the installed app. Receive the + branded confirmation email, inspect the sender and layout, use its button, and + successfully sign in to the app. A `/confirmed` page alone is not proof that + the account can sign in. +- [ ] **Password recovery:** for that account, use Forgot password in the app, + receive the branded email, open `/reset-password` via its button, set a new + password, then successfully sign in with the new password. +- [ ] **Password changed:** after enabling the security notification, complete a + reset on a test account you control and verify the separate notification + arrives. Check its organization/support destinations without sending a message. +- [ ] **Used/expired recovery link:** confirm it does not permit another reset; + request a fresh link and check that recovery still works. +- [ ] **Second inbox provider:** repeat delivery checks using another inbox + provider you control (for example Gmail and Outlook). Check Spam/Junk too. +- [ ] **Authentication headers:** inspect a delivered email's headers for SPF, + DKIM, and DMARC results; resolve failures before calling the setup complete. +- [ ] **Fallback link:** use a fresh email and copy its fallback URL into the + browser; verify that it completes the same action as the button. +- [ ] **Provider evidence:** Supabase Auth logs show no SMTP failure and both + messages arrive in the recipient inboxes. If using Resend, check its delivery + logs too. Provider acceptance alone does not prove inbox delivery or working links. + +When these pass, record the actual results, mark this delivery PR ready, and merge it +to complete the delivery work for #152 and #171. Do not close either as completed while +required provider verification or real delivery is still pending. Record Gmail's +limited-volume tradeoff explicitly; do not describe it as a verified production +transactional service merely because the HTML has been installed. + +## Operating and troubleshooting + +Use **Supabase → Logs → Auth** for request and SMTP errors. With Gmail, check +the recipient inbox/spam folder and any delivery failures in the sender mailbox. +For Resend, use **Resend → Emails/Logs** for delivery, bounce, suppression, and +quota details. FastAPI does not send these messages and cannot establish delivery +from its own logs. Check the selected provider's allowance before a signup +campaign, and review failures after activation. + +| Symptom | Check | +| --- | --- | +| Only team members receive mail | Correct Supabase project, custom SMTP saved, Send Email hook status; domain verification if using Resend. | +| SMTP authentication/sender error | Gmail: matching sender/username and a valid app password. Resend: dedicated key and domain scope. Check host and port for the chosen provider. | +| Rate-limit error or missing messages | Both Supabase's hourly limit and the selected provider's allowance; use a new request after limits permit. | +| Email arrives, link fails | Tracking disabled, correct template slot, intact placeholder, existing backend redirects; request a fresh link. Some inbox scanners consume one-time links. | +| Reset page says it is unavailable | Existing backend `SUPABASE_ANON_KEY` and deployment configuration; see [RELEASING.md](../RELEASING.md). | +| Password reset gives no account-specific detail | Expected: the app deliberately avoids disclosing whether an account exists. | + +If an installation breaks delivery, restore the saved template/settings for any +previously working custom provider. Reverting a Git commit does not restore +Supabase configuration. The built-in sender is restricted and is not a production +fallback. For a compromised/rotated Resend key, create a replacement scoped key, +update the SMTP password, test delivery, then revoke the old key. diff --git a/docs/WORK_LOG.md b/docs/WORK_LOG.md index f0fbf33..9761d4c 100644 --- a/docs/WORK_LOG.md +++ b/docs/WORK_LOG.md @@ -7,14 +7,17 @@ claims as completed work. ## Current status -- Implemented and validated [#150](https://github.com/Coding-Moves/one-concept/issues/150), - the next open issue in ascending order, on `codex/150-bounded-startup-state` - from refreshed `origin/develop` (`a9aab63`). Published as - [PR #185](https://github.com/Coding-Moves/one-concept/pull/185) into `develop`. -- Compact startup includes 50 learned/saved detail records, exact totals, and - cursor access to older records. Saved search/filter and offline reading remain - available; full History UI (#159) is separate. Existing clients retain their - legacy response until updated. No new APK, migration, or production release. +- The owner split email branding from deferred provider setup. The three HTML + files and their independent installation guide are in ready-for-review + [PR #188](https://github.com/Coding-Moves/one-concept/pull/188), targeting `develop`. +- [PR #187](https://github.com/Coding-Moves/one-concept/pull/187) stays in draft for + SMTP delivery preparation. The owner is not purchasing a domain. Gmail setup + and real delivery remain unverified; no production configuration was changed. +- Removed duplicate template files from this draft with a follow-up commit and + redirected its installation references to #188. Original commits remain intact; + no history was rewritten. The new PR preserves nine focused template commits. +- PRs #185 (#150) and [#186](https://github.com/Coding-Moves/one-concept/pull/186) + are merged into `develop`; their implementation is separate from this draft. - The owner reinforced the preference for more focused commits in **all future PRs** for this project. Saved in `AGENTS.md`; retain individual commits and related tests without artificial splits or empty commits. @@ -22,6 +25,152 @@ claims as completed work. card follow-up #180, and branch sync #181 are also merged. Their handoffs below record the status at the time; this task does not authorize a production release. +## Resend authentication email (#152 and #171) — 2026-09-12 + +### Separate template PR — scope supersedes the earlier combined handoffs + +- Owner requested a non-draft PR for reusable templates independent of Resend + or domain purchase. Published #188 from fresh `develop` (`a6e81f1`) and narrowed + this draft to provider documentation. Template installation remains manual in + Supabase and is independent of app release. Delivery issues remain open. +- The final HTML in #188 matches the previously validated source byte for byte + (60 Chromium scenarios). This cleanup changes documentation and removes the + duplicated sources; local Markdown paths and whitespace checks passed. No + backend/mobile or live-email tests were run for the split. +- Follow-up commit: `docs: keep deferred email delivery separate from templates`. + Before this draft is ready, complete provider configuration and real-inbox + acceptance. No payments, SMTP changes, messages, or release were performed. + +### Password-changed notification follow-up + +- Owner requested a matching replacement for Supabase's Password changed email. + Add one standalone security notification in the same draft PR, followed by + installation notes and validation. Preserve the two existing action templates. +- Rechecked Supabase's password-changed notification semantics and the support + email in `mobile/src/screens/AboutScreen.tsx`. Use the existing text masthead + and GitHub organization link; point support to the app's published email. + Do not include a fabricated reset token/link or change any Supabase setting. +- Delivered `backend/email-templates/password-changed.html`: dark masthead, + completed-change confirmation, and a separate section directing unrecognized + changes to Forgot password and the app's support email. Added installation, + notification-enablement, and real-inbox acceptance instructions to the runbook. +- **Validation:** all 12 local Chromium scenarios passed at 320/390/600/960px + with normal, doubled, and stripped styles. Checked exact organization/support + links, support-address agreement with the app, no token variables or external + resources, no styled horizontal overflow, and readable line heights. Inspected + mobile, desktop, and doubled-text previews. Backend/mobile tests were not run + for this standalone HTML/documentation change; live notification delivery and + actual email-client rendering still require verification. +- Commit `95b9db6` adds the notification; `docs: explain password changed email + installation and validation` records its runbook, map, and handoff. Local + Markdown file/anchor checks and `git diff --check` passed. +- **Handoff:** same draft PR #187. Copy the new raw HTML and its subject into + Supabase's Security → Password changed template and enable that notification. + No messages sent or production settings changed. Preserve existing commits. + +### Template redesign follow-up + +- Owner requested professional replacement HTML for signup and password recovery, + with less generic copy and the official logo if available. Inspected app config, + theme, authentication flow, and all six image assets' filenames; viewed the + configured icon and Android foreground. No separate One Concept logo was found. + Owner selected polished text branding after the starter-icon finding. +- Planned focused commits: record revised scope; redesign signup and verify its + layout; apply the same design to recovery and verify its copy/links; update the + installation notes and record final visual checks. Preserve prior commits. +- Rechecked Supabase template variables and Gmail CSS guidance. Keep account + actions as HTML text/links, inline styles, table layout, scalable typography, + and a full copyable verification URL. No image generation or remote logo needed. +- Delivery remains manual: the owner will paste the replacement HTML into the + matching Supabase templates. Do not enable providers, send emails, or mark the + draft ready without verified delivery. Browser checks are not inbox tests. +- Delivered a dark One Concept masthead, shorter account-specific messages, + full-width buttons, and a quieter fallback area. Signup HTML is 3,176 bytes + (18.2% smaller); recovery is 3,309 bytes (17.3% smaller). Each is 41 lines. + Both retain all three `{{ .ConfirmationURL }}` occurrences and need no images. +- Owner also requested a clickable Coding Moves attribution. Verified + `https://github.com/Coding-Moves` through GitHub's organization API and linked + the masthead text once in each email. The authentication destinations are unchanged. +- **Validation:** 48 final Chromium scenarios passed: two templates, four widths + (320/390/600/960), normal/doubled/stripped text styles, and normal/long dummy + verification URLs. Checked exact link destinations, copyable full URLs, no + network resources, styled overflow, readable line heights, and 44px+ actions. + Inspected mobile, desktop, and doubled-text renders. Recovery's heading keeps + "password" together normally and permits a soft hyphen at enlarged sizes. All authored + text/button color pairs exceed 6:1 contrast. Local Markdown file links and + whitespace checks passed. Actual Gmail/Outlook rendering and delivery remain + unverified; backend/mobile tests were not run for standalone HTML/doc changes. +- Preview artifacts and the local browser harness are under + `/tmp/one-concept-email-redesign/`, outside Git. They use dummy links only; + the owner must copy the repository HTML, not a preview with example URLs. +- Commits: `4c75b72` records scope; `ac02745` redesigns signup; `fded7b1` redesigns + recovery; `e09ad7a` improves heading wrapping. The organization link is recorded + by `feat: link email branding to Coding Moves GitHub organization`. + Installation/map changes and this handoff are recorded by + `docs: update replacement email instructions and visual validation`. +- **Handoff:** update the same draft PR #187; replace the two HTML bodies using + the unchanged subjects in `docs/EMAIL_SETUP.md`. Gmail setup and both inbox + acceptance flows still require verification. No Supabase settings were changed. + +### Original preparation + +- Verified the mobile app uses Supabase `signUp` and `resetPasswordForEmail`; + FastAPI supplies the existing `/confirmed` and `/reset-password` landing pages. + There is no committed email template/setup runbook. Live SMTP configuration + and actual delivery are unverified; source inspection cannot establish them. +- Owner selected Resend and requested one PR covering both overlapping issues. + Prepare signup and recovery templates using the existing confirmation URLs, + with a Resend/Supabase/DNS guide and a concrete activation checklist. Magic-link + sign-in is not currently offered in the app and is outside this chunk. +- Read the official Resend SMTP/domain and Supabase SMTP/template documentation. + A verified owned domain is required for real-user Resend delivery; the free + sending plan does not provide one. Owner will perform account/registration + steps. No credentials belong in source or the PR. +- Scope clarification: owner requires a $0 setup and will not purchase a domain. + NIC.UA documents free .pp.ua registration/renewal and DNS, but card verification, + phone activation, and public registrant data apply. Presented these conditions + to the owner; no registration, payment, or disclosure of contact data performed. + Resend acceptance of a future free domain and inbox delivery remain unverified. +- Planned commits: scope/verification; signup email; password recovery email; + installation and operational guide; validation and PR handoff. Preserve small + meaningful commits, owner authorship, and the existing authentication flow. +- The PR will track both issues, with live activation explicitly pending. + Keep it in draft until both delivery flows have passed real-inbox checks; + repository changes alone do not establish that either issue is resolved. +- Delivered standalone signup and recovery HTML with One Concept / Coding Moves + branding, the existing Supabase confirmation variable in both action and + fallback links, system fonts, inline styles, and no remote assets. Refined the + copy to focus on account actions, following Supabase's transactional guidance. +- Added `docs/EMAIL_SETUP.md`: the $0 constraint, a conditional free-domain route, + DNS and scoped-key steps, exact SMTP fields, existing redirect URLs, template + installation, separate Supabase/Resend limits, and a live acceptance checklist. +- **Validation:** 24 local Chromium scenarios passed across both templates at + 320/390/600/960 pixels, with normal, doubled, and stripped styling. Verified + complete dummy verification URLs, copyable fallback links, no remote resources, + and no horizontal overflow with normal/enlarged text. Visual review caught + overlapping enlarged text; switched to relative line spacing and rechecked. + Inspected mobile, desktop, and enlarged-text screenshots. Text/button contrast + exceeds 4.5:1; relative Markdown links and whitespace checks passed. +- **Limits:** no messages sent, accounts created, domain registered, payment made, + DNS edited, SMTP activated, or production template installed. Resend acceptance, + real inbox delivery, and native email-client rendering remain unverified. The + first preview launch lacked Playwright's bundled browser; the installed Chrome + completed validation. Backend pytest/mobile builds were not run because this + chunk changes only standalone templates and documentation, not application code. +- Commits so far: `cf7cf8d` scope; `a17ede9` signup; `ed28df7` recovery; + `a44d624` account-focused copy; `2dd12c7` setup guide; `de1d290` scalable text. + `fd448f9` records validation and activation limits. +- **PR:** [#187](https://github.com/Coding-Moves/one-concept/pull/187), draft into + `develop`, with closing references for both #152 and #171. Both issues remain + open until the live activation checklist passes and the PR merges. Publication + bookkeeping: `docs: link combined authentication email pull request`. +- **Original owner handoff (superseded by Gmail choice above):** create the free + Resend account and decide whether NIC.UA's + card-verification/public-contact terms are acceptable before registering any + free domain. The question remains pending; no consent was inferred. Complete + the exact DNS/SMTP/template steps in `docs/EMAIL_SETUP.md` once a suitable domain + is active. No purchase or provider switch is authorized by the $0 scope. + ## Bounded startup state (#150) — 2026-09-12 - Read the exact Expo SDK 57 documentation before mobile edits.