ratatui_ruby 0.7.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +2 -2
- data/.builds/ruby-3.3.yml +2 -2
- data/.builds/ruby-3.4.yml +2 -2
- data/.builds/ruby-4.0.0.yml +2 -2
- data/.pre-commit-config.yaml +1 -1
- data/AGENTS.md +3 -3
- data/CHANGELOG.md +70 -1
- data/LICENSES/LGPL-3.0-or-later.txt +304 -0
- data/LICENSES/MIT-0.txt +16 -0
- data/README.md +33 -5
- data/Rakefile +1 -1
- data/doc/concepts/application_architecture.md +44 -3
- data/doc/concepts/application_testing.md +43 -1
- data/doc/concepts/async.md +32 -2
- data/doc/concepts/custom_widgets.md +247 -0
- data/doc/concepts/event_handling.md +32 -3
- data/doc/concepts/interactive_design.md +32 -2
- data/doc/contributors/auditing/parity.md +7 -1
- data/doc/contributors/design/ruby_frontend.md +85 -1
- data/doc/contributors/design/rust_backend.md +67 -1
- data/doc/contributors/developing_examples.md +56 -2
- data/doc/contributors/documentation_style.md +20 -3
- data/doc/contributors/future_work.md +169 -0
- data/doc/contributors/index.md +1 -1
- data/doc/contributors/v1.0.0_blockers.md +15 -175
- data/doc/getting_started/quickstart.md +35 -9
- data/doc/getting_started/why.md +1 -1
- data/doc/index.md +2 -1
- data/doc/troubleshooting/debugging.md +32 -2
- data/doc/troubleshooting/terminal_limitations.md +8 -2
- data/doc/troubleshooting/tui_output.md +127 -6
- data/examples/app_all_events/README.md +14 -2
- data/examples/app_all_events/app.rb +1 -1
- data/examples/app_all_events/model/app_model.rb +1 -1
- data/examples/app_all_events/model/event_color_cycle.rb +1 -1
- data/examples/app_all_events/model/event_entry.rb +1 -1
- data/examples/app_all_events/model/msg.rb +1 -1
- data/examples/app_all_events/model/timestamp.rb +1 -1
- data/examples/app_all_events/update.rb +1 -1
- data/examples/app_all_events/view/app_view.rb +1 -1
- data/examples/app_all_events/view/controls_view.rb +1 -1
- data/examples/app_all_events/view/counts_view.rb +1 -1
- data/examples/app_all_events/view/live_view.rb +1 -1
- data/examples/app_all_events/view/log_view.rb +1 -1
- data/examples/app_all_events/view.rb +1 -1
- data/examples/app_color_picker/README.md +20 -2
- data/examples/app_color_picker/app.rb +1 -1
- data/examples/app_color_picker/clipboard.rb +1 -1
- data/examples/app_color_picker/color.rb +1 -1
- data/examples/app_color_picker/controls.rb +1 -1
- data/examples/app_color_picker/copy_dialog.rb +1 -1
- data/examples/app_color_picker/export_pane.rb +1 -1
- data/examples/app_color_picker/harmony.rb +1 -1
- data/examples/app_color_picker/input.rb +1 -1
- data/examples/app_color_picker/main_container.rb +1 -1
- data/examples/app_color_picker/palette.rb +1 -1
- data/examples/app_login_form/README.md +8 -2
- data/examples/app_login_form/app.rb +1 -1
- data/examples/app_stateful_interaction/README.md +2 -2
- data/examples/app_stateful_interaction/app.rb +71 -17
- data/examples/timeout_demo.rb +1 -1
- data/examples/verify_quickstart_dsl/README.md +6 -0
- data/examples/verify_quickstart_dsl/app.rb +3 -3
- data/examples/verify_quickstart_layout/README.md +6 -0
- data/examples/verify_quickstart_layout/app.rb +3 -3
- data/examples/verify_quickstart_lifecycle/README.md +13 -1
- data/examples/verify_quickstart_lifecycle/app.rb +10 -4
- data/examples/verify_readme_usage/README.md +6 -0
- data/examples/verify_readme_usage/app.rb +3 -3
- data/examples/widget_barchart/README.md +6 -0
- data/examples/widget_barchart/app.rb +2 -2
- data/examples/widget_block/README.md +7 -1
- data/examples/widget_block/app.rb +2 -2
- data/examples/widget_box/README.md +6 -0
- data/examples/widget_box/app.rb +9 -6
- data/examples/widget_calendar/README.md +6 -0
- data/examples/widget_calendar/app.rb +2 -2
- data/examples/widget_canvas/README.md +4 -0
- data/examples/widget_canvas/app.rb +2 -2
- data/examples/widget_cell/README.md +6 -0
- data/examples/widget_cell/app.rb +2 -3
- data/examples/widget_center/README.md +4 -0
- data/examples/widget_center/app.rb +2 -2
- data/examples/widget_chart/README.md +6 -0
- data/examples/widget_chart/app.rb +2 -2
- data/examples/widget_gauge/README.md +6 -0
- data/examples/widget_gauge/app.rb +2 -2
- data/examples/widget_layout_split/README.md +6 -0
- data/examples/widget_layout_split/app.rb +3 -3
- data/examples/widget_line_gauge/README.md +6 -0
- data/examples/widget_line_gauge/app.rb +2 -2
- data/examples/widget_list/README.md +6 -0
- data/examples/widget_list/app.rb +2 -2
- data/examples/widget_map/README.md +8 -2
- data/examples/widget_map/app.rb +2 -2
- data/examples/widget_overlay/README.md +7 -1
- data/examples/widget_overlay/app.rb +2 -2
- data/examples/widget_popup/README.md +6 -0
- data/examples/widget_popup/app.rb +2 -2
- data/examples/widget_ratatui_logo/README.md +6 -0
- data/examples/widget_ratatui_logo/app.rb +2 -3
- data/examples/widget_ratatui_mascot/README.md +6 -0
- data/examples/widget_ratatui_mascot/app.rb +2 -2
- data/examples/widget_rect/README.md +12 -0
- data/examples/widget_rect/app.rb +40 -26
- data/examples/widget_render/README.md +6 -0
- data/examples/widget_render/app.rb +2 -2
- data/examples/widget_render/app.rbs +41 -0
- data/examples/widget_rich_text/README.md +6 -0
- data/examples/widget_rich_text/app.rb +2 -2
- data/examples/widget_scroll_text/README.md +6 -0
- data/examples/widget_scroll_text/app.rb +2 -2
- data/examples/widget_scrollbar/README.md +6 -0
- data/examples/widget_scrollbar/app.rb +2 -2
- data/examples/widget_sparkline/README.md +6 -0
- data/examples/widget_sparkline/app.rb +2 -2
- data/examples/widget_style_colors/README.md +6 -0
- data/examples/widget_style_colors/app.rb +2 -2
- data/examples/widget_table/README.md +8 -2
- data/examples/widget_table/app.rb +2 -2
- data/examples/widget_tabs/README.md +6 -0
- data/examples/widget_tabs/app.rb +2 -2
- data/examples/widget_text_width/README.md +6 -0
- data/examples/widget_text_width/app.rb +4 -4
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/extconf.rb +2 -2
- data/ext/ratatui_ruby/src/rendering.rs +1 -1
- data/ext/ratatui_ruby/src/style.rs +0 -8
- data/ext/ratatui_ruby/src/widgets/chart.rs +0 -118
- data/ext/ratatui_ruby/src/widgets/list_state.rs +36 -0
- data/lib/ratatui_ruby/buffer/cell.rb +34 -2
- data/lib/ratatui_ruby/buffer.rb +2 -2
- data/lib/ratatui_ruby/cell.rb +34 -2
- data/lib/ratatui_ruby/event/focus_gained.rb +26 -2
- data/lib/ratatui_ruby/event/focus_lost.rb +26 -2
- data/lib/ratatui_ruby/event/key/character.rb +18 -2
- data/lib/ratatui_ruby/event/key/media.rb +2 -2
- data/lib/ratatui_ruby/event/key/modifier.rb +10 -2
- data/lib/ratatui_ruby/event/key/navigation.rb +2 -2
- data/lib/ratatui_ruby/event/key/system.rb +2 -2
- data/lib/ratatui_ruby/event/key.rb +114 -2
- data/lib/ratatui_ruby/event/mouse.rb +42 -2
- data/lib/ratatui_ruby/event/none.rb +10 -2
- data/lib/ratatui_ruby/event/paste.rb +34 -2
- data/lib/ratatui_ruby/event/resize.rb +34 -2
- data/lib/ratatui_ruby/event.rb +26 -2
- data/lib/ratatui_ruby/frame.rb +74 -2
- data/lib/ratatui_ruby/layout/constraint.rb +58 -2
- data/lib/ratatui_ruby/layout/layout.rb +47 -2
- data/lib/ratatui_ruby/layout/rect.rb +403 -2
- data/lib/ratatui_ruby/layout.rb +2 -2
- data/lib/ratatui_ruby/list_state.rb +113 -2
- data/lib/ratatui_ruby/output_guard.rb +171 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +2 -2
- data/lib/ratatui_ruby/schema/bar_chart.rb +50 -2
- data/lib/ratatui_ruby/schema/block.rb +21 -15
- data/lib/ratatui_ruby/schema/calendar.rb +2 -2
- data/lib/ratatui_ruby/schema/canvas.rb +10 -2
- data/lib/ratatui_ruby/schema/center.rb +10 -2
- data/lib/ratatui_ruby/schema/chart.rb +2 -28
- data/lib/ratatui_ruby/schema/clear.rb +10 -2
- data/lib/ratatui_ruby/schema/constraint.rb +58 -2
- data/lib/ratatui_ruby/schema/cursor.rb +10 -2
- data/lib/ratatui_ruby/schema/draw.rb +10 -2
- data/lib/ratatui_ruby/schema/gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/layout.rb +18 -2
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +10 -2
- data/lib/ratatui_ruby/schema/list_item.rb +10 -2
- data/lib/ratatui_ruby/schema/overlay.rb +10 -2
- data/lib/ratatui_ruby/schema/paragraph.rb +10 -2
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +2 -2
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +2 -2
- data/lib/ratatui_ruby/schema/rect.rb +58 -2
- data/lib/ratatui_ruby/schema/row.rb +10 -2
- data/lib/ratatui_ruby/schema/scrollbar.rb +2 -2
- data/lib/ratatui_ruby/schema/shape/label.rb +10 -2
- data/lib/ratatui_ruby/schema/sparkline.rb +10 -2
- data/lib/ratatui_ruby/schema/style.rb +18 -2
- data/lib/ratatui_ruby/schema/table.rb +2 -2
- data/lib/ratatui_ruby/schema/tabs.rb +2 -2
- data/lib/ratatui_ruby/schema/text.rb +34 -2
- data/lib/ratatui_ruby/scrollbar_state.rb +10 -2
- data/lib/ratatui_ruby/style/style.rb +18 -2
- data/lib/ratatui_ruby/style.rb +2 -2
- data/lib/ratatui_ruby/table_state.rb +10 -2
- data/lib/ratatui_ruby/terminal_lifecycle.rb +144 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +34 -2
- data/lib/ratatui_ruby/test_helper/snapshot.rb +74 -9
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +98 -2
- data/lib/ratatui_ruby/test_helper/terminal.rb +50 -2
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +18 -2
- data/lib/ratatui_ruby/test_helper.rb +10 -2
- data/lib/ratatui_ruby/tui/buffer_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/canvas_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/core.rb +2 -2
- data/lib/ratatui_ruby/tui/layout_factories.rb +32 -2
- data/lib/ratatui_ruby/tui/state_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/style_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/text_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/widget_factories.rb +2 -2
- data/lib/ratatui_ruby/tui.rb +11 -3
- data/lib/ratatui_ruby/version.rb +3 -3
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -2
- data/lib/ratatui_ruby/widgets/bar_chart.rb +58 -2
- data/lib/ratatui_ruby/widgets/block.rb +37 -15
- data/lib/ratatui_ruby/widgets/calendar.rb +2 -2
- data/lib/ratatui_ruby/widgets/canvas.rb +10 -2
- data/lib/ratatui_ruby/widgets/cell.rb +10 -2
- data/lib/ratatui_ruby/widgets/center.rb +10 -2
- data/lib/ratatui_ruby/widgets/chart.rb +2 -28
- data/lib/ratatui_ruby/widgets/clear.rb +10 -2
- data/lib/ratatui_ruby/widgets/cursor.rb +10 -2
- data/lib/ratatui_ruby/widgets/gauge.rb +16 -2
- data/lib/ratatui_ruby/widgets/line_gauge.rb +16 -2
- data/lib/ratatui_ruby/widgets/list.rb +41 -2
- data/lib/ratatui_ruby/widgets/list_item.rb +10 -2
- data/lib/ratatui_ruby/widgets/overlay.rb +10 -2
- data/lib/ratatui_ruby/widgets/paragraph.rb +10 -2
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -2
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -2
- data/lib/ratatui_ruby/widgets/row.rb +10 -2
- data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -2
- data/lib/ratatui_ruby/widgets/shape/label.rb +10 -2
- data/lib/ratatui_ruby/widgets/sparkline.rb +10 -2
- data/lib/ratatui_ruby/widgets/table.rb +62 -2
- data/lib/ratatui_ruby/widgets/tabs.rb +2 -2
- data/lib/ratatui_ruby/widgets.rb +2 -2
- data/lib/ratatui_ruby.rb +116 -81
- data/sig/examples/app_all_events/view.rbs +7 -1
- data/sig/examples/app_all_events/view_state.rbs +7 -1
- data/sig/examples/app_color_picker/app.rbs +5 -0
- data/sig/examples/app_stateful_interaction/app.rbs +7 -1
- data/sig/examples/verify_quickstart_dsl/app.rbs +7 -1
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +7 -1
- data/sig/examples/verify_readme_usage/app.rbs +7 -1
- data/sig/examples/widget_block_demo/app.rbs +6 -0
- data/sig/examples/widget_box_demo/app.rbs +7 -1
- data/sig/examples/widget_calendar_demo/app.rbs +7 -1
- data/sig/examples/widget_cell_demo/app.rbs +7 -1
- data/sig/examples/widget_chart_demo/app.rbs +7 -1
- data/sig/examples/widget_gauge_demo/app.rbs +7 -1
- data/sig/examples/widget_layout_split/app.rbs +7 -1
- data/sig/examples/widget_line_gauge_demo/app.rbs +7 -1
- data/sig/examples/widget_list_demo/app.rbs +5 -0
- data/sig/examples/widget_map_demo/app.rbs +7 -1
- data/sig/examples/widget_popup_demo/app.rbs +7 -1
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +7 -1
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +7 -1
- data/sig/examples/widget_rect/app.rbs +7 -1
- data/sig/examples/widget_render/app.rbs +7 -1
- data/sig/examples/widget_rich_text/app.rbs +7 -1
- data/sig/examples/widget_scroll_text/app.rbs +7 -1
- data/sig/examples/widget_scrollbar_demo/app.rbs +7 -1
- data/sig/examples/widget_sparkline_demo/app.rbs +7 -1
- data/sig/examples/widget_style_colors/app.rbs +7 -1
- data/sig/examples/widget_table_demo/app.rbs +7 -1
- data/sig/examples/widget_text_width/app.rbs +7 -1
- data/sig/ratatui_ruby/event.rbs +7 -1
- data/sig/ratatui_ruby/frame.rbs +15 -3
- data/sig/ratatui_ruby/list_state.rbs +11 -1
- data/sig/ratatui_ruby/ratatui_ruby.rbs +8 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +7 -1
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +6 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +6 -0
- data/sig/ratatui_ruby/schema/block.rbs +7 -1
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -0
- data/sig/ratatui_ruby/schema/canvas.rbs +6 -0
- data/sig/ratatui_ruby/schema/center.rbs +6 -0
- data/sig/ratatui_ruby/schema/chart.rbs +6 -9
- data/sig/ratatui_ruby/schema/constraint.rbs +6 -0
- data/sig/ratatui_ruby/schema/cursor.rbs +6 -0
- data/sig/ratatui_ruby/schema/draw.rbs +6 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +9 -1
- data/sig/ratatui_ruby/schema/layout.rbs +6 -0
- data/sig/ratatui_ruby/schema/line_gauge.rbs +9 -1
- data/sig/ratatui_ruby/schema/list.rbs +9 -1
- data/sig/ratatui_ruby/schema/list_item.rbs +7 -1
- data/sig/ratatui_ruby/schema/overlay.rbs +6 -0
- data/sig/ratatui_ruby/schema/paragraph.rbs +6 -0
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +6 -0
- data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +5 -0
- data/sig/ratatui_ruby/schema/rect.rbs +30 -0
- data/sig/ratatui_ruby/schema/row.rbs +7 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +6 -0
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -0
- data/sig/ratatui_ruby/schema/style.rbs +7 -1
- data/sig/ratatui_ruby/schema/table.rbs +11 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +6 -0
- data/sig/ratatui_ruby/schema/text.rbs +7 -1
- data/sig/ratatui_ruby/scrollbar_state.rbs +7 -1
- data/sig/ratatui_ruby/session.rbs +7 -1
- data/sig/ratatui_ruby/table_state.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/terminal.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -1
- data/sig/ratatui_ruby/test_helper.rbs +7 -1
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/core.rbs +7 -1
- data/sig/ratatui_ruby/tui/layout_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/state_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/style_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/text_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/widget_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui.rbs +7 -1
- data/sig/ratatui_ruby/version.rbs +6 -0
- data/tasks/autodoc/examples.rb +9 -3
- data/tasks/autodoc/member.rb +1 -1
- data/tasks/autodoc/name.rb +1 -1
- data/tasks/bump/cargo_lockfile.rb +1 -1
- data/tasks/bump/changelog.rb +1 -1
- data/tasks/bump/header.rb +1 -1
- data/tasks/bump/history.rb +1 -1
- data/tasks/bump/links.rb +1 -1
- data/tasks/bump/manifest.rb +1 -1
- data/tasks/bump/ruby_gem.rb +1 -1
- data/tasks/bump/sem_ver.rb +1 -1
- data/tasks/bump/unreleased_section.rb +1 -1
- data/tasks/license/headers_md.rb +223 -0
- data/tasks/license/headers_rb.rb +210 -0
- data/tasks/license/license_utils.rb +130 -0
- data/tasks/license/snippets_md.rb +315 -0
- data/tasks/license/snippets_rdoc.rb +150 -0
- data/tasks/license.rake +91 -0
- data/tasks/rdoc_config.rb +1 -1
- data/tasks/resources/build.yml.erb +13 -7
- data/tasks/sourcehut.rake +3 -1
- data/tasks/terminal_preview/app_screenshot.rb +1 -1
- data/tasks/terminal_preview/crash_report.rb +1 -1
- data/tasks/terminal_preview/example_app.rb +1 -1
- data/tasks/terminal_preview/launcher_script.rb +1 -1
- data/tasks/terminal_preview/preview_collection.rb +1 -1
- data/tasks/terminal_preview/preview_timing.rb +1 -1
- data/tasks/terminal_preview/safety_confirmation.rb +1 -1
- data/tasks/terminal_preview/saved_screenshot.rb +1 -1
- data/tasks/terminal_preview/system_appearance.rb +1 -1
- data/tasks/terminal_preview/terminal_window.rb +1 -1
- data/tasks/terminal_preview/window_id.rb +1 -1
- data/tasks/website/index_page.rb +1 -1
- data/tasks/website/version.rb +1 -1
- data/tasks/website/version_menu.rb +1 -1
- data/tasks/website/versioned_documentation.rb +1 -1
- data/tasks/website/website.rb +1 -1
- metadata +15 -3
- data/doc/migration/v0_7_0.md +0 -236
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
3
|
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
4
|
-->
|
|
5
5
|
|
|
@@ -41,6 +41,11 @@ The Rust backend is a pure rendering engine. It receives Ruby objects representi
|
|
|
41
41
|
|
|
42
42
|
The backend implements one generic rendering function that accepts any Ruby `Value` and dispatches based on class name. There is no compile-time knowledge of Ruby types—everything is runtime reflection.
|
|
43
43
|
|
|
44
|
+
<!-- SPDX-SnippetBegin -->
|
|
45
|
+
<!--
|
|
46
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
47
|
+
SPDX-License-Identifier: MIT-0
|
|
48
|
+
-->
|
|
44
49
|
```rust
|
|
45
50
|
// rendering.rs
|
|
46
51
|
pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
@@ -58,20 +63,32 @@ pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
```
|
|
66
|
+
<!-- SPDX-SnippetEnd -->
|
|
61
67
|
|
|
62
68
|
### 3. No Custom Rust Structs for UI
|
|
63
69
|
|
|
64
70
|
Do not define Rust structs that mirror Ruby UI components. This would create synchronization problems when Ruby classes change.
|
|
65
71
|
|
|
66
72
|
**What We Do:**
|
|
73
|
+
<!-- SPDX-SnippetBegin -->
|
|
74
|
+
<!--
|
|
75
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
76
|
+
SPDX-License-Identifier: MIT-0
|
|
77
|
+
-->
|
|
67
78
|
```rust
|
|
68
79
|
// Extract directly from Ruby object
|
|
69
80
|
let text: String = node.funcall("text", ())?;
|
|
70
81
|
let style_val: Value = node.funcall("style", ())?;
|
|
71
82
|
let style = parse_style(style_val)?;
|
|
72
83
|
```
|
|
84
|
+
<!-- SPDX-SnippetEnd -->
|
|
73
85
|
|
|
74
86
|
**What We Don't Do:**
|
|
87
|
+
<!-- SPDX-SnippetBegin -->
|
|
88
|
+
<!--
|
|
89
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
90
|
+
SPDX-License-Identifier: MIT-0
|
|
91
|
+
-->
|
|
75
92
|
```rust
|
|
76
93
|
// NO: Rust struct mirroring Ruby
|
|
77
94
|
struct Paragraph {
|
|
@@ -80,6 +97,7 @@ struct Paragraph {
|
|
|
80
97
|
block: Option<Block>,
|
|
81
98
|
}
|
|
82
99
|
```
|
|
100
|
+
<!-- SPDX-SnippetEnd -->
|
|
83
101
|
|
|
84
102
|
### 4. Immediate Mode Rendering
|
|
85
103
|
|
|
@@ -91,6 +109,11 @@ This mirrors Ratatui's own immediate mode paradigm. The Rust backend is stateles
|
|
|
91
109
|
|
|
92
110
|
Ruby's GC can move or collect objects at any time. All data extracted from Ruby must be owned (copied) before use, never borrowed.
|
|
93
111
|
|
|
112
|
+
<!-- SPDX-SnippetBegin -->
|
|
113
|
+
<!--
|
|
114
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
115
|
+
SPDX-License-Identifier: MIT-0
|
|
116
|
+
-->
|
|
94
117
|
```rust
|
|
95
118
|
// SAFE: Convert to owned String immediately
|
|
96
119
|
let text: String = node.funcall::<_, String>("text", ())?.into_owned();
|
|
@@ -100,11 +123,17 @@ let text_ref: &str = node.funcall("text", ())?; // DON'T
|
|
|
100
123
|
do_something_that_might_gc();
|
|
101
124
|
use(text_ref); // CRASH: text_ref may be invalid
|
|
102
125
|
```
|
|
126
|
+
<!-- SPDX-SnippetEnd -->
|
|
103
127
|
|
|
104
128
|
---
|
|
105
129
|
|
|
106
130
|
## Directory Structure
|
|
107
131
|
|
|
132
|
+
<!-- SPDX-SnippetBegin -->
|
|
133
|
+
<!--
|
|
134
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
135
|
+
SPDX-License-Identifier: MIT-0
|
|
136
|
+
-->
|
|
108
137
|
```
|
|
109
138
|
ext/ratatui_ruby/src/
|
|
110
139
|
├── lib.rs # Entry point, Ruby module registration
|
|
@@ -123,6 +152,7 @@ ext/ratatui_ruby/src/
|
|
|
123
152
|
├── canvas.rs
|
|
124
153
|
└── ...
|
|
125
154
|
```
|
|
155
|
+
<!-- SPDX-SnippetEnd -->
|
|
126
156
|
|
|
127
157
|
---
|
|
128
158
|
|
|
@@ -159,6 +189,11 @@ Pure functions for extracting style information from Ruby values. Handles `parse
|
|
|
159
189
|
|
|
160
190
|
The routing layer that maps Ruby class names to widget renderers:
|
|
161
191
|
|
|
192
|
+
<!-- SPDX-SnippetBegin -->
|
|
193
|
+
<!--
|
|
194
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
195
|
+
SPDX-License-Identifier: MIT-0
|
|
196
|
+
-->
|
|
162
197
|
```rust
|
|
163
198
|
pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
164
199
|
let class_name: String = node.class().name()?.into_owned();
|
|
@@ -191,6 +226,7 @@ pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
191
226
|
}
|
|
192
227
|
}
|
|
193
228
|
```
|
|
229
|
+
<!-- SPDX-SnippetEnd -->
|
|
194
230
|
|
|
195
231
|
**Namespace Pattern:** All built-in widgets use the `RatatuiRuby::Widgets::*` namespace. The dispatcher matches on full class names, not prefixes.
|
|
196
232
|
|
|
@@ -198,6 +234,11 @@ pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
198
234
|
|
|
199
235
|
Each widget has its own module with a standard interface:
|
|
200
236
|
|
|
237
|
+
<!-- SPDX-SnippetBegin -->
|
|
238
|
+
<!--
|
|
239
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
240
|
+
SPDX-License-Identifier: MIT-0
|
|
241
|
+
-->
|
|
201
242
|
```rust
|
|
202
243
|
// widgets/paragraph.rs
|
|
203
244
|
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
@@ -222,6 +263,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
222
263
|
Ok(())
|
|
223
264
|
}
|
|
224
265
|
```
|
|
266
|
+
<!-- SPDX-SnippetEnd -->
|
|
225
267
|
|
|
226
268
|
---
|
|
227
269
|
|
|
@@ -229,6 +271,11 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
229
271
|
|
|
230
272
|
### Step 1: Create the Widget Module
|
|
231
273
|
|
|
274
|
+
<!-- SPDX-SnippetBegin -->
|
|
275
|
+
<!--
|
|
276
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
277
|
+
SPDX-License-Identifier: MIT-0
|
|
278
|
+
-->
|
|
232
279
|
```rust
|
|
233
280
|
// src/widgets/my_widget.rs
|
|
234
281
|
|
|
@@ -249,18 +296,31 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
249
296
|
Ok(())
|
|
250
297
|
}
|
|
251
298
|
```
|
|
299
|
+
<!-- SPDX-SnippetEnd -->
|
|
252
300
|
|
|
253
301
|
### Step 2: Register in `widgets/mod.rs`
|
|
254
302
|
|
|
303
|
+
<!-- SPDX-SnippetBegin -->
|
|
304
|
+
<!--
|
|
305
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
306
|
+
SPDX-License-Identifier: MIT-0
|
|
307
|
+
-->
|
|
255
308
|
```rust
|
|
256
309
|
pub mod my_widget;
|
|
257
310
|
```
|
|
311
|
+
<!-- SPDX-SnippetEnd -->
|
|
258
312
|
|
|
259
313
|
### Step 3: Add Dispatch Arm in `rendering.rs`
|
|
260
314
|
|
|
315
|
+
<!-- SPDX-SnippetBegin -->
|
|
316
|
+
<!--
|
|
317
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
318
|
+
SPDX-License-Identifier: MIT-0
|
|
319
|
+
-->
|
|
261
320
|
```rust
|
|
262
321
|
"RatatuiRuby::Widgets::MyWidget" => widgets::my_widget::render(frame, area, node),
|
|
263
322
|
```
|
|
323
|
+
<!-- SPDX-SnippetEnd -->
|
|
264
324
|
|
|
265
325
|
### Step 4: Test
|
|
266
326
|
|
|
@@ -274,6 +334,11 @@ Some widgets (List, Table, Scrollbar) support stateful rendering where a mutable
|
|
|
274
334
|
|
|
275
335
|
### The Pattern
|
|
276
336
|
|
|
337
|
+
<!-- SPDX-SnippetBegin -->
|
|
338
|
+
<!--
|
|
339
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
340
|
+
SPDX-License-Identifier: MIT-0
|
|
341
|
+
-->
|
|
277
342
|
```rust
|
|
278
343
|
pub fn render_stateful_widget(
|
|
279
344
|
frame: &mut Frame,
|
|
@@ -299,6 +364,7 @@ pub fn render_stateful_widget(
|
|
|
299
364
|
Ok(())
|
|
300
365
|
}
|
|
301
366
|
```
|
|
367
|
+
<!-- SPDX-SnippetEnd -->
|
|
302
368
|
|
|
303
369
|
**State Precedence:** When using stateful rendering, the State object's values take precedence over Widget properties. This is documented in Ruby.
|
|
304
370
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
4
|
-->
|
|
5
5
|
|
|
6
6
|
# Developing Examples
|
|
@@ -12,6 +12,11 @@ Guidelines for creating and testing examples in the `examples/` directory.
|
|
|
12
12
|
Every interactive example should follow this pattern, living in its own directory:
|
|
13
13
|
|
|
14
14
|
`examples/my_example/app.rb`:
|
|
15
|
+
<!-- SPDX-SnippetBegin -->
|
|
16
|
+
<!--
|
|
17
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
18
|
+
SPDX-License-Identifier: MIT-0
|
|
19
|
+
-->
|
|
15
20
|
```ruby
|
|
16
21
|
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
17
22
|
require "ratatui_ruby"
|
|
@@ -57,6 +62,7 @@ end
|
|
|
57
62
|
|
|
58
63
|
MyExampleApp.new.run if __FILE__ == $PROGRAM_NAME
|
|
59
64
|
```
|
|
65
|
+
<!-- SPDX-SnippetEnd -->
|
|
60
66
|
|
|
61
67
|
### Naming Convention (Required)
|
|
62
68
|
|
|
@@ -89,6 +95,11 @@ All interactive examples must fit within an **80×24 terminal** (standard VT100
|
|
|
89
95
|
Every example must also have an RBS file documenting its public methods. Type signatures live in a centralized location:
|
|
90
96
|
|
|
91
97
|
`sig/examples/my_example/app.rbs`:
|
|
98
|
+
<!-- SPDX-SnippetBegin -->
|
|
99
|
+
<!--
|
|
100
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
101
|
+
SPDX-License-Identifier: MIT-0
|
|
102
|
+
-->
|
|
92
103
|
```rbs
|
|
93
104
|
class MyExampleApp
|
|
94
105
|
# @public
|
|
@@ -98,11 +109,17 @@ class MyExampleApp
|
|
|
98
109
|
def run: () -> void
|
|
99
110
|
end
|
|
100
111
|
```
|
|
112
|
+
<!-- SPDX-SnippetEnd -->
|
|
101
113
|
|
|
102
114
|
## Directory Structure
|
|
103
115
|
|
|
104
116
|
Examples are organized across three locations:
|
|
105
117
|
|
|
118
|
+
<!-- SPDX-SnippetBegin -->
|
|
119
|
+
<!--
|
|
120
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
121
|
+
SPDX-License-Identifier: MIT-0
|
|
122
|
+
-->
|
|
106
123
|
```
|
|
107
124
|
examples/
|
|
108
125
|
my_example/
|
|
@@ -119,6 +136,7 @@ sig/examples/
|
|
|
119
136
|
my_example/
|
|
120
137
|
app.rbs ← REQUIRED: Type signatures (centralized, not local to example)
|
|
121
138
|
```
|
|
139
|
+
<!-- SPDX-SnippetEnd -->
|
|
122
140
|
|
|
123
141
|
### Key Requirements
|
|
124
142
|
|
|
@@ -194,17 +212,28 @@ This keeps the UI self-documenting and users can see exact parameter names when
|
|
|
194
212
|
|
|
195
213
|
Examples with mouse interaction should use the **Frame API**. By calling `@tui.layout_split` inside `@tui.draw`, you obtain the exact `Rect`s used for rendering. Store these rects in instance variables (e.g., `@sidebar_rect`) to use them in your `handle_input` method for hit testing:
|
|
196
214
|
|
|
215
|
+
<!-- SPDX-SnippetBegin -->
|
|
216
|
+
<!--
|
|
217
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
218
|
+
SPDX-License-Identifier: MIT-0
|
|
219
|
+
-->
|
|
197
220
|
```ruby
|
|
198
221
|
if @sidebar_rect&.contains?(event.x, event.y)
|
|
199
222
|
# Handle click
|
|
200
223
|
end
|
|
201
224
|
```
|
|
225
|
+
<!-- SPDX-SnippetEnd -->
|
|
202
226
|
|
|
203
227
|
## Testing Examples
|
|
204
228
|
|
|
205
229
|
Example tests live in a centralized test tree:
|
|
206
230
|
|
|
207
231
|
`test/examples/my_example/test_app.rb`:
|
|
232
|
+
<!-- SPDX-SnippetBegin -->
|
|
233
|
+
<!--
|
|
234
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
235
|
+
SPDX-License-Identifier: MIT-0
|
|
236
|
+
-->
|
|
208
237
|
```ruby
|
|
209
238
|
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
210
239
|
require "ratatui_ruby"
|
|
@@ -228,6 +257,7 @@ class TestMyExampleApp < Minitest::Test
|
|
|
228
257
|
end
|
|
229
258
|
end
|
|
230
259
|
```
|
|
260
|
+
<!-- SPDX-SnippetEnd -->
|
|
231
261
|
|
|
232
262
|
## Snapshot Testing Pattern (REQUIRED)
|
|
233
263
|
|
|
@@ -243,6 +273,11 @@ All example tests MUST use snapshot testing via the `assert_snapshots` API, not
|
|
|
243
273
|
|
|
244
274
|
### Basic Pattern
|
|
245
275
|
|
|
276
|
+
<!-- SPDX-SnippetBegin -->
|
|
277
|
+
<!--
|
|
278
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
279
|
+
SPDX-License-Identifier: MIT-0
|
|
280
|
+
-->
|
|
246
281
|
```ruby
|
|
247
282
|
def test_initial_render
|
|
248
283
|
with_test_terminal do
|
|
@@ -253,6 +288,7 @@ def test_initial_render
|
|
|
253
288
|
end
|
|
254
289
|
end
|
|
255
290
|
```
|
|
291
|
+
<!-- SPDX-SnippetEnd -->
|
|
256
292
|
|
|
257
293
|
Snapshot auto-saved to: `test/examples/widget_foo/snapshots/initial_render.txt`
|
|
258
294
|
|
|
@@ -260,6 +296,11 @@ Snapshot auto-saved to: `test/examples/widget_foo/snapshots/initial_render.txt`
|
|
|
260
296
|
|
|
261
297
|
For examples with timestamps, random data, or other non-deterministic output:
|
|
262
298
|
|
|
299
|
+
<!-- SPDX-SnippetBegin -->
|
|
300
|
+
<!--
|
|
301
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
302
|
+
SPDX-License-Identifier: MIT-0
|
|
303
|
+
-->
|
|
263
304
|
```ruby
|
|
264
305
|
private def assert_normalized_snapshots(snapshot_name)
|
|
265
306
|
assert_plain_snapshot(snapshot_name) do |actual|
|
|
@@ -280,6 +321,7 @@ def test_after_event
|
|
|
280
321
|
end
|
|
281
322
|
end
|
|
282
323
|
```
|
|
324
|
+
<!-- SPDX-SnippetEnd -->
|
|
283
325
|
|
|
284
326
|
See `test/examples/app_all_events/test_app.rb` for a complete example.
|
|
285
327
|
|
|
@@ -287,9 +329,15 @@ See `test/examples/app_all_events/test_app.rb` for a complete example.
|
|
|
287
329
|
|
|
288
330
|
When UI changes are intentional, regenerate all snapshots:
|
|
289
331
|
|
|
332
|
+
<!-- SPDX-SnippetBegin -->
|
|
333
|
+
<!--
|
|
334
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
335
|
+
SPDX-License-Identifier: MIT-0
|
|
336
|
+
-->
|
|
290
337
|
```bash
|
|
291
338
|
UPDATE_SNAPSHOTS=1 bin/agent_rake test
|
|
292
339
|
```
|
|
340
|
+
<!-- SPDX-SnippetEnd -->
|
|
293
341
|
|
|
294
342
|
## Widget Attribute Cycling
|
|
295
343
|
|
|
@@ -327,6 +375,11 @@ Use hardcoded realistic data. Examples:
|
|
|
327
375
|
**For large datasets (≥ 10 items):**
|
|
328
376
|
Use the [Faker](https://github.com/faker-ruby/faker) gem with **deterministic seeding** so data is consistent across runs:
|
|
329
377
|
|
|
378
|
+
<!-- SPDX-SnippetBegin -->
|
|
379
|
+
<!--
|
|
380
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
381
|
+
SPDX-License-Identifier: MIT-0
|
|
382
|
+
-->
|
|
330
383
|
```ruby
|
|
331
384
|
require "faker"
|
|
332
385
|
|
|
@@ -337,6 +390,7 @@ Faker::Config.random = Random.new(12345)
|
|
|
337
390
|
users = Array.new(50) { Faker::Name.name }
|
|
338
391
|
emails = Array.new(50) { Faker::Internet.email }
|
|
339
392
|
```
|
|
393
|
+
<!-- SPDX-SnippetEnd -->
|
|
340
394
|
|
|
341
395
|
In tests, set the same seed before each test to ensure snapshot consistency.
|
|
342
396
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
3
|
-
|
|
4
|
-
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
5
4
|
-->
|
|
6
5
|
|
|
7
6
|
# Documentation Style Guide
|
|
@@ -33,13 +32,24 @@ Every public class must begin with a **Context-Problem-Solution** narrative.
|
|
|
33
32
|
### Example
|
|
34
33
|
|
|
35
34
|
**Bad (Generic/Descriptive):**
|
|
35
|
+
<!-- SPDX-SnippetBegin -->
|
|
36
|
+
<!--
|
|
37
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
38
|
+
SPDX-License-Identifier: MIT-0
|
|
39
|
+
-->
|
|
36
40
|
```ruby
|
|
37
41
|
# A widget for displaying list items.
|
|
38
42
|
# It allows the user to select an item from an array of strings.
|
|
39
43
|
# Supports scrolling and custom styling.
|
|
40
44
|
```
|
|
45
|
+
<!-- SPDX-SnippetEnd -->
|
|
41
46
|
|
|
42
47
|
**Good (Alexandrian/Zinsser/Plain Language):**
|
|
48
|
+
<!-- SPDX-SnippetBegin -->
|
|
49
|
+
<!--
|
|
50
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
51
|
+
SPDX-License-Identifier: MIT-0
|
|
52
|
+
-->
|
|
43
53
|
```ruby
|
|
44
54
|
# Displays a selectable list of items.
|
|
45
55
|
#
|
|
@@ -50,6 +60,7 @@ Every public class must begin with a **Context-Problem-Solution** narrative.
|
|
|
50
60
|
#
|
|
51
61
|
# Use it to build main menus, navigation sidebars, or logs.
|
|
52
62
|
```
|
|
63
|
+
<!-- SPDX-SnippetEnd -->
|
|
53
64
|
|
|
54
65
|
## 3. Method and Attribute Documentation
|
|
55
66
|
|
|
@@ -74,6 +85,11 @@ Every public class must begin with a **Context-Problem-Solution** narrative.
|
|
|
74
85
|
|
|
75
86
|
### Example
|
|
76
87
|
|
|
88
|
+
<!-- SPDX-SnippetBegin -->
|
|
89
|
+
<!--
|
|
90
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
91
|
+
SPDX-License-Identifier: MIT-0
|
|
92
|
+
-->
|
|
77
93
|
```ruby
|
|
78
94
|
# The styling to apply to the content.
|
|
79
95
|
attr_reader :style
|
|
@@ -86,6 +102,7 @@ Every public class must begin with a **Context-Problem-Solution** narrative.
|
|
|
86
102
|
super
|
|
87
103
|
end
|
|
88
104
|
```
|
|
105
|
+
<!-- SPDX-SnippetEnd -->
|
|
89
106
|
|
|
90
107
|
## 4. RDoc Specifics
|
|
91
108
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Future Work
|
|
7
|
+
|
|
8
|
+
Ideas for post-v1.0.0 development. These do not block the initial release.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Port Upstream Ratatui Examples
|
|
13
|
+
|
|
14
|
+
Ratatui ships [example applications](https://github.com/ratatui/ratatui/tree/main/examples) demonstrating real-world patterns. Porting these to RatatuiRuby would:
|
|
15
|
+
|
|
16
|
+
1. Validate API parity in realistic usage
|
|
17
|
+
2. Provide learning resources for Ruby developers
|
|
18
|
+
3. Surface gaps in the alignment audit
|
|
19
|
+
|
|
20
|
+
**Candidates for porting:**
|
|
21
|
+
|
|
22
|
+
- `demo2` — Kitchen sink showcasing all widgets
|
|
23
|
+
- `async` — Background task handling
|
|
24
|
+
- `user_input` — Text input patterns
|
|
25
|
+
- `popup` — Modal dialogs
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Cross-Platform Distribution
|
|
30
|
+
|
|
31
|
+
[CosmoRuby](https://github.com/igravious/cosmoruby) aims to build Ruby with [Cosmopolitan Libc](https://justine.lol/cosmopolitan/), producing single binaries that run on Linux, macOS, Windows, and BSD without recompilation.
|
|
32
|
+
|
|
33
|
+
**Gap:** RatatuiRuby is a native extension. CosmoRuby does not yet support native extensions.
|
|
34
|
+
|
|
35
|
+
**When this becomes viable:**
|
|
36
|
+
|
|
37
|
+
- CosmoRuby adds native extension support
|
|
38
|
+
- Demand emerges for single-binary TUI app distribution
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Terminal Graphics Protocols
|
|
43
|
+
|
|
44
|
+
Terminal graphics protocols (Sixel, Kitty graphics, iTerm2 inline images) bypass the character cell model. Supporting them requires extension points that do not exist today.
|
|
45
|
+
|
|
46
|
+
### What Third Parties Need
|
|
47
|
+
|
|
48
|
+
| Extension Point | Purpose | Status |
|
|
49
|
+
|-----------------|---------|--------|
|
|
50
|
+
| `Draw::RawCmd` | Write raw escape sequences, bypassing the cell buffer | Not available |
|
|
51
|
+
| Terminal capability queries | Detect if terminal supports Sixel, Kitty, etc. | Not available |
|
|
52
|
+
| Frame hooks | Run code before/after buffer flush | Not available |
|
|
53
|
+
|
|
54
|
+
### Why These Matter
|
|
55
|
+
|
|
56
|
+
The `ratatui-image` crate provides graphics support for Rust Ratatui apps. A `ratatui_ruby-sixels` gem could wrap it, but only if RatatuiRuby exposes:
|
|
57
|
+
|
|
58
|
+
1. **Raw output access** — Sixel data writes directly to stdout as escape sequences
|
|
59
|
+
2. **Capability detection** — Apps need to query terminal support before sending graphics
|
|
60
|
+
3. **Render coordination** — Graphics must be positioned after the cell buffer renders
|
|
61
|
+
|
|
62
|
+
### Implementation Sketch
|
|
63
|
+
|
|
64
|
+
<!-- SPDX-SnippetBegin -->
|
|
65
|
+
<!--
|
|
66
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
67
|
+
SPDX-License-Identifier: MIT-0
|
|
68
|
+
-->
|
|
69
|
+
```ruby
|
|
70
|
+
# Proposed API (not implemented)
|
|
71
|
+
|
|
72
|
+
# 1. Raw draw command
|
|
73
|
+
class Draw
|
|
74
|
+
RawCmd = Data.define(:bytes)
|
|
75
|
+
def self.raw(bytes) = RawCmd.new(bytes:)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# 2. Terminal queries
|
|
79
|
+
RatatuiRuby.terminal_supports?(:sixel) # => true/false
|
|
80
|
+
RatatuiRuby.terminal_supports?(:kitty) # => true/false
|
|
81
|
+
RatatuiRuby.terminal_size_pixels # => { width: 1920, height: 1080 }
|
|
82
|
+
|
|
83
|
+
# 3. Custom widget using raw output
|
|
84
|
+
class SixelImage
|
|
85
|
+
def render(area)
|
|
86
|
+
sixel_data = encode_image_as_sixel(@image, area)
|
|
87
|
+
[RatatuiRuby::Draw.raw(sixel_data)]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
<!-- SPDX-SnippetEnd -->
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Custom Backends
|
|
96
|
+
|
|
97
|
+
Currently hardcoded to Crossterm. A third party might want:
|
|
98
|
+
|
|
99
|
+
- SSH backend (render to a remote terminal)
|
|
100
|
+
- Web backend (render to browser canvas)
|
|
101
|
+
- Recording backend (capture frames for replay)
|
|
102
|
+
|
|
103
|
+
**Gap:** No backend plugin architecture.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Custom Event Sources
|
|
108
|
+
|
|
109
|
+
Currently hardcoded to Crossterm events. A third party might want:
|
|
110
|
+
|
|
111
|
+
- Network events (WebSocket messages as TUI events)
|
|
112
|
+
- File watcher events
|
|
113
|
+
- IPC events
|
|
114
|
+
|
|
115
|
+
**Gap:** No event source plugin architecture.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Event Test Doubles
|
|
120
|
+
|
|
121
|
+
The TestHelper module provides `MockFrame` and `StubRect` for testing render logic in isolation. However, testing `handle_event` currently requires `init_test_terminal` and `inject_test_event`.
|
|
122
|
+
|
|
123
|
+
For pure unit tests of Kit components, stub event objects would be useful:
|
|
124
|
+
|
|
125
|
+
<!-- SPDX-SnippetBegin -->
|
|
126
|
+
<!--
|
|
127
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
128
|
+
SPDX-License-Identifier: MIT-0
|
|
129
|
+
-->
|
|
130
|
+
```ruby
|
|
131
|
+
# Proposed API (not implemented)
|
|
132
|
+
StubKeyEvent = Data.define(:code, :modifiers) do
|
|
133
|
+
def initialize(code:, modifiers: [])
|
|
134
|
+
super
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def key? = true
|
|
138
|
+
def mouse? = false
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
StubMouseEvent = Data.define(:kind, :button, :x, :y, :modifiers) do
|
|
142
|
+
def initialize(kind:, button: "left", x:, y:, modifiers: [])
|
|
143
|
+
super
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def key? = false
|
|
147
|
+
def mouse? = true
|
|
148
|
+
def down? = kind == "down"
|
|
149
|
+
def up? = kind == "up"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Usage
|
|
153
|
+
event = StubKeyEvent.new(code: "a", modifiers: ["ctrl"])
|
|
154
|
+
result = component.handle_event(event)
|
|
155
|
+
assert_equal :consumed, result
|
|
156
|
+
```
|
|
157
|
+
<!-- SPDX-SnippetEnd -->
|
|
158
|
+
|
|
159
|
+
This would let developers test component event handling without terminal dependencies.
|
|
160
|
+
|
|
161
|
+
## Prioritization
|
|
162
|
+
|
|
163
|
+
When prioritizing post-1.0 work, consider:
|
|
164
|
+
|
|
165
|
+
1. **Port upstream examples** — Validates parity, provides learning resources
|
|
166
|
+
2. **`Draw::RawCmd`** — Lowest effort, highest impact for graphics support
|
|
167
|
+
3. **Terminal capability queries** — Required for any graphics work
|
|
168
|
+
4. **Backend plugins** — Large undertaking, defer until clear demand
|
|
169
|
+
5. **CosmoRuby** — Blocked on upstream; monitor progress
|
data/doc/contributors/index.md
CHANGED