ratatui_ruby 1.2.0 → 1.2.1

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