๐ง Zenode โ AI Coding Prompt (Cursor / Claude Code / Copilot)
Defines a modular JavaScript visual workflow ecosystem with four packages, a build plan, and a competitive analysis for AI coding sessions.
What this file does
Defines a modular JavaScript visual workflow ecosystem with four packages, a build plan, and a competitive analysis for AI coding sessions.
When to use it
- Starting a new visual workflow or diagramming library project
- Planning a modular monorepo with independent packages
- Evaluating competitive positioning for a developer tool
- Defining a phased build plan with clear milestones
Assumes this stack
๐ง Zenode โ AI Coding Prompt (Cursor / Claude Code / Copilot)
Paste this entire prompt at the start of your Cursor session or as a persistent .cursorrules file in your project root.
๐ฏ End Goal โ What Zenode Becomes
Zenode is a modular JavaScript ecosystem for visual workflows.
Each package solves an independent problem. All packages work better together. Developers adopt one package and expand naturally. The full stack is more powerful than any individual part.
"Use just the canvas. Use just the runtime.
Use the full stack. Each part works standalone
and integrates with your existing tools."
This is the same model as TanStack, Prisma, and Babel โ modular by default, opinionated when you want it.
๐ฆ The Zenode Ecosystem โ Package Architecture
@zenode/designer โ Layer 1 โ Visual canvas only
@zenode/serializer โ Layer 2 โ Export/import formats only
@zenode/runtime โ Layer 3 โ Workflow execution engine only
@zenode/transformer โ Layer 4 โ Output shape transformation only
@zenode/core โ All four layers, pre-wired, one install
@zenode/react โ React wrapper around @zenode/designer
@zenode/vue โ Vue wrapper around @zenode/designer
@zenode/bpmn-presets โ Free BPMN 2.0 shape preset library
zenode.min.js โ CDN/UMD bundle of @zenode/core
Why This Architecture Wins
Company A โ Uses only @zenode/designer. Has their own execution engine.
Exports Zenode JSON โ feeds their own backend.
Company B โ Uses only @zenode/runtime. Has their own diagram tool (React Flow, etc).
Imports their diagram JSON โ Zenode executes it.
Company C โ Already uses BPMN.js. Has years of BPMN XML diagrams.
@zenode/runtime imports and EXECUTES their existing BPMN XML.
Zero diagram rebuild. Zero migration cost.
โ This is a unique product. No JavaScript BPMN runtime exists.
Company D โ Uses @zenode/core. Gets the full stack, tightest integration.
Interoperability Contract
Every package speaks the same JSON dialect. Clean boundaries:
// @zenode/designer โ outputs standard ZenodeState JSON
const diagramState = engine.toJSON();
// @zenode/serializer โ converts from/to any format
import { toBPMN, toMermaid, fromBPMN } from '@zenode/serializer';
const bpmnXml = toBPMN(diagramState);
// @zenode/runtime โ executes ANY compatible JSON (not just Zenode diagrams)
import { ZenodeRuntime } from '@zenode/runtime';
const runtime = ZenodeRuntime.fromBPMN(existingBpmnXml); // works with 3rd party XML
// @zenode/transformer โ shapes any execution result
import { createTransformer } from '@zenode/transformer';
const output = createTransformer(result => ({ ok: true, data: result.finalOutput }));
๐บ Four-Layer System
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LAYER 1 โ @zenode/designer (Visual Designer) โ
โ D3.js canvas, shapes, connections, context pad, plugins โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ LAYER 2 โ @zenode/serializer (Serialization) โ
โ JSON, XML, BPMN 2.0, Mermaid, DOT, DSL export/import โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ LAYER 3 โ @zenode/runtime (Execution Engine) โ
โ Converts diagram โ executable workflow, runs resolvers โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ LAYER 4 โ @zenode/transformer (Output Shaping) โ
โ User defines their own output object from execution resultโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ฐ Pricing Per Package
| Package | Free | Paid |
|---|---|---|
@zenode/designer | Core canvas, shapes, basic connections | Pro $149/yr โ minimap, themes, undo+, PNG export |
@zenode/serializer | JSON, Mermaid, DOT, DSL | Pro $99/yr โ BPMN 2.0, image export |
@zenode/runtime | 100 executions/day | Runtime $49/mo โ unlimited executions |
@zenode/core (bundle) | All free tiers | Bundle $199/yr โ all pro features |
@zenode/bpmn-presets | Always free | โ |
| Embed License | โ | $200/mo โ OEM/white-label embed |
| Enterprise Runtime | โ | $500/mo โ production SLA + support |
Runtime pricing scales with value extracted. A startup running 50 workflows/day pays nothing. An enterprise running 50,000/day pays appropriately.
๐ Competitive Map
| React Flow | BPMN.js | JointJS+ | n8n | Zenode | |
|---|---|---|---|---|---|
| Framework agnostic | โ | โ | โ | N/A | โ |
| Open source core | โ | โ | โ | โ | โ |
| Custom shapes API | โ ๏ธ | โ | โ | โ | โ |
| BPMN export | โ | โ | โ | โ | โ Pro |
| JSON export | โ | โ | โ | โ | โ |
| JS workflow execution | โ | โ | โ | โ | โ |
| Live node status | โ | โ | โ | โ | โ |
| Plugin context pad | โ | โ hardcoded | โ | โ | โ |
| Visual effects | โ | โ | โ | โ | โ |
| Modular packages | โ | โ | โ | โ | โ |
| Embeddable UMD | โ ๏ธ | โ | โ ๏ธ | โ | โ |
| Price | Free/Pro | Free | $1200+/yr | Free/Cloud | Free/$149/yr |
The unique moat: No JavaScript library executes BPMN diagrams at runtime. Camunda has a Java runtime. Flowable has a Java runtime. @zenode/runtime that executes BPMN XML from any source โ including BPMN.js diagrams โ is a completely unclaimed space.
๐ Immediate Focus โ Ship Layer 1 First
Current task: Get
@zenode/designerlive and production-ready. Everything else is future. The designer is the entry point for every other package.
Why Layer 1 First
Layer 1 live โ GitHub stars โ developer adoption
โ NPM downloads โ credibility
โ Real user feedback โ better Layer 2 API design
โ Potential acqui-hire interest starts here
Layer 3 alone โ No distribution channel, no one finds it
Layer 2 alone โ No demo, no one understands what it serializes
The designer is your marketing, your demo, your distribution channel, and your feedback loop all in one. Everything else depends on it having real users.
Layer 1 Ship Checklist
โฌ Context Action Pad complete (Phase L1ยท3)
โฌ Connection markers/arrowheads (Phase L1ยท2+)
โฌ Basic undo/redo (10 steps free โ enough for launch)
โฌ README with working GIF and 10-line quick start
โฌ Live demo deployed (Vercel/Netlify โ link in README)
โฌ NPM publish @zenode/designer v0.1.0
โฌ Show HN post
That is the complete v0.1.0 launch scope. Everything else comes after users.
๐งฉ Config Management โ Keep It Small
// โ
CORRECT โ small config, API-first
const engine = new ZenodeEngine('#canvas', {
canvas: { width: 800, height: 600, backgroundColor: '#fff' },
grid: { type: 'dotted', size: 20, color: '#ccc' },
theme: 'dark',
contextPad: { trigger: 'select', position: 'top-right' }
});
engine.addNode({ preset: 'bpmn-task', x: 100, y: 200, meta: { label: 'Review' } });
engine.addEdge({ from: 'node-1', fromPort: 'right', to: 'node-2', toPort: 'left' });
// โ WRONG โ never declare node instances inside config
const engine = new ZenodeEngine('#canvas', {
shapes: { default: { rectangle: [{ id: 'task0', width: 120 ... }] } }
});
Config = canvas environment only. Nodes/edges added via API. Styles via presets.
๐งฉ Shape Renderer Contract โ Core Plugin Architecture
โ ๏ธ This is the foundation everything else is built on. Set up before Phase 1.3. Never bypass.
// src/types/index.ts
export interface ShapeRenderer {
draw: (g: D3Selection, config: ResolvedShapeConfig, theme: ThemeConfig) => void;
getPath: (config: ResolvedShapeConfig) => string;
getBounds: (config: ResolvedShapeConfig) => BoundingBox;
getPorts: (config: ResolvedShapeConfig) => PortMap;
}
export interface BoundingBox { x: number; y: number; width: number; height: number; }
export interface PortMap {
top: Point; bottom: Point; left: Point; right: Point; center: Point;
[key: string]: Point; // custom ports
}
type Point = { x: number; y: number };
Overlay โ Always Use getPath()
// โ WRONG โ ignores actual geometry
g.append('rect').attr('width', node.width).attr('height', node.height);
// โ
CORRECT โ matches exact shape regardless of renderer
g.append('path')
.attr('d', registry.get(node.type).getPath(node))
.attr('fill', 'none').attr('stroke', overlayColor)
.attr('pointer-events', 'none'); // โ always none on overlays
Rectangle Renderer โ Reference (per-corner borderRadius)
export const RectangleRenderer: ShapeRenderer = {
draw(g, config) {
g.append('path').attr('d', this.getPath(config))
.attr('fill', config.color).attr('stroke', config.stroke.color)
.attr('stroke-width', config.stroke.width)
.attr('stroke-dasharray', config.stroke.strokeDasharray.join(','));
},
getPath(config) {
const { x, y, width: w, height: h, borderRadius: r } = config;
const tl=r?.leftTop??0, tr=r?.rightTop??0, bl=r?.leftBottom??0, br=r?.rightBottom??0;
return [`M ${x+tl} ${y}`,`H ${x+w-tr}`,`Q ${x+w} ${y} ${x+w} ${y+tr}`,
`V ${y+h-br}`,`Q ${x+w} ${y+h} ${x+w-br} ${y+h}`,
`H ${x+bl}`,`Q ${x} ${y+h} ${x} ${y+h-bl}`,
`V ${y+tl}`,`Q ${x} ${y} ${x+tl} ${y}`,'Z'].join(' ');
},
getBounds(c) { return { x:c.x, y:c.y, width:c.width, height:c.height }; },
getPorts(c) {
return {
top:{x:c.x+c.width/2,y:c.y}, bottom:{x:c.x+c.width/2,y:c.y+c.height},
left:{x:c.x,y:c.y+c.height/2}, right:{x:c.x+c.width,y:c.y+c.height/2},
center:{x:c.x+c.width/2,y:c.y+c.height/2}
};
}
};
Plugin System
export interface ZenodePlugin { name: string; install(engine: ZenodeEngine): void; }
engine.use(MyPlugin);
// engine.registerShape() + engine.use() being public IS the plugin system.
// Not a future feature โ the natural result of correct Phase 1 architecture.
โ What Is Already Built โ Do Not Rebuild
Core
- D3.js SVG canvas: zoom, pan, scroll, double-click reset, configurable extents
- Grid: dotted/sheet/line/cross, color, transparency, size
ZenodeEngineclass: canvas init, element management, state- Shape preview with real-time grid snapping
- Shape placement:
mouseClickโplacedNodes[]โg.placed-nodes - Config merger with typed defaults
Shape System
ShapeRenderercontract:draw,getPath,getBounds,getPortsShapeRegistryโsrc/nodes/registry.tsRectangleRenderer,CircleRenderer,RhombusRendererall implementedengine.registerShape()exposed- Overlay via
renderer.getPath()โsrc/nodes/overlay.ts roundedRectPath()โsrc/nodes/geometry/rectanglePath.ts
Interaction
- Selection: single, lasso, Escape/Delete, configurable keyboard shortcuts
- Visual Effects:
VisualState,applyEffects(), Glow + Stroke Flow + Gradient - Alignment guides: renderer-bounds-aware, RAF-throttled, config-driven
g.lassolayer in canvas
Connections
- Ports via
renderer.getPorts(), hover circles, crosshair cursor - Drawing: ghost line, drop to finalise, stored in
connections[] - Types: straight, curved, s-shaped, l-bent
- Smart routing (Pro, gated via LicenseManager)
- Labels: pill-styled, double-click inline edit
- Navigation controls: floating zoom/focus buttons
๐ Build Plan โ Layer by Layer
LAYER 1 โ @zenode/designer
The D3.js visual canvas. The entry point for the entire ecosystem. โก Ship this first. Everything else depends on it having real users.
L1 ยท Phase 1 โ Core Shape Lifecycle โ COMPLETE
- Shape placement, drag & drop, grid snapping
- Selection, lasso, keyboard shortcuts
- Visual Effects System (Glow, Stroke Flow, Gradient)
- Alignment guides
L1 ยท Phase 2 โ Connections โ COMPLETE
- Ports via
renderer.getPorts() - Drawing connections, ghost line
- Types: straight, curved, s-shaped, l-bent
- Smart routing (Pro)
- Connection labels, inline edit
- Navigation controls
L1 ยท Phase 2+ โ Connection Markers โฌ
- Arrowheads via SVG
<marker>:none,arrow,filled-arrow,diamond,circle - Per-connection color, width, dash pattern
- Animated flow direction via Visual Effects
strokeAnimation
L1 ยท Phase 3 โ Context Action Pad ๐ง IN PROGRESS
The floating toolbar near selected nodes/edges. Plugin-extensible from day one. Modelled after BPMN.js's context pad. One of the most visible UX differentiators.
โโโโฌโโโฌโโโฌโโโฌโโโ
โ๐โโกโโ๏ธโโโโโ โ HTML overlay. Never inside SVG.
โโโโดโโโดโโโดโโโดโโโ
Types โ src/types/index.ts
export type ContextPadTarget =
| { kind: 'node'; id: string; data: NodeData }
| { kind: 'edge'; id: string; data: EdgeData };
export interface ContextPadAction {
id: string;
icon: string; // inline SVG string
tooltip: string;
targets?: Array<'node' | 'edge'>; // omit = both
appliesTo?: string[]; // specific node/edge types
group?: 'primary' | 'secondary' | 'danger' | string;
handler: (target: ContextPadTarget, engine: ZenodeEngine) => void;
isVisible?: (target: ContextPadTarget, engine: ZenodeEngine) => boolean;
isDisabled?: (target: ContextPadTarget, engine: ZenodeEngine) => boolean;
}
export interface ContextPadConfig {
enabled: boolean;
trigger: 'hover' | 'select';
offset: { x: number; y: number };
position: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
showDefaults: boolean;
suppressDefaults?: string[];
}
Registry โ src/contextpad/registry.ts
export class ContextPadRegistry {
private actions = new Map<string, ContextPadAction>();
register(action: ContextPadAction): void { this.actions.set(action.id, action); }
unregister(id: string): void { this.actions.delete(id); }
getActionsFor(target: ContextPadTarget, engine: ZenodeEngine): ContextPadAction[] {
return [...this.actions.values()]
.filter(a => {
if (a.targets && !a.targets.includes(target.kind)) return false;
if (a.appliesTo && !a.appliesTo.includes(target.data.type)) return false;
if (a.isVisible && !a.isVisible(target, engine)) return false;
return true;
})
.sort((a, b) => (a.group ?? 'primary').localeCompare(b.group ?? 'primary'));
}
}
Default Actions โ src/contextpad/defaults.ts
delete (both), connect (nodes), edit-label (both), properties (both), duplicate (nodes), change-color (both) โ all suppressible.
Renderer โ src/contextpad/renderer.ts
HTML <div> overlay. Position = canvas bbox โ D3 zoom transform โ SVG clientRect offset.
show(target, actions, config): void // render buttons
hide(): void
reposition(transform: ZoomTransform): void // RAF-throttled โ called on every zoom/pan
Critical Rules
- HTML overlay, not SVG โ
position: absolutediv over the SVG wrapper parent - Position always from D3 zoom transform โ never cache screen coordinates
- Hides on: drag start, canvas click,
node:deleted,edge:deleted, Escape isVisible/isDisabledcalled every render โ live engine state, never memoized- Actions are stateless โ
(target, engine)args only, never close over stale data - Plugins can add AND remove โ
engine.unregisterContextAction()is first-class API
Engine Methods
engine.registerContextAction(action: ContextPadAction): void
engine.unregisterContextAction(id: string): void
engine.showContextPad(targetId: string, kind: 'node' | 'edge'): void
engine.hideContextPad(): void
engine.startConnectionFrom(nodeId: string): void
engine.beginLabelEdit(id: string, kind: 'node' | 'edge'): void
engine.duplicateNode(id: string): string
engine.on('contextpad:open', ({ target }) => {})
engine.on('contextpad:close', () => {})
engine.on('contextpad:action', ({ actionId, target }) => {})
engine.on('contextpad:properties', ({ target }) => {})
Plugin Pattern
engine.use({
name: 'my-workflow-plugin',
install(engine) {
engine.registerContextAction({
id: 'run-node', icon: `<svg>...</svg>`, tooltip: 'Run this step',
targets: ['node'], appliesTo: ['http-request', 'script'],
group: 'primary',
handler: (t, e) => e.setNodeStatus(t.id, 'running'),
isDisabled: (t, e) => e.getNode(t.id)?.meta?.locked === true
});
engine.unregisterContextAction('duplicate');
}
});
L1 ยท Phase 4 โ Swimlanes / Pools / Lanes โฌ
engine.addNode({ type: 'pool', label: 'Order Process', orientation: 'horizontal' });
engine.addNode({ type: 'lane', label: 'Sales Team', parentId: 'pool-1' });
g.swimlaneslayer โ belowg.placed-nodes- Child positions clamped to parent lane bounds during drag
SwimLaneRendererimplementsShapeRenderer- Cross-lane connection ports on swimlane boundary
- Validation rule:
no-orphan-swimlane
L1 ยท Phase 5 โ BPMN Feature Parity โฌ
Bringing Zenode to full BPMN.js feature parity:
- Copy/paste โ
engine.copyNodes()/engine.pasteNodes()โ Ctrl+C/V - Resize handles โ 8-point handles, snap to grid, ports recompute after resize
- Node search โ
engine.search(query)โ searches label, type, meta - Node groups โ
engine.groupNodes()/engine.ungroupNodes() - Color picker โ
change-colorcontext pad action, persisted in state - BPMN presets โ
@zenode/bpmn-presetsfree package (events, tasks, gateways, containers) - Toon theme โ
themes/toon.cssโ 2-hour effort, high marketing value
L1 ยท Phase 6 โ Full Public Designer API โฌ
Node API
engine.addNode(config: NodeConfig): string
engine.removeNode(id: string): void
engine.updateNode(id: string, patch: Partial<NodeConfig>): void
engine.getNode(id: string): NodeData | null
engine.getAllNodes(): NodeData[]
engine.getNodesInViewport(): NodeData[] // viewport culling for performance
engine.focusNode(id: string): void
engine.highlight(id: string, durationMs?: number): void
engine.duplicateNode(id: string): string
engine.copyNodes(ids: string[]): void
engine.pasteNodes(offset?): string[]
engine.groupNodes(ids: string[]): string
engine.ungroupNodes(groupId: string): void
engine.lock(): void
engine.unlock(): void
engine.setReadOnly(bool: boolean): void
Edge API
engine.addEdge(config: EdgeConfig): string
engine.removeEdge(id: string): void
engine.getEdge(id: string): EdgeData | null
engine.getAllEdges(): EdgeData[]
engine.getPath(fromNodeId: string, toNodeId: string): string[]
Live Node Status (Free โ nobody else has this)
engine.setNodeStatus(id: string, status: 'idle'|'running'|'success'|'error'|'warning'): void
// running โ animated pulse ring
// success โ green checkmark badge
// error โ red border + shake
// warning โ amber badge
Validation (Free)
engine.addRule(rule: BuiltInRule | CustomRule): void
// built-ins: 'no-cycles', 'all-nodes-connected', 'single-entry-point',
// 'single-exit-point', 'max-depth', 'no-orphan-swimlane'
engine.validate(): ValidationResult // { valid, errors, warnings }
Canvas API
engine.clear(): void engine.reset(): void
engine.zoomTo(level): void engine.panTo(x, y): void
engine.fitToScreen(): void engine.getViewport(): ViewportState
engine.search(query): NodeData[]
engine.setTheme(theme): void // โ Pro
engine.enableMinimap(): void // โ Pro
Undo/Redo
- Command pattern.
Ctrl+Z/Ctrl+Shift+Z - Free: 10 steps. Pro: up to 200.
Events
engine.on('node:placed'|'node:moved'|'node:deleted'|'node:selected'|
'node:click'|'node:doubleclick'|'node:config:change'|
'edge:created'|'edge:deleted'|'workflow:change'|
'validation:error'|'export:complete'|
'contextpad:open'|'contextpad:close'|
'contextpad:action'|'contextpad:properties'|
'runtime:step'|'runtime:complete'|'runtime:error', handler)
engine.off(event, handler)
engine.once(event, handler)
LAYER 2 โ @zenode/serializer
Converting designer state to every format that matters. Usable without the designer.
L2 ยท Phase 7 โ Export / Import โฌ
// JSON โ round-trip perfect, versioned
engine.toJSON(): ZenodeState // version: "1.0"
engine.fromJSON(state): void
// Formats (Free)
engine.toXML(): string // generic workflow XML
engine.toMermaid(): string // GitHub README ready
engine.toDOT(): string // Graphviz
engine.toDSL(): string // Zenode DSL, version-controllable
// BPMN 2.0 (Pro) โ the enterprise unlock
engine.toBPMN(): string // Camunda / Activiti compatible
engine.fromBPMN(xml): void // import from any BPMN tool
// Image (Pro)
engine.toImage('png'|'svg'): Promise<Blob>
Read-only viewer embed โ free, drives distribution:
<script src="https://cdn.zenode.dev/zenode.min.js"></script>
<div id="diagram"></div>
<script>Zenode.render('#diagram', workflowJSON, { readOnly: true });</script>
The round-trip:
Editor โโtoJSON()โโโถ DB โโfromJSON()โโโถ Viewer (read-only)
โโtoBPMN()โโโโโโโถ Camunda/Activiti
โโtoMermaid()โโโโถ GitHub README
โโcreateRuntime()โถ Server execution
LAYER 3 โ @zenode/runtime
The feature that separates Zenode from every diagramming library. The diagram IS the workflow logic. Change the diagram, change the behavior.
L3 ยท Phase 8 โ Execution Runtime โฌ
Diagram JSON
โ createRuntime({ resolvers, outputTransformer })
ExecutionGraph (topological sort, port routing)
โ runtime.execute(inputData)
StepRunner (calls resolver per node, routes via port)
โ
ExecutionResult (per-node outputs, history, timing)
โ outputTransformer(result)
Your output object
Runtime API
const runtime = engine.createRuntime({
resolvers: {
'http-request': async (node, input) => {
const res = await fetch(node.meta.url, { method: node.meta.method, body: JSON.stringify(input.data) });
return { data: await res.json() };
},
'condition': (node, input) => ({
port: evaluateExpression(node.meta.expression, input.data) ? 'true' : 'false',
data: input.data
}),
'transform': (node, input) => ({ data: applyMapping(node.meta.mapping, input.data) }),
'send-email': async (node, input) => {
await emailService.send({ to: node.meta.recipient, body: template(node.meta.template, input.data) });
return { sent: true };
},
},
hooks: {
onStepStart: (nodeId) => engine.setNodeStatus(nodeId, 'running'),
onStepComplete: (nodeId) => engine.setNodeStatus(nodeId, 'success'),
onStepError: (nodeId) => engine.setNodeStatus(nodeId, 'error'),
},
timeout: 30000,
maxSteps: 1000, // infinite loop guard
});
const output = await runtime.execute({ userId: '123' });
Diagram-as-Config (the core value proposition)
// Store diagram in DB. Load and run server-side without code changes.
// Business user changes the diagram โ behavior changes. No deployments.
const def = await db.workflows.findById(id);
const engine = ZenodeRuntime.fromJSON(def.definition);
const runtime = engine.createRuntime({ resolvers: myResolvers });
const result = await runtime.execute(triggerData);
Runtime Types โ src/runtime/types.ts
export interface RuntimeResolver {
(node: NodeData, input: ResolverInput): Promise<ResolverOutput> | ResolverOutput;
}
export interface ResolverInput { data: unknown; meta: Record<string, unknown>; context: ExecutionContext; }
export interface ResolverOutput { data: unknown; port?: string; error?: string; }
export interface ExecutionResult {
finalStatus: 'success' | 'error' | 'timeout';
finalOutput: ResolverOutput | null;
nodeHistory: Array<{ id: string; type: string; input: unknown; output: unknown; ms: number }>;
errors: Array<{ nodeId: string; message: string }>;
totalMs: number;
}
export interface RuntimeConfig {
resolvers: Record<string, RuntimeResolver>;
outputTransformer?: (result: ExecutionResult) => unknown;
hooks?: { onStepStart?(...): void; onStepComplete?(...): void; onStepError?(...): void; };
timeout?: number;
maxSteps?: number;
}
Workflow Simulation / Step Runner (Pro)
const ctrl = engine.simulate(inputData?);
ctrl.step(); ctrl.play(delayMs); ctrl.pause(); ctrl.reset();
ctrl.on('step', ({ nodeId, inputData, outputData, port }) => {});
// Visual: active node โ 'running', completed โ 'success'
LAYER 4 โ @zenode/transformer
User defines exactly what comes out. Zenode never imposes a schema.
L4 ยท Phase 9 โ Output Transformation โฌ
// The outputTransformer in RuntimeConfig IS Layer 4.
// It lives in @zenode/transformer as a standalone utility.
// Pattern 1: REST API response
outputTransformer: (r) => ({ ok: r.finalStatus === 'success', payload: r.finalOutput?.data, trace: r.nodeHistory.map(n=>n.id) })
// Pattern 2: Event sourcing
outputTransformer: (r) => r.nodeHistory.map(n => ({ event: `workflow.${n.type}.completed`, data: n.output }))
// Pattern 3: BPMN audit log
outputTransformer: (r) => ({ processId: workflowId, activities: r.nodeHistory.map(n => ({ activityId: n.id, status: 'completed' })) })
// Pattern 4: Pass-through
outputTransformer: (r) => r.finalOutput?.data
Schema-Driven Property Panels (connects Layer 1 to Layer 4)
// Node declares what data it needs. Zenode auto-renders the form.
// Values โ node.meta โ available to resolvers at runtime.
{
type: 'http-request',
schema: {
url: { type: 'string', required: true, label: 'Endpoint URL' },
method: { type: 'select', options: ['GET','POST','PUT','DELETE'] },
headers: { type: 'keyvalue', label: 'Headers' },
body: { type: 'textarea', label: 'Request Body' },
timeout: { type: 'number', default: 5000 }
}
}
// Opens via contextpad:properties event โ auto-rendered panel.
// Emits node:config:change on every field change.
๐ Architecture Rules
TypeScript
- Strict mode. No
any. JSDoc on all public methods. Barrel export fromsrc/types/index.ts. ZenodeEngine= designer API.ZenodeRuntime= execution. Never mix them.
D3.js Layer Order (strict)
1. g.swimlanes (below everything)
2. g.grid
3. g.connections
4. g.placed-nodes
5. g.preview (pointer-events: none always)
6. g.guides (pointer-events: none always)
7. g.lasso
Context pad: HTML overlay, never in SVG stack.
State
ZenodeEngineowns designer state.ZenodeRuntimeowns execution state. Spread never mutate.- Emit typed events on every mutation.
CSS Variables
/* Canvas */ --zenode-canvas-bg --zenode-grid-color
/* Nodes */ --zenode-node-bg --zenode-node-border --zenode-node-shadow
/* Interaction */ --zenode-selection-color --zenode-guide-color --zenode-port-color
/* Status */ --zenode-status-running --zenode-status-success
--zenode-status-error --zenode-status-warning
/* Edges */ --zenode-edge-color
/* Context Pad */ --zenode-contextpad-bg --zenode-contextpad-border
--zenode-contextpad-radius --zenode-contextpad-action-size
--zenode-contextpad-action-hover --zenode-contextpad-action-active
--zenode-contextpad-danger --zenode-contextpad-disabled
--zenode-contextpad-divider
/* Swimlanes */ --zenode-swimlane-bg --zenode-swimlane-header-bg
--zenode-swimlane-border --zenode-lane-divider
Performance
-
500 nodes:
getNodesInViewport()culling -
1000 nodes: OffscreenCanvas for static background
- Guides + context pad: RAF-throttled
- Connection recalc: only affected connections on node move
- Runtime: async/await with timeout + maxSteps guard
๐ File Structure
zenode/
โโโ packages/
โ โโโ designer/ โ @zenode/designer (Layer 1)
โ โ โโโ src/
โ โ โ โโโ core/
โ โ โ โ โโโ engine.ts
โ โ โ โ โโโ canvas.ts
โ โ โ โ โโโ grid.ts
โ โ โ โ โโโ history.ts
โ โ โ โ โโโ license.ts
โ โ โ โโโ nodes/
โ โ โ โ โโโ registry.ts
โ โ โ โ โโโ placement.ts
โ โ โ โ โโโ selection.ts
โ โ โ โ โโโ overlay.ts # Uses renderer.getPath โ NEVER plain rect
โ โ โ โ โโโ resize.ts
โ โ โ โ โโโ status.ts
โ โ โ โ โโโ geometry/
โ โ โ โ โ โโโ rectanglePath.ts
โ โ โ โ โโโ shapes/
โ โ โ โ โโโ rectangle.ts
โ โ โ โ โโโ circle.ts
โ โ โ โ โโโ rhombus.ts
โ โ โ โ โโโ swimlane.ts
โ โ โ โ โโโ group.ts
โ โ โ โโโ contextpad/
โ โ โ โ โโโ registry.ts
โ โ โ โ โโโ renderer.ts # HTML overlay โ position from D3 zoom transform
โ โ โ โ โโโ defaults.ts
โ โ โ โ โโโ index.ts
โ โ โ โโโ connections/
โ โ โ โ โโโ manager.ts
โ โ โ โ โโโ ports.ts
โ โ โ โ โโโ drawing.ts
โ โ โ โ โโโ markers.ts
โ โ โ โ โโโ paths/
โ โ โ โ โโโ straight.ts
โ โ โ โ โโโ curved.ts
โ โ โ โ โโโ s-shaped.ts
โ โ โ โ โโโ l-bent.ts
โ โ โ โโโ effects/
โ โ โ โ โโโ engine.ts
โ โ โ โโโ validation/
โ โ โ โ โโโ engine.ts
โ โ โ โ โโโ rules/
โ โ โ โโโ plugins/
โ โ โ โ โโโ manager.ts
โ โ โ โ โโโ auto-layout.ts
โ โ โ โ โโโ minimap.ts
โ โ โ โ โโโ collaboration.ts
โ โ โ โโโ types/
โ โ โ โ โโโ index.ts # ALL designer interfaces
โ โ โ โโโ themes/
โ โ โ โโโ dark.css
โ โ โ โโโ light.css
โ โ โ โโโ toon.css
โ โ โโโ package.json
โ โ
โ โโโ serializer/ โ @zenode/serializer (Layer 2)
โ โ โโโ src/
โ โ โ โโโ json.ts
โ โ โ โโโ xml.ts
โ โ โ โโโ bpmn.ts # Pro
โ โ โ โโโ mermaid.ts
โ โ โ โโโ dot.ts
โ โ โ โโโ dsl.ts
โ โ โ โโโ image.ts # Pro
โ โ โโโ package.json
โ โ
โ โโโ runtime/ โ @zenode/runtime (Layer 3)
โ โ โโโ src/
โ โ โ โโโ engine.ts # ZenodeRuntime class
โ โ โ โโโ executor.ts # Step-by-step execution
โ โ โ โโโ graph.ts # Topological sort, port routing
โ โ โ โโโ simulation.ts # Pro โ visual step-through
โ โ โ โโโ types.ts
โ โ โโโ package.json
โ โ
โ โโโ transformer/ โ @zenode/transformer (Layer 4)
โ โ โโโ src/
โ โ โ โโโ transformer.ts # outputTransformer factory
โ โ โ โโโ schema.ts # Schema-driven property panels
โ โ โ โโโ patterns.ts # REST, event-sourcing, BPMN audit presets
โ โ โโโ package.json
โ โ
โ โโโ core/ โ @zenode/core (all four layers)
โ โ โโโ src/index.ts # re-exports all four packages, pre-wired
โ โ โโโ package.json
โ โ
โ โโโ bpmn-presets/ โ @zenode/bpmn-presets (free, always)
โ โโโ src/
โ โ โโโ presets.ts # start-event, task, gateway, pool etc.
โ โโโ package.json
โ
โโโ examples/
โ โโโ custom-shapes/
โ โ โโโ hexagon.ts
โ โ โโโ star.ts
โ โโโ plugins/
โ โโโ automation-plugin.ts # adds 'run-node' context pad action
โ โโโ runtime/
โ โโโ approval-workflow.ts # designer โ execute โ output
โ โโโ http-pipeline.ts
โโโ demo/ # Layer 1 demo app (ship first)
โโโ tests/
โโโ pnpm-workspace.yaml # monorepo
โโโ package.json
๐งช Before Writing Any Code โ Ask These First
- Does this mutate engine state? โ Command pattern + emit event.
- Does this render something? โ D3 data join on the correct layer group.
- Does this need to be undoable? โ Yes if it changes nodes, connections, viewport, swimlane.
- Is this a premium feature? โ Gate via
LicenseManager.check('feature-name'). - Does this need a config option? โ Add to
src/types/index.tswith typed default. - Does this change appearance? โ
--zenode-*CSS variables โ never hardcoded. - Does this involve coordinates? โ
d3.pointer(event, svgRoot), account for zoom transform. - Does this draw an overlay, ring, or port? โ
registry.get(node.type).getPath(node)โ never plain rect. - Is this a new shape? โ Implement full
ShapeRenderer:draw,getPath,getBounds,getPorts. - Does this add a button near a node/edge? โ
engine.registerContextAction()โ never hardcode. - Does this involve context pad position? โ Always from D3 zoom transform โ never cached screen coords.
- Does this involve workflow execution? โ
ZenodeRuntimeinpackages/runtime/โ never inengine.ts. - Does this transform execution output? โ
outputTransformerinRuntimeConfigโ never in runtime core. - Is this a child of a swimlane? โ Position clamped to parent bounds, ports recomputed after move.
- Does this belong in a specific package? โ Designer UI =
@zenode/designer. Formats =@zenode/serializer. Execution =@zenode/runtime. Output =@zenode/transformer.
๐บ Master Build Order
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LAYER 1 โ @zenode/designer โ SHIP THIS FIRST
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
L1ยทP1 Core shapes (placement, drag, selection, effects, guides)
โ
L1ยทP2 Connections (ports, drawing, types, routing, labels, nav)
โฌ L1ยทP2+ Connection markers (arrowheads, animated flow)
๐ง L1ยทP3 Context Action Pad โ IN PROGRESS
โโ ContextPadRegistry + ContextPadRenderer (HTML overlay)
โโ Default: delete, connect, edit-label, properties, duplicate, change-color
โโ registerContextAction() / unregisterContextAction()
โโ Position from D3 zoom transform, hides on drag/deselect/delete/Escape
โโ Works for nodes AND edges
โโ --zenode-contextpad-* CSS vars
โฌ L1ยทP4 Swimlanes (g.swimlanes layer, child clamping, cross-lane ports)
โฌ L1ยทP5 BPMN parity (copy/paste, resize handles, search, groups, presets, toon theme)
โฌ L1ยทP6 Full public API (status, validation, undo/redo, schema panels, DSL)
โโ @zenode/designer v0.1.0 LAUNCH โโ
โโ README + GIF + quick start
โโ Live demo deployed
โโ NPM publish
โโ Show HN post โ devto article #1: "Visual workflow designer, no framework needed"
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LAYER 2 โ @zenode/serializer
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โฌ L2ยทP7 JSON, XML, Mermaid, DOT, DSL (Free)
BPMN 2.0 (Pro) โ devto article #2: "Export any diagram to BPMN 2.0"
Read-only viewer embed (Free)
Image export (Pro)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LAYER 3 โ @zenode/runtime
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โฌ L3ยทP8 ZenodeRuntime (topo sort, step executor, resolver API, port routing)
Execution hooks โ setNodeStatus() visual sync
Diagram-as-Config server-side pattern
Simulation/step runner (Pro)
BPMN XML execution from any source โ devto article #3: "JS BPMN runtime"
This article + HN post = acquisition interest starts
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LAYER 4 โ @zenode/transformer
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โฌ L4ยทP9 outputTransformer factory + reference patterns
Schema-driven property panels (node.meta โ resolver input)
Full round-trip demo: design โ serialize โ execute โ transform
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SHIP @zenode/core
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โฌ @zenode/core bundle (all four layers, pre-wired)
โฌ Full demo app: designer + runtime demo + theme switcher (light/dark/toon)
โฌ zenode.min.js UMD (<120kb gzipped, iframe-safe)
โฌ Product Hunt launch โ "The complete visual workflow ecosystem for JavaScript"
This is the moment. Four separate launch events built to this.
๐งพ Context Snapshot โ Source of Truth
โ Confirmed implemented
ShapeRenderercontract (draw,getPath,getBounds,getPorts) โsrc/types/index.tsShapeRegistryโsrc/nodes/registry.ts- Built-in renderers:
rectangle.ts,circle.ts,rhombus.ts engine.registerShape()exposed- Overlay via
renderer.getPath()โsrc/nodes/overlay.ts - Selection: single, lasso, Escape/Delete, keyboard shortcut callbacks
- Visual Effects:
VisualState,src/effects/engine.ts, Glow + Stroke Flow + Gradient - Alignment guides:
getBounds()-aware, RAF-throttled roundedRectPath()โsrc/nodes/geometry/rectanglePath.tsg.lassolayer- Connections: ports, drawing, straight/curved/s-shaped/l-bent, smart routing (Pro)
- Connection labels, inline edit
- Navigation controls: floating zoom/focus buttons
๐ Permanent Rules
- All shape visuals through
ShapeRegistry+ShapeRenderer. - Overlay/selection/lasso/ports always use
getPath/getBounds/getPorts. - Geometry helpers in
src/nodes/geometry/โsrc/components/shapeTypes/is deleted, never recreate. engine.registerShape()is the only shape extension point.engine.registerContextAction()is the only context pad extension point.- Context pad = HTML overlay only โ position always from D3 zoom transform.
- Effects via
effectsEngine.applyEffects()after draw โ never insideShapeRenderer.draw(). - Execution in
packages/runtime/โ never inpackages/designer/. - Output transformation in
RuntimeConfig.outputTransformerโ never in runtime core. - Config = canvas environment only. Nodes via
engine.addNode(). Styles via presets. - Each package has its own
package.json, README, and NPM page. They are independently installable.
End of prompt. Immediate focus: Complete L1ยทPhase 3 โ Context Action Pad, then ship @zenode/designer v0.1.0.
What's inside
14 sections including package architecture, pricing, competitive map, build plan with 6 phases, and 8 code examples
Change this for your project
- Replace
@zenode/designerwith your own package name - Replace
martinmanu/Zenodewith your repository name - Replace
$149/yrwith your own pricing - Replace
src/nodes/registry.tswith your own file paths
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Package architecture that lets users adopt one piece and expand naturally
- Competitive comparison table highlighting unique moat
- Phased build plan with clear completion criteria per layer
Related Documents
dbt Core
Defines a complete dbt project setup with layered architecture, SQL style, Jinja best practices, and CI/CD workflow guidance.
HarmonyOS ไธป้ขไธๆ ทๅผ - Cursor Rules
Defines HarmonyOS theming rules for dark/light mode, page brightness, and screen-on control with code examples.
Cursor Development Environment
Documents a Docker-based dev container setup with test commands and OpenClaw agent configuration for the ClawDrive project.
Journey from Concept to Code: Transforming Cursor into a Devin-like AI Assistant
Documents the creation of aicode.cursorrules, a set of rules that turns Cursor/Windsurf into a Devin-like AI assistant with automated planning and self-evolution.