| Crates.io | tears |
| lib.rs | tears |
| version | 0.8.0 |
| created_at | 2025-12-29 17:44:34.681425+00 |
| updated_at | 2026-01-22 06:10:19.464794+00 |
| description | A simple and elegant framework for building TUI applications using The Elm Architecture (TEA) |
| homepage | https://github.com/akiomik/tears |
| repository | https://github.com/akiomik/tears |
| max_upload_size | |
| id | 2011036 |
| size | 363,001 |
A simple and elegant framework for building TUI applications using The Elm Architecture (TEA).
Built on top of ratatui, Tears provides a clean, type-safe, and functional approach to terminal user interface development.
Add this to your Cargo.toml:
[dependencies]
tears = "0.8"
ratatui = "0.30"
crossterm = "0.29"
tokio = { version = "1", features = ["full"] }
See the Optional Features section for information about enabling ws (WebSocket) and http (HTTP Query/Mutation) features.
Every tears application implements the Application trait with four required methods:
use tears::prelude::*;
use ratatui::Frame;
struct App;
enum Message {}
impl Application for App {
type Message = Message; // Your message type
type Flags = (); // Initialization data (use () if none)
// Initialize your app
fn new(_flags: ()) -> (Self, Command<Message>) {
(App, Command::none())
}
// Handle messages and update state
fn update(&mut self, _msg: Message) -> Command<Message> {
Command::none()
}
// Render your UI
fn view(&self, frame: &mut Frame) {
// Use ratatui widgets here
}
// Subscribe to events (keyboard, timers, etc.)
fn subscriptions(&self) -> Vec<Subscription<Message>> {
vec![]
}
}
To run your application, create an Runtime and call run():
#[tokio::main]
async fn main() -> Result<()> {
let runtime = Runtime::<App>::new((), 60);
// Setup terminal (see complete example below)
// ...
runtime.run(&mut terminal).await?;
Ok(())
}
Here's a simple counter application that increments every second:
use color_eyre::eyre::Result;
use crossterm::event::{Event, KeyCode};
use ratatui::{Frame, text::Text};
use tears::prelude::*;
use tears::subscription::{terminal::TerminalEvents, time::{Message as TimerMessage, Timer}};
#[derive(Debug, Clone)]
enum Message {
Tick,
Input(Event),
InputError(String),
}
struct Counter {
count: u32,
}
impl Application for Counter {
type Message = Message;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
(Counter { count: 0 }, Command::none())
}
fn update(&mut self, msg: Message) -> Command<Message> {
match msg {
Message::Tick => {
self.count += 1;
Command::none()
}
Message::Input(Event::Key(key)) if key.code == KeyCode::Char('q') => {
Command::effect(Action::Quit)
}
Message::InputError(e) => {
eprintln!("Input error: {e}");
Command::effect(Action::Quit)
}
_ => Command::none(),
}
}
fn view(&self, frame: &mut Frame) {
let text = Text::raw(format!("Count: {} (Press 'q' to quit)", self.count));
frame.render_widget(text, frame.area());
}
fn subscriptions(&self) -> Vec<Subscription<Message>> {
vec![
Subscription::new(Timer::new(1000)).map(|timer_msg| {
match timer_msg {
TimerMessage::Tick => Message::Tick,
}
}),
Subscription::new(TerminalEvents::new()).map(|result| match result {
Ok(event) => Message::Input(event),
Err(e) => Message::InputError(e.to_string()),
}),
]
}
}
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;
// Setup terminal
let mut terminal = ratatui::init();
// Run application at 60 FPS
let runtime = Runtime::<Counter>::new((), 60);
let result = runtime.run(&mut terminal).await;
// Restore terminal
ratatui::restore();
result
}
Tears follows The Elm Architecture (TEA) pattern:
ββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β βββββββββββ ββββββββββ ββββββββ β
β β Model βββββββΆβ View βββββββΆβ UI β β
β βββββββββββ ββββββββββ ββββββββ β
β β² β
β β β
β ββββββ΄ββββββ ββββββββββββββββ β
β β Update βββββββ Messages β β
β ββββββββββββ ββββββββββββββββ β
β β² β² β
β β β β
β ββββββ΄ββββββ ββββββββ΄βββββββ β
β β Commands β βSubscriptionsβ β
β ββββββββββββ βββββββββββββββ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββ
terminal::TerminalEvents): Keyboard, mouse, and resize eventstime::Timer): Periodic tick eventssignal::Signal): OS signal handling (Unix/Windows)websocket::WebSocket, requires ws): Real-time bidirectional communicationhttp::Query, requires http): HTTP data fetching with cachinghttp::Mutation, requires http): HTTP data modificationsmock::MockSource): Controllable mock for testingCreate custom subscriptions by implementing the SubscriptionSource trait.
Check out the examples/ directory for more examples:
counter.rs - A simple counter with timer and keyboard inputviews.rs - Multiple view states with navigation and conditional subscriptionssignals.rs - OS signal handling with graceful shutdown (SIGINT, SIGTERM, etc.)websocket.rs - WebSocket echo chat demonstrating real-time communication (requires ws feature)http_todo.rs - HTTP Todo list with Query subscription, Mutation, and cache management (requires http feature)Run an example:
cargo run --example counter
cargo run --example views
cargo run --example signals
cargo run --example websocket --features ws,rustls
cargo run --example http_todo --features http
Tears supports optional features that can be enabled in your Cargo.toml:
[dependencies]
tears = { version = "0.8", features = ["ws", "rustls"] }
ws: Enables WebSocket subscription supportwss:// support):
native-tls - Platform's native TLSrustls - Pure Rust TLS with native certificatesrustls-tls-webpki-roots - Pure Rust TLS with webpki certificates[dependencies]
tears = { version = "0.8", features = ["http"] }
http: Enables HTTP Query and Mutation support
Query subscription for automatic data fetching with cachingMutation for data modifications (POST, PUT, PATCH, DELETE)QueryClient for cache management and invalidationTears is inspired by battle-tested architectures:
The framework is designed with these principles:
Tears requires Rust 1.86.0 or later (uses edition 2024).
Licensed under the Apache License, Version 2.0. See LICENSE for details.
Contributions are welcome! Please feel free to submit issues or pull requests.
Built with β€οΈ using ratatui