ratatui_ruby 1.3.0 → 1.4.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.
Files changed (263) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ratatui_ruby/Cargo.lock +2 -1
  3. data/ext/ratatui_ruby/Cargo.toml +2 -1
  4. data/ext/ratatui_ruby/src/events.rs +157 -18
  5. data/lib/ratatui_ruby/test_helper/snapshot.rb +58 -10
  6. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  7. data/lib/ratatui_ruby/test_helper.rb +2 -0
  8. data/lib/ratatui_ruby/version.rb +1 -1
  9. metadata +17 -270
  10. data/.builds/ruby-3.2.yml +0 -54
  11. data/.builds/ruby-3.3.yml +0 -54
  12. data/.builds/ruby-3.4.yml +0 -54
  13. data/.builds/ruby-4.0.0.yml +0 -54
  14. data/.pre-commit-config.yaml +0 -16
  15. data/.rubocop.yml +0 -10
  16. data/AGENTS.md +0 -147
  17. data/CHANGELOG.md +0 -771
  18. data/README.md +0 -187
  19. data/README.rdoc +0 -302
  20. data/Rakefile +0 -11
  21. data/Steepfile +0 -50
  22. data/doc/concepts/application_architecture.md +0 -321
  23. data/doc/concepts/application_testing.md +0 -193
  24. data/doc/concepts/async.md +0 -190
  25. data/doc/concepts/custom_widgets.md +0 -247
  26. data/doc/concepts/debugging.md +0 -401
  27. data/doc/concepts/event_handling.md +0 -162
  28. data/doc/concepts/interactive_design.md +0 -146
  29. data/doc/contributors/auditing/parity.md +0 -239
  30. data/doc/contributors/design/ruby_frontend.md +0 -448
  31. data/doc/contributors/design/rust_backend.md +0 -434
  32. data/doc/contributors/design.md +0 -11
  33. data/doc/contributors/developing_examples.md +0 -400
  34. data/doc/contributors/documentation_style.md +0 -121
  35. data/doc/contributors/index.md +0 -21
  36. data/doc/contributors/releasing.md +0 -215
  37. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  38. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  39. data/doc/contributors/todo/align/term.md +0 -351
  40. data/doc/contributors/todo/align/terminal.md +0 -647
  41. data/doc/contributors/todo/future_work.md +0 -169
  42. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  43. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  44. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  45. data/doc/custom.css +0 -22
  46. data/doc/getting_started/quickstart.md +0 -291
  47. data/doc/getting_started/why.md +0 -93
  48. data/doc/images/app_all_events.png +0 -0
  49. data/doc/images/app_cli_rich_moments.gif +0 -0
  50. data/doc/images/app_color_picker.png +0 -0
  51. data/doc/images/app_debugging_showcase.gif +0 -0
  52. data/doc/images/app_debugging_showcase.png +0 -0
  53. data/doc/images/app_external_editor.gif +0 -0
  54. data/doc/images/app_login_form.png +0 -0
  55. data/doc/images/app_stateful_interaction.png +0 -0
  56. data/doc/images/verify_quickstart_dsl.png +0 -0
  57. data/doc/images/verify_quickstart_layout.png +0 -0
  58. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  59. data/doc/images/verify_readme_usage.png +0 -0
  60. data/doc/images/widget_barchart.png +0 -0
  61. data/doc/images/widget_block.png +0 -0
  62. data/doc/images/widget_box.png +0 -0
  63. data/doc/images/widget_calendar.png +0 -0
  64. data/doc/images/widget_canvas.png +0 -0
  65. data/doc/images/widget_cell.png +0 -0
  66. data/doc/images/widget_center.png +0 -0
  67. data/doc/images/widget_chart.png +0 -0
  68. data/doc/images/widget_gauge.png +0 -0
  69. data/doc/images/widget_layout_split.png +0 -0
  70. data/doc/images/widget_line_gauge.png +0 -0
  71. data/doc/images/widget_list.png +0 -0
  72. data/doc/images/widget_map.png +0 -0
  73. data/doc/images/widget_overlay.png +0 -0
  74. data/doc/images/widget_popup.png +0 -0
  75. data/doc/images/widget_ratatui_logo.png +0 -0
  76. data/doc/images/widget_ratatui_mascot.png +0 -0
  77. data/doc/images/widget_rect.png +0 -0
  78. data/doc/images/widget_render.png +0 -0
  79. data/doc/images/widget_rich_text.png +0 -0
  80. data/doc/images/widget_scroll_text.png +0 -0
  81. data/doc/images/widget_scrollbar.png +0 -0
  82. data/doc/images/widget_sparkline.png +0 -0
  83. data/doc/images/widget_style_colors.png +0 -0
  84. data/doc/images/widget_table.png +0 -0
  85. data/doc/images/widget_tabs.png +0 -0
  86. data/doc/images/widget_text_width.png +0 -0
  87. data/doc/index.md +0 -34
  88. data/doc/troubleshooting/async.md +0 -4
  89. data/doc/troubleshooting/terminal_limitations.md +0 -131
  90. data/doc/troubleshooting/tui_output.md +0 -197
  91. data/examples/app_all_events/README.md +0 -114
  92. data/examples/app_all_events/app.rb +0 -98
  93. data/examples/app_all_events/model/app_model.rb +0 -159
  94. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  95. data/examples/app_all_events/model/event_entry.rb +0 -94
  96. data/examples/app_all_events/model/msg.rb +0 -39
  97. data/examples/app_all_events/model/timestamp.rb +0 -56
  98. data/examples/app_all_events/update.rb +0 -75
  99. data/examples/app_all_events/view/app_view.rb +0 -80
  100. data/examples/app_all_events/view/controls_view.rb +0 -54
  101. data/examples/app_all_events/view/counts_view.rb +0 -61
  102. data/examples/app_all_events/view/live_view.rb +0 -72
  103. data/examples/app_all_events/view/log_view.rb +0 -57
  104. data/examples/app_all_events/view.rb +0 -9
  105. data/examples/app_cli_rich_moments/README.md +0 -81
  106. data/examples/app_cli_rich_moments/app.rb +0 -189
  107. data/examples/app_color_picker/README.md +0 -156
  108. data/examples/app_color_picker/app.rb +0 -76
  109. data/examples/app_color_picker/clipboard.rb +0 -86
  110. data/examples/app_color_picker/color.rb +0 -193
  111. data/examples/app_color_picker/controls.rb +0 -92
  112. data/examples/app_color_picker/copy_dialog.rb +0 -168
  113. data/examples/app_color_picker/export_pane.rb +0 -128
  114. data/examples/app_color_picker/harmony.rb +0 -58
  115. data/examples/app_color_picker/input.rb +0 -176
  116. data/examples/app_color_picker/main_container.rb +0 -180
  117. data/examples/app_color_picker/palette.rb +0 -111
  118. data/examples/app_debugging_showcase/README.md +0 -119
  119. data/examples/app_debugging_showcase/app.rb +0 -318
  120. data/examples/app_external_editor/README.md +0 -62
  121. data/examples/app_external_editor/app.rb +0 -344
  122. data/examples/app_login_form/README.md +0 -58
  123. data/examples/app_login_form/app.rb +0 -109
  124. data/examples/app_stateful_interaction/README.md +0 -35
  125. data/examples/app_stateful_interaction/app.rb +0 -328
  126. data/examples/timeout_demo.rb +0 -45
  127. data/examples/verify_quickstart_dsl/README.md +0 -55
  128. data/examples/verify_quickstart_dsl/app.rb +0 -49
  129. data/examples/verify_quickstart_layout/README.md +0 -77
  130. data/examples/verify_quickstart_layout/app.rb +0 -73
  131. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  132. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  133. data/examples/verify_readme_usage/README.md +0 -49
  134. data/examples/verify_readme_usage/app.rb +0 -42
  135. data/examples/verify_website_managed/README.md +0 -48
  136. data/examples/verify_website_managed/app.rb +0 -36
  137. data/examples/verify_website_menu/README.md +0 -60
  138. data/examples/verify_website_menu/app.rb +0 -84
  139. data/examples/verify_website_spinner/README.md +0 -44
  140. data/examples/verify_website_spinner/app.rb +0 -34
  141. data/examples/widget_barchart/README.md +0 -58
  142. data/examples/widget_barchart/app.rb +0 -240
  143. data/examples/widget_block/README.md +0 -44
  144. data/examples/widget_block/app.rb +0 -258
  145. data/examples/widget_box/README.md +0 -54
  146. data/examples/widget_box/app.rb +0 -255
  147. data/examples/widget_calendar/README.md +0 -48
  148. data/examples/widget_calendar/app.rb +0 -115
  149. data/examples/widget_canvas/README.md +0 -31
  150. data/examples/widget_canvas/app.rb +0 -130
  151. data/examples/widget_cell/README.md +0 -45
  152. data/examples/widget_cell/app.rb +0 -112
  153. data/examples/widget_center/README.md +0 -33
  154. data/examples/widget_center/app.rb +0 -118
  155. data/examples/widget_chart/README.md +0 -50
  156. data/examples/widget_chart/app.rb +0 -220
  157. data/examples/widget_gauge/README.md +0 -50
  158. data/examples/widget_gauge/app.rb +0 -229
  159. data/examples/widget_layout_split/README.md +0 -53
  160. data/examples/widget_layout_split/app.rb +0 -260
  161. data/examples/widget_line_gauge/README.md +0 -50
  162. data/examples/widget_line_gauge/app.rb +0 -219
  163. data/examples/widget_list/README.md +0 -58
  164. data/examples/widget_list/app.rb +0 -382
  165. data/examples/widget_map/README.md +0 -48
  166. data/examples/widget_map/app.rb +0 -95
  167. data/examples/widget_overlay/README.md +0 -45
  168. data/examples/widget_overlay/app.rb +0 -250
  169. data/examples/widget_popup/README.md +0 -45
  170. data/examples/widget_popup/app.rb +0 -106
  171. data/examples/widget_ratatui_logo/README.md +0 -43
  172. data/examples/widget_ratatui_logo/app.rb +0 -104
  173. data/examples/widget_ratatui_mascot/README.md +0 -43
  174. data/examples/widget_ratatui_mascot/app.rb +0 -95
  175. data/examples/widget_rect/README.md +0 -53
  176. data/examples/widget_rect/app.rb +0 -222
  177. data/examples/widget_render/README.md +0 -46
  178. data/examples/widget_render/app.rb +0 -186
  179. data/examples/widget_render/app.rbs +0 -41
  180. data/examples/widget_rich_text/README.md +0 -44
  181. data/examples/widget_rich_text/app.rb +0 -193
  182. data/examples/widget_scroll_text/README.md +0 -46
  183. data/examples/widget_scroll_text/app.rb +0 -109
  184. data/examples/widget_scrollbar/README.md +0 -46
  185. data/examples/widget_scrollbar/app.rb +0 -155
  186. data/examples/widget_sparkline/README.md +0 -51
  187. data/examples/widget_sparkline/app.rb +0 -277
  188. data/examples/widget_style_colors/README.md +0 -43
  189. data/examples/widget_style_colors/app.rb +0 -83
  190. data/examples/widget_table/README.md +0 -57
  191. data/examples/widget_table/app.rb +0 -285
  192. data/examples/widget_tabs/README.md +0 -50
  193. data/examples/widget_tabs/app.rb +0 -183
  194. data/examples/widget_text_width/README.md +0 -44
  195. data/examples/widget_text_width/app.rb +0 -117
  196. data/migrate_to_buffer.rb +0 -145
  197. data/mise.toml +0 -8
  198. data/tasks/autodoc/examples.rb +0 -87
  199. data/tasks/autodoc/member.rb +0 -58
  200. data/tasks/autodoc/name.rb +0 -21
  201. data/tasks/autodoc.rake +0 -21
  202. data/tasks/bump/bump_workflow.rb +0 -49
  203. data/tasks/bump/cargo_lockfile.rb +0 -21
  204. data/tasks/bump/changelog.rb +0 -104
  205. data/tasks/bump/header.rb +0 -32
  206. data/tasks/bump/history.rb +0 -32
  207. data/tasks/bump/links.rb +0 -69
  208. data/tasks/bump/manifest.rb +0 -33
  209. data/tasks/bump/patch_release.rb +0 -19
  210. data/tasks/bump/release_branch.rb +0 -17
  211. data/tasks/bump/release_from_trunk.rb +0 -49
  212. data/tasks/bump/repository.rb +0 -54
  213. data/tasks/bump/ruby_gem.rb +0 -29
  214. data/tasks/bump/sem_ver.rb +0 -44
  215. data/tasks/bump/unreleased_section.rb +0 -73
  216. data/tasks/bump.rake +0 -61
  217. data/tasks/doc/documentation.rb +0 -59
  218. data/tasks/doc/link/file_url.rb +0 -30
  219. data/tasks/doc/link/relative_path.rb +0 -61
  220. data/tasks/doc/link/web_url.rb +0 -55
  221. data/tasks/doc/link.rb +0 -52
  222. data/tasks/doc/link_audit.rb +0 -116
  223. data/tasks/doc/problem.rb +0 -40
  224. data/tasks/doc/source_file.rb +0 -93
  225. data/tasks/doc.rake +0 -905
  226. data/tasks/example_viewer.html.erb +0 -172
  227. data/tasks/extension.rake +0 -14
  228. data/tasks/license/headers_md.rb +0 -223
  229. data/tasks/license/headers_rb.rb +0 -210
  230. data/tasks/license/license_utils.rb +0 -130
  231. data/tasks/license/snippets_md.rb +0 -315
  232. data/tasks/license/snippets_rdoc.rb +0 -150
  233. data/tasks/license.rake +0 -91
  234. data/tasks/lint.rake +0 -170
  235. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  236. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  237. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  238. data/tasks/rbs_predicates.rake +0 -31
  239. data/tasks/rdoc_config.rb +0 -29
  240. data/tasks/resources/build.yml.erb +0 -60
  241. data/tasks/resources/index.html.erb +0 -141
  242. data/tasks/resources/rubies.yml +0 -7
  243. data/tasks/sourcehut.rake +0 -122
  244. data/tasks/steep.rake +0 -11
  245. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  246. data/tasks/terminal_preview/crash_report.rb +0 -54
  247. data/tasks/terminal_preview/example_app.rb +0 -27
  248. data/tasks/terminal_preview/launcher_script.rb +0 -48
  249. data/tasks/terminal_preview/preview_collection.rb +0 -60
  250. data/tasks/terminal_preview/preview_timing.rb +0 -24
  251. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  252. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  253. data/tasks/terminal_preview/system_appearance.rb +0 -13
  254. data/tasks/terminal_preview/terminal_window.rb +0 -138
  255. data/tasks/terminal_preview/window_id.rb +0 -16
  256. data/tasks/terminal_preview.rake +0 -30
  257. data/tasks/test.rake +0 -36
  258. data/tasks/website/index_page.rb +0 -30
  259. data/tasks/website/version.rb +0 -122
  260. data/tasks/website/version_menu.rb +0 -68
  261. data/tasks/website/versioned_documentation.rb +0 -83
  262. data/tasks/website/website.rb +0 -53
  263. data/tasks/website.rake +0 -28
