jellyflow-runtime
jellyflow-runtime builds on jellyflow-core with the headless runtime layer:
- editor and view-state payloads;
- persistence file payloads without owning a project directory policy;
- effective interaction policy resolution under
runtime::policy; - validation rules and diagnostics, including connect/reconnect/delete planners;
- schema/profile pipeline hooks;
- renderer-neutral node-kind view descriptors for adapter palettes, inspectors, and custom node renderer lookup;
- undo/redo store dispatch;
- XyFlow-style node/edge change projections and ordered adapter-array apply helpers under
runtime::xyflow; - renderer-neutral selection-box helpers under
runtime::selection; - renderer-neutral node drag planning, parent expansion, and commit helpers under
runtime::drag; - renderer-neutral node resize planning, parent expansion, and commit helpers under
runtime::resize; - renderer-neutral viewport pan/zoom helpers under
runtime::viewport; - renderer-neutral viewport animation and double-click zoom planning under
runtime::viewport; - renderer-neutral viewport pan inertia planning under
runtime::viewport; - renderer-neutral auto-pan frame helpers under
runtime::auto_pan; - renderer-neutral store-level rendering reads through
NodeGraphStore::rendering_queryandruntime::rendering::RenderingQueryResult; - renderer-neutral binding reads through
NodeGraphStore::binding_queryand binding-derived layout context throughNodeGraphStore::layout_context_with_binding_pins; - renderer-neutral delete selection planning under
runtime::deleteand key-bound routing underruntime::keyboard; - fit-view math that uses Jellyflow canvas geometry;
- renderer-neutral geometry under
runtime::geometry, including handle endpoints, edge path commands, and numeric hit testing; - reusable headless conformance fixtures and a runner under
runtime::conformance.
The crate stays UI-agnostic. Fret-specific conversions, widgets, rendering, and event binding remain adapter responsibilities.
use ;
use ;
use NodeGraphStore;
let store = new;
assert_eq!;
Headless Interaction Contracts
Renderer adapters should translate pointer and keyboard input into Jellyflow runtime calls, then validate behavior before rendering. The runtime crate supports that split with:
NodeGraphStore::apply_selection_boxandruntime::selection::compute_selection_boxfor deterministic canvas-space selection;NodeGraphStore::plan_delete_selection,NodeGraphStore::apply_delete_selection,NodeGraphStore::apply_delete_selection_for_key, andruntime::keyboard::KeyboardIntentfor deterministic selected node/edge deletion through effective policy, configured delete keys, cascaded connected-edge deletion, normal graph transactions, selection cleanup, and adapter-owned pre-deleteAccept/Veto/Replacedecisions;runtime::connection::{resolve_connection_target_from_handles, ConnectionTargetCandidate}for resolving adapter-provided handle geometry and connectability into XyFlow-style target feedback without owning DOM hit testing;runtime::gesture::{PointerSessionClaimInput, PointerSessionClaimOutcome}plusNodeGraphStore::resolve_pointer_session_claimfor deterministic pointer ownership arbitration with stable rejection reasons, andNodeGraphStore::{apply_node_drag_session, apply_connect_edge_session, apply_viewport_drag_pan_session}for ordinary adapter gesture lifecycles through store commits and gesture events;NodeGraphStore::plan_node_drag,NodeGraphStore::apply_node_drag, andruntime::dragfor deterministic canvas-space node dragging with selected-node co-dragging, policy filtering, snap-to-grid, global/per-node extents, node-origin-aware clamping, and parent group expansion;NodeGraphStore::plan_node_resize,NodeGraphStore::apply_node_resize, andruntime::resizefor deterministic target-size node resizing with min/max bounds, XyFlow-style control directions, node-origin-aware position updates for left/top controls, normal graph transactions, and pointer-resize session lifecycle helpers;runtime::viewport::{ViewportTransform, ViewportPanRequest, ViewportZoomRequest}plusNodeGraphStore::apply_viewport_panandNodeGraphStore::apply_viewport_zoomfor deterministic drag-pan and zoom-around-pointer state changes;runtime::viewport::{ViewportAnimationRequest, ViewportAnimationOptions, ViewportAnimationPlan, ViewportAnimationFrame, ViewportDoubleClickZoomInput}plusruntime::viewport::resolve_viewport_double_click_zoomfor deterministic animation sampling and normalized double-click zoom planning without runtime-owned timers or raw platform event detection;runtime::viewport::{ViewportPanInertiaRequest, ViewportPanInertiaPlan, ViewportPanInertiaFrame}plusruntime::viewport::plan_viewport_pan_inertiafor deterministic pan inertia sampling from adapter-provided logical screen px/s release velocity andNodeGraphPanInertiaTuning;runtime::auto_pan::{AutoPanRequest, SelectionAutoPanRequest, AutoPanPlan}plusNodeGraphStore::{apply_auto_pan, apply_selection_auto_pan}for deterministic edge-proximity auto-pan frames that feed the normal viewport publication path;NodeGraphStore::layout_facts_queryfor adapter-facing report-once/read-many layout facts after measurement publication. It returns the current layout-facts revision, renderer-facingrendering_queryresult, visible edge endpoints, and connection target candidates; selector subscriptions can tracklayout_facts_revisionfor redraw/re-query decisions.NodeGraphStore::rendering_queryis the narrow read path for deterministic group/node/edge order and visible node/edge lists;NodeGraphStore::binding_queryfor renderer-neutral knowledge-canvas binding facts. Core persists the binding records, while runtime resolves graph-local endpoints with measurements, node origin, and handle geometry. Source anchors remain opaque host-owned payloads;runtime::events::NodeGraphGestureEventnode drag start/update/end payloads for adapters that want XyFlow-style drag lifecycle callbacks without coupling the runtime to pointer capture;runtime::events::NodeGraphGestureEventviewport move start/update/end payloads for adapters that want XyFlow-styleonMoveStart,onMove, andonMoveEndcallbacks around pan/zoom gestures;- rules-derived connect/reconnect/delete planners for graph transactions;
runtime::xyflowprojections for XyFlow-style node/edge changes, callbacks, and exact adapter-owned ordered-array apply helpers;runtime::conformance::{ConformanceScenario, ConformanceSuite, ConformanceFixtureDirectory, ConformanceBehavior, ConformanceAction, ConformanceTraceEvent, run_conformance_scenario, run_conformance_suite}for reusable behavior contracts, fixture checks, fixture discovery, and explicit golden approval updates around a realNodeGraphStore.
Adapters that need XyFlow-style custom nodes should register semantic node kinds through
schema::NodeRegistry, then read NodeRegistry::view_descriptors() to build their own
framework-local renderer registry. NodeKindViewDescriptor.renderer_key is adapter-owned data
rather than a component reference, so React, Svelte, native, and future adapters can map the same
headless schema to different renderer implementations while preserving node kind, ports, default
data, default size, category, and search metadata.
For create-node palettes, adapters can call
NodeGraphStore::apply_create_node_from_schema(registry, CreateNodeRequest::new(kind, pos)). The
store uses the same dispatch/history/profile path as other graph edits while NodeRegistry
resolves aliases, canonical kind, default data, default size, and schema-declared ports.
Run conformance fixture suites before renderer smoke tests. They prove the adapter is translating
intent into the same runtime actions and callback ordering that Jellyflow expects, and they return
aggregate reports that separate trace mismatches from scenario execution errors. Suites can be saved
and loaded as pretty JSON files through ConformanceSuite::save_json, load_json, and
load_json_if_exists; directories can be discovered recursively through
ConformanceFixtureDirectory::load_json and load_json_if_exists, so adapters and agents can keep
durable golden fixture assets in their own repos. Approval is explicit: approve_actual_traces
returns an updated suite/report, while file and directory approve_actual_traces_to_json helpers
write back only when every scenario executes without errors. GPU, windowing, screenshot, and pixel
smoke tests should live in adapter crates such as future wgpu, egui, or Fret integrations, where
they can verify input capture, platform wiring, and rendered pixels.
Drag parent expansion is runtime-owned: a child with effective expand_parent = true can expand
its parent group rect through GraphOp::SetGroupRect, while NodeExtent::Parent with
expand_parent = false still clamps to the current parent group rect. Jellyflow stores node
positions in canvas space, so left/top group expansion does not add sibling compensation ops. Raw
pointer capture, drag handles, resize handles, renderer-specific grouping UI, screenshots, and
pixels remain adapter responsibilities.
Resize planning is runtime-owned: adapters provide normalized canvas-space NodeResizeRequest or
NodePointerResizeRequest values, and the runtime produces node resize transactions from existing
GraphOp::SetNodeSize, GraphOp::SetNodePos for left/top controls, and GraphOp::SetGroupRect
when expand_parent = true expands a parent group. NodeExtent::Parent with
expand_parent = false still clamps to the current parent group rect. Adapters still own resize
handle UI, raw pointer capture, cursor policy, renderer feedback, and pixels. Exact XyFlow
node-owned child containment is intentionally not modeled as a renderer or DOM dependency in
jellyflow-runtime.
Delete selection planning is runtime-owned: adapters maintain view-state selection and translate
platform keyboard input into direct delete calls or KeyboardIntent. The runtime resolves the
configured delete key, effective deletable policy, selected nodes/edges, cascaded connected-edge
deletion, delete selection transactions, XyFlow-style callback projections, and stale selection
cleanup. For XyFlow-style onBeforeDelete, adapters call prepare_delete_selection or
prepare_delete_selection_for_key, await their own hook/UI, then call apply_pre_delete_resolution
with PreDeleteResolution::Accept, Veto, or Replace. Adapters still own raw key capture,
focus/input suppression, confirmation dialogs, async scheduling, renderer feedback, screenshots,
and pixels.
ConformanceAction::dispatch_transaction is intentionally kept as a low-level graph-operation
fixture escape hatch; adapter feel fixtures should prefer ConformanceBehavior session contracts
or interaction-specific actions such as node drag, node resize, connect/reconnect, delete,
viewport gestures, viewport animation frames, and double-click zoom plan or rejection assertions.
Rendering fixtures should use ConformanceRenderingQueryContract or
ConformanceAction::assert_rendering_query, which assert NodeGraphStore::rendering_query without
producing renderer traces. Pan inertia fixtures
should use the sampled-frame actions and rejection assertion so adapters can prove release-momentum
traces without moving frame loops into runtime. Parent expansion fixtures should use
ConformanceAction::apply_node_drag, which exercises the same runtime interaction boundary and
records set_group_rect graph-commit traces when parent expansion occurs. Resize fixtures should
use ConformanceAction::apply_node_resize or apply_node_pointer_resize, which exercises the same
runtime interaction boundary and records set_node_size, set_node_pos plus set_node_size, and
set_group_rect graph-commit traces when parent expansion occurs. Delete fixtures should use
ConformanceAction::apply_delete_selection or apply_delete_selection_for_key, which records
remove_node or remove_edge graph commits, XyFlow-style delete/disconnect callbacks, and
selection cleanup traces.
The runtime crate also includes a thin renderer-free example harness for agents and CI:
cargo run -p jellyflow-runtime --example conformance_harness -- check <fixture-dir>
cargo run -p jellyflow-runtime --example conformance_harness -- approve <fixture-dir>
cargo run -p jellyflow-runtime --example knowledge_canvas
NodeGraphStore::rendering_query uses the deterministic linear read backend by default. Large graph
adapters can opt into the snapshot-built spatial backend for local measurement while keeping the
same public query result contract:
let editor_config = default.with_spatial_index_enabled;
Measure local workloads before enabling it broadly. The current spatial backend keeps a store-local node index cache and rebuilds it when graph/layout facts, node origin, zoom, or spatial tuning changes. Pan-only queries reuse the same index. The runtime crate includes a benchmark for 1k/10k/50k node rendering-query workloads:
cargo bench -p jellyflow-runtime --bench rendering_query
cargo bench -p jellyflow-runtime --bench schema_create_node
For a copyable external adapter skeleton, start with the non-workspace headless template:
cargo test --manifest-path templates/headless-adapter/Cargo.toml
cargo run --manifest-path templates/headless-adapter/Cargo.toml -- check
Viewport conformance is also headless. Runtime tests cover:
- screen-delta pan conversion at the current zoom;
- anchored zoom that keeps the pointer's canvas coordinate stable;
- auto-pan conversion from pointer-edge proximity and elapsed frame time into viewport pan frames;
NodeGraphStoreview-state publication for viewport intent;- viewport move gesture callback ordering;
- fixture-runner traces for pan, zoom, auto-pan, view changes, gestures, and XyFlow-style callbacks;
- conformance assertions for viewport animation frame sampling, sampled-frame viewport traces, and double-click zoom plan or rejection outcomes.
- conformance assertions for pan inertia frame sampling, sampled-frame viewport traces, and rejected below-threshold inertia plans.
- conformance assertions for visible node ids, visible edge ids, and render order before renderer-specific draw batching.
Adapters still own raw wheel delta normalization, pinch detection, pointer capture, cursor policy,
raw double-click detection, release velocity estimation, frame scheduling, animation and inertia
cancellation policy, sampled-frame commits, edge routing and draw batching, resize handles, window
event loops, screenshots, and pixel assertions. For selection workflows, adapters own the
screen-space selection rectangle and pointer/session ownership, then call SelectionAutoPanRequest
for the shared edge-proximity viewport motion. Low-level geometry helpers remain available for
custom routing and hit testing; adapters that need store-derived endpoints after reporting
measurements should prefer NodeGraphStore::layout_facts_query.
use ;
use ;
let endpoints = edge_position
.expect;
let path = straight_edge_path.expect;
assert!;