Localized screenshots everywhere: the 128-capture matrix and the background parallel generator - #96
Merged
Merged
Conversation
The language screenshot matrix is the audit the app-i18n wave promised, and its first run delivered: the diff-mode picker (Rendered Diff / Source Diff / Result), the Inline / Side by Side layout picker, the theme and content-width card descriptors, and the review-verdict rows all rendered English under every other language — computed display vars and rawValue-driven pickers, the classes the check-strings gate cannot see. All routed through String(localized:) now; 13 new keys translated across the seven locales (617 total). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
ax.swift gains menuitem (press a menu item by title anywhere in the menu bar — top-level menu names are system-localized, item titles resolve from loc/), menukey (press by keyboard equivalent: Settings ⌘, and Quit ⌘Q have no app-owned titles), sidebar-state (is there a native list outside the web area?), id (press by AXIdentifier — the Open panel's OKButton; plain Return never reaches the panel's bridged content), and menulist for discovery. list now prints identifiers. pkey.swift accepts combined modifiers (cmd shift opt ctrl). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
--lang all iterates English + the seven locales; localized captures land in out/<site-code>/ mirroring site/img/. Scenes act on controls by localized accessibility title, resolved at run time from loc/<lang>.lproj (app strings) and SwiftUI's own Localizable.loctable (the system sidebar toggle) via loc-lookup.py — never hardcoded. --lang pairs -AppleLanguages with -AppleLocale so dates and numbers render natively. The demo Location now arrives through the app's own Open panel with pid-targeted keys and is VERIFIED via AX before any scene runs. open -a is banned: with /Applications and dist alive under one bundle id, Launch Services sometimes spawned a third instance for the folder and the scene captured a folderless window. Launch failures now skip the capture loudly, and quit_app sweeps stray dist instances. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
The 14 localized pages reference /img/<code>/app-*.png; the checker now fails when a page serves another language's app screenshots or references an image missing on disk (a bad path silently 404s to alt text). Image promotion follows once the capture matrix lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Josh called the half-translated sidebar: English Open Files/Locations above a translated プルリクエスト. The landmark convention is dropped — Open Files, Locations, and Recents are now real translations in all seven locales (Apple's own Finder vocabulary, verified against the system's loctables: 場所, Orte, Emplacements, Ubicaciones, Locaties, Localizações, 位置…), Pull Requests keeps its defensible split (CJK translated, European loanword). Site prose follows: ~40 section references per locale renamed, singular Location unified, old English(gloss)headings flattened, in-app prose values that named the sections mid-sentence updated. UI command/setting labels the site cites verbatim stay English per their separate convention. Also repairs three German alt attributes that ASCII closing quotes had been silently truncating. CaptureChrome (-pm.captureChrome, generator-only): windows draw active chrome — colored traffic lights, accent selection — without the app ever taking focus, so the screenshot generator can drive many backgrounded instances in parallel without stealing the machine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Josh asked for a matrix that doesn't take an hour or the machine. Scenes now drive instances entirely through pid-targeted channels: AX row selection on the sidebar outline (rows discard posted clicks and carry no AXPress), pid-addressed AppleEvents for the demo Location and pullmark:// links (open -a resolved by bundle id and sometimes spawned a third instance for the document), posted keys and clipboard-free unicode typing, and a -pm.captureChrome drive channel for the one page interaction with no AX path (the edit scene's block reveal — the click listener is delegated). CaptureChrome makes it all photograph correctly: windows draw active chrome (colored traffic lights, accent selection, key-window nomination so a never-activated app stops dropping key events) without the app ever taking focus. Languages run in parallel — one instance per language on cascaded frames — so the full 128-capture matrix lands in one language's wall clock (~5 minutes) while the machine stays usable. No Launch Services registration remains, so there is nothing to unregister. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Hovering the capture instances mid-run turned up a class the gate cannot see: ternaries and custom view parameters feeding .help, titles, and subtitles — nav-history tooltips, checks summaries, comment badges, the What's New sheet titles, the View menu's Inline/Side-by-Side flip, Open Quickly row titles, lightbox buttons, the Compare Revisions sheet, share tooltips, remote-doc reasons, and friends. All routed through String(localized:) with whole keys (concatenations folded, English plural hacks split into real singular/plural pairs). CI check durations were hand-built "3m 32s" strings — now DateComponentsFormatter in the launch language (ja: 3分32秒), demo fixtures included. 62 new keys, translated across the seven locales: 679 total, gate green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Two captures from the first parallel run came out wrong after the human (understandably) clicked around a capture window mid-run: the instance activates, input lands inside, and the scene photographs whatever resulted. Every drive step that shapes a capture now ends with '|| return 1' (set -e is suppressed inside functions called in a condition, so failures were silent), and the pr scene selects the PR row explicitly instead of trusting launch selection — the folder delivery can steal it for the folder's README even without help. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
An opus review pass over all 128 captures surfaced four systemic classes, each now closed: - Gray traffic lights on ~60% of captures: the repeated become-key blessing regressed what the one-shot version had right — AppKit reconciles a key-claim it knows is false and captures raced the flicker. Back to once per window (timer only discovers new windows). - Missing blame gutters: pm.blame is sticky in the defaults domain SHARED across instances and runs, and blind toggle presses turned it off for whichever instance pressed second. The scene now ENSURES the checkbox state (ax.swift setcheck), and the generator snapshots and restores the flag so runs stop leaking state into the human's own app. - Blank content panes (3 captures): WebKit under parallel load hadn't painted yet. capture() now runs blankcheck.swift (content-region stddev) and retries before failing the scene loudly. - Wrong Settings tab (es themes): pressing Appearance before the Settings window appeared matched the MAIN window's same-titled toolbar menu. The scene waits for the Settings window and presses inside it only (ax.swift presswin skips the 1052-wide capture window). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Eight captures in the fixed matrix still came up blank through the retry loop: WebKit suspends a fully occluded window's WebContent process, sometimes before first paint, and the parallel workers' cascaded frames overlap. With -pm.captureChrome the web view now opts out of page-visibility process suppression, so backgrounded instances render no matter what covers them. Normal runs are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Three more from the capture reviews: the titlebar's ' · editing' marker was hardcoded English (now a localized key); the PR state chip shared the "Open" key with the Open ACTION buttons, so German conjugated it wrong (Öffnen) and ja carried the 開く nit — split into pr-status-open with the state reading per locale (Offen, オープン, 开启, Ouverte…); and demo-session paths now display as ~/Documents/PullMark Demo instead of the raw /var/folders temp dir in titlebars, tooltips, and Open Quickly rows — three reviewers flagged the temp path as unfit for published screenshots. 681 keys, gate green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
The real mechanism behind every lost blame gutter: demo mode uses an isolated defaults suite WIPED AT STARTUP, the parallel generator launches a fresh instance per scene, and with one shared suite name every launch nuked every live instance's state — @AppStorage observed the wipe and reset blame mid-scene. Two rounds of toggle choreography (blind press, ensure-state) lost to that and were removed. Now: the generator pins -pm.blame per scene through the argument domain (per-process, wipe-proof — the appearance pin already proved the suite honors it), demo suites are named per-pid so instances can't touch each other at all (with a liveness-checked janitor for dead instances' plists), the themes scene confirms the tab switch by window title and re-presses until it lands, capture() retries gray lights as well as blank panes with a longer budget, and nl/es/pt-BR get real Light/Dark values (Licht/Donker, Claro/Oscuro, Claro/Escuro) instead of the identity English the first translation wave left. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Full-parallel runs flake at ~5% (WebKit under eightfold load) and the same combos succeed solo essentially always — so instead of chasing zero-flake parallelism, failed captures re-run sequentially at the end of the run. Retry combos are consumed before re-invoking (each child run clears the bookkeeping at exit), and PM_GEN_FIXUP guards recursion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
Every page now serves screenshots in its own language: 112 localized
captures in site/img/{zh,ja,fr,de,nl,es,pt}/ plus the 16 English ones
refreshed from the same build — all from one generator run, every
capture machine-verified (content painted, active chrome) and the
risk-concentrated scenes human-reviewed per locale. Cache-busters
bumped on all sixteen referencing pages; check-site-i18n is fully
green for the first time since the localized refs landed (96 pages,
screenshot existence and per-language serving verified).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
demoDefaultsSuiteIsNotTheRealDomain pinned the old shared suite name (hasSuffix .demo); it now asserts the per-pid contract — the demo prefix plus a numeric process id — and documents why the suites went per-process. (The local run that should have caught this piped make test through head, which masked the failing exit code — CI caught it.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every localized page now serves screenshots in its own language — 8 scenes × light/dark × English + 7 locales, 128 captures, regenerated in ~6 minutes by a generator that no longer takes over the machine. Along the way, live auditing of the capture instances turned into the deepest localization QA pass the app has had: 78 new keys (681 total), a locale-aware duration formatter, and a fully retranslated sidebar.
The generator, rebuilt (Josh's ask: "not an hour, and run it in the background")
select-row/disclose; SwiftUI rows discard posted clicks), pid-addressed AppleEvents for the demo Location andpullmark://capture/…links (open -aresolved by bundle id and once handed the folder to a surprise third instance), posted keys and clipboard-free typing, and a capture-only URL channel for the one interaction with no AX path (the edit scene's block reveal).-pm.captureChromemakes windows draw genuinely active chrome without focus (realmakeKey()— a background app is allowed; two rounds of faked notifications raced AppKit into gray traffic lights and are memorialized in the commit log).blankcheck(content actually painted) andlightcheck(active chrome) with in-place retries, and still-failing combos re-run solo in an automatic fix-up pass. Machine-checked ×128, plus per-locale human review of the risk-concentrated scenes.Localization QA the live audit caught (all fixed + translated ×7)
English(gloss)headings flattened."3m 32s"strings — nowDateComponentsFormatterin the launch language (ja: 3分32秒), demo fixtures included..help, titles, and subtitles (nav-history tooltips, checks summaries, comment badges, What's New titles, the Inline/Side-by-Side flip, Open Quickly rows, lightbox buttons, the Compare Revisions sheet, share tooltips, remote-doc reasons — 62 keys).· editinglocalized, and nl/es/pt-BR got real Light/Dark values instead of identity English.~/Documents/PullMark Demoinstead of raw/var/folders/…in titlebars, tooltips, and Open Quickly.Samples
French rendered-diff blame (gutter, localized chrome):
Dutch Settings → Appearance (theme cards with localized descriptors, Licht/Donker):
Japanese edit mode, dark (one revealed source block, localized 編集中 marker, clean demo path):
Verified
make test: 617 tests +strings OK: 681 keysacross all seven locales.check-site-i18n.py: fully green for the first time — 96 pages, hreflang matrix, per-language screenshot serving, and image existence all verified.🤖 Generated with Claude Code
https://claude.ai/code/session_01JcXC4FCipx7zTVNRVbTjq6