From 04cf30ad7b20972e1df62983e87c99f76b3b7667 Mon Sep 17 00:00:00 2001 From: dovvnloading Date: Fri, 14 Aug 2026 14:49:12 -0400 Subject: [PATCH] Revert canvas rendering changes from #322, #323 and #324 These three changes were attempts at the drag artifact that were not confirmed to work before shipping, and the most recent one made things worse: with #324 in place the connection lag became visible in the light theme as well, where it had previously been unnoticeable. The most likely cause of that regression is the composition flag. The audit behind #324 established that the flag added in #323 never reached the browser, because the window library passes a conflicting switch and duplicate switches resolve last-one-wins. #324 corrected the delivery, so disabling delegated composition took effect for the first time - and it applies to both themes equally, which matches the report exactly. The stroke width increase and the explicit high-quality smoothing hint in the same change also affect both themes and cannot be ruled out. This restores graphlink_desktop.py, styles.css and gl-vars-dev.css to their state as of #321. The measured performance fixes in #319, #320 and #321 are untouched. Co-Authored-By: Claude Opus 5 --- graphlink_desktop.py | 21 --------------------- web_ui/src/app/styles.css | 26 ++------------------------ web_ui/src/lib/tokens/gl-vars-dev.css | 9 +++------ 3 files changed, 5 insertions(+), 51 deletions(-) diff --git a/graphlink_desktop.py b/graphlink_desktop.py index 9a91c3d..9cabd1f 100644 --- a/graphlink_desktop.py +++ b/graphlink_desktop.py @@ -207,27 +207,6 @@ def main() -> int: return 1 logger.info("backend healthy at %s", base_url) - # Canvas drag fix, corrected delivery (full audit, 2026-08-14): recent - # WebView2 builds hand independently-composited page layers to Windows' - # compositor to assemble on screen; a dragged node card and the redrawn - # connection lines can land one display frame apart. The first attempt - # at disabling that (#323) passed --disable-features=DelegatedCompositing - # through this environment variable - and the audit found it NEVER took - # effect: pywebview's own Windows backend passes - # --disable-features=ElasticOverscroll in the browser arguments it - # builds (site-packages/webview/platforms/edgechromium.py), duplicate - # --disable-features switches resolve last-one-wins, so one of the two - # lists was silently discarded. The reliable delivery is to put BOTH - # feature names in ONE merged list here: whichever --disable-features - # value wins, it now carries DelegatedCompositing (and keeps pywebview's - # ElasticOverscroll intent). Unknown feature names are ignored by - # runtimes that predate them, so this stays inert where not applicable. - _composition_flags = "--disable-features=ElasticOverscroll,DelegatedCompositing" - _existing_args = os.environ.get("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "") - os.environ["WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"] = ( - f"{_existing_args} {_composition_flags}".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 562e884..40f29bb 100644 --- a/web_ui/src/app/styles.css +++ b/web_ui/src/app/styles.css @@ -43,13 +43,6 @@ body, #root { height: 100%; margin: 0; - /* Theme-matched page background (dark drag-parity audit): with no - explicit background the page root stays transparent, so any region the - renderer clears or presents before content paint resolves to the - embedding window's own backdrop rather than the theme's canvas color. - Painting the root opaquely in the theme's window color makes both - themes behave identically at that boundary. */ - background-color: var(--gl-surface-window); } .app-shell { @@ -410,28 +403,13 @@ body, font-size: var(--gl-node-font-size, 11px); } -/* Dark-theme drag-parity fix (full audit, 2026-08-14): the connection line - used to be painted at stroke-width 1.5 in --gl-surface-border-strong - - #505050 on dark's #1E1E1E canvas (a BRIGHT light-on-dark hairline, - ~2.07:1) vs #AFAFAF on light's #E1E1E1 (a soft dark-on-light line, - ~1.68:1). A sub-2px stroke drops into the renderer's hairline - anti-aliasing regime below ~67% zoom, whose stepping is visibly harsher - for light-on-dark than dark-on-light - so the identical drag produced a - line that visibly crawled/stepped against the node in dark while reading - as smooth in light. Three equalizers: a dedicated per-theme stroke token - (--gl-graph-edge-stroke: dark #444444 reproduces light's contrast RATIO - on the dark canvas; light keeps #AFAFAF unchanged), stroke-width 2 to - stay out of hairline mode at working zooms, and geometricPrecision to - pin high-quality anti-aliasing rather than pixel snapping. */ .scene-canvas .react-flow__edge-path { - stroke: var(--gl-graph-edge-stroke, var(--gl-surface-border-strong, var(--gl-surface-text-muted))); - stroke-width: 2; - shape-rendering: geometricPrecision; + stroke: var(--gl-surface-border-strong, var(--gl-surface-text-muted)); + stroke-width: 1.5; } .scene-canvas .react-flow__edge.selected .react-flow__edge-path { stroke: var(--gl-surface-text-primary); - shape-rendering: geometricPrecision; } .scene-minimap { diff --git a/web_ui/src/lib/tokens/gl-vars-dev.css b/web_ui/src/lib/tokens/gl-vars-dev.css index a873f1d..6a05e26 100644 --- a/web_ui/src/lib/tokens/gl-vars-dev.css +++ b/web_ui/src/lib/tokens/gl-vars-dev.css @@ -118,7 +118,6 @@ --gl-frame-purple: #7c7c7c; --gl-frame-red: #7c7c7c; --gl-frame-yellow: #8e8e8e; - --gl-graph-edge-stroke: #444444; --gl-graph-node-badge-fill: #484848; --gl-graph-node-body-end: #292929; --gl-graph-node-body-start: #303030; @@ -150,9 +149,9 @@ --gl-semantic-status-info: #828282; --gl-semantic-status-success: #838383; --gl-semantic-status-warning: #919191; - --gl-shadow-1: 0 1px 3px rgba(0, 0, 0, 0.18); - --gl-shadow-2: 0 4px 12px rgba(0, 0, 0, 0.203); - --gl-shadow-3: 0 8px 28px rgba(0, 0, 0, 0.248); + --gl-shadow-1: 0 1px 3px rgba(0, 0, 0, 0.40); + --gl-shadow-2: 0 4px 12px rgba(0, 0, 0, 0.45); + --gl-shadow-3: 0 8px 28px rgba(0, 0, 0, 0.55); --gl-space-1: 4px; --gl-space-2: 8px; --gl-space-3: 12px; @@ -253,7 +252,6 @@ --gl-frame-purple: #838383; --gl-frame-red: #838383; --gl-frame-yellow: #717171; - --gl-graph-edge-stroke: #AFAFAF; --gl-graph-node-badge-fill: #B7B7B7; --gl-graph-node-body-end: #D6D6D6; --gl-graph-node-body-start: #CFCFCF; @@ -384,7 +382,6 @@ --gl-frame-purple: #838383; --gl-frame-red: #838383; --gl-frame-yellow: #717171; - --gl-graph-edge-stroke: #AFAFAF; --gl-graph-node-badge-fill: #B7B7B7; --gl-graph-node-body-end: #D6D6D6; --gl-graph-node-body-start: #CFCFCF;