Skip to content

State restoration and continuity across devices - #5663

Open
shai-almog wants to merge 115 commits into
masterfrom
feature/state-restoration-continuity
Open

State restoration and continuity across devices#5663
shai-almog wants to merge 115 commits into
masterfrom
feature/state-restoration-continuity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

What this is

A Codename One app that the operating system kills comes back to its first screen. Lifecycle.stop() kept the current Form in a plain field, so a suspend and resume looked right and a reclaimed process lost everything -- which on Android is the ordinary outcome of a few minutes in another app.

com.codename1.continuity saves what the user was doing and brings it back, and on Apple platforms offers that same work to the other devices the person is signed in to.

The substrate was already here and unused: com.codename1.router keeps a stack of deep-link paths, which is exactly a serializable, portable "where the user is". Navigation.restoreStack rebuilds it without animating through every screen on the way.

Two packages, because they cost different things

iOS / macOS Android Simulator & desktop JavaScript
Restore after the process is killed yes yes yes yes
Restore the @Route screen stack yes yes yes yes
Carry on where they left off, on a device they're holding yes -- simulated --
A key/value store synced across devices yes -- simulated --
Carry state to any other device your StateRelay your StateRelay your StateRelay your StateRelay

com.codename1.continuity buys a native define and one NSUserActivityTypes entry, and no entitlement. com.codename1.continuity.sync buys the iCloud key-value store, whose entitlement has to be granted on the App ID -- handing that to an app that only wanted to pass work to the tablet in the user's other hand would fail its codesigning for a capability it never asked for. Same split, same reason, as usesSmartHome / usesHomeAccessoryData.

Three decisions worth recording

  • Saving is continuous, not at shutdown. Every navigation schedules a checkpoint written once per event-loop pass. Android's generated activity blocks the platform main thread until the app's stop() returns, so an app that saved there would pay for it on every suspend.
  • Nothing happens until the application opts in. start() is unchanged for every existing app, and restore() is never called for anyone -- where restoration belongs in a launch is a decision only the app can make.
  • Codename One runs no relay server. Continuation between Apple devices is the platform's; everything else goes through a StateRelay against the app's own endpoint, because deciding which saved states belong to the same person is the app's account system's question.

The load-bearing detail

The iOS delegate matches continuity before intents. The intents block ends in a general branch that hands any remaining activity to Java and returns Java's answer, and Intents.dispatchUserActivity correctly declines a type it never declared -- so an app using both would have had its own continuation asked about by the wrong framework, told no, and dropped.

NSUserActivityTypes stays a single key for the same reason a second one is worse than none: iOS reads a duplicated key unpredictably. The two contributors meet in userActivityTypesKey.

Android needs nothing injected -- no permission, no manifest entry, no dependency. The bridge exists there for one job: flushing the checkpoint from onSaveInstanceState, the last callback guaranteed before a background process is reclaimed. Both cross-device capabilities report themselves unsupported rather than being emulated, because an app told "yes" by a bridge that then dropped the state is worse off than one told "no", which can fall back to a relay and reach an iPhone as easily as another Android.

Verification

The load-bearing one: a real Xcode build of the generated project produced one NSUserActivityTypes array carrying both the sample's three App Intent ids and ...hellocodenameone.continuity. BUILD SUCCEEDED for iOS and watchOS, and a deliberate probe inside the #ifdef CN1_USE_CONTINUITY block failed the build, so that check is not vacuous. Dead-code elimination left both native callbacks with real bodies.

Also green locally: 46 core unit tests, 10 plist-merge tests, 7 preflight tests, 5 shipped-hooks tests; SpotBugs 0 findings across core-unittests, android, ios, codenameone-maven-plugin and build-hint-catalog; and the cast-semantics, control-character, copyright, build-hint, snippet and prose gates.

Paired change

The builder half is mirrored in BuildDaemon; that PR has to land with this one.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T09:29:08.129889Z a3878e3 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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

ℹ️ 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/continuity/StateCodec.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java
Comment thread CodenameOne/src/com/codename1/continuity/StateCodec.java Outdated
@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

@github-actions

github-actions Bot commented Sep 2, 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)

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

  • Tests: 222 total, 0 failed, 0 skipped
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

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

