# How Interactive Run Management Works in the No-Mistakes Terminal UI (TUI)

> Discover how interactive run management in the No-Mistakes TUI uses a Bubble Tea state machine for seamless RPC calls, aborts, reruns, and findings inspection with event-driven updates.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-25

---

**The No-Mistakes TUI translates keyboard inputs into daemon RPC calls through a Bubble Tea state machine, rendering pipeline runs as scrollable lists while handling aborts, reruns, and findings inspection via event-driven updates.**

The No-Mistakes CLI ships a full-screen Terminal User Interface (TUI) that eliminates context switching when managing pipeline executions. Built on the **Bubble Tea** framework, this interactive layer in `kunchenguid/no-mistakes` serves as a thin front-end for the daemon’s run-management API. Every keystroke—from selecting runs to aborting pipelines—triggers RPC calls through a strict state machine that maintains consistency with the daemon’s internal safety checks.

## Architecture Overview

The TUI architecture separates presentation from business logic through a clean client-server model. On startup, the `app` model in [`internal/tui/app.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/app.go) initializes a `daemon.Client` and subscribes to the `Run` event stream via [`internal/tui/events.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/events.go).

The core model maintains three critical pieces of state:

- `runs []runInfo` – A live snapshot of all pipeline runs received from the daemon.
- `cursor int` – The index of the currently highlighted run in the list.
- `state enum` – The current view state (`statePipeline`, `stateRunDetail`, `stateAbortConfirm`, etc.).

This state machine ensures that keyboard shortcuts only trigger actions valid for the current context, preventing illegal operations like aborting a completed run.

## Subscribing to Daemon Events

Real-time synchronization begins in [`internal/tui/events.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/events.go), where the UI subscribes to the daemon’s `Run` stream. The daemon pushes updates—`EventRunCreated`, `EventRunUpdated`, `EventRunFinished`, and `EventRunAborted`—as they occur.

When the TUI receives an `EventRunUpdated` message, the model’s `runs` slice refreshes immediately. This **event-driven** approach eliminates polling overhead while ensuring the user always views the latest status, branch names, and execution summaries. The subscription logic handles connection drops gracefully, attempting reconnection without blocking the main UI thread.

## Navigating the Run List

The primary interface, implemented in [`internal/tui/pipeline.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/pipeline.go), renders the `runs` slice as a scrollable list. Users navigate using `j` and `k` keys, which invoke the `moveCursor()` method to adjust the `cursor` index.

Selection highlighting applies the primary-action style defined in [`internal/tui/review.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/review.go). When the user presses **Enter** or **Space** on a selected run, the state machine transitions to `stateRunDetail`, triggering a view switch in [`app.go`](https://github.com/kunchenguid/no-mistakes/blob/main/app.go) that reveals detailed panes for logs, findings, and CI status.

## Interacting with Individual Runs

Once inside a run’s detail view, the TUI exposes four distinct interaction surfaces, each mapped to specific daemon RPCs.

### Viewing Details and Findings

The *Review* pane, rendered by [`internal/tui/review.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/review.go), displays static analysis findings as a scrollable list. The `renderFindingsWithSelection()` function accepts the raw JSON findings, current cursor position, and a `selected map[string]bool` to produce a viewport-aware string.

Users toggle individual findings between `[ ]` and `[x]` using the **Space** key. The rendering logic respects viewport boundaries, ensuring the cursor remains centered while scrolling through large result sets with `j` and `k`.

### Aborting and Rerunning Pipelines

Actionable commands live in [`internal/tui/commands.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/commands.go). When a run status indicates *Running*, pressing `a` triggers the `abortRun()` method:

```go
func (m *model) abortRun() tea.Cmd {
    if m.state != stateRunDetail || m.selectedRun == nil {
        return nil
    }
    return func() tea.Msg {
        err := m.client.AbortRun(context.Background(), m.selectedRun.ID)
        if err != nil {
            return errMsg{err}
        }
        return runAbortedMsg{runID: m.selectedRun.ID}
    }
}

```

The UI immediately transitions to an *Aborting* state and waits for the daemon’s `EventRunAborted` confirmation. Similarly, pressing `r` on a failed run invokes `client.StartRun()` with the original branch and intent parameters, then returns the view to the pipeline list to display the new execution.

### Branch Sync and CI Monitoring

The *Branch-Sync* pane ([`internal/tui/branch_sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/branch_sync.go)) spawns short-lived daemon commands to execute `axi sync` without leaving the TUI. Output streams back into the run’s custody fields in real-time.

For CI feedback, [`internal/tui/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/ci.go) uses `renderLogTail()` to stream remote job logs. The helper applies color coding to PASS/FAIL lines and auto-scrolls the viewport as new lines arrive, matching the behavior of local log tailing.

## The Update Loop and State Machine

All user input flows through the `Update(msg tea.Msg)` method in [`internal/tui/app.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/app.go). This function pattern-matches against the current `state` and key pressed, returning a `tea.Cmd` that either performs an asynchronous RPC or schedules a UI refresh.

```go
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.String() {
        case "j":
            m.moveCursor(1)
        case "k":
            m.moveCursor(-1)
        case "a":
            return m, m.abortRun()
        case "r":
            return m, m.rerun()
        case "enter":
            return m.enterRunDetail()
        }
    }
    return m, nil
}

```

The UI never mutates daemon state directly. All modifications—aborts, reruns, syncs—traverse the public API defined in [`internal/daemon/manager.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/manager.go), ensuring the same validation logic protects both CLI and interactive operations.

## Summary

- **Event-Driven Architecture**: The TUI subscribes to daemon event streams via [`internal/tui/events.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/events.go), receiving real-time updates without polling.
- **State Machine Safety**: [`internal/tui/app.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/app.go) enforces valid state transitions, preventing context-inappropriate actions like aborting finished runs.
- **Viewport-Aware Rendering**: [`internal/tui/review.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/review.go) handles cursor-centric scrolling and finding selection through `renderFindingsWithSelection()`.
- **RPC-Only Mutations**: All interactive commands in [`internal/tui/commands.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/commands.go) invoke daemon RPCs (`AbortRun`, `StartRun`), maintaining safety guarantees.
- **Graceful Shutdown**: Pressing `Ctrl-C` triggers a `Shutdown` RPC and waits for active runs to finish before exiting.

## Frequently Asked Questions

### How does the TUI handle real-time updates from the daemon?

The TUI maintains a persistent gRPC stream subscription in [`internal/tui/events.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/events.go). As the daemon emits `EventRunUpdated` messages, the Bubble Tea framework routes these to the `Update()` method, which refreshes the `runs` slice and triggers a re-render. This push-based model ensures the run list reflects current status within milliseconds of daemon state changes.

### Can users interact with runs while they are executing?

Yes. The TUI supports **live interaction** with running pipelines. Users can press `a` to send an `AbortRun` RPC or monitor CI logs in real-time via [`internal/tui/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/ci.go). The state machine in [`app.go`](https://github.com/kunchenguid/no-mistakes/blob/main/app.go) tracks transitional states (like *Aborting*) to prevent duplicate commands while waiting for daemon confirmation.

### What happens if the daemon connection drops during an interactive session?

The event subscription logic in [`internal/tui/events.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/events.go) implements reconnection logic. If the stream breaks, the TUI attempts to re-establish the connection without blocking the main update loop. The local model retains the last known state of all runs, allowing users to continue viewing historical data even during temporary disconnections.

### How are keyboard shortcuts mapped to specific run actions?

Key mappings reside in [`internal/tui/commands.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/commands.go). The `Update()` method in [`app.go`](https://github.com/kunchenguid/no-mistakes/blob/main/app.go) dispatches to command functions based on the current `state` enum. For example, the `a` key only triggers `abortRun()` when `m.state == stateRunDetail` and `m.selectedRun` is non-nil, ensuring shortcuts are context-sensitive and cannot invoke invalid operations.