Skip to content

App intents: Siri, Spotlight and shortcuts from one declaration - #5559

Open
shai-almog wants to merge 137 commits into
masterfrom
app-intents
Open

App intents: Siri, Spotlight and shortcuts from one declaration#5559
shai-almog wants to merge 137 commits into
masterfrom
app-intents

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Siri, Spotlight, the Shortcuts app, an Android launcher shortcut and a widget button all ask an application the same question: what can you do, and will you do it now? This models them as one concept under com.codename1.intents. You declare a capability once and the framework projects it to each of them.

@AppIntent(value = "log_workout", title = "Log a workout",
        phrases = {"Log a workout in ${applicationName}"}, headless = true)
public static IntentResult logWorkout(@IntentParam("minutes") int minutes) {
    WorkoutStore.append(minutes);
    return IntentResult.spoken("Logged " + minutes + " minutes.");
}

There was no existing support for any of this — NSSiriUsageDescription only ever fell out of the generic ios.NS*UsageDescription mechanism, and Android had none at all.

Three constraints shaped the design

Each of them fails silently if ignored, which is why they drove the shape rather than being worked around:

Reflection does not exist on a translated iOS build. Class.getAnnotation returns null there, Class.forName is a compile error in core, and the dead-code eliminator strips any method with no Java caller. So a handler is a public static method and the build generates a direct static call to it. Nothing is looked up at runtime.

CHECKCAST is unchecked in ParparVM, so a bad cast hands the wrong object onward instead of throwing. The generated parameter coercion therefore never casts a payload value — every branch is instanceof-guarded, and an entity arrives as its id and becomes an object only through its own BY_ID query.

Swift cannot call the translated Java. Parser.readNativeFiles scans only *.m for mangled symbols, so a Java method named solely from Swift becomes an empty stub with no error anywhere. The App Intents declarations are consequently a thin Swift shell that hands off to CN1IntentHost, an Objective-C shim; the behaviour stays in Objective-C and Java.

Most of this is not Swift at all

Core Spotlight carries indexing and NSUserActivity carries donation — both Objective-C, both long predating the port's deployment floor. So an app that only publishes content to device search pays nothing, including no change to its deployment target.

Only a declared @AppIntent can contribute the App Intents floor, and even that is a fallback: every generated Swift type is availability-guarded. Two build hints keep the decision with the developer:

Hint Effect
ios.intents.minDeploymentTarget The floor a declared intent contributes; empty contributes none
ios.intents.appIntents=false Keeps indexing and donation, suppresses App Intents generation

A deployment target pinned below what a declared intent needs is reported rather than silently raised or silently dropped.

Android is not Siri parity, and doesn't claim to be

Android has no contract by which an assistant invokes a capability and receives a typed result. So phrases, system disambiguation and spoken results are documented as iOS-only and isVoiceInvocationSupported() answers false. What Android does get is real: launcher shortcuts, donation, and genuinely headless execution — easier there than on iOS, since the port already boots without an Activity.

Intents and @Route stay separate

They answer different questions: a deep link has no return channel and cannot disambiguate. They meet only where an intent opens the app, so opensRoute builds the URL from the bound parameters and navigates through the existing route table. One screen, one route pattern, two ways in. Nothing in the routing layer changed.

A live @Route bug fixed on the way in

ProcessAnnotationsMojo gated dispatch on class-level annotations alone, so the documented static-factory @Route form was silently dropped in real builds. The per-processor tests missed it by calling processClass directly, bypassing the Mojo. The new test drives the Mojo itself and fails without the fix.

Verification

  • 4853 core tests, 642 plugin tests (+47 new)
  • SpotBugs 0 and PMD 0 over core; no new check-cast-semantics baseline entries
  • The processor run end-to-end against the new sample — which is how the nested-class emission bug was caught (Outer$Inner is not legal in generated source; a nested entity is the ordinary case)
  • Android port and JavaSE port compile; developer guide renders clean

What still needs a Mac

The plan records these as spikes and they are unchanged by this PR:

  1. Does Xcode emit App Intents metadata below IPHONEOS_DEPLOYMENT_TARGET = 16? If yes, no app ever changes floor and the third tier above is dropped entirely.
  2. The build server's actual Xcode/Swift version — the repo's highest check is xcodeVersion >= 9.

The Objective-C/Java halves are verified locally; the Swift and native compilation are not, and cannot be here.

Companion PR

The builder changes are mirrored to BuildDaemon, without which cloud builds would silently ship without intents while the docs promise them.

🤖 Generated with Claude Code

…codename1.intents)

Siri, Spotlight, the Shortcuts app, an Android launcher shortcut and a widget
button all ask an application the same question: what can you do, and will you
do it now? This models them as one concept. An application declares a capability
once and the framework projects it to each of them.

    @AppIntent(value = "log_workout", title = "Log a workout",
            phrases = {"Log a workout in ${applicationName}"}, headless = true)
    public static IntentResult logWorkout(@IntentParam("minutes") int minutes) {
        WorkoutStore.append(minutes);
        return IntentResult.spoken("Logged " + minutes + " minutes.");
    }

Three constraints shaped the design, and each of them fails silently if ignored:

Reflection does not exist on a translated iOS build. Class.getAnnotation returns
null there and Class.forName is banned in core, and the dead-code eliminator
strips any method with no Java caller. So the handler is a public static method
and the build generates a direct static call to it. Nothing is looked up at
runtime.

CHECKCAST is unchecked in ParparVM, so a bad cast hands the wrong object onward
instead of throwing. The generated parameter coercion therefore never casts a
payload value: every branch is instanceof-guarded, and an entity arrives as its
id and becomes an object only through its own BY_ID query.

Swift cannot call the translated Java. The eliminator scans only .m sources for
mangled symbols, so a method named solely from Swift becomes an empty stub with
no error anywhere. The App Intents declarations are consequently a thin Swift
shell that hands off to CN1IntentHost, an Objective-C shim; the behaviour stays
in Objective-C and Java.

