NodeDefinition
A NodeDefinition is the declarative description of one built-in graph node type. Prompt, Delay, Merge, Session, and so on. The catalog lives in threadle’s shared package (packages/shared/src/nodes/) and is the single place that owns identity, connectivity, port limits, and menu metadata for first-party types.
Custom nodes (folders under ~/.config/threadle/nodes/) are not NodeDefinitions. They all reuse the one built-in type custom. See Custom nodes.
Why it exists
Section titled “Why it exists”Before the registry, adding a built-in meant editing about six parallel lists by hand: connection tables, port limits, palette rows, wire-menu rows, mute allowlist, “can run” switches. Those lists drifted.
Now you register once. Menus, wiring product, ports, mute, and run eligibility are derived from the definition. Vue cards and runner case arms stay explicit. They need real UI and I/O code. But the checklist is short. See Adding a built-in node.
Where it lives
Section titled “Where it lives”| File | Role |
|---|---|
packages/shared/src/nodes/definition.ts | NodeDefinition interface + PORT_UNLIMITED |
packages/shared/src/nodes/builtins.ts | One entry per built-in type |
packages/shared/src/nodes/registry.ts | Lookups + derived sets/menus |
Import helpers from @threadle/shared:
import { getNodeDefinition, allNodeDefinitions, paletteBlockEntries, wireMenuBlocks, isValidConnection, REQUIRES_INPUT, MUTABLE_NODE_TYPES,} from "@threadle/shared";What a definition declares
Section titled “What a definition declares”| Field | Meaning |
|---|---|
id | NodeType string ("prompt", "knot", …) |
label / glyph | Canonical name and tile glyph |
wireColor | CSS color, or "provider" for session/agent colors |
textSource / textSink | Participate in the text→text connection product |
structuralTo | Extra edges outside that product (e.g. Session → context) |
forbidTextTo | Block pairs that would otherwise be allowed |
ports | Default handle capacity (in / out. Use PORT_UNLIMITED) |
requiresInput | Preflight: must have an inbound wire |
emitsText | Soft lint for dangling outbound text |
mutable | Mute / bypass allowed |
executesOnRun | Counts toward ▶ Run (true or "session-if-wired") |
skipExec / skipInDetached | Never run / also skip in the server executor |
palette | Nodes-tab row, or false if listed elsewhere |
wireMenu | Wire-drop / empty-canvas entry (or an array of flavors) |
Example (simplified Delay):
{ id: "delay", label: "Delay", glyph: "◷", wireColor: "var(--wire-delay)", textSource: true, textSink: true, ports: { in: 1, out: 1 }, requiresInput: true, emitsText: true, executesOnRun: true, mutable: true, palette: { /* label, glyph, hint, iconBg, order */ }, wireMenu: { /* same shape */ },}What is derived
Section titled “What is derived”From the catalog, threadle builds:
- Valid connections. Text sources × text sinks, plus
structuralTo, minusforbidTextTo - Port limits. Default max wires per handle
- Palette blocks. Nodes tab “blocks” section
- Wire / add menu. Including context flavors (three
contextentries) - Mute set. Context-menu mute/bypass
- Run eligibility. Which nodes make ▶ Run available
- Skip sets. Canvas and detached executors
You should not hand-edit those lists for a new built-in. Change the definition instead.
What stays outside the catalog
Section titled “What stays outside the catalog”Still written by hand when you add a type:
NodeType+ data shape + zod ingraph.tsbuildNodedefaults in the editor- Vue Flow card (
#node-…slot) - Canvas runner + detached executor
cases - Optional CSS
--wire-…token
That split is intentional: the registry owns policy and discovery. The editor/server own pixels and side effects.
Built-in vs custom
Section titled “Built-in vs custom”NodeDefinition (built-in) | Custom node | |
|---|---|---|
| Who ships it | threadle release | You (or an import) |
| Storage | Repo catalog | ~/.config/threadle/nodes/<id>/ |
| Graph type | Its own NodeType | Always type: "custom" |
| When to use | First-party vocabulary | Your transforms and tools |
If you only need stdin→stdout text, stop at Custom nodes
or the stdlib pack. Reach for NodeDefinition only when the type
must be part of threadle itself.
Related
Section titled “Related”- Adding a built-in node. Contributor checklist
- Custom nodes. User-authored nodes
- Stdlib pack. Curated utilities instead of new builtins
- Tutorial: Extract URLs. First custom node walkthrough
- Graph & wires. How nodes sit on the canvas