Construyendo interfaces de usuario de terminal con Charm
Si alguna vez has usado lazygit, k9s o htop, has experimentado cómo se siente una interfaz de usuario de terminal bien construida: receptiva, estructurada y sorprendentemente elegante. Construir algo así solía significar lidiar con ncurses o escribir códigos de escape en crudo. El ecosistema de Charm cambia eso por completo.
Este artículo explica cómo construir interfaces de usuario de terminal con Charm usando Go, enfocándose en Bubble Tea y sus bibliotecas complementarias. Las versiones principales actuales de las bibliotecas de Charm usan rutas de módulos de Go actualizadas, que verás reflejadas en los ejemplos a continuación. Si piensas en componentes y estado, el modelo mental te resultará inmediatamente familiar.
Puntos clave
- Bubble Tea sigue la arquitectura Elm (Model, Update, View), haciendo que la gestión de estado sea predecible y componible.
- Lip Gloss proporciona estilos declarativos para la salida de terminal, mientras que Bubbles ofrece componentes de interfaz de usuario listos para usar como entradas de texto, spinners y viewports.
- Las bibliotecas del ecosistema Charm se superponen de manera limpia, permitiéndote construir TUIs pulidas en Go sin códigos de escape en crudo o ncurses.
- Huh y Wish extienden el stack para formularios y TUIs alojadas en SSH, respectivamente.
¿Qué es una TUI y por qué construir una?
Una Interfaz de Usuario de Terminal (TUI) es una aplicación interactiva y visualmente estructurada que se ejecuta dentro de un terminal. A diferencia de una CLI simple que acepta un comando y sale, una TUI mantiene el estado, responde a la entrada del teclado en tiempo real y renderiza diseños dinámicos.
Vale la pena construir TUIs cuando quieres:
- Una herramienta de desarrollo que funcione por SSH sin navegador
- Menor consumo de recursos que una aplicación Electron
- Algo scriptable que aún se sienta pulido
El ecosistema Charm de un vistazo
Charm mantiene varias bibliotecas que funcionan bien juntas:
| Biblioteca | Función |
|---|---|
| Bubble Tea | Framework de aplicación (bucle de eventos, estado) |
| Lip Gloss | Estilos y diseño |
| Bubbles | Componentes de UI preconstruidos |
| Huh | Primitivas de formularios y entrada |
| Wish | Servidor SSH para alojar TUIs remotamente |
Bubble Tea es el núcleo. Todo lo demás se superpone sobre él.
Cómo funciona la arquitectura de Bubble Tea (Model, Update, View)
Bubble Tea sigue la arquitectura Elm, un patrón que los desarrolladores frontend reconocerán de Redux o el useReducer de React.
Cada aplicación de Bubble Tea define tres cosas:
- Model — el estado de tu aplicación
- Update — una función que maneja mensajes y devuelve un nuevo modelo
- View — una función que renderiza el modelo como una vista de terminal
package main
import (
"fmt"
"log"
tea "charm.land/bubbletea/v2"
)
type model struct {
count int
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "up":
m.count++
case "q", "ctrl+c":
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() tea.View {
return tea.NewView(fmt.Sprintf("Contador: %d\n(presiona arriba para incrementar, q para salir)", m.count))
}
func main() {
p := tea.NewProgram(model{})
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}
Observa la ruta de importación charm.land/bubbletea/v2. Las versiones actuales de las bibliotecas de Charm usan rutas de módulo charm.land en lugar de las importaciones antiguas github.com/charmbracelet/* que aparecen en muchos tutoriales anteriores.
Añadiendo diseños y estilos con Lip Gloss
Lip Gloss maneja los estilos. Defines estilos como valores y los aplicas a cadenas antes de renderizar.
import "charm.land/lipgloss/v2"
var titleStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FF79C6")).
Padding(0, 1)
func (m model) View() tea.View {
return tea.NewView(titleStyle.Render("Mi aplicación TUI"))
}
Nota: Las versiones recientes de Lip Gloss cambiaron cómo funciona el comportamiento de color adaptativo. La detección de fondo y la adaptación de estilos ahora se manejan de manera más explícita en el código de la aplicación en lugar de automáticamente por la biblioteca.
Discover how at OpenReplay.com.
Usando componentes preconstruidos de Bubbles
Bubbles proporciona componentes listos para usar: entradas de texto, spinners, barras de progreso, viewports y más. Cada componente sigue el mismo contrato Model/Update/View, por lo que puedes incrustarlos directamente en tu propio modelo.
import "charm.land/bubbles/v2/textinput"
type model struct {
input textinput.Model
}
Esta componibilidad es lo que hace que Bubble Tea escale de manera limpia. Construyes modelos pequeños y enfocados y los compones en otros más grandes, exactamente como componer componentes en un framework frontend.
Cuándo recurrir a Huh o Wish
Si tu TUI necesita formularios (entradas de múltiples campos, confirmaciones, menús de selección), Huh maneja eso sin que tengas que construirlo desde cero. Para alojar una TUI por SSH de modo que los usuarios puedan acceder sin instalar nada localmente, Wish envuelve tu programa Bubble Tea en un servidor SSH.
Comenzando
go mod init mytui
go get charm.land/bubbletea/v2
go get charm.land/lipgloss/v2
go get charm.land/bubbles/v2
Ejecuta tu programa con:
func main() {
p := tea.NewProgram(model{})
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}
Conclusión
El stack de Charm Bubble Tea te proporciona una forma estructurada y componible de construir aplicaciones de terminal en Go. El patrón Model/Update/View mantiene el estado predecible, Lip Gloss maneja los estilos de manera declarativa, y Bubbles te proporciona componentes que de otro modo pasarías días escribiendo. Si te sientes cómodo con el pensamiento frontend basado en componentes, construir interfaces de usuario de terminal con Charm se sentirá menos como aprender un nuevo paradigma y más como aplicar uno que ya conoces, solo que en un terminal.
Preguntas frecuentes
Bubble Tea es una biblioteca de Go, por lo que necesitas al menos una comprensión básica de Go para usarla. Dicho esto, la arquitectura inspirada en Elm es sencilla. Si estás familiarizado con conceptos como estado, mensajes y funciones de renderizado de cualquier lenguaje, puedes aprender la sintaxis específica de Go relativamente rápido.
Las versiones actuales usan rutas de módulo charm.land como charm.land/bubbletea/v2 en lugar de las importaciones antiguas github.com/charmbracelet que se encuentran en muchos tutoriales anteriores. La función Init devuelve solo un comando, y la función View devuelve una vista de terminal en lugar de una cadena en crudo.
Sí. Debido a que Update es una función pura que toma un mensaje y devuelve un nuevo modelo y comando, puedes hacer pruebas unitarias de la lógica de tu aplicación llamando a Update directamente con mensajes específicos y haciendo aserciones sobre el estado del modelo devuelto. La función View también se puede probar verificando su salida renderizada.
Sí. La biblioteca Wish te permite envolver cualquier programa de Bubble Tea en un servidor SSH. Los usuarios se conectan vía SSH e interactúan con tu TUI directamente en su terminal sin instalar nada localmente. Esto es útil para herramientas de desarrollo compartidas, dashboards o demos interactivas.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.