Z²ᴱ logo

TUI Architecture

Bubble Tea component tree and rendering pipeline.

The TUI is built with the Bubble Tea framework (github.com/charmbracelet/bubbletea) and lives in internal/ui/model.go (~1,333 LOC).

Component Tree

  • AppModel
    • Transcript (viewport.Model) - rendered markdown via glamour
    • Composer (textarea.Model) - input area, sends on Enter
    • StatusBar (lipgloss) - model name, connection state, step counter, mode
    • ModelPicker (list.Model) - /model command overlay
    • RuntimeBridge (goroutine) - stream events to tea.Cmd

Core Components

Transcript

  • Wraps bubbles/viewport.Model
  • Displays agent conversation as rendered markdown
  • Uses glamour for terminal-compatible markdown rendering
  • Supports scrolling with PgUp/PgDn, Home/End
  • Auto-scrolls to bottom on new content

Composer

  • Wraps bubbles/textarea.Model
  • Bottom-anchored input bar
  • Enter sends the message
  • Ctrl+L clears content
  • Supports multiline input

Status Bar

  • Custom lipgloss-rendered bar at the bottom
  • Shows:
    • Current model name (e.g., opencode-go/deepseek-v4-pro)
    • Connection state (connected/connecting/error)
    • Current step number (e.g., Step 3/30)
    • Mode indicator (normal/demo)

Model Picker

  • Triggered by typing /model in the composer
  • Shows available models from the catalog, grouped by provider
  • /model <provider>/<model> switches directly
  • /model <provider> lists that provider's models
  • Selection updates the active model at runtime (no restart)

Streaming Pipeline

SSE chunks travel through a 6-stage pipeline:

  1. SSE Chunk: Raw data: lines from the OpenCode Go gateway
  2. Gateway Client: Parses SSE, extracts text deltas and tool call fragments
  3. Stream Bridge (stream.go): Translates gateway events into tea.Msg types
  4. TUI Update: AppModel.Update() handles each message type
  5. Lipgloss Render: AppModel.View() produces the final string
  6. Terminal: Bubble Tea writes to the terminal via the alt screen

Message Types

MessageSourceHandler
submitMsgComposer Enter keyStarts agent runtime
streamChunkMsgGateway SSEAppends to transcript, accumulates tool calls
toolResultMsgExecutor completionAppends observation to chat
stepMsgAgent loop tickUpdates step counter
modelSwitchMsgModel pickerSwaps active model config

Key Design Decisions

  • No goroutine sharing: All mutable state flows through tea.Cmd/Msg channel
  • Alt screen: Uses tea.WithAltScreen() for full-screen TUI
  • Mouse support: tea.WithMouseCellMotion() for scroll wheel and selection
  • Synchronous agent loop: Agent runs on a goroutine but sends results as messages back to the main TUI loop

On this page