Skip to content

Watch apps: one entry point, a phone-watch channel, and a simulator that can run the pair - #5487

Open
shai-almog wants to merge 69 commits into
masterfrom
watch-apps-product
Open

Watch apps: one entry point, a phone-watch channel, and a simulator that can run the pair#5487
shai-almog wants to merge 69 commits into
masterfrom
watch-apps-product

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

isWatch() existed without a product on top of it. This turns the watch render
slice into a watch app: one setting builds it on both platforms, the two apps can
talk, and you can develop the pair on your desktop.

Bugs this fixes

Wearables are new and nothing depends on them, so these are fixed rather than
preserved:

  • A cloud build never produced a watch app. codename1.watchMain became a
    build argument only on the local path; the server lifts only codename1.arg.*
    keys out of the uploaded settings file, so the daemon asked for watchMain and
    got nothing.
  • The documented companion default never embedded the watch app.
    watchNative.embedCompanion defaulted to false, so the "Embed Watch Content"
    phase was actively removed even in companion mode.
  • watchMain reached only iOS. Wear OS was enabled by an unrelated
    android.wear hint, so a project had to declare the same intent twice.
  • The simulator had no watch form factor. JavaSEPort never overrode
    isWatch(), so the guide's advice to iterate on a watch layout locally was
    untrue.
  • A Wear app could not scroll. onGenericMotionEvent read only the mouse
    axes; rotary input arrives on SOURCE_ROTARY_ENCODER / AXIS_SCROLL.
  • A round Wear face clipped its own corners. No display cutout is reported,
    so the safe area came back zero.

What is new

One setting. codename1.watchMain is the entire opt-in on both platforms.
Nine build hints are deleted; bundle id, deployment target, team id and display
name are derived. codename1.watchStandalone is the only other setting — the one
thing not inferable from the project. Net new hints: zero.

com.codename1.wearable — the phone↔watch channel, same API on Apple Watch
and Wear OS, modelled on com.codename1.car (portable API, SPI bridge, inert
when there is nothing on the other end). It exposes the three transports the
platforms actually give us, because picking the wrong one is the usual reason a
watch app "never gets the update": sendMessage for a live answer, putData for
state that survives sleep and relaunch, transferFile for bulk. Callbacks arrive
on the EDT and are queued across a cold start — the platform starts an app purely
to hand it a payload. Backed by WCSession on Apple and the Wearable Data Layer
on Android, both gated by API scan so apps that never talk to a watch link
nothing.

A simulator that runs the pair. Four generated watch skins (Apple Watch 41/45,
Wear round, Wear square), isWatch() and the "watch" override layer, and a
Watch menu that launches watchMain in a second process wired to the first — so
sendMessage and putData genuinely round-trip on the desktop. Two processes,
not two windows: Display is a singleton and sharing it would hide the bugs that
only appear once the pair is real.

Complications as surfaces families. A complication is a WidgetKit widget in
an accessory family, so WATCH_CIRCULAR/RECTANGULAR/INLINE/CORNER join
WidgetSize rather than getting an API of their own.

The guide, rewritten around the two-app model, with the data-sharing decision
table as its centre.

Not yet done, and stated as such

  • The watchOS widget extension target and the Wear complication/tile services
    that render the watch families are not generated yet. The guide says so.
  • The watch target still compiles the shared ParparVM translation and boots the
    phone Stub, so the watch binary is not separately tree-shaken.
  • Companion mode on Android does not yet emit a second wear APK; standalone works.

Verification

  • codenameone-maven-plugin: 322 pass, 1 skipped, including 14 new tests
  • core, JavaSE, iOS and Android ports all build; settings tool compiles
  • docs gates green: snippet validation (634 blocks), Asciidoctor --failure-level WARN, Vale, capitalization
  • Not run here: the build-ios-watch golden suite needs Xcode 26. It is the
    real gate for the companion-embed change, which alters the generated Xcode
    project.

Server-side half: codenameone/BuildDaemon#watch-apps-product

🤖 Generated with Claude Code

shai-almog and others added 6 commits July 29, 2026 08:11
…the cloud

Declaring codename1.watchMain is now the entire opt-in for a watch app on both
Apple Watch and Wear OS. Nine build hints are deleted; the bundle id, deployment
target, signing team and display name are derived from settings the project
already has. The only other recognized setting is codename1.watchStandalone,
which says the watch app ships on its own rather than inside the phone app --
the one thing that cannot be inferred.

Three shipped bugs fall out of this:

- A cloud build never produced a watch app. codename1.watchMain was lifted into
  a build argument only on the local path; the server reads only codename1.arg.*
  keys out of the uploaded settings file, so the daemon's WatchNativeBuilder
  asked for "watchMain" and got nothing. createAntProject now mirrors the
  secondary entry points into that namespace.

- The documented companion default never embedded the watch app.
  watchNative.embedCompanion defaulted to false, so the "Embed Watch Content"
  phase was actively removed even in companion mode. Embedding is what declaring
  a watchMain next to a phone main means, so it is no longer opt-in.

- watchMain reached only the iOS build. Wear OS was enabled by an unrelated
  android.wear hint, so a project had to say the same thing twice. Both
  platforms now read the same declaration.

The five byte-identical watchMain/tvMain blocks in CN1BuildMojo collapse into
one table, and WatchNativeBuilder gains the unit tests it never had (10 cases
pinning enablement, distribution, the Info.plist and the generated entry point)
plus 4 covering the cloud mirroring.

Mirrored to the BuildDaemon (WatchNativeBuilder, AndroidGradleBuilder,
IPhoneBuilder), which is the code cloud builds actually run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d watch app

A watch app and a phone app are two apps on two devices with two sandboxes, and
until now Codename One gave them no way to talk. com.codename1.wearable is that
channel, and it is the same API on Apple Watch and Wear OS.

The API exposes the three transports the platforms actually provide, because
choosing the wrong one is the usual reason a watch app "doesn't get the update":
sendMessage for a live request/response while both apps are awake, putData for
state that must survive sleep and relaunch, and transferFile for bulk. Payloads
carry the primitive types both platforms can move natively. Callbacks arrive on
the EDT and are queued across a cold start -- the platform starts an app purely
to hand it a message, so dropping what arrives before init() finishes would lose
exactly the payload that mattered. With nothing on the other end the whole API is
inert, so app code needs no platform conditionals. Modelled on
com.codename1.car: portable API, spi/WearableBridge from Display, no-op default.

The simulator could not do watch development at all: JavaSEPort never overrode
isWatch(), so it was always false and the guide's advice to iterate on a watch
layout locally was untrue. It now reads watch=true from the skin the same way it
reads tablet, prepends "watch" to the platform overrides so the existing theme
and CSS layers apply, and ships four generated skins -- Apple Watch 41mm and
45mm, Wear round and Wear square. The round one matters: it is where a layout
that assumes a rectangle falls apart, and its safe area is inset accordingly.

A Watch menu launches the project's watchMain in a second simulator process, and
JavaSEWearableBridge connects the pair so sendMessage and putData genuinely round
-trip on the desktop. Two processes rather than two windows in one JVM: Display
is a singleton, and sharing it would hide precisely the bugs that appear once the
pair is real. Replicated data is files in the shared app home, so a value
published while the peer was not running is simply there when it starts; live
messages need a loopback socket, so isReachable() is false with no peer open,
matching the device instead of papering over it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CN1WatchConnectivity is the WCSession delegate behind the phone-to-watch API. The
same file compiles into both the phone target and the watch target: WCSession is
symmetric, so the two halves of a pair run identical code and the Java API behaves
identically at both ends. The three transports land where they belong --
sendMessage on sendMessage:replyHandler:, replicated data on the session's
application context (which survives both apps being killed and is handed to the
peer whenever it next runs), and transferFile on transferFile:metadata:.

Payloads cross as opaque bytes, so the native layer never has to understand the
value model. Reply blocks for inbound messages are parked until the Java side has
hopped to the EDT and answered, which is what lets a listener do real work rather
than having to respond inside the delegate callback.

Gated by API scan like CarPlay and surfaces before it: the builder defines
CN1_USE_WATCHCONNECTIVITY and links WatchConnectivity.framework only when the app
references com.codename1.wearable, so apps that never talk to a watch carry no
WCSession symbols. Unlike the CarPlay and widgets defines this one deliberately
survives on the watch slice -- that is the half that needs it most. It is undone
on tvOS and Mac Catalyst, where WatchConnectivity does not exist.

Mirrored to the BuildDaemon.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three things a Wear OS app needs that the port did not provide.

The Data Layer bridge is the Android half of com.codename1.wearable. It is
injected into the generated project rather than living in the port, because the
port cannot reference play-services-wearable -- the same reason the Android Auto
glue is injected. The three transports land where they belong: a live message on
MessageClient (nearby nodes only), replicated data on a DataItem marked urgent so
the system does not sit on it for minutes, and a file on a background-synced
DataItem. MessageClient is one-way, so a request carries its reply token in the
path and the answer comes back on a matching reply path, which is what makes the
reply handler behave identically to WCSession's. Unlike Apple, Wear allows several
watches on one phone, so sends fan out to every connected node. The listener
service is what Android starts to deliver a message when the app is not running --
exactly the case the API's cold-start queue exists for.

Rotary input: the rotating side button and bezel report on SOURCE_ROTARY_ENCODER /
AXIS_SCROLL, which onGenericMotionEvent did not read -- it handled only the mouse
axes, so a Wear app could not scroll at all. It now feeds the same wheel path the
Digital Crown uses, scaled by the device's own scroll factor.

Round-screen safe area: a circular face reports no display cutout, so the safe
area came back zero and a layout drawn to the full rectangle had its corners eaten
by the bezel. The largest rectangle inside a circle loses about 14.6% a side, and
that is now reserved on top of whatever the system asks for.

Mirrored to the BuildDaemon.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A watch complication is a WidgetKit widget in an accessory family, and a Wear
complication is the same shape again: content-driven, rendered while the app is
not running, fed by a timeline. That is exactly what com.codename1.surfaces
already models, so complications are four new WidgetSize families rather than a
second API with its own serialization, image handling and state model.

WATCH_CIRCULAR / WATCH_RECTANGULAR / WATCH_INLINE / WATCH_CORNER map onto the
WidgetKit accessory families, and the Swift renderer resolves the most specific
published layout: accessoryRectangular prefers "watchRectangular" and falls back
to "lockscreen", so an app that only published a lock-screen layout still gets a
complication, and one that designed for both gets what it designed.
accessoryCorner is emitted behind an os(watchOS) guard -- the symbol does not
exist on iOS, so naming it unguarded would fail to compile the phone extension
over code that could never run.

WidgetTimeline kept one field per family and a switch in three accessors, which
did not survive four more families; it is now a map keyed by family, and the
serializer's content check iterates the enum instead of naming members. Both
changes mean the next family costs one enum constant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The chapter documented build hints. It now documents the product: how one project
produces two apps, what they do and do not share, how to run the pair while you
develop, how they exchange information, and how a complication is published.

The section that matters most is the data one, because the mistake it prevents is
the common one. A watch app and a phone app are two apps in two sandboxes, so
Storage, Preferences and the SQLite database are per device -- a value written on
the phone is simply not on the watch. The three transports exist because they
answer three different questions, and choosing the wrong one is the usual reason
a watch app "never gets the update", so the chapter leads with a decision table
and says plainly which to reach for by default.

Also corrected: the old chapter told developers to iterate on a watch layout in
the simulator, which was untrue until this branch made isWatch() work there.

The complications section states honestly that the families and descriptor
pipeline are in place but the platform targets that render them on a watch face
are not generated yet, rather than implying a working feature.

Snippets are extracted into docs/demos as the guide requires; Vale, LanguageTool,
the capitalization check, snippet validation and the warning-free Asciidoctor
build all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 11:21
chatgpt-codex-connector[bot]

This comment was marked as resolved.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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

Developer Guide quality checks:

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

This comment was marked as resolved.

"wakeups" and the British "honouring" both trip the gate; the guide is US English.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 11:36
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

chatgpt-codex-connector[bot]

This comment was marked as resolved.

The copyright gate checks added and modified sources, so editing a file that
never had a header brings it into scope. Five files needed one:
GenerateWatchSkins (new), the settings tool's main class, the wearables guide
snippet, the surfaces Swift renderer resource, and BuildHintSchemaDefaults --
which carried a truncated hybrid header naming Codename One in the copyright line
but Oracle in the grant, and matched neither accepted form.

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

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings July 29, 2026 11:44
The Codename One runtime has no java.util.EnumMap, so the Ant build (which
compiles core against CLDC11) failed where the Maven build had not. Lookups here
are by key, so the ordering an EnumMap would give buys nothing.

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

This comment was marked as resolved.

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@shai-almog

shai-almog commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

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

SpotBugs treats DM_NUMBER_CTOR and DM_FP_NUMBER_CTOR as build-breaking, and
valueOf caches small values rather than allocating. Five sites across the
wearable API plus the simulator bridge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 12:07
chatgpt-codex-connector[bot]

This comment was marked as resolved.

This comment was marked as resolved.

…nch edits

CodenameOneView.java is uniformly CRLF on master (885 of 885 lines) and this
branch had rewritten the whole file to LF; AndroidImplementation.java is mostly
CRLF and had picked up 138 converted lines. The result was a diff of 1187
insertions / 1015 deletions across the Android port for what is really 176
insertions / 4 deletions -- the actual change (round-screen safe-area insets,
rotary encoder input, the wearable hook) was buried in ending churn, and every
line falsely showed up as this branch's in blame.

Rebuilt both files against master's bytes: unchanged lines keep master's exact
ending, new lines take the file's dominant one. Verified the rebuild is
ending-only -- `git diff -w --ignore-cr-at-eol HEAD` over Ports/Android is
empty -- and that the raw diff against master now equals the
whitespace-ignoring diff (89/4 for CodenameOneView, 8/0 for
AndroidImplementation), i.e. no churn is left.

No functional change.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 68 out of 69 changed files in this pull request and generated no new comments.

Suppressed comments (3)

tools/watch-skins/GenerateWatchSkins.java:65

  • GenerateWatchSkins.main() assumes 2 command-line arguments; running it without them will throw ArrayIndexOutOfBoundsException before producing a helpful error. Please validate args length and provide a short usage message.
    maven/codenameone-maven-plugin/src/main/resources/com/codename1/builders/wearable/CN1WearableListenerService.java:154
  • onDataChanged() calls ensureAppRunning() before verifying that any event is from a known node and within the CN1 path prefix. This contradicts the service's own threat model (dropping unknown sources) and can also cause unnecessary app launches for events that are immediately discarded. Only call ensureAppRunning() after at least one event passes the provenance/path checks.
    maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/AndroidGradleBuilder.java:341
  • The Javadoc block above watchMainClass() documents the lifecycle class the generated stub instantiates, but that behavior is implemented by appLifecycleClass(), not watchMainClass(). Also, having two consecutive Javadoc comments here is confusing (the first one is effectively orphaned). Please move/merge the Javadoc onto appLifecycleClass() and keep watchMainClass() documented only as the raw declared watch entry point.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7306d51b6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// --- helpers ------------------------------------------------------------

