Skip to content

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

Open
shai-almog wants to merge 33 commits into
masterfrom
feat/cn1-ai
Open

Add com.codename1.ai package, ChatView, Speech/TTS, and build-time AI dependency injection#5035
shai-almog wants to merge 33 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

JavaScript port screenshot updates

Compared 22 screenshots: 20 matched, 2 missing references.

  • ChatInput — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/javascript/screenshots/ChatInput.png.

    ChatInput
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput.png in workflow artifacts.

  • ChatView — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/javascript/screenshots/ChatView.png.

    ChatView
    Preview info: JPEG preview quality 10; JPEG preview quality 10.
    Full-resolution PNG saved as ChatView.png in workflow artifacts.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Android screenshot updates

Compared 112 screenshots: 110 matched, 2 missing references.

  • ChatInput — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/android/screenshots/ChatInput.png.

    ChatInput
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput.png in workflow artifacts.

  • ChatView — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/android/screenshots/ChatView.png.

    ChatView
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatView.png in workflow artifacts.

Native Android coverage

  • 📊 Line coverage: 11.86% (6818/57508 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.65% (34259/355043), branch 4.14% (1397/33740), complexity 5.19% (1679/32380), method 9.02% (1366/15152), 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 1067.000 ms
Base64 CN1 encode 157.000 ms
Base64 encode ratio (CN1/native) 0.147x (85.3% faster)
Base64 native decode 1144.000 ms
Base64 CN1 decode 478.000 ms
Base64 decode ratio (CN1/native) 0.418x (58.2% 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

iOS screenshot updates

Compared 112 screenshots: 110 matched, 2 missing references.

  • ChatInput — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots/ChatInput.png.

    ChatInput
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as ChatInput.png in workflow artifacts.

  • ChatView — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots/ChatView.png.

    ChatView
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as ChatView.png in workflow artifacts.

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 87000 ms
Simulator Boot (Run) 3000 ms
App Install 12000 ms
App Launch 6000 ms
Test Execution 316000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 831.000 ms
Base64 CN1 encode 2368.000 ms
Base64 encode ratio (CN1/native) 2.850x (185.0% slower)
Base64 native decode 538.000 ms
Base64 CN1 decode 1579.000 ms
Base64 decode ratio (CN1/native) 2.935x (193.5% slower)
Base64 SIMD encode 480.000 ms
Base64 encode ratio (SIMD/native) 0.578x (42.2% faster)
Base64 encode ratio (SIMD/CN1) 0.203x (79.7% faster)
Base64 SIMD decode 475.000 ms
Base64 decode ratio (SIMD/native) 0.883x (11.7% faster)
Base64 decode ratio (SIMD/CN1) 0.301x (69.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 95.000 ms
Image createMask (SIMD on) 16.000 ms
Image createMask ratio (SIMD on/off) 0.168x (83.2% faster)
Image applyMask (SIMD off) 275.000 ms
Image applyMask (SIMD on) 125.000 ms
Image applyMask ratio (SIMD on/off) 0.455x (54.5% faster)
Image modifyAlpha (SIMD off) 188.000 ms
Image modifyAlpha (SIMD on) 110.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.585x (41.5% faster)
Image modifyAlpha removeColor (SIMD off) 179.000 ms
Image modifyAlpha removeColor (SIMD on) 136.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.760x (24.0% faster)
Image PNG encode (SIMD off) 1434.000 ms
Image PNG encode (SIMD on) 1105.000 ms
Image PNG encode ratio (SIMD on/off) 0.771x (22.9% faster)
Image JPEG encode 824.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

iOS Metal screenshot updates

Compared 112 screenshots: 110 matched, 2 missing references.

  • ChatInput — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-metal/ChatInput.png.

    ChatInput
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as ChatInput.png in workflow artifacts.

  • ChatView — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-metal/ChatView.png.

    ChatView
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as ChatView.png in workflow artifacts.

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 57000 ms
Simulator Boot (Run) 1000 ms
App Install 10000 ms
App Launch 6000 ms
Test Execution 257000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 576.000 ms
Base64 CN1 encode 1382.000 ms
Base64 encode ratio (CN1/native) 2.399x (139.9% slower)
Base64 native decode 291.000 ms
Base64 CN1 decode 882.000 ms
Base64 decode ratio (CN1/native) 3.031x (203.1% slower)
Base64 SIMD encode 481.000 ms
Base64 encode ratio (SIMD/native) 0.835x (16.5% faster)
Base64 encode ratio (SIMD/CN1) 0.348x (65.2% faster)
Base64 SIMD decode 373.000 ms
Base64 decode ratio (SIMD/native) 1.282x (28.2% slower)
Base64 decode ratio (SIMD/CN1) 0.423x (57.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 63.000 ms
Image createMask (SIMD on) 17.000 ms
Image createMask ratio (SIMD on/off) 0.270x (73.0% faster)
Image applyMask (SIMD off) 122.000 ms
Image applyMask (SIMD on) 49.000 ms
Image applyMask ratio (SIMD on/off) 0.402x (59.8% faster)
Image modifyAlpha (SIMD off) 115.000 ms
Image modifyAlpha (SIMD on) 54.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.470x (53.0% faster)
Image modifyAlpha removeColor (SIMD off) 162.000 ms
Image modifyAlpha removeColor (SIMD on) 69.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.426x (57.4% faster)
Image PNG encode (SIMD off) 1152.000 ms
Image PNG encode (SIMD on) 882.000 ms
Image PNG encode ratio (SIMD on/off) 0.766x (23.4% faster)
Image JPEG encode 556.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 11 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>
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