Skip to content

feat(desktop): expose Config::with_background_throttling to opt out of WKWebView WebContent suspension#5587

Open
goosewobbler wants to merge 1 commit into
DioxusLabs:mainfrom
goosewobbler:feat/desktop-background-throttling-policy
Open

feat(desktop): expose Config::with_background_throttling to opt out of WKWebView WebContent suspension#5587
goosewobbler wants to merge 1 commit into
DioxusLabs:mainfrom
goosewobbler:feat/desktop-background-throttling-policy

Conversation

@goosewobbler
Copy link
Copy Markdown

Closes #5586.

What

Adds Config::with_background_throttling(BackgroundThrottlingPolicy) to dioxus-desktop, surfacing the policy that wry already supports via WebViewBuilder::with_background_throttling. Forwards the value to the WebView at construction.

use dioxus::desktop::wry::BackgroundThrottlingPolicy;

let config = dioxus::desktop::Config::new()
    .with_background_throttling(BackgroundThrottlingPolicy::Disabled);

Three variants from wry:

  • Disabled — never throttle.
  • Suspend — fully suspend tasks when the view isn't in a window (current implicit default).
  • Throttle — limit rather than suspend.

The setting is applied at WebView creation; non-Apple platforms ignore it in wry today, so this is effectively a macOS opt-out.

Why

On macOS, WKWebView aggressively suspends the WebContent process (the process running page JS) when the host window isn't visible/focused, freezing all JS timers, fetches, and event handlers. On a headless host — CI runner, hidden window, no focus — that suspension is permanent.

Without this knob, there's no way for a Dioxus app to opt out from Rust-side config. The workarounds I tried first (caffeinate -dims, NSAppSleepDisabled=YES, JS-side AudioContext/muted HTMLAudioElement keepalive) all failed because the throttling is WebKit-internal, not OS-level App Nap. Calling WebViewBuilder::with_background_throttling(Disabled) at construction was the only fix.

Concrete cases this unblocks:

  • Automation / E2E testing on macOS CI. This came up building @wdio/dioxus-service — an embedded WebDriver server inside the app process polls the webview over a wdio:// IPC channel, and the polling loop froze within seconds on macOS-ARM GitHub Actions runners. The same suite passes interactively on macOS and on Linux/Windows CI.
  • System-tray apps with a hidden main window doing background work in the webview.
  • Background renderers / streaming UIs that should keep ticking off-screen.

See #5586 for the broader motivation discussion.

Implementation

Mirrors the existing pattern for other forwarded wry attributes (with_background_color, with_data_directory, etc.):

  1. Config gets background_throttling: Option<BackgroundThrottlingPolicy> (default None).
  2. New builder method Config::with_background_throttling.
  3. webview.rs forwards the value to WebViewBuilder::with_background_throttling when set.

None preserves current behaviour exactly — the attribute is only set if the app calls the new builder method.

Test plan

  • cargo check -p dioxus-desktop clean.
  • Verified end-to-end on macOS-ARM CI with a downstream consumer (@wdio/dioxus-service): full E2E suite that previously timed out within the first spec now passes consistently across runs (reference run — once we wired this API up to set Disabled under automation, the polling-loop hang disappeared).
  • No new unit/integration tests added — the call is a pass-through to a wry method that's already tested upstream. Happy to add a smoke test if you'd like one (suggestions on placement welcome — I didn't see an obvious existing pattern for Config builder methods).

Notes

  • wry::BackgroundThrottlingPolicy is reachable via the existing pub use wry; line in dioxus_desktop::lib, so consumers can use dioxus::desktop::wry::BackgroundThrottlingPolicy without adding wry as a direct dep.
  • Considered naming the method with_inactive_scheduling_policy to match Apple's docs term, but kept it aligned with wry's with_background_throttling for consistency with the upstream API.

WKWebView aggressively suspends the WebContent process when the host
window is not visible/focused. For long-running background work — most
notably automation drivers like @wdio/dioxus-service whose JS polling
loop must keep firing across spec boundaries on a headless CI runner —
the default Suspend policy freezes the process and never recovers.

wry already exposes WebViewBuilder::with_background_throttling with a
BackgroundThrottlingPolicy enum (Disabled / Suspend / Throttle), but
dioxus-desktop hasn't surfaced it through Config. Add a builder method
that stores the policy and apply it during WebView construction in
webview.rs alongside the existing with_background_color call.

When unset, behaviour is unchanged (wry falls back to its platform
default, which on macOS is effectively Suspend).
@goosewobbler goosewobbler requested a review from a team as a code owner May 26, 2026 15:47
goosewobbler added a commit to webdriverio/desktop-mobile that referenced this pull request May 26, 2026
Greptile flagged the [patch.crates-io] blocks using a moving branch
reference: a force-push or accidental commit on the fork branch would
silently change what CI compiles against. We don't commit Cargo.lock
for these fixtures so there's no second line of defence.

Switch from \`branch = "feat/..."\` to \`rev = "<sha>"\` in both
fixture Cargo.toml files, pinned to the same commit currently on
goosewobbler/dioxus@feat/desktop-background-throttling-policy-v0.7.9
(c03c394d3). Also add the upstream PR link
(DioxusLabs/dioxus#5587) to the fixture comment so future readers
can see exactly what's pending.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@goosewobbler
Copy link
Copy Markdown
Author

Note for reviewers: the failing Check | Minimum dependency versions job is unrelated to this PR's changes — it's the upstream rhai 1.24.0 / 1.25.0 min-versions issue that @Booyaka101 has already analysed in #5583. Same failure on current main (run 26470310750), and our PR added zero deps or version constraints (only packages/desktop/src/{config.rs,webview.rs}). Not addressable from this diff.

goosewobbler added a commit to webdriverio/desktop-mobile that referenced this pull request May 27, 2026
The previous rule overcorrected by banning all ticket/PR references.
The real distinction is whether the reference is load-bearing — an
issue link whose resolution removes the commented code is the opposite
of drift, it's an active signal to update.

Refine the Comments section:
- Keep the "no version numbers / transient stack traces / 'used to do X'"
  rule — these rot silently
- Add an explicit positive case for tracking refs that gate code removal,
  with a concrete example matching the upstream-issue-workaround pattern
  we use elsewhere in this repo (e.g. the dioxus-bridge throttling fix
  linked to DioxusLabs/dioxus#5587).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
goosewobbler added a commit to webdriverio/desktop-mobile that referenced this pull request May 27, 2026
The previous rule overcorrected by banning all ticket/PR references.
The real distinction is whether the reference is load-bearing — an
issue link whose resolution removes the commented code is the opposite
of drift, it's an active signal to update.

Refine the Comments section:
- Keep the "no version numbers / transient stack traces / 'used to do X'"
  rule — these rot silently
- Add an explicit positive case for tracking refs that gate code removal,
  with a concrete example matching the upstream-issue-workaround pattern
  we use elsewhere in this repo (e.g. the dioxus-bridge throttling fix
  linked to DioxusLabs/dioxus#5587).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit caad347)
goosewobbler added a commit to webdriverio/desktop-mobile that referenced this pull request May 28, 2026
…throttling (#294)

* chore: update package dependencies and configurations

- Bump versions of several packages in package.json, including `zod` to ^4.4.3 and `@types/node` to ^25.9.1 across various fixtures and packages.
- Update `electron-nightly` version in pnpm-workspace.yaml to 44.0.0-nightly.20260521.
- Adjust pnpm-lock.yaml to reflect the updated package versions and ensure consistency across the workspace.
- Add `vitest` as a dependency in multiple packages to enhance testing capabilities.

These changes ensure that the project is using the latest compatible versions of dependencies, improving stability and performance.

* chore: update package dependencies and versions

- Bump `@releasekit/release` to version `^0.23.0` in `package.json` and `pnpm-lock.yaml`.
- Upgrade `lint-staged` to version `^17.0.5` in `package.json` and `pnpm-lock.yaml`.
- Update `rollup-plugin-node-externals` to version `^9.0.1` in `packages/bundler/package.json`.
- Upgrade `happy-dom` to version `^20.9.0` in `packages/dioxus-bridge/package.json`.
- Update `@electron/packager` to version `^20.0.0` in both `packages/electron-service/package.json` and `packages/native-types/package.json`.
- Bump `puppeteer-core` to version `^25.0.4` in `packages/electron-service/package.json`.
- Upgrade `@puppeteer/browsers` to version `^3.0.3` in `packages/electron-service/package.json`.

These updates ensure compatibility with the latest features and improvements across the project.

* chore(native-utils): align tsx peer range with devDependencies

Bump peerDependencies.tsx from ^4.21.0 to ^4.22.3 to match the devDependency
floor. Although ^4.21.0 already satisfies 4.22.x, the older minimum allows
consumers to install patches we no longer actively test against.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(bundler): pin rollup-plugin-node-externals to ^8.x

v9.0.1 requires Node >=24, which breaks the Tauri package-test Docker
images (Ubuntu/Debian/Fedora/Arch — all intentionally on Node 20 LTS, see
arch.dockerfile for the Node 26 undici incompatibility note). The
workspace's stated engine (>=24.11.0) is for our dev toolchain, not the
distros where end users install built artefacts, so the package-build
chain still needs to run cleanly on Node 20.

v8.1.2 supports Node 18+ and is API-compatible for the way bundler uses
the plugin (single getConfig call in rollup.config.ts).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(electron-service): tighten engines for puppeteer-core v25

puppeteer-core v25 raised its minimum to Node 20.19 / 22+. The engines
field still allowed Node 18.12+ and 20.9.x, which are below the transitive
minimum. All puppeteer-core imports are type-only so consumers on older
Node versions don't crash at runtime, but the engines constraint should
match what the package actually supports.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(deps): pin @wdio/* and webdriverio at 9.27.0 in default catalog

9.27.1 regressed Dioxus E2E on macOS-ARM: executeScript calls time out
after 30s × 3 retries on a trivial polyfill wrapper, with the whole spec
suite cascading into timeouts after the first test. Linux and Windows
pass cleanly with 9.27.1 on the same commit, and main was green at
9.27.0 before this PR.

Pinning exact (rather than ^) so future patches don't quietly re-introduce
the regression. Catalog comment notes the reason to revisit. Fixture
package.jsons keep their direct ^9.27.1 specs — they simulate end-user
installs and should track latest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(fixtures/dioxus-app): pin @wdio/* + webdriverio to 9.27.0

The fixture's direct ^9.27.1 specs bypassed the default-catalog pin in
pnpm-workspace.yaml, so Package - Dioxus [macOS-ARM] would still resolve
webdriverio@9.27.1 transitively and hit the same executeScript-timeout
regression the catalog pin was added to avoid.

webdriverio is added as a direct devDep so the peer dependency
resolution can't pick up 9.27.1 from sibling fixtures (electron/tauri
package tests still pin ^9.27.1 — they don't exercise the Dioxus
embedded driver and pass cleanly on macOS-ARM).

Lift the pin alongside the catalog one when an upstream fix lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Revert "fix(deps): pin @wdio/* and webdriverio at 9.27.0"

The macOS-ARM Dioxus E2E (standard + multiremote) still fail with the
same first-spec-passes / spec-1+ -all-timeout pattern at 9.27.0. wdio is
not the cause. Unpin so the dep update PR's intent is preserved and the
real culprit can be hunted properly.

Also reverts fixtures/package-tests/dioxus-app pin (added in the same
attempt). Engines bump on packages/electron-service stays — that one is
unrelated to the Dioxus regression.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(diagnostic): reorder Dioxus standard specs to test position hypothesis

Move api.spec.ts (currently first alphabetically and the only spec that
passes on macOS-ARM CI) to last. If the failure is positional —
every spec after the first hangs — api.spec.ts will now fail in last
position and application.spec.ts (now first) will pass. Revert once
diagnosed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: revert diagnostic spec reorder

Diagnostic served its purpose: confirmed the macOS-ARM failure is
positional and triggered by tests that throw a JS error inside
browser.dioxus.execute() (invoke-rejection or thrown-error). The bridge
recovers cleanly from those locally; the hang is specific to GHA
macOS-ARM runners. Investigation continues in a separate change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(dioxus): harden script execute against WKWebView throw edge case + add invoke tracing

Two changes targeting the macOS-ARM CI hang where scripts that throw
inside browser.dioxus.execute poison the polling loop:

- packages/dioxus-service/src/commands/execute.ts: wrap the user function
  in `new Promise((resolve, reject) => { try { ... .then(resolve, reject); }
  catch (e) { reject(e); } })`. Any synchronous throw becomes an explicit
  promise rejection regardless of how the surrounding AsyncFunction body
  handles it. Speculative — covers a WKWebView-specific path where a sync
  throw inside a sync IIFE inside an AsyncFunction body might not propagate
  cleanly to the awaiting caller.

- packages/dioxus-bridge/src/invoke.rs: emit a tracing::debug line for
  every incoming invoke command. On CI, captured backend logs will show the
  last command processed before the hang, narrowing the failure window to
  the JS-side step that follows. wdio_dioxus_bridge=debug is already
  enabled in the e2e fixture's tracing filter so this surfaces in artifacts
  without additional plumbing.

Local M-series Mac runs the full standard E2E suite green in 17s with both
changes applied (was 19s without — no regression).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(deps,docs): align shipped Node range with parent webdriverio (>=18.20.0)

Two strands:

1. Bring the shipped Node range and the actual runtime deps back in line
   with what end users get from webdriverio. The repo had drifted into
   inconsistency (engines said one thing, deps required newer).

   - All engines fields set to `>=18.20.0` (matches webdriverio v9).
   - puppeteer-core ^25.0.4 → ^24.41.0 in @wdio/electron-service runtime
     deps (v25 requires Node 22.12+; v24 supports Node 18+).
   - @electron/packager ^20.0.0 → ^18.4.4 in @wdio/electron-service
     runtime deps (v19+ require Node 22.12+; v18 supports Node 16.13+).
     Only `allOfficialArchsForPlatformAndVersion` + `OfficialArch` types
     are used — both exist in v18, no API drift, full test suite passes.
   - pnpm.overrides for puppeteer-core: ^24.41.0 — without it pnpm hoists
     v25 to satisfy webdriverio's puppeteer-core peer range
     `>=22.x || <=24.x`, which is effectively unconstrained because of
     the OR. Drop the override when WDIO v10 lands and we bump our floor.
   - devDeps untouched: contributor toolchain (vitest, eslint, lint-staged,
     jsdom, ora, @inquirer/prompts, @releasekit/release, @electron/packager
     in native-types devDeps, @puppeteer/browsers in electron-service
     devDeps) can require Node 20+ — they don't ship to end users.

2. Documented the policy in CONTRIBUTING.md + cross-refs from README.md,
   docs/setup.md, AGENTS.md. The strategy section is version-agnostic:
   points at the engines field per package.json as the source of truth,
   no specific Node/dep versions baked into prose. Contributors keep the
   concrete "Node 24 LTS" recommendation at the install step.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(dioxus-bridge): instrument polling loop catches to surface silent throw

CI traces on macOS-ARM show the polling loop's cadence flipping from
~13ms (normal empty-queue path) to ~100ms (outer-catch back-off)
immediately after a script that rejects via invoke() completes. The
outer catch is silent (`} catch {`), so we have no visibility into what
it's catching. ~500ms later the loop dies entirely.

Add a console.warn in the outer catch + in the result-delivery catch
capturing the actual error string. console.warn is already wrapped to
forward to invoke('log_frontend') fire-and-forget, so the lines land in
captured backend logs without further plumbing. Drop these once
root-caused.

Local smoke test: full standard E2E still passes in 19s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(dioxus-bridge): add independent heartbeat to prove JS aliveness

CI diagnostic from the previous push showed zero log_frontend invokes
reaching Rust after the polling loop appeared to die — telling us that
console.warn from inside the loop's outer catch either never fired OR
its underlying fetch hung too. Either way the in-loop diagnostic can't
differentiate "loop died but JS alive" from "whole webview wedged".

Add a setInterval(2s) heartbeat outside the polling loop's lifecycle. It
fires `void invoke('__diag', 'heartbeat #N')` fire-and-forget. Combined
with the existing __embedded_poll trace, the next failing run tells us:

- heartbeats keep arriving after polls stop → loop died, JS+fetch alive
  (problem is in the loop's await chain)
- heartbeats also stop → JS frozen or fetch globally broken (problem is
  in webview / wdio:// scheme handler)

Wire __diag handler in dioxus-bridge that just emits tracing::warn to
the wdio_dioxus_bridge::diag target. Drop both once root-caused.

Local smoke test: full standard E2E still passes in 19s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(dioxus-e2e): fail-fast settings to speed up macOS-ARM diagnostic loop

Standard run was taking ~64 min in CI because every failing spec waited
its full retry window (~120s) then moved on to the next, cascading
through all 7 remaining specs. With root-causing in progress that's a
~hour feedback cycle per push.

Three settings tightened, all clearly marked as diagnostic-only:

- bail:1 → stop after first spec failure (we already know they all
  cascade, so subsequent failures add nothing new)
- connectionRetryCount:0 → WDIO no longer retries failed commands 3×,
  so a hung executeScript fails in ~5s instead of ~90s
- mochaOpts.timeout:30000 → match the new script-window so the test
  itself fails as soon as the script gives up
- timeouts.script:5000 on each capability → embedded driver's per-session
  Axum-side script_timeout drops from default 30s to 5s

Net: failing CI run should complete in <5 min instead of ~64 min.
Local 8-spec passes in 17s with the new settings (was 19s — diagnostic
overhead is noise).

Revert all four together when the root cause is found.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(dioxus-e2e): suppress macOS App Nap on Dioxus E2E to test process-freeze hypothesis

CI diagnostic showed both the polling loop AND an independent setInterval
heartbeat stop firing at the exact same instant during spec-boundary
worker restarts on macOS-ARM runners. That points to the whole Dioxus app
process being suspended by macOS (App Nap / process throttling), not a
bug in the loop or the bridge fetch chain.

Why only Dioxus + macOS:
  * Dioxus uses an in-process embedded WebDriver (Axum + JS polling loop
    in the host app). When the app naps, both freeze with it.
  * Tauri uses external tauri-driver + WebKitWebDriver, which signal
    automation context to macOS and keep their target webview awake.
  * macOS App Nap criteria — no visible/focused windows, no user input,
    system idle — are always met on a headless CI runner.

Two suppressions on the macOS branch of the Dioxus E2E command:
  * NSAppSleepDisabled=YES propagates to the spawned app process and
    disables App Nap entirely for the test duration.
  * caffeinate -dims wraps the pnpm command to prevent display/idle/disk/
    system sleep over its lifetime.

If heartbeat #3+ now fires and tests pass, the App Nap hypothesis is
confirmed and we move to a proper fix (wry config or driver-side
keep-alive signal). If they still fail, we're back to the process-state
debugger.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(dioxus-bridge): silent AudioContext to defeat WKWebView background throttling

App-process-level workaround (NSAppSleepDisabled + caffeinate) didn't fix
the macOS-ARM Dioxus E2E hang in the previous run — both polls AND the
independent setInterval heartbeat still stopped at the same instant.
That rules out OS-managed App Nap and points to **WKWebView's WebContent
process throttling**, which is a WebKit-internal mechanism unaffected by
OS-level sleep assertions.

WebKit suspends the WebContent process running JS when the host window
isn't visible/focused — always true on a headless CI runner. Tauri
escapes this because tauri-driver/WebKitWebDriver signals automation
context to WebKit. Dioxus's embedded driver has no such external signal.

Workaround: start a silent AudioContext oscillator at bridge load when
the embedded driver is active. WebKit classifies pages with active audio
graphs as "playing media" and exempts them from background throttling.
Zero audible output (gain 0). Gated on __WDIO_EMBEDDED_PORT so end-user
apps without the embedded driver never run this.

Diagnostic invokes added: __diag tracks `audio-keepalive started
(state=...)` so the next CI log shows whether the AudioContext actually
started or was deferred by autoplay policy. Either way, the oscillator
ties up the hardware-output graph which is enough to defeat throttling
even when the context is suspended.

Long-term fix is to expose `Config::with_background_throttling` upstream
in Dioxus (which wraps wry's BackgroundThrottlingPolicy::Disabled). PR
to dioxus pending. The JS workaround stays until that lands and we can
bump dioxus across our test fixtures.

Local 8-spec passes in 18s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(dioxus-bridge): switch keepalive from AudioContext to muted HTMLAudioElement

Last CI run confirmed the throttling diagnosis: `audio-keepalive started
(state=suspended)` — the AudioContext was created but blocked by WebKit's
autoplay policy, so the oscillator never actually played and no media
exemption was granted. Same 2-heartbeat death pattern persisted.

Muted HTMLMediaElement autoplay is more permissive in WKWebView than
audio-graph playback. Switch to a looping `<audio>` element pointing at a
small base64-encoded silent WAV data URL, with `muted=true volume=0`.
Reports `audio-keepalive started (playing=true, muted=true)` if it
actually starts, or `play() rejected: <name>: <msg>` if blocked.

If this run still shows heartbeats stopping at #2, autoplay is also
blocking muted HTMLMediaElement and we need to either (a) fork dioxus to
expose `Config::with_background_throttling`, or (b) configure
`mediaTypesRequiringUserActionForPlayback` via wry — neither of which
is exposed in Dioxus's current Config API.

Local 8-spec passes in 15s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(dioxus-bridge): disable WKWebView background throttling under automation

Root cause confirmed across four CI iterations: WKWebView's WebContent
process (the process running page JS) gets fully suspended when the host
window is not visible/focused, which is always the case on a headless
macOS-ARM runner. Both the embedded driver's polling loop AND an
independent setInterval heartbeat freeze at the same instant, every
time, at a spec boundary. Tauri E2E is unaffected because tauri-driver/
WebKitWebDriver signals automation context to WebKit externally and
keeps the view awake.

Proper fix uses wry's BackgroundThrottlingPolicy::Disabled to tell
WKWebView never to suspend the view. dioxus-desktop didn't expose this
yet — patched the fork (goosewobbler/dioxus, branch
feat/desktop-background-throttling-policy-v0.7.9) to add
Config::with_background_throttling. Upstream PR to follow.

Bridge calls this automatically when automation::is_requested() returns
true (set by @wdio/dioxus-service via DIOXUS_WEBVIEW_AUTOMATION=true).
End-user apps that don't set this env var keep the default behaviour.

Workspace-side changes:
- packages/dioxus-bridge/src/lib.rs: call config.with_background_throttling
  (Disabled) when DIOXUS_WEBVIEW_AUTOMATION is set
- packages/dioxus-bridge/guest-js/index.ts: drop the silent-audio
  workaround; the proper fix replaces it. Heartbeat kept for one more CI
  cycle so we can verify #3+ now fires.
- fixtures/e2e-apps/dioxus/Cargo.toml: [patch.crates-io] block pointing
  the dioxus family (dioxus, dioxus-core, dioxus-desktop) at the fork
  branch. Whole family patched (not just dioxus-desktop) because Cargo
  treats crates.io dioxus-core != git-fork dioxus-core even at the same
  version, causing type-identity mismatches across the dep tree.
- fixtures/package-tests/dioxus-app/src-dioxus/Cargo.toml: same patch.

Local 8-spec passes in 15s. Drop the [patch.crates-io] block when an
upstream dioxus-desktop release includes the API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(dioxus-bridge): gate with_background_throttling call behind Cargo feature

Build Dioxus Crates CI job was failing because standalone `cargo check`
on wdio-dioxus-bridge doesn't inherit the binary apps' [patch.crates-io]
block — it sees the published dioxus-desktop@0.7 which doesn't have
Config::with_background_throttling. Error E0599: no method named
`with_background_throttling` found for struct `dioxus_desktop::Config`.

Gate the call behind a new optional `with-background-throttling` Cargo
feature on wdio-dioxus-bridge:
  - Off by default → standalone bridge check sees no call, builds clean
    against published dioxus-desktop.
  - Enabled by the binary apps that include the [patch.crates-io] block
    that points dioxus-desktop at the fork with the API.

Wired through wdio-dioxus-embedded-driver as a forwarding feature so the
binary apps only need to enable one feature on their direct driver dep:

    wdio-dioxus-embedded-driver = { path = "...", features = ["with-background-throttling"] }

Enabled on the two binary apps that have the patch:
  - fixtures/e2e-apps/dioxus
  - fixtures/package-tests/dioxus-app/src-dioxus

Drop the feature gate when upstream Dioxus releases the API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(dioxus-service): set DIOXUS_WEBVIEW_AUTOMATION on embedded driver spawn

Last CI confirmed the bridge's automation gate was returning false:
  > wdio_dioxus_bridge: DIOXUS_WEBVIEW_AUTOMATION not set — automation disabled

…so the background-throttling fix never executed and the WebContent
process froze at spec 0-1 as before. The env var was only being set by
the external driver path (packages/dioxus-driver/src/webdriver.rs sets
it on the WebKitWebDriver / msedgedriver subprocess that then propagates
to the launched app). The embedded driver path has no external driver
subprocess — the launcher spawns the app binary directly — so the env
var was never set there.

Add it to the env block in providers/embedded.ts so the bridge's
automation::is_requested() returns true and
config.with_background_throttling(Disabled) actually fires.

Comment in dioxus-driver/src/webdriver.rs already names the bridge as
the consumer; this commit just closes the gap on the other path.

Local 8-spec passes in 14s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(dioxus): drop diagnostic scaffolding now throttling fix is verified

CI is green end-to-end with the wry background-throttling fix in place
on macOS-ARM. Reverts the temporary diagnostic layer:

- packages/dioxus-bridge/guest-js/index.ts: drop the heartbeat
  setInterval and the verbose console.warn calls in the poll-loop
  catches. The polling loop is back to silent catches now that the
  root cause (WKWebView WebContent throttling) is fixed.
- packages/dioxus-bridge/src/invoke.rs: drop the per-invoke
  tracing::debug! that was added to trace where the loop was stalling.
- packages/dioxus-bridge/src/lib.rs: drop the __diag fire-and-forget
  Rust command (only consumer was the heartbeat).
- e2e/wdio.dioxus-embedded.conf.ts: restore default mocha timeout
  (60s, matching sibling wdio.dioxus.conf.ts), drop bail:1, restore
  connectionRetryCount:3, drop per-capability timeouts.script:5000.
- .github/workflows/_ci-e2e-dioxus-all-providers.reusable.yml: drop
  the macOS-only caffeinate -dims + NSAppSleepDisabled wrapper. App
  Nap turned out not to be the root cause — wry background throttling
  was the real one, and that's fixed at the wry level now.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(dioxus-bridge): move throttling fix from bridge to app level

The published bridge crate previously exposed a `with-background-throttling`
Cargo feature that, when enabled, made `install()` call
`Config::with_background_throttling(Disabled)`. That method only exists in
our forked dioxus-desktop, so the feature ties the bridge's published
API surface to an upstream-pending PR — and means end users who hit the
freeze on macOS-ARM CI have to (a) patch dioxus and (b) enable a feature
flag on the bridge.

Move the call to the app's `main.rs` instead:

- packages/dioxus-bridge: drop the `with-background-throttling` feature
  and its call site in `install_with_registry_and_config`. Bridge is now
  agnostic to the throttling API.
- packages/dioxus-embedded-driver: drop the forwarded feature; re-export
  `wdio_dioxus_bridge::automation` so apps depending only on this crate
  can still gate behaviour on `DIOXUS_WEBVIEW_AUTOMATION`.
- fixtures/e2e-apps/dioxus + fixtures/package-tests/dioxus-app: drop the
  feature opt-in; add the throttling call directly in `main.rs`, gated on
  `wdio_dioxus_embedded_driver::automation::is_requested()`. The
  `[patch.crates-io]` block stays in the fixtures (only there) until the
  upstream Dioxus PR lands.

End users hitting the issue now copy a 3-line snippet into main.rs and
patch dioxus themselves. Once upstream lands, the patch goes away and the
snippet still works against published dioxus-desktop — no bridge churn.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(deps): address Greptile review on PR #294

- packages/native-types: align @electron/packager to ^18.4.4. v19+
  requires Node >=22.12.0 (ESM-only); the rest of the PR deliberately
  downgrades electron-service to the same line for Node 18 support.
- fixtures/package-tests/dioxus-app: switch @wdio/* devDeps from
  hard-pinned ^9.27.1 to catalog:default so the fixture inherits the
  workspace catalog version rather than diverging.
- pnpm-lock.yaml: regen for the above.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(dioxus-fixtures): pin [patch.crates-io] to a commit SHA

Greptile flagged the [patch.crates-io] blocks using a moving branch
reference: a force-push or accidental commit on the fork branch would
silently change what CI compiles against. We don't commit Cargo.lock
for these fixtures so there's no second line of defence.

Switch from \`branch = "feat/..."\` to \`rev = "<sha>"\` in both
fixture Cargo.toml files, pinned to the same commit currently on
goosewobbler/dioxus@feat/desktop-background-throttling-policy-v0.7.9
(c03c394d3). Also add the upstream PR link
(DioxusLabs/dioxus#5587) to the fixture comment so future readers
can see exactly what's pending.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Revert "chore(deps): switch dioxus-app fixture to catalog:default"

Greptile's suggestion to use catalog references doesn't fit this
fixture's purpose: package-test apps run in an isolated install
environment that doesn't resolve pnpm catalog: links. All sibling
package-test apps (electron-script, electron-builder, electron-forge,
tauri-app) use explicit ^9.27.1 strings for the same reason — restore
the same pattern here.

This reverts only the catalog change from 003847f; the @electron/packager
downgrade in that commit stays.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(biome): update schema version to 2.4.15 in biome.jsonc

* chore: gitignore Dioxus app build dirs

Add the two Dioxus fixture target/ paths in the same section style as
the existing Tauri pattern:

- **/src-dioxus/target/* — matches the package-test app layout
  (fixtures/package-tests/dioxus-app/src-dioxus/target/)
- fixtures/e2e-apps/dioxus/target/ — explicit path for the e2e app
  which doesn't use the src-dioxus wrapper

Verified via git check-ignore that both targets now match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(forge-fixtures): pin Electron back to 41.2.0 to test hypothesis

CI failures since this PR are concentrated on forge fixtures (both e2e
and package-tests). Builder fixtures got the same Electron 41 → 42
bump and are NOT failing, so the regression is Electron-42-specific
to Forge — likely a Forge 7.11.2 + Electron 42 packaging interaction
(7.11.2 changelog includes a TTY-detection fix and a lodash → eta
templating swap, either could explain the silent abort at
"❯ Finalizing package" we see in build logs).

Pin Electron back to 41.2.0 in the three forge fixtures to isolate
the variable:
- fixtures/e2e-apps/electron-forge (was catalog:default → 42.2.0)
- fixtures/package-tests/electron-forge-app-cjs (was 42.2.0)
- fixtures/package-tests/electron-forge-app-esm (was 42.2.0)

If CI goes green on forge, Electron 42 is confirmed as the trigger.
Then we have options: keep this pin, file upstream issue, or bump
when Forge 7.11.3 lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* revert(deps): pin Electron back to 41.2.0 across all fixtures + catalog

Electron 42 + Forge 7.11.2 combo broke forge packaging (silent abort
at "❯ Finalizing package"). Earlier partial pin (just forge fixtures
to 41.2.0) caused a chromedriver version mismatch — the workspace
catalog still on 42.2.0 → wdio downloaded chromedriver 148 → fixture
Electron 41 ships chromedriver 138 → driver/binary mismatch on launch
(see run 26501340841 job 78042413833).

Revert the whole Electron bump in this PR:
- pnpm-workspace.yaml catalog: 42.2.0 → 41.2.0
- fixtures/e2e-apps/electron-forge: explicit 41.2.0 → catalog:default
  (catalog is now 41.2.0 again, so this stays consistent with sibling
  e2e fixtures that all use catalog:default)
- fixtures/package-tests/electron-builder-app-{cjs,esm}: 42.2.0 → 41.2.0
- fixtures/package-tests/electron-forge-app-{cjs,esm}: already at
  41.2.0 from previous commit

Electron 42 bump can be retried as a separate PR once Forge 7.11.3
lands with a fix, or with a workaround verified.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* revert(deps): pin script package-tests Electron back to 41.2.0 too

Missed these in the last revert pass — script package-tests fixtures
also bumped from 41.2.0 to 42.2.0 in this PR. Pin them back to keep
the entire Electron revert consistent across all fixtures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* pin(forge): exact 7.11.1 across all fixtures (no caret)

Forge 7.11.2 introduced a TTY-detection fix (#4219) and a lodash → eta
templating swap (#4208) that broke our forge fixtures' packaging in CI
(silent abort at "❯ Finalizing package", no output written). Reverting
Electron 41 → 42 didn't fix it on its own because pnpm still resolved
to 7.11.2 via the `^7.11.2` ranges.

Pin Forge to exact `7.11.1` everywhere — no caret, no patch float —
across:
- fixtures/e2e-apps/electron-forge
- fixtures/package-tests/electron-forge-app-{cjs,esm} (5 packages each:
  cli + maker-deb/rpm/squirrel/zip)
- e2e/package.json

Per maintainer note: certain build-tool deps (Forge especially) need
exact pins. Floating patches have repeatedly bitten us with silent
behaviour changes that are hard to diagnose. Bump deliberately when
we've validated a new version works, not as a side effect of pnpm
install.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ci): disable turbo cache for forge e2e build + catalog cleanup

Two changes:

1. Disable turbo cache for `electron-forge-e2e-app#build` and
   `electron-forge-e2e-app#build:mac-universal`. The turbo cache key for
   per-package overrides doesn't pick up changes from the default build
   task's inputs, so a one-time silent-fail Forge build keeps getting
   served from cache across runs (latest hash 3ea815c66ebbcb29 — every
   Linux forge e2e job hits it and the `out/` is empty, then "Setup
   Protocol Handlers" can't find the binary). Disabling cache for this
   one task means each CI run rebuilds fresh — ~10s cost, worth it to
   guarantee no stale empty cache.

2. Catalog cleanup:
   - Add `@electron-forge/cli: 7.11.1` and `electron-builder: 26.8.1`
     to the default catalog. e2e + e2e-apps fixtures now use
     `catalog:default` instead of duplicating the version string.
     Package-tests fixtures keep explicit pins (per the isolated-install
     rule documented in feedback memory).
   - Remove carets from all `@wdio/*` and `webdriverio` catalog entries.
     Floating patches have caused intermittent CI breakages — version
     bumps should be deliberate, not a side effect of `pnpm install`.
     Same rule we're now applying to forge.

Net: 3 catalog refs replace 4 explicit pins (e2e + e2e-apps forge cli;
e2e-apps builder). Package-tests fixtures unchanged. Catalog itself
gains 2 entries (forge, builder) and loses 8 carets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: pin Node to 24.15.0 to avoid 24.16.0 packager regression

GitHub Actions runner images (ubuntu24/20260525.x, win25-vs2026/20260525.x,
macos-15/20260525.x) bumped Node.js from 24.15.0 to 24.16.0 on May 26.
Since that rollout, every CI run on this branch fails cross-platform with
@electron/packager silently exiting at "Finalizing package" — clean exit 0,
no output, dist-electron empty, downstream "Could not find Electron app".

Same exact commit 93354e2 was green pre-rollout and fails post-rollout.
Workspace lockfile is frozen, so the only relevant moving part is the
runner toolchain.

Node 24.16.0 changes touching the subprocess hot path:
- src: coerce spawnSync args to string once (#62633)
- process: handle rejections only when needed (#62919)

Pin until upstream confirms a fix in @electron/packager or Node.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: pin Node 24.15.0 across all workflow callers

Follow-up to 92f7af6 — every workflow that calls setup-workspace or
setup-node passes node-version: '24' explicitly, which overrode the
default I just pinned. Bulk-update all 23 callers to '24.15.0' so the
pin actually takes effect.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(switch-catalog): parse unquoted workspace entries + expand globs

The catalog-switch script was silently broken:

1. The regex `/^-\s*['"]|['"]$/g` only stripped the `- ` prefix when
   workspace entries were quoted (`- 'foo'`). Our pnpm-workspace.yaml
   uses unquoted entries (`- foo`), so every path kept the literal
   `- ` prefix and fs.existsSync failed — script warned on every entry
   and updated nothing. Replace with two separate replaces: strip
   leading `- ` unconditionally, then strip optional surrounding
   quotes.

2. The script also iterated `packages/*` and `examples/*` glob entries
   as literal paths. Add a small `expandWorkspacePath` helper that
   expands trailing `/*` to the actual subdirectories (the only glob
   pattern this workspace uses).

3. Suppress the "no package.json" warning for Rust-only workspace
   packages (Cargo.toml, no package.json) — those are expected and
   shouldn't be flagged.

Verified: `pnpm catalog:default` now correctly updates 7 packages,
zero warnings, and triggers `pnpm install` to apply the change.

Also picks up the JS-side ripple: `e2e/package.json` and
`packages/native-utils/package.json` got re-sorted by the script when
it found that some of their deps weren't on the catalog reference yet
(harmless — the script writes sorted output even for "no changes" runs
once it touches the file, which it now correctly does).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 1b3e5dd)

* catalog: add electron-builder + forge to next + minimum catalogs

Without entries in the non-default catalogs, the `pnpm catalog:next`
and `pnpm catalog:minimum` switch script writes broken refs into
package.json (pnpm doesn't fall back to default when a `catalog:<name>`
ref is missing — it errors). Mirror the default values into both:

- minimum: same as default for both (we don't currently test against
  older forge/builder lines, and these tools' versions don't materially
  affect what we exercise — we run against the built binary)
- next: electron-builder uses its `next` dist-tag; forge has no `next`
  tag on npm, so it stays at the default exact pin

Catalog switching is a local dev tool (not used in CI), so this is
quality-of-life, not a blocker.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 6a3214f)

* fix(switch-catalog): fall back to default for deps missing in target catalog

Switching to a non-default catalog (e.g. `pnpm catalog:minimum`) used
to blindly write `catalog:<target>` for every dep in the default
catalog, even when that dep didn't exist in the target. pnpm doesn't
fall back to default for missing refs — it errors out. So any
default-only dep (e.g. @wdio/xvfb, which is v9-only with no v8 to
populate the `minimum` catalog) made `pnpm catalog:minimum` fail.

Fix at the script level rather than padding non-default catalogs with
duplicate entries: parse all catalogs into a Map<name, Set<dep>>, and
for each switch, check if the target catalog has the dep. If it does,
write `catalog:<target>`; if not, write `catalog:default` as a
fallback. End state: a package switched to `minimum` ends up with a
mix of `catalog:minimum` refs (for v8-supported deps) and
`catalog:default` refs (for new-since-v8 deps like @wdio/xvfb), which
is semantically what you want for minimum-version testing.

Also drop the placeholder forge/builder entries that were just added
to `minimum`/`next` — no longer needed since the script handles the
missing-in-target case. Future predictable hits (e.g. @wdio/xvfb →
@wdio/display-server rename in WDIO v10) are now automatic.

Verified: round-trip `default → minimum → default` produces zero diff,
and `e2e/package.json` correctly ends up with @wdio/xvfb on
`catalog:default` during the `minimum` step.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 01a76af)

* docs(agents,catalog): tighter comment rules + drop redundant catalog comments

- pnpm-workspace.yaml: drop two comments that either restated the code
  (the "pinned exact" block above @wdio/*) or coupled rationale to a
  specific version that will drift ("Forge 7.11.2 silently broke ..."
  above the build-tool block). Rephrase the non-default-catalog comment
  to drop the @wdio/xvfb-specific example, which would need updating
  the moment the package is renamed.
- AGENTS.md Comments section: spell out the default-no-comments stance,
  the "don't restate what code says" rule, and the "don't couple comments
  to drift-prone details like version numbers, file paths, ticket refs,
  or PR/commit names" rule. Show the rationale-vs-citation contrast
  inline so future contributors (humans and LLMs alike) get the spirit
  of the rule, not just the letter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 1c4b32d)

* docs(agents): allow load-bearing tracking refs in comments

The previous rule overcorrected by banning all ticket/PR references.
The real distinction is whether the reference is load-bearing — an
issue link whose resolution removes the commented code is the opposite
of drift, it's an active signal to update.

Refine the Comments section:
- Keep the "no version numbers / transient stack traces / 'used to do X'"
  rule — these rot silently
- Add an explicit positive case for tracking refs that gate code removal,
  with a concrete example matching the upstream-issue-workaround pattern
  we use elsewhere in this repo (e.g. the dioxus-bridge throttling fix
  linked to DioxusLabs/dioxus#5587).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit caad347)

* Revert "chore(deps,docs): align shipped Node range with parent webdriverio (>=18.20.0)"

This reverts de68a0b. CI failures concentrated on Windows started
appearing after de68a0b — most likely from the puppeteer-core
^25.0.4 → ^24.41.0 downgrade interacting with Windows runner AV
behaviour, but a full bisect wasn't worth the cycles given the
strategic context.

@wdio/electron-service@10.0.0 already shipped without Node 18 support,
so the Node 18 alignment work in de68a0b was bridging to a promise we
couldn't keep anyway. Cleaner to revert and wait for WDIO v10 to
formally bump the floor to Node 20+, at which point we align and stay
aligned.

Reverts:
- engines fields across 14 packages
- @wdio/electron-service runtime deps: puppeteer-core, @electron/packager
- pnpm.overrides for puppeteer-core
- The `## Node version support` section in CONTRIBUTING.md and its
  cross-refs in README.md / docs/setup.md / AGENTS.md

Kept (small): a one-line callout in CONTRIBUTING.md and docs/setup.md
distinguishing contributor toolchain Node from end-user Node, pointing
at the `engines` field as source of truth. This distinction is useful
regardless of which Node line we eventually align with.

Follow-up issue tracking the alignment plan: filed separately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit e228e8e)

* fix(fixtures): replace shx-based clean:dist with tsx helper

shx rm -rf ENOENT-errors on Windows when the target doesn't exist —
seen in a recent CI run where Forge's e2e-app build had partial output
state (out/ present, dist/ missing) and the BuildManager's pre-build
clean:dist step crashed trying to scandir the missing dist/.

Replace the three e2e-apps electron fixtures' clean:dist scripts with
a small tsx helper that uses Node's native fs.rmSync({ force: true,
recursive: true }) — which handles missing paths cleanly across
platforms and avoids the pnpm dlx shx download dance entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 2c06d80)

* fix(fixtures): drop spaces in Forge productName on package-tests fixtures

Win-only package-test Forge silent-finalize bug: Forge stops at
"❯ Finalizing package" and exits 0 without writing the .exe — fingerprint
of @electron/packager's Windows pipeline (rcedit, signtool, asar)
choking on paths with spaces in productName. The fixture set
packagerConfig.name to "Forge App Example", which made the output
path "dist-electron/Forge App Example-win32-x64/...".

The e2e Forge fixture has no name/executableName override — it lets
Forge default to the package.json name (no spaces) and packages
cleanly on Windows. Mirror that approach here: drop both
packagerConfig.name and packagerConfig.executableName. Output paths
become "dist-electron/electron-forge-app-example-{cjs,esm}-win32-x64/..."
and wdio-electron-service auto-detects them.

Tests are unaffected: the only "Forge App Example" assertion is on
the renderer HTML <title>, which lives in src/renderer/index.html and
doesn't depend on Forge's productName. The package.json name
assertion (`html.toContain('electron-forge-app-example')`) was already
passing against the package.json name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 653d30c)

* ci: exclude temp paths from Windows Defender in setup-workspace

Two recurring Windows-only CI flakes share the same root cause: Defender
real-time scanning intercepts .exe writes by build/extraction tools, the
tool exits 0 but the destination directory ends up missing the
executable.

- chromedriver: "browser folder (.../chromedriver/win64-X.Y.Z) exists
  but the executable (chromedriver-win64/chromedriver.exe) is missing"
  during @puppeteer/browsers extraction
- Forge: Forge logs through "❯ Finalizing package" and exits 0 without
  writing the app .exe, downstream "Could not find Electron app built
  with Electron Forge!"

Adding Defender exclusions for the temp/runner-temp/workspace paths
and the test processes addresses both at the source. GitHub-hosted
Windows runners run as admin so Add-MpPreference works without elevation.
Runner is ephemeral so the lowered security posture is bounded to the
job lifetime.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit c4fd55b)

* docs(ci): trim Defender exclusion comment

Drop the package names and verbatim error strings — both drift if
upstream tools rename or rephrase. Keep the WHY (Defender intercepts
.exe writes during build/extract).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(cherry picked from commit 860191e)

* fix(ci): override yauzl@3.3.1; drop Node 24.15.0 pin

Root cause confirmed via electron/forge#4277: extract-zip > yauzl@2 is
broken on Node 24.16+. The Forge maintainer recommends overriding to
yauzl@3.3.1 until extract-zip is replaced by an internal lib.

Switching from "pin Node" to "fix the actual broken dep":
- Add pnpm override: yauzl: ^3.3.1
- Revert all 23 node-version pins back to '24' (latest)
- Restore setup-workspace default to '24'

This is the cleaner fix — published @wdio/electron-service consumers
don't need to pin Node, and the workaround disappears when extract-zip
is replaced upstream.

See:
- electron/forge#4277 (Forge's report + fix recommendation)
- electron/electron#51619 (Electron's tracking issue)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: drop Windows Defender exclusion step

Added during the yauzl@2 + Node 24.16 debug as a guess; the real fix
is the yauzl@3.3.1 override (a1be02d). Never observed Defender
actually interfering with a build — removing per "don't add code for
hypothetical scenarios".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(electron-service): downgrade @electron/packager to ^18.4.4

@electron/packager v19+ require Node 22.12+, but electron-service's
engines.node still declares ^20.19.0 || >=22.12.0 — so the Node 20
code path was broken in practice on both main (v19.0.5) and this PR
(v20.0.0).

Downgrade to ^18.4.4 (Node 16.13+) to:
- Align with electron-service's declared Node range
- Match native-types which already pins ^18.4.4 for type imports
- Match what Forge bundles transitively at runtime (18.4.4 via
  forge 7.11.1/.2)

binaryPath.ts imports allOfficialArchsForPlatformAndVersion — a stable
utility unaffected by the v18→v20 packager rewrites.

The Node-version-policy question (drop Node 20? when?) belongs in #297.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(electron-service): drop Node 20 from engines.node

Several direct deps already require Node 22.12+:
- puppeteer-core@25 (>=22.12.0)
- @electron/fuses@2.1.0 (>=22.12.0)
- @puppeteer/browsers@3.0.3 (>=22.12.0)

The ^20.19.0 bracket in engines.node was unreachable in practice on
both main and this PR — pnpm fails the engine check against any of
the above before npm install completes. Tighten to >=22.12.0 to
match what actually works.

Leaving @electron/packager at ^18.4.4 (separate commit 94e7428) for
its independent value: alignment with native-types' type-import dep
and with what Forge bundles transitively at runtime.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(dioxus-service): refresh execute.ts wrapper comment

The comment claimed the wrapper body MUST be synchronous and pointed
at executeAsync — both stale since the Promise wrapper landed (the
body still has no top-level await, but does return a Promise).

Replace with a description of how each driver path awaits the returned
Promise: embedded via guest-js's AsyncFunction polling loop, external
via the W3C executeScript spec contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <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.

feat(desktop): expose Config::with_background_throttling to opt out of WKWebView WebContent suspension

1 participant