The 8 cell kinds

Every Quilt sheet is built from eight primitives. Each is a small, focused idea. Together, they're a complete reactive system.

πŸ“¦

value

A static piece of data. The atom of the spreadsheet.
Ζ’

formula

A reactive expression. Recomputes when inputs change.
β–Ά

program

An async expression. Side effects, awaits, runtime state.
πŸ‘

sensor

A polled input source. Reads the world on a schedule.
🌐

api

An outbound call. Fetch, POST, GraphQL β€” same cell shape.
πŸ””

listener

Fires on change. Notifications, logs, side effects.
β†ͺ

router

Context-aware dispatch. Different output for different callers.
πŸ”Œ

io

A physical port. LED, relay, motor, sensor pin.

value

A static, named piece of data. The simplest cell. The atom of the spreadsheet.

πŸ“¦

value kind: value

The reactive primitive of constants. Mutable from outside, but doesn't recompute from inputs.

Mental model

A value cell is a named box that holds a piece of data. It's reactive in that other cells can listen to it, but it doesn't have inputs of its own. It's the leaf of the cell graph.

Syntax

- id: temperature
  kind: value
  value: 22.5

- id: user.name
  kind: value
  value: "Alice"

- id: enabled
  kind: value
  value: true

- id: tags
  kind: value
  value: ["urgent", "frontend", "ux"]

When the value changes

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” value β”‚ was 22.5 β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ change β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ formula β”‚ recomputes β”‚ depends β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ formula β”‚ recomputes β”‚ depends β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Changing a value cell cascades through every cell that depends on it. The order of writes doesn't matter; the engine handles the topology.

Use cases

Configuration
A threshold, a setting, a label.
User input
A form field, a slider value, a search box.
Constants
An API key, a URL, a color code.
State
A counter, a toggle, a current mode.

What it is NOT

A value cell is not a constant. It can be changed from outside (by user input, by an external system, by a listener). The difference from a formula is that it has no expr β€” it doesn't recompute; it's a leaf.

formula

A reactive expression. The core of the reactive system.

Ζ’

formula kind: formula

A pure expression that recomputes whenever any of its inputs change. Sync, deterministic, no side effects.

Mental model

A formula cell is a pure function from its dependencies to its value. When any dependency changes, the formula recomputes. It's the workhorse of the reactive system.

Syntax

- id: fahrenheit
  kind: formula
  expr: "sensor.temp * 1.8 + 32"

- id: status
  kind: formula
  expr: 'heat_index > 30 ? "hot" : "ok"'

- id: average
  kind: formula
  expr: "(a + b + c) / 3"

How it works

formula cell β”‚ β”‚ has expr: "a + b * 2" β”‚ β”œβ”€ looks for: a, b β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” β”‚ a β”‚ β”‚ b β”‚ ← other cells β”‚ (any β”‚ β”‚ (any β”‚ β”‚ kind)β”‚ β”‚ kind)β”‚ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ when a or b changes β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ eval expr with β”‚ β”‚ a, b in scope β”‚ β”‚ β†’ 5 + 7 * 2 = 19 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What you can use

The expression is a small JavaScript-like language. Numbers, strings, booleans, arrays, objects. Arithmetic, comparison, ternary, function calls (Math, JSON, Array.prototype). No statements β€” just an expression.

// All of these are valid:
expr: "a + b"
expr: 'status === "ok"'
expr: "Math.sqrt(x * x + y * y)"
expr: "items.filter(i => i.active).length"
expr: "JSON.stringify(data)"

Use cases

Unit conversion
Celsius to Fahrenheit, meters to feet, etc.
Aggregations
Sum, average, max, min over a list.
Conditional logic
Status, category, threshold checks.
Computed views
A derived value for display or export.

Limitations

No async. No side effects. No `await`, no `setTimeout`, no `fetch`. For those, use program. A formula must be a pure expression β€” its output is determined entirely by its inputs.

program

An async expression. The escape hatch from pure formulas.

β–Ά

program kind: program

A small async body that can await, call other cells, and produce a value. Use when formulas aren't enough.

Mental model

A program cell is a function that can do work. It can call other cells, await async operations, throw errors, and return a value. It's the bridge between the synchronous, pure reactive core and the messy outside world.

Syntax

- id: summarize
  kind: program
  code: |
    const items = runtime.get('raw_data').data;
    const filtered = items.filter(i => i.score > 0.8);
    const summary = await runtime.call('api.llm', {
      prompt: 'Summarize: ' + JSON.stringify(filtered)
    });
    runtime.set('summary_text', summary);
    return summary;

The runtime API

Inside a program cell, you have access to a runtime object:

runtime.get(id)            // β†’ { data, status, error, computedAt }
runtime.set(id, value)      // set another cell
runtime.call(id, args)      // β†’ await a program or api cell
runtime.cells               // β†’ a Proxy of all cell values
runtime.log(...args)        // β†’ log to the engine console

Use cases

Multi-step pipelines
Fetch β†’ parse β†’ transform β†’ store.
Composed async
Call multiple cells in sequence, combine results.
State machines
Update state based on complex conditions.
Custom logic
Anything formulas can't express.

When to use a program vs a formula

Use a formula when the computation is pure and synchronous. Use a program when you need await, side effects, or want to set other cells. Programs are the escape hatch β€” use them when needed, not by default.

sensor

A polled input source. The cell that reads the world.

πŸ‘

sensor kind: sensor

A cell that holds the latest reading from an external source. Polled, scheduled, or pushed.

Mental model

A sensor cell is a window into the world. It's the cell that says "the temperature is X right now" or "the user is on page Y" or "the timer has fired N times". The engine doesn't know how the value is updated β€” it just holds the latest reading.

Syntax

- id: sensor.temp
  kind: sensor
  source: dht22
  default: 22
  poll_ms: 1000

- id: sensor.motion
  kind: sensor
  source: pir
  default: false

- id: sensor.clock
  kind: sensor
  source: timer
  interval_ms: 60000

How it works

The engine doesn't poll directly. Instead, adapters push values into the sensor cell. The cell then re-evaluates, and any formula that depends on it recomputes.

physical world β”‚ β”‚ temperature changes β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ adapter β”‚ (driver code, OS call, network) β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ runtime.set('sensor.temp', 23.5) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ sensor β”‚ value updates β”‚ .temp β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ dependents recompute β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ formula β”‚ β”‚ formula β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Sources

A sensor's source is an identifier for an adapter. Each runtime (TypeScript, Rust, Live) defines its own set of sources. Common ones:

source: dht22          // temperature/humidity sensor (ESP32)
source: pir            // motion sensor
source: timer          // clock-based
source: ble            // Bluetooth Low Energy
source: mqtt           // MQTT subscription
source: websocket      // WebSocket
source: interval       // client-side timer
source: file           // file watcher

Use cases

IoT
Temperature, humidity, motion, light, soil moisture.
Realtime data
Stock prices, weather, location, server metrics.
User input
Mouse position, scroll, keystrokes (in Live).
Polling
An external API you want to call on a schedule.

api

An outbound call. The cell that talks to the world.

🌐

api kind: api

A cell that calls an external service. Lazy, cached, addressable. Like a function over the network.

Mental model

An api cell is a named, addressable network call. You reference it by id; the engine makes the HTTP request; the result is cached. The cell is the function; the network is the implementation.

Syntax

- id: api.users
  kind: api
  endpoint: "https://api.example.com/users"
  method: GET

- id: api.weather
  kind: api
  endpoint: "https://api.weather.com/v1/current"
  method: GET
  params:
    lat: 37.7749
    lon: -122.4194
  cache_ttl: 300   # 5 minutes

How it works

API cells are lazy. They don't fetch until something reads them. When read, the engine calls the endpoint, caches the result (per the cache_ttl), and returns the value. Subsequent reads (within the TTL) return the cached value without re-fetching.

formula depends on api.users β”‚ β”‚ runtime.get('api.users') β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ api β”‚ β”‚ .users β”‚ cached? ──yes──▢ return cached β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ no β–Ό GET https://api.example.com/users β”‚ β”‚ response β–Ό cache + return

Per-context memoization

The same api cell called from different formulas can have different cached values, depending on the caller's context. The engine tracks the caller and respects the cache accordingly.

Use cases

External data
REST APIs, GraphQL, gRPC-web.
LLM calls
OpenAI, Anthropic, local models.
Microservices
Internal services, RPCs.
Webhooks
Send events to external systems.

listener

Fires on change. The cell that reacts to the world.

πŸ””

listener kind: listener

A cell that watches another cell and fires an action when a condition is met. The "if X then Y" primitive.

Mental model

A listener cell watches a target cell. When the target changes, the listener evaluates a condition. If the condition is true, it fires an action. The action is a side effect β€” log, alert, HTTP call, anything.

