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 280 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 280 commits into
masterfrom
watch-apps-product

Conversation

@shai-almog

@shai-almog shai-almog commented Jul 29, 2026

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 compiles its OWN ParparVM translation, rooted at
    codename1.watchMain, and boots a watch stub. Verified end to end: the watch
    target compiles, links and renders on the watchOS simulator (166 pass / 3 fail
    / 2 skip). Tree-shaking is real -- the phone tree carries 24 generated SVG
    classes, the watch tree none -- and that is also the one known regression:
    those classes are reached reflectively, so SVGStatic,
    SVGAnimatedScreenshotTest and LottieAnimatedScreenshotTest fall back to a
    placeholder render. The watch pass needs the same reflective roots the phone
    pass keeps. A project whose watch entry point IS the phone main keeps a single
    translation and is unaffected.
  • Companion mode on Android does not yet emit a second wear APK; standalone works.

Verification

CI is the gate. Everything below is what was additionally checked locally before
pushing, and the numbers move as the branch does.

  • codenameone-maven-plugin: 466 pass, 1 skipped
  • core-unittests: 4754 pass, with SpotBugs, PMD and Checkstyle at zero
  • core, JavaSE, iOS and Android ports build; the injected Android bridge sources
    type-check against a stub harness (nothing in CI compiles them) and the
    WatchConnectivity native passes clang -fsyntax-only for iphoneos/arm64
  • docs gates green: snippet validation (659 blocks), Asciidoctor
    --failure-level WARN, Vale, capitalization
  • build-ios-watch does run in CI -- it is a job in Test iOS UI build
    scripts
    , on macos-15 with DEVELOPER_DIR pointed at Xcode 26, and this PR
    touches paths that trigger it. It is the real gate for anything that alters
    the generated Xcode project. It is not run on the authoring machine, and
    during the rapid review cycle several of its runs were cancelled by
    supersession, so check it against the CURRENT head rather than an older run.

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

🤖 Generated with Claude Code

Copilot AI lite 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.

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.

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings July 29, 2026 11:44
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.

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.

@shai-almog

