Skip to content

feat: add lean Rust desktop SDK with typed IPC - #367

Merged
Mohamed Mansour (mohamedmansour) merged 43 commits into
mainfrom
mmansour/rust-desktop-runtime
Sep 23, 2026
Merged

Mohamed Mansour (mohamedmansour) merged 43 commits into
mainfrom
mmansour/rust-desktop-runtime

Conversation

@mohamedmansour

@mohamedmansour Mohamed Mansour (mohamedmansour) commented Jun 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add one Rust-native desktop SDK using WKWebView on macOS, WebView2 on Windows, and GTK4/WebKitGTK 6 on Linux, without Electron, a bundled browser engine, Node, or a localhost application server.
  • Keep production apps lean: microsoft-webui-desktop has no default features. native enables the system webview; source enables development compilation; cli enables separate tooling. App-root packaging defaults to optimized runtime-only builds.
  • Use the same DesktopApp builder for source and immutable bundles, preserving window configuration, shell settings, application identity, route providers, and handlers before startup rendering.
  • Include standalone app scaffolding, the Contact Book Rust host, native/portable packaging, and platform-neutral window customization with explicit capability validation.

Typed application IPC

Replace the unmerged string-method API with generated Rust and TypeScript interfaces from one proto3 contract:

Flow Application API
JavaScript to Rust request/reply Generated host methods returning typed promises
Rust to JavaScript request/reply Generated renderer clients returning typed futures
Rust to JavaScript notifications Generated emitters and scoped JS subscriptions
JavaScript to Rust notifications Generated emitters and Rust startup/session handlers
  • Add webui desktop ipc generate, the build-time microsoft-webui-desktop-build crate, and browser-only @microsoft/webui-desktop. Shared bindings preserve bigint/64-bit values, binary bytes, native maps, optional fields, and oneofs.
  • Enforce schema identity, stable method IDs and retirement history, generated validation, and explicit host permissions. Generation checks output containment, stages changes with rollback on reported publication failures, and supports drift-only --check.
  • Keep application payloads protobuf binary. Native messages carry bounded admission and availability metadata, without base64 payloads, per-byte object arrays, idle polling, or automatic retries.
  • Run Rust application handlers off the native UI thread on lazy, bounded SDK workers. Requests support deadlines and cooperative cancellation; notification acceptance does not imply subscriber completion.
  • Bound SDK-owned frames, bytes, pending calls, tasks, and callbacks. Preserve byte reservations through actual native NSData, GBytes, and IStream lifetimes; retain separate control capacity under payload saturation.
  • Bind credentials, callbacks, and handles to an admitted document. Full navigation and close retire old sessions; same-document routing preserves them. BFCache restoration explicitly reconnects through fresh native admission, without reviving old handles or replaying calls.
  • Serve the same embedded bootstrap/runtime in source and packaged modes. Lifecycle events and window controls remain separate from application IPC.

Window customization and ownership

Cross-platform options cover native/custom titlebars, caption-control insets, background, effects, geometry, and native controls. Unsupported features fail before launch rather than silently becoming no-ops. Frames own lifecycle subscriptions, bounded command queues, and IPC state; weak handles cannot keep a closed frame accepting work. Wakeups are coalesced, callbacks run outside registry locks, and persisted geometry uses bounded reads and atomic replacement.

Release resource measurements

Measured SDK source: b72f4e91, unchanged by the subsequent CI metadata repair. Apple M4 Pro, macOS arm64, optimized runtime-only builds, one warmup per mode and five rotated triples. This is a minimal empty-schema harness, not the Contact Book application. Registered-unused injects the bootstrap; admitted-idle additionally imports the real browser runtime and retains an authenticated connection without application traffic.

Median or artifact IPC disabled Registered-unused Admitted-idle
Host physical footprint 22.079 MiB 22.032 MiB 22.079 MiB
WebContent physical footprint 14.719 MiB 15.032 MiB 15.813 MiB
Executable 1,475,424 B 1,475,520 B 1,475,520 B
Executable + logical resources 1,524,979 B 1,525,075 B 1,525,075 B

Admission versus registered-unused adds a paired median 0.781 MiB of WebContent footprint (range +0.641 to +0.984 MiB). The paired host delta, +0.062 MiB, is within observed variability; GPU variation prevents a strong total-memory conclusion. Both executable variants have 49 normal-edge dependency entries excluding the harness, without compiler/parser, Tokio, Rayon, prost-build or prost-reflect. Disabled builds still include shared SDK IPC code, so the 96-byte executable difference is not the total size of the IPC implementation.

