Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Added
Changed
Fixed
Removed
0.5.0 - 2026-01-16
Added
Changed
-
BREAKING: Rebrand to
Rooibos: The gem is now namedrooibosand the module is top-levelRooibosinstead ofRatatuiRuby::Tea. Update your code, including: -
require "ratatui_ruby/tea"→require "rooibos" -
RatatuiRuby::Tea::*→Rooibos::* -
If you need a full migration guide, reach out to the mailing list
Fixed
Removed
0.4.0 - 2026-01-16
Added
-
Timer Commands:
Command.wait(seconds, tag)andCommand.tick(seconds, tag)for timed events. After the duration, sendstagto the update function. Responds to cancellation cooperatively — sendsCommand.cancel(self)when cancelled so you can handle it. UsesConcurrent::Cancellation.timeoutinternally.Command.tickis an alias forCommand.wait; the “recurring tick” pattern is achieved by re-dispatching in the update function. -
Command.uncancellable Factory: Creates a fresh
Concurrent::Cancellationthat never fires. Use for commands wrapping non-cancellable blocking I/O (e.g.,Net::HTTPrequests). -
Command.batch: Fire-and-forget parallel execution.
Command.batch(cmd1, cmd2)orCommand.batch([cmds])runs children in parallel; each child sends its own messages independently. On cancellation, emitsCommand.cancel(self)so you can detect the batch was stopped. Child errors surface asCommand::Errorso you can handle failures in your update function. One failing child does not stop the others. Requires all child commands to be Ractor-shareable. -
Command.all: Aggregating parallel execution.
Command.all(:tag, cmd1, cmd2)orCommand.all(:tag, [cmds])runs children in parallel and waits for all to complete, then sends a single aggregated message. Array syntax produces[:tag, [results]]; variadic syntax splats results as[:tag, result1, result2]. On cancellation, emitsCommand.cancel(self). Child errors surface asCommand::Error. Requires all child commands to be Ractor-shareable. -
Outlet#source: Command composition for multi-step workflows.
out.source(child_command, token, timeout: 30.0)runs a child command synchronously within a custom command, returning its result (ornilif cancelled/timed out). Exceptions from failed children propagate to the caller. Use this to orchestrate sequential API calls, conditional fetches, or any workflow that needs one result before starting the next. -
Command.http: Native HTTP client for API calls. Supports GET, POST, PUT, PATCH, DELETE with DWIM syntax:
Command.http(get: 'url'),Command.http(:get, 'url', :tag), etc. Returns hash-basedHttpResponsewithdeconstruct_keysfor pattern matching:{ type: :http, envelope:, status:, body:, headers: }or{ type: :http, envelope:, error: }. Optionalparser:keyword invokes a callable on the response body for JSON, YAML, CSV, or custom parsing. SSL, default 10s timeout, and cancellation-before-request are supported. Parsers and parsed results must be Ractor-shareable. -
Streaming Command Data Loss Fix: Fixed race condition in
Command.system(stream: true)where fast commands could lose stdout/stderr data. Reader threads nowjoininstead ofkillto ensure all output is processed before completion. -
Message::Predicates Mixin: Include in custom message types for safe predicate calls. Returns
falsefor any unknown predicate method (ending in?). Enables pattern matching workflows where messages can respond to predicates likemsg.http?ormsg.timer?without raisingNoMethodError. Includesrespond_to_missing?for introspection parity. -
Message::Timer: Predicate-rich response type for timer commands (
Command.wait,Command.tick). Includestimer?predicate anddeconstruct_keysfor pattern matching withtype: :timerdiscriminator. Containsenvelope(routing symbol) andelapsed(actual wait time in seconds). -
Message::HttpResponse: Moved from
Command::HttpResponsetoMessage::HttpResponsewith added predicates. Includeshttp?,success?, anderror?predicates. Implementsdeconstruct_keysfor pattern matching withtype: :httpdiscriminator. -
Message::System::Batch: Response type for
Command.system(batch mode). Includessystem?,success?(exit 0), anderror?(non-zero exit) predicates. Containsenvelope,stdout,stderr, andstatus. Implementsdeconstruct_keysfor pattern matching. -
Message::System::Stream: Response type for
Command.system(..., stream: true). Includessystem?,stdout?,stderr?, andcomplete?predicates. Containsenvelope,streamtype (:stdout,:stderr,:complete),content(for output lines), andstatus(for completion). Implements conditionaldeconstruct_keysbased on stream type. -
Message::All: Response type for
Command.allaggregating parallel execution. Includesall?predicate. Containsenvelope,resultsarray, andnestedboolean. Implementsdeconstruct_keysfor pattern matching. -
Command Parameter Rename: Renamed
tagparameter toenvelopeacross all commands for consistency:Command.wait,Command.tick,Command.system, andCommand.all. These now useenvelope:in their data definitions. Messages emit withenvelope:for pattern matching. -
Dependencies: Added
concurrent-ruby(~> 1.3) andconcurrent-ruby-edge(~> 0.7) for robust concurrency primitives. -
Rooibos.normalize_initHelper: NewRooibos.normalize_init(result)normalizes Init callable returns. Accepts the output of aFragment.Initcallable and always returns[model, command]. Use when composing child fragment initialization in parent fragments.
Changed
-
Terminology: “Bag” → “Fragment”: Renamed Fractal Architecture units from “bags” to “fragments” throughout the codebase. A fragment is a module containing
Model,INITIAL,UPDATE, andVIEWconstants. Parent fragments compose child fragments via routing. Theexamples/app_fractal_dashboard/bags/directory is nowexamples/app_fractal_dashboard/fragments/. All API documentation, code comments, and examples updated to reflect this terminology change. -
Runtime API Signature (Breaking):
Rooibos.runsignature changed: -
Fragment parameter is now positional instead of keyword:
Rooibos.run(MyApp)instead ofRooibos.run(fragment: MyApp) -
Removed
argv:andenv:parameters - Runtime now automatically usesARGVandENVglobals -
Added
fps:parameter (default 60) for configurable frame rate -
Renamed
init:parameter tocommand:in explicit parameters API for clarity -
Fragment Convention Rename (Breaking): Fragment constants have new naming conventions:
-
INITIALconstant →Initcallable. The runtime callsInit.()to get the initial model. -
UPDATEconstant →Updatecallable (capitalized). -
VIEWconstant →Viewcallable (capitalized). -
Init is now a lambda/callable instead of a frozen constant. This enables parameterized initialization and returning
[model, command]tuples for initial commands. -
Internal Method Rename (Breaking):
Runtime.normalize_update_resultrenamed toRuntime.normalize_update_returnfor clarity. Only affects code calling private Runtime internals. -
Command.custom Ractor Validation (Breaking):
Command.custom(callable)now validates in debug mode that the callable is Ractor-shareable. Callables that capture mutable state will raiseInvariant. Define callables at module level or useRactor.make_shareable. -
Model Validation Timing (Breaking): Runtime now validates model Ractor-shareability immediately after Init returns, not just during the Update cycle. This catches mutable models earlier, enforcing immutability at startup. Models must be frozen (
.freeze) or use immutable data structures (Data.define). This is a good breaking change that prevents subtle concurrency bugs. -
CancellationToken Replaced (Breaking): Custom commands now receive
Concurrent::Cancellationinstead ofCancellationToken. The method to check cancellation changes fromtoken.cancelled?(British) totoken.canceled?(American). -
Outlet Accepts Channel (Breaking):
Outlet.newnow accepts aConcurrent::Promises::Channelinstead ofThread::Queue. -
Outlet#put (Breaking):
out.put(msg), the common case now sendsmsgdirectly instead of[msg];out.put(a, b, c)sends[a, b, c]. Previously all calls wrapped arguments in an array. Update functions that matchedwhen Arraymay need adjustment. -
Command.all Output (Breaking):
Command.allnow emitsMessage::Allobjects instead of raw arrays. Previously emitted[:tag, [results]](nested) or[:tag, result1, result2](variadic). Now emitsMessage::Allwithenvelope,results, andnestedfields. Use hash pattern matching:in { type: :all, envelope:, results:, nested: }.
Fixed
-
Streaming Command Data Loss: Fixed race condition where fast commands (e.g.,
echo hello) could lose stdout/stderr messages. The streaming reader threads were being killed immediately after the child process exited, before they could finish reading buffered output. Now the runtime joins the reader threads to ensure all data is processed before sending:complete.
Removed
-
CancellationToken Class: Removed in favor of
Concurrent::Cancellationfrom concurrent-ruby-edge. -
CancellationToken::NONE: Removed. Use
Command.uncancellablefactory instead.
0.3.1 - 2026-01-11
Added
-
CancellationToken: Cooperative cancellation mechanism for long-running custom commands. Commands check
cancelled?periodically and stop gracefully whencancel!is called. IncludesCancellationToken::NONEnull object for commands that ignore cancellation. -
Command::Custom Mixin: Include in your class to mark it as a custom command. Provides
rooibos_command?brand predicate androoibos_cancellation_grace_period(default 2.0 seconds) for configuring cleanup time after cancellation. -
Command::Outlet: Messaging gateway for custom commands. Use
put(tag, *payload)to send results back to the update function. Validates Ractor-shareability in debug mode. -
Custom Command Dispatch: Runtime now dispatches custom commands (objects with
rooibos_command?returning true) in background threads. Commands receive anOutletfor messaging and aCancellationTokenfor cooperative shutdown. -
Command.custom Factory: Wraps lambdas/procs to give them unique identity for dispatch tracking. Each
Command.custom(callable)call produces a distinct wrapper, enabling targeted cancellation. Accepts optionalgrace_period:to override the default 2.0 second cleanup window. -
Command.cancel Factory: Request cancellation of a running command. Returns a
Command::Cancelsentinel that the runtime routes to the appropriate command’s CancellationToken. -
Runtime Cancellation Dispatch: The runtime now handles
Command::Cancelby signaling the target command’sCancellationToken, enabling cooperative cancellation of long-running commands. Respectsrooibos_cancellation_grace_period: waits for the grace period, then force-kills unresponsive threads. UseFloat::INFINITYto never force-kill. -
Graceful Shutdown: On exit, runtime signals all active commands then respects each command’s grace period. Commands with
Float::INFINITYgrace are waited on indefinitely (user has SIGKILL). Final queue messages are processed before returning. -
Automatic Error Propagation: Custom commands that raise unhandled exceptions now produce a
Command::Errormessage instead of corrupting the TUI display. The runtime catches exceptions and pushesCommand::Error.new(command:, exception:)to the queue. Pattern match onCommand::Errorin your update function to handle failures uniformly. Factory methodCommand.error(command, exception)is available for testing. -
Command::System Cancellation: Streaming shell commands now respect cooperative cancellation. When cancelled, sends
SIGTERMfor graceful shutdown, thenSIGKILLif the child process doesn’t exit. Prevents orphaned child processes from lingering after app exit.
Changed
Fixed
-
Ractor Enforcement is Debug-Only: The Ractor-shareability check now only runs in debug mode (and automated tests). Production skips this check for performance, matching the original specification. Previously, the check ran unconditionally.
Removed
0.3.0 - 2026-01-08
Added
-
Router DSL: New
Rooibos::Routermodule provides declarative routing for Fractal Architecture: -
route :prefix, to: ChildBag— declares a child bag route -
keymap { key "q", -> { Command.exit } }— declares keyboard handlers -
keymap { key "x", handler, when: -> (m) { m.ready? } }— guards (also:if:,only:,guard:,unless:,except:,skip:) -
keymap { only when: guard do ... end }— nested guard blocks apply to all keys within (also:skip when: ...) -
mousemap { click -> (x, y) { ... } }— declares mouse handlers -
action :name, handler— declares reusable actions for key/mouse handlers -
from_router— generates an UPDATE lambda from routes and handlers -
Composition Helpers: New helper methods for Fractal Architecture reduce boilerplate:
-
Rooibos.route(command, :prefix)— wraps a command to route results to a child bag -
Rooibos.delegate(message, :prefix, child_update, child_model)— dispatches prefixed messages to child bags -
Command Mapping:
Command.map(inner_command, &mapper)wraps a child command and transforms its result message. Essential for parent bags routing child command results. -
Shortcuts Module:
require "rooibos/shortcuts"andinclude Rooibos::Shortcutsfor short aliases: -
Cmd.exit— alias forCommand.exit -
Cmd.sh(command, tag)— alias forCommand.system -
Cmd.map(command, &block)— alias forCommand.map -
Sync Event Integration: Runtime now handles
Event::SyncfromRatatuiRuby::SyntheticEvents. When a Sync event is received, the runtime waits for all pending async threads and processes their results before continuing. Useinject_syncin tests for deterministic async verification. -
Streaming Command Output:
Command.systemnow accepts astream:keyword argument. Whenstream: true, the runtime sends incremental messages ([:tag, :stdout, line],[:tag, :stderr, line]) as output arrives, followed by[:tag, :complete, {status:}]when the command finishes. Invalid commands send[:tag, :error, {message:}]. Default behavior (stream: false) remains unchanged. -
Custom Shell Modal Example: Added
examples/app_fractal_dashboard/bags/custom_shell_modal.rbdemonstrating a 3-bag fractal architecture for a modal that runs arbitrary shell commands with streaming output. Features interleaved stdout/stderr, exit status indication, and Ractor-safe implementation usingtui.overlayfor opaque rendering.
Changed
-
Command Module Rename (Breaking): The
Cmdmodule is nowCommandwith Rubyish naming: -
Cmd::Quit→Command::Exit(useCommand.exitfactory) -
Cmd::Exec→Command::System(useCommand.system(cmd, tag)factory)
Fixed
Removed
0.2.0 - 2026-01-08
Added
-
The Elm Architecture (TEA): Implemented the core Model-View-Update (MVU) runtime. Use
Rooibos.run(model, view: ..., update: ...)to start an interactive application with predictable state management. -
Async Command System: Side effects (database, HTTP, shell) are executed asynchronously in a thread pool. Results are dispatched back to the main loop as messages, ensuring the UI never freezes.
-
Ractor Safety Enforcement: The runtime strictly enforces that all
ModelandMessageobjects are Ractor-shareable (deeply frozen). This guarantees thread safety by design and prepares for future parallelism. -
Flexible Update Returns: The
updatefunction supports multiple return signatures for developer ergonomics: -
[Model, Cmd]— Standard tuple. -
Model— Implicitly[Model, Cmd::None]. -
Cmd— Implicitly[CurrentModel, Cmd]. -
Startup Commands:
Rooibos.runaccepts aninit:parameter to dispatch an initial command immediately after startup, useful for loading initial data without blocking the first render. -
View Validation: The
viewfunction must return a valid widget. ReturningnilraisesRatatuiRuby::Error::Invariantto catch bugs early.
0.1.0 - 2026-01-07
Added
-
First Release: Empty release of
rooibos, a Ruby implementation of The Elm Architecture (TEA) forratatui_ruby. Scaffolding generated byratatui_ruby-devtools.