feat(joint-router-avoid): new avoid router package - #3443
Conversation
… content after paper unfreeze Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds libavoid-based orthogonal routing to JointJS via a new workspace package and demo, with supporting changes in @joint/core to share the rightAngle path algorithm and improve routing/anchor handling.
Changes:
- Introduces
@joint/router-avoidwith main-thread + Web Worker providers, plus Karma/QUnit end-to-end tests. - Refactors
rightAnglerouter to reuse a new exportedalg.rightAnglePath()implementation and adjustsLinkViewanchor usage. - Adds an
examples/avoid-routerdemo app and wires new workspace deps into the monorepo.
Reviewed changes
Copilot reviewed 43 out of 47 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Adds workspace entries and deps for the new package + demo. |
| packages/joint-router-avoid/tsconfig.json | Base TS config for the new package. |
| packages/joint-router-avoid/tsconfig.esm.json | ESM build TS config. |
| packages/joint-router-avoid/tsconfig.cjs.json | CJS build TS config (currently unused by scripts). |
| packages/joint-router-avoid/test/libavoid-loader.mjs | Test-only loader to expose libavoidJs global for UMD. |
| packages/joint-router-avoid/test/index.js | QUnit end-to-end tests for RouterService behavior. |
| packages/joint-router-avoid/test/index.html | Browser test harness for the package. |
| packages/joint-router-avoid/src/RouterService.mts | Core routing service syncing a dia.Graph to libavoid providers. |
| packages/joint-router-avoid/src/providers/WorkerProvider.mts | Provider implementation backed by a Web Worker. |
| packages/joint-router-avoid/src/providers/Worker.mts | Worker-side libavoid integration + debounced batching. |
| packages/joint-router-avoid/src/providers/Provider.mts | Provider abstraction types and interface. |
| packages/joint-router-avoid/src/providers/MainThreadProvider.mts | Main-thread libavoid provider implementation. |
| packages/joint-router-avoid/src/init.mts | Public init/load helpers to bootstrap libavoid and RouterService. |
| packages/joint-router-avoid/src/index.mts | Package entrypoint exports. |
| packages/joint-router-avoid/SECURITY.md | Package-level security policy doc. |
| packages/joint-router-avoid/rollup.config.mjs | Rollup UMD build config for the package. |
| packages/joint-router-avoid/README.md | Package documentation (currently inconsistent with shipped API). |
| packages/joint-router-avoid/package.json | New workspace package manifest. |
| packages/joint-router-avoid/LICENSE | MPL-2.0 license file for the package. |
| packages/joint-router-avoid/karma.conf.js | Karma config for running browser tests + WASM proxying. |
| packages/joint-router-avoid/eslint.config.mjs | ESLint flat config for the package. |
| packages/joint-router-avoid/.gitignore | Ignores build/test artifacts in the new package. |
| packages/joint-core/types/index.d.ts | Exposes alg types from @joint/core. |
| packages/joint-core/types/alg.d.ts | Adds TS types for alg.rightAnglePath(). |
| packages/joint-core/src/routers/rightAngle.mjs | Refactors to delegate core pathing to alg.rightAnglePath(). |
| packages/joint-core/src/dia/LinkView.mjs | Ensures connection points use the latest stored anchors. |
| packages/joint-core/src/core.mjs | Exports alg from the core entrypoint. |
| packages/joint-core/src/alg/rightAnglePath.mjs | New shared right-angle path algorithm implementation. |
| packages/joint-core/src/alg/index.mjs | Exports rightAnglePath from alg. |
| package.json | Updates pack-all to include the new package (name currently mismatched). |
| examples/avoid-router/webpack.config.js | Webpack config for the demo + copies libavoid.wasm. |
| examples/avoid-router/styles.scss | Shared styling for the demo UI. |
| examples/avoid-router/src/simple-graph/shapes.js | Demo shapes for the simple graph tab. |
| examples/avoid-router/src/simple-graph/resize-tool.js | Demo resize tool for nodes. |
| examples/avoid-router/src/simple-graph/example.js | Simple graph demo wiring initAvoid(). |
| examples/avoid-router/src/simple-graph-extra/shapes.js | Demo shapes for “simple extra” tab. |
| examples/avoid-router/src/simple-graph-extra/resize-tool.js | Resize tool for “simple extra”. |
| examples/avoid-router/src/simple-graph-extra/example.js | “Simple extra” demo with custom unroutable handling. |
| examples/avoid-router/src/large-graph/shapes.js | Demo shapes for the large graph tab. |
| examples/avoid-router/src/large-graph/example.js | Large graph demo using Worker provider + pending styling. |
| examples/avoid-router/src/index.js | Demo tab switcher/initializer. |
| examples/avoid-router/src/common.js | Shared paper setup + interaction/zoom helpers. |
| examples/avoid-router/README.md | Demo usage instructions. |
| examples/avoid-router/package.json | Demo workspace package manifest. |
| examples/avoid-router/index.html | Demo page with tab UI + bundle script. |
| examples/avoid-router/.gitignore | Ignores demo build artifacts. |
Suppressed comments (1)
packages/joint-router-avoid/src/providers/Worker.mts:252
flushMessageQueueis currently aconstdebounced with the module-level defaultdebounceTime. To supportdebounceTimecoming frominitoptions, it needs to be re-creatable/reassignable.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
# Conflicts: # packages/joint-core/src/routers/rightAngle.mjs
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 43 out of 48 changed files in this pull request and generated no new comments.
Suppressed comments (3)
packages/joint-router-avoid/src/init.mts:72
initAvoidRouter()currently mutates the caller-providedoptionsobject when applying defaults. Since this is a public API, mutating inputs can cause surprising side effects if the same options object is reused elsewhere. Prefer computing defaults into local constants and leavingoptionsuntouched.
packages/joint-router-avoid/src/providers/Worker.mts:236- In the Worker implementation,
linksByPointeris populated fromconnRef.ginhandleUpdateConnector(), buthandleDeleteConnector()never removes that mapping. This can leak memory across connector churn and can also cause incorrect route attribution if libavoid reuses a pointer value for a later connector.
packages/joint-router-avoid/src/init.mts:61 initAvoidRouter()is documented/advertised as acceptingoptions?, but the function signature currently requiresoptions. Making it optional (defaulting to{}) aligns the public API with the docs and avoids forcing callers to pass an empty object.
This issue also appears on line 70 of the same file.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 45 out of 50 changed files in this pull request and generated no new comments.
Suppressed comments (7)
packages/joint-router-avoid/src/RouterService.mts:49
- The
TrackElementCallbackTSDoc is inverted: the implementation treatstrackElement(...) === trueas “track/include as an obstacle”, but the doc says “true to exclude”. This will cause consumers to implement the callback backwards.
packages/joint-router-avoid/src/RouterService.mts:35 - The
TrackLinkCallbackcontract in the TSDoc is inverted: the implementation treatstrackLink(...) === trueas “track/include”, but the doc says “true to exclude”. This will cause consumers to implement the callback backwards.
This issue also appears on line 45 of the same file.
packages/joint-router-avoid/src/RouterService.mts:196
- This comment refers to a
link:pendingevent, but the public event names arelink:routingandlink:routing:cancelled. The mismatch makes the routing-cycle logic harder to follow/maintain.
packages/joint-router-avoid/src/RouterService.mts:91 - This
UnroutableReasondoc still referencesskipElement, but the option is namedtrackElement. It also omits the'unsupported'reason even though it’s part of the union type.
packages/joint-router-avoid/README.md:80 - The
InitAvoidOptionsexample uses option names that don’t exist in the implementation (skipLink,skipElement,updateDebounceTime,libraryFilePath). The actual API usestrackLink,trackElement,workerUpdateDebounceTime, andlibavoidFilePath(plussetRouteAttributes/changeFlag).
packages/joint-router-avoid/README.md:95 - The
RouterServicesection documents methods/events that don’t exist (getRoute,addGraphListeners,link:pending,link:pending:cancelled) and says listening starts automatically. In code/tests/examples, consumers must callrouterService.start(), and the events arelink:routing,link:routed,link:routing:cancelled, andidle.
packages/joint-router-avoid/src/index.mts:2 - The package entrypoint only exports the callback types under
Skip*names, but the actual option property names aretrackLink/trackElement. Exporting the real type names too would make the public API clearer (and avoids encouraging the outdatedskip*naming).
Summary
@joint/router-avoid, a new package that routes JointJS links via libavoid (WASM, throughlibavoid-js), keeping a graph's links obstacle-avoiding and orthogonally routed automatically - norouter:attribute needed once a link connects two elements. Ships both a main-thread provider and a Web-Worker-backed provider (useWorker: true) for larger graphs.rightAnglerouter's path-finding into a new public@joint/corealg.rightAnglePathfunction, shared between the built-inrightAnglerouter and@joint/router-avoid's fallback route (used while avoid computes a link's real route, or when a link can't be routed by avoid at all). Fixed a couple of bugs in the process (stale flat-property references left over from the bbox-based refactor) that were breaking most of therightAnglerouter's own test suite.useModelGeometryto therightAnglerouter's options.examples/avoid-router-ts, a TypeScript demo app with three tabs: a simple graph, a "simple graph extra" showing two independentRouterServiceinstances routing two disjoint subgraphs on the samedia.Graphwith different settings, and a large graph routed via a Web Worker.@joint/router-avoid's public and internal API (types,RouterService, providers, the Worker script).API surface
initAvoidRouter(graph, options)/loadAvoidRouter(filePath?)- entry points (also on the UMD global asjoint.routers.avoid.*).RouterServiceOptions/InitAvoidOptionscallbacks (skipLink,skipElement,interceptUnroutableLink,setRouteAttributes) all take a single object parameter (e.g.skipLink: ({ link }) => …) rather than positional args, for forward-compatible extension.updateDebounceTime(Worker-only) controls how long queued shape/connector updates are batched before being flushed and processed in one transaction.interceptUnroutableLinkgives the consumer first refusal on a link avoid can't route (loose end, or connected to an element excluded viaskipElement), before the built-inrightAnglefallback route applies.Test plan
yarn testpasses for@joint/router-avoid(21/21)@joint/core's test suite passes, including the previously-brokenrightAnglerouter suiteexamples/avoid-router-tstype-checks (tsc --noEmit) and builds (webpack)🤖 Generated with Claude Code