bubblon

package module
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 2 Imported by: 0

README

Bubblon

Release GoDoc Build License

The Bubblon Logo

Bubblon is a solution for managing nested Bubble Tea models (or views/screens). This is a typical use case in TUIs, such as navigating from a main list to a sublist when an item is selected. The "canonical" way is to use a view-switching model, where you keep track of the current view (e.g., main or sub) and the selected item, then render the appropriate model(s) for that view.

By contrast, Bubblon uses a "model stack" architecture, where the controller determines the active model. Instead of bloating a single Model with state for everything, each view is encapsulated in its own tea.Model with its own Update(), View(), and logic. The controller then pushes/pops models on the stack as the user navigates.

Read the blog post for a more architectural point of view.

Features
  • Modular: Each view is self-contained. Rendering another model is one line away.
  • Reusability of sub-models due to way less coupling between models.
  • Easier to reason about, especially when state gets complex. Instead of adding more and more state to manage, it can be passed to the new model so that each model is responsible only for its own state.
  • No new interfaces: Keeps complexity low by avoiding new abstractions (the models should implement the tea.Model interface only).

Note that the stack approach is suitable only for "fullscreen" views or views that render on top of each other.

Installation

To install Bubblon, use go get:

go get github.com/donderom/bubblon/v2@latest

The latest version of Bubblon supports Bubble Tea v2, to use it with Bubble Tea v1:

go get github.com/donderom/bubblon@v1.2.1

Import the bubblon package into your code:

import "github.com/donderom/bubblon"

Usage

To run the controller, update the Bubble Tea program initialization from:

mainModel := MainModel.New()
program := tea.NewProgram(mainModel)

to:

...
import "github.com/donderom/bubblon"
 
mainModel := MainModel.New()
controller, err := bubblon.New(mainModel)
program := tea.NewProgram(controller)

At any point within the MainModel, you can open a new model by sending a bubblon.Open() command:

func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	...
	return m, bubblon.Open(SubModel.New())
}

There are no requirements other than for MainModel and SubModel to implement the tea.Model interface.

To close the current view and return to the previous one, send the bubblon.Close command:

func (m SubModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	...
	return m, bubblon.Close
}

When the SubModel is closed, the MainModel will receive a bubblon.Closed message.

The whole navigation is based on these two commands.

Model replacement

The bubblon.Replace command replaces the current model with a new one:

func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	...
	return m, bubblon.Replace(newModel)
}

It can be thought of as a combination of bubblon.Close followed by bubblon.Open, but it's internally optimized for performance.

While bubblon.Replace replaces only the current model, bubblon.ReplaceAll replaces all models with a new one:


func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	...
	return m, bubblon.ReplaceAll(newModel)
}
Error propagation

Sending the bubblon.Fail message will cause the app to exit and populate the Err field, which can be accessed after the program terminates for logging, etc.

From any model managed by bubblon:

func (m SomeModel) Update(msg tea.Msg) (tea.Mode, tea.Cmd) {
	...
	err := ...
	if err != nil {
		return m, bubblon.Fail(err)
	}
}

Handle the error after program termination:

import "github.com/donderom/bubblon"

controller, _ := bubblon.New(SomeModel.New())
program := tea.NewProgram(controller, tea.WithAltScreen())
m, _ := p.Run()

if m, ok := m.(bubblon.Controller); ok && m.Err != nil {
	log.Fatal(m.Err)
}
Send a message on open/close

To send a message to a newly opened model or to the parent model after closing a nested one, use the regular tea.Sequence command:

return m, tea.Sequence(bubblon.Open(model), messageOnOpen)
return m, tea.Sequence(bubblon.Close, messageOnClose)

Used By

  • sqwat - A TUI editor for files in the Stanford Question Answering Dataset (SQuAD) format.

Know a project using this library? Open a pull request and add it here!

License

MIT

Documentation

Overview

Package bubblon enables a model-stack architecture for Bubble Tea apps.

Index

Constants

This section is empty.

Variables

View Source
var ErrNilModel = errors.New("model cannot be nil")

ErrNilModel is returned when attempting to initialize a Controller with a nil model.

Functions

func Close

func Close() tea.Msg

Close is a command instructing bubblon to close the current model. A notification to the parent model is sent on closure.

func Cmd

func Cmd(msg tea.Msg) tea.Cmd

Cmd is a helper function that wraps a tea.Msg as a tea.Cmd. Should be used only for static messages.

func Fail

func Fail(err error) tea.Cmd

Fail is a command to propagate the error to the controller and quit the app.

func Open

func Open(model tea.Model) tea.Cmd

Open is a command to push a new model onto the stack. The new model will become the active model receiving updates and rendering.

func Replace

func Replace(model tea.Model) tea.Cmd

Replace combines closing the current model and opening a new one in a single command.

func ReplaceAll

func ReplaceAll(model tea.Model) tea.Cmd

ReplaceAll closes all the models and opens a new one in a single command.

Types

type Closed

type Closed struct{}

Closed is a message sent to the parent model indicating that the top model has been closed. The message is not sent if the model is replaced.

type Controller

type Controller struct {
	Err error
	// contains filtered or unexported fields
}

Controller implements a stack-based navigation model for Bubble Tea apps. It manages a stack of tea.Model instances, where only the top model receives updates and renders. In case of the Fail message, Err is set to the error from the message.

func New

func New(model tea.Model) (Controller, error)

New creates a new Controller initialized with the given model. Returns an error if the model is nil.

func (Controller) Init

func (c Controller) Init() tea.Cmd

Init initializes the initial model, if one exists.

func (Controller) Update

func (c Controller) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update handles incoming messages to update the state and delegate messages to the top model.

func (Controller) View

func (c Controller) View() tea.View

View renders the view of the top model on the stack. Returns an empty string if there is no model.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL