feat(apple): App Store screenshot harness + CI zip artifact
apple / swift (push) Successful in 54s
release / apple (push) Successful in 8m1s
apple / screenshots (push) Failing after 6m42s
ci / rust (push) Successful in 1m25s
ci / web (push) Successful in 42s
android / android (push) Successful in 3m27s
ci / docs-site (push) Successful in 53s
ci / bench (push) Failing after 3m1s
deb / build-publish (push) Successful in 2m33s
decky / build-publish (push) Successful in 12s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m13s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m26s
docker / deploy-docs (push) Successful in 6s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m7s

A DEBUG-only "shot mode" renders one mock-populated screen full-bleed
(PUNKTFUNK_SHOT_SCENE=<name> -> ScreenshotHostView instead of ContentView),
so the OS can screenshot the REAL, fully-rendered UI. tools/screenshots.sh
drives it: screencapture for the mac window, `simctl io booted screenshot`
for the iOS/iPad/tvOS Simulators, at exactly the App Store Connect sizes.

ImageRenderer was tried first and rejected: it can't rasterize this app's
chrome (NavigationStack, Form/TabView, Liquid-Glass/NSVisualEffect all render
black or the "can't render" placeholder). Capturing the live window/Simulator
avoids that. Only the stream hero is synthetic (StreamView needs a live
connection) - a synthwave frame + the real glass HUD, overridable via
PUNKTFUNK_SHOT_HERO.

CI: a new `screenshots` job in apple.yml builds the iOS (+ tvOS best-effort)
xcframework slices, runs the harness per platform best-effort, and attaches
the result as a single zip artifact (punktfunk-appstore-screenshots). It is
isolated from the build/test job and skipped on PRs, so a capture gap (missing
Simulator runtime, or no Screen Recording grant for the mac window capture)
never reds the core signal.

Generated PNGs (clients/apple/screenshots/) are gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 19:43:41 +02:00
parent b54f781524
commit 32879f45bf
8 changed files with 762 additions and 0 deletions
+47
View File
@@ -174,6 +174,53 @@ signing, bundle id `io.unom.punktfunk`. Notes:
in a simulator via `xcrun simctl install/launch``SIMCTL_CHILD_PUNKTFUNK_AUTOCONNECT=…`
passes the dev autoconnect env through).
## App Store screenshots
Automated, faithful screenshots of the real UI for App Store Connect — one set per platform at
exactly the accepted pixel sizes. Driver: **`tools/screenshots.sh`**.
```sh
tools/screenshots.sh all # macOS + (if full Xcode) iOS, iPadOS, tvOS → ./screenshots
tools/screenshots.sh macos # just macOS
OUT=~/Desktop/shots tools/screenshots.sh ios ipad tvos
PUNKTFUNK_SHOT_HERO=~/frame.png tools/screenshots.sh ios # real captured frame behind the hero
```
How it works: the app has a DEBUG-only **shot mode** (`Sources/PunktfunkClient/Screenshots/`).
Launched with `PUNKTFUNK_SHOT_SCENE=<name>` it renders **one** mock-populated screen full-bleed
(`ScreenshotHostView`) instead of `ContentView`, then the OS screenshots the *real, fully-rendered*
window — `screencapture` on macOS, `xcrun simctl io booted screenshot` on the Simulators. The five
scenes (`ShotScenes.all`): `01-stream` (the stream hero — a synthetic frame + the glass HUD, since
`StreamView` needs a live connection), `02-hosts`, `03-pair`, `04-trust`, `05-settings`. Mock data
is in `ShotMock`; nothing touches a host.
Output pixels are App Store Connect's required/largest sizes (Apple auto-derives the smaller ones):
`mac` 2880×1800 · `iphone-6.9` 1320×2868 (hero 2868×1320) · `ipad-13` 2064×2752 (hero 2752×2064) ·
`appletv` 1920×1080.
Why not `ImageRenderer` (the obvious offscreen route)? It can't rasterize this app's chrome —
`NavigationStack`, `Form`/`TabView`, and Liquid-Glass/`NSVisualEffect` materials all render black or
SwiftUI's "can't render" placeholder. Capturing the live window/Simulator avoids that entirely.
Requirements / gotchas:
- **macOS**: only the Swift toolchain is needed, **plus a one-time Screen Recording grant** for
your terminal (System Settings → Privacy & Security → Screen Recording) — without it
`screencapture -l` fails with "could not create image from window". (A no-permission fallback,
`PUNKTFUNK_SHOT_SELFCAPTURE=<dir>`, uses `cacheDisplay` — but it omits material blur and can't
read `ScrollView` content, so it's for quick checks, not submission.)
- **iOS/iPadOS/tvOS**: needs **full Xcode** (xcodebuild + Simulators), not just Command Line Tools,
and the matching device Simulators installed (iPhone 16 Pro Max, iPad Pro 13", Apple TV). Run it
on a full-Xcode Mac (e.g. the `macos-arm64` CI mini).
- The hero defaults to a synthetic synthwave frame — set `PUNKTFUNK_SHOT_HERO` to a real captured
frame for a production-quality lead screenshot.
**CI**: the `apple` workflow's **`screenshots`** job runs this on the `macos-arm64` runner on every
main push + manual dispatch (skipped on PRs), and attaches the result as a single zip artifact,
**`punktfunk-appstore-screenshots`** (download it from the run's Artifacts). It's best-effort and
isolated from the build/test job — a missing Simulator runtime or a runner without the Screen
Recording grant only drops that platform, never reds the build. (The macOS window capture in
particular needs that grant on the runner; the Simulator shots don't.)
## Notes for whoever picks this up next
1. **cbindgen import quirk** (the predicted "small compile fixes", now fixed): the