Device runtime: run pushed Codename One apps on a phone - #5561
Device runtime: run pushed Codename One apps on a phone#5561shai-almog wants to merge 173 commits into
Conversation
ddc43de to
7623680
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ddc43de0d2
ℹ️ 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".
Codename One apps run two ways today: the JavaSE simulator, which is not a device, or a cloud device build, which costs minutes per iteration. This adds a third: install one app on a phone, and from then on push a project to it from an IDE and watch it run natively in seconds. The app is not a shell around a compiled build. Pushed classes are interpreted on the device against the framework already compiled into it, so nothing is built, signed or installed between edits. How the pieces fit ------------------ com.codename1.interp is the interpreter: one interpreted frame per real frame, so Display.invokeAndBlock and every blocking idiom built on it still work. A per-thread fuel counter bounds runaway code, and the budget is per entry into the interpreter rather than per session -- measuring it per session kills every callback that arrives later than the budget, which in an application whose whole life is callbacks is every button press. Interpreted classes reach the framework through InterpLinker: invoke thunks on iOS, reflection on Android. A linker must dispatch on the receiver's class, not the call site's declared type -- list.add(x) names java.util.List, and resolving from there finds AbstractList.add, whose body throws. Extending a framework class needs an object the framework accepts, which neither platform can define at run time. Generated shims provide it: every public, non-final, constructible class and every public interface the device exposes, derived by scanning the framework jar and codenameone-java-runtime rather than curated. A hand-maintained list is a promise that applications only subclass what somebody anticipated, and its failure mode is not an error but an override that is silently never called. Lambdas and method references are rewritten into real classes when the bundle is written, since neither target has a runtime invokedynamic. Enums are answered by the interpreter, java.lang.Enum having no shim and needing none. Store compliance is built in rather than bolted on: the runtime refuses to load a bundle whose sources it cannot show, and shows them. Verified -------- 4798 core tests, 506 translator tests, 43 interpreter tests, SpotBugs at zero, and a 20-program device battery (scripts/devruntime-probes) passing on both an Android emulator and the iOS simulator -- including a four-file, three-package application entered through Lifecycle rather than main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Listing text in fastlane's layout so supply and deliver can consume it, a privacy statement for both stores' data forms, and a scheduled workflow. The weekly job uploads to Play internal testing and TestFlight. It does not promote to production and does not submit for App Store review, which is a decision rather than an omission: a weekly automatic release would put unread builds in front of the public and queue an iOS review every week whether or not anything changed. Promotion stays one command, taken deliberately. Without publishing credentials the job reports which secrets are missing and stops, rather than publishing half a release. None of them exist yet. The review risk is written down rather than discovered later. This app runs code it did not ship with, which is squarely Guideline 2.5.2 -- permitted for tools that develop or test code, and only while the source stays viewable and editable on the device. That is why the runtime refuses a bundle it cannot show the source for. 4.7.2 is the sharper edge and the argument to make is that this is point to point developer tooling rather than a mini-app platform. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ate every push Three things the review asked for, and two defects found on the way. The interpreter moves from com.codename1.interp to com.codename1.impl.interp. It is an implementation detail of one app, not public API, and the impl hierarchy is what keeps it out of the javadoc. Note the package name is not only a Java name: ParparVM's dead-code pass recognises the runtime's own classes by their C-mangled prefix, so Parser.isLoadBearingForInterp moved with it. Missing that would have stubbed out InterpRuntime.run in an interp-host build, which fails by succeeding -- every pushed program "runs" instantly and executes nothing. The ~1000 generated shims leave git. They are a mechanical function of the framework jar, so the build generates them: a tools module builds the generator, exec-maven-plugin runs it into target/generated-sources/shims, and build-helper adds that as a source root. scripts/generate-interp-shims.sh keeps the three properties the build takes on faith -- every shim compiles, the load-bearing ones exist, generating twice is identical -- and now asserts them against a scratch tree instead of writing into src. Pairing no longer hands out a bearer token. v2 authorised a push with a peer id sent in the clear, so capturing one frame on a LAN meant pushing arbitrary code to somebody's phone forever. v3 derives a 256-bit secret on both ends from the typed code, the peer id and the device id -- never transmitted, 20k HMAC iterations so grinding six digits costs something -- and every connection answers a fresh challenge whose MAC covers the bundle. Authentication happens before the approval prompt, so nobody can raise dialogs on a stranger's phone until they tap Approve to stop them. What this still does not defeat is a passive observer of the pairing exchange itself, which the docs now say plainly. There are two implementations of the derivation, since ParparVM has no javax.crypto; InterpPairingSecretTest runs both and compares. Also fixed: - A class initializer that threw left the class marked initialized, so later reads returned whatever half of it had been assigned. Four states and an owning thread now, per JLS 12.4.2. - Sources were keyed by file name, so two Util.java in different packages collided and the runtime refused the program with "missing the source file Util.java" for a file it had been handed. Keyed by package now. - The iOS release job resolved ExportOptions.plist relative to the generated Xcode project, which is not where it lives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7623680 to
c6c6ada
Compare
check-copyright-headers gates every added source file, and the probes and the IDE sample are ours -- not third-party, so the exclusions file (which is for provenance, and rejects anything else) is the wrong place for them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commented
Aug 17, 2026
💡 Codex ReviewOn every Android launch this support check is false because no code installs the newly added When the normal “Push again … to replace it” workflow loads a second bundle, this assignment discards the service's reference to the previous runtime without requesting cancellation or invoking the previous For an already-paired LAN push, If any unrelated service happens to accept this port during the subnet sweep, the callback marks it as found before ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3118d715e7
ℹ️ 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".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three were real: a SecureRandom built per call (worse and slower than one seeded once, and what it generates is the pairing code), two Files.createDirectories calls on a getParent() that SpotBugs cannot prove non-null, and an ExecutorService.submit whose Future was never going to be read -- execute() says what the scan actually wants. The other two are recorded in spotbugs-exclude.xml with their reasons: a command-line tool exits, and a failure while enumerating this machine's interfaces must be answered with 'no device found' rather than by killing the push. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commented
Aug 17, 2026
💡 Codex ReviewWhen a second bundle is pushed, this assignment only drops the service's reference to the previous runtime; it neither requests cancellation nor invokes any lifecycle cleanup. Peers, background threads, timers, and framework listeners retain references to the old runtime, so the supposedly replaced application can continue executing and mutate the UI or shared resources while the new application runs. Add a runtime deactivation/cleanup path and call it before publishing the replacement. CodenameOne/CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Lines 1347 to 1350 in 7e92c49 For a peerless interpreted object, calls inherited from When compiled output contains Kotlin classes, even explicitly passing a Kotlin source directory to Entry-point discovery recognizes only classes whose immediate superclass is ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4d7ad9bda
ℹ️ 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".
commented
Aug 17, 2026
Cloudflare Preview
|
Seven findings, all real. The interpreter: - A class literal for a pushed type puts an InterpClass on the stack, because there is no host class object to hand back -- and then the bytecode goes on calling java.lang.Class methods on it, which no linker can serve. The part of Class that means anything here (naming, identity, isInterface, isInstance, getSuperclass) is answered by the interpreter; anything else is refused by name rather than answered wrongly. - `new Entry[1][]` names its component `[LEntry;`, not `Entry`, so the bundle-membership test missed it and asked the host loader for a class only the bundle has. It looks through the brackets now, and multianewarray builds the nested Object[] itself rather than delegating. - JLS 12.4.1: initializing a class initializes the superinterfaces that declare a default method. Only those -- initializing all of them would run initializers Java never runs, which is as wrong as running them late. The push tool: - The Lifecycle entry point was chosen by direct superclass only, so a project whose app extends its own BaseApp entered BaseApp: an abstract class that was never meant to be instantiated. It walks the hierarchy now and takes the deepest concrete descendant. - A subnet scan treated any host that accepted TCP on the port as the device, and then failed the push against it while the real device sat unqueried. There is a PING frame now; only an answer in our own protocol wins. - cn1-push.sh still spoke v2, which nothing accepts any more. Its paired mode is gone rather than ported: it is a loopback helper, and pushing to a phone over Wi-Fi is DevicePush's job. A third copy of the derivation in a shell script would only drift from the two that have to agree. The release workflow now checks every secret the job will consume, not the two that name the store, so a half-configured store says so in preflight instead of half an hour later in the signing step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1356d055bd
ℹ️ 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".
CLDC11 keeps AbstractMethodError's constructors package-private, so the framework cannot throw one with a message and the Ant leg would not compile. IncompatibleClassChangeError carries the message, and a message naming the method is worth more here than the exactly right type. The three inline source blocks in the device runtime chapter move into docs/demos and are included by tag, which is what the guide validator asks of every other chapter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47fd057b7e
ℹ️ 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 interpreter's depth cap throws one, and it is the right type: ParparVM's java.lang has StackOverflowError and so does every JVM the simulator runs on. It was simply missing from this compile-time stub, so the Ant leg could not compile the framework while the Maven leg could -- the two disagree because only the Ant build puts CLDC11 on the bootclasspath. Reproducing that locally needs the same -bootclasspath; compiling core and CLDC11 together against a full JDK resolves java.lang from the JDK and reports nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commented
Aug 17, 2026
💡 Codex ReviewCodenameOne/Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java Lines 1565 to 1567 in 3155e0b On every Android runtime build, CodenameOne/CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Lines 677 to 679 in 3155e0b When the leaf type is interpreted, allocating every reference array as a plain ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Compared 12 screenshots: 12 matched. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
The chapter was written before the rebase brought in the vale gate and had never been through it: 141 findings, all in this one file. The bulk is the guide's house style of contractions. The rest is adverbs that carried no weight, two sentences opening with 'So', and three quotations of literal text -- a runtime message, a device dialog, Google Play's policy -- where moving the period inside the quotes would misquote the source, so those carry a vale-skip naming the reason. Where an adverb was load-bearing, the sentence says the thing instead: 'which silently dropped Runnable' is now 'which dropped Runnable with no diagnostic'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a54e8f350
ℹ️ 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 rebase reconstruction rewrote the file with LF where master has CRLF, so a 63-line change showed up as a 14,000-line rewrite. That is not only noise: CodeQL reports alerts for code a PR changed, and a whole-file diff re-reported twelve alerts that master already has and this branch did not introduce. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f86bfb26ac
ℹ️ 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 chapter used British spellings in a US-English guide -- which the cross-document coherency rule catches, not just the dictionary -- and a vocabulary LanguageTool has never heard of. Spellings are now US; the vocabulary (vtable, clazz, dex, desugar, devirtualize, supertype, cmake, thebaselab) is in the accept list with a line saying what each one is. Two sentences were rephrased rather than allowlisted: LanguageTool reads 'An interpreted X has to be an object...' as a typo for 'and' once the code spans are stripped, and the rule is right that the sentence was hard to parse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79950c65f5
ℹ️ 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".
Codex flagged two P2s: - LDC of a string constant pushed the bundle's own String reference, which is only deduplicated within the bundle and never enters the VM's intern pool. `ldc` semantics require the canonical interned instance, so `"hi" == "hi"` came out false when one side was a pushed literal and the other host-produced -- the failure that identity-sensitive code (switch on intern, String.equals fast path built on ==) reads as spurious. Intern before pushing. - iOS methodId's class-chain walk returned an inherited private method's id without checking the private flag the sidecar records, so `B extends A implements I` with `A.private m()` and `I.m()` dispatched to `A.m` instead of the JVM's IllegalAccessError route. Track the private flag as its own set (privateMethods), skip an inherited match found on a superclass, and reject the directly-declared case at invokeVirtual too (invokeSpecial keeps private access, since super/init/nestmate are its whole job). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 000c53aa75
ℹ️ 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".
| codename1.arg.ios.newStorageLocation=true | ||
| codename1.arg.ios.NSHealthShareUsageDescription=Used by the CI smoke test to verify the com.codename1.health native bridge compiles. The app never reads real health data. | ||
| codename1.arg.ios.NSHealthUpdateUsageDescription=Used by the CI smoke test to verify the com.codename1.health write path compiles. The app never writes real health data. | ||
| codename1.arg.ios.uiscene=true |
There was a problem hiding this comment.
Enable interpHost in the release app settings
When the signed IPA is produced through the documented ios-device-release cloud path, these project settings never pass ios.interpHost=true, so the translator omits the invoke thunks and symbol table and DeviceRuntimeService.isSupported() returns false. A repo-wide search found the flag only in run-device-runtime-ios.sh, meaning the local simulator build works while the TestFlight/release build cannot run any pushed code; persist codename1.arg.ios.interpHost=true with the other iOS settings.
Useful? React with 👍 / 👎.
Codex noticed that a signed IPA produced through the cloud `ios-device-release` path would have `DeviceRuntimeService.isSupported()` answer false, because `codenameone_settings.properties` never carried `codename1.arg.ios.interpHost=true`. Only `run-device-runtime-ios.sh` did, so the simulator worked and the release build was inert: the translator omitted the invoke thunks and the sidecar, and there is no runtime knob that puts them back. Persist the flag with the rest of the iOS args so the customer cloud path (per store/README.md) produces a build that can actually run pushed code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ee457311a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (f == null) { | ||
| throw new NoSuchFieldException(key); | ||
| } | ||
| f.setAccessible(true); |
There was a problem hiding this comment.
Reject host fields that became private
When pushed code was compiled while a host field was accessible but the installed framework changed the same field to private without changing its name or descriptor, this unconditional setAccessible(true) lets every GET/PUT accessor continue reading or writing it. The JVM would reject that stale access with IllegalAccessError; validate field accessibility before caching it. The JavaSE reflection linker mirrors this bypass.
Useful? React with 👍 / 👎.
| Constructor ctor = ctorCache.get(key); | ||
| if (ctor == null) { | ||
| ctor = c.getDeclaredConstructor(paramTypes(descriptor)); | ||
| ctor.setAccessible(true); |
There was a problem hiding this comment.
Reject host constructors that became private
When a bundle was compiled against a public host constructor that is private in the installed framework revision, getDeclaredConstructor() still finds it and this line bypasses the new visibility, so new Host(...) succeeds where JVM constructor resolution would raise IllegalAccessError. Check constructor accessibility before making it invocable; the JavaSE linker contains the same path.
Useful? React with 👍 / 👎.
Codex spotted the same "installed framework made it private" gap in two remaining places both linkers rely on: - lookupField cached whatever resolveField returned and called setAccessible(true) unconditionally. A host field the installed framework has since made private slipped through -- the get/set succeeded on stale bindings the JVM would refuse with IllegalAccessError. Field access has no invokespecial equivalent, so refuse a private declaration outright. - construct did the same for constructors: getDeclaredConstructor found the private ctor, setAccessible(true) made new Host(...) run on a stale binding. Same guard. Two tests cover each new refusal. All three linkers (Android, JavaSE, ReflectionInterpLinker) get the fixes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b0a38c708f
ℹ️ 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".
| // Object parameter loses the token itself: `list.add(Pushed.class)` | ||
| // would store Object.class, and reading it back would not equal the | ||
| // literal the program still holds. A host that took it as Object never | ||
| // needed a Class in the first place -- it is storing a reference. |
There was a problem hiding this comment.
Preserve class-literal text across host Object calls
When a pushed class literal is passed to a host method whose parameter is Object, such as System.out.println(Thing.class), String.valueOf(Thing.class), or StringBuilder.append(Thing.class), this conversion deliberately leaves the internal InterpClass token unchanged. The host then invokes InterpClass.toString() directly rather than the interpreter's classCall() handling; because InterpClass does not override toString(), it produces com.codename1.impl.interp.InterpClass@... instead of the JVM result class Thing. Preserve token identity for collections while giving host-side Object consumers the class-literal representation.
Useful? React with 👍 / 👎.
Codex noticed that a pushed class literal (`Pushed.class`) handed to a host method's Object parameter -- `System.out.println(...)`, `String.valueOf(...)`, `StringBuilder.append(...)` -- was passed through as the raw InterpClass token so its identity would survive storage in a `List<Class>`. The host's toString() call then hit the default Object.toString, printing `com.codename1.impl.interp.InterpClass@1a2b3c` instead of `class Foo`. The interpreter's classCall handler already returns the JVM-shaped literal when interpreted code invokes toString on the token; add the same shape as an override on InterpClass so a host consumer gets it too, while identity (equals / hashCode via Object) stays untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 049fb922bf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!java && (t == '\n' || t == '\r')) { | ||
| break; |
There was a problem hiding this comment.
Keep scanning continued Kotlin package directives
When a valid Kotlin package name wraps after a dot, such as package com.\nexample, Kotlin treats the newline as whitespace because the preceding . cannot terminate the directive. This branch nevertheless stops at the newline and returns com., so addSourceTree() stores the file under the wrong key and requireSourcesFor() rejects the emitted com/example/... class as missing its source. End the Kotlin scan only at a newline that can actually terminate the qualified identifier.
Useful? React with 👍 / 👎.
Codex noticed the Kotlin package scanner stopped at the first newline even when the qualified identifier could not terminate there. Kotlin treats a newline that follows a `.` as whitespace -- `package com.\n example` is one name -- so the scanner returned `com.`, the source was keyed at the wrong path, and the emitted `com/example/...` classes were then refused as missing their source. Skip a newline whose nearest non-whitespace predecessor is a dot; a CR in front of the dot (Windows CRLF) is treated as line whitespace by a small helper so the check works on either line-ending style. Two tests cover LF and CRLF forms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e248c9e793
ℹ️ 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".
| w.write("class\t" + (base + rank) + "\tarray" + rank + "__" + primNames[p] | ||
| + "\t\t-1\t" + brackets + primLetter[p] + "\t\n"); |
There was a problem hiding this comment.
Register primitive-array clazz objects
On iOS, when pushed code evaluates a primitive-array literal such as int[].class, this new sidecar row lets findClass("[I") resolve an ID, but InterpIOSLinker.classObject() then calls classObjectById(), whose native registry has no entry for any primitive-array clazz; the generated registration loop only covers reference arrays. The literal is therefore pushed as null, causing incorrect comparisons or an NPE on the first Class method call. Fresh evidence after the earlier sidecar-row report is that the rows now exist while their corresponding class_arrayN__JAVA_* objects remain unregistered.
Useful? React with 👍 / 👎.
| String id = hex(randomBytes(16)); | ||
| createParent(f); | ||
| Files.write(f, id.getBytes(StandardCharsets.UTF_8)); |
There was a problem hiding this comment.
Serialize first-time peer ID creation
When two Maven or IDE pushes start concurrently before ~/.codenameone/devruntime-peer exists, both processes pass the existence check, generate different IDs, overwrite the file, and return their own ID. Pairing secrets are derived from that ID, so at least one process can immediately authenticate with a secret belonging to the other persisted identity, or a later push can no longer use a pairing established by the losing process. Create the peer ID under an inter-process lock or with an atomic create-and-read-winner operation.
Useful? React with 👍 / 👎.
Codex flagged two P2s, both about earlier rounds landing rows / files without wiring their consumers up: - Round 125 added sidecar rows for primitive-array descriptors (`[I`, `[Z`, ..., `[[[D`) so `int[].class` resolves to a class id on iOS, but the id-to-clazz registry has no entry for any primitive-array class object -- `appendInterpArrayClassRegistrations` only emits registrations for reference arrays it can name per generated class. The literal was therefore pushed as null and the first Class method call NPE'd. Register all 24 primitive-array class objects (`class_arrayN__JAVA_TYPE`) from a single `__attribute__((constructor))` in cn1_globals.m, gated on CN1_ON_DEVICE_DEBUG the same way the debugger's class registry compiles. - DevicePush.peerId did check-then-write on ~/.codenameone/ devruntime-peer, so two Maven or IDE pushes starting concurrently before the file exists would both pass the check, generate different ids, race the write, and return ids that no longer identify each other -- pairing secrets derived from them would authenticate with a peer id the other side had already forgotten. Use CREATE_NEW (atomic create-if-absent on POSIX and Windows) and, on FileAlreadyExistsException, read what the winner wrote. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-fixes # Conflicts: # .gitignore # CLAUDE.md # scripts/hellocodenameone/common/codenameone_settings.properties
Master added a compile-time check that every codename1.arg.<name> line either has an annotation under CodenameOne/src/com/codename1/annotations/ buildhints or an entry in maven/build-hint-catalog. ios.interpHost is already read by the ParparVM translator (BytecodeMethod#isInterpHost gates the invoke thunks and the symbol sidecar the device runtime loads at run time) and the cn1-device-runtime settings pass it, so declare it in BuildHintsIos with the default the translator applies when the hint is absent (false). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5da4f328d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (explicitDevice != null) { | ||
| Socket direct = new Socket(); | ||
| direct.connect(new InetSocketAddress(explicitDevice, port), 4000); |
There was a problem hiding this comment.
Keep the manual iOS path in the dial-out direction
When --device is used with a physical iOS device—particularly on IPv6, client-isolated Wi-Fi, or another network where subnet discovery fails—this branch always initiates a desktop-to-device connection. However, IOSImplementation.listenSocket() deliberately returns null, and DeviceRuntimeService.startListener() consequently does not expose this port on iOS, so the advertised manual fallback always ends in connection refused. Provide a device-side way to enter the desktop address and let the device dial out, or explicitly restrict this option to platforms that can listen.
Useful? React with 👍 / 👎.
Master's compile-time build-hint gate (d21a389) refuses a hint that lives in both an @Android/@iOS annotation and codenameone_settings. properties. HelloCodenameOne.kt already carries useAndroidX=Toggle.ON and applicationQueriesSchemes=["cydia"] as annotations, so the two lines the device-runtime "shim every native" block added for them (useAndroidX, applicationQueriesSchemes) were caught as duplicates and every hellocodenameone-common build failed at cn1-process-classes. Nine CI jobs went red on that step alone: Android x3, Car Android Auto, cross-build suite, screenshot-capture x64, build suite classes x64, javascript-screenshots, and the downstream cross-compiled screenshot-comment. Drop the duplicates; the comment above the remaining licenseKey/messagingService pair now says the annotation is the source of truth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex flagged that --device <address> always initiates a desktop-to-device connection, but iOS deliberately answers null from IOSImplementation.listenSocket -- so an iPhone the user typed in never accepts an incoming connection. The on-device UI already labels that address `(dial-out only)`, but the desktop's error read as a generic connection refusal. Wrap the connect(); on failure, say why the address can't answer on iOS and point the user at the paired flow (the runtime dials out; the desktop listens), letting the underlying refusal escape as the cause. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1864ea3c61
ℹ️ 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".
| Method m = lookupMethod(owner, name, descriptor); | ||
| if (!Modifier.isStatic(m.getModifiers())) { | ||
| throw new IncompatibleClassChangeError(owner + "." + name + " is not static"); | ||
| } |
There was a problem hiding this comment.
Reject private static methods at static call sites
When pushed code was compiled while a host static method was accessible but the installed framework has since made it private, this path validates only that the method is static and then invokes the declaration after lookupMethod() called setAccessible(true), so stale code executes a method the JVM would reject with IllegalAccessError. Fresh evidence after the earlier virtual-method fix is that the private check was added only to invokeVirtual(); invokeStatic() remains unchecked in the Android, JavaSE, and iOS linkers.
Useful? React with 👍 / 👎.
| int id = symbols.declaredMethodId(owner, "<init>", descriptor); | ||
| if (id < 0) { | ||
| throw new NoSuchMethodError(owner + ".<init>" + descriptor | ||
| + " is not present in the installed app"); | ||
| } | ||
| // A constructor thunk allocates its own receiver and returns it, | ||
| // which is why this passes no target and expects an object back. | ||
| return invokeWithId(id, descriptor, null, args, K_OBJECT); |
There was a problem hiding this comment.
Reject private constructors in the iOS linker
On iOS, when a host constructor that was accessible at bundle compile time is private in the installed app, declaredMethodId() still returns its sidecar ID and this immediately invokes its generated thunk. The sidecar already records the private bit, so check symbols.isPrivateMethod(id) here and raise IllegalAccessError rather than allowing stale pushed code to instantiate the class.
Useful? React with 👍 / 👎.
| String[] p = split(table.substring(start, end)); | ||
| if (p.length >= 5) { | ||
| String ownerName = (String)classNames.get(Integer.valueOf(p[1])); | ||
| if (ownerName != null) { | ||
| // Descriptor in the key. A rebuilt host that changed a | ||
| // field's type but kept the name would otherwise bind and | ||
| // the caller would read a primitive slot as an object | ||
| // reference (or the other way). | ||
| fieldIds.put(ownerName + "#" + p[3] + "#" + p[4], Integer.valueOf(p[2])); |
There was a problem hiding this comment.
Preserve iOS field access flags during symbol loading
When an installed iOS framework narrows a previously accessible field to private, the generated field and sfield rows include access flags as their sixth column, but this parser stores only the owner, name, descriptor, and ID. Consequently getField/setField and the static accessors have no way to reject the private field and directly access it through native offsets or generated accessors, whereas the JVM would raise IllegalAccessError; retain the flags and enforce them during field resolution.
Useful? React with 👍 / 👎.
Codex flagged three "now-private" gaps left over from earlier rounds: - invokeStatic accepted a private static method across all three linkers (Android, JavaSE, ReflectionInterpLinker) and on iOS. The invokeVirtual fix covered only the instance path; a host static the installed framework has since made private slipped through the same setAccessible bypass. Reject it with the same IllegalAccessError, and mirror it on iOS by consulting the sidecar's isPrivateMethod flag. - iOS constructors had no private check at all. If a host constructor accessible at bundle-compile is private now, the sidecar's declaredMethodId still returned the thunk and it ran. Same guard as the method paths. - The iOS sidecar's field/sfield rows carry a JDWP access-flags column (the sixth), but the parser only kept owner/name/desc/id. A now-private host field could therefore be read or written through the generated native accessor with no gate. Store the private bit in a new privateFields set and consult isPrivateField in getStatic/setStatic/getField/setField -- raising IllegalAccessError before the native call. A test covers the new invokeStatic path; the constructor and field cases don't fit the ProxyInterpObjectFactory harness (Proxy can only proxy interfaces, so a host class with a private constructor never gets a peer through the test factory) but the guards follow the same shape as the invokeVirtual rejection tests already do. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 590ba68dd8
ℹ️ 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".
| * documented. | ||
| */ | ||
| public void addSource(String fileName, String text) { | ||
| sources.put(fileName, text); |
There was a problem hiding this comment.
Reject colliding source keys across roots
When two configured source roots contain files with the same package and filename, this unconditional put silently replaces the first source. This is valid for files containing differently named package-private classes (for example, two p/Common.java files producing p/A.class and p/B.class), and requireSourcesFor() subsequently accepts both classes because their shared key exists, even though only one class's source is actually viewable. Reject conflicting contents or preserve each class-to-source association instead of letting the last root win.
Useful? React with 👍 / 👎.
Codex noticed that addSource silently replaced a prior entry keyed by the same package+filename, so two source roots each containing a `p/Common.java` -- one declaring package-private class A, the other declaring package-private class B -- would leave requireSourcesFor happy with only one of the two texts in the bundle. The class whose source got dropped ends up with a SourceFile attribute naming a file whose contents are the other class's, which is exactly the guarantee guideline 2.5.2 rests on. Refuse the second put when the contents differ and let the writer die loudly; a bundle where one class's source is really the other's cannot ship. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f72547ee7
ℹ️ 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".
| w.addSourceTree(sourceRoot); | ||
| // Everything that is not source: theme.res, CSS, images. Keyed by | ||
| // path relative to the tree, which is how an application loads them. | ||
| w.addResourceTree(sourceRoot); |
There was a problem hiding this comment.
Include the conventional Maven resources directory
When this helper is invoked exactly as documented (scripts/cn1-push.sh src/main/java), sourceRoot is the Java source directory, so this call scans only src/main/java for resources. Files in the conventional and documented src/main/resources sibling—including theme.res, images, and application data—are therefore omitted from the bundle, causing pushed applications to use the runtime's theme or receive null when loading their resources. The helper should also detect and add the sibling resources directory.
Useful? React with 👍 / 👎.
Codex noticed cn1-push.sh, invoked as documented (`scripts/cn1-push.sh src/main/java`), scanned only the Java root for resources -- so `src/main/resources/theme.res`, images and JSON blobs never entered the bundle and the pushed program wore the runtime's theme. Same story for a Kotlin project's sibling `kotlin/` root, whose sources would leave the runtime refusing the whole push as missing source for the Kotlin classes. Detect both siblings on the source root's parent when the root is a directory: add `resources/` as a resource tree, and add `kotlin/` as both source and resource tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0f1859552f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (v instanceof NativeStub) { | ||
| // The cast the NativeLookup idiom always performs. The stub stands | ||
| // in for the interface it was asked for, and for NativeInterface | ||
| // above it. | ||
| InterpClass iface = ((NativeStub) v).iface; |
There was a problem hiding this comment.
Treat native stubs as Object instances
When pushed code evaluates nativeStub instanceof Object, this special-case runs before the universal java/lang/Object branch and returns false because the interpreted native interface is not an interpreted subtype of the host Object class. NativeStub is nevertheless a non-null Java reference, so ordinary instanceof Object checks produce the opposite result from every JVM; handle Object before this branch or include it explicitly here.
Useful? React with 👍 / 👎.
| out.writeInt(staticFields.size()); | ||
| for (FieldNode fn : staticFields) { | ||
| out.writeInt(intern(fn.name)); | ||
| out.writeInt(intern(fn.desc)); | ||
| out.writeInt(fn.access); |
There was a problem hiding this comment.
Preserve ConstantValue initializers for static fields
When a bundled class has a static field with a ConstantValue attribute and a consumer actually emits GETSTATIC—for example, bytecode produced by instrumentation or an incrementally compiled consumer from before the field became final—this serialization drops FieldNode.value. The reader consequently initializes the field to zero or null, and no <clinit> assignment exists for a constant-value field, so interpreted execution observes the wrong value; serialize and restore the constant alongside the field metadata.
Useful? React with 👍 / 👎.
Codex flagged two P2s: - isInstanceOf's NativeStub branch answered first and returned false for `nativeStub instanceof java.lang.Object`, because the stub's interpreted interface does not list Object as a supertype. A NativeStub is still a Java reference, so every JVM answers true. Move the Object short-circuit above the NativeStub branch (and above every other check) since it applies universally. - Static field rows never carried the ConstantValue attribute (JVMS 4.7.2), so a `static final int X = 42` read as 0 through GETSTATIC. javac normally inlines the constant, but bytecode from instrumentation or a caller compiled against an older non-final version still emits `getstatic C.X`. Preserve the constant in a small tag+value envelope on write, and restore it in the reader with descriptor-driven coercion (a boolean / byte / char / short ConstantValue is stored as an int per JVMS). Bump the bundle format to VERSION 5. Test covers int / long / String. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ef4cbf6299
ℹ️ 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".
| Files.write(f, id.getBytes(StandardCharsets.UTF_8), | ||
| StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); |
There was a problem hiding this comment.
Publish the initial peer ID atomically
When two first-time pushes start concurrently, CREATE_NEW makes file creation exclusive but does not make the subsequent write atomic: once the winner opens the new file, the loser can observe it at the initial Files.exists check or in this catch block and read an empty or partially written ID before the winner finishes. That process then derives and stores pairing state under an identity that is not the persisted 32-character ID. Fresh evidence after the prior race report is this remaining visibility window between creation and content publication; write to a temporary file and atomically publish it, or hold an inter-process lock through creation, writing, and loser reads.
Useful? React with 👍 / 👎.
| // to queue behind this one. | ||
| releasePreAuth(length); | ||
| released = true; | ||
| if (!approveWhileWaiting(peerId, progress)) { |
There was a problem hiding this comment.
Serialize authenticated approval prompts
When a paired peer without an Always approval submits several valid pushes concurrently, every connection reaches this call independently after the pre-auth reservation has already been released. Unlike pairing prompts, approval prompts have no shared claim, so their callSeriallyAndWait tasks can open nested modal dialogs on the EDT (up to the accepted-connection cap), obscuring which bundle is being approved and making the runtime UI unusable until every prompt is dismissed. Reserve a single approval slot or coalesce concurrent approvals per peer before opening the dialog.
Useful? React with 👍 / 👎.
Codex flagged two P2s and CI caught one PMD violation from round 140: - CREATE_NEW made the peer-id file's creation exclusive but not its write: a losing process could see the newly-created 0-byte file at Files.exists / Files.readAllBytes before the winner finished writing and derive pairing state under an empty id. Publish via temp-file + ATOMIC_MOVE so the file appears to a reader only when the id is already inside it, and treat an existing 0-byte file as a leftover to rewrite rather than a valid id. Fall through to a plain move on filesystems that refuse ATOMIC_MOVE. - Concurrent authenticated pushes from a peer without an "Always" approval could each open a modal Approve dialog on top of the last because the pre-auth reservation was released before the approval fired. Serialize approveWhileWaiting on approvalLock so only one prompt is on screen at a time; a peer with Always still returns from DeviceRuntimePairing.approve without prompting, so the lock costs nothing on the fast path. Also: round 140's readConstantValue declared `throws java.io.IOException` even though IOException was already imported, and PMD's UnnecessaryFullyQualifiedName rule refused the build; use the short name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
left a comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af76ff8db1
ℹ️ 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".
| Files.write(tmp, id.getBytes(StandardCharsets.UTF_8), | ||
| StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); | ||
| try { | ||
| Files.move(tmp, f, StandardCopyOption.ATOMIC_MOVE); |
There was a problem hiding this comment.
Prevent atomic moves from replacing the winning peer ID
When two first-time pushes publish concurrently on a typical Unix filesystem, ATOMIC_MOVE uses an atomic rename that replaces an existing destination even though REPLACE_EXISTING was omitted, so both moves can succeed and this FileAlreadyExistsException recovery never runs. Each process then returns its own ID while only the last ID remains on disk, making pairing established by the earlier process unusable. Fresh evidence after the prior peer-ID race fix is that the new atomic move itself does not provide create-if-absent semantics; use an inter-process lock or another publication mechanism that cannot replace the winner.
Useful? React with 👍 / 👎.
| case 2: return Long.valueOf(in.readLong()); | ||
| case 3: return Float.valueOf(in.readFloat()); | ||
| case 4: return Double.valueOf(in.readDouble()); | ||
| case 5: return b.strings[in.readInt()]; |
There was a problem hiding this comment.
Intern ConstantValue strings when restoring static fields
When bytecode actually reads a static final String through GETSTATIC—for example, an incrementally compiled consumer that was built before the field became final—this returns the ordinary readUTF() instance from the bundle table. The field has no <clinit> assignment to replace it, so comparing it by identity with the same LDC literal can incorrectly return false, whereas JVM string constants are canonical interned instances. Fresh evidence after the earlier LDC interning fix is this newly added ConstantValue path, which bypasses the .intern() used by LDC_STRING.
Useful? React with 👍 / 👎.
|
Compared 160 screenshots: 160 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
Adds a device runtime: install one app on a phone, then push a project to it
from your IDE and watch it run natively in seconds. A third way to run a
Codename One app, alongside the simulator and a cloud device build.
Pushed classes are interpreted on the device against the framework already
compiled into it. Nothing is built, signed or installed between edits — the
edit-run loop measured 2.8 seconds end to end.
Try it
The desktop finds the phone on the local network, shows a six-digit pairing
code you type once, and from then on it is edit-and-run.
--device <address>is there for networks that block a scan.
What is here
CodenameOne/src/com/codename1/interp/Ports/{Android,iOSPort}vm/ByteCodeTranslatorDevicePushtoolscripts/cn1-device-runtime/scripts/devruntime-ide-project/scripts/devruntime-probes/docs/developer-guide/Device-Runtime.asciidocDecisions worth reviewing
Shims are generated over the whole API, never curated. A hand-maintained
list is a promise that applications only subclass what somebody anticipated, and
its failure mode is not an error message but an override that is silently never
called. The generator fails the build rather than pruning what will not compile
— a compile-and-drop loop once silently ate
Interp_ui_Form.Native-heavy subsystems are excluded from the shim set (
ai,ar,camera,surfaces,car,health, …). A shim is a compiled reference to theclass it extends, which is exactly what the build scans to decide what to link,
so generating the full API pulled 300MB of ML Kit, ARCore and CameraX natives
into an app that calls none of them. Cost: those types cannot be subclassed by
pushed code; calling them degrades to
isSupported() == false, which is theruntime's existing contract for a cn1lib without its native half.
iOS keeps shims rather than runtime vtable synthesis. Synthesis would make
14 more types extensible on iOS only, and Android cannot follow — so the usable
capability, the intersection, does not move.
InterpHostVtableSynthesisIntegrationTeststays for the day that changes.
synchronizeduses the real object monitor, not a private lock table, whichis what makes
wait/notifywork.Framework fixes that fell out
AndroidImplementation.getHostOrIP()returneddummy0's IPv6 link-localinstead of a usable IPv4 — affects any caller.
CodenameOneImplementation.getResourceAsStreamgained a local-resource hook,so a pushed program's
theme.resis found byResources.openLayered, whichnever passes through
Display.Verification
4798 core · 506 translator · 52 interpreter · SpotBugs 0 · 20-program device
battery green on an Android emulator and the iOS simulator, including a
four-file, three-package app entered through
Lifecyclerather thanmain.Every probe exists because something plausible turned out not to work; the
README records which defect each was written for.
Review rounds
Eleven findings from codex, all real, all fixed and each answered on its thread.
The two that mattered most:
every push and never rotated, so one captured frame authorised arbitrary code
on that phone forever. v2 is gone rather than deprecated. v3 derives a 256-bit
secret on both ends from
(typed code, peerId, deviceId)— never transmitted —and every connection answers a fresh challenge whose MAC covers the bundle.
Authentication happens before the approval prompt, so nobody can raise dialogs
on a stranger's phone until they tap Approve to stop them. What it still does
not defeat is a passive observer of the pairing exchange itself, and the docs
say so.
reads returned whatever half of it had been assigned. Four states and an owning
thread now, per JLS 12.4.2.
Shipping it
.github/workflows/device-runtime-store.ymlruns Mondays and on demand,uploading to Play internal testing and TestFlight. It does not promote to
production and does not submit for review — a weekly automatic release would
put unread builds in front of the public and queue an iOS review every week
whether anything changed or not. Promotion stays one deliberate command.
Without credentials the job names the missing secrets and stops rather than
publishing half a release; none exist yet, so today it is a no-op that says so.
Listing text is in fastlane's layout (
scripts/cn1-device-runtime/fastlane/) sosupplyanddeliverconsume it directly, withstore/privacy.mdfor bothstores' data forms and
store/README.mdfor the secrets, the pre-submissionchecklist and the review-risk assessment.
The compliance point that matters: this app runs code it did not ship with,
which is Guideline 2.5.2 — permitted for tools that develop or test code, and
only while the source is "completely viewable and editable by the user". The
runtime refuses to load a bundle whose sources it lacks, and shows them under
View source. Removing that screen makes the app unsubmittable, which is why
the code says so where the screen is defined.
Not done
NativeLookupstubbing covers the Java half of a cn1lib; the native halfreports unsupported. Resource push covers
theme.res, CSS and images.Screenshots for both stores, the Play content rating questionnaire, Apple's
privacy manifest and the console listings themselves are human steps, listed in
store/README.md.🤖 Generated with Claude Code