AGENTS.md

  1# UI Development Instructions
  2
  3## General Guidelines
  4
  5- Never use commands to send messages when you can directly mutate children
  6  or state.
  7- Keep things simple; do not overcomplicate.
  8- Create files if needed to separate logic; do not nest models.
  9- Never do IO or expensive work in `Update`; always use a `tea.Cmd`.
 10- Never change the model state inside of a command. Use messages and update
 11  the state in the main `Update` loop.
 12- Use the `github.com/charmbracelet/x/ansi` package for any string
 13  manipulation that might involve ANSI codes. Do not manipulate ANSI strings
 14  at byte level! Some useful functions:
 15  - `ansi.Cut`
 16  - `ansi.StringWidth`
 17  - `ansi.Strip`
 18  - `ansi.Truncate`
 19
 20## Architecture
 21
 22### Rendering Pipeline
 23
 24The UI uses a **hybrid rendering** approach:
 25
 261. **Screen-based (Ultraviolet)**: The top-level `UI` model creates a
 27   `uv.ScreenBuffer`, and components draw into sub-regions using
 28   `uv.NewStyledString(str).Draw(scr, rect)`. Layout is rectangle-based via
 29   a `uiLayout` struct with fields like `layout.header`, `layout.main`,
 30   `layout.editor`, `layout.sidebar`, `layout.pills`, `layout.status`.
 312. **String-based**: Sub-components like `list.List` and `completions` render
 32   to strings, which are painted onto the screen buffer.
 333. **`View()`** creates the screen buffer, calls `Draw()`, then
 34   `canvas.Render()` flattens it to a string for Bubble Tea.
 35
 36### Main Model (`model/ui.go`)
 37
 38The `UI` struct is the top-level Bubble Tea model. Key fields:
 39
 40- `width`, `height` — terminal dimensions
 41- `layout uiLayout` — computed layout rectangles
 42- `state uiState``uiOnboarding | uiInitialize | uiLanding | uiChat`
 43- `focus uiFocusState``uiFocusNone | uiFocusEditor | uiFocusMain`
 44- `chat *Chat` — wraps `list.List` for the message view
 45- `textarea textarea.Model` — the input editor
 46- `dialog *dialog.Overlay` — stacked dialog system
 47- `completions`, `attachments` — sub-components
 48
 49Keep most logic and state here. This is where:
 50
 51- Message routing happens (giant `switch msg.(type)` in `Update`)
 52- Focus and UI state is managed
 53- Layout calculations are performed
 54- Dialogs are orchestrated
 55
 56### Centralized Message Handling
 57
 58The `UI` model is the **sole Bubble Tea model**. Sub-components (`Chat`,
 59`List`, `Attachments`, `Completions`, etc.) do not participate in the
 60standard Elm architecture message loop. They are stateful structs with
 61imperative methods that the main model calls directly:
 62
 63- **`Chat`** and **`List`** have no `Update` method at all. The main model
 64  calls targeted methods like `HandleMouseDown()`, `ScrollBy()`,
 65  `SetMessages()`, `Animate()`.
 66- **`Attachments`** and **`Completions`** have non-standard `Update`
 67  signatures (e.g., returning `bool` for "consumed") that act as guards, not
 68  as full Bubble Tea models.
 69- **Sidebar** is not its own model: it's a `drawSidebar()` method on `UI`.
 70
 71When writing new components, follow this pattern:
 72
 73- Expose imperative methods for state changes (not `Update(tea.Msg)`).
 74- Return `tea.Cmd` from methods when side effects are needed.
 75- Handle rendering via `Render(width int) string` or
 76  `Draw(scr uv.Screen, area uv.Rectangle)`.
 77- Let the main `UI.Update()` decide when and how to call into the component.
 78
 79### Chat View (`model/chat.go`)
 80
 81The `Chat` struct wraps a `list.List` with an ID-to-index map, mouse
 82tracking (drag, double/triple click), animation management, and a `follow`
 83flag for auto-scroll. It bridges screen-based and string-based rendering:
 84
 85```go
 86func (m *Chat) Draw(scr uv.Screen, area uv.Rectangle) {
 87    uv.NewStyledString(m.list.Render()).Draw(scr, area)
 88}
 89```
 90
 91Individual chat items in `chat/` should be simple renderers that cache their
 92output and invalidate when data changes (see `cachedMessageItem` in
 93`chat/messages.go`).
 94
 95## Key Patterns
 96
 97### Composition Over Inheritance
 98
 99Use struct embedding for shared behaviors. See `chat/messages.go` for
100examples of reusable embedded structs for highlighting, caching, and focus.
101
102### Interface Hierarchy
103
104The chat message system uses layered interface composition:
105
106- **`list.Item`** — base: `Render(width int) string`
107- **`MessageItem`** — extends `list.Item` + `list.RawRenderable` +
108  `Identifiable`
109- **`ToolMessageItem`** — extends `MessageItem` with tool call/result/status
110  methods
111- **Opt-in capabilities**: `Focusable`, `Highlightable`, `Expandable`,
112  `Animatable`, `Compactable`, `KeyEventHandler`
113
114Key interface locations:
115
116- List item interfaces: `list/item.go`
117- Chat message interfaces: `chat/messages.go`
118- Tool message interfaces: `chat/tools.go`
119- Dialog interface: `dialog/dialog.go`
120
121### Tool Renderers
122
123Each tool has a dedicated renderer in `chat/`. The `ToolRenderer` interface
124requires:
125
126```go
127RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string
128```
129
130`NewToolMessageItem` in `chat/tools.go` is the central factory that routes
131tool names to specific types:
132
133| File                  | Tools rendered                                 |
134| --------------------- | ---------------------------------------------- |
135| `chat/bash.go`        | Bash, JobOutput, JobKill                       |
136| `chat/file.go`        | View, Write, Edit, MultiEdit, Download         |
137| `chat/search.go`      | Glob, Grep, LS, Sourcegraph                    |
138| `chat/fetch.go`       | Fetch, WebFetch, WebSearch                     |
139| `chat/agent.go`       | Agent, AgenticFetch                            |
140| `chat/diagnostics.go` | Diagnostics                                    |
141| `chat/references.go`  | References                                     |
142| `chat/lsp_restart.go` | LSPRestart                                     |
143| `chat/todos.go`       | Todos                                          |
144| `chat/mcp.go`         | MCP tools (`mcp_` prefix)                      |
145| `chat/generic.go`     | Fallback for unrecognized tools                |
146| `chat/assistant.go`   | Assistant messages (thinking, content, errors) |
147| `chat/user.go`        | User messages (input + attachments)            |
148
149### Styling
150
151- All styles are defined in `styles/styles.go` (massive `Styles` struct with
152  nested groups for Header, Pills, Dialog, Help, etc.).
153- Access styles via `*common.Common` passed to components.
154- Use semantic color fields rather than hardcoded colors.
155
156### Dialogs
157
158- Implement the `Dialog` interface in `dialog/dialog.go`:
159  `ID()`, `HandleMsg()` returning an `Action`, `Draw()` onto `uv.Screen`.
160- `Overlay` manages a stack of dialogs with push/pop/contains operations.
161- Dialogs draw last and overlay everything else.
162- Use `RenderContext` from `dialog/common.go` for consistent layout (title
163  gradients, width, gap, cursor offset helpers).
164
165### Shared Context
166
167The `common.Common` struct holds `*app.App` and `*styles.Styles`. Thread it
168through all components that need access to app state or styles.
169
170## File Organization
171
172- `model/` — Main UI model and major sub-models (chat, sidebar, header,
173  status, pills, session, onboarding, keys, etc.)
174- `chat/` — Chat message item types and tool renderers
175- `dialog/` — Dialog implementations (models, sessions, commands,
176  permissions, API key, OAuth, filepicker, reasoning, quit)
177- `list/` — Generic lazy-rendered scrollable list with viewport tracking
178- `common/` — Shared `Common` struct, layout helpers, markdown rendering,
179  diff rendering, scrollbar
180- `completions/` — Autocomplete popup with filterable list
181- `attachments/` — File attachment management
182- `styles/` — All style definitions, color tokens, icons
183- `diffview/` — Unified and split diff rendering with syntax highlighting
184- `anim/` — Animated spinnner
185- `image/` — Terminal image rendering (Kitty graphics)
186- `logo/` — Logo rendering
187- `util/` — Small shared utilities and message types
188
189## Common Gotchas
190
191- Always account for padding/borders in width calculations.
192- Use `tea.Batch()` when returning multiple commands.
193- Pass `*common.Common` to components that need styles or app access.
194- The `list.List` only renders visible items (lazy). No render cache exists
195  at the list level — items should cache internally if rendering is
196  expensive.
197- Dialog messages are intercepted first in `Update` before other routing.
198- Focus state determines key event routing: `uiFocusEditor` sends keys to
199  the textarea, `uiFocusMain` sends them to the chat list.