Skip to content

Merge dev into main - #132

Merged
Lev Nachmanson (levnach) merged 178 commits into
mainfrom
dev
Aug 5, 2026
Merged

Merge dev into main#132
Lev Nachmanson (levnach) merged 178 commits into
mainfrom
dev

Conversation

@levnach

Copy link
Copy Markdown
Collaborator

Summary

  • promote the current dev branch to main
  • add a distributable msagljs Agent Skill for core layout, live SVG/WebGL embedding, and document-ready graph export
  • document msagljs@dev discovery and installation for supported agent hosts
  • add CI coverage for skill validation, page-load rendering, and SVG/PDF/PNG/EPS/PostScript output

This PR contains the full accumulated dev branch history because dev was already substantially ahead of main before the skill was added.

Validation

  • yarn build
  • yarn test --runInBand (76 suites, 307 tests passed; 83 skipped)
  • yarn tsc -p tsconfig.skill-examples.json
  • gh skill publish --dry-run .
  • clean local gh skill install --from-local
  • live SVG page load, failure-state, and standalone download smoke tests
  • SVG, PDF, PNG, EPS, PS, JGF, TXT, and font-option export smoke tests
  • independent code review with no remaining high-confidence findings

Patrick Wheeler and others added 30 commits March 27, 2026 20:05
Implements the corridor routing algorithm that bypasses the cone spanner /
visibility graph entirely. The pipeline is:
  CDT → BFS on dual graph → sleeve → funnel → polyline

Key components:
- findContainingTriangle: R-tree accelerated triangle lookup
- findSleeveBFS: BFS on CDT dual graph avoiding obstacle interiors
- funnelFromDiagonals: standalone funnel algorithm
- corridorRoute: public API combining all steps

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Corridor to EdgeRoutingMode enum
- Add routeCorridorEdges() that builds CDT on padded obstacles and
  routes all edges through the CDT dual graph with funnel optimization
- Wire into routeEdges() dispatch in driver.ts
- Add 'Corridor' option to SVG and WebGL renderer dropdown menus
- Export routeCorridorEdges from @msagl/core

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes:
1. Replace BFS with A* using Euclidean distance between triangle
   centroids as edge weights and straight-line distance to target
   as heuristic. This produces much shorter paths by selecting
   better homotopy classes.
2. The obstacle crossing logic (canCrossEdge) remains — it correctly
   allows crossing source/target obstacle boundaries while blocking
   other obstacles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port locations added as isolated sites (null Owner) inside other
nodes' obstacles created 'holes' in the obstacle-interior check,
allowing A* to traverse through obstacle triangles. Fix: build CDT
with only obstacle polylines, no port sites. findContainingTriangle
locates the starting triangle at query time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The RelativeFloatingPort constructor wasn't assigning ports to edges.
Use SplineRouter.CreatePortsIfNeeded which handles this correctly,
and add a null guard for edges that still lack ports.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When corridorRoute returns null (no path found), fall back to a
straight line between source and target. This ensures every edge
gets a curve, preventing the tileMap from crashing on null.parStart.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SplineRouter.CreatePortsIfNeeded creates RelativeFloatingPort objects
but never assigns them to edge.sourcePort/targetPort — they were
garbage collected. Now we assign ports directly:
  ed.sourcePort = RelativeFloatingPort.mk(...)

Added integration test on gameofthrones.json (407 nodes, 2639 edges):
all edges routed successfully, CDT 8ms + routing 329ms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bezier smoothing (SmoothedPolyline.createCurve) can push curves outside
the corridor and through obstacle nodes. Replace with Polyline.toCurve()
which produces exact line segments that stay within the CDT corridor.

Also adds diagnostic test confirming 0/2639 edges cross through
non-endpoint node boundaries on the gameofthrones graph.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compute where source→target line exits the source padded obstacle and
enters the target. Route through free space between those boundary
points. This eliminates sharp turns near node boundaries caused by
funnel waypoints on obstacle corners. Arrowhead trimming clips the
final center→boundary straight segments at the actual node boundary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Route center-to-center to find the globally optimal funnel path, then
trim obstacle-interior waypoints so edges leave/enter nodes in the
actual optimal direction (toward the first/last free-space waypoint).
Arrowhead trimming clips the center→free-space segment at the node
boundary. Falls back to keeping original obstacle-corner waypoints
when trimming would cross a nearby obstacle (0 crossings on GOT graph).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rp corners

