Claude Code Plugins

Community-maintained marketplace

Feedback

>

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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 [object Object]

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 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