diff --git a/graphlink_desktop.py b/graphlink_desktop.py index 9cabd1f..0d7c521 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 39c8c39..40f29bb 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;