Instead of trimming waypoints post-funnel, virtually collapse the
source/target obstacle boundary vertices to their node centers in
sleeveToDiagonals(). The funnel then routes directly from center to
center through smooth free-space diagonals — no sharp turns at obstacle
corners. Arrowhead trimming clips the curve at node boundaries.

Degenerate diagonals (both endpoints collapsed to same point) are
skipped, keeping the funnel well-formed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous canCrossEdge blocked crossing any constrained edge not
belonging to source/target obstacles. This prevented the A* from
reaching the target when obstacles' padded boundaries touch or overlap,
creating impenetrable walls of constrained edges in free space.

Fix: remove the constrained-edge crossing check entirely. The
triangleIsInsideObstacle check on neighbor triangles is sufficient
to prevent entering obstacle interiors — a triangle is inside an
obstacle only when all 3 sites belong to the same non-allowed obstacle.
Crossing a constrained boundary edge enters a triangle with at most
2 sites from that obstacle, which is correctly classified as free space.

Tested on 40 .gv files (16,597 edges): 0 sleeve failures, 0 null curves.
Edge length ratio unchanged (GOT: 1.028, root.gv: 1.027).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tileMap.buildUpToLevel() was hardcoded to use SplineRouter for
rerouting edges on subsets of visible nodes at different zoom levels.
This caused 'failed to create sleeve' errors from pathOptimizer.ts
when the edge routing mode was Corridor.

Now checks the edge routing mode and uses routeCorridorEdges when
in Corridor mode, falling back to SplineRouter otherwise.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of running A* independently for each edge, group edges by
source node and run one Dijkstra from each source triangle. The
shortest-path tree reaches all target triangles, allowing sleeve
extraction via parent pointers.

Target obstacle-interior triangles are reached but not expanded
through, preventing paths from crossing through non-target obstacles.

GOT benchmark: 156ms (was 224ms with per-edge A*) — 30% speedup.
Edge length ratio: 1.035 (was 1.028) — slightly longer paths since
Dijkstra has no target heuristic, but still within 3.5% of spline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply modelMatrix coordinate offset in zoomTo() so the canvas
correctly centers on the found node in tile-space coordinates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preprocesses the CDT dual (triangles as nodes, shared edges as arcs)
for O(1) shortest-path queries via bidirectional upward Dijkstra.

Edge-difference heuristic with lazy priority updates for contraction
ordering. Shortcut edges encode the contracted middle node for
path unpacking.

GOT benchmark (3258 triangles):
- Preprocessing: 81ms
- 100 queries: 38ms (0.38ms per query)
- All queries produce valid sleeves

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace full vertex collapse with constrained movement: each source/
target obstacle vertex moves toward the node center only as far as
all adjacent sleeve triangle orientations are preserved.

For vertex v moving toward center s, each triangle (v,a,b) gives
constraint t < c0/|c1| where c0 = cross(b-a, v-a), c1 = cross(b-a, s-v).
Take t* = min of all upper bounds. O(k) per vertex.

Results on GOT: 0.7% crossings (was 1.5% with full collapse).
Edge length ratio unchanged.

