Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions graphlink_desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,25 +207,25 @@ 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"
# 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"{_composition_flags} {_existing_args}".strip()
f"{_existing_args} {_composition_flags}".strip()
)

import webview # pywebview - the native (non-Qt, non-browser) window
Expand Down
26 changes: 24 additions & 2 deletions web_ui/src/app/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ 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 {
Expand Down Expand Up @@ -403,13 +410,28 @@ 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-surface-border-strong, var(--gl-surface-text-muted));
stroke-width: 1.5;
stroke: var(--gl-graph-edge-stroke, var(--gl-surface-border-strong, var(--gl-surface-text-muted)));
stroke-width: 2;
shape-rendering: geometricPrecision;
}

.scene-canvas .react-flow__edge.selected .react-flow__edge-path {
stroke: var(--gl-surface-text-primary);
shape-rendering: geometricPrecision;
}

.scene-minimap {
Expand Down
9 changes: 6 additions & 3 deletions web_ui/src/lib/tokens/gl-vars-dev.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
--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;
Expand Down Expand Up @@ -149,9 +150,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.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-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-space-1: 4px;
--gl-space-2: 8px;
--gl-space-3: 12px;
Expand Down Expand Up @@ -252,6 +253,7 @@
--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;
Expand Down Expand Up @@ -382,6 +384,7 @@
--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;
Expand Down
Loading