Skip to content

TraekEngine

Core state management class that manages the conversation tree: nodes, parent-child relationships (a DAG — nodes can have multiple parents), spatial layout, and search.

import { TraekEngine } from 'traek'
const engine = new TraekEngine(config?: Partial<TraekEngineConfig>)

The config is merged with DEFAULT_TRACK_ENGINE_CONFIG (also exported). See Types for all TraekEngineConfig fields.

PropertyTypeDescription
nodesNode[]All nodes in the conversation tree (reactive $state)
activeNodeIdstring | nullID of the currently active node — new nodes attach to it by default
versionnumber (read-only)Monotonic counter bumped on every mutation; useful for auto-save
collapsedNodesSvelteSet<string>IDs of collapsed nodes (their descendants are hidden)
searchQuerystringCurrent search query
searchMatchesstring[]IDs of nodes matching the current search
currentSearchIndexnumberIndex of the current search match (0-based)
pendingFocusNodeIdstring | nullNode the canvas should center on next (set by focusOnNode)

addNode(content, role, options?): MessageNode

Section titled “addNode(content, role, options?): MessageNode”

Adds a message node. If options.parentIds is omitted, the node is attached to the active node (or becomes a root when there is none). The new node becomes active (unless type is 'thought').

addNode(
content: string,
role: 'user' | 'assistant' | 'system',
options?: {
type?: string // 'text' (default), 'thought', or a custom type
parentIds?: string[]
autofocus?: boolean // center the canvas on the new node
x?: number // explicit position in grid units
y?: number
data?: unknown
deferLayout?: boolean // skip layout; call flushLayoutFromRoot() after a batch
}
): MessageNode
const root = engine.addNode('Hello!', 'user')
const reply = engine.addNode('Hi there.', 'assistant', { parentIds: [root.id] })

addCustomNode(component, props?, role?, options?): CustomTraekNode

Section titled “addCustomNode(component, props?, role?, options?): CustomTraekNode”

Like addNode, but renders an arbitrary Svelte component instead of a message. Takes the same options as addNode.

addNodes(payloads: AddNodePayload[]): MessageNode[]

Section titled “addNodes(payloads: AddNodePayload[]): MessageNode[]”

Bulk add (e.g. loading a saved project) with a single layout pass. Payloads may include an id for round-tripping; parents may reference ids in the same batch or existing nodes. See Types for AddNodePayload.

updateNode(nodeId: string, updates: Partial<MessageNode>): void

Section titled “updateNode(nodeId: string, updates: Partial<MessageNode>): void”

Shallow-merges updates into the node. Used heavily during streaming:

engine.updateNode(node.id, { content: accumulated, status: 'streaming' })
engine.updateNode(node.id, { status: 'done' })

Deletes a single node. Surviving children are re-parented (the deleted id is stripped from their parentIds).

deleteNodeAndDescendants(nodeId: string): void

Section titled “deleteNodeAndDescendants(nodeId: string): void”

Deletes a node and its full subtree, then navigates to the deleted node’s first parent if the active node was removed.

Restores the most recently deleted node(s). The undo buffer expires after 30 seconds; returns false if there is nothing to restore.

duplicateNode(nodeId: string): Node | null

Section titled “duplicateNode(nodeId: string): Node | null”

Creates a sibling copy with the same parents, role, type, and content.

Sets activeNodeId to the given node, so the next message sent creates a new branch from it.

Returns the linear path from the root to the active node (following each node’s primary parent). Use this to build the message history for an LLM request:

const messages = engine
.contextPath()
.map((n) => ({ role: n.role, content: (n as MessageNode).content ?? '' }))

Asks the canvas to center the viewport on a node (sets pendingFocusNodeId).

Clears a pending focus request.

MethodReturnsDescription
getNode(id)Node | undefinedO(1) lookup by ID
getChildren(parentId)Node[]Children of a node (null for roots)
getParent(nodeId)Node | nullPrimary parent (first in parentIds)
getSiblings(nodeId)Node[]Children of the same primary parent (includes self)
getDescendants(nodeId)Node[]All descendants via BFS (excludes thought nodes)
getDescendantCount(nodeId)numberCount of visible descendants
getAncestorPath(nodeId)string[]All ancestor IDs across every parent link (includes self)
getDepth(nodeId)numberDepth along the primary-parent chain (root = 0, not found = -1)
getMaxDepth()numberMaximum depth across all nodes (-1 for an empty tree)
getActiveLeaf(nodeId)Node | undefinedFollows children downward to a leaf
getSiblingIndex(nodeId){ index, total }Position among siblings

Positions (metadata.x / metadata.y) are stored in grid units (config.gridStep pixels per unit).

setNodePosition(nodeId, xPx, yPx, snapThresholdPx?): void

Section titled “setNodePosition(nodeId, xPx, yPx, snapThresholdPx?): void”

Sets a node’s position from canvas pixel coordinates (e.g. during a drag). Marks the node as manually positioned and re-layouts its subtree. When snapThresholdPx is set, snaps to the grid within that distance.

moveNodeAndDescendants(nodeId, dx, dy): void

Section titled “moveNodeAndDescendants(nodeId, dx, dy): void”

Moves a node by a pixel delta and re-layouts its subtree.

Rounds a node’s position to integer grid coordinates (e.g. on drop).

layoutChildren(parentId): void / flushLayoutFromRoot(): void

Section titled “layoutChildren(parentId): void / flushLayoutFromRoot(): void”

Re-run automatic layout for one subtree, or from every root (use after batched addNode calls with deferLayout: true).

Adds an extra parent link. Returns false if it would create a cycle (checked with the exported wouldCreateCycle helper) or already exists.

removeConnection(parentId, childId): boolean

Section titled “removeConnection(parentId, childId): boolean”

Removes a parent link.

MethodDescription
toggleCollapse(nodeId)Collapse/expand a subtree
isCollapsed(nodeId)Whether a node is collapsed
isInCollapsedSubtree(nodeId)Whether a node is hidden by a collapsed ancestor
getHiddenDescendantCount(nodeId)How many descendants collapsing hides
MethodDescription
searchNodesMethod(query)Case-insensitive content search; updates searchMatches, auto-expands collapsed matches, focuses the first match
nextSearchMatch() / previousSearchMatch()Cycle through matches
clearSearch()Reset search state
MethodDescription
addTag(nodeId, tag)Add a tag (stored in metadata.tags)
removeTag(nodeId, tag)Remove a tag
getTags(nodeId)Get a node’s tags

serialize(title?: string): ConversationSnapshot

Section titled “serialize(title?: string): ConversationSnapshot”

Serializes the full engine state into a JSON-safe snapshot. Component references from addCustomNode are stripped — only node.type is stored.

TraekEngine.fromSnapshot(snapshot, config?): TraekEngine (static)

Section titled “TraekEngine.fromSnapshot(snapshot, config?): TraekEngine (static)”

Creates an engine from a snapshot. Input is validated with Zod (conversationSnapshotSchema) and throws on invalid data.

const snapshot = engine.serialize('My chat')
localStorage.setItem('chat', JSON.stringify(snapshot))
const restored = TraekEngine.fromSnapshot(JSON.parse(localStorage.getItem('chat')))