Hidden process-launch-to-connection-ready p50/p95 was 254.34/358.45 ms. Admission after DOM-ready was 2.95/52.52 ms, retaining both slower samples. These warm-cache, hidden-window observations are not foreground startup or first-paint measurements, and no startup improvement is claimed. Product byte totals exclude observer/container metadata and signing; resource observations include constant measurement scaffolding. All 72 owned host/helper PIDs exited.

Earlier, before typed IPC, SDK consolidation reduced the measured macOS Contact Book executable from 2,494,672 to 1,314,368 bytes and its logical app bundle from 2,746,433 to 1,566,129 bytes. Those remain historical consolidation measurements, not the current Contact Book artifact size.

Verification and remaining platform acceptance

  • cargo xtask check passes with all new files tracked, including formatting, linting, dependency audit, workspace tests, generated-artifact checks, native/source/CLI coverage, builds, WASM, examples, benchmarks, and docs. Strict desktop native library/test Clippy also passes.
  • Portable IPC, browser/runtime, and generator regressions pass. Independent reviews verified fixes for ordering, control-credit starvation, wire validation, artifact publication, native completion wakeups, negotiated Windows limits, and native/browser buffer ownership.
  • Six actual WKWebView executions passed across three predetermined source/package pairs. Each verified the four flows, payloads through 256 KiB, cancellation/recovery, same-document routing, explicit close, and two BFCache restorations with old callbacks/handles remaining inactive. No hidden errors, retries, or forced cleanup were accepted.
  • macOS navigation testing reproduced and fixed browser-owned outbound GET cancellation before page retirement. Bodyless reads remain explicitly abortable by the SDK; payload POSTs do not opt into the browser's 64 KiB keepalive quota, and genuine transport failures remain errors. Independent delayed-response and partial-stream probes verify replacement isolation and credit release.
  • The first new CI run stopped during npm supply-chain verification, before native testing. Eight machine-local mirror URLs were removed through lockfile regeneration with all versions and integrity pins unchanged; strict verification remains enabled. The existing generated-file license exemption now explicitly includes the two protoc-generated browser codecs, with authored runtime/test checks retained.
  • Windows production cross-compilation/Clippy and Linux binding checks pass locally. CI runs the actual source/package fixture on all three operating systems and retains diagnostic artifacts. Live Windows and Linux native acceptance is pending the repaired CI run; local cross-checking is not runtime proof.

The previous macOS Contact Book UI/screenshot verification remains applicable to the unchanged visual shell; the new IPC fixture is non-visual protocol verification.

Add the desktop sidecar, native shell backends, app-root packaging, and Contact Book desktop runner.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Populate the new component asset roots field for desktop build option initializers after rebasing on main.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a cross-platform DesktopFrame and DesktopFrameBackend contract, route platform launch through PlatformFrameBackend, and update the Contact Book runner to use the generic API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reuse already-loaded bundle manifests and serve packaged assets through a manifest-backed index to avoid duplicate manifest parsing and per-request asset metadata reads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mohamedmansour
Mohamed Mansour (mohamedmansour) marked this pull request as draft June 24, 2026 19:11
@mohamedmansour
Mohamed Mansour (mohamedmansour) deleted the mmansour/rust-desktop-runtime branch August 15, 2026 19:30
@mohamedmansour
Mohamed Mansour (mohamedmansour) restored the mmansour/rust-desktop-runtime branch September 20, 2026 04:57
…-runtime

# Conflicts:
#	Cargo.lock
#	crates/webui-cli/src/main.rs
#	deny.toml
#	docs/.webui-press/config.json
#	docs/ai.md
#	docs/guide/integrations/index.md
#	examples/app/contact-book-manager/README.md
#	examples/app/contact-book-manager/package.json
Merges origin/main (133 commits ahead) and adapts webui-desktop,
webui-desktop-cli, and the contact-book-manager desktop example to the
webui-handler API refactor from #390 (bundler-neutral state
projection):

- Replace WebUIProtocol/CompiledRouteCache/WebUIHandler::handle with
  the new Protocol wrapper and WebUIHandler::render across
  runtime.rs and bundle.rs.
- Build test fixtures with --plugin=webui so navigation state
  projection actually populates per-component keys.
