Repository navigation
Developer guide: a figure harness that renders themed app UI - #5734
Conversation
Completes the half of the screenshot work #5668 deliberately left out. That PR shipped the font fix and the tolerance-aware comparer but not the theming machinery, because its 24 figures are schematic layout diagrams -- green labelled blocks showing where a layout manager puts things -- and rendering those under a real theme would bury the teaching point under component chrome. They keep their own pass and their hand-rolled styling. GuideFigure is a pure factory: it builds a Form and never shows it, so the renderer owns the window size, the theme and the appearance and one figure renders on several devices in both appearances. FigureDevice takes its geometry from devices.json rather than inventing it, naming the entry each profile comes from, and renders at the device's logical resolution with no skin -- a skin would frame every picture in a bezel and, worse, set isIOS in JavaSEPort.loadTrueTypeFont, which resolves native: fonts against host-installed faces and makes the output depend on the machine. The density is read once during Display.init and cached on the implementation, so a process cannot render two profiles; each pass is its own JVM, driven by three exec executions. Dark mode is checked rather than trusted. Setting it without UIManager.refreshTheme() leaves styles cached at their light values and every dark frame renders light -- a failure invisible in review that repeats across every figure -- so the renderer refreshes, then asserts Display agrees and that the resulting background is actually dark. The gate no longer hardcodes 24. Generated figures live in img/generated/, which makes the directory the manifest: a golden that stops being produced is now an error instead of a silent gap, verified by removing one and watching the comparer fail. The native themes are compiled before the framework build, because the antrun copy that puts them in the javase jar is failonerror=false and otherwise fails open. Two real consumers rather than unexercised infrastructure. The Components chapter gets an iOS/Android pair, retiring a caption claiming iOS draws no border on a button, and the native themes chapter's "Light and dark mode" section gets the light/dark pair it had no visual evidence for at all. Neither figure uses a FontImage glyph, whose antialiased edges cannot be byte-reproduced across platforms, so neither needs a tolerance sidecar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f8f828041f
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The themed figures render against the compiled native themes, which makes those themes an input to this guide the same way the build hint sources already are -- and neither on.pull_request.paths nor the paths-filter mentioned them. A PR editing native-themes/<name>/theme.css, the CSS compiler that renders it, or build-native-themes.sh would have changed what the figures look like without this workflow running at all, leaving the committed images stale until some later docs or release build failed against them. Added to both lists, because they serve different purposes and the file already carries two comments about exactly this: the trigger starts the workflow, and the figure comparison step is separately gated on the docs filter, so an input present in only one of them still skips the check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Cloudflare Preview
|
…amples The check/radio, MultiButton and SpanLabel figures were 320x480 captures from the HVGA era. They now render through the figure harness under Android Material at a current phone profile. The samples move with them. Each figure's build() carries the exact tagged region the chapter used to include from a snippet fixture, and the chapter now includes it from the figure class, so the listing beside a picture is the code that drew it. A compile-only fixture and a screenshot are two sources for one example, and nothing keeps them in step -- which is how the chapter came to caption an iOS button render with a claim about borders that the sample beside it never demonstrated. One canonical variant each, on Android Material, because the subject is the component rather than the platform. Android is the right single choice: these render through the port's bundled Roboto, which is what Material 3 specifies, while an iOS Modern render comes out in Roboto rather than SF and is typographically wrong -- worth having only where the platform difference is the point, as in the button pair. GuideFigure's contract now says a figure MAY show its form, which these samples do because that is what a reader would write. The renderer prepares the returned form at the target geometry either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s them com.codename1.ui's package documentation embeds 36 guide screenshots as absolute URLs under /img/developer-guide/. Nothing in this repository produces that path: scripts/website/build.sh rsyncs docs/developer-guide/ into the site's static/developer-guide/, preserving subdirectories, so these files are served from /developer-guide/img/ instead. The URLs are rewritten to that shape and each one is now checked to resolve to a file that ships. One of them was newly broken by this branch rather than merely misspelt. components-button.png is deleted by the figure harness change -- its caption claimed iOS draws no border on a button, which Liquid Glass made false -- and the javadoc still pointed at it. It now takes the iOS render of the pair that replaced it. The three figures this PR regenerates moved under img/generated/ and their URLs move with them. Because the URLs really are derived from the file layout, a comment beside the package declaration records that, so the next move under docs/developer-guide/img knows it has to bring them along. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… tolerance CI rendered all three differently from this Mac. Not a flake and not a docs bug: the differing pixels fall in narrow column runs exactly the width of an icon, at the icon positions, and every text column is byte-identical, with both renders the same size. Java2D rasterizes the same glyph from the same bundled font at the same size slightly differently on macOS and Linux. The three figures added alongside these that carry no FontImage glyph reproduced byte-for-byte, which is the control. Measured: 732/134561 pixels (0.544%) for the check and radio figure, 460/208181 (0.221%) for MultiButton, 368/129244 (0.285%) for SpanLabel, worst channel delta 134 to 136. The committed images are now the CI runner's own output, so the gate that actually runs compares byte-for-byte and the sidecar budget only covers regenerating a figure on a developer's Mac. Each budget is set just above its own measurement rather than one loose number shared by all three, and maxChannelDelta stays at the 160 the existing sidecar uses -- clear of the measured worst, and still failing a solid overwrite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 181 screenshots: 181 matched. |
|
Compared 149 screenshots: 149 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 160 screenshots: 160 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 144 screenshots: 144 matched. |
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
Compared 217 screenshots: 217 matched. |
Takes the harness from two hand-written consumers to the whole set of figures that have a code sample beside them. 150 such pairs exist across the guide; 64 now render, and the remainder are recorded below rather than quietly dropped. Every one of these figures is generated from the snippet the chapter already printed: the sample moves into the figure's build(), still inside its original tag, and the chapter includes it from there. The listing beside a picture is therefore the code that drew it, and the compile-only fixture that used to hold that code is retired -- 49 of them here. A fixture and a screenshot are two sources for one example with nothing keeping them in step, which is how a caption came to describe an iOS button render that the sample beside it never demonstrated. What did not convert, and why: - 58 samples build no Form at all; they are fragments that add to an existing one, so there is nothing to photograph. - 14 do not compile once lifted out of their fixture, because they lean on helpers the fixture declared outside the tagged region. - 9 render, but render empty: a bare title bar, a lone microphone icon, a form whose content arrives from the network or a gesture. Their existing screenshots show real content, so replacing them with a blank phone would be a regression. The degenerate-render check that caught them looks for a near-uniform image rather than trusting that a successful render is a useful one. - 3 throw on the way up for the same reason -- a null toolbar, a null image. Two harness changes fall out of running it at this scale. The renderer now collects every failure and reports them together instead of stopping at the first, which turns "fix one, rediscover the next" into a single list. And the appearance assertion now checks only the dark direction: a dark render that silently comes out light is the expensive failure, while a figure is perfectly entitled to a dark background of its own in light mode, as the image-masking figure's red form is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c55684689c
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
23 of the converted figures came out byte-identical to another figure. The cause is one shape of sample: it does not build the state the caption describes, it ATTACHES it -- a dialog opened from a button listener, a browser callback fired by a link, a menu named but never opened, a zoom applied by a gesture. Rendering that offscreen photographs the launch form, so "with tint" and "with green tint" produced the same picture, as did the three toolbar animation states, the before and after of a browser callback, and the portrait and landscape multiline tables. None of the existing checks could see it. Each figure rendered without throwing, was not blank, and matched its own committed copy, so the render and the comparison both passed while the guide showed one picture under two captions. The degenerate-render check I added looks for a near-uniform image, which these are not. So the comparer now rejects byte-identical figures outright. Two guide figures that are the same image mean at least one is not showing what its caption says, and the message names the whole clashing group. Verified it bites by copying one figure over another and watching the run fail. The 23 go back to their previous screenshots, with their samples restored to the snippet fixtures they came from -- an old screenshot that shows the right thing beats a new one that does not. Producing them honestly needs the harness to drive the interaction before painting, which is a piece of work in its own right rather than something to bolt on here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The byte-identical check catches a launch form only when two of them collide. These eight did not collide with anything, so they passed every gate while showing the reader something other than the caption: a "Blur Dialog" that is a form with a Blur button, a "Popup Dialog" that is a form with a Show button, an AutoComplete "with pictures" whose field is empty because no one typed, a capture-photo figure that is a lone shutter icon, three ImageViewer figures whose image never loads offscreen, and a PWA add-app-banner figure showing an unrelated before-unload form. Found by looking for the shape rather than by eye: figures whose sample attaches an interaction -- a listener, a Dialog, a URLImage -- instead of building the state, then inspecting that shortlist. All eight go back to their previous screenshots with their samples restored to the fixtures. That leaves 38 figures whose picture is exactly what its printed sample builds, and the byte-identical gate to stop the collision case coming back. The rest need a harness that can drive an interaction and wait for it before painting, which is its own piece of work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58458cfc45
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…nistic Two of these would have broken the byte-exact gate on their own, without anyone touching the code. The localization, picker and calendar figures render the current date, so the committed golden stops matching the moment the day rolls over. And the figure JVM pinned display scaling but not Locale.getDefault() or the time zone, which JavaSE's L10NManager is built from -- the committed localization figure carried Israeli currency and date formatting because that is where it was generated, and no other machine could reproduce it. The locale and time zone are now pinned to US and UTC before Display.init, alongside retinaScale, because the implementation reads them as it comes up. That is worth doing for every figure rather than only the visibly dated ones: number and date formatting leak into more places than it looks. Pinning cannot save a figure whose SUBJECT is the current date, so those three go back to their previous screenshots, along with three more the same review identified: both BrowserComponent figures, whose native peer is not rasterized by an offscreen Form paint and which therefore showed an empty viewport where the caption promised a website, and the SliderBridge download figure, which showed a zero-value slider because nobody pressed download. Verified the result is reproducible rather than assuming it: two consecutive full runs now produce byte-identical output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6871f83d75
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…URLs The figures are rendered by the framework itself, so a change to how a component paints, how a layout measures, or how the JavaSE port rasterizes changes every generated PNG without touching docs/ at all. The workflow did not trigger on any of that, and the comparison is byte-exact, so the committed figures would have gone stale silently until some later docs or release build failed on them. CodenameOne, Ports/JavaSE and the two Maven modules join the trigger and the paths-filter -- both lists, since the comparison step is gated separately from the trigger. Nine javadoc image URLs broke in the previous commit: the images moved under img/generated/ and the URLs in com.codename1.ui's package documentation did not move with them. All 37 are now verified to resolve to a file that ships, which is how the earlier round should have been checked rather than by reading the diff. The Hi World figure kept a caption pointing at a blue iOS status bar that the render no longer contains. Rendering it on the iOS profile does not bring the bar back either: it came from an old iOS skin, and the modern theme does not paint one. The caption was stale iOS-7-era guidance, so the figure keeps the iOS profile its caption talks about and the caption now describes what is actually in the picture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0bbf30196d
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ring The crop was reading where a child was LAID OUT, and prepare() sizes the content pane to the whole device, so a form whose child sits in BorderLayout.CENTER had that child stretched to fill it. Every such figure reported the full height and cropped nothing: the table, CSV, filesystem and tree figures were 409x917 with most of the page blank. Reading what the content PREFERS instead takes them to 111-218 pixels. The six that go back to their old screenshots were found by measuring rather than by grepping for suspicious API calls, which is what the last two rounds did and is why they kept missing cases. Comparing each figure against the image it replaces exposed the ones that render a component in its resting state when the caption describes a state someone has to reach: a Tree with every branch collapsed, a FileSystemTree showing only "home", an Accordion showing three collapsed items, an AutoComplete with an empty field and no suggestions, a CodeEditor whose backend initializes on a later EDT turn so the editor area is simply blank, and a ScaleImageLabel that draws placeholder triangles because its images are not there. Note the measurement needed reading, not thresholding: it also flagged figures that are correct and merely shorter now that the crop works, so every flagged figure was looked at before being kept or reverted. That leaves 26. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a81507a580
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The previous crop fix traded one wrong reading for another. Preferring the laid-out height cropped nothing for a child stretched by BorderLayout.CENTER; preferring the declared height cropped the Hi World figure to a 26-pixel sliver, because a component that paints itself declares no intrinsic size and reports zero while legitimately filling the viewport. Both shapes now work: a declared preferred height is used when there is one, and a component that declares none is taken at the height it was given. A third shape turned up while checking that fix. A form with an empty content pane -- the toolbar figures, whose content is entirely in the title area -- fell back to the content pane's stretched height and produced a toolbar with a blank page beneath it. It now crops just below the title area: 919 pixels down to 74, which is the toolbar and nothing else. autosize goes back to its old screenshot. It renders three purple bars with none of the six strings whose different automatic font sizes are the entire point of the figure. I had passed it in the previous round by looking at a montage scaled too small to see that the text was missing, which is the same mistake in a different form: the check has to be able to fail. That leaves 25. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ainst The build failed on 17 figures: the goldens were rendered on a Mac and the runner does not reproduce them. Two separate causes, and only one of them is the familiar glyph difference. Fourteen differ in pixels only, between 0.08% and 1.22% of the image, in the same antialiased glyph edges as before. Those take measured tolerance sidecars, and since the committed image is now the runner's own output the budget covers regenerating a figure locally rather than the gate itself. Seven differ in HEIGHT, by two to five pixels. That one is mine: the crop is computed from the laid-out content, text component heights come from font metrics, and Java2D rounds those differently on the two platforms. No tolerance sidecar covers a size change, and it should not -- a different size is a different picture. So the size check now explains at the point of failure why a locally regenerated figure can differ, and says not to commit the local render to silence it, which would only move the failure back to CI. Verified both directions: the CI artifact now matches the committed set byte-for-byte, and a local regeneration lands within the sidecars for every figure that differs only in pixels. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c79faf1057
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The toolbar sample calls Toolbar.setGlobalToolbar(true), and every figure is built in the same process, so the nine figures registered after it were given a toolbar their own samples never asked for. The output depended on the order of the manifest, which is not a property any of these figures should have. The renderer now snapshots and restores that flag around each build, rather than patching the one sample, because the next global left set would repeat this with no symptom other than a figure quietly changing when an unrelated entry moves. Eight figures change as a result: badge-floating-button, floatinghint, signature2, csv-parsing, glasspane and the three fontimage figures. Their committed goldens are stale until the runner produces the new ones, which is where the goldens have to come from -- rendering them here and committing a Mac's output is what turned the build red last time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6faa04a003
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ComponentGroup does nothing unless the theme sets ComponentGroupBool, which the iOS theme does by default and Android Material does not. Rendered on Android the sample came out as plain separate rows -- the opposite of the grouping the section exists to show. It now renders on the iOS profile, where the checkboxes form one connected list and the radios a button bar. The sidecars added in the previous commit all carried maxChannelDelta=255, which is the full range of a channel and therefore no bound at all: only the area budget was actually constraining anything, so a recolour confined to a small part of a mostly empty figure could have passed. Each sidecar now takes its own measured worst delta plus headroom -- 54, 80, 84, 88, 123, 158, 160, 165 -- and stays at 255 only for the three figures that genuinely flip a pixel between extremes, where the measurement leaves no room below it. The eight figures affected by the global-toolbar leak are baselined from the runner's output for that fix, rather than from a render on this machine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6fed8f112d
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ot show Three came from review: the floating hint figure needs text in the first field for the comparison its caption draws, the share button figure needs the share sheet open, and the signature figure needs the drawing editor open with its Cancel, Reset and Save controls. All three render the component at rest. Two more came from finally doing the check I should have run at the start: reading every remaining figure's caption against its picture. Both toggle button figures are captioned "toggle button converted sample", and the modern themes do not style a toggle button at all -- the originals came from the legacy theme. Rendering them on the iOS profile changes nothing, which I verified rather than assumed, so the picture is a plain list of checkboxes under a caption about buttons. The ComponentGroup variant does show grouping on iOS, but its caption is about toggle buttons too, so it goes with it. That the caption audit found two figures my visual pass had approved is the point: I was checking whether a picture looked sensible, not whether it showed what the prose beside it claims. That leaves 20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5c9149780b
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… them com.codename1.ui's package documentation carries 37 absolute URLs to guide figures, and those URLs are derived from the file layout, because the site build rsyncs docs/developer-guide/ to /developer-guide/. Moving a figure breaks them with no symptom: nothing in the docs checks reads a Java file, and javadoc renders a missing image without complaint. They have now drifted three times in this branch -- once when figures moved into img/generated/, once when nine more followed, and again when reverting figures moved two of them back out while their URLs stayed behind. Two were still pointing at img/generated/components-signature2.png and img/generated/components-floatinghint.png, which no longer exist. Fixing those two by hand a fourth time is not a fix, so check-guide-links.py now walks CodenameOne/src for these URLs and fails when one names a file that does not ship. Verified it bites by pointing a URL at a file that is not there and watching it exit 1 -- the first probe was worthless because the file I picked happened to exist at the path I invented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b9d4cc9c7a
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…OS one The renderer refuses to run when the platform name is "ios", on the grounds that a skin puts the host's installed fonts back into the output. That check is too narrow: an Android skin reports "and" and sails through it, while still having decided platform name, fonts, geometry and device class before any figure is built. The ios pass could then render the iOS theme against Android simulator state and write it to the same golden file, so the figures would depend on whoever launched the generator -- a shell, an IDE run configuration, an inherited environment. JavaSEPort.hasSkins() is "skin != null || dskin != null", and every skin path, the stored preference included, sits behind it. Clearing both properties before Display.init is therefore what actually guarantees no skin loads, and it belongs beside the locale, time zone and retina scale that are pinned there for the same reason. The platform-name check stays as a second line of defence. Output is unchanged: with no skin set, which is how the generator is launched, this renders byte-identical to before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Completes the half of the screenshot work #5668 deliberately left out. That PR
shipped the font fix and the tolerance-aware comparer but not the theming
machinery, because its 24 figures turned out to be schematic layout diagrams
whose job is to show where a layout manager puts things -- re-theming those
would bury the teaching point under component chrome. They keep their own pass
and their hand-rolled styling; this is for figures whose subject is a real
interface.
The harness
GuideFigureis a pure factory: it builds aFormand never shows it, so therenderer owns window size, theme and appearance and one figure renders on
several devices in both appearances.
FigureDevicetakes geometry fromdevices.jsonrather than inventing it,naming the entry each profile comes from, and renders at the logical resolution
with no skin -- a skin frames every picture in a bezel and sets
isIOSinJavaSEPort.loadTrueTypeFont, which resolvesnative:fonts againsthost-installed faces and makes output depend on the machine that produced it.
Density is read once during
Display.initand cached on the implementation, soone process cannot render two profiles: each pass is its own JVM, via three
execexecutions.Dark mode is checked, not trusted
Setting dark mode without
UIManager.refreshTheme()leaves styles cached attheir light values and every dark frame renders light -- invisible in review and
repeated across every figure. The renderer refreshes, then asserts
Displayagrees and that the background really is dark.
The gate no longer hardcodes 24
Generated figures now live in
img/generated/, so the directory is themanifest. A golden that stops being produced is an error instead of a silent
gap -- verified by removing one and watching the comparer fail:
Native themes are compiled before the framework build, because the antrun copy
that puts them in the javase jar is
failonerror=falseand otherwise fails open.Two real consumers, not unexercised infrastructure
The Components chapter gets an iOS/Android pair, retiring a caption asserting
iOS draws no border on a button. The native themes chapter's "Light and dark
mode" section gets the light/dark pair it had no visual evidence for at all.
Neither figure uses a
FontImageglyph -- whose antialiased edges cannot bebyte-reproduced across platforms -- so neither needs a tolerance sidecar.
Ran the full CI sequence locally: 28/28 figures match byte-for-byte, plus
structure, xrefs, snippets, missing-code-blocks, links, unused images,
capitalization, asciidoctor
--failure-level WARN, Vale 0, LanguageToolstatus: ok0 on both edited chapters, control characters, copyright headers.🤖 Generated with Claude Code