Skip to content

Serve WebSockets from the backend, and rebuild the CI screenshot server on it - #5880

Open
shai-almog wants to merge 12 commits into
masterfrom
feature/backend-websockets
Open

shai-almog wants to merge 12 commits into
masterfrom
feature/backend-websockets

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The server-side backend couldn't serve WebSockets, and couldn't be made to from
outside: HttpServer had no upgrade path, Request exposes no descriptor,
drop(fd) owns every close, and a Response can only describe a status, a body
or a file. So this is built in the way serveHttp2 was, rather than bolted on.

The gap showed from the client side. Codename One has shipped
com.codename1.io.WebSocket on every port for years, and the only server-side
RFC 6455 code in the tree was two private, hand-rolled, test-only
implementations. An app written in Codename One end to end had a WebSocket
client and nothing of ours to point it at.

The API

An endpoint implements com.codename1.backend.WebSocket, registered per path on
HttpServer or Backend.Builder:

Backend.builder().port(8080).websocket("/echo", new Echo()).start();

Only onOpen, onText and onBinary have to be written. A PING is answered
with its PONG before the endpoint is told. Messages arrive whole, and a session
may be written to from any thread, which is what makes a broadcast possible.

What the frame layer refuses

More interesting than what it accepts, because none of these is something a
conformant client produces — which is exactly why they need a test that builds
frames by hand:

  • a length that could have been spelled shorter (one value with two spellings in
    front of a proxy is a smuggling primitive)
  • an unmasked client frame, a reserved bit with nothing negotiated, a reserved
    opcode
  • a fragmented or oversized control frame, a continuation with no message open,
    a second data frame while one is open
  • a close code a peer may not send — 1004 included, which a range check over
    1000..1011 lets through, and which is how the fuzz test caught my own bug

Text is validated as UTF-8 incrementally, so a message that goes wrong in its
first fragment fails there rather than after however much more the peer chose to
send.

Three things this had to avoid rather than participate in

  • The borrowed read buffer. HTTP parses in place inside a per-host-thread
    array, which is free for a request and wrong for a websocket, which stops
    between every message by design. The upgrade copies out whatever the client
    pipelined behind its handshake — a browser routinely puts its first frame in
    the same packet — releases the borrow, and owns its buffer from then on.
  • The request deadline. sweepDeadlines sheds anything idle past
    CN1_HTTP_TIMEOUT_MS, which is aimed at a client that began a request and
    stopped, and is indistinguishable from a websocket doing its job.
  • The drain accounting. An open session gives back the activeRequests
    count serve() raised for it. Otherwise stop() waits out its whole window
    for every silent peer, every time.

The test is the screenshot transport, used for real