Also updates figure dump test to generate combined figures showing
original sleeve + diagonals + route in a single image.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…agonals shown

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ged (green solid), no path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only changed boundary edges shown: orange dashed (original) and
green solid (after legal vertex movement). Unchanged edges hidden.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevents tiny sliver edges inside source/target obstacles when
two adjacent vertices both move toward the center but don't fully
collapse. Also fixes the figure rendering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…orange

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Lev Nachmanson (levnach) and others added 22 commits May 8, 2026 09:15
Set MAX_LEVELS / MAX_TILE_LEVELS to 30 (effectively no cap) in
maxPerTile.spec.ts and benchmarkLoadingAllGraphs.spec.ts, and update
the report headers / LaTeX caption accordingly. On all benchmark
graphs the natural capacity-met stop in TileMap.subdivideLevel fires
first (verified by 'done subdividing at level X because each tile
contains less than 500' in every run); the cap was never binding.

Per-graph levels and per-tile maxima are bit-identical to the
previous Z=8 runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mizer

The benchmark runs each graph through layoutGraphWithMds twice on fresh
geometry, once with EdgeRoutingMode.Sleeve and once with
EdgeRoutingMode.Spline, and reports the wall-clock time and the sum of
edge curve lengths for each router. Disabled by default; opt in with
MSAGL_BENCH=1.

While bringing it up, the spline router crashed on Game of Thrones with
'Cannot read properties of undefined (reading containsPoint)'. Two
distinct null-triangle cases existed in PathOptimizer:

1. findChannelTriangles fed res.containsEnd back as the next start
   triangle without checking for null. getAllCrossedTriangles is a
   best-effort BFS that can leave containsEnd null on degenerate input
   (collinear points, points exactly on triangle boundaries). The fix
   relocates the next start triangle via the CDT's triangle R-tree
   (getRectangleNodeOnTriangles + PointIsInsideOfTriangle) when the BFS
   doesn't deliver one. getAllCrossedTriangles itself now returns an
   empty result on null input instead of throwing.

2. findSourceTriangle returns undefined when no triangle of the local
   CDT contains poly.start. The caller in run() now treats that as a
   'failed to create sleeve' case and bails out, mirroring the existing
   handling for getSleeve returning null.

Both code paths are degeneracy fallbacks: this.poly is left unchanged
and the spline router falls back to the unrefined polyline for that
edge, instead of aborting the whole routing call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A stripped-down WebGL demo of @msagl that always uses sleeve routing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…deploy script

Sample dropdown previously fetched two JSON graphs cross-origin from
raw.githubusercontent.com. To support the anonymous review demo at
https://gjs-sleeve.surge.sh, the example now serves all nine paper
graphs from its own deployment with no cross-origin dependency:

* settings.ts: replace SAMPLE_GRAPHS with all 9 graphs from the paper
  benchmark (gameofthrones, composers, ca-GrQc, facebook_combined,
  ca-HepTh, delaunay_n15, ca-HepPh, ca-CondMat, deezer_europe), each
  pointing at a relative ./graphs/* URL. Add a per-graph etaSec
  estimate and a SLOW_GRAPH_ETA_SEC = 30 threshold.

* app.ts: when the user picks a graph whose etaSec is at or above the
  threshold, window.confirm() warns 'loading might take a long time'
  before fetching; cancel resets the dropdown.

* graphs/: stage the nine graphs (large edge-list / mtx / csv files
  pre-gzipped; the parser auto-gunzips via DecompressionStream). Total
  payload ~3.6 MB. .gitignore: scope the 'graphs' ignore to repo root
  so the example's per-graph data is tracked.

* package.json: build script now copies examples/webgl-sleeve/graphs/
  into website/static/webgl-sleeve/graphs/ so the surge publish dir is
  self-contained.

* deploy-webgl-sleeve.sh (new, at repo root): build the example then
  'surge website/static/webgl-sleeve gjs-sleeve.surge.sh'. Verifies a
  surge identity (cached login or SURGE_LOGIN/SURGE_TOKEN env vars).
  Supports --skip-build for republishing already-built assets.

* website/static/webgl-sleeve/app.js: rebuilt artifact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Renderer: new private _maxTileLevels (default 8) plus setMaxTileLevels /
  maxTileLevels accessors; _update() forwards to TileMap.buildUpToLevel
  instead of the hardcoded 8.
- webgl-sleeve example: ?maxLevels= URL param plumbs into the renderer;
  ?graph= alongside ?url= for deep-linking by SAMPLE_GRAPHS label;
  window.__msaglReady / __msaglRenderer exposed for the harness.
- New bench/smoothness.mjs Puppeteer harness: serves the bundled demo
  on a random port, runs (graph × {pyramid, singleLayer} × trials),
  captures rAF frame times, PerformanceObserver long tasks, and a
  Tileset2D probe that sums visible-tile element counts at peak zoom.
  Writes JSONL. ca-HepPh pyramid hits the in-browser limit; the other
  three benchmark graphs complete the full ablation.

Used to produce Table 4 / §6 (Browsing smoothness) in the GD 2026
submission paper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite of the browsing-smoothness harness to match what the GD 2026
paper's tab:smoothness reports:

- Replace the (graph, maxLevels) ablation matrix with a single
  per-graph run; widen the graph list to the nine benchmarks the
  paper uses (gameofthrones, composers, four SNAP collab networks,
  facebook_combined, deezer_europe, delaunay_n15).
- Drive each trial as three deterministic dives picked by a seeded
  mulberry32 PRNG inside the laid-out graph footprint
  (window.__msaglGraphBox); each dive interpolates minZoom -> maxZoom
  -> minZoom over a wall-clock budget so jank does not stretch the
  measurement window.
- Probe the deck.gl Tileset2D at peak zoom of each dive and record
  per-frame elements and tiles visible, so the report can show the
  largest number of graphical objects the browser had to draw.
- Bump protocolTimeout and keep one puppeteer browser warm across
  graphs, while still recycling pages between trials so released
  graph state can be GCed.

Add a header comment pointing readers at the paper-side
post-processor and canonical JSONL, which now live in
~/dev/paper_msagljs/ next to the rest of the benchmark artefacts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Renderer (renderer-webgl):
- Interpolate node box size per frame between the current and next-finer tile
  level (GeometryLayer sizeLerp uniform + instanceSizesFiner) so node size is
  continuous across tile-level switches instead of jumping.
- Render node labels at a fixed pixel size derived from the box's minimum
  on-screen size over the band: getLabelSize * min(scaleCurr, 2*scaleFiner) *
  2^(nativeZoom-1). No pulsation and the label never overflows its box at any
  zoom, on uniform (GoT) and adaptive-scale (Composers) graphs alike.
- Draw earlier-listed style layers (nodes) on the nearer depth slice so opaque
  node boxes render over edges and hide the edge ends that run to node centers.
- Make edges twice thinner and slightly transparent.

Routing (core):
- Add a required trimEdges flag to routeSleeveEdges: the full-graph layout
  (driver) trims curves to node borders and keeps arrowheads, while the tile
  pyramid routes coarse levels untrimmed (curves to node centers) so edges stay
  connected under per-level/per-frame node scaling. When not trimming, clear any
  arrowheads left by a prior trimmed pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	.github/workflows/test.yml
On the finest tile level there is no finer level to interpolate toward
(scaleCurr == scaleFiner == 1), so node boxes grow with zoom while
fixed-pixel labels stayed put on screen. Render finest-level labels in
'common' units so they scale at the same speed as the box; coarser levels
keep their fixed-pixel labels. The transition stays continuous across the
level switch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an interactive @msagl/renderer-webgl citation-graph viewer with:
- author/title search; repeated Enter walks one author's papers by citation
  rank (inline pill, no dropdown over the canvas); mode switch keeps the query
- LOD-aware zoom so rarely-cited nodes (only in the finest tiles) become
  visible and highlighted: new Renderer.zoomToNode(); SearchControl uses it too
- defaults to rise_3k.json (~2.8k papers / ~10k citations)

Ships the built viewer under website/static/citation-graph so it appears on
the GitHub Pages site.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…serves them

The webgl-sleeve sample graphs 404'd on GitHub Pages because the graphs/
folder was only produced by the example build script and never tracked in
website/static, so Docusaurus deploy shipped the demo without them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the vague "nine sample graphs" wording with a table naming each
bundled sample and a deep link (?graph=<file>) that loads it directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…plines, None)

The EdgeRoutingMode reference explained Spline, SplineBundling, Rectilinear,
StraightLine and Sleeve but left RectilinearToCenter, SugiyamaSplines and None
without their own sections. Add them so every enum value is described.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.9 to 2.0.10.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.10/CHANGELOG.md)
- [Commits](chimurai/http-proxy-middleware@v2.0.9...v2.0.10)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
In TileMap.addNodeToLevel, dataByEntity.get(entity) can return undefined
for edges/labels that have no clips at the current tile level. Iterating
that undefined value with for..of threw 'TypeError: i.get is not a
function or its return value is not iterable', aborting the WebGL render.

Guard every dataByEntity.get(...) lookup with a '?? []' fallback so
missing entities are skipped instead of crashing the render.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1c16320-f8f4-42d3-9e4c-889223f2a531
Add ../JSONfiles/gameofthrones.json to the sample-graph dropdown in both
the SVG and WebGL renderer examples, alongside the existing composers.json
JSON sample.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1c16320-f8f4-42d3-9e4c-889223f2a531
…on) load without crashing

The deployed website/static/renderer-svg/app.js was stale (from 2023) and
predated the JSON/JGF parser fixes, so loading citation-style JSON graphs
threw an uncaught error on the live GitHub Pages site. Rebuilt from current
source.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cc0d891e-4cbf-471e-bec0-4b95c4cb95d3
Signed-off-by: Lev Nachmanson <levnach@hotmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Teach compatible agents to embed live graph viewers, run core layouts, and export document-ready vector assets. Add locked helper tooling and CI smoke coverage for discovery, browser loading, and SVG/PDF/PNG/EPS/PS output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return s.toFixed(2)
}
lines.push(
r.name.replace(/_/g, '\\_') + ' & ' +
}
lines.push(
r.name.replace(/_/g, '\\_') + ' & ' +
r.distinctSources.toLocaleString('en-US').replace(/,/g, '\\,') + ' & ' +
lines.push(
r.name.replace(/_/g, '\\_') + ' & ' +
r.distinctSources.toLocaleString('en-US').replace(/,/g, '\\,') + ' & ' +
r.vcRoots.toLocaleString('en-US').replace(/,/g, '\\,') + ' & ' +
fmt(r.astarMs) + ' & ' +
fmt(r.dijkstraMs) + ' & ' +
fmt(r.dijkstraVcMs) + ' & ' +
'$' + pct(r.dijkstraMs, r.astarMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ & ' +
fmt(r.dijkstraMs) + ' & ' +
fmt(r.dijkstraVcMs) + ' & ' +
'$' + pct(r.dijkstraMs, r.astarMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ & ' +
'$' + pct(r.dijkstraVcMs, r.dijkstraMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ \\\\')
}

function texEscape(name: string): string {
return name.replace(/_/g, '\\_')
fmt(r.astarMs) + ' & ' +
fmt(r.dijkstraMs) + ' & ' +
fmt(r.dijkstraVcMs) + ' & ' +
'$' + pct(r.dijkstraMs, r.astarMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ & ' +
fmt(r.astarMs) + ' & ' +
fmt(r.dijkstraMs) + ' & ' +
fmt(r.dijkstraVcMs) + ' & ' +
'$' + pct(r.dijkstraMs, r.astarMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ & ' +
fmt(r.dijkstraMs) + ' & ' +
fmt(r.dijkstraVcMs) + ' & ' +
'$' + pct(r.dijkstraMs, r.astarMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ & ' +
'$' + pct(r.dijkstraVcMs, r.dijkstraMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ \\\\')
fmt(r.dijkstraMs) + ' & ' +
fmt(r.dijkstraVcMs) + ' & ' +
'$' + pct(r.dijkstraMs, r.astarMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ & ' +
'$' + pct(r.dijkstraVcMs, r.dijkstraMs).replace('%', '\\,\\%').replace('+', '+').replace('-', '-') + '$ \\\\')
@levnach

Copy link
Copy Markdown
Collaborator Author

CI status: all build matrices, the new agent-skill jobs, CLA, and both CodeQL analysis jobs pass. The separate CodeQL policy gate reports 16 alerts in pre-existing dev files (examples/chrome-routing-bench/* and benchmark specs under modules/core/test/layout/); none are in the new skills/msagljs change. They are being left untouched because they are outside this skill implementation.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Promotes the substantially-ahead dev branch changes to main, including a new distributable msagljs Agent Skill, new website demos/docs, and significant renderer/core updates (notably sleeve routing + tile-pyramid UX improvements) across the monorepo.

Changes:

  • Add an msagljs Agent Skill (docs, references, examples, and CI smoke tests for live rendering + static export formats).
  • Introduce/extend sleeve routing support end-to-end (core routing mode + driver wiring + WebGL demo/docs updates).
  • Improve WebGL renderer UX/interaction (top-bar layout, edge picking/highlighting, continuous node sizing across tile levels) and add new demo pages.

Reviewed changes

Copilot reviewed 126 out of 157 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
website/static/webgl-sleeve/index.html New deployed WebGL sleeve demo page
website/static/renderer-webgl/index.html Refresh renderer-webgl deployed UI (top bar, URL loader, smooth corners toggle, error banner)
website/static/citation-graph/index.html New deployed citation-graph demo page
website/docs/intro.md Link to new sleeve/tile-pyramid demos page
website/docs/demos.md New docs page describing demos + paper links
website/docs/api.md Document new routing modes (RectilinearToCenter, Sleeve) and clarify others
tsconfig.skill-examples.json Add TS config for skill example typechecking
SUPPORT.md Minor wording normalization
skills/msagljs/SKILL.md New skill manifest + usage guidance
skills/msagljs/scripts/smoke-live-page.mjs New puppeteer smoke test for live SVG embedding
skills/msagljs/scripts/package.json Skill helper tool dependencies for smoke/export scripts
skills/msagljs/references/troubleshooting.md Skill troubleshooting reference
skills/msagljs/references/packages-and-inputs.md Skill package selection + parsing inputs reference
skills/msagljs/references/live-web-embedding.md Skill guidance for live SVG/WebGL embedding
skills/msagljs/references/layout-and-routing.md Skill guidance for layout/routing selection
skills/msagljs/references/document-export.md Skill guidance for static SVG/PDF/PNG/EPS/PS export
skills/msagljs/references/core-layout.md Skill guidance for headless core layout workflow
skills/msagljs/references/browser-renderers.md Skill guidance for renderer usage + lifecycle
skills/msagljs/examples/network.txt Skill example edge-list input
skills/msagljs/examples/network.json Skill example JSON input
skills/msagljs/examples/network.jgf Skill example JGF input
skills/msagljs/examples/network.dot Skill example DOT input
skills/msagljs/examples/live-webgl-page.ts Skill example: live WebGL embedding
skills/msagljs/examples/live-svg-page.ts Skill example: live SVG embedding + SVG download
skills/msagljs/examples/live-page.html Skill example HTML host page
skills/msagljs/examples/latex-figure.tex Skill example LaTeX include snippet
skills/msagljs/examples/core-layout.ts Skill example: core layout without renderer
README.md Document discovery/installation of msagljs Agent Skill
modules/renderer-webgl/src/layers/graph-layer.ts Edge picking support + layer depth ordering + tile-level props plumbing
modules/renderer-webgl/src/layers/graph-highlighter.ts Add edge index/picking color mapping + direct node highlighting helpers
modules/renderer-webgl/src/layers/get-edge-layers.ts Support pickable edges + tweak default edge styling
modules/renderer-webgl/src/layers/geometry-layer.ts Continuous cross-tile node size interpolation + cluster-highlight dampening
modules/renderer-webgl/src/layers/curve-layer.ts Add per-edge picking colors + hovered-edge highlighting/thickening
modules/renderer-webgl/src/index.ts Export TooltipProvider from renderer entrypoint
modules/renderer-webgl/src/controls/search-control.ts Use renderer.zoomToNode() helper (remove GeomNode dependency)
modules/renderer-webgl/package.json Bump version + update internal dependency ranges
modules/renderer-svg/package.json Bump version + update internal dependency ranges
modules/renderer-common/src/layout.ts Worker layout supersede handling + applyLayoutSettings helper + new options (smoothCorners)
modules/renderer-common/src/index.ts Add smoothCorners to LayoutOptions
modules/renderer-common/package.json Bump version + update internal dependency ranges
modules/parser/test/dotparser.spec.ts Add tests for gzip + URL/file loading + JGF parsing variants
modules/parser/src/index.ts Export parseJGF from public API
modules/parser/package.json Bump version + update dependency ranges
modules/drawing/package.json Bump version + update dependency ranges
modules/core/test/utils/svgDebugWriter.ts Gate debug SVG filesystem writes behind env var
modules/core/test/routing/sleeveRouterGot.spec.ts New GOT sleeve-routing stability/behavior tests
modules/core/test/routing/sleeveRouter.spec.ts New sleeve router unit tests + subgraph routing case
modules/core/test/routing/sleeveEdgeLengths.spec.ts New sleeve vs spline edge-length ratio benchmarks (non-asserting)
modules/core/test/routing/SingleSourceSingleTargetShortestPathOnVisibilityGraph.spec.ts Update tests to new GetPath signature
modules/core/test/math/geometry/pointArray.spec.ts New PointArray tests
modules/core/test/math/geometry/point.spec.ts Add tests for new in-place Point mutation APIs
modules/core/test/layout/layoutEditing/incrementalDragger.spec.ts Remove test-time filesystem output
modules/core/test/layout/largeGraphs.spec.ts Add skipped large-graph loader/layout tests scaffold
modules/core/src/structs/graph.ts Update pagerank to use undirected-degree propagation
modules/core/src/routing/visibility/VisibilityGraph.ts Remove edge-length shrink helper
modules/core/src/routing/visibility/VisibilityEdge.ts Remove LengthMultiplier from visibility edges
modules/core/src/routing/splineRouter.ts Remove unused edge-length multiplier plumbing
modules/core/src/routing/spline/pathOptimizer.ts Add degeneracy handling + triangle lookup fallback
modules/core/src/routing/spline/bundling/CostCalculator.ts Use distPP to reduce Point allocations
modules/core/src/routing/SingleSourceSingleTargetShortestPathOnVisibilityGraph.ts Simplify A* path API (remove shrink/multiplier plumbing)
modules/core/src/routing/PreGraph.ts Avoid concat allocation in AddGraph
modules/core/src/routing/passportRouting.ts Extract passport/obstacle helpers for cluster-aware routing reuse
modules/core/src/routing/interactiveEdgeRouter.ts Remove unused multiplier flag + avoid intermediate array allocations
modules/core/src/routing/EdgeRoutingSettings.ts Add smoothCorners setting
modules/core/src/routing/EdgeRoutingMode.ts Add Sleeve routing mode
modules/core/src/math/geometry/smoothedPolyline.ts Cleanup + fix bezier end-point usage in curve construction
modules/core/src/math/geometry/rectangle.ts Reduce Point allocations + add addCoords + correct containsRect comment/spelling
modules/core/src/math/geometry/pointArray.ts New Float64Array-backed PointArray utility
modules/core/src/math/geometry/point.ts Optimize dist/close ops + make x/y fields mutable + add in-place mutation APIs
modules/core/src/math/geometry/index.ts Export PointArray
modules/core/src/math/geometry/debugSvg.ts New browser-safe DebugCurve SVG serializer/downloader
modules/core/src/math/geometry/bezierSeg.ts Make lengthOnControlPolygon iterative (avoid allocations/recursion)
modules/core/src/layout/mds/AllPairsDistances.ts Optimize stress computation loop and symmetry accounting
modules/core/src/layout/layered/ProperLayeredGraph.ts Replace filter allocations with counted loops
modules/core/src/layout/incremental/multipole/minimumEnclosingDisc.ts Replace recursive Welzl with iterative implementation
modules/core/src/layout/driver.ts Wire Sleeve routing through routeEdges()
modules/core/src/layout/core/tile.ts Bundle CurveClips by endpoint pairs to reduce per-tile edge geometry
modules/core/src/index.ts Export new debug SVG helpers + sleeve routing APIs + CDT + PointArray
modules/core/package.json Bump core version
examples/webgl-sleeve/tsconfig.json New TS config for webgl-sleeve example
examples/webgl-sleeve/src/worker.ts Worker entrypoint for webgl-sleeve example
examples/webgl-sleeve/src/settings.ts Sample graph catalog + layout/routing UI constants
examples/webgl-sleeve/src/drag-n-drop.ts Drag/drop helper for example UI
examples/webgl-sleeve/package.json New webgl-sleeve example package + build pipeline to website/static
examples/webgl-sleeve/index.html Example host page for webgl-sleeve
examples/webgl-sleeve/bench/.gitignore Ignore bench output
examples/webgl-renderer/src/settings.ts Add Sleeve routing option and add GOT sample to list
examples/webgl-renderer/package.json Ensure worker is bundled for deployment build
examples/webgl-renderer/index.html Refresh example UI to match deployed renderer-webgl page
examples/svg-renderer/src/settings.ts Add GOT sample and Sleeve routing option
examples/svg-renderer/src/app.ts Support Sleeve routing option
examples/citation-graph/tsconfig.json New TS config for citation-graph example
examples/citation-graph/src/worker.ts Worker entrypoint for citation-graph example
examples/citation-graph/README.md Document citation-graph demo behavior + tooltip provider
examples/citation-graph/package.json New citation-graph example package + deployment build
examples/citation-graph/index.html Citation-graph example host page
examples/citation-graph/.gitignore Ignore built artifacts + node_modules
examples/chrome-routing-bench/server.js Local benchmark server for paper experiments
examples/chrome-routing-bench/README.md Benchmark usage documentation
examples/chrome-routing-bench/loading.html Loading benchmark page
examples/chrome-routing-bench/index.html Routing-modes benchmark page
examples/chrome-routing-bench/.gitignore Ignore local msagl bundle artifact
deploy-webgl-sleeve.sh Script to build + publish webgl-sleeve demo via surge
.gitignore Ignore top-level graphs directory
.github/workflows/test.yml Expand Node matrix + add agent-skill validation job + refine deploy gating
.github/workflows/codeql-analysis.yml Change CodeQL branch triggers
.github/copilot-instructions.md Add repo-specific Copilot contributor guidance
Files not reviewed (1)
  • skills/msagljs/scripts/package-lock.json: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +22 to 32
if (layoutInProgress && layoutWorker) {
// Supersede the in-flight request. Clear handlers first so terminate()
// does not fire our onerror and reject the (now abandoned) promise — that
// would surface as "Uncaught (in promise)" in the example code which does
// not catch rejections from setOptions/setGraph.
layoutWorker.onmessage = null
layoutWorker.onerror = null
layoutWorker.terminate()
layoutWorker = null
layoutInProgress = false
}
layoutSettings.edgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.SugiyamaSplines
} else {
layoutSettings.edgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.Spline
layoutSettings.edgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.Sleeve
routeEdges(geomGraph, Array.from(geomGraph.deepEdges), null)
// console.timeEnd('routeEdges')
}
console.log(`layout: ${(performance.now() - t0).toFixed(1)}ms`)
Comment on lines +5 to +6
const ROOT_BENCH = '/Users/levnach/dev/msagljs/examples/chrome-routing-bench';
const ROOT_GRAPHS = '/Users/levnach/dev/paper_msagljs/graphs';
@@ -0,0 +1,161 @@
<html>
@@ -0,0 +1,161 @@
<html>
Comment on lines 15 to +19
push:
branches: [ "main" ]
branches: [ "dev" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
branches: [ "dev" ]
Comment on lines +56 to +58
- Sleeve routing exists on the repository's development branch but is not
exposed by the npm `@msagl/core` version locked by the portable exporter.
Check the installed package before using it in application code.
@levnach
Lev Nachmanson (levnach) merged commit 66b2cc6 into main Aug 5, 2026
10 of 11 checks passed
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.

4 participants