Skip to content

Launching the app

There are several ways to run this app, and one command that reaches all of them:

pnpm launch

That opens a menu. Every entry is documented below, and every entry is also a direct argument, so pnpm launch wifi skips the menu. The script lives at scripts/launch.sh and runs the same commands this page describes, with the preflight checks already built in.

Which mode do I want?

I want to... Mode Command
See a change now, no phone involved iOS Simulator pnpm launch sim
Check labels, traits, and hit targets Simulator with Inspector pnpm launch a11y
Test on real hardware, real VoiceOver Wi-Fi to an iPhone pnpm launch wifi
Put a build in a tester's hands TestFlight pnpm launch testflight
Reinstall after a native change USB install pnpm launch device
Check Android Android emulator pnpm launch android
Reconnect a dev client that is already on Metro only pnpm launch metro
Know whether a build will pass before it runs Preflight pnpm launch check

This app does not run in Expo Go. It uses native modules (audio, video, reanimated), so it needs a custom dev client. That client gets built into the simulator by pnpm launch sim, and onto a phone by pnpm launch device. Once it is installed, JavaScript changes hot reload and no rebuild is needed.

iOS Simulator

pnpm launch sim      # or: pnpm ios

This builds the dev client into the simulator, installs it, and starts Metro in the same terminal. The first run takes a few minutes because it compiles the native project. Later runs are warm and take seconds.

While Metro runs:

Key What it does
r Reload the app
j Open React Native DevTools (console, network, element inspector)
m Toggle the in-app dev menu
i Open on iOS
a Open on Android

The backend is optional. Without it the app runs and the Settings screen shows "Backend status" as unreachable. To run it alongside, open a second terminal:

pnpm dev:backend     # http://127.0.0.1:8000

The simulator shares the Mac's network, so localhost resolves and no address configuration is needed.

Picking a specific simulator

pnpm ios uses the last booted device. To choose another:

xcrun simctl list devices available          # see what is installed
cd apps/mobile && pnpm exec expo run:ios --device "iPhone SE (3rd generation)"

The iPhone SE is worth keeping around. It is the smallest supported display (375x667) and the reference device for the one-screen rule, so it is where a layout breaks first.

Simulator with Accessibility Inspector

pnpm launch a11y

Same as the simulator mode, and it also opens Xcode's Accessibility Inspector and the Simulator ahead of the build.

In the Inspector, set the target dropdown at the top left to Simulator, then to the running app. Two tools matter:

  • The pointer icon inspects one element at a time and shows its label, value, traits, and frame. This is the fastest way to answer "does this control have a real label?"
  • Audit runs a static pass over the current screen and flags missing labels, small hit targets, and contrast problems.

The Inspector reports what an element exposes. It does not report reading order, focus behavior, or whether an announcement actually fires. Those need the manual VoiceOver pass in Accessibility. Treat this mode as the fast check between releases, not a replacement for it.

Wi-Fi to a physical iPhone

This is the everyday loop for real-device work: install the dev client once over USB, then iterate over Wi-Fi with no cable and no rebuilds.

Once per phone

pnpm launch device      # or: pnpm ios:device

Connect the iPhone by USB, unlock it, and tap Trust. Select the phone when prompted. If the build asks for a signing team, open apps/mobile/ios/SeniorHealthApp.xcworkspace in Xcode, select the SeniorHealthApp target, and set your team under Signing & Capabilities.

Then, in Xcode, open Window > Devices and Simulators, select the iPhone, and check Connect via network. After that even native rebuilds run without the cable.

Every session after that

pnpm launch wifi        # or: pnpm deploy:iphone

This runs the preflight checks below, prints the Mac's LAN IP, then starts two servers together:

  • the FastAPI backend on 0.0.0.0:8000, so the phone can reach it (the default 127.0.0.1 binding is reachable only from the Mac)
  • Metro bound to the LAN with --dev-client, with a QR code

Open the installed Senior Health App on the phone. It reconnects on its own; scan the QR code if it does not. The app derives the backend address from the Metro URL, so there is nothing to configure. Edit a file, save, and the change appears on the phone. Ctrl+C stops both servers.

Why the Wi-Fi path fails, and how to tell

The connection needs the phone to reach two ports on the Mac, 8081 for Metro and 8000 for the backend. Almost every failure is one of these, in rough order of how often it happens. pnpm launch wifi checks the first four before starting anything.

