Developing Examples
Guidelines for creating and testing examples in the examples/ directory.
Example Structure
Every interactive example should follow this pattern, living in its own directory:
examples/my_example/app.rb:
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "ratatui_ruby" class MyExampleApp def initialize # Initialize state (styles must be initialized in run when @tui is available) end def run RatatuiRuby.run do |tui| @tui = tui # Store for use in private methods loop do render break if handle_input == :quit end end end private def render @tui.draw do |frame| # 1. Split layout using Session helpers areas = @tui.layout_split(frame.area, constraints: [@tui.constraint_fill(1)]) # 2. Create and render widgets widget = @tui.paragraph(text: "Hello", block: @tui.block(borders: [:all])) frame.render_widget(widget, areas[0]) end end def handle_input case @tui.poll_event in { type: :key, code: "q" } :quit in { type: :key, code: code } # Handle other keys else # Ignore unhandled events end end end MyExampleApp.new.run if __FILE__ == $PROGRAM_NAME
Naming Convention (Required)
Example directories must follow a prefixing convention to categorize them alphabetically: - app_: Application showcases (e.g., app_analytics). Class name: AppAnalytics. - widget_: Widget-focused demonstrations (e.g., widget_gauge_demo). Class name: WidgetGaugeDemo. - verify_: Documentation verification examples (e.g., verify_readme_usage). Class name: VerifyReadmeUsage.
The directory and class names must match (snake_case directory maps to PascalCase class).
This convention enables the terminal_preview:update rake task to automatically capture terminal output for all examples without maintaining a manual registry.
Terminal Size Constraint
All interactive examples must fit within an 80Ă24 terminal (standard VT100 dimensions). This ensures: - Examples work on minimal terminal configurations - Tests can use the default with_test_terminal size (80x24) - Examples remain discoverable and self-documenting through visible hotkey help
Layout pattern for 80Ă24: - Bottom control panel: Allocate ~5-7 lines at bottom for a full-width control block with hotkey documentation. Style hotkeys with bold and underline to make them discoverable. Use double-space or pipe separators to compress multiple controls per line. This keeps all UI text at full readability while maximizing space for the main content area.
Best practices: - Use descriptive names (e.g., âYellow on Blackâ not âYellowâ) so controls are self-documenting and discoverable. - Style hotkeys visually: Use modifiers: [:bold, :underlined] on hotkey letters to make them stand out from descriptions. Example: i (bold, underlined) followed by Items. - Test early by running the example at 80Ă24 and verifying all content is visible without wrapping, scrolling, or clipping.
Every example must also have an RBS file documenting its public methods:
examples/my_example/app.rbs:
class MyExampleApp # @public def self.new: () -> MyExampleApp # @public def run: () -> void end
Key Requirements
-
Only
runshould be public. All other methods (render,handle_input, helper methods) must be private. This prevents tests from calling internal methods directly. -
Use
RatatuiRuby.runfor terminal management. Never callinit_terminalorrestore_terminaldirectly. Therunblock handles terminal setup/teardown automatically and safely, even if an exception occurs. -
Use the Session API (
tui) for cleaner code. Accept thetuiblock parameter fromRatatuiRuby.runand use it throughout your app: -
@tui.draw { |frame| ... }instead ofRatatuiRuby.draw -
@tui.poll_eventinstead ofRatatuiRuby.poll_event -
@tui.style(...)instead ofRatatuiRuby::Style.new(...) -
@tui.paragraph(...)instead ofRatatuiRuby::Paragraph.new(...) -
@tui.block(...)instead ofRatatuiRuby::Block.new(...) -
@tui.layout_split(...)instead ofRatatuiRuby::Layout.split(...) -
@tui.constraint_fill(...)instead ofRatatuiRuby::Constraint.fill(...) -
@tui.text_line(...)instead ofRatatuiRuby::Text::Line.new(...) -
@tui.text_span(...)instead ofRatatuiRuby::Text::Span.new(...) -
Event handling must include a catch-all pattern. When using pattern matching in
handle_input, always include anelseclause at the end to catch unmatched events (mouse events, resize events, focus events, etc.). Without it, unmatched events will raiseNoMatchingPatternError:
ruby def handle_input case @tui.poll_event in { type: :key, code: "q" } :quit in { type: :mouse, kind: "down", x:, y: } handle_click(x, y) else # Ignore other events end end
-
Use keyboard keys to cycle through widget attributes. Users should be able to interactively explore all widget options. Common patterns:
-
Arrow keys: Navigate or adjust values
-
Letter keys: Cycle through styles, modes, or variants. Prefer all lowercase keys to avoid confusion and simplify the UI description.
-
Space: Toggle or select
-
qor Ctrl+C: Quit -
Arrow keys: Navigate or adjust values
-
Letter keys: Cycle through styles, modes, or variants. Prefer all lowercase keys to avoid confusion and simplify the UI description.
-
Space: Toggle or select
-
qor Ctrl+C: Quit
-
-
Naming Conventions for Controls
When documenting hotkeys and cycling options in the UI, use consistent naming:
-
Parameter names: Always match the actual Ruby parameter name. For example:
-
Use âScroll Paddingâ (not âScroll Padâ) for the
scroll_padding:parameter -
Use âHighlight Styleâ (not âHighlightâ) for the
highlight_style:parameter -
Use âRepeat Symbolâ (not âRepeatâ) for the
repeat_highlight_symbol:parameter -
Display names for cycled values: Create a
namefield in your options hash to keep display names paired with values. Initialize style arrays insiderunwhen@tuiis available: ââruby # In run method, after @tui = tui: @styles = [ { name: âYellow Boldâ, style: @tui.style(fg: :yellow, modifiers: [:bold]) }, { name: âBlue on Whiteâ, style: @tui.style(fg: :blue, bg: :white) } ]
# In controls: âh: Highlight Style (#{@styles[:name]})â # Outputs: âh: Highlight Style (Yellow Bold)â ââ
This keeps the UI self-documenting and users can see exact parameter names when they read the hotkey help.
-
Hit Testing
Examples with mouse interaction should use the Frame API. By calling @tui.layout_split inside @tui.draw, you obtain the exact Rects used for rendering. Store these rects in instance variables (e.g., @sidebar_rect) to use them in your handle_input method for hit testing:
if @sidebar_rect&.contains?(event.x, event.y) # Handle click end
Testing Examples
Example tests live alongside examples as test_app.rb files in the same directory.
Testing Pattern
examples/my_example/test_app.rb:
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "ratatui_ruby" require "ratatui_ruby/test_helper" require "minitest/autorun" require_relative "app" class TestMyExampleApp < Minitest::Test include RatatuiRuby::TestHelper def setup @app = MyExampleApp.new end def test_initial_render with_test_terminal do inject_key(:q) # Queue quit event @app.run # Run the app loop content = buffer_content.join("\n") assert_includes content, "Expected Text" end end def test_keyboard_interaction with_test_terminal do inject_key("s") # Press 's' to cycle something inject_key(:q) # Then quit @app.run content = buffer_content.join("\n") assert_includes content, "Changed State" end end def test_mouse_interaction with_test_terminal do # Click at (10, 5) inject_click(x: 10, y: 5) inject_key(:q) @app.run content = buffer_content.join("\n") assert_includes content, "Clicked at (10, 5)" end end end
Testing Guidelines
-
Inject events, observe buffer. Tests should only interact through:
-
inject_key,inject_click,inject_event, etc. for input -
buffer_contentfor output verification -
Never call internal methods. Donât call
render,handle_input,__send__, or access instance variables withinstance_variable_get. Tests verify behavior through the publicrunmethod. -
Use
inject_key(:q)to exit. All examples should support quitting withq, so inject this as the final event to terminate the loop. -
Assert and refute. When testing which item was clicked/selected, also verify the opposite didnât happen:
ruby assert_includes content, "Left Panel clicked" refute_includes content, "Right Panel clicked" -
Test state cycling. If an example cycles through options (styles, modes, etc.), test that pressing the key actually changes the rendered output.
Widget Attribute Cycling
Examples should demonstrate widget configurability by allowing interactive cycling:
| Widget | Attribute | Key Suggestion |
|---|---|---|
| Tabs | highlight_style | Space |
| Tabs | divider | d |
| Tabs | style | s |
| Block | border_type | Space |
| Block | border_color | c |
| List | highlight_style | Space |
| Sparkline | direction | d |
| Scrollbar | orientation | o |
| Scrollbar | theme | s |
Display the current state in the UI (e.g., in a title or status bar or paragraph) so users can see what changed. Display the hotkey in the UI as well, so users can see how to change it; the hotkey should not disappear as app state changes.