ratatui_ruby 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +6 -0
  7. data/CHANGELOG.md +44 -7
  8. data/README.md +11 -4
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +84 -10
  11. data/doc/application_testing.md +75 -29
  12. data/doc/contributors/design/ruby_frontend.md +39 -3
  13. data/doc/contributors/design/rust_backend.md +1 -0
  14. data/doc/contributors/developing_examples.md +129 -44
  15. data/doc/contributors/examples_audit/p1_high.md +21 -0
  16. data/doc/contributors/examples_audit/p2_moderate.md +81 -0
  17. data/doc/contributors/examples_audit.md +41 -0
  18. data/doc/event_handling.md +11 -3
  19. data/doc/images/app_all_events.png +0 -0
  20. data/doc/images/app_color_picker.png +0 -0
  21. data/doc/images/app_login_form.png +0 -0
  22. data/doc/images/app_stateful_interaction.png +0 -0
  23. data/doc/images/verify_quickstart_dsl.png +0 -0
  24. data/doc/images/verify_quickstart_layout.png +0 -0
  25. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  26. data/doc/images/verify_readme_usage.png +0 -0
  27. data/doc/images/widget_barchart_demo.png +0 -0
  28. data/doc/images/widget_block_demo.png +0 -0
  29. data/doc/images/widget_canvas_demo.png +0 -0
  30. data/doc/images/widget_cell_demo.png +0 -0
  31. data/doc/images/widget_center_demo.png +0 -0
  32. data/doc/images/widget_chart_demo.png +0 -0
  33. data/doc/images/widget_list_demo.png +0 -0
  34. data/doc/images/widget_overlay_demo.png +0 -0
  35. data/doc/images/widget_render.png +0 -0
  36. data/doc/images/widget_rich_text.png +0 -0
  37. data/doc/images/widget_scroll_text.png +0 -0
  38. data/doc/images/widget_sparkline_demo.png +0 -0
  39. data/doc/images/widget_table_demo.png +0 -0
  40. data/doc/images/widget_tabs_demo.png +0 -0
  41. data/doc/images/widget_text_width.png +0 -0
  42. data/doc/quickstart.md +69 -76
  43. data/doc/terminal_limitations.md +92 -0
  44. data/examples/app_all_events/README.md +45 -27
  45. data/examples/app_all_events/app.rb +38 -35
  46. data/examples/app_all_events/model/app_model.rb +157 -0
  47. data/examples/app_all_events/model/event_entry.rb +17 -0
  48. data/examples/app_all_events/model/msg.rb +37 -0
  49. data/examples/app_all_events/update.rb +73 -0
  50. data/examples/app_all_events/view/app_view.rb +8 -8
  51. data/examples/app_all_events/view/controls_view.rb +8 -6
  52. data/examples/app_all_events/view/counts_view.rb +12 -8
  53. data/examples/app_all_events/view/live_view.rb +8 -7
  54. data/examples/app_all_events/view/log_view.rb +10 -15
  55. data/examples/app_color_picker/README.md +84 -44
  56. data/examples/app_color_picker/app.rb +24 -62
  57. data/examples/app_color_picker/controls.rb +90 -0
  58. data/examples/app_color_picker/copy_dialog.rb +45 -49
  59. data/examples/app_color_picker/export_pane.rb +126 -0
  60. data/examples/app_color_picker/input.rb +99 -67
  61. data/examples/app_color_picker/main_container.rb +178 -0
  62. data/examples/app_color_picker/palette.rb +55 -26
  63. data/examples/app_login_form/README.md +47 -0
  64. data/examples/app_login_form/app.rb +2 -3
  65. data/examples/app_stateful_interaction/README.md +31 -0
  66. data/examples/app_stateful_interaction/app.rb +272 -0
  67. data/examples/timeout_demo.rb +43 -0
  68. data/examples/verify_quickstart_dsl/README.md +48 -0
  69. data/examples/verify_quickstart_dsl/app.rb +2 -0
  70. data/examples/verify_quickstart_layout/README.md +71 -0
  71. data/examples/verify_quickstart_layout/app.rb +2 -0
  72. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  73. data/examples/verify_quickstart_lifecycle/app.rb +8 -2
  74. data/examples/verify_readme_usage/README.md +43 -0
  75. data/examples/verify_readme_usage/app.rb +8 -2
  76. data/examples/widget_barchart_demo/README.md +49 -0
  77. data/examples/widget_barchart_demo/app.rb +5 -5
  78. data/examples/widget_block_demo/README.md +34 -0
  79. data/examples/widget_block_demo/app.rb +256 -0
  80. data/examples/widget_box_demo/README.md +45 -0
  81. data/examples/widget_calendar_demo/README.md +39 -0
  82. data/examples/widget_canvas_demo/README.md +27 -0
  83. data/examples/widget_canvas_demo/app.rb +123 -0
  84. data/examples/widget_cell_demo/README.md +36 -0
  85. data/examples/widget_cell_demo/app.rb +31 -24
  86. data/examples/widget_center_demo/README.md +29 -0
  87. data/examples/widget_center_demo/app.rb +116 -0
  88. data/examples/widget_chart_demo/README.md +41 -0
  89. data/examples/widget_chart_demo/app.rb +7 -2
  90. data/examples/widget_gauge_demo/README.md +41 -0
  91. data/examples/widget_layout_split/README.md +44 -0
  92. data/examples/widget_line_gauge_demo/README.md +41 -0
  93. data/examples/widget_list_demo/README.md +49 -0
  94. data/examples/widget_list_demo/app.rb +91 -107
  95. data/examples/widget_map_demo/README.md +39 -0
  96. data/examples/{app_map_demo → widget_map_demo}/app.rb +2 -2
  97. data/examples/widget_overlay_demo/app.rb +248 -0
  98. data/examples/widget_popup_demo/README.md +36 -0
  99. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  100. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  101. data/examples/widget_rect/README.md +38 -0
  102. data/examples/widget_render/README.md +37 -0
  103. data/examples/widget_rich_text/README.md +35 -0
  104. data/examples/widget_rich_text/app.rb +62 -33
  105. data/examples/widget_scroll_text/README.md +37 -0
  106. data/examples/widget_scroll_text/app.rb +0 -1
  107. data/examples/widget_scrollbar_demo/README.md +37 -0
  108. data/examples/widget_sparkline_demo/README.md +42 -0
  109. data/examples/widget_sparkline_demo/app.rb +4 -3
  110. data/examples/widget_style_colors/README.md +34 -0
  111. data/examples/widget_table_demo/README.md +48 -0
  112. data/examples/{app_table_select → widget_table_demo}/app.rb +46 -8
  113. data/examples/widget_tabs_demo/README.md +41 -0
  114. data/examples/widget_tabs_demo/app.rb +15 -1
  115. data/examples/widget_text_width/README.md +35 -0
  116. data/examples/widget_text_width/app.rb +106 -0
  117. data/exe/.gitkeep +0 -0
  118. data/ext/ratatui_ruby/Cargo.lock +11 -4
  119. data/ext/ratatui_ruby/Cargo.toml +2 -1
  120. data/ext/ratatui_ruby/src/events.rs +238 -26
  121. data/ext/ratatui_ruby/src/frame.rs +113 -1
  122. data/ext/ratatui_ruby/src/lib.rs +34 -4
  123. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  124. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  125. data/ext/ratatui_ruby/src/text.rs +1 -1
  126. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  127. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  128. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  129. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  130. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  131. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  132. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  133. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  134. data/ext/ratatui_ruby/src/widgets/table.rs +113 -1
  135. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  136. data/lib/ratatui_ruby/cell.rb +4 -4
  137. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  138. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  139. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  140. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  141. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  142. data/lib/ratatui_ruby/event/key.rb +111 -51
  143. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  144. data/lib/ratatui_ruby/event/paste.rb +1 -1
  145. data/lib/ratatui_ruby/frame.rb +96 -0
  146. data/lib/ratatui_ruby/list_state.rb +88 -0
  147. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  148. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  149. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  150. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  151. data/lib/ratatui_ruby/schema/list.rb +25 -4
  152. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  153. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  154. data/lib/ratatui_ruby/schema/style.rb +24 -4
  155. data/lib/ratatui_ruby/schema/table.rb +21 -3
  156. data/lib/ratatui_ruby/schema/text.rb +69 -1
  157. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  158. data/lib/ratatui_ruby/session/autodoc.rb +65 -0
  159. data/lib/ratatui_ruby/session.rb +22 -7
  160. data/lib/ratatui_ruby/table_state.rb +90 -0
  161. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  162. data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
  163. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  164. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  165. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  166. data/lib/ratatui_ruby/test_helper.rb +65 -358
  167. data/lib/ratatui_ruby/version.rb +1 -1
  168. data/lib/ratatui_ruby.rb +42 -19
  169. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  170. data/sig/examples/widget_block_demo/app.rbs +32 -0
  171. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  172. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  173. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  174. data/sig/ratatui_ruby/event.rbs +11 -1
  175. data/sig/ratatui_ruby/frame.rbs +2 -0
  176. data/sig/ratatui_ruby/list_state.rbs +13 -0
  177. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  178. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  179. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  180. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  181. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  182. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  183. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  184. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  185. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  186. data/sig/ratatui_ruby/schema/text.rbs +8 -6
  187. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  188. data/sig/ratatui_ruby/session.rbs +13 -0
  189. data/sig/ratatui_ruby/table_state.rbs +15 -0
  190. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  191. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  192. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  193. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  194. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  195. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  196. data/tasks/autodoc/examples.rb +79 -0
  197. data/tasks/autodoc/inventory.rb +9 -7
  198. data/tasks/autodoc.rake +11 -5
  199. data/tasks/bump/changelog.rb +3 -3
  200. data/tasks/bump/links.rb +67 -0
  201. data/tasks/sourcehut.rake +61 -21
  202. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  203. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  204. metadata +111 -37
  205. data/doc/images/app_table_select.png +0 -0
  206. data/doc/images/widget_block_padding.png +0 -0
  207. data/doc/images/widget_block_titles.png +0 -0
  208. data/doc/images/widget_list_styles.png +0 -0
  209. data/examples/app_all_events/model/events.rb +0 -180
  210. data/examples/app_all_events/model/highlight.rb +0 -57
  211. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  212. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  213. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  214. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  215. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  216. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  217. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  218. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  219. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  220. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  221. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  222. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  223. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  224. data/examples/app_all_events/view_state.rb +0 -42
  225. data/examples/app_color_picker/scene.rb +0 -201
  226. data/examples/widget_block_padding/app.rb +0 -67
  227. data/examples/widget_block_titles/app.rb +0 -69
  228. data/examples/widget_list_styles/app.rb +0 -141
  229. data/examples/widget_table_flex/app.rb +0 -95
  230. data/sig/examples/widget_block_padding/app.rbs +0 -11
  231. data/sig/examples/widget_block_titles/app.rbs +0 -11
  232. data/sig/examples/widget_list_styles/app.rbs +0 -11
  233. data/tasks/bump/comparison_links.rb +0 -41
  234. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -84,9 +84,11 @@ All interactive examples must fit within an **80×24 terminal** (standard VT100
84
84
  - **Style hotkeys visually:** Use `modifiers: [:bold, :underlined]` on hotkey letters to make them stand out from descriptions. Example: `i` (bold, underlined) followed by `Items`.
85
85
  - Test early by running the example at 80×24 and verifying all content is visible without wrapping, scrolling, or clipping.
86
86
 
87
- Every example must also have an RBS file documenting its public methods:
87
+ ## Type Signatures
88
88
 
89
- `examples/my_example/app.rbs`:
89
+ Every example must also have an RBS file documenting its public methods. Type signatures live in a centralized location:
90
+
91
+ `sig/examples/my_example/app.rbs`:
90
92
  ```rbs
91
93
  class MyExampleApp
92
94
  # @public
@@ -97,6 +99,27 @@ class MyExampleApp
97
99
  end
98
100
  ```
99
101
 
102
+ ## Directory Structure
103
+
104
+ Examples are organized across three locations:
105
+
106
+ ```
107
+ examples/
108
+ my_example/
109
+ app.rb ← REQUIRED: The runnable example code
110
+ README.md ← REQUIRED: Purpose, architecture, hotkeys, usage
111
+
112
+ test/examples/
113
+ my_example/
114
+ test_app.rb ← REQUIRED: Tests (centralized, not local to example)
115
+ snapshots/ ← Auto-created by assert_snapshot
116
+ initial_render.txt
117
+
118
+ sig/examples/
119
+ my_example/
120
+ app.rbs ← REQUIRED: Type signatures (centralized, not local to example)
121
+ ```
122
+
100
123
  ### Key Requirements
101
124
 
102
125
  1. **Only `run` should be public.** All other methods (`render`, `handle_input`, helper methods) must be private. This prevents tests from calling internal methods directly.
@@ -127,7 +150,6 @@ end
127
150
  # Ignore other events
128
151
  end
129
152
  end
130
- ```
131
153
 
132
154
  5. **Use keyboard keys to cycle through widget attributes.** Users should be able to interactively explore all widget options. Common patterns:
133
155
  - Arrow keys: Navigate or adjust values
@@ -135,7 +157,17 @@ end
135
157
  - Space: Toggle or select
136
158
  - `q` or Ctrl+C: Quit
137
159
 
138
- 5. **Naming Conventions for Controls**
160
+ 6. **All examples must include a README.md** explaining:
161
+ - What problem the example solves
162
+ - Architecture (if applicable)
163
+ - Hotkeys (if interactive): Document all keyboard/mouse controls
164
+ - Key concepts demonstrated
165
+ - Usage instructions
166
+ - Learning outcomes
167
+
168
+ See examples/app_color_picker/README.md and examples/app_all_events/README.md for patterns. Adhere to docs/contributors/documentation_style.md.
169
+
170
+ 7. **Naming Conventions for Controls**
139
171
 
140
172
  When documenting hotkeys and cycling options in the UI, use consistent naming:
141
173
 
@@ -158,7 +190,7 @@ When documenting hotkeys and cycling options in the UI, use consistent naming:
158
190
 
159
191
  This keeps the UI self-documenting and users can see exact parameter names when they read the hotkey help.
160
192
 
161
- 7. **Hit Testing**
193
+ 8. **Hit Testing**
162
194
 
163
195
  Examples with mouse interaction should use the **Frame API**. By calling `@tui.layout_split` inside `@tui.draw`, you obtain the exact `Rect`s used for rendering. Store these rects in instance variables (e.g., `@sidebar_rect`) to use them in your `handle_input` method for hit testing:
164
196
 
@@ -170,11 +202,9 @@ end
170
202
 
171
203
  ## Testing Examples
172
204
 
173
- Example tests live alongside examples as `test_app.rb` files in the same directory.
174
-
175
- ### Testing Pattern
205
+ Example tests live in a centralized test tree:
176
206
 
177
- `examples/my_example/test_app.rb`:
207
+ `test/examples/my_example/test_app.rb`:
178
208
  ```ruby
179
209
  $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
180
210
  require "ratatui_ruby"
@@ -191,56 +221,75 @@ class TestMyExampleApp < Minitest::Test
191
221
 
192
222
  def test_initial_render
193
223
  with_test_terminal do
194
- inject_key(:q) # Queue quit event
195
- @app.run # Run the app loop
196
-
197
- content = buffer_content.join("\n")
198
- assert_includes content, "Expected Text"
224
+ inject_key(:q)
225
+ @app.run
226
+ assert_snapshot("initial_render")
199
227
  end
200
228
  end
229
+ end
230
+ ```
201
231
 
202
- def test_keyboard_interaction
203
- with_test_terminal do
204
- inject_key("s") # Press 's' to cycle something
205
- inject_key(:q) # Then quit
206
- @app.run
232
+ ## Snapshot Testing Pattern (REQUIRED)
207
233
 
208
- content = buffer_content.join("\n")
209
- assert_includes content, "Changed State"
210
- end
211
- end
234
+ All example tests MUST use snapshot testing via the `assert_snapshot` API, not manual content assertions.
212
235
 
213
- def test_mouse_interaction
214
- with_test_terminal do
215
- # Click at (10, 5)
216
- inject_click(x: 10, y: 5)
217
- inject_key(:q)
218
- @app.run
236
+ ### Why Snapshots
219
237
 
220
- content = buffer_content.join("\n")
221
- assert_includes content, "Clicked at (10, 5)"
222
- end
238
+ - **Exact verification:** Captures complete screen state, character-by-character
239
+ - **Auto-update:** `UPDATE_SNAPSHOTS=1 bin/agent_rake test` regenerates all snapshots
240
+ - **Auto-managed:** Snapshots live in `test/examples/{name}/snapshots/{test_name}.txt`
241
+ - **Maintainable:** No tedious manual string checks
242
+ - **Self-documenting:** Snapshots show exactly what output is expected
243
+
244
+ ### Basic Pattern
245
+
246
+ ```ruby
247
+ def test_initial_render
248
+ with_test_terminal do
249
+ inject_key(:q)
250
+ @app.run
251
+
252
+ assert_snapshot("initial_render")
223
253
  end
224
254
  end
225
255
  ```
226
256
 
227
- ### Testing Guidelines
257
+ Snapshot auto-saved to: `test/examples/widget_foo_demo/snapshots/initial_render.txt`
228
258
 
229
- 1. **Inject events, observe buffer.** Tests should only interact through:
230
- - `inject_key`, `inject_click`, `inject_event`, etc. for input
231
- - `buffer_content` for output verification
259
+ ### With Normalization (for dynamic content)
232
260
 
233
- 2. **Never call internal methods.** Don't call `render`, `handle_input`, `__send__`, or access instance variables with `instance_variable_get`. Tests verify behavior through the public `run` method.
261
+ For examples with timestamps, random data, or other non-deterministic output:
234
262
 
235
- 3. **Use `inject_key(:q)` to exit.** All examples should support quitting with `q`, so inject this as the final event to terminate the loop.
263
+ ```ruby
264
+ private def assert_normalized_snapshot(snapshot_name)
265
+ assert_snapshot(snapshot_name) do |actual|
266
+ actual.map do |line|
267
+ line.gsub(/\d{2}:\d{2}:\d{2}/, "XX:XX:XX") # Mask timestamps
268
+ .gsub(/Random ID: \d+/, "Random ID: XXX") # Mask random values
269
+ end
270
+ end
271
+ end
236
272
 
237
- 4. **Assert and refute.** When testing which item was clicked/selected, also verify the opposite didn't happen:
238
- ```ruby
239
- assert_includes content, "Left Panel clicked"
240
- refute_includes content, "Right Panel clicked"
241
- ```
273
+ def test_after_event
274
+ with_test_terminal do
275
+ inject_key("a")
276
+ inject_key(:q)
277
+ @app.run
278
+
279
+ assert_normalized_snapshot("after_event")
280
+ end
281
+ end
282
+ ```
283
+
284
+ See `test/examples/app_all_events/test_app.rb` for a complete example.
242
285
 
243
- 5. **Test state cycling.** If an example cycles through options (styles, modes, etc.), test that pressing the key actually changes the rendered output.
286
+ ### Regenerating Snapshots
287
+
288
+ When UI changes are intentional, regenerate all snapshots:
289
+
290
+ ```bash
291
+ UPDATE_SNAPSHOTS=1 bin/agent_rake test
292
+ ```
244
293
 
245
294
  ## Widget Attribute Cycling
246
295
 
@@ -259,3 +308,39 @@ Examples should demonstrate widget configurability by allowing interactive cycli
259
308
  | Scrollbar | theme | s |
260
309
 
261
310
  Display the current state in the UI (e.g., in a title or status bar or paragraph) so users can see what changed. Display the hotkey in the UI as well, so users can see how to change it; the hotkey should not disappear as app state changes.
311
+
312
+ ## Data Quality
313
+
314
+ Examples must use **realistic, meaningful data**—not dummy placeholder text. This ensures examples:
315
+ - Demonstrate the widget's real-world usage
316
+ - Provide educational value to users reading the code
317
+ - Look professional and polished
318
+
319
+ ### Guidelines
320
+
321
+ **For small datasets (< 10 items):**
322
+ Use hardcoded realistic data. Examples:
323
+ - Geographic coordinates with city names (see `widget_map_demo/app.rb`)
324
+ - Real product names or person names
325
+ - Meaningful status values ("Completed", "Pending", "Failed")
326
+
327
+ **For large datasets (≥ 10 items):**
328
+ Use the [Faker](https://github.com/faker-ruby/faker) gem with **deterministic seeding** so data is consistent across runs:
329
+
330
+ ```ruby
331
+ require "faker"
332
+
333
+ # Seed Faker for reproducible output
334
+ Faker::Config.random = Random.new(12345)
335
+
336
+ # Generate realistic data
337
+ users = Array.new(50) { Faker::Name.name }
338
+ emails = Array.new(50) { Faker::Internet.email }
339
+ ```
340
+
341
+ In tests, set the same seed before each test to ensure snapshot consistency.
342
+
343
+ **Avoid:**
344
+ - Repeated placeholder text like "Lorem ipsum" or "Background Layer" × 20
345
+ - Generic numbered items like "Item 1", "Item 2" (unless the context demands it)
346
+ - Nonsensical or visually jarring content
@@ -0,0 +1,21 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Priority 1: High (Polish & Ecosystem)
7
+
8
+ Important for v1.0.0 quality and ecosystem goals. Not blocking release, but recommended before ship.
9
+
10
+ ---
11
+
12
+ ## 1. Ensure All Widgets Have Examples
13
+
14
+ **Status:** Done
15
+
16
+ Verified that all widget types shipped with ratatui_ruby have at least one example showing how to use them.
17
+
18
+ **Action:**
19
+ - [x] Check widget manifest against example inventory
20
+ - [x] Create `widget_canvas_demo`, `widget_center_demo`, and `widget_overlay_demo`
21
+
@@ -0,0 +1,81 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Priority 2: Moderate (Quality Gates)
7
+
8
+ These are v1.0.0 quality improvements that refine the example suite after P0 is complete. Not blocking, but recommended for maintainability and API consistency.
9
+
10
+ ---
11
+
12
+ ## 1. Add RDoc Cross-Links (Examples & Aliases)
13
+
14
+ **Status:** Important for API discoverability — Documentation should link library and examples
15
+
16
+ RDoc should cross-link between:
17
+ - **Library classes/methods** ↔ **Examples that use them** (See also: examples/widget_foo_demo)
18
+ - **Primary methods** ↔ **DWIM/TIMTOWTDI aliases** (See also: tui.foo_bar as alias for tui.foo(:bar))
19
+
20
+ ### Current Practice
21
+
22
+ Done for:
23
+ - `RatatuiRuby::Frame#set_cursor_position` ↔ `RatatuiRuby::Cursor` (cross-linking)
24
+ - Limited elsewhere
25
+
26
+ ### Gaps
27
+
28
+ - Most widget classes have no "See also: example_foo_demo" links
29
+ - Aliases/TIMTOWTDI variants are not documented as such
30
+ - Users can't easily find examples for a given class/method
31
+
32
+ ### Action
33
+
34
+ 1. Add `# See also: examples/widget_foo_demo/app.rb` to class/method RDoc
35
+ 2. Link DWIM methods to TIMTOWTDI variants: `# Also available as: tui.constraint_length (DWIM) vs tui.constraint(:length) (TIMTOWTDI)`
36
+ 3. Create consistent pattern across all public APIs in `lib/ratatui_ruby/`
37
+
38
+ ### Example Pattern
39
+
40
+ ```ruby
41
+ # Renders text with styling.
42
+ #
43
+ # See also: examples/widget_paragraph_demo/app.rb (basic paragraph rendering)
44
+ class Paragraph < Data.define(...)
45
+ # ...
46
+ end
47
+
48
+ # DWIM version of constraint creation
49
+ # Also available as: constraint(type, value) for explicit control
50
+ def constraint_length(length)
51
+ constraint(:length, length)
52
+ end
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Dependencies
58
+
59
+ - P0 (developing_examples.md, README.md, tests) should be complete before consolidation
60
+
61
+ ---
62
+
63
+ ## 4. Enhance Widget Examples with Functional Context
64
+
65
+ **Status:** Recommended — Move beyond "parameter playgrounds" to "real-world patterns"
66
+
67
+ Current `widget_*` examples mostly focus on interactive parameter turning (changing colors, borders, etc.). While useful for API discovery, they don't show *how* to use the widget in a real application logic flow.
68
+
69
+ ### The Standard: widget_tabs_demo
70
+
71
+ The `widget_tabs_demo` was enhanced to show **conditional rendering** of content based on the selected tab in git commit `38ceed39a011d557cc66e11a4598d3341dc7a0cc`. It doesn't just highlight the tab; it changes the screen content. This connects the widget (the tabs) to the problem it solves (view segregation).
72
+
73
+ ### Action
74
+
75
+ Identify other widget examples that could benefit from this "functional context" treatment:
76
+
77
+ - **widget_popup_demo:** Show a multi-step modal flow (e.g., Confirm -> Success) rather than just a static overlay.
78
+ - **widget_list_demo:** Show a master-detail view where selecting a list item updates a detail pane.
79
+ - **widget_input_demo:** (If created) Show specific validation logic (email vs number).
80
+
81
+ **Goal:** Every widget example should answer "How do I build a feature with this?" not just "What does this parameter do?"
@@ -0,0 +1,41 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Examples Audit Report
7
+
8
+ Audit of ratatui_ruby `examples/` directory for v1.0.0 readiness.
9
+
10
+ ## P0: Critical (Completed ✓)
11
+
12
+ All P0 critical items have been completed:
13
+
14
+ 1. **Migrate Example Tests to Snapshot API** ✓
15
+ - Migrated 31 test files from manual `buffer_content` assertions to `assert_snapshot` and `assert_rich_snapshot`
16
+ - Added deterministic seeding for tests with random content (Faker, RATA_SEED)
17
+ - Generates `.txt` (plain) and `.ansi` (styled) snapshots for mutation-testing capability
18
+
19
+ ---
20
+
21
+ ## [P1: High (Polish)](./examples_audit/p1_high.md)
22
+
23
+ 1. **[Ensure All Widgets Have Examples](./examples_audit/p1_high.md#1-ensure-all-widgets-have-examples)** (Completeness)
24
+ - Check widget manifest against example inventory
25
+ - Create examples for any missing widgets
26
+
27
+ ---
28
+
29
+ ## [P2: Moderate (Quality)](./examples_audit/p2_moderate.md)
30
+
31
+ 1. **[Add RDoc Cross-Links](./examples_audit/p2_moderate.md#1-add-rdoc-cross-links-examples--aliases)** (Documentation discoverability)
32
+ - Link library classes/methods to examples
33
+ - Link DWIM/TIMTOWTDI aliases
34
+ - Create consistent pattern across public APIs
35
+
36
+ ---
37
+
38
+ ## Success Criteria for v1.0.0
39
+
40
+ - ✓ All P0 items are fixed
41
+ - ✓ All P1 items are mitigated (or time has run out)
@@ -10,7 +10,15 @@ SPDX-License-Identifier: AGPL-3.0-or-later
10
10
 
11
11
  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`.
12
12
 
13
- ## 1. Symbol and String Comparison (Simplest)
13
+ ## 1. Blocking vs. Polling
14
+
15
+ 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.
16
+
17
+ * **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.
18
+ * **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.
19
+ * **Non-Blocking:** `RatatuiRuby.poll_event(timeout: 0.0)` returns immediately.
20
+
21
+ ## 2. Symbol and String Comparison (Simplest)
14
22
 
15
23
  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.
16
24
 
@@ -36,7 +44,7 @@ if event == :enter
36
44
  end
37
45
  ```
38
46
 
39
- ## 2. Predicate Methods (Intermediate)
47
+ ## 3. Predicate Methods (Intermediate)
40
48
 
41
49
  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.
42
50
 
@@ -82,7 +90,7 @@ if event.mouse? && event.scroll_up?
82
90
  end
83
91
  ```
84
92
 
85
- ## 3. Pattern Matching (Powerful)
93
+ ## 4. Pattern Matching (Powerful)
86
94
 
87
95
  For complex applications, Ruby 3.0+ Pattern Matching with the `type:` discriminator is the most idiomatic and concise approach.
88
96
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file