Raw captures
Real in-app UI on simulator: correct language, Release build, seeded demo content, no Dev Client chrome.
Manual App Store screenshots fall apart as soon as you add a second locale. Add iPad sizes, Devanagari line heights (Sisu teaches Nepali and English), and "hide anything that looks like debug chrome," and you end up with folders named final-final-2.
We built a repeatable pipeline while shipping Sisu and reuse it across Codse Expo apps: plant locale into the running simulator, capture real UI, frame marketing slides, map folders to App Store Connect locales, validate, then upload with asc — only when a human says go.

For each storefront locale and required device size:
Real in-app UI on simulator: correct language, Release build, seeded demo content, no Dev Client chrome.
Device bezel plus localized headline copy, sized for the ASC device type.
Remapped to ASC locale codes (en → en-US, and so on) and checked for pixel size.
Local validate first. Upload with asc only on purpose — never as a side effect of regenerate.
Device targets we ship for Sisu-style universal apps:
| ASC device type | Pixel size we aim for | Notes |
|---|---|---|
IPHONE_65 | 1242×2688 | Primary phone set |
IPAD_PRO_3GEN_129 | 2064×2752 (2048×2732 also accepted) | Separate raw + fancy trees |
Keep one product frame consistent across locales. Mixed bezels look accidental on the product page.
This trips teams once.
| In-app | ASC listing locale | Notes |
|---|---|---|
en | en-US | Default storefront |
en-GB | en-GB | |
es | es-ES | |
fr | fr-FR | |
pt | pt-PT | European Portuguese unless you also ship pt-BR |
hi | hi | |
ne | none | Nepali is in-app on Sisu — generate fancy for QA, do not upload as a storefront locale |
Stage scripts should remap folders. Before asc screenshots upload, move ne/ aside so the CLI does not treat it as a child locale.
Framing English UI and translating only the marketing headline is weak ASO. Shoppers see the UI inside the device.
We use a small bootstrap module (copy into each app) plus an allowlisted route file planted into the app container:
# Plant route + locale, then relaunch
./scripts/plant-shot-route.sh <sim-udid> /home ne light
Contract we keep stable across apps:
| Param | Aliases | Effect |
|---|---|---|
lang / locale | ne, en, es-ES, … | Sets app locale |
theme / appearance | light | dark | system | Forces color scheme when the app supports it |
| path | allowlisted only | Navigates to a known screen |
Deep links work too (sisu://home?lang=ne). Disk planting is better for headless sims when Simulator.app is not open.
Rules that matter:
Documents/../scripts/seed-shot-content.sh <sim-udid>
./scripts/capture-raw-locale.sh ne
# or batch storefront langs:
./scripts/capture-all-store-locales.sh
Name raws by screen and variant (home.png, play.png, category.png, …). iPad is a parallel tree (raw-ipad/, separate sim UDID). Do not stretch iPhone captures and hope.
Marketing slides are HTML templates + YAML, rendered to device-sized PNGs.
./scripts/generate-fancy-all.sh # iPhone 65
./scripts/generate-fancy-ipad.sh # iPad 12.9 / 13
Before render we run layout calibration (@chenglou/pretext + node-canvas) so type sizes and device offsets keep comfortable air between copy and bezel per locale. Devanagari and long compounds will wreck a single global font size. Per-lang layout JSON fixes that without hand-nudging every PNG.
Frame colors should come from the product (Sisu's playful purple, not a generic AI gradient kit). One outcome per slide — screenshots sell, they do not document the whole app.
bash ./scripts/stage-asc-screenshots.sh --validate
bash ./scripts/stage-asc-ipad.sh --validate
Staging remaps en → en-US, es → es-ES, and so on, and checks sizes. After every regenerate, spot-check a tall-script locale (ne / hi) and a long-copy locale: no text-on-device overlap, phone still visible, gaps look intentional.
Keep upload disabled by default. When you are ready:
source .secrets/asc.sh
export APP_ID=…
mv screenshots/asc/iphone65/ne /tmp/ne-preview # skip non-store locale
asc screenshots upload --app "$APP_ID" --version 1.0.7 \
--path ./screenshots/asc/iphone65 \
--device-type IPHONE_65 --replace
asc screenshots upload --app "$APP_ID" --version 1.0.7 \
--path ./screenshots/asc/ipad129 \
--device-type IPAD_PRO_3GEN_129 --replace
Same discipline for listing metadata: asc metadata validate locally; asc metadata push only after explicit OK.
Apple can OCR text in screenshot captions. Put real phrases there when they read naturally — keyword stuffing still looks like keyword stuffing.
After Xcode upgrades, re-check simulator UDIDs in your shot settings file. Stale UDIDs fail in confusing ways.
Put durable commands and locale maps in AGENTS.md. That file survives context loss better than chat history.
Suggested tree:
screenshots/raw/{lang}/
screenshots/raw-ipad/{lang}/
screenshots/koubou/ # templates, yaml, strings
screenshots/fancy/iphone65/{lang}/
screenshots/fancy/ipad129/{lang}/
screenshots/asc/iphone65/{asc-locale}/
screenshots/asc/ipad129/{asc-locale}/
scripts/plant-shot-route.sh
scripts/capture-*.sh
scripts/generate-fancy-*.sh
scripts/stage-asc-*.sh
src/platform/shot-bootstrap.ts
Secrets stay in .secrets/asc.sh. Never commit machine-absolute p8 paths in tracked config.
You can. It looks wrong. Shoppers see the UI inside the device. Re-plant locale and recapture raws when in-app strings change.
Nepali is not an App Store Connect listing locale. On Sisu we keep it for in-app QA and fancy previews, then move the staged folder aside before upload.
If the app is universal or iPad-capable, ship the required iPad sizes. Run a parallel capture + fancy path — do not stretch phone screenshots.
Recapture only the affected locales and screens, rerun fancy for those langs, restage, spot-check bezel gaps, then upload with --replace for that device type. Full-matrix runs are for major visual or copy passes.
Yes. Point DEVELOPER_DIR at Xcode-beta, boot via simctl, plant routes on disk, and capture without assuming Simulator.app is open.
Ship the pipeline once, copy shot-bootstrap plus plant/capture/stage scripts into the next Expo app, and update the locale table. Screenshots become a checklist instead of a release blocker.
For the binary side of the same release week, see How we ship Sisu on Xcode Cloud.