원클릭으로
tui-bubbletea-architecture
Building terminal UIs in Go
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Building terminal UIs in Go
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Index of Build Systems Skills
Coordination patterns for distributed dataflow systems including barriers, epochs, and distributed snapshots
Windowing, sessionization, time-series aggregation, and late data handling for streaming systems
Comprehensive guide to GNU Debugger (GDB) for debugging C/C++/Rust programs. Covers breakpoints, stack traces, variable inspection, TUI mode, .gdbinit customization, Python scripting, remote debugging, and core file analysis.
Paxos consensus algorithm including Basic Paxos, Multi-Paxos, roles, phases, and practical implementations
Gossip protocols for disseminating information, failure detection, and eventual consistency in large-scale distributed systems
| name | tui-bubbletea-architecture |
| description | Building terminal UIs in Go |
Use this skill when:
The Elm Architecture (Model-Update-View):
package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
)
// Model holds application state
type model struct {
count int
}
// Init returns initial command
func (m model) Init() tea.Cmd {
return nil
}
// Update handles messages and returns new model
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "up", "k":
m.count++
case "down", "j":
if m.count > 0 {
m.count--
}
}
}
return m, nil
}
// View renders the UI
func (m model) View() string {
return fmt.Sprintf(
"Count: %d\n\n"+
"Press ↑/k to increment, ↓/j to decrement\n"+
"Press q to quit\n",
m.count,
)
}
func main() {
p := tea.NewProgram(model{count: 0})
if _, err := p.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
type tickMsg time.Time
type fetchedDataMsg struct {
data string
err error
}
// Command that sends tick message every second
func tickEvery() tea.Cmd {
return tea.Tick(time.Second, func(t time.Time) tea.Msg {
return tickMsg(t)
})
}
// Command that fetches data
func fetchData() tea.Cmd {
return func() tea.Msg {
data, err := callAPI()
return fetchedDataMsg{data: data, err: err}
}
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tickMsg:
m.lastTick = time.Time(msg)
return m, tickEvery()
case fetchedDataMsg:
if msg.err != nil {
m.error = msg.err
return m, nil
}
m.data = msg.data
return m, nil
case tea.KeyMsg:
if msg.String() == "r" {
return m, fetchData()
}
}
return m, nil
}
type model struct {
// UI state
activeTab int
cursor int
viewport viewport.Model
// Data state
items []Item
loading bool
error error
// Sub-models
input textinput.Model
list list.Model
spinner spinner.Model
}
func (m model) Init() tea.Cmd {
return tea.Batch(
m.spinner.Tick,
loadItems(),
)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "tab":
m.activeTab = (m.activeTab + 1) % 3
case "enter":
return m, submitForm(m.input.Value())
}
case loadedItemsMsg:
m.items = msg.items
m.loading = false
return m, nil
case spinner.TickMsg:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
cmds = append(cmds, cmd)
}
// Update sub-models
var cmd tea.Cmd
m.input, cmd = m.input.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m model) View() string {
var s strings.Builder
// Header
s.WriteString(headerStyle.Render("My Application"))
s.WriteString("\n\n")
// Tabs
s.WriteString(m.renderTabs())
s.WriteString("\n\n")
// Content based on active tab
switch m.activeTab {
case 0:
s.WriteString(m.renderHomeTab())
case 1:
s.WriteString(m.renderSearchTab())
case 2:
s.WriteString(m.renderSettingsTab())
}
// Footer
s.WriteString("\n\n")
s.WriteString(footerStyle.Render("Press q to quit"))
return s.String()
}
func (m model) renderTabs() string {
tabs := []string{"Home", "Search", "Settings"}
var renderedTabs []string
for i, tab := range tabs {
if i == m.activeTab {
renderedTabs = append(renderedTabs, activeTabStyle.Render(tab))
} else {
renderedTabs = append(renderedTabs, inactiveTabStyle.Render(tab))
}
}
return lipgloss.JoinHorizontal(lipgloss.Top, renderedTabs...)
}
type loadingMsg struct{}
type loadedMsg struct {
items []Item
}
type errorMsg struct {
err error
}
func loadItems() tea.Cmd {
return func() tea.Msg {
items, err := fetchItemsFromAPI()
if err != nil {
return errorMsg{err: err}
}
return loadedMsg{items: items}
}
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "r" {
m.loading = true
return m, loadItems()
}
case loadedMsg:
m.items = msg.items
m.loading = false
return m, nil
case errorMsg:
m.error = msg.err
m.loading = false
return m, nil
}
return m, nil
}
func (m model) View() string {
if m.loading {
return m.spinner.View() + " Loading..."
}
if m.error != nil {
return errorStyle.Render(fmt.Sprintf("Error: %v", m.error))
}
return m.renderItems()
}
type page int
const (
listPage page = iota
detailPage
editPage
)
type model struct {
currentPage page
pageStack []page
selectedID int
}
func (m model) navigateTo(p page) (model, tea.Cmd) {
m.pageStack = append(m.pageStack, m.currentPage)
m.currentPage = p
return m, nil
}
func (m model) navigateBack() (model, tea.Cmd) {
if len(m.pageStack) == 0 {
return m, tea.Quit
}
m.currentPage = m.pageStack[len(m.pageStack)-1]
m.pageStack = m.pageStack[:len(m.pageStack)-1]
return m, nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "esc":
return m.navigateBack()
case "enter":
if m.currentPage == listPage {
return m.navigateTo(detailPage)
}
}
}
return m, nil
}
func (m model) View() string {
switch m.currentPage {
case listPage:
return m.renderList()
case detailPage:
return m.renderDetail()
case editPage:
return m.renderEdit()
default:
return "Unknown page"
}
}
func TestUpdate(t *testing.T) {
m := model{count: 5}
// Test increment
m, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}})
if m.(model).count != 6 {
t.Errorf("Expected count 6, got %d", m.(model).count)
}
// Test decrement
m, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}})
if m.(model).count != 5 {
t.Errorf("Expected count 5, got %d", m.(model).count)
}
}
DON'T mutate model directly in View:
// ❌ BAD
func (m model) View() string {
m.count++ // Never mutate in View!
return fmt.Sprintf("Count: %d", m.count)
}
// ✅ GOOD
func (m model) View() string {
return fmt.Sprintf("Count: %d", m.count)
}
DON'T block in Update:
// ❌ BAD
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
time.Sleep(5 * time.Second) // Blocks UI!
return m, nil
}
// ✅ GOOD
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, doAsyncWork() // Return command instead
}
func doAsyncWork() tea.Cmd {
return func() tea.Msg {
time.Sleep(5 * time.Second)
return workCompleteMsg{}
}
}