Quilt Patterns

A visual cookbook of the 20 idioms that solve 80% of problems. Every pattern is a small cell graph that you can fork, customize, and ship. Each diagram is a real Quilt sheet.

Foundations

The five patterns that every Quilt sheet is built from. Master these and the rest is composition.

01. Hello, cell

The smallest possible sheet: a value cell.

     ┌─────────┐
     │  📦     │
     │  "hi"   │
     └─────────┘
   value cell
- id: greeting kind: value value: "hi"

Use when you just need a named value. A constant, a setting, a label. The atom of the spreadsheet model.

02. Reactive cascade

One value flows through a chain of formulas. Change the input, every dependent updates.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  ƒ   │────────▶│  ƒ   │
   │ raw  │         │ norm │         │ final│
   └──────┘         └──────┘         └──────┘
   value          formula         formula

   wire: raw → norm → final
   change raw, both formulas recompute
- id: raw kind: value value: 100 - id: norm kind: formula expr: "(raw - 0) / 100" - id: final kind: formula expr: "norm * 10"

Use when you have a value that needs transformation. The reactive engine handles the propagation order. You just write the math.

03. Sensor & actuator

Read the world, decide, act on the world. The cyber-physical loop.

   ┌──────────┐         ┌──────┐         ┌──────┐
   │   👁     │────────▶│  ƒ   │────────▶│  🔌  │
   │ sensor   │         │ logic│         │ motor│
   │ .temp    │         │      │         │      │
   └──────────┘         └──────┘         └──────┘
   sensor          formula       io

   read temp → decide → drive motor
- id: sensor.temp kind: sensor source: dht22 default: 22 - id: logic kind: formula expr: 'sensor.temp > 25 ? 100 : 0' - id: motor.fan kind: io port: "gpio2" direction: out

Use when you're reading from the physical world (sensors, APIs, user input) and driving something back (LEDs, motors, HTTP). The fundamental input → process → output loop.

04. Listener & alert

React to changes by firing side effects. The "if X then Y" pattern.

   ┌──────┐         ┌──────┐
   │  ƒ   │────────▶│  🔔  │
   │ temp │         │alert │
   └──────┘         └──────┘
   formula     listener

   when temp changes, fire if condition
- id: temp kind: value value: 22 - id: alert kind: listener watch: temp condition: "temp > 30" action: "console.log('⚠️ hot!')"

Use when you need to act on a change, not just see a value. Notifications, logging, audit, external side effects.

05. API call

Call out to the world. An outbound HTTP call as a cell.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  🌐  │────────▶│  ƒ   │
   │  url │         │  api  │         │ parse│
   │      │         │       │         │      │
   └──────┘         └──────┘         └──────┘
   value          api          formula

   build url → fetch → parse response
- id: url kind: value value: "https://api.example.com/users" - id: response kind: api endpoint: "https://api.example.com/users" method: GET - id: user_count kind: formula expr: "response.length"

Use when the data you need isn't in your sheet. APIs, webhooks, microservices. The cell model is uniform, so the engine doesn't care if the data is local or remote.

State

How to manage state in a reactive system. The patterns that turn a sheet into a finite-state machine.

06. State machine

A cell whose value is constrained to a finite set of named states.

        ┌────────┐
   ┌───▶│ idle   │◀────┐
   │    └────────┘     │
   │         │         │
   │    start│    stop │
   │         ▼         │
   │    ┌────────┐     │
   │    │ running│─────┘
   │    └────────┘
   │
   current_state: formula (one of: idle, running)
   transitions:   program (validate + apply)
- id: current_state kind: value value: "idle" enum: [idle, running, paused, error] - id: transition kind: program code: | const valid = { idle: ['running'], running: ['paused', 'idle'], paused: ['running'] }; if (valid[runtime.get('current_state').data]?.includes(runtime.get('next_state').data)) { return runtime.set('current_state', runtime.get('next_state').data); } return runtime.get('current_state').data; - id: next_state kind: value value: "running"

Use when you have a system with discrete states (UI modes, workflow stages, protocol states). A cell as a finite-state machine is cleaner than nested if-else.

07. Counter

A cell that increments. The simplest possible stateful reactive system.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  ▶   │────────▶│  📦  │
   │  0   │         │ incr │         │  N   │
   └──────┘         └──────┘         └──────┘

   on increment: count = count + 1
- id: count kind: value value: 0 - id: increment kind: listener watch: count condition: "false" # external trigger action: "runtime.set('count', runtime.get('count').data + 1)"

Use when you need to count events, clicks, requests, anything. The pattern is universal — it's the "how many?" question, encoded in cells.

08. Toggle / latch

A boolean that flips on each trigger. The "is it on?" question.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  ƒ   │────────▶│  📦  │
   │ false│         │ NOT  │         │ true │
   └──────┘         └──────┘         └──────┘

   on trigger: state = !state
- id: state kind: value value: false - id: toggle kind: listener watch: state condition: "false" action: "runtime.set('state', !runtime.get('state').data)"

Use when you have a binary state (on/off, enabled/disabled, show/hide). A toggle is the latch — flip it, it stays.

09. Debounce

Wait until a stream of events stops, then fire. The "user is done typing" pattern.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  ▶   │────────▶│  📦  │
   │ input│         │ 250ms│         │ final│
   └──────┘         │ wait │         └──────┘
                    └──────┘
                   resets on each input
- id: input kind: value value: "" - id: timer kind: program code: | let handle; return (value) => { clearTimeout(handle); handle = setTimeout(() => runtime.set('final', value), 250); }; - id: final kind: value value: ""

Use when you have a fast stream of events (typing, scrolling, sensor readings) but only want to act on the "settled" value. The classic search-as-you-type case.

10. Throttle

Fire at most once per time window. The "don't spam me" pattern.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  ▶   │────────▶│  📦  │
   │input │         │ 1Hz  │         │ last │
   │      │         │ limit│         │      │
   └──────┘         └──────┘         └──────┘

   only emit at most every 1s
- id: input kind: value value: 0 - id: last_emit kind: value value: 0 - id: throttled kind: program code: | const now = Date.now(); if (now - runtime.get('last_emit').data > 1000) { runtime.set('last_emit', now); return runtime.get('input').data; } return runtime.get('last_emit').data;

Use when you have a fast event source but want to limit the rate of actions. Network requests, UI updates, hardware writes. Don't over-drive your actuators.

Data

Patterns for transforming, filtering, and remembering data. The data engineering layer.

11. Transform pipeline

A series of cells that each apply one transformation. Unix pipes as cells.

   raw ──▶ parse ──▶ clean ──▶ enrich ──▶ save
   📦        ƒ         ƒ         ƒ        📦

   each step is a cell
   output of one is input of the next
- id: raw kind: value value: " Alice, 30 " - id: parsed kind: formula expr: "raw.trim().split(',')" - id: cleaned kind: formula expr: "[parsed[0].trim(), parseInt(parsed[1].trim())]" - id: enriched kind: formula expr: "{name: cleaned[0], age: cleaned[1], valid: cleaned[1] >= 18}" - id: save kind: api endpoint: "https://api.example.com/users" method: POST

Use when you have data that needs many transformations. Each step is a cell — you can inspect, swap, and reuse them independently.

12. Filter & map

Apply a function to every element of a list. The "transform all the things" pattern.

   list ──▶ map ──▶ filtered ──▶ sum
   📦       ƒ        ƒ          ƒ

   map: x → f(x)
   filter: keep only where predicate(x)
- id: numbers kind: value value: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - id: doubled kind: formula expr: "numbers.map(n => n * 2)" - id: evens kind: formula expr: "doubled.filter(n => n % 2 === 0)" - id: sum kind: formula expr: "evens.reduce((a, b) => a + b, 0)"

Use when you have a list and want to derive a new list. Map, filter, reduce are universal primitives — they compose as cells.

13. Reducer

Fold a stream of values into a single accumulated value. The "running total" pattern.

   ┌──────┐         ┌──────┐
   │ events│────┐    │      │
   └──────┘    │    │      │
   ┌──────┐    ├───▶│  ƒ   │──▶ total
   │ events│────┤    │ fold │
   └──────┘    │    │      │
   ┌──────┐    │    │      │
   │ events│────┘    │      │
   └──────┘         └──────┘

   each event: total = (total, event) → total'
- id: events kind: value value: [10, 20, 30, 40] - id: total kind: formula expr: "events.reduce((acc, e) => acc + e.value, 0)" - id: count kind: formula expr: "events.length" - id: average kind: formula expr: "count > 0 ? total / count : 0"

Use when you have a stream and want a single derived value. Running totals, averages, max, min — all reducers.

14. Cache

Remember a computed value so you don't recompute it. The "memoize" pattern.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  💾  │────────▶│  ƒ   │
   │ input│         │ cache│         │expensive
   └──────┘         └──────┘         └──────┘

   on input change, recompute & store
   on no change, return cached
- id: key kind: value value: "user:42" - id: cached kind: value value: null - id: expensive kind: program code: | const cacheKey = runtime.get('key').data; const cached = runtime.get('cached').data; if (cached && cached.key === cacheKey) { return cached.value; } const value = await expensive_operation(cacheKey); runtime.set('cached', { key: cacheKey, value }); return value;

Use when computation is expensive, or you don't want to re-hit an API. The cache cell is the layer. You can swap it (LRU, TTL, distributed) without touching the rest.

15. Time series

