Skip to content

[FEAT] README hero screenshot generated from a seeded iOS simulator - #481

Merged
justin13888 merged 4 commits into
masterfrom
feat/readme-ios-screenshot
Sep 23, 2026
Merged

justin13888 merged 4 commits into
masterfrom
feat/readme-ios-screenshot

Conversation

@justin13888

@justin13888 justin13888 commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Description

This PR adds mise run screenshot-ios, which generates the README hero image (a real screenshot of the iOS Library timeline, filled with a curated CC0 photo set). The README now leads with that image, centred under the title as a <picture> that serves the light or dark variant to match the reader's GitHub theme.

Draft — waiting on the first Mac run. images/readme-hero-{light,dark}.png are not committed yet, because they can only be produced on macOS and this branch was built on Linux. Until they land, the README shows a broken image. The PNGs follow once the Mac run is reviewed and critiqued.

Pipeline

  1. xtask screenshot-seed runs anywhere and is unit-tested.
    • Fetches 42 CC0 photos (Unsplash's pre-2017 CC0 releases, mirrored on Wikimedia Commons) with curl. Each is verified against the SHA-256 pinned in xtask/screenshot/seed.toml and cached in target/screenshot/cache/. The first run downloads about 335 MB; later runs use the cache.
    • Normalizes every photo: rotated upright, long edge cut to 2048 px, RGB ICC profile kept, EXIF reduced to a capture date.
    • The output is byte-reproducible, and the task prints a digest of the set.
  2. mise-tasks/screenshot-ios runs on macOS.
    • Builds the app and erases a dedicated Capsule README simulator (iPhone 17 Pro, newest iOS 26.x).
    • Grants Photos access, imports the seed set, sets the timeline to 3 columns, and pins the canonical 9:41 status bar.
    • Captures light and dark with --mask=alpha once two consecutive frames match.
  3. xtask screenshot-compose runs anywhere and is unit-tested. It keeps Apple's screen mask and the capture's colour profile, adds a soft drop shadow, and writes a deterministic PNG on a transparent canvas.

Design decisions

  • Capture runs on a local Mac only; there is no CI job.
  • Photos are CC0 only, fetched and pinned rather than committed.
  • The set follows a "photographer's year" theme: a warm, cohesive palette with no identifiable faces.
  • Light and dark variants are served via <picture>.
  • The phone is shown bare, with rounded corners and a shadow, and no device bezel art.
  • The grid uses 3 columns, captured on an iPhone 17 Pro running iOS 26, and displayed at 360 px.
  • The PNG is left unoptimized, because the docs site and GitHub handle optimization.
  • The empty ## Screenshots TODO section is removed.

One deviation from the approved plan. Capture dates are stored as offsets from the run date, not as absolute timestamps. The app titles sections against the real clock ("Sat, September 12" in the current year; "September 12, 2025" otherwise), so absolute dates would drift into year-suffixed headers over time. The cost is that a run on a different day re-dates the headers. A future --anchor is rejected, and a run in early January warns when some sections would carry a year suffix.

Changed paths

Path What changed
xtask/src/screenshot.rs, xtask/src/screenshot/{manifest,seed,capture_exif,compose}.rs New seed and compose commands
xtask/src/main.rs Dispatches the two commands; initializes tracing (stderr, RUST_LOG, default info)
xtask/screenshot/seed.toml The 42-photo manifest: URL, SHA-256, source page, author, licence, day offset, time
xtask/Cargo.toml, Cargo.toml, Cargo.lock Adds image (jpeg and png only) as a workspace dependency; enables toml_edit's serde feature; adds dev-dependencies kamadak-exif and tempfile; sets opt-level = 3 for image, zune-jpeg, png and fdeflate in the dev profile. Lockfile changes only add packages; no existing package's version moves
capsule-docs/.../design/dependencies.md Adds a "Tooling image processing" row, scoped to build tooling
mise-tasks/screenshot-ios The macOS capture task
capsule-swift/README.md Adds "Regenerating the README Screenshot"
README.md, README.*.md (12) Hero <picture> after the H1; Screenshots section removed; translations regenerated
xtask/translations/readme/*.json (12) Drops the orphaned "Screenshots" heading entry

Validation (Linux, at 747e4959)

  • cargo nextest run -p xtask: 114 passed, including 32 new screenshot tests.
    • Manifest: validation, and the checked-in manifest must parse and have full 3-column rows.
    • Cache: fetch, hit, corrupt-refetch, pin rejection.
    • Normalize: orientation, resize, EXIF date, ICC filter, determinism, idempotent run, anchor changes only the dates.
    • Compose: canvas geometry, mask fallback, own-mask respect, shadow, ICC passthrough, determinism.
  • mise run format-check-rust, lint-check-rust, architecture-check, license-check, translate-readme-check, i18n-check, i18n-guard, check-md: all pass. Clippy was also run pedantic over xtask's test targets: clean.
  • shellcheck mise-tasks/screenshot-ios: clean. The script avoids bash-4-only syntax, since macOS ships bash 3.2.
  • Real seed run, twice: the first run did three live fetches plus 39 from cache; the second was cache-only. Both produced digest 1a1e6544…71d17 (anchor 2026-09-23). The run after the review fixes produced the same digest.
  • Compose on a stand-in capture, twice: byte-identical output.
  • On Linux, mise run screenshot-ios exits 1 with a clear message, as designed.
  • cargo deny check advisories reports 14 advisories. All are pre-existing and in crates this PR does not touch (libcrux, AES-GCM, h2, rustls via jsonwebtoken, proc-macro-error2); none of the added crates appears in any advisory tree. The advisory check is not part of the repo's gate map.

CI at 747e4959

Passed: Rust tests, all four Rust cross builds, iOS "Build & test Capsule.app" (macos-26), Markdown, Docs, Vision, and commit lint.

Failed, all pre-existing. The same ci.yml jobs also fail on master at 070e84d2 (run 33572237092), each for an environment reason this PR does not touch:

Job Cause
Rust (fmt + clippy + build) fmt, clippy and the full build all pass, including xtask and the new crates. build-check-wasm then fails because the runner lacks the wasm32-unknown-unknown target (E0463: can't find crate for core)
Web bun install: "lockfile had changes, but lockfile is frozen". This PR changes no web files
Kotlin; Build Capsule.apk Android SDK setup: "Failed to find package 'tools'". This PR changes no Kotlin or Android files
required Aggregates the failures above

Coverage gaps

  • The simulator orchestration has never run. It is macOS-only and has no automated test. Unproven until the first Mac run: xcodebuild product path, runtime and device-type resolution, simctl privacy/addmedia/status_bar/io --mask=alpha, and whether simctl spawn … defaults write … -int 3 reaches the app's defaults (if it doesn't, the grid stays at 5 columns).
  • The settle heuristic is a guess. It waits 6 s minimum, then takes the first pair of identical frames, with a 30 s cap. If the grid sits still before its thumbnails load, it could capture early.
  • Cross-architecture pixels are unproven. Seed bytes are proven reproducible on one x86_64 host only. Lanczos output could differ on arm64, which would change the digest but not the look.
  • The downloader is not unit-tested. CurlFetcher is exercised only by the live run above; the cache logic around it is tested through a fake fetcher.
  • Nothing checks that the README's images exist.

Open questions for the reviewer

  1. The alt text stays English in all 12 translated READMEs, because translate-readme passes HTML through verbatim. Is that acceptable?
  2. Every regeneration re-dates the visible headers, so it is a visible content diff. Should regeneration be limited to deliberate refreshes?
  3. The opt-level = 3 overrides add some compile time to check-rust runs that build xtask. Is that acceptable?

Related Issues

N/A

Contributor Checklist

  • I agree to the Contributor License Agreement for this and future contributions.
  • My code follows the project's style guidelines according to CONTRIBUTING.md.
  • Tests pass
  • No sensitive info / secrets
  • Docs updated if needed

Adds the platform-independent halves of the README hero screenshot
pipeline:

- `xtask screenshot-seed` fetches the CC0 photos pinned by SHA-256 in
  `xtask/screenshot/seed.toml` into a verified cache. It then normalizes
  each one (upright, long edge 2048 px, RGB ICC kept, EXIF reduced to a
  capture date) into a byte-reproducible seed set, and prints its digest.
  Capture dates are relative to an anchor day, so section headers read as
  recent dates of the current year.
- `xtask screenshot-compose` keeps the capture's own screen mask, adds a
  soft drop shadow, pads onto a transparent canvas, and writes a
  deterministic PNG.

`image` (jpeg + png only) is admitted as a build-tooling dependency;
product media stays with Rawshift.
`mise run screenshot-ios` (macOS only) seeds the photos, builds the app,
and erases a dedicated "Capsule README" simulator (iPhone 17 Pro, newest
iOS 26.x). It then grants Photos access, imports the seed set, sets the
timeline to 3 columns, and pins the canonical 9:41 status bar. Light and
dark are each captured once the screen is stable, using Apple's alpha
screen mask, and composited into images/readme-hero-{light,dark}.png.

Each run starts from an erased simulator, so re-running is safe. Devices
left behind by an older runtime are deleted. On other hosts the task
fails with a message instead of skipping silently, because it is only
ever invoked explicitly.
A centred <picture> directly under the title shows the light or dark
hero to match the reader's GitHub theme, at 360 px wide. The empty
"Screenshots" TODO section is removed. The translated READMEs are
regenerated to match, and each locale's translation data drops the
orphaned heading.

The images come from `mise run screenshot-ios`.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Deploying capsule with  Cloudflare Pages  Cloudflare Pages

Latest commit: ea72d92
Status:⚡️  Build in progress...

View logs

The <picture> at the top of each README references
images/readme-hero-{light,dark}.png, but the files were never
committed, so the hero rendered as a broken image. These are the
1536x2952 captures produced by `mise run screenshot-ios`.
@justin13888
justin13888 marked this pull request as ready for review September 23, 2026 18:01
@justin13888
justin13888 merged commit f436de8 into master Sep 23, 2026
13 of 18 checks passed
@justin13888
justin13888 deleted the feat/readme-ios-screenshot branch September 23, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant