Skip to content

fix(desktop): preserve edits across websocket reconnects - #5812

Open
nl310 wants to merge 1 commit into
DioxusLabs:mainfrom
nl310:nl310/fix-desktop-edits-reconnect
Open

fix(desktop): preserve edits across websocket reconnects#5812
nl310 wants to merge 1 commit into
DioxusLabs:mainfrom
nl310:nl310/fix-desktop-edits-reconnect

Conversation

@nl310

@nl310 nl310 commented Sep 5, 2026

Copy link
Copy Markdown

A webview that reconnects while its previous edits worker is idle can stop receiving UI updates. The old worker is still registered as Connected; handle_connection removes that entry and rejects the replacement. A transport regression against unmodified main (74a4973) reproduces this with Protocol(ResetWithoutClosingHandshake) after the first successful edit and ACK.

This follows up on #4391 and the iOS suspension report in #4374. The affected connection logic is also present in 0.7.10 and v0.8.0-alpha.1. This PR targets current main.

Change

  • Keep each webview's pending edits in an outbox that survives socket replacement. A connection generation prevents a superseded worker from consuming edits or clearing the active socket.
  • Retain an edit until its matching ACK arrives. Socket read errors no longer acknowledge an unapplied edit.
  • Prefix each private desktop edit frame with an eight-byte little-endian sequence number and echo that header as the ACK. The native interpreter remembers the last applied sequence across reconnects, so an ACK lost after rendering cannot replay DOM mutations twice. Rust and the generated native JavaScript change together.
  • Bind animation callbacks and reconnect timers to their originating socket generation. Read the endpoint and server key together, and publish replacement listener credentials before notifying the UI.

The JavaScript changes are in NativeInterpreter.waitForRequest; the existing LiveView edit helpers are unchanged. This does not reload the page or recreate application state.

Reproduction and validation

The failing baseline test opens a queue, sends and acknowledges an edit, drops the idle socket, reconnects, and attempts another edit. It fails on unmodified main; the regression now passes with the replacement transport.

Validated locally on macOS with Rust 1.98.0:

  • cargo test -p dioxus-desktop --lib — 7 passed, including 5 socket regressions for idle reconnect, unacknowledged replay, repeated handovers, listener/key rotation, and invalid client keys.
  • node --test packages/interpreter/tests/native_reconnect.cjs — 5 passed. These load the generated native interpreter and exercise deferred rendering, lost ACKs, stale sockets/timers, automatic reconnect, and server authentication.
  • cargo check -p dioxus-desktop --lib --target aarch64-apple-ios — passed.
  • cargo clippy -p dioxus-desktop --lib --tests --no-deps -- -D warnings — passed.
  • Rustfmt for the changed Rust files and git diff --check — passed.

The reconnect tests are added to the existing macOS/Linux Playwright CI jobs. An initial Clippy run including workspace dependencies stopped on existing dioxus-core-macro formatting-borrow lints under Rust 1.98.0; no unrelated code was changed. The full workspace test suite was not run.

Remaining validation

Draft pending maintainer feedback on the private transport change and physical-iPhone validation. The socket and JavaScript harnesses reproduce the transport failures but do not simulate all WKWebView or OS suspension behavior. On a physical device, background or lock a small counter app for several minutes, resume and interact, and repeat both while idle and while an update is pending. An application with an open input dialog should also retain its draft through resume.

@nl310
nl310 marked this pull request as ready for review September 5, 2026 09:54
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