From 43c1dd95b214eb0978425ce5142051b34937d787 Mon Sep 17 00:00:00 2001 From: marinom2 Date: Wed, 29 Jul 2026 10:55:33 +0300 Subject: [PATCH 1/2] fix(desktop): stop WebKitGTK falling off the fast path on hybrid-GPU Linux The desktop app is slow on an ordinary Linux desktop-with-a-GPU, and it looks like a weak machine rather than a bug. WebKitGTK hands rendered frames to the compositor as DMA-BUFs. When the box has two DRM devices - an Intel iGPU driving `modesetting` alongside a discrete NVIDIA card - the import fails and WebKit quietly falls back instead of surfacing an error. The window still draws, so the only symptom is that everything feels heavy. Measured on an RTX 5060 Ti + HD 530, X11, WebKitGTK 2.52.3, idle window at 3840x2160: the web process burns 2.7-14.3% CPU with the renderer enabled and 0.0% with it disabled. Hardware GL was fine throughout (direct rendering yes, OpenGL 4.6) - the GPU was never the problem, the buffer handoff was. Set only when the operator has expressed no preference, so anyone whose stack handles DMA-BUF properly can opt back in with WEBKIT_DISABLE_DMABUF_RENDERER=0. Also adds a `cargo check` job, because nothing in CI compiles this crate today: release.yml builds it, but only on a `v*` tag, so a Rust change can sit on main until someone tries to cut installers and discovers it there. This commit would itself have been unverified without it. --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ desktop/src-tauri/src/main.rs | 24 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e54821..258dca2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,33 @@ jobs: # Keep the standalone SDK compiling (it's excluded from the app tsconfig). - run: npx tsc --noEmit -p sdk/tsconfig.json + desktop: + name: Desktop (cargo check) + runs-on: ubuntu-latest + # The Tauri backend is the one part of this repo that nothing compiles until + # a release is cut: release.yml builds it, but only on a `v*` tag. So a Rust + # change can sit on main for weeks and first break at the moment you try to + # ship installers. `cargo check` is the cheap half of that build - it type- + # checks and borrow-checks without linking or bundling - so it catches the + # error where it is still a one-line fix. + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + workspaces: desktop/src-tauri + # tauri's build script links against the system webview; without these the + # crate fails to configure and the check never reaches our code. + - name: System deps for the webview + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libwebkit2gtk-4.1-dev libsoup-3.0-dev librsvg2-dev \ + libjavascriptcoregtk-4.1-dev build-essential pkg-config + - name: cargo check + working-directory: desktop/src-tauri + run: cargo check --locked --all-targets + e2e: name: E2E (Playwright) runs-on: ubuntu-latest diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index 042f2e8..41fdfa5 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -371,7 +371,31 @@ fn notify(app: AppHandle, title: String, body: String) -> Result<(), String> { .map_err(|e| e.to_string()) } +/// Work around WebKitGTK's DMA-BUF renderer on hybrid-GPU Linux boxes. +/// +/// WebKitGTK hands rendered frames to the compositor as DMA-BUFs. On a machine +/// with two DRM devices - an Intel iGPU driving `modesetting` alongside a +/// discrete NVIDIA card, which is the ordinary desktop-with-a-GPU arrangement - +/// that import fails and WebKit drops to a fallback path instead of reporting +/// an error. The window still draws, so it reads as "the app is slow" rather +/// than as a bug. Measured on an idle 3840x2160 window (RTX 5060 Ti + HD 530, +/// X11, WebKitGTK 2.52.3): 2.7-14.3% CPU in the web process with the renderer +/// enabled, 0.0% with it disabled. +/// +/// Only set when the operator has not expressed a preference, so anyone whose +/// setup handles DMA-BUF correctly can opt back in with +/// `WEBKIT_DISABLE_DMABUF_RENDERER=0`. Must run before the webview initialises. +#[cfg(target_os = "linux")] +fn disable_dmabuf_renderer_by_default() { + if std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() { + std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); + } +} + fn main() { + #[cfg(target_os = "linux")] + disable_dmabuf_renderer_by_default(); + tauri::Builder::default() .plugin(tauri_plugin_notification::init()) .invoke_handler(tauri::generate_handler![ From 1d0d91a560dc1e38756338f4f61a68577adc875d Mon Sep 17 00:00:00 2001 From: marinom2 Date: Wed, 29 Jul 2026 10:57:03 +0300 Subject: [PATCH 2/2] chore(deps): keep next and its eslint config in one dependabot PR They are a matched pair, but `next` is a production dependency and `eslint-config-next` is a development one, so grouping by dependency-type proposes them in two PRs that cannot pass independently: eslint-config-next 16 pulls eslint 10 and expects next 16, while eslint 10 removed the eslintrc API that `next lint` from next 15 still calls. That is not hypothetical. PR #161 merged the development half on its own and lint stopped running entirely - "Unknown options: useEslintrc" before it read a file - and stayed broken until the pair was pinned back. The two currently-open dependabot PRs are the same shape: #167 would take eslint to 10 and eslint-config-next to 16 while next stays on 15. Dependabot puts a dependency in the first group it matches, so listing the trio first keeps them together and makes the upgrade a single reviewable change that either works or doesn't. --- .github/dependabot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6098600..d2a8732 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,20 @@ updates: interval: weekly open-pull-requests-limit: 5 groups: + # `next` and `eslint-config-next` ship as a matched pair, and eslint's + # major is load-bearing for that config - but next is a PRODUCTION + # dependency while the other two are DEVELOPMENT ones, so the split below + # proposes them in separate pull requests. Neither can pass alone: + # eslint-config-next 16 pulls eslint 10 and expects next 16, while eslint + # 10 removed the eslintrc API that `next lint` from next 15 still calls. + # PR #161 landed exactly that half-upgrade and left lint unrunnable until + # it was pinned back. Dependabot assigns a dependency to the first group + # it matches, so listing these first keeps them moving together. + nextjs-toolchain: + patterns: + - "next" + - "eslint" + - "eslint-config-next" production: dependency-type: production development: