Instatic's Tree Mutation API: A Tree-Agnostic Architecture for Document Mutations
Instatic's tree mutation API provides pure, mutative-compatible functions in src/core/page-tree/mutations.ts that operate on any NodeTree<PageNode> structure, enabling identical manipulation of pages, visual components, and layouts without document-type branching.
Instatic represents every hierarchical document—from pages to visual components—as a NodeTree of PageNode objects. Because a Page itself is simply a NodeTree<PageNode> containing nodes and rootNodeId, the same mutation logic applies universally across all tree-like structures in the CoreBunch/Instatic repository. This design eliminates redundant code paths while ensuring consistent behavior between the visual editor and plugin ecosystems.
What Makes Instatic's Tree Mutation API Tree-Agnostic?
The tree-agnostic nature stems from generic type signatures and unified implementation patterns that avoid discriminating between document types.
Generic NodeTree Interface
Every mutation function receives a NodeTree<PageNode> as its first argument. Since the tree shape is defined solely by the generic NodeTree<TNode> interface, the API treats page trees, visual-component trees, and layout hierarchies identically. This generic approach means the same insertNode or moveNode call works without modification regardless of the specific document being edited.
Pure Mutative Design Pattern
The functions mutate the supplied draft directly while remaining safe for deep-cloned objects via structuredClone. This pure mutative compatibility allows the visual editor—via Zustand's Mutative middleware—and external plugins—via VM boundaries—to share the exact same implementation without side effects or state synchronization issues.
Absence of Type-Specific Branching
The implementation satisfies the no-vc-mode-branches-in-mutations rule by containing no kind === 'visualComponent' conditionals. The same set of eleven mutation functions handles all tree operations uniformly, whether manipulating a root page or a nested visual component.
Core Mutation Functions and Usage
Located in src/core/page-tree/mutations.ts, the API exports eleven primary mutation functions: createNode, insertNode, deleteNode, updateNodeProps, setBreakpointOverride, clearBreakpointOverride, renameNode, toggleNodeLocked, toggleNodeHidden, moveNode, duplicateNode, and wrapNode.
Creating and Inserting Nodes
import { createNode, insertNode } from '@core/page-tree';
const draft: NodeTree<PageNode> = {
rootNodeId: 'root',
nodes: {
root: {
id: 'root',
moduleId: 'root-module',
children: [],
parentId: null,
props: {},
breakpointOverrides: {},
classIds: []
},
},
};
const newNode = createNode('text-module', { text: 'Hello, world!' });
insertNode(draft, newNode, 'root');
Moving Nodes Within the Tree
import { moveNode } from '@core/page-tree';
// Moves 'node-id-to-move' to index 2 under 'new-parent-id'
moveNode(draft, 'node-id-to-move', 'new-parent-id', 2);
The Unified Operation Dispatcher
The applyTreeOperation function serves as a thin wrapper that maps discriminated-union TreeOperation types to the concrete mutation functions. Both the visual editor and plugin SDK call this dispatcher, ensuring a single source of truth for tree transformations.
import { applyTreeOperation } from '@core/page-tree';
import type { TreeOperation } from '@core/page-tree/operationSchema';
const op: TreeOperation = {
kind: 'renameNode',
nodeId: 'some-node',
name: 'New Label',
};
const result = applyTreeOperation(draft, op);
// result.affectedNodeIds lists IDs requiring cache invalidation
Supporting Infrastructure
Several modules collaborate to support the tree mutation API:
src/core/page-tree/selectors.ts: Provides traversal utilities includinggetParent,isAncestor, andcollectSubtreeIdsused by mutation functions to navigate the tree structure.src/core/page-tree/operationSchema.ts: Defines theTreeOperationdiscriminated union that theapplyTreeOperationdispatcher consumes.src/__tests__/page-tree/mutations.test.ts: Validates tree-agnostic behavior across all mutation scenarios.
Summary
- Instatic's tree mutation API operates on the generic
NodeTree<PageNode>interface insrc/core/page-tree/mutations.ts, treating pages, visual components, and layouts identically. - Pure mutative functions work with both Zustand's Mutative middleware and plugin VM boundaries without code duplication or side effects.
- Eleven core functions handle node lifecycle operations without document-type branching, adhering to the
no-vc-mode-branches-in-mutationsrule. applyTreeOperationprovides a unified dispatcher consumed by both the visual editor and plugin SDK, returning affected node IDs for cache invalidation.
Frequently Asked Questions
How does Instatic's tree mutation API handle different document types without branching logic?
The API treats all hierarchical structures as generic NodeTree<PageNode> instances. Because pages, visual components, and layouts share the same tree shape—containing nodes and rootNodeId—the mutation functions require no kind === 'visualComponent' checks or type-specific conditionals, satisfying the no-vc-mode-branches-in-mutations architectural rule.
What makes the mutation functions "pure" yet "mutative"?
Each function mutates the supplied draft object directly (mutative behavior), but because they perform no external side effects and work safely with structuredClone outputs, they remain pure from a functional programming perspective. This allows seamless integration with both immutable state libraries like Zustand and direct mutation scenarios across VM boundaries.
Can plugins use the same mutation API as the visual editor?
Yes. The applyTreeOperation dispatcher in src/core/page-tree/mutations.ts provides a shared entry point that both the visual editor and plugin SDK consume. This guarantees identical mutation semantics regardless of which environment initiates the tree operation, with the dispatcher returning affectedNodeIds for proper cache invalidation.
Where is the tree-agnostic behavior tested?
The test suite in src/__tests__/page-tree/mutations.test.ts validates that all eleven mutation functions behave correctly across different tree configurations, confirming the API's agnostic design through comprehensive unit tests against the NodeTree<PageNode> interface.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →