iterion

ADR-002: Separating the Run console from the Workflow editor

Context

The Iterion editor is a React/Vite SPA embedded in the Go binary (pkg/server/). It originally hosted a single concern: authoring .bot workflows visually — drag-and-drop on a ReactFlow canvas, inspector panels for declarations, validation against the IR compiler, save/load through a REST file API.

When the run console UI was added (Phase 1 = pkg/runview backend + REST endpoints; Phase 2 = WebSocket transport + frontend RunView), a design choice surfaced:

Should the live execution view be integrated into the studio (one canvas, one page, with a mode: edit | observe toggle), or should it live as a separate route/page with its own canvas, store, and transport layer?

The separation was retained. This ADR documents the reasoning so future contributors understand why fusing the two views — a tempting simplification — would be a step backwards.

Status quo at the time of decision

Decision

The run console is implemented as a separate page in the same SPA:

Alternatives considered

1. Single page with a mode: edit | observe toggle

One canvas, one route, one store. A toggle (or implicit detection based on the URL) switches between editing the document and observing a run.

Rejected because:

2. Side-by-side split-pane (editor left, run right, simultaneous)

The studio and the run console rendered in two panes of the same page, both alive at once.

Rejected because:

3. Modal/drawer overlay over the studio

Open the run view as a slide-over panel above the studio.

Rejected because:

Arguments in favor

1. Orthogonal concerns

Editing structure (AST mutations, validation, save/load) and visualizing execution state (event streams, scrubbing, logs) are genuinely different domains. They share no business logic, they have no overlapping invariants, and they evolve independently.

2. Incompatible data models

Aspect Editor RunView
Source of truth AST + IR Sequenced event stream
Refresh trigger User action (validate/save) WebSocket push (~1–10 ms)
Persistence model File save via REST Append-only events.jsonl (read-only)
Mutation surface Full (CRUD on nodes/edges) None
Memory pressure Bounded by file size Bounded by ring buffer + virtualization

A unified state container would have to special-case nearly every operation. Two stores stay simple.

3. Store isolation as a safety property

Because useDocumentStore and useRunStore are physically separate Zustand instances, no editor action can ever mutate run state and vice-versa. This is a load-bearing guarantee: a run is pinned to a specific workflow hash, and accidental edits during observation would break that contract. Separation enforces it at the type level.

4. Canvas semantics stay clean

In EditorView, clicking a node means “select for inspection/edit”. In RunView, clicking a node means “show me this node’s artifacts at the current sequence”. Sharing a canvas would force every handler to branch on mode. Two canvases keep each interaction model crisp.

Navigation friction is minimal because the studio and the run view exchange contextual deep-links:

6. Independent scaling

RunView ships dedicated optimizations — virtualized event log, ring buffer for logs, throttled WebSocket batching — without bloating the editor bundle or complicating its render path. The studio remains lean and focused.

Arguments against

1. No live-edit during a run

A user cannot tweak a .bot file while observing a running execution of it. This is intentional: the run is pinned to a specific workflow hash via the store, and editing the source mid-run has no clear semantics (does the change apply? to future iterations only? to already-scheduled branches?). The current model — edit, save, launch a new run — is unambiguous and matches the engine’s actual behavior.

2. No native side-by-side

If a user wants to watch a run while editing a different workflow, they need two browser tabs. This is a real but minor cost. If demand grows, a parent split layout can compose the two existing views without altering them.

3. Two ReactFlow components instead of one

Canvas (editor) and RunCanvasIR (run view) duplicate some boilerplate (ReactFlowProvider setup, layout helpers). The duplication is acceptable: the requirements diverge enough (editable vs. read-only, domain-specific overlays) that a shared component would accumulate configuration flags faster than it would save lines.

4. Minor chrome duplication

Each view ships its own header, breadcrumb, and toolbar. Minimal cost; shared atoms (buttons, icons) are factored where appropriate.

Consequences

References