- Always ship the desktop runtime's full computed per-request state
  in partial responses instead of trusting the generic
  component-navigation projection, since desktop route providers
  inject fields (route params, basePath, tokens) no single
  component's template declares.
- Add the four new BuildOptions fields (css_bundle, metafile, theme,
  projection_manifests) and drop the removed ResolvedTokens::warnings
  field across webui-desktop-cli and the example app.
- Bump stale 0.0.17 path-dependency pins to the current workspace
  version (0.0.29).
- Add syn/system-deps to deny.toml's bans skip list (unavoidable
  gtk4-rs transitive duplicates) and drop skip entries that cargo
  deny now reports as unnecessary after the Cargo.lock refresh.

cargo xtask check passes end to end.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Collapse the desktop titlebar to zero height/padding with var(x, 0px)
  fallbacks so it renders nothing in a plain browser, where the desktop
  runtime never defines --webui-titlebar-*. A style() container query
  could detect the vars directly but is too new to be load-bearing, so
  the conservative fallback approach is used instead.
- Replace the titlebar app name link with a plain, aria-hidden label
  (it duplicated <title> and duplicated the skip link's target).
- Style .skip-link with the standard visually-hidden-until-focused
  pattern instead of rendering as a stray visible link.
- Move the skip-link target off <route id="main-content">, which
  silently drops unknown attributes and never emits that id, onto a
  real <main id="main-content" tabindex="-1"> landmark wrapping the
  route so the skip link actually moves focus.
- Reorder markup so the skip link is the first focusable element.

Updates the five affected Playwright visual snapshots; the diffs are
the removal of the previously mis-collapsed titlebar's stray height.
Fix defects in the desktop window contract and the example that adopts it.

Drag regions resolved hit-testing with e.target.closest(), but WebUI renders
through Web Components and a document-level listener sees shadow DOM events
retargeted to the host element. A titlebar declared inside any component's
shadow root would never have been draggable. Walk composedPath() instead,
innermost first, so the nearest webui-drag/webui-no-drag ancestor wins. Also
ignore non-primary buttons, so right-clicking a titlebar no longer starts a
window drag, and skip non-element path entries.

Report a poisoned command-queue mutex as a distinct Unavailable error rather
than QueueFull, whose help text told callers to wait for a drain that will
never happen. Replace the magic queue bound with MAX_QUEUED_WINDOW_COMMANDS
and compare with >= so the cap cannot be overrun.

Emit the CustomEvent name directly instead of formatting it through Rust's
Debug, and document the remaining public event, command, and response items.

In the example, correct --color-neutral-900, which is not a defined token
(the scale skips 800 to 950), and subtract the titlebar height from cb-app's
100vh so the titlebar and app together are exactly one window tall instead of
overflowing by the titlebar height. Both resolve to the previous values in a
browser, where the custom properties are undefined.

Restore the CRLF endings of README.md, package.json, and index.html, which
had been rewritten to LF and were producing whole-file diffs.

Revert the five contact-book visual baselines. They were regenerated on
macOS, but CI baselines them on ubuntu-latest; the differences are font
metrics and emoji fallback, the same environmental variance that makes the
untouched commerce suite fail locally.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Desktop apps previously exposed five window fields and no way to observe or
control the window after launch. DesktopShellConfig was serialized into the
manifest and documented, but no backend read it and platform_capabilities()
returned all-false everywhere, so it was a documented no-op.

Define the window, event, and window-state contract in webui-desktop and
implement it in all three backends:

- WindowOptions gains min/max size, resizable, maximized, fullscreen,
  always_on_top, center, background, titlebar, effect, and remember_state.
- TitlebarStyle supports native, hidden-inset, overlay, and none. For
  non-native styles the runtime injects --webui-titlebar-inset-start/-end,
  --webui-titlebar-height, and --webui-window-background so content can lay
  out around native controls without hardcoding per-OS offsets.
- Drag regions use webui-drag/webui-no-drag attributes rather than Chromium's
  -webkit-app-region, which WKWebView and WebKitGTK do not implement.
- Eighteen lifecycle events dispatch to Rust handlers and mirror to web
  content as webui:* CustomEvents. Close and navigation requests are
  cancelable.
- WindowHandle queues UI-thread commands from any thread, bounded by
  MAX_QUEUED_WINDOW_COMMANDS.
- remember_state persists geometry and rejects stale or off-screen state.

Wire platform_capabilities() to advertise only what each backend implements,
so an unsupported request fails at startup with an actionable message instead
of silently doing nothing. macOS adds app menu and tray; Windows adds Mica and
Acrylic; Linux advertises no effects (GTK4 has no portable blur) and no tray
(GTK4 removed GtkStatusIcon).

Consolidate the navigation allowlist into one tested function. The three
backends had drifted apart: macOS accepted the entire about: scheme, Windows
only about:blank, and Linux had no policy at all. The shared policy is
deny-by-default and rejects origin lookalikes such as webui://app.evil.

Split the Windows and Linux backends into submodules alongside macOS.

Verified on macOS: the example launches with a hidden-inset titlebar, and
cargo xtask check passes. Windows and Linux are verified against vendored
sources but have never been compiled locally; CI is their first real check.
Visual confirmation of the rendered window is still outstanding.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address an independent review of the desktop window work plus defects
found during macOS runtime verification and Windows cross-compilation.

Correctness:
- Linux registered a WebKit script-message handler but never defined the
  `window.webuiHostPostMessage` global the injected drag script calls, so
  every drag, minimize, maximize, and close message was silently dropped.
  Rename the handler to `webuiHost` to match macOS and inject an explicit
  bridge alias.
- Linux attached a `GestureDrag` to the whole webview, starting a native
  window move on any pointer drag in content. This broke text selection,
  scrolling, and sliders, and ignored the `webui-drag` contract. Remove it
  so dragging flows only through the `StartDrag` host message.
- Linux `read_body` returned a truncated body as a valid request on stream
  error; return `400` instead.
- Report `WindowResized`/`WindowMoved` in logical pixels on Windows, which
  alone reported physical pixels. Native sizing and hit-testing are
  unchanged.
- Fix `pack_point` using a nonexistent `isize: From<u16>` impl, and add the
  missing `window_dpi` helper.
- Update the Linux and Windows dev-serve `WindowOptions` initializer, which
  had not grown with the struct.

Contract:
- Add `WindowId::PRIMARY` so every backend reports one primary-window
  identity; macOS previously reported `0` and the others `1`.
- Replace `window_effects: bool` with `supported_effects`. No backend
  implements every `WindowEffect`, so a single boolean could not be honest.
  Windows now maps `vibrancy` onto the acrylic backdrop it advertises and
  rejects `tabbed`, which has no Windows analogue.

Events: emit `WindowMaximized`/`WindowUnmaximized` and `NavigationCompleted`
on macOS, and window-state transitions, `Ready`, and `Exiting` on Windows and
Linux. All 18 events are now emitted on every backend except `WindowMoved` on
Linux, which GTK4 and Wayland make unavailable.

Titlebar: suppress text selection in `[webui-drag]` regions at the framework
level. Double-clicking a titlebar selected its text before the drag script
converted the gesture to a maximize toggle, which no native titlebar does.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The Linux desktop backend in microsoft-webui-desktop-cli depends on gtk4
and webkit6, and that crate is a workspace member, so `cargo clippy
--workspace`, `cargo test --workspace`, and the workspace build all now
need GTK's development packages on Linux runners. No workflow installed
them, so Lint failed in glib-sys's build script before any Rust compiled
and gated every downstream job.

Add libgtk-4-dev and libwebkitgtk-6.0-dev to the shared build action,
folded into the existing protoc step so Linux jobs still run one apt
update. Both pr.yml and docs.yml use this action, and the only raw
workspace cargo commands live in jobs that consume it, so this covers
every affected job.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CI installed GTK for the first time, which compiled the Linux backend and
surfaced seven errors that no local toolchain could reach.

The substantive one is the command wakeup. `WindowHandle::set_wakeup`
requires a `Send + Sync` callback, but the closure captured
`ApplicationWindow` and `WebView`, which are neither, so queueing a window
command from a background thread could never have compiled. The UI-thread
handles now live in thread-local storage and the wakeup callback captures
nothing, hopping to the default main context to reach them. This mirrors
the Windows backend, which posts a message instead of capturing its HWND.

Also fixed:

- `display_bounds` called `window.display()`, ambiguous between `RootExt`
  and `WidgetExt`. Both return `gdk::Display`, not an `Option`, so the
  `let Some(..) else` binding was wrong as well.
- `connect_webview_events` cloned the event registry into the load-changed
  closure and then reused the moved binding for the policy closure.
- Dropped scheme and protocol imports that `linux/protocol.rs` owns.

Docs: replace the short IPC note in the desktop guide with a full message
passing section covering `invokeDesktop`, the `webui:*` lifecycle events
with their exact payload fields, and the window control channel. Event
shapes were captured from running code rather than inferred. Re-export
`DEFAULT_MAX_IPC_PAYLOAD_BYTES` so the documented limit is reachable.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
`state_store` has no use after `install_events`, so the clone only created
a value that was immediately dropped. Caught by `clippy::redundant-clone`
on Linux, which could not run until GTK was available in CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread crates/webui-desktop/src/event.rs Outdated
Comment thread crates/webui-desktop/src/ipc.rs Outdated
@mohamedmansour
Mohamed Mansour (mohamedmansour) marked this pull request as ready for review September 21, 2026 04:11
`EventRegistry::dispatch` held the handler mutex while invoking user
callbacks. `std::sync::Mutex` is not reentrant, so a handler that
registered another handler or dispatched a nested event deadlocked the UI
thread with no way to recover. The handler list is now copy-on-write
behind `Arc<[_]>`: dispatch clones the `Arc` and drops the guard before
invoking anything, so re-entry is safe. Cloning is a refcount bump rather
than a per-dispatch allocation, which matters because resize events fire
continuously during a window drag.

Dispatch now also runs every handler instead of short-circuiting on the
first cancellation, so whether a handler observes an event no longer
depends on registration order.

`IpcRegistry::dispatch_frame` applied the size limit to the inbound frame
and request payload but never to the response, so a small request could
return an unbounded allocation. Handler payloads are checked before
encoding, and the finished frame is checked after, since protobuf framing
and long handler error messages both add bytes. Both paths return the
existing structured `payload-too-large` error.

Regression tests cover all three. The deadlock test dispatches on a worker
thread with a bounded wait so a regression fails the suite instead of
hanging it forever.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Packaged desktop apps linked the build-time template compiler they
never call. `from_source` and the bundle/package surface now sit behind
a default-on `source` feature, so a packaged app can build with
`--no-default-features` and let LTO drop the parser.

Measured on contact-book-desktop (stripped release):
  default 2,494,672 bytes
  lean    1,331,008 bytes
  saved   1,163,664 bytes (46.65%)

The default build is byte-identical to before, so dev mode is unchanged.

The `webui` CLI also execs a separate `webui-desktop` sidecar without
checking it matches, so a stale binary on PATH silently won.
`webui-desktop` now answers `--webui-version` and the shim verifies it
before dispatch, failing with an actionable diagnostic on skew or on a
sidecar too old to answer.

Also fix a race in the webui-press E2E suite: the injected live-reload
client could call location.reload() while the test drove its own
cross-document navigation, interrupting an in-flight page.goto().

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The `-cli` suffix hid that this crate is the platform backend library
every packaged desktop app links, not a developer-only CLI. Its library
was already named `webui_desktop_runner`, and the scaffolder in init.rs
already aliased the dependency as `webui-desktop-runner`, so the package
name was the odd one out.

Rename the package and its directory together so both match the library
name. The `webui-desktop` binary name is unchanged, and no `use`
statements move. Both desktop crates are new in this PR and unpublished,
so this costs nothing now and would be a breaking change later.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
mddinbox
mddinbox previously approved these changes Sep 21, 2026

@mddinbox mddinbox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the current head c291ddf and rechecked both prior findings after the author fixes. Event dispatch now releases the registry lock before callbacks, and IPC response payloads and encoded frames are bounded with structured errors. The focused desktop suite passes all 58 tests and CI is green. No remaining issues found.

Expose one desktop SDK with native, source, and CLI features instead of
requiring applications to coordinate runtime and runner crates. Keep
headless and production graphs free of compiler and CLI dependencies.

Unify source and bundle app construction, preserve shell and app identity,
and generate standalone app workspaces with explicit source opt-in.
Package optimized runtime-only executables by default while allowing
explicit application features and debug builds.

Own subscriptions and command queues by the frame lifetime, bound retained
title storage, avoid reentrant wakeup locks, and reject work after shutdown.
Retain native menu targets, isolate persisted geometry by app identity,
and write bounded window state atomically.

Reject native message objects before formatting, decode untrusted NSString
UTF-16 safely, and attach Windows wakeups only after receiver installation.
Extend native CI and the feature matrix with regression coverage.

Measured Contact Book release packaging:
  executable: 2,494,672 -> 1,314,368 bytes
  bundle:     2,746,433 -> 1,566,129 bytes
  production dependency graph: 95 -> 42 packages

Document the remaining application IPC gap explicitly: current generic
messaging is JS-to-Rust byte-oriented request/reply, not a generated
bidirectional RPC and notification contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mohamedmansour Mohamed Mansour (mohamedmansour) changed the title feat: add Rust desktop packaging runtime feat: add Rust desktop SDK and packaging runtime Sep 21, 2026
The Navigation API only grants interception to documents with an
HTTP-family origin. Desktop shells serve applications from a custom
scheme, where WebKit refuses to treat a navigation as same-document and
reports canIntercept: false on every NavigateEvent even though
window.navigation exists. The router bailed out of its navigate handler
and resolved each route through navigateDocument, so every route change
became a full document load and the web engine retained one document per
navigation.

Gate the Navigation API path on the origin scheme and intercept
capture-phase link clicks plus popstate otherwise, driving the same
handleNavigation path through history.pushState. Browsers without the
Navigation API take the same fallback.

The scheme is the gate rather than a runtime probe because probing with
history.replaceState is misleading: a same-document state change is
interceptable on a custom scheme while an actual navigation is not.

Measured on a packaged macOS app over 120 route changes:

  before  164 document loads, WebContent 190.9 MB and still climbing
  after     1 document load,  WebContent  72.6 MB

The baseline never finished the workload because each navigation
reloaded the document and restarted it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread packages/webui-router/src/fallback-navigation.ts Outdated
Comment thread packages/webui-router/src/fallback-navigation.ts Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The desktop generator still compiles application protobuf codecs with ts-proto. Keep the fixed-layout framework envelope independent while retaining the application codec toolchain required by release CI and generator tests.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread pnpm-lock.yaml Outdated
Replace private feed tarball metadata with the corresponding public npm integrity records so locked installs satisfy the repository supply-chain policy.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@mddinbox mddinbox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The generator now emits hello.wireVersion: 3, but the checked-in crates/webui-desktop/src/generated/ipc/native-bootstrap.js still rejects every handshake whose version is not 2. The release-consumer acceptance step consequently fails on Linux, macOS, and Windows with Generated IPC asset differs: native-bootstrap.js; if shipped, the stale asset would reject the v3 handshake as invalid-payload. Please regenerate and commit the checked-in bootstrap with the repository's explicit sync-bootstrap.mjs --write flow, then rerun the release-consumer checks.

Comment thread crates/webui-desktop-build/src/emit/typescript.rs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
mddinbox
mddinbox previously approved these changes Sep 22, 2026

@mddinbox mddinbox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the latest head 46dd9cf, including the one-commit delta that regenerated the checked-in desktop runtime asset to match the v3 wire contract. The previously reported bootstrap/version mismatch is fixed, all prior authored threads are resolved, and the latest PR Checks run is green across the desktop build and native IPC validation. No remaining confirmed issues.

Move generated desktop IPC payload encoding and decoding to WebUI-owned Rust and TypeScript codecs, keeping Rust in charge of registry policy and the TypeScript layer as a thin native messaging facade.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…otocol

Measures DesktopRuntime::from_bundle_config_and_manifest (protocol compiled
ahead of time at build) against DesktopRuntime::from_source (protocol
compiled from templates on every launch): ~452us vs ~2.63ms, a ~5.8x win
for the precompiled path, confirming the packaged/bundle desktop launch
path should be preferred over the source feature outside development.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…op frame

The webuiHost message handler used for drag/minimize/maximize/close was
injected with UserContentInjectedFrames::AllFrames, letting an embedded
iframe on Linux invoke native window commands through the convenience
window.webuiHostPostMessage alias. macOS already restricts this bridge to
the main frame via WKScriptMessage.frameInfo().isMainFrame(), and Windows
only subscribes to top-level WebMessageReceived.

Restrict injection to UserContentInjectedFrames::TopFrame to keep the
alias out of subframes. This is a partial mitigation: WebKitGTK 6's
register_script_message_handler has no forMainFrameOnly equivalent and
script-message-received reports no sending-frame identity, so a subframe
can still reach window.webkit.messageHandlers.webuiHost directly. Document
this platform gap in the message handler and in DESIGN.md so it isn't
mistaken for a complete fix.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@mddinbox mddinbox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The newly added locked fetch fails on the current head in all three platform jobs (PR Checks run 35823332602): Cargo reports that it cannot update crates/webui-desktop/tests/fixtures/native-ipc/Cargo.lock because --locked was passed. This means the checked-in fixture lockfile does not match the manifest/resolved feature graph, so the job exits before the native IPC fixture runs and the PR cannot pass its merge gate. Regenerate the fixture lockfile from this manifest (including the native platform feature graph) and commit the resulting Cargo.lock, then keep --locked to preserve reproducible CI.

Comment thread .github/workflows/pr.yml
Regenerate the fixture lockfile after removing the desktop SDK's direct prost dependency. Keep all package versions and the locked CI fetch unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drive shared browser and desktop layouts from explicit mode state. Add accessible caption controls, drag exclusions, contained desktop scrolling, and responsive header and navigation layouts.

Preserve macOS frameless window capabilities and close vetoes. Add source/bundle rendering, browser interaction and native close-veto regressions without enabling application IPC.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread examples/app/contact-book-manager/desktop/src/main.rs Outdated
Prepare one serializable projected response for desktop and web while preserving the owned and raw JSON string APIs. Remove the desktop full-state bypass and pass client projection metadata through desktop builds and packaging.

Exclude projection inputs from bundle assets and protect them during output cleanup, including symlink-parent paths. Cover source/package/web parity, reserved-state filtering, unknown-surface fallback and manifest handling.

In the local 1000-contact unused-state benchmark, desktop payload falls from 1,385,467 to 187,669 bytes and P50 from 925 to 475 microseconds. Owned-web allocations are unchanged; its measured latency cost is 2-4 percent. Keep the representative benchmark and parity guards.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Build source and packaged test frames from per-test templates, state, assets, theme and valid projection metadata instead of requiring a prior frontend build. Preserve production validation and add a regression for missing projection metadata.

Verify static assets are served in both modes and retain the route-mutation and token assertions. The source-enabled tests pass in a tracked-only checkout with no generated dist or installed frontend packages.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Integrate the WebUI Press npm package relocation while preserving desktop runtime, projection and clean-checkout tests. The relocated Press suite and full cargo xtask check pass on the merged tree.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use FileRenameInfoEx with replacement and POSIX semantics directly so existing readers retain their snapshots and new opens see the replacement. Preserve strict concurrent-read assertions, report unsupported storage and native save failures, and add held-reader, descriptor and read-only regressions.

Keep Windows native IPC registration within the workspace argument limit by accepting the existing frame context. Windows-target Clippy and executable linking, portable persistence tests and cargo xtask check pass; Windows execution is left to CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mohamedmansour
Mohamed Mansour (mohamedmansour) merged commit 8d0fc32 into main Sep 23, 2026
35 checks passed
@mohamedmansour
Mohamed Mansour (mohamedmansour) deleted the mmansour/rust-desktop-runtime branch September 23, 2026 22:44
Mohamed Mansour (mohamedmansour) added a commit that referenced this pull request Sep 27, 2026
## Release

Bumps WebUI to `0.0.30` across Rust, npm, Python, and .NET package
metadata and updates desktop crate version examples. Native desktop and
Press support is distributed as opt-in, version-matched platform
packages; the former standalone `webui-press` npm command is replaced by
`webui press` without a Node command wrapper. The six new desktop
platform packages include READMEs. The native IPC fixture shares the
workspace lockfile after #568.

Previous release tag: `v0.0.29`

## Changes since `v0.0.29`

Features:

- WebUI Press ships native npm artifacts for macOS, Windows, and Linux
and runs through the opt-in `webui press` command (Add native npm
distribution for WebUI Press #555 by @copilot-swe-agent, fix: distribute
native desktop and Press sidecars #570 by @mohamedmansour).
- A lean Rust desktop SDK adds system webviews, app scaffolding, and
typed application IPC (feat: add lean Rust desktop SDK with typed IPC
#367 by @mohamedmansour).
- Opt-in native desktop npm packages make `webui desktop` available
without adding desktop webview dependencies to the base installation
(fix: distribute native desktop and Press sidecars #570 by
@mohamedmansour).
- Windows desktop apps gain native App SDK caption controls (feat: add
native Windows App SDK caption controls #560 by @mohamedmansour).
- Desktop apps can be configured without package metadata (feat:
configure desktop apps without package metadata #569 by
@mohamedmansour).

Fixes:

- Duplicate outlets at one route level produce an actionable build
warning instead of silently dropping later outlets (Warn on duplicate
outlets at the same route level #551 by @copilot-swe-agent).
- Press builds keep temporary inputs and outputs on the same filesystem
volume (fix: keep Press builds on one filesystem volume #552 by
@mohamedmansour).
- Streaming terminal markup stays inside the HTML body (fix: keep
streaming terminal inside body #558 by @mohamedmansour).
- The WebUI reference loader selects the correct source or
installed-package reference for the target project (fix: select WebUI
reference by target project #559 by @mohamedmansour).
- The Contact Book desktop icon displays correctly and header
double-click behavior is restored (fix: show Contact Book desktop icon
and restore header double-click #562 by @mohamedmansour).
- Linux desktop IPC preserves navigation and shutdown reasons when
retiring native requests (fix: preserve Linux IPC retirement reasons
before cancellation #566 by @mohamedmansour).
- Reactive callbacks wait until components are ready (fix: defer
reactive callbacks until components are ready #564 by @mohamedmansour).
- Windows desktop state reads tolerate transient file-open contention
while retaining atomic state replacement (fix: handle Windows state
contention and synchronize IPC tests #571 by @mohamedmansour).

Docs:

- Routing, project-specific reference installation, and interactivity
guidance were updated (Warn on duplicate outlets at the same route level
#551 by @copilot-swe-agent, fix: select WebUI reference by target
project #559 by @mohamedmansour, fix: defer reactive callbacks until
components are ready #564 by @mohamedmansour).
- Press installation and desktop SDK, caption control, icon,
configuration, and opt-in native sidecar documentation were expanded
(Add native npm distribution for WebUI Press #555 by @copilot-swe-agent,
feat: add lean Rust desktop SDK with typed IPC #367 by @mohamedmansour,
feat: add native Windows App SDK caption controls #560 by
@mohamedmansour, fix: show Contact Book desktop icon and restore header
double-click #562 by @mohamedmansour, feat: configure desktop apps
without package metadata #569 by @mohamedmansour, fix: distribute native
desktop and Press sidecars #570 by @mohamedmansour).
- Architecture and subsystem references were clarified, with the todo
tutorial kept aligned (chore: keep DESIGN.md architectural and preserve
internal contracts #563 by @mohamedmansour).

Maintenance:

- The WebUI Press npm package moved into the shared `packages/`
workspace (chore: move webui-press npm package #556 by @mohamedmansour).
- Desktop acceptance and native IPC test orchestration moved to Rust
xtask and the workspace Cargo lockfile (chore: migrate desktop
acceptance tests to Rust xtask #568 by @mohamedmansour).
- Desktop IPC capacity assertions now wait for the admitted handshake
worker to release its permit (test(desktop): wait for handshake capacity
before checking retired work #572 by @mohamedmansour).
- GitHub Actions dependencies were updated (chore(deps): bump the
github-actions group with 2 updates #565 by Dependabot).

## Validation

- `cargo xtask desktop-acceptance` and the full `cargo xtask check`
passed on the merged 0.0.30 tree.
- `cargo xtask publish-stage --target aarch64-apple-darwin
--native-only` staged core, Press, and desktop binaries. Seven local
0.0.30 tarballs were packed and installed offline in a fresh npm
consumer; installed binaries matched the staged release artifacts by
SHA-256.
- Installed `webui build` and `inspect` succeeded; `webui serve`
returned HTTP 200. Installed `webui press build` rendered a one-page
full site after installing its expected `esbuild`, TypeScript, and
framework peers. Installed `webui desktop init`, `build`, `package`, and
`run` succeeded. Both `desktop run` and the packaged macOS `.app` loaded
an image and executed page JavaScript that sent the expected heading to
a local probe. Core and desktop builds also passed with Node absent from
`PATH`.
- `pnpm --dir docs build` passed after refreshing the staged workspace
CLI. `cargo xtask native-ipc --timeout 60` passed source and packaged
macOS native IPC, including two BFCache restores in each mode, plus the
no-IPC fixture.
- PR Checks run 36299856666 finished green: Build (Linux) passed
unchanged on a targeted rerun after an intermittent packaged
history-navigation assertion; Build (macOS) passed on a targeted rerun
after GitHub reported its first hosted runner lost communication; Build
(Windows) and all six native npm platform jobs passed. All 41 current PR
checks are green. The deterministic acceptance-fixture correction for
the Linux race is separately green in #573 but is not yet part of this
release branch. Local npm smoke covers macOS arm64, not the full release
matrix.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.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.

3 participants