mirror of
https://github.com/VibedByKaKi/t3-code-android-nightly.git
synced 2026-10-09 11:51:15 +02:00
208 lines
12 KiB
Markdown
208 lines
12 KiB
Markdown
# Mobile app-store screenshot harness
|
||
|
||
> For maintainers. Using T3 Code? See [docs/user](../user/).
|
||
|
||
The screenshot harness runs the real mobile application against three disposable local T3
|
||
environments. It creates an isolated base directory and server for each environment, real Git
|
||
projects with deterministic content, seeded orchestration projections, and persisted terminal
|
||
history. The app pairs with every server through its normal connection flow and React Navigation
|
||
opens the production Home, Thread, ThreadTerminal, ThreadReview, and SettingsEnvironments routes.
|
||
|
||
No screenshot-specific screen recreates application UI. `EXPO_PUBLIC_SHOWCASE=1` only enables the
|
||
non-rendering pairing/readiness coordinator, disables terminal autofocus so captures do not contain
|
||
the software keyboard, and supplies deterministic T3 Connect discovery rows to the real
|
||
Environments screen. The local environment cards always come from real paired servers.
|
||
|
||
## Capture the default matrix
|
||
|
||
From the repository root:
|
||
|
||
pnpm screenshots:mobile
|
||
|
||
The command:
|
||
|
||
1. Creates three temporary T3 base directories and starts a local server for each on an available
|
||
port.
|
||
2. Creates T3 Code, React, and Linux Git repositories with recognizable favicons, feature branches,
|
||
and a deterministic T3 Code review diff.
|
||
3. Seeds each server's migrated SQLite database with playful threads, messages, activities, and
|
||
terminal history, then adds two persisted mobile-outbox tasks waiting to send.
|
||
4. Starts an isolated Metro server, builds the selected native apps, and boots each device.
|
||
5. Pairs each clean app installation with Moonbase Terminal, Suspense Station, and Kernel Cabin.
|
||
6. Navigates to the real application route for every requested scene.
|
||
7. Sets the requested system appearance and palette, normalizes status bars, converts captures to 24-bit RGB PNGs without alpha, and
|
||
validates dimensions, aspect ratio, file size, and screenshot count before succeeding.
|
||
8. Writes store-ready folders beneath `artifacts/app-store/screenshots/` that can be uploaded
|
||
directly to App Store Connect or Google Play Console.
|
||
|
||
The servers, Metro, temporary root directory, and devices started by the runner are cleaned up after
|
||
capture. Pass `--keep-running` to retain them for inspection; the runner prints the base-directory
|
||
paths and server ports.
|
||
|
||
Captures wait for the real environment snapshot to hydrate and for the requested route to become
|
||
active. Both platforms record readiness in the simulator/emulator app container. A final settle
|
||
delay allows native terminal and Git review data to finish rendering.
|
||
|
||
A full capture regenerates the selected native project with Expo's clean production prebuild before
|
||
building it. Use --skip-build for repeated captures after the first build.
|
||
|
||
The harness uses fixed Metro port `8199`, which separates it from Expo's normal default port but is
|
||
shared across every checkout. The readiness check only verifies that the port is open; it does not
|
||
verify process ownership. Concurrent screenshot harnesses in different worktrees can therefore
|
||
collide or attach to the wrong Metro process.
|
||
|
||
Every configured device defaults to dark appearance and the `t3-code` palette, so plain
|
||
`pnpm screenshots:mobile` produces 35 dark PNGs. Pass `--appearance light`, `--appearance dark`, or
|
||
`--appearance both` to override the configured appearance; `both` produces 70 PNGs.
|
||
|
||
Pass `--theme <id>` (repeatable) or `--theme all` to capture the app's other palettes: `t3-code`,
|
||
`t3-chat`, `grove`, `ocean`, `ember`, and `iris`. The runner hands the palette to the app as a launch
|
||
argument, the app applies it to both color schemes, and a scene only reports itself ready once the
|
||
requested palette is active — so a capture can never show the previous theme. `--theme all`
|
||
multiplies the run by six; only the native build is shared.
|
||
|
||
The default matrix is:
|
||
|
||
| Output folder | Capture target | Upload dimensions | Store slot |
|
||
| ------------------------------------- | ---------------------------- | ----------------- | ----------------------------------------- |
|
||
| `apple/iphone-6.9/dark/t3-code/` | disposable iPhone 17 Pro Max | 1320×2868 | App Store Connect iPhone 6.9-inch |
|
||
| `apple/iphone-6.5/dark/t3-code/` | disposable iPhone 14 Plus | 1284×2778 | App Store Connect iPhone 6.5-inch |
|
||
| `apple/ipad-13/dark/t3-code/` | iPad Pro 13-inch (M5) | 2752×2064 | App Store Connect iPad 13-inch, landscape |
|
||
| `google-play/phone/dark/t3-code/` | Pixel AVD at 420 dpi | 1080×1920 | Google Play phone, portrait 9:16 |
|
||
| `google-play/tablet-7/dark/t3-code/` | Pixel AVD at 600dp width | 1080×1920 | Google Play 7-inch tablet, portrait 9:16 |
|
||
| `google-play/tablet-10/dark/t3-code/` | Pixel AVD at 800dp width | 1440×2560 | Google Play 10-inch tablet, portrait 9:16 |
|
||
|
||
Each target captures thread, terminal, review, thread list, and environments, and every target but
|
||
the iPad also captures agent activity. Each palette folder's five or six screenshots satisfy the configured Apple limit of 1–10, Google
|
||
phone requirement of 2–8, and Google tablet recommendation/slot minimum of 4 with a maximum of 8.
|
||
Every palette gets its own leaf folder so one upload slot never mixes themes and each folder keeps a
|
||
store-legal screenshot count.
|
||
|
||
The agent-activity scene shows what a user sees away from the app. The app stages the same Live
|
||
Activity (iOS) or ongoing Live Update (Android) the relay would publish for four seeded threads. On
|
||
iOS the runner then locks the simulator and pushes the matching approval alert with `simctl push`;
|
||
on Android the staged update carries the alert and the runner opens the notification shade. Locking
|
||
the simulator and answering the notification permission prompt use
|
||
[AXe](https://github.com/cameroncooke/AXe), so install it (`brew tap cameroncooke/axe && brew
|
||
install axe`) or set `AXE_PATH` before capturing iOS. The iPad skips the scene because the lock
|
||
screen does not follow the app's landscape self-rotation.
|
||
|
||
The generated tree is deliberately aligned with the store upload fields:
|
||
|
||
artifacts/app-store/screenshots/
|
||
├── apple/
|
||
│ ├── iphone-6.9/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
|
||
│ ├── iphone-6.5/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
|
||
│ └── ipad-13/dark/t3-code/{thread,terminal,review,threads,environments}.png
|
||
└── google-play/
|
||
├── phone/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
|
||
├── tablet-7/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
|
||
└── tablet-10/dark/t3-code/{thread,terminal,review,threads,environments,agent-activity}.png
|
||
|
||
A light-only run writes the same tree under `light/`; `--appearance both` writes both appearance
|
||
folders, and each requested theme adds a sibling folder next to `t3-code/`.
|
||
|
||
Edit [mobile-showcase.config.ts](../../scripts/mobile-showcase.config.ts) to change simulator or AVD
|
||
names, light/dark appearance, default palette, iOS orientation, scenes, output directory, capture
|
||
delay, Android ABI, or viewport. The selectable palette ids come from `MOBILE_THEME_IDS` in
|
||
[themePalettes.ts](../../packages/shared/src/themePalettes.ts), so the harness and the app's
|
||
appearance settings can never drift apart.
|
||
|
||
## Capture in GitHub Actions
|
||
|
||
Run the `Mobile Showcase Screenshots` workflow from GitHub's Actions tab, choose `all`, `ios`, or
|
||
`android`, select `light`, `dark`, or `both`, and pick a palette (or `all`, which raises each job's
|
||
timeout from 60 to 300 minutes). The default dispatch captures both appearances of the `t3-code`
|
||
palette and runs iOS and Android concurrently: iPhone and iPad capture on a
|
||
12-vCPU Blacksmith macOS runner, while Android phone, 7-inch tablet, and 10-inch tablet capture on a
|
||
16-vCPU Blacksmith Linux runner with a KVM-accelerated x86_64 emulator.
|
||
|
||
Every job uploads its PNGs even when capture fails, which makes partial runs useful for diagnosis.
|
||
The separate validation step is success-gated: it runs before upload only when capture succeeds. If
|
||
capture fails, the `always()` upload still publishes partial PNGs without re-validating them.
|
||
Download `app-store-connect-screenshots` and `google-play-screenshots` from the workflow run's
|
||
Artifacts section. Artifacts are retained for 14 days.
|
||
|
||
The workflow uses the same checked-in device and scene matrix as local capture. Android remains
|
||
ARM64 by default for local Apple Silicon development; CI sets `T3_SHOWCASE_ANDROID_ABI=x86_64` so the
|
||
debug APK matches its accelerated emulator.
|
||
|
||
## Fast iteration
|
||
|
||
Capture one scene or device:
|
||
|
||
pnpm screenshots:mobile --device iphone-6.9 --scene thread
|
||
pnpm screenshots:mobile --platform android --scene review
|
||
|
||
Override the configured appearance or capture both variants:
|
||
|
||
pnpm screenshots:mobile --appearance light
|
||
pnpm screenshots:mobile --appearance dark
|
||
pnpm screenshots:mobile --appearance both
|
||
|
||
Capture other palettes:
|
||
|
||
pnpm screenshots:mobile --device iphone-6.9 --theme ocean
|
||
pnpm screenshots:mobile --device iphone-6.9 --theme ocean --theme ember
|
||
pnpm screenshots:mobile --device iphone-6.9 --theme all
|
||
|
||
Reuse the native build and retain the disposable environment:
|
||
|
||
pnpm screenshots:mobile --device ipad-13 --skip-build --keep-running
|
||
|
||
By default, let the screenshot runner start Metro on port `8199`. To keep Metro in a separate
|
||
terminal, start it with the same showcase environment and explicit harness port:
|
||
|
||
cd apps/mobile
|
||
APP_VARIANT=development EXPO_PUBLIC_SHOWCASE=1 pnpm exec expo start --dev-client --port 8199
|
||
|
||
Then run the capture from the repository root:
|
||
|
||
pnpm screenshots:mobile --skip-build --skip-metro --device iphone-6.9
|
||
|
||
`pnpm --filter @t3tools/mobile showcase` starts Expo on its normal port, so it is not compatible with
|
||
the harness's `--skip-metro` mode.
|
||
|
||
List the matrix and flags:
|
||
|
||
pnpm screenshots:mobile --list
|
||
|
||
Validate existing files without starting Metro, servers, simulators, or emulators:
|
||
|
||
pnpm screenshots:mobile --validate-only
|
||
pnpm screenshots:mobile --platform ios --validate-only
|
||
|
||
## Customize the seeded environment
|
||
|
||
- Project repository, thread projections, conversation, terminal transcript, and Git changes:
|
||
[mobile-showcase-environment.ts](../../scripts/mobile-showcase-environment.ts)
|
||
- Device and capture matrix:
|
||
[mobile-showcase.config.ts](../../scripts/mobile-showcase.config.ts)
|
||
- Simulator/emulator orchestration:
|
||
[mobile-showcase.ts](../../scripts/mobile-showcase.ts)
|
||
|
||
Fixture timestamps are generated relative to capture startup so every route shows stable relative
|
||
labels while the server still receives valid current data. The same deterministic three-environment
|
||
ensemble serves iPhone, iPad, Android phone, and Android tablet captures; responsive differences
|
||
come entirely from the production app layout.
|
||
|
||
The Pending rows use the production offline outbox and point at the real T3 Code and React fixture
|
||
projects. Showcase coordination holds those two entries in the outbox for capture, just like a task
|
||
currently open for editing, so reconnecting the seeded environments cannot deliver and remove them
|
||
before the screenshot is taken.
|
||
|
||
The Environments capture presents the three local fixture transports as a Tailscale HTTPS hostname,
|
||
a Helsinki VPS hostname, and a Tailnet IPv4 address. This display-only substitution keeps the cards
|
||
remote-first while the harness retains reliable loopback connections to its ephemeral servers.
|
||
|
||
## Local prerequisites
|
||
|
||
- iOS: Xcode command-line tools, the configured simulator runtimes, and installed CocoaPods.
|
||
- Android: SDK resolution checks `ANDROID_HOME`, then `ANDROID_SDK_ROOT`, then defaults to
|
||
`$HOME/Library/Android/sdk` on macOS or `$HOME/Android/Sdk` on other platforms. The resolved SDK
|
||
must provide `adb` and `emulator`, and the configured AVD must exist.
|
||
|
||
The harness is the source of truth for upload dimensions; do not resize its output. If store rules
|
||
change, update the target's `storeAsset` specification. Capture fails when a PNG is the wrong size,
|
||
has alpha, is not 8-bit RGB, exceeds the configured file-size limit, violates Google Play's 9:16
|
||
shape/bounds, or leaves a full output set below its store minimum.
|