Files
gh-human-frontier-labs-inc-…/references/architecture-best-practices.md
2025-11-29 18:47:30 +08:00

3.1 KiB

Bubble Tea Architecture Best Practices

Model Design

Keep State Flat

Avoid: Deeply nested state Prefer: Flat structure with clear fields

// Good
type model struct {
    items []Item
    cursor int
    selected map[int]bool
}

// Avoid
type model struct {
    state struct {
        data struct {
            items []Item
        }
    }
}

Separate Concerns

  • UI state in model
  • Business logic in separate functions
  • Network/IO in commands

Component Ownership

Each component owns its state. Don't reach into component internals.

Update Function

Message Routing

Route messages to appropriate handlers:

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        return m.handleKeyboard(msg)
    case tea.WindowSizeMsg:
        return m.handleResize(msg)
    }
    return m.updateComponents(msg)
}

Command Batching

Batch multiple commands:

var cmds []tea.Cmd
cmds = append(cmds, cmd1, cmd2, cmd3)
return m, tea.Batch(cmds...)

View Function

Cache Expensive Renders

Don't recompute on every View() call:

type model struct {
    cachedView string
    dirty bool
}

func (m model) View() string {
    if m.dirty {
        m.cachedView = m.render()
        m.dirty = false
    }
    return m.cachedView
}

Responsive Layouts

Adapt to terminal size:

if m.width < 80 {
    // Compact layout
} else {
    // Full layout
}

Performance

Minimize Allocations

Reuse slices and strings where possible

Defer Heavy Operations

Move slow operations to commands (async)

Debounce Rapid Updates

Don't update on every keystroke for expensive operations

Error Handling

User-Friendly Errors

Show actionable error messages

Graceful Degradation

Fallback when features unavailable

Error Recovery

Allow user to retry or cancel

Testing

Test Pure Functions

Extract business logic for easy testing

Mock Commands

Test Update() without side effects

Snapshot Views

Compare View() output for visual regression

Accessibility

Keyboard-First

All features accessible via keyboard

Clear Indicators

Show current focus, selection state

Help Text

Provide discoverable help (? key)

Code Organization

File Structure

main.go         - Entry point, model definition
update.go       - Update handlers
view.go         - View rendering
commands.go     - Command definitions
messages.go     - Custom message types

Component Encapsulation

One component per file for complex TUIs

Debugging

Log to File

f, _ := os.OpenFile("debug.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
log.SetOutput(f)
log.Printf("Debug: %+v", msg)

Debug Mode

Toggle debug view with key binding

Common Pitfalls

  1. Forgetting tea.Batch: Returns only last command
  2. Not handling WindowSizeMsg: Fixed-size components
  3. Blocking in Update(): Freezes UI - use commands
  4. Direct terminal writes: Use tea.Println for above-TUI output
  5. Ignoring ready state: Rendering before initialization complete