Syntax

- id: alert.hot
  kind: listener
  watch: sensor.temp
  condition: "sensor.temp > 30"
  action: "console.log('⚠️ Hot!')"

- id: audit
  kind: listener
  watch: critical.value
  condition: "true"
  action: |
    runtime.set('audit_log', [...runtime.get('audit_log').data, {
      who: 'system',
      what: 'critical.value',
      from: oldValue,
      to: runtime.get('critical.value').data,
      when: Date.now()
    }])

How it works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ target β”‚ (e.g. sensor.temp) β”‚ cell β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ value changes β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ listener β”‚ evaluate condition β”‚ .hot β”‚ β†’ "sensor.temp > 30" β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ condition true? β–Ό yes fire action β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ console.log(...) β”‚ β”‚ runtime.set(...) β”‚ β”‚ runtime.call(...)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Use cases

Alerts
Threshold breaches, error conditions.
Logging
Every change, every value, for audit.
Side effects
Write to disk, send a notification, call an API.
Triggers
Start a workflow, kick off a backup.

Listeners vs formulas

A formula computes a value. A listener fires a side effect. The distinction: formulas are pure; listeners are not. Use formulas for values, listeners for actions.

router

Context-aware dispatch. The cell that decides.

β†ͺ

router kind: router

A cell that returns different values based on who is asking. Multi-tenant, role-based, A/B tests.

Mental model

A router cell is a single cell with multiple possible outputs. The output depends on the caller. The same cell id, different values for different callers.

Syntax

- id: data.public
  kind: router
  routes:
    - when: "caller.role === 'admin'"
      expr: "all_data"
    - when: "caller.role === 'user'"
      expr: "filtered_data"
    - when: "caller.role === 'guest'"
      expr: "public_data"
  default: "public_data"

How it works

When something reads the router cell, it provides a caller context. The router evaluates each route's when clause; the first match wins. The result is the cell's value for that caller.

caller A (admin) ──▢ router ──▢ all_data caller B (user) ──▢ router ──▢ filtered_data caller C (guest) ──▢ router ──▢ public_data (same cell, different output per caller)

Per-context memoization

Each caller gets their own cached value. The same router cell, called from two contexts, evaluates twice and caches twice. This is what makes routers work without leaking data between callers.

Use cases

Authorization
Different data for different roles.
A/B testing
Different logic for different cohorts.
Multi-tenant
Tenant A sees A's data, tenant B sees B's.
Feature flags
Show feature X to some users, Y to others.

io

A physical port. The cell that drives the world.

πŸ”Œ

io kind: io

A cell that drives a physical actuator. LED, relay, motor, pin. The output of the cyber-physical loop.

Mental model

An io cell is the end of the cell graph, in the physical world. It writes to a port β€” GPIO pin, serial, PWM channel, motor controller. The cell is a name for a physical output.

Syntax

- id: led.green
  kind: io
  port: "gpio2"
  direction: out

- id: motor.fan
  kind: io
  port: "pwm0"
  direction: out

- id: relay.heater
  kind: io
  port: "relay1"
  direction: out
  invert: false

How it works

When a formula writes to an io cell, the value is dispatched to the port driver. On an ESP32, that means setting a GPIO pin or a PWM duty cycle. On a server, it could mean calling a hardware API (a Modbus device, a serial port, a DAQ).

formula depends on io.led β”‚ β”‚ runtime.set('led.green', true) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ io β”‚ dispatch to port β”‚ .green β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ GPIO write β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ LED on β”‚ (or motor, relay, etc.) β”‚ pin 2 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Use cases

Home automation
Lights, locks, thermostats, fans.
Robotics
Motors, servos, valves.
Industrial
Relays, contactors, stepper drivers.
Wearables
Haptic motors, LEDs, buzzers.

How they compose

The 8 cell kinds are a complete reactive system. Any non-trivial sheet is a graph of them.

sensor ──▢ formula ──▢ io β”‚ β”‚ β”‚ β–Ό β”‚ listener β”‚ └──▢ api ──▢ program ──▢ formula β”‚ β–Ό router β”‚ β–Ό (per-caller output)

A real system is rarely one kind. A weather station is sensor cells (DHT22, motion, light), formula cells (heat index, comfort, threshold), listener cells (alert on heat), and io cells (drive the fan, light the LED). The 8 kinds compose into anything.