Physical-device dogfood gate
The pre-store gate from APP_STORE_DEPLOYMENT.md, turned into something you can actually work through on a phone and paste into issue #1684 with the boxes ticked.
Run it on a physical device, on a dev or preview build, against
production — eas.json's preview/production profiles already carry the
prod Supabase URL and EXPO_PUBLIC_API_URL. A simulator will not surface the
things this gate exists to catch: real network flakiness, safe-area chrome,
permission dialogs, keyboard behaviour, deep links.
Record device + OS + build profile at the top when you post results:
Device: iPhone 15 Pro / iOS 26.x Build: preview (EAS) Backend: prod
Status of this gate
Not run. It needs a physical device; nothing in this task could execute it. Everything below is the checklist, plus — under "Known suspects" — a list of things static analysis says will fail, so whoever runs it knows where to look first.
1. Auth
- Sign up with a new email; the confirmation path works end to end
- Sign in with an existing account
- Turnstile challenge (if a site key is in the build) renders, completes, and doesn't block the button forever
- Wrong password shows a readable error, not a raw Supabase string
- Session survives a force-quit and relaunch
- Stale/revoked token signs you out cleanly rather than hanging (#1655)
- Sign out returns to the auth stack with no flash of authed UI
2. Entries — the core loop
- Create an entry: title, body, mood, 2+ tags, assigned notebook
- Create an entry without a mood — saves, and the server-inferred mood appears
- Read it back in the detail view; formatting survives
- Edit it — every field persists after a reload
- Delete it, with a confirmation step
- Long entry (2,000+ words) scrolls and saves without lag
- Entry with emoji, non-Latin characters and line breaks round-trips intact
- Pull-to-refresh on the list picks up an entry created on the web
3. Notebooks
- Create, rename and delete a notebook
- Entry counts are right after adding and removing entries
- Notebook detail lists the correct entries
- Creating an entry from inside a notebook pins it there
4. Goals
- Create a goal, edit it, delete it
- Record a check-in; progress updates
- Goal detail matches what the web dashboard shows for the same goal
5. Sleep
- Log a night; it appears on the Stream
- Editing the same date updates rather than duplicating (one entry per night)
- Hours validation rejects nonsense (negative, 30+)
6. Search
- Search matches on title and body
- Clearing the query restores the full list
- No-match state reads "No entries found", not a blank screen
- Search behaves with 100+ entries loaded (it filters the loaded list client-side — note if that's visibly wrong on a large journal)
7. Share links
- Share an entry from the app; the link opens on the web
- Tapping a
hivejournal.com/share/...link from another app opens it in HiveJournal, not Safari/Chrome (iOSapplinks:, Android intent filter) - Cold-start deep link works (app fully quit, then tap the link)
- A revoked/expired share link fails gracefully
8. Stream, Analysis, and the rest of the tabs
- Stream mixes entries, sleep and drops in the right order
- Journal Analysis loads and renders a readable reflection
- JQ chat (floating button) opens, sends, and receives
- DreamPro list and detail load
- JQ Bridge invitations and connections load
- Drops received list loads and mark-as-read sticks
- Graphene tab plays audio; playback pill docks above the tab bar and survives navigation (#1644)
9. Settings and account
- Edit profile — display name and profile photo (exercises the photo-library permission prompt)
- Email preferences save
- Export Data — see "Known suspects"
- Delete Account is reachable, warns clearly, and actually deletes (use a throwaway account — this is irreversible)
10. Chrome, layout and theme
- Safe-area insets correct on a notched/Dynamic Island device — nothing under the status bar or home indicator (#1644)
- Landscape is either locked out or doesn't break layout (
orientation: portraitinapp.json) - Keyboard doesn't cover the entry editor's save control
- Floating overlays (satisfaction slider, action button, JQ) don't sit on top of the tab bar
- Dark mode — device set to dark: the app is light-only and consistent, native modals/keyboard/alerts included (see "Known suspects")
- Text still fits at the largest OS text size
11. Network and error states
- Airplane mode: every screen shows an error, not a spinner forever
- Slow 3G (Network Link Conditioner / emulator throttle): no duplicate submits, no torn UI
- Losing connectivity mid-save surfaces a real failure message
- Recovering connectivity refetches without a restart
- Backgrounding for 10+ minutes and returning refreshes rather than showing stale data
12. Empty states
Sign in as a brand-new account and check each reads intentionally, not broken:
- Entries list · [ ] Notebooks · [ ] Goals · [ ] Sleep · [ ] Stream
- Analysis · [ ] Dreams · [ ] Drops · [ ] JQ Bridge
13. Performance
- Cold start under ~3s to first usable screen
- Scrolling a 100+ entry list stays smooth
- No visible jank switching tabs
- Memory stays flat over ~10 minutes of use
Known suspects — check these first
Found by static analysis while preparing the store artifacts. Each is expected to fail; confirm on device and file (or link) an issue for whatever reproduces.
| # | Where | What's expected to happen | Evidence |
|---|---|---|---|
| 1 | Settings → Export Data | ✅ Fixed in #1706 — it used to throw before writing anything (expo-file-system@19 moved documentDirectory / writeAsStringAsync / EncodingType off the default export). Ported to the File/Paths API. Still needs the device check: the share sheet on iOS and Android, and the clipboard fallback when Sharing.isAvailableAsync() is false — neither was verifiable without hardware. | SettingsScreen.tsx |
| 2 | Dark mode | ✅ Fixed in #1707 — there still isn't any dark mode (no screen reads the color scheme; 71 hardcoded backgroundColor: '#fff'), but app.json no longer claims one: userInterfaceStyle went "automatic" → "light", and expo-system-ui was added so that declaration is honored on Android too (without it the setting is a no-op there and only warns at prebuild). Still needs the device check on a build cut after this change: on a phone set to dark, native modals, the keyboard, alerts and action sheets should all come up light. | app.json, App.tsx:24 |
| 3 | DreamPro → dream detail → AI breakdown | Likely a crash or a dead button: the screen imports generateAIBreakdown, saveAIBreakdown and AIDreamBreakdown from services/api, none of which are exported. | DreamDetailScreen.tsx:23-27 |
| 4 | Action panel step prompts | Same shape — imports getStepPrompts, which services/api doesn't export. | ActionPanel.tsx:13 |
| 5 | Permission prompts (location, camera, photos) | ✅ Fixed in #1709 — no longer a suspect, but still worth eyeballing on device: each prompt should read as purpose-specific with the right app name, the app should never ask for "Always" location, and no microphone prompt should appear anywhere. | app.json plugins |
| 6 | Duplicate style keys | StyleSheet.create objects with the same key twice — the second silently wins, so some styling never applies. Cosmetic, but visible. | DreamDetailScreen.tsx:816, WorkoutWindowCheckInScreen.tsx:517 |
| 7 | Reporting / blocking | There is no way to report or block anyone, anywhere in the product, even though Drops and JQ Bridge deliver other people's content. Both stores require one. | product-wide |
How to post results
Paste the whole checklist into issue #1684 with boxes ticked, the device line at the top, and for each failure either a new issue link or a one-line "expected, tracked in #N". A gate with unexplained blanks isn't a gate.