Most of the feature is not Swift at all. Core Spotlight carries indexing and
NSUserActivity carries donation, both Objective-C and both long predating the
port's deployment floor -- so an application that only publishes content to
device search pays nothing, including no change to its deployment target. Only a
declared @AppIntent can contribute the App Intents floor, and two build hints
(ios.intents.minDeploymentTarget, ios.intents.appIntents) keep that decision with
the developer. A deployment target pinned below what a declared intent needs is
reported rather than silently raised or silently dropped.

Android gets launcher shortcuts, donation and genuinely headless execution -- the
last easier there than on iOS, since the port already boots without an Activity.
It does not get Siri: Android has no contract by which an assistant invokes a
capability and receives a typed result, so phrases, system disambiguation and
spoken results are documented as iOS-only and isVoiceInvocationSupported()
answers false rather than pretending.

Intents and @route stay separate because they answer different questions -- a
deep link has no return channel and cannot disambiguate. They meet only where an
intent opens the app, so opensRoute builds the URL from the bound parameters and
navigates through the existing route table. One screen, one route pattern.

Also fixes a live @route bug found on the way in: ProcessAnnotationsMojo gated
dispatch on class-level annotations alone, so the documented static-factory
@route form was silently dropped in real builds. The per-processor tests missed
it by calling processClass directly, bypassing the Mojo; the new test drives the
Mojo itself and fails without the fix.

Verified: 4853 core tests, 642 plugin tests, SpotBugs and PMD at zero over core,
no new cast-semantics baseline entries, and the processor run end to end against
the new sample -- which is how the nested-class emission bug (Outer$Inner is not
legal in generated source) was caught. The Xcode-side questions the plan records
still need a Mac: whether App Intents metadata is emitted below iOS 16, and the
build server's Swift version.

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: 727474bb0f

ℹ️ 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/Android/src/com/codename1/impl/android/intents/AndroidIntentBridge.java Outdated
@github-actions

github-actions Bot commented Aug 17, 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)
    • 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.

Four failures, all real and all mine.

Android SpotBugs (SIC_INNER_SHOULD_BE_STATIC_ANON, x2). CN1IntentService held
two anonymous inner classes. Both capture nothing from the service, so they are
now named static nested classes -- which also stops them pinning the enclosing
instance for the life of an invocation. I had compiled the Android port locally
but not run its SpotBugs; both android and ios modules are clean now.

