module RatatuiRuby::Tea::Command
Commands represent side effects.
The MVU pattern separates logic from effects. Your update function returns a pure model transformation. Side effects go in commands. The runtime executes them.
Commands produce messages, not callbacks. The tag argument names the message so your update function can pattern-match on it. This keeps all logic in update and ensures messages are Ractor-shareable.
Examples
# Terminate the application [model, Command.exit] # Run a shell command; produces [:got_files, {stdout:, stderr:, status:}] [model, Command.system("ls -la", :got_files)] # No side effect [model, nil]
Constants
- Cancel
-
Sentinel value for command cancellation.
Long-running commands (WebSocket listeners, database pollers) run until stopped. Stopping them requires signaling from outside the command. The runtime tracks active commands by their object identity and routes cancel requests.
This type carries the handle (command object) to cancel. The runtime pattern-matches on
Command::Canceland signals the token. - Error
-
Sentinel value for command errors.
Commands run in threads. Exceptions bubble up silently. The update function never sees them, and backtraces in STDERR corrupt the TUI display.
The runtime catches exceptions and pushes
Errorto the queue. Pattern match on it in your update function. - Exit
-
Sentinel value for application termination.
The runtime detects this before dispatching. It breaks the loop immediately.
- System
-
Runs a shell command and routes its output back as messages.
Apps run external tools: linters, compilers, scripts, system utilities. The runtime dispatches the command in a thread, so the UI stays responsive. Batch mode (default) waits for completion; streaming mode shows output live. Orphaned child processes linger and waste resources, so cancellation sends
SIGTERMfor graceful shutdown, thenSIGKILLto prevent orphans.Use it to run builds, lint files, execute scripts, or invoke any CLI tool.
Batch Mode (default)
A single message arrives when the command finishes:
[tag, {stdout:, stderr:, status:}]Streaming Mode
Messages arrive incrementally:
-
[tag, :stdout, line]for each stdout line -
[tag, :stderr, line]for each stderr line -
[tag, :complete, {status:}]when the command finishes -
[tag, :error, {message:}]if the command cannot start
The
statusis the integer exit code (0 = success). -
Public Class Methods
Source
# File lib/ratatui_ruby/tea/command.rb, line 83 def self.cancel(handle) Cancel.new(handle:) end
Request cancellation of a running command.
The model stores the command handle (the command object itself). Returning Command.cancel(handle) signals the runtime to stop it.
- handle
-
The command object to cancel.
Example
# Dispatch and store handle cmd = FetchData.new(url) [model.with(active_fetch: cmd), cmd] # User clicks cancel when :cancel_clicked [model.with(active_fetch: nil), Command.cancel(model.active_fetch)]
Source
# File lib/ratatui_ruby/tea/command.rb, line 115 def self.error(command, exception) Error.new(command:, exception:) end
Creates an error sentinel.
The runtime produces this automatically when a command raises. Use this factory for testing or for commands that want to signal error completion without raising.
- command
-
The command that failed.
- exception
-
The exception that was raised.
Example
def update(message, model) case message in Command::Error(command:, exception:) model.with(error: "#{command.class} failed: #{exception.message}") end end
Source
# File lib/ratatui_ruby/tea/command.rb, line 53 def self.exit Exit.new end
Creates a quit command.
Returns a sentinel the runtime detects to terminate the application.
Example
def update(message, model) case message in { type: :key, code: "q" } [model, Command.exit] else [model, nil] end end
Source
# File lib/ratatui_ruby/tea/command.rb, line 268 def self.system(command, tag, stream: false) System.new(command:, tag:, stream:) end
Creates a shell execution command.
- command
-
Shell command string to execute.
- tag
-
Symbol or class to tag the result message.
- stream
-
If
true, the runtime sends incremental stdout/stderr
messages as they arrive. If <tt>false</tt> (default), waits for completion and sends a single message with all output.
Example (Batch Mode)
# Return this from update: [model.with(loading: true), Command.system("ls -la", :got_files)] # Then handle it later: def update(message, model) case message in [:got_files, {stdout:, status: 0}] [model.with(files: stdout.lines), nil] in [:got_files, {stderr:, status:}] [model.with(error: stderr), nil] end end
Example (Streaming Mode)
# Return this from update: [model.with(loading: true), Command.system("tail -f log.txt", :log, stream: true)] # Then handle incremental messages: def update(message, model) case message in [:log, :stdout, line] [model.with(lines: [*model.lines, line]), nil] in [:log, :stderr, line] [model.with(errors: [*model.errors, line]), nil] in [:log, :complete, {status:}] [model.with(loading: false, exit_status: status), nil] in [:log, :error, {message:}] [model.with(loading: false, error: message), nil] end end