Wit

How to add a node or renderer

Extending Wit's vocabulary and output formats. For developers.

Three ways to get a new node

  • A definition — no code. Write #name(…) … name# and use it. The right answer for almost every new node.
  • The opaque node — @node(type: x, …) passes every parameter to the renderer, which dispatches on type. The seam for renderer-specific widgets.
  • A core name — code in the parser, runtime and every renderer. Only for shapes that belong to the language.

A definition can give the opaque node a friendly name, so writers type @chart(…) and the renderer receives the raw payload:

#chart(literal src, kind)
@node(type: chart, src: ::src::, kind: ::kind::) node@
chart#

@chart(src: data/q3.csv, kind: bar)
<div data-type="chart" data-src="data/q3.csv" data-kind="bar"></div>

Adding a core name

A core name is a commitment every renderer must keep. Test first:

  • Write the case in docs/spec.md or tests/spec/, and add it to tests/spec/pending.txt.
  • Add the name to CORE_VOCAB_NAMES, and any parameter it reads to CORE_PARAMS, in packages/parser/src/core-names.ts. The parser splits a core use's parens against CORE_PARAMS.
  • Add a handler to the HTML and Markdown renderers.
  • Add prose-safety cases to packages/render-html/src/prose-safety.test.ts — a new name must not turn ordinary sentences into structure (a known name glues to a word, and opens the parenthesis form).
  • Delete the pending line; refresh snapshots with WIT_SNAPSHOT_UPDATE=1 pnpm test and read git diff tests/rendered.

Writing a renderer

Walk the expanded AST and switch on each node's kind; handle a nodeUse by its name and params. The kinds are a closed, serialisable union — wit tour lists the 24 and shows which a document uses.

Wiring it in

The CLI and your own program run the same steps: scanCloseless, parse, loadExternalData if the document loads data, resolve, expand, then your renderer. See API.

Common mistakes

  • A core name without a spec case. Write the case first.
  • A parameter missing from CORE_PARAMS. Without it, the previous value does not stop at that key and swallows it.
  • Copying 0.2 snippets. Pipes, { … } calls and ... are gone; run wit migrate on old examples.

See also

Architecture · Custom renderer · Embed the parser · Design principles.