Skip to content
Source
Sign inSign up

mobile-app-store-screenshots.md

219 lines13.1 KB

1# Mobile app-store screenshot harness
2​
3> For maintainers. Using T3 Code? See [docs/user](../user/).
4​
5The screenshot harness runs the real mobile application against three disposable local T3
6environments. It creates an isolated base directory and server for each environment, real Git
7projects with deterministic content, seeded orchestration projections, and persisted terminal
8history. The app pairs with every server through its normal connection flow and React Navigation
9opens the production Home, Thread, ThreadTerminal, ThreadReview, and SettingsEnvironments routes.
10​
11No screenshot-specific screen recreates application UI. `EXPO_PUBLIC_SHOWCASE=1` only enables the
12non-rendering pairing/readiness coordinator, disables terminal autofocus so captures do not contain
13the software keyboard, and supplies deterministic T3 Connect discovery rows to the real
14Environments screen. The local environment cards always come from real paired servers.
15​
16## Capture the default matrix
17​
18From the repository root:
19​
20 pnpm screenshots:mobile
21​
22The command:
23​
241. Creates three temporary T3 base directories and starts a local server for each on an available
25 port.
262. Creates T3 Code, React, and Linux Git repositories with recognizable favicons, feature branches,
27 and a deterministic T3 Code review diff.
283. Seeds each server's migrated SQLite database with playful threads, messages, activities, and
29 terminal history, then adds two persisted mobile-outbox tasks waiting to send.
304. Starts an isolated Metro server, builds the selected native apps, and boots each device.
315. Pairs each clean app installation with Moonbase Terminal, Suspense Station, and Kernel Cabin.
326. Navigates to the real application route for every requested scene.
337. Sets the requested system appearance and palette, normalizes status bars, converts captures to 24-bit RGB PNGs without alpha, and
34 validates dimensions, aspect ratio, file size, and screenshot count before succeeding.
358. Writes store-ready folders beneath `artifacts/app-store/screenshots/` that can be uploaded
36 directly to App Store Connect or Google Play Console.
37​
38The servers, Metro, temporary root directory, and devices started by the runner are cleaned up after
39capture. Pass `--keep-running` to retain them for inspection; the runner prints the base-directory
40paths and server ports.
41​
42Captures wait for the real environment snapshot to hydrate and for the requested route to become
43active. Both platforms record readiness in the simulator/emulator app container. A final settle
44delay allows native terminal and Git review data to finish rendering.
45​
46A full capture regenerates the selected native project with Expo's clean production prebuild before
47building it. Use --skip-build for repeated captures after the first build.
48​
49The harness uses fixed Metro port `8199`, which separates it from Expo's normal default port but is
50shared across every checkout. The readiness check only verifies that the port is open; it does not
51verify process ownership. Concurrent screenshot harnesses in different worktrees can therefore
52collide or attach to the wrong Metro process.
53​
54Every configured device defaults to dark appearance and the `t3-code` palette, so plain
55`pnpm screenshots:mobile` produces 45 dark PNGs. Pass `--appearance light`, `--appearance dark`, or
56`--appearance both` to override the configured appearance; `both` produces 90 PNGs.
57​
58Pass `--theme <id>` (repeatable) or `--theme all` to capture the app's other palettes: `t3-code`,
59`t3-chat`, `grove`, `ocean`, `ember`, and `iris`. The runner hands the palette to the app as a launch
60argument, the app applies it to both color schemes, and a scene only reports itself ready once the
61requested palette is active — so a capture can never show the previous theme. `--theme all`
62multiplies the run by six; only the native build is shared.
63​
64The default matrix is:
65​
66| Output folder | Capture target | Upload dimensions | Store slot |
67| --------------------------------------- | ------------------------------ | ----------------- | ----------------------------------------- |
68| `apple/iphone-6.9/dark/t3-code/` | disposable iPhone 17 Pro Max | 1320×2868 | App Store Connect iPhone 6.9-inch |
69| `apple/iphone-6.5/dark/t3-code/` | disposable iPhone 14 Plus | 1284×2778 | App Store Connect iPhone 6.5-inch |
70| `apple/iphone-duo-closed/dark/t3-code/` | iPhone Duo, folded portrait | 1398×2034 | App Store Connect iPhone Duo |
71| `apple/iphone-duo-open/dark/t3-code/` | iPhone Duo, unfolded landscape | 2853×2007 | App Store Connect iPhone Duo |
72| `apple/ipad-13/dark/t3-code/` | iPad Pro 13-inch (M5) | 2752×2064 | App Store Connect iPad 13-inch, landscape |
73| `google-play/phone/dark/t3-code/` | Pixel AVD at 420 dpi | 1080×1920 | Google Play phone, portrait 9:16 |
74| `google-play/tablet-7/dark/t3-code/` | Pixel AVD at 600dp width | 1080×1920 | Google Play 7-inch tablet, portrait 9:16 |
75| `google-play/tablet-10/dark/t3-code/` | Pixel AVD at 800dp width | 1440×2560 | Google Play 10-inch tablet, portrait 9:16 |
76​
77Each target captures thread, terminal, review, thread list, and environments, and every target except
78the iPad and Duo also captures agent activity. Each palette folder's five or six screenshots satisfy the configured Apple limit of 1–10, Google
79phone requirement of 2–8, and Google tablet recommendation/slot minimum of 4 with a maximum of 8.
80The two Duo folders each contain five scenes and upload to the same iPhone Duo slot (ten images
81total). They omit agent activity to stay within that shared limit. Duo capture requires
82an installed iOS runtime that supports iPhone Duo and `agent-device` on PATH, or
83`AGENT_DEVICE_PATH` pointing to its executable; it sets and verifies the
84simulator fold pose before launching the app.
85​
86Every palette gets its own leaf folder so one upload slot never mixes themes and each folder keeps a
87store-legal screenshot count.
88​
89The agent-activity scene shows what a user sees away from the app. The app stages the same Live
90Activity (iOS) or ongoing Live Update (Android) the relay would publish for four seeded threads. On
91iOS the runner then locks the simulator and pushes the matching approval alert with `simctl push`;
92on Android the staged update carries the alert and the runner opens the notification shade. Locking
93the simulator and answering the notification permission prompt use
94[AXe](https://github.com/cameroncooke/AXe), so install it (`brew tap cameroncooke/axe && brew
95install axe`) or set `AXE_PATH` before capturing iOS. The iPad skips the scene because the lock
96screen does not follow the app's landscape self-rotation.
97​
98The generated tree is deliberately aligned with the store upload fields:
99​
100 artifacts/app-store/screenshots/
101 ├── apple/
102 │ ├── iphone-6.9/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
103 │ ├── iphone-6.5/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
104 │ ├── iphone-duo-closed/dark/t3-code/{thread,terminal,review,threads,environments}.png
105 │ ├── iphone-duo-open/dark/t3-code/{thread,terminal,review,threads,environments}.png
106 │ └── ipad-13/dark/t3-code/{thread,terminal,review,threads,environments}.png
107 └── google-play/
108 ├── phone/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
109 ├── tablet-7/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
110 └── tablet-10/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
111​
112A light-only run writes the same tree under `light/`; `--appearance both` writes both appearance
113folders, and each requested theme adds a sibling folder next to `t3-code/`.
114​
115Edit [mobile-showcase.config.ts](../../scripts/mobile-showcase.config.ts) to change simulator or AVD
116names, light/dark appearance, default palette, iOS orientation, scenes, output directory, capture
117delay, Android ABI, or viewport. The selectable palette ids come from `MOBILE_THEME_IDS` in
118[themePalettes.ts](../../packages/shared/src/themePalettes.ts), so the harness and the app's
119appearance settings can never drift apart.
120​
121## Capture in GitHub Actions
122​
123Run the `Mobile Showcase Screenshots` workflow from GitHub's Actions tab, choose `all`, `ios`, or
124`android`, select `light`, `dark`, or `both`, and pick a palette (or `all`, which raises each job's
125timeout from 60 to 300 minutes). The default dispatch captures both appearances of the `t3-code`
126palette and runs iOS and Android concurrently: iPhone and iPad capture on a
12712-vCPU Blacksmith macOS runner, while Android phone, 7-inch tablet, and 10-inch tablet capture on a
12816-vCPU Blacksmith Linux runner with a KVM-accelerated x86_64 emulator.
129​
130Every job uploads its PNGs even when capture fails, which makes partial runs useful for diagnosis.
131The separate validation step is success-gated: it runs before upload only when capture succeeds. If
132capture fails, the `always()` upload still publishes partial PNGs without re-validating them.
133Download `app-store-connect-screenshots` and `google-play-screenshots` from the workflow run's
134Artifacts section. Artifacts are retained for 14 days.
135​
136The workflow uses the same checked-in device and scene matrix as local capture. Android remains
137ARM64 by default for local Apple Silicon development; CI sets `T3_SHOWCASE_ANDROID_ABI=x86_64` so the
138debug APK matches its accelerated emulator.
139​
140## Fast iteration
141​
142Capture one scene or device:
143​
144 pnpm screenshots:mobile --device iphone-duo-closed --device iphone-duo-open
145 pnpm screenshots:mobile --device iphone-6.9 --scene thread
146 pnpm screenshots:mobile --platform android --scene review
147​
148Override the configured appearance or capture both variants:
149​
150 pnpm screenshots:mobile --appearance light
151 pnpm screenshots:mobile --appearance dark
152 pnpm screenshots:mobile --appearance both
153​
154Capture other palettes:
155​
156 pnpm screenshots:mobile --device iphone-6.9 --theme ocean
157 pnpm screenshots:mobile --device iphone-6.9 --theme ocean --theme ember
158 pnpm screenshots:mobile --device iphone-6.9 --theme all
159​
160Reuse the native build and retain the disposable environment:
161​
162 pnpm screenshots:mobile --device ipad-13 --skip-build --keep-running
163​
164By default, let the screenshot runner start Metro on port `8199`. To keep Metro in a separate
165terminal, start it with the same showcase environment and explicit harness port:
166​
167 cd apps/mobile
168 APP_VARIANT=development EXPO_PUBLIC_SHOWCASE=1 pnpm exec expo start --dev-client --port 8199
169​
170Then run the capture from the repository root:
171​
172 pnpm screenshots:mobile --skip-build --skip-metro --device iphone-6.9
173​
174`pnpm --filter @t3tools/mobile showcase` starts Expo on its normal port, so it is not compatible with
175the harness's `--skip-metro` mode.
176​
177List the matrix and flags:
178​
179 pnpm screenshots:mobile --list
180​
181Validate existing files without starting Metro, servers, simulators, or emulators:
182​
183 pnpm screenshots:mobile --validate-only
184 pnpm screenshots:mobile --platform ios --validate-only
185​
186## Customize the seeded environment
187​
188- Project repository, thread projections, conversation, terminal transcript, and Git changes:
189 [mobile-showcase-environment.ts](../../scripts/mobile-showcase-environment.ts)
190- Device and capture matrix:
191 [mobile-showcase.config.ts](../../scripts/mobile-showcase.config.ts)
192- Simulator/emulator orchestration:
193 [mobile-showcase.ts](../../scripts/mobile-showcase.ts)
194​
195Fixture timestamps are generated relative to capture startup so every route shows stable relative
196labels while the server still receives valid current data. The same deterministic three-environment
197ensemble serves iPhone, iPad, Android phone, and Android tablet captures; responsive differences
198come entirely from the production app layout.
199​
200The Pending rows use the production offline outbox and point at the real T3 Code and React fixture
201projects. Showcase coordination holds those two entries in the outbox for capture, just like a task
202currently open for editing, so reconnecting the seeded environments cannot deliver and remove them
203before the screenshot is taken.
204​
205The Environments capture presents the three local fixture transports as a Tailscale HTTPS hostname,
206a Helsinki VPS hostname, and a Tailnet IPv4 address. This display-only substitution keeps the cards
207remote-first while the harness retains reliable loopback connections to its ephemeral servers.
208​
209## Local prerequisites
210​
211- iOS: Xcode command-line tools, the configured simulator runtimes, and installed CocoaPods.
212- Android: SDK resolution checks `ANDROID_HOME`, then `ANDROID_SDK_ROOT`, then defaults to
213 `$HOME/Library/Android/sdk` on macOS or `$HOME/Android/Sdk` on other platforms. The resolved SDK
214 must provide `adb` and `emulator`, and the configured AVD must exist.
215​
216The harness is the source of truth for upload dimensions; do not resize its output. If store rules
217change, update the target's `storeAsset` specification. Capture fails when a PNG is the wrong size,
218has alpha, is not 8-bit RGB, exceeds the configured file-size limit, violates Google Play's 9:16
219shape/bounds, or leaves a full output set below its store minimum.