# Interactive demonstration of RatatuiRuby debugging features.
This example lets you trigger each debugging feature with a hotkey to verify your setup works before encountering a real bug.
Hotkeys
- d
-
Enable debug_mode! — Shows the debug socket path for remote attachment
- p
-
Trigger test_panic! — Deliberately crashes to verify Rust backtrace visibility
- t
-
Cause TypeError — Passes wrong type to widget factory to show Rust stack frames
- b
-
Show backtrace status — Displays current debug configuration
- q
-
Quit
Usage
# Normal mode (no backtraces): ruby examples/verify_debugging_usage/app.rb # With Rust backtraces only: RUST_BACKTRACE=1 ruby examples/verify_debugging_usage/app.rb # Full debug mode (stops at startup for debugger attachment): RR_DEBUG=1 ruby examples/verify_debugging_usage/app.rb
Remote Debugging
When you press [d] to enable debug_mode!, the app continues running but prints a socket path. From another terminal:
rdbg --attach
This gives you a full debugger REPL while the TUI keeps running.
Source Code
# frozen_string_literal: true #-- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com> # SPDX-License-Identifier: MIT-0 #++ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "ratatui_ruby" ## # Interactive demonstration of RatatuiRuby debugging features. # # This example lets you trigger each debugging feature with a hotkey to verify # your setup works before encountering a real bug. # # == Hotkeys # # [d] Enable debug_mode! — Shows the debug socket path for remote attachment # [p] Trigger test_panic! — Deliberately crashes to verify Rust backtrace visibility # [t] Cause TypeError — Passes wrong type to widget factory to show Rust stack frames # [b] Show backtrace status — Displays current debug configuration # [q] Quit # # == Usage # # # Normal mode (no backtraces): # ruby examples/verify_debugging_usage/app.rb # # # With Rust backtraces only: # RUST_BACKTRACE=1 ruby examples/verify_debugging_usage/app.rb # # # Full debug mode (stops at startup for debugger attachment): # RR_DEBUG=1 ruby examples/verify_debugging_usage/app.rb # # == Remote Debugging # # When you press [d] to enable debug_mode!, the app continues running but # prints a socket path. From another terminal: # # rdbg --attach # # This gives you a full debugger REPL while the TUI keeps running. class VerifyDebuggingUsage def initialize @status_message = "Press a key to test debugging features" @show_debug_info = false @quit = false # If debug mode was enabled via RR_DEBUG=1 at startup, capture the socket path if RatatuiRuby::Debug.enabled? @socket_path = begin ::DEBUGGER__.create_unix_domain_socket_name rescue NameError nil end @show_debug_info = true @status_message = "RR_DEBUG=1 detected — debug mode active" end end def run RatatuiRuby.run do |tui| @tui = tui @loop_count = 0 loop do @loop_count += 1 # 🎯 Breakpoint every 250 loops. Try: p @status_message if RatatuiRuby::Debug.enabled? && (@loop_count % 250).zero? you_found_me = "🎉 You found me! Loop ##{@loop_count}" # rubocop:disable Lint/Debugger debugger # rubocop:enable Lint/Debugger _ = you_found_me # Suppress unused variable warning end render break if @quit || handle_input == :quit end end end private def render @tui.draw do |frame| constraints = [ @tui.constraint_length(3), # Status @tui.constraint_length(5), # Config @tui.constraint_length(6), # Actions ] if @show_debug_info constraints << @tui.constraint_length(6) # Debug info end constraints << @tui.constraint_fill(1) # Spacer constraints << @tui.constraint_length(3) # Help chunks = @tui.layout_split(frame.area, direction: :vertical, constraints:) idx = 0 render_status(frame, chunks[idx]) idx += 1 render_config(frame, chunks[idx]) idx += 1 render_actions(frame, chunks[idx]) idx += 1 if @show_debug_info render_debug_info(frame, chunks[idx]) idx += 1 end # Skip spacer idx += 1 render_help(frame, chunks[idx]) end end private def render_status(frame, area) frame.render_widget( @tui.paragraph( text: @status_message, alignment: :center, block: @tui.block( title: " Status ", title_alignment: :center, borders: [:all], border_style: { fg: :yellow } ) ), area ) end private def render_config(frame, area) config_lines = [ "Rust Backtraces: #{flag(RatatuiRuby::Debug.rust_backtrace_enabled?)}", "Full Debug Mode: #{flag(RatatuiRuby::Debug.enabled?)}", "Remote Debugging: #{remote_mode_description}", ].join("\n") frame.render_widget( @tui.paragraph( text: config_lines, block: @tui.block( title: " Current Debug Configuration ", borders: [:all], border_style: { fg: :cyan } ) ), area ) end private def render_actions(frame, area) actions_lines = [ "[d] Enable debug_mode! and show socket info", "[p] Trigger test_panic! to verify backtrace visibility", "[t] Cause TypeError (pass wrong type to widget)", "[b] Refresh debug status", ].join("\n") frame.render_widget( @tui.paragraph( text: actions_lines, block: @tui.block( title: " Available Actions ", borders: [:all], border_style: { fg: :green } ) ), area ) end private def render_debug_info(frame, area) socket_display = @socket_path || "(socket not available)" info_lines = [ "Socket: #{socket_display}", "Attach: rdbg --attach", "Hint: type 'continue' if you see SIGURG", ] frame.render_widget( @tui.paragraph( text: info_lines.join("\n"), block: @tui.block( title: " Remote Debugging ", borders: [:all], border_style: { fg: :magenta } ) ), area ) end private def render_help(frame, area) frame.render_widget( @tui.paragraph( text: "[d] debug_mode! [p] test_panic! [t] TypeError [b] status [q] quit", alignment: :center, block: @tui.block( borders: [:all], border_style: { fg: :dark_gray } ) ), area ) end private def flag(value) value ? "✓ enabled" : "✗ disabled" end private def remote_mode_description case RatatuiRuby::Debug.remote_debugging_mode when :open attached = debugger_attached? ? " — ATTACHED" : " — waiting" "✓ open#{attached}" when :open_nonstop attached = debugger_attached? ? " — ATTACHED" : "" "✓ open_nonstop#{attached}" else "✗ not configured" end end # ☣️ FRAGILE: This pokes at debug gem internals. # # Private instance variables can change between gem versions. This code # may silently break. We accept that risk here because this showcase # exists specifically to demonstrate debugger attachment status. # # For production apps, checking Debug.enabled? is sufficient — knowing # whether a client has attached rarely matters. private def debugger_attached? return false unless defined?(::DEBUGGER__::SESSION) ui = ::DEBUGGER__::SESSION.instance_variable_get(:@ui) return false unless ui # The @sock instance variable is set when a client connects sock = ui.instance_variable_get(:@sock) !sock.nil? rescue false end private def handle_input case @tui.poll_event in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] } :quit in { type: :key, code: "d" } enable_debug_mode! in { type: :key, code: "p" } trigger_test_panic! in { type: :key, code: "t" } trigger_type_error! in { type: :key, code: "b" } @status_message = "Debug status refreshed at #{Time.now.strftime('%H:%M:%S')}" else nil end end private def enable_debug_mode! if RatatuiRuby::Debug.enabled? @status_message = "Debug mode already enabled!" else # debug_mode! returns the socket path and suppresses the debug gem's output @socket_path = RatatuiRuby.debug_mode! @status_message = "debug_mode! enabled" @show_debug_info = true end end private def trigger_test_panic! if RatatuiRuby::Debug.rust_backtrace_enabled? @status_message = "Triggering test_panic! — check stderr for backtrace..." else @status_message = "Triggering test_panic! — backtrace hidden (set RUST_BACKTRACE=1)" end render # Show the message before crashing # Give a moment for the render to complete sleep 0.1 # This will crash the app with a Rust panic. If RUST_BACKTRACE=1 or # debug mode is enabled, you'll see the full Rust stack trace after # the terminal is restored. RatatuiRuby::Debug.test_panic! end private def trigger_type_error! if RatatuiRuby::Debug.rust_backtrace_enabled? @status_message = "Triggering TypeError — check stderr for error message..." else @status_message = "Triggering TypeError — set RUST_BACKTRACE=1 for stack trace" end render # Show the message before crashing sleep 0.1 # Bypass the factory's DWIM coercion to trigger a real Rust TypeError. # Uses Widgets::Table.new directly with invalid rows type. bad_table = RatatuiRuby::Widgets::Table.new(rows: 42, widths: []) @tui.draw { |f| f.render_widget(bad_table, f.area) } end end VerifyDebuggingUsage.new.run if __FILE__ == $PROGRAM_NAME