Copyright headers on the two new Swift files. The checker requires the complete
block header starting with /*, which Swift accepts; my // banner was judged
partial. The existing surfaces Swift files carry no header at all and so are
never checked, which is why they were not a useful model.

Inline guide snippets. The developer guide forbids [source,java] blocks inline
-- snippets have to live under a compiled source root and be included by tag, so
they are compiled and cannot rot. The five blocks moved into
AppIntentsJava00NSnippet.java and the chapter now includes them. They compile.

Renamed com.codename1.intents.Entity to AppEntity, which is what compiling those
snippets exposed: it collides with com.codename1.annotations.Entity (the ORM)
under the wildcard imports both the guide scaffold and ordinary CN1 apps use. I
had already named the annotation @IntentEntity to dodge that clash and missed
the runtime type. Better found here than by an app that uses the ORM and intents
together.

Verified: 4853 core tests, 642 plugin tests, SpotBugs and PMD zero over core,
android and ios, cast-semantics clean, copyright clean, all 662 guide snippet
includes validated, the guide renders warning-free, and the demo snippets
compile on JDK 17.

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: 21f1a3ee92

ℹ️ 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/intents/Intents.java Outdated
@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

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

All seven were correct. Fixed rather than argued with.

P1 Any installed app could invoke any capability. The trampoline has to be
exported, because the launcher is what starts a shortcut, and it accepted an
arbitrary intent id with attacker-supplied parameters. Keeping the service
unexported was no defence, since the activity proxied straight to it. It now
refuses a caller that is neither this application nor a resolved home-screen
launcher, and refuses silently so probing learns nothing from the difference
between a rejected known id and a rejected unknown one.

P1 NSUserActivityTypes was emitted inside the surfaces branch, so an app that
declared intents without also enabling the widget extension got no plist keys
and donation appeared to succeed while nothing was ever suggested. It has
nothing to do with widgets; it is now emitted for any app that declares an
intent.

P1 The generated perform() turned every outcome into a dialog-only success.
A failed handler made Shortcuts record success and run the following actions;
values, entities and routes were discarded. It now throws CN1IntentFailure on
failure and returns the value through ReturnsValue<String>. Navigation for an
opens() result moved into the framework rather than the generated Swift, which
fixes Android at the same time -- the route table is Java, and the platforms
only know how to bring the app forward.

P1 discoverable=false was ignored: isDiscoverable was emitted only when the
unrelated destructive flag was set, and then always true. A donation-only
capability was therefore listed in the Shortcuts catalogue before it had ever
been donated. Now emitted from the flag it belongs to, and destructive gets
what it actually wanted, a requestConfirmation() before the handler runs.

P2 A cold-start headless shortcut visibly opened the app: the trampoline asked
the declaration table which does not exist yet when a tap starts a dead process
-- the common case. The headless flag now travels in the shortcut URI, so no
runtime lookup is needed.

P2 Non-required parameters were emitted as non-optional Swift types, so the
system prompted for a value the handler was willing to do without and the
Java-side default never applied. Optional now, and an absent value is left out
of the payload entirely.

P2 clearIndex only knew what this process had indexed, while shortcuts outlive
the process -- so after a restart it removed nothing and every previously
indexed item stayed visible. It now asks the platform what is actually
published.

The cast-semantics gate then caught two of the new methods holding implicit
casts inside catch(Throwable); only the platform call is guarded now.

Verified: 4855 core tests, 651 plugin tests, SpotBugs zero over core, android,
ios and the plugin, PMD zero, cast-semantics clean, copyright clean, 662 guide
snippet includes validated.

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: 14e4b1ac1c

ℹ️ 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/Android/src/com/codename1/impl/android/intents/CN1IntentService.java Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actions Bot commented Aug 17, 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 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 8.21% (7996/97445 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.21% (42450/517111), branch 2.96% (1450/48905), complexity 3.24% (1700/52405), method 4.99% (1381/27684), class 10.09% (372/3685)
    • 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: 8.21% (7996/97445 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.21% (42450/517111), branch 2.96% (1450/48905), complexity 3.24% (1700/52405), method 4.99% (1381/27684), class 10.09% (372/3685)
    • 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 220ms / native 87ms = 2.5x speedup
SIMD float-mul (64K x300) java 159ms / native 66ms = 2.4x 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 94.000 ms
Base64 CN1 decode 84.000 ms
Base64 native encode 336.000 ms
Base64 encode ratio (CN1/native) 0.280x (72.0% faster)
Base64 native decode 263.000 ms
Base64 decode ratio (CN1/native) 0.319x (68.1% faster)
Image encode benchmark status skipped (SIMD unsupported)

One of the six was already fixed by the previous commit (destructive intents now
call requestConfirmation before the handler runs). The rest were real.

P1 The generated shortcuts.xml would have failed the Android build outright.
android:shortcutShortLabel is defined as a resource reference, so AAPT rejects a
literal during resource linking. Labels now go into a generated
cn1_shortcuts_strings.xml and the shortcut references them.

P1 A static shortcut for a parameterized intent invoked the handler with nulls.
A static shortcut carries no parameters and Android has no picker on that path,
so the log_workout example in our own sample would have logged a zero-minute
workout of no kind. Only intents that can run as declared -- no required
parameter, or a default for each -- become launcher shortcuts now, and the build
says which were skipped and why. They stay invocable and donatable with values
bound.

P2 exposure was recorded in the Java declaration but omitted from intents.json,
so both builders treated a MODEL-only intent as a normal one and published it to
Siri and the launcher. It travels in the manifest now and both builders honour
it. An absent list still means the default, so nothing built before the field
existed changes behaviour.

P2 A closed vocabulary was documented and never enforced: the generated
dispatcher coerced whatever arrived and called the handler. It now rejects a
value outside the declared options rather than quietly substituting the default,
which would run the handler with something nobody asked for.

P2 Registering a dynamic intent produced an id nothing could run. The dispatcher
only knows build-time ids, so every registered parameterization failed as
unknown. registerDynamicIntent now takes a DynamicIntent that names the declared
intent it runs and the values it binds; invocation resolves to that base with
the bound values underneath anything supplied at call time, so a binding is a
default rather than a lock. A parameterization that names an undeclared base, or
shadows a declared id, is refused instead of being advertised.

The cast-semantics gate then caught the new lookup holding a for-each inside
catch(Throwable) -- a CHECKCAST ParparVM expands to nothing, so the guard was
protecting against something that cannot fire where it matters. Restructured.

Verified: 4861 core tests, 658 plugin tests, SpotBugs zero over core, android,
ios and the plugin, PMD zero, cast-semantics clean, copyright clean, 662 guide
snippet includes validated, guide renders warning-free.

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: 9a529a528f

ℹ️ 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/iOSPort/src/com/codename1/impl/ios/IOSIntentBridge.java Outdated
Vale reported 17 alerts and LanguageTool 2 matches on the new chapter, and both
are zero-tolerance in CI. All in the chapter I added.

Vale: contractions the Microsoft style wants (can't, that's, they're, it's,
isn't), punctuation moved inside quotes, a capital after a heading colon,
rhetorical questions rewritten out of the first person, and the adverbs
"silently" and "quietly" removed -- which in two places meant saying the thing
plainly instead, that no error is reported anywhere and that the build stops
rather than acting behind your back.

LanguageTool: "on screen" used as a predicate adjective, and "afterwards" where
American English prefers "afterward".

Verified locally: vale clean across all 114 guide files, LanguageTool 0 matches
against the rendered HTML, asciidoctor renders warning-free, and all 662 snippet
includes still validate.

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: 6b803c071c

ℹ️ 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/Android/src/com/codename1/impl/android/intents/AndroidIntentBridge.java Outdated
P1 The trampoline's caller check was itself spoofable. getReferrer() returns the
caller-supplied Intent.EXTRA_REFERRER in preference to anything the platform
attributes, so an attacker just claimed to be the launcher. Android offers no
reliable caller identity for startActivity at all -- getCallingPackage() is null
unless the caller used startActivityForResult, which a launcher does not.

So trust now comes from the URI rather than the caller. Shortcuts published at
runtime carry a nonce minted on first use into private preferences, which no
other app can read; those URIs may do anything. A URI without it -- a build-time
static shortcut, or a fabrication -- is held to exactly what the launcher already
offers: discoverable, not destructive, no unsatisfied required parameter. Its
parameters are dropped, so nothing can be injected. An attacker is bounded to
what the user could already do by tapping the app's own shortcut.

P1 Parameterized Siri phrases did not bind. Only ${applicationName} was
translated, so "${minutes}" stayed literal text: the phrase read the placeholder
back to the user and never supplied the argument. Parameters now become
\(.$name); a placeholder naming nothing is left alone rather than emitting an
interpolation that would not compile.

P1 Deleting the last declaration left the previous build's output in
target/classes to be packaged again -- publishing removed shortcuts and calling
handlers that no longer exist. The empty case now deletes what it used to write.

P2 The Android service capped every wait at 25 seconds, so an intent declaring
more had its runtime torn down mid-handler with nothing reported. The backstop
now derives from the declaration, plus a margin, since the framework already
enforces the real deadline.

P2 ios.intents.appIntents=false removed the donation implementation along with
the declarations, so NSClassFromString found nothing and donation silently did
nothing -- the opposite of what the option promises. The bridge and donation shim
are now generated either way.

P2 A required parameter with no default became null, 0 or false and the handler
ran anyway. It now fails before any side effect, including an entity id that
resolves to nothing, which is just as missing as an absent value.

P2 Concurrent Intents.index() calls could consume each other's staged
thumbnails. Stage-and-submit is now one transaction.

P2 A MODEL-only feature raised the iOS deployment floor even though it produces
no App Intent. The floor is decided from the intents that actually reach the
platform.

P2 A donated parameterization stopped working once the process died: the
shortcut outlives the runtime but the binding did not. Donation now resolves to
the base intent with the bound values merged in, so the shortcut carries
something the build-time registry can run.

SpotBugs then caught a SecureRandom constructed for a single use; it is held now.

Verified: 4861 core tests, 660 plugin tests, SpotBugs zero over core, android,
ios and the plugin, PMD zero, cast-semantics clean, copyright clean, vale clean
across 114 guide files, 662 snippet includes validated.

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

ℹ️ 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/iOSPort/nativeSources/IOSNative.m Outdated
Comment thread Ports/iOSPort/nativeSources/IOSNative.m Outdated
Three of these are regressions the previous round's security fix introduced,
which is a fair reminder that locking a door can wall in the people who live
there.

P1 Every build-time static shortcut merely opened the app. A static shortcut has
no nonce -- there is no runtime at build time to mint one -- and on a cold start
the declaration table does not exist either, so the new unauthenticated-request
guard rejected the invocation before anything could queue it. A request that
arrives before the declarations do is now parked and judged when they appear,
using registerIntents as the hook, since that fires exactly when the generated
dispatcher installs itself.

P1 The "open" branch ignored the nonce entirely, so any installed app could
foreground this one and drive its selection handler with an entity id of its
choosing. Indexed shortcuts are always published at runtime and therefore always
carry the nonce, so requiring it costs nothing and closes the hole.

P1 The headless flag was taken from the URI even on an unauthenticated request,
which let another app run a non-headless intent in a process with no window.
It is now believed only from a URI this application published, and the
declaration wins wherever it is available.

P2 Donation was gated on iOS 16 even though NSUserActivity donation predates App
Intents by years, so the ios.intents.appIntents=false opt-out kept the bridge and
still donated nothing. Donation is now plain Objective-C with no availability
gate and no Swift at all -- which removes the dependency rather than working
around it, so the Swift donation shim is gone and the opt-out emits no Swift.

P2 Images staged for a result were never released, so an app returning fresh
imagery on each invocation retained every blob for the life of the process. The
completion consumes them now, as indexing already did.

Verified: 4861 core tests, 660 plugin tests, SpotBugs zero over core, android,
ios and the plugin, PMD zero, cast-semantics clean, copyright clean.

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: 52eb32ad38

ℹ️ 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/intents/Intents.java
Comment thread CodenameOne/src/com/codename1/intents/Intents.java
Comment thread CodenameOne/src/com/codename1/intents/DynamicIntent.java Outdated
P1 An iOS cold start dropped the activity. On the legacy lifecycle the platform
delivers a donated NSUserActivity before the generated bootstrap runs, so the
declaration lookup necessarily failed and the activity was reported unhandled --
tapping a donated shortcut from a dead process launched the app and did nothing.
It is queued now and run once setDispatcher installs the table, then dropped if
nothing turns out to declare it. Only ids shaped like ours are held: a handoff or
third-party type is reverse-DNS and cannot match, so this claims nothing that
belongs to somebody else.

P2 donate() bypassed exposure. The static builders filter a MODEL-only intent out
of the launcher and out of App Intents, but the runtime donation path published
it anyway -- exposing exactly what it opted out of. Checked at the call now.

P2 A dynamic id could contain a colon. Android identifies indexed entries by that
separator, so donating a "call:mum" and then clearing the content index would
disable the donated action along with real content. Dynamic ids are held to the
same shape the build enforces for declared ones.

Not fixed, and documented in the code where it lives rather than argued about:
the iOS result value is reduced to text. ReturnsValue<T> fixes T in the struct's
signature at build time, while a handler's value is IntentResult.value(Object) --
nothing in the declaration says what type it will be, and it can differ between
two invocations of the same intent. ReturnsValue<String> is the only type-stable
choice for every handler, and the alternative is returning no value at all. The
wire document keeps the JSON type for consumers that care. Typing it properly
needs the declaration to state a return type, which is a deliberate future
addition rather than something derivable from what exists today.

PMD then objected to a helper named like a constant; renamed.

Verified: 4866 core tests, SpotBugs zero over core, android, ios and the plugin,
PMD zero, cast-semantics clean.

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

ℹ️ 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/Android/src/com/codename1/impl/android/intents/AndroidIntentBridge.java Outdated
P2 Indexed entity thumbnails never reached Android. The serializer hands the
bytes over in the images map -- the JSON carries only the name they were
registered under -- and the bridge discarded it, so every indexed entry showed
the application icon and a list of content was a column of identical squares.
The blobs are matched by name now and become the shortcut's icon, falling back
to the app icon only when an entity has none.

P2 An optional Date ignored its declared default. Every other type honoured one,
so a date silently arrived as null instead of the documented fallback -- most
visibly on iOS, where an absent optional parameter is deliberately left out of
the payload entirely.

Verified: 660 plugin tests, SpotBugs zero over android and the plugin,
cast-semantics clean.

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: 984e2b9cbd

ℹ️ 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/intents/Intents.java Outdated
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/IOSIntentBridge.java Outdated
…d snippets

P2 Snippets were documented and silently thrown away. Java serialized the node
tree and its images; the generated Swift returned only the value and the spoken
line, and nothing ever rendered one.

They render now, and by reusing the surfaces renderer rather than growing a
second layout vocabulary -- a snippet is a small layout shown while the app may be
off screen, which is exactly what a widget is, so cn1RenderNode already knew how.
The renderer is written under the same filenames the surfaces builder uses, so an
app with both features gets one copy rather than two conflicting declarations.
The images reach it as files in a per-invocation cache directory, which is how
the widget extension resolves them too.

P2 A declared opensRoute template was never expanded. The framework navigated
only when the handler returned an explicit URL, so an intent that returned ok()
foregrounded the app onto whatever screen it was already showing -- which is the
one thing opensRoute exists to prevent. Expanded from the values the intent ran
with; a placeholder with no value navigates nowhere rather than routing somewhere
unintended.

P2 With ios.intents.appIntents=false the plist keys were omitted, so donation ran
and iOS had no declaration for the activity types it produced. Emitted for that
configuration too.

P2 iOS donation did not resolve a parameterization, so a suggestion outlived the
process while its binding did not and failed as unknown after a restart. It now
resolves to the base intent with the bindings merged, as Android already did.

Verified: 4868 core tests, SpotBugs zero over core, android, ios and the plugin,
PMD zero, cast-semantics clean, copyright clean.

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

ℹ️ 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/intents/Exposure.java
Comment thread CodenameOne/src/com/codename1/intents/Intents.java Outdated
Comment thread Ports/Android/src/com/codename1/impl/android/intents/AndroidIntentBridge.java Outdated
…omise

P1 CN1IntentBridge.swift would not compile on an App Intents build. It uses
CustomLocalizedStringResourceConvertible, which AppIntents declares, but only
imported Foundation -- canImport tests availability without importing, and the
import in the separately generated CN1AppIntents.swift is file-scoped.

P1 asTools() was documented in three places and did not exist, so an application
following the Exposure.MODEL documentation would fail to compile. It is
implemented now: model-exposed intents project down to com.codename1.ai.Tool with
a JSON schema built from their declared parameters, and a handler that runs the
intent and returns its serialized result. Entity parameters are described as the
id string that actually crosses the boundary. The projection stays one-way and
opt-in -- a model calls a capability because it inferred it should, which is not
the same trust as a person asking for it by name.

P2 Route template values were substituted verbatim, so an id containing "/" added
a path segment and stopped matching the route it was built for, and "?" or "#"
could invent a query or a fragment. Encoded now: the value is data, the template
is structure.

P2 The Android thumbnail map was staged on the bridge instance, so two threads
indexing at once could have the second overwrite it between the first parsing its
JSON and reading its blobs -- publishing the other request's thumbnails or none.
Carried through as a parameter, the same fix the iOS staging race got.

PMD then wanted @OverRide on the new handler.

Verified: 4872 core tests, SpotBugs zero over core, android, ios and the plugin,
PMD zero, cast-semantics clean.

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: 860ef935d1

ℹ️ 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/intents/Intents.java Outdated
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/IOSIntentCallbacks.java Outdated
Comment thread Ports/Android/src/com/codename1/impl/android/intents/AndroidIntentBridge.java Outdated
Comment thread CodenameOne/src/com/codename1/intents/Intents.java
@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

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

…they never owed

A real Xcode toolchain turned four assumptions into measurements.

The deployment target was the big one. The design carried a third tier -- an app
declaring an @AppIntent pays the App Intents floor -- as a fallback in case Xcode
refused to extract metadata below it. It does not refuse. A target built at
IPHONEOS_DEPLOYMENT_TARGET = 13.0 with availability-fenced declarations emits a
complete Metadata.appintents carrying every intent and entity. So the tier is
gone: ios.intents.minDeploymentTarget now defaults to contributing nothing, no
app's minimum moves, and the intents are simply not offered on devices too old to
run them. The pin conflict it used to raise is now only reachable when a project
deliberately asks for the floor and deliberately pins below it.

Three compile failures in the generated Swift, each of which would have shipped:

- A snippet result needs `import SwiftUI` as well as `import AppIntents`. The
  .result(value:dialog:view:) overload lives in the _AppIntents_SwiftUI
  cross-import overlay, which only activates when both modules are imported;
  without it the call is "extra argument 'view'".
- A phrase parameter interpolates as \(\.$name), a key path. AppShortcutPhraseToken
  itself carries only .applicationName; the parameter interpolation is declared
  over KeyPath<Intent, IntentParameter<Value>>.
- CN1SurfaceRenderer.swift is now compiled into the app target too, which can
  deploy far below the extension's 16.1. Its ProgressView / Link / Text(_:style:)
  uses are iOS 14, so the functions carry @available(iOS 14.0, *), and an app that
  declares an intent but publishes no widgets gets a stub CN1SurfaceConfig.swift
  rather than an unresolved cn1SurfacesAppGroup.

And Apple enforces two phrase rules the processor did not: at most one parameter
per phrase, and that parameter must be an entity. Both are halting errors in
appintentsmetadataprocessor that produce no metadata at all -- the app builds,
ships, and has no intents. The processor now reports them against the declaration
that caused them, along with phrases on a non-discoverable intent. The sample and
the guide carried an invalid two-parameter phrase; they no longer do.

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

ℹ️ 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/iOSPort/nativeSources/IOSNative.m Outdated
Comment thread Ports/iOSPort/nativeSources/IOSNative.m
Comment thread Ports/iOSPort/nativeSources/IOSNative.m Outdated
Two different launcher shortcuts tapped before the declaration table loaded left only the
second: parkUntrustedRequest cleared the queue on every call. The first tap's wake started
the runtime and ran the surviving request, and its own wake then found the runtime already
up and returned with nothing to do, so one user action disappeared with nothing logged.
The comment justified the clear as "the user pressing twice", which is true of a repeated
id and false of two different ones.

Repeats of the same id still collapse to one run. Distinct ids are all kept, bounded at
eight because the queue is filled from an exported entry point, and registerIntents now
drains all of them.

That turns the service handshake into a counting problem: parkedFinished was a single flag,
so the first handler to finish told CN1IntentService every handler was done and the runtime
could be torn down under the others. It is a count of outstanding invocations now, and the
budget the service waits out is the longest of the declarations rather than the last one
dispatched.

Verified reflectively against the compiled class: two distinct taps plus a repeat leave
exactly the two ids, and twenty more leave eight.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

shai-almog and others added 2 commits August 19, 2026 06:29
resolveBridge reached through Display for the bridge, and on the earliest cold-start path
-- before Display.init, which this feature deliberately supports -- that dereferences an
implementation which is still null. The throw was caught, and then Log.e was called from
the handler: it routes through the same missing implementation, so it can throw on its own,
and that second throw escapes resolveBridge and completeOrQueue with it. The answer is
never queued, the Swift continuation is never resumed, and the assistant waits forever on
an intent that has already finished. Nothing reports it, because the reporting is what
failed.

Two changes. The dereference is not attempted before Display is initialized -- that is a
supported moment, not an error, and returning null lets the caller queue the result the way
it already knows how. And the logging inside the handler is itself guarded, because a lost
log line costs incomparably less than a hung assistant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The declared ids are recorded so a cold start can recognise its own activity types before
the dispatcher exists, and they are rewritten on every publication. An update that removes
the last @AppIntent while still using the package to index content publishes nothing, and
the processor has deleted the bootstrap by then -- so nothing rewrites the record and the
previous version's ids survive.

They were still being claimed. No dispatcher would ever arrive to drain the queue, so a
stale suggestion opened the app and did nothing, and an application continuation that
reused one of those ids was swallowed permanently.

The proof that the record is stale is already written in the method: the bootstrap installs
the dispatcher from the stub's main, before Display.init. So a Display that is up with no
dispatcher means this build declares nothing at all, and anything the record still names
belongs to a version that is gone. The claim is refused and the record dropped, outside the
lock because it writes to storage. The pre-Display window is untouched, which is the one
case the queue exists for.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

…e generated half

The sample declared no @AppIntent, so CI never built the half of this feature that the
build writes. No declaration means IPhoneBuilder emits no Swift at all -- CN1AppIntents,
CN1AppEntities, the AppShortcutsProvider and the CN1IntentHost shim were never handed to
Xcode -- and AndroidGradleBuilder writes no cn1_shortcuts.xml for AAPT to accept or reject.
Every generated-code defect found in review over the last day was the kind a compiler
reports in a second, and none of them could have been caught here.

The declarations are chosen for the shapes that generate differently rather than for
realism: a closed vocabulary (a Swift AppEnum), an optional parameter with a default, an
entity with all three queries (an AppEntity plus its EntityQuery, and the key-path phrase
interpolation), a destructive intent (the confirmation branch), and a parameterless one --
the only shape Android publishes as a static launcher shortcut, which is what gets the
resource path compiled. Its title carries an apostrophe on purpose, so AAPT itself proves
the string-resource escaping rather than a unit test's idea of it.

IntentsApiTest covers the runtime half on the device VM: the generated registry, the
coercion around every parameter, the declared default, a value outside the closed
vocabulary, and entity resolution behind an id -- including an id that resolves to nothing.
Coercion under a VM whose CHECKCAST is unchecked is not the same code path as coercion in a
JUnit suite, which is the reason it belongs here.

The test reports whether the generated dispatcher is installed rather than assuming it. It
is installed by the bootstrap the device builders splice into the stub, so the table is
populated on iOS and Android and empty on ports with no such splice; the dispatch
assertions run where there is something to dispatch, and say so in the log where there is
not.

Verified before pushing rather than by watching CI: the processor accepts the set and
generates a 4-intent, 1-entity registry; the Swift it produces type-checks against the iOS
26.2 SDK at -target arm64-apple-ios13.0 with the bridging header and the shared renderer;
the Android resources come out with formatted="false", the escaped apostrophe, a resolved
package and exactly one shortcut. That type-check also found the last defect fixed here:
an intent with no parameters emitted `var params` that is never mutated, which is a Swift
warning in a file the developer never wrote and cannot fix.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Adding a test to the device suite is only half of adding it: the contract requires every
registered test to belong to a feature, and three separate jobs enforce that. All three
failed on the same sentence -- "Registered tests without a feature: IntentsApiTest" -- from
the contract validator, its own unit tests, and the website build, which normalizes every
port report through the same code.

So the test now has a feature, and each checked-in port report carries it as "not-run",
which is what it is: this test has never run on any port, and the first real run of each
suite replaces the entry. Their summaries are recomputed from the results rather than
edited by hand, because the validator compares the two. The contract's own test asserts the
registered-test count, which moves from 171 to 172.

Also adds the licence header to UIManagerLargeTextScaleTest, which arrived from master in
the merge and has none. It fails the header gate inside this PR's diff range whoever wrote
it, and the gate is right: the file is missing a header the repo requires.

Verified with the two entry points CI uses: `port_status.py validate` reports a valid
contract of 172 tests across 58 features and 11 ports, `accept` takes a checked-in report,
and the contract's 36 unit tests pass -- which is one better than the branch was before,
since the run had a pre-existing error that this mapping resolves.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@shai-almog

shai-almog commented Aug 19, 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 46ms / native 2ms = 23.0x speedup
SIMD float-mul (64K x300) java 46ms / native 3ms = 15.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 176.000 ms
Base64 CN1 decode 116.000 ms
Base64 SIMD encode 94.000 ms
Base64 encode ratio (SIMD/CN1) 0.534x (46.6% faster)
Base64 SIMD decode 95.000 ms
Base64 decode ratio (SIMD/CN1) 0.819x (18.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 21.000 ms
Image createMask (SIMD on) 17.000 ms
Image createMask ratio (SIMD on/off) 0.810x (19.0% faster)
Image applyMask (SIMD off) 131.000 ms
Image applyMask (SIMD on) 36.000 ms
Image applyMask ratio (SIMD on/off) 0.275x (72.5% faster)
Image modifyAlpha (SIMD off) 38.000 ms
Image modifyAlpha (SIMD on) 27.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.711x (28.9% faster)
Image modifyAlpha removeColor (SIMD off) 36.000 ms
Image modifyAlpha removeColor (SIMD on) 32.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.889x (11.1% faster)

The test asserted everything correctly and then hung: an assertion-only test has to call
done(), and mine returned true without it. The runner waited out the full budget, retried
once, waited again, and reported "timeout waiting for DONE stage=retry-created" -- which is
the runner being right about a test that never said it had finished. Sixty-three other
tests in this suite call done(); this one did not.

The catch now calls fail() as well, for the same reason in the other direction: a test that
throws and merely returns false is reported as hung rather than as failed, which hides what
actually went wrong behind a timeout.

The run that caught this is worth recording, because it is the coverage working. Android
built the APK with the generated cn1_shortcuts.xml and cn1_shortcuts_strings.xml, AAPT
accepted both, the app installed and started, and the log shows the generated registry
present on the device with all four declarations: "supported=true headless=true voice=false
indexing=true declarations=4". Every dispatch assertion ran against real generated code on
a real emulator -- coercion, the declared default, the closed vocabulary and entity
resolution -- and the two deliberate rejections came back as failed results with the
framework logging the cause, exactly as intended.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@shai-almog

shai-almog commented Aug 19, 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 69ms / native 5ms = 13.8x speedup
SIMD float-mul (64K x300) java 137ms / native 4ms = 34.2x 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 214.000 ms
Base64 CN1 decode 132.000 ms
Base64 SIMD encode 104.000 ms
Base64 encode ratio (SIMD/CN1) 0.486x (51.4% faster)
Base64 SIMD decode 94.000 ms
Base64 decode ratio (SIMD/CN1) 0.712x (28.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 20.000 ms
Image createMask (SIMD on) 17.000 ms
Image createMask ratio (SIMD on/off) 0.850x (15.0% faster)
Image applyMask (SIMD off) 54.000 ms
Image applyMask (SIMD on) 118.000 ms
Image applyMask ratio (SIMD on/off) 2.185x (118.5% slower)
Image modifyAlpha (SIMD off) 46.000 ms
Image modifyAlpha (SIMD on) 42.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off) 65.000 ms
Image modifyAlpha removeColor (SIMD on) 47.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.723x (27.7% faster)

@shai-almog

shai-almog commented Aug 19, 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 55ms / native 4ms = 13.7x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x 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 248.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.262x (73.8% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.492x (50.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 8.000 ms
Image createMask ratio (SIMD on/off) 0.667x (33.3% faster)
Image applyMask (SIMD off) 25.000 ms
Image applyMask (SIMD on) 19.000 ms
Image applyMask ratio (SIMD on/off) 0.760x (24.0% faster)
Image modifyAlpha (SIMD off) 17.000 ms
Image modifyAlpha (SIMD on) 12.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.706x (29.4% faster)
Image modifyAlpha removeColor (SIMD off) 21.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.619x (38.1% faster)

The iOS device build failed compiling the Swift this feature generates:
"CN1IntentBridge.swift:70:9: error: cannot find 'CN1IntentHost' in scope", three times over.
CN1IntentHost.h was staged next to the Swift and nothing ever imported it. Objective-C
declarations in the same target are not automatically visible to Swift -- they have to come
through the bridging header -- and that header was written containing a single comment and
never added to.

The same sweep that hands the generated Swift to the app target globs only '*.swift', so
CN1IntentHost.m was staged, would now be imported, and still never compiled: an undefined
symbol at link rather than a missing type at compile. It is named explicitly rather than
globbing '*.m', because that directory is full of translated Objective-C the project
already lists.

This was reported in review and I marked it resolved on the strength of a grep that matched
the bridging header's *existence* rather than an import of this file. It took an actual
device build to disprove that, which is the coverage the sample's new declarations bought:
before them no CI job compiled a line of this Swift.

Verified by reproducing the failure and the fix outside Xcode: with the bridging header as
the builder used to write it, swiftc reports exactly the CI error at exactly line 70; with
the line this now appends, the whole generated set type-checks against the iOS 26.2 SDK at
-target arm64-apple-ios13.0.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

The Linux half of the Windows cross-build sat in its first step for a full hour and was
cancelled having installed nothing; the screenshot-comment job then failed downstream
because the artifact it downloads was never produced. The visible failure was
"Artifact not found for name: windows-cross-screenshot-raw", which says nothing about the
hour spent inside apt.

Every network call in that step is now bounded and retried. scripts/ci/apt-get-install.sh
already exists for exactly this -- its header documents the same hang taking out
archetype-smoke on 2026-08-18 -- and this workflow had simply never adopted it; it wraps
the install in timeout 600 and retries, and skips the round trip when the packages are
already present. apt.llvm.org is a single host with no mirror and llvm.sh both adds its
repo and installs from it, so that call gets an explicit timeout as well as retries: retry
alone does not help against a download that never returns.

Both trigger blocks now list the three CI shell helpers this step runs through, so a change
to one is exercised here rather than merged untested -- the same reason every other consumer
lists them.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

The Mac Catalyst build failed to compile the delegate:

  error: call to undeclared function
    'com_codename1_impl_ios_IOSIntentCallbacks_nativeSpotlightItemSelected___java_lang_String'
  error: call to undeclared function
    'com_codename1_impl_ios_IOSIntentCallbacks_nativeUserActivity___...'

Both branches call translated Java entry points and nothing imported the header that
declares them. The iOS slice accepted that as an implicit declaration and Catalyst does
not, so the same source compiled on one Apple platform and not the other -- and the
platform that rejected it is the one whose job runs the whole suite through xcodebuild.

The class is translated in that build: com_codename1_impl_ios_IOSIntentCallbacks.m is
compiled there, and nothing referenced its header at all, which is the shape of the bug.
The import sits inside the CN1_USE_INTENTS guard because CodenameOne_GLViewController.h
undefines that for watchOS and tvOS, where the class is not translated and the header does
not exist.

Found by the sample declaring intents. Before that, no CI job compiled these branches on
any platform, because CN1_USE_INTENTS is only defined for an app that references the
package and no app in this repository did.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

…ging

My previous attempt bounded every network call in that step except the first one, and the
first one is where it hangs. The job sat for 59 minutes after "Get:5 noble-security
InRelease" and was cancelled having installed nothing, for the second time in a day, each
time surfacing downstream as "Artifact not found for name: windows-cross-screenshot-raw" --
a message about a job that never ran.

Acquire::Retries does not help here: it covers a request that *fails*, and a mirror that
accepts the connection and then stops sending never fails, so apt waits on it forever. The
per-request Timeout options bound each transfer, the outer `timeout` bounds the whole run in
case they do not, and a second attempt costs little because a stalled mirror is usually a
different mirror next time while a genuinely broken source fails the same way twice.

The step also carries timeout-minutes: 20 now. Adding up the inner bounds gave a worst case
of 97 minutes against a 60-minute job, which is not a bound at all -- and retuning the shared
apt-get-install.sh to fix that arithmetic would change four other workflows. A step cap is
the honest control: it cannot compound, it leaves the build the budget it needs, and it
attributes the failure to the step that stalled rather than to a cancelled job. The llvm.sh
allowance drops from 3x900s to 2x600s for the same reason.

Every consumer of apt-get-update.sh gets the bound, which is the point of it being shared.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

The bound added earlier worked -- the website build reported "first attempt failed or
stalled; retrying once" and gave up after ten minutes instead of hanging for an hour -- and
then failed the job anyway with exit 124. That is a better failure, but it is still a red PR
that no change in this repository can fix: archive.ubuntu.com stalled mid-fetch on two
consecutive attempts, the runner's own azure mirror having been Ign'd, and every job that
starts by refreshing apt would have failed for as long as that lasted.

Refreshing the index is not what any of these jobs are here to do. The runner images ship
with a populated index, so an install of ordinary packages usually succeeds from it, and
apt-get-install.sh is bounded and exits non-zero naming the package it could not fetch. The
gate is the install, not the refresh -- which is what apt-get-install.sh already assumed,
since it has always invoked this script with `|| true`. The standalone callers now agree
with it.

Verified both halves rather than reasoned about them: with sudo forced to fail, this script
exits 0 and prints the warning chain, and apt-get-install.sh still exits 1 with
"could not install: <package>".

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

The cross-build's step cap fired 20 minutes in, and the log shows why: it reached
add-apt-repository at 21:42 having started at 21:24. Eighteen minutes went on fetching the
package index, and the install never got a fair attempt.

That was my own compounding. apt-get-install.sh refreshes inside its three-attempt retry
loop, which cost nothing while the refresh was unbounded and instant-failing -- and once I
made the refresh bounded and retried, each of those three attempts could spend the whole
refresh budget before trying to install. The refresh now happens once, before the loop.

A stale index is not what makes an install fail three times in a row; an unreachable archive
is, and refetching it three times does not make it reachable. The cross-build step also
stops refreshing explicitly, because the helper it calls next already does.

Verified: with sudo forced to fail, the refresh now runs once rather than three times, and
apt-get-install.sh still exits 1 with "could not install: <package>".

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Mac Catalyst rejected the shim with "call to undeclared function
'com_codename1_impl_ios_IOSIntentCallbacks_nativeQueryEntities___java_lang_String_java_lang_String_java_lang_String'".
The file already imported the callbacks header. What it lacked was xmlvm.h, which is what
defines NEW_CODENAME_ONE_VM -- so the #else branch compiled, naming the old-VM spelling
without the _R_<returnType> suffix that the translator emits for a method returning one, and
the header declares only the suffixed form.

The iOS slice let that through as an implicit declaration and Catalyst does not, so the same
file compiled on one Apple platform and not the other. Every other native in this port
includes xmlvm.h first for exactly this reason; this one was written without it.

This is the third defect of the same shape found by the sample declaring intents -- a native
call that no CI job had ever compiled, because CN1_USE_INTENTS is only defined for an app
that references the package and none did.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Declaring an intent broke the tvOS build with 48 errors: every App Intents type needs
tvOS 16 and the slice's floor is 13, so CN1AppIntents.swift and CN1AppEntities.swift could
not compile. They reach that target because they are staged into <Main>-src for the iOS app
target and this builder copies that directory wholesale.

Excluded rather than availability-widened, because the feature is already compiled out
here: CodenameOne_GLViewController.h undefines CN1_USE_INTENTS for TARGET_OS_TV, so the
natives behind these declarations are unsupported stubs on this slice. Widening the
annotations would have produced a tvOS app advertising actions that cannot run, which is
worse than one that offers none.

The surfaces renderer travels with them because a snippet reuses it -- GraphicsContext
needs tvOS 15 -- and the tvOS slice built fine without it until intents started staging it.

The watch slice was already fine: it compiles a specific file list rather than the whole
directory, so it never picked these up. This is the same review finding I resolved earlier
on a grep that matched the words "watch" and "tv" in an unrelated comment; the tvOS half
was never actually implemented, and only a build with a declared intent could say so --
build-ios-tv passed on this branch until the sample declared one.

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

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