Skip to content

Developer guide: a figure harness that renders themed app UI - #5734

Merged
shai-almog merged 18 commits into
masterfrom
devguide-figure-harness
Sep 8, 2026
Merged

shai-almog merged 18 commits into
masterfrom
devguide-figure-harness

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

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

GuideFigure is a pure factory: it builds a Form and never shows it, so the
renderer owns window size, theme and appearance and one figure renders on
several devices in both appearances.

FigureDevice takes geometry from devices.json rather 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 isIOS in
JavaSEPort.loadTrueTypeFont, which resolves native: fonts against
host-installed faces and makes output depend on the machine that produced it.

Density is read once during Display.init and cached on the implementation, so
one process cannot render two profiles: each pass is its own JVM, via three
exec executions.

Dark mode is checked, not trusted

Setting dark mode without UIManager.refreshTheme() leaves styles cached at
their light values and every dark frame renders light -- invisible in review and
repeated across every figure. The renderer refreshes, then asserts Display
agrees and that the background really is dark.

The gate no longer hardcodes 24

Generated figures now live in img/generated/, so the directory is the
manifest. A golden that stops being produced is an error instead of a silent
gap -- verified by removing one and watching the comparer fail:

::error::mig-layout.png is committed as a generated figure but nothing produced it

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, 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 FontImage glyph -- whose antialiased edges cannot be
byte-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, LanguageTool
status: ok 0 on both edited chapters, control characters, copyright headers.

🤖 Generated with Claude Code

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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-08T08:31:57.888483Z b5e0aab New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 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".

Comment thread .github/workflows/developer-guide-docs.yml
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>
@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

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>
shaiblah and others added 2 commits September 7, 2026 20:46
…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>
@shai-almog

shai-almog commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.21% (9146/99286 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.97% (47024/524433), branch 3.53% (1752/49663), complexity 3.51% (1859/52966), method 5.40% (1505/27866), class 10.85% (405/3731)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.21% (9146/99286 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.97% (47024/524433), branch 3.53% (1752/49663), complexity 3.51% (1859/52966), method 5.40% (1505/27866), class 10.85% (405/3731)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 147ms / native 142ms = 1.0x speedup
SIMD float-mul (64K x300) java 168ms / native 158ms = 1.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 77.000 ms
Base64 CN1 decode 84.000 ms
Base64 native encode 352.000 ms
Base64 encode ratio (CN1/native) 0.219x (78.1% faster)
Base64 native decode 274.000 ms
Base64 decode ratio (CN1/native) 0.307x (69.3% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ✅ ByteCodeTranslator: 0 findings (no issues)
    • ✅ android: 0 findings (no issues)
    • ✅ build-hint-catalog: 0 findings (no issues)
    • ✅ build-hint-tools: 0 findings (no issues)
    • ✅ codenameone-maven-plugin: 0 findings (no issues)
    • ✅ core-unittests: 0 findings (no issues)
    • ✅ ios: 0 findings (no issues)
  • ✅ PMD: 0 findings (no issues) [Report archive]
  • ✅ Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1153 seconds

Build and Run Timing

Metric Duration
Simulator Boot 91000 ms
Simulator Boot (Run) 1000 ms
App Install 14000 ms
App Launch 3000 ms
Test Execution 367000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 53ms / native 3ms = 17.6x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 164.000 ms
Base64 CN1 decode 111.000 ms
Base64 native encode 301.000 ms
Base64 encode ratio (CN1/native) 0.545x (45.5% faster)
Base64 native decode 332.000 ms
Base64 decode ratio (CN1/native) 0.334x (66.6% faster)
Base64 SIMD encode 49.000 ms
Base64 encode ratio (SIMD/CN1) 0.299x (70.1% faster)
Base64 SIMD decode 44.000 ms
Base64 decode ratio (SIMD/CN1) 0.396x (60.4% faster)
Base64 encode ratio (SIMD/native) 0.163x (83.7% faster)
Base64 decode ratio (SIMD/native) 0.133x (86.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 16.000 ms
Image createMask ratio (SIMD on/off) 2.667x (166.7% slower)
Image applyMask (SIMD off) 38.000 ms
Image applyMask (SIMD on) 29.000 ms
Image applyMask ratio (SIMD on/off) 0.763x (23.7% faster)
Image modifyAlpha (SIMD off) 35.000 ms
Image modifyAlpha (SIMD on) 30.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.857x (14.3% faster)
Image modifyAlpha removeColor (SIMD off) 35.000 ms
Image modifyAlpha removeColor (SIMD on) 28.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.800x (20.0% faster)

@shai-almog

shai-almog commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 426 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 100ms / native 5ms = 20.0x speedup
SIMD float-mul (64K x300) java 75ms / native 2ms = 37.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 206.000 ms
Base64 CN1 decode 123.000 ms
Base64 native encode 1027.000 ms
Base64 encode ratio (CN1/native) 0.201x (79.9% faster)
Base64 native decode 566.000 ms
Base64 decode ratio (CN1/native) 0.217x (78.3% faster)
Base64 SIMD encode 54.000 ms
Base64 encode ratio (SIMD/CN1) 0.262x (73.8% faster)
Base64 SIMD decode 49.000 ms
Base64 decode ratio (SIMD/CN1) 0.398x (60.2% faster)
Base64 encode ratio (SIMD/native) 0.053x (94.7% faster)
Base64 decode ratio (SIMD/native) 0.087x (91.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 5.000 ms
Image createMask ratio (SIMD on/off) 0.417x (58.3% faster)
Image applyMask (SIMD off) 125.000 ms
Image applyMask (SIMD on) 77.000 ms
Image applyMask ratio (SIMD on/off) 0.616x (38.4% faster)
Image modifyAlpha (SIMD off) 77.000 ms
Image modifyAlpha (SIMD on) 61.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.792x (20.8% faster)
Image modifyAlpha removeColor (SIMD off) 59.000 ms
Image modifyAlpha removeColor (SIMD on) 54.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.915x (8.5% faster)

@shai-almog

shai-almog commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 309 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 64ms / native 3ms = 21.3x speedup
SIMD float-mul (64K x300) java 65ms / native 3ms = 21.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 144.000 ms
Base64 CN1 decode 94.000 ms
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.429x (57.1% faster)
Image applyMask (SIMD off) 53.000 ms
Image applyMask (SIMD on) 43.000 ms
Image applyMask ratio (SIMD on/off) 0.811x (18.9% faster)
Image modifyAlpha (SIMD off) 40.000 ms
Image modifyAlpha (SIMD on) 34.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.850x (15.0% faster)
Image modifyAlpha removeColor (SIMD off) 40.000 ms
Image modifyAlpha removeColor (SIMD on) 32.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.800x (20.0% faster)

@shai-almog

shai-almog commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1495 seconds

Build and Run Timing

Metric Duration
Simulator Boot 99000 ms
Simulator Boot (Run) 1000 ms
App Install 17000 ms
App Launch 1000 ms
Test Execution 539000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 83ms / native 4ms = 20.7x speedup
SIMD float-mul (64K x300) java 110ms / native 4ms = 27.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 817.000 ms
Base64 CN1 decode 199.000 ms
Base64 native encode 1591.000 ms
Base64 encode ratio (CN1/native) 0.514x (48.6% faster)
Base64 native decode 576.000 ms
Base64 decode ratio (CN1/native) 0.345x (65.5% faster)
Base64 SIMD encode 140.000 ms
Base64 encode ratio (SIMD/CN1) 0.171x (82.9% faster)
Base64 SIMD decode 72.000 ms
Base64 decode ratio (SIMD/CN1) 0.362x (63.8% faster)
Base64 encode ratio (SIMD/native) 0.088x (91.2% faster)
Base64 decode ratio (SIMD/native) 0.125x (87.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 14.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.214x (78.6% faster)
Image applyMask (SIMD off) 250.000 ms
Image applyMask (SIMD on) 220.000 ms
Image applyMask ratio (SIMD on/off) 0.880x (12.0% faster)
Image modifyAlpha (SIMD off) 197.000 ms
Image modifyAlpha (SIMD on) 254.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.289x (28.9% slower)
Image modifyAlpha removeColor (SIMD off) 220.000 ms
Image modifyAlpha removeColor (SIMD on) 90.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.409x (59.1% faster)

@shai-almog

shai-almog commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

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>

@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: 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".

shaiblah and others added 2 commits September 8, 2026 05:34
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>

@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: 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>

@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: 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".

Comment thread .github/workflows/developer-guide-docs.yml
Comment thread CodenameOne/src/com/codename1/ui/package-info.java
…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>

@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: 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>

@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: 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".

shaiblah and others added 2 commits September 8, 2026 06:24
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>

@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: 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>

@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: 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".

Comment thread docs/developer-guide/img/generated/graphics-fontimage-material.tolerance Outdated
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>

@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: 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>

@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: 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".

Comment thread CodenameOne/src/com/codename1/ui/package-info.java Outdated
… 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>

@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: 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>
@shai-almog
shai-almog merged commit 59c6ef0 into master Sep 8, 2026
32 checks passed
@shai-almog
shai-almog deleted the devguide-figure-harness branch September 8, 2026 09:44
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.

2 participants