Skip to content

Edges attach to node borders + edge-label width as hard minimum edge length#53

Merged
mvalancy merged 1 commit into
developfrom
fix/edge-border-and-label-min
Jun 14, 2026
Merged

Edges attach to node borders + edge-label width as hard minimum edge length#53
mvalancy merged 1 commit into
developfrom
fix/edge-border-and-label-min

Conversation

@mvalancy

Copy link
Copy Markdown
Member

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)

  • Edges attached 0px from node centers (both endpoints).
  • A 200px edge couldn't fit its 104px "Depends On" label → +74px overflow onto the cards; a 470px edge (same label) fit fine.

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.
  • updateEdgePositions draws 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 + pad guarantees the label fits in the border gap at any angle.
  • forceLink.distance floored to it (rest length) plus a new position-based minEdge constraint force (like forceCollide, per-edge, respects pinned nodes) — a hard floor, not just a spring preference.

Verified (live, via the diagnostic)

before after
edge endpoint → node center 0px 85px (on border)
unpinned hub+5-spoke: edges overflowing label 1 0
…labels overlapping their own cards 2 0
…min clear span vs max label width 72 < 106 116 ≥ 107

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 edgeGeometry unit 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

…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>
@github-actions

Copy link
Copy Markdown

🧪 Comprehensive Test Suite

  • Unit suites (Node 18.x & 20.x) — core, web, server, mcp-server: ✅ passed
  • Installer & deploy config: ✅ passed

Full-stack smoke gate runs in the CI workflow.

@mvalancy mvalancy merged commit 2f1673e into develop Jun 14, 2026
16 checks passed
@mvalancy mvalancy deleted the fix/edge-border-and-label-min branch June 14, 2026 05:36
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