Status: accepted (plan A) — plan A confirmed working on Linux/WebKitGTK (2026-09-18); measurements still pending in P-001 Graph surface in desktop webview · Extends ADR-0003 wgpu for graph rendering
Context
The graph renderer is wgpu. On desktop the UI lives in a system webview. WebView2 and WKWebView expose WebGPU; WebKitGTK (Linux) does not by default (Linux Desktop Setup). A WebGL2 fallback works everywhere but has no compute shaders, so GPU force layouts — the feature that makes 100k nodes fluid — are unavailable there. The brief explicitly allows a different implementation for desktop.
Decision (proposed)
Ship both, chosen at runtime:
flowchart TD S[start desktop app] --> P{probe: navigator.gpu?} P -->|yes| A[plan A: wasm renderer in the webview canvas<br/>identical to web build] P -->|no| B{feature graph-native enabled?} B -->|yes| O[plan B: native wgpu overlay<br/>moonkale-editor-graph-desktop] B -->|no| W[plan A with WebGL2<br/>CPU layouts, label of degraded mode]
- Plan A — the web code path inside the webview. One renderer, one set of bugs. Default when WebGPU is present.
- Plan B —
moonkale-editor-graph-desktop, a desktop-only crate (Project Structure rule: single-platform feature → separate crate). A nativewgpusurface as a GTK sibling widget (Linux) / child view (others), positioned over a placeholder<div>in the panel; the editor (scene, layout, interaction, style) is shared and unchanged — onlySurfaceProviderdiffers. - The choice is visible to the user (Diagnostics panel,
graphSurfacecontext key) so bug reports say which path they hit.
Why not always plan B on desktop
Z-order: webview-drawn popups/menus/tooltips render under a native overlay. Plan B must draw its own popups on the surface (or cut holes). That is real work and a second UI style; only pay it where plan A can’t run.
Why not dioxus-native (Blitz)
It would make this trivial but has no JS engine, so CodeMirror/Milkdown/xterm can’t run — blocked until Rust-native Editor Candidates land. Long-term answer, not a Phase-2 one.
Consequences
- Two surfaces to test on Linux; CI needs a WebKitGTK job and a Vulkan (lavapipe) job.
- Input routing and HiDPI for the overlay are new code (
graph-desktop/src/{input,sync}.rs). - Measurements required before acceptance: fps and layout time at 10k/50k/100k on (a) WebKitGTK with WebGPU flag on, (b) WebGL2, (c) native overlay; on both NVIDIA and AMD GPUs of the dev box.
Plan B, as it was sketched in code
The crate editors/graph-desktop held this design as comments only; it was removed in Milestone 18 phase 1 (no code ever depended on it). Recorded here so the design is not lost:
- Selected by a
graph-nativefeature of the desktop crate; the graph editor (scene, layout, interaction, styles) stays unchanged, only where pixels land differs. - Overlay: on Linux a sibling GTK widget in the same container as the WebKitGTK view (
wry::WebViewExtUnix::webview()→ parent) with awgpu::Surfacefrom its raw window handle, positioned over a hole the panel leaves; on Windows/macOS a child HWND/NSView would work, though WebGPU in the webview normally makes it unnecessary. Known hard part: z-order with popups the webview draws (draw popups natively on the same surface, or cut their region out). - Geometry sync: observe the placeholder
<div>(ResizeObserver, scroll, viadocument::eval) and move/resize/show/hide the surface, including when the workbench docks the panel elsewhere or another tab covers it. - Input: pointer and keyboard on the native surface routed to the graph editor’s interaction code; focus hand-off with the webview; HiDPI scale.
- Probe: at start evaluate
!!navigator.gpuin the webview; prefer the in-webview canvas when WebGPU is present, else enable the overlay; expose the choice as agraphSurfacecontext key and in a Diagnostics panel.