Terminal Output During TUI Sessions
The Problem
Writing to stdout or stderr during a TUI session corrupts the display.
When your application is running inside RatatuiRuby.run, the terminal is in âraw modeâ and RatatuiRuby has taken control of the display buffer. Any output via puts, warn, p, print, or direct writes to $stdout/$stderr will:
-
Corrupt the screen layout - Characters appear in random positions
-
Mix with TUI output - Text interleaves with your widgets unpredictably
-
Trigger escape sequence errors - Partial ANSI codes can break rendering
Why This Happens
In raw mode: - The terminal doesnât process newlines or carriage returns normally - Output bypasses the TUIâs controlled buffer - Cursor position is undefined from the TUIâs perspective
Safe Patterns
Use guard_io to swallow output from gems
If youâre using a gem that might write to stdout/stderr, wrap its calls:
RatatuiRuby.run do |tui| RatatuiRuby.guard_io do SomeChattyGem.do_something # Any puts/warn calls are swallowed end # Outside guard_io, you can still debug intentionally: # Object::STDERR.puts "debug: something" # Escape hatch (corrupts display!) end
Defer output until after the TUI exits
messages = [] RatatuiRuby.run do |tui| # Collect messages instead of printing them messages << "Something happened" # ... TUI logic ... end # Now safe to print messages.each { |msg| puts msg }
Use Logger to write to a file
The Logger class from Rubyâs standard library is the idiomatic solution:
require "logger" require "tmpdir" LOG = Logger.new(File.join(Dir.tmpdir, "my_app.log")) LOG.level = Logger::DEBUG RatatuiRuby.run do |tui| LOG.info "Application started" LOG.debug "Processing event: #{event.inspect}" # ... TUI logic ... end
Display messages in the TUI itself
RatatuiRuby.run do |tui| @status_message = "Something happened" tui.draw do |frame| # Show status in the UI frame.render_widget( tui.paragraph(text: @status_message), status_area ) end end
Library Behavior
RatatuiRuby automatically defers its own warnings (like experimental feature notices) during TUI sessions. They are queued and printed after restore_terminal is called.
You donât need to do anything special for library warningsâtheyâre handled automatically.
Bypassing guard_io
If you need to write to stdout/stderr even when guard_io is active (e.g., for pipeline integration or IPC), use the original IO constants:
RatatuiRuby.guard_io do SomeChattyGem.do_something # This is swallowed # But this gets through: Object::STDOUT.puts "structured output for downstream tools" end
This works regardless of whether guard_io is active. During a TUI session, the display will be corruptedâbut the output will reach its destination.
Headless Mode (Batch/Pipeline/CLI)
If your app supports both TUI and non-TUI modes (e.g., my_app --no-tui), call headless! at startup to silence guard_io warnings:
if ARGV.include?("--no-tui") RatatuiRuby.headless! # guard_io calls are now silent no-ops process_batch_work else RatatuiRuby.run do |tui| # TUI mode - guard_io works normally end end
When headless, guard_io becomes a no-op (output flows normally), and calling run or init_terminal raises an error.
Temporarily Exiting TUI Mode
Some apps need to temporarily leave TUI mode for user interactionâlike lazygit does when opening an external editor for commit messages. Use restore_terminal and init_terminal:
RatatuiRuby.run do |tui| # ... TUI is active ... if user_wants_external_editor RatatuiRuby.restore_terminal # Now in normal terminal mode system("$EDITOR", filename) puts "Press Enter to return to the TUI..." gets RatatuiRuby.init_terminal end # ... TUI is active again ... end
This pattern lets you hand control back to the user or spawn external processes that need normal terminal access.