Skip to content

fix(web): keep shared edge routes orthogonal#57

Merged
naorsabag merged 4 commits into
masterfrom
fix/orthogonal-shared-edge-routes
May 2, 2026
Merged

fix(web): keep shared edge routes orthogonal#57
naorsabag merged 4 commits into
masterfrom
fix/orthogonal-shared-edge-routes

Conversation

@naorsabag
Copy link
Copy Markdown
Owner

@naorsabag naorsabag commented May 2, 2026

Summary

  • Insert orthogonal bend points when routed edge points would otherwise render diagonal SVG segments.
  • Include straight sibling edges when calculating shared source trunks, so bent branches split later while straight roads stay straight.
  • Add focused layout regressions for diagonal route points and shared straight+bent source routes.

Test plan

  • npx tsx -e "... buildOrthogonalPath assertion ..."
  • npx tsx -e "... bundleSharedSourcePrefixes assertion ..."
  • npx tsc --noEmit -p packages/web
  • Browser-verified http://localhost:8788/flow/8AdyEvHrojv0

Note: npm test -w @openhop/web -- flow-layout.test.ts ... is currently blocked locally because @openhop/shared is not resolvable in the workspace install, and npm install hit a Windows EACCES on node_modules\@openhop\shared.

Summary by CodeRabbit

  • Bug Fixes

    • Flow layouts now defer certain back-edges to prevent nodes from appearing on isolated rows; node positions snap to a row grid and connected routes shift to stay aligned.
    • Routing orthogonalization now inserts right-angle corners to avoid diagonal segments.
    • Self-loop visuals scaled and positioned more consistently.
    • Route bundling no longer shifts straight sibling routes when mixed with bent routes.
  • Tests

    • Added tests covering these layout and routing edge cases.

Ensure layout routes never render diagonal road segments and let straight sibling edges delay branch points for shared source trunks.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ead058fe-6878-4836-873f-5789137aee3b

📥 Commits

Reviewing files that changed from the base of the PR and between d6a8e98 and 2d758ea.

📒 Files selected for processing (1)
  • packages/web/src/lib/flow-layout.ts

Walkthrough

Defers promotion of back-edge layout edges, orthogonalizes intermediate route points (inserting 90° corners), refines shared-source trunk bundling to avoid shifting straight routes, snaps ELK node y-positions to a row grid (shifting routes accordingly), and exports SELF_LOOP_WIDTH/SELF_LOOP_HEIGHT. Tests updated/added accordingly.

Changes

Flow Layout: topology, orthogonalization, bundling, snapping, self-loop constants

Layer / File(s) Summary
Topology collection / Back-edge deferral
packages/web/src/lib/flow-layout.ts
buildFlowTopology defers edges whose target was already seen into deferredBackEdges and later promotes them only when the source would otherwise be absent from layoutNodeIds; self-edges are skipped.
Orthogonalization helpers
packages/web/src/lib/flow-layout.ts
Added chooseOrthogonalCorner and orthogonalizeRoutePoints to convert diagonal consecutive transitions into orthogonal corner point sequences.
Path generation update
packages/web/src/lib/flow-layout.ts
buildOrthogonalPath now runs orthogonalizeRoutePoints(dedupeRoutePoints(points)) (instead of only deduping) before producing the SVG M/L path.
Self-loop dimensions exported
packages/web/src/lib/flow-layout.ts
Replaced internal SELF_LOOP_OFFSET with exported SELF_LOOP_WIDTH = 48 and SELF_LOOP_HEIGHT = 40, and updated self-loop intermediate point construction to use these constants.
Bundling / trunk alignment refinement
packages/web/src/lib/flow-layout.ts
bundleSharedSourcePrefixes now includes routes with route.length >= 2 for grouping, but computes naturalTrunkX/Y and reapplies trunk shifts only from the bent subset (route.length >= 3), leaving straight/short routes unshifted.
Row-grid snapping & route shifting around ELK
packages/web/src/lib/flow-layout.ts
computeElkLayout snaps node y-positions to a row grid and shifts routes (snapPositionsToRowGrid, shiftRoutesAfterSnap) after the first ELK pass (normalizing first-pass routes via orthogonalizeRoutePoints(dedupeRoutePoints(route)) for port inference) and again after the second pass prior to final bundling.
Tests: new & updated cases
packages/web/__tests__/flow-layout.test.ts
Added test ensuring a node with only a back-edge source is retained and row-aligned; refined self-loop ear assertions to use NODE_WIDTH/HEIGHT and SELF_LOOP_*; added orthogonal 90° bend insertion test; added bundling test with a straight sibling route to assert trunk delay/shift behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(web): keep shared edge routes orthogonal' directly relates to the main changes: orthogonalizing edge routes and fixing shared source prefix bundling to work correctly with orthogonal paths.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/orthogonal-shared-edge-routes

