Photograph six screens of the app, and frame them for the store - #164
Conversation
There was a problem hiding this comment.
💡 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".
6de18f8 to
ab5d603
Compare
9caa01b to
1ddc055
Compare
ab5d603 to
d6323f4
Compare
d6323f4 to
35e717e
Compare
35e717e to
2f51daa
Compare
6992e5e to
1aa1d85
Compare
There was a problem hiding this comment.
💡 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".
48b2bab to
521b270
Compare
1c60470 to
a627c84
Compare
521b270 to
92bef39
Compare
92bef39 to
d018cb0
Compare
d018cb0 to
f43a499
Compare
a12787b to
9588596
Compare
f43a499 to
1515784
Compare
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
1515784 to
dcc4fc5
Compare
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
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
mainand 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
deliverfrom there. Nothing is committed;.gitignorealready said as much.bundle exec fastlane ios screenshotsWhat 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.
hiandsvare given the English set, because the app has no Hindi or Swedish UI either — copied in rather than left out, since a localedeliveruploads 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.jsonholds the whole design andscripts/frame-screenshots.pydraws 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 lookNothing 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 textin 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.swiftis the whole of it, and it readsnilin 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.pybefore 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
screenshotsjob besidebuildrather 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_DEVICESandODR_SCREENSHOT_LANGUAGESoverride each by hand.scripts/store-screenshots.pyowns 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. Thelistingjob 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
store-screenshots.pyreports no problem againsten-US, and names the other eight as missing, which is what an incomplete run should doFastfileparses, and the runtime and simulator resolution pick the right two out of realsimctl list devicesoutputproject.pbxprojreopens with the UI test target intactsnapshotis ours —OpenDocumentReaderUITests/Snapshots.swift, 115 lines — rather than fastlane's vendored 320-line helper, soscripts/format.shnow covers every Swift source here without an exceptionWorth knowing before merge
Meeting notesbesideQuartalsbericht..ods, whose figure columns are still at the old single width. Nothing else drifts.dry_runrelease run is the first look at all nine locales at once.🤖 Generated with Claude Code