Skip to content

feat(joint-router-avoid): new avoid router package - #3443

Closed
Geliogabalus wants to merge 47 commits into
clientIO:masterfrom
Geliogabalus:routers-libavoid
Closed

feat(joint-router-avoid): new avoid router package#3443
Geliogabalus wants to merge 47 commits into
clientIO:masterfrom
Geliogabalus:routers-libavoid

Conversation

@Geliogabalus

@Geliogabalus Geliogabalus commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds @joint/router-avoid, a new package that routes JointJS links via libavoid (WASM, through libavoid-js), keeping a graph's links obstacle-avoiding and orthogonally routed automatically - no router: 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.
  • Extracts the rightAngle router's path-finding into a new public @joint/core alg.rightAnglePath function, shared between the built-in rightAngle router 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 the rightAngle router's own test suite.
  • Adds useModelGeometry to the rightAngle router's options.
  • Adds examples/avoid-router-ts, a TypeScript demo app with three tabs: a simple graph, a "simple graph extra" showing two independent RouterService instances routing two disjoint subgraphs on the same dia.Graph with different settings, and a large graph routed via a Web Worker.
  • Full TSDoc across @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 as joint.routers.avoid.*).
  • RouterServiceOptions/InitAvoidOptions callbacks (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.
  • interceptUnroutableLink gives the consumer first refusal on a link avoid can't route (loose end, or connected to an element excluded via skipElement), before the built-in rightAngle fallback route applies.

Test plan

  • yarn test passes for @joint/router-avoid (21/21)
  • @joint/core's test suite passes, including the previously-broken rightAngle router suite
  • examples/avoid-router-ts type-checks (tsc --noEmit) and builds (webpack)
  • Manually verified all three demo tabs (simple / simple extra / large graph) render and route correctly via a headless-browser smoke test
  • Manual review of the libavoid integration (main-thread and Worker providers) against a real app

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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-avoid with main-thread + Web Worker providers, plus Karma/QUnit end-to-end tests.
  • Refactors rightAngle router to reuse a new exported alg.rightAnglePath() implementation and adjusts LinkView anchor usage.
  • Adds an examples/avoid-router demo 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

  • flushMessageQueue is currently a const debounced with the module-level default debounceTime. To support debounceTime coming from init options, 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.

Comment thread packages/joint-router-avoid/src/RouterService.mts Outdated
Comment thread packages/joint-router-avoid/src/RouterService.mts Outdated
Comment thread packages/joint-router-avoid/src/providers/Worker.mts Outdated
Comment thread package.json Outdated
Comment thread packages/joint-router-avoid/README.md Outdated
# Conflicts:
#	packages/joint-core/src/routers/rightAngle.mjs
@Geliogabalus Geliogabalus changed the title Add libavoid router support feat(joint-router-avoid): new avoid router package Aug 13, 2026
@Geliogabalus
Geliogabalus marked this pull request as ready for review August 13, 2026 13:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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-provided options object 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 leaving options untouched.
    packages/joint-router-avoid/src/providers/Worker.mts:236
  • In the Worker implementation, linksByPointer is populated from connRef.g in handleUpdateConnector(), but handleDeleteConnector() 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 accepting options?, but the function signature currently requires options. 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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 TrackElementCallback TSDoc is inverted: the implementation treats trackElement(...) === true as “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 TrackLinkCallback contract in the TSDoc is inverted: the implementation treats trackLink(...) === true as “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:pending event, but the public event names are link:routing and link:routing:cancelled. The mismatch makes the routing-cycle logic harder to follow/maintain.
    packages/joint-router-avoid/src/RouterService.mts:91
  • This UnroutableReason doc still references skipElement, but the option is named trackElement. It also omits the 'unsupported' reason even though it’s part of the union type.
    packages/joint-router-avoid/README.md:80
  • The InitAvoidOptions example uses option names that don’t exist in the implementation (skipLink, skipElement, updateDebounceTime, libraryFilePath). The actual API uses trackLink, trackElement, workerUpdateDebounceTime, and libavoidFilePath (plus setRouteAttributes/changeFlag).
    packages/joint-router-avoid/README.md:95
  • The RouterService section 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 call routerService.start(), and the events are link:routing, link:routed, link:routing:cancelled, and idle.
    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 are trackLink/trackElement. Exporting the real type names too would make the public API clearer (and avoids encouraging the outdated skip* naming).

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.

3 participants