1# T3 Code Mobile
2
3> [!NOTE]
4> To install the released app from the App Store or Google Play, see [Mobile app](../../docs/user/install.md#mobile-app). This guide covers building it from source.
5
6## Quickstart
7
8> [!NOTE]
9> Uses native modules so using Expo Go is not supported. You need to use the Expo Dev Client.
10
11This app has three variants:
12
13- `development`: Expo dev client, installable side-by-side as `T3 Code Dev`
14- `preview`: persistent internal preview build, installable side-by-side as `T3 Code Preview`
15- `production`: store/release build as `T3 Code`
16
17Run commands from `apps/mobile`.
18
19T3 Connect is optional and disabled in a fresh clone. Public configuration belongs in the
20repository-root `.env` or `.env.local`, not an `apps/mobile/.env` file. See
21[`../../.env.example`](../../.env.example).
22
23## Development
24
25For simulator, emulator, or Android phone development, boot or connect a device, then install the
26native client that matches this checkout before starting Metro:
27
28```bash
29vp run mobile:install ios [simulator-udid]
30# Or: vp run mobile:install android [emulator-or-phone-serial]
31vp run dev:client
32```
33
34The device argument can be omitted when exactly one device is booted or connected. The helper
35compares this checkout's Expo fingerprint and the installed binary with its last install record,
36and reuses a matching client. Otherwise it installs the build for that fingerprint from
37`~/.cache/t3code/native-clients/artifacts`, then the shared task cache, and builds only when
38neither has it. A build runs in the `Mobile Dev Client` workflow (Blacksmith runners with warm
39Gradle and ccache state, and a macOS runner for iOS) when `gh` may dispatch workflows on
40`pingdotgg/t3code` and HEAD is pushed; the helper waits for the client in the shared cache.
41Otherwise, or when HEAD lacks your native edits or `T3_NATIVE_CLIENT_BUILD=local` is set, it builds
42on this machine. Main's client is built there on every native change, so most checkouts hit. One iOS simulator build serves every iPhone and iPad simulator, and one APK per
43ABI (x86_64 or arm64-v8a) serves every emulator and phone of that ABI. Build output goes to the
44log file it prints (`T3_NATIVE_CLIENT_VERBOSE=1` streams it instead). Builds are shared back when
45your task-cache login (`bash infra/task-cache/install.sh`) can write. `T3_NATIVE_CLIENT_CACHE=read`
46skips the upload and `off` skips the shared cache. Physical iPhones still use `vp run ios:dev`.
47
48`--session <name>` installs into an agent-device session opened elsewhere, such as a device pool
49or a cloud simulator, instead of a local device: `vp run mobile:install android --session pool-1
50--metro http://<this-machine>:8081`. It uses only the cached build (x86_64 by default; `--abi`
51for ARM) and fails with the next step when no machine has built this fingerprint yet.
52
53For a read-only decision, run `node ../../scripts/mobile-native-client.ts check <ios|android>
54[device]`: exit 0 means compatible, 2 means an install is needed, and 1 means an operational error.
55An externally installed client is unverified until the helper installs it once.
56
57Start Metro for an already verified dev client:
58
59```bash
60vp run dev:client
61```
62
63Metro keeps its transform cache between ordinary starts. If the cache itself is causing stale or
64invalid output, clear it for one development-client start:
65
66```bash
67vp run dev:client:reset
68```
69
70Run that reset once after installing or changing the Uniwind dependency patch. Cached transforms
71can otherwise reference its previous pnpm package path. Ordinary Metro starts still keep the cache.
72
73Component edits use Fast Refresh. See [mobile development lifecycle](../../docs/internals/mobile-development.md)
74before changing runtime ownership or refresh behavior.
75
76Build and run the local iOS dev client:
77
78```bash
79vp run ios:dev
80```
81
82After changing a native dependency patch, rerun CocoaPods before rebuilding an existing iOS
83project. pnpm gives each patch hash a new package path; Pods can otherwise keep compiling the
84previous directory.
85
86If your Xcode account only has a Personal Team, use a bundle identifier you control and opt into the
87reduced-capability local build. Personal Team builds omit the widget and share extensions, push
88entitlement, and native Sign in with Apple entitlement; builds without this opt-in are unchanged.
89
90```bash
91T3CODE_IOS_PERSONAL_TEAM=1 \
92T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code.dev \
93vp run ios:dev
94```
95
96Build and install a self-contained Release app that does not need Metro:
97
98```bash
99vp run ios:release
100```
101
102The Personal Team equivalent also needs a unique bundle identifier:
103
104```bash
105T3CODE_IOS_PERSONAL_TEAM=1 \
106T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID=com.example.t3code \
107vp run ios:release
108```
109
110Build and run the local iOS preview app:
111
112```bash
113vp run ios:preview
114```
115
116Inspect the resolved Expo config for a variant:
117
118```bash
119vp run config:dev
120vp run config:preview
121```
122
123Run static checks for mobile native code:
124
125```bash
126node ../../scripts/mobile-native-static-check.ts
127```
128
129The native lint task runs SwiftLint for Swift plus ktlint and detekt for Kotlin. Missing native tools are reported as warnings and skipped locally. CI installs the default toolset from `apps/mobile/Brewfile` before running the native checks.
130
131## EAS Builds
132
133Preview and production variants use Expo fingerprinting so OTA updates only reach binaries with matching native dependencies, config plugins, and patches. CI uses the `preview:dev` profile to reuse a compatible native build when possible.
134
135The development variant uses `appVersion` to avoid recalculating the native fingerprint for each Metro launch manifest. `MOBILE_VERSION_POLICY` can override either default. If you distribute a custom Release build with the development identity and publish OTA updates to it, set `MOBILE_VERSION_POLICY=fingerprint` for both its build and updates. Changing the runtime policy requires a native rebuild for OTA matching; an existing dev client can still load local Metro bundles.
136
137For preview or production EAS environments, set `T3CODE_CLERK_PUBLISHABLE_KEY`,
138`T3CODE_CLERK_JWT_TEMPLATE`, and `T3CODE_RELAY_URL`
139as EAS environment variables. Expo config maps the canonical values into the mobile build.
140
141Create a PR preview dev-client build manually:
142
143```bash
144vp run eas:ios:preview:dev
145```
146
147Create a cloud dev-client build:
148
149```bash
150vp run eas:ios:dev
151```
152
153Create a persistent preview build:
154
155```bash
156vp run eas:ios:preview
157```
158
159Android equivalents:
160
161```bash
162vp run eas:android:dev
163vp run eas:android:preview:dev
164vp run eas:android:preview
165```