Adding a new pipeline node to megane

SkillDev tools

Register a new pipeline node type across every surface it must touch (types, catalog, executor, engine dispatch, React component, editor palette, and the optional JS/Python builders). Use whenever you add a node to the visual pipeline, or notice a node is only half-wired. Complements the `add-format` skill (formats vs. nodes) and the prose walkthrough at docs/docs/dev/custom-nodes.md.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Adding a new pipeline node to megane skill

What this skill tells your AI

The instructions your AI receives, as published by megane-labs/megane in .agents/skills/add-node/SKILL.md and read by ahel’s review.

A node type is not a single class — it is a set of coordinated declarations spread across ~8 files. Miss one and the node either fails to compile, never appears in the editor, or silently does nothing at runtime. This checklist is the agent-facing companion to the full walkthrough in docs/docs/dev/custom-nodes.md; read that page for copy-paste boilerplate and rationale.

Nodes are a TypeScript-frontend concept only — the Rust core has no notion of nodes. All of the work below is in src/pipeline/ and src/components/.

When to use this skill

  • Adding a new node type to the visual pipeline.
  • Reviewing a bug like "the new node compiles but never runs" or "it runs but isn't in the Add Node menu".
  • Extending the JS/Python builder API to construct an existing node type.

Compile-time guardrail

Surfaces 1–5 and 7 below are declared as Record<PipelineNodeType, …>, so tsc refuses to build until every table has an entry for the new type. Run npm run build:app early and often — the type errors are your to-do list. This is the project's substitute for a runtime node base class.

Registration checklist

Walk every item. The order roughly follows the data dependency.

1. Type + structural metadata — src/pipeline/types.ts

  • Add the string literal to the PipelineNodeType union.
  • Add a NODE_TYPE_LABELS entry (display name).
  • Add a NODE_CATEGORY entry (data_load | bond | filter | modify | overlay | viewport).
  • Add a NODE_PORTS entry — typed inputs / outputs ({ name, dataType, label }).
  • Define a XxxParams interface (with the type: "xxx" discriminant), add it to the PipelineNodeParams union, and add a case "xxx": to defaultParams().
  • Only if the node's input type is resolved from whatever is wired in (like filter/modify): add a GENERIC_NODE_ACCEPTS entry. Otherwise skip it.

2. Documentation metadata — src/pipeline/catalog.ts

  • Add a NODE_CATALOG entry: description, per-param params: ParamDoc[], promptInputs/promptOutputs, inPrompt, and pythonClass (or null). This feeds the AI system prompt and the autogenerated Node Reference — keep it runtime-dependency-free (types + pure data only).

3. Executor — src/pipeline/executors/<name>.ts

  • Export executeXxx(params, inputs: Map<string, PipelineData[]>) => Map<string, PipelineData>. Keep it a pure function: no Three.js, no DOM, no mutation of the inputs (spread new objects, copy typed arrays). Model it on executors/modify.ts / filter.ts.

4. Engine dispatch — src/pipeline/execute.ts

  • Import the executor and add a case "xxx": inside the switch (data.params.type) in executePipeline(). Set edgeOutputs.set(id, outputs) and add a "no input" warning like the neighboring cases.

5. React component — src/components/nodes/<Name>Node.tsx

  • Wrap the shared NodeShell (it draws the header, toggle, delete, error badge, and typed handles from NODE_PORTS). Render only the param body. Read/write params exclusively via usePipelineStore((s) => s.updateNodeParams) — never mutate data.params. Follow FilterNode.tsx.

6. Editor registration — src/components/PipelineEditor.tsx

  • Add the component to the nodeTypes map.
  • Add the node type to the correct ADD_NODE_GROUPS group so it shows up in the "Add Node" menu.

7. JS builder (optional) — src/pipeline/builder.ts

  • If the node should be constructable from the TypeScript Pipeline API, add a PipelineNode subclass: nodeType, _outPorts/_inpPorts (alias → port name), and _toSerializedParams().

8. Python builder (optional) — python/megane/pipeline.py

  • Mirror the JS subclass: a PipelineNode subclass with _node_type/_out_ports/_inp_ports and params stored on self, plus an isinstance(node, Xxx) branch in Pipeline._serialize_node() that copies the params into the node dict. The serialized type and param keys must match the TS side exactly or the JSON won't round-trip.

9. Tests (required — Codecov patch ≥ 70 %, CRITICAL RULE #8)

  • Executor unit test in tests/ts/pipeline/executors/<name>.test.ts.
  • defaultParams("xxx") shape test.
  • Component test in tests/ts/components/nodes/<Name>Node.test.tsx (asserts updateNodeParams fires).
  • If you added builder/Python subclasses: a serialize → deserialize round-trip test.
  • Reproduce the gate locally: npm test -- --coverage.

10. E2E (required for the UI change — CRITICAL RULE #9, local-only)

  • npm run build:wasm && npm run build:app, then run at least --project=pipeline-editor and the relevant modifier/feature project; sweep the neighborhood for side effects.
  • Re-baseline only genuinely-intended pixel changes (MEGANE_E2E_UPDATE=1), visually inspect the PNGs, and commit them under tests/e2e/baselines/<project>/. Full runbook: e2e-coverage skill.

11. Docs

  • The Node Reference is autogenerated from catalog.ts — no hand-editing. But if the node introduces a new concept, mention it in docs/docs/guide/pipeline/index.md.

Common omissions caught by this checklist

SymptomMissing step
tsc error "property 'xxx' is missing"§1 or §2 — a Record<PipelineNodeType, …> table has no entry
Node runs but isn't in the Add Node menu§6 — ADD_NODE_GROUPS
Node is in the menu but renders blank / throws§5 — component not in nodeTypes, or wrong nodeType prop
Node appears but does nothing at runtime§4 — no case in executePipeline()
Pipeline(...).to_json() omits the node's params§8 — no isinstance branch in _serialize_node()
Loaded JSON drops params / "Unknown node type"§1 port entry missing (drives VALID_NODE_TYPES) or §7/§8 key mismatch

Source-of-truth pointers

  • Structural metadata (types, ports, params, defaults): src/pipeline/types.ts
  • Prose + param docs (AI prompt + Node Reference): src/pipeline/catalog.ts
  • Execution: src/pipeline/executors/, src/pipeline/execute.ts
  • UI: src/components/nodes/, src/components/PipelineEditor.tsx
  • Programmatic builders: src/pipeline/builder.ts, python/megane/pipeline.py
  • Prose walkthrough: docs/docs/dev/custom-nodes.md

Signals

GitHub stars
22
Forks
2
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
add-node
Source
github.com/megane-labs/megane