From 4fb213a41521f7567ca9e5bde99e21dcfd675a56 Mon Sep 17 00:00:00 2001 From: dovvnloading Date: Fri, 14 Aug 2026 14:13:01 -0400 Subject: [PATCH] Disable delegated window composition so drags reach the screen as one frame Recent WebView2 builds hand independently-composited page layers to the Windows compositor to assemble on screen. A node card being dragged lives on its own such layer, while its connection lines are redrawn into a different one, and Windows can apply the card's movement one display frame before the freshly drawn connection arrives - the line visibly steps out of sync with the node it is attached to. Everything measured inside the renderer (layout geometry, frame captures) shows the two perfectly attached, because the divergence happens below the renderer at window composition; a thin bright line on the dark theme's near-black background makes each misstep obvious while the same misstep is nearly invisible on the light theme, which is why the report was dark-only. The launcher now disables delegated composition for the app's WebView2 instance, making the renderer assemble the complete frame itself before handing one finished image to Windows. The flag is appended in front of any WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS the user already set and is ignored by runtimes that predate the feature. Also reverts the will-change stylesheet addition from #322: promoting more layers cannot help under this mechanism and only increases the number of independently-composited surfaces. Co-Authored-By: Claude Fable 5 --- graphlink_desktop.py | 21 +++++++++++++++++++++ web_ui/src/app/styles.css | 20 -------------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/graphlink_desktop.py b/graphlink_desktop.py index 9cabd1ff..0d7c521b 100644 --- a/graphlink_desktop.py +++ b/graphlink_desktop.py @@ -207,6 +207,27 @@ def main() -> int: return 1 logger.info("backend healthy at %s", base_url) + # Canvas drag-lag fix (dark-theme "connection detaches from the node" + # report): recent WebView2 builds hand independently-composited page + # layers to Windows' compositor to assemble on screen. A node card being + # dragged lives on its own such layer (Chromium promotes it once its + # position changes every frame), while the connection lines are redrawn + # into a different one - and Windows can apply the card's movement one + # display frame before the freshly drawn connection arrives, so the line + # visibly steps out of sync with the node it is attached to. Everything + # the renderer itself reports (layout geometry, its own frame captures) + # looks perfectly attached, because the divergence happens below the + # renderer, at window composition. Disabling delegated composition makes + # the renderer assemble the complete frame itself before handing one + # finished image to Windows. Appended in front of any flags the user + # already set in this variable; unknown feature names are ignored by + # WebView2, so this is inert on runtimes that predate the feature. + _composition_flags = "--disable-features=DelegatedCompositing" + _existing_args = os.environ.get("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "") + os.environ["WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"] = ( + f"{_composition_flags} {_existing_args}".strip() + ) + import webview # pywebview - the native (non-Qt, non-browser) window # The token reaches the SPA as a URL FRAGMENT, which the browser never diff --git a/web_ui/src/app/styles.css b/web_ui/src/app/styles.css index 39c8c398..40f29bb3 100644 --- a/web_ui/src/app/styles.css +++ b/web_ui/src/app/styles.css @@ -403,26 +403,6 @@ body, font-size: var(--gl-node-font-size, 11px); } -/* Dark-theme drag-lag fix: node cards move as GPU-composited layers (the - browser promotes them on its own once their transform changes every - frame of a drag), while the connection lines live in an SVG that gets - re-rasterized on the main thread. Those are two different routes to the - screen, and when they fall a frame out of step the connection visibly - trails or stutters against the node it is attached to. The 1.5px stroke - on a near-black background makes every misstep stark in dark mode; the - identical misstep is nearly invisible against the light theme, which is - why the artifact reads as dark-only. Declaring will-change on BOTH the - node wrappers and the connection layer makes the browser put them on the - same compositing path from the first frame, instead of flipping the - nodes onto a separate one mid-gesture. */ -.scene-canvas .react-flow__node { - will-change: transform; -} - -.scene-canvas .react-flow__edges { - will-change: transform; -} - .scene-canvas .react-flow__edge-path { stroke: var(--gl-surface-border-strong, var(--gl-surface-text-muted)); stroke-width: 1.5;