Skip to content

Photograph six screens of the app, and frame them for the store - #164

Merged
andiwand merged 5 commits into
mainfrom
claude/app-screenshots-metadata-ul8mq3
Aug 17, 2026
Merged

Photograph six screens of the app, and frame them for the store#164
andiwand merged 5 commits into
mainfrom
claude/app-screenshots-metadata-ul8mq3

Conversation

@andiwand

@andiwand andiwand commented Aug 16, 2026

Copy link
Copy Markdown
Member

The last of the stack: #165 (odrcore 6.7.1), #166 (the onboarding) and #167 (opening a document at its top) are all merged, so this is against main and is purely the screenshot work.

The store copy beside these is written, so it lives in git and the release uploads what is committed. A screenshot is not written, it is taken: it is worth what the build it came off is worth, and a picture of 1.38 sitting in git through 1.41 is a picture of an app nobody can install. So these are taken during the release run, from the build going out, framed there, and handed to deliver from there. Nothing is committed; .gitignore already said as much.

bundle exec fastlane ios screenshots

What a set is

Six pictures per device — the folder, a text document, a spreadsheet with its sheet tabs, an edit under way, a pdf and a Word file — on a 6.9" iPhone and a 13" iPad. Both, because an app that runs on both has to hand in both.

Nine of the eleven store locales are photographed in their own language. hi and sv are given the English set, because the app has no Hindi or Swedish UI either — copied in rather than left out, since a locale deliver uploads nothing for keeps whatever the last release put there.

The framing

A raw simulator capture is not what a store listing looks like. Each one is set on a coloured ground, in a phone-shaped frame, under two lines of copy in that locale's language, with a row of format chips beside it.

Nothing is drawn from an image file. Every part of it is a rounded rectangle, a plain rectangle or a line of text, so a new device size is a change to numbers rather than new artwork. fastlane/frames/frames.json holds the whole design and scripts/frame-screenshots.py draws it. The one asset is Nunito, under the OFL, which ships beside its licence.

scripts/frame-screenshots.py --locale en-US    # one locale, for a look

Nothing is tapped to get there

The way to a document is the document browser, which is Apple's UI in eleven languages and a different tap in each. So the app takes -ODRScreenshot text in Debug builds and puts itself on that screen: one launch per picture, and each waits for the view to say it has something to show rather than for a number of seconds. ScreenshotMode.swift is the whole of it, and it reads nil in a Release build.

The folder holds twenty-seven documents. Nine are the real ones the other screenshots open; the rest are copies of those under names of their own, never opened, because a folder of nine reads as an app nobody keeps anything in.

The documents are localized too, which is most of what a reader has to show. They are not committed: the lane runs scripts/make-screenshot-documents.py before it builds, because they are build output and nothing but a screenshot run opens them. It writes 81 files — nine formats in nine languages — byte for byte reproducible, so a rerun that changes no wording writes the same bytes. EXCLUDED_SOURCE_FILE_NAMES = "sample-*" keeps them out of the archive that ships.

The app builds without them, and CI compiles the screenshot target without them, which is all CI does with it — both checked.

The device is drawn to Apple's own dimensions

The phone and the tablet are drawn, not pasted from a mockup, so they are the shape of whatever was captured. What they are drawn to is measured rather than guessed: the bezel comes from the published body and display sizes (2.54mm on the phone, 8.44mm on the iPad), and the display corner, the Dynamic Island, and the side buttons' positions and lengths were measured off Apple's own bezel artwork. The metal is lit across the band's own width, which is what makes an edge read as turned rather than as a grey stripe.

Two departures from that artwork are deliberate: the device is cropped by the canvas, and it casts a shadow. Both would have to go if we ever moved to Apple's frames, whose licence forbids redistributing them anyway - so they are not in this repository and there is nothing to download at release time.

One real fix came out of it

Photographing the iPad turned up a bug that has nothing to do with screenshots: a pdf or a Word document opened part way down its own first page, with the heading cut off. That is #167, now merged.

The release run

A screenshots job beside build rather than behind it: it signs nothing, it just drives simulators. It runs on a dry run too, and archives both the framed set and the raw captures — the raw ones are where to look when a picture comes out wrong.

The lane picks the newest iOS the runner has and a simulator that runs it, because the same model name exists on several runtimes and a picture taken on last year's iOS shows last year's buttons. ODR_SCREENSHOT_IOS, ODR_SCREENSHOT_DEVICES and ODR_SCREENSHOT_LANGUAGES override each by hand.

