Every Node, Documented

I just added structured documentation to every single node in Lux. 110 of them. This isn’t a docs site update or a separate markdown file — the documentation lives inside the nodes themselves and flows through the entire system.

The NodeDoc struct

A new type in lux-core gives every node four documentation fields:

pub struct NodeDoc {
    pub summary: String,      // one-liner for search results and hover
    pub description: String,  // full explanation for the inspector
    pub see_also: Vec<String>, // related node names
    pub tags: Vec<String>,    // search synonyms ("plus", "sum", "+")
}

And four builder methods on NodeInfo to populate them:

NodeInfo::new("Add", "Math/Arithmetic")
    .input("a", PinType::Any, PinValue::Number(0.0))
    .input("b", PinType::Any, PinValue::Number(0.0))
    .output("result", PinType::Any)
    .summary("Add two values together")
    .description("Polymorphic addition: works with Number, Int, Vec2, Vec3, Vec4. Scalars broadcast to match vector dimensions.")
    .tags(&["plus", "sum", "+"])
    .see_also(&["Subtract", "Multiply"])

Same builder pattern as pins. Plugin authors add four lines and they’re done.

Pin-level documentation

Node-level docs tell you what a node does. But when you’re staring at a pin called damping with a default of 10.0, you need to know: what are the units? What does “higher” mean? What happens at zero?

Two new builder methods — .input_doc() and .output_doc() — attach descriptions to individual pins:

NodeInfo::new("Spring", "Animation")
    .input("target", PinType::Number, PinValue::Number(0.0))
        .input_doc("Value to spring toward")
    .input("stiffness", PinType::Number, PinValue::Number(100.0))
        .input_doc("Spring force (higher=snappier)")
    .input("damping", PinType::Number, PinValue::Number(10.0))
        .input_doc("Friction (higher=less bounce)")
    .output("out", PinType::Number)
        .output_doc("Current position")
    .output("velocity", PinType::Number)
        .output_doc("Current velocity")

Each method sets the description on the most recently added pin, so the chaining reads naturally — the doc line sits right below the pin it describes.

203 pins across 72 nodes are now documented. The priority was pins where the meaning isn’t obvious from the name alone: timing parameters in seconds, angles in radians, normalized 0–1 ranges, seed values for deterministic randomness, and creative controls like stiffness, duty cycle, and easing curves.

Where it all shows up

Inspector panel — select a node and you see its summary and description below the pin controls. No hunting through external docs.

Pin tooltips — hover over a pin and the tooltip now shows its description below the type label. Instead of just damping: Number, you see damping: Number with “Friction (higher=less bounce)” underneath.

Search — the tags feed directly into the fuzzy search system. Typing “bounce” finds Spring. Typing “+” finds Add. Typing “disc” finds Circle. The search already worked on names, but tags let users search by concept.

The --dump-docs export

Run lux --dump-docs and you get every node dumped as JSON to stdout:

{
  "type_name": "circle_node",
  "name": "Circle",
  "category": "Shape/Primitives",
  "summary": "Draw a filled or stroked circle",
  "description": "Produces a circle layer with configurable fill and stroke...",
  "tags": ["ellipse", "disc", "round", "shape"],
  "see_also": ["Rect", "Line", "Path"],
  "inputs": [
    {"name": "center", "pin_type": "Vec2", "description": "Center position"},
    {"name": "radius", "pin_type": "Number", "description": "Circle radius in pixels"},
    ...
  ],
  "outputs": [
    {"name": "layer", "pin_type": "Layer", "description": ""}
  ]
}

Sorted by category, then name. This is the foundation for a static documentation site — pipe it into a Hugo data template or a search index and you get a complete, always-up-to-date node reference generated straight from the source.

Keeping it honest

Two tests enforce the documentation contract:

  • all_nodes_have_summary — every registered node must have a non-empty summary. No undocumented nodes slip through.
  • see_also_references_resolve — every “see also” reference must point to an actual node name in the registry. No dead links.

These run in CI. If someone adds a new node without a summary, or renames a node without updating cross-references, the build breaks.

What this enables

The goal has always been that Lux should be learnable without leaving the app. With 110+ nodes and growing, discoverability is the bottleneck — not capability. Structured docs solve this at the source:

  • In-app help that’s always in sync with the code
  • Smarter search that understands synonyms and concepts
  • Static site generation from a single JSON export
  • See also navigation so users discover related nodes naturally

110 nodes. 203 pins. Documented where they’re defined, surfaced where they’re needed.

← Back to blog