bubbletea

安装量: 119
排名: #7212

安装

npx skills add https://github.com/ggprompts/tfe --skill bubbletea

Production-ready skill for building beautiful terminal user interfaces with Go, Bubbletea, and Lipgloss.

When to Use This Skill

Use this skill when:

  • Creating new TUI applications with Go

  • Adding Bubbletea components to existing apps

  • Fixing layout/rendering issues (borders, alignment, overflow)

  • Implementing mouse/keyboard interactions

  • Building dual-pane or multi-panel layouts

  • Adding visual effects (metaballs, waves, rainbow text)

  • Troubleshooting TUI rendering problems

Core Principles

CRITICAL: Before implementing ANY layout, consult references/golden-rules.md for the 4 Golden Rules. These rules prevent the most common and frustrating TUI layout bugs.

The 4 Golden Rules (Summary)

  • Always Account for Borders - Subtract 2 from height calculations BEFORE rendering panels

  • Never Auto-Wrap in Bordered Panels - Always truncate text explicitly

  • Match Mouse Detection to Layout - Use X coords for horizontal, Y coords for vertical

  • Use Weights, Not Pixels - Proportional layouts scale perfectly

Full details and examples in references/golden-rules.md.

Creating New Projects

This project includes a production-ready template system. When this skill is bundled with a new project (via new_project.sh), use the existing template structure as the starting point.

Project Structure

All new projects follow this architecture:

your-app/
├── main.go              # Entry point (minimal, ~21 lines)
├── types.go             # Type definitions, structs, enums
├── model.go             # Model initialization & layout calculation
├── update.go            # Message dispatcher
├── update_keyboard.go   # Keyboard handling
├── update_mouse.go      # Mouse handling
├── view.go              # View rendering & layouts
├── styles.go            # Lipgloss style definitions
├── config.go            # Configuration management
└── .claude/skills/bubbletea/  # This skill (bundled)

Architecture Guidelines

  • Keep main.go minimal (entry point only, ~21 lines)

  • All types in types.go (structs, enums, constants)

  • Separate keyboard and mouse handling into dedicated files

  • One file, one responsibility

  • Maximum file size: 800 lines (ideally <500)

  • Configuration via YAML with hot-reload support

Available Components

See references/components.md for the complete catalog of reusable components:

  • Panel System: Single, dual-pane, multi-panel, tabbed layouts

  • Lists: Simple list, filtered list, tree view

  • Input: Text input, multiline, forms, autocomplete

  • Dialogs: Confirm, input, progress, modal

  • Menus: Context menu, command palette, menu bar

  • Status: Status bar, title bar, breadcrumbs

  • Preview: Text, markdown, syntax highlighting, images, hex

  • Tables: Simple and interactive tables

Effects Library

Beautiful physics-based animations available in the template:

  • 🔮 Metaballs - Lava lamp-style floating blobs

  • 🌊 Wave Effects - Sine wave distortions

  • 🌈 Rainbow Cycling - Animated color gradients

  • 🎭 Layer Compositor - ANSI-aware multi-layer rendering

See references/effects.md for usage examples and integration patterns.

Layout Implementation Pattern

When implementing layouts, follow this sequence:

1. Calculate Available Space

func (m model) calculateLayout() (int, int) {
    contentWidth := m.width
    contentHeight := m.height

    // Subtract UI elements
    if m.config.UI.ShowTitle {
        contentHeight -= 3  // title bar (3 lines)
    }
    if m.config.UI.ShowStatus {
        contentHeight -= 1  // status bar
    }

    // CRITICAL: Account for panel borders
    contentHeight -= 2  // top + bottom borders

    return contentWidth, contentHeight
}

2. Use Weight-Based Panel Sizing

// Calculate weights based on focus/accordion mode
leftWeight, rightWeight := 1, 1
if m.accordionMode && m.focusedPanel == "left" {
    leftWeight = 2  // Focused panel gets 2x weight
}

// Calculate actual widths from weights
totalWeight := leftWeight + rightWeight
leftWidth := (availableWidth * leftWeight) / totalWeight
rightWidth := availableWidth - leftWidth

3. Truncate Text to Prevent Wrapping

// Calculate max text width to prevent wrapping
maxTextWidth := panelWidth - 4  // -2 borders, -2 padding

// Truncate ALL text before rendering
title = truncateString(title, maxTextWidth)
subtitle = truncateString(subtitle, maxTextWidth)

func truncateString(s string, maxLen int) string {
    if len(s) <= maxLen {
        return s
    }
    return s[:maxLen-1] + "…"
}

Mouse Interaction Pattern

Always check layout mode before processing mouse events:

func (m model) handleLeftClick(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
    if m.shouldUseVerticalStack() {
        // Vertical stack mode: use Y coordinates
        topHeight, _ := m.calculateVerticalStackLayout()
        relY := msg.Y - contentStartY

        if relY < topHeight {
            m.focusedPanel = "left"  // Top panel
        } else {
            m.focusedPanel = "right" // Bottom panel
        }
    } else {
        // Side-by-side mode: use X coordinates
        leftWidth, _ := m.calculateDualPaneLayout()

        if msg.X < leftWidth {
            m.focusedPanel = "left"
        } else {
            m.focusedPanel = "right"
        }
    }

    return m, nil
}

Common Pitfalls to Avoid

See references/troubleshooting.md for detailed solutions to common issues:

❌ DON'T: Set explicit Height() on bordered panels

// BAD: Can cause misalignment
panelStyle := lipgloss.NewStyle().
    Border(border).
    Height(height)  // Don't do this!

✅ DO: Fill content to exact height

// GOOD: Fill content lines to exact height
for len(lines) < innerHeight {
    lines = append(lines, "")
}
panelStyle := lipgloss.NewStyle().Border(border)

Testing and Debugging

When panels don't align or render incorrectly:

  • Check height accounting - Verify contentHeight calculation subtracts all UI elements + borders

  • Check text wrapping - Ensure all strings are truncated to maxTextWidth

  • Check mouse detection - Verify X/Y coordinate usage matches layout orientation

  • Check border consistency - Use same border style for all panels

See references/troubleshooting.md for the complete debugging decision tree.

Configuration System

All projects support YAML configuration with hot-reload:

theme: "dark"
keybindings: "default"

layout:
  type: "dual_pane"
  split_ratio: 0.5
  accordion_mode: true

ui:
  show_title: true
  show_status: true
  mouse_enabled: true
  show_icons: true

Configuration files are loaded from:

  • ~/.config/your-app/config.yaml (user config)

  • ./config.yaml (local override)

Dependencies

Required:

github.com/charmbracelet/bubbletea
github.com/charmbracelet/lipgloss
github.com/charmbracelet/bubbles
gopkg.in/yaml.v3

Optional (uncomment in go.mod as needed):

github.com/charmbracelet/glamour       # Markdown rendering
github.com/charmbracelet/huh           # Forms
github.com/alecthomas/chroma/v2        # Syntax highlighting
github.com/evertras/bubble-table       # Interactive tables
github.com/koki-develop/go-fzf         # Fuzzy finder

Reference Documentation

All reference files are loaded progressively as needed:

  • golden-rules.md - Critical layout patterns and anti-patterns

  • components.md - Complete catalog of reusable components

  • troubleshooting.md - Common issues and debugging decision tree

  • emoji-width-fix.md - Battle-tested solution for emoji alignment across terminals (xterm, WezTerm, Termux, Windows Terminal)

External Resources

Best Practices Summary

  • Always consult golden-rules.md before implementing layouts

  • Always use weight-based sizing for flexible layouts

  • Always truncate text explicitly (never rely on auto-wrap)

  • Always match mouse detection to layout orientation

  • Always account for borders in height calculations

  • Never set explicit Height() on bordered Lipgloss styles

  • Never assume layout orientation in mouse handlers

Follow these patterns and you'll avoid 90% of TUI layout bugs.

返回排行榜