Skip to content

Add com.codename1.ai package, ChatView, Speech/TTS, and build-time AI dependency injection#5035

Merged
shai-almog merged 35 commits into
masterfrom
feat/cn1-ai
May 27, 2026
Merged

Add com.codename1.ai package, ChatView, Speech/TTS, and build-time AI dependency injection#5035
shai-almog merged 35 commits into
masterfrom
feat/cn1-ai

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

Introduces the full AI/LLM surface for Codename One plus the
build-server plumbing that auto-wires every native dependency it
needs. Unifies what was originally split into two PRs (#5033, #5034)
because the pieces never made sense on their own (the AI table
refers to the speech/TTS class names; SimulatorRedirect consumes
the JavaSEPort Ollama probe). Rebased on current master.

com.codename1.ai

  • LlmClient static factories for OpenAI, Anthropic, Gemini,
    Ollama, and a generic OpenAI-compatible endpoint. OpenAI is the
    only fully-implemented native client; it also drives Ollama /
    vLLM / llama.cpp because the wire format matches. Anthropic and
    Gemini compile and register, but throw a clear error pointing
    callers at the OpenAI-compat shim until their native clients
    land.
  • Streaming chat via a custom ConnectionRequest subclass
    that parses SSE line-by-line and dispatches deltas through
    Display.callSerially. AsyncResource.cancel() kills the
    socket.
  • Value types: ChatRequest (builder), ChatMessage,
    MessagePart hierarchy, Tool, ToolCall, ToolChoice,
    ResponseFormat, ChatResponse, Usage, Embedding +
    request/response, full LlmException taxonomy.
  • ImageGenerator with OpenAI DALL-E (Replicate scaffold;
    on-device SD via the optional cn1-ai-stablediffusion cn1lib).
  • Utility extras: PromptTemplate, Tokenizer, RetryPolicy,
    ConversationStore, SafetyFilter.

com.codename1.media.SpeechRecognizer + TextToSpeech

New core APIs routed through Display and no-op
CodenameOneImplementation hooks. Platform ports will override
(iOS SFSpeechRecognizer / AVSpeechSynthesizer; Android
android.speech.*). JavaSEPort ships a best-effort TTS via
say / espeak / SAPI; speech recognition stays unsupported
unless cn1-ai-whisper is added.

SecureStorage non-prompting overloads

Single-arg get/set/remove(account, ...) added next to the
existing biometric-gated methods. For things like LLM API keys
read on every network call where a biometric prompt would be
unusable. Base class returns null/false on unsupported ports.

com.codename1.components.ChatView

Scrollable message list, ChatBubble + ChatInput, theme-aware
UIIDs, streaming appendToLastMessage that marshals through
callSerially, and a bindToLlm(client, baseRequest)
convenience that wires the input bar directly to chatStream.

Build-time scanner additions

  • AiDependencyTable in the maven plugin: 18 entries mapping
    com/codename1/ai/* (plus the speech/TTS sister APIs) to iOS
    Pods, Swift Packages, Android Gradle deps, Info.plist usage
    strings, and Android permissions.
  • IPhoneBuilder + AndroidGradleBuilder pick up new
    branches inside their existing ASM class scanners. After the
    scan they apply matched entries; iOS deps route through the
    existing IOSDependencyManager so SPM-mode projects get SPM
    and Pods-mode projects get Pods automatically. Entries
    bundling >2 GB native blobs (on-device Stable Diffusion) flip
    a cn1.ai.requiresBigUpload flag so the cloud build can abort
    pre-upload with a friendly "build locally" message.

JavaSE simulator: Ollama detection

probeOllamaAsync() pings localhost:11434 at startup; sets
cn1.ai.ollamaDetected when reachable. SimulatorRedirect
reads that property and, with cn1.ai.simulatorRedirect=auto
or =ollama, routes any LlmClient.openai(...) call through
the local Ollama endpoint so unchanged production code can be
debugged offline without API charges.

Tests

  • 9 plugin tests (AiDependencyTableTest): pods, SPM
    routing, big-upload flagging, accumulator dedup,
    false-positive guard.
  • 23 core-unittests across 5 files: ChatRequestBuilderTest,
    JsonHelperTest (escaping, null omission, raw-JSON inlining,
    integer formatting), PromptTemplateTest, TokenizerTest,
    OpenAiSseDecoderTest (delta aggregation, fragmented
    tool-call argument reassembly, terminal finish_reason
    capture, error status mapping including context_length_exceeded).
  • All 32 pass; full reactor mvn install -pl core,codenameone-maven-plugin,javase -am -Plocal-dev-javase -DskipTests -Dspotbugs.skip=true builds clean.

scripts/create-ai-cn1lib.sh

Bootstrap helper that generates a new AI cn1lib repo from
cn1lib-archetype and drops in a .github/workflows/publish.yml
that publishes to Maven Central on every merge to master.

Tracked follow-ups (not blocking)

  • iOS / Android native bridges for SpeechRecognizer + TextToSpeech (need device testing).
  • BuildDaemon mirror of the scanner additions (separate PR — codenameone/BuildDaemon#84).
  • Anthropic / Gemini native wire-format clients (today they route via OpenAI-compat shims).
  • The thirteen cn1-ai-* cn1lib repos themselves (bootstrap via the new script).
  • iOS system-framework linkage for Speech.framework / AVFAudio / CoreML etc. — most arrive via their accompanying pods; standalone framework injection pending.

Test plan

  • cd maven && mvn install -pl core,codenameone-maven-plugin,javase -am -Plocal-dev-javase -DskipTests -Dspotbugs.skip=true builds clean
  • cd maven/codenameone-maven-plugin && mvn test -Dtest=AiDependencyTableTest -- 9/9 pass
  • cd maven/core-unittests && mvn test -Dtest="ChatRequestBuilderTest,JsonHelperTest,PromptTemplateTest,TokenizerTest,OpenAiSseDecoderTest" -- 23/23 pass
  • Simulator: LlmClient.openai(System.getenv("OPENAI_API_KEY")).chatStream(req, listener) streams tokens
  • Simulator (Ollama running): cn1.ai.simulatorRedirect=ollama routes LlmClient.openai("sk-fake") calls to localhost:11434
  • TextToSpeech.speak("hello") works on the simulator (macOS via say)
  • Permission-conflict check: app manually declaring android.xpermissions with CAMERA + using a (future) com.codename1.ai.mlkit.barcode.* class does not produce duplicate <uses-permission> lines in the final manifest

Generated with Claude Code

… dependency injection

Introduces the full AI/LLM surface for Codename One plus the
build-server plumbing that auto-wires every native dependency it
needs. Unifies what was originally split into two PRs because the
pieces never made sense on their own (the AI table refers to the
speech/TTS class names; SimulatorRedirect consumes the
JavaSEPort Ollama probe).

### com.codename1.ai

- LlmClient with static factories for OpenAI, Anthropic, Gemini,
  Ollama, and a generic OpenAI-compatible endpoint. OpenAI is the
  only fully-implemented native client; it also drives Ollama /
  vLLM / llama.cpp because the wire format matches. Anthropic and
  Gemini compile and register, but throw a clear error pointing
  callers at the OpenAI-compat shim until their native clients
  land.
- Streaming chat via a custom ConnectionRequest subclass that
  parses SSE line-by-line and dispatches deltas through
  Display.callSerially. AsyncResource.cancel() kills the socket.
- Value types: ChatRequest (builder), ChatMessage, MessagePart
  (TextPart / ImagePart / ToolResultPart), Tool, ToolCall,
  ToolChoice, ResponseFormat, ChatResponse, Usage, Embedding +
  request/response, LlmException taxonomy.
- ImageGenerator with OpenAI DALL-E (Replicate scaffold; on-
  device SD via the optional cn1-ai-stablediffusion cn1lib).
- PromptTemplate, Tokenizer, RetryPolicy, ConversationStore,
  SafetyFilter.

### com.codename1.media.SpeechRecognizer + TextToSpeech

- New core APIs routed through Display + CodenameOneImplementation
  no-op hooks. Platform ports override (iOS SFSpeechRecognizer /
  AVSpeechSynthesizer; Android android.speech.*). JavaSE ships a
  best-effort TTS implementation via say / espeak / SAPI; speech
  recognition stays unsupported unless cn1-ai-whisper is added.

### SecureStorage non-prompting overloads

- Single-argument get/set/remove(account, ...) added next to the
  existing biometric-gated methods. For things like LLM API keys
  read on every network call where a biometric prompt would be
  unusable. Base class returns null/false on unsupported ports.

### com.codename1.components.ChatView

- Scrollable message list (BoxLayout.Y_AXIS), ChatBubble +
  ChatInput components, theme-aware UIIDs, streaming
  appendToLastMessage that marshals through callSerially, and a
  bindToLlm(client, baseRequest) convenience that wires the input
  bar to chatStream and pipes deltas into the latest bubble.

### Build-time scanner additions

- AiDependencyTable in the maven plugin: 18 entries mapping the
  com.codename1.ai.* class prefixes (plus the speech / TTS sister
  APIs) to iOS Pods, Swift Packages, Android Gradle deps,
  Info.plist usage strings, and Android permissions.
- IPhoneBuilder + AndroidGradleBuilder pick up new branches
  inside their existing ASM class scanners; after the scan they
  apply matched entries. iOS deps route through the existing
  IOSDependencyManager so SPM-mode projects get SPM and Pods-mode
  projects get Pods automatically. Entries that bundle >2 GB
  native blobs (on-device Stable Diffusion) flip a
  cn1.ai.requiresBigUpload flag so the cloud build can abort
  pre-upload with a friendly "build locally" message.

### JavaSE simulator: Ollama detection

- Probe at startup pings localhost:11434; sets
  cn1.ai.ollamaDetected when reachable. SimulatorRedirect reads
  the property and, with cn1.ai.simulatorRedirect=auto or =ollama,
  routes any LlmClient.openai(...) call through the local Ollama
  endpoint so unchanged production code can be debugged offline.

### Tests

- 9 plugin tests (AiDependencyTableTest): pods, SPM routing,
  big-upload flagging, accumulator dedup, false-positive guard.
- 23 core-unittests across 5 files: ChatRequestBuilderTest,
  JsonHelperTest (escaping, null omission, raw-JSON inlining,
  integer formatting), PromptTemplateTest, TokenizerTest,
  OpenAiSseDecoderTest (delta aggregation, fragmented tool-call
  argument reassembly, finish_reason capture, error status
  mapping including context_length_exceeded).

### scripts/create-ai-cn1lib.sh

- Bootstrap helper that generates a new AI cn1lib repo from
  cn1lib-archetype and drops in a .github/workflows/publish.yml
  that publishes to Maven Central on every merge to master.

### Tracked follow-ups

- iOS / Android native bridges for SpeechRecognizer + TextToSpeech.
- BuildDaemon mirror of the scanner additions (separate PR).
- Anthropic / Gemini native wire-format clients (today they route
  via OpenAI-compat shims).
- The thirteen cn1-ai-* cn1lib repos themselves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

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

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

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

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 114 screenshots: 114 matched.

Native Android coverage

  • 📊 Line coverage: 11.86% (6818/57506 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.64% (34232/355017), branch 4.15% (1399/33740), complexity 5.21% (1687/32384), method 9.05% (1371/15156), class 14.56% (306/2102)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 11.86% (6818/57506 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.64% (34232/355017), branch 4.15% (1399/33740), complexity 5.21% (1687/32384), method 9.05% (1371/15156), class 14.56% (306/2102)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 797.000 ms
Base64 CN1 encode 183.000 ms
Base64 encode ratio (CN1/native) 0.230x (77.0% faster)
Base64 native decode 906.000 ms
Base64 CN1 decode 326.000 ms
Base64 decode ratio (CN1/native) 0.360x (64.0% faster)
Image encode benchmark status skipped (SIMD unsupported)

The static-analysis gate (.github/scripts/generate-quality-report.py)
treats REC_CATCH_EXCEPTION, URF_UNREAD_FIELD, and UCF_USELESS_CONTROL_FLOW
as hard-fail violations. Cleaned up the AI code accordingly:

- OpenAiClient.postResponse + embed.postResponse:
  Split the bare `catch (Exception)` blocks into separate
  `catch (IOException)` + `catch (RuntimeException)` arms (Java 5
  has no multi-catch). The IOException path covers JSON parse
  failures from JsonHelper.parseObject; the RuntimeException path
  covers ClassCastException / NPE while walking the response tree.
  Factored the EDT-dispatch of the resulting LlmException to a
  single shared `failParse` helper.

- OpenAiClient.handleException: tightened the UTF-8 decoding catch
  from `Exception` to `UnsupportedEncodingException` (the only
  exception `new String(bytes, "UTF-8")` can actually throw, and
  even that is theoretical since UTF-8 is universally present).

- OpenAiSseDecoder.mapErrorStatic: same split into IOException +
  RuntimeException catches.

- ImageGenerator.postResponse + handleException: same split treatment.

- ImageGenerator.ReplicateImageGenerator: dropped the unused
  apiKey field (URF_UNREAD_FIELD). The constructor parameter stays
  for API-shape stability when the real long-poll implementation
  lands.

- ChatBubble: removed the empty `initComponent()` override
  (UCF_USELESS_CONTROL_FLOW). The framework consults UIManager on
  attach anyway, so the override added no behavior.

Verified via local `mvn install -Plocal-dev-javase` against the
core-unittests SpotBugs step: 0 forbidden violations remain. The
2 leftover findings (RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE and
WMI_WRONG_MAP_ITERATOR) are style-level and not in the gate's
forbidden set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 69000 ms
Simulator Boot (Run) 1000 ms
App Install 13000 ms
App Launch 3000 ms
Test Execution 297000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 614.000 ms
Base64 CN1 encode 1336.000 ms
Base64 encode ratio (CN1/native) 2.176x (117.6% slower)
Base64 native decode 278.000 ms
Base64 CN1 decode 1102.000 ms
Base64 decode ratio (CN1/native) 3.964x (296.4% slower)
Base64 SIMD encode 457.000 ms
Base64 encode ratio (SIMD/native) 0.744x (25.6% faster)
Base64 encode ratio (SIMD/CN1) 0.342x (65.8% faster)
Base64 SIMD decode 372.000 ms
Base64 decode ratio (SIMD/native) 1.338x (33.8% slower)
Base64 decode ratio (SIMD/CN1) 0.338x (66.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 57.000 ms
Image createMask (SIMD on) 10.000 ms
Image createMask ratio (SIMD on/off) 0.175x (82.5% faster)
Image applyMask (SIMD off) 119.000 ms
Image applyMask (SIMD on) 59.000 ms
Image applyMask ratio (SIMD on/off) 0.496x (50.4% faster)
Image modifyAlpha (SIMD off) 176.000 ms
Image modifyAlpha (SIMD on) 61.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.347x (65.3% faster)
Image modifyAlpha removeColor (SIMD off) 157.000 ms
Image modifyAlpha removeColor (SIMD on) 67.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.427x (57.3% faster)
Image PNG encode (SIMD off) 1056.000 ms
Image PNG encode (SIMD on) 822.000 ms
Image PNG encode ratio (SIMD on/off) 0.778x (22.2% faster)
Image JPEG encode 434.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 59000 ms
Simulator Boot (Run) 1000 ms
App Install 21000 ms
App Launch 45000 ms
Test Execution 325000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 921.000 ms
Base64 CN1 encode 2113.000 ms
Base64 encode ratio (CN1/native) 2.294x (129.4% slower)
Base64 native decode 458.000 ms
Base64 CN1 decode 1582.000 ms
Base64 decode ratio (CN1/native) 3.454x (245.4% slower)
Base64 SIMD encode 541.000 ms
Base64 encode ratio (SIMD/native) 0.587x (41.3% faster)
Base64 encode ratio (SIMD/CN1) 0.256x (74.4% faster)
Base64 SIMD decode 486.000 ms
Base64 decode ratio (SIMD/native) 1.061x (6.1% slower)
Base64 decode ratio (SIMD/CN1) 0.307x (69.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 70.000 ms
Image createMask (SIMD on) 54.000 ms
Image createMask ratio (SIMD on/off) 0.771x (22.9% faster)
Image applyMask (SIMD off) 216.000 ms
Image applyMask (SIMD on) 193.000 ms
Image applyMask ratio (SIMD on/off) 0.894x (10.6% faster)
Image modifyAlpha (SIMD off) 217.000 ms
Image modifyAlpha (SIMD on) 107.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.493x (50.7% faster)
Image modifyAlpha removeColor (SIMD off) 253.000 ms
Image modifyAlpha removeColor (SIMD on) 82.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.324x (67.6% faster)
Image PNG encode (SIMD off) 1524.000 ms
Image PNG encode (SIMD on) 1065.000 ms
Image PNG encode ratio (SIMD on/off) 0.699x (30.1% faster)
Image JPEG encode 765.000 ms

shai-almog and others added 3 commits May 25, 2026 00:58
The CI static-analysis gate also enforces
RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE (line 841 of
.github/scripts/generate-quality-report.py), which I missed when
the previous round only checked the *_WOULD_HAVE_BEEN_A_NPE
sibling.

Util.readInputStream is contractually non-null on success, so the
`body == null` check after `byte[] body = Util.readInputStream(input)`
is dead code. Removed it.

Verified locally: 0 forbidden bug instances remain in the
spotbugsXml.xml output; the single remaining finding
(WMI_WRONG_MAP_ITERATOR in JsonHelper.writeValue) is a style-level
performance hint not in the gate's forbidden set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build-test (8) runs PMD via maven-pmd-plugin in core-unittests
and feeds every violation through the same forbidden-rule gate
the SpotBugs path uses. Cleaned up 87 violations across nine rule
categories. Verified locally with mvn verify in core-unittests:
zero violations.

- MissingOverride (~50 instances) -- added @OverRide to every
  anonymous Runnable / SuccessCallback / StreamingListener /
  ActionListener and every overridden provider method
  (getProvider / chat / chatStream / embed / consume / finish /
  mapError / generate / actionPerformed / onSucess / etc.)
  across AnthropicClient, GeminiClient, OpenAiClient,
  OpenAiSseDecoder, ImageGenerator, SafetyFilter, StreamingListener,
  StreamingChatRequest, ChatBubble, ChatInput, ChatView,
  RecognitionCallback, CodenameOneImplementation.

- ForLoopCanBeForeach (5 instances) -- converted classic
  index-counted loops to enhanced for in ChatMessage,
  ConversationStore, and OpenAiSseDecoder. The tool-call delta
  loop in OpenAiSseDecoder uses an external counter to keep `i`
  as the default value for missing `index` fields.

- ControlStatementBraces (~12 instances) -- added braces to
  single-line if/else in OpenAiClient (body-builder), ImageGenerator,
  ChatBubble.defaultUiidFor, and ConversationStore.parseRole.

- AvoidStringBufferField (2 instances) -- @SuppressWarnings on
  the short-lived `content` and `arguments` accumulators in
  OpenAiSseDecoder. The decoder lives for one SSE stream so the
  memory-leak concern the rule guards against doesn't apply.

- EmptyCatchBlock (1) -- added Log.e on the listener.onError
  swallow in StreamingChatRequest.failWith.

- SimplifyBooleanReturns (1) -- collapsed the trailing
  `if (...) return true; return false;` in RetryPolicy.shouldRetry
  to a direct `return t instanceof LlmNetworkException`.

- UnnecessaryFullyQualifiedName (4) -- replaced inline
  `java.io.IOException`, `java.io.UnsupportedEncodingException`,
  `com.codename1.ui.Display.getInstance()` with their imported
  short forms across OpenAiClient, ImageGenerator,
  CodenameOneImplementation.

- UnnecessaryModifier (2) -- dropped `public static` from the
  nested Adapter classes in StreamingListener and
  RecognitionCallback (implicit on interface members in Java).

- UnusedFormalParameter (1) -- @SuppressWarnings on
  ImageGenerator.ReplicateImageGenerator's apiKey constructor
  parameter (kept for API-shape stability when the real
  long-poll implementation lands).

Also removed an obsolete Arrays.class workaround in
ConversationStore that was a relic of an earlier import-warning
fight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-test (8) also runs Checkstyle (build-test (17) ran PMD but
not Checkstyle in the same path -- both gates must pass). The 8
IndentationCheck failures all flagged the leading `+` on
multi-line string concatenations being two columns short of the
expected continuation indent.

Indented the continuation lines by 2 extra spaces in the
multi-line UnsupportedOperationException messages in:

- AnthropicClient.chat / .embed
- GeminiClient.chat / .embed
- ImageGenerator.onDevice / .ReplicateImageGenerator.generate

Verified locally via `mvn checkstyle:check`: 0 violations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [HTML preview] [Download]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 1 findings (Normal: 1)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

shai-almog and others added 17 commits May 25, 2026 15:50
Acts on six pieces of feedback raised on PR #5035:

1. **Drop scripts/create-ai-cn1lib.sh** -- redundant now that the
   cn1libs ship in-tree.

2. **Fold JsonHelper into JSONParser**. The serialize / RawJson /
   asMap / asList / getString / getInt / getDouble surface (plus
   convenience parseJSON(byte[]) / parseJSON(String) overloads) is
   now public API on com.codename1.io.JSONParser. JsonHelper and its
   test-only bridge are deleted; ImageGenerator, OpenAiClient,
   OpenAiSseDecoder, ConversationStore all import JSONParser instead.

3. **Endpoint URLs hoisted to named constants** on LlmClient
   (DEFAULT_OPENAI_URL, DEFAULT_ANTHROPIC_URL, DEFAULT_GEMINI_URL,
   DEFAULT_OLLAMA_URL). Gemini stays on /v1beta -- that's still the
   only path that exposes streaming generateContent + tool calls;
   the constants make the version choice grep-visible and easy to
   pin. setBaseUrl(...) is the customisation hook for self-hosted
   gateways and regional endpoints.

4. **LlmException.ErrorType enum** so callers can do a single
   try/catch + switch (the more idiomatic shape for most apps):

       switch (e.getType()) {
           case RATE_LIMIT:        ...
           case AUTH:              ...
           case CONTEXT_LENGTH:    ...
           ...
       }

   The subclasses (LlmRateLimitException etc.) remain for cases
   where instanceof + extra typed state (e.g. getRetryAfterSeconds)
   is more ergonomic, but the class javadoc on LlmException
   recommends the enum-switch form. Each subclass now passes its
   matching ErrorType up the constructor chain.

5. **Tool / ToolCall linkage**. Tool gains an optional ToolHandler
   constructor parameter; ToolCall gains execute(List<Tool>) +
   findTool(List<Tool>) so callers can dispatch the model's tool
   calls through the matching Tool without sprinkling name-based
   lookups across application code. Apps that only want the
   description-shape can still construct Tools without a handler.

6. **ChatView decoupled from LlmClient**. Moved bindToLlm into a
   new com.codename1.ai.LlmChatBinding helper class. ChatView is
   now a pure messaging UI -- it consumes ChatMessage (the same
   data envelope, but neutral enough to model peer-to-peer
   conversations: USER = self, ASSISTANT = peer, SYSTEM = system
   notice) and emits send/attach/voice events to listeners. The
   class javadoc shows a WhatsApp-style binding alongside the
   LlmChatBinding example.

Plus **13 AI cn1lib scaffolds** under maven/cn1-ai-<feature>/:

  cn1-ai-mlkit-text / -barcode / -face / -labeling / -translate /
  -smartreply / -langid / -pose / -segmentation / -docscan,
  cn1-ai-tflite, cn1-ai-whisper, cn1-ai-stablediffusion

Each ships a public Java facade in the corresponding
com.codename1.ai.<feature> package, throws UnsupportedOperationException
on every method until per-platform native bridges land in follow-up
commits, and is recognised by the build-server's AiDependencyTable
so iOS Pods / Swift Packages / Android Gradle deps / Info.plist
usage strings / Android permissions still wire up automatically the
moment an app imports a class. Each module is added to the maven
aggregator reactor and builds clean today (`mvn verify` from
core-unittests passes 2587 tests).

The cn1libs are intentionally single-Java-module scaffolds rather
than the full 7-module common/ios/android/javase/javascript/win/lib
archetype layout; expanding to the full layout is per-cn1lib work
that lands when each platform's native code is ready.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-test (8) flagged three java.io.IOException / .InputStreamReader /
.ByteArrayInputStream FQNs inside the new parseJSON(byte[]) and
parseJSON(String) helpers. Added the missing imports and switched
to short names. Local mvn verify is clean (2587 tests, 0 PMD).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e pattern

Acts on two follow-up review notes:

1. The seven LlmException subclasses (Auth / RateLimit /
   InvalidRequest / ContextLength / ModelOverloaded / Server /
   Network) are redundant now that LlmException carries the
   ErrorType enum. Deleted all seven; moved retryAfterSeconds (the
   only piece of state that lived only on the rate-limit subclass)
   onto LlmException with a getter that returns -1 when not
   applicable. Every existing caller and test was rewritten to:

       new LlmException(message, status, code, body, cause,
                        LlmException.ErrorType.X)

   and instanceof checks were turned into:

       e.getType() == LlmException.ErrorType.X

   The class javadoc now shows a single try/catch + switch over
   getType() as the recommended pattern.

2. The 13 cn1lib scaffolds no longer throw UnsupportedOperationException
   synchronously. Each facade now uses the standard Codename One
   NativeInterface pattern:

   - A package-private Native<Feature> interface extending
     NativeInterface defines the platform contract.
   - The facade resolves it via NativeLookup.create(Native<Feature>.class)
     in a background thread (Display.scheduleBackgroundTask) and
     completes an AsyncResource on the EDT.
   - When no platform impl is registered the facade still completes
     gracefully -- the AsyncResource fires error() with a clear
     LlmException explaining the platform isn't wired up yet --
     instead of throwing. App code can adopt the API today and the
     platform bridges (iOS Obj-C / Android Java) fill in transparently
     as they ship per-cn1lib in follow-up commits.

   Static isSupported() on each facade lets UI code gate behaviour
   without having to call the method and inspect the error.

Versioning + release alignment (review note 3):

   Each cn1lib pom inherits <parent> codenameone:8.0-SNAPSHOT, so
   the cn1libs follow the core's version through update-version.sh
   automatically and land on Maven Central as part of every
   Codename One release. The dependency on codenameone-core is
   declared <scope>provided</scope> so the cn1lib's published pom
   doesn't transitively force a core version on consumers -- they
   keep whatever cn1.version their app already declares.

Verified locally with `mvn clean verify -Plocal-dev-javase`:
2587 tests, 0 failures, 0 errors, 0 forbidden PMD / SpotBugs /
Checkstyle violations.

Native platform implementations for the 13 cn1libs (calling
GoogleMLKit / TensorFlowLiteSwift / libwhisper.a / Core ML / ONNX
Runtime on iOS and Android) require device-testable bindings and
ship in follow-up per-cn1lib commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the existing CodenameOne convention (58 sibling package-info
files under com/codename1/*). Documents each package's purpose, the
public surface (the single facade class), and the build-server's
auto-injection behaviour.

- com.codename1.ai -- main client / value-types / streaming surface
- com.codename1.ai.mlkit.text / .barcode / .face / .labeling /
  .translate / .smartreply / .langid / .pose / .segmentation /
  .docscan
- com.codename1.ai.tflite
- com.codename1.ai.whisper
- com.codename1.ai.imagegen (Stable Diffusion)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the previous stub-throwing single-module skeletons with proper
multi-module cn1lib layouts. Each cn1-ai-* directory is now
root + common + ios + android + javase + lib, with the .cn1lib mojo
producing nativeios.zip / nativeand.zip / nativese.zip bundles.

Native bridges call the real underlying SDKs (not stubs):

- mlkit-text:         GoogleMLKit/TextRecognition (iOS) + text-recognition
- mlkit-barcode:      GoogleMLKit/BarcodeScanning + barcode-scanning
- mlkit-face:         GoogleMLKit/FaceDetection + face-detection
- mlkit-labeling:     GoogleMLKit/ImageLabeling + image-labeling
- mlkit-translate:    GoogleMLKit/Translate + translate (with model d/l)
- mlkit-smartreply:   GoogleMLKit/SmartReply + smart-reply
- mlkit-langid:       GoogleMLKit/LanguageID + language-id
- mlkit-pose:         GoogleMLKit/PoseDetection + pose-detection
- mlkit-segmentation: GoogleMLKit/SegmentationSelfie + segmentation-selfie
- mlkit-docscan:      VisionKit + CIDetector (iOS), play-services-mlkit-docscan
- tflite:             TensorFlowLiteObjC + org.tensorflow:tensorflow-lite
- whisper:            whisper.cpp C API + JNI .so (Android)
- stablediffusion:    Core ML / ONNX Runtime (local-build only; >2 GB)

Generator + idempotency:

scripts/gen-ai-cn1libs.py is the single source of truth -- the 13
cn1lib trees are 100% rewritten from it. CI runs the generator and
fails if the checked-in tree drifts.

JVM tests:

Each common module ships a JUnit 5 test that exercises the facade
contract against a mock NativeInterface. All 13 modules build and
test cleanly via `mvn package`; .cn1lib archives include real native
sources (verified by unzip).

CI:

- PR CI step packages all 13 cn1libs as part of the standard pipeline.
- ai-cn1lib-native-check.yml lint-compiles every iOS Obj-C bridge via
  clang -fsyntax-only against stub ML Kit / TFLite headers.
- ai-cn1lib-android-check.yml compiles every Android Java bridge with
  javac against android.jar + real ML Kit / TFLite / ONNX Runtime jars
  pulled from Google Maven, so missing symbols surface in PR review.

Not yet wired up (separate follow-up):

- End-to-end emulator/simulator UI integration tests that build a CN1
  app per cn1lib, push it through ParparVM / Android Gradle, boot the
  iOS simulator / Android emulator, feed fixture images and assert on
  bridge output. The lint workflows above are a fast static gate but
  not behavioural; that work needs cn1app-archetype test scaffolding +
  Espresso/XCTest harnesses that aren't in scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iOS Obj-C cleanup:
- Move whisper.cpp and Stable Diffusion C extern declarations OUTSIDE
  the @implementation block (was rejected by clang -- Obj-C only allows
  method definitions inside @implementation, not C functions / structs).
- Forward-declare `struct whisper_context` and define
  `struct whisper_full_params` BEFORE the extern that takes it by value
  (was a forward-reference compile error).
- Rebuild every other lib's .m file via the regenerated template so
  whitespace stays consistent (the previous output had mixed 8/0 leading
  indent from a textwrap.dedent quirk).

Android lint workflow:
- Replace fragile `yes | sdkmanager` (SIGPIPE under `set -o pipefail`)
  with printf'd y-stream.
- Switch from a Gradle classpath probe to Maven dependency:copy with
  explicit `<type>aar</type>` entries, then unzip classes.jar out of
  each AAR. Most ML Kit deps are AAR-only on Google Maven, so the
  earlier classpath approach silently dropped half the symbols.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- iOS stub headers got included multiple times per source (each ML Kit
  SDK header symlinks to the same stub), producing duplicate-interface
  errors. Add `#pragma once` so clang dedupes.
- The PR CI `git diff --quiet` check was tripping `set -e -o pipefail`
  in a way that made git emit its --help text instead of the actual
  diff (exit 129). Switch to `git status --porcelain` for the drift
  check, which is exit-code-safe under the same shell options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#pragma once relies on canonical-path comparison. clang on macos-14
treats each of our 13 SDK-name symlinks as a distinct canonical path,
so the same stub gets parsed many times -> duplicate-interface errors.
Switching to #ifndef CN1_AI_STUB_HEADERS_INCLUDED / #define / #endif
makes clang dedupe by content even when the file is reached through
different symlinks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier `Run JavaSE port unit tests` step already installs
codenameone-core + codenameone-javase into the local m2 with the
local-dev-javase profile, so the cn1-ai-* common modules can resolve
codenameone-core from the repo without rebuilding javase (which
needs the local-dev-javase profile and was failing on stub CEF
classes when -am pulled it back into the reactor).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cn1lib build-mojo lives in codenameone-maven-plugin. Earlier PR
steps only run its tests; they don't `install`, so subsequent steps
can't resolve the plugin. Install it explicitly before the cn1-ai
package step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plugin depends on codenameone-designer (jar-with-dependencies),
codenameone-android, codenameone-ios, codenameone-parparvm, and
java-runtime. None of these are in m2 from earlier steps, so the
install of just the plugin failed during resolution. Add -am to
install the plugin's reactor dependencies in one shot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ules

Three fixes prompted by review of the prior PR:

1. Match `mvn cn1:generate-native-interfaces` output verbatim. The hand-
   rolled iOS .h/.m had two real bugs:
   - Whitespace: `- (NSString *)recognize:` instead of canonical
     `-(NSString*)recognize:`. Cosmetic.
   - **Wrong selectors for multi-param methods**: the prior generator
     wrote `translate:(NSString*)param :(NSString*)param1 :(NSString*)
     param2` (selector `translate:::`) instead of `translate:(NSString
     *)param param1:(NSString*)param1 param2:(NSString*)param2`
     (selector `translate:param1:param2:`). The CN1 runtime dispatcher
     would NOT have found the multi-param methods.
   Generator now produces output that diffs clean against
   `cn1:generate-native-interfaces` for all 13 cn1libs.

2. Add the missing `javascript/` and `win/` modules. Every cn1lib now
   has the canonical seven-module layout (root + common + ios +
   android + javase + javascript + win + lib) and emits a `.cn1lib`
   that bundles nativeios.zip + nativeand.zip + nativese.zip +
   nativejavascript.zip + nativewin.zip.

3. Replace the stub-headers `clang -fsyntax-only` lint with a real
   `pod install` + `xcodebuild build` per cn1lib. Each lib gets its
   own job (parallel matrix) on macos-14: synthesise a static-library
   Xcode project that links every `nativeios/*.m`, install the
   matching `GoogleMLKit/*` / `TensorFlowLiteObjC` pod, and let
   xcodebuild prove the bridges compile against the real SDK
   headers. Catches what the stub-header lint silently missed --
   notably the selector bug above.

JavaSE impls also now `implements NativeXxx` (NativeLookup on JavaSE
casts to the interface, so the missing `implements` clause would have
broken simulator runs once the JavaSE port wired this up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two screenshot tests under scripts/hellocodenameone/common that
exercise the new AI UI surface added in this PR:

- ChatViewScreenshotTest emits a representative system / user /
  assistant exchange with the typing indicator visible. Covers
  ChatBubbleUser, ChatBubbleAssistant, ChatBubbleSystem, and
  ChatTypingIndicator UIIDs against iOS Modern and Android Material
  themes via the existing build-ios / build-ios-metal / Build Android
  screenshot pipeline.

- ChatInputScreenshotTest renders the input row standalone with all
  three optional buttons (attach, voice, send) visible -- baseline for
  ChatInput, ChatInputField, ChatSendButton, ChatAttachButton, and
  ChatVoiceButton.

Both are registered in Cn1ssDeviceRunner.DEFAULT_TEST_CLASSES next to
SheetScreenshotTest so they ride the same screenshot capture path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The synthesised CN1AIProbe.xcodeproj is too sparse for CocoaPods to
inject HEADER_SEARCH_PATHS / FRAMEWORK_SEARCH_PATHS into via
integrate_targets => true (the build configurations don't have the
expected baseConfigurationReference slot, so pod install runs but the
xcframework paths never reach xcodebuild). Result: every ML Kit
import landed at "'MLKitTextRecognition/MLKTextRecognizer.h' file not
found".

Switch to integrate_targets => false. pod install now just downloads
the pods + generates a per-target xcconfig under
`Pods/Target Support Files/...`. xcodebuild then loads that xcconfig
via -xcconfig so HEADER_SEARCH_PATHS / FRAMEWORK_SEARCH_PATHS reach
the compiler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs in the previous attempt:

1. `find Pods/Target\ Support\ Files -name '*.debug.xcconfig' | head -1`
   returned the first PER-POD xcconfig (PromisesObjC etc.) which only
   wires up that single pod's framework path. The build still couldn't
   find `MLKitTextRecognition/MLKTextRecognizer.h`.
   Explicitly target the aggregator
   `Pods-CN1AIProbe/Pods-CN1AIProbe.debug.xcconfig` which carries
   HEADER_SEARCH_PATHS / FRAMEWORK_SEARCH_PATHS for every transitive
   pod the consumer target depends on.

2. Empty bash array expansion under `set -u` exited the cn1libs that
   ship without a pod (whisper / stablediffusion / docscan) with
   "XCCONFIG_ARG[@]: unbound variable". Drop `set -u` from this step
   and switch to a plain string variable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three review-driven fixes:

1. Remove the win/ module from every cn1-ai-* lib. UWP is not a
   runtime target -- the C# stubs were dead weight inside every
   .cn1lib. Drop the module from the root pom, the lib pom, the
   generator, and delete the existing win/ trees. .cn1lib archives
   now ship 6 entries (was 7).

2. Replace `codename1.arg.ios.plistInject` build hints with implicit
   simulator-side injection. Each JavaSE NativeXxxImpl constructor
   calls a one-shot `ensureSimulatorHints()` that:
   - reads `getProjectBuildHints()` (returns null off-simulator, so
     this is a no-op on real devices)
   - sets the default value ONLY when the hint is absent -- never
     overwrites a developer-customised string
   This way the developer never has to copy a Camera/Microphone
   usage description out of docs into their properties file, but
   stays in full control of the wording. Only mlkit-text, barcode,
   and face declared `NSCameraUsageDescription` in the previous
   plistInject form; same coverage, now via the helper.

3. Style ChatView in the modern themes. The earlier ChatView
   screenshot rendered without bubble fills because the default
   themes had no ChatBubble*/ChatInput*/ChatTypingIndicator
   UIIDs. iOSModern now uses iMessage-style accent fills for user
   bubbles + neutral fills for assistant bubbles + a centred muted
   row for system; AndroidMaterial mirrors with Material 3 primary
   container / surface variant tokens. ChatView itself now wraps
   each bubble in a FlowLayout row (USER -> RIGHT, ASSISTANT ->
   LEFT, SYSTEM -> CENTER) so the bubble's visible width tracks
   its content rather than spanning the chat surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pod-generated xcconfig lives under
`Pods/Target Support Files/Pods-CN1AIProbe/...` -- a path with a
space. Expanding it through an unquoted `$XCCONFIG_ARG` made the
shell word-split it, so xcodebuild saw `-xcconfig Pods/Target`
followed by `Support` as a positional arg -> "Unknown build
action 'Support'" exit 65.

Switch to a bash array, quote the expansion, and rely on the
already-dropped `set -u` so an empty array doesn't trip up the
no-pod cn1libs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog and others added 13 commits May 26, 2026 12:16
The previous synth pbxproj + `:integrate_targets => false` approach
fell over on ML Kit because ML Kit ships as `.xcframework` (multi-arch
wrapper) -- not raw `.framework`. CocoaPods normally extracts the
correct simulator slice via a script phase added during integration,
but with `integrate_targets => false` we skipped that phase, so the
framework lookup `#import <MLKitTextRecognition/MLKTextRecognizer.h>`
saw the wrapper directory and not the actual framework.

Switch to xcodegen (brew on macos-14) to produce a real, complete
pbxproj that CocoaPods can integrate normally. With normal pod install
the workspace gets:
  - baseConfigurationReference injected on each build configuration
  - the [CP] Embed Pods Frameworks script phase
  - the xcframework -> framework extraction phase
xcodebuild now builds via `-workspace CN1AIProbe.xcworkspace` so the
embed phase + extracted frameworks reach clang's framework search path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
xcodegen 2.45.4 emits projects in Xcode 16's objectVersion=77 format.
The macos-14 runner ships Xcode 15.4, which refused them with "future
Xcode project file format (77)". macos-15 carries Xcode 16+ so it can
read the project.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
macos-15 runners returned 403 on actions/checkout for this repo (org
runner policy, presumably). Revert to macos-14 and explicitly select
the highest-version Xcode 16.* installed on the runner image so the
default xcode-select (Xcode 15.4) doesn't reject xcodegen's
objectVersion=77 projects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two iterations on the matrix:

1. The per-class ML Kit header paths I was using
   (`<MLKitTextRecognition/MLKTextRecognizer.h>` etc.) aren't actually
   exported by the GoogleMLKit pods. ML Kit ships ONE umbrella header
   per framework, named after the framework itself. Switch every iOS
   bridge to import the umbrella (`<MLKitTextRecognition/MLKitText
   Recognition.h>`, `<MLKitVision/MLKitVision.h>`, etc.) so clang's
   framework header search resolves and the public classes -- which
   the umbrella re-exports -- stay visible to the .m bodies.

2. The cn1libs that ship without a CocoaPod (whisper / stable
   diffusion) declare extern symbols that the real build server
   resolves against the bundled static library / Swift runner. The
   probe build had no such library so the link phase failed.
   Switch the probe target from `framework` to `library.static` --
   .a archives don't link, they archive .o files, so unresolved
   externs are fine. We still compile-check every source file,
   which is the whole point of this gate.

3. Also fix tflite's umbrella import: the pod is
   `TensorFlowLiteObjC` but the produced framework's module name --
   the one used in the angle-bracket prefix -- is
   `TFLTensorFlowLite`. So `<TFLTensorFlowLite/TFLTensorFlowLite.h>`,
   not `<TensorFlowLiteObjC/...>`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ML Kit splits each framework into a user-facing recognizer/detector
module + a sibling Common module holding the actual class /
property definitions. clang's modules system requires importing
the Common one explicitly even though the main framework re-exports
those symbols at link time.

Add the Common imports for the four cn1libs that hit this:
- mlkit-text:          MLKitTextRecognitionCommon
- mlkit-labeling:      MLKitImageLabelingCommon
- mlkit-pose:          MLKitPoseDetectionCommon
- mlkit-segmentation:  MLKitSegmentationCommon

Also fix mlkit-text's recognizer-options class name -- the canonical
options are `MLKTextRecognizerOptions`, not the `MLKCommonTextRecognizer
Options` placeholder I had.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MLKSegmentationMask exposes only `pixelBuffer` (a CVPixelBuffer); the
mask's width / height come from CVPixelBufferGetWidth / GetHeight, not
properties on the mask itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MLKSegmentationMask's CVPixelBuffer property is `buffer`, not
`pixelBuffer`. My previous switch to `.pixelBuffer` was based on a
faulty memory of the API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both tests previously extended `BaseTest`, which renders against
whatever theme is currently installed -- and that's the legacy
iOS 7 / Android Holo Light default. So the new ChatBubble*/
ChatInput* UIIDs I styled in `native-themes/{ios-modern,
android-material}/theme.css` never reached the rendered screenshot,
and the bubbles came out unstyled.

Switch both tests to `DualAppearanceBaseTest`, which is the existing
mechanism used by the other theme-fidelity tests
(SpanLabelThemeScreenshotTest, ButtonThemeScreenshotTest, etc.). It:

1. Installs the modern native theme via UIManager.setThemeProps
   from the .res shipped alongside the test resources.
2. Drives two captures per test: `_light` (CN.isDarkMode()=false) and
   `_dark` (true), refreshing the theme between them so the
   prefers-color-scheme @media block lights up the $DarkChatBubble*
   entries we already emit.

Same content (system + user + assistant + streaming + typing indicator
for ChatView, attach+voice+send for ChatInput), just routed through
the dual-appearance harness so the ChatBubble UIIDs actually render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captured from CI run 26485725588 (iOS GL + iOS Metal, commit
abdfff7) and from runs 26485725529 (Android) and 26485725524
(JavaScript). One light + dark pair per test per platform = 16
new baselines:

  scripts/ios/screenshots/         ChatView_light.png, ChatView_dark.png,
                                    ChatInput_light.png, ChatInput_dark.png
  scripts/ios/screenshots-metal/   (same four)
  scripts/android/screenshots/     (same four)
  scripts/javascript/screenshots/  (same four)

User bubbles right-aligned with accent fill (iMessage-style), assistant
bubbles left-aligned with neutral surface fill, system row centred with
muted text, typing indicator + message input visible. Now that the
tests extend DualAppearanceBaseTest the modern theme is installed before
each capture and ChatBubble* / ChatInput* UIIDs render correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit ceda4cc into master May 27, 2026
43 checks passed
shai-almog added a commit that referenced this pull request May 27, 2026
The Cn1ssDeviceRunner.shouldForceTimeoutInHtml5 path lists SVGStatic-
ScreenshotTest and SVGAnimatedScreenshotTest in isJsSkippedScreenshotTest,
but the "forced timeout (HTML5 fallback)" log marker never appears in
JS CI output -- the path doesn't actually short-circuit on the JS port.
Tests get run on JS anyway. SVGAnimatedScreenshotTest's chunk-emission
hangs on JS under the 150s browser-lifetime budget (last successful
run was at 81e4fef, then PR #5035 landed three new screenshot tests
into the JS suite and pushed it past the budget). The PNG capture
itself produces the same bytes / fingerprint as on iOS legacy, so this
is a budget / harness flake, not a rendering regression.

Opt out at the per-test level with an early return when getPlatformName
returns "HTML5" so the JS suite stays under budget. The framework-level
shouldForceTimeoutInHtml5 path can be debugged separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liannacasper pushed a commit that referenced this pull request May 27, 2026
* Add build-time SVG transcoder with SMIL animation support

New maven/svg-transcoder module parses SVG files with the JDK's StAX reader
(no Batik dependency) and emits Codename One Image subclasses that render
via the Graphics shape API. Covers shapes (rect, circle, ellipse, line,
polyline, polygon, path including arcs), groups with affine transforms,
linear gradients via LinearGradientPaint, and SMIL animations (animate,
animateTransform, set) interpolated against wall-clock time.

A new TranscodeSVGMojo runs in generate-sources, scans src/main/svg, and
emits one class per SVG into target/generated-sources/svg plus an
SVGRegistry class. The runtime base class lives at com.codename1.svg.
GeneratedSVGImage; Resources.getImage now falls back to a global registry
populated reflectively from the generated SVGRegistry so transcoded images
appear under their source filename for any Resources opened in the VM.

The hellocodenameone module includes five SVG fixtures (static shapes,
gradient, path, two animated) and two screenshot tests; animated images
expose setAnimationTimeMillis so tests can pin the frame deterministically.

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

* Drop reflective SVGRegistry probe to keep ParparVM iOS build alive

The lazy Class.forName/getMethod/invoke probe in Resources.getImage made
java.lang.reflect.Method.invoke and java.lang.Class.getMethod reachable
through Resources, which on iOS pulled symbols ParparVM's static
reachability analyzer otherwise strips. The generated Resources.m then
emitted calls to virtual_java_lang_Class_getMethod___...,
virtual_java_lang_reflect_Method_invoke___... that the linker never sees,
failing the iOS / native-ios / packaging jobs.

Removes the auto-probe and instead documents the one-line
SVGRegistry.install(resources) call required after loading a theme. The
generated registry's install method still populates both the per-instance
map and the global fallback, so a single startup call covers all
Resources opened in the VM. JavaSE-only behavior is unchanged.

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

* Satisfy PMD: braces on every control statement, one decl per line

CI's quality report failed on ControlStatementBraces, MissingOverride,
and OneDeclarationPerLine for the new SVG runtime + transcoder mojo.
Wrap every single-line if/for body in braces, split the px/py decl,
and add the @OverRide annotation on MutableResource.setImage that
became required once Resources gained the matching method. No
behavioral change.

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

* Remove unrelated files accidentally staged via git add -A

The previous commit swept in IDE configs, the local quality report and
unrelated website syndication scripts that were untracked in the working
tree. Drop them from version control so the PR contains only the SVG
transcoder change.

* Use MathUtil.acos so CLDC11 / iOS port still build

CLDC11's java.lang.Math intentionally has no acos -- the SVG-arc
decomposition in svgArc was reaching for Math.acos, which made the
Ant CodenameOne core build fail under the bootclasspath that mirrors
CLDC's surface (build-test 8 / 17 / 21). Route through MathUtil.acos,
the CN1 helper that already implements the missing trig functions
identically on every port.

* Strip non-ASCII characters from all new sources

The Ant CodenameOne core build uses javac with ASCII encoding, so
em-dashes (--), arrows (->) and similar typographic flourishes I had
sprinkled through doc comments fail with "unmappable character for
encoding ASCII" on the Android port and CodenameOne core build steps
(build-test 8 / 17 -- build-test 21 happens to pass because Maven sets
UTF-8). Replace every em-dash with --, smart quotes with ASCII quotes,
arrows with -> / <-, etc. Generated output is unchanged.

* Address review: AnimationTime clock, real scaled(), DPI sizing, ui package, core-unittests

Move GeneratedSVGImage from com.codename1.svg into com.codename1.ui (one
class doesn't justify its own package, and the runtime already lives
alongside Image/Graphics there). Drop com.codename1.svg entirely.

Wire animation timing through com.codename1.ui.animations.AnimationTime
instead of System.currentTimeMillis(). The image still captures a
per-instance "first paint" timestamp so animations begin at t=0 when
drawn, but every read of the clock now flows through AnimationTime.now()
so AnimationTime.setTime(...) in a test pins the entire animation graph
deterministically. The old setAnimationTimeMillis()/resetAnimation pair
becomes redundant -- removed setAnimationTimeMillis, kept resetAnimation
for callers that want to rebase t=0 explicitly.

Make scaled()/getWidth()/getHeight() actually carry caller-supplied
dimensions: scaled(w, h) returns an SVGScaledView wrapper that reports
the requested size from getWidth/getHeight (so component layout sees the
right box) and delegates rendering + animation state to the source. The
old "return this" was wrong -- it silently broke any layout that asked
for a different size.

Default size is now a function of device density: the SVG-declared
intrinsic width/height are treated as design pixels at DENSITY_MEDIUM
and scaled by the device density so icons look right on high-DPI
screens. Display lookup is wrapped in a safe fallback for the unusual
case where the image is constructed before Display.init.

Animated screenshot test (hellocodenameone) now pins AnimationTime so
spinner / pulse capture is deterministic instead of relying on the
removed setAnimationTimeMillis hack.

Add maven/core-unittests/.../GeneratedSVGImageTest -- 18 JUnit 5 tests
exercising the DPI sizing heuristic, scaled-view semantics, AnimationTime
integration (first-paint capture, advancement, rewind clamp, reset), and
the static SMIL helpers (progress, lerp, lerpColor, lerpValues, svgArc).
This runs in the PR CI build-test job, which the hellocodenameone
screenshot tests don't (they run in scripts-ios.yml / ios-packaging.yml
when scripts/hellocodenameone/** changes -- not in the same job).

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

* Register SVG screenshot tests in Cn1ssDeviceRunner so they actually run

The hellocodenameone screenshot harness doesn't auto-discover BaseTest
subclasses -- DEFAULT_TEST_CLASSES is an explicit array (line ~140-225).
Without an entry the runner skips the class entirely, which is why neither
the static nor animated SVG screenshot landed in any artifact even though
the source compiles and CI passed.

Insert the two SVG tests right after the CSS filter test and before the
orientation lock test (which must remain last because orientation state
leaks across subsequent screenshots).

Move the AnimationTime.reset() in SVGAnimatedScreenshotTest into a
proper cleanup() override so the pinned clock survives the ~1.5s screen-
shot capture window but is released before the next test runs. Avoids a
state leak into OrientationLockScreenshotTest.

* Shape-clip gradient fills; convert animated SVG test to 6-frame grid

LinearGradientPaint.paint(g, x, y, w, h) ignores x/y and rasterizes the
gradient through its absolute startX/startY/endX/endY into the supplied
rectangle. Calling g.setColor(paint) then g.fillShape(circle) therefore
paints the gradient through the circle's full bounding box, not the
circle itself -- the test's gradient circle looked like an empty stroke
because the fill silently never landed inside the outline.

Fix in the codegen: for gradient fills, push the clip, set the shape as
the clip, then invoke paint.paint(g, bx, by, bw, bh) within the path's
bounding rectangle, then pop the clip. Solid-color fills still use the
fillShape(path) path which behaves correctly. Stroke emission is
unchanged. Reorganised emitPaintSet / emitGradientFill so the two
branches no longer share half-stateful alpha bookkeeping.

Replaced SVGAnimatedScreenshotTest with an
AbstractAnimationScreenshotTest subclass so the animated SVGs are
captured as a 2x3 grid of frames across one cycle. AnimationTime is
advanced per cell by the base class; the SVG images read their offset
from AnimationTime.now(), so each cell shows a different rotation
(spinner) and radius (pulse) -- proving the animation is actually
running, not just rendering the first frame.

Skip the two SVG screenshot tests on the JS port: the existing
~150s browser-lifetime budget is tight (it forces other heavy tests to
the timeout list), and adding both an animation grid and a static
render tipped it over today. Revisit when the JS port harness gets
a longer-lived window.

* Enable antialiasing and animate opacity on transcoded SVGs

Two regressions called out on the latest screenshots:

1. Rendered shapes look stair-stepped (no antialiasing). The drawImage
   path on GeneratedSVGImage now flips g.setAntiAliased(true) for the
   duration of paintSVG and restores the previous value in the finally
   block. Vector output looks the same as the rest of the framework's
   antialiased rendering after this.

2. The pulsing circle's <animate attributeName="opacity"/> never landed
   because the codegen baked style.getOpacity() in as a static Float at
   build time. Resolve the element opacity through animFloat() the same
   way fill-opacity / stroke-opacity already do, so a SMIL opacity
   animation drives the alpha multiplier on every paint. The pulse now
   actually fades.

Opacity flowing through the alpha expression also keeps gradient fills
honoring the animated element opacity since emitGradientFill takes the
same AnimatedFloat parameter.

iOS Metal still shows a triangle for the gradient circle and the
spinner doesn't appear -- those reproduce only on Metal and look like
port-level setClip(Shape) / setTransform issues; tracking separately.

* SVG transcoder full stack: CSS-driven, registry-wired, dev guide

End-to-end integration so SVG support behaves like the rest of CN1's
image pipeline:

CSS pipeline
- Patch maven/css-compiler/.../CSSTheme.getBackgroundImage to recognize
  url(*.svg). Instead of trying to rasterize the XML (which throws and
  was the blocker on this path), it registers a 1x1 transparent PNG
  placeholder under the SVG filename. The theme.res keeps a reference
  the runtime can later overwrite.
- Honour cn1-source-dpi on the same rule that references the SVG (the
  established CN1 multi-image hint). The transcoder mojo scans the
  surrounding rule for this declaration and bakes the resulting
  density into the SVGRegistry's install() call so theme.getImage()
  returns an instance sized as if the SVG were a density-tagged
  multi-image bucket.

Build flow
- Add transcode-svg to the cn1app-archetype's common/pom.xml so every
  new project picks it up. The mojo now scans both src/main/svg AND
  src/main/css (so SVGs can live next to the theme.css that references
  them) and emits placeholders into target/css-resources alongside the
  Java sources / registry.
- Walks each CSS rule's block for url(*.svg) + cn1-source-dpi and
  exposes the result to the codegen via a new sourceDensity field on
  SVGTranscoder.GeneratedClass. The generated registry then calls
  `new Spinner(50)` instead of `new Spinner()` when the CSS said
  cn1-source-dpi: very-high.

Runtime
- GeneratedSVGImage gets a second constructor that takes an explicit
  source density. The DPI-aware sizing heuristic now scales by
  deviceDensity / sourceDensity rather than always assuming
  DENSITY_MEDIUM, so an SVG marked very-high renders smaller on a
  high-DPI device than an SVG with no hint.
- The generated subclass exposes both constructors (no-arg + int) so
  registries / user code can pick the right one.

Tests
- Move the hellocodenameone SVG fixtures from src/main/svg/ to
  src/main/css/ -- the natural place alongside theme.css.
- Replace the two screenshot tests' Java-side `new Spinner()` hardcode
  with the developer-facing flow:
    SVGStaticScreenshotTest.prepare() calls
        com.codename1.generated.svg.SVGRegistry.install(globalRes);
    SVGAnimated/Static both pull images via
        Resources.getGlobalResources().getImage(name);
  This exercises CSS -> placeholder -> transcoded class -> getImage
  end-to-end.
- theme.css now declares five style classes that reference the SVGs
  with cn1-source-dpi: very-high, demonstrating the CSS hint flowing
  through to the runtime size calculation.

Docs
- New docs/developer-guide/SVG-Transcoder.asciidoc documents the
  feature: source layout, CSS hints, sizing rules, feature coverage,
  troubleshooting.

Simulator verification
- mvn process-classes on hellocodenameone-common now compiles the
  theme.css to a theme.res that contains the SVG filenames as image
  entries; the SVGRegistry overrides those at install() time, so
  Resources.getImage(name) returns the SVG.

* Add cn1-svg-width / cn1-svg-height for cross-DPI millimeter sizing

Many SVGs in the wild ship with arbitrary intrinsic dimensions (a
1024x1024 export of what's really a 24x24 icon, a 600x600 export of a
16x16 control, etc.), making the cn1-source-dpi heuristic the wrong
tool for the job. Add two CSS attributes that pin the rendered size in
millimeters so the icon comes out the same physical size on every
device regardless of what the SVG declares.

    HomeIcon {
        background: url(home.svg);
        cn1-svg-width: 6mm;
        cn1-svg-height: 6mm;
    }

Routes both values through Display.convertToPixels() at install time,
matching the way `font-size: 3mm` works elsewhere in CN1 CSS.

Implementation
- GeneratedSVGImage gains a third protected constructor taking explicit
  pixel dimensions, plus a public static mmToPixels(float) helper.
- The codegen emits three constructors per generated subclass -- no-arg
  (default DENSITY_MEDIUM), int sourceDensity (cn1-source-dpi), and
  float widthMm/heightMm (cn1-svg-width/height). SVGRegistry picks
  whichever the CSS rule declared.
- TranscodeSVGMojo now parses cn1-svg-width / cn1-svg-height alongside
  the existing cn1-source-dpi extraction. Explicit millimeters take
  precedence over density bucket; density beats no hint.
- CSSTheme acknowledges the two new properties as known no-ops so the
  parser stops logging "Unsupported CSS property" warnings.

Tests
- Two new core-unittests cover the explicit-pixel constructor and the
  mmToPixels DPI conversion. Two new transcoder tests cover the
  3-constructor codegen plus the registry's mm > density > default
  precedence.
- hellocodenameone's theme.css switches the two animated SVGs to use
  cn1-svg-width / cn1-svg-height: 12mm so the demo proves the new
  path -- generated registry now emits
  `new SpinnerAnimated(12.0f, 12.0f)`.

Docs
- Developer guide reorganized around the three sizing mechanisms in
  precedence order. Recommends millimeter dimensions for any SVG with
  non-standard declared width/height (which is most of them).

* SVG: seamless install, fail-fast dimensions, text rendering, accurate docs

Seamless installation
- Resources gains a tiny OpenHook API. Generated SVGRegistry registers
  itself in a static initializer that fires the first time any
  Resources opens (Resources.probeSVGRegistry does Class.forName on
  the literal class name). ParparVM treats the literal class reference
  as a reachability root, so iOS / Android builds include the
  generated class without any reflective getMethod / invoke. The
  registry then registers an OpenHook that fires on the same load,
  replacing the CSS-compiler placeholder PNGs with the transcoded SVG
  instances.
- Drops the explicit SVGStaticScreenshotTest.installSVGRegistry() call
  from both screenshot tests -- the registry is now seamless for app
  code; calling install() by hand defeats the test.

Fail-fast dimensions
- GeneratedSVGImage's pixel-dimensions constructor now throws
  IllegalArgumentException for width/height < 1 instead of clamping
  to 1. Same for SVGScaledView's scaled(w, h). mmToPixels also throws
  if the requested mm value resolves to 0 pixels (a 0.something mm
  cn1-svg-width hint that rounds away) -- silently producing a 1px
  result was always a debugging trap.

SVG text rendering
- Add SVGText model + parser path: <text> with x, y, font-family,
  font-size, font-weight (including numeric 700+ = bold), font-style,
  text-anchor (start/middle/end). <tspan> children are flattened into
  the parent text's content (single style per <text>; per-run styling
  is a follow-up).
- Codegen emits g.drawStringBaseline through a Font.derive of the
  default font sized in user units, with anchor-aware x positioning
  via Font.stringWidth. Fill / opacity / animation flow through the
  same paths as shape fills.

Docs
- Strip "introduced in 8.0" and v1 language from the developer guide
  per project convention. Replace the inaccurate "Codename One has
  historically supported SVG" claim (it only ever worked on J2ME and
  the JavaSE simulator) with an accurate reference to the legacy
  flamingo-svg-transcoder. Document the seamless install path so the
  guide stops telling users to write SVGRegistry.install() manually.
- Note text and clip-path coverage in the feature matrix.

Tests
- Two new parser tests for text + numeric font-weight.
- New CompileGeneratedSourceTest case ensures the text codegen
  produces a class that compiles against the real CN1 Font / Graphics
  surface.

* SVG: broader hellocodenameone fixtures + initializr skill update

Hellocodenameone test coverage
- logo_text.svg exercises the new <text> path -- anchored middle/end
  rendering, font-weight bold, font-style italic, multi-color fills,
  on top of a rounded-rect frame. Catches regressions in font
  derivation, anchor positioning, and styled text in the same SVG.
- wave_path.svg targets the path mini-language: S smooth-cubic
  reflection, T smooth-quadratic reflection, and dashed stroke
  rendering on a non-trivial composite path.
- color_morph.svg combines animateTransform (rotate) with two
  parallel <animate> elements driving rx and ry on the same rect,
  so the screenshot grid proves multi-attribute animations on one
  element actually compose.

All three are referenced from theme.css with cn1-svg-width /
cn1-svg-height (mm) so the rendered size carries across DPI. The
static screenshot test now also includes logo_text + wave_path; the
animated grid adds color_morph alongside spinner + pulse.

Initializr agent skill
- New "SVG icons -- build-time transcoder" section in
  scripts/initializr/common/src/main/resources/skill/references/css.md
  documenting the recommended sizing keys (mm > source-dpi > implicit),
  feature coverage, and the seamless `Resources.getImage(name)` path.
  Pointed at the developer-guide chapter for full detail. Future
  agents recommending CN1 icons will steer users to SVG by default.

* SVG: clip-path / mask support via shape clipping

- New SVGClipPath model and parser path for <clipPath> (in <defs> or
  top-level). <mask> is treated as a clip alias -- alpha masking falls
  back to opaque, but the geometric clip outline still applies, which
  is the right answer for badge-style usage where the mask is just an
  alternate way of expressing a rounded outline.
- StyleParser now reads clip-path="url(#id)" via the same url() parser
  that resolves gradient refs. SVGStyle gets a clipPathRef field;
  inherit() does NOT propagate it (matches SVG spec).
- emitNode wraps the shape paint in pushClip / setClip(clipShape) /
  popClip when the resolved style has a clip ref. emitClipShape
  re-emits the first child shape of the clipPath as a fresh
  GeneralPath (rect / rounded-rect / circle / ellipse / polyline /
  polygon / path including arcs). Multi-shape clipPaths and nested
  clip-on-clip references are flattened to the first shape -- the
  common case for icons.

Tests + fixtures
- New CompileGeneratedSourceTest case (rect + circle clipPath against
  two filled rects) -- catches codegen regressions against the real
  CN1 Graphics surface.
- clipped_badge.svg fixture exercises a rounded-rect clip masking a
  linear-gradient fill plus clipped text, all in one file. Added to
  theme.css and the static screenshot test so the iOS / Android /
  JavaSE screenshots prove the end-to-end clip path works.

* Add clipped_badge.svg fixture + SVGClipPath model file

* Fix Vale lints in SVG transcoder dev guide

CI's docs/developer-guide quality gate flagged five Vale errors:
- Replace ASCII 'x' dimensions (1x1, 24x24, 1024x1024) with proper
  multiplication signs so proselint.Typography stops complaining.
- Reword 'it is' to drop the Microsoft.Contractions violation.

Doc content is otherwise unchanged.

* Install transcoded SVGs from the per-platform Stub before init()

Replaces the previous Class.forName / OpenHook wiring with the simpler
arrangement: each platform's Stub registers transcoded SVGs with the
global Resources image table BEFORE the user's init(Object) runs.

Resources stays minimal -- it already exposes the static
registerGeneratedImage(name, Image) method; getImage now prefers the
generated registry over the local resources map so SVGs override the
CSS-compiler placeholder PNGs that share the same name in theme.res.

The transcode-svg mojo always emits SVGRegistry.installGlobal() now
(even with zero SVGs it's a no-op), so callers can reference it
unconditionally:

* iOS  -- IPhoneBuilder detects the generated SVGRegistry.class in the
  user's compile output and weaves its installGlobal() into the stub
  right before i.init(this) in the generated Stub.java.
* Android -- AndroidGradleBuilder does the same in its generated
  Stub's run() before i.init(this).
* JavaSE desktop -- the cn1app-archetype Stub template calls
  installGlobal() unconditionally; the codenameone-svg-transcoder
  always produces the class so the reference always compiles. (The
  hellocodenameone JavaSE Stub gets the same change manually.)
* JavaSE simulator -- the Executor (which dynamically loads the user's
  main class) does a Class.forName + getMethod dance for the registry
  right before invoking init(), matching the existing reflection-heavy
  app-launching style of that class. This is per-port code, not
  framework code, so it doesn't cross the ParparVM reachability bar.

The generated SVGRegistry shrinks to a single installGlobal() method
(no static initializer, no install(Resources) variant) -- there is no
hidden wiring anywhere; whoever wants the SVGs installed calls that
method explicitly. Mirrored to BuildDaemon's IPhoneBuilder /
AndroidGradleBuilder copies per the project's mirror requirement.

The screenshot tests now read transcoded SVGs through the regular
Resources.getGlobalResources().getImage(name) path; no glue code in
test land either.

* Broaden SVG transcoder unit coverage

Expands the test surface to catch regressions in the less-trodden paths:

- multiStopGradientCompiles -- 5-stop linear gradient (red -> yellow ->
  lime -> aqua -> blue). Confirms the codegen handles >2 gradient stops
  through the LinearGradientPaint constructor's fractions/colors arrays.
- deeplyNestedGroupsCompile -- five-deep <g transform="..."> nesting.
  Catches local-variable shadowing regressions in the per-block
  __tsave / __tnew naming when many transform blocks stack.
- skewTransformsCompile -- skewX and skewY composed with translate.
  Skew used to drop on the floor in emitApplyMatrix; covered now.
- valuesAnimationCompiles -- two parallel <animate values="..."> on the
  same circle (r driving size, opacity driving fade). Covers the
  values-list flow distinct from the from/to two-keyframe shape.

Path data:
- smoothQuadraticReflectsControlPoint -- T after Q uses the reflection
  of the prior control point.
- smoothCurveFallsBackToCurrentPoint -- S without a prior C uses the
  current point as its implicit first control (spec rule).
- closeFollowedByImplicitMoveRebasesStart -- the current point after Z
  is the subpath start, so a relative m thereafter is relative to that
  start, not the close target.

Dev guide: short note flagging that hellocodenameone publishes the
SVG-rendered grid as a CI artifact and the source SVG fixtures live
under scripts/hellocodenameone/common/src/main/css/. Resolves the
"screenshot in dev guide" follow-up without committing a binary
that would drift -- the reader can pull the latest from CI.

73 tests passing (up from 66, +7 from this commit).

* PMD LogicInversion: use mm <= 0f || NaN instead of !(mm > 0f)

PMD's LogicInversion rule complained about the negated-greater-than
check in GeneratedSVGImage.mmToPixels. The original form deliberately
caught NaN (since `NaN > 0` is false, `!(NaN > 0)` is true) but PMD
can't see the intent. Spell it out explicitly with
`mm <= 0f || Float.isNaN(mm)` so the NaN guard survives and the rule
is happy.

* Fix SVG screenshot test: grid layout + drawString for Metal

Two regressions called out:

1. clipped_badge.svg never appeared in the static screenshot capture.
   Root cause: BoxLayout.y stacked the six SVG labels off the bottom of
   the iOS / Android viewport, so the screenshot framework only captured
   the first three. Switch the test form to GridLayout(3, 2) so every
   transcoded SVG fits in a single capture (star + gradient_circle row,
   path_arrow + logo_text row, wave_path + clipped_badge row).

2. logo_text.svg rendered as nothing on iOS Metal. The
   Graphics.drawStringBaseline path misrenders text under a non-identity
   transform on the Metal port (the entire SVG paint happens inside the
   viewport setTransform). Switch the codegen to plain
   Graphics.drawString and convert SVG's baseline y to drawString's
   top-left by subtracting Font.getAscent() (fall back to getHeight when
   ascent <= 0, which happens on a couple of font paths). drawString is
   the more widely-supported entry point on every CN1 port.

* Fix SVG text + gradient rendering on iOS Metal and Android

Text was painted with `Font.getDefaultFont().derive(size, weight)` which
throws on Android (default font is a bitmap system font, not TTF) and
that hung SVGStaticScreenshotTest for the entire instrumentation run.
Switch to `Font.createTrueTypeFont("native:MainRegular" / ...)` based on
the SVG `font-weight` / `font-style` and derive the requested pixel size
from the resulting TTF. Never use `createSystemFont`.

Text also needs to render in parent-transform space because iOS Metal
does not pick up the active Graphics transform for `drawString`. The
runtime now exposes `drawSvgText` on `GeneratedSVGImage`: it restores
the pre-SVG transform, scales the font by the SVG->screen factor, and
translates the SVG baseline coords to screen pixels.

Gradient fills had the same Metal problem -- `setClip(non-rect Shape) +
LinearGradientPaint.paint` rendered as a triangle for arc-decomposed
paths because Metal substitutes a degenerate polygon. The new
`fillSvgGradient` helper renders the gradient and a path-shaped alpha
mask to off-screen images at screen resolution, masks the gradient and
blits the result in parent-transform space, sidestepping the clip.

Generator updated to emit `drawSvgText(...)` / `fillSvgGradient(...)`
in place of the inline recipes, and to drop the now-unused
`LinearGradientPaint` / `MultipleGradientPaint` / `Font` imports.

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

* Revert SVG Metal workarounds; document Metal bugs in test + dev guide

The previous commit added drawSvgText / fillSvgGradient helpers that
restored the parent transform to bypass two iOS Metal renderer bugs.
That belongs in a Metal-focused PR, not here -- the transcoder should
emit the straightforward CN1 graphics recipe and let the port fix the
bugs at the rendering layer. Reverted those helpers and the inline
emission they replaced.

Kept the GeneratedSVGImage.svgTextFont helper: that one is not a Metal
workaround. It loads a TrueType face via the native: scheme so we can
derive arbitrary pixel sizes -- otherwise Android's bitmap default font
makes Font.derive throw and hangs the screenshot suite mid-paint.

Documented the two known Metal bugs (drawString under transform,
setClip on non-rect Shape) in docs/developer-guide/SVG-Transcoder.asciidoc
and in the SVGStaticScreenshotTest javadoc so the next reviewer knows
the Metal goldens are intentionally capturing the broken behavior.

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

* Fix stale Quick Start + Troubleshooting copy in SVG dev guide

App code no longer calls SVGRegistry.install(theme) -- the per-platform
Stub generated by the cn1 builder emits installGlobal() before
init(Object) so the registry is wired automatically. Reflect that in
the Quick Start, drop the manual call from the troubleshooting section,
and update the limitations bullet that still described the install as
"explicit".

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

* Add SVG screenshot goldens for Android, iOS legacy, iOS Metal

Captured from CI run 26469559178 (Android) and 26469560225 (iOS) on
commit f8e1b4e. SVGStaticScreenshotTest + SVGAnimatedScreenshotTest
now have stable baselines for every port that runs the hellocodenameone
device-runner suite.

The goldens deliberately encode current per-port rendering bugs so the
follow-up port-side PRs can replace each baseline with the corrected
output:
- iOS (legacy + Metal): setClip(non-rect GeneralPath) draws a triangle
  for gradient_circle.svg and clipped_badge.svg.
- iOS (legacy + Metal): <animate> on fill color does not tick, so
  color_morph.svg's red diamonds vanish on legacy and freeze on Metal.
- Android: gradient_circle.svg paints the fill plus an outline of the
  same circle stacked.

Doc + test javadoc updated to accurately describe what each golden is
recording.

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

* Fix Vale lint warnings in SVG-Transcoder.asciidoc

Vale's Microsoft style guide flags contractions ("does not" -> "doesn't",
"did not" -> "didn't"), discouraged Latinisms ("e.g." -> "for example"),
and a couple of weak adverbs ("deliberately", "separately"). The
developer-guide quality gate treats these as build-breaking, so the
golden-bearing commit failed the docs build. Reworded the affected
sentences without changing meaning.

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

* Skip SVGRegistry emission when project has no SVGs; load it generically

Two follow-ups on the SVG transcoder:

1. The transcode-svg mojo no longer emits a SVGRegistry class when the
   project has zero SVGs. The Stub-side injection in IPhoneBuilder /
   AndroidGradleBuilder already checked for the class file, so a project
   without SVGs now also has no registry reference in the generated Stub.
   A leftover SVGRegistry.java from a previous build (e.g. after an SVG
   is removed) is swept on the next run so the `.isFile()` check stays
   honest.

2. JavaSE-side, the registry now loads from JavaSEPort.init() via a
   reflective Class.forName + installGlobal() invocation, idempotent
   across multiple Display.init() calls. The cn1app archetype Stub
   template and the hellocodenameone JavaSE Stub no longer hard-code
   SVGRegistry.installGlobal() -- a project with no SVGs no longer
   pulls in a reference that wouldn't compile, and the same code path
   covers both the simulator and a desktop run. Dropped the older
   reflective load from Executor.java since JavaSEPort.init() runs in
   both code paths.

Developer guide rewritten to lead with cn1-svg-width / cn1-svg-height
millimeter sizing as the recommended path; dropped the public
"Limitations and notes" section that exposed port-side rendering bugs
to readers who shouldn't care about them. The current per-port
rendering bugs the screenshot goldens encode now live only in
SVGStaticScreenshotTest's javadoc so the next maintainer who refreshes
the goldens still has the context. Resources / CSSTheme / SVGTranscoder
javadoc references to the old `install(theme)` entry point updated.

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

* Drop 'silently' adverb flagged by Vale in SVG dev guide

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

* Skip SVG screenshot tests on JS port at the per-test level

The Cn1ssDeviceRunner.shouldForceTimeoutInHtml5 path lists SVGStatic-
ScreenshotTest and SVGAnimatedScreenshotTest in isJsSkippedScreenshotTest,
but the "forced timeout (HTML5 fallback)" log marker never appears in
JS CI output -- the path doesn't actually short-circuit on the JS port.
Tests get run on JS anyway. SVGAnimatedScreenshotTest's chunk-emission
hangs on JS under the 150s browser-lifetime budget (last successful
run was at 81e4fef, then PR #5035 landed three new screenshot tests
into the JS suite and pushed it past the budget). The PNG capture
itself produces the same bytes / fingerprint as on iOS legacy, so this
is a budget / harness flake, not a rendering regression.

Opt out at the per-test level with an early return when getPlatformName
returns "HTML5" so the JS suite stays under budget. The framework-level
shouldForceTimeoutInHtml5 path can be debugged separately.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liannacasper pushed a commit that referenced this pull request May 28, 2026
…5057)

* Document AI/LLM, ChatView, speech, and ML Kit cn1libs from PR #5035

PR #5035 introduced com.codename1.ai (LlmClient, ChatRequest, streaming
SSE, tool calls, embeddings, image generation, conversation persistence,
retries, simulator Ollama redirect), the com.codename1.components.ChatView
component, com.codename1.media.SpeechRecognizer / TextToSpeech, the
non-prompting SecureStorage overloads, and the cn1-ai-mlkit-{barcode,
docscan,face} cn1libs. The PR shipped the framework code but left both
the developer guide and the initializr authoring skill silent on every
new API.

Adds a new "AI, Chat UI, and Speech" chapter to the developer guide:
provider factories, streaming, tool calling, structured output, multi-
modal messages, embeddings, image generation, conversation store, prompt
templates, retry policy, simulator redirect, SecureStorage key handling,
ChatView wiring and theming UIIDs, SpeechRecognizer / TextToSpeech, the
three ML Kit cn1libs, and a capture-photo -> describe -> speak example.
Includes an SVG mockup of the ChatView surface.

Adds references/ai-and-speech.md to the bundled initializr authoring
skill, indexed from SKILL.md and exercised from
GeneratorModelMatrixTest, so Claude Code (and any agent that respects
the skills convention) inside a generated project picks up the new
APIs automatically.

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

* Reword three LanguageTool snags in the new AI chapter

Local reproduction with language-tool-python==2.9.4 (the CI-pinned
version) flagged three matches in Ai-And-Speech.asciidoc:

- PCT_SINGULAR_NOUN_PLURAL_VERB_AGREEMENT on "the simulator, the cloud
  build, and your CI pipeline route everything" -- the rule wanted
  the singular-vs-plural agreement reworded. Rephrased to ":
  the simulator, the cloud builder, and your CI pipeline all run
  the same code."
- MORFOLOGIK_RULE_EN_US on "thread-safe mutators" -- the en_US
  dictionary doesn't recognise "mutators". Spelled the methods out
  ("addMessage, appendToLastMessage, and setTypingIndicatorVisible").
- MORFOLOGIK_RULE_EN_US on "SAPI on Windows" -- the flagged span was
  the two-word phrase, so the accept-list entry for "SAPI" alone did
  not match it. Wrapped SAPI in a code span so the HTML extractor
  replaces it with the inline placeholder before LanguageTool sees
  the surrounding prose.

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

* Replace ChatView SVG mockup with a real simulator screenshot

The first revision shipped a hand-authored SVG sketch of the ChatView
component. Swap it for an actual PNG produced by the JavaSE simulator
running the component under the iOS Modern theme so the developer
guide shows the real rendering, not a wireframe.

- Adds ChatViewDevGuideScreenshotTest in scripts/hellocodenameone
  (under common/src/test/java, where cn1:test discovers AbstractTest
  implementers). The test installs iOSModernTheme.res, builds a Form
  with a populated ChatView, paints it to a PNG, and writes it to
  Storage as chat-view.png. Running it via mvn -pl javase -am test
  (with the test profile activator) drops ~/.cn1/chat-view.png.
- Adds docs/developer-guide/img/chat-view.png, captured from a local
  simulator run via the test above.
- Drops docs/developer-guide/img/chat-view-mockup.svg.
- Points the chapter's image:: at the new PNG with caption "ChatView
  rendered in the JavaSE simulator under the iOS Modern theme".

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

* Wire Anthropic and Gemini through their OpenAI-compatible endpoints

The original AnthropicClient and GeminiClient were scaffolds that
threw UnsupportedOperationException on every call, with documentation
telling callers to route through LlmClient.localOpenAiCompatible
manually. Both providers now publish first-party OpenAI-compatible
endpoints that speak the canonical /chat/completions wire format:

  - https://api.anthropic.com/v1/chat/completions
  - https://generativelanguage.googleapis.com/v1beta/openai/chat/completions

That is the same shape OpenAiClient already implements end-to-end --
streaming, tool calls, multi-modal image parts, structured JSON
output, the entire StreamingChatRequest plumbing. So both subclasses
now extend OpenAiClient and only override the provider name and the
default model:

  - LlmClient.anthropic(key) -> claude-sonnet-4-5
  - LlmClient.gemini(key)    -> gemini-2.0-flash

LlmClient.DEFAULT_GEMINI_URL is bumped to the OpenAI-compat path
(`/v1beta/openai`). AnthropicClient still overrides embed() with the
helpful "Anthropic doesn't publish embeddings; use Voyage AI" error
because that part remains true.

Documentation updated: the AI chapter and the initializr authoring
skill no longer hedge with "throws an LlmException until native
clients land"; both providers work out of the box, with a default-
model table per provider added to the skill.

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

* Reframe the API-key storage section as a WARNING admonition

The previous wording explained two SecureStorage code paths
(biometric-gated vs non-prompting) and ended with "remain the
right choice for credentials a human user authenticates against,"
which read more like an API tour than safety guidance. The actual
risk that needs surfacing is that hard-coded keys in a mobile
binary are extractable.

Replace the prose block with an asciidoc WARNING admonition that
spells out the rule (no key in source / resource / git, fetch from
a server the user authenticates against, then cache locally with
the non-prompting overloads) and keeps just the short SecureStorage
code sample. Mirror the same simplification in the initializr
authoring skill.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog added a commit that referenced this pull request May 29, 2026
Consolidated follow-up to the May 29 weekly index. Covers the four
surfaces that moved from "you need a cn1lib for this" to "it is in
the framework" this release:

- Connectivity (PR #5021): com.codename1.io.{wifi,bonjour,usb} +
  NetworkManager.addNetworkTypeListener with the per-platform
  implementations and the three new CN1_INCLUDE_* defines that keep
  unused entitlements out of Apple's API-usage scan.
- Identity (PRs #5018, #5039): OIDC client backed by
  ASWebAuthenticationSession / Custom Tabs with PKCE; Sign in with
  Apple in core; refreshed Google/Facebook/Microsoft/Auth0/Firebase
  wrappers; WebAuthn / passkey client in W3C JSON wire format with
  iOS 16 and Android API 28 native bindings; Auth0/Firebase passkey
  helpers; legacy Oauth2 deprecated.
- Sharing (PR #5036): ShareResult callbacks
  (SHARED_TO/DISMISSED/FAILED) on iOS and Android via
  UIActivityViewController.completionWithItemsHandler and
  Intent.createChooser with IntentSender; IOSShareExtensionBuilder
  in the Maven plugin generates a complete .ios.appext bundle.
- AI (PRs #5035, #5057): com.codename1.ai with LlmClient for
  OpenAI/Anthropic/Gemini/Ollama, streaming SSE, ChatView,
  SpeechRecognizer/TextToSpeech, SecureStorage non-prompting
  overloads, simulator Ollama redirect, AiDependencyTable build-time
  injection, and the cn1-ai-mlkit-{barcode,docscan,face} cn1libs.

Closes with the structural element common to all four (scanner-driven
gating that mirrors the NFC/biometrics pattern from two weeks ago).
shai-almog added a commit that referenced this pull request May 29, 2026
Consolidated follow-up to the May 29 weekly index. Covers the four
surfaces that moved from "you need a cn1lib for this" to "it is in
the framework" this release:

- Connectivity (PR #5021): com.codename1.io.{wifi,bonjour,usb} +
  NetworkManager.addNetworkTypeListener with the per-platform
  implementations and the three new CN1_INCLUDE_* defines that keep
  unused entitlements out of Apple's API-usage scan.
- Identity (PRs #5018, #5039): OIDC client backed by
  ASWebAuthenticationSession / Custom Tabs with PKCE; Sign in with
  Apple in core; refreshed Google/Facebook/Microsoft/Auth0/Firebase
  wrappers; WebAuthn / passkey client in W3C JSON wire format with
  iOS 16 and Android API 28 native bindings; Auth0/Firebase passkey
  helpers; legacy Oauth2 deprecated.
- Sharing (PR #5036): ShareResult callbacks
  (SHARED_TO/DISMISSED/FAILED) on iOS and Android via
  UIActivityViewController.completionWithItemsHandler and
  Intent.createChooser with IntentSender; IOSShareExtensionBuilder
  in the Maven plugin generates a complete .ios.appext bundle.
- AI (PRs #5035, #5057): com.codename1.ai with LlmClient for
  OpenAI/Anthropic/Gemini/Ollama, streaming SSE, ChatView,
  SpeechRecognizer/TextToSpeech, SecureStorage non-prompting
  overloads, simulator Ollama redirect, AiDependencyTable build-time
  injection, and the cn1-ai-mlkit-{barcode,docscan,face} cn1libs.

Closes with the structural element common to all four (scanner-driven
gating that mirrors the NFC/biometrics pattern from two weeks ago).
shai-almog added a commit that referenced this pull request May 29, 2026
Consolidated follow-up to the May 29 weekly index. Covers the four
surfaces that moved from "you need a cn1lib for this" to "it is in
the framework" this release:

- Connectivity (PR #5021): com.codename1.io.{wifi,bonjour,usb} +
  NetworkManager.addNetworkTypeListener with the per-platform
  implementations and the three new CN1_INCLUDE_* defines that keep
  unused entitlements out of Apple's API-usage scan.
- Identity (PRs #5018, #5039): OIDC client backed by
  ASWebAuthenticationSession / Custom Tabs with PKCE; Sign in with
  Apple in core; refreshed Google/Facebook/Microsoft/Auth0/Firebase
  wrappers; WebAuthn / passkey client in W3C JSON wire format with
  iOS 16 and Android API 28 native bindings; Auth0/Firebase passkey
  helpers; legacy Oauth2 deprecated.
- Sharing (PR #5036): ShareResult callbacks
  (SHARED_TO/DISMISSED/FAILED) on iOS and Android via
  UIActivityViewController.completionWithItemsHandler and
  Intent.createChooser with IntentSender; IOSShareExtensionBuilder
  in the Maven plugin generates a complete .ios.appext bundle.
- AI (PRs #5035, #5057): com.codename1.ai with LlmClient for
  OpenAI/Anthropic/Gemini/Ollama, streaming SSE, ChatView,
  SpeechRecognizer/TextToSpeech, SecureStorage non-prompting
  overloads, simulator Ollama redirect, AiDependencyTable build-time
  injection, and the cn1-ai-mlkit-{barcode,docscan,face} cn1libs.

Closes with the structural element common to all four (scanner-driven
gating that mirrors the NFC/biometrics pattern from two weeks ago).
shai-almog added a commit that referenced this pull request May 29, 2026
Consolidated follow-up to the May 29 weekly index. Covers the four
surfaces that moved from "you need a cn1lib for this" to "it is in
the framework" this release:

- Connectivity (PR #5021): com.codename1.io.{wifi,bonjour,usb} +
  NetworkManager.addNetworkTypeListener with the per-platform
  implementations and the three new CN1_INCLUDE_* defines that keep
  unused entitlements out of Apple's API-usage scan.
- Identity (PRs #5018, #5039): OIDC client backed by
  ASWebAuthenticationSession / Custom Tabs with PKCE; Sign in with
  Apple in core; refreshed Google/Facebook/Microsoft/Auth0/Firebase
  wrappers; WebAuthn / passkey client in W3C JSON wire format with
  iOS 16 and Android API 28 native bindings; Auth0/Firebase passkey
  helpers; legacy Oauth2 deprecated.
- Sharing (PR #5036): ShareResult callbacks
  (SHARED_TO/DISMISSED/FAILED) on iOS and Android via
  UIActivityViewController.completionWithItemsHandler and
  Intent.createChooser with IntentSender; IOSShareExtensionBuilder
  in the Maven plugin generates a complete .ios.appext bundle.
- AI (PRs #5035, #5057): com.codename1.ai with LlmClient for
  OpenAI/Anthropic/Gemini/Ollama, streaming SSE, ChatView,
  SpeechRecognizer/TextToSpeech, SecureStorage non-prompting
  overloads, simulator Ollama redirect, AiDependencyTable build-time
  injection, and the cn1-ai-mlkit-{barcode,docscan,face} cn1libs.

Closes with the structural element common to all four (scanner-driven
gating that mirrors the NFC/biometrics pattern from two weeks ago).
shai-almog added a commit that referenced this pull request May 29, 2026
- Opening description rewritten so it does not read as a comma list.
- Dropped the "AI first, OAuth second" meta-paragraph.
- Anthropic and Gemini are no longer described as "still in flight";
  both are fully implemented in this release.
- Replaced the SecureStorage "small thing that matters more than it
  sounds" framing with a proper "How to handle API keys" section
  that opens with the security rule (never check in, never embed,
  never hardcode), explains the proper shape (fetch from your
  backend over an authenticated request, cache to the platform
  keychain via SecureStorage), and gives a concrete getOpenAiKey
  example.
- ChatView ASCII mockup replaced with a screenshot reference and a
  richer code example that drives the chat manually
  (ConversationStore + per-message lifecycle + error path).
- Removed the Speech / TTS subsection entirely. The native iOS /
  Android bridges are still tracked follow-ups in PR #5035 and are
  not shipping in this release.
- iOS Share Extension paragraph expanded into a real "how your app
  appears in other apps' share menus" section with a pom.xml
  configuration block, the cn1:generate-ios-share-extension Mojo
  invocation, and a host-side payload-read example.
- AI cn1libs rewritten. The 13 cn1libs land in a feature table with
  a one-line "what it gives you" column each, followed by a "how to
  add and use them" paragraph and three short worked examples
  (barcode, text recognition, translation). The "why these are
  cn1libs and not core" answer follows the concrete list rather
  than appearing without context.
- "What ties this together" section removed.
shai-almog added a commit that referenced this pull request May 30, 2026
Consolidated follow-up to the May 29 weekly index. Covers the four
surfaces that moved from "you need a cn1lib for this" to "it is in
the framework" this release:

- Connectivity (PR #5021): com.codename1.io.{wifi,bonjour,usb} +
  NetworkManager.addNetworkTypeListener with the per-platform
  implementations and the three new CN1_INCLUDE_* defines that keep
  unused entitlements out of Apple's API-usage scan.
- Identity (PRs #5018, #5039): OIDC client backed by
  ASWebAuthenticationSession / Custom Tabs with PKCE; Sign in with
  Apple in core; refreshed Google/Facebook/Microsoft/Auth0/Firebase
  wrappers; WebAuthn / passkey client in W3C JSON wire format with
  iOS 16 and Android API 28 native bindings; Auth0/Firebase passkey
  helpers; legacy Oauth2 deprecated.
- Sharing (PR #5036): ShareResult callbacks
  (SHARED_TO/DISMISSED/FAILED) on iOS and Android via
  UIActivityViewController.completionWithItemsHandler and
  Intent.createChooser with IntentSender; IOSShareExtensionBuilder
  in the Maven plugin generates a complete .ios.appext bundle.
- AI (PRs #5035, #5057): com.codename1.ai with LlmClient for
  OpenAI/Anthropic/Gemini/Ollama, streaming SSE, ChatView,
  SpeechRecognizer/TextToSpeech, SecureStorage non-prompting
  overloads, simulator Ollama redirect, AiDependencyTable build-time
  injection, and the cn1-ai-mlkit-{barcode,docscan,face} cn1libs.

Closes with the structural element common to all four (scanner-driven
gating that mirrors the NFC/biometrics pattern from two weeks ago).
shai-almog added a commit that referenced this pull request May 30, 2026
- Opening description rewritten so it does not read as a comma list.
- Dropped the "AI first, OAuth second" meta-paragraph.
- Anthropic and Gemini are no longer described as "still in flight";
  both are fully implemented in this release.
- Replaced the SecureStorage "small thing that matters more than it
  sounds" framing with a proper "How to handle API keys" section
  that opens with the security rule (never check in, never embed,
  never hardcode), explains the proper shape (fetch from your
  backend over an authenticated request, cache to the platform
  keychain via SecureStorage), and gives a concrete getOpenAiKey
  example.
- ChatView ASCII mockup replaced with a screenshot reference and a
  richer code example that drives the chat manually
  (ConversationStore + per-message lifecycle + error path).
- Removed the Speech / TTS subsection entirely. The native iOS /
  Android bridges are still tracked follow-ups in PR #5035 and are
  not shipping in this release.
- iOS Share Extension paragraph expanded into a real "how your app
  appears in other apps' share menus" section with a pom.xml
  configuration block, the cn1:generate-ios-share-extension Mojo
  invocation, and a host-side payload-read example.
- AI cn1libs rewritten. The 13 cn1libs land in a feature table with
  a one-line "what it gives you" column each, followed by a "how to
  add and use them" paragraph and three short worked examples
  (barcode, text recognition, translation). The "why these are
  cn1libs and not core" answer follows the concrete list rather
  than appearing without context.
- "What ties this together" section removed.
shai-almog added a commit that referenced this pull request May 31, 2026
…AI) (#5087)

* Blog: platform APIs in the core (connectivity + identity + sharing + AI)

Consolidated follow-up to the May 29 weekly index. Covers the four
surfaces that moved from "you need a cn1lib for this" to "it is in
the framework" this release:

- Connectivity (PR #5021): com.codename1.io.{wifi,bonjour,usb} +
  NetworkManager.addNetworkTypeListener with the per-platform
  implementations and the three new CN1_INCLUDE_* defines that keep
  unused entitlements out of Apple's API-usage scan.
- Identity (PRs #5018, #5039): OIDC client backed by
  ASWebAuthenticationSession / Custom Tabs with PKCE; Sign in with
  Apple in core; refreshed Google/Facebook/Microsoft/Auth0/Firebase
  wrappers; WebAuthn / passkey client in W3C JSON wire format with
  iOS 16 and Android API 28 native bindings; Auth0/Firebase passkey
  helpers; legacy Oauth2 deprecated.
- Sharing (PR #5036): ShareResult callbacks
  (SHARED_TO/DISMISSED/FAILED) on iOS and Android via
  UIActivityViewController.completionWithItemsHandler and
  Intent.createChooser with IntentSender; IOSShareExtensionBuilder
  in the Maven plugin generates a complete .ios.appext bundle.
- AI (PRs #5035, #5057): com.codename1.ai with LlmClient for
  OpenAI/Anthropic/Gemini/Ollama, streaming SSE, ChatView,
  SpeechRecognizer/TextToSpeech, SecureStorage non-prompting
  overloads, simulator Ollama redirect, AiDependencyTable build-time
  injection, and the cn1-ai-mlkit-{barcode,docscan,face} cn1libs.

Closes with the structural element common to all four (scanner-driven
gating that mirrors the NFC/biometrics pattern from two weeks ago).

* Platform-APIs post: rewrite around AI + OAuth headline

- Retitled "AI, OAuth, And Other Platform APIs In The Core".
- Reordered sections so the two headline pieces come first: AI
  (with deep tutorials) and OAuth / OIDC (with provider walk-
  throughs and a migration). WiFi / connectivity and share-sheet
  callbacks land at the end.
- Opener no longer runs together; the NFC / biometrics / crypto
  call-back is linked to the previous post explicitly.
- AI section expanded with concrete examples for streaming, tool
  calls, embeddings, image generation, the simulator Ollama
  redirect, the SecureStorage non-prompting overloads, the ChatView
  binding, and an ASCII mockup of the ChatView surface for readers
  who haven't seen it.
- New subsection explaining why the ML Kit AI features stay in
  cn1libs even though the LlmClient surface lives in core (core
  carries the plumbing every AI app wants; specialised verticals
  with large native dependencies stay opt-in; the cloud-build big-
  upload guard rules out the multi-gigabyte models).
- OAuth section now walks through OIDC discovery, the four
  provider wrappers (Google, Microsoft, Auth0, Facebook), Sign in
  with Apple, an Oauth2-to-OidcClient migration example, and the
  WebAuthn / passkey client.
- Dev-guide references go to the HTML version on the website.
- Hero image lands at /blog/platform-apis-in-the-core.jpg.
- Wrap-up has the link back to the intro and forward to the
  Wednesday post.

Also updates the developer-workflow post (rebased on top) to add
the forward link to this post in its wrap-up.

* Platform-APIs post: review fixes

- Opening description rewritten so it does not read as a comma list.
- Dropped the "AI first, OAuth second" meta-paragraph.
- Anthropic and Gemini are no longer described as "still in flight";
  both are fully implemented in this release.
- Replaced the SecureStorage "small thing that matters more than it
  sounds" framing with a proper "How to handle API keys" section
  that opens with the security rule (never check in, never embed,
  never hardcode), explains the proper shape (fetch from your
  backend over an authenticated request, cache to the platform
  keychain via SecureStorage), and gives a concrete getOpenAiKey
  example.
- ChatView ASCII mockup replaced with a screenshot reference and a
  richer code example that drives the chat manually
  (ConversationStore + per-message lifecycle + error path).
- Removed the Speech / TTS subsection entirely. The native iOS /
  Android bridges are still tracked follow-ups in PR #5035 and are
  not shipping in this release.
- iOS Share Extension paragraph expanded into a real "how your app
  appears in other apps' share menus" section with a pom.xml
  configuration block, the cn1:generate-ios-share-extension Mojo
  invocation, and a host-side payload-read example.
- AI cn1libs rewritten. The 13 cn1libs land in a feature table with
  a one-line "what it gives you" column each, followed by a "how to
  add and use them" paragraph and three short worked examples
  (barcode, text recognition, translation). The "why these are
  cn1libs and not core" answer follows the concrete list rather
  than appearing without context.
- "What ties this together" section removed.

* Platform-APIs post: real ChatView screenshot + per-cn1lib subsections

- Replaced the placeholder ChatView image reference with a real
  screenshot pulled from the ChatViewDevGuideScreenshotTest output
  (scripts/ios/screenshots-metal/ChatView_light.png), cropped to
  remove the test-harness caption and downscaled for web payload.
- Replaced the AI cn1lib quick-table and the "Why are these cn1libs"
  paragraph with thirteen per-cn1lib subsections (cn1-ai-mlkit-{
  text, barcode, face, labeling, translate, smartreply, langid,
  pose, segmentation, docscan}, cn1-ai-tflite, cn1-ai-whisper,
  cn1-ai-stablediffusion). Each subsection covers TL;DR, per-
  platform native bridge, use cases, and a real working code
  sample using the actual facade signature (TextRecognizer.
  recognize / BarcodeScanner.scan / FaceDetector.detect / ...
  PoseDetector.detect / SelfieSegmenter.segment / ... etc).
- Up front: the cn1libs aren't in the CN1 Preferences picker yet,
  so the manual pom.xml dependency snippet is the supported path;
  the shared pattern is given once with just the artifactId
  changing per cn1lib.
- Dropped the mention of scripts/create-ai-cn1lib.sh as an
  implementation detail.

* Platform-APIs post: shift to 2026-05-31, retime forward links

Tighter daily cadence: the platform-APIs post moves from Monday
2026-06-01 to Sunday 2026-05-31, and the codegen post (PR #5088)
moves from Wednesday 2026-06-03 to Monday 2026-06-01.

- Front-matter date: 2026-06-01 -> 2026-05-31.
- Wrap-up: "The next post is on Wednesday" -> "Tomorrow's post"
  with the link to /blog/build-time-codegen/.
- In-text reference: "Wednesday's post" (in the embeddings
  paragraph that mentions the upcoming ORM) -> "tomorrow's post".
- Developer-workflow post forward link (added on top of the
  merged base by this PR): "Monday's post" -> "Tomorrow's post"
  to match the new platform-APIs date.
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