ratatui_ruby 1.2.0 → 1.2.2

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 (260) 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/version.rb +1 -1
  6. metadata +1 -255
  7. data/.builds/ruby-3.2.yml +0 -54
  8. data/.builds/ruby-3.3.yml +0 -54
  9. data/.builds/ruby-3.4.yml +0 -54
  10. data/.builds/ruby-4.0.0.yml +0 -54
  11. data/.pre-commit-config.yaml +0 -16
  12. data/.rubocop.yml +0 -10
  13. data/AGENTS.md +0 -147
  14. data/CHANGELOG.md +0 -751
  15. data/README.md +0 -187
  16. data/README.rdoc +0 -302
  17. data/Rakefile +0 -11
  18. data/Steepfile +0 -50
  19. data/doc/concepts/application_architecture.md +0 -321
  20. data/doc/concepts/application_testing.md +0 -193
  21. data/doc/concepts/async.md +0 -190
  22. data/doc/concepts/custom_widgets.md +0 -247
  23. data/doc/concepts/debugging.md +0 -401
  24. data/doc/concepts/event_handling.md +0 -162
  25. data/doc/concepts/interactive_design.md +0 -146
  26. data/doc/contributors/auditing/parity.md +0 -239
  27. data/doc/contributors/design/ruby_frontend.md +0 -448
  28. data/doc/contributors/design/rust_backend.md +0 -434
  29. data/doc/contributors/design.md +0 -11
  30. data/doc/contributors/developing_examples.md +0 -400
  31. data/doc/contributors/documentation_style.md +0 -121
  32. data/doc/contributors/index.md +0 -21
  33. data/doc/contributors/releasing.md +0 -215
  34. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  35. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  36. data/doc/contributors/todo/align/term.md +0 -351
  37. data/doc/contributors/todo/align/terminal.md +0 -647
  38. data/doc/contributors/todo/future_work.md +0 -169
  39. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  40. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  41. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  42. data/doc/custom.css +0 -22
  43. data/doc/getting_started/quickstart.md +0 -291
  44. data/doc/getting_started/why.md +0 -93
  45. data/doc/images/app_all_events.png +0 -0
  46. data/doc/images/app_cli_rich_moments.gif +0 -0
  47. data/doc/images/app_color_picker.png +0 -0
  48. data/doc/images/app_debugging_showcase.gif +0 -0
  49. data/doc/images/app_debugging_showcase.png +0 -0
  50. data/doc/images/app_external_editor.gif +0 -0
  51. data/doc/images/app_login_form.png +0 -0
  52. data/doc/images/app_stateful_interaction.png +0 -0
  53. data/doc/images/verify_quickstart_dsl.png +0 -0
  54. data/doc/images/verify_quickstart_layout.png +0 -0
  55. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  56. data/doc/images/verify_readme_usage.png +0 -0
  57. data/doc/images/widget_barchart.png +0 -0
  58. data/doc/images/widget_block.png +0 -0
  59. data/doc/images/widget_box.png +0 -0
  60. data/doc/images/widget_calendar.png +0 -0
  61. data/doc/images/widget_canvas.png +0 -0
  62. data/doc/images/widget_cell.png +0 -0
  63. data/doc/images/widget_center.png +0 -0
  64. data/doc/images/widget_chart.png +0 -0
  65. data/doc/images/widget_gauge.png +0 -0
  66. data/doc/images/widget_layout_split.png +0 -0
  67. data/doc/images/widget_line_gauge.png +0 -0
  68. data/doc/images/widget_list.png +0 -0
  69. data/doc/images/widget_map.png +0 -0
  70. data/doc/images/widget_overlay.png +0 -0
  71. data/doc/images/widget_popup.png +0 -0
  72. data/doc/images/widget_ratatui_logo.png +0 -0
  73. data/doc/images/widget_ratatui_mascot.png +0 -0
  74. data/doc/images/widget_rect.png +0 -0
  75. data/doc/images/widget_render.png +0 -0
  76. data/doc/images/widget_rich_text.png +0 -0
  77. data/doc/images/widget_scroll_text.png +0 -0
  78. data/doc/images/widget_scrollbar.png +0 -0
  79. data/doc/images/widget_sparkline.png +0 -0
  80. data/doc/images/widget_style_colors.png +0 -0
  81. data/doc/images/widget_table.png +0 -0
  82. data/doc/images/widget_tabs.png +0 -0
  83. data/doc/images/widget_text_width.png +0 -0
  84. data/doc/index.md +0 -34
  85. data/doc/troubleshooting/async.md +0 -4
  86. data/doc/troubleshooting/terminal_limitations.md +0 -131
  87. data/doc/troubleshooting/tui_output.md +0 -197
  88. data/examples/app_all_events/README.md +0 -114
  89. data/examples/app_all_events/app.rb +0 -98
  90. data/examples/app_all_events/model/app_model.rb +0 -159
  91. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  92. data/examples/app_all_events/model/event_entry.rb +0 -94
  93. data/examples/app_all_events/model/msg.rb +0 -39
  94. data/examples/app_all_events/model/timestamp.rb +0 -56
  95. data/examples/app_all_events/update.rb +0 -75
  96. data/examples/app_all_events/view/app_view.rb +0 -80
  97. data/examples/app_all_events/view/controls_view.rb +0 -54
  98. data/examples/app_all_events/view/counts_view.rb +0 -61
  99. data/examples/app_all_events/view/live_view.rb +0 -72
  100. data/examples/app_all_events/view/log_view.rb +0 -57
  101. data/examples/app_all_events/view.rb +0 -9
  102. data/examples/app_cli_rich_moments/README.md +0 -81
  103. data/examples/app_cli_rich_moments/app.rb +0 -189
  104. data/examples/app_color_picker/README.md +0 -156
  105. data/examples/app_color_picker/app.rb +0 -76
  106. data/examples/app_color_picker/clipboard.rb +0 -86
  107. data/examples/app_color_picker/color.rb +0 -193
  108. data/examples/app_color_picker/controls.rb +0 -92
  109. data/examples/app_color_picker/copy_dialog.rb +0 -168
  110. data/examples/app_color_picker/export_pane.rb +0 -128
  111. data/examples/app_color_picker/harmony.rb +0 -58
  112. data/examples/app_color_picker/input.rb +0 -176
  113. data/examples/app_color_picker/main_container.rb +0 -180
  114. data/examples/app_color_picker/palette.rb +0 -111
  115. data/examples/app_debugging_showcase/README.md +0 -119
  116. data/examples/app_debugging_showcase/app.rb +0 -318
  117. data/examples/app_external_editor/README.md +0 -62
  118. data/examples/app_external_editor/app.rb +0 -344
  119. data/examples/app_login_form/README.md +0 -58
  120. data/examples/app_login_form/app.rb +0 -109
  121. data/examples/app_stateful_interaction/README.md +0 -35
  122. data/examples/app_stateful_interaction/app.rb +0 -328
  123. data/examples/timeout_demo.rb +0 -45
  124. data/examples/verify_quickstart_dsl/README.md +0 -55
  125. data/examples/verify_quickstart_dsl/app.rb +0 -49
  126. data/examples/verify_quickstart_layout/README.md +0 -77
  127. data/examples/verify_quickstart_layout/app.rb +0 -73
  128. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  129. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  130. data/examples/verify_readme_usage/README.md +0 -49
  131. data/examples/verify_readme_usage/app.rb +0 -42
  132. data/examples/verify_website_managed/README.md +0 -48
  133. data/examples/verify_website_managed/app.rb +0 -36
  134. data/examples/verify_website_menu/README.md +0 -60
  135. data/examples/verify_website_menu/app.rb +0 -84
  136. data/examples/verify_website_spinner/README.md +0 -44
  137. data/examples/verify_website_spinner/app.rb +0 -34
  138. data/examples/widget_barchart/README.md +0 -58
  139. data/examples/widget_barchart/app.rb +0 -240
  140. data/examples/widget_block/README.md +0 -44
  141. data/examples/widget_block/app.rb +0 -258
  142. data/examples/widget_box/README.md +0 -54
  143. data/examples/widget_box/app.rb +0 -255
  144. data/examples/widget_calendar/README.md +0 -48
  145. data/examples/widget_calendar/app.rb +0 -115
  146. data/examples/widget_canvas/README.md +0 -31
  147. data/examples/widget_canvas/app.rb +0 -130
  148. data/examples/widget_cell/README.md +0 -45
  149. data/examples/widget_cell/app.rb +0 -112
  150. data/examples/widget_center/README.md +0 -33
  151. data/examples/widget_center/app.rb +0 -118
  152. data/examples/widget_chart/README.md +0 -50
  153. data/examples/widget_chart/app.rb +0 -220
  154. data/examples/widget_gauge/README.md +0 -50
  155. data/examples/widget_gauge/app.rb +0 -229
  156. data/examples/widget_layout_split/README.md +0 -53
  157. data/examples/widget_layout_split/app.rb +0 -260
  158. data/examples/widget_line_gauge/README.md +0 -50
  159. data/examples/widget_line_gauge/app.rb +0 -219
  160. data/examples/widget_list/README.md +0 -58
  161. data/examples/widget_list/app.rb +0 -382
  162. data/examples/widget_map/README.md +0 -48
  163. data/examples/widget_map/app.rb +0 -95
  164. data/examples/widget_overlay/README.md +0 -45
  165. data/examples/widget_overlay/app.rb +0 -250
  166. data/examples/widget_popup/README.md +0 -45
  167. data/examples/widget_popup/app.rb +0 -106
  168. data/examples/widget_ratatui_logo/README.md +0 -43
  169. data/examples/widget_ratatui_logo/app.rb +0 -104
  170. data/examples/widget_ratatui_mascot/README.md +0 -43
  171. data/examples/widget_ratatui_mascot/app.rb +0 -95
  172. data/examples/widget_rect/README.md +0 -53
  173. data/examples/widget_rect/app.rb +0 -222
  174. data/examples/widget_render/README.md +0 -46
  175. data/examples/widget_render/app.rb +0 -186
  176. data/examples/widget_render/app.rbs +0 -41
  177. data/examples/widget_rich_text/README.md +0 -44
  178. data/examples/widget_rich_text/app.rb +0 -193
  179. data/examples/widget_scroll_text/README.md +0 -46
  180. data/examples/widget_scroll_text/app.rb +0 -109
  181. data/examples/widget_scrollbar/README.md +0 -46
  182. data/examples/widget_scrollbar/app.rb +0 -155
  183. data/examples/widget_sparkline/README.md +0 -51
  184. data/examples/widget_sparkline/app.rb +0 -277
  185. data/examples/widget_style_colors/README.md +0 -43
  186. data/examples/widget_style_colors/app.rb +0 -83
  187. data/examples/widget_table/README.md +0 -57
  188. data/examples/widget_table/app.rb +0 -285
  189. data/examples/widget_tabs/README.md +0 -50
  190. data/examples/widget_tabs/app.rb +0 -183
  191. data/examples/widget_text_width/README.md +0 -44
  192. data/examples/widget_text_width/app.rb +0 -117
  193. data/migrate_to_buffer.rb +0 -145
  194. data/mise.toml +0 -8
  195. data/tasks/autodoc/examples.rb +0 -87
  196. data/tasks/autodoc/member.rb +0 -58
  197. data/tasks/autodoc/name.rb +0 -21
  198. data/tasks/autodoc.rake +0 -21
  199. data/tasks/bump/bump_workflow.rb +0 -49
  200. data/tasks/bump/cargo_lockfile.rb +0 -21
  201. data/tasks/bump/changelog.rb +0 -104
  202. data/tasks/bump/header.rb +0 -32
  203. data/tasks/bump/history.rb +0 -32
  204. data/tasks/bump/links.rb +0 -69
  205. data/tasks/bump/manifest.rb +0 -33
  206. data/tasks/bump/patch_release.rb +0 -19
  207. data/tasks/bump/release_branch.rb +0 -17
  208. data/tasks/bump/release_from_trunk.rb +0 -49
  209. data/tasks/bump/repository.rb +0 -54
  210. data/tasks/bump/ruby_gem.rb +0 -29
  211. data/tasks/bump/sem_ver.rb +0 -44
  212. data/tasks/bump/unreleased_section.rb +0 -73
  213. data/tasks/bump.rake +0 -61
  214. data/tasks/doc/documentation.rb +0 -59
  215. data/tasks/doc/link/file_url.rb +0 -30
  216. data/tasks/doc/link/relative_path.rb +0 -61
  217. data/tasks/doc/link/web_url.rb +0 -55
  218. data/tasks/doc/link.rb +0 -52
  219. data/tasks/doc/link_audit.rb +0 -116
  220. data/tasks/doc/problem.rb +0 -40
  221. data/tasks/doc/source_file.rb +0 -93
  222. data/tasks/doc.rake +0 -905
  223. data/tasks/example_viewer.html.erb +0 -172
  224. data/tasks/extension.rake +0 -14
  225. data/tasks/license/headers_md.rb +0 -223
  226. data/tasks/license/headers_rb.rb +0 -210
  227. data/tasks/license/license_utils.rb +0 -130
  228. data/tasks/license/snippets_md.rb +0 -315
  229. data/tasks/license/snippets_rdoc.rb +0 -150
  230. data/tasks/license.rake +0 -91
  231. data/tasks/lint.rake +0 -170
  232. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  233. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  234. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  235. data/tasks/rbs_predicates.rake +0 -31
  236. data/tasks/rdoc_config.rb +0 -29
  237. data/tasks/resources/build.yml.erb +0 -60
  238. data/tasks/resources/index.html.erb +0 -141
  239. data/tasks/resources/rubies.yml +0 -7
  240. data/tasks/sourcehut.rake +0 -122
  241. data/tasks/steep.rake +0 -11
  242. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  243. data/tasks/terminal_preview/crash_report.rb +0 -54
  244. data/tasks/terminal_preview/example_app.rb +0 -27
  245. data/tasks/terminal_preview/launcher_script.rb +0 -48
  246. data/tasks/terminal_preview/preview_collection.rb +0 -60
  247. data/tasks/terminal_preview/preview_timing.rb +0 -24
  248. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  249. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  250. data/tasks/terminal_preview/system_appearance.rb +0 -13
  251. data/tasks/terminal_preview/terminal_window.rb +0 -138
  252. data/tasks/terminal_preview/window_id.rb +0 -16
  253. data/tasks/terminal_preview.rake +0 -30
  254. data/tasks/test.rake +0 -36
  255. data/tasks/website/index_page.rb +0 -30
  256. data/tasks/website/version.rb +0 -122
  257. data/tasks/website/version_menu.rb +0 -68
  258. data/tasks/website/versioned_documentation.rb +0 -83
  259. data/tasks/website/website.rb +0 -53
  260. 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 -->