Skip to content

fix(frontend): the browser build stopped opening the Netplay pane at you - #517

Merged
doublegate merged 2 commits into
mainfrom
fix/wasm-netplay-autoopen
Sep 12, 2026
Merged

fix(frontend): the browser build stopped opening the Netplay pane at you#517
doublegate merged 2 commits into
mainfrom
fix/wasm-netplay-autoopen

Conversation

@doublegate

Copy link
Copy Markdown
Owner

The browser build stopped opening the Netplay pane at you

On the wasm demo, loading a ROM force-opened the "Netplay (browser)" window every single session. Reported by the maintainer.

Why it did that, and why the reason was half right

The comment at the site gave a genuine reason — the WebRTC handshake keys on the ROM hash, so the lobby has nothing to offer before a ROM exists. That is an argument for enabling a menu entry at ROM load. It is not an argument for opening a window nobody asked for.

The reason it was written that way is the actual defect: the Netplay menu item was #[cfg(not(target_arch = "wasm32"))], so the browser had no Netplay entry at all. Force-opening was the only way a wasm user could ever reach the lobby.

Three changes

  • app.rs no longer sets wasm_lobby.open on RomLoaded. WasmLobbyState::default() was already false; that one assignment was the entire behaviour. It is now the only site in the crate that touches the flag — the menu.
  • ui_shell.rs gains a wasm entry in the same menu group as the native one, same glyph::WIFI, labelled "Netplay (browser)..." after the window it opens. A browser cannot open a UDP socket, so these are two transports rather than one feature built twice, and the label says which you are getting. Gated on rom_interactive — the predicate that group already uses — so it lights up once a ROM is loaded.
  • MenuAction::OpenPanel(ToolPanel::Netplay) routes to the lobby on wasm instead of to netplay_panel, which renders only a "this UDP netplay panel is native-only" note there. Routing to it would have offered the user a window that says it does not work. The lobby is an App-owned window rather than a ToolPanel and draws inside the overlay's egui frame, so the overlay is forced visible exactly as open_chip_panel already does for chip panels.

Native behaviour is unchanged — the native entry, its replay_locked gating and the UDP panel are untouched.

Verified

Both wasm clippy gates (wasm-winit default and wasm-canvas), the native workspace gate, cargo fmt, and markdownlint. grep confirms exactly one site now opens the lobby and the default is false.

The Pages deploy on merge is what puts it in front of a browser; I have not clicked it in one.

🤖 Generated with Claude Code

https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj

On the wasm demo, loading a ROM force-opened the "Netplay (browser)" window
every session. The comment at the site gave a real reason -- the WebRTC
handshake keys on the ROM hash, so the lobby has nothing to offer before a ROM
exists -- and that is an argument for ENABLING a menu entry at ROM load, not for
opening a window nobody asked for.

Why it was written that way is the actual defect: the Netplay menu item was
`cfg(not(target_arch = "wasm32"))`, so the browser had no entry at all and
force-opening was the only route to the lobby. Three changes:

  * `app.rs` no longer sets `wasm_lobby.open` on `RomLoaded`. The struct's
    default was already `false`; that one assignment was the whole behaviour,
    and it is now the only site in the crate that touches the flag (the menu).
  * `ui_shell.rs` gains a wasm Netplay entry in the same menu group as the
    native one, with the same `glyph::WIFI`, labelled "Netplay (browser)..."
    after the window it opens. A browser cannot open a UDP socket, so the two
    are different transports rather than one feature built twice, and the label
    says so. Gated on `rom_interactive`, the predicate that group already uses.
  * `MenuAction::OpenPanel(ToolPanel::Netplay)` routes to the lobby on wasm
    instead of to `netplay_panel`, which renders only a "native-only" note
    there -- routing to it would offer the user a window that says it does not
    work. The lobby is an `App`-owned window rather than a `ToolPanel` and draws
    inside the overlay's egui frame, so the overlay is forced visible exactly as
    `open_chip_panel` does.

Native behaviour is unchanged. Verified with the two wasm clippy gates
(`wasm-winit` default and `wasm-canvas`), the native workspace gate, and fmt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Copilot AI lite review requested due to automatic review settings September 12, 2026 15:46
@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: f778ddfa-8766-4bc6-b685-0d6d5a2803bd


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

Resolves an issue in the WebAssembly build by adding a dedicated, ROM-gated Netplay menu item and removing the automatic force-opening of the lobby upon ROM load.

Blocking issues

None found.

Suggestions

  • crates/rustynes-frontend/src/app.rs lines 9058-9067: Instead of leaving a large comment detailing the removed auto-open behavior and its history, simply delete the block. Git history and the PR description already provide this context.
  • crates/rustynes-frontend/src/app.rs line 11158: Testing logic by string-matching raw source code (squash(&production(APP_SRC))) is extremely brittle and bypasses semantic checks. If the wasm32 target lacks a test harness, consider refactoring the UI routing logic into a platform-agnostic, unit-testable module rather than parsing syntax shapes.

Nitpicks

  • CHANGELOG.md lines 32-46: The changelog entry is quite conversational ("pane at you", "honest form of the reason"); consider condensing it to strictly describe the user-facing change.
  • crates/rustynes-frontend/src/app.rs lines 4808-4814: The inline comment is unusually verbose for a standard UI routing conditional.

Automated first-pass review by agy on a self-hosted runner -- not a human review.

Earlier review rounds (newest first)
Round reviewed at 2026-09-12 17:48 UTC

Antigravity review (Gemini via Ultra)

This PR introduces a dedicated menu entry for the browser Netplay lobby and removes the behavior that forced it open automatically upon loading a ROM.

Blocking issues

  • Silent failure path: In crates/rustynes-frontend/src/app.rs, if self.debugger is None when handling ToolPanel::Netplay on wasm32, self.wasm_lobby.open = true is set but d.force_visible() is bypassed. This will silently swallow the user's menu click without displaying the lobby frame.

Suggestions

  • crates/rustynes-frontend/src/app.rs (around line 9055): Remove the block comment explaining the old auto-open behavior. The code should document the current state; historical context belongs in the PR description or git history.
  • crates/rustynes-frontend/src/ui_shell.rs (around line 1537): Similarly, condense the large explanatory comment. It is sufficient to document why the button is gated behind rom_interactive.
  • Missing tests: The style guide mandates tests for behavior changes. If possible, add a test verifying that the wasm32 Netplay menu item is appropriately enabled/disabled based on ROM load state.

Nitpicks

  • The CHANGELOG.md entry is overly conversational and lengthy; consider condensing it to simply describe the fix and the new menu integration.

Automated first-pass review by agy on a self-hosted runner -- not a human review.

…ch other

The v2.6.18 follow-up fix had no gate. Antigravity's review said so, and it
was the one of its four points that was right: this adds the gate.

WHY A SOURCE-SHAPE GATE. Both properties live behind
`#[cfg(target_arch = "wasm32")]`, so a native `cargo test` cannot execute
either one, and there is no wasm test harness in this crate. The shape is
what is available to assert, so it is asserted precisely rather than
approximately -- the same idiom, in the same module, as
`every_wasm_rom_entry_point_corrects_the_header`, including that test's
recorded lesson about a file that reads itself.

WHAT IS PINNED, AND WHY BOTH HALVES TOGETHER. The defect was that loading a
ROM force-opened the lobby on every browser session. Removing that alone
would have made the feature UNREACHABLE rather than unobtrusive, because the
Netplay menu item was `cfg(not(wasm32))` and the force-open was the only
route to the lobby that existed. So the fix is two changes that are only
correct as a pair, and the test fails if either is undone:

1. `app.rs` contains EXACTLY ONE site assigning `wasm_lobby.open = true`.
   A count, not a `contains`, and that is what makes it fail in BOTH
   directions: a bare `contains` would stay green if the ROM-load force-open
   came back ALONGSIDE the menu route -- the pre-fix state plus a menu.
2. That one site is the `OpenPanel(ToolPanel::Netplay)` route.
3. `ui_shell.rs` offers the wasm entry at all, gated on `rom_interactive`.

The third assertion matches a WINDOW around the label rather than one long
literal: the enable predicate, the label and the emitted action sit on
separate lines that rustfmt is free to re-wrap, and pinning the exact joined
spelling would fail on a reflow that changed nothing. Characters, not bytes,
per the rule about slicing in a diagnostic path.

DEMONSTRATED BY MUTATION -- three, one per assertion, all CAUGHT:

  M1  reintroduce the wasm-gated auto-open in the ROM-loaded arm
      -> CAUGHT: "expected exactly one site opening the browser lobby", left: 2
  M2  retarget the menu route to a different ToolPanel
      -> CAUGHT: "the wasm OpenPanel(Netplay) route no longer opens the lobby"
  M3  replace `rom_interactive` with `true` on the menu entry
      -> CAUGHT: "the wasm Netplay entry is no longer gated on a loaded ROM"

Two of the three had to be REFORMULATED first, and both reformulations are
recorded because they are the project's own two traps:

  - M1 first inserted the assignment ungated and came back BUILD-FAILED --
    `wasm_lobby` is a `cfg(wasm32)` field, so the mutation has to reproduce
    the defect AS IT EXISTED, wasm-gated, rather than approximate it. A
    mutant that does not compile is not a catch.
  - M2 first anchored on the single-line form of the condition and hit TWO
    occurrences: the production site, and the test's own string literal.
    That is the self-reading hazard the neighbouring test documents, showing
    up in the mutation harness instead of in the assertion.

The self-read guard is therefore load-bearing and is asserted explicitly:
every literal this test searches for also appears in its own body, so
without cutting the test module off first all three assertions would be
permanently true.

Gates: `cargo fmt --all --check` clean; `cargo clippy -p rustynes-frontend
--all-targets -- -D warnings` clean; the test passes and fails under all
three mutations, with the tree restored byte-identical to its pre-mutation
snapshot afterwards.

No production code changes, so the emulation core is untouched and the
browser behaviour is exactly what the previous commit shipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
@doublegate

Copy link
Copy Markdown
Owner Author

Worked all four points. One is adopted, one blocking finding is refuted with evidence, two are declined with reasons.

Blocking: "silent failure path when self.debugger is None" — refuted

The claim is that a menu click on ToolPanel::Netplay with debugger == None sets wasm_lobby.open = true, skips force_visible(), and silently swallows the click. That state is unreachable, and it is unreachable for a structural reason rather than by luck.

The menu bar does not exist independently of the debugger — it is drawn by it. app.rs:10414:

shell_out = debugger.render_shell(device, queue, encoder, &window, view, size, ...);

debugger there is self.debugger.as_mut().expect("checked above"). MenuAction::OpenPanel(...) is produced inside render_shell, so with debugger == None there is no menu bar to click. self.debugger is None only between construction (app.rs:1125, 1268) and graphics init (app.rs:8439) — a window in which nothing is rendered at all.

Both lobby draw sites make the same requirement independently: app.rs:9930 (expect("dbg_visible implies a debugger")) and app.rs:10306 (expect("checked above")). So a None debugger means the lobby cannot be drawn by any path either — force_visible() is not the load-bearing part.

The tell that this is a general property rather than a defect in this diff: the two lines immediately below the cited block are

if let Some(d) = self.debugger.as_mut() {
    d.open_panel(panel);
}

Identical shape. If the finding held, it would indict every menu action in dispatch_menu_action, not the four lines this PR added.

Missing tests — adopted, and this was the right call

Correct, and now fixed in 5d499777: the_browser_netplay_lobby_is_menu_reachable_and_never_auto_opens.

Both properties sit behind #[cfg(target_arch = "wasm32")] and this crate has no wasm test harness, so a native cargo test cannot execute either one. The test asserts the source shape instead — the same idiom, in the same module, as the existing every_wasm_rom_entry_point_corrects_the_header.

What it pins, and why both halves together: removing the auto-open alone would have made the lobby unreachable rather than unobtrusive, because the Netplay menu item was cfg(not(wasm32)) and the force-open was the only route that existed. So:

  1. app.rs holds exactly one site assigning wasm_lobby.open = true. A count, not a contains — a bare contains would stay green if the ROM-load force-open returned alongside the menu route, which is the pre-fix state plus a menu.
  2. That site is the OpenPanel(ToolPanel::Netplay) route.
  3. ui_shell.rs offers the wasm entry, gated on rom_interactive — matched as a window around the label, since rustfmt is free to re-wrap those three lines.

Demonstrated by mutation, three, one per assertion, all CAUGHT:

mutation result
reintroduce the wasm-gated auto-open in the ROM-loaded arm CAUGHT — expected exactly one site opening the browser lobby, left: 2
retarget the menu route to a different ToolPanel CAUGHT — the wasm OpenPanel(Netplay) route no longer opens the browser lobby
replace rom_interactive with true on the menu entry CAUGHT — the wasm Netplay entry is no longer gated on a loaded ROM

Two needed reformulating first, and both are this project's recorded traps rather than incidental: the first came back BUILD-FAILED because wasm_lobby is a cfg(wasm32) field, so the mutation had to reproduce the defect as it existed (a mutant that does not compile is not a catch); the second anchored on the single-line form of the condition and matched two occurrences — the production site and the test's own string literal, which is exactly the self-reading hazard the neighbouring test documents, surfacing in the mutation harness instead of in the assertion. The self-read guard is asserted explicitly for that reason.

Comment length (app.rs ~9059, ui_shell.rs ~1537) — declined

House style here is deliberate and written down in AGENTS.md: comments explain the why at the depth of the surrounding code. More specifically, this repository has a standing rule that prose asserting an intent is how a defect survives releases — Pixel Provenance shipped non-functional for four releases under a comment claiming it worked.

The app.rs comment sits at a site where the code is an absence. Without it, the next reader finds a ROM-load path that conspicuously does not open the lobby and no record of whether that is deliberate; "restore the auto-open, the handshake needs the ROM hash" is a plausible and wrong next change, and the comment is what refutes it in place. The ui_shell.rs one records why the browser entry is a separate item with a different label rather than the native one un-gated — a browser cannot open a UDP socket, so these are two transports, not one feature built twice.

Git history is not an equivalent home for either: git blame on a line that does not exist finds nothing.

CHANGELOG tone — declined

The entry follows the format of every entry around it, which is this project's convention rather than this PR's invention.

@doublegate
doublegate merged commit caa20cf into main Sep 12, 2026
29 checks passed
@doublegate
doublegate deleted the fix/wasm-netplay-autoopen branch September 12, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants