1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! # osteak — Elm for ratatui
//!
//! You bring the loop. osteak brings the structure.
//!
//! ## The Problem
//!
//! Every ratatui app that grows past demo complexity hits the "bag of
//! booleans" wall. State scatters across fields, transitions become
//! implicit, and race conditions emerge when async work outlives UI
//! signals.
//!
//! ## The Solution
//!
//! osteak brings the [Elm Architecture] to ratatui:
//!
//! - **Model** — your application state (the struct implementing [`Tea`])
//! - **Message** — an enum of everything that can happen ([`Tea::Msg`])
//! - **Update** — process a message, mutate state, return a [`Cmd`]
//! - **View** — render the current state to a ratatui [`Frame`]
//!
//! ## You Keep Your Event Loop
//!
//! osteak does **not** own your event loop. You call [`Tea::update`] and
//! [`Tea::view`] when you want. This lets you integrate with any async
//! runtime, any event source, any rendering strategy.
//!
//! For simple apps, the optional [`runner`] module provides a ready-made
//! event loop powered by crossterm and tokio.
//!
//! ## Quick Start
//!
//! ```rust,no_run
//! use osteak::{Tea, Cmd, Action};
//! 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());
//! }
//! }
//! ```
//!
//! [Elm Architecture]: https://guide.elm-lang.org/architecture/
//! [`Frame`]: ratatui::Frame
pub use Action;
pub use Cmd;
pub use Sub;
use Frame;
/// The core TEA trait. Implement this on your application state.
///
/// Your model (the struct implementing this trait) is the single source
/// of truth. All state lives here. [`update`](Tea::update) takes `&mut self` —
/// no cloning per event.
///
/// # Associated Types
///
/// - [`Msg`](Tea::Msg) — the message type. Must be `Send + 'static`
/// because [`Action::Task`] futures need to send messages across threads.
///
/// # Required Methods
///
/// - [`update`](Tea::update) — process a message, mutate state, return a [`Cmd`].
/// - [`view`](Tea::view) — render to a ratatui [`Frame`]. Takes `&mut self`
/// because ratatui's `StatefulWidget` pattern requires mutable access to
/// render state (scroll positions, list selection, etc.).
///
/// # Optional Methods
///
/// - [`init`](Tea::init) — return an [`Action`] to run at startup (default: no-op).
/// - [`subscriptions`](Tea::subscriptions) — return active [`Sub`]scriptions
/// for external event sources (default: none).
///
/// # Terminal Ownership
///
/// Your model must **not** own the `Terminal`. Keep the terminal in your
/// event loop and pass the [`Frame`] to [`view`](Tea::view) via
/// `terminal.draw(|f| model.view(f))`. This separation is enforced by the
/// borrow checker and is the universal ratatui pattern.