ℹ️ 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/continuity/Continuity.java
Comment thread CodenameOne/src/com/codename1/continuity/AppState.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.21% (9146/99268 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.97% (47024/524333), branch 3.53% (1751/49663), complexity 3.51% (1858/52961), method 5.40% (1505/27861), class 10.86% (405/3729)
    • 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: 9.21% (9146/99268 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.97% (47024/524333), branch 3.53% (1751/49663), complexity 3.51% (1858/52961), method 5.40% (1505/27861), class 10.86% (405/3729)
    • 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 217ms / native 174ms = 1.2x speedup
SIMD float-mul (64K x300) java 189ms / native 141ms = 1.3x 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 94.000 ms
Base64 CN1 decode 83.000 ms
Base64 native encode 349.000 ms
Base64 encode ratio (CN1/native) 0.269x (73.1% faster)
Base64 native decode 324.000 ms
Base64 decode ratio (CN1/native) 0.256x (74.4% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 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 62ms / native 4ms = 15.5x speedup
SIMD float-mul (64K x300) java 66ms / native 5ms = 13.2x 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 202.000 ms
Base64 CN1 decode 141.000 ms
Base64 SIMD encode 102.000 ms
Base64 encode ratio (SIMD/CN1) 0.505x (49.5% faster)
Base64 SIMD decode 93.000 ms
Base64 decode ratio (SIMD/CN1) 0.660x (34.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 29.000 ms
Image createMask ratio (SIMD on/off) 2.900x (190.0% slower)
Image applyMask (SIMD off) 59.000 ms
Image applyMask (SIMD on) 73.000 ms
Image applyMask ratio (SIMD on/off) 1.237x (23.7% slower)
Image modifyAlpha (SIMD off) 62.000 ms
Image modifyAlpha (SIMD on) 65.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.048x (4.8% slower)
Image modifyAlpha removeColor (SIMD off) 54.000 ms
Image modifyAlpha removeColor (SIMD on) 55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.019x (1.9% slower)

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 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 32ms / native 2ms = 16.0x speedup
SIMD float-mul (64K x300) java 34ms / native 3ms = 11.3x 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 108.000 ms
Base64 CN1 decode 67.000 ms
Base64 SIMD encode 47.000 ms
Base64 encode ratio (SIMD/CN1) 0.435x (56.5% faster)
Base64 SIMD decode 42.000 ms
Base64 decode ratio (SIMD/CN1) 0.627x (37.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 27.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.074x (92.6% faster)
Image applyMask (SIMD off) 22.000 ms
Image applyMask (SIMD on) 38.000 ms
Image applyMask ratio (SIMD on/off) 1.727x (72.7% slower)
Image modifyAlpha (SIMD off) 41.000 ms
Image modifyAlpha (SIMD on) 45.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.098x (9.8% slower)
Image modifyAlpha removeColor (SIMD off) 15.000 ms
Image modifyAlpha removeColor (SIMD on) 40.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 2.667x (166.7% slower)

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 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 Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 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.

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 166 screenshots: 166 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 55ms / native 4ms = 13.7x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.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 264.000 ms
Base64 CN1 decode 157.000 ms
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.242x (75.8% faster)
Base64 SIMD decode 61.000 ms
Base64 decode ratio (SIMD/CN1) 0.389x (61.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.429x (57.1% faster)
Image applyMask (SIMD off) 57.000 ms
Image applyMask (SIMD on) 19.000 ms
Image applyMask ratio (SIMD on/off) 0.333x (66.7% faster)
Image modifyAlpha (SIMD off) 18.000 ms
Image modifyAlpha (SIMD on) 13.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.722x (27.8% faster)
Image modifyAlpha removeColor (SIMD off) 23.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.565x (43.5% faster)

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300) java 61ms / native 3ms = 20.3x 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 active (NEON-accelerated)
Base64 CN1 encode 144.000 ms
Base64 CN1 decode 86.000 ms
Image encode benchmark iterations 100
Image createMask (SIMD off) 5.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.200x (80.0% faster)
Image applyMask (SIMD off) 39.000 ms
Image applyMask (SIMD on) 29.000 ms
Image applyMask ratio (SIMD on/off) 0.744x (25.6% faster)
Image modifyAlpha (SIMD off) 32.000 ms
Image modifyAlpha (SIMD on) 28.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.875x (12.5% faster)
Image modifyAlpha removeColor (SIMD off) 34.000 ms
Image modifyAlpha removeColor (SIMD on) 28.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.824x (17.6% faster)

@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: 85e4b48ba4

ℹ️ 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/iOSPort/nativeSources/CodenameOne_GLAppDelegate.m
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/sync/SyncedStore.java Outdated

@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: 2c5a39af91

ℹ️ 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/continuity/Continuity.java
Comment thread CodenameOne/src/com/codename1/continuity/StateCodec.java

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

ℹ️ 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/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated

@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: 646d895af1

ℹ️ 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/impl/continuity/LocalContinuityBridge.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java

@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: 3f2b72c7e5

ℹ️ 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/continuity/Continuity.java
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated

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

ℹ️ 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/continuity/Continuity.java

@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: 1185b5b3f2

ℹ️ 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/router/Navigation.java
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated

@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: 5eaf1db562

ℹ️ 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/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/sync/SyncedStore.java Outdated

@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: 34598c5aa2

ℹ️ 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/continuity/sync/SyncedStore.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java
Comment thread CodenameOne/src/com/codename1/continuity/AppState.java
@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 98000 ms
Simulator Boot (Run) 0 ms
App Install 12000 ms
App Launch 10000 ms
Test Execution 473000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 73ms / native 7ms = 10.4x speedup
SIMD float-mul (64K x300) java 77ms / native 4ms = 19.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 277.000 ms
Base64 CN1 decode 149.000 ms
Base64 native encode 691.000 ms
Base64 encode ratio (CN1/native) 0.401x (59.9% faster)
Base64 native decode 358.000 ms
Base64 decode ratio (CN1/native) 0.416x (58.4% faster)
Base64 SIMD encode 62.000 ms
Base64 encode ratio (SIMD/CN1) 0.224x (77.6% faster)
Base64 SIMD decode 75.000 ms
Base64 decode ratio (SIMD/CN1) 0.503x (49.7% faster)
Base64 encode ratio (SIMD/native) 0.090x (91.0% faster)
Base64 decode ratio (SIMD/native) 0.209x (79.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.250x (75.0% faster)
Image applyMask (SIMD off) 53.000 ms
Image applyMask (SIMD on) 56.000 ms
Image applyMask ratio (SIMD on/off) 1.057x (5.7% slower)
Image modifyAlpha (SIMD off) 64.000 ms
Image modifyAlpha (SIMD on) 43.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.672x (32.8% faster)
Image modifyAlpha removeColor (SIMD off) 53.000 ms
Image modifyAlpha removeColor (SIMD on) 69.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.302x (30.2% slower)

@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 70ms / native 7ms = 10.0x speedup
SIMD float-mul (64K x300) java 51ms / native 3ms = 17.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 170.000 ms
Base64 CN1 decode 131.000 ms
Base64 native encode 728.000 ms
Base64 encode ratio (CN1/native) 0.234x (76.6% faster)
Base64 native decode 332.000 ms
Base64 decode ratio (CN1/native) 0.395x (60.5% faster)
Base64 SIMD encode 62.000 ms
Base64 encode ratio (SIMD/CN1) 0.365x (63.5% faster)
Base64 SIMD decode 62.000 ms
Base64 decode ratio (SIMD/CN1) 0.473x (52.7% faster)
Base64 encode ratio (SIMD/native) 0.085x (91.5% faster)
Base64 decode ratio (SIMD/native) 0.187x (81.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 9.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.444x (55.6% faster)
Image applyMask (SIMD off) 75.000 ms
Image applyMask (SIMD on) 65.000 ms
Image applyMask ratio (SIMD on/off) 0.867x (13.3% faster)
Image modifyAlpha (SIMD off) 85.000 ms
Image modifyAlpha (SIMD on) 75.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.882x (11.8% faster)
Image modifyAlpha removeColor (SIMD off) 54.000 ms
Image modifyAlpha removeColor (SIMD on) 38.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.704x (29.6% faster)

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

ℹ️ 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/iOSPort/src/com/codename1/impl/ios/IOSContinuityCallbacks.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/AppState.java

@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: 33e4b0d530

ℹ️ 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/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/StateCodec.java Outdated

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

ℹ️ 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/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java
@shai-almog

shai-almog commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 65000 ms
Simulator Boot (Run) 0 ms
App Install 16000 ms
App Launch 1000 ms
Test Execution 519000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 95ms / native 4ms = 23.7x speedup
SIMD float-mul (64K x300) java 89ms / native 7ms = 12.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 550.000 ms
Base64 CN1 decode 467.000 ms
Base64 native encode 2255.000 ms
Base64 encode ratio (CN1/native) 0.244x (75.6% faster)
Base64 native decode 7505.000 ms
Base64 decode ratio (CN1/native) 0.062x (93.8% faster)
Base64 SIMD encode 86.000 ms
Base64 encode ratio (SIMD/CN1) 0.156x (84.4% faster)
Base64 SIMD decode 52.000 ms
Base64 decode ratio (SIMD/CN1) 0.111x (88.9% faster)
Base64 encode ratio (SIMD/native) 0.038x (96.2% faster)
Base64 decode ratio (SIMD/native) 0.007x (99.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.400x (60.0% faster)
Image applyMask (SIMD off) 165.000 ms
Image applyMask (SIMD on) 60.000 ms
Image applyMask ratio (SIMD on/off) 0.364x (63.6% faster)
Image modifyAlpha (SIMD off) 223.000 ms
Image modifyAlpha (SIMD on) 154.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.691x (30.9% faster)
Image modifyAlpha removeColor (SIMD off) 120.000 ms
Image modifyAlpha removeColor (SIMD on) 221.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.842x (84.2% slower)

…able origin

ContinuityCallback lets a port call from any thread, and the decision reads
`enabled` and the application's choice, which the event thread owns. Reading them
from elsewhere was argued safe in one direction -- a decline is recoverable,
because the port retains the activity and offers it again -- and that argument
died the moment the "off" answer became a CLAIM, two rounds ago and by my hand:
a claim drops the activity, so a stale read there loses an arrival outright
rather than delaying it.

Marshalled rather than published. This framework is single threaded on the event
thread; the answer to reading its state from elsewhere is to stop, not to put
memory barriers around fields that have one owner. Claimed on the way out,
because that is then the truth: the framework has taken the activity and will
deal with it, and nothing else answers to this application's own activity type,
which the check above has already established.

Every port shipped here already marshals -- the iOS one hands over through
callSerially, the simulator's hooks are dispatched on the event thread, Android
has no continuation callback at all -- so this is the guarantee for a bridge
written elsewhere rather than a change to how ours behave. I checked all three
before changing anything.

The test that comes with it is a REGRESSION GUARD and says so: a
memory-visibility fix has nothing a test can expose, and this one passes against
the code without it. What it pins is what the marshalling must not break -- a
port calling from its own thread is told the activity was taken, and the arrival
actually arrives.

Separately, an inbound device id longer than a checkpoint can hold is refused,
where an oversized title is dropped. The two are not alike: a title is a label a
receiving device may show, while the origin id is the key every mark and every
dedup decision is made against, and admit() refuses a state without one anyway.
Carrying it is what does damage -- commit() writes it through Util.writeUTF,
which throws every time, so the arrival is parked, re-applied on every retry and
holds every relay publication behind it.

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

Copy link
Copy Markdown

💡 Codex Review

stack.clear();
stack.addAll(previous);

P1 Badge Preserve screens chosen before show failures

When the restored form's show callback calls Continuity.clear(), displays a login form, and then throws, this catch unconditionally replaces both the stack and current form with their pre-restore values. The outer lifecycle check sees shown == false and assumes restoreStack() already performed a safe rollback, so the login form is not recovered and the signed-out user is returned to the previous account's screen. Fresh evidence beyond the earlier logout rollback comments is that this catch performs its own unconditional rollback before Continuity.restore() can distinguish the application's replacement screen; only undo the display when the failed restored form is still current.


if (Display.isInitialized() && !Display.getInstance().isEdt()) {
final Map<String, Object> info = userInfo;
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
decide(info);
}
});
return true;

P2 Badge Preserve declines from background callbacks

For a conforming custom bridge that invokes this callback from a background thread after Display is initialized but before continuity is enabled—for example, after a sync-only listener installed the callback—this branch returns true before the queued decide() runs. decide() subsequently returns false because the application has not chosen whether to enable continuity, but the bridge has already been told the activity was claimed and may discard it, so a later enable() cannot recover the cold-launch continuation. Fresh evidence beyond the earlier cross-thread visibility finding is that moving the decision to the EDT introduced this mismatch between the synchronous return value and the queued decision.

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

ContinuityStateTest stops the tvOS run dead. The console's last line is the
support-probe INFO and then nothing -- no stage=ran, no suite finished, and
SUITE:FINISHED never appears -- so every test after it alphabetically is
reported as "missing actual" and the job fails on DesktopMode and
Media360Panorama, which are fine and never got to run.

Established before changing anything:

- It is this branch. The same tvOS job passed on location-button-android17 with
  all 144 tests matching.
- It is tvOS. The same test runs to completion on the iOS GL job in this very
  run, where the suite finishes normally.
- It is not today's commits. The tvOS comment on this PR reported the same two
  missing at 13:46 yesterday, so it has been there since the test was added.
- It is not a crash the console records, and it is not any of the framework's
  blocking calls: all three callSeriallyAndWait sites are on worker threads
  that this test never starts.

The one difference the log does show is that isContinuationSupported() answers
false on tvOS and true on iOS.

That is as far as evidence goes without running tvOS, so this adds phase markers
rather than guessing at a fix. The next run says which statement it stops on,
and the markers keep earning their place afterwards: this is a device
conformance test whose whole job is to report what a platform does.

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: 97f3cb3f3f

ℹ️ 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/continuity/Continuity.java
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
Found it. The phase markers pushed one commit ago say the tvOS suite stops in
setStateProvider(), which calls enable(), which mints the device id -- and it
did that through Util.getUUID(). Instantiating Util.UUID runs a static
initialiser that seeds itself from getUniqueDeviceID(), which reaches
CN.getProperty("User-Agent"), and the Apple ports answer that by standing up a
web view.

tvOS has no WebKit. The call never returns, enable() hangs on the event thread,
and the device suite stops dead on the first test that installs a state
provider -- taking every test after it alphabetically with it, which is why the
job failed on DesktopMode and Media360Panorama, two tests that are perfectly
fine and never ran.

The two consoles from the same CI run settle it: iOS logs 1056 com.apple.WebKit
lines, the first burst starting at the exact instant of that test's support-probe
line, and tvOS logs zero.

An id that identifies one installation to the devices it syncs with does not
need a browser's user agent to seed it, and must not need a working web view to
exist. Time plus two draws from a Random seeded independently of it is ample:
this runs once per install and the result is persisted. Formatted through
Integer.toHexString on each half, because Long.toHexString is in neither
vm/JavaAPI nor Ports/CLDC11 -- the same trap that made this branch red two
commits ago.

The test asserts the SHAPE of the id, which is what a test can reach here: the
JavaSE port answers getProperty("User-Agent") out of a table and never builds a
browser, so the hang itself does not reproduce in the harness. Reverting to
Util.getUUID() produces a dashed UUID and fails that assertion, which is what
makes it worth writing.

Note the wider hazard this leaves behind: Util.getUUID() hangs on tvOS for ANY
caller, not just this one. That is not mine to fix in this PR, but it is worth
knowing.

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: 3b742d14aa

ℹ️ 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/router/Navigation.java Outdated
… a decline

Three findings I had not seen, two of them from a PR issue comment rather than a
review thread -- my monitor only watched threads, which is the gap this exposed.
All three are consequences of my own recent changes.

restoreStack() rolls its screen back when show() throws, and it did that whenever
the display had changed -- which is true of two different things. show() installs
the form and only THEN runs its listeners, so a listener that finds the session
expired, calls clear(), opens a login form and then throws has already replaced
the screen with its own choice. Re-showing the pre-restore form put the
signed-out account's screen back in front of the user, which is the one thing
that callback ran to prevent. Asking whether `top` is still current separates the
two. Continuity.restore() cannot correct this from outside: by the time it runs,
the rollback has happened -- which is why the guard I added there last round was
not enough on its own.

clear() cleared this class's parked slot and left the PORT holding its own. A
Handoff that cold-launches a logged-out app reaches IOSContinuityCallbacks
before anything has installed a callback and is held there, so a clear() before
the first enable() cleared nothing that existed, and the enable() that came with
the later login drained the port into the next account. It drains now, through a
one-shot confined to that call rather than by recording a choice: clear() is a
logout, not "I do not want continuity", and an arrival that comes AFTER it is not
from before it and must still be held for the enable() that is coming. The test
pins both halves.

And the marshalling from last round answered "claimed" unconditionally while the
queued decision could decline -- because the application has not said whether it
wants continuity. A bridge acts on the synchronous answer: it lets go of an
activity the framework never kept, so the enable() following a sync-only
listener has nothing to deliver. That is exactly the loss the retention contract
exists to prevent, reintroduced by the fix for the visibility problem.

The background path now declines when the application has not chosen, and only
claims when it has. applicationHasChosen is the one flag that can be read from
that thread: it goes false to true once and never back, so a stale read answers
false -- a decline, which the port recovers from by offering again. `enabled` has
no such property, which is why the decision itself stays marshalled.

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

ℹ️ 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/continuity/Continuity.java Outdated
Two of my own fixes were in direct conflict and this is the seam between them.

Installing a callback is ALSO how a port is asked to re-offer a continuation it
declined earlier and is holding -- our iOS port does exactly that, and it is what
recovers a Handoff that cold-launched the app before anything was listening.
Making installation strictly once removed that event: a sync-only application
installs the seam through SyncedStore.addChangeListener, the arrival is declined
and held, and the enable() that came with the login skipped the install and never
asked for it again. The cold-launch continuation was lost.

The distinction that matters is not how many times setCallback is called but
WHY. "Make sure a seam exists" is what every store listener wants and must not
grow with their number -- that was the reported harm, and it is unbounded in
application code. "And hand me anything you kept" is worth a re-install and
happens at three bounded moments where the framework's answer to a held arrival
changes: enable(), disable() and clear(). refreshBridge() is the first kind, not
the second: it exists for a bridge the port has SWAPPED, and the instance guard
is exactly that test.

The SPI says so now, because a port cannot honour a contract it is not told
about: setCallback replaces the seam, may be called more than once, and a port
registering a native observer must register it once and replace the reference.

The test that asserted "exactly one callback" is rewritten. That was the wrong
invariant and only the retention path showed it: what must hold is that the count
does not grow with the number of LISTENERS, which it now checks with three of
them, and that enabling does ask for what the port held.

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: 315ccc936b

ℹ️ 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/iOSPort/src/com/codename1/impl/ios/IOSContinuityCallbacks.java Outdated
The same defect I fixed in StateCodec.fromJson, in a second parser I had not
enumerated. An NSUserActivity from an untagged or hand-written sender crosses
the native boundary as JSON and is parsed back to a Map here, and this parser
used the static defaults -- so a raw JSON true reached the listeners and the
provider as the String "true", passed validation because a String is a
representable type, and was acknowledged.

includeNulls goes with it, and that half is not in the report. fromMap() refuses
a null nested in a list -- a property list cannot carry one, and the iOS
sanitiser drops it and shifts every index after it -- but only if it can see it.
With the parser dropping nulls, the list simply arrives one element shorter and
the check I added for exactly this corruption never fires. The two settings are
one configuration and both doors need it.

Enumerated rather than patched where it was reported: there are three JSON
parses in this feature. StateCodec is the reference, this one now matches it,
and the third -- the synced store's {"keys":[...]} in IOSContinuityBridge -- is
correct with the defaults, because our own native side writes it, its elements
are strings and the loop discards anything that is not one. That one is
annotated rather than changed, so the next reader does not have to work it out
again or "fix" it.

Verified by inspection and by compiling the port. There is no unit-test harness
for the iOS port's Java -- maven/ios has no test source root and core-unittests
does not depend on it -- and parse() is private static, so nothing here can
exercise it. Building that harness is worth doing and is not this change.

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

ℹ️ 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/iOSPort/src/com/codename1/impl/ios/IOSContinuityCallbacks.java Outdated
Comment thread CodenameOne/src/com/codename1/continuity/Continuity.java Outdated
…here

Two findings that turn out to be one knot, and both undo something I got wrong.

The generation was read too late. Every hop between the activity and the
decision is a queue, and a logout already sitting on the event queue runs first,
so a generation read after those hops is the one AFTER the logout: every later
check passes and the previous account's state is restored and persisted by a
session that promised nothing from before it survives -- clear() deliberately
leaves continuity enabled, so nothing else refuses it. I bound deliver() to the
arrival two rounds ago and missed that the iOS port added a hop of its own in
front of it.

The port hands over directly now and the framework marshals, which is the right
split since the framework started marshalling: the generation is read when the
activity actually arrives, and the delegate gets the framework's real answer
instead of an unconditional true.

That left the synchronous answer. It could not be state-dependent -- `enabled`
is not monotonic, and `applicationHasChosen` only LOOKED safe: I argued a stale
false meant declining and that a decline is recoverable because the port
re-offers, and that is untrue once enable() has installed the seam, because no
later install comes. A decline then strands the arrival with a port that was
already told to let go, or loses it with one that does not retain.

So the framework claims it and keeps it. An arrival that reaches the callback
before the application has chosen is parked HERE, and enable() drains that slot
-- the retention that used to be borrowed from the port now lives where the
state does, and no argument about cross-thread visibility is load-bearing any
more.

Two tests were rewritten and I checked in each case that the harm they were
written for is still prevented, rather than that they now pass. One asserted a
decline that the new design deliberately does not make; it asserts instead that
the arrival is not lost, which is what it was always about. The other had to move
onto a background thread: called from the event thread the decision runs inline,
before the queued logout, and deliver()'s older guard covers it -- the first
version passed with the new check removed.

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: 06f9ba62a1

ℹ️ 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/continuity/Continuity.java Outdated
A port may retain the same continuation its pre-enable callback declined --
ContinuityBridge says so -- and since last commit this class parks a copy too.
So both can be holding one, and enable() re-offers the port's while draining
ours. Ours went straight to dispatch, past the (origin, sequence) check that
admission exists for, so the listeners and the provider ran twice on one
arrival: the user is asked to continue the same work, or has it restored, twice.

The comment I wrote to justify parking said the two copies dedup at admission.
They only do if they both go through it, and the code I wrote next to that
comment did not. It calls admit() now.

The test drives both holders at once -- a bridge that keeps a declined arrival
and a framework that parks the same one -- and counts restoreState calls, which
is the observable the user actually feels. It fails with two when the drain
bypasses admission.

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

ℹ️ 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 +1462 to +1465
// A one-shot rather than applicationHasChosen: clear() is a logout, not "I do not want
// continuity", and it deliberately leaves an enabled framework enabled. Recording a
// choice here would make every arrival AFTER the clear be dropped instead of held for
// the enable() that is about to come -- and an arrival after the clear is not from

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 Disable continuity across the logout gap

When an app follows the documented logout flow and calls only Continuity.clear(), this block deliberately leaves enabled true. Unlike the previously reported queued or held arrivals, a continuation that genuinely arrives after clear() returns therefore passes Callback.decide() and admit(), allowing the signed-out account's payload and routes to be restored and persisted over the login screen; a later enable() is also a no-op because the framework never became disabled. Either make the logout operation suspend arrivals or require the documented logout flow to pair clear() with disable().

Useful? React with 👍 / 👎.

Comment thread CodenameOne/src/com/codename1/continuity/StateCodec.java
Comment on lines +3224 to +3228
// Read from whatever thread the port called on. An int read from another thread
// yields a value the event thread wrote at some point and never a future one, so it
// can be stale-old but never stale-new: the worst it does is refuse an arrival that
// raced the logout exactly, which is the answer that side of the race wants.
final int arrivedIn = lifecycle;

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 Safely publish lifecycle generations to callback threads

When a conforming bridge invokes the callback from a long-lived background thread after a clear()/disable() and subsequent enable, this unsynchronized read may continue observing the old lifecycle value because the EDT write has no happens-before relationship with it. The queued decide() then compares that stale generation with the current one and silently discards a valid arrival. Fresh evidence after the earlier visibility finding is that the replacement still reads the same plain field off-EDT and explicitly permits a stale value; make the generation safely published or capture it through synchronized EDT coordination.

Useful? React with 👍 / 👎.

An origin is the key every mark and every dedup decision is made against, so a
state without one cannot take part at all -- admit() logs it and drops it.
Counting the read as SUCCESSFUL anyway is what does the damage: it clears the
unread flag and releases a checkpoint queued during the GET, and that publish
overwrites the relay's only copy of remote work nothing here could read.

Fixed on the relay's own path rather than in the codec. Requiring an origin in
requireKnownTypes was my first attempt and it was wrong: the codec also reads
states this device built, so a round trip through toMap started failing for any
AppState whose deviceId was never set -- eight wire tests said so. AppState does
not require an origin; the relay contract does. Checked in pollFinished, it also
covers every StateRelay rather than only the one shipped here.

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

ℹ️ 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/continuity/StateCodec.java Outdated
A relay answering with a valid object that carries no fields this build knows --
{"error":"temporarily unavailable"} returned with a 2xx is the shape -- came back
as null, and null means "the relay holds nothing" to the code that reads a fetch.
The publisher is released and a local checkpoint overwrites a document this
device never managed to read.

NON-EMPTY is the whole condition, and the distinction is the point. A bare {} is
a plausible way for an endpoint to say it holds nothing, alongside the 404 and
the empty body this class documents, and refusing that would leave such an
endpoint unable to publish anything ever. An object carrying fields none of which
are ours is a different thing: something is there and this build cannot read it.

Thrown from fromJson rather than fromMap, so the platform continuation path is
untouched -- an unrecognised NSUserActivity still comes back as null and is
declined, which is what stops the callback claiming it and prompting the user
over nothing.

An existing test asserted null for that document. Its stated harm is about
FABRICATING a state, and that is preserved and now asserted on the path it
applies to -- fromMap still answers null -- with the relay's reading asserted
beside it.

Also: AppStateWireTest now extends UITestBase, as the other three test classes in
this package already did. Core code logs, and Log.print() reaches
Display.getInstance() the first time it runs, so in a class with no Display the
first test that makes the framework log dies in Util.cleanup(). Which test that
is depends on the order they run in, so the class passed until an edit moved a
different one to the front. It fails in isolation without this change and without
any of mine, which is how I know it was already there rather than something I
introduced.

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: 217be7b114

ℹ️ 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/continuity/Continuity.java
…ync opt-in

routeStackChanged() returns early while a restore is being applied, and it has
to: without that the rebuild checkpoints and republishes the state it is
applying, and the two devices bounce it back and forth. But the restored form's
show callback is application code and may navigate -- a screen that redirects to
a newer one, an expired detail page sending the user to a list. Both
notifications for that navigation land inside the window and are dropped, so the
checkpoint recorded the routes that ARRIVED instead of the ones the user is on,
and a process death before the next one restored the screen the application had
redirected away from.

The reconciliation goes AFTER commit(), and the ordering is the whole of it.
commit() clears the pending flag as part of settling the arrival, so asking
before it set a flag commit then wiped and the scheduled flush found nothing
owed -- the fix looked right and did nothing, which the test caught. It is also
after the lifecycle branch, because a callback that ends the session leaves the
stack different from what was restored too, and checkpointing there writes for a
session that has just ended -- an existing test caught that one.

Separately, ios.continuity.sync=true is a DECLARATION and the build ignored it.
The hint documents itself as "set true to say so explicitly", and the signing
preflight already reads it that way -- it is how a project says it wants the
store without that check having to read bytecode. The builder used it only as a
veto, so a project that says so and whose usage the scan cannot see got neither
the entitlement nor the define, while the preflight warned about a profile for a
capability the build was never going to ask for. Both flags now, for the reason
the scan's own comment gives: an entitlement without the define is a SyncedStore
that reports itself unsupported on the device.

Only an explicit true does it. Unset still means "the bytecode decides", which is
what keeps an app that merely hands work to a nearby device from being given an
iCloud entitlement its App ID may not carry.

That half has no unit-test seam: the flag resolution lives inside build(), and
the plist tests beside it drive static helpers rather than the build flow. I
verified the placement instead -- both consumers, the entitlement block and
injectToPlist(), run later in the same method -- and the plugin's 1938 tests
still pass.

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

ℹ️ 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/continuity/Continuity.java
…y enable()

The door my own parking change opened, and the symmetry I missed when I closed
the same one for clear().

Callback.decide() parks an arrival that reaches the seam before the application
has chosen -- a synced-store listener installs that seam without enabling
continuity -- so by the time a logged-out app says "off" there can be a copy here
as well as at the port. disable()'s early return drained only the port's, and
enable() drains this slot on purpose, so the login restored a payload and routes
that arrived before the application said it wanted none. disable() documents the
opposite.

The full path below already clears it as part of ending the session; the early
return leaves before reaching that, which is the whole of the difference between
the two.

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: 1f1aaf2ca3

ℹ️ 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/continuity/Continuity.java Outdated
The previous commit put `parked = null` inside `if (!enabled)`, which is
check-a-static-then-assign-a-static and trips the forbidden PMD rule
NonThreadSafeSingleton. Both paths want it -- the full one clears the slot a few
lines further down anyway -- so it is hoisted above the split rather than
suppressed: one statement, said once, and the shape the rule objects to is gone.

Recording the process failure rather than only the fix: I ran the quality gate
and the commit in one chained command, so the push went out while the gate was
reporting a violation. The gate's exit code has to be read before the commit,
not beside it.

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: 8b44659b47

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

/// Hands an arrival to the framework, or holds it. Called on whatever thread the activity
/// arrived on; the framework marshals what it needs to.
private static boolean deliverToFramework(String activityType, String userInfoJson) {
ContinuityCallback c = callback;

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 Safely publish the installed iOS callback

When continuity is already enabled and iOS invokes nativeContinuation() on its platform thread, this unsynchronized read has no happens-before relationship with the EDT write in setCallback(), so it may still observe null. The arrival is then reported unclaimed and placed in pendingType/pendingJson, but no later callback installation is guaranteed to re-offer it, leaving a valid Handoff ignored indefinitely; publish the callback across threads or synchronously obtain it through EDT coordination.

Useful? React with 👍 / 👎.

ContinuityCallback lets a port call from any thread and says nothing about the
map outliving the call, so a port that recycles one buffer per arrival -- or
clears it once the call returns -- is within its rights. The off-EDT branch
queued only the reference and had ALREADY told that port the activity was
claimed, so the decision read whatever the bridge had put there since: a
different state, or none at all.

Read into an AppState before returning instead. fromMap() detaches it completely,
which is why no copy helper is needed here -- setPayload deep-copies, the routes
are rebuilt into a new list, and everything else a payload may hold is immutable
-- so what is queued is the framework's own object.

The claim gets honest with it, which is the part worth having: a document that
yields no state is now declined rather than claimed, because nothing was taken.

The test drives a bridge that empties and refills its map the instant the call
returns, and checks the payload that reaches the provider is the one that was
handed over. Without the fix nothing is delivered at all.

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: 285561b401

ℹ️ 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/router/Navigation.java Outdated
I gated the display half of this rollback last round and left the stack half
unconditional, which made the two disagree. A show callback that navigates
somewhere of its own and then throws -- or navigates and has a later listener
throw -- has already changed both, so erasing the stack while leaving that
screen up describes a place the user is not: back() then works on a history that
does not include what is in front of them.

Same rule as the ordinary navigations already use, and for the same reason:
whatever ran later and changed the stack meant to, and it wins.

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

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

}
}
List<String> routes = usableRoutes(state.getRoutes());
if (routes.isEmpty()) {

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 Apply filtered routes before the payload-only commit

When an inbound state contains a valid payload but every route exceeds the local writeUTF limit, usableRoutes() returns an empty list and this branch commits before state.setRoutesUnchecked(routes) runs. The provider has already applied the payload, but persist(state) repeatedly fails on the original oversized routes, leaving the arrival parked, reapplying its payload on retries, and blocking relay publications. Update the state with the filtered routes before taking this early return.

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.

1 participant