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;