Repository navigation
Add the Expo app shell with sign-in and the five tabs - #123
Conversation
apps/mobile is @debatelab/mobile: Expo SDK 57, React Native 0.86, React 19 and Expo Router, as a second npm workspace next to packages/core. It has the DebateLab identity (com.adam.debatelab, scheme debatelab), the colours from the mocks and the website's dark palette, Inter, and the five tabs from the mocks with native tabs on iOS and a drawn tab bar for the web preview. Prepare, Practice, Library and You show a placeholder until their PRs land. Strings come from the website's catalogs; app-only strings are under mobile.* in all three languages. The lockfile adds the app's tree. No existing package changes version: one entry (sax) moves to the root and 32 lose their dev-only flag. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The app has its own Supabase client on the shared projects.ts. It defaults to staging and only reaches production when a build sets EXPO_PUBLIC_SUPABASE_ENV=production. The session is stored with Supabase's LargeSecureStore pattern: AES-256-CTR ciphertext in AsyncStorage, a fresh key per write in the Keychain. Refresh pauses while the app is in the background. Sign-in follows the website's AuthProvider contract: signInWithOtp then a six-digit verifyOtp, or demo-code-login's magic-link token hash with a per-install id. Google sign-in waits for the first development build, because Expo Go cannot receive OAuth redirects. A cold start without a network keeps the user signed in. supabase-js keeps the stored session when the refresh fails offline but reports INITIAL_SESSION as null after about 30 seconds of retries, so the app starts from the stored session and refreshes once the network is back. scripts/screenshots.ts serves the web export and captures sign-in and the signed-in tabs at iPhone and iPad sizes, light and dark, English and Hungarian. With a staging demo code it also checks the offline cold start. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…install eslint gives apps/mobile its own block: no browser globals, no react-refresh, react-hooks on, require() allowed for PNGs only, and window, document, localStorage, sessionStorage and location rejected outside .web.ts(x) files. Generated Expo folders are ignored. The root typecheck runs the app's own tsc (TypeScript 6, React 19 types), and knip knows the app's Expo Router entry points. knip ignores expo-updates, which its Expo plugin expects until app.json disables updates; EAS Update is not set up yet. Vercel now installs only the root package and @debatelab/core, so the website deploy does not install React Native. Checked in a clean clone with npm 10.9.9 and 12.0.2: 853 packages, no react-native, root React 18.3.1, and npm run build passes. No allowScripts entry is needed: no package the app adds has an install script. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The app's AGENTS.md and README replace the Expo template text with the DebateLab rules: Expo Go modules only, staging by default, strings and project keys shared with the website. The root AGENTS.md, README and architecture guide describe the apps/mobile boundary. The development guide covers the screenshot harness and loading the app in Expo Go on an iPhone from the VPS: Metro over the tailnet via tailnet-preview run with REACT_NATIVE_PACKAGER_HOSTNAME, a manifest check, and two fallbacks if App Transport Security rejects plain HTTP. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. Comment |
getRandomBytes falls back to Math.random in development builds without nativeCallSyncHook, which includes Expo Go's bridgeless runtime, so the session's AES key was not cryptographically random there. getRandomBytesAsync always calls the native generator. Overlapping setItem calls could leave the key from one write and the ciphertext from the other; decrypting that pair returns garbage rather than the session. Reads, writes and removes now run one at a time per key, and a failed operation does not block the ones queued after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
supabase-js loads the session before it signs out. Offline with an expired access token the refresh fails and it returns that error before removing the stored session, so the app threw and stayed signed in. Sign-out now tries the global scope, falls back to the local scope, then removes the stored session and clears the React session whatever the server said, as the website does. A server failure is only logged. The auth-change counter moves to a ref so sign-out can invalidate a stored session read that is still in flight. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The screenshot script asserted the tabs right after they rendered from storage and reloaded before supabase-js's first auth event, which arrives after about 30 seconds of offline retries. It passed even without the INITIAL_SESSION fallback it was meant to cover. The auth context now reports when that first event has been handled, the root view carries testID auth-settled once it has, and the script waits for it before checking that the tabs are still shown and before reloading. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The sign-in screen showed the server's English error text under a Spanish or Hungarian interface. Known failures now map to translated keys: each demo-code-login error, Supabase Auth's expired code and rate limit codes, and network failures. Anything else shows the existing generic message for that step and logs the raw text. The screen keeps the key in state and translates it at render, so the message follows the language. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
AES-CTR with the wrong key returns garbage rather than throwing, so the catch only fires when that garbage is not valid UTF-8. Either way supabase-js cannot parse it and treats the user as signed out. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Overlapping writes could leave the Keychain key from one write and the ciphertext from another, which reads as signed out and is invisible when using the app. The test holds a ciphertext write open to force that interleaving, checks that a read waits for a pending write, and checks that a failed write does not block the next. Vitest and knip now pick up tests under apps/mobile/src. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
signOut no longer throws, so the error state and its message could never show. The settings.signOutFailed key stays for the website. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Review record, round 1 at
Also on the new head: Accepted as is: a token refresh already in flight when the user signs out could, in theory, write the session back. That race is in supabase-js itself and the website shares it. Offline, sign-out waits behind supabase-js's first-load retries (about 30 seconds) before it completes. |
|
Review record, round 2 at Both Vercel projects built |






