Quickstart
A wall-clock 30-minute path from npm create mobile-surfaces to a working Live Activity rendered on a physical device. Each step calls out which trap rule (MSxxx) it addresses; if a step fails, follow the link to the catalog entry.
Prerequisites
- macOS with Xcode 26.2 installed (Xcode major must be ≥ 26 per MS010).
- An iOS 17.2+ device. The simulator can render Live Activities but cannot receive APNs pushes; the full loop requires a real phone.
- Apple Developer account (free or paid; paid required only for TestFlight).
- Node 24, pnpm 10. See
compatibility.mdfor the pinned row.
1. Scaffold (2 min)
npm create mobile-surfaces@latest my-app
cd my-app
The CLI walks through name, bundle id, and an Apple Team ID placeholder. Install completes when the harness compiles.
2. Simulator smoke (3 min)
pnpm mobile:sim
When the app loads, tap Start in the harness, then Cmd-L to lock the simulator. A Lock-Screen Live Activity appears. Simulators render the Lock-Screen view but Dynamic Island shows up only on Pro models; Live Activity remote pushes are device-only.
If the activity does not appear, see troubleshooting.
3. Mint an APNs auth key (5 min)
Live Activity remote start and update need an APNs JWT. The key is one .p8 file from the Apple Developer portal.
-
Sign in to developer.apple.com/account → Certificates, Identifiers & Profiles → Keys.
-
Click +. Name the key (e.g.
Mobile Surfaces dev). -
Tick Apple Push Notifications service (APNs). If you need iOS 18 broadcast support, also tick Broadcast Notifications (MS034).
-
Click Continue → Register.
-
Download the
.p8file. Apple lets you download once. Save it outside the repo:mkdir -p ~/.mobile-surfaces mv ~/Downloads/AuthKey_*.p8 ~/.mobile-surfaces/ chmod 600 ~/.mobile-surfaces/AuthKey_*.p8 -
Copy the 10-character Key ID from the key detail page.
-
Copy your 10-character Team ID from the Membership tab.
-
The bundle id is
expo.ios.bundleIdentifierfromapps/mobile/app.json(default:com.<owner>.<project>).
Avoid com.example.*; the App Store and TestFlight uploaders reject it.
4. Wire APNs (1 min)
pnpm surface:setup-apns
Interactive wizard. It validates the four APNS_* env vars and writes them to .env.local. The script handles the .push-type.liveactivity topic suffix internally; do not include it in APNS_BUNDLE_ID (MS018).
5. Install on device (5 min)
Replace the XXXXXXXXXX placeholder for appleTeamId in apps/mobile/app.json with your real Team ID, then:
pnpm mobile:run:ios:device
First install: Settings → General → VPN & Device Management → trust your developer profile. Then re-launch the app.
6. Start on device (1 min)
In the harness, tap Start. Lock the phone. A Lock-Screen Live Activity appears.
Note the push-to-start token and per-activity push token in the harness panel. Both are needed for the next step.
7. Push an update (3 min)
From your laptop, with the four APNS_* vars set:
pnpm mobile:push:device:liveactivity -- \
--activity-token=<paste per-activity token> \
--event=update \
--snapshot-file=./data/surface-fixtures/active-progress.json \
--env=development
The Lock Screen reflects the new state. The default priority is 5 (MS015) which is correct for content-state updates; reserve 10 for transitions the user must see immediately.
8. Push end (1 min)
pnpm mobile:push:device:liveactivity -- \
--activity-token=<paste> \
--event=end \
--snapshot-file=./data/surface-fixtures/completed.json \
--env=development
The activity dismisses.
What to do next
You’ve validated the harness end-to-end. Time to build your real app on top of it. The harness is a fixture-driven playground - your app is what you replace it with.
- Building your app: concrete migration steps from the harness to a production screen, with a worked package-delivery example covering domain types, snapshot derivation, state management, token forwarding, and backend send.
- Scenarios: the canonical delivery flow rendered step by step across all five surfaces.
- Concepts: the contract, the surfaces, the adapter boundary.
- Surfaces: what each
kindactually drives. - Backend: domain event → snapshot → APNs walkthrough.
- Push: the wire-layer reference and SDK.
- Observability: alertable errors and operator response.
- Troubleshooting: symptom-to-fix recipes.
Common 30-minute traps
- Activity starts but never updates on push. Check MS013 (App Group identity across host + widget). If running on simulator, switch to device; APNs Live Activity pushes are device-only.
- APNs returns 400 BadDeviceToken. MS014: your token was minted by a dev-client build, and the production endpoint will not accept it. Pass
--env=development. - APNs returns 400 TopicDisallowed. MS018:
APNS_BUNDLE_IDincludes the.push-type.liveactivitysuffix. Pass the bare bundle id. - Remote start returns 200 but no activity appears. MS019: push-to-start tokens go silent after a force-quit until next launch. Apple radar FB21158660; ask the user to open the app once.