Themes & UI v1.0

Color palettes, layout anatomy, tool rendering, and keyboard navigation for the Automergent terminal UI.

Theme System Overview

Automergent ships with 11 built-in color themes, each defining a complete 16-slot color palette plus 50+ pre-built lipgloss styles derived from those colors. Themes are hot-swappable at runtime — every renderer, diff pane, markdown block, and tool card repaints instantly.

16 Color Slots per Theme

Every theme must fill these 16 named color slots, defined in internal/tui/themes/theme.go:

SlotPurposeUsage
BackgroundPrimary backgroundTerminal bg, pane fill, code blocks
SurfaceRaised surfacesUser bubbles, tool slabs, input bg, diff bg
OverlayOverlays & secondaryDropdown bg, inactive borders, menu dims
TextPrimary textBody text, headings, bold elements
SubtextSecondary textLabels, hints, descriptions, timestamps
MutedDe-emphasized textDim text, status bar, system messages
AccentPrimary accentLinks, focused borders, active indicators, brand
AccentAltSecondary accentAlternating highlights, agent labels
GreenSuccess / addSuccess badges, diff additions, confirm buttons
RedError / deleteError text, diff deletions, reject buttons
YellowWarning / cautionWarning text, tool calls, feedback buttons
BlueInfo / brandBrand glyph, directory names, info indicators
MagentaAccent secondaryAgent kind labels, alternate highlights
CyanCode / inlineInline code, code blocks, search highlights
BorderNormalDefault bordersPane borders, dividers, separators
BorderFocusedFocused bordersActive pane, focused input, palette border
Color model: Automergent uses color.Color (Go's image/color) for all slots, supporting truecolor, 256-color, and 16-color terminal palettes. The themes.Mix() function blends colors linearly, and themes.Luminance() / themes.IsDark() handle brightness detection for adaptive rendering.

Pre-built Lipgloss Styles

Each theme generates 50+ lipgloss styles via themes.NewStyles() in internal/tui/themes/theme.go:90. These cover every UI element:

Layout & Chrome
  • Header, HeaderBrand, HeaderCenter, HeaderRight, HeaderPill
  • StatusBar, StatusBarRight
  • Input, InputFocused
  • Palette, PaletteItem, PaletteSelect, PaletteDim
Messages & Tool
  • UserMsg, UserLabel, UserBubble
  • AssistantMsg, AssistantLabel, AssistantBubble
  • ToolCall, ToolResult, ToolBox, ToolName
  • ToolHeader, ToolBody, ToolStatus, ToolDuration
  • ToolIcon, ToolAccent
Overlays & UI
  • ConfirmBox, ConfirmAllow, ConfirmAlways, ConfirmReject, ConfirmFeedback
  • DiffPane, DiffAction, ActivePane, InactivePane
  • FileTree, FileTreeDir, FileTreeFile, FileTreeSelect
  • HelpBox
Utility
  • Base, Error, Border, Code, Dim, Bold
  • Success, Warning, Timestamp
  • SystemMsg

Custom Themes via CreateCustomTheme()

You can create themes programmatically using themes.CreateCustomTheme():

// internal/tui/themes/engine.go:174
theme := themes.CreateCustomTheme("my-theme", map[string]string{
    "background":     "#1a1a2e",
    "surface":        "#16213e",
    "overlay":        "#0f3460",
    "text":           "#e6e6e6",
    "subtext":        "#b0b0b0",
    "muted":          "#808080",
    "accent":         "#e94560",
    "accent_alt":     "#533483",
    "green":          "#00b894",
    "red":            "#e94560",
    "yellow":         "#fdcb6e",
    "blue":           "#0984e3",
    "magenta":        "#6c5ce7",
    "cyan":           "#00cec9",
    "border_normal":  "#2d3436",
    "border_focused": "#e94560",
})

engine.RegisterTheme(theme)
engine.SetTheme("my-theme")

Register the theme with the engine, and it becomes available in the /theme picker alongside all built-in themes.

All 11 built-in themes with their actual color palettes. Hover over any swatch to see its hex value.

modern default
Clean, grayscale-focused palette with high-contrast white accents. Minimal color noise; semantic colors borrowed from Catppuccin for visibility.
catppuccin Mocha
Catppuccin Mocha. Warm, pastel palette with mauve accent. Gentle on the eyes for extended sessions.
dracula Classic
Dracula. Purple-dominant palette with vibrant green, pink, and cyan. Bold contrast, iconic look.
nord Arctic
Nord. Cool arctic blues with frosty cyan accent. Calm, muted tones inspired by the Aurora Borealis.
gruvbox Dark
Gruvbox Dark. Warm retro palette with orange accent and earthy greens/browns. Cozy and readable.
onedark Atom
Atom One Dark. Blue accent with purple alt. Balanced contrast, familiar from Atom editor.
tokyonight Night
Tokyo Night. Deep indigo with blue-violet accents. Moody and immersive, inspired by Tokyo's nightscape.
solarized-dark Ethan Schoonover
Solarized Dark. Precision-crafted color relationships with blue accent. Scientifically balanced contrast.
solarized-light Light
Solarized Light. Warm parchment background with the same precise color relationships as its dark sibling.
high-contrast A11y
High Contrast. Pure black background with maximum-saturation primaries. Meets WCAG AAA for accessibility.
monokai Classic
Monokai. The original Sublime Text theme. Vibrant green accent on warm dark, nostalgic and energetic.

Switching Themes

Interactive Theme Picker

/theme

Opens the Spotlight-style theme picker with a scrollable list of all registered themes. The currently active theme is marked with (current).

Direct Switch

/theme catppuccin
/theme dracula
/theme high-contrast

Switches to the named theme immediately. All UI elements repaint in one frame. The engine rebuilds every lipgloss style, markdown renderer, and diff palette from the new color slots.

Keyboard Navigation in Theme Picker

KeyAction
↑ kMove selection up
↓ j TabMove selection down
EnterApply selected theme
EscCancel and close picker
Ctrl+TabJump to next section (in palette categories)
Programmatic switching: The ThemeEngine.SetTheme() method accepts a name string and returns a boolean. Call CycleTheme() to iterate through all themes. Use OnThemeChange() to register listeners that fire on every switch.

TUI Layout

The Automergent TUI is a full-screen terminal application built with Bubble Tea. Every element adapts to the active theme:

┌─ Header Bar ─────────────────────────────────────────────────────┐ │ ✦ AUTOMERGENT claude-3.5-sonnet · $0.12 · 14.2k tok · main │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ╭─ User ─────────────────────────────────────╮ │ │ │ Refactor the auth module to use JWT │ │ │ ╰─────────────────────────────────────────────╯ │ │ │ │ ● Read internal/auth/handler.go · L1–120 · 120 lines 12ms │ │ ● Edit handler.go · 3 lines replaced 8ms │ │ +3 −1 ──────────────────────────────────── │ │ 22 │ token, err := jwt.Sign(token, secret) │ │ 21 │ token, err := jwt.Sign(token, secret, jwt.SigningMethodRS256)│ │ │ │ ● Bash ───────────────────────────────── ✓ exit 0 2s │ │ $ go test ./internal/auth/... │ │ ok github.com/.../auth 0.847s │ │ │ ├─ Input Area ─────────────────────────────────────────────────────┤ │ └─ accept-edits mode · esc interrupts · ctrl+c stops │ ││ Type a message… │ ├──────────────────────────────────────────────────────────────────┤ │ ▐ ACCEPT EDITS ▌ ▸ read_file… ctx 12% ##────── main │ └──────────────────────────────────────────────────────────────────┘

The floating pill header displays at the top, showing:

Conversation Area

The main scrollable region displays the conversation as styled message bubbles:

Input Area

A thin border-topped text input sits below the conversation. Displays:

Bottom Dock

The dock sits below the status bar and shows running background processes:

Status Bar

The bottom bar shows the current state with independent slots:

Diff Pane

Toggle with Ctrl+D or /diff. Shows a file-tabbed diff view of all edits made this session:

File Tree Sidebar

Toggle with Ctrl+T or /tree (alias /files). Displays a navigable project file tree:

Command Palette

Spotlight-style command palette, opened with Ctrl+P or by typing / in the input:

Tool Card Rendering

Every tool call is rendered as a compact card in the conversation area. Automergent uses per-family renderers that collapse verbose output into scannable one-liners:

Renderer files: tool_read.go · tool_edit.go · tool_terminal.go · tool_box.go · tool_web.go · tool_agent.go · tool_inspect.go

Read Cards (tool_read.go)

Reads are always one line, even for 500-line files. What matters is which file and how much, not the contents:

● Read  internal/tui/components/tool_box.go · L40–160 · 121 lines    12ms
● Read  src/main.rs · L1→ · 45 lines                              8ms
● List  internal/tui/components/ · 24 entries                       3ms
● Grep  "TODO|FIXME" · 14 matches across 6 files                 22ms

The call line carries chips for the line range (L40–160), line count, and duration. Errors show the first line of error text as a chip.

Edit Cards (tool_edit.go)

Edits expand to show the diff — vertical space is always worth spending here:

● Edit  handler.go  ·  1 line replaced                              8ms
  +1 −1 ──────────────────────────────────────────
     34 │ limit := c.tailLimit()
     35 │ limit := c.tailLimit() + 2
     ↑ first 3 hidden · ctrl+e expands

Diff boxes use theme-derived colors: additions get a Green-tinted background, deletions get Red-tinted. Word-level changes are highlighted within lines using Mix() blends at higher saturation.

Terminal Cards (tool_terminal.go)

Terminal output shows a themed slab (Surface background) with the command and merged output tail:

● Bash ───────────────────────────────────── ✓ exit 0        2s
  $ go test ./internal/auth/...
  ok   github.com/.../auth  0.847s

The command appears once on the $ row. Stderr sections and failure preambles are stripped since the exit-chip already conveys the result. Running commands show ◌ running… and expand as output arrives.

Tool Card Anatomy

ElementStyleDescription
GlyphstatusGlyph()● success, ● error, ● running, ◌ pending
NameToolNameBold text in tool's accent color
SubjectDimFile path, command, or search pattern
ChipsDimMetadata: line ranges, line counts, exit codes
DurationToolDurationMuted italic, right-aligned
BodyToolBoxSurface-bg panel with diff, output, or error
Expand hintDimctrl+e expands when output is truncated

Keyboard Shortcuts

Automergent supports three keybinding schemes. Switch with /keybindings or /keys:

Default Scheme

KeyAction
EnterSubmit message
Ctrl+CQuit (press twice to force exit)
Ctrl+QQuit (alias)
EscCancel / interrupt / close overlay
Ctrl+DToggle diff pane
Ctrl+LToggle LSP integration
Ctrl+SOpen session list
Ctrl+NNew session
Ctrl+UClear input
↑ / kScroll up
↓ / jScroll down
PgUpPage up
PgDnPage down
Ctrl+JSend after current tool / steer at tool boundary
Ctrl+POpen command palette
Ctrl+TToggle file tree
Ctrl+WToggle diff pane (alias)
Ctrl+EExpand truncated tool output
Ctrl+RToggle review mode / inline rename
Ctrl+XDrop queued message
Ctrl+OPull queued message back into input
Shift+↑/↓Select in queue strip
Y / NConfirm / deny in approval dialogs

Vim Scheme

KeyDiffers from Default
kScroll up (primary)
jScroll down (primary)
Ctrl+UPage up
Ctrl+DPage down
qQuit (alongside Ctrl+C)
EscCancel (same as default)

Emacs Scheme

KeyDiffers from Default
Ctrl+PScroll up (primary)
Ctrl+NScroll down (primary)
Alt+VPage up
Ctrl+VPage down
Ctrl+KClear input
Ctrl+GCancel (primary)

Switching Schemes

/keybindings default
/keybindings vim
/keybindings emacs

Or open the sub-palette with /keybindings (no args) and select interactively.

Notification System

Automergent delivers notifications through multiple channels, configured in config.yaml:

notifications:
  desktop: true        # OS-level desktop notifications
  bell: true           # Terminal bell (\a) on completion
  contextWarning: true # Warning when context window is nearly full

Desktop Notifications

When desktop: true, Automergent sends OS-level notifications via the terminal's notification protocol. Triggered when:

Terminal Bell

When bell: true, a \a (BEL) character is emitted for:

Context Warnings

When contextWarning: true, the system warns before the context window fills up:

Toast Stack

In-app notifications appear as toast cards above the status bar:

Background Task Notifications

Both shell and agent managers emit completion hooks wired in notifications.go:

// Shell completion → toast
shellNotificationMsg{
    id:       "sh-001",
    command:  "go test ./...",
    status:   "success",
    exitCode: 0,
    duration: 2.3s,
}

 // Agent completion → toast
agentNotificationMsg{
    id:     "agent-042",
    name:   "refactor-auth",
    kind:   "sub-agent",
    status: "success",
    summary: "Refactored 3 files, added JWT validation",
    duration: 45s,
}