Skip to content
Merged
Show file tree
Hide file tree
Changes from 123 commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
0149194
feat(library): orthogonal edge routing with node + edge avoidance (ch…
FelixTJDietrich Jul 15, 2026
09bf94d
refactor(library): extract routeStepEdge routing primitive
FelixTJDietrich Jul 15, 2026
9e10c91
feat(library): central edge-geometry solver core (unwired)
FelixTJDietrich Jul 15, 2026
b0c36fc
test(library): shadow-compare parity gate for the central solver
FelixTJDietrich Jul 15, 2026
a234ce8
feat(library): wire central edge solver into rendering behind edgeRou…
FelixTJDietrich Jul 15, 2026
abf26a4
feat(library): route neighbours around the live drag preview in centr…
FelixTJDietrich Jul 15, 2026
4b94568
feat(library): make the central edge solver the default routing engine
FelixTJDietrich Jul 15, 2026
38e9f1f
refactor(library): retire the central-vs-per-edge parity scaffolding
FelixTJDietrich Jul 15, 2026
d601b11
refactor(library): delete the per-edge geometry-publish cascade
FelixTJDietrich Jul 15, 2026
bf2fd5b
refactor(library): route step edges solely from the central solver
FelixTJDietrich Jul 15, 2026
6248a76
refactor(library): remove the edgeRouting flag — central is the only …
FelixTJDietrich Jul 15, 2026
89087d1
refactor(library): fix solver correctness gaps + delete enableStraigh…
FelixTJDietrich Jul 15, 2026
87ca7b2
refactor(library): break orthogonalRouter↔edgeUtils import cycle
FelixTJDietrich Jul 15, 2026
f24bdcf
refactor(library): cut dead params, stray docs, and a needless export
FelixTJDietrich Jul 15, 2026
93a05b9
refactor(library): cut comment theatre across the edge subsystem
FelixTJDietrich Jul 16, 2026
66fe8e7
test(library): stabilize the routing fuzzer and de-theatre the solver…
FelixTJDietrich Jul 16, 2026
09fc31f
test(webapp): fix edge-fresh-bend consecutive-drag gesture
FelixTJDietrich Jul 16, 2026
b36fbaf
perf(library): memoize per-edge routing across drag frames
FelixTJDietrich Jul 16, 2026
8c12c3d
test(webapp): add a drag-lag scaling benchmark
FelixTJDietrich Jul 16, 2026
cdd8714
perf(library): index routed polylines to drop collectNeighbors from O…
FelixTJDietrich Jul 16, 2026
7897dd4
feat(library): generalize the orthogonal router to a target SET
FelixTJDietrich Jul 16, 2026
2165d42
feat(library): auto-optimise edge endpoint anchors
FelixTJDietrich Jul 16, 2026
035d637
fix(library): commit the A* anchor route unless a straight shot is clean
FelixTJDietrich Jul 16, 2026
5fbbbe2
perf(library): skip getEdgePosition when both edge ends are anchored
FelixTJDietrich Jul 16, 2026
88fcf25
perf(library): weigh anchor cost and cache it on an intrinsic key
FelixTJDietrich Jul 16, 2026
ff2f044
test(webapp): raise the drag search budget for auto anchors
FelixTJDietrich Jul 16, 2026
9582e98
feat(library): straighten auto edges between overlapping nodes
FelixTJDietrich Jul 16, 2026
e683d76
test: cover auto endpoint anchoring end to end
FelixTJDietrich Jul 16, 2026
06a2fc3
fix(library): stop auto anchors from grazing their own endpoint node
FelixTJDietrich Jul 16, 2026
4cc0037
feat(library): corner-minimising geometric port assignment
FelixTJDietrich Jul 18, 2026
828ab7c
test(webapp): metric quality gate for auto edge routing
FelixTJDietrich Jul 18, 2026
f13dc6d
feat(library): routeConflictScore — cross-edge cost primitive
FelixTJDietrich Jul 18, 2026
dabaee2
feat(library): cross-edge crossing/overlap term in the anchor cost
FelixTJDietrich Jul 18, 2026
525e512
fix(library): straighten an edge with one PINNED end
FelixTJDietrich Jul 18, 2026
13dbe18
fix(library): route fully-pinned edges first so autos see them
FelixTJDietrich Jul 18, 2026
05dc689
test(webapp): routing-case-56 — auto edge straight beside a pinned edge
FelixTJDietrich Jul 18, 2026
a19a740
fix(library): centre a lone edge on its node side
FelixTJDietrich Jul 18, 2026
ba2f545
fix(library): a tight overlap is not a straight shot; centre same-par…
FelixTJDietrich Jul 18, 2026
f32ac52
fix(library): crossing-aware side assignment and non-crossing port order
FelixTJDietrich Jul 18, 2026
80c1f6d
test(webapp): add routing-case-60 fixture
FelixTJDietrich Jul 18, 2026
9a1c998
fix(library): geometric bundle mirror; the band orders, never aims
FelixTJDietrich Jul 18, 2026
422d619
test(library): pin invariant sweeps
FelixTJDietrich Jul 18, 2026
b9a3f2a
feat(library): spread ports evenly across a shared side
FelixTJDietrich Jul 18, 2026
99c34d1
feat(library): merge UML generalisation arrowheads; more generous spa…
FelixTJDietrich Jul 18, 2026
6506442
fix(library): make UML generalisation trees route cleanly
FelixTJDietrich Jul 18, 2026
ea59173
revert(library): drop the inheritance arrowhead merge
FelixTJDietrich Jul 18, 2026
c1b949d
refactor(library): one home for the rect-side vocabulary
FelixTJDietrich Jul 18, 2026
1a929e0
fix(library): close four gaps between the router's claims and its code
FelixTJDietrich Jul 18, 2026
b8ea2ab
fix(library): key the node index on geometry, not array identity
FelixTJDietrich Jul 18, 2026
96413c7
test(webapp): stop running the assertion-free benchmark in CI; genera…
FelixTJDietrich Jul 18, 2026
87348ff
docs(library): make the geometry comments true and cut the theatre
FelixTJDietrich Jul 18, 2026
7156e8c
refactor(library): one home for clamp and lexLess
FelixTJDietrich Jul 18, 2026
7aee6ce
test: remove vacuous assertions and a dead fixture
FelixTJDietrich Jul 18, 2026
d62eeca
test(library): guard the determinism invariant directly
FelixTJDietrich Jul 18, 2026
2d4b3aa
Merge remote-tracking branch 'origin/main' into feat/orthogonal-edge-…
FelixTJDietrich Jul 18, 2026
cf42977
chore: add changeset for the edge-routing rewrite
FelixTJDietrich Jul 18, 2026
f7e0368
fix(library): read the source node id as a primitive in the ghost line
FelixTJDietrich Jul 18, 2026
27a4bca
test: stabilise the routing tests and type them strictly
FelixTJDietrich Jul 18, 2026
43cbf87
Merge remote-tracking branch 'origin/main' into feat/orthogonal-edge-…
FelixTJDietrich Jul 18, 2026
ff3ba4f
test(webapp): count a merged line-jump crossing as bridged
FelixTJDietrich Jul 18, 2026
3cf66ff
fix: make the CI gates pass with the central router
FelixTJDietrich Jul 18, 2026
f9bff29
fix(library): pin a new connection to a shaped node's drop point
FelixTJDietrich Jul 19, 2026
4c67f05
test(webapp): refresh two visual baselines for the new routing
FelixTJDietrich Jul 19, 2026
d422485
fix(library): externalize @xyflow/system so the entry stops bundling it
FelixTJDietrich Jul 19, 2026
dc0a1d9
fix(library): detect neighbour edges by segment, not just vertices
FelixTJDietrich Jul 19, 2026
228403f
fix(library): key the node-obstacle cache on identity, not just geometry
FelixTJDietrich Jul 19, 2026
5f7dcf0
fix(library): keep a native-handle connection in its previewed lane a…
FelixTJDietrich Jul 19, 2026
9ab7c67
fix(library): seat side ports in non-crossing order, not coordinate o…
FelixTJDietrich Jul 19, 2026
d88c7dd
fix(library): order side ports by band-clamped tangential, then reach
FelixTJDietrich Jul 19, 2026
ef25761
fix(library): make side assignment dodge nodes and price crossings
FelixTJDietrich Jul 19, 2026
61bf23b
fix(library): spread edges across four-centre node sides and place th…
FelixTJDietrich Jul 20, 2026
873abee
fix(library): let endpoints own a short edge instead of a centred ben…
FelixTJDietrich Jul 20, 2026
4dc6616
fix(library): reserve an endpoint zone so no bend handle ever starves…
FelixTJDietrich Jul 20, 2026
feb24dd
fix(library): keep every edge handle present and visible on short edges
FelixTJDietrich Jul 20, 2026
dd1dd51
fix(library): stop pinned endpoint grips merging into overlapping ben…
FelixTJDietrich Jul 20, 2026
132527c
fix(library): drop the pinned-grip centre dot; keep a plain white handle
FelixTJDietrich Jul 20, 2026
5313620
feat(library): scale the endpoint grip outline with zoom
FelixTJDietrich Jul 20, 2026
fc06111
fix(library): a dragged bend arm rests on the adjacent parallel arm, …
FelixTJDietrich Jul 20, 2026
95e4924
fix(library): collapse a squeezed loop to the clean route, not a resi…
FelixTJDietrich Jul 20, 2026
70aa0cf
style(library): thicken the endpoint grip outline to 4 flow-units
FelixTJDietrich Jul 21, 2026
5f79feb
fix(library): collapse a loop only when its arms actually meet, not a…
FelixTJDietrich Jul 21, 2026
0d160b7
fix(library): stop the pre-solve re-projection from wiping hand-drawn…
FelixTJDietrich Jul 21, 2026
8b3a2d4
fix(library): keep a bend lane the user dragged close to a stub, don'…
FelixTJDietrich Jul 21, 2026
eabbf06
fix(library): endpoint reconnect handle takes precedence over bend ha…
FelixTJDietrich Jul 21, 2026
4630db2
fix(library): keep bend handles grabbable, endpoint wins only the ove…
FelixTJDietrich Jul 21, 2026
1a2b102
feat(library): connecting at a node corner exits the side facing the …
FelixTJDietrich Jul 21, 2026
4c49c39
Revert "feat(library): connecting at a node corner exits the side fac…
FelixTJDietrich Jul 21, 2026
58aee97
feat(library): resolve only a TRUE corner-overshoot drop by the route…
FelixTJDietrich Jul 21, 2026
65cb7fb
feat(library): pick a corner-overshoot side by the angle aimed, not t…
FelixTJDietrich Jul 21, 2026
f0b3b8b
fix(library): stabilize corner anchoring — resolve the node exterior …
FelixTJDietrich Jul 21, 2026
c8b2f7c
fix(library): make solver caches exact for their inputs
FelixTJDietrich Jul 21, 2026
6b8b139
feat(library): optimize orthogonal edge routing
FelixTJDietrich Jul 22, 2026
970c2e7
perf(library): stabilize holistic edge routing at scale
FelixTJDietrich Jul 23, 2026
f301341
Merge remote-tracking branch 'origin/main' into feat/orthogonal-edge-…
FelixTJDietrich Jul 23, 2026
4617db5
fix(ci): validate stripped performance probes precisely
FelixTJDietrich Jul 23, 2026
32fd3c9
fix(webapp): keep template routing clean and automatic
FelixTJDietrich Jul 23, 2026
36642ff
fix(webapp): balance template preset visuals
FelixTJDietrich Jul 23, 2026
32d7050
fix(library): preserve legacy diagram imports
FelixTJDietrich Jul 23, 2026
5d6c29a
fix(library): stabilize shared edge junctions
FelixTJDietrich Jul 23, 2026
524c4ef
chore(visual): refresh Playwright baselines
github-actions[bot] Jul 23, 2026
fee0cac
chore: rerun checks after visual refresh
FelixTJDietrich Jul 23, 2026
4c36a70
fix(library): keep channel search bounded
FelixTJDietrich Jul 23, 2026
f638223
fix(library): align interfaces and shape-aware routes
FelixTJDietrich Jul 23, 2026
e77a977
chore(visual): refresh Playwright baselines
github-actions[bot] Jul 23, 2026
a20926b
fix(library): separate required interface sockets
FelixTJDietrich Jul 23, 2026
3a58786
chore(visual): refresh Playwright baselines
github-actions[bot] Jul 23, 2026
0cf3be3
fix(library): refine required interface seams
FelixTJDietrich Jul 23, 2026
d15f614
chore(visual): refresh Playwright baselines
github-actions[bot] Jul 23, 2026
62dd859
fix(library): keep required sockets attached while reconnecting
FelixTJDietrich Jul 23, 2026
a51e912
fix(library): harden edge routing integration
FelixTJDietrich Jul 23, 2026
363ccb1
fix(library): preserve native preview handles
FelixTJDietrich Jul 23, 2026
31292b3
fix(library): align package connection geometry
FelixTJDietrich Jul 23, 2026
69e497f
fix(library): unify element toolbars
FelixTJDietrich Jul 23, 2026
d0e8a0c
refactor(library): harden routing integration
FelixTJDietrich Jul 23, 2026
008fbd6
perf(library): improve worker routing responsiveness
FelixTJDietrich Jul 23, 2026
47e0459
test(webapp): harden routing performance checks
FelixTJDietrich Jul 23, 2026
be6f8dd
test(webapp): require sustained perf gestures
FelixTJDietrich Jul 23, 2026
15859ff
fix(library): keep edge routing on one visible generation
FelixTJDietrich Jul 24, 2026
60bfaf9
fix(library): hold edges until the first exact routing generation
FelixTJDietrich Jul 24, 2026
f5e1d07
fix(library): mint unique ids for every cloned child node
FelixTJDietrich Jul 24, 2026
0587fc9
fix(library): flush a frame before measuring export bounds
FelixTJDietrich Jul 24, 2026
4810db4
refactor(library): idiomatic UI, dead-code and comment cleanup
FelixTJDietrich Jul 24, 2026
04370cc
fix(library): restore hover tooltips on the paint and tag triggers
FelixTJDietrich Jul 24, 2026
7975105
test(webapp): give the Worker settlement poll room and poll gently
FelixTJDietrich Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clear-signs-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tumaet/webapp": patch
---

Start every bundled design-pattern template in a clean, visually balanced editing state, with stable shared hierarchy trunks and automatic routing preserved for associations.
5 changes: 5 additions & 0 deletions .changeset/orthogonal-edge-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tumaet/apollon": minor
---

Create cleaner diagrams with automatic edges that balance connection points, reduce collisions with nodes and nearby edges, and stay responsive while you drag, without losing pinned endpoints, hand-placed bends, aligned component interfaces, or compatibility with older diagrams; embedding apps can also normalize diagrams in server-side code through the new `@tumaet/apollon/model` entry.
20 changes: 16 additions & 4 deletions .github/workflows/pr-health-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -393,18 +393,30 @@ jobs:
run: pnpm --filter @tumaet/apollon run test

- name: Verify ESM exports-map resolution
# The library is ESM-only. `import.meta.resolve` (sync, no side
# effects) validates every subpath in the exports map — actually
# evaluating the module would require jsdom.
# The library is ESM-only. `import.meta.resolve` validates every
# subpath in the exports map. The model-only entry is intentionally
# DOM-free; the follow-up check evaluates its complete runtime graph.
working-directory: standalone/server
run: |
node --input-type=module -e '
const root = import.meta.resolve("@tumaet/apollon");
const internals = import.meta.resolve("@tumaet/apollon/internals");
const exportEntry = import.meta.resolve("@tumaet/apollon/export");
const model = import.meta.resolve("@tumaet/apollon/model");
const css = import.meta.resolve("@tumaet/apollon/style.css");
console.log({ root, internals, exportEntry, css });
const schema = import.meta.resolve("@tumaet/apollon/schema");
const packageJson = import.meta.resolve("@tumaet/apollon/package.json");
console.log({
root,
internals,
exportEntry,
model,
css,
schema,
packageJson,
});
'
node ../../scripts/check-model-entry.mjs

- name: Lint published package shape (publint)
working-directory: library
Expand Down
8 changes: 8 additions & 0 deletions docs/library/api/model-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ import { importDiagram } from "@tumaet/apollon"
editor.model = importDiagram(maybeV2OrV3Json) // → guaranteed v4
```

Server code that only needs to normalize model JSON can use the DOM-free entry:

```ts
import { importDiagram } from "@tumaet/apollon/model"

export const normalizeDiagram = (data: unknown) => importDiagram(data)
```

The v2 / v3 detectors and converters live behind `@tumaet/apollon/internals`
and are **not** part of the stability guarantee — only `importDiagram` is.

Expand Down
13 changes: 10 additions & 3 deletions docs/library/embedding/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,16 @@ export default function Page() {

Remix and Nuxt have equivalent client-only loading.

`@tumaet/apollon` is a client module in its entirety (`"use client"`), so its
pure helpers (`importDiagram`, `createApollonTheme`, …) are client-only too —
import them from client components, not Server Components.
`@tumaet/apollon` is a client module in its entirety (`"use client"`), so
import the editor and theme helpers from client components, not Server
Components. Model migration is the exception: server code can import the
DOM-free entry directly.

```ts
import { importDiagram } from "@tumaet/apollon/model"

export const normalizeDiagram = (data: unknown) => importDiagram(data)
```

Non-React hosts that want imperative control mount `ApollonEditor` directly —
see [Vanilla JS / CDN](/library/embedding/vanilla).
13 changes: 8 additions & 5 deletions docs/library/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ to React 19 before upgrading Apollon — on React 18 the install fails with an

## `"use client"` / RSC

The main entry is now a client module (`"use client"`) in its entirety, so its
pure helpers (`importDiagram`, `createApollonTheme`, …) are client-only too.
React Server Components must treat `@tumaet/apollon` as client-only: import it
from client components, not Server Components. See
[SSR](/library/embedding/react#ssr-nextjs-remix-nuxt-sveltekit).
The main entry is now a client module (`"use client"`) in its entirety. React
Server Components must therefore treat `@tumaet/apollon` as client-only and
import it from client components, not Server Components.

Model migration is also available from the DOM-free
`@tumaet/apollon/model` entry. Server code that only needs to normalize a
diagram can import `importDiagram` from that subpath without loading the
editor. See [SSR](/library/embedding/react#ssr-nextjs-remix-nuxt-sveltekit).

## Removed deep imports (CDN caveat)

Expand Down
8 changes: 6 additions & 2 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
"ignoreExportsUsedInFile": true,
"workspaces": {
"library": {
"entry": ["lib/index.tsx", "lib/internals.ts", "lib/export/index.ts"],
"entry": [
"lib/index.tsx",
"lib/internals.ts",
"lib/export/index.ts",
"lib/model.ts"
],
"project": ["lib/**/*.{ts,tsx}"],
"ignoreBinaries": ["build", "run"]
},
Expand All @@ -28,7 +33,6 @@
"ignoreDependencies": [
"@capacitor/file-transfer",
"@capacitor/splash-screen",
"@capacitor/android",
"tailwindcss",
"jspdf",
"svg2pdf.js",
Expand Down
89 changes: 37 additions & 52 deletions library/lib/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import {
ReactFlowInstance,
ConnectionMode,
ReactFlow,
type Edge,
} from "@xyflow/react"
import { type MouseEvent as ReactMouseEvent, useCallback } from "react"
import { useCallback } from "react"
import {
CustomBackground,
ReconnectConnectionLine,
AssessmentSelectionDebug,
ScrollOverlay,
AlignmentGuides,
} from "@/components"
// Imported DIRECTLY, not via the `@/components` barrel: this component pulls in the
// edge-geometry solver to preview the committed auto route, and the barrel is
// imported by `constants.ts`, so routing it through the barrel forms a
// `constants → components → solver → edgeAnchoring` import cycle that leaves module
// constants undefined at init. The direct path keeps the solver out of the barrel.
import { ConnectionPreviewLine } from "@/components/ConnectionPreviewLine"
import { OverlayLayer } from "@/overlay/OverlayLayer"
import "@xyflow/react/dist/style.css"
// Shared, embed-safe @tumaet/ui primitives + --apollon-/--home- design tokens
Expand All @@ -26,6 +30,7 @@ import "@/styles/fonts.css"
import "@/styles/app.css"
import {
useDiagramStore,
useEdgeGeometryStore,
useMetadataStore,
useOverlayStore,
} from "./store/context"
Expand All @@ -36,7 +41,6 @@ import { diagramEdgeTypes } from "./edges"
import {
useNodeDragStop,
useConnect,
useReconnect,
useElementInteractions,
useDragOver,
useNodeDrag,
Expand All @@ -50,37 +54,30 @@ import {
useRemoteDraggingNodes,
applyDraggingOverlay,
} from "./hooks/useRemoteDraggingNodes"
import {
getConnectionLineType,
resolveReconnectPreviewBasePoints,
} from "./utils/edgeUtils"
import { IPoint } from "./edges/Connection"
import { getConnectionLineType } from "./utils/edgeUtils"
import {
CollaborationLayer,
type CollaborationAwarenessApi,
type CollaborationLayerOptions,
} from "@/components/collaboration/CollaborationLayer"
import { TooltipProvider } from "@/components/ui"
import { EdgeGeometrySolver } from "@/components/EdgeGeometrySolver"

interface AppProps {
onReactFlowInit: (instance: ReactFlowInstance) => void
collaboration: CollaborationLayerOptions
awareness: CollaborationAwarenessApi
/** Disabled by the off-screen export mount, which must materialize the whole
* model regardless of its synthetic viewport. */
onlyRenderVisibleElements?: boolean
}
const proOptions = { hideAttribution: true }
const isPointArray = (value: unknown): value is IPoint[] =>
Array.isArray(value) &&
value.every(
(point) =>
typeof point === "object" &&
point !== null &&
"x" in point &&
"y" in point &&
typeof point.x === "number" &&
typeof point.y === "number"
)

function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
function App({
onReactFlowInit,
collaboration,
awareness,
onlyRenderVisibleElements = true,
}: AppProps) {
useKeyboardShortcuts()

const { nodes, onNodesChange, edges, onEdgesChange, diagramId, previewMode } =
Expand All @@ -102,8 +99,6 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
scrollEnabled,
keyboardShortcuts,
connectionGuidanceActive,
startReconnectPreview,
stopReconnectPreview,
} = useMetadataStore(
useShallow((state) => ({
diagramType: state.diagramType,
Expand All @@ -112,8 +107,6 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
scrollEnabled: state.scrollEnabled,
keyboardShortcuts: state.keyboardShortcuts,
connectionGuidanceActive: state.connectionGuidanceActive,
startReconnectPreview: state.startReconnectPreview,
stopReconnectPreview: state.stopReconnectPreview,
}))
)

Expand Down Expand Up @@ -143,11 +136,11 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
const onDragOver = useDragOver()
const { onConnect, onConnectEnd, onConnectStart, onEdgesDelete } =
useConnect()
const onReconnect = useReconnect()
const { onBeforeDelete, onNodeDoubleClick, onEdgeDoubleClick } =
useElementInteractions()
const { onPaneClicked } = usePaneClicked()
const multiSelectionMode = useMultiSelectionMode()
const routingReady = useEdgeGeometryStore((state) => state.routingReady)

const handleReactFlowInit = useCallback(
(instance: ReactFlowInstance) => {
Expand All @@ -156,25 +149,6 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
[onReactFlowInit]
)

const handleReconnectStart = useCallback(
(_event: ReactMouseEvent, edge: Edge, handleType: "source" | "target") => {
const storedPoints = isPointArray(edge.data?.points)
? edge.data.points
: undefined

startReconnectPreview(
edge.id,
handleType,
resolveReconnectPreviewBasePoints(storedPoints, undefined, [])
)
},
[startReconnectPreview]
)

const handleReconnectEnd = useCallback(() => {
stopReconnectPreview()
}, [stopReconnectPreview])

return (
<TooltipProvider>
<div
Expand Down Expand Up @@ -205,7 +179,20 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
nodeTypes={diagramNodeTypes}
edgeTypes={diagramEdgeTypes}
nodes={displayNodes}
edges={edges}
// The solver reads DiagramStore directly. Keep provisional React
// Flow edges unmounted until this model's first exact generation;
// nodes still mount below so their runtime handles can be measured.
edges={routingReady ? edges : []}
// React Flow's viewport culling keeps large off-screen diagrams out
// of the DOM while the central solver still optimizes every edge.
// This is purely a rendering boundary: export and exact geometry
// continue to use the complete diagram.
// Bootstrap one complete measurement pass before enabling viewport
// culling. Otherwise off-screen nodes never mount their handles and
// the holistic router cannot produce a stable first generation.
onlyRenderVisibleElements={
routingReady ? onlyRenderVisibleElements : false
}
onDragOver={onDragOver}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
Expand All @@ -216,11 +203,8 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
zoomOnDoubleClick={false}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragStop}
onReconnect={onReconnect}
onReconnectStart={handleReconnectStart}
onReconnectEnd={handleReconnectEnd}
connectionLineType={connectionLineType}
connectionLineComponent={ReconnectConnectionLine}
connectionLineComponent={ConnectionPreviewLine}
connectionMode={ConnectionMode.Loose}
// Lift the selected edge (and its bend/endpoint handles) above other
// edges so an overlapping edge's interaction ribbon can't steal the
Expand All @@ -244,7 +228,7 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
onBeforeDelete={onBeforeDelete}
onPaneClick={onPaneClicked}
proOptions={proOptions}
edgesReconnectable={isDiagramModifiable}
edgesReconnectable={false}
nodesConnectable={isDiagramModifiable}
nodesDraggable={isDiagramModifiable}
panOnScroll={!scrollLock || scrollEnabled}
Expand Down Expand Up @@ -278,6 +262,7 @@ function App({ onReactFlowInit, collaboration, awareness }: AppProps) {
<CustomBackground />
<AlignmentGuides />
<AssessmentSelectionDebug />
<EdgeGeometrySolver />
{/* Renders every registered control (built-in + host-injected) into
its region: header, rails, corners, on-canvas. The chrome itself is
registered at construction (imperative) or by the React wrapper. */}
Expand Down
Loading
Loading