Diagram edges
Edges as a diagramming primitive — text nodes wired with directional arrowhead markers, edge labels (pass / fail / retry), and an animated flowing dash on the main path. Toggle the flow.
{
"version": "xenolith.v1",
"nodes": [
{ "id": "start", "type": "Note", "position": { "x": 0, "y": 140 },
"render": { "title": "Start", "category": "logic" },
"pins": [
{ "id": "start:in", "kind": "data", "direction": "in", "type": "flow", "multiple": true },
{ "id": "start:out", "kind": "data", "direction": "out", "type": "flow", "multiple": true }
] },
{ "id": "build", "type": "Note", "position": { "x": 250, "y": 140 },
"render": { "title": "Build", "category": "logic" },
"pins": [
{ "id": "build:in", "kind": "data", "direction": "in", "type": "flow", "multiple": true },
{ "id": "build:out", "kind": "data", "direction": "out", "type": "flow", "multiple": true }
] },
{ "id": "test", "type": "Note", "position": { "x": 500, "y": 140 },
"render": { "title": "Run tests", "category": "logic" },
"pins": [
{ "id": "test:in", "kind": "data", "direction": "in", "type": "flow", "multiple": true },
{ "id": "test:out", "kind": "data", "direction": "out", "type": "flow", "multiple": true }
] },
{ "id": "ship", "type": "Note", "position": { "x": 780, "y": 40 },
"render": { "title": "Ship 🚀", "category": "logic" },
"pins": [
{ "id": "ship:in", "kind": "data", "direction": "in", "type": "flow", "multiple": true },
{ "id": "ship:out", "kind": "data", "direction": "out", "type": "flow", "multiple": true }
] },
{ "id": "fix", "type": "Note", "position": { "x": 780, "y": 250 },
"render": { "title": "Fix & retry", "category": "logic" },
"pins": [
{ "id": "fix:in", "kind": "data", "direction": "in", "type": "flow", "multiple": true },
{ "id": "fix:out", "kind": "data", "direction": "out", "type": "flow", "multiple": true }
] }
],
"edges": [
{ "id": "e1", "from": { "node": "start", "pin": "start:out" }, "to": { "node": "build", "pin": "build:in" }, "opts": { "markerEnd": "arrow", "animated": true } },
{ "id": "e2", "from": { "node": "build", "pin": "build:out" }, "to": { "node": "test", "pin": "test:in" }, "opts": { "markerEnd": "arrow", "animated": true } },
{ "id": "e3", "from": { "node": "test", "pin": "test:out" }, "to": { "node": "ship", "pin": "ship:in" }, "opts": { "markerEnd": "arrow", "animated": true, "label": "pass" } },
{ "id": "e4", "from": { "node": "test", "pin": "test:out" }, "to": { "node": "fix", "pin": "fix:in" }, "opts": { "markerEnd": "arrow", "animated": true, "label": "fail" } },
{ "id": "e5", "from": { "node": "fix", "pin": "fix:out" }, "to": { "node": "build", "pin": "build:in" }, "opts": { "markerEnd": "arrow", "animated": true, "label": "retry" } }
]
} import { useEffect, useState } from 'react'
import { XenolithGraph, XenolithControls, XenolithPanel, XenolithButton, useEditor, useEdges } from '@xenolithengine/graph-react'
import { buildDiagram } from '@xenolithengine/demo/diagram'
import { DemoStage } from '../Layout.js'
// Showcase: edges as a diagramming primitive. Text nodes wired with directional edges — arrowhead
// markers, edge labels (pass / fail / retry), and an animated flowing dash on the main path.
//
// Canon: state lives where it's used. The panel owns `animated` AND owns the side-effect that
// pushes it into the editor, because the panel IS inside <XenolithGraph> — `useEditor()` gives
// it the editor directly. `useEdges()` is the public live edge list — no @internal `editor.graph`.
function DiagramPanel() {
const editor = useEditor()
const edges = useEdges()
const [animated, setAnimated] = useState(true)
useEffect(() => {
for (const e of edges) editor.setEdgeOptions(e.id, { animated })
}, [editor, edges, animated])
return (
<XenolithPanel position="top-left" style={{ display: 'flex', flexDirection: 'column', gap: 6, width: 200 }}>
<p style={{ margin: 0, fontSize: 11, textTransform: 'uppercase', letterSpacing: '.05em', color: 'var(--xeno-muted)' }}>Diagram edges</p>
<XenolithButton active={animated} style={{ width: '100%' }} onClick={() => setAnimated(!animated)}>
{animated ? '⏸ Stop flow' : '▶ Animate flow'}
</XenolithButton>
<span style={{ color: 'var(--xeno-muted)', fontSize: 11, lineHeight: 1.45 }}>
Directional edges with arrowheads + labels. The main path animates a flowing dash.
</span>
</XenolithPanel>
)
}
/** Showcase: flowchart-style directional edges (markers, labels, animation). */
export function DiagramDemo(): React.ReactElement {
return (
<DemoStage>
<XenolithGraph className="xeno" resizeToWindow={false} onReady={buildDiagram}>
<XenolithControls position="bottom-left" />
<DiagramPanel />
</XenolithGraph>
</DemoStage>
)
} // The flowchart showcase is just DATA: a xenolith.v1 file loaded with editor.loadJSON. No imperative
// node/edge wiring — the same JSON round-trips through serialization and loads on any host. Edges
// carry the render opts (arrowhead marker, animated flow, label) right in the file.
import type { XenolithEditor, EdgeId } from '@xenolithengine/graph-editor'
import diagram from './diagram.json'
export interface DiagramHandle {
/** Toggle the flowing-dash animation on every edge. */
setAnimated(on: boolean): void
}
export function buildDiagram(editor: XenolithEditor): DiagramHandle {
editor.loadJSON(diagram)
editor.fitView({ padding: 72, maxZoom: 1 })
const edgeIds = diagram.edges.map((e) => e.id as unknown as EdgeId)
return { setAnimated: (on) => { for (const id of edgeIds) editor.setEdgeOptions(id, { animated: on }) } }
}