Files
enricobuehler 32879f45bf
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
feat(apple): App Store screenshot harness + CI zip artifact
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>
2026-06-22 19:44:03 +02:00

58 lines
2.6 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// App Store screenshot harness device catalog.
//
// The harness captures the REAL running UI (not an offscreen ImageRenderer snapshot, which can't
// rasterize NavigationStack / Form / Liquid-Glass they come out black). The app is launched in
// "shot mode" (PUNKTFUNK_SHOT_SCENE=<name>, see ScreenshotHost) showing one mock-populated scene
// full-bleed, and the OS screenshots it: `xcrun simctl io booted screenshot` on the iOS/tvOS
// simulators (native pixels = the exact App Store size), `screencapture` for the mac window.
// tools/screenshots.sh drives it. DEBUG-only none of this ships in Release.
//
// This catalog records the target App Store sizes; on Apple platforms only the mac size is read
// at runtime (to size the capture window) the simulator IS the device, so iOS/tvOS pixels are
// whatever the booted device is.
#if DEBUG
import CoreGraphics
enum ShotOrientation { case natural, portrait, landscape }
/// A target App Store canvas: a natural-orientation pixel size + backing scale.
struct ShotDevice {
let id: String
let naturalWidth: Int
let naturalHeight: Int
let scale: CGFloat
func pixels(_ o: ShotOrientation) -> (w: Int, h: Int) {
let long = max(naturalWidth, naturalHeight)
let short = min(naturalWidth, naturalHeight)
switch o {
case .natural: return (naturalWidth, naturalHeight)
case .portrait: return (short, long)
case .landscape: return (long, short)
}
}
/// Logical point size (pixels / scale) used to size the mac capture window so that a
/// `screencapture` on a 2× display yields exactly `pixels(_:)`.
func points(_ o: ShotOrientation) -> CGSize {
let (w, h) = pixels(o)
return CGSize(width: CGFloat(w) / scale, height: CGFloat(h) / scale)
}
/// Mac: 2880×1800 (16:10 Retina) an accepted size; on a 1× display the window capture is
/// 1440×900, also accepted.
static let mac = ShotDevice(id: "mac", naturalWidth: 2880, naturalHeight: 1800, scale: 2)
/// iPhone 6.9" (required) for reference / the driver script's simulator choice.
static let iphone69 = ShotDevice(id: "iphone-6.9", naturalWidth: 1320, naturalHeight: 2868,
scale: 3)
/// iPad 13" (required).
static let ipad13 = ShotDevice(id: "ipad-13", naturalWidth: 2064, naturalHeight: 2752,
scale: 2)
/// Apple TV (always landscape).
static let appleTV = ShotDevice(id: "appletv", naturalWidth: 1920, naturalHeight: 1080,
scale: 1)
}
#endif