Back to .md Directory

๐Ÿง  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.

May 2, 2026
0 downloads
3 views
ai prompt claude cursor copilot workflow
View source

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

JavaScriptTypeScriptD3.jsNode.jsReactVue

๐Ÿง  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

PackageFreePaid
@zenode/designerCore canvas, shapes, basic connectionsPro $149/yr โ€” minimap, themes, undo+, PNG export
@zenode/serializerJSON, Mermaid, DOT, DSLPro $99/yr โ€” BPMN 2.0, image export
@zenode/runtime100 executions/dayRuntime $49/mo โ€” unlimited executions
@zenode/core (bundle)All free tiersBundle $199/yr โ€” all pro features
@zenode/bpmn-presetsAlways 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 FlowBPMN.jsJointJS+n8nZenode
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โš ๏ธโŒโš ๏ธโŒโœ…
PriceFree/ProFree$1200+/yrFree/CloudFree/$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/designer live 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
  • ZenodeEngine class: 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

  • ShapeRenderer contract: draw, getPath, getBounds, getPorts
  • ShapeRegistry โ€” src/nodes/registry.ts
  • RectangleRenderer, CircleRenderer, RhombusRenderer all implemented
  • engine.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.lasso layer 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

  1. HTML overlay, not SVG โ€” position: absolute div over the SVG wrapper parent
  2. Position always from D3 zoom transform โ€” never cache screen coordinates
  3. Hides on: drag start, canvas click, node:deleted, edge:deleted, Escape
  4. isVisible/isDisabled called every render โ€” live engine state, never memoized
  5. Actions are stateless โ€” (target, engine) args only, never close over stale data
  6. 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.swimlanes layer โ€” below g.placed-nodes
  • Child positions clamped to parent lane bounds during drag
  • SwimLaneRenderer implements ShapeRenderer
  • 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-color context pad action, persisted in state
  • BPMN presets โ€” @zenode/bpmn-presets free 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 from src/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

  • ZenodeEngine owns designer state. ZenodeRuntime owns 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

  1. Does this mutate engine state? โ†’ Command pattern + emit event.
  2. Does this render something? โ†’ D3 data join on the correct layer group.
  3. Does this need to be undoable? โ†’ Yes if it changes nodes, connections, viewport, swimlane.
  4. Is this a premium feature? โ†’ Gate via LicenseManager.check('feature-name').
  5. Does this need a config option? โ†’ Add to src/types/index.ts with typed default.
  6. Does this change appearance? โ†’ --zenode-* CSS variables โ€” never hardcoded.
  7. Does this involve coordinates? โ†’ d3.pointer(event, svgRoot), account for zoom transform.
  8. Does this draw an overlay, ring, or port? โ†’ registry.get(node.type).getPath(node) โ€” never plain rect.
  9. Is this a new shape? โ†’ Implement full ShapeRenderer: draw, getPath, getBounds, getPorts.
  10. Does this add a button near a node/edge? โ†’ engine.registerContextAction() โ€” never hardcode.
  11. Does this involve context pad position? โ†’ Always from D3 zoom transform โ€” never cached screen coords.
  12. Does this involve workflow execution? โ†’ ZenodeRuntime in packages/runtime/ โ€” never in engine.ts.
  13. Does this transform execution output? โ†’ outputTransformer in RuntimeConfig โ€” never in runtime core.
  14. Is this a child of a swimlane? โ†’ Position clamped to parent bounds, ports recomputed after move.
  15. 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

  • ShapeRenderer contract (draw, getPath, getBounds, getPorts) โ€” src/types/index.ts
  • ShapeRegistry โ€” 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.ts
  • g.lasso layer
  • Connections: ports, drawing, straight/curved/s-shaped/l-bent, smart routing (Pro)
  • Connection labels, inline edit
  • Navigation controls: floating zoom/focus buttons

๐Ÿ“Œ Permanent Rules

  1. All shape visuals through ShapeRegistry + ShapeRenderer.
  2. Overlay/selection/lasso/ports always use getPath/getBounds/getPorts.
  3. Geometry helpers in src/nodes/geometry/ โ€” src/components/shapeTypes/ is deleted, never recreate.
  4. engine.registerShape() is the only shape extension point.
  5. engine.registerContextAction() is the only context pad extension point.
  6. Context pad = HTML overlay only โ€” position always from D3 zoom transform.
  7. Effects via effectsEngine.applyEffects() after draw โ€” never inside ShapeRenderer.draw().
  8. Execution in packages/runtime/ โ€” never in packages/designer/.
  9. Output transformation in RuntimeConfig.outputTransformer โ€” never in runtime core.
  10. Config = canvas environment only. Nodes via engine.addNode(). Styles via presets.
  11. 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/designer with your own package name
  • Replace martinmanu/Zenode with your repository name
  • Replace $149/yr with your own pricing
  • Replace src/nodes/registry.ts with 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