scripts/store-screenshots.py owns what a full set is: which locales, which screens, and the sizes App Store Connect accepts. It reads the size off each PNG rather than trusting the simulator's name. The listing job then stages the set for both apps and uploads it with the text it already uploads.

Both apps get the same pictures. They are one app built twice, and the one thing that differs on screen — the banner Lite carries — is not in a screenshot either way. The set is taken with the Pro scheme, which links no ad sdk and so cannot raise a consent form in front of the camera.

Checked

  • a full English set was captured on both devices and framed: store-screenshots.py reports no problem against en-US, and names the other eight as missing, which is what an incomplete run should do
  • the six screens come out right on both devices — headings intact, the folder listing all twenty-seven, the search hit highlighted
  • every generated package is a valid ODF or OOXML zip, and the generator is idempotent
  • Fastfile parses, and the runtime and simulator resolution pick the right two out of real simctl list devices output
  • both workflows parse, and project.pbxproj reopens with the UI test target intact
  • the hand-off to snapshot is ours — OpenDocumentReaderUITests/Snapshots.swift, 115 lines — rather than fastlane's vendored 320-line helper, so scripts/format.sh now covers every Swift source here without an exception

Worth knowing before merge

  • Only English has been regenerated. The other eight languages keep the sheet they had — four quarters and twenty rows, against English's six months and forty. Each is internally consistent; they differ from each other until the rest follow.
  • The filler names are English in the other eight locales. They fall back the way a language with no samples of its own is photographed with the English set, so the German folder shows Meeting notes beside Quartalsbericht.
  • A full regeneration would rewrite the eight non-English .ods, whose figure columns are still at the old single width. Nothing else drifts.
  • The sample documents and the eleven headlines are worded by me, and did not get the translator-and-reviewer pass the store copy in Write the store copy ahead of the release, and translate the app again #162 got.
  • The first dry_run release run is the first look at all nine locales at once.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6de18f86bd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread OpenDocumentReader/DocumentViewController.swift
@andiwand andiwand changed the title Write the store copy before the release, not during it Take the store screenshots during the release, and upload them Aug 16, 2026
@andiwand
andiwand changed the base branch from main to sync-the-test-folder August 16, 2026 13:43
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from 6de18f8 to ab5d603 Compare August 16, 2026 15:09
@andiwand
andiwand force-pushed the sync-the-test-folder branch from 9caa01b to 1ddc055 Compare August 16, 2026 15:09
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from ab5d603 to d6323f4 Compare August 16, 2026 15:40
Base automatically changed from sync-the-test-folder to main August 16, 2026 15:49
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from d6323f4 to 35e717e Compare August 16, 2026 15:50
@andiwand
andiwand marked this pull request as draft August 16, 2026 16:02
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from 35e717e to 2f51daa Compare August 17, 2026 05:49
@andiwand andiwand changed the title Take the store screenshots during the release, and upload them Photograph six screens of the app, and frame them for the store Aug 17, 2026
@andiwand
andiwand changed the base branch from main to bump-odrcore-6.7.1 August 17, 2026 05:50
Base automatically changed from bump-odrcore-6.7.1 to main August 17, 2026 05:56
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch 2 times, most recently from 6992e5e to 1aa1d85 Compare August 17, 2026 09:28
@andiwand
andiwand marked this pull request as ready for review August 17, 2026 09:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9970480d0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread OpenDocumentReader/DocumentViewController.swift Outdated
@andiwand andiwand mentioned this pull request Aug 17, 2026
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from 48b2bab to 521b270 Compare August 17, 2026 09:58
@andiwand
andiwand changed the base branch from main to drop-the-onboarding August 17, 2026 09:58
@andiwand
andiwand force-pushed the drop-the-onboarding branch from 1c60470 to a627c84 Compare August 17, 2026 10:11
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from 521b270 to 92bef39 Compare August 17, 2026 10:12
Base automatically changed from drop-the-onboarding to main August 17, 2026 10:42
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from 92bef39 to d018cb0 Compare August 17, 2026 10:43
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from d018cb0 to f43a499 Compare August 17, 2026 10:45
@andiwand
andiwand changed the base branch from main to open-a-document-at-its-top August 17, 2026 10:45
@andiwand
andiwand force-pushed the open-a-document-at-its-top branch from a12787b to 9588596 Compare August 17, 2026 10:55
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from f43a499 to 1515784 Compare August 17, 2026 10:55
Base automatically changed from open-a-document-at-its-top to main August 17, 2026 11:03
The store copy beside these is written, so it lives in git and the
release uploads what is committed. A screenshot is not written, it is
taken: it is worth what the build it came off is worth. So these are
taken during the release run, from the build going out, framed there,
and handed to deliver from there. Nothing is committed.

