Skip to content

Qt-removal R7.5b-3: Smart Guides canvas visual (closes R7.5b)#150

Merged
dovvnloading merged 1 commit into
mainfrom
qt-removal/r7-5b3-smart-guides
Jul 26, 2026
Merged

Qt-removal R7.5b-3: Smart Guides canvas visual (closes R7.5b)#150
dovvnloading merged 1 commit into
mainfrom
qt-removal/r7-5b3-smart-guides

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

Summary

Ports the legacy scene's smart-guide alignment snapping — the last and largest of R7.5's three canvas-visual parity gaps. While dragging a node, dashed guide lines appear when its edges or centers align with another node's, and the position snaps to the alignment. With this, R7.5b is complete: all three canvas visuals (faded connections, orthogonal routing, smart guides) are real, and the Help panel's "Grid and Guide Controls" entry is restored to its original legacy wording — every claim in it is now true for the first time in the rewrite.

The algorithm

web_ui/src/app/canvas/smartGuides.ts is a pure, framework-free port of legacy's _calculate_smart_guide_snap (confirmed against graphlink_scene.py by recon, not reimplemented from memory): six same-key-only alignment keys (left/center/right, top/middle/bottom), strict < 5px tolerance, first-hit key ordering, first-candidate ordering, per-axis independence (X and Y may snap against different candidates), early exit once both axes snap, and candidate-valued guide lines spanning the union of the two rects. Sixteen unit tests cover every documented rule by name.

Recorded design decisions

  • Grid-snap interaction — layered pass: React Flow's native snapToGrid runs first (inside RF, before the change is emitted); the smart-guide pass then overwrites only the axes it matched. This reproduces legacy's per-axis "guides beat grid" priority without re-implementing grid-snap manually. Known consequence (recorded in the ledger): tolerance is evaluated against the already-grid-snapped position, so with large grid sizes an off-grid candidate can be unreachable by guides — an accepted fidelity trade.
  • Candidate exclusions: co-moving selected nodes (legacy's !isSelected()), plus — a documented extension with no legacy precedent — a dragged Frame/Container's own members, which move in lockstep and would otherwise self-align permanently.
  • Multi-select guides: all co-movers' guides render, a deliberate deviation from legacy's last-item-only behavior (an artifact of Qt's per-item processing order, not a design choice).
  • Rendering: RF's <ViewportPortal> (the QGraphicsLineItem analog — same transformed layer, flow coordinates), dash color rgba(128,128,128,0.784) = Qt's QColor(128,128,128,200) exactly.

Defects found and fixed before merge

Live drag verification surfaced two integration bugs the entire automated suite could not see, plus the adversarial review found one:

  1. Node dimensions unavailable from local statetoFlowNodes rebuilds the nodes array from every scene snapshot with fresh objects, wiping the measured bookkeeping RF writes back via dimension changes.
  2. Dimensions unavailable from RF's internal store either — verified against the installed @xyflow/system source: adoptUserNodes rebuilds internal measured from the user objects, so both copies get wiped on every publish. Final fix: measuredNodeSize prefers the internal store and falls back to the live DOM element's offsetWidth/offsetHeight (pre-transform layout px = flow units at any zoom — also the most faithful translation of legacy's own sceneBoundingRect() reads).
  3. Drag-release bounce (review finding) — RF's drag-stop change carries its raw internal pointer position, so a snapped node visibly bounced off its corrected position on release; fixed by settling the drag-stop change at the last corrected frame's position. Pre-existing mechanism (drag-factor scaling) that smart guides newly exposed at default settings.

Test plan

  • smartGuides.test.ts (new): 16 unit tests, one per documented algorithm rule (back-solve math for all six keys, strict-tolerance boundary at exactly 5px, first-hit vs closest, candidate ordering, different-candidate X/Y, early exit, guide extents)
  • backend/tests/test_canvas.py: intent test; ViewPopover.test.tsx: checkbox coverage; sceneStore fixture updated
  • Full verification: 867 backend / 1157 frontend tests passing, typecheck/lint/build clean, codegen in sync, Qt-free burn-down gate unchanged
  • Adversarial review: hand re-derived 8 test expectations, verified ViewportPortal against installed types, traced RF's drag-stop internals — 1 real defect (fixed above), 1 accepted deviation (documented), 1 consequence note (recorded)
  • Live end-to-end drag against a real running backend (browser pane displayed, synthetic pointer gesture): dragged a chat node to a proposed x=103, 3px from another node's left edge at x=100 → position snapped to exactly 100 mid-drag, a vertical dashed guide rendered at left:100px, top:100px, height:270px (union-span math exact: min-of-tops 100 → max-of-bottoms 370), the guide cleared on release, and the backend committed exactly {x: 100} — also proving the release-bounce fix end-to-end

Ports the legacy scene's smart-guide alignment snapping - the last and
largest of R7.5's 3 canvas-visual parity gaps. While dragging, dashed
guide lines appear when the dragged node's edges/centers align with
another node's, and the position snaps to the alignment.

The snap algorithm is a pure module (smartGuides.ts) porting legacy's
_calculate_smart_guide_snap exactly: 6 same-key-only alignment keys,
strict <5px tolerance, first-hit key/candidate ordering, per-axis
independence, early exit, candidate-valued union-span guide lines.
Integrated as the recorded layered-pass decision (guides overwrite
per-axis after RF's native grid-snap) with guide lines rendered via
ViewportPortal in Qt's exact dash color.

Live drag verification surfaced two integration bugs the automated
suite could not see - node dimensions were unavailable from both the
local nodes array (toFlowNodes rebuilds wipe them) and RF's internal
store (adoptUserNodes rebuilds internal measured from the user
objects) - fixed with a DOM-measurement fallback, plus a review-found
drag-release bounce off corrected positions, fixed by settling the
drag-stop change at the last corrected frame's position.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant