# App Store Screenshots

> End-to-end playbook for producing localized, caption-framed App Store screenshots (iPhone + iPad) from real simulator captures, then replacing them on App Store Connect. Use when asked to create, localize, redesign, or upload App Store screenshots for an iOS app. Covers capture automation via the accessibility tree, an HTML/CSS frame compositor, ASC upload mapping, and review-submission rebuild.

- Skill: `framara/app-store-screenshots` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add framara/app-store-screenshots`
- Raw SKILL.md: https://api.skillmd.com/api/skills/framara/app-store-screenshots/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: framara (https://skillmd.com/u/framara)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/framara/app-store-screenshots

---


# App Store Screenshots — capture → frame → upload

Battle-tested end to end on a shipping game (SAPPER, 2026): 7 shots × 10
languages × iPhone + iPad = 140 frames, replaced across 39 App Store Connect
locales and resubmitted for review in one day.

The philosophy: App Store screenshots are **marketing frames, not raw UI** —
a big localized headline over a device mock containing a *real, localized,
real-data* capture. Keep three independent layers so each can change cheaply:

1. **Raw captures** — slow to make, often not regenerable (live data, daily
   seeds). Archive them in git.
2. **Captions + design config** — one JSON for all locales and shots.
3. **Compositor** — `scripts/generate.py` re-renders every frame in minutes
   after any copy or design tweak. No recapture needed.

## Prerequisites

- **Xcode + iOS simulators** for the target devices (iPhone 17 Pro Max class
  for 6.9", iPad Pro 13" class) — captures use `xcrun simctl`.
- **[XcodeBuildMCP](https://github.com/cameroncooke/XcodeBuildMCP) CLI**
  (`brew install cameroncooke/xcodebuildmcp/xcodebuildmcp`) — provides
  element-ref UI automation (`xcodebuildmcp ui-automation snapshot-ui/tap`)
  and **bundles the AXe binary** the capture scripts depend on, at
  `/opt/homebrew/Cellar/xcodebuildmcp/<ver>/libexec/bundled/axe`.
  (Alternative: install AXe standalone — `brew install cameroncooke/axe/axe`.)
- **[asc CLI](https://github.com/rorkai/App-Store-Connect-CLI)**, authenticated
  against the App Store Connect API — uploads, localizations, builds, review
  submissions.
- **Google Chrome** — headless renderer for the frame compositor.
- **Python 3** — capture loops, compositor, verification (stdlib only).
- A **DEBUG build of the app** with a screenshot-mode launch flag (see
  Phase 1) installed on the simulators.

## References (read the one matching your phase)

- `references/design-principles.md` — **read this first, before any capture**:
  the 7 conversion principles, the composition pattern library, worked
  teardowns of 7 real listings (`references/examples/`), concept checklist
- `references/sizes-and-devices.md` — simulators, exact pixel sizes, ASC
  display types, device-mock CSS values, type scale tables
- `references/captions.md` — caption anatomy, the narrative arc,
  transcreation rules per locale (glossary, line breaks, CJK), captions.json
- `references/capture-locales.md` — per-locale capture loops, iPhone vs iPad
  differences, state discriminators, every automation gotcha
- `references/compositor.md` — the HTML/CSS frame generator, auto-fit and
  auto-push JS (with the bugs they prevent), render command, review checklist
- `references/asc-upload.md` — locale mapping table, the fan-out abort gotcha
  + mandatory verification, review-submission rebuild (incl. Game Center)

## Bundled scripts (copy into the app's repo and adapt)

- `scripts/generate.py` — the frame compositor (HTML/CSS → headless Chrome)
- `scripts/captions.example.json` — the copy/config data shape
- `scripts/loop_locales_skeleton.py` — per-locale capture loop with the
  battle-tested helpers (frame-targeted taps, state assertions, retries)
- `scripts/verify_asc_screenshots.py` — post-upload per-locale verification

## Phase summary

**0 — Plan the concept** (`references/design-principles.md`). Panel 1 is the
value prop and comes from user research, not the design file. Sell the
outcome, not the feature. One message per shot. Find the visual hook before
writing the headline — cover the copy and the idea should still read.
Humanize where it makes sense. Put a true trust signal (rating, count, award,
press) in every panel. Shortest copy wins because it buys the biggest type.
Then: 5–7 shots, arc hook → core choice → retention → secondary mode →
social → monetization → trust. Backgrounds rotate through the app's own
palette, never repeating adjacently. No prices in screenshots (they vary by
storefront and drift). Captions transcreated per locale with explicit line
breaks.

**1 — Prepare the app.** Add a DEBUG-only launch flag (e.g.
`-MarketingScreenshots YES`) that: hides store prices behind a neutral
localized tag; forces the accessibility overlay on custom-drawn surfaces so
automation can read them. Make sure a debug menu can reset/replay any state
you need to capture.

**2 — Capture iPhone.** Use a sim whose native screenshot IS the store size
(no resizing, ever). Build each app state once in English, then per locale:
background the app (state saves on backgrounding!) → terminate → relaunch
with `-AppleLanguages "(xx)" -AppleLocale xx_XX` → re-shoot with
locale-independent navigation. Clean status bar via `simctl status_bar`.

**3 — Capture iPad.** Same flow, three traps: `axe button home` is a silent
no-op (use `button lock` to background); the status bar shows a date in the
SYSTEM language (switch `.GlobalPreferences` + reboot per locale); all layout
metrics differ from iPhone — re-probe everything.

**4 — Frame.** `generate.py` + `captions.json` → headless Chrome at exact
store resolution. Auto-fit shrinks long headlines; auto-push moves the device
below wrapping subs. Validate every PNG's dimensions; eyeball the longest
Latin locale and one CJK locale on every shot type. Auto-fit firing is a copy
smell — if a locale renders below its base size, shorten the headline instead
of accepting smaller type.

**5 — Upload.** Stage a hardlink tree keyed by ASC locale codes (app
languages fan out: en serves en-US/GB/AU/CA + all store-only locales).
Upload with `--replace` to the display types the listing ALREADY uses.
**The fan-out aborts all remaining locales on the first delivery failure
while exiting 0** — run `verify_asc_screenshots.py` and re-run the gaps.

**6 — Resubmit (if a review was pending).** Save the pending submission's
item IDs → cancel → new build + screenshots → attach build → recreate
submission → re-add every item (the appStoreVersions item only attaches
after screenshot assets finish processing — add it last) → audit the count →
submit.

## Hard-won rules (apply everywhere)

- Concept before capture: if the raw capture only makes sense once the
  headline is read, re-capture (different state, tighter crop) rather than
  writing a longer headline. Copy sharpens an idea; it can't rescue one.
- A trust signal must be true. Never invent ratings, counts, awards or press.
- Frame-target every tap from `axe describe-ui` output; fixed coordinates
  break across locales and states.
- The accessibility tree contains ALL layers at once — find a geometric
  discriminator (e.g. a known card's y-position) to assert which screen
  you're on, and verify after every navigation.
- The first tap after a cold launch or transition is often eaten: retry loops
  with state assertions, never fire-and-forget.
- Long-press = separate `touch --down`, real sleep, `touch --up`. The
  single-command delay form sometimes registers as a tap.
- After any swipe, wait until two consecutive position reads agree before
  computing tap targets (content decelerates).
- Anything that reads localized a11y labels (solvers, content checks) runs in
  English only; per-locale passes navigate by geometry.
- Verification is part of every phase: look at the captures with your eyes,
  measure the frames, list the uploads. Every silent failure we hit was
  caught by checking, not by exit codes.

## Final checklist (before submit)

**Concept** (`references/design-principles.md`)
- [ ] Panel 1 states an outcome a stranger would want, in one line
- [ ] Cover all copy — the app's purpose still reads
- [ ] One message per shot, nameable in one sentence, no duplicates
- [ ] Every panel carries one trust signal, and every one is TRUE
- [ ] Exactly one accent segment per headline, on the meaning-carrying words
      in each locale (not the same line number as English)
- [ ] Strip viewed at thumbnail size: layouts, device treatments and headline
      positions vary; every panel still reads at 200 px wide

**Craft**
- [ ] Shot narrative + captions reviewed in all locales (line breaks explicit)
- [ ] No prices, no system UI artifacts (charging bolt, app-switch breadcrumb,
      notification banners), clean 9:41 status bar
- [ ] Raw captures archived in git (NOT regenerable)
- [ ] All frames exactly store resolution; spot-check longest-copy locales
      (de, it) and CJK on every shot type
- [ ] No headline shrunk below its `baseSize` by auto-fit (shorten the copy
      instead of accepting smaller type)

**Ship**
- [ ] ASC per-locale verification after upload (count / order / COMPLETE)
- [ ] Submission items audited before submit