Six pictures per device - the folder, a text document, a spreadsheet, an
edit under way, a pdf and a Word file - on a 6.9" iPhone and a 13" iPad,
in the nine languages the app speaks. The app takes -ODRScreenshot in
Debug builds and puts itself on the screen asked for, so nothing has to
be tapped through Apple's document browser in eleven languages.

The documents in them are written here too, in every language, and so is
the framing: a coloured ground, a phone drawn to the dimensions Apple
publishes for it, and a line of copy. Nothing comes from an image file
except the font.

One fix rode along, and it is not about screenshots: a pdf or a Word
document opened part way down its own first page on a wide screen, with
the heading cut off. The viewport was named before the web view had the
width it would keep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6tn8P25cBDmrA67zrhfwN
@andiwand
andiwand force-pushed the claude/app-screenshots-metadata-ul8mq3 branch from 1515784 to dcc4fc5 Compare August 17, 2026 11:09
andiwand and others added 4 commits August 17, 2026 13:31
Eighty-two files of build output, in a repository, kept in step with the
script that writes them by hand. The lane runs the script before it
builds instead.

Before the build and not during it: the target picks its resources up off
the disk as the build starts, so a file written by a build phase lands in
the next one.

Nothing else needs them. The app builds without them - only a screenshot
run opens one - and CI compiles the screenshot target without them too,
which is all it does with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6tn8P25cBDmrA67zrhfwN
Eight native reviewers, one per language, against the English. They came
back with 87 corrections: calques that read as broken sentences, dropped
clauses, wrong register in the contract, European Portuguese in the
Brazilian one, a European VAT label on a Brazilian invoice, and a missing
dotless i in Turkish.

Three of them were the same fault in every language rather than eight
separate slips. A clause of the risks paragraph - "and is being chased" -
had been dropped from five. The lowercase second sentence of the first
headline, which is deliberate in English, reads as a typo in French,
Russian and Polish. And "Billed to" was calqued on three invoices.

One was ours: the contacts column headed "Role" holds Design, Support,
Sales and Engineering, which are departments. Every language had
translated the mistake faithfully. It says Team now, and the three
languages that used a first-name-only word for a column of full names
say the full-name one.

Every language also gets the six months and forty rows English has, so
the spreadsheet is the same document everywhere. Their labels are the
reviewers' own, and no language repeats a row or a file name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6tn8P25cBDmrA67zrhfwN
Three hundred and twenty lines of theirs, kept byte for byte so it could
be replaced wholesale, and held out of the formatter for the same reason.
A hundred and fifteen of ours, which the formatter now covers like every
other source here.

What it does is the part we use: read the language and the region fastlane
left in its cache directory, put them on the app's arguments, and write
the screen back there under the name the release expects - including
taking "Clone 2 of" off a device name, which xcodebuild adds when it runs
several at once.

What went: the macOS, tvOS and watchOS branches, the landscape correction
for pictures we only ever take upright, a Swift 4 fallback, and sixty
lines waiting on the network activity indicator, which iOS stopped
drawing at 13 and which the test replaces with a marker of its own. It no
longer forwards launch arguments from the lane either, because the lane
sets none.

The lane now skips snapshot's helper version check. That check does not
read the helper - it refuses to start unless a file called
SnapshotHelper.swift exists somewhere and carries a version marker at the
bottom. The file was a token as much as a helper.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6tn8P25cBDmrA67zrhfwN
The framing script needs to know which locales there are and what size
a picture has to be, and it was reaching for that by reading the other
script off disk and running it. A plain import does the same thing, once
the file is named something Python can say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6tn8P25cBDmrA67zrhfwN
@andiwand
andiwand merged commit 73fa472 into main Aug 17, 2026
4 checks passed
@andiwand
andiwand deleted the claude/app-screenshots-metadata-ul8mq3 branch August 17, 2026 13:04
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