Skip to content

os-tack/osteak

Repository files navigation

osteak

Crates.io docs.rs CI License

Elm Architecture for ratatui. We built this because our TUI kept accumulating state bugs as it grew — booleans disagreeing, async tasks outliving the UI state that spawned them, the usual. TEA fixed it for us. Maybe it helps you too.

You bring the event loop. osteak gives you update, view, and a Cmd type to describe side effects. There's an optional runner if you don't have a loop yet.

Quick Start

cargo add osteak
use osteak::{Tea, Cmd};
use ratatui::Frame;
use ratatui::widgets::Paragraph;

struct Counter { count: i32 }

enum Msg { Increment, Decrement, Quit }

impl Tea for Counter {
    type Msg = Msg;

    fn update(&mut self, msg: Msg) -> Cmd<Msg> {
        match msg {
            Msg::Increment => { self.count += 1; Cmd::dirty() }
            Msg::Decrement => { self.count -= 1; Cmd::dirty() }
            Msg::Quit => Cmd::quit(),
        }
    }

    fn view(&mut self, frame: &mut Frame) {
        let text = format!("Count: {}", self.count);
        frame.render_widget(Paragraph::new(text), frame.area());
    }
}

With the built-in runner

# use osteak::{Tea, Cmd};
# use ratatui::Frame;
# use ratatui::widgets::Paragraph;
# struct Counter { count: i32 }
# enum Msg { Increment, Decrement, Quit }
# impl Tea for Counter {
#     type Msg = Msg;
#     fn update(&mut self, msg: Msg) -> Cmd<Msg> { Cmd::none() }
#     fn view(&mut self, frame: &mut Frame) {}
# }
use crossterm::event::{Event, KeyCode, KeyEventKind};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    osteak::runner::run(Counter { count: 0 }, |ev| {
        let Event::Key(k) = ev else { return None };
        if k.kind != KeyEventKind::Press { return None; }
        match k.code {
            KeyCode::Up => Some(Msg::Increment),
            KeyCode::Down => Some(Msg::Decrement),
            KeyCode::Char('q') => Some(Msg::Quit),
            _ => None,
        }
    }).await
}

Without the runner

Write your own loop — osteak doesn't take control. See examples/counter_manual.rs.

Architecture

    ┌──────────────────────┐
    │    Your Event Loop    │
    │  (or osteak::runner)  │
    └──────┬───────────────┘
           │ Msg
    ┌──────▼───────────────┐
    │   Tea::update(&mut)   │
    │   → Cmd { action,     │
    │         dirty }       │
    └──────┬───────────────┘
           │
  ┌────────┼────────┐
  │        │        │
Task     None     Quit
(you spawn)
  │
  │ Msg (on completion)
  └──────► back to update

Features

Feature Default Description
crossterm-backend yes Crossterm backend + EventStream
tokio-runtime yes Tokio integration for the runner

Just the traits, no runtime:

cargo add osteak --no-default-features

Examples

cargo run --example counter

MSRV

1.86.0 (same as ratatui 0.30).

License

MIT

About

Elm for ratatui — you bring the loop

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages