Watch apps: one entry point, a phone-watch channel, and a simulator that can run the pair - #5487
Watch apps: one entry point, a phone-watch channel, and a simulator that can run the pair#5487shai-almog wants to merge 69 commits into
Conversation
…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>
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
"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>
Cloudflare Preview
|
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>
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>
|
Compared 12 screenshots: 12 matched. |
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>
…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>
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
💡 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)); |
There was a problem hiding this comment.
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 👍 / 👎.
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>
|
@codex review |
There was a problem hiding this comment.
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*.xcconfigfiles 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 documentappLifecycleClass(), andwatchMainClass()should keep the short one-line comment.
There was a problem hiding this comment.
💡 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".
| synchronized (pendingWinnerPaths) { | ||
| if (!pendingWinnerPaths.add(path)) { | ||
| return; |
There was a problem hiding this comment.
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 👍 / 👎.
| if (recentlySeen(sourceNodeId) || sourceNodeId.equals(localNodeId(context))) { | ||
| return true; |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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,
overrideNamesalready includeswatch(e.g. generated skins setoverrideNames=watch,ios,applewatch), butgetPlatformOverrides()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 withwatch, or de-duplicating/reordering sowatchleads 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 (frommktemp) and then writes the xcconfig to a different filename with the.xcconfigsuffix. This leaves a stray temp file behind each run. Use anmktemptemplate that includes the suffix so the created file is the one you write/read.
tools/watch-skins/GenerateWatchSkins.java:65main()assumes at least 2 arguments and will throwArrayIndexOutOfBoundsExceptionif invoked incorrectly. Since this is a standalone generator tool, it should validateargs.lengthand emit a clear usage message (and optionally fail if the output directory can't be created).
|
@codex review |
There was a problem hiding this comment.
💡 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".
| DataItemBuffer items = Tasks.await(dataClient.getDataItems(), | ||
| TIMEOUT_SECONDS, TimeUnit.SECONDS); |
There was a problem hiding this comment.
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 👍 / 👎.
|
@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>
There was a problem hiding this comment.
💡 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; |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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
@paramtags 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.
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
Compared 146 screenshots: 146 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 146 screenshots: 146 matched. Benchmark ResultsDetailed Performance Metrics
|
|
@codex review |
|
Compared 147 screenshots: 147 matched. |
|
Compared 147 screenshots: 147 matched. |
There was a problem hiding this comment.
💡 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".
| int64_t now = (int64_t) ([[NSDate date] timeIntervalSince1970] * 1000.0); | ||
| last = now > last ? now : last + 1; | ||
| int64_t result = last; |
There was a problem hiding this comment.
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 👍 / 👎.
|
Compared 146 screenshots: 146 matched. Benchmark ResultsDetailed Performance Metrics
|
|
@codex review |
There was a problem hiding this comment.
💡 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()) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
💡 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".
| ctx[cn1WearableValueKey(path)] = (payload == nil ? [NSData data] : payload); | ||
| ctx[cn1WearableStampKey(path)] = @(cn1WearableNextSequence()); | ||
| [ctx removeObjectForKey:cn1WearableTombKey(path)]; | ||
| NSError *err = nil; | ||
| [s updateApplicationContext:ctx error:&err]; |
There was a problem hiding this comment.
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 👍 / 👎.
isWatch()existed without a product on top of it. This turns the watch renderslice 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:
codename1.watchMainbecame abuild argument only on the local path; the server lifts only
codename1.arg.*keys out of the uploaded settings file, so the daemon asked for
watchMainandgot nothing.
companiondefault never embedded the watch app.watchNative.embedCompaniondefaulted tofalse, so the "Embed Watch Content"phase was actively removed even in companion mode.
watchMainreached only iOS. Wear OS was enabled by an unrelatedandroid.wearhint, so a project had to declare the same intent twice.JavaSEPortnever overrodeisWatch(), so the guide's advice to iterate on a watch layout locally wasuntrue.
onGenericMotionEventread only the mouseaxes; rotary input arrives on
SOURCE_ROTARY_ENCODER/AXIS_SCROLL.so the safe area came back zero.
What is new
One setting.
codename1.watchMainis the entire opt-in on both platforms.Nine build hints are deleted; bundle id, deployment target, team id and display
name are derived.
codename1.watchStandaloneis the only other setting — the onething not inferable from the project. Net new hints: zero.
com.codename1.wearable— the phone↔watch channel, same API on Apple Watchand Wear OS, modelled on
com.codename1.car(portable API, SPI bridge, inertwhen 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":
sendMessagefor a live answer,putDataforstate that survives sleep and relaunch,
transferFilefor bulk. Callbacks arriveon the EDT and are queued across a cold start — the platform starts an app purely
to hand it a payload. Backed by
WCSessionon Apple and the Wearable Data Layeron 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 aWatch menu that launches
watchMainin a second process wired to the first — sosendMessageandputDatagenuinely round-trip on the desktop. Two processes,not two windows:
Displayis a singleton and sharing it would hide the bugs thatonly 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/CORNERjoinWidgetSizerather 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
that render the watch families are not generated yet. The guide says so.
phone Stub, so the watch binary is not separately tree-shaken.
Verification
codenameone-maven-plugin: 322 pass, 1 skipped, including 14 new tests--failure-level WARN, Vale, capitalizationbuild-ios-watchgolden suite needs Xcode 26. It is thereal gate for the companion-embed change, which alters the generated Xcode
project.
Server-side half: codenameone/BuildDaemon#watch-apps-product
🤖 Generated with Claude Code