gentleman-bubbletea
Gentleman-Programming/Gentleman.Dots/skills/gentleman-bubbletea/SKILL.md
Bubbletea TUI patterns for Gentleman.Dots installer. Trigger: When editing Go files in installer/internal/tui/, working on TUI screens, or adding new UI features.
Skill2k starsChanged 3 months ago
What's in it
- When to Use
- Critical Patterns
- Pattern 1: Screen Constants in model.go
- Pattern 2: Model Struct Holds All State
- Pattern 3: Update Pattern with Type Switch
- Pattern 4: Key Handlers Return (Model, Cmd)
- Decision Tree
- Code Examples
- Example 1: Adding Screen to handleKeyPress
- Example 2: Screen Options Pattern
- Example 3: Scrollable View Pattern
- Example 4: Custom Message Pattern
- Commands
- Resources
---
name: gentleman-bubbletea
description: >
Bubbletea TUI patterns for Gentleman.Dots installer.
Trigger: When editing Go files in installer/internal/tui/, working on TUI screens, or adding new UI features.
license: Apache-2.0
metadata:
author: gentleman-programming
version: "1.0"
---
## When to Use
Use this skill when:
- Adding new screens to the TUI installer
- Handling keyboard input or navigation
- Creating new UI components with Lipgloss
- Working on screen transitions or state management
---
## Critical Patterns
### Pattern 1: Screen Constants in model.go
All screens MUST be defined as `Screen` constants in `model.go`:
```go
type Screen int
const (
ScreenWelcome Screen = iota
ScreenMainMenu
ScreenOSSelect
// ... new screens go here
ScreenNewFeature // Add new screen
ScreenNewFeatureCat // Add category screen if needed
)
```
### Pattern 2: Model Struct Holds All State
The `Model` struct in `model.go` holds ALL application state:
```go
type Model struct {
Screen Screen
PrevScreen Screen // For back navigation
Width int
Height int
Cursor int
// Add new state here
NewFeatureData []SomeType
NewFeatureScroll int
}
```
### Pattern 3: Update Pattern with Type Switch
All input handling goes through `Update()` with a type switch:
```go
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
return m.handleKeyPress(msg)
case tea.WindowSizeMsg:
m.Width = msg.Width
m.Height = msg.Height
return m, nil
case customMsg:
// Handle custom messages
return m, nil
}
return m, nil
}
```
### Pattern 4: Key Handlers Return (Model, Cmd)
Separate handler per screen, always return `(tea.Model, tea.Cmd)`:
```go
func (m Model) handleNewFeatureKeys(key string) (tea.Model, tea.Cmd) {
options := m.GetCurrentOptions()
switch key {
case "up", "k":
if m.Cursor > 0 {
m.Cursor--
// Skip separator
if strings.HasPrefix(options[m.Cursor], "───") && m.Cursor > 0 {
m.Cursor--
}
}
case "down", "j":
if m.Cursor < len(options)-1 {
m.Cursor++
if strings.HasPrefix(options[m.Cursor], "───") && m.Cursor < len(options)-1 {
m.Cursor++
}
}
case "enter", " ":
// Handle selection
return m.handleNewFeatureSelection()
case "esc":
m.Screen = m.PrevScreen
m.Cursor = 0
}
return m, nil
}
```
---
## Decision Tree
```
Adding a new screen?
├── Define Screen constant in model.go
├── Add state fields to Model struct
├── Add handler in handleKeyPress switch
├── Create handle{Screen}Keys function in update.go
├── Add view case in view.go
└── Add title in GetScreenTitle()
Adding navigation to existing screen?
├── Use m.PrevScreen for back navigation
├── Reset m.Cursor = 0 on screen change
└── Save scroll position if scrollable
Adding scrollable content?
├── Add {Screen}Scroll int to Model
├── Calculate visibleItems from m.Height
├── Handle up/down for scroll position
└── Reset scroll on screen exit
```
---
## Code Examples
### Example 1: Adding Screen to handleKeyPress
```go
// In handleKeyPress switch statement:
case ScreenNewFeature:
return m.handleNewFeatureKeys(key)
case ScreenNewFeatureCat:
return m.handleNewFeatureCatKeys(key)
```
### Example 2: Screen Options Pattern
```go
func (m Model) GetCurrentOptions() []string {
switch m.Screen {
case ScreenNewFeature:
categories := make([]string, len(m.NewFeatureData)+2)
for i, item := range m.NewFeatureData {
categories[i] = item.Name
}
categories[len(m.NewFeatureData)] = "─────────────"
categories[len(m.NewFeatureData)+1] = "← Back"
return categories
// ...
}
}
```
### Example 3: Scrollable View Pattern
```go
func (m Model) handleNewFeatureCatKeys(key string) (tea.Model, tea.Cmd) {
data := m.NewFeatureData[m.SelectedNewFeature]
visibleItems := m.Height - 9
if visibleItems < 5 {
visibleItems = 5
}
maxScroll := len(data.Items) - visibleItems
if maxScroll < 0 {
maxScroll = 0
}
switch key {
case "up", "k":
if m.NewFeatureScroll > 0 {
m.NewFeatureScroll--
}
case "down", "j":
if m.NewFeatureScroll < maxScroll {
m.NewFeatureScroll++
}
case "esc", "q", "enter", " ":
m.Screen = ScreenNewFeature
m.NewFeatureScroll = 0
}
return m, nil
}
```
### Example 4: Custom Message Pattern
```go
// Define message type
type newFeatureLoadedMsg struct {
data []SomeType
err error
}
// Send message from command
func loadNewFeatureCmd() tea.Cmd {
return func() tea.Msg {
data, err := loadData()
return newFeatureLoadedMsg{data: data, err: err}
}
}
// Handle in Update
case newFeatureLoadedMsg:
if msg.err != nil {
m.ErrorMsg = msg.err.Error()
return m, nil
}
m.NewFeatureData = msg.data
return m, nil
```
---
## Commands
```bash
cd installer && go build ./cmd/gentleman-installer # Build installer
cd installer && go test ./internal/tui/... # Run TUI tests
cd installer && go test -run TestNewFeature # Run specific test
```
---
## Resources
- **Model**: See `installer/internal/tui/model.go` for state management
- **Update**: See `installer/internal/tui/update.go` for input handling
- **View**: See `installer/internal/tui/view.go` for rendering
- **Styles**: See `installer/internal/tui/styles.go` for Lipgloss styles
More agent context in Gentleman-Programming/Gentleman.Dots
6 other files this repository gives its agents.
AGENTS.md
Skill
- gentleman-e2eskills/gentleman-e2e/SKILL.md
- gentleman-installerskills/gentleman-installer/SKILL.md
- gentleman-systemskills/gentleman-system/SKILL.md
- gentleman-trainerskills/gentleman-trainer/SKILL.md
- go-testingskills/go-testing/SKILL.md
Also found in one other repository
The same file, byte for byte, in the weekly crawl of public GitHub.
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

