Forkshop has four user-facing primitives. Most of the API surface is either an instance of one of these or a function over them.

Node

A Node is a positioned thing on the canvas. It carries an id, a kind, an x/y/width/height, and an optional label. Three kinds ship in the engine:

  • inline-react — renders a React element directly in the host context. No iframe.
  • iframe-route — renders a route of your app inside an iframe. routePath: "/pricing" becomes <iframe src="/pricing">.
  • iframe-component — renders a single component preview inside an iframe via a generated preview route.

Nodes are plain data. Boards return them; the engine positions and renders them.

NodeType

A NodeType is a plugin object that tells the engine how to render a kind of Node. It satisfies this interface:

type NodeType<T extends AnyNode> = {
  id: string
  match: (node: AnyNode) => node is T
  render: (props: RenderProps<T>) => ReactNode
  agentMatch?: (node: T, activity: AgentActivitySnapshot) => AgentMatchResult
}

match is a TypeScript type guard that decides whether a NodeType handles a given Node. render returns the React tree for it. agentMatch is optional — it tells the engine whether agent activity on a file should highlight this Node.

There is no defineNodeType() factory. A NodeType is just an object satisfying the interface. The engine ships three built-ins (inlineReactNodeType, iframeRouteNodeType, iframeComponentNodeType) collected in BUILTIN_NODE_TYPES.

Layout

A Layout arranges Nodes on the canvas. The engine ships two:

  • Gallery — flat grid or stack. Auto-flows entries when they omit row/column; honors explicit coordinates when set.
  • Tree — hierarchical view derived from URL paths.

Boards reference a built-in by string id (layout: "gallery" or layout: "tree") inside defineBoard(). Custom Layouts are authored with defineLayout() and registered in forkshopConfig.layouts; a Board then references the Layout object directly (layout: chartsLayout).

Layouts decide where Nodes sit. NodeTypes decide what each tile renders. The two are independent.

Board

A Board is one entry in your sidebar. Create one with defineBoard() — a typed config that becomes a React component the engine mounts inside <BoardRegistry>. The engine owns canvas, sidebar, selection routing, and positions wiring.

import { defineBoard, forkshopIcons } from "@forkshop/engine"

export default defineBoard({
  id: "charts",
  label: "Charts",
  icon: forkshopIcons.flows,
  match: (s) => s.kind === "section" && s.sectionId === "charts",
  layout: "gallery",
  useEntries: () => [/* … */],
})

Boards live in app/forkshop/ in your project. Drop them into the boards prop on <BoardRegistry>; no switch statement, no canvas wiring. See Boards for the full anatomy.