Initial commit
This commit is contained in:
168
references/architecture-best-practices.md
Normal file
168
references/architecture-best-practices.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user