Claude-skill-registry gentleman-bubbletea
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/gentleman-bubbletea" ~/.claude/skills/majiayu000-claude-skill-registry-gentleman-bubbletea && rm -rf "$T"
manifest:
skills/data/gentleman-bubbletea/SKILL.mdsource content
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:
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:
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:
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):
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
// In handleKeyPress switch statement: case ScreenNewFeature: return m.handleNewFeatureKeys(key) case ScreenNewFeatureCat: return m.handleNewFeatureCatKeys(key)
Example 2: Screen Options Pattern
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
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
// 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
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
for state managementinstaller/internal/tui/model.go - Update: See
for input handlinginstaller/internal/tui/update.go - View: See
for renderinginstaller/internal/tui/view.go - Styles: See
for Lipgloss stylesinstaller/internal/tui/styles.go