Contributing

July 22, 2026 ยท View on GitHub

This file is for contributor-facing notes. The top-level README should stay focused on installation, usage, and the language from a user's point of view.

For embedded-specific architecture notes, see embedded/readme.md.

JS core principles

The JavaScript side of Krill is organized as a pipeline:

  1. user input is parsed into a normalized model
  2. that model is turned into a rendering tree
  3. the rendering tree is rendered over time into pattern events
  4. playback code emits those events to a device

The important boundary is between the parsed model and the runtime tree. Syntax work belongs in the parser and evaluator. Musical behavior belongs in operators, patterns, and playback structures. Try not to blur those layers.

Main flow

The main server entry point is main.js. It creates the web server and forwards /command input to js/application.js.

js/application.js is the integration point for the JS runtime:

If you are changing behavior, find the layer that actually owns that behavior before editing. Avoid putting feature logic into js/application.js unless the change is truly about application wiring.

Parser and evaluator principles

The parser side is intentionally small:

When adding syntax, keep these rules in mind:

  • If the language accepts a new form, start in grammar.txt
  • Keep the output model explicit and stable so the renderer can consume it without parser-specific assumptions
  • Prefer extending existing node shapes when the concept already exists
  • Avoid pushing execution details into the parse model if they belong in operator behavior

The JS parser output is also the bridge toward the embedded implementation, so model changes should be made deliberately.

Rendering tree and operator principles

js/renderer/render-tree.js is the bridge between parsed data and runtime behavior. It recursively walks parsed nodes and dispatches them to render-node constructors.

The render-node framework lives in js/renderer/nodes/render-nodes.js. Render nodes follow a simple contract:

  • they receive arguments that can be rendered
  • tick() advances stateful children
  • render() returns a pattern-like result for the current cycle

Most feature work on the JS side should happen here:

Representative files:

Playback principles

Playback should stay generic.

If a feature changes musical meaning, it usually belongs in the parser or operator layer, not in the playback loop. Try not to add feature-specific branches to the engine or tree player unless the feature is fundamentally about scheduling.

Guidelines for new features

Use this rule of thumb:

Typical render-node work usually touches four places:

  1. grammar.txt if the syntax is new
  2. a new or updated file in js/renderer/nodes/
  3. render-node dispatch in js/renderer/render-tree.js
  4. tests in tests/

Tests

There is no useful npm test script at the moment. Use the test files directly.

Useful entry points:

For changes that affect user-visible musical behavior, prefer adding or updating an end-to-end case in tests/test-cases.json in addition to narrower unit coverage.

Where to look first

If a change spans multiple layers, the usual order is: syntax, model shape, operator or pattern behavior, playback implications, tests.