# Wire

> Connect the spec to behavior — wire up $context, $events, actions, effects, guards, and element bindings

- Skill: `johnlindquist/wire` (Agent Skill)
- Install (CLI): `npx skillmds@latest add johnlindquist/wire`
- Raw SKILL.md: https://api.skillmd.com/api/skills/johnlindquist/wire/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: johnlindquist (https://skillmd.com/u/johnlindquist)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/johnlindquist/wire

---


# Wire: Runtime Semantics

Connect the spec to behavior — context, events, actions, effects, bindings, guards. This is where the state machine comes alive.

## When to Use

When adding `$context`, `$events`, `$actions`, `$effects`, or wiring `binding`, `visibleWhen`, `enabledWhen`, `onPress`, `onChange` on elements.

## Context (`$context`)

Every piece of runtime state needs a typed field with a default:
```json
"$context": {
  "email":      { "type": "string",  "default": "" },
  "submitting": { "type": "boolean", "default": false },
  "error":      { "type": "string",  "default": null }
}
```

**Decisions:**
- Only declare fields the spec actually reads or writes. No speculative context.
- Use `null` as default for "not yet known" values (error messages, fetched data).
- Use empty collections (`[]`, `{}`) for "nothing yet" — distinct from `null` ("never fetched").
- Name fields by what they represent, not by where they're used: `error` not `errorBannerText`.

## Events (`$events`)

Every trigger the machine responds to:
```json
"$events": {
  "SUBMIT":        { "source": "user",    "payload": {} },
  "INPUT_CHANGED": { "source": "user",    "payload": { "name": { "type": "string" }, "value": { "type": "string" } } },
  "HTTP_OK":       { "source": "network", "payload": { "data": { "type": "object" } } }
}
```

**Decisions:**
- Name events as past-tense facts or imperative commands: `SUBMIT`, `HTTP_OK`, `TIMER_EXPIRED`.
- Source types: `user` (clicks, input), `network` (API responses), `timer`, `system` (lifecycle), `storage`.
- Payload should carry only what the machine needs to decide or store. Don't pass entire API responses if you only need one field.

## Bindings and Conditional Display

- **`binding`** — connects element props to expressions: `{ "content": ["var", "context.label"] }`
- **`visibleWhen`** — show only when truthy: `["!=", ["var", "context.error"], null]`
- **`enabledWhen`** — interactive only when truthy: `["==", ["var", "context.submitting"], false]`
- **`onPress`** / **`onChange`** — actions/effects triggered by interaction

**Decisions:**
- Prefer `visibleWhen` over separate states when the difference is purely whether an element shows. Use separate states when the difference changes what the user can *do*.
- Disable buttons during async operations. Don't rely on guards alone — users should see why they can't act.
- Always pair a disabled button with a visible reason (loading indicator, validation message).

## Guards

Guards control whether a transition fires:
```json
"on": {
  "SUBMIT": {
    "target": "loading",
    "guard": ["&&",
      ["!=", ["var", "context.email"], ""],
      ["==", ["var", "context.submitting"], false]
    ]
  }
}
```

**Decisions:**
- Guards should be simple boolean checks. Complex logic belongs in context preparation, not guard expressions.
- If a guard prevents a transition, the user should understand why. Pair guards with `enabledWhen` on the triggering element.
- Use `always` transitions (with guards) for automatic routing — e.g., skip a step if data already exists.

## Actions and Effects

**Actions** are pure state mutations (synchronous):
- `assign` — update context: `{ "kind": "assign", "path": "context.error", "value": null }`
- `emit` — raise an event: `{ "kind": "emit", "event": "FORM_RESET" }`
- `log` — debug output: `{ "kind": "log", "level": "info", "message": "submitted" }`

**Effects** are side effects (async, external):
- `http` — API call
- `timer.start` / `timer.cancel` — delayed events
- `navigate` — route change
- `focus` — move focus to an element
- `storage.write` — persist to storage

**Decisions:**
- Clear context on state entry, not exit. Entry actions are guaranteed to run; exit actions may be skipped if the machine is destroyed.
- Set loading flags in `entry`, clear them in `exit`.
- Focus management is a first-class concern. Define `focus` effects in `onEnter` for every state that changes the interactive surface.

## Principle

Runtime semantics exist to close the gap between "what this looks like" and "what this does." Every binding, guard, and action should make the spec more precise — not more complex. If wiring feels complicated, the state machine probably needs simplification first.

