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,162 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Event Handling
7
-
8
- `ratatui_ruby` provides a rich, object-oriented event system that supports multiple coding styles, from simple boolean predicates to modern Ruby pattern matching.
9
-
10
- Events are retrieved using `RatatuiRuby.poll_event`. This method returns an instance of a subclass of `RatatuiRuby::Event` (e.g., `RatatuiRuby::Event::Key`, `RatatuiRuby::Event::Mouse`). When no event is available, it returns `RatatuiRuby::Event::None`—a [null object](https://martinfowler.com/eaaCatalog/specialCase.html) that safely responds to all event predicates with `false`.
11
-
12
- ## 1. Blocking vs. Polling
13
-
14
- Most applications run in a loop (e.g., a game loop or UI loop). To prevent high CPU usage, the loop should wait briefly for input.
15
-
16
- * **Default (Polling):** `RatatuiRuby.poll_event` (no args) waits for **0.016s** (approx 60 FPS). If no event occurs, it returns `RatatuiRuby::Event::None`. This keeps the application responsive.
17
- * **Blocking:** `RatatuiRuby.poll_event(timeout: nil)` waits **forever** until an event occurs. Use this for scripts that only react to input and do not need to update the UI on a timer.
18
- * **Non-Blocking:** `RatatuiRuby.poll_event(timeout: 0.0)` returns immediately.
19
-
20
- ## 2. Symbol and String Comparison (Simplest)
21
-
22
- For simple key events, `RatatuiRuby::Event::Key` objects can be compared directly to Symbols or Strings. This is often the quickest way to get started.
23
-
24
- * **String**: Matches the key character (e.g., "a", "q").
25
- * **Symbol**: Matches special keys (e.g., `:enter`, `:esc`) or modifier combinations (e.g., `:ctrl_c`).
26
-
27
- > [!NOTE]
28
- > On macOS, the **Option** key is mapped to `alt`. The **Command** key is typically intercepted by the terminal emulator and may not be sent to the application, or it may be mapped to Meta/Alt depending on your terminal settings.
29
-
30
- For a complete list of supported keys, modifiers, and event types, please refer to the [API Documentation for RatatuiRuby::Event](file:///Users/kerrick/Developer/ratatui_ruby/lib/ratatui_ruby/event.rb).
31
-
32
- <!-- SPDX-SnippetBegin -->
33
- <!--
34
- SPDX-FileCopyrightText: 2025 Kerrick Long
35
- SPDX-License-Identifier: MIT-0
36
- -->
37
- ```ruby
38
- event = RatatuiRuby.poll_event
39
-
40
- # 1. Check for quit keys
41
- if event == "q" || event == :ctrl_c
42
- break
43
- end
44
-
45
- # 2. Check for special key
46
- if event == :enter
47
- submit_form
48
- end
49
- ```
50
- <!-- SPDX-SnippetEnd -->
51
-
52
- ## 3. Predicate Methods (Intermediate)
53
-
54
- If you need more control or logic (e.g. `if/elsif`), or need to handle non-key events like Resize or Mouse, use the predicate methods.
55
-
56
- ### Polymorphic Predicates
57
-
58
- Safe to call on *any* event object. They return `true` only for the matching event type.
59
-
60
- Available: `key?`, `mouse?`, `resize?`, `paste?`, `focus_gained?`, `focus_lost?`.
61
-
62
- <!-- SPDX-SnippetBegin -->
63
- <!--
64
- SPDX-FileCopyrightText: 2025 Kerrick Long
65
- SPDX-License-Identifier: MIT-0
66
- -->
67
- ```ruby
68
- event = RatatuiRuby.poll_event
69
-
70
- if event.key?
71
- handle_keypress(event)
72
- elsif event.mouse?
73
- handle_click(event)
74
- elsif event.resize?
75
- resize_layout(event.width, event.height)
76
- end
77
- ```
78
- <!-- SPDX-SnippetEnd -->
79
-
80
- ### Helper Predicates
81
-
82
- Specific to certain event classes to simplify checks.
83
-
84
- #### `RatatuiRuby::Event::Key`
85
- * `ctrl?`, `alt?`, `shift?`: Check if modifier is held.
86
- * `text?`: Returns `true` if the event is a printable character (length == 1).
87
-
88
- <!-- SPDX-SnippetBegin -->
89
- <!--
90
- SPDX-FileCopyrightText: 2025 Kerrick Long
91
- SPDX-License-Identifier: MIT-0
92
- -->
93
- ```ruby
94
- if event.key? && event.ctrl? && event.code == "s"
95
- save_file
96
- end
97
- ```
98
- <!-- SPDX-SnippetEnd -->
99
-
100
- #### `RatatuiRuby::Event::Mouse`
101
- * `down?`, `up?`, `drag?`: Check mouse action.
102
- * `scroll_up?`, `scroll_down?`: Check scroll direction.
103
-
104
- <!-- SPDX-SnippetBegin -->
105
- <!--
106
- SPDX-FileCopyrightText: 2025 Kerrick Long
107
- SPDX-License-Identifier: MIT-0
108
- -->
109
- ```ruby
110
- if event.mouse? && event.scroll_up?
111
- scroll_view(-1)
112
- end
113
- ```
114
- <!-- SPDX-SnippetEnd -->
115
-
116
- ## 4. Pattern Matching (Powerful)
117
-
118
- For complex applications, Ruby 3.0+ Pattern Matching with the `type:` discriminator is the most idiomatic and concise approach.
119
-
120
- <!-- SPDX-SnippetBegin -->
121
- <!--
122
- SPDX-FileCopyrightText: 2025 Kerrick Long
123
- SPDX-License-Identifier: MIT-0
124
- -->
125
- ```ruby
126
- loop do
127
- case RatatuiRuby.poll_event
128
-
129
- # Match specific key code
130
- in type: :key, code: "q"
131
- break
132
-
133
- # Match complex combo
134
- in type: :key, code: "c", modifiers: ["ctrl"]
135
- break
136
-
137
- # Capture variables
138
- in type: :key, code: "up" | "down" => direction
139
- move_cursor(direction)
140
-
141
- # Match mouse events
142
- in type: :mouse, kind: "down", x:, y:
143
- handle_click(x, y)
144
-
145
- in type: :none
146
- # No event available, continue loop
147
- end
148
- end
149
- ```
150
- <!-- SPDX-SnippetEnd -->
151
-
152
- ## Summary of Event Classes
153
-
154
- | Event Class | Discriminator (`type:`) | Attributes | Predicate |
155
- | :--- | :--- | :--- | :--- |
156
- | `RatatuiRuby::Event::Key` | `:key` | `code`, `modifiers` | `key?` |
157
- | `RatatuiRuby::Event::Mouse` | `:mouse` | `kind`, `x`, `y`, `button`, `modifiers` | `mouse?` |
158
- | `RatatuiRuby::Event::Resize` | `:resize` | `width`, `height` | `resize?` |
159
- | `RatatuiRuby::Event::Paste` | `:paste` | `content` | `paste?` |
160
- | `RatatuiRuby::Event::FocusGained` | `:focus_gained` | (none) | `focus_gained?` |
161
- | `RatatuiRuby::Event::FocusLost` | `:focus_lost` | (none) | `focus_lost?` |
162
- | `RatatuiRuby::Event::None` | `:none` | (none) | `none?` |
@@ -1,146 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Interactive TUI Design Patterns
7
-
8
- Canonical patterns for building responsive, interactive terminal user interfaces with ratatui_ruby.
9
-
10
- ## The Cached Layout Pattern
11
-
12
- **Context:** In immediate-mode TUI development, you render once per event loop. The render happens, the user clicks, you respond. This cycle repeats 60 times a second.
13
-
14
- **Problem:** Your layout has constraints. When you render, you calculate where each widget goes. When the user clicks, you need to know which widget was under the cursor. Two separate calculations means two separate constraint definitions. Change the layout once and forget to update the hit test logic—bugs happen.
15
-
16
- **Solution:** Calculate layout once. Cache the results. Reuse them everywhere.
17
-
18
- ### The Three-Phase Lifecycle
19
-
20
- Structure your event loop into three clear phases:
21
-
22
- <!-- SPDX-SnippetBegin -->
23
- <!--
24
- SPDX-FileCopyrightText: 2026 Kerrick Long
25
- SPDX-License-Identifier: MIT-0
26
- -->
27
- ```ruby
28
- def run
29
- RatatuiRuby.run do |tui|
30
- @tui = tui
31
- loop do
32
- @tui.draw do |frame|
33
- calculate_layout(frame.area) # Phase 1: Geometry (once per frame)
34
- render(frame) # Phase 2: Draw
35
- end
36
- break if handle_input == :quit # Phase 3: Input
37
- end
38
- end
39
- end
40
- ```
41
- <!-- SPDX-SnippetEnd -->
42
-
43
- **Phase 1: Layout Calculation**
44
-
45
- Call this inside your `draw` block. It uses the current terminal area provided by the frame:
46
-
47
- <!-- SPDX-SnippetBegin -->
48
- <!--
49
- SPDX-FileCopyrightText: 2026 Kerrick Long
50
- SPDX-License-Identifier: MIT-0
51
- -->
52
- ```ruby
53
- def calculate_layout(area)
54
- # Main area vs sidebar (70% / 30%)
55
- main_area, @sidebar_area = @tui.layout_split(
56
- area,
57
- direction: :horizontal,
58
- constraints: [
59
- @tui.constraint_percentage(70),
60
- @tui.constraint_percentage(30),
61
- ]
62
- )
63
-
64
- # Within main area, left vs right panels
65
- @left_rect, @right_rect = @tui.layout_split(
66
- main_area,
67
- direction: :horizontal,
68
- constraints: [
69
- @tui.constraint_percentage(@left_ratio),
70
- @tui.constraint_percentage(100 - @left_ratio)
71
- ]
72
- )
73
- end
74
- ```
75
- <!-- SPDX-SnippetEnd -->
76
-
77
- **Phase 2: Rendering**
78
-
79
- Reuse the cached rects. Build and draw:
80
-
81
- <!-- SPDX-SnippetBegin -->
82
- <!--
83
- SPDX-FileCopyrightText: 2026 Kerrick Long
84
- SPDX-License-Identifier: MIT-0
85
- -->
86
- ```ruby
87
- def render(frame)
88
- frame.render_widget(build_widget(@left_rect), @left_rect)
89
- frame.render_widget(build_widget(@right_rect), @right_rect)
90
- end
91
- ```
92
- <!-- SPDX-SnippetEnd -->
93
-
94
- **Phase 3: Event Handling**
95
-
96
- Reuse the cached rects. Test clicks:
97
-
98
- <!-- SPDX-SnippetBegin -->
99
- <!--
100
- SPDX-FileCopyrightText: 2025 Kerrick Long
101
- SPDX-License-Identifier: MIT-0
102
- -->
103
- ```ruby
104
- def handle_input
105
- event = RatatuiRuby.poll_event
106
-
107
- case event
108
- in type: :mouse, kind: "down", x:, y:
109
- if @left_rect.contains?(x, y)
110
- handle_left_click
111
- elsif @right_rect.contains?(x, y)
112
- handle_right_click
113
- end
114
- else
115
- nil
116
- end
117
- end
118
- ```
119
- <!-- SPDX-SnippetEnd -->
120
-
121
- ### Why This Matters
122
-
123
- - **Single source of truth:** Change constraints once. They apply everywhere.
124
- - **No duplication:** Write `Layout.split(area, constraints:)` once. Use the result in render and input.
125
- - **Testable:** Layout geometry is explicit. You can verify it.
126
- - **Foundation for components:** In Gem 1.5, the `Component` class automates this caching. This pattern teaches the mental model.
127
-
128
- ## Layout.split
129
-
130
- `Layout.split` computes layout geometry without rendering. It returns an array of `Rect` objects. While you can call `RatatuiRuby::Layout.split` directly, we recommend using the `TUI` helper (`tui.layout_split`) for cleaner application code.
131
-
132
- <!-- SPDX-SnippetBegin -->
133
- <!--
134
- SPDX-FileCopyrightText: 2026 Kerrick Long
135
- SPDX-License-Identifier: MIT-0
136
- -->
137
- ```ruby
138
- # Preferred (TUI API)
139
- left, right = tui.layout_split(area, constraints: [...])
140
-
141
- # Manual (Core API)
142
- left, right = RatatuiRuby::Layout.split(area, constraints: [...])
143
- ```
144
- <!-- SPDX-SnippetEnd -->
145
-
146
- Use it to establish the single source of truth inside your `draw` block. Store the results in instance variables and reuse them in both `render` and `handle_input`.
@@ -1,239 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Parity Auditing Process
7
-
8
- This guide describes the **process** for auditing RatatuiRuby against upstream Rust Ratatui to find API parity gaps of any kind.
9
-
10
- ## Philosophy
11
-
12
- RatatuiRuby should behave identically to Rust Ratatui. When a Rust user can do something with a widget, a Ruby user should be able to do the equivalent. Deviations are bugs.
13
-
14
- ## The Audit Mindset
15
-
16
- ### Ask the Right Questions
17
-
18
- Every audit begins with a question:
19
-
20
- - "Does RatatuiRuby support everything upstream supports for widget X?"
21
- - "When I pass type Y to parameter Z, does Ruby do what Rust does?"
22
- - "Are there features in upstream that we haven't exposed at all?"
23
-
24
- ### Symptoms That Trigger Audits
25
-
26
- - **Inspect strings in output** (`#<data RatatuiRuby::...>`) — type conversion failure
27
- - **Missing methods** — user can't access upstream functionality
28
- - **Wrong behavior** — code runs but produces different results than Rust
29
- - **Type mismatches** — Ruby API accepts different types than Rust API
30
-
31
- ## The Three-Layer Model
32
-
33
- Every RatatuiRuby feature has three layers. Gaps can occur at any layer:
34
-
35
- <!-- SPDX-SnippetBegin -->
36
- <!--
37
- SPDX-FileCopyrightText: 2026 Kerrick Long
38
- SPDX-License-Identifier: MIT-0
39
- -->
40
- ```
41
- ┌─────────────────────────────┐
42
- │ Ruby API (lib/**/*.rb) │ ← What users see
43
- ├─────────────────────────────┤
44
- │ Rust Bindings (ext/**/*.rs)│ ← Bridge layer
45
- ├─────────────────────────────┤
46
- │ Upstream Ratatui │ ← Source of truth
47
- └─────────────────────────────┘
48
- ```
49
- <!-- SPDX-SnippetEnd -->
50
-
51
- ### Layer 1: Ruby API Gaps
52
- Ruby doesn't expose a parameter that upstream supports.
53
-
54
- ### Layer 2: Binding Gaps
55
- Ruby exposes a parameter, but the Rust binding converts it incorrectly.
56
-
57
- ### Layer 3: Upstream Changes
58
- New upstream features we haven't implemented yet.
59
-
60
- ## Audit Process
61
-
62
- ### 1. Choose an Audit Scope
63
-
64
- Define what you're auditing:
65
- - **Single widget**: All parameters of `Tabs`
66
- - **Single feature**: All places that accept styled text
67
- - **Version delta**: Everything new in Ratatui 0.29
68
-
69
- ### 2. Establish Source of Truth
70
-
71
- For any scope, identify the authoritative upstream source:
72
- - **Method signatures** → Rust source files
73
- - **Type constraints** → Generic bounds (`Into<Line>`, `&str`, etc.)
74
- - **Behavior** → Upstream documentation and tests
75
-
76
- ### 3. Inventory Our Implementation
77
-
78
- List everything in our codebase related to the scope:
79
- - Ruby classes and methods
80
- - Rust binding functions
81
- - Conversion/parsing logic
82
-
83
- ### 4. Compare Systematically
84
-
85
- For each item in the upstream source of truth, ask:
86
- 1. Do we expose this at all?
87
- 2. If yes, does our implementation match the type signature?
88
- 3. If yes, does our behavior match?
89
-
90
- ### 5. Document Findings
91
-
92
- For each gap found, record:
93
- - What the gap is
94
- - Where it occurs (files, lines)
95
- - What upstream expects
96
- - What we currently do
97
-
98
- ### 6. Fix and Verify
99
-
100
- Apply fixes, then verify:
101
- - Compiles without errors
102
- - Tests pass
103
- - Visual/manual verification if applicable
104
-
105
- ## Verifying Completeness
106
-
107
- Finding gaps is not enough. You must verify you've found **all** gaps within your scope.
108
-
109
- ### The Completeness Mindset
110
-
111
- An initial audit pass often finds the obvious issues. But "obvious" means "incomplete." After your first pass, stop and ask:
112
-
113
- - Did I search for **every variation** of the pattern I was looking for?
114
- - Are there **synonyms or related terms** I should also search?
115
- - Did I check **all code paths**, not just the happy path?
116
- - Are there **duplicate implementations** (e.g., `render` vs `render_stateful`)?
117
-
118
- ### Broaden Your Search Terms
119
-
120
- Your first search term won't catch everything. For each pattern, think of variations:
121
-
122
- | If you searched for... | Also search for... |
123
- |------------------------|-------------------|
124
- | `funcall("to_s"` | `String::try_convert` |
125
- | `Into<Line>` | `Into<Span>`, `Into<Text>` |
126
- | One widget's file | All widget files |
127
- | The method you're fixing | Related methods in the same file |
128
-
129
- ### Work From the Complete List
130
-
131
- Don't spot-check. Generate the **complete list** of potential issues, then classify each one:
132
-
133
- 1. Run a search that catches all possible instances
134
- 2. Count them (e.g., "31 `to_s` call sites")
135
- 3. Review **every single one**, marking each as "gap" or "correct"
136
-
137
- Half-measures lead to half-fixes.
138
-
139
- ### Verify Against Upstream Exhaustively
140
-
141
- For type-related gaps, don't just check the methods you already know about. Search upstream for **all uses of the pattern**:
142
-
143
- - `grep -rn 'Into<Line' /path/to/ratatui/src/widgets` finds EVERY place upstream accepts `Line`
144
- - Then verify we handle EACH of those correctly
145
-
146
- ### Question Your Assumptions
147
-
148
- After completing your audit, challenge yourself:
149
-
150
- - "Is there anywhere I should look that I haven't?"
151
- - "Is there any term I should search that I haven't?"
152
- - "Am I sure I didn't miss anything?"
153
-
154
- If you can't answer confidently, keep searching.
155
-
156
- ### The "One More Pass" Rule
157
-
158
- When you think you're done, do one more verification pass with a different approach:
159
-
160
- - If you searched our code first, now search upstream first
161
- - If you searched for method names, now search for type signatures
162
- - If you audited file-by-file, now audit feature-by-feature
163
-
164
- This catches gaps your initial framing missed.
165
-
166
-
167
- ## Search Strategies
168
-
169
- Different gap types require different search approaches:
170
-
171
- ### Finding Type Conversion Gaps
172
-
173
- Look for places where we convert Ruby objects to simpler types:
174
- - `funcall("to_s", ...)` — converting to string
175
- - `String::try_convert(...)` — same
176
- - `u16::try_convert(...)` — numeric conversion
177
-
178
- Then check: does upstream accept richer types?
179
-
180
- ### Finding Missing Features
181
-
182
- Compare file structure:
183
- - What files/modules exist upstream?
184
- - What methods exist in each upstream struct?
185
- - What parameters does each upstream method accept?
186
-
187
- ### Finding Behavioral Differences
188
-
189
- Run equivalent code in both environments and compare output.
190
-
191
- ## Red Flags in Our Code
192
-
193
- When reviewing RatatuiRuby code, these patterns suggest potential gaps:
194
-
195
- | Pattern | Potential Gap |
196
- |---------|---------------|
197
- | `funcall("to_s", ...)` | Upstream may accept styled types |
198
- | `usize` or `u16` for padding | Upstream may accept content types |
199
- | Missing `parse_*` call before using a value | Type not being converted properly |
200
- | Widget with fewer Ruby methods than Rust methods | Missing functionality |
201
- | `// TODO` or `// FIXME` comments | Known gaps |
202
-
203
- ## Upstream Patterns to Watch For
204
-
205
- When reading upstream Rust code, these signatures indicate rich type support:
206
-
207
- | Rust Signature | Meaning | Ruby Should Accept |
208
- |----------------|---------|-------------------|
209
- | `Into<Span<'a>>` | Styled single fragment | `Text::Span` or `String` |
210
- | `Into<Line<'a>>` | Styled line | `Text::Line`, `Text::Span`, or `String` |
211
- | `Into<Text<'a>>` | Multi-line styled | `Text::Text`, `Text::Line`, or `String` |
212
- | `T: AsRef<str>` | Any string-like | `String` (OK) |
213
- | `&'a str` | String slice | `String` (OK) |
214
-
215
- ## Keeping Audits Maintainable
216
-
217
- ### Create Checklists
218
-
219
- For each widget or feature area, maintain a checklist of all parameters with their expected types.
220
-
221
- ### Track Upstream Changes
222
-
223
- When upgrading Ratatui versions:
224
- 1. Read the changelog
225
- 2. Search for new `pub fn` in widget files
226
- 3. Audit new functionality before exposing it
227
-
228
- ### Automate Where Possible
229
-
230
- Consider tooling that:
231
- - Compares upstream method counts to ours
232
- - Flags new `to_s` calls in code review
233
- - Tests styled content rendering
234
-
235
- ## Example Audit Narrative
236
-
237
- > "I noticed inspect strings appearing in my tabs. I asked: 'What types does upstream accept for the divider parameter?' I found `Into<Span>`. I then asked: 'What does our binding do?' I found we call `to_s`. Gap identified."
238
-
239
- This narrative approach—asking questions, finding answers, comparing—works for any parity issue.