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