First piece of the native iOS app from plans/2026-10-08-native-ios-app.md: the Expo app workspace at
apps/mobile, the five-tab shell, sign-in, and the screenshot harness that stands in for a simulator on the VPS. It targets the long-livedfeat/native-iosbranch, which reachesmainonly when Adam approves a milestone.What the app does so far:
AuthProvider. Google sign-in waits for the first development build, because Expo Go cannot receive an OAuth redirect, and Sign in with Apple comes after that.EXPO_PUBLIC_SUPABASE_ENV=productionand never a fallback. The project URLs and anon keys have one copy,src/lib/supabase/projects.ts.mobile.*in the three catalogs.Every native module is in the App Store build of Expo Go, so Adam can run this milestone on his iPhone from the VPS dev server over the tailnet with no EAS build. docs/development.md has the runbook; the Metro manifest was checked to hand out the tailnet hostname.
Repo changes outside the app:
apps/*joins the npm workspaces. The lockfile diff is additions plus dev-flag changes; no pre-existing package changed version. React 18 stays on the web and React 19 in the app, and the app's web export bundles only 19.vercel.jsongets aninstallCommandthat installs the root andpackages/coreonly, so website deploys skip the React Native tree. Proven in a clean clone with npm 10.9.9 (Vercel's Node 22) and 12.0.2: 853 packages, no expo or react-native, build passes.window,documentand web storage outside*.web.ts(x)files andscripts/.npm run typecheckruns the app's tsc; knip runs the app as a workspace.Checks, run locally on Node 22.23.2 because GitHub Actions starts no jobs on this account (billing):
npm run verifyexit 0 at the final head: 273 Vitest tests, 30 collaboration tests, 333 Deno Edge Function tests, the build and boundary check, the service-worker test, 30 Chromium smoke e2e tests.npm ciwith scripts enabled, then lint, typecheck, build and the app's web export, all pass at each of the four commits; the Vercel install command variant builds the website..verify/mobile-shots/: sign-in, its error and demo states, and the signed-in tabs, at iPhone and iPad sizes, light and dark, English and Hungarian, plus the offline cold start. Signed-in shots used a staging demo code; the address on You is masked. A selection is attached below.Not proven here: anything on a real iPhone (native tabs, large titles, the encrypted store, App Transport Security against a plain-HTTP tailnet name), email-code sign-in end to end, and the sign-out button. Those are the milestone checks on Adam's phone.
🤖 Generated with Claude Code