Review rate limit: 8/10 reviews remaining, refill in 9 minutes and 28 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/lib/flow-layout.ts`:
- Around line 322-347: The handle-inference step is still receiving the raw ELK
polylines, so call orthogonalizeRoutePoints (or the same normalization used in
buildOrthogonalPath, i.e., orthogonalizeRoutePoints(dedupeRoutePoints(...)))
before passing routes into inferPortAssignmentsFromRoutes; locate where
inferPortAssignmentsFromRoutes is invoked and replace the raw route argument(s)
with the normalized/deduped route(s) to ensure port inference uses the
orthogonalized path (reference functions: orthogonalizeRoutePoints,
buildOrthogonalPath, inferPortAssignmentsFromRoutes).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 423ae476-d7e2-4311-9daa-60f51547a4b2

📥 Commits

Reviewing files that changed from the base of the PR and between 2a46d8e and d6d9f0c.

📒 Files selected for processing (2)
  • packages/web/__tests__/flow-layout.test.ts
  • packages/web/src/lib/flow-layout.ts

Comment thread packages/web/src/lib/flow-layout.ts
naorsabag and others added 3 commits May 2, 2026 13:14
…row grid

Two layout issues hit examples/order-flow.yaml's cron node:

1. pushLayoutEdge unified sources and targets in one layoutSeen set, so
   any back-edge whose target was already seen got dropped entirely.
   For a node like cron that only feeds an already-seen target
   (cron -> order-service), this dropped its only edge and left ELK
   with a floating node placed on its own row.

   Defer back-edges and promote them post-pass for any source that
   otherwise has no layout edge. This keeps cron in the layered layout
   without changing the behavior for normal forward edges.

2. Even with the back-edge restored, ELK's per-column node placement
   produced y values that did not align across columns (cron at y=941
   vs events at y=1000), breaking the visual row grid.

   Add a snapPositionsToRowGrid step that rounds each node's y to the
   nearest multiple of NODE_HEIGHT + spacing (240px) anchored at the
   first row's y. Same-row nodes stay on the same row; outliers like
   cron get pulled onto the nearest grid row alongside events.

Adds a regression test loading examples/order-flow.yaml and asserting:
- cron has a layout edge into order-service
- cron and events share the same y after layout

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
snapPositionsToRowGrid changed each node's y to align to the row grid,
but ELK had already produced routes against the un-snapped positions.
Source-side route segments stayed at the old port y while the node
moved, opening a 10px gap between the cardinal handle (computed from
the snapped position) and ELK's route start.

For self-loop edges this was visible: buildSelfLoopRoute uses the
snapped node position to anchor the ear at cardinal y=360, while
adjacent forward edges (api->worker, api->cache) drew from ELK's
y=370 port. The self-loop appeared on a separate parallel road
instead of branching from the shared trunk.

snapPositionsToRowGrid now returns per-node y deltas. shiftRoutesAfterSnap
walks each route and shifts source-side points (y matching route[0].y)
by the source node's delta and target-side points by the target's
delta, so source and target ends stay anchored to the snapped nodes
without breaking orthogonality of the H-V-H middle bend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
inferPortAssignmentsFromRoutes inspects the first and last route
segments to decide which side each edge exits/enters. When ELK
produced a polyline whose endpoint segment had a diagonal nub
(rare but possible on non-strict-orthogonal output), the inference
saw the wrong direction and picked an off-axis port handle.

Run orthogonalizeRoutePoints(dedupeRoutePoints(route)) on every
route before passing them to inferPortAssignmentsFromRoutes so the
inference operates on the same corrected geometry that
buildOrthogonalPath ultimately renders.

Addresses CodeRabbit review on PR #57.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@naorsabag naorsabag merged commit 620df89 into master May 2, 2026
7 checks passed
@naorsabag naorsabag deleted the fix/orthogonal-shared-edge-routes branch May 4, 2026 05:19
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