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 152 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>
💡 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. |
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>
💡 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". |
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".
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>
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>
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>
💡 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>
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>
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>
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".
One P2 from codex: `retirePrevious()` stopped and detached the previous program's runtime but left its Form current. If the replacement showed no UI, `runProgram`'s before/after Form comparison saw no change and reported "ran" as a legitimate no-UI run, leaving the old (now inert, potentially restyled by the new theme) form on screen. `retirePrevious()` now calls `DeviceRuntimeForm.showIt()` on the event thread, right after the previous program's stop() returns and before the detach. That is the same teardown a Stop performs, so a successor that shows nothing lands on the runtime's own form and the before/after comparison in `runProgram` correctly reports the no-UI case. 5275 tests pass, SpotBugs 0 on core-unittests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de38d65c40
ℹ️ 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".
| int end = i; | ||
| while (end < code.length()) { | ||
| char t = code.charAt(end); | ||
| if (t == ';' || t == '\n' || t == '\r' || t == '{') { |
There was a problem hiding this comment.
Keep scanning Java package names across newlines
When a valid Java package declaration wraps between identifiers, such as package com.\nexample;, this treats the newline as the declaration terminator and records the package as com. rather than com.example. addSourceTree() consequently stores the file under the wrong key, and requireSourcesFor() refuses the generated com/example/... class as missing its source. Only Kotlin package declarations should use the line ending here; Java declarations must be scanned through the semicolon.
Useful? React with 👍 / 👎.
One P2 from codex: `packageOf` treated a newline as a package-name
terminator for both languages. A legal Java `package com.\nexample;`
was cut to `com.` and the source ended up keyed under the wrong
path -- so `requireSourcesFor()` refused the generated
`com/example/...` classes as missing source.
Java declarations now end at `;` (or `{`) only; newlines inside a
Java package name are just formatting and stay in the scan. Kotlin
still terminates at the first line break because Kotlin's package
declaration has no semicolon. The distinction lives on the same
`java` boolean the file-name overload already computes.
Also converted the older Kotlin-shaped tests that were passing
Kotlin syntax through the default `packageOf` (whose default file
name is `.java`, so Java rules) over to `packageOfKotlin`; those
happened to pass under the old always-stop-at-newline behaviour
but they were mislabelled. New test asserts the wrapped Java case.
5275 tests pass, SpotBugs 0 on core-unittests and BCT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 127ce6075b
ℹ️ 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".
| + (names == null ? 0 : names.length) + " theme(s)"); | ||
| if (names != null && names.length > 0) { | ||
| Resources.setGlobalResources(pushed); | ||
| UIManager.getInstance().setThemeProps(pushed.getTheme(names[0])); |
There was a problem hiding this comment.
Apply pushed themes on the EDT
When a bundle contains theme.res, install() calls this method from the socket/connection worker before the later callSeriallyAndWait that runs the program. setThemeProps() ultimately calls current.refreshTheme(true), traversing and mutating the live runtime form while the EDT may be painting or handling input, which can cause race-dependent UI corruption or exceptions on each themed push. Move the global-resource, theme-property, and refresh operations into the EDT section.
Useful? React with 👍 / 👎.
One P2 from codex: `applyPushedTheme` was called from `install()` on the socket worker thread before the `callSeriallyAndWait` that runs the program. Its body reached `UIManager.refreshTheme()`, which walks the current form and updates cached styles, while the EDT could be painting or handling input on the previous program's UI -- race-dependent corruption or exceptions on every themed push. Split into `applyPushedTheme` (entry point) and `applyPushedThemeOnEdt` (the body). The entry point hops to the event thread through `callSeriallyAndWait` when it is not already there; teardown paths already on the UI (stopProgram) take the inline branch and don't re-enter the pump. The theme parse, `Resources.setGlobalResources`, `UIManager.setThemeProps` and `refreshTheme` all now run on the EDT. 5275 tests pass, SpotBugs 0 on core-unittests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3e4de8cc0
ℹ️ 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[] src = (Object[]) args[i]; | ||
| Object[] converted = classArrayFor(src); | ||
| if (converted != null) { |
There was a problem hiding this comment.
Reuse Class[] materializations for aliased arguments
When the same interpreter-owned Class<?>[] is passed to two Class[] parameters, this loop calls classArrayFor(src) independently for each argument, so host code receives two distinct arrays instead of the single aliased array Java requires. Consequently a == b is false, mutations through one parameter are not visible through the other during the call, and the two entries in classArrayPairs can copy conflicting results back onto the source in sequence. Cache the materialization by source-array identity and reuse it for every matching argument.
Useful? React with 👍 / 👎.
One P2 from codex: `Class<?>[]` argument materialisation ran `classArrayFor(src)` independently for each slot the same interpreter-owned array was passed to. Host code received two distinct arrays instead of the aliased one Java requires -- `a == b` was false, mutations through one slot were invisible through the other for the duration of the call, and both entries in `classArrayPairs` copied their results back onto the source in sequence so the later pair overwrote the earlier's changes. The `Class[]`-argument loop now scans `classArrayPairs` for an existing entry keyed by source identity and reuses that host array when it finds one. A fresh materialisation only happens the first time a src is encountered; every subsequent slot with the same reference points at the same dst, so alias identity holds through the host call and the mirror-back pass has one dst to copy back. 5275 tests pass, SpotBugs 0 on core-unittests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`AvoidBranchingStatementAsLastInLoop` fired on the cluster-lookup inner loop in `hostCall`: the `break` after the assignment IS the loop's intent (one cluster claims the slot, no further clusters compete for it), so the standing NOPMD annotation is the honest way to say so. The loop cannot flatten -- `moreSpecificElement` threads throwable into the iteration, so a stream `.filter` + `.findFirst` would either swallow it or force every arm to declare a throws that does not belong there. 5275 tests pass locally on core-unittests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8ac804a9e7
ℹ️ 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".
| # Platform.Version=17, which AGP rejects. 36 is the newest coherent one | ||
| # here. Drop this once the SDK package is fixed. | ||
| API="${CN1_ANDROID_API:-36}" | ||
| /usr/bin/sed -i '' -e "s/compileSdkVersion 37/compileSdkVersion $API/" \ |
There was a problem hiding this comment.
Make the in-place sed invocation portable
On Linux hosts using GNU sed, this aborts the Android runner before Gradle starts: GNU sed 4.9 documents the option as -i[SUFFIX], so the separate '' is parsed as an input filename; the command edits build.gradle but exits 2 with sed: can't read : No such file or directory, which is fatal under set -e. The same BSD-only form is repeated for the ABI edit below, so use a portable helper or select the GNU/BSD syntax by platform.
Useful? React with 👍 / 👎.
One P2 from codex: the two `sed -i ''` invocations in `run-device-runtime-android.sh` used the BSD form for the in-place backup suffix. GNU sed (Linux 4.9+) documents the option as `-i[SUFFIX]` and treats the separate `''` as an input filename, so the command aborted with `sed: can't read : No such file or directory` under `set -e` before Gradle started. Detect once via `sed --version` -- only GNU answers -- and pack the invocation into a `SED_INPLACE` array that expands to `sed -i` on GNU and `sed -i ''` on BSD. Both edits (compile / target SDK version and the ABI filter injection) now go through that array so the script survives on Linux and stays on macOS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02491b6df0
ℹ️ 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".
| addIfPresent(interfaces, "java/lang/Comparable"); | ||
| addIfPresent(interfaces, "java/io/Serializable"); |
There was a problem hiding this comment.
Avoid requesting an unsupported two-interface enum peer
On both device runtimes, Comparable and Serializable are present, so constructing any interpreted enum adds both interfaces here and passes them to ShimObjectFactory.createPeer(). That factory explicitly throws when hostInterfaceNames.length > 1, meaning enum class initialization fails with UnsupportedOperationException before even a basic Color.RED program can run; enum peers need a representation that supports both inherited interfaces, or must avoid requesting the unsupported combination.
Useful? React with 👍 / 👎.
One P1 from codex: constructing any interpreted enum passed both Comparable and Serializable to `ShimObjectFactory.createPeer()`, which explicitly rejects `hostInterfaceNames.length > 1` because its shim generator emits one shim per interface. Enum class init therefore failed with UnsupportedOperationException before even a basic `Color.RED` program could run. Two changes: Enum interfaces now advertise only Comparable, not Serializable. Comparable is what pushed code actually needs (Collections.sort calls `compareTo`, which `enumCall` answers) and the enum peer's single-interface shim satisfies that path. Serializable is a marker with no methods, this runtime has no serialization support, and forcing it kept enums off the shimmable single-slot. A general filter follows the interface-collection loop: any `java/io/Serializable` or `java/lang/Cloneable` that pushed classes brought in through their own supertype chain -- the shape `class Task implements Runnable, Serializable` is common -- is dropped so the peer gets built around the substantive interface alone. Cloneable is already a documented deviation (Device-Runtime.asciidoc `Object.clone() isn't supported`), and Serializable is unusable on a runtime with no serialization; a `(Serializable) x` cast in pushed code still fails, but the peer construction that never used to run now succeeds. 5275 tests pass, SpotBugs 0 on core-unittests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 503fb906a7
ℹ️ 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/io/Serializable".equals(n) || "java/lang/Cloneable".equals(n)) { | ||
| interfaces.removeElementAt(i); |
There was a problem hiding this comment.
Preserve declared marker-interface assignability
When pushed code creates an intersection lambda such as (Runnable & Serializable) () -> {}, InterpLambdaDesugar records both interfaces, but this filter deletes Serializable before constructing the peer. Since isInstanceOf() checks host interfaces through that peer, r instanceof Serializable now returns false, diverging from the JVM; fresh evidence beyond the enum report is the existing IntersectionLambda conformance case in InterpConformanceTest, which explicitly performs this check and compares its output with the JVM. The same loss causes host methods declaring a marker-interface parameter to reject the object, so marker assignability must be preserved without merely passing the unsupported two-interface combination to the current factory.
Useful? React with 👍 / 👎.
One P2 from codex refining round 116: the blanket filter of
`Serializable` and `Cloneable` from the interface list made
`instanceof Serializable` on the intersection lambda
`(Runnable & Serializable) () -> {}` -- and every `class Foo
implements Runnable, Serializable {}` -- diverge from the JVM.
The peer stopped advertising the marker (so the shim factory
stays inside its one-interface-per-shim capability), but
`isInstanceOf` was asking only the peer via `linker.isInstance`
and lost the assignability the class actually declared. The
existing `IntersectionLambda` conformance test exercises this
directly.
`isInstanceOf` now falls back to `declaresHostSupertype`, which
walks `type.collectHostSupertypes` and translates each extern to
its name, when neither the interpreted subclass check nor the
peer's `linker.isInstance` matched. That answers true for a
marker the class declared even though the peer's shim doesn't
carry it, so `r instanceof Serializable` matches the JVM. The
peer construction still filters those markers so
`hostInterfaceNames.length` stays at 1; the two concerns live in
different code paths now and each does what it should.
5275 tests pass, SpotBugs 0 on core-unittests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9566371da5
ℹ️ 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(linkLocalV4 != null) { | ||
| return linkLocalV4; | ||
| } | ||
| if(routableV6 != null) { | ||
| return routableV6; |
There was a problem hiding this comment.
Prefer routable IPv6 over link-local IPv4
When an Android device has only a link-local IPv4 address but also has a routable IPv6 address, this ordering returns the 169.254.* address first. DeviceRuntimeService.sweep() then treats it as the usable /24, fails to find a desktop reachable over IPv6, and the UI advertises the inferior address; return routableV6 before either link-local fallback.
Useful? React with 👍 / 👎.
| if (Files.exists(f)) { | ||
| InputStream in = Files.newInputStream(f); | ||
| try { | ||
| p.load(in); | ||
| } finally { |
There was a problem hiding this comment.
Lock the pairing-secret read-modify-write
When two Maven/IDE pushes pair with different devices concurrently, both processes can load the same old properties file, add only their own secret, and atomically replace the file in succession; the last replacement silently discards the other newly established pairing, causing its immediate or next push to demand pairing again. The load/update/replace transaction needs an inter-process lock or a merge performed while holding exclusive ownership.
Useful? React with 👍 / 👎.
Two P2s from codex. `AndroidImplementation.getHostOrIP()` now prefers a routable IPv6 address over the link-local IPv4 fallback. A device that only has `169.254.*` for IPv4 but a routable IPv6 was reporting the link-local address; `DeviceRuntimeService.sweep()` then walked a /24 that had no desktop, and the UI advertised the unreachable address instead of the one that would work. The ordering is now routable v4 (unchanged) -> routable v6 -> link-local v4 -> link-local v6. `DevicePush.rememberSecret()` holds an OS-level advisory lock on a sibling `.lock` file for the whole load/update/replace transaction. Two concurrent Maven / IDE pushes pairing with different devices could each read the same old properties, add their own line, and race the atomic move so the later replacement silently lost the earlier's newly-established secret. The lock serialises the transaction across processes; the write itself still goes through the tempfile-then-atomic-move pattern so the target keeps its owner-only permissions. `RandomAccessFile` on the lock path creates the file if absent without racing another process's create attempt. 5275 tests pass, SpotBugs 0 on core-unittests, BCT and Android. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 504f9d494a
ℹ️ 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".
| while (i + 2 < text.length() | ||
| && !(text.charAt(i) == '"' | ||
| && text.charAt(i + 1) == '"' | ||
| && text.charAt(i + 2) == '"')) { |
There was a problem hiding this comment.
Ignore escaped Java text-block delimiters
In a valid Java package-info.java annotation, a text block can contain a backslash followed by three quotes, where the first quote is escaped and the text block continues. This loop nevertheless treats the three quotes as the closing delimiter, exposing any later package token inside the literal; packageOf() then stores the source under a bogus key, and requireSourcesFor() rejects the bundle because real/package-info.java appears missing. Account for Java text-block escapes when locating the delimiter while retaining Kotlin raw-string behavior.
Useful? React with 👍 / 👎.
One P2 from codex: Java text blocks process backslash escapes, so
a legal `@Foo("""... \""" ...""")` on a `package-info.java` had
its first escaped quote treated as the closing delimiter of the
block. The scanner then walked past the block content and read a
later `package fake` token inside the literal, keying the source
at the wrong path and refusing `real/package-info.java` as
missing.
`stripComments` now honours Java text-block escapes for
non-Kotlin sources (`kotlinNested == false`). When a `\` appears
inside the block, the escape and the following character are
consumed as one unit: `\"` no longer triggers the closing check,
and `\<newline>` (line continuation) does not leak the real
newline out of the literal to the outer scanner. Kotlin raw
strings do not process escapes and continue to consume each
character verbatim. New Java test covers the escaped-triple-quote
case.
5275 tests pass, SpotBugs 0 on core-unittests and BCT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69a6d3de19
ℹ️ 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 (t == ';' || t == '{') { | ||
| break; |
There was a problem hiding this comment.
Track backticks while scanning Kotlin package names
When a valid Kotlin package contains a backtick-escaped {, such as package foo.a{b``, kotlinc emits foo/a{b/Test.class, but this loop treats the brace inside the escaped identifier as the end of the package declaration. The source is consequently stored as `foo/a/Test.kt`, and `requireSourcesFor()` rejects the bundle because the class expects `foo/a{b/Test.kt`; scan terminators only outside backtick-escaped segments.
Useful? React with 👍 / 👎.
| for (Class k = c; k != null; k = k.getSuperclass()) { | ||
| try { | ||
| m = k.getDeclaredMethod(name, types); | ||
| break; |
There was a problem hiding this comment.
Reject inherited private methods during host lookup
When the call-site owner is a subclass and a superclass has a private method with the requested signature, this walk selects that private declaration and the later setAccessible(true) invokes it. For example, with A.private m(), B extends A implements I, and I declaring m, javac emits invokevirtual B.m, for which the JVM raises IllegalAccessError; the Android runtime instead executes A.m. Detect an inherited private match and report the linkage/access failure rather than bypassing it; the JavaSE linker contains the same lookup pattern.
Useful? React with 👍 / 👎.
Two P2s from codex.
`packageOf` now tracks Kotlin backtick pairs while scanning the
package name. A legal `package foo.`a{b`.Test` contains `{` inside
a backtick-escaped segment; kotlinc keeps that character as part
of the name (`foo/a{b/Test.class`), but the scanner was treating
the brace as the class-body opener and cut the package at `foo`.
The terminator check (`;` / `{` / newline for Kotlin) now fires
only outside a backtick pair. New test covers `package foo.`a{b``.
Host-side method lookup rejects an inherited private method
match. `class A { private m() {} } class B extends A implements I`
where `I` declares `m` resolves to `A.m` on the JVM and raises
IllegalAccessError -- private methods are not inherited. Both
linkers (Android, JavaSE) and the test-only ReflectionInterpLinker
were walking the superclass chain, hitting the private declaration,
calling `setAccessible(true)` and running it. They now throw
IllegalAccessError when the match is on a strictly-superclass and
its modifier is private, matching the JVM. New test covers a
subclass call to a superclass's private method.
5276 tests pass, SpotBugs 0 on core-unittests and BCT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-fixes # Conflicts: # .gitignore
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 618da30658
ℹ️ 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".
| for (Class k = c; k != null; k = k.getSuperclass()) { | ||
| try { | ||
| f = k.getDeclaredField(name); | ||
| break; |
There was a problem hiding this comment.
Resolve interface fields before superclass fields
When a host B extends A implements I, where A has a private static field X and I exposes a public static field X, valid Java source referencing B.X produces a field reference owned by B, and JVM field resolution selects I.X. This loop instead walks through A first, finds its private field, and the later setAccessible(true) returns the wrong value; the mirrored JavaSE linker has the same ordering. Search each class's declared field and superinterfaces before recursing into its superclass, matching JVM field-resolution order.
Useful? React with 👍 / 👎.
Codex flagged that field resolution walked the whole superclass chain before recursing into interfaces, so `B extends A implements I` where `A` has a private static X and `I` a public static X returned A.X via setAccessible -- a value valid Java never observes. JVMS 5.4.3.2 checks declared, superinterfaces, superclass at each level; the linker now does the same on all three implementations and a test covers the ordering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0499814f2a
ℹ️ 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".
| // would otherwise let this run the private method the JVM refuses. | ||
| for (Class k = c; k != null; k = k.getSuperclass()) { | ||
| try { | ||
| Method candidate = k.getDeclaredMethod(name, types); |
There was a problem hiding this comment.
Include the return type in host method resolution
When pushed code is compiled against a different framework revision where a host method's return type changed but its name and parameters did not, this lookup accepts the installed method because getDeclaredMethod() ignores return types. For example, bytecode expecting String value() can bind to an installed Object value(); the interpreter then treats the returned object according to the newer descriptor and fails later or executes a method the JVM would reject with NoSuchMethodError. Compare the candidate's return type with the descriptor before caching it; the mirrored JavaSE reflection linker has the same lookup behavior.
Useful? React with 👍 / 👎.
Codex noticed getDeclaredMethod ignores the return type, so pushed code compiled against `String value()` used to bind to an installed `Object value()` when the host revision broadened the return, and the interpreter would then treat the returned object as if the descriptor still held. JVMS 5.4.3.3 requires all three parts to match; each linker now resolves the descriptor's return type and skips a candidate whose declared return differs (both on the superclass chain and in the interface pool). A test covers a broadened return. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93f32f5ae8
ℹ️ 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".
| private Field lookupField(String owner, String name) | ||
| throws ClassNotFoundException, NoSuchFieldException { | ||
| String key = owner + '#' + name; |
There was a problem hiding this comment.
Match host fields by descriptor
When pushed code was compiled against a framework revision where a host field kept its name but changed type, this cache and lookup still select the installed field because the descriptor is omitted. The interpreter then processes its value using the stale descriptor instead of raising NoSuchFieldError; on iOS the corresponding name-only symbol lookup can even treat primitive storage as an object reference when the change crosses that boundary. Include the descriptor in field resolution and cache keys; the JavaSE and iOS linkers mirror this omission.
Useful? React with 👍 / 👎.
| Method m = lookupMethod(owner, name, descriptor); | ||
| try { | ||
| return m.invoke(target, args); |
There was a problem hiding this comment.
Reject static methods at instance invocation sites
When a host method changed from instance to static between the framework used to compile the bundle and the framework installed on the device, lookupMethod() can return the static declaration here and Method.invoke(target, ...) simply executes it while ignoring target. The JVM instead raises IncompatibleClassChangeError, so stale pushed code can silently run a different API shape; validate that virtual and special targets are non-static, as invokeStatic() already validates the opposite case. The JavaSE linker has the same unchecked path.
Useful? React with 👍 / 👎.
Codex flagged two remaining unchecked bindings: - Field resolution ignored the descriptor. A rebuilt host that kept the name but changed the type could bind, and the caller would read the wrong slot -- for a primitive/reference change, a long as an object reference. Cache keys include the descriptor now, and each linker skips a candidate whose declared type differs. On iOS this meant threading the descriptor through the sidecar's field-id lookup (its interface walk too) and appending it to the key. - invokeVirtual and invokeSpecial let a static bind. Method.invoke would then ignore the receiver and run it as a class-level call, silently executing a different API shape. Each linker now throws IncompatibleClassChangeError, mirroring what invokeStatic already did for the opposite case; on iOS the sidecar's static-flag column gives it directly. Two tests cover the mismatched-type field read and the instance-to-static virtual bind. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a02af80b5
ℹ️ 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".
| interpreter is on-device-debug only, so there is nothing to weigh | ||
| here against correctness. */ | ||
| CN1_WRITE_BARRIER(target, refValue); | ||
| CN1_SATB_DELETE(slot); |
There was a problem hiding this comment.
Load the SATB old value atomically
When concurrent marking is active and interpreted code writes a compiled volatile reference field while another thread accesses it, CN1_SATB_DELETE(slot) expands in cn1_globals.h to a non-atomic *(JAVA_OBJECT volatile*) read of _Atomic(JAVA_OBJECT) storage. This races with the atomic accesses and invokes undefined behavior before the release store. Fresh evidence after the atomic load/store hardening is this remaining raw read inside the deletion barrier; use an atomic-aware SATB path that loads the old reference atomically.
Useful? React with 👍 / 👎.
Codex flagged that the SATB deletion barrier on a volatile object field's setter read the slot with `*(JAVA_OBJECT volatile*)`, which is UB when the storage is declared `_Atomic(JAVA_OBJECT)` -- it races with the atomic accesses on the same slot. Load the old reference atomically (memory_order_acquire, matching the getter) in the volatile branch and enqueue that directly, so the deletion barrier still fires ahead of the release store. The non-volatile branch keeps CN1_SATB_DELETE, since plain fields have no atomic storage to conflict with. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runner's cold Maven cache hit a 403 on maven-clean-plugin (and then maven-resources-plugin) from repo.maven.apache.org and Maven treated that as a permanent resolution failure, so the whole step died before JavaAPI compiled -- twice in a row. The Linux cross-compile job already funnels its mvn calls through retry.sh with the exact same match-list; the Windows run job was the one hold-out still on plain pwsh + mvn. Switch it to bash + retry.sh with RETRY_ONLY_MATCHING narrowed to resolution failures so the mvn test that runs the capture cannot launder a real test failure into a green retry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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