@@ -1,448 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Ruby Frontend Design (`ratatui_ruby`)
7
-
8
- This document describes the architectural design and guiding principles of the Ruby layer in `ratatui_ruby`. It is intended for contributors, architects, and AI agents working on the codebase.
9
-
10
- ## Guiding Design Principles
11
-
12
- ### 1. Three-Tier Namespace Architecture
13
-
14
- The gem provides three distinct namespaces, each with a specific purpose:
15
-
16
- **Tier 1: `Ratatui::` — Upstream Alignment**
17
-
18
- Pure upstream Ratatui types with 1:1 API correspondence. If it exists in Rust Ratatui, it has the same name and location here.
19
-
20
- | Rust Module | Ruby Module | Purpose |
21
- |-------------|-------------|---------|
22
- | `ratatui::layout` | `Ratatui::Layout` | Rect, Constraint, Layout, Position, Size |
23
- | `ratatui::widgets` | `Ratatui::Widgets` | All widgets (Table, List, Paragraph, Block, etc.) |
24
- | `ratatui::style` | `Ratatui::Style` | Style, Color |
25
- | `ratatui::text` | `Ratatui::Text` | Span, Line |
26
- | `ratatui::buffer` | `Ratatui::Buffer` | Cell (for buffer inspection) |
27
- | `ratatui::backend` | `Ratatui::Backend` | WindowSize |
28
- | `ratatui::Terminal` | `Ratatui::Terminal` | Terminal lifecycle (draw, size, cursor) |
29
- | `ratatui::Frame` | `Ratatui::Frame` | Frame object for render callbacks |
30
-
31
- **Tier 2: `Crossterm::` — Backend Alignment**
32
-
33
- Direct exposure of crossterm functionality. These are terminal I/O primitives that Ratatui builds upon.
34
-
35
- | Rust Module | Ruby Module | Purpose |
36
- |-------------|-------------|---------|
37
- | `crossterm::terminal` | `Crossterm::Terminal` | `supports_keyboard_enhancement?`, `window_size` |
38
- | `crossterm::style` | `Crossterm::Style` | `available_color_count`, `force_color_output` |
39
-
40
- **Tier 3: `RatatuiRuby::` — Ruby Convenience Layer**
41
-
42
- Ruby-specific conveniences, the main entry point, and the TUI DSL facade. This is where Ruby idioms live.
43
-
44
- - `RatatuiRuby.run { |tui| ... }` — Main entry point with setup/teardown
45
- - `RatatuiRuby.tty?`, `RatatuiRuby.dumb?`, `RatatuiRuby.interactive?` — Environment detection (Ruby-specific)
46
- - `RatatuiRuby::TUI` — DSL facade with shorthand factory methods
47
- - `RatatuiRuby::TestHelper` — Testing utilities
48
- - `RatatuiRuby::Error` — Exception hierarchy
49
-
50
- **Why Three Tiers?**
51
-
52
- 1. **Upstream Purity**: Users who want exact Ratatui API parity use `Ratatui::` and `Crossterm::` namespaces.
53
- 2. **Ruby Idioms**: Users who want convenience use `RatatuiRuby.run`, the TUI facade, and helper methods.
54
- 3. **Documentation Mapping**: A contributor reading Ratatui's Rust docs immediately knows where to find the Ruby equivalent.
55
- 4. **Predictability**: As upstream adds types, their Ruby placement is deterministic.
56
-
57
- > [!NOTE]
58
- > This three-tier architecture will be rolled out incrementally as 1.x releases. The `Ratatui::` and `Crossterm::` namespaces are additive—`RatatuiRuby::` will continue to work by delegating to them. No breaking changes required.
59
-
60
- ### 2. Two-Layer Architecture
61
-
62
- The Ruby frontend implements a "Mullet Architecture": structured namespaces in the library, flat ergonomic DSL for users.
63
-
64
- **Layer 1: Schema Classes (The Library)**
65
-
66
- Located in `lib/ratatui_ruby/widgets/`, `lib/ratatui_ruby/layout/`, etc.
67
-
68
- These are the actual `Data.define` classes that the Rust backend expects. They have deep, explicit namespaces that match Ratatui:
69
-
70
- <!-- SPDX-SnippetBegin -->
71
- <!--
72
- SPDX-FileCopyrightText: 2026 Kerrick Long
73
- SPDX-License-Identifier: MIT-0
74
- -->
75
- ```ruby
76
- RatatuiRuby::Widgets::Paragraph.new(text: "Hello")
77
- RatatuiRuby::Layout::Constraint.length(20)
78
- RatatuiRuby::Style::Style.new(fg: :red)
79
- ```
80
- <!-- SPDX-SnippetEnd -->
81
-
82
- **Layer 2: TUI Facade (The DSL)**
83
-
84
- Located in `lib/ratatui_ruby/tui.rb` and `lib/ratatui_ruby/tui/*.rb` mixins.
85
-
86
- The `TUI` class provides shorthand factory methods that hide namespace verbosity:
87
-
88
- <!-- SPDX-SnippetBegin -->
89
- <!--
90
- SPDX-FileCopyrightText: 2026 Kerrick Long
91
- SPDX-License-Identifier: MIT-0
92
- -->
93
- ```ruby
94
- RatatuiRuby.run do |tui|
95
- tui.paragraph(text: "Hello")
96
- tui.constraint_length(20)
97
- tui.style(fg: :red)
98
- end
99
- ```
100
- <!-- SPDX-SnippetEnd -->
101
-
102
- **Why This Matters:**
103
-
104
- Users write application code using the TUI API and rarely touch deep namespaces. Contributors maintaining the library work with explicit, documentable, IDE-friendly classes. Both audiences are served without compromise.
105
-
106
- ### 3. Explicit Over Magic
107
-
108
- The TUI facade uses explicit factory method definitions, not runtime metaprogramming.
109
-
110
- **What We Do:**
111
-
112
- <!-- SPDX-SnippetBegin -->
113
- <!--
114
- SPDX-FileCopyrightText: 2026 Kerrick Long
115
- SPDX-License-Identifier: MIT-0
116
- -->
117
- ```ruby
118
- # lib/ratatui_ruby/tui/widget_factories.rb
119
- module RatatuiRuby
120
- class TUI
121
- module WidgetFactories
122
- def paragraph(**kwargs)
123
- Widgets::Paragraph.new(**kwargs)
124
- end
125
-
126
- def table(**kwargs)
127
- Widgets::Table.new(**kwargs)
128
- end
129
- end
130
- end
131
- end
132
- ```
133
- <!-- SPDX-SnippetEnd -->
134
-
135
- **What We Don't Do:**
136
-
137
- <!-- SPDX-SnippetBegin -->
138
- <!--
139
- SPDX-FileCopyrightText: 2026 Kerrick Long
140
- SPDX-License-Identifier: MIT-0
141
- -->
142
- ```ruby
143
- # NO: Dynamic method generation
144
- RatatuiRuby.constants.each do |const|
145
- define_method(const.underscore) { |**kw| RatatuiRuby.const_get(const).new(**kw) }
146
- end
147
- ```
148
- <!-- SPDX-SnippetEnd -->
149
-
150
- **Benefits of Explicit Definitions:**
151
-
152
- 1. **IDE Support**: Solargraph and Ruby LSP provide autocomplete because methods exist at parse time.
153
- 2. **RDoc**: Each method can have its own documentation with examples.
154
- 3. **RBS Types**: Each method has an explicit type signature.
155
- 4. **Debugging**: Stack traces show real method names, not `define_method` closures.
156
- 5. **Decoupling**: Internal class names can change without breaking the public TUI API.
157
-
158
- ### 4. Data-Driven UI (Immediate Mode)
159
-
160
- All UI components are pure, immutable `Data.define` value objects. They describe *desired appearance* for a single frame, not live stateful objects.
161
-
162
- **Widgets Are Inputs:**
163
-
164
- <!-- SPDX-SnippetBegin -->
165
- <!--
166
- SPDX-FileCopyrightText: 2026 Kerrick Long
167
- SPDX-License-Identifier: MIT-0
168
- -->
169
- ```ruby
170
- # This is just data. It has no behavior, no side effects.
171
- paragraph = RatatuiRuby::Widgets::Paragraph.new(
172
- text: "Hello",
173
- style: RatatuiRuby::Style::Style.new(fg: :red)
174
- )
175
-
176
- # Pass to renderer as input
177
- frame.render_widget(paragraph, area)
178
- ```
179
- <!-- SPDX-SnippetEnd -->
180
-
181
- **Immediate Mode Loop:**
182
-
183
- Every frame, the application constructs a fresh view tree and passes it to `draw`. No widget state persists between frames. This is Ratatui's core paradigm.
184
-
185
- <!-- SPDX-SnippetBegin -->
186
- <!--
187
- SPDX-FileCopyrightText: 2026 Kerrick Long
188
- SPDX-License-Identifier: MIT-0
189
- -->
190
- ```ruby
191
- loop do
192
- tui.draw do |frame|
193
- # Fresh tree every frame
194
- frame.render_widget(tui.paragraph(text: "Time: #{Time.now}"), frame.area)
195
- end
196
- break if tui.poll_event.key? && tui.poll_event.code == "q"
197
- end
198
- ```
199
- <!-- SPDX-SnippetEnd -->
200
-
201
- ### 5. Separation of Configuration and Status
202
-
203
- Widgets (Configuration) and State (Status) are strictly separated.
204
-
205
- **Configuration (Input):**
206
-
207
- Widgets define *what* to render. They are created, rendered, and discarded.
208
-
209
- <!-- SPDX-SnippetBegin -->
210
- <!--
211
- SPDX-FileCopyrightText: 2026 Kerrick Long
212
- SPDX-License-Identifier: MIT-0
213
- -->
214
- ```ruby
215
- list = tui.list(items: ["A", "B", "C", "D", "E"])
216
- ```
217
- <!-- SPDX-SnippetEnd -->
218
-
219
- **Status (Output):**
220
-
221
- State objects track *runtime metrics* computed by the Rust backend: scroll offsets, selection positions, etc. They persist across frames.
222
-
223
- <!-- SPDX-SnippetBegin -->
224
- <!--
225
- SPDX-FileCopyrightText: 2026 Kerrick Long
226
- SPDX-License-Identifier: MIT-0
227
- -->
228
- ```ruby
229
- # Created once
230
- @list_state = RatatuiRuby::ListState.new
231
-
232
- # Used every frame
233
- frame.render_stateful_widget(list, area, @list_state)
234
-
235
- # Read back computed values
236
- puts "Scroll offset: #{@list_state.offset}"
237
- ```
238
- <!-- SPDX-SnippetEnd -->
239
-
240
- **Precedence Rule:**
241
-
242
- When using `render_stateful_widget`, the State object is the source of truth. Widget properties like `selected_index` are ignored.
243
-
244
- ### 6. No Render Logic in Ruby
245
-
246
- Ruby defines data structures. Rust renders them.
247
-
248
- The classes in `lib/ratatui_ruby/widgets/` contain no rendering code. They are pure structural definitions that the Rust extension walks and converts to Ratatui primitives.
249
-
250
- **Ruby's Job:**
251
- - Define `Data.define` classes with attributes
252
- - Validate inputs (types, ranges)
253
- - Provide convenience constructors
254
-
255
- **Rust's Job:**
256
- - Walk the Ruby object tree
257
- - Extract attributes via `funcall`
258
- - Construct Ratatui widgets
259
- - Render to the terminal buffer
260
-
261
- This separation ensures rendering performance remains in Rust while Ruby handles the ergonomic API layer.
262
-
263
- ---
264
-
265
- ## Directory Structure
266
-
267
- <!-- SPDX-SnippetBegin -->
268
- <!--
269
- SPDX-FileCopyrightText: 2026 Kerrick Long
270
- SPDX-License-Identifier: MIT-0
271
- -->
272
- ```
273
- lib/ratatui_ruby/
274
- ├── tui.rb # TUI class, includes all mixins
275
- ├── tui/ # TUI facade mixins
276
- │ ├── core.rb # draw, poll_event, get_cell_at
277
- │ ├── layout_factories.rb # rect, constraint_*, layout_split
278
- │ ├── style_factories.rb # style
279
- │ ├── widget_factories.rb # paragraph, block, table, list, etc.
280
- │ ├── text_factories.rb # span, line, text_width
281
- │ ├── state_factories.rb # list_state, table_state, scrollbar_state
282
- │ ├── canvas_factories.rb # shape_map, shape_line, etc.
283
- │ └── buffer_factories.rb # cell (for buffer inspection)
284
- ├── layout/ # ratatui::layout
285
- │ ├── rect.rb
286
- │ ├── constraint.rb
287
- │ └── layout.rb
288
- ├── widgets/ # ratatui::widgets
289
- │ ├── paragraph.rb
290
- │ ├── block.rb
291
- │ ├── table.rb
292
- │ ├── list.rb
293
- │ ├── row.rb # Table row wrapper
294
- │ ├── cell.rb # Table cell wrapper (NOT buffer cell)
295
- │ └── ...
296
- ├── style/ # ratatui::style
297
- │ └── style.rb
298
- ├── text/ # ratatui::text
299
- │ ├── span.rb
300
- │ └── line.rb
301
- ├── buffer/ # ratatui::buffer
302
- │ └── cell.rb # For get_cell_at inspection
303
- └── schema/ # Legacy location (being migrated)
304
- ```
305
- <!-- SPDX-SnippetEnd -->
306
-
307
- ---
308
-
309
- ## Adding a New Widget
310
-
311
- ### Step 1: Create the Schema Class
312
-
313
- Define the Data class in the appropriate namespace directory:
314
-
315
- <!-- SPDX-SnippetBegin -->
316
- <!--
317
- SPDX-FileCopyrightText: 2026 Kerrick Long
318
- SPDX-License-Identifier: MIT-0
319
- -->
320
- ```ruby
321
- # lib/ratatui_ruby/widgets/my_widget.rb
322
- module RatatuiRuby
323
- module Widgets
324
- # A widget that displays foo with optional styling.
325
- #
326
- # [content] The text content to display.
327
- # [style] Optional styling for the content.
328
- # [block] Optional block border wrapper.
329
- class MyWidget < Data.define(:content, :style, :block)
330
- def initialize(content:, style: nil, block: nil)
331
- super
332
- end
333
- end
334
- end
335
- end
336
- ```
337
- <!-- SPDX-SnippetEnd -->
338
-
339
- ### Step 2: Add the RBS Type
340
-
341
- <!-- SPDX-SnippetBegin -->
342
- <!--
343
- SPDX-FileCopyrightText: 2026 Kerrick Long
344
- SPDX-License-Identifier: MIT-0
345
- -->
346
- ```rbs
347
- # sig/ratatui_ruby/widgets/my_widget.rbs
348
- module RatatuiRuby
349
- module Widgets
350
- class MyWidget < Data
351
- attr_reader content: String
352
- attr_reader style: Style::Style?
353
- attr_reader block: Block?
354
-
355
- def self.new: (content: String, ?style: Style::Style?, ?block: Block?) -> MyWidget
356
- end
357
- end
358
- end
359
- ```
360
- <!-- SPDX-SnippetEnd -->
361
-
362
- ### Step 3: Add the TUI Factory Method
363
-
364
- <!-- SPDX-SnippetBegin -->
365
- <!--
366
- SPDX-FileCopyrightText: 2026 Kerrick Long
367
- SPDX-License-Identifier: MIT-0
368
- -->
369
- ```ruby
370
- # lib/ratatui_ruby/tui/widget_factories.rb
371
- def my_widget(**kwargs)
372
- Widgets::MyWidget.new(**kwargs)
373
- end
374
- ```
375
- <!-- SPDX-SnippetEnd -->
376
-
377
- ### Step 4: Implement Rust Rendering
378
-
379
- See `rust_backend.md` for the Rust implementation steps.
380
-
381
- ### Step 5: Register in Requires
382
-
383
- Add to `lib/ratatui_ruby.rb`:
384
-
385
- <!-- SPDX-SnippetBegin -->
386
- <!--
387
- SPDX-FileCopyrightText: 2026 Kerrick Long
388
- SPDX-License-Identifier: MIT-0
389
- -->
390
- ```ruby
391
- require_relative "ratatui_ruby/widgets/my_widget"
392
- ```
393
- <!-- SPDX-SnippetEnd -->
394
-
395
- ---
396
-
397
- ## TUI Mixin Architecture
398
-
399
- The `TUI` class is composed of 8 focused mixins, each with a single responsibility:
400
-
401
- | Mixin | Methods | Purpose |
402
- |-------|---------|---------|
403
- | `Core` | `draw`, `poll_event`, `get_cell_at`, `draw_cell` | Terminal I/O operations |
404
- | `LayoutFactories` | `rect`, `constraint_*`, `layout`, `layout_split` | Layout construction |
405
- | `StyleFactories` | `style` | Style construction |
406
- | `WidgetFactories` | `paragraph`, `block`, `table`, `list`, etc. | Widget construction |
407
- | `TextFactories` | `span`, `line`, `text_width` | Text construction |
408
- | `StateFactories` | `list_state`, `table_state`, `scrollbar_state` | State object construction |
409
- | `CanvasFactories` | `shape_map`, `shape_line`, `shape_circle`, etc. | Canvas shape construction |
410
- | `BufferFactories` | `cell` | Buffer cell construction (for testing) |
411
-
412
- This modular structure keeps each file focused (~20-50 lines) and makes it easy to locate and modify factory methods.
413
-
414
- ---
415
-
416
- ## Thread and Ractor Safety
417
-
418
- ### Shareable (Frozen Data Objects)
419
-
420
- These are deeply frozen and `Ractor.shareable?`:
421
-
422
- - `Event::*` objects from `poll_event`
423
- - `Buffer::Cell` objects from `get_cell_at`
424
- - `Layout::Rect` objects from `Layout.split`
425
-
426
- ### Not Shareable (I/O Handles)
427
-
428
- These have side effects and are intentionally not Ractor-safe:
429
-
430
- - `TUI` — Has terminal I/O methods
431
- - `Frame` — Valid only during the `draw` block; invalid after
432
-
433
- <!-- SPDX-SnippetBegin -->
434
- <!--
435
- SPDX-FileCopyrightText: 2026 Kerrick Long
436
- SPDX-License-Identifier: MIT-0
437
- -->
438
- ```ruby
439
- # OK: Cache TUI during run loop
440
- RatatuiRuby.run do |tui|
441
- @tui = tui
442
- loop { render; handle_input }
443
- end
444
-
445
- # NOT OK: Include in immutable Model
446
- Model = Data.define(:tui, :count) # Don't do this
447
- ```
448
- <!-- SPDX-SnippetEnd -->