# Frontend and Runtime State Model

## Three Different Lifetimes

The workbench presents one visual system but its data has three lifetimes that
must not be collapsed in a later internal manager.

| Lifetime | Identity | Storage | Survives service restart? | Current UI |
| --- | --- | --- | --- | --- |
| Client session | selected cases, current case, cursor, concurrency, active job/batch, timers and overlay state | Browser memory | No | Gallery, stream, comparison and overlays |
| Live execution | `job_id`, `batch_id`, event buffer, process control and newest isolated workspace | Python memory + `output/<case>/<job_id>/` | Event buffer/control: no; workspace: yes | `/current`, `/events`, live stream |
| Archive | display `case.n`, delete id `case~n`, target/render/result/log snapshots | `output/_runs/<case>/<n>/` | Yes | Database and archived comparison |

## Client State

Pinned `web/static/app.js` initializes:

```text
jobId, cursor, startTime, polling, timerHandle, toolCalls,
sel, batchId, batchTimer, currentCase, concurrency
```

The client does not have a durable `run_id` or `archive_id` concept. It normally
knows one `currentCase`, and most detailed routes take only the base case name.

### Single-run transition

```mermaid
stateDiagram-v2
  [*] --> Idle
  Idle --> Starting: case card / valid upload
  Starting --> Error: request error
  Starting --> Running: job_id returned
  Running --> Running: poll events by cursor
  Running --> Complete: done + complete
  Running --> Error: done + error
  Running --> Stopped: stop requested
  Complete --> IdleLike: Gallery reloads; result remains visible
  Error --> Idle: new action resets UI
  Stopped --> Idle: new action resets UI
```

Observed display statuses are lowercased in source and rendered uppercase by
CSS: idle, running, complete, error and stopped. The Stop button appears only
for running status. `finish()` treats any false/missing `accepted` value as
`NOT ACCEPTED` and builds one compact final summary.

### Batch transition

```mermaid
stateDiagram-v2
  [*] --> Selecting
  Selecting --> Selecting: toggle cases / concurrency
  Selecting --> BatchRunning: Run with non-empty selection
  BatchRunning --> BatchRunning: poll batch + follow lead job
  BatchRunning --> Done: all workers joined
  BatchRunning --> Stopped: batch stop flag
  Done --> Selecting: cases and DB count refresh
  Stopped --> Selecting: queued work stops
```

Concurrency is clamped to 1–8 server-side. Workers run jobs independently; one
complete or error frees a worker for another case. The UI displays all item
statuses but follows only the first running job's detailed event stream.

## Job and Artifact State

Each job receives an eight-character id and a fresh isolated workspace. No old
workspace is reused. The runner copies each new render into sequential
`frames/fNNNN.png` files and attaches its URL to the emitted event. It archives
only after a non-stopped run with `result.json`.

Required final artifacts in the audited canary:

- `target.png`
- `code.html`
- `render.png`
- `result.json`
- `logs/events.jsonl`
- grounding output and overlay
- one or more epoch PNG/JSON pairs
- optional frames, raw CLI logs, assets and derived debug inputs

The configured live-workspace resolver returns the newest run subdirectory by
filesystem modification time, keeping up to three recent workspaces per case on
a best-effort basis. This gives stable isolation during execution but weakens
historical UI identity when only a base case is supplied.

## Archive Identity Mismatch

The Database correctly lists every archived version and comparison uses its
archive-specific target/render URLs. Debug and Pipeline do something different:

1. `/db` returns display `case9.1`, base `case9`, id `case9~1`.
2. The Debug/Pipeline card actions pass `base`, not archive `id`.
3. `/debug/case9` and `/timeline/case9` resolve the newest live workspace.

With multiple runs, choosing an older Database card can therefore compare the
older archived target/render but open Debug/Pipeline for the newest live run of
that base case. `/log/{vid}` already understands `case~n`, but the UI does not
expose it and the Debug/Timeline routes do not accept archive identity.

This is the most important data-integrity issue for a future unified manager:
every detail surface must receive the same immutable run identity.

## Event Model

Persisted event types observed in case9:

| Type | UI treatment | Key purpose |
| --- | --- | --- |
| `status` | Ignored by event renderer; top status comes from polling envelope. | Lifecycle. |
| `meta` | Sets target URL. | Run binding. |
| `grounding` | Card with detected block count and optional overlay. | Initial evidence. |
| `epoch` | Start creates an epoch divider; end has no direct row. | Chapter boundary. |
| `assistant` | “thinking” card with text. | Reasoning narration. |
| `tool_call` | Number, tool, group and first 220 chars of serialized input. | Action intent. |
| `tool_result` | Nested under matching call; can update live render. | Outcome/evidence. |
| `gate` | Spatial/visual pills, details and latest render. | Verification decision. |
| `error` | Red error row. | Failure signal, including recoverable/nonterminal events. |
| `complete` | Used as polling final payload, not rendered as a stream row. | Final summary. |

The case9 log had 63 render frames, 143 tool calls and one epoch snapshot. A
future HTML replay needs an explicit rule for events without frames, several
events sharing a frame, and frames produced between semantically meaningful
steps.

## Overlay State

Four independent boolean `hidden` states exist: comparison, Database, Debug and
Pipeline. Overlays can be layered rather than routed. Database can sit below
Debug/Pipeline; comparison can sit above any of them.

Independent document-level Escape listeners cause cascade closure:

- Debug over Database: the comparison listener closes Database, then the Debug
  listener closes Debug in the same key event.
- Comparison over Pipeline over Database: comparison closes first; the Pipeline
  listener then sees comparison hidden and closes Pipeline; Database remains
  until the second Escape.

There is no explicit overlay stack, focus stack or browser-history entry.

## Partial and Error States

The API permits combinations that a future manager must model explicitly:

- result exists but final render does not (`case1` in the audit);
- live workspace exists but archive does not;
- archive exists while in-memory Job does not after restart;
- result rejected with complete infrastructure status;
- run contains recoverable error events and still completes;
- timeline has no epochs;
- debug has no code artifact;
- epoch aggregate tool count differs from persisted event count.

Using one broad “complete/error” field would erase these distinctions. A later
data model should separate infrastructure lifecycle, artifact completeness,
gate verdict, evidence completeness and archival state.