scripts/lib/cn1ss.sh now starts the backend server, so every on-device UI leg —
iOS, watchOS, tvOS, macOS, Catalyst, Android, Java SE and the browser — points
four independent real client stacks at it on every run
(NSURLSessionWebSocketTask, the hand-rolled Android and Java SE clients, and
the browser's own WebSocket), sending masked, fragmented,
multi-hundred-kilobyte binary messages under ACK-paced flow control.

Replaying a full device conversation against the old server and the new one gives
identical ACK/NACK text, identical CN1SS:INFO:/CN1SS:WARN: lines and
byte-identical files.

CN1SS_WS_SERVER picks the arm — javase everywhere, native on the
JavaScript leg, which is the only leg that is Linux, already builds ParparVM, is
a single job rather than a matrix, and talks to a client that is not one of ours.
Both are proved with --selfcheck before a leg starts, so a broken server costs
seconds rather than a 40-minute timeout.

Two traps worth naming, both found by checking rather than by reasoning:

  • Not with CN1_BACKEND_HTTPS=0, tempting as it is for avoiding the TLS
    packages: build.sh deletes cn1_backend_crypto.c in that mode, and a native
    with no C symbol takes its Java method with it — so Crypto.sha1 would vanish
    and every handshake would compute the wrong accept.
  • scripts/common/java/Cn1ssScreenshotServer.java stays. Native Windows has
    no backend arm, and CleanTargetIntegrationTest and
    scripts/windows/run-hello.bat compile and run that file directly. Its
    javadoc now says so.

The UI workflows now watch vm/backend, without which a websocket regression
would break the screenshot transport on every leg while triggering none of them.

Verification

  • 155 tests in maven/backend, 40 of them new: the frame codec, the
    resumable UTF-8 validator, the handshake, and 14 that drive a live server over
    a real socket through RawWebSocketClient — a client built to be wrong on
    purpose. The one failure is the pre-existing ValuesTest float case, which
    fails identically on master.
  • The resumable validator is cross-checked against the existing whole-buffer
    Utf8.isValid over ~1.9M cases, including real text split at every offset.
  • The handshake matches RFC 6455's own worked vector and agrees with the JDK's
    SHA-1 + base64 over 500 random keys.
  • scripts/check-native-signatures.sh: the backend's 260 natives all resolve,
    including the new shutdownImpl.
  • Copyright, control-character, ASCII, actionlint, guide-structure,
    snippet-compile and Vale gates all clean (Vale: 0/0/0 across 124 files).

Not in this change

permessage-deflate and RFC 8441. Both are noted in Limits worth knowing; every
client falls back correctly without them, and RFC 8441 in particular would
rewrite much of the HTTP/2 request lifecycle that serves ordinary traffic today.

🤖 Generated with Claude Code

shai-almog and others added 2 commits September 21, 2026 16:07
…er on it

The server-side backend could not serve WebSockets, and could not be made to
from outside: HttpServer had no upgrade path, Request exposes no descriptor,
drop(fd) owns every close, and a Response can only describe a status, a body or
a file. So this is built in the way serveHttp2 was, rather than bolted on.

An endpoint implements com.codename1.backend.WebSocket and is registered per
path on HttpServer or Backend.Builder. Sessions carry the usual callbacks, PING
is answered before the endpoint is told, and a session may be written to from
any thread so a broadcast is possible.

What the frame layer refuses matters more than what it accepts, and each of
these is a case no conformant client produces: a non-minimal length (one value
with two spellings in front of a proxy is a smuggling primitive), an unmasked
client frame, a reserved bit with nothing negotiated, a fragmented or oversized
control frame, a continuation with no message open, and a close code a peer may
not send -- 1004 included, which a range check over 1000..1011 lets through.
Text is validated as UTF-8 incrementally, so a message that goes wrong in its
first fragment fails there rather than after however much more the peer sends.

Three things this had to avoid rather than participate in:

- The borrowed read buffer. HTTP parses in place inside a per-host-thread array,
  which is free for a request and wrong for a websocket, since a websocket stops
  between every message by design. The upgrade copies out whatever the client
  pipelined behind its handshake -- a browser routinely puts its first frame in
  the same packet -- releases the borrow, and owns its buffer from then on.
- The request deadline. sweepDeadlines sheds anything idle past
  CN1_HTTP_TIMEOUT_MS, which is aimed at a client that began a request and
  stopped, and is indistinguishable from a websocket doing its job. Sessions get
  CN1_WS_IDLE_TIMEOUT_MS instead.
- The drain accounting. An open session is a connection, not a request in
  flight, so it gives back the activeRequests count serve() raised for it.
  Otherwise stop() waits out its whole window for every silent peer, every time.

The test is the screenshot transport, used for real. scripts/lib/cn1ss.sh now
starts the backend server, so every on-device UI leg -- iOS, watchOS, tvOS,
macOS, Catalyst, Android, Java SE and the browser -- points four independent
real client stacks at it on every run, sending masked, fragmented,
multi-hundred-kilobyte binary messages under ACK-paced flow control. Replaying a
full device conversation against the old server and the new one gives identical
ACK/NACK text, identical CN1SS:INFO/CN1SS:WARN lines and identical files.

CN1SS_WS_SERVER picks the arm. The JavaScript leg runs the translated binary,
because it is the only leg that is Linux, already builds ParparVM, is a single
job rather than a matrix, and talks to a client that is not one of ours; every
other leg runs the same Java on a JVM. Not with CN1_BACKEND_HTTPS=0, which looks
free and deletes cn1_backend_crypto.c -- and a native with no C symbol takes its
Java method with it, so Crypto.sha1 would vanish and every handshake would
compute the wrong accept.

scripts/common/java/Cn1ssScreenshotServer.java stays. Native Windows has no
backend arm, and CleanTargetIntegrationTest and scripts/windows/run-hello.bat
compile and run that file directly.

The UI workflows now watch vm/backend, without which a websocket regression
would break the screenshot transport on every leg while triggering none of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A websocket route had to be registered by hand, while an HTTP route did not.
@WebSocketMapping closes that: the build finds the endpoint and registers it in
the generated entry point, so a websocket needs no more start-up code than a
@RestController does.

On the TYPE rather than on a method, and that is the one real design decision
here. A @GetMapping marks a call -- one request in, one response out, and the
method signature is the whole contract. A websocket is a connection, and its
contract is seven callbacks sharing per-connection state, which is an object.
An annotation on a method would mean either that the method runs per connection,
which is a factory pretending to be a handler, or that it runs once and the
annotation is on the wrong element.

Everything else is what @GetMapping already does: the path is relative to a
class-level @RequestMapping, a constructor taking a DataSource or an
EntityManager is injected through the same requireX check, and two endpoints
claiming one path is a build error rather than something scan order settles.

Two things the tests caught that reasoning did not:

- finish() took controllers.values().iterator().next() unguarded, so a module
  with only websocket endpoints threw NoSuchElementException. The entry point
  package now comes from whichever kind of class is present.
- A path with no leading slash is normalised rather than refused, because
  @GetMapping is lenient about it and the annotation set is Spring's on purpose.
  A class-level @RequestMapping with no slash is still refused: that one cannot
  be repaired without guessing.

Registration is emitted in path order, so the generated source is byte-identical
between builds rather than following the scan.

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

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

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

@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Native fidelity (Android, Material 3)

54 pairs compared -- median 95.6%, worst 91.3% (FlatButton_pressed_dark), 25th pct 94.9%, mean 95.7%.

Distribution -- >=99%: 2 | 95-99%: 37 | 90-95%: 15 | <90%: 0

Component State Appearance Material Fidelity SSIM mean delta vs base Geometry
FlatButton pressed dark normal 91.3% 0.899 3.10 0.0 ok
Tabs normal light normal 92.3% 0.914 3.05 0.0 ok
Button pressed dark normal 92.6% 0.949 4.32 0.0 ok
FlatButton normal dark normal 93.2% 0.938 2.69 0.0 ok
Button disabled dark normal 93.3% 0.928 2.20 0.0 ok
Button pressed light normal 93.3% 0.952 3.68 0.0 ok
FlatButton normal light normal 93.7% 0.941 2.29 0.0 ok
FlatButton pressed light normal 93.8% 0.942 2.56 0.0 ok
FloatingActionButton pressed light normal 94.4% 0.927 2.82 0.0 OFF (h 0.88)
RadioButton normal dark normal 94.5% 0.963 2.19 0.0 ok
RadioButton normal light normal 94.7% 0.964 1.85 0.0 ok
CheckBox selected dark normal 94.7% 0.950 2.60 0.0 ok
RadioButton selected dark normal 94.8% 0.963 2.42 0.0 ok
CheckBox normal dark normal 94.9% 0.952 2.69 0.0 ok
Button normal dark normal 94.9% 0.949 3.16 0.0 ok
CheckBox normal light normal 95.0% 0.953 2.30 0.0 ok
Toolbar normal dark normal 95.1% 0.906 1.60 0.0 ok
RaisedButton pressed dark normal 95.2% 0.950 2.39 0.0 ok
CheckBox disabled dark normal 95.2% 0.954 1.47 0.0 ok
CheckBox disabled light normal 95.2% 0.956 1.34 0.0 ok
Tabs normal dark normal 95.2% 0.913 3.65 0.0 ok
RadioButton disabled dark normal 95.3% 0.963 1.18 0.0 ok
RadioButton selected light normal 95.3% 0.964 1.84 0.0 ok
CheckBox selected light normal 95.4% 0.952 2.06 0.0 ok
Switch selected light normal 95.4% 0.966 1.59 0.0 ok
Switch selected dark normal 95.5% 0.966 1.86 0.0 ok
RadioButton disabled light normal 95.5% 0.966 1.08 0.0 ok
Switch disabled dark normal 95.6% 0.961 0.85 0.0 ok
Dialog normal light normal 95.7% 0.930 2.30 0.0 ok
RaisedButton pressed light normal 95.8% 0.958 2.16 0.0 ok
Button normal light normal 95.8% 0.953 2.46 0.0 ok
Dialog normal dark normal 95.8% 0.932 2.30 0.0 ok
Switch normal light normal 96.0% 0.961 1.45 0.0 ok
FloatingActionButton normal light normal 96.1% 0.937 1.14 0.0 ok
FloatingActionButton pressed dark normal 96.2% 0.951 2.45 0.0 ok
Switch normal dark normal 96.2% 0.962 1.39 0.0 ok
TextField disabled dark normal 96.2% 0.965 0.76 0.0 ok
RaisedButton normal dark normal 96.4% 0.952 1.78 0.0 ok
Switch disabled light normal 96.4% 0.970 0.61 0.0 ok
Button disabled light normal 96.8% 0.960 1.06 0.0 ok
ProgressBar normal dark normal 96.9% 0.967 2.03 0.0 OFF (h 1.50)
RaisedButton disabled dark normal 97.0% 0.955 0.89 0.0 ok
FloatingActionButton normal dark normal 97.1% 0.952 1.43 0.0 ok
ProgressBar normal light normal 97.3% 0.974 1.53 0.0 OFF (h 1.50)
RaisedButton disabled light normal 97.3% 0.961 0.79 0.0 ok
TextField disabled light normal 97.3% 0.965 0.79 0.0 ok
RaisedButton normal light normal 97.3% 0.961 1.40 0.0 ok
TextField normal dark normal 97.6% 0.958 1.83 0.0 ok
TextField normal light normal 97.6% 0.958 1.63 0.0 ok
Slider normal dark normal 98.4% 0.990 0.87 0.0 ok
Toolbar normal light normal 98.7% 0.974 1.28 0.0 ok
Slider normal light normal 99.0% 0.991 0.47 0.0 ok
Slider disabled dark normal 99.6% 0.993 0.22 0.0 ok
Slider disabled light normal 99.6% 0.993 0.18 0.0 ok
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Component State Appearance bbox dx,dy (px) w ratio h ratio center off (px) radius native->cn1 (px)
FloatingActionButton pressed light +0,+0 0.929 0.881 4.0 -
FloatingActionButton normal light +0,+0 0.946 0.912 2.9 -
Button pressed dark +0,+0 0.947 0.975 2.6 -
Button disabled dark +0,+0 0.947 0.975 2.6 -
Button pressed light +0,+0 0.947 0.975 2.6 -
Button normal dark +0,+0 0.947 0.975 2.6 -
RaisedButton pressed dark +0,+0 0.945 0.975 2.6 -
RaisedButton pressed light +0,+0 0.945 0.975 2.6 -
Button normal light +0,+0 0.947 0.975 2.6 -
RaisedButton normal dark +0,+0 0.945 0.975 2.6 -
Button disabled light +0,+0 0.947 0.975 2.6 -
RaisedButton disabled dark +0,+0 0.945 0.975 2.6 -
RaisedButton disabled light +0,+0 0.945 0.975 2.6 -
RaisedButton normal light +0,+0 0.945 0.975 2.6 -
RadioButton normal dark +1,+1 1.029 1.000 2.2 -
RadioButton normal light +1,+1 1.029 1.000 2.2 -
RadioButton selected dark +1,+1 1.029 1.000 2.2 -
RadioButton disabled dark +1,+1 1.029 1.000 2.2 -
RadioButton selected light +1,+1 1.029 1.000 2.2 -
Switch selected light +0,+0 1.080 1.063 2.2 -
RadioButton disabled light +1,+1 1.029 1.000 2.2 -
CheckBox selected dark +1,+1 1.013 1.000 1.8 -
CheckBox normal dark +1,+1 1.013 1.000 1.8 -
CheckBox normal light +1,+1 1.013 1.000 1.8 -
CheckBox disabled dark +1,+1 1.013 1.000 1.8 -
CheckBox disabled light +1,+1 1.013 1.000 1.8 -
CheckBox selected light +1,+1 1.013 1.000 1.8 -
Switch selected dark +0,+0 1.060 1.031 1.6 -
Switch disabled dark +0,-1 1.060 1.031 1.6 -
Dialog normal light +0,+0 1.007 0.982 1.6 -
Dialog normal dark +0,+0 1.007 0.982 1.6 -
Switch normal light +0,-1 1.060 1.031 1.6 -
Switch normal dark +0,-1 1.060 1.031 1.6 -
Switch disabled light +0,-1 1.060 1.031 1.6 -
FloatingActionButton pressed dark +0,+0 0.963 0.963 1.4 -
FloatingActionButton normal dark +0,+0 0.963 0.963 1.4 -
Toolbar normal light -1,+1 1.000 1.000 1.4 -
FlatButton pressed dark +0,+0 0.977 0.975 1.1 -
FlatButton normal dark +0,+0 0.977 0.975 1.1 -
FlatButton normal light +0,+0 0.977 0.975 1.1 -
FlatButton pressed light +0,+0 0.977 0.975 1.1 -
TextField normal dark +0,+0 1.015 1.036 1.1 -
TextField normal light +0,+0 1.015 1.036 1.1 -
Toolbar normal dark +0,+0 1.000 1.032 1.0 -
ProgressBar normal dark +0,+0 1.000 1.500 1.0 -
ProgressBar normal light +0,+0 1.000 1.500 1.0 -
TextField disabled dark +0,+0 1.015 1.018 0.7 -
TextField disabled light +0,+0 1.015 1.018 0.7 -
Tabs normal light +0,+1 1.000 0.984 0.5 -
Slider normal dark +1,+0 0.996 1.000 0.5 -
Slider normal light +1,+0 0.996 1.000 0.5 -
Tabs normal dark +0,+0 1.000 1.000 0.0 -
Slider disabled dark +0,+0 1.000 1.000 0.0 -
Slider disabled light +0,+0 1.000 1.000 0.0 -

Side-by-side comparisons (worst first)

  • FlatButton_pressed_dark -- 91.25% fidelity (SSIM 0.8985) (no change)

    native FlatButton_pressed_dark cn1 FlatButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • Tabs_normal_light -- 92.28% fidelity (SSIM 0.9140) (no change)

    native Tabs_normal_light cn1 Tabs_normal_light
    Left: native widget. Right: Codename One render.

  • Button_pressed_dark -- 92.61% fidelity (SSIM 0.9485) (no change)

    native Button_pressed_dark cn1 Button_pressed_dark
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_dark -- 93.24% fidelity (SSIM 0.9381) (no change)

    native FlatButton_normal_dark cn1 FlatButton_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_disabled_dark -- 93.26% fidelity (SSIM 0.9278) (no change)

    native Button_disabled_dark cn1 Button_disabled_dark
    Left: native widget. Right: Codename One render.

  • Button_pressed_light -- 93.34% fidelity (SSIM 0.9521) (no change)

    native Button_pressed_light cn1 Button_pressed_light
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_light -- 93.72% fidelity (SSIM 0.9410) (no change)

    native FlatButton_normal_light cn1 FlatButton_normal_light
    Left: native widget. Right: Codename One render.

  • FlatButton_pressed_light -- 93.77% fidelity (SSIM 0.9424) (no change)

    native FlatButton_pressed_light cn1 FlatButton_pressed_light
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_pressed_light -- 94.44% fidelity (SSIM 0.9273) (no change)

    native FloatingActionButton_pressed_light cn1 FloatingActionButton_pressed_light
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_dark -- 94.46% fidelity (SSIM 0.9630) (no change)

    native RadioButton_normal_dark cn1 RadioButton_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_light -- 94.69% fidelity (SSIM 0.9643) (no change)

    native RadioButton_normal_light cn1 RadioButton_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_dark -- 94.71% fidelity (SSIM 0.9502) (no change)

    native CheckBox_selected_dark cn1 CheckBox_selected_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_dark -- 94.79% fidelity (SSIM 0.9630) (no change)

    native RadioButton_selected_dark cn1 RadioButton_selected_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_dark -- 94.93% fidelity (SSIM 0.9516) (no change)

    native CheckBox_normal_dark cn1 CheckBox_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_normal_dark -- 94.94% fidelity (SSIM 0.9491) (no change)

    native Button_normal_dark cn1 Button_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_light -- 95.03% fidelity (SSIM 0.9531) (no change)

    native CheckBox_normal_light cn1 CheckBox_normal_light
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_dark -- 95.07% fidelity (SSIM 0.9059) (no change)

    native Toolbar_normal_dark cn1 Toolbar_normal_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_pressed_dark -- 95.19% fidelity (SSIM 0.9501) (no change)

    native RaisedButton_pressed_dark cn1 RaisedButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_dark -- 95.20% fidelity (SSIM 0.9538) (no change)

    native CheckBox_disabled_dark cn1 CheckBox_disabled_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_light -- 95.21% fidelity (SSIM 0.9562) (no change)

    native CheckBox_disabled_light cn1 CheckBox_disabled_light
    Left: native widget. Right: Codename One render.

  • Tabs_normal_dark -- 95.23% fidelity (SSIM 0.9125) (no change)

    native Tabs_normal_dark cn1 Tabs_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_dark -- 95.29% fidelity (SSIM 0.9630) (no change)

    native RadioButton_disabled_dark cn1 RadioButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_light -- 95.33% fidelity (SSIM 0.9642) (no change)

    native RadioButton_selected_light cn1 RadioButton_selected_light
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_light -- 95.37% fidelity (SSIM 0.9524) (no change)

    native CheckBox_selected_light cn1 CheckBox_selected_light
    Left: native widget. Right: Codename One render.

  • Switch_selected_light -- 95.37% fidelity (SSIM 0.9658) (no change)

    native Switch_selected_light cn1 Switch_selected_light
    Left: native widget. Right: Codename One render.

  • Switch_selected_dark -- 95.45% fidelity (SSIM 0.9656) (no change)

    native Switch_selected_dark cn1 Switch_selected_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_light -- 95.46% fidelity (SSIM 0.9657) (no change)

    native RadioButton_disabled_light cn1 RadioButton_disabled_light
    Left: native widget. Right: Codename One render.

  • Switch_disabled_dark -- 95.57% fidelity (SSIM 0.9613) (no change)

    native Switch_disabled_dark cn1 Switch_disabled_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_light -- 95.65% fidelity (SSIM 0.9300) (no change)

    native Dialog_normal_light cn1 Dialog_normal_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_pressed_light -- 95.78% fidelity (SSIM 0.9578) (no change)

    native RaisedButton_pressed_light cn1 RaisedButton_pressed_light
    Left: native widget. Right: Codename One render.

  • Button_normal_light -- 95.79% fidelity (SSIM 0.9528) (no change)

    native Button_normal_light cn1 Button_normal_light
    Left: native widget. Right: Codename One render.

  • Dialog_normal_dark -- 95.79% fidelity (SSIM 0.9322) (no change)

    native Dialog_normal_dark cn1 Dialog_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_normal_light -- 95.97% fidelity (SSIM 0.9612) (no change)

    native Switch_normal_light cn1 Switch_normal_light
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_normal_light -- 96.09% fidelity (SSIM 0.9367) (no change)

    native FloatingActionButton_normal_light cn1 FloatingActionButton_normal_light
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_pressed_dark -- 96.16% fidelity (SSIM 0.9513) (no change)

    native FloatingActionButton_pressed_dark cn1 FloatingActionButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • Switch_normal_dark -- 96.17% fidelity (SSIM 0.9616) (no change)

    native Switch_normal_dark cn1 Switch_normal_dark
    Left: native widget. Right: Codename One render.

  • TextField_disabled_dark -- 96.21% fidelity (SSIM 0.9648) (no change)

    native TextField_disabled_dark cn1 TextField_disabled_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_dark -- 96.41% fidelity (SSIM 0.9516) (no change)

    native RaisedButton_normal_dark cn1 RaisedButton_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_disabled_light -- 96.43% fidelity (SSIM 0.9698) (no change)

    native Switch_disabled_light cn1 Switch_disabled_light
    Left: native widget. Right: Codename One render.

  • Button_disabled_light -- 96.80% fidelity (SSIM 0.9596) (no change)

    native Button_disabled_light cn1 Button_disabled_light
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_dark -- 96.91% fidelity (SSIM 0.9669) (no change)

    native ProgressBar_normal_dark cn1 ProgressBar_normal_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_dark -- 97.02% fidelity (SSIM 0.9549) (no change)

    native RaisedButton_disabled_dark cn1 RaisedButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_normal_dark -- 97.07% fidelity (SSIM 0.9521) (no change)

    native FloatingActionButton_normal_dark cn1 FloatingActionButton_normal_dark
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_light -- 97.26% fidelity (SSIM 0.9739) (no change)

    native ProgressBar_normal_light cn1 ProgressBar_normal_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_light -- 97.26% fidelity (SSIM 0.9611) (no change)

    native RaisedButton_disabled_light cn1 RaisedButton_disabled_light
    Left: native widget. Right: Codename One render.

  • TextField_disabled_light -- 97.29% fidelity (SSIM 0.9649) (no change)

    native TextField_disabled_light cn1 TextField_disabled_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_light -- 97.32% fidelity (SSIM 0.9613) (no change)

    native RaisedButton_normal_light cn1 RaisedButton_normal_light
    Left: native widget. Right: Codename One render.

  • TextField_normal_dark -- 97.61% fidelity (SSIM 0.9583) (no change)

    native TextField_normal_dark cn1 TextField_normal_dark
    Left: native widget. Right: Codename One render.

  • TextField_normal_light -- 97.62% fidelity (SSIM 0.9582) (no change)

    native TextField_normal_light cn1 TextField_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_dark -- 98.39% fidelity (SSIM 0.9900) (no change)

    native Slider_normal_dark cn1 Slider_normal_dark
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_light -- 98.69% fidelity (SSIM 0.9739) (no change)

    native Toolbar_normal_light cn1 Toolbar_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_light -- 98.95% fidelity (SSIM 0.9908) (no change)

    native Slider_normal_light cn1 Slider_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_disabled_dark -- 99.56% fidelity (SSIM 0.9927) (no change)

    native Slider_disabled_dark cn1 Slider_disabled_dark
    Left: native widget. Right: Codename One render.

  • Slider_disabled_light -- 99.59% fidelity (SSIM 0.9932) (no change)

    native Slider_disabled_light cn1 Slider_disabled_light
    Left: native widget. Right: Codename One render.

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 172 screenshots: 172 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 46ms / native 3ms = 15.3x speedup
SIMD float-mul (64K x300) java 52ms / native 3ms = 17.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 149.000 ms
Base64 CN1 decode 102.000 ms
Base64 SIMD encode 77.000 ms
Base64 encode ratio (SIMD/CN1) 0.517x (48.3% faster)
Base64 SIMD decode 72.000 ms
Base64 decode ratio (SIMD/CN1) 0.706x (29.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 9.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.333x (66.7% faster)
Image applyMask (SIMD off) 51.000 ms
Image applyMask (SIMD on) 33.000 ms
Image applyMask ratio (SIMD on/off) 0.647x (35.3% faster)
Image modifyAlpha (SIMD off) 26.000 ms
Image modifyAlpha (SIMD on) 19.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.731x (26.9% faster)
Image modifyAlpha removeColor (SIMD off) 26.000 ms
Image modifyAlpha removeColor (SIMD on) 44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.692x (69.2% slower)

Most of RFC 6455 is about frames a conformant client never sends, so a server can
be wrong in ways nothing it talks to will reveal. ws-conformance.sh drives the
Autobahn fuzzing client at an echo endpoint and holds the result to two rules.

Every case must be strictly green. NON-STRICT is a failure here, not a pass with
a note: it means RFC-legal but lenient, and leniency in a frame parser is the
whole bug class this exists to close.

And the set of cases that RAN must equal a committed manifest. Without that, a
spec edit, an image bump or a server that dies on case 3.2 silently shrinks the
suite to the cases it happens to pass, and the job still reports green. The
manifest records COVERAGE, not permitted failures -- there is no per-case
tolerance anywhere in check-autobahn.py, and growing it is a diff somebody reads.

Result on the Java SE arm: 301 cases, all strictly green, first run.

Sections 12 and 13 are permessage-deflate and are EXCLUDED rather than tolerated
as UNIMPLEMENTED. Tolerating that verdict would also tolerate it for a case that
used to work, which is the regression this suite is for. Deleting the exclusion
when deflate lands grows the manifest by 216 entries.

The gate was checked against three mutations. Accepting any close code in
1000..1011 fails 7.9.3-7.9.5 with WRONG CODE; a manifest entry that did not run
fails; a NON-STRICT verdict fails. A fourth is worth recording because it did
NOT fail: accepting unmasked client frames passes Autobahn, because its client
always masks. That rule is covered by WebSocketServerTest instead, which is the
reason both layers exist.

Three things the harness had to learn, none of which CI would have shown:

- `--network host` means the host's network on Linux and the VM's on macOS,
  where it removes the host.containers.internal alias that is the only way in.
  The run then writes no report and exits 0.
- An installed docker binary whose daemon is down is on PATH exactly like a
  working one, and `docker run` fails with 125 -- which under set -e ends the
  script right after it says it is starting, and reads as a hang. The runtime is
  probed with `info` now.
- macOS ships bash 3.2, where an empty array expanded under `set -u` aborts the
  script. CI runs bash 5 and would never have shown it.

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

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 172 screenshots: 172 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 59ms / native 3ms = 19.6x speedup
SIMD float-mul (64K x300) java 45ms / native 3ms = 15.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 145.000 ms
Base64 CN1 decode 94.000 ms
Base64 SIMD encode 72.000 ms
Base64 encode ratio (SIMD/CN1) 0.497x (50.3% faster)
Base64 SIMD decode 78.000 ms
Base64 decode ratio (SIMD/CN1) 0.830x (17.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 5.000 ms
Image createMask ratio (SIMD on/off) 0.625x (37.5% faster)
Image applyMask (SIMD off) 57.000 ms
Image applyMask (SIMD on) 32.000 ms
Image applyMask ratio (SIMD on/off) 0.561x (43.9% faster)
Image modifyAlpha (SIMD off) 59.000 ms
Image modifyAlpha (SIMD on) 25.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.424x (57.6% faster)
Image modifyAlpha removeColor (SIMD off) 50.000 ms
Image modifyAlpha removeColor (SIMD on) 28.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.560x (44.0% faster)

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 157 screenshots: 157 matched.

Native Android coverage

  • 📊 Line coverage: 9.37% (9331/99611 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.10% (47834/525841), branch 3.60% (1793/49819), complexity 3.57% (1898/53096), method 5.52% (1542/27918), class 11.04% (413/3742)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.37% (9331/99611 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.10% (47834/525841), branch 3.60% (1793/49819), complexity 3.57% (1898/53096), method 5.52% (1542/27918), class 11.04% (413/3742)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 196ms / native 79ms = 2.4x speedup
SIMD float-mul (64K x300) java 106ms / native 88ms = 1.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 80.000 ms
Base64 CN1 decode 84.000 ms
Base64 native encode 359.000 ms
Base64 encode ratio (CN1/native) 0.223x (77.7% faster)
Base64 native decode 288.000 ms
Base64 decode ratio (CN1/native) 0.292x (70.8% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The guide's prose gates are whole-corpus and warnings-are-errors, and the
chapter was clean at 0/0/0 before this branch, so every one of these is mine.

Two I could not run locally at first and so did not catch before pushing:

- The paragraph-capitalisation check refuses a paragraph whose first prose word
  is lowercase, and "and a route points at one:" is one.
- LanguageTool flagged six: "serialises" mixes variants with the eleven
  "serialize"s already in the chapter; "backpressure" is two words to its
  dictionary; and four prose uses of "websocket" want the product's own
  capitalisation. The code spellings -- the .websocket(...) call, the include
  tags -- are left exactly as they are, because they are identifiers.

Running it locally afterwards found three more the CI report had not reached:
a comma before "so" introducing a dependent clause, and "Testsuite" and
"Podman", both product names, which are what languagetool-accept.txt is for.

The guide renders and passes every gate: vale 0/0/0 over 124 files, paragraph
capitalisation 0 issues, LanguageTool 0 matches, structure, xrefs, links,
snippets, code blocks, API names and image alt text all clean.

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

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 21, 2026

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-22T08:16:16.090490Z ed27bae Manual request
ℹ️ About Codex in GitHub

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

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

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f3a2e218a

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
Comment thread vm/backend/src/com/codename1/backend/Backend.java Outdated
Comment thread scripts/lib/cn1ss.sh
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java Outdated
Comment thread vm/backend/src/com/codename1/backend/Backend.java Outdated
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
BackendWebSocketIntegrationTest builds the real binary through build.sh and
drives it over a raw socket. maven/backend covers the same protocol on a JVM and
that is not the same test: the Java SE arm is always pool mode, reaches no
native, and -- the part that matters most here -- hands out descriptor numbers
monotonically and never recycles one, so every use-after-close bug in the upgrade
and teardown paths is structurally invisible there. Six tests, including
descriptor reuse across twelve connections and a session that parks between
messages, all green against the translated binary.

Running the Autobahn suite against a real server found two things no unit test
would have.

The deferred-close sweep blocked the reactor thread. retire() waits up to 100ms
for a writer to leave, which is right when drop() calls it from the connection's
own thread and wrong from the sweep, which runs on the thread that also accepts.
isQuiescent() is the non-blocking form, and the sweep uses it.

And a websocket pins a pool worker for the life of its connection, so workerCount
is the ceiling on concurrent connections in pool mode -- which is the mode the
Java SE arm and every TLS server run in. Past that ceiling the failure is silent
and actively misleading: the process is healthy, the listener is bound, kill -0
says it is alive, and connections are refused because no worker ever comes back
to accept them. Measured: an eight-worker server reached case 9.4.4 and refused
everything after it, and finding that from the outside took a bisect. The server
now logs once when websockets hold half the pool, the conformance harness
provisions workers for the suite it runs, and the guide says what the ceiling is.

Result: 301 cases, strictly green, on BOTH arms.

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

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 172 screenshots: 172 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 172 screenshots: 172 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 172 screenshots: 172 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD float-mul (64K x300) java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 245.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.265x (73.5% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.492x (50.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 40.000 ms
Image createMask ratio (SIMD on/off) 5.714x (471.4% slower)
Image applyMask (SIMD off) 24.000 ms
Image applyMask (SIMD on) 18.000 ms
Image applyMask ratio (SIMD on/off) 0.750x (25.0% faster)
Image modifyAlpha (SIMD off) 16.000 ms
Image modifyAlpha (SIMD on) 11.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.688x (31.3% faster)
Image modifyAlpha removeColor (SIMD off) 20.000 ms
Image modifyAlpha removeColor (SIMD on) 12.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.600x (40.0% faster)

…re is a fallback

CI caught both of these, and the second is the more important one.

build.sh runs generate-contract.sh whenever contract/ exists, which needs
codenameone-core, codenameone-backend and the CN1 Maven plugin installed in the
repo-local .m2-repo. The JavaScript leg builds only the parparvm module, so the
native screenshot server could not be built there at all: "codenameone-core is
not in .m2-repo", no binary.

CN1_BACKEND_STANDALONE_DEMO is for a demo that needs neither the contract nor
demo/common. It has to skip BOTH, and that is not a convenience -- demo/common's
GreeterService is written against the contract's Pet, so dropping the contract
while still compiling common fails at javac with seventeen missing symbols. That
is how this flag was wrong the first time I wrote it, and the A/B that caught it
needed gen/ moved aside: with a populated gen/ the contract step returns early
and the bug is invisible.

Verified from a fresh-checkout state against an empty .m2-repo: without the flag
exit 1 with the CI message, with it a 2.9MB binary that passes its own
self-check. petserver, which does use the contract and demo/common, still builds.

The second fix is the one that cost eleven minutes of CI. When the server failed
to start, three runner scripts logged "relying on base64 fallback" and CARRIED
ON. There is no base64 fallback -- Cn1ssDeviceRunnerHelper says so in as many
words -- so the suite ran, delivered nothing, and failed on a count gate long
afterwards with no mention of the transport. They exit 6 now, the way the watch,
tv and fidelity legs already did, and say why.

Both arms still pass conformance: 301 cases, strictly green.

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

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 61ms / native 3ms = 20.3x speedup
SIMD float-mul (64K x300) java 63ms / native 4ms = 15.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 234.000 ms
Base64 CN1 decode 108.000 ms
Base64 native encode 907.000 ms
Base64 encode ratio (CN1/native) 0.258x (74.2% faster)
Base64 native decode 351.000 ms
Base64 decode ratio (CN1/native) 0.308x (69.2% faster)
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.274x (72.6% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.583x (41.7% faster)
Base64 encode ratio (SIMD/native) 0.071x (92.9% faster)
Base64 decode ratio (SIMD/native) 0.179x (82.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.286x (71.4% faster)
Image applyMask (SIMD off) 67.000 ms
Image applyMask (SIMD on) 36.000 ms
Image applyMask ratio (SIMD on/off) 0.537x (46.3% faster)
Image modifyAlpha (SIMD off) 34.000 ms
Image modifyAlpha (SIMD on) 28.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.824x (17.6% faster)
Image modifyAlpha removeColor (SIMD off) 33.000 ms
Image modifyAlpha removeColor (SIMD on) 27.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.818x (18.2% faster)

@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 711 total, 0 failed, 57 skipped

Benchmark Results

  • Execution Time: 20047 ms

  • Hotspots (Top 20 sampled methods):

    • 6.46% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (102 samples)
    • 5.44% java.util.ArrayList.indexOf (86 samples)
    • 5.13% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (81 samples)
    • 5.06% java.lang.StringBuilder.append (80 samples)
    • 3.67% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (58 samples)
    • 2.66% org.objectweb.asm.tree.analysis.Analyzer.analyze (42 samples)
    • 2.66% java.lang.System.identityHashCode (42 samples)
    • 2.59% com.codename1.tools.translator.Parser.classIndex (41 samples)
    • 2.41% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (38 samples)
    • 2.34% java.lang.String.equals (37 samples)
    • 2.28% java.util.HashMap.hash (36 samples)
    • 1.90% java.lang.Object.hashCode (30 samples)
    • 1.65% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (26 samples)
    • 1.52% com.codename1.tools.translator.BytecodeMethod.equals (24 samples)
    • 1.33% java.io.FileOutputStream.writeBytes (21 samples)
    • 1.27% com.codename1.tools.translator.BytecodeMethod.appendMethodC (20 samples)
    • 1.27% java.lang.StringCoding.encode (20 samples)
    • 1.20% com.codename1.tools.translator.NativeSymbolIndex.<init> (19 samples)
    • 1.20% java.io.FileOutputStream.open0 (19 samples)
    • 1.14% com.codename1.tools.translator.BytecodeMethod.updateInlinableFieldDependencies (18 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

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

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Native fidelity (iOS Modern, Metal)

68 pairs compared -- median 95.0%, worst 83.5% (Tabs_normal_dark), 25th pct 92.5%, mean 94.6%.

Distribution -- >=99%: 3 | 95-99%: 31 | 90-95%: 29 | <90%: 5

Component State Appearance Material Fidelity SSIM mean delta vs base Geometry
Tabs normal dark glass 83.5% 0.884 11.66 0.0 ok
Tabs normal light glass 86.4% 0.896 8.01 0.0 ok
Toolbar normal light glass 87.6% 0.951 4.24 0.0 ok
Toolbar normal dark glass 87.7% 0.950 3.69 0.0 ok
Button pressed dark glass 89.9% 0.958 4.21 0.0 OFF (off 12px, w 1.10)
TextField normal light normal 90.0% 0.958 2.78 -0.4 ok
Spinner normal dark normal 90.8% 0.881 3.39 -0.7 ok
TextField disabled light normal 91.1% 0.962 2.24 -0.2 ok
Spinner normal light normal 91.6% 0.910 4.11 -0.2 ok
RaisedButton disabled light glass 91.8% 0.961 3.09 +0.2 ok
Button normal dark glass 91.8% 0.958 3.52 0.0 OFF (off 12px, w 1.10)
Slider disabled dark normal 92.1% 0.943 2.45 -0.3 ok
Switch selected light normal 92.1% 0.981 1.47 0.0 ok
CheckBox normal light normal 92.2% 0.994 0.68 0.0 ok
RadioButton normal light normal 92.2% 0.994 0.68 0.0 ok
FlatButton normal dark glass 92.2% 0.983 2.21 +4.3 ok
Button normal light glass 92.4% 0.961 3.65 0.0 OFF (off 12px, w 1.10)
Slider normal dark normal 92.5% 0.944 2.63 -0.2 ok
RaisedButton disabled dark glass 92.7% 0.965 2.72 +1.3 ok
CheckBox disabled light normal 92.7% 0.995 0.50 0.0 ok
RadioButton disabled light normal 92.7% 0.995 0.50 0.0 ok
Button pressed light glass 92.9% 0.962 3.29 0.0 OFF (off 12px, w 1.10)
FlatButton pressed dark glass 92.9% 0.985 1.26 +2.3 ok
TabsGeom normal dark normal 93.0% 0.892 8.76 0.0 ok
TabsGeom normal light normal 93.0% 0.886 7.10 0.0 ok
FlatButton pressed light glass 93.2% 0.982 1.90 +4.3 ok
Button disabled dark glass 93.5% 0.962 2.47 0.0 OFF (off 12px, w 1.10)
Slider disabled light normal 94.0% 0.963 1.75 -0.1 OFF (h 4.50)
FlatButton normal light glass 94.2% 0.983 1.79 +1.3 ok
Button disabled light glass 94.4% 0.964 2.36 0.0 OFF (off 12px, w 1.10)
RaisedButton pressed dark glass 94.6% 0.965 2.07 +1.5 ok
RaisedButton normal dark glass 94.6% 0.966 2.08 +1.7 ok
RaisedButton pressed light glass 94.8% 0.967 2.22 +2.0 ok
RadioButton selected light normal 94.8% 0.992 0.86 0.0 ok
RaisedButton normal light glass 95.0% 0.967 2.19 +2.4 ok
Slider normal light normal 95.1% 0.959 1.56 0.0 ok
Switch normal light normal 95.5% 0.986 0.70 0.0 ok
CheckBox disabled dark normal 95.8% 0.993 0.25 0.0 ok
RadioButton disabled dark normal 95.8% 0.993 0.25 0.0 ok
CheckBox selected light normal 95.8% 0.994 0.67 0.0 ok
Switch selected dark normal 95.9% 0.985 1.02 0.0 ok
Switch disabled light normal 96.4% 0.990 0.53 0.0 ok
CheckBox normal dark normal 96.5% 0.993 0.35 0.0 ok
RadioButton normal dark normal 96.5% 0.993 0.35 0.0 ok
GlassPanelPhoto normal light glass 96.7% 0.980 7.95 +0.6 ok
GlassPanelPhoto normal dark glass 96.8% 0.983 8.78 +0.6 ok
TabOne normal dark normal 96.8% 0.953 4.44 +0.7 ok
Switch normal dark normal 96.8% 0.986 0.82 0.0 ok
RadioButton selected dark normal 96.8% 0.991 0.57 0.0 ok
TextField normal dark normal 96.9% 0.949 3.30 -0.2 ok
TextField disabled dark normal 97.2% 0.953 2.37 -0.2 ok
ProgressBar normal dark normal 97.5% 0.997 0.60 +3.0 ok
TabOne normal light normal 97.5% 0.961 2.89 +1.9 ok
ProgressBar normal light normal 97.6% 0.999 0.61 +2.2 ok
CheckBox selected dark normal 97.8% 0.993 0.38 0.0 ok
Dialog normal dark normal 97.9% 0.961 1.38 +0.9 ok
Dialog normal light normal 98.0% 0.963 1.42 +0.9 ok
Switch disabled dark normal 98.0% 0.987 0.41 +2.5 ok
GlassIcon normal dark normal 98.6% 0.984 3.55 0.0 ok
GlassText normal dark normal 98.6% 0.984 3.52 0.0 ok
GlassIcon normal light normal 98.7% 0.986 3.54 0.0 ok
GlassText normal light normal 98.7% 0.986 3.58 0.0 ok
GlassPanelGrad normal light normal 98.9% 0.994 4.38 +0.7 ok
GlassPanelGrad normal dark normal 99.0% 0.993 3.84 +0.5 ok
GlassPanelGrey normal dark normal 99.0% 0.992 3.30 +0.6 ok
GlassPanelGrey normal light normal 99.1% 0.994 3.29 +0.7 ok
GlassPanelRed normal dark normal 99.1% 0.993 3.22 +0.5 ok
GlassPanelRed normal light normal 99.1% 0.994 3.49 +0.7 ok
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Component State Appearance bbox dx,dy (px) w ratio h ratio center off (px) radius native->cn1 (px)
Button pressed dark +0,+0 1.104 1.000 11.5 45.4 -> 45.3
Button normal dark +0,+0 1.104 1.000 11.5 45.8 -> 45.2
Button normal light +0,+0 1.104 1.000 11.5 44.5 -> 45.3
Button pressed light +0,+0 1.104 1.000 11.5 48.1 -> 45.4
Button disabled dark +0,+0 1.104 1.000 11.5 45.1 -> 45.2
Button disabled light +0,+0 1.104 1.000 11.5 44.0 -> 45.3
TabOne normal dark -3,+0 1.007 0.948 4.9 80.3 -> 77.3
TabOne normal light -3,+0 1.007 0.948 4.9 79.1 -> 77.0
Toolbar normal light +4,+7 0.987 0.926 3.5 62.5 -> 56.1
Toolbar normal dark +4,+7 0.987 0.919 3.2 60.5 -> 55.5
Switch selected light +0,+0 1.028 1.026 2.7 -
RaisedButton disabled dark +0,+0 1.024 1.011 2.6 49.8 -> 46.1
Slider disabled light +0,-29 1.000 4.500 2.5 -
CheckBox disabled light +1,-3 1.000 1.023 2.2 -
RadioButton disabled light +1,-3 1.000 1.023 2.2 -
CheckBox disabled dark +1,-3 1.000 1.023 2.2 -
RadioButton disabled dark +1,-3 1.000 1.023 2.2 -
RaisedButton disabled light +0,+0 1.019 1.011 2.1 54.1 -> 96.6
RaisedButton pressed dark +0,+0 1.019 1.011 2.1 44.5 -> 44.3
RaisedButton normal dark +0,+0 1.019 1.011 2.1 44.4 -> 44.3
RaisedButton pressed light +0,+0 1.019 1.011 2.1 44.7 -> 44.3
RaisedButton normal light +0,+0 1.019 1.011 2.1 44.5 -> 44.1
Switch normal light +0,+0 1.023 1.013 2.1 -
Switch disabled light +0,+0 1.023 1.013 2.1 -
Switch normal dark +0,+0 1.023 1.013 2.1 -
Switch disabled dark +0,+0 1.023 1.013 2.1 -
CheckBox normal dark +0,-2 1.000 1.000 2.0 -
RadioButton normal dark +0,-2 1.000 1.000 2.0 -
RadioButton selected dark +0,-2 1.000 1.000 2.0 -
CheckBox selected dark +0,-2 1.000 1.000 2.0 -
Tabs normal dark +9,+0 0.973 1.012 1.8 79.7 -> 80.7
Tabs normal light +9,+0 0.973 1.012 1.8 79.2 -> 80.3
TabsGeom normal dark +9,+0 0.973 1.012 1.8 80.5 -> 80.9
TabsGeom normal light +9,+0 0.973 1.012 1.8 79.3 -> 80.6
FlatButton normal dark +4,+0 0.966 1.011 1.6 91.8 -> 95.7
FlatButton pressed dark +4,+0 0.966 1.011 1.6 95.3 -> 97.9
Switch selected dark +0,+0 1.017 1.013 1.6 -
CheckBox normal light +0,-2 1.000 1.011 1.5 -
RadioButton normal light +0,-2 1.000 1.011 1.5 -
RadioButton selected light +0,-2 1.000 1.011 1.5 -
CheckBox selected light +0,-2 1.000 1.011 1.5 -
Spinner normal light +11,-9 0.978 1.055 1.1 -
GlassIcon normal dark +0,+1 0.999 1.000 1.1 92.1 -> 91.5
GlassText normal dark +0,+1 0.999 1.000 1.1 92.1 -> 91.5
Spinner normal dark +12,-9 0.977 1.055 1.0 -
GlassPanelGrey normal dark +0,+1 1.000 1.000 1.0 26.4 -> 26.9
TextField disabled light +0,+1 0.999 0.996 0.7 13.9 -> 38.7
FlatButton normal light +0,+0 0.993 1.011 0.7 91.9 -> 51.8
Slider disabled dark +0,-1 1.000 1.015 0.5 -
FlatButton pressed light +1,+0 0.986 1.011 0.5 95.4 -> 65.6
Slider normal light +0,+1 1.000 0.964 0.5 -
GlassPanelPhoto normal light +0,+0 1.000 1.005 0.5 25.6 -> 31.4
GlassPanelPhoto normal dark +0,+0 1.000 1.005 0.5 23.0 -> 27.0
ProgressBar normal dark +0,+0 1.000 0.900 0.5 -
ProgressBar normal light +0,+0 1.000 0.900 0.5 -
GlassIcon normal light +0,+0 1.000 1.005 0.5 91.6 -> 92.3
GlassText normal light +0,+0 1.000 1.005 0.5 91.6 -> 92.3
GlassPanelGrad normal light +0,+0 1.000 1.005 0.5 22.3 -> 25.9
GlassPanelGrad normal dark +1,+1 0.998 0.995 0.5 25.7 -> 22.5
GlassPanelGrey normal light +0,+0 1.000 1.005 0.5 22.4 -> 26.0
GlassPanelRed normal dark +1,+1 0.998 0.995 0.5 26.6 -> 22.8
GlassPanelRed normal light +0,+0 1.000 1.005 0.5 22.1 -> 26.1
TextField normal light +0,+0 1.000 1.000 0.0 13.4 -> 40.5
Slider normal dark +0,+0 1.000 1.000 0.0 -
TextField normal dark +0,+0 1.000 1.000 0.0 6.4 -> 15.1
TextField disabled dark +0,+0 1.000 1.000 0.0 6.4 -> 15.9
Dialog normal dark +0,+0 1.000 1.000 0.0 -
Dialog normal light +0,+0 1.000 1.000 0.0 -

Side-by-side comparisons (worst first)

  • Tabs_normal_dark -- 83.45% fidelity (SSIM 0.8842) (no change)

    native Tabs_normal_dark cn1 Tabs_normal_dark
    Left: native widget. Right: Codename One render.

  • Tabs_normal_light -- 86.44% fidelity (SSIM 0.8961) (no change)

    native Tabs_normal_light cn1 Tabs_normal_light
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_light -- 87.64% fidelity (SSIM 0.9511) (no change)

    native Toolbar_normal_light cn1 Toolbar_normal_light
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_dark -- 87.70% fidelity (SSIM 0.9495) (no change)

    native Toolbar_normal_dark cn1 Toolbar_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_pressed_dark -- 89.93% fidelity (SSIM 0.9576) (no change)

    native Button_pressed_dark cn1 Button_pressed_dark
    Left: native widget. Right: Codename One render.

  • TextField_normal_light -- 90.03% fidelity (SSIM 0.9576) (-0.37 vs baseline)

    native TextField_normal_light cn1 TextField_normal_light
    Left: native widget. Right: Codename One render.

  • Spinner_normal_dark -- 90.78% fidelity (SSIM 0.8814) (-0.69 vs baseline)

    native Spinner_normal_dark cn1 Spinner_normal_dark
    Left: native widget. Right: Codename One render.

  • TextField_disabled_light -- 91.13% fidelity (SSIM 0.9619) (-0.22 vs baseline)

    native TextField_disabled_light cn1 TextField_disabled_light
    Left: native widget. Right: Codename One render.

  • Spinner_normal_light -- 91.58% fidelity (SSIM 0.9099) (-0.21 vs baseline)

    native Spinner_normal_light cn1 Spinner_normal_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_light -- 91.76% fidelity (SSIM 0.9613) (+0.15 vs baseline)

    native RaisedButton_disabled_light cn1 RaisedButton_disabled_light
    Left: native widget. Right: Codename One render.

  • Button_normal_dark -- 91.84% fidelity (SSIM 0.9583) (no change)

    native Button_normal_dark cn1 Button_normal_dark
    Left: native widget. Right: Codename One render.

  • Slider_disabled_dark -- 92.13% fidelity (SSIM 0.9434) (-0.31 vs baseline)

    native Slider_disabled_dark cn1 Slider_disabled_dark
    Left: native widget. Right: Codename One render.

  • Switch_selected_light -- 92.13% fidelity (SSIM 0.9806) (no change)

    native Switch_selected_light cn1 Switch_selected_light
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_light -- 92.16% fidelity (SSIM 0.9938) (no change)

    native CheckBox_normal_light cn1 CheckBox_normal_light
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_light -- 92.16% fidelity (SSIM 0.9938) (no change)

    native RadioButton_normal_light cn1 RadioButton_normal_light
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_dark -- 92.21% fidelity (SSIM 0.9833) (+4.25 vs baseline)

    native FlatButton_normal_dark cn1 FlatButton_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_normal_light -- 92.37% fidelity (SSIM 0.9609) (no change)

    native Button_normal_light cn1 Button_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_dark -- 92.52% fidelity (SSIM 0.9441) (-0.22 vs baseline)

    native Slider_normal_dark cn1 Slider_normal_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_dark -- 92.65% fidelity (SSIM 0.9653) (+1.25 vs baseline)

    native RaisedButton_disabled_dark cn1 RaisedButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_light -- 92.71% fidelity (SSIM 0.9953) (no change)

    native CheckBox_disabled_light cn1 CheckBox_disabled_light
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_light -- 92.71% fidelity (SSIM 0.9953) (no change)

    native RadioButton_disabled_light cn1 RadioButton_disabled_light
    Left: native widget. Right: Codename One render.

  • Button_pressed_light -- 92.88% fidelity (SSIM 0.9617) (no change)

    native Button_pressed_light cn1 Button_pressed_light
    Left: native widget. Right: Codename One render.

  • FlatButton_pressed_dark -- 92.90% fidelity (SSIM 0.9849) (+2.31 vs baseline)

    native FlatButton_pressed_dark cn1 FlatButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • TabsGeom_normal_dark -- 93.01% fidelity (SSIM 0.8920) (no change)

    native TabsGeom_normal_dark cn1 TabsGeom_normal_dark
    Left: native widget. Right: Codename One render.

  • TabsGeom_normal_light -- 93.02% fidelity (SSIM 0.8861) (no change)

    native TabsGeom_normal_light cn1 TabsGeom_normal_light
    Left: native widget. Right: Codename One render.

  • FlatButton_pressed_light -- 93.20% fidelity (SSIM 0.9819) (+4.33 vs baseline)

    native FlatButton_pressed_light cn1 FlatButton_pressed_light
    Left: native widget. Right: Codename One render.

  • Button_disabled_dark -- 93.52% fidelity (SSIM 0.9618) (no change)

    native Button_disabled_dark cn1 Button_disabled_dark
    Left: native widget. Right: Codename One render.

  • Slider_disabled_light -- 93.96% fidelity (SSIM 0.9625) (-0.05 vs baseline)

    native Slider_disabled_light cn1 Slider_disabled_light
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_light -- 94.18% fidelity (SSIM 0.9825) (+1.32 vs baseline)

    native FlatButton_normal_light cn1 FlatButton_normal_light
    Left: native widget. Right: Codename One render.

  • Button_disabled_light -- 94.35% fidelity (SSIM 0.9636) (no change)

    native Button_disabled_light cn1 Button_disabled_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_pressed_dark -- 94.56% fidelity (SSIM 0.9653) (+1.46 vs baseline)

    native RaisedButton_pressed_dark cn1 RaisedButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_dark -- 94.57% fidelity (SSIM 0.9659) (+1.69 vs baseline)

    native RaisedButton_normal_dark cn1 RaisedButton_normal_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_pressed_light -- 94.80% fidelity (SSIM 0.9665) (+2.04 vs baseline)

    native RaisedButton_pressed_light cn1 RaisedButton_pressed_light
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_light -- 94.84% fidelity (SSIM 0.9916) (no change)

    native RadioButton_selected_light cn1 RadioButton_selected_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_light -- 95.01% fidelity (SSIM 0.9673) (+2.40 vs baseline)

    native RaisedButton_normal_light cn1 RaisedButton_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_light -- 95.09% fidelity (SSIM 0.9588) (-0.04 vs baseline)

    native Slider_normal_light cn1 Slider_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_normal_light -- 95.51% fidelity (SSIM 0.9863) (no change)

    native Switch_normal_light cn1 Switch_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_dark -- 95.76% fidelity (SSIM 0.9928) (no change)

    native CheckBox_disabled_dark cn1 CheckBox_disabled_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_dark -- 95.76% fidelity (SSIM 0.9928) (no change)

    native RadioButton_disabled_dark cn1 RadioButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_light -- 95.80% fidelity (SSIM 0.9938) (no change)

    native CheckBox_selected_light cn1 CheckBox_selected_light
    Left: native widget. Right: Codename One render.

  • Switch_selected_dark -- 95.90% fidelity (SSIM 0.9850) (no change)

    native Switch_selected_dark cn1 Switch_selected_dark
    Left: native widget. Right: Codename One render.

  • Switch_disabled_light -- 96.35% fidelity (SSIM 0.9896) (+0.04 vs baseline)

    native Switch_disabled_light cn1 Switch_disabled_light
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_dark -- 96.52% fidelity (SSIM 0.9927) (no change)

    native CheckBox_normal_dark cn1 CheckBox_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_dark -- 96.52% fidelity (SSIM 0.9927) (no change)

    native RadioButton_normal_dark cn1 RadioButton_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelPhoto_normal_light -- 96.72% fidelity (SSIM 0.9804) (+0.64 vs baseline)

    native GlassPanelPhoto_normal_light cn1 GlassPanelPhoto_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelPhoto_normal_dark -- 96.76% fidelity (SSIM 0.9832) (+0.59 vs baseline)

    native GlassPanelPhoto_normal_dark cn1 GlassPanelPhoto_normal_dark
    Left: native widget. Right: Codename One render.

  • TabOne_normal_dark -- 96.77% fidelity (SSIM 0.9526) (+0.73 vs baseline)

    native TabOne_normal_dark cn1 TabOne_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_normal_dark -- 96.80% fidelity (SSIM 0.9855) (no change)

    native Switch_normal_dark cn1 Switch_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_dark -- 96.81% fidelity (SSIM 0.9908) (no change)

    native RadioButton_selected_dark cn1 RadioButton_selected_dark
    Left: native widget. Right: Codename One render.

  • TextField_normal_dark -- 96.88% fidelity (SSIM 0.9492) (-0.20 vs baseline)

    native TextField_normal_dark cn1 TextField_normal_dark
    Left: native widget. Right: Codename One render.

  • TextField_disabled_dark -- 97.19% fidelity (SSIM 0.9526) (-0.15 vs baseline)

    native TextField_disabled_dark cn1 TextField_disabled_dark
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_dark -- 97.47% fidelity (SSIM 0.9973) (+3.03 vs baseline)

    native ProgressBar_normal_dark cn1 ProgressBar_normal_dark
    Left: native widget. Right: Codename One render.

  • TabOne_normal_light -- 97.51% fidelity (SSIM 0.9608) (+1.89 vs baseline)

    native TabOne_normal_light cn1 TabOne_normal_light
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_light -- 97.60% fidelity (SSIM 0.9989) (+2.19 vs baseline)

    native ProgressBar_normal_light cn1 ProgressBar_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_dark -- 97.79% fidelity (SSIM 0.9932) (no change)

    native CheckBox_selected_dark cn1 CheckBox_selected_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_dark -- 97.86% fidelity (SSIM 0.9614) (+0.89 vs baseline)

    native Dialog_normal_dark cn1 Dialog_normal_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_light -- 97.95% fidelity (SSIM 0.9634) (+0.86 vs baseline)

    native Dialog_normal_light cn1 Dialog_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_disabled_dark -- 98.01% fidelity (SSIM 0.9869) (+2.48 vs baseline)

    native Switch_disabled_dark cn1 Switch_disabled_dark
    Left: native widget. Right: Codename One render.

  • GlassIcon_normal_dark -- 98.59% fidelity (SSIM 0.9841) (no change)

    native GlassIcon_normal_dark cn1 GlassIcon_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassText_normal_dark -- 98.59% fidelity (SSIM 0.9837) (no change)

    native GlassText_normal_dark cn1 GlassText_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassIcon_normal_light -- 98.67% fidelity (SSIM 0.9860) (no change)

    native GlassIcon_normal_light cn1 GlassIcon_normal_light
    Left: native widget. Right: Codename One render.

  • GlassText_normal_light -- 98.69% fidelity (SSIM 0.9862) (no change)

    native GlassText_normal_light cn1 GlassText_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelGrad_normal_light -- 98.91% fidelity (SSIM 0.9935) (+0.67 vs baseline)

    native GlassPanelGrad_normal_light cn1 GlassPanelGrad_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelGrad_normal_dark -- 98.98% fidelity (SSIM 0.9928) (+0.55 vs baseline)

    native GlassPanelGrad_normal_dark cn1 GlassPanelGrad_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelGrey_normal_dark -- 98.99% fidelity (SSIM 0.9917) (+0.57 vs baseline)

    native GlassPanelGrey_normal_dark cn1 GlassPanelGrey_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelGrey_normal_light -- 99.05% fidelity (SSIM 0.9936) (+0.67 vs baseline)

    native GlassPanelGrey_normal_light cn1 GlassPanelGrey_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelRed_normal_dark -- 99.09% fidelity (SSIM 0.9926) (+0.52 vs baseline)

    native GlassPanelRed_normal_dark cn1 GlassPanelRed_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelRed_normal_light -- 99.10% fidelity (SSIM 0.9939) (+0.67 vs baseline)

    native GlassPanelRed_normal_light cn1 GlassPanelRed_normal_light
    Left: native widget. Right: Codename One render.

The browser screenshot leg delivered 31 of 181 images, with no error on either
side, the suite reporting completion, and the server reporting 31 written. Every
local test passed.

advance() arms SOCKET_TIMEOUT_MILLIS on the descriptor at EVERY park. That is
right for a half-sent request -- without it a virtual thread parks for ever --
and it is fatal for a websocket, which parks between messages by design and looks
exactly like a client that began a request and stopped. The upgrade set the
websocket's own allowance and the first park overwrote it, so any connection
quiet for more than fifteen seconds was closed by sweepDeadlines while both peers
still believed it was open.

Fixed the way the descriptor's other per-host state already works: VtHost gains
webSocketByFd, grown by ensureCapacity and cleared by forget() and by
setHandle(fd, 0) -- the second because a recycled descriptor number must not
inherit a websocket's idle allowance for the HTTP connection that gets it next.
advance() reads the flag where it arms the clock.

Invisible to everything that was testing this. The Java SE arm is pool mode and
never goes through advance(). The Autobahn suite has no idle gaps anywhere near
fifteen seconds. Both unit and conformance suites passed throughout.

So the test is the shape of the bug rather than the shape of the code: deliver,
go quiet for longer than the request timeout, deliver again. It runs on both
arms, and on the translated one CN1_HTTP_TIMEOUT_MS is lowered to 1500 so the
wait is six seconds and the websocket allowance it must not inherit is four times
that. Reverting the one-line fix fails it with "no answer to idleafter".

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

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 52ms / native 3ms = 17.3x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 161.000 ms
Base64 CN1 decode 146.000 ms
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.167x (83.3% faster)
Image applyMask (SIMD off) 82.000 ms
Image applyMask (SIMD on) 79.000 ms
Image applyMask ratio (SIMD on/off) 0.963x (3.7% faster)
Image modifyAlpha (SIMD off) 68.000 ms
Image modifyAlpha (SIMD on) 46.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.676x (32.4% faster)
Image modifyAlpha removeColor (SIMD off) 44.000 ms
Image modifyAlpha removeColor (SIMD on) 46.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.045x (4.5% slower)

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 98000 ms
Simulator Boot (Run) 1000 ms
App Install 18000 ms
App Launch 4000 ms
Test Execution 440000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 68ms / native 5ms = 13.6x speedup
SIMD float-mul (64K x300) java 70ms / native 3ms = 23.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 167.000 ms
Base64 CN1 decode 104.000 ms
Base64 native encode 299.000 ms
Base64 encode ratio (CN1/native) 0.559x (44.1% faster)
Base64 native decode 259.000 ms
Base64 decode ratio (CN1/native) 0.402x (59.8% faster)
Base64 SIMD encode 52.000 ms
Base64 encode ratio (SIMD/CN1) 0.311x (68.9% faster)
Base64 SIMD decode 49.000 ms
Base64 decode ratio (SIMD/CN1) 0.471x (52.9% faster)
Base64 encode ratio (SIMD/native) 0.174x (82.6% faster)
Base64 decode ratio (SIMD/native) 0.189x (81.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.286x (71.4% faster)
Image applyMask (SIMD off) 56.000 ms
Image applyMask (SIMD on) 93.000 ms
Image applyMask ratio (SIMD on/off) 1.661x (66.1% slower)
Image modifyAlpha (SIMD off) 53.000 ms
Image modifyAlpha (SIMD on) 100.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.887x (88.7% slower)
Image modifyAlpha removeColor (SIMD off) 69.000 ms
Image modifyAlpha removeColor (SIMD on) 42.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.609x (39.1% faster)

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 150 screenshots: 150 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 223 screenshots: 223 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6ff5dea88

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java Outdated
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java Outdated
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
shai-almog and others added 2 commits September 22, 2026 08:50
Eighteen from codex, and I checked the two P1 premises by running them rather
than by reading.

A websocket-only builder could not start. Backend counted HTTP routers alone and
threw "This server has no handlers" -- so the module a @WebSocketMapping-only
project generates compiled, started, and died before binding a port. My processor
test asserted the emitted SOURCE and never ran it, which is why it saw nothing.
Websocket routes now count as handlers and an HTTP 404 fallback goes in.

The pooled arm closed idle websockets at the HTTP timeout. The worker keeps the
descriptor blocking inside runWebSocket, so pooledDeadlines is never consulted
while fill() waits -- what times the read out is SO_RCVTIMEO, installed at accept
as CN1_HTTP_TIMEOUT_MS. Measured with a 2s HTTP timeout and a 5s idle: gone, and
gone again with CN1_WS_IDLE_TIMEOUT_MS=0, which is documented as "never". This is
the pooled twin of the bug CI found on the virtual-thread arm, and my own idle
test missed it by waiting 3s against a 15s default -- calibrated to pass rather
than to probe.

The rest, each real:

- routes are installed before the listener accepts, not after start() returns
- a refused upgrade drops instead of parsing what the client pipelined behind it,
  which otherwise runs a request the refusal promised not to serve
- deferred closes release the TLS session, which the early return was skipping
- onOpen throwing ends the session, as onText and onBinary already do
- fail() and a failed read report through onError, which the contract promises
- no data frames once a Close has been sent
- outbound close codes are validated, so an endpoint cannot ask for 1006
- closeForShutdown records 1001 instead of leaving onClose to report 1006
- shutdown goodbyes are bounded and concurrent rather than serial and blocking
- stop() from a websocket callback discounts its own turn
- getSubprotocols() throwing is handled like the router callback
- reassembly memory is reserved against a process-wide ceiling
- the processor accepts an inherited WebSocket and refuses a query string in a
  mapped path, which routing can never match
- the native cache key covers the translator and the boot classpath

One thing worth recording: the bounded-shutdown fix used CountDownLatch, the Java
SE arm compiled it, and only the translated build objected -- the server-safe
class library has java.util.concurrent.atomic and no CountDownLatch. An
AtomicInteger and a bounded poll instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I had every UI screenshot leg -- iOS, watchOS, tvOS, macOS, Catalyst, Android,
the car leg, the browser and both fidelity legs -- trigger on vm/backend/src,
impl, native and build.sh. That is wasteful and it is not what the risk needs.

The transport is a backend application, so a websocket regression genuinely can
break every screenshot run. But the websocket code is already covered three ways
that all trigger on vm/**: maven/backend's unit tests, vm/tests against the
translated binary, and the Autobahn conformance job. A change to the ORM, the
database layer, the HTTP parser or a native has no path to this transport at all,
and firing ten macOS and emulator jobs for one is a large bill for nothing.

What is left is the pair of files nothing else exercises: the server application
itself and the script that launches it. Those two can break the transport without
any other job noticing, which was the actual gap.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if(callerFd < 0) {
return workOutstanding();

P2 Badge Discount WebSocket turns even without a serving HTTP fd

The follow-up's new SERVING_WS marker is still bypassed because this early return runs whenever SERVING_FD is absent, and WebSocket callbacks never set SERVING_FD—only ordinary HTTP/HTTP2 handlers do. Consequently an endpoint that calls server.stop() from onText, onBinary, onPing, or onPong still waits the full drain and release windows for its own webSocketTurns count. Consult SERVING_WS before this return or track the WebSocket descriptor separately.

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java Outdated
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java Outdated
Comment thread vm/backend/demo/cn1ss/com/demo/SelfCheck.java
Comment thread vm/backend/src/com/codename1/backend/WebSocketSession.java
@shai-almog

shai-almog commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

Native fidelity (ios-27-metal)

68 pairs compared -- median 94.5%, worst 68.8% (Tabs_normal_dark), 25th pct 91.6%, mean 92.8%.

Distribution -- >=99%: 0 | 95-99%: 31 | 90-95%: 23 | <90%: 14

Component State Appearance Material Fidelity SSIM mean delta vs base Geometry
Tabs normal dark glass 68.8% 0.834 29.29 n/a ok
Toolbar normal dark glass 74.5% 0.930 7.16 n/a ok
Button normal dark glass 79.3% 0.938 7.66 n/a OFF (off 11px)
Button disabled dark glass 79.6% 0.942 7.41 n/a OFF (off 11px)
RaisedButton disabled dark glass 81.2% 0.954 6.32 n/a ok
Tabs normal light glass 81.8% 0.877 13.56 n/a ok
Button pressed dark glass 86.3% 0.948 5.27 n/a OFF (off 11px)
TextField normal light normal 86.6% 0.948 3.33 n/a ok
Toolbar normal light glass 86.9% 0.949 3.85 n/a ok
TabsGeom normal dark normal 87.7% 0.847 24.01 n/a ok
TabsGeom normal light normal 89.4% 0.867 21.23 n/a ok
Button normal light glass 89.5% 0.959 4.54 n/a OFF (off 11px)
Button pressed light glass 89.5% 0.959 4.33 n/a OFF (off 11px)
TextField disabled light normal 89.5% 0.953 2.50 n/a ok
Spinner normal dark normal 90.7% 0.880 3.47 n/a ok
FlatButton normal dark glass 90.9% 0.981 2.38 n/a ok
Button disabled light glass 91.6% 0.962 3.21 n/a OFF (off 11px)
Spinner normal light normal 91.6% 0.910 4.20 n/a ok
CheckBox normal light normal 91.7% 0.994 0.72 n/a ok
RadioButton normal light normal 91.7% 0.994 0.72 n/a ok
Slider disabled dark normal 92.1% 0.943 2.45 n/a ok
Switch selected light normal 92.1% 0.981 1.47 n/a ok
CheckBox disabled light normal 92.3% 0.995 0.52 n/a ok
RadioButton disabled light normal 92.3% 0.995 0.52 n/a ok
FlatButton pressed dark glass 92.4% 0.982 1.37 n/a ok
RaisedButton disabled light glass 92.4% 0.960 2.43 n/a ok
Slider normal dark normal 92.5% 0.944 2.63 n/a ok
FlatButton pressed light glass 93.3% 0.981 1.90 n/a ok
FlatButton normal light glass 93.9% 0.981 1.88 n/a ok
Slider disabled light normal 94.0% 0.963 1.75 n/a OFF (h 4.50)
RaisedButton pressed dark glass 94.1% 0.966 2.29 n/a ok
RaisedButton normal dark glass 94.1% 0.966 2.32 n/a ok
RadioButton selected light normal 94.3% 0.990 0.95 n/a ok
GlassPanelPhoto normal dark glass 94.5% 0.950 13.19 n/a ok
RaisedButton pressed light glass 94.5% 0.963 2.16 n/a ok
RaisedButton normal light glass 94.6% 0.963 2.17 n/a ok
GlassPanelPhoto normal light glass 94.7% 0.952 11.54 n/a ok
Slider normal light normal 95.1% 0.959 1.56 n/a ok
CheckBox disabled dark normal 95.4% 0.992 0.25 n/a ok
RadioButton disabled dark normal 95.4% 0.992 0.25 n/a ok
Switch normal light normal 95.5% 0.986 0.70 n/a ok
CheckBox selected light normal 95.8% 0.995 0.65 n/a ok
CheckBox normal dark normal 95.8% 0.992 0.39 n/a ok
RadioButton normal dark normal 95.8% 0.992 0.39 n/a ok
Switch selected dark normal 95.9% 0.985 1.02 n/a ok
TextField normal dark normal 96.0% 0.938 4.56 n/a ok
RadioButton selected dark normal 96.1% 0.989 0.67 n/a ok
TabOne normal light normal 96.2% 0.954 8.00 n/a ok
Switch disabled light normal 96.4% 0.990 0.53 n/a ok
TextField disabled dark normal 96.4% 0.940 3.42 n/a ok
TabOne normal dark normal 96.8% 0.953 4.61 n/a ok
Switch normal dark normal 96.8% 0.986 0.82 n/a ok
GlassIcon normal dark normal 97.0% 0.952 3.33 n/a OFF (w 0.07, h 0.36)
GlassText normal dark normal 97.1% 0.953 3.30 n/a OFF (off 7px, w 0.05, h 0.12)
GlassPanelGrey normal dark normal 97.3% 0.958 3.41 n/a -
ProgressBar normal dark normal 97.5% 0.997 0.60 n/a ok
ProgressBar normal light normal 97.6% 0.999 0.61 n/a ok
GlassPanelRed normal dark normal 97.8% 0.980 6.75 n/a -
GlassPanelGrad normal dark normal 97.8% 0.974 4.87 n/a OFF (off 106px, h 0.02)
CheckBox selected dark normal 97.9% 0.995 0.37 n/a ok
Dialog normal dark normal 97.9% 0.961 1.38 n/a ok
Dialog normal light normal 98.0% 0.963 1.42 n/a ok
Switch disabled dark normal 98.0% 0.987 0.41 n/a ok
GlassIcon normal light normal 98.2% 0.976 3.63 n/a ok
GlassText normal light normal 98.3% 0.978 3.60 n/a ok
GlassPanelGrad normal light normal 98.5% 0.989 5.48 n/a ok
GlassPanelGrey normal light normal 98.8% 0.989 3.57 n/a ok
GlassPanelRed normal light normal 98.9% 0.992 4.09 n/a ok
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Component State Appearance bbox dx,dy (px) w ratio h ratio center off (px) radius native->cn1 (px)
GlassPanelGrad normal dark +13,+0 0.975 0.019 105.5 102.0 -> 0.0
Button normal dark +0,-6 1.094 1.067 10.9 90.5 -> 45.2
Button disabled dark +0,-6 1.094 1.067 10.9 92.0 -> 45.2
Button pressed dark +0,-6 1.094 1.080 10.8 79.4 -> 45.3
Button normal light +0,+0 1.094 1.000 10.5 45.7 -> 45.3
Button pressed light +0,+0 1.094 1.000 10.5 87.5 -> 45.4
Button disabled light +0,+0 1.094 1.000 10.5 49.1 -> 45.3
GlassText normal dark +502,+97 0.046 0.117 6.5 102.3 -> 19.5
GlassIcon normal dark +486,+71 0.074 0.361 5.7 102.3 -> 60.1
TabOne normal light +0,+0 0.993 0.948 4.6 80.9 -> 77.0
TabOne normal dark +1,+0 0.986 0.948 4.6 95.2 -> 76.4
Toolbar normal dark +6,-3 0.983 1.009 3.5 90.3 -> 55.5
Toolbar normal light +6,+7 0.984 0.926 3.2 70.9 -> 56.1
RaisedButton disabled dark +0,-6 1.009 1.079 2.7 89.6 -> 46.1
Switch selected light +0,+0 1.028 1.026 2.7 -
Slider disabled light +0,-29 1.000 4.500 2.5 -
Switch normal light +0,+0 1.023 1.013 2.1 -
Switch disabled light +0,+0 1.023 1.013 2.1 -
Switch normal dark +0,+0 1.023 1.013 2.1 -
Switch disabled dark +0,+0 1.023 1.013 2.1 -
Spinner normal light +11,-8 0.978 1.052 1.6 -
RaisedButton disabled light +0,+0 1.014 1.011 1.6 71.9 -> 96.6
FlatButton normal light +0,+0 0.980 1.011 1.6 86.2 -> 51.8
Switch selected dark +0,+0 1.017 1.013 1.6 -
Spinner normal dark +12,-8 0.977 1.052 1.5 -
CheckBox normal light +0,+0 0.977 0.977 1.4 -
RadioButton normal light +0,+0 0.977 0.977 1.4 -
RadioButton selected light +0,+0 0.977 0.977 1.4 -
CheckBox selected light +0,+0 0.977 0.977 1.4 -
CheckBox normal dark +0,+0 0.977 0.977 1.4 -
RadioButton normal dark +0,+0 0.977 0.977 1.4 -
RadioButton selected dark +0,+0 0.977 0.977 1.4 -
CheckBox selected dark +0,+0 0.977 0.977 1.4 -
Tabs normal dark +12,+0 0.968 1.012 1.1 92.9 -> 80.9
Tabs normal light +12,+0 0.968 1.012 1.1 85.9 -> 80.3
TabsGeom normal dark +12,+0 0.968 1.012 1.1 98.6 -> 80.0
TabsGeom normal light +11,+0 0.970 1.012 1.1 79.8 -> 80.6
FlatButton pressed light +1,+0 0.973 1.011 1.1 94.6 -> 65.6
RaisedButton pressed dark +0,+0 1.009 1.011 1.1 44.6 -> 44.3
RaisedButton normal dark +0,+0 1.009 1.011 1.1 44.5 -> 44.3
CheckBox disabled dark +1,+0 0.966 0.977 1.1 -
RadioButton disabled dark +1,+0 0.966 0.977 1.1 -
RaisedButton pressed light +0,+0 1.009 1.000 1.0 43.3 -> 44.3
RaisedButton normal light +0,+0 1.009 1.000 1.0 43.3 -> 44.1
TextField disabled light +0,+1 0.999 0.996 0.7 13.9 -> 38.8
FlatButton normal dark +4,+0 0.952 1.011 0.7 86.1 -> 95.7
FlatButton pressed dark +4,+0 0.952 1.011 0.7 94.7 -> 97.9
GlassPanelPhoto normal light +1,+1 0.997 0.995 0.7 35.1 -> 33.3
GlassPanelGrey normal light +1,+1 0.997 0.995 0.7 30.0 -> 26.1
Slider disabled dark +0,-1 1.000 1.015 0.5 -
CheckBox disabled light +1,+0 0.977 0.988 0.5 -
RadioButton disabled light +1,+0 0.977 0.988 0.5 -
Slider normal light +0,+1 1.000 0.964 0.5 -
ProgressBar normal dark +0,+0 1.000 0.900 0.5 -
ProgressBar normal light +0,+0 1.000 0.900 0.5 -
GlassIcon normal light +1,+0 0.998 1.005 0.5 91.7 -> 92.4
GlassText normal light +1,+0 0.998 1.005 0.5 91.7 -> 92.4
GlassPanelGrad normal light +1,+0 0.998 1.005 0.5 24.3 -> 26.1
GlassPanelRed normal light +1,+0 0.998 1.005 0.5 24.4 -> 26.3
TextField normal light +0,+0 1.000 1.000 0.0 13.4 -> 40.5
Slider normal dark +0,+0 1.000 1.000 0.0 -
GlassPanelPhoto normal dark +3,+1 0.994 0.991 0.0 67.3 -> 68.4
TextField normal dark +0,+0 1.000 1.000 0.0 6.4 -> 15.1
TextField disabled dark +0,+0 1.000 1.000 0.0 6.4 -> 15.9
Dialog normal dark +0,+0 1.000 1.000 0.0 -
Dialog normal light +0,+0 1.000 1.000 0.0 -

Side-by-side comparisons (worst first)

  • Tabs_normal_dark -- 68.75% fidelity (SSIM 0.8336) (no baseline)

    native Tabs_normal_dark cn1 Tabs_normal_dark
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_dark -- 74.49% fidelity (SSIM 0.9295) (no baseline)

    native Toolbar_normal_dark cn1 Toolbar_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_normal_dark -- 79.28% fidelity (SSIM 0.9380) (no baseline)

    native Button_normal_dark cn1 Button_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_disabled_dark -- 79.63% fidelity (SSIM 0.9423) (no baseline)

    native Button_disabled_dark cn1 Button_disabled_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_dark -- 81.15% fidelity (SSIM 0.9544) (no baseline)

    native RaisedButton_disabled_dark cn1 RaisedButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • Tabs_normal_light -- 81.80% fidelity (SSIM 0.8766) (no baseline)

    native Tabs_normal_light cn1 Tabs_normal_light
    Left: native widget. Right: Codename One render.

  • Button_pressed_dark -- 86.28% fidelity (SSIM 0.9483) (no baseline)

    native Button_pressed_dark cn1 Button_pressed_dark
    Left: native widget. Right: Codename One render.

  • TextField_normal_light -- 86.57% fidelity (SSIM 0.9483) (no baseline)

    native TextField_normal_light cn1 TextField_normal_light
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_light -- 86.94% fidelity (SSIM 0.9488) (no baseline)

    native Toolbar_normal_light cn1 Toolbar_normal_light
    Left: native widget. Right: Codename One render.

  • TabsGeom_normal_dark -- 87.73% fidelity (SSIM 0.8469) (no baseline)

    native TabsGeom_normal_dark cn1 TabsGeom_normal_dark
    Left: native widget. Right: Codename One render.

  • TabsGeom_normal_light -- 89.37% fidelity (SSIM 0.8672) (no baseline)

    native TabsGeom_normal_light cn1 TabsGeom_normal_light
    Left: native widget. Right: Codename One render.

  • Button_normal_light -- 89.45% fidelity (SSIM 0.9588) (no baseline)

    native Button_normal_light cn1 Button_normal_light
    Left: native widget. Right: Codename One render.

  • Button_pressed_light -- 89.45% fidelity (SSIM 0.9588) (no baseline)

    native Button_pressed_light cn1 Button_pressed_light
    Left: native widget. Right: Codename One render.

  • TextField_disabled_light -- 89.54% fidelity (SSIM 0.9532) (no baseline)

    native TextField_disabled_light cn1 TextField_disabled_light
    Left: native widget. Right: Codename One render.

  • Spinner_normal_dark -- 90.72% fidelity (SSIM 0.8800) (no baseline)

    native Spinner_normal_dark cn1 Spinner_normal_dark
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_dark -- 90.88% fidelity (SSIM 0.9805) (no baseline)

    native FlatButton_normal_dark cn1 FlatButton_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_disabled_light -- 91.57% fidelity (SSIM 0.9621) (no baseline)

    native Button_disabled_light cn1 Button_disabled_light
    Left: native widget. Right: Codename One render.

  • Spinner_normal_light -- 91.58% fidelity (SSIM 0.9097) (no baseline)

    native Spinner_normal_light cn1 Spinner_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_light -- 91.66% fidelity (SSIM 0.9935) (no baseline)

    native CheckBox_normal_light cn1 CheckBox_normal_light
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_light -- 91.66% fidelity (SSIM 0.9935) (no baseline)

    native RadioButton_normal_light cn1 RadioButton_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_disabled_dark -- 92.13% fidelity (SSIM 0.9434) (no baseline)

    native Slider_disabled_dark cn1 Slider_disabled_dark
    Left: native widget. Right: Codename One render.

  • Switch_selected_light -- 92.13% fidelity (SSIM 0.9806) (no baseline)

    native Switch_selected_light cn1 Switch_selected_light
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_light -- 92.31% fidelity (SSIM 0.9948) (no baseline)

    native CheckBox_disabled_light cn1 CheckBox_disabled_light
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_light -- 92.31% fidelity (SSIM 0.9948) (no baseline)

    native RadioButton_disabled_light cn1 RadioButton_disabled_light
    Left: native widget. Right: Codename One render.

  • FlatButton_pressed_dark -- 92.38% fidelity (SSIM 0.9821) (no baseline)

    native FlatButton_pressed_dark cn1 FlatButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_light -- 92.40% fidelity (SSIM 0.9596) (no baseline)

    native RaisedButton_disabled_light cn1 RaisedButton_disabled_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_dark -- 92.52% fidelity (SSIM 0.9441) (no baseline)

    native Slider_normal_dark cn1 Slider_normal_dark
    Left: native widget. Right: Codename One render.

  • FlatButton_pressed_light -- 93.29% fidelity (SSIM 0.9811) (no baseline)

    native FlatButton_pressed_light cn1 FlatButton_pressed_light
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_light -- 93.91% fidelity (SSIM 0.9811) (no baseline)

    native FlatButton_normal_light cn1 FlatButton_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_disabled_light -- 93.96% fidelity (SSIM 0.9625) (no baseline)

    native Slider_disabled_light cn1 Slider_disabled_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_pressed_dark -- 94.08% fidelity (SSIM 0.9659) (no baseline)

    native RaisedButton_pressed_dark cn1 RaisedButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_dark -- 94.13% fidelity (SSIM 0.9660) (no baseline)

    native RaisedButton_normal_dark cn1 RaisedButton_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_light -- 94.26% fidelity (SSIM 0.9903) (no baseline)

    native RadioButton_selected_light cn1 RadioButton_selected_light
    Left: native widget. Right: Codename One render.

  • GlassPanelPhoto_normal_dark -- 94.49% fidelity (SSIM 0.9495) (no baseline)

    native GlassPanelPhoto_normal_dark cn1 GlassPanelPhoto_normal_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_pressed_light -- 94.53% fidelity (SSIM 0.9633) (no baseline)

    native RaisedButton_pressed_light cn1 RaisedButton_pressed_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_light -- 94.64% fidelity (SSIM 0.9632) (no baseline)

    native RaisedButton_normal_light cn1 RaisedButton_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelPhoto_normal_light -- 94.72% fidelity (SSIM 0.9520) (no baseline)

    native GlassPanelPhoto_normal_light cn1 GlassPanelPhoto_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_light -- 95.09% fidelity (SSIM 0.9588) (no baseline)

    native Slider_normal_light cn1 Slider_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_dark -- 95.41% fidelity (SSIM 0.9924) (no baseline)

    native CheckBox_disabled_dark cn1 CheckBox_disabled_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_dark -- 95.41% fidelity (SSIM 0.9924) (no baseline)

    native RadioButton_disabled_dark cn1 RadioButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • Switch_normal_light -- 95.51% fidelity (SSIM 0.9863) (no baseline)

    native Switch_normal_light cn1 Switch_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_light -- 95.80% fidelity (SSIM 0.9951) (no baseline)

    native CheckBox_selected_light cn1 CheckBox_selected_light
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_dark -- 95.84% fidelity (SSIM 0.9922) (no baseline)

    native CheckBox_normal_dark cn1 CheckBox_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_dark -- 95.84% fidelity (SSIM 0.9922) (no baseline)

    native RadioButton_normal_dark cn1 RadioButton_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_selected_dark -- 95.90% fidelity (SSIM 0.9850) (no baseline)

    native Switch_selected_dark cn1 Switch_selected_dark
    Left: native widget. Right: Codename One render.

  • TextField_normal_dark -- 96.02% fidelity (SSIM 0.9375) (no baseline)

    native TextField_normal_dark cn1 TextField_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_dark -- 96.13% fidelity (SSIM 0.9894) (no baseline)

    native RadioButton_selected_dark cn1 RadioButton_selected_dark
    Left: native widget. Right: Codename One render.

  • TabOne_normal_light -- 96.16% fidelity (SSIM 0.9537) (no baseline)

    native TabOne_normal_light cn1 TabOne_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_disabled_light -- 96.35% fidelity (SSIM 0.9896) (no baseline)

    native Switch_disabled_light cn1 Switch_disabled_light
    Left: native widget. Right: Codename One render.

  • TextField_disabled_dark -- 96.38% fidelity (SSIM 0.9402) (no baseline)

    native TextField_disabled_dark cn1 TextField_disabled_dark
    Left: native widget. Right: Codename One render.

  • TabOne_normal_dark -- 96.76% fidelity (SSIM 0.9529) (no baseline)

    native TabOne_normal_dark cn1 TabOne_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_normal_dark -- 96.80% fidelity (SSIM 0.9855) (no baseline)

    native Switch_normal_dark cn1 Switch_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassIcon_normal_dark -- 97.03% fidelity (SSIM 0.9521) (no baseline)

    native GlassIcon_normal_dark cn1 GlassIcon_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassText_normal_dark -- 97.06% fidelity (SSIM 0.9528) (no baseline)

    native GlassText_normal_dark cn1 GlassText_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelGrey_normal_dark -- 97.28% fidelity (SSIM 0.9578) (no baseline)

    native GlassPanelGrey_normal_dark cn1 GlassPanelGrey_normal_dark
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_dark -- 97.47% fidelity (SSIM 0.9973) (no baseline)

    native ProgressBar_normal_dark cn1 ProgressBar_normal_dark
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_light -- 97.60% fidelity (SSIM 0.9989) (no baseline)

    native ProgressBar_normal_light cn1 ProgressBar_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelRed_normal_dark -- 97.75% fidelity (SSIM 0.9800) (no baseline)

    native GlassPanelRed_normal_dark cn1 GlassPanelRed_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelGrad_normal_dark -- 97.84% fidelity (SSIM 0.9742) (no baseline)

    native GlassPanelGrad_normal_dark cn1 GlassPanelGrad_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_dark -- 97.85% fidelity (SSIM 0.9945) (no baseline)

    native CheckBox_selected_dark cn1 CheckBox_selected_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_dark -- 97.86% fidelity (SSIM 0.9614) (no baseline)

    native Dialog_normal_dark cn1 Dialog_normal_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_light -- 97.95% fidelity (SSIM 0.9634) (no baseline)

    native Dialog_normal_light cn1 Dialog_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_disabled_dark -- 98.01% fidelity (SSIM 0.9869) (no baseline)

    native Switch_disabled_dark cn1 Switch_disabled_dark
    Left: native widget. Right: Codename One render.

  • GlassIcon_normal_light -- 98.19% fidelity (SSIM 0.9759) (no baseline)

    native GlassIcon_normal_light cn1 GlassIcon_normal_light
    Left: native widget. Right: Codename One render.

  • GlassText_normal_light -- 98.28% fidelity (SSIM 0.9776) (no baseline)

    native GlassText_normal_light cn1 GlassText_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelGrad_normal_light -- 98.51% fidelity (SSIM 0.9894) (no baseline)

    native GlassPanelGrad_normal_light cn1 GlassPanelGrad_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelGrey_normal_light -- 98.81% fidelity (SSIM 0.9889) (no baseline)

    native GlassPanelGrey_normal_light cn1 GlassPanelGrey_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelRed_normal_light -- 98.89% fidelity (SSIM 0.9923) (no baseline)

    native GlassPanelRed_normal_light cn1 GlassPanelRed_normal_light
    Left: native widget. Right: Codename One render.

Eight findings, and five of them are consequences of the previous round rather
than of the original work. Fair: a fix is code too.

My "read timeout" was not one. ServerSocket.setTimeout sets SO_RCVTIMEO AND
SO_SNDTIMEO -- the native says so in a comment, and the Java SE arm reads one map
for both -- so giving a websocket a five-minute read allowance also gave it a
five-minute SEND allowance, and CN1_WS_IDLE_TIMEOUT_MS=0 removed the send bound
altogether. A peer that stopped reading could then block a broadcast thread for
ever, which is the exact failure SO_SNDTIMEO was added for. There is now a
setReceiveTimeout on both arms, with its own native, and the send deadline stays
where accept left it.

My shutdown fix started one thread per session. On the translated runtime that is
a pthread with a 16MB stack that ABORTS THE PROCESS if creation fails, against a
default ceiling of 4096 connections -- a graceful shutdown that kills the process
is worse than an abrupt one. A fixed small fleet walks the sessions through a
shared cursor instead, still bounded by the same deadline.

My shutdown retirement ignored retire()'s answer, so a descriptor with a writer
still inside it was closed anyway -- the cross-connection corruption the deferred
close exists to prevent, and external writers are not in workOutstanding() so
stop() can return while that write is in flight. Failed retirements defer now,
and the sweep skips them.

My `closing` guard was checked before the lock, so a sender could pass it, pause,
and write a data frame after the Close. Rechecked under the lock.

And websocket routing used the raw target while every HTTP route matches the
CANONICAL path, so /ch%61t missed /chat and fell through to a catch-all or a 404
-- endpoint selection, and whatever authentication hangs off it, depending on URI
spelling. It routes on pathFrom(0) now, and a registration that could never match
is refused rather than silently unreachable.

The rest:

- a write that fails part-way has left a partial frame on the wire, so the
  session is marked unusable instead of merely rethrowing
- Java SE reads and writes both flipped the same SocketChannel between blocking
  and non-blocking with separate selectors; whichever finished first restored the
  mode under the other. Counted transitions, because a lock held across the
  select would make a broadcast wait out the reader's idle timeout
- the native self-check accepted any 101 without checking Sec-WebSocket-Accept,
  which is precisely the failure it exists to catch: with SHA-1 returning the
  wrong digest it reported selfcheck=ok. It uses RFC 6455's own key and verifies
  the accept value the standard prints -- A/B'd, exit 1 against exit 0

Both arms still pass 301 Autobahn cases strictly green; 163 backend tests, 7
translated; 261 natives resolve.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed27baeafe

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread vm/backend/src/com/codename1/backend/Backend.java Outdated
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
Comment thread vm/backend/src/com/codename1/backend/HttpServer.java
shai-almog and others added 2 commits September 22, 2026 13:57
The registration API did not belong in this runtime. Everything else here is
asked for while the server starts -- a @RestController is found by the build, a
Handler is called, Backend.Handlers is invoked with whatever was opened -- and I
had added a mutable setter an application pokes at afterwards. In a generated
backend there is no such "afterwards": the build writes main, so
server.websocket(path, endpoint) is a call the application never makes.

So registration is a callback now. HttpServer.start takes WebSocketRoutes and
runs it against a WebSocketRegistry before any thread that could accept exists;
Backend.Builder takes WebSocketEndpoints, which receives the DataSource and the
EntityManager exactly as Handlers does. The generated bootstrap emits that
callback instead of a chain of setters. There is no websocket() on a started
server any more, and no way to add one: both writes live in a private Registry
reachable only from start().

That is a structural fix rather than a described one. The registration race was
real -- start binds the listener and starts its pollers before it returns, so an
upgrade arriving before the application got its reference back found no route and
was answered as ordinary HTTP -- and it is now unrepresentable rather than
avoided by convention. It also dissolves the finding about builder paths
bypassing validation: there is one path into the map and it validates.

Two findings from this round that the redesign does NOT dissolve:

The SERVING_WS discount was unreachable. workOutstandingBesidesCaller returns
workOutstanding() whenever callerFd < 0, and SERVING_FD is only set around HTTP
handlers -- so a websocket callback calling stop() always took that early return
and the marker I added for it could never run. A control nothing reaches is not a
control. It is consulted before the early return now.

And a session that upgraded while stop() was running missed its goodbye: the
snapshot is taken while the listener is still accepting, so a session inserted
just after it was shut down without a Close and reported 1006 for a shutdown the
server performed deliberately. Upgrades are refused with 503 before the snapshot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The snippet was a `public static Backend start()` that nothing calls, ending in
`.start()`, with no cleanup. All three of those are wrong, and the guide already
said so about the builder two sections earlier.

`.start()` is the test entry point -- its own javadoc says "Tests want this; a
process wants run()". It returns the server without installing a signal handler
and without waiting, so the snippet bound a port, leaked the server and returned.
`run()` is what a main is: bind, install a handler that drains what is in flight,
block. The example is a main now and calls run().

The framing was wrong too. The builder is not a normal way to register a
websocket; it is the same narrow case the configuration section already names --
a server that writes its own main instead of using the generated one. For an
ordinary project the annotation is the whole story and the entry point the build
writes does the registration. The section says that first now, and the builder
follows as the exception it is.

The HttpServer example had the same shape and now ends in awaitTermination(),
which is what demo/bench does.

The API itself is unchanged: .webSockets(callback) sits beside .handler() and
.handlers() and has exactly their use case. What had no use case was the example
I wrote for it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant