charmed-bubbletea 0.1.2

A powerful TUI framework based on The Elm Architecture
Documentation

Bubbletea

An Elm-architecture TUI framework for Rust: pure update/view, command-driven effects, and a deterministic event loop.

TL;DR

The Problem: Terminal apps often mix state mutation and I/O in ways that are hard to test and reason about.

The Solution: Bubbletea separates pure state transitions from effects. Model + Message + Cmd gives you a testable core and a predictable runtime.

Why Bubbletea

  • Predictable: pure update and view functions.
  • Composable: child models compose cleanly.
  • Testable: drive updates with messages and assert on views.
  • Async-ready: optional async runtime support.

Role in the charmed_rust (FrankenTUI) stack

Bubbletea is the runtime core. bubbles, huh, glow, and wish all build on bubbletea’s Model abstraction. lipgloss styles the output, and harmonica provides time-based motion helpers used in animations.

Crates.io package

Package name: charmed-bubbletea
Library crate name: bubbletea

Installation

[dependencies]
bubbletea = { package = "charmed-bubbletea", version = "0.1.2" }

Enable optional features:

bubbletea = { package = "charmed-bubbletea", version = "0.1.2", features = ["async", "macros"] }

Quick Start

use bubbletea::{Cmd, Message, Model, Program};

struct Counter {
    count: i32,
}

impl Model for Counter {
    fn update(&mut self, msg: Message) -> Cmd {
        if let Some(delta) = msg.downcast_ref::<i32>() {
            self.count += delta;
        }
        Cmd::none()
    }

    fn view(&self) -> String {
        format!("Count: {}", self.count)
    }
}

fn main() {
    Program::new(Counter { count: 0 }).run().unwrap();
}

Core Concepts

  • Model: your application state and logic.
  • Message: events that drive state transitions.
  • Cmd: effect descriptions (I/O, timers, async work).
  • Program: event loop runner.

API Overview

Key types live in crates/bubbletea/src/:

  • model.rs: Model trait and lifecycle hooks.
  • message.rs: Message type and downcasting helpers.
  • cmd.rs: command helpers (Cmd::none, Cmd::batch, Cmd::quit).
  • program.rs: event loop and runner configuration.
  • key.rs: key event types and helpers.

Feature Flags

  • macros: derive macros for Model via bubbletea-macros.
  • async: async runtime integration (tokio-based).
  • thread-pool: batch commands with rayon.

Testing Guidance

Test update and view directly:

let mut app = Counter { count: 0 };
app.update(Message::new(1));
assert_eq!(app.view(), "Count: 1");

For headless runs:

let model = Program::new(app).without_renderer().run().unwrap();

Troubleshooting

  • No input events: ensure the program is running in a tty.
  • Output garbled: disable alternate screen or set a compatible TERM.
  • Async commands not running: enable the async feature and use tokio.

Limitations

  • Terminal rendering is bound by terminal capabilities.
  • High-frequency animation depends on your event loop tick rate.

FAQ

Can I use bubbletea without lipgloss?
Yes. Bubbletea renders strings; you can render them any way you want.

Do I need the macros crate?
No. It’s optional and only adds ergonomics.

Is async required?
No. Synchronous apps work out of the box.

About Contributions

Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via gh and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.

License

MIT. See LICENSE at the repository root.