Edges attach to node borders + edge-label width as hard minimum edge length#53
Merged
Merged
Conversation
…e length
Two long-standing graph-layout problems, measured with the geometry diagnostic
before changing anything (baseline: edges 0px from node centers; a 200px edge
couldn't fit its 104px "Depends On" label -> +74px overflow onto the cards).
1. BORDER ATTACHMENT (dynamic anchor). New pure, unit-tested edgeGeometry.ts:
rectBorderPoint() returns where the center line crosses a card's border;
edgeBorderEndpoints() gives border-to-border endpoints. updateEdgePositions
now draws the line + hitbox border-to-border and puts the arrow on the
TARGET border. The anchor slides around the border as nodes move around each
other -> always the shortest border-to-border connection.
2. LABEL WIDTH = MINIMUM EDGE LENGTH. minEdgeLength() = halfDiag(src) +
halfDiag(tgt) + labelW + pad guarantees the label fits in the border gap at
ANY angle. forceLink.distance is floored to it (rest length), and a new
position-based 'minEdge' constraint force (like forceCollide, per-edge,
respecting pinned nodes) makes it a HARD floor, not just a spring preference.
Verified on the live stack via the diagnostic:
border attachment: endpoint-to-center 0px -> 85px (on the border) for every edge.
unpinned cluster (hub + 5 spokes, before the force): 1 edge overflowing,
2 labels overlapping their cards, minClearSpan 72 < labelW 106.
unpinned cluster (after): 0 overflowing, 0 overlapping, minClearSpan 116 >= 107.
No regressions: web units 113, perf budgets 3/3 (settle/tick/drift within
budget), THE GATE 5/5, living-graph 3/3.
(Pinned/user-placed nodes are deliberately left where the user put them; the
floor governs the auto-layout. Drag-time clamping is a possible follow-up.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🧪 Comprehensive Test Suite
Full-stack smoke gate runs in the CI workflow. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses the graph-organization issues: edges burying into node centers, and edge labels that can't display when nodes pack together. Studied + measured with the geometry diagnostic (#52) before changing anything.
Baseline (measured)
Changes
1. Border attachment with a dynamic anchor (
packages/web/src/lib/edgeGeometry.ts, pure + unit-tested):rectBorderPoint(center, dims, toward)→ where the center-line crosses a card border;edgeBorderEndpoints()→ border-to-border endpoints.updateEdgePositionsdraws the line + hitbox border-to-border and puts the arrow on the target border. As nodes move around each other the anchor slides around the border = shortest border-to-border path.2. Edge-label width = hard minimum edge length:
minEdgeLength = halfDiag(src) + halfDiag(tgt) + labelW + padguarantees the label fits in the border gap at any angle.forceLink.distancefloored to it (rest length) plus a new position-basedminEdgeconstraint force (likeforceCollide, per-edge, respects pinned nodes) — a hard floor, not just a spring preference.Verified (live, via the diagnostic)
No regressions: web units 113, perf budgets 3/3 (settle/tick/drift within budget — the per-tick border calc + constraint are cheap), THE GATE 5/5, living-graph 3/3. 10 new
edgeGeometryunit tests.Scope note: pinned/user-placed nodes are intentionally left where the user put them (the floor governs auto-layout). Drag-time clamping (so a user can't drag two nodes closer than the label) is a clean follow-up if you want it.
🤖 Generated with Claude Code