Skip to content

Check build hints at compile time instead of shipping them inert - #5586

Open
shai-almog wants to merge 43 commits into
masterfrom
build-hint-annotations
Open

Check build hints at compile time instead of shipping them inert#5586
shai-almog wants to merge 43 commits into
masterfrom
build-hint-annotations

Conversation

@shai-almog

@shai-almog shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

The problem

A build hint is a codename1.arg.<name>=<value> line that reaches a builder as request.getArg(name, default). Nothing checked the name, so a misspelling was accepted, copied into the build request, never read, and silently discarded — a green build with the setting simply not applied.

Our own agent reference had been shipping keys in exactly that state:

Documented in skill/references/build-hints.md Actually read by the builders
android.xPermissions android.xpermissions (AndroidGradleBuilder.java:1206)
android.minSdkVersion android.min_sdk_version
android.sdkVersion (nothing — android.targetSDKVersion is the real one)

The change

Most hints can now be written as annotations on the application's main class, where javac does the checking: a misspelled name is an unknown symbol, a wrong value type is a type error, and a value outside a hint's supported set is an unknown enum constant.

@Ios(newStorageLocation = true, themeMode = IosThemeMode.MODERN)
@Android(minSdkVersion = 24, useAndroidX = true)
@Desktop(titleBar = DesktopTitleBar.NATIVE)
public class MyApplication extends Lifecycle {
}

The builders are untouched. BuildHintAnnotationProcessor converts the annotations back into the same key/value pairs, and CN1BuildMojo merges them before the command-line overlay, the CN1Lib merges and both preflights — so a library still appends onto an annotation-supplied value and -D still wins. Simulator publishes them as system properties at startup so cn1:run sees hints that no longer live in the properties file.

The properties file is untouched too. It stays the way to set the long tail and the open-ended families such as android.permission.<NAME> that an annotation cannot express, with no new warnings or errors on that path. Declaring one hint both ways is a build error.

Scope: generated projects are deliberately not migrated here

Every project the archetype and the initializr produce is pinned to a released Codename One version — the initializr hardcodes 7.0.267 in GeneratorModel.CN1_PLUGIN_VERSION — and no released core carries com.codename1.annotations.buildhints. A generated project would import annotations that do not resolve and fail to compile before the user has written a line.

So the archetype's __mainName__.java and codenameone_settings.properties, and the initializr's common.zip and four source archives, are unchanged. They move to annotations in a follow-up once a release containing the package is out. scripts/skindesigner (7.0.255) stays on properties for the same reason, and cn1:migrate-build-hints refuses any project whose core lacks the package.

The in-repo tool projects that build against 8.0-SNAPSHOT from source are migrated: gamebuilder, video-builder, cn1playground, certificatewizard, guibuilder, fidelity-app, purchase-test-app, settings, hellocodenameone, protocol-e2e.

docs/demos is deliberately excluded: it is the developer guide's snippet project, full of intentionally incomplete fragments, and running the annotation processors over it fails by design.

One catalog, generated views

The hint set was described in five places that had drifted apart: a prose table in the developer guide, a runtime scraper of that table in the Settings tool that guessed each type by string-matching the description, a fifteen-entry schema in the simulator, a fourteen-entry separator map in the plugin, and a hand-written agent reference. Only 147 of ~520 names appeared in more than one.

maven/build-hint-catalog is now the single source of truth — 529 hints: 457 mined from the builders, 56 documented-but-unread (external), 16 dynamic families, 82 exposed as annotation attributes across 7 annotations and 10 enums. The annotations, the binding table the processor reads back, the developer guide's table and the simulator's editor schema are all generated from it.

The guide's table goes from 208 rows to 529 with no prose lost, and gains Type, Default and Annotation columns it never had.

Enums are emitted only where the accepted set is demonstrable from the code that reads the hint — HardeningPreflight rejects an unknown harden.level, IOSDependencyManager throws on an unknown ios.dependencyManager, and GenerateDesktopAppWrapperMojo silently falls back to native on an unknown desktop.titleBar, which is precisely the failure this removes.

Settings tool

It no longer scrapes the guide's AsciiDoc and guesses types; it reads the catalog. It also validates closed value domains, and refuses to edit a hint an annotation already owns — reading META-INF/codenameone/build-hints.properties and showing "Set by @Ios(themeMode) on the main class" — because writing a property for such a hint would create the duplicate declaration that fails the next build.

Gates

  • scripts/check-build-hint-catalog.sh fails when code reads a hint the catalog does not describe, and when our own docs or project templates name one that no builder reads. Its baseline is empty, so it is a hard gate rather than a ratchet.
  • scripts/gen-build-hint-annotations.sh --check fails on generated-file drift.
  • A JUnit suite checks the catalog's own consistency (attribute-name legality including the JLS 9.6.1 Object/Annotation method-name rule, enum domains, separators matching what LibraryHintMerger used to define).

Both scripts run in the Java 8 leg of PR CI.

Verification

maven/integration-tests/build-hint-annotations-test.sh generates a project from the archetype, adds the annotations by hand (the template no longer ships them), and checks the emitted wire values — a list joined with the hint's own separator, an enum written as the catalog's value rather than the constant name, and an attribute nobody set written not at all — that they reach the real build request, and that declaring one hint twice fails with a message naming both the hint and the annotation attribute.

Also confirmed empirically, since the whole "unset means absent" design rests on it: javac records a member the developer wrote even when its value equals the annotation's default, and omits one left unwritten.

Notes for review

  • The annotations live in com.codename1.annotations.buildhints, not ...annotations.build: .gitignore carries a repo-wide **/build/* rule that silently swallowed every source under the com.codename1.build.shared package path. The rule now un-ignores build when it is a Java package rather than a build output directory.
  • Six mined "hints" turned out to be the constant half of a concatenation (android.permission., harden., …) and are modelled as dynamic families rather than settable keys.
  • The skill-reference corrections that remain are unrelated to annotations: they delete or fix hints no builder reads, which is right for the published version too.

Pre-existing problems surfaced but not fixed here

  • cn1:test build hints are broken today. RunTestsMojo.java:253 sets the forked runner's CWD to target/cn1-reports, where neither of loadCodenameOneSettings()'s fallbacks looks, so buildHint() returns null there for properties-file hints too.
  • The Settings tool's tests never ran (<skipTests>true</skipTests>), so one had rotted against a GridLayout(3, 2) that became (5, 2) in d15cfa5. The skip is now a property (default unchanged) and the assertion tracks the column count.
  • cn1playground's checked-in GeneratedAccess_* files are stale against the current core; building it regenerates ~3,300 lines. Left alone to keep this change scoped.

Follow-up

Once a release carrying com.codename1.annotations.buildhints is published:

  1. Move the archetype's __mainName__.java and settings template to annotations.
  2. Rebuild the initializr's common.zip and the four source archives, bump GeneratorModel.CN1_PLUGIN_VERSION, and bind process-annotations in the generated common POM.
  3. Migrate scripts/initializr and scripts/skindesigner themselves.
  4. Restore the generated annotation table in the agent skill reference.

🤖 Generated with Claude Code

A build hint is a `codename1.arg.<name>=<value>` line that reaches a builder as
`request.getArg(name, default)`. Nothing checked the name, so a misspelling was
accepted, copied into the build request, never read, and silently discarded: a
green build with the setting simply not applied.

Our own agent reference had been shipping `android.xPermissions`,
`android.minSdkVersion` and `android.sdkVersion` for exactly that reason. The
builders read `android.xpermissions`, `android.min_sdk_version`, and nothing at
all.

Most hints can now be written as annotations on the application's main class,
where javac does the checking: a misspelled name is an unknown symbol, a wrong
value type is a type error, and a value outside a hint's supported set is an
unknown enum constant.

    @iOS(newStorageLocation = true, themeMode = IosThemeMode.MODERN)
    @android(minSdkVersion = 24, useAndroidX = true)
    @desktop(titleBar = DesktopTitleBar.NATIVE)
    public class MyApplication extends Lifecycle {
    }

The builders are untouched: `BuildHintAnnotationProcessor` converts the
annotations back into the same key/value pairs and `CN1BuildMojo` merges them
before the command-line overlay, the CN1Lib merges and both preflights, so a
library still appends onto an annotation-supplied value and `-D` still wins.
`Simulator` publishes them as system properties at startup so `cn1:run` sees
hints that no longer live in the properties file.

The properties file is untouched too. It stays the way to set the long tail and
the open-ended families such as `android.permission.<NAME>` that an annotation
cannot express, with no new warnings or errors. Declaring one hint both ways is
a build error.

One catalog, five generated views
---------------------------------
The hint set had been described in five places that had drifted apart: a prose
table in the developer guide, a runtime scraper of that table in the Settings
tool that guessed each type by string-matching the description, a fifteen-entry
schema in the simulator, a fourteen-entry separator map in the plugin, and a
hand-written agent reference. Only 147 of ~520 names appeared in more than one.

`maven/build-hint-catalog` is now the single source of truth (529 hints: 457
mined from the builders, 56 documented-but-unread, 16 dynamic families; 82
exposed as annotation attributes). The annotations, the binding table the
processor reads back, the guide's table, the simulator's editor schema and the
agent reference are all generated from it. The guide's table goes from 208 rows
to 529 with no prose lost.

Enums are emitted only where the accepted set is demonstrable from the code that
reads the hint -- `HardeningPreflight` rejects an unknown `harden.level`,
`IOSDependencyManager` throws on an unknown `ios.dependencyManager`, and
`GenerateDesktopAppWrapperMojo` silently falls back to `native` on an unknown
`desktop.titleBar`, which is the failure this removes.

Generated projects
------------------
The archetype and all four initializr templates now carry the annotations, and
`cn1:migrate-build-hints` moves an existing project over. Eleven in-repo
projects are migrated. `java.version` deliberately stays in the properties file:
it picks the toolchain that compiles the class the annotations live on.

Gates
-----
`scripts/check-build-hint-catalog.sh` fails when code reads a hint the catalog
does not describe, and when our own docs or templates name one that no builder
reads. Its baseline is empty, so it is a hard gate rather than a ratchet.
`scripts/gen-build-hint-annotations.sh --check` fails on generated-file drift.
Both run in the Java 8 leg of PR CI.

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: 8d2cfcfde3

ℹ️ 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 maven/pom.xml
Comment thread scripts/build_hint_miner.py Fixed
Comment thread scripts/build_hint_miner.py Fixed
Comment thread scripts/check-build-hint-catalog.py Fixed
Comment thread scripts/check-build-hint-catalog.py Fixed
Comment thread scripts/check-build-hint-catalog.py Fixed
Comment thread tools/build-hint-bootstrap/gen_external.py Fixed
Comment thread tools/build-hint-bootstrap/gen_external.py Fixed
Comment thread tools/build-hint-bootstrap/gen_external.py Fixed
Comment thread tools/build-hint-bootstrap/gen_catalog.py Fixed
Comment thread tools/build-hint-bootstrap/gen_catalog.py Fixed
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

  • Tests: 934 total, 0 failed, 0 skipped
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

`.gitignore` carries a repo-wide `**/build/*`. The catalog's package is
`com.codename1.build.shared`, so all 13 of its sources sat under a path segment
named `build` and `git add` silently skipped them. Only `pom.xml` was committed:
the module built locally from the working tree and produced an empty jar in CI,
which is why `codenameone-maven-plugin` then failed with `cannot find symbol` on
`BuildHints` and nearly every job went red.

The sibling `platform-feature-catalog` lives in the same package and is fine,
because it was added before that rule existed -- tracked files stay tracked, so
nothing ever pointed at the hazard.

Un-ignore `build` when it is a Java package rather than a build output
directory, with the rationale beside the rule so the next file added there is
not lost the same way. `maven/core/build/*` and `CodenameOne/build/*` stay
ignored.

Also from review:

- Every bare `open()` in the four Python scripts now uses a context manager, so
  the handle closes even if parsing or `json.dump` raises, and the writes state
  their encoding.
- The generator no longer emits an IP literal as an annotation default. PMD
  reads `default "127.0.0.1"` as hardcoded configuration, and the default clause
  is documentation only -- the processor emits a hint solely for members the
  developer actually wrote -- so the value moves to the javadoc where it belongs.
- Files the migration touched that never carried a copyright header now have the
  complete one. The archetype's `__mainName__.java` is excluded instead: it is a
  template for the user's own application class, and stamping a Codename One GPL
  header onto it would put our licence on their code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scripts/check-build-hint-catalog.py Fixed
Comment thread tools/build-hint-bootstrap/gen_catalog.py Fixed
Comment thread tools/build-hint-bootstrap/gen_catalog.py Fixed
Comment thread tools/build-hint-bootstrap/gen_catalog.py Fixed

@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


P1 Badge Pin generated projects to a version containing the annotations

The updated Initializr starter archives now import and use com.codename1.annotations.buildhints, but GeneratorModel.java:44 still generates projects pinned to CN1 7.0.267, whose core artifact predates this package. Consequently every newly generated barebones, Kotlin, Grub, or Tweet project fails compilation on the unresolved annotations unless the user manually changes the CN1 version; either defer these template changes or update the generated runtime/plugin version to the first release containing them.

ℹ️ 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 archived bootstrap ran generation at module scope, so gen_external.py's
`import gen_catalog` -- which only wants three helper functions -- rewrote every
catalog source as a side effect. Generation and its diagnostics now live in
`main()` behind a `__main__` guard, and the module-level file reads became
`load_license()` / `load_mined()` / `load_docs()`, so importing does no I/O and
cannot fail on inputs the archived copy deliberately does not carry.

Verified both directions: importing leaves the catalog untouched, and running
the two scripts end to end still reproduces the committed catalog byte for byte.

Also drops `json` and `subprocess` from check-build-hint-catalog.py. Both were
left from an earlier version that shelled out to the miner instead of importing
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 12 screenshots: 12 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

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

ℹ️ 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 scripts/gen-build-hint-annotations.sh Outdated
@github-actions

github-actions Bot commented Aug 22, 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)

@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.03% (8905/98658 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.82% (46035/521644), branch 3.45% (1702/49379), complexity 3.43% (1809/52720), method 5.26% (1459/27762), class 10.51% (389/3702)
    • 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.03% (8905/98658 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.82% (46035/521644), branch 3.45% (1702/49379), complexity 3.43% (1809/52720), method 5.26% (1459/27762), class 10.51% (389/3702)
    • 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 163ms / native 203ms = 0.8x speedup
SIMD float-mul (64K x300) java 114ms / native 146ms = 0.7x 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 64.000 ms
Base64 CN1 decode 73.000 ms
Base64 native encode 459.000 ms
Base64 encode ratio (CN1/native) 0.139x (86.1% faster)
Base64 native decode 383.000 ms
Base64 decode ratio (CN1/native) 0.191x (80.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 63ms / native 4ms = 15.7x speedup
SIMD float-mul (64K x300) java 62ms / native 5ms = 12.4x 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 gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 206.000 ms
Base64 CN1 decode 131.000 ms
Base64 SIMD encode 100.000 ms
Base64 encode ratio (SIMD/CN1) 0.485x (51.5% faster)
Base64 SIMD decode 99.000 ms
Base64 decode ratio (SIMD/CN1) 0.756x (24.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 26.000 ms
Image createMask (SIMD on) 142.000 ms
Image createMask ratio (SIMD on/off) 5.462x (446.2% slower)
Image applyMask (SIMD off) 68.000 ms
Image applyMask (SIMD on) 58.000 ms
Image applyMask ratio (SIMD on/off) 0.853x (14.7% faster)
Image modifyAlpha (SIMD off) 59.000 ms
Image modifyAlpha (SIMD on) 60.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.017x (1.7% slower)
Image modifyAlpha removeColor (SIMD off) 60.000 ms
Image modifyAlpha removeColor (SIMD on) 34.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.567x (43.3% faster)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 62ms / native 4ms = 15.5x speedup
SIMD float-mul (64K x300) java 62ms / native 5ms = 12.4x 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 gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 204.000 ms
Base64 CN1 decode 130.000 ms
Base64 SIMD encode 102.000 ms
Base64 encode ratio (SIMD/CN1) 0.500x (50.0% faster)
Base64 SIMD decode 92.000 ms
Base64 decode ratio (SIMD/CN1) 0.708x (29.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 29.000 ms
Image createMask (SIMD on) 27.000 ms
Image createMask ratio (SIMD on/off) 0.931x (6.9% faster)
Image applyMask (SIMD off) 182.000 ms
Image applyMask (SIMD on) 72.000 ms
Image applyMask ratio (SIMD on/off) 0.396x (60.4% faster)
Image modifyAlpha (SIMD off) 71.000 ms
Image modifyAlpha (SIMD on) 67.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.944x (5.6% faster)
Image modifyAlpha removeColor (SIMD off) 83.000 ms
Image modifyAlpha removeColor (SIMD on) 71.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.855x (14.5% faster)

@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300) java 55ms / native 3ms = 18.3x 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 gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 246.000 ms
Base64 CN1 decode 127.000 ms
Base64 SIMD encode 66.000 ms
Base64 encode ratio (SIMD/CN1) 0.268x (73.2% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.496x (50.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 13.000 ms
Image createMask (SIMD on) 9.000 ms
Image createMask ratio (SIMD on/off) 0.692x (30.8% faster)
Image applyMask (SIMD off) 26.000 ms
Image applyMask (SIMD on) 20.000 ms
Image applyMask ratio (SIMD on/off) 0.769x (23.1% faster)
Image modifyAlpha (SIMD off) 18.000 ms
Image modifyAlpha (SIMD on) 169.000 ms
Image modifyAlpha ratio (SIMD on/off) 9.389x (838.9% slower)
Image modifyAlpha removeColor (SIMD off) 20.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.650x (35.0% faster)

@shai-almog

shai-almog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

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

Three separate gates rejected generated output. Each is fixed in the generator
so the class of problem cannot come back through a catalog edit.

Unmappable characters. The prose is imported from the developer guide, which
uses typographic punctuation, and `CodenameOne/src` is also compiled by an Ant
javac step with ASCII encoding where a single em dash is
`error: unmappable character for encoding ASCII` -- a build failure, not a
warning. A Unicode escape would not have helped: javac expands `\uXXXX` before
it strips comments, so the character reappears. `toAscii` now folds the
punctuation that actually occurs, and *refuses* anything it has no mapping for
rather than dropping it, because silently deleting a character from a hint's
documentation is the worse outcome.

Broken table. `ios.spm.packages` is documented as `identity|url|requirement`,
and a bare `|` starts a new AsciiDoc cell, so asciidoctor reported "dropping
cells from incomplete row" for the whole 529-row table. Cells are escaped now.

Vale. The guide enforces the Microsoft style as errors, and the generated table
feeds it, so the catalog's prose has to satisfy it too: contractions, no
"and so on", no stray adverbs. A default value is not prose, though -- the one
remaining hit was `android.file_paths`, whose default is an XML fragment -- so
a quoted default now carries the `// vale-skip:` comment .vale.ini documents
for individual false positives.

Also fixes a data bug the guide exposed. The miner preserved Java escape
sequences instead of decoding them, so `android.file_paths` and
`android.facebook_permissions` recorded defaults containing literal
backslashes that the build never sees, and those reached the rendered table.
The miner decodes escapes and re-quotes safely, and the two catalog entries are
corrected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scripts/build_hint_miner.py Fixed

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

ℹ️ 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".

`killedThreadReportsItselfFinished` failed the Java 21 leg with
"FormTest timed out after 5000ms; edt=initialized pendingSerialCalls=0". The
waits in this class used a 5000ms deadline, which is exactly the `@FormTest`
timeout in EDTTestInterceptor -- so on a loaded runner the poll loop consumed
the entire harness budget and the interceptor fired first. The report then said
only that the method timed out, with nothing about which condition never became
true.

The waits now use 2000ms, well inside the harness budget and still roughly two
thousand times the ~1ms these threads actually take to stop. A genuine
regression now fails on the test's own assertion, which names what went wrong.

Pre-existing (the test arrived with #5526) and unrelated to the build hint work:
core-unittests has no dependency on the JavaSE port, so none of the simulator
registration in this branch runs there, this branch changes nothing under
com.codename1.db or EasyThread, and the Java 8 leg passed the same commit.

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

ℹ️ 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".

A mojo's defaultPhase does not bind it to a project -- the project's POM has to
-- and nothing turns a build hint annotation back into a codename1.arg.* pair
except the process-annotations goal. So migrating a project without that
binding deleted working properties and replaced them with annotations no goal
ever reads: the hints vanished from the build with no diagnostic anywhere.

Five projects in this branch were already in that state. gamebuilder,
docs/demos, video-builder and cn1playground bind the plugin but not that goal,
so the binding is added. input-validation-app's common module has no build
section at all, so its migration is reverted rather than inventing a lifecycle
for a demo app.

The goal now checks the reactor for the binding and refuses with the execution
block to paste, so this cannot happen to anyone else.

Three more from the same review:

- The deletion pass recognized only `key=value`. `Properties.load` also accepts
  `key:value`, `key value`, escaped separators inside the key, and logical
  continuation lines; a declaration it failed to match was left behind while the
  annotation was added, so the next build failed with the duplicate-hint error
  this goal exists to prevent. Keys are parsed the way Properties.load defines
  them now, with a unit test per form.

- The settings file was read as ISO-8859-1 and written back as UTF-8, turning
  any unrelated non-ASCII byte -- an accented displayName, say -- into mojibake.
  It is written back as ISO-8859-1.

- cn1.androidTheme and cn1.nativeTheme are deprecated aliases of and.themeMode
  and nativeTheme, which the builders honour as fallbacks. Neither declared
  aliasOf, so conflict detection missed them and one value silently won.

Also: the generation script rebuilt the generator only when its class was
absent, so editing a catalog source and rerunning regenerated every view from
the previous build's bytecode -- reporting success while ignoring the edit, and
passing --check on a tree that was genuinely stale. It always rebuilds now.

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: 0edef42ca4

ℹ️ 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".

…plicating a hint

docs/demos is the developer guide's snippet project: deliberately incomplete
code fragments that illustrate @entity, @route, @AppIntent and @mapped. Binding
process-annotations there put those snippets in front of the other processors,
which correctly rejected six of them, so the migration is reverted and its two
hints are back in the properties file. That the project omitted the goal was the
point, not an oversight.

The other three newly bound projects were checked rather than assumed:
gamebuilder, video-builder and cn1playground each run process-annotations
cleanly and emit 6, 3 and 5 hints respectively.

Settings could still create the duplicate the migration is careful to avoid. In
a generated project ios.themeMode and its neighbours are annotations, but the
Build Hints UI decides a hint is inactive from the properties file alone and its
Add button writes a property -- producing a second declaration that fails the
next build. The tool now reads META-INF/codenameone/build-hints.properties, the
file the processor writes on every build and deletes when the last annotation
goes, and renders those hints read-only with the attribute that owns them:
"Set by @iOS(themeMode) on the main class." An unbuilt project has no such file
and behaves as before.

Also fixes the SpotBugs finding this branch introduced: `backslashes % 2 == 1`
in the continuation scan is false for negative odd numbers, so it is `!= 0`.
The count cannot go negative, but the idiom is wrong regardless of that.

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


P1 Badge Keep Initializr templates compatible with the pinned runtime

Every Initializr source archive now imports com.codename1.annotations.buildhints and uses the new annotations, while GeneratorModel.CN1_PLUGIN_VERSION still rewrites generated projects to 7.0.267, whose codenameone-core predates that package; the generated common POM also omits process-annotations. Consequently all newly downloaded Initializr projects fail compilation instead of receiving the defaults removed from common.zip's settings file. Leave these templates property-backed until Initializr targets the release containing this feature, or bump the generated version and bind the processor.

ℹ️ 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".

@shai-almog

shai-almog commented Aug 22, 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: 290 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 69ms / native 3ms = 23.0x speedup
SIMD float-mul (64K x300) java 75ms / native 4ms = 18.7x 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 156.000 ms
Base64 CN1 decode 91.000 ms
Base64 native encode 728.000 ms
Base64 encode ratio (CN1/native) 0.214x (78.6% faster)
Base64 native decode 648.000 ms
Base64 decode ratio (CN1/native) 0.140x (86.0% faster)
Base64 SIMD encode 48.000 ms
Base64 encode ratio (SIMD/CN1) 0.308x (69.2% faster)
Base64 SIMD decode 44.000 ms
Base64 decode ratio (SIMD/CN1) 0.484x (51.6% faster)
Base64 encode ratio (SIMD/native) 0.066x (93.4% faster)
Base64 decode ratio (SIMD/native) 0.068x (93.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.286x (71.4% faster)
Image applyMask (SIMD off) 67.000 ms
Image applyMask (SIMD on) 64.000 ms
Image applyMask ratio (SIMD on/off) 0.955x (4.5% faster)
Image modifyAlpha (SIMD off) 53.000 ms
Image modifyAlpha (SIMD on) 83.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.566x (56.6% slower)
Image modifyAlpha removeColor (SIMD off) 90.000 ms
Image modifyAlpha removeColor (SIMD on) 62.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.689x (31.1% faster)

Every project the archetype and the initializr produce is pinned to a released
Codename One version -- the initializr hardcodes 7.0.267 in
GeneratorModel.CN1_PLUGIN_VERSION -- and no released core carries
com.codename1.annotations.buildhints. So a generated project would import
annotations that do not resolve and fail to compile before the user has written
a line, and the settings those templates stopped declaring would simply be gone.

The templates are reverted to exactly their previous state: the archetype's
__mainName__.java and codenameone_settings.properties, and the initializr's
common.zip and four source archives. They can move to annotations in a follow-up
once a release containing the package is out.

The generated build hint table is dropped from the agent skill reference for the
same reason -- it documented a form those projects cannot use yet -- so the
generator no longer rewrites markdown at all.

What stays from that area is unrelated to annotations: the skill reference
described build hints that no builder reads, so a reader copying them got a
green build and no effect. android.xPermissions is spelled android.xpermissions,
android.minSdkVersion is android.min_sdk_version, and android.sdkVersion,
android.googlePlayVersion, build.compile, build.timeout, javascript.html5,
javascript.bundleResources and ios.orientation do not exist at all. Those
corrections are right for the published version too, and the catalog gate now
holds our own documentation to them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three from the same review, each a follow-up to the previous round's fix.

Trying the conventional target/classes before the classpath looked harmless and
was not. A project that moves to a configured output directory without running
clean leaves the old target/classes in place, manifest and class together, so
the staleness check compares two obsolete files against each other, finds them
consistent, and publishes last week's hints while the real ones sit on the
classpath. The classpath is searched first now -- it is the output the build is
actually using -- with the conventional path kept only for a launch that never
passed the module's output directory at all.

The computed-site file was keyed by path, so a builder already listed absorbed a
SECOND computed hint for free: adding `platform + ".maps.apiKey"` beside the
provider expression kept the gate green with the new hint uncatalogued. Keyed by
file AND expression now, whitespace-normalised so a reformat is not a change,
and split from both ends so an expression containing a pipe still parses. The
line number is deliberately not part of the key, so moving code does not churn
the file. Verified by adding exactly that second expression: the gate names it.

Alias discovery still used a raw indexOf, so a commented-out `// import ...Ios
as Old` above the live `import ...Ios as BuildIos` won, and the live @BuildIos
was never looked for -- reinstating the bug the alias support was added for. It
uses the same comment-aware walk as the marker search, and the occurrence must
be the target of an `import` on that line, so a mention of the package in code
is not read as one.

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: 3fbc47f587

ℹ️ 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 Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java
Three from the same review.

and.captureRecord is not an abbreviation of android.captureRecord: the builder
reads the long name and then lets the short one override it, so the two name one
setting. Uncatalogued as an alias, @android(captureRecord) and a properties line
spelling it the short way were both accepted -- and the properties line wins in
the builder, so the compile-checked annotation was silently ineffective, which
is exactly the failure this feature exists to remove. Marked as an alias, with a
processor test asserting the pair now conflicts. and.facebook_permissions has
the same override relationship and is marked too: nothing can conflict with it
today since the long name is not annotated, but recording it means annotating
that name later cannot reintroduce this.

Settings looked for an annotation's argument list with indexOf('('), and
parentheses are optional -- a bare @iOS is legal Java and Kotlin. It therefore
adopted whatever call came next, so `@Ios` above a `configure(teamId = "...")`
read as owning ios.teamId and the tool withheld Add and the editor for a hint
the processor never emits. The next LIVE character after the name must now be
the paren, comments skipped, so an annotation's own list is still found across
one and a bare annotation adopts nothing.

The simulator never checked the manifest's main-class stamp. Change
codename1.mainName without a clean build and the old class and its manifest stay
together in the output directory, perfectly consistent with each other, so the
timestamp check passes them and the previous application's hints get published.
The native merge has refused this since it was written; the simulator does now
too.

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: 9162f41247

ℹ️ 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 Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java
Two from the same review.

Publishing an annotation hint the properties file also declares buries the
error rather than reporting it: buildHint() reads the system property before the
settings file, so the line the developer just added is silently ignored in the
simulator while the device build refuses to run at all. Reachable because editing
the properties file does not touch the class, so the timestamp check still finds
the manifest current. The pair is detected before publishing now, and the
annotation value is withheld with a message naming the hint.

Aliases have to collapse for that check, and the catalog that knows about them is
a build-time artifact this port cannot reach. Rather than give the JavaSE port a
dependency on it, the processor writes the other spellings of each hint into the
manifest -- cn1.buildHints.alias.<name> -- alongside the origin it already
records, and only where a hint has more than one. The conflict check and the
manifest now derive that set from one method instead of two that can disagree.

This is the third time the simulator has lagged a check the native merge already
performed. Recorded in the reply as such: if a fourth appears the two paths want
a shared decision rather than parallel implementations.

skipNonCode had no triple-quote branch, so a Kotlin raw string or Java text block
read as an empty literal followed by a new one -- and an embedded quote then
opened a literal that swallowed the annotation after it, leaving the hint unowned
and Add free to write the duplicate. Both directions are tested: a raw string
containing a quote no longer hides the annotation after it, and an annotation
written inside one is still not ownership.

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: 392e2a5f9a

ℹ️ 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 Ports/JavaSE/src/com/codename1/impl/javase/Simulator.java
A reload re-enters main() in the same JVM rather than starting a process, so
everything published on the previous launch is still set. The rule that protects
a -D value -- an existing value always wins -- then protected the PREVIOUS
build's annotation value too: editing @desktop(titleBar = ...) and reloading
kept showing the old setting, and deleting the annotation altogether kept it
forever, since the missing manifest takes an early return that touched nothing.

What this method installed is now withdrawn at the top, before any early return,
so a removed annotation and an unreadable manifest clear the value as surely as
a changed one replaces it.

Only what it installed. A -D was never a candidate, because a key that is
already set is skipped rather than published, so it can never enter the withdraw
set -- the command line keeps winning without a special case for it.

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: 01e471ff4c

ℹ️ 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".

Two from the same review.

The triple-quote branch closed at the next """, which is wrong in BOTH languages
and in opposite ways -- and getting it wrong over-consumes past a live
annotation, so the hint reads as unowned and Settings writes the duplicate.

  Java   escapes DO apply, so \""" is an escaped quote and two more, not a
         delimiter. Reading it as one made the REAL delimiter open a second
         text block that ran past whatever followed.
  Kotlin escapes do NOT apply, and a run of four or more quotes closes at its
         LAST three: """a"""" holds a" .

So the scanner needs to know which language it is reading, and it can: the
caller already picks the file by extension. The flag is threaded from there
down to skipNonCode, with the two-argument entry point keeping Java rules for
callers that have no file to name.

Separately, the manifest was merged over the source result as a union. It cannot
ADD ownership the source does not show: an attribute deleted from the main class
and not yet rebuilt is precisely that, and the union kept Add and the editor
hidden for a hint nothing owns any more, until the user happened to rebuild with
nothing to suggest that was the fix. The source now decides WHICH hints are
owned and the manifest only supplies their origins -- except when no source file
could be read at all, which the scan now reports as null rather than as an empty
result, because there the manifest is all there 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: cd00b3a4c7

ℹ️ 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 scripts/build_hint_miner.py
Three from the same review.

Ten real hints had no catalog row while the gate reported success.
IPhoneBuilder's WALLET_INJECTION_HINTS holds them in a String[][] and reaches
getArg as hintAndMarker[0], so no literal anywhere in the tree sits at a getArg
call -- invisible to every literal search, this gate included, which is exactly
the hole the computed-site accounting was added to close. A subscript now counts
as a computed name alongside a built one; that adds precisely one site, and its
ten expansions are catalogued and accounted for.

An output directory keeps class files whose source is gone. Rename the main
class, update codename1.mainName, skip the clean, and the old annotated .class
still sits there -- so every incremental build failed with a placement error
naming a class the developer had already deleted, and the orphan's hints were
merged in besides. A class that is not the main one and has no source under the
project is now ignored.

Two limits on that, and the second only because the tests caught it: the main
class is never dropped, because failing to find ITS source would silently apply
none of a project's hints, which is worse than any placement message; and a
project with no source tree at all is left alone entirely. Absence of a source
tree is not evidence a class is orphaned, it means this is a layout the lookup
does not know.

The migration read byName(), which returns the ALIAS entry for a legacy
spelling -- and an alias's own isAnnotated() is false even though the setting it
names has an annotation. So cn1.androidTheme, cn1.nativeTheme and
and.captureRecord, the spellings an existing project is most likely to be
carrying, were reported as having no annotation and left behind. Resolved
through the alias now.

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

ℹ️ 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 scripts/check-build-hint-catalog.py
3,345 generated lines leave git. The table is rendered from
maven/build-hint-catalog every time the developer guide is built, so it cannot
drift from the catalog and a hand edit has nothing to survive in -- which is
what a generated file living in the repository always eventually invites, and
what the drift gate and the pr.yml path re-inclusion existed to compensate for.
Both of those go with it.

Two renderers, so the step is a script rather than a workflow line:
developer-guide-docs.yml runs it before the Asciidoctor lint, which is ahead of
everything else that reads the guide -- the HTML and PDF build, Vale -- and
scripts/website/build.sh runs it before its own asciidoctor call.

The generator grows a --table-only mode for them. A documentation build has no
business rewriting CodenameOne/src and Ports/JavaSE on its way past, and the
mode's output is byte-identical to what the full run wrote, so this is not a
second implementation of the table.

Forgetting the step cannot be quiet: asciidoctor reports "include file not
found" and the lint fails on it. Verified in both directions -- the render fails
without the table and succeeds after generating it.

scripts/gen-build-hint-annotations.sh still writes the file for a local
asciidoctor run, and it is gitignored, so that copy is never reviewed and never
committed.

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

ℹ️ 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".

Four from the same review, two of them consequences of the alias fix I made
last round.

Verification searched the manifest for the key the FILE used. A legacy spelling
is deleted under its own name and comes back under the canonical one, so
migrating cn1.nativeTheme, cn1.androidTheme or and.captureRecord reported the
hint missing and rolled a correct migration back. The two lists are separate
now: migratedKeys is what gets deleted, verifiedKeys is what must come back.

Declaring one setting under two spellings with different values was resolved by
whichever Properties.stringPropertyNames happened to enumerate last -- and then
both lines were deleted, so a migration could change what the app builds with
and still report success, since the check asks whether the hint came back and
not what it holds. There is no rule to apply: and.captureRecord is read after
android.captureRecord and overrides it, while the theme aliases are each handed
to Display.setProperty and resolved in the framework. So the ambiguous case is
refused, naming both spellings, and the developer decides. Equal values are not
ambiguous and migrate.

Settings accepted a bare @build or @android whatever it meant. Those names are
ordinary enough that another library's annotation with a matching attribute read
as ownership, and the editor was withheld for a hint the processor never emits.
The simple name now counts only when an import brings it in from
com.codename1.annotations.buildhints -- by name or on demand -- while the fully
qualified spelling still needs none. Nine existing tests failed on this, all
because their snippets used a bare annotation with no import, which no real
source does; they carry the import now and are better tests for it.

Two identical computed expressions in one file collapsed to one key, so a second
getArg(hintAndMarker[0], ...) over a different table would have been validated
against the first table's expansions and its own hints never checked. The
accounting line carries a call count -- `#2` -- and a mismatch is reported with
the number to record, which forces the second call to be looked at.

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

ℹ️ 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 Outdated
Generating the build hint table instead of committing it took away a trigger
nobody had to think about: while the table was checked in, editing the catalog
produced a diff under docs/ and the documentation workflow ran on it for free.
Now a change to maven/build-hint-catalog or to the renderer changes what the
guide contains without touching a file under docs/ at all, so the Asciidoctor
lint, Vale, and the HTML and PDF build never saw it -- and a malformed table
would have merged and surfaced in the release documentation build.

Both paths are added to the workflow trigger AND to the paths-filter the steps
are gated on. Triggering alone is not enough: the HTML and PDF build and the
steps beside it are conditional on that filter, so they would have started and
skipped.

The render step also moves ahead of everything that reads the guide rather than
sitting just before the lint -- the image and snippet checks read it too. It is
unconditional, because every one of those steps is not, and asciidoctor reports
a missing include as an error, so a future step inserted above it fails loudly
rather than rendering a guide with the table quietly absent.

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

ℹ️ 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".

Three from the same review.

A closed domain rejected spellings the runtime honours.
IOSImplementation.installNativeTheme compares against flat, liquid and iphone
alongside the catalogued values, AndroidImplementation against material and
holo, so Settings told a developer that a working configuration was invalid and
then refused to save the edit -- and the migration refused those values as
outside the domain, which is exactly what a project old enough to be carrying a
legacy spelling would have hit.

They are recorded as valueAliases, deliberately NOT as domain values: an alias
must not become an enum constant, because two constants for one behaviour is an
API that asks a question with no right answer. Validation and migration both go
through canonicalValue, so `flat` saves and migrates to IosThemeMode.IOS7, while
the picklist and the annotation still offer one spelling per concept.

`import ...Ios as BuildIos` puts BuildIos in scope and NOT Ios, so counting it
as a simple-name import attributed another library's @iOS to us -- the same
misattribution the import check had just been added to prevent. An import with
an `as` clause now contributes only its alias marker.

The orphan check hard-coded src/main/java. A module may add generated-sources or
replace the conventional root, and Kotlin does not require a file to be named
after the class it declares, so a live class could be read as orphaned -- and
then silently dropped, taking its hints with it and suppressing the placement
error that would have explained it.

It now asks Maven for the compile source roots, which is where generated and
Kotlin roots already are, and looks for the file name the COMPILER recorded in
the SourceFile attribute rather than one derived from the class name. That is
the one reliable link back from a class to its source, and it is what makes the
Kotlin case answerable at all. ClassScanner records it; AnnotatedClass exposes
it. Unknown roots, or a class compiled without debug information, still answer
"has a source", because the only thing this decides is whether to IGNORE a
class.

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: 186a75cfbb

ℹ️ 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 scripts/build_hint_miner.py
Three from the same review.

Three more real hints had no catalog row. MacNativeBuilder reads them through
parseEntitlementBool(request, hint, def), so every caller passes a literal, none
of the calls is an accessor, and a literal search of accessor calls walks past
all of them. The miner now recognises a helper that forwards one of its own
parameters to getArg/arg/booleanArg and mines that helper's CALLERS instead --
which found exactly the three named, by looking rather than by being told.
Same-file only, deliberately: a private helper's name is not unique across the
tree, and mining calls to a same-named method of an unrelated class would invent
hints rather than find them.

The orphan check matched a SourceFile name anywhere under any source root, so
moving a class to another package without a clean left an orphan that the NEW
file answered for -- App.java satisfying the lookup for the old package's
App.class -- and the stale class stayed, failing the placement check on every
incremental build. The package is part of the match now, read from the file
rather than inferred from its directory, since Kotlin does not require the two
to agree.

Settings had the same conventional-roots assumption on the other side, and
falling through to null there let the caller trust a stale manifest again -- the
bug the source scan exists to prevent, reappearing for anyone whose layout is
merely unusual. It searches the project for a file that DECLARES the class now:
package statement plus a class or object declaration, so a configured root or a
Kotlin file named after something else is found anyway. Bounded in depth, in
queue length and in how many files it will open, and target/ and build/ are
skipped so a compiled copy of the same source cannot answer for it.

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

ℹ️ 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 scripts/build_hint_miner.py
Two from the same review.

IPhoneBuilder builds a hint name into a local -- `"ios." + privacyKey` -- and
passes the local to getArg, so the accessor's argument is a bare variable and
read as forwarding while the name is assembled two lines up. That is the fourth
route by which a literal reaches an accessor without sitting at one. The miner
recognises a local assembled from a literal now and reports it, which surfaced
this site and no other.

The finding underneath it is sharper than "uncatalogued", and worth stating
exactly: ios.NSBluetoothAlwaysUsageDescription, its Peripheral twin and
ios.NSSpeechRecognitionUsageDescription were matched by the dynamic family
ios.NS*UsageDescription, so the gate was right that they were "described" and
they still had no annotation, no documentation row and no editor entry. That
family exists so an app can set an ARBITRARY Apple key. A key the platform
feature catalog injects is a known one, and known keys get described
individually -- they are now, as @IosPrivacy attributes like the other fourteen.

So the new cross-check requires a CONCRETE row for every injected plist key and
deliberately does not accept the dynamic pattern. My first version did accept it
and was therefore inert; the negative test passing is what showed that, after I
went looking for why an injected unknown key did not fail the gate.

Second finding: matching a source file by name and package alone kept a stale
class when a Kotlin type is renamed in place without renaming its file, or when
one type is deleted from a file holding several -- the survivor answered for it,
the orphan stayed, and the placement check failed every incremental build. The
file must declare the type now: class, interface, enum, object or record.
Unreadable still answers yes, as everywhere else in this guard, because what it
decides is whether to IGNORE an annotated class.

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

ℹ️ 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".

Three from the same review, all fallout from the orphan filter.

A nested type's binary name is Main$Wrong and no source declares a type spelled
that way, so the search found nothing, the class was read as an orphan and
dropped, and the placement check that would have said "annotations belong on the
main class" never ran. The build then succeeded with the requested hints
silently absent -- the exact failure this feature exists to remove, reintroduced
by the guard meant to protect it. The lookup uses the outermost simple name now,
which is the type a source file actually declares. There is a test putting an
annotation on a nested class and asserting the placement error.

CN1BuildMojo's guard rescanned every class file without that filter, so a stale
annotated .class left by a rename without a clean failed every build with "no
manifest was produced", naming a class already deleted. Rather than duplicate
the orphan test there, the guard now asks about the MAIN class alone: the
processor honours no other class, so no other class is evidence that annotations
went unprocessed. That is both narrower and immune to orphans by construction.
It still scans everything when the project names no main class, since then there
is nothing more specific to ask.

The Settings search decided as it walked, so its budget could be spent on
unrelated files before reaching the one Kotlin source whose name differs from
its class -- the only layout that search exists for, and so exactly the case it
dropped. Files are collected first and examined in two passes: every file named
after the class, then Kotlin files that are not. Java is excluded from the
second pass because a public Java type must be named after its file, so a
differently named .java cannot declare an application's main class.

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

ℹ️ 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".

Three from the same review.

A scalar with surrounding whitespace is no longer migrated. It looks like it
means what it says and it does not: AndroidGradleBuilder compares
android.hideStatusBar with .equals("true"), so `=true ` is false today, while
other builders trim or ignore case. Trimming it into an annotation `true` would
change what the app builds with and still report success, since the verification
asks whether the hint came back and not what it holds. Which reading is right
differs per builder, so this refuses and says why rather than picking one. A
string keeps its whitespace and migrates as before -- there the space is the
value.

Reducing Main$Wrong to Main so its file could be found let the LIVE outer class
vouch for a nested type that had been deleted, so the orphan stayed and failed
the placement check on every incremental build: one silent failure swapped for a
loud permanent one. The nested name has to be declared too. An unnamed segment
-- Main$1, an anonymous class -- asks nothing, since no source declares one and
none can carry an annotation.

Both declaration checks looked for `class X` with indexOf, so a commented-out
`// class Wrong` left by the edit that deleted the type vouched for its own
orphan, and on the Settings side an unrelated file mentioning the main class in
a comment or a string answered for it -- ownership then reads as empty and
Settings offers Add for a hint the real main class annotates. The processor
blanks comments and string literals before looking; Settings reuses the
comment-aware walk it already had, for the package statement as well as the
declaration.

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: 3235077b25

ℹ️ 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".

Two from the same review, and the last two places still asking the wrong
question.

Settings returned from the conventional path on the file merely EXISTING. Move a
Kotlin main class into a differently named file and leave the old Main.kt
holding something else, and it returned that unrelated file and never reached
the declaration search added for exactly this -- reporting the annotated hints
as unowned, which is the state that lets Add write the duplicate. The
conventional file now has to declare the class before it is accepted.

cn1:migrate-build-hints had the same hard-coded three roots and filename, and
aborted with "Could not find the source" on a project Maven compiles perfectly
well: a module may add src/app/java, and Kotlin does not require a file to be
named after its class. It asks the owning MavenProject for its compile source
roots and looks for the file that DECLARES the class.

The declaration test is the annotation processor's, made public and called,
rather than a third copy. Two copies have already drifted apart in this change
and a third would drift again; what counts as a declaration should have one
answer, and it now has tests of its own -- comment, block comment, string, a
commented-out declaration followed by the real one, a longer name that must not
match, and that blanking preserves offsets.

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: 80e6c2a9b5

ℹ️ 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 on lines +593 to +595
case BOOLEAN:
if ("true".equalsIgnoreCase(v.trim())) return "true";
if ("false".equalsIgnoreCase(v.trim())) return "false";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve case-sensitive boolean semantics during migration

Fresh evidence after the whitespace fix is that mixed-case values are still normalized: when a project has codename1.arg.android.hideStatusBar=TRUE, AndroidGradleBuilder's exact .equals("true") comparison currently treats it as false, but this case-insensitive branch migrates it to @Android(hideStatusBar = true) and deletes the property. Because verification checks only that the key was emitted, the migration succeeds while changing build behavior; reject noncanonical casing unless the specific consumer is known to accept it.

Useful? React with 👍 / 👎.

Comment on lines +2340 to +2342
while (i < text.length()
&& (text.charAt(i) == ' ' || text.charAt(i) == '\t')) {
i++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize legal whitespace before the main class name

When a valid Java or Kotlin declaration puts a newline or comment between the keyword and name, such as class\nMain, this loop skips only spaces and tabs, so both the conventional lookup and fallback search fail to recognize the configured main class. If an annotation was added since the last build, annotationOwnedHintsFromSource() then falls back to a missing or stale manifest, and Settings can offer Add and write a duplicate property that the next build rejects; consume all legal whitespace and skipped comments before reading the identifier.

Useful? React with 👍 / 👎.

Comment on lines +323 to +328
// The nested type has to be there too. Reducing Main$Wrong to Main so the
// file can be found let the LIVE outer class vouch for a nested type that
// had been deleted, and the orphan then failed the placement check on
// every incremental build -- swapping one silent failure for a loud
// permanent one.
return nested == null || declaresType(text, nested);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match nested owners by their full declaration path

Fresh evidence after the nested-orphan fix is that this verifies only the innermost simple name, not its enclosing declaration chain. If stale Main$A$Wrong.class remains after A.Wrong is deleted while the same source still declares an unrelated Main.B.Wrong, declaresType(text, "Wrong") returns true, the orphan is retained, and every incremental process-annotations run fails placement until a clean build; match the full Main.A.Wrong nesting identity.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant