openpencil/packages/pen-engine
Kayshen-X a7d73ebb62 feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.

- design-agent system prompt + in-process design toolset (parity-locked with
  the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
  (per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
  export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
  bubbles, model-pill bottom toolbar, header, empty-state pills, the
  PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
  (switch / close / + / Cmd+T) with each run bound to its tab

Large checkpoint commit spanning the working tree (Rust shell crates).
2026-07-02 21:21:06 +08:00
..
src feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle 2026-07-02 21:21:06 +08:00
CLAUDE.md feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle 2026-07-02 21:21:06 +08:00
LICENSE feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle 2026-07-02 21:21:06 +08:00
package.json feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle 2026-07-02 21:21:06 +08:00
README.md feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle 2026-07-02 21:21:06 +08:00
tsconfig.json feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle 2026-07-02 21:21:06 +08:00

@zseven-w/pen-engine

Headless design engine for OpenPencil — framework-free document management, selection, history, viewport, and spatial queries. Build your own design tool UI on top of this engine.

Install

npm install @zseven-w/pen-engine
# or
bun add @zseven-w/pen-engine

Overview

pen-engine is the core runtime that powers OpenPencil's editor. It manages the entire document lifecycle without any framework dependency — React, Vue, Svelte, or vanilla JS all work. The optional browser.ts entry adds GPU-accelerated canvas rendering via CanvasKit/Skia.

DesignEngine
  |- DocumentManager    Node CRUD, tree operations
  |- SelectionManager   Multi-select, hover tracking
  |- HistoryManager     Undo/redo with debounce + batch
  |- PageManager        Multi-page support
  |- VariableManager    Design variables ($refs)
  |- ViewportController Zoom, pan, coordinate transforms
  |- SpatialIndex       R-tree for hit testing & spatial queries
  |- EventEmitter       Typed event system

Quick Start

import { DesignEngine } from '@zseven-w/pen-engine';

const engine = new DesignEngine();

// Load or create a document
engine.loadDocument(myDocument);

// Add a node
engine.addNode(null, {
  id: 'frame-1',
  type: 'frame',
  name: 'Header',
  width: 1200,
  height: 80,
  layout: 'horizontal',
});

// Select, undo, inspect
engine.select(['frame-1']);
engine.undo();
console.log(engine.getDocument());

Features

Document Operations

Create, query, and mutate the node tree:

engine.addNode(parentId, node, index?);
engine.updateNode(id, { fill: [{ type: 'solid', color: '#FF0000' }] });
engine.removeNode(id);
engine.moveNode(id, newParentId, index);
engine.duplicateNode(id);
engine.groupNodes(['node-1', 'node-2']);
engine.ungroupNode(groupId);
engine.getNodeById(id);

Selection & Hover

engine.select(['node-1', 'node-2']);
engine.clearSelection();
engine.getSelection(); // string[]
engine.setHoveredId('node-3');
engine.getHoveredId(); // string | null

History (Undo / Redo)

Structural history with debouncing and batch support:

engine.undo();
engine.redo();
engine.canUndo; // boolean
engine.canRedo; // boolean

// Batch multiple mutations into a single history entry
engine.batch(() => {
  engine.updateNode('a', { x: 100 });
  engine.updateNode('b', { x: 200 });
});

Viewport

Pan, zoom, and coordinate conversion:

engine.setViewport(zoom, panX, panY);
engine.zoomToRect(x, y, w, h, containerW, containerH);
engine.getContentBounds(); // { x, y, w, h } | null
engine.screenToScene(screenX, screenY); // { x, y }
engine.sceneToScreen(sceneX, sceneY); // { x, y }

Hit Testing (Spatial Index)

R-tree backed queries for click and marquee selection:

engine.hitTest(x, y); // PenNode | null
engine.searchRect(x, y, w, h); // PenNode[]

Multi-Page

engine.addPage(); // returns pageId
engine.removePage(pageId);
engine.setActivePage(pageId);
engine.getActivePage();

Design Variables

engine.setVariable('primary', { type: 'color', value: '#2563EB' });
engine.removeVariable('primary');
engine.renameVariable('primary', 'brand');
engine.resolveVariable('$primary'); // '#2563EB'

SVG Import

Isomorphic SVG parser (DOM in browser, regex fallback in Node.js):

import { parseSvgToNodes } from '@zseven-w/pen-engine';

const nodes = parseSvgToNodes(svgString, 400);
engine.addNode(null, nodes[0]);

Events

Typed event system for reactive UI binding:

const unsub = engine.on('document:change', (doc) => {
  /* re-render */
});
engine.on('selection:change', (ids) => {
  /* update UI */
});
engine.on('viewport:change', (viewport) => {
  /* update zoom indicator */
});
unsub(); // unsubscribe

Browser Canvas (Optional)

GPU-accelerated rendering via CanvasKit/Skia — import from @zseven-w/pen-engine/browser:

import { attachCanvas, attachInteraction } from '@zseven-w/pen-engine/browser';

const binding = await attachCanvas(engine, canvasElement);
const detach = attachInteraction(engine, canvasElement);

// Later
binding.dispose();
detach();

API Reference

Method Description
loadDocument(doc) Load a PenDocument
getDocument() Get current document
createDocument() Create empty document
addNode(parent, node, index?) Insert node
updateNode(id, updates) Partial update
removeNode(id) Delete node + children
moveNode(id, parent, index) Reparent node
duplicateNode(id) Deep clone
groupNodes(ids) Group into frame
ungroupNode(id) Dissolve group
select(ids) Set selection
undo() / redo() History navigation
batch(fn) Batch mutations
setViewport(z, x, y) Set viewport
hitTest(x, y) Point query
searchRect(x, y, w, h) Area query
importSVG(svg, parent?) Parse and insert SVG
dispose() Clean up resources

License

MIT