shai-almog commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 8.09% (7868/97198 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.07% (41636/515701), branch 2.88% (1402/48723), complexity 3.18% (1663/52272), method 4.90% (1355/27642), class 9.97% (367/3680)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 8.09% (7868/97198 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.07% (41636/515701), branch 2.88% (1402/48723), complexity 3.18% (1663/52272), method 4.90% (1355/27642), class 9.97% (367/3680)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 239ms / native 267ms = 0.8x speedup
SIMD float-mul (64K x300) java 181ms / native 93ms = 1.9x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 73.000 ms
Base64 CN1 decode 84.000 ms
Base64 native encode 334.000 ms
Base64 encode ratio (CN1/native) 0.219x (78.1% faster)
Base64 native decode 274.000 ms
Base64 decode ratio (CN1/native) 0.307x (69.3% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

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

Copilot AI review requested due to automatic review settings July 29, 2026 12:47
chatgpt-codex-connector[bot]

This comment was marked as resolved.

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings July 29, 2026 12:56
chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@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: e99b7e550c

ℹ️ 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".

The Cloudflare Pages step required both an immutable URL and a
"Deployment alias URL:" line in wrangler's output, and failed the job when
either was absent. wrangler does not print the alias line reliably -- one
deploy of this branch printed it and the next, to the same preview branch, did
not -- while the upload, the alias and the deployment had all succeeded. The
job went red over a line of console output.

The alias still existed: the branch URL this build could not scrape serves the
site. PREVIEW_BRANCH is built from a PR number, so it is always lowercase,
hyphenated and well inside the 28 characters Cloudflare truncates an alias to,
and the URL can simply be rebuilt from it. Only accepted if it responds;
otherwise the immutable deployment URL is used and a warning is emitted, since
that is a working preview link too.

A missing immutable URL is still fatal -- there is nothing to fall back to.

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread CodenameOne/src/com/codename1/wearable/WearableConnection.java Outdated
requestReplayAfterDrain ran the replay inline whenever a data listener was
registered, including from inside the drain it would re-enter. Past MAX_PENDING
tracked transfers that recursed: the re-offer parked, parking evicted a
one-shot, the eviction asked for a replay, and the listener was still there so
it ran -- a frame deeper each time. It now defers while a drain is in flight or
anything is parked, and drainPending will not finish with a request outstanding,
since a replay that parked nothing would otherwise be stranded.

That is not the same question as "has anyone started listening", and the Android
spool needs the second one. Its drain runs in the bridge constructor, before the
app's init() has registered anything, so a cold launch correctly leaves every
record on disk -- and nothing but inbound traffic ever re-ran it, so with no new
traffic the previous run's one-shot messages and removals stayed there. Asking
through requestReplayAfterDrain cannot fix it: a spooled MESSAGE record on a
process holding only a DATA listener would drain, find nothing deliverable,
re-arm and run again without bound. runWhenListenerRegisters never runs inline
and fires from both registrations.

Two in the generated script. A disjunction with a watchOS operand is
unconditionally true on the watch, so `#if os(watchOS) || FEATURE` selects that
arm however FEATURE evaluates; leaving it undecided kept an #else that imported
an iOS-only product into the watch target.

And a regression from the parenthesis normalizer added last round: it stripped
the directive before the selection test, so `#ifndef TARGET_OS_WATCH` -- FALSE
on the watch, whose arm is the #else -- read as a positive watch test and the
watch's own arm was suppressed. Definedness is read from the raw text now, ahead
of any normalization, and never selects.

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: 37365dd87b

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread CodenameOne/src/com/codename1/wearable/WearableConnection.java Outdated
A message the port has written down was parked as an ordinary Runnable, which is
what evictOne discards FIRST and silently. A spool drain that queued more than
the cap while a listener existed, and then lost that listener before the EDT ran
the batch -- an app deregistering on pause -- had every re-parked message reach
that path. Neither callback fired: not delivered, because nothing was, and not
dropped, because there was none. The record stayed on disk still marked in
flight, unclaimable for the life of the process, having spent an attempt from a
budget meant to bound poison payloads.

Such a message is a one-shot by definition -- this process holds the only
in-memory copy and the record behind it is claimed -- so it is marked as one,
which also moves it behind everything replaceable in the eviction order, and it
carries a dropped callback. The re-park keeps both; it used to strip them.

The removal path had the same hole by another route. An evicted removal is
re-announced, so the LISTENER recovers, but that re-announcement goes through the
plain entry point carrying no callback, so a port holding the removal durably
heard neither outcome. Replicated carries the release too now, returned from
both eviction branches -- superseded included, since superseded is still not
delivered.

On the port side unclaimSpooled gives the record back without deleting it, and
refunds the attempt. The budget bounds a record that keeps killing the process;
an eviction is an observed event on a process that is plainly still alive, and
charging for it is what let a burst past the cap delete a record that had never
once been seen.

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: 07fd3ee194

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread Ports/Android/src/com/codename1/impl/android/CodenameOneView.java
shai-almog and others added 2 commits August 13, 2026 08:26
Routing both message call sites through the form that carries onDropped left the
three-argument deliver with no callers, and SpotBugs is a zero-findings gate --
UPM_UNCALLED_PRIVATE_METHOD failed build-test (8). Its javadoc explained why a
delivery is parked rather than dropped, which is still worth saying, so it moves
onto the overload that survives.

The gate command in CLAUDE.md is the reason this was reported clean locally.
core-unittests is declared inside the unittests profile, so -am never reaches it
from the android/ios/plugin invocation the file documents, and the
spotbugsXml.xml left in its target is whatever an earlier run wrote. Reading
that file after the documented command answers a question about some previous
build. The missing invocation is now documented next to it, along with the fact
that the module analyses the CORE classes -- so removing a caller anywhere in
core can make a private method dead without the phone or plugin analysis ever
mentioning it.

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

A truncated or bad-Base64 spool entry left replaySpooled without calling any of
its completion callbacks, and claimSpooled had already put the key in
SPOOL_IN_FLIGHT. spoolBusy() then stayed true for the life of the process, so
deliverableNow answered no to everything after it and every reply-bearing
request was downgraded to a plain spooled message whose sender timed out. One
unreadable entry disabled live requests entirely. Such a record is now
discarded, which means deleted and unclaimed rather than abandoned where it
sits, and the contract that every exit runs exactly one callback is written
down.

Apple Watch published no safe area at all. getDisplaySafeArea() reads globals
that viewSafeAreaInsetsDidChange fills, and the watch host does not run it, so
every layout was handed the full rectangle and corner controls sat under the
rounded bezel. The host derives and publishes insets when the surface starts or
resizes, from the corner ratio the generated skins are drawn with so the
simulator and the device agree. A rounded RECTANGLE, not the inscribed circle a
round Wear face needs -- that rule would throw away a third of the screen.

On Wear, the API 23-27 branch applies the round inset from a posted callback,
after the form is already laid out, and the surface callback that would re-lay
it returns early on unchanged dimensions. It now revalidates when the rectangle
moved, as the API 28 branch does. On a round face this branch is the only source
of inset, so it always moved and was always ignored.

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
…tring

The companion passed -Dskin to guarantee it comes up on a watch skin at all,
which CN.isWatch() and the "watch" override layer both depend on. But a reload
runs the same code in the same JVM and a permanent property outranks the
preference the skin menu writes, so selecting Apple Watch 41mm, Wear Round or
Wear Square saved the choice, reloaded, and came straight back up on 45mm --
which made every watch form factor except the default unusable. The property is
cleared after the first load: loadSkinFile has just written that skin to the
preference, so a reload with no choice made still finds a watch skin, and dskin
stays set so hasSkins() is unaffected.

The watch HealthKit gate collapsed the detected usage to one boolean and then
accepted either purpose string. Apple wants the disclosure for the operation:
a watch that only reads and declares only NSHealthUpdateUsageDescription is
refused at authorization exactly as if it had declared nothing. Read and write
are now carried separately from the same root the entitlement decision uses,
each requiring its own string, and the error names the one that is missing
rather than offering a choice that does not exist. A hint-only answer still has
no direction, so either string remains acceptable there.

Basic settings declared a four-row grid for nine fields. GridLayout takes its
preferred height from the declared count, so the row the watch and TV entry
points forced was compressed or clipped.

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

ℹ️ 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 and others added 2 commits August 13, 2026 09:24
bondedNodeIds() asks the capability client, so it lists peers that actually have
this app; connectedNodes() is the unscoped Data Layer list and includes any
watch paired to the phone. Seeding the quorum from the first and then adding the
second back put devices in it that cannot publish an acknowledgement even in
principle -- no app, no listener, nothing to write one -- so allTook never became
true and a file every real recipient had taken stayed published until the
seven-day hard cap.

Not asked yet is a different answer from nobody has it, and an empty quorum
retires a transfer nothing acknowledged. The sweep runs on the transfer worker,
which may block, so bondedNodeIds() has just made the blocking query by the time
the quorum is built -- if bondedKnown is still false that query failed, and the
sweep defers to the coalescing interval instead of deciding on nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An iOS AppIcon set declares iPhone and iPad idioms, which is why it is filtered
out of the catalog staged for the watch -- and nothing replaced it, so every
watch product built here shipped with no icon. That does not affect building,
running or testing; it fails App Store submission, which is the one place it
cannot be worked around, and it left the standalone watch product unshippable
without hand-editing the generated project.

The icon is scaled from the project's own, as the phone's is, into a WatchImages
catalog the phone never sees. One 1024x1024 image in the single-size form Xcode
has accepted since 14: the per-device idiom list it replaces has to enumerate
every watch size ever shipped and needs a new entry for each new one, and the
deployment floor here is watchOS 10.

Compiling the catalog is not enough on its own -- actool promotes a set to the
app icon only when the target names it -- so the watch target now sets
ASSETCATALOG_COMPILER_APPICON_NAME.

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: 945bce5c1a

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread tools/watch-skins/GenerateWatchSkins.java Outdated
Comment thread tools/watch-skins/GenerateWatchSkins.java Outdated
The Apple models were 352x430 and 396x484, labelled logical points and in fact
Retina pixels. The watch host reports points -- scaleValue is 1 on that slice, so
getDisplayWidthImpl returns CN1WatchRenderingView.logicalWidth() unscaled -- so
the simulator offered twice the space on each axis and a layout that fitted in
it could reflow or clip on the device. 176x215 and 198x242 are what the device
gives you.

The corner radius follows, as a ratio rather than a count: the skins are no
longer in one coordinate space, with the Apple models in points and the Wear
ones in pixels, so a fixed 28 was twice the drawn corner on one of them. The
ratio is the one CN1WatchHost derives the device safe area from, which is why
the regenerated 45mm skin advertises the same five-point inset the watch does.
Bezel margins are proportional now too; 70/90 was chosen against a 396-wide
model and framed a 198-wide one in a third of an image of border.

Helvetica is what makes loadSkinFile set its internal isIOS flag, whatever
platformName says, so a Wear skin declaring it resolved native:* faces through
the iOS font candidates -- Apple typography and Apple metrics in a preview of an
archive carrying the Android Material theme. The Wear models ask for Roboto.

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread docs/developer-guide/Wearables.asciidoc Outdated
Comment thread Ports/iOSPort/nativeSources/WATCHOS_PORT.md
shai-almog and others added 2 commits August 13, 2026 11:11
…the docs

writeWatchAppIcon puts the catalog in <Main>-src, the same place the watch
Info.plist and the staged watch sources go. The script looked for it beside the
project, which is one level up in dist, so the directory test failed silently:
the catalog was never added to the watch's resources phase and
ASSETCATALOG_COMPILER_APPICON_NAME named a set the target did not contain. The
icon fix therefore did nothing until now. The path convention is pinned by a
test, because a wrong string in generated ruby has no compiler to catch it.

Two documents describing behaviour this branch has already changed. The guide
told the reader no watch icon is generated and to add an AppIcon set by hand,
which would now mean replacing a valid generated catalog. WATCHOS_PORT.md still
said the watch compiles the phone's single translation and that watchMain is not
the entry point -- so a maintainer diagnosing missing watch output would look at
the Core Graphics backend when the honest answer is now that the watch root may
simply not reach the code, and would put watch UI in the phone lifecycle where
it will not run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Publishing a real safe area on the watch changed where bottom-anchored content
sits, which is the entire point of it: getDisplaySafeArea() used to return the
full rectangle, so a component honouring it drew into the rounded bezel. Nine
tests moved and 208 did not, and the nine are all bottom-anchored -- the
lightweight pickers, the sheets, the tab bars. That distribution is the evidence
this is the intended change rather than a rendering regression: nothing that
ignores the safe area moved at all.

Checked rather than accepted. TabsTheme_light now sits the tab labels above the
curve instead of on it, and the clipped glyph fragments the old golden carried
just above them -- content drawn where the bezel eats it -- are gone. Sheet is
inset the same way with its content intact.

Recorded here rather than refreshed quietly, because a golden that changes for a
reason nobody wrote down is indistinguishable from one that regressed.

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread Ports/Android/src/com/codename1/impl/android/CodenameOneView.java
… its say

The API 28+ branch tested for a change before applyRoundScreenInset ran, and
that runs at the end of the method for every branch. On a round watch reporting
no cutout and no system bars the branch concluded nothing had moved -- its own
guard skips it when all four insets are zero -- and the round inset then changed
all four with no revalidation asked for. The form stayed sized to the full
rectangle with its corners under the bezel, which is the fault the API 23-27
branch already had fixed one round ago and this one still had.

There is now a single comparison at the end, against a snapshot taken before any
branch runs, so the round inset is part of the question rather than applied
after it was answered. The posted API 23-27 branch keeps its own: it runs after
the method has returned, so the end-of-method test has already happened.

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread tools/watch-skins/GenerateWatchSkins.java Outdated
shai-almog and others added 4 commits August 13, 2026 19:15
…e in

Moving those models to logical points left ppi at the physical Retina 326.
JavaSEPort turns it into pixelMilliRatio = ppi / 25.4, which every
millimetre-based size is converted through, so the simulator made each one twice
the size the device gives it: IOSImplementation.getDeviceDensity() answers
DENSITY_MEDIUM for a display narrower than 500, and the watch now reports 198.
163 is that density. The Wear models are still in pixels and keep 326.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Probing usr/lib/swift/<M>.swiftmodule and usr/include/<M> found the Swift
overlays -- Darwin, Dispatch and ObjectiveC each have one, which is why the
three modules that got tested passed -- and missed nearly everything else.
Apple declares its C modules in shared maps, and of the 78 the watchOS SDK's
usr/include/module.modulemap names, 66 matched no path at all: SQLite3, zlib,
MachO, notify, os_object, TargetConditionals among them. A staged source
importing any of those was called unattributed, and one unattributed import
switches strict package filtering off wholesale, mirroring every phone package
product into the watch target.

The maps are now parsed, along with the toolchain's own swiftmodule directories,
and memoised: about 2800 names in roughly a second, once per build.

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

theGeneratedPredicatesDecideCorrectly failed on build-linux-jdk8: these plugin
tests run on Linux too, where there is no xcrun to ask for an SDK path. The
guard I wrote for that -- `|| sdk_names.empty?` -- cannot fire, because the
helper seeds the Swift standard names before it reads anything and so never
returns an empty map. On Linux it therefore asked whether a host with no Apple
SDK knows about SQLite3, and failed.

Guarded on whether an SDK path was actually found, with the lookup itself
rescued: a missing xcrun raises rather than returning empty. Verified both ways
-- against the real SDKs, and against an xcrun that exits non-zero, which takes
the skip.

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

The notice naming kinds that will appear on no platform was guarded on
widgetExtensionBuilder existing, and parseSurfacesManifest clears
surfacesExtensionEnabled before that builder is created whenever every declared
kind is watch-only. The one case the notice exists for is therefore the one case
it could not reach: the build discarded every declared surface and reported only
that the iOS lowering had been skipped.

It is emitted from parseSurfacesManifest now, naming the kinds. The block at the
extension-generation site keeps the mixed case -- watch-only kinds alongside iOS
ones, where the extension is built and quietly carries none of them -- and its
comment no longer claims to cover both, which was how this hid.

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

ℹ️ 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 and others added 3 commits August 13, 2026 21:05
putSecondaryEntryPointArguments read only the unprefixed project setting, while
the cloud mirror leaves an existing codename1.arg.* value alone -- so
-Dcodename1.arg.watchMain=... reached the daemon and was ignored locally, and
one invocation produced two different products. An absent project setting also
disabled the watch target outright, whatever the command line asked for. That is
worse than an override that works nowhere, because nothing in the local build
says the flag was dropped.

The overlaid argument comes first now, the project setting behind it, which is
the order the mirror already resolves them in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`== 1` and `!= 0` say exactly what the bare macro says, and the atom matcher
accepted only the bare form -- so `#if TARGET_OS_WATCH == 1` was left undecided,
its #else survived, and a phone-only package imported there was mirrored into
the watch target.

The mirror of the `== 0` reading already in cn1_watch_excludes_watch: that side
learned two rounds ago that a zero comparison is a negation, and this side was
not taught the same thing about a one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Requiring both a device and a simulator slice rejected a valid watchOS-only
archive: the framework phase then skipped it and the watch target failed on
undefined symbols, over a simulator slice nothing in that build was going to
load. The same conjunction was in the bundle check, over
CFBundleSupportedPlatforms and SupportedPlatformVariant, and is corrected with
it -- it is the same rule about the same artifacts, and only the archive half
was reported.

EITHER slice here, deliberately, rather than a specific one. This generator
hands the developer an Xcode project and does not know which destination they
will build for. The cloud builder does -- it selects the SDK and the destination
itself -- so it asks for exactly the matching slice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants