Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    gentleman-programming

    gentleman-bubbletea

    gentleman-programming/gentleman-bubbletea
    Coding
    1,321
    4 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    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.

    SKILL.md

    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
    Recommended Servers
    Vercel Grep
    Vercel Grep
    Gemini
    Gemini
    Nimble MCP Server
    Nimble MCP Server
    Repository
    gentleman-programming/gentleman.dots
    Files