RulesetEditor
DomainDrag-and-drop visual rule chain editor. The blocks come from a catalog (typed blocks with params), each block has a form built from its schema, and the editor has undo, multi-select, groups, flows that run each other and validation.
MQTT Ingress
Trigger
out
Temp > 85 °C
Filter
in
TrueFalse
Send Alert
Action
in
Log Reading
Action
in
100%
<RulesetEditor
initialNodes={nodes}
initialEdges={edges}
/>Sensing
Flow input
out
Boiler temperature
site/boiler/temp
out
Too hot?
msg.payload.temp > 90
in
yesno
Hold 2 min
2 min
in
out
Tell the operator
—
in
out
Log it
info: Boiler is fine
in
out
Log the error
error: Compare failed
in
out
Escalate
escalation
in
100%
<RulesetEditor
catalog={catalog} // blocks with typed params, from data
choices={{ brokers: [...] }} // lists for source fields
initialNodes={nodes}
initialEdges={edges}
initialGroups={groups}
active={chain.active} // a missing required param is then an error
onChange={(graph) => save(graph)}
/>Flow input
out
Boiler temperature
site/boiler/temp
out
Too hot?
msg.payload.temp > 90
in
yesno
Hold 2 min
2 min
in
out
Tell the operator
—
in
out
Log it
info: Boiler is fine
in
out
Log the error
error: Compare failed
in
out
Escalate
escalation
in
100%
// The blocks are scattered: press "Arrange the blocks" (bottom right) for a layered layout, one undo step.
// Shift / Ctrl / ⌘ and drag on the empty canvas draws a selection frame; the frame button does it with one finger.
// Drag a block: it snaps to a 12 px grid and to the edges of other blocks (guide lines); hold Alt to move freely.
// The mini map shows above 30 blocks or when blocks leave the view; the binoculars button toggles it.
const ref = useRef<RulesetEditorRef>(null);
<RulesetEditor ref={ref} catalog={catalog} initialNodes={nodes} initialEdges={edges}
onSelectionChange={(ids) => setSelected(ids)}
nodeMenuItems={({ nodeIds }) => [{ kind: 'item', label: 'Open live data', onSelect: () => open(nodeIds) }]}
toolbarExtra={<MyButton />} bannerSlot={<DraftBanner />} />
ref.current?.arrange(); ref.current?.select(['n1']); ref.current?.reveal('n4');Sensing
Flow input
out
Boiler temperature
site/boiler/temp
out
Too hot?
msg.payload.temp > 90
in
yesno
Hold 2 min
2 min
in
out
Tell the operator
—
in
out
Log it
info: Boiler is fine
in
out
Log the error
error: Compare failed
in
out
Escalate
escalation
in
100%
<RulesetEditor readOnly catalog={catalog} initialNodes={nodes} initialEdges={edges} />Drag nodes from the palette to start
Connect ● output ports to ○ input ports
100%
<RulesetEditor />