private File dataFile(String path) {
return new File(dataDir, encodePath(path));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape dot-only simulator paths

When an application publishes the valid relative path ".", encodePath() preserves it and this resolves to dataDir itself rather than to a value file. Consequently putData(new WearableMessage(".")) always fails during replacement, and removeData(".") can delete the data directory when it is empty; ".." similarly resolves outside the intended directory. Escape dot-only names (or reject them consistently at the public API boundary) before constructing the file.

Useful? React with 👍 / 👎.

shai-almog and others added 3 commits August 2, 2026 22:23
Fixes the intermittent HealthEdtDeliveryTest.aFacadeActionDeliversOnTheEdt
failure. It was not a flaky test -- it was the contract leaking.

EdtResult marshals completion to the EDT, but a listener attached AFTER the
resource has settled is run by AsyncResource.ready inline, on whichever thread
attaches it. openHealthSettings and openProviderSetup complete before they
return, so an off-EDT caller writing the ordinary

    openHealthSettings().onResult(cb)

races the callSerially across two adjacent statements. Win the race and the
callback lands on the EDT; lose it and the same line delivers off the EDT. That
is exactly the "may or may not be on the EDT" asymmetry EdtResult exists to
remove, and it failed intermittently, which is worse than failing always.

The guarantee is now applied where the callback is INVOKED rather than where
the resource is completed, so it holds however late the listener attaches.
Already on the EDT still runs inline, keeping the no-runnable-per-link property.

Added aLateListenerStillDeliversOnTheEdt, which forces the losing side by
waiting for the resource to settle before attaching. Verified as a control: it
fails deterministically without the fix ("expected: <true> but was: <false>")
and passes with it, rather than relying on a rare race to show up.

except() is deliberately NOT wrapped the same way: a late except on an
already-failed resource fires synchronously and that is relied on to read an
error back without waiting (HealthFallbackTest.errorOf documents the trick).
Wrapping it made 15 tests report a failing call as succeeding. The asymmetry is
left, and commented, rather than silently half-fixed.

Full core-unittests: 4677 tests, 0 failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mirror of the change one commit earlier, on the deletion path. When a Data
Layer outage outlasts the inline attempts, the catch discarded the only
callback this state ever gets: if the path is now empty, or an unchanged
lower-ranked replica is the survivor, nothing else is guaranteed to fire, so
the listener stayed wrong for the life of the process while getData() reported
otherwise.

It now schedules the same deferred resolution, with afterDeletion=true. That
flag matters: on the first-sight path an empty result means only "nothing to
announce", but after a deletion an empty result IS the answer and has to be
delivered as a removal -- and the stamp dropped, so a value republished later
with a lower sequence is not filtered as older.

Reporting a removal on a failed query is still refused, as before: another node
may be publishing the path, and a removal is not recoverable app-side. What
changed is that silence is no longer the fallback.

The flag lives beside pendingWinnerPaths under the same monitor, so it cannot
outlive the resolution that owns it.

Mirrored to BuildDaemon; both injected sources type-check.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 70 out of 71 changed files in this pull request and generated no new comments.

Suppressed comments (3)

scripts/run-ios-ui-tests.sh:686

  • The ARCHS override xcconfig is created with mktemp but never cleaned up, so repeated local runs will leave a growing set of cn1-ios-archs*.xcconfig files in the temp directory. Using a deterministic path under the derived-data directory avoids leaking temp files while keeping the same behavior.
    tools/watch-skins/GenerateWatchSkins.java:65
  • main() reads args[0]/args[1] without validating the argument count, so invoking this utility without both arguments will throw ArrayIndexOutOfBoundsException. Since the file includes usage docs and is callable outside the Maven build, it should fail with a clear usage message instead.
    maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/AndroidGradleBuilder.java:341
  • This Javadoc describes the lifecycle class the generated stub instantiates, but it currently attaches to watchMainClass() (which only returns the declared watch main). This makes the generated Javadoc misleading; the block should document appLifecycleClass(), and watchMainClass() should keep the short one-line comment.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a446e4cd09

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1625 to +1627
synchronized (pendingWinnerPaths) {
if (!pendingWinnerPaths.add(path)) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve deletion state when coalescing retries

When a first-sight change schedules a non-deletion resolution and a deletion for the same path arrives before that timer runs, this early return discards afterDeletion=true. If the deferred query then finds the path empty, wasAfterDeletion(path) remains false, so no dataRemoved callback is sent; because the deletion was the last Data Layer event, the listener can retain the deleted value indefinitely. Upgrade an existing pending entry to deletion state before returning.

Useful? React with 👍 / 👎.

Comment on lines +190 to +191
if (recentlySeen(sourceNodeId) || sourceNodeId.equals(localNodeId(context))) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter local DataItem echoes from peer listeners

On Android, independently of the reported simulator-restart case, Play Services echoes this app's own putData() and removeData() items with the local node as their host, and this branch explicitly accepts that node as trusted. onDataChanged() consequently forwards ordinary local publications to WearableDataListener, although WearableDataListener.java:25-39 defines these callbacks as peer changes; applications that perform work in response can therefore process their own write twice, unlike on iOS. Keep local items available for winner bookkeeping without exposing them as peer callbacks.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 70 out of 71 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java:15075

  • In watch skins, overrideNames already includes watch (e.g. generated skins set overrideNames=watch,ios,applewatch), but getPlatformOverrides() prepends another "watch". This yields duplicate overrides (e.g. [watch, watch, ios, ...]), which can cause redundant lookups and makes override ordering harder to reason about. Prefer returning the existing list when it already starts with watch, or de-duplicating/reordering so watch leads exactly once.
    public String[] getPlatformOverrides() {
        if(isWatch()) {
            // "watch" leads, matching the iOS and Android ports, so a resource or
            // CSS override written for a device also applies here. The skin's own
            // overrideNames follow, which is where "applewatch" / "android-watch"
            // come from.
            String[] out = new String[platformOverrides.length + 1];
            out[0] = "watch";
            System.arraycopy(platformOverrides, 0, out, 1, platformOverrides.length);
            return out;

scripts/run-ios-ui-tests.sh:691

  • ARCH_XCCONFIG="$(mktemp -t cn1-ios-archs).xcconfig" creates an extra unused temp file (from mktemp) and then writes the xcconfig to a different filename with the .xcconfig suffix. This leaves a stray temp file behind each run. Use an mktemp template that includes the suffix so the created file is the one you write/read.
    tools/watch-skins/GenerateWatchSkins.java:65
  • main() assumes at least 2 arguments and will throw ArrayIndexOutOfBoundsException if invoked incorrectly. Since this is a standalone generator tool, it should validate args.length and emit a clear usage message (and optionally fail if the output directory can't be created).

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a446e4cd09

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1064 to +1065
DataItemBuffer items = Tasks.await(dataClient.getDataItems(),
TIMEOUT_SECONDS, TimeUnit.SECONDS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep data-path enumeration off the EDT

When application code calls WearableConnection.getDataPaths() from the Codename One EDT, this synchronous Tasks.await() can block painting and input for up to five seconds while Play Services is slow or unavailable. The bridge's state-query methods avoid this stall by returning cached snapshots, but this separate public enumeration path has no thread restriction or asynchronous alternative; serve cached paths on the EDT or perform the query off-thread.

Useful? React with 👍 / 👎.

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

… runner

build-ios-metal failed with "1 of 149 expected screenshots were not produced",
naming BrowserComponent. The app console shows what actually happened:

  15:01:33.403 suite starting test=BrowserComponentScreenshotTest
  15:01:57.740 CN1SS:ERR: BrowserComponent DOM loaded, but its native peer
               was not composited into the screen capture

The DOM loaded; WebKit's native peer just had not reached the capture in time.
On failure the test emits nothing, so the suite reports a MISSING screenshot,
which reads like a lost capture rather than a slow one.

Two things were wrong.

The budget did not mean what it said. waitedMs accumulated only the 250ms
retry delays and ignored the Display.screenshot() each attempt performs, which
on a loaded runner costs far more than the delay -- so a nominal 12s budget
actually gave up after 24.3s of wall clock, and no amount of reading the
constant would tell you that. It is now a wall-clock deadline.

And the budget was too tight for this job. This is not a correctness
threshold: the assertion is that the peer composites at all, and
containsRenderedBrowserContent still decides that. It only bounds how long a
loaded runner may take. Raised to 45s.

Why this job got slow enough to matter is worth recording, because it is a
consequence of a fix rather than a regression: master defaults
watchNative.embedCompanion to false, so the app->watch dependency and the
Embed Watch Content phase are stripped and the iOS jobs never build the watch
target. This branch fixes that documented bug, so the dependency is now real
and every iOS screenshot job legitimately builds the watch app too. That is
correct for a companion project; it also removed the headroom this test was
relying on.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0c66f95c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!mine.known) {
return NO;
}
return mine.stamp >= theirs.stamp;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a consistent tiebreak for equal iOS publication stamps

When the phone and watch publish the same path during the same millisecond, both entries receive the same wall-clock sequence, and this local-first comparison makes each device select its own value. Each side then suppresses the peer callback and getData() permanently returns different data until another write occurs. Break equal stamps using a stable identifier shared by both devices so every participant chooses the same winner.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 71 out of 72 changed files in this pull request and generated no new comments.

Suppressed comments (4)

tools/watch-skins/GenerateWatchSkins.java:65

  • GenerateWatchSkins.main() assumes args[0]/args[1] exist; running it without exactly 2 args will throw ArrayIndexOutOfBoundsException instead of producing a clear usage error.
    scripts/run-ios-ui-tests.sh:686
  • ARCH_XCCONFIG is built as "$(mktemp ...).xcconfig". mktemp creates a temp file, but the string concatenation makes the script write to a different path, leaving an unused temp file behind and defeating mktemp’s atomic file creation.
    maven/codenameone-maven-plugin/src/main/java/com/codename1/util/IOSWidgetExtensionBuilder.java:564
  • This documentation block includes @param tags referring to a "kind" parameter, but the following method is hasIosSurface() (no parameters). As written, the comments are misleading and look like they belong to isWatchOnly()/hasWatchFamily().
    Ports/iOSPort/nativeSources/UIWebViewEventDelegate.m:28
  • TargetConditionals.h is included twice back-to-back. The outer include is sufficient for TARGET_OS_WATCH, so the second include is redundant and makes the preprocessor structure harder to scan.

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: e0c66f95c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 63ms / native 5ms = 12.6x speedup
SIMD float-mul (64K x300) java 63ms / native 5ms = 12.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 198.000 ms
Base64 CN1 decode 135.000 ms
Base64 SIMD encode 100.000 ms
Base64 encode ratio (SIMD/CN1) 0.505x (49.5% faster)
Base64 SIMD decode 99.000 ms
Base64 decode ratio (SIMD/CN1) 0.733x (26.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 31.000 ms
Image createMask (SIMD on) 26.000 ms
Image createMask ratio (SIMD on/off) 0.839x (16.1% faster)
Image applyMask (SIMD off) 70.000 ms
Image applyMask (SIMD on) 63.000 ms
Image applyMask ratio (SIMD on/off) 0.900x (10.0% faster)
Image modifyAlpha (SIMD off) 68.000 ms
Image modifyAlpha (SIMD on) 60.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.882x (11.8% faster)
Image modifyAlpha removeColor (SIMD off) 78.000 ms
Image modifyAlpha removeColor (SIMD on) 66.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.846x (15.4% faster)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 59ms / native 4ms = 14.7x speedup
SIMD float-mul (64K x300) java 65ms / native 3ms = 21.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 188.000 ms
Base64 CN1 decode 127.000 ms
Base64 SIMD encode 101.000 ms
Base64 encode ratio (SIMD/CN1) 0.537x (46.3% faster)
Base64 SIMD decode 91.000 ms
Base64 decode ratio (SIMD/CN1) 0.717x (28.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 117.000 ms
Image createMask (SIMD on) 16.000 ms
Image createMask ratio (SIMD on/off) 0.137x (86.3% faster)
Image applyMask (SIMD off) 43.000 ms
Image applyMask (SIMD on) 34.000 ms
Image applyMask ratio (SIMD on/off) 0.791x (20.9% faster)
Image modifyAlpha (SIMD off) 34.000 ms
Image modifyAlpha (SIMD on) 27.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.794x (20.6% faster)
Image modifyAlpha removeColor (SIMD off) 41.000 ms
Image modifyAlpha removeColor (SIMD on) 28.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.683x (31.7% faster)

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0c66f95c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +62 to +64
int64_t now = (int64_t) ([[NSDate date] timeIntervalSince1970] * 1000.0);
last = now > last ? now : last + 1;
int64_t result = last;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Advance the iOS sequence past observed peer stamps

When either device's clock is ahead—such as after manual clock changes or a backward correction on the other device—this generator continues issuing wall-clock-based stamps below the peer's existing stamp. Because getData() and application-context delivery always select the greater stamp, subsequent putData() and removeData() calls from the behind device are silently ignored until real time catches up. Persist the local floor and advance it whenever a peer stamp is observed, as the Android bridge's Lamport-style clock does.

Useful? React with 👍 / 👎.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 4ms = 13.5x speedup
SIMD float-mul (64K x300) java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 245.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.265x (73.5% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.492x (50.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 7.000 ms
Image createMask ratio (SIMD on/off) 0.583x (41.7% faster)
Image applyMask (SIMD off) 22.000 ms
Image applyMask (SIMD on) 18.000 ms
Image applyMask ratio (SIMD on/off) 0.818x (18.2% faster)
Image modifyAlpha (SIMD off) 16.000 ms
Image modifyAlpha (SIMD on) 11.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.688x (31.3% faster)
Image modifyAlpha removeColor (SIMD off) 21.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.619x (38.1% faster)

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0c66f95c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// phone main, so it is not opt-in. A standalone watch app ships on its
// own instead, so strip any dependency/copy phase Xcode or an earlier
// generator run left behind.
if (isStandalone()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Archive the standalone watch target

When codename1.watchStandalone=true, this branch removes the watch target from the phone target's dependencies and embed phase, but no other packaging path selects the watch target, and its build settings still set SKIP_INSTALL=YES. A normal build of the generated phone scheme therefore produces only the phone app and no distributable standalone watch app; configure the standalone target as the archive product instead of merely detaching it.

Useful? React with 👍 / 👎.

/// - `contents`: the file bytes
public static void transferFile(String path, String name, byte[] contents) {
WearableBridge b = bridge();
if (b != null && b.isSupported() && path != null && contents != null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject empty transfer paths before dispatch

When an app calls transferFile("", ...), this guard accepts the path even though WearableMessage explicitly rejects empty paths. JavaSE throws immediately while constructing the transfer wrapper, whereas Android and iOS send it and later construct the received WearableMessage on the EDT, where the resulting IllegalArgumentException can terminate delivery processing. Reject empty paths consistently before invoking the bridge.

Useful? React with 👍 / 👎.

…ry pass -sdk

Two findings from the codex backlog, both real.

android.wear.standalone was treated as an independent trigger for legacy Wear
mode (`legacyWear = android.wear || android.wear.standalone`). The relationship
is directional and I inverted it: android.wear implied standalone, but the
standalone sub-hint only ever applied INSIDE android.wear=true and never
implied Wear on its own. A legacy phone project carrying a stray
android.wear.standalone=true was therefore given the API 23 floor and a
REQUIRED android.hardware.type.watch feature, and Play filters that APK off
every phone -- a shipping app made undeliverable, with no build error.

Rather than patch the boolean in place, the decision is now two named helpers,
legacyWearMode / legacyWearStandalone, with AndroidLegacyWearHintTest pinning
the direction (standalone alone does not enable Wear; wear implies standalone
unless explicitly opted out). It reads fine either way round, which is how it
got inverted, so the invariant belongs in a test rather than a comment.

run-ios-device-release-build.sh: the destination-enumeration fallback logged
"retrying with -sdk iphoneos only" but the loop only STRIPPED the -destination
pair and never added -sdk, so the retry ran with neither. It now passes it.
Noted in the comment that -sdk overrides SDKROOT for every target, so an
embedded watch target fails the link in this fallback -- deliberate for a
last-resort path, where failing loudly beats a silent mis-build.

Plugin suite: 445 tests, 0 failures. Mirrored to BuildDaemon.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated no new comments.

Suppressed comments (3)

tools/watch-skins/GenerateWatchSkins.java:66

  • GenerateWatchSkins.main() assumes two arguments and will throw ArrayIndexOutOfBoundsException if invoked incorrectly (e.g., from a misconfigured build step). Add an explicit args-length check with a clear usage error so failures are actionable.
    Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java:15076
  • getPlatformOverrides() unconditionally prepends "watch" when isWatch() is true, but platformOverrides is derived directly from the skin's overrideNames (loadSkinFile splits overrideNames). The generated watch skins also include "watch" in overrideNames, so this produces a duplicate "watch" entry (e.g. "watch,watch,ios,applewatch"), which can lead to redundant override lookups and makes override ordering harder to reason about.
    public String[] getPlatformOverrides() {
        if(isWatch()) {
            // "watch" leads, matching the iOS and Android ports, so a resource or
            // CSS override written for a device also applies here. The skin's own
            // overrideNames follow, which is where "applewatch" / "android-watch"
            // come from.
            String[] out = new String[platformOverrides.length + 1];
            out[0] = "watch";
            System.arraycopy(platformOverrides, 0, out, 1, platformOverrides.length);
            return out;
        }

maven/codenameone-maven-plugin/src/main/java/com/codename1/util/IOSWidgetExtensionBuilder.java:531

  • mapFamily() calls family.startsWith("watch") without a null check. If a Kind's iosFamilies list contains a null entry (e.g. from malformed/partial JSON), this will throw NullPointerException during generation. The rest of the method uses null-safe "literal".equals(family) comparisons, so adding the same guard here keeps behavior consistent.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c7731d9f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +394 to +398
ctx[cn1WearableValueKey(path)] = (payload == nil ? [NSData data] : payload);
ctx[cn1WearableStampKey(path)] = @(cn1WearableNextSequence());
[ctx removeObjectForKey:cn1WearableTombKey(path)];
NSError *err = nil;
[s updateApplicationContext:ctx error:&err];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Serialize iOS application-context updates

When two threads call putData() for different paths concurrently, both can copy the same old applicationContext, mutate separate dictionaries, and then replace the entire context; whichever updateApplicationContext runs last silently discards the other publication. removeData() uses the same unsynchronized read-modify-write sequence, so serialize all local context mutations or maintain a synchronized canonical dictionary before publishing it.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants