169 lines
3.1 KiB
Markdown
169 lines
3.1 KiB
Markdown
# Bubble Tea Architecture Best Practices
|
|
|
|
## Model Design
|
|
|
|
### Keep State Flat
|
|
❌ Avoid: Deeply nested state
|
|
✅ Prefer: Flat structure with clear fields
|
|
|
|
```go
|
|
// 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:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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
|
|
```go
|
|
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
|