A cell whose value is a list of timestamped values. The "I have data over time" pattern.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────add──│  📦  │──window─▶│  ƒ   │
   │ new  │         │series│         │ avg  │
   └──────┘         └──────┘         └──────┘

   on new value: append to series
   on change: compute windowed stats
- id: series kind: value value: [] - id: append kind: listener watch: input condition: "true" action: | runtime.set('series', [...runtime.get('series').data, { t: Date.now(), v: runtime.get('input').data }]) - id: window_60s kind: formula expr: "series.filter(p => Date.now() - p.t < 60000)" - id: avg_60s kind: formula expr: "window_60s.reduce((a, p) => a + p.v, 0) / Math.max(1, window_60s.length)"

Use when you care about how something changes over time. Heart rate, temperature, latency, sales. The series is the model.

Composition

Patterns for combining cells into larger systems. The architectural layer.

16. Router

A cell that returns different values based on who is asking. The "context-aware" pattern.

   caller A ──▶ router ──▶ result_A
   caller B ──▶ router ──▶ result_B
                 (same cell, different output)

   based on caller.role, return different value
- id: router kind: router routes: - when: "caller.role === 'admin'" expr: "all_data" - when: "caller.role === 'user'" expr: "filtered_data" - when: "caller.role === 'guest'" expr: "public_data"

Use when the same logical cell should return different things to different callers. Authorization, A/B tests, multi-tenant, feature flags. The cell is the policy.

17. Sub-sheet

A cell that is itself a sheet. The "composable unit" pattern.

   ┌─────────────────┐
   │   parent sheet  │
   │                 │
   │  ┌────────────┐ │
   │  │ sub-sheet  │ │  (a cell, itself a sheet)
   │  │            │ │
   │  │  cells:{}  │ │
   │  │            │ │
   │  └────────────┘ │
   │                 │
   └─────────────────┘

   sub-sheet has its own scope, but
   exposes named outputs to parent
# In parent.yaml - id: weather kind: program sheet: "./weather.yaml" inputs: [location] outputs: [temp, condition] # In weather.yaml cells: - id: temp kind: value value: 22 - id: condition kind: value value: "sunny"

Use when you have a meaningful subsystem that should be reusable. A weather module, a payment flow, a search. Sub-sheets are like functions, but they're inspectable.

18. Retry / fallback

If the primary call fails, try the fallback. The "resilient" pattern.

   ┌──────┐
   │      │ success ──▶ result
   │  🌐  │
   │primary│ failure
   │      │     │
   └──────┘     ▼
              ┌──────┐
              │  🌐  │ success ──▶ result
              │backup│
              │      │ failure
              └──────┘     │
                            ▼
                       ┌──────┐
                       │  📦  │
                       │empty │
                       └──────┘
- id: primary kind: api endpoint: "https://primary.example.com/data" - id: backup kind: api endpoint: "https://backup.example.com/data" - id: with_fallback kind: program code: | try { return await runtime.call('primary'); } catch (e) { try { return await runtime.call('backup'); } catch (e2) { return { error: 'all sources down', last: e2.message }; } }

Use when reliability matters. The network is unreliable, APIs go down, services have outages. The fallback is the resilience.

19. Rate limit

Allow at most N calls per time window. The "be polite" pattern.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  📦  │────────▶│  ▶   │────────▶│  🌐  │
   │request│         │limit │         │  api │
   │       │         │ 10/m │         │      │
   └──────┘         └──────┘         └──────┘
                  (drop if over)
- id: window_start kind: value value: 0 - id: count kind: value value: 0 - id: allowed kind: program code: | const now = Date.now(); const start = runtime.get('window_start').data; if (now - start > 60000) { runtime.set('window_start', now); runtime.set('count', 0); } const c = runtime.get('count').data + 1; if (c > 10) return false; runtime.set('count', c); return true;

Use when you're calling an external API with rate limits (Twitter, OpenAI, Stripe). Stay under the limit, get a 429 if you go over. The cell is the budget.

20. Audit log

Every change to a cell is recorded. The "who did what when" pattern.

   ┌──────┐         ┌──────┐         ┌──────┐
   │  ƒ   │────────▶│  🔔  │────────▶│  📦  │
   │value │         │ log  │         │ log  │
   │      │         │ on   │         │      │
   └──────┘         │change│         └──────┘
                    └──────┘
                       │
                       ▼ append
                    ┌──────┐
                    │  📦  │
                    │ audit│
                    │ log  │
                    └──────┘
- id: critical kind: value value: 0 - id: audit kind: value value: [] - id: log_change kind: listener watch: critical condition: "true" action: | runtime.set('audit', [...runtime.get('audit').data, { who: caller.id, what: 'critical', from: oldValue, to: runtime.get('critical').data, when: Date.now() }])

Use when compliance matters, or you want to know what happened. Every change has a who, a what, and a when. The audit log is automatic.