Symptom Cause Fix
App opens, spins, "Could not connect to the server" macOS firewall is blocking incoming connections to node Allow node when prompted, or System Settings > Network > Firewall > Options
Nothing connects, no prompt appears A VPN is up on the Mac. The phone's packets never reach it Disconnect the VPN for LAN work
Metro starts on port 8082 and the phone looks for 8081 Another Metro is still running from an earlier session lsof -nP -iTCP:8081 -sTCP:LISTEN, then kill the old process
Backend refuses to start Port 8000 is taken, usually by a local docs server Stop it, or run docs on 8001 with pnpm docs:serve
App connects to Metro, backend shows unreachable The backend is bound to 127.0.0.1 instead of 0.0.0.0 Use pnpm dev:backend:lan, which pnpm launch wifi does for you
Phone and Mac are both "on Wi-Fi" but cannot see each other A guest SSID, or client isolation on the router Put both on the main network, not the guest one
Same, on a corporate or hotel network Client isolation again, and it is usually not something you can turn off Use USB, or a phone hotspot with both devices joined to it
Worked yesterday, not today DHCP handed the Mac a new LAN IP Restart pnpm launch wifi; it prints the current IP and Metro re-advertises
Mac is on Ethernet, phone on Wi-Fi Fine if both are the same subnet, broken if the router keeps them apart Check the printed IP is on the phone's subnet, or move the Mac to Wi-Fi
App is stale after a native change The dev client is a native binary. New native modules, plugin changes, permissions, icons, and splash all need a rebuild pnpm launch device

When nothing else explains it, open Safari on the phone and go to http://<the printed IP>:8000/health. A JSON response means the network is fine and the problem is the app. No response means the problem is the network, the firewall, or the binding, and the table above applies.

Falling back to USB always works, because it does not use the network at all.

Testers who are not on your Wi-Fi

A LAN address is useless to anyone outside the house. Give the backend a stable public hostname with Backend over Cloudflare Tunnel, and put that hostname in EXPO_PUBLIC_API_URL before building.

TestFlight

pnpm launch testflight

The script runs the checks first, then the two commands, and asks before it submits. What it does, in order:

  1. Confirms eas is installed and signed in.
  2. Warns if EXPO_PUBLIC_API_URL is unset in apps/mobile/.env. Testers are not on your Wi-Fi, so a build without it ships pointing at a laptop nobody can reach, and the only fix is another build.
  3. Warns if EXPO_PUBLIC_DEV_AUTO_LOGIN is still set, which would sign testers in as the seeded dev account.
  4. Runs pnpm expo:doctor. Ten seconds here beats finding dependency drift ten minutes into a cloud build.
  5. Runs the build, from apps/mobile.
  6. Offers to submit.

By hand it is:

cd apps/mobile
eas build --profile production --platform ios   # store-signed, auto-increments the build number
eas submit --platform ios --latest              # uploads to App Store Connect

Processing on Apple's side takes 5 to 20 minutes. eas.json assigns the Tester group on submit, so testers are notified without any App Store Connect clicking. Every build is universal, so the same build installs on iPad.

The profiles in apps/mobile/eas.json:

Profile What it is for
development Dev client, internal distribution, includes an iOS simulator build
preview Production-like build, internal distribution, no store signing
production Store-signed, auto-incrementing build number, what TestFlight uses

Never run EAS from the root

Every eas command runs from apps/mobile. Never from the repo root.

Running one at the root makes Expo scaffold a second, empty app there: a root app.json, eas.json, tsconfig.json, and ios/, pointing at a different EAS project under a personal account. That phantom app has native files and no JavaScript entry point, so the build gets all the way to the end and then fails with "Unknown error. See logs of the Bundle JavaScript build phase". It reads like an app bug. It is not one, and diagnosing it costs a full cloud build.

pnpm launch testflight and pnpm launch check both detect those stray root files and offer to delete them.

To tell a real app bug from this trap, run npx expo export --platform ios in apps/mobile. If it produces a bundle, the app is fine and the build was simply started in the wrong directory.

Android emulator

pnpm launch android      # or: pnpm android

Start an emulator in Android Studio first, or the build has nothing to install to. adb devices should list it. Everything else matches the iOS simulator flow.

Metro only

pnpm launch metro

Starts Metro with --dev-client and nothing else. Use it when the dev client is already installed and only JavaScript changed, which is the common case. It skips the native build entirely, so it starts in seconds.

Preflight checks

pnpm launch check

Runs pnpm expo:doctor and pnpm verify (types, lint, tests, formatting), and cleans up a phantom root scaffold if it finds one. Worth running before any EAS build, since the same failures cost minutes in the cloud instead of seconds here.

Where the commands live

The launcher wraps scripts that all live in the root package.json, so they can be run directly from the repo root, or from anywhere with pnpm -w.

Command What it does
pnpm launch The menu described on this page
pnpm ios Build and run in the iOS simulator
pnpm android Build and run in the Android emulator
pnpm ios:device Build and install on a USB-connected iPhone
pnpm deploy:iphone LAN backend plus LAN Metro, prints the Mac's IP
pnpm dev:backend Backend only, on 127.0.0.1:8000
pnpm dev:backend:lan Backend only, on 0.0.0.0:8000 so a phone can reach it
pnpm expo:doctor SDK 57 dependency and config health

Running one of these from apps/mobile fails with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL: Command "deploy:iphone" not found, because they are root scripts. Use pnpm -w <script> from a subdirectory.