ratatui_ruby 0.9.1 → 0.10.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 (267) 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 +2 -1
  7. data/CHANGELOG.md +98 -0
  8. data/REUSE.toml +5 -0
  9. data/Rakefile +1 -1
  10. data/Steepfile +49 -0
  11. data/doc/concepts/debugging.md +401 -0
  12. data/doc/getting_started/quickstart.md +8 -3
  13. data/doc/images/app_all_events.png +0 -0
  14. data/doc/images/app_color_picker.png +0 -0
  15. data/doc/images/app_debugging_showcase.gif +0 -0
  16. data/doc/images/app_debugging_showcase.png +0 -0
  17. data/doc/images/app_login_form.png +0 -0
  18. data/doc/images/app_stateful_interaction.png +0 -0
  19. data/doc/images/verify_quickstart_dsl.png +0 -0
  20. data/doc/images/verify_quickstart_layout.png +0 -0
  21. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  22. data/doc/images/verify_readme_usage.png +0 -0
  23. data/doc/images/widget_barchart.png +0 -0
  24. data/doc/images/widget_block.png +0 -0
  25. data/doc/images/widget_box.png +0 -0
  26. data/doc/images/widget_calendar.png +0 -0
  27. data/doc/images/widget_canvas.png +0 -0
  28. data/doc/images/widget_cell.png +0 -0
  29. data/doc/images/widget_center.png +0 -0
  30. data/doc/images/widget_chart.png +0 -0
  31. data/doc/images/widget_gauge.png +0 -0
  32. data/doc/images/widget_layout_split.png +0 -0
  33. data/doc/images/widget_line_gauge.png +0 -0
  34. data/doc/images/widget_list.png +0 -0
  35. data/doc/images/widget_map.png +0 -0
  36. data/doc/images/widget_overlay.png +0 -0
  37. data/doc/images/widget_popup.png +0 -0
  38. data/doc/images/widget_ratatui_logo.png +0 -0
  39. data/doc/images/widget_ratatui_mascot.png +0 -0
  40. data/doc/images/widget_rect.png +0 -0
  41. data/doc/images/widget_render.png +0 -0
  42. data/doc/images/widget_rich_text.png +0 -0
  43. data/doc/images/widget_scroll_text.png +0 -0
  44. data/doc/images/widget_scrollbar.png +0 -0
  45. data/doc/images/widget_sparkline.png +0 -0
  46. data/doc/images/widget_style_colors.png +0 -0
  47. data/doc/images/widget_table.png +0 -0
  48. data/doc/images/widget_tabs.png +0 -0
  49. data/doc/images/widget_text_width.png +0 -0
  50. data/doc/troubleshooting/async.md +4 -0
  51. data/examples/app_debugging_showcase/README.md +119 -0
  52. data/examples/app_debugging_showcase/app.rb +318 -0
  53. data/examples/widget_canvas/app.rb +19 -14
  54. data/examples/widget_gauge/app.rb +18 -3
  55. data/examples/widget_layout_split/app.rb +10 -4
  56. data/examples/widget_list/app.rb +22 -6
  57. data/examples/widget_rect/app.rb +7 -6
  58. data/examples/widget_rich_text/app.rb +62 -37
  59. data/examples/widget_style_colors/app.rb +26 -47
  60. data/examples/widget_table/app.rb +28 -5
  61. data/examples/widget_text_width/app.rb +6 -4
  62. data/ext/ratatui_ruby/Cargo.lock +48 -1
  63. data/ext/ratatui_ruby/Cargo.toml +6 -2
  64. data/ext/ratatui_ruby/src/color.rs +82 -0
  65. data/ext/ratatui_ruby/src/errors.rs +28 -0
  66. data/ext/ratatui_ruby/src/events.rs +15 -14
  67. data/ext/ratatui_ruby/src/lib.rs +56 -0
  68. data/ext/ratatui_ruby/src/rendering.rs +3 -1
  69. data/ext/ratatui_ruby/src/style.rs +48 -21
  70. data/ext/ratatui_ruby/src/terminal.rs +40 -9
  71. data/ext/ratatui_ruby/src/text.rs +21 -9
  72. data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
  73. data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
  74. data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
  75. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  76. data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
  77. data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
  78. data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
  79. data/lib/ratatui_ruby/buffer/cell.rb +25 -15
  80. data/lib/ratatui_ruby/buffer.rb +134 -2
  81. data/lib/ratatui_ruby/cell.rb +13 -5
  82. data/lib/ratatui_ruby/debug.rb +215 -0
  83. data/lib/ratatui_ruby/event/key.rb +3 -2
  84. data/lib/ratatui_ruby/event.rb +1 -1
  85. data/lib/ratatui_ruby/layout/constraint.rb +49 -0
  86. data/lib/ratatui_ruby/layout/layout.rb +119 -13
  87. data/lib/ratatui_ruby/layout/position.rb +55 -0
  88. data/lib/ratatui_ruby/layout/rect.rb +188 -0
  89. data/lib/ratatui_ruby/layout/size.rb +55 -0
  90. data/lib/ratatui_ruby/layout.rb +4 -0
  91. data/lib/ratatui_ruby/style/color.rb +149 -0
  92. data/lib/ratatui_ruby/style/style.rb +51 -4
  93. data/lib/ratatui_ruby/style.rb +2 -0
  94. data/lib/ratatui_ruby/symbols.rb +435 -0
  95. data/lib/ratatui_ruby/synthetic_events.rb +1 -1
  96. data/lib/ratatui_ruby/table_state.rb +51 -0
  97. data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
  98. data/lib/ratatui_ruby/test_helper/event_injection.rb +6 -1
  99. data/lib/ratatui_ruby/test_helper.rb +9 -0
  100. data/lib/ratatui_ruby/text/line.rb +245 -0
  101. data/lib/ratatui_ruby/text/span.rb +158 -0
  102. data/lib/ratatui_ruby/text.rb +99 -0
  103. data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
  104. data/lib/ratatui_ruby/tui/core.rb +13 -2
  105. data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
  106. data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
  107. data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
  108. data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
  109. data/lib/ratatui_ruby/tui.rb +22 -1
  110. data/lib/ratatui_ruby/version.rb +1 -1
  111. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  112. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  113. data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
  114. data/lib/ratatui_ruby/widgets/block.rb +14 -6
  115. data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
  116. data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
  117. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  118. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  119. data/lib/ratatui_ruby/widgets/chart.rb +6 -0
  120. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  121. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  122. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  123. data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
  124. data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
  125. data/lib/ratatui_ruby/widgets/list.rb +87 -3
  126. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  127. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  128. data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
  129. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
  130. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
  131. data/lib/ratatui_ruby/widgets/row.rb +45 -0
  132. data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
  133. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  134. data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
  135. data/lib/ratatui_ruby/widgets/table.rb +13 -3
  136. data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
  137. data/lib/ratatui_ruby/widgets.rb +1 -0
  138. data/lib/ratatui_ruby.rb +40 -9
  139. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  140. data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
  141. data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
  142. data/sig/examples/app_all_events/view.rbs +1 -1
  143. data/sig/examples/app_stateful_interaction/app.rbs +5 -5
  144. data/sig/examples/widget_block_demo/app.rbs +6 -6
  145. data/sig/manifest.yaml +5 -0
  146. data/sig/patches/data.rbs +26 -0
  147. data/sig/patches/debugger__.rbs +8 -0
  148. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  149. data/sig/ratatui_ruby/buffer.rbs +18 -0
  150. data/sig/ratatui_ruby/cell.rbs +44 -0
  151. data/sig/ratatui_ruby/clear.rbs +18 -0
  152. data/sig/ratatui_ruby/constraint.rbs +26 -0
  153. data/sig/ratatui_ruby/debug.rbs +45 -0
  154. data/sig/ratatui_ruby/draw.rbs +30 -0
  155. data/sig/ratatui_ruby/event.rbs +68 -8
  156. data/sig/ratatui_ruby/frame.rbs +4 -4
  157. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  158. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  159. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  160. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  161. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  162. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  163. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  164. data/sig/ratatui_ruby/ratatui_ruby.rbs +83 -4
  165. data/sig/ratatui_ruby/rect.rbs +17 -0
  166. data/sig/ratatui_ruby/style/color.rbs +22 -0
  167. data/sig/ratatui_ruby/style/style.rbs +29 -0
  168. data/sig/ratatui_ruby/symbols.rbs +141 -0
  169. data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
  170. data/sig/ratatui_ruby/table_state.rbs +6 -0
  171. data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
  172. data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
  173. data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
  174. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
  175. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
  176. data/sig/ratatui_ruby/text/line.rbs +27 -0
  177. data/sig/ratatui_ruby/text/span.rbs +23 -0
  178. data/sig/ratatui_ruby/text.rbs +12 -0
  179. data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
  180. data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
  181. data/sig/ratatui_ruby/tui/core.rbs +2 -2
  182. data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
  183. data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
  184. data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
  185. data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
  186. data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
  187. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  188. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  189. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  190. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  191. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  192. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  193. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  194. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  195. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  196. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  197. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  198. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  199. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  200. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  201. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  202. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  203. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  204. data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
  205. data/tasks/steep.rake +11 -0
  206. metadata +80 -63
  207. data/doc/contributors/v1.0.0_blockers.md +0 -870
  208. data/doc/troubleshooting/debugging.md +0 -101
  209. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
  210. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
  211. data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
  212. data/lib/ratatui_ruby/schema/block.rb +0 -198
  213. data/lib/ratatui_ruby/schema/calendar.rb +0 -84
  214. data/lib/ratatui_ruby/schema/canvas.rb +0 -239
  215. data/lib/ratatui_ruby/schema/center.rb +0 -67
  216. data/lib/ratatui_ruby/schema/chart.rb +0 -159
  217. data/lib/ratatui_ruby/schema/clear.rb +0 -62
  218. data/lib/ratatui_ruby/schema/constraint.rb +0 -151
  219. data/lib/ratatui_ruby/schema/cursor.rb +0 -50
  220. data/lib/ratatui_ruby/schema/gauge.rb +0 -72
  221. data/lib/ratatui_ruby/schema/layout.rb +0 -122
  222. data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
  223. data/lib/ratatui_ruby/schema/list.rb +0 -135
  224. data/lib/ratatui_ruby/schema/list_item.rb +0 -51
  225. data/lib/ratatui_ruby/schema/overlay.rb +0 -51
  226. data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
  227. data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
  228. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
  229. data/lib/ratatui_ruby/schema/rect.rb +0 -174
  230. data/lib/ratatui_ruby/schema/row.rb +0 -76
  231. data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
  232. data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
  233. data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
  234. data/lib/ratatui_ruby/schema/style.rb +0 -97
  235. data/lib/ratatui_ruby/schema/table.rb +0 -141
  236. data/lib/ratatui_ruby/schema/tabs.rb +0 -85
  237. data/lib/ratatui_ruby/schema/text.rb +0 -217
  238. data/sig/examples/app_all_events/model/events.rbs +0 -15
  239. data/sig/examples/app_all_events/view_state.rbs +0 -21
  240. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
  241. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
  242. data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
  243. data/sig/ratatui_ruby/schema/block.rbs +0 -18
  244. data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
  245. data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
  246. data/sig/ratatui_ruby/schema/center.rbs +0 -17
  247. data/sig/ratatui_ruby/schema/chart.rbs +0 -39
  248. data/sig/ratatui_ruby/schema/constraint.rbs +0 -30
  249. data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
  250. data/sig/ratatui_ruby/schema/draw.rbs +0 -33
  251. data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
  252. data/sig/ratatui_ruby/schema/layout.rbs +0 -27
  253. data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
  254. data/sig/ratatui_ruby/schema/list.rbs +0 -28
  255. data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
  256. data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
  257. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
  258. data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
  259. data/sig/ratatui_ruby/schema/rect.rbs +0 -48
  260. data/sig/ratatui_ruby/schema/row.rbs +0 -28
  261. data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
  262. data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
  263. data/sig/ratatui_ruby/schema/style.rbs +0 -19
  264. data/sig/ratatui_ruby/schema/table.rbs +0 -32
  265. data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
  266. data/sig/ratatui_ruby/schema/text.rbs +0 -31
  267. /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
@@ -258,20 +258,25 @@ These larger examples combine widgets into complete applications, demonstrating
258
258
  |-------------|--------------|-------------------|
259
259
  | [All Events](../examples/app_all_events/app.rb) | Model-View-Update | Event handling, unidirectional data flow, scalable structure |
260
260
  | [Color Picker](../examples/app_color_picker/app.rb) | Component-Based | Hit testing, modal dialogs, encapsulated state |
261
+ | [Debugging Showcase](../examples/app_debugging_showcase/app.rb) | Simple Loop | Remote debugging, Rust backtraces, improved error messages |
261
262
  | [Login Form](../examples/app_login_form/app.rb) | Overlay + Center | Modal forms, cursor positioning, text input |
262
263
  | [Stateful Interaction](../examples/app_stateful_interaction/app.rb) | State Objects | ListState/TableState, offset read-back, mouse click-to-row |
263
264
 
264
265
  #### All Events
265
266
 
266
- [![all_events](./images/app_all_events.png)](../examples/app_all_events/README.md)
267
+ [![all_events](../images/app_all_events.png)](../examples/app_all_events/README.md)
267
268
 
268
269
  #### Color Picker
269
270
 
270
- [![color_picker](./images/app_color_picker.png)](../examples/app_color_picker/README.md)
271
+ [![color_picker](../images/app_color_picker.png)](../examples/app_color_picker/README.md)
272
+
273
+ #### Debugging Showcase
274
+
275
+ [![debugging_showcase](../images/app_debugging_showcase.gif)](../examples/app_debugging_showcase/README.md)
271
276
 
272
277
  #### Login Form
273
278
 
274
- [![login_form](./images/app_login_form.png)](../examples/app_login_form/README.md)
279
+ [![login_form](../images/app_login_form.png)](../examples/app_login_form/README.md)
275
280
 
276
281
 
277
282
  ## Next Steps
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
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
@@ -0,0 +1,119 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Debugging Showcase
7
+
8
+ [![Debugging Showcase](../../doc/images/app_debugging_showcase.gif)](app.rb)
9
+
10
+ Interactive demonstration of RatatuiRuby's debugging features.
11
+
12
+ For comprehensive documentation, see the [Debugging Guide](../../doc/concepts/debugging.md).
13
+
14
+ ## What This Example Does
15
+
16
+ This app lets you trigger each debugging feature with a hotkey. Test your setup before encountering a real bug.
17
+
18
+ | Key | Action |
19
+ |-----|--------|
20
+ | `d` | Enable `debug_mode!` programmatically |
21
+ | `p` | Trigger a Rust panic |
22
+ | `t` | Trigger a Rust `TypeError` |
23
+ | `b` | Refresh debug status |
24
+ | `q` | Quit |
25
+
26
+ ## Library Features Showcased
27
+
28
+ Reading this code will teach you how to:
29
+
30
+ * **Enable Remote Debugging**: Call `RatatuiRuby.debug_mode!` and receive the socket path
31
+ * **Detect Debug State**: Query `Debug.enabled?`, `Debug.rust_backtrace_enabled?`, and `Debug.remote_debugging_mode`
32
+ * **Trigger Test Panics**: Use `Debug.test_panic!` to verify Rust backtrace visibility
33
+ * **See Improved Error Messages**: Bypass DWIM coercion to see `TypeError` messages with value context
34
+
35
+ **Note:** Rust backtraces only appear for panics (`[p]`). Exceptions (`[t]`) show Ruby backtraces with improved error messages from Rust.
36
+
37
+ ## Environment Variables
38
+
39
+ The example behaves differently depending on which environment variables you set.
40
+
41
+ ### No Environment Variables
42
+
43
+ <!-- SPDX-SnippetBegin -->
44
+ <!--
45
+ SPDX-FileCopyrightText: 2026 Kerrick Long
46
+ SPDX-License-Identifier: MIT-0
47
+ -->
48
+ ```bash
49
+ ruby examples/app_debugging_showcase/app.rb
50
+ ```
51
+ <!-- SPDX-SnippetEnd -->
52
+
53
+ The TUI starts normally. Rust backtraces are disabled. Press `[d]` to enable remote debugging mid-session.
54
+
55
+ ### `RUST_BACKTRACE=1`
56
+
57
+ <!-- SPDX-SnippetBegin -->
58
+ <!--
59
+ SPDX-FileCopyrightText: 2026 Kerrick Long
60
+ SPDX-License-Identifier: MIT-0
61
+ -->
62
+ ```bash
63
+ RUST_BACKTRACE=1 ruby examples/app_debugging_showcase/app.rb
64
+ ```
65
+ <!-- SPDX-SnippetEnd -->
66
+
67
+ Enables Rust backtraces. Press `[p]` to see the full Rust stack trace with file names and line numbers.
68
+
69
+ ### `RR_DEBUG=1`
70
+
71
+ <!-- SPDX-SnippetBegin -->
72
+ <!--
73
+ SPDX-FileCopyrightText: 2026 Kerrick Long
74
+ SPDX-License-Identifier: MIT-0
75
+ -->
76
+ ```bash
77
+ RR_DEBUG=1 ruby examples/app_debugging_showcase/app.rb
78
+ ```
79
+ <!-- SPDX-SnippetEnd -->
80
+
81
+ Full debug mode at startup. The debugger opens a socket and **waits for a connection** before the TUI starts. You'll see the socket path printed before the app runs.
82
+
83
+ In another terminal:
84
+
85
+ <!-- SPDX-SnippetBegin -->
86
+ <!--
87
+ SPDX-FileCopyrightText: 2026 Kerrick Long
88
+ SPDX-License-Identifier: MIT-0
89
+ -->
90
+ ```bash
91
+ rdbg --attach
92
+ ```
93
+ <!-- SPDX-SnippetEnd -->
94
+
95
+ When you attach, you'll see "Stop by SIGURG" — that's normal! SIGURG is how the debug gem signals the app to pause. Type `continue` to resume.
96
+
97
+ This uses the [ruby/debug](https://github.com/ruby/debug?tab=readme-ov-file#readme) gem for remote debugging.
98
+
99
+ ### When to Use Which
100
+
101
+ `RR_DEBUG=1` includes Rust backtraces automatically (it calls `enable_rust_backtrace!` internally).
102
+
103
+ Use `RUST_BACKTRACE=1` alone when you want backtraces but **don't** want the debugger to stop and wait for a connection at startup.
104
+
105
+ ## What Problems Does This Solve?
106
+
107
+ ### "Is my Rust backtrace setup working?"
108
+
109
+ Press `[p]`. If you see a stack trace with file names and line numbers, you're good. If not, you need `RUST_BACKTRACE=1`.
110
+
111
+ ### "How do I attach a debugger to a TUI?"
112
+
113
+ TUIs run in raw mode — you can't type into a REPL. Press `[d]` to enable remote debugging. The socket path appears in the UI. Run `rdbg --attach` from another terminal.
114
+
115
+ ### "What does a Rust TypeError look like?"
116
+
117
+ Press `[t]`. The error message shows the expected type and the actual value you passed (e.g., `expected array for rows, got 42`). Note: This shows Ruby's backtrace — only panics (`[p]`) show Rust backtraces.
118
+
119
+ [Read the source code →](app.rb)
@@ -0,0 +1,318 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: MIT-0
6
+ #++
7
+
8
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
9
+
10
+ require "ratatui_ruby"
11
+
12
+ ##
13
+ # Interactive demonstration of RatatuiRuby debugging features.
14
+ #
15
+ # This example lets you trigger each debugging feature with a hotkey to verify
16
+ # your setup works before encountering a real bug.
17
+ #
18
+ # == Hotkeys
19
+ #
20
+ # [d] Enable debug_mode! — Shows the debug socket path for remote attachment
21
+ # [p] Trigger test_panic! — Deliberately crashes to verify Rust backtrace visibility
22
+ # [t] Cause TypeError — Passes wrong type to widget factory to show Rust stack frames
23
+ # [b] Show backtrace status — Displays current debug configuration
24
+ # [q] Quit
25
+ #
26
+ # == Usage
27
+ #
28
+ # # Normal mode (no backtraces):
29
+ # ruby examples/verify_debugging_usage/app.rb
30
+ #
31
+ # # With Rust backtraces only:
32
+ # RUST_BACKTRACE=1 ruby examples/verify_debugging_usage/app.rb
33
+ #
34
+ # # Full debug mode (stops at startup for debugger attachment):
35
+ # RR_DEBUG=1 ruby examples/verify_debugging_usage/app.rb
36
+ #
37
+ # == Remote Debugging
38
+ #
39
+ # When you press [d] to enable debug_mode!, the app continues running but
40
+ # prints a socket path. From another terminal:
41
+ #
42
+ # rdbg --attach
43
+ #
44
+ # This gives you a full debugger REPL while the TUI keeps running.
45
+ class VerifyDebuggingUsage
46
+ def initialize
47
+ @status_message = "Press a key to test debugging features"
48
+ @show_debug_info = false
49
+ @quit = false
50
+
51
+ # If debug mode was enabled via RR_DEBUG=1 at startup, capture the socket path
52
+ if RatatuiRuby::Debug.enabled?
53
+ @socket_path = begin
54
+ ::DEBUGGER__.create_unix_domain_socket_name
55
+ rescue NameError
56
+ nil
57
+ end
58
+ @show_debug_info = true
59
+ @status_message = "RR_DEBUG=1 detected — debug mode active"
60
+ end
61
+ end
62
+
63
+ def run
64
+ RatatuiRuby.run do |tui|
65
+ @tui = tui
66
+ @loop_count = 0
67
+
68
+ loop do
69
+ @loop_count += 1
70
+
71
+ # 🎯 Breakpoint every 250 loops. Try: p @status_message
72
+ if RatatuiRuby::Debug.enabled? && (@loop_count % 250).zero?
73
+ you_found_me = "🎉 You found me! Loop ##{@loop_count}"
74
+ # rubocop:disable Lint/Debugger
75
+ debugger
76
+ # rubocop:enable Lint/Debugger
77
+ _ = you_found_me # Suppress unused variable warning
78
+ end
79
+
80
+ render
81
+ break if @quit || handle_input == :quit
82
+ end
83
+ end
84
+ end
85
+
86
+ private def render
87
+ @tui.draw do |frame|
88
+ constraints = [
89
+ @tui.constraint_length(3), # Status
90
+ @tui.constraint_length(5), # Config
91
+ @tui.constraint_length(6), # Actions
92
+ ]
93
+
94
+ if @show_debug_info
95
+ constraints << @tui.constraint_length(6) # Debug info
96
+ end
97
+
98
+ constraints << @tui.constraint_fill(1) # Spacer
99
+ constraints << @tui.constraint_length(3) # Help
100
+
101
+ chunks = @tui.layout_split(frame.area, direction: :vertical, constraints:)
102
+
103
+ idx = 0
104
+ render_status(frame, chunks[idx])
105
+ idx += 1
106
+ render_config(frame, chunks[idx])
107
+ idx += 1
108
+ render_actions(frame, chunks[idx])
109
+ idx += 1
110
+
111
+ if @show_debug_info
112
+ render_debug_info(frame, chunks[idx])
113
+ idx += 1
114
+ end
115
+
116
+ # Skip spacer
117
+ idx += 1
118
+ render_help(frame, chunks[idx])
119
+ end
120
+ end
121
+
122
+ private def render_status(frame, area)
123
+ frame.render_widget(
124
+ @tui.paragraph(
125
+ text: @status_message,
126
+ alignment: :center,
127
+ block: @tui.block(
128
+ title: " Status ",
129
+ title_alignment: :center,
130
+ borders: [:all],
131
+ border_style: { fg: :yellow }
132
+ )
133
+ ),
134
+ area
135
+ )
136
+ end
137
+
138
+ private def render_config(frame, area)
139
+ config_lines = [
140
+ "Rust Backtraces: #{flag(RatatuiRuby::Debug.rust_backtrace_enabled?)}",
141
+ "Full Debug Mode: #{flag(RatatuiRuby::Debug.enabled?)}",
142
+ "Remote Debugging: #{remote_mode_description}",
143
+ ].join("\n")
144
+
145
+ frame.render_widget(
146
+ @tui.paragraph(
147
+ text: config_lines,
148
+ block: @tui.block(
149
+ title: " Current Debug Configuration ",
150
+ borders: [:all],
151
+ border_style: { fg: :cyan }
152
+ )
153
+ ),
154
+ area
155
+ )
156
+ end
157
+
158
+ private def render_actions(frame, area)
159
+ actions_lines = [
160
+ "[d] Enable debug_mode! and show socket info",
161
+ "[p] Trigger test_panic! to verify backtrace visibility",
162
+ "[t] Cause TypeError (pass wrong type to widget)",
163
+ "[b] Refresh debug status",
164
+ ].join("\n")
165
+
166
+ frame.render_widget(
167
+ @tui.paragraph(
168
+ text: actions_lines,
169
+ block: @tui.block(
170
+ title: " Available Actions ",
171
+ borders: [:all],
172
+ border_style: { fg: :green }
173
+ )
174
+ ),
175
+ area
176
+ )
177
+ end
178
+
179
+ private def render_debug_info(frame, area)
180
+ socket_display = @socket_path || "(socket not available)"
181
+ info_lines = [
182
+ "Socket: #{socket_display}",
183
+ "Attach: rdbg --attach",
184
+ "Hint: type 'continue' if you see SIGURG",
185
+ ]
186
+
187
+ frame.render_widget(
188
+ @tui.paragraph(
189
+ text: info_lines.join("\n"),
190
+ block: @tui.block(
191
+ title: " Remote Debugging ",
192
+ borders: [:all],
193
+ border_style: { fg: :magenta }
194
+ )
195
+ ),
196
+ area
197
+ )
198
+ end
199
+
200
+ private def render_help(frame, area)
201
+ frame.render_widget(
202
+ @tui.paragraph(
203
+ text: "[d] debug_mode! [p] test_panic! [t] TypeError [b] status [q] quit",
204
+ alignment: :center,
205
+ block: @tui.block(
206
+ borders: [:all],
207
+ border_style: { fg: :dark_gray }
208
+ )
209
+ ),
210
+ area
211
+ )
212
+ end
213
+
214
+ private def flag(value)
215
+ value ? "✓ enabled" : "✗ disabled"
216
+ end
217
+
218
+ private def remote_mode_description
219
+ case RatatuiRuby::Debug.remote_debugging_mode
220
+ when :open
221
+ attached = debugger_attached? ? " — ATTACHED" : " — waiting"
222
+ "✓ open#{attached}"
223
+ when :open_nonstop
224
+ attached = debugger_attached? ? " — ATTACHED" : ""
225
+ "✓ open_nonstop#{attached}"
226
+ else
227
+ "✗ not configured"
228
+ end
229
+ end
230
+
231
+ # ☣️ FRAGILE: This pokes at debug gem internals.
232
+ #
233
+ # Private instance variables can change between gem versions. This code
234
+ # may silently break. We accept that risk here because this showcase
235
+ # exists specifically to demonstrate debugger attachment status.
236
+ #
237
+ # For production apps, checking Debug.enabled? is sufficient — knowing
238
+ # whether a client has attached rarely matters.
239
+ private def debugger_attached?
240
+ return false unless defined?(::DEBUGGER__::SESSION)
241
+
242
+ ui = ::DEBUGGER__::SESSION.instance_variable_get(:@ui)
243
+ return false unless ui
244
+
245
+ # The @sock instance variable is set when a client connects
246
+ sock = ui.instance_variable_get(:@sock)
247
+ !sock.nil?
248
+ rescue
249
+ false
250
+ end
251
+
252
+ private def handle_input
253
+ case @tui.poll_event
254
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
255
+ :quit
256
+
257
+ in { type: :key, code: "d" }
258
+ enable_debug_mode!
259
+
260
+ in { type: :key, code: "p" }
261
+ trigger_test_panic!
262
+
263
+ in { type: :key, code: "t" }
264
+ trigger_type_error!
265
+
266
+ in { type: :key, code: "b" }
267
+ @status_message = "Debug status refreshed at #{Time.now.strftime('%H:%M:%S')}"
268
+
269
+ else
270
+ nil
271
+ end
272
+ end
273
+
274
+ private def enable_debug_mode!
275
+ if RatatuiRuby::Debug.enabled?
276
+ @status_message = "Debug mode already enabled!"
277
+ else
278
+ # debug_mode! returns the socket path and suppresses the debug gem's output
279
+ @socket_path = RatatuiRuby.debug_mode!
280
+ @status_message = "debug_mode! enabled"
281
+ @show_debug_info = true
282
+ end
283
+ end
284
+
285
+ private def trigger_test_panic!
286
+ if RatatuiRuby::Debug.rust_backtrace_enabled?
287
+ @status_message = "Triggering test_panic! — check stderr for backtrace..."
288
+ else
289
+ @status_message = "Triggering test_panic! — backtrace hidden (set RUST_BACKTRACE=1)"
290
+ end
291
+ render # Show the message before crashing
292
+
293
+ # Give a moment for the render to complete
294
+ sleep 0.1
295
+
296
+ # This will crash the app with a Rust panic. If RUST_BACKTRACE=1 or
297
+ # debug mode is enabled, you'll see the full Rust stack trace after
298
+ # the terminal is restored.
299
+ RatatuiRuby::Debug.test_panic!
300
+ end
301
+
302
+ private def trigger_type_error!
303
+ if RatatuiRuby::Debug.rust_backtrace_enabled?
304
+ @status_message = "Triggering TypeError — check stderr for error message..."
305
+ else
306
+ @status_message = "Triggering TypeError — set RUST_BACKTRACE=1 for stack trace"
307
+ end
308
+ render # Show the message before crashing
309
+ sleep 0.1
310
+
311
+ # Bypass the factory's DWIM coercion to trigger a real Rust TypeError.
312
+ # Uses Widgets::Table.new directly with invalid rows type.
313
+ bad_table = RatatuiRuby::Widgets::Table.new(rows: 42, widths: [])
314
+ @tui.draw { |f| f.render_widget(bad_table, f.area) }
315
+ end
316
+ end
317
+
318
+ VerifyDebuggingUsage.new.run if __FILE__ == $PROGRAM_NAME
@@ -37,24 +37,26 @@ class WidgetCanvas
37
37
 
38
38
  private def render
39
39
  @tui.draw do |frame|
40
- # Define shapes
40
+ # Define shapes using terse aliases (circle, rectangle, point, map, label)
41
+ # These are shorter forms of shape_circle, shape_rectangle, etc.
41
42
  shapes = []
42
43
 
43
- # 1. Static Grid (Lines)
44
+ # 1. Static Grid (Lines) - using shape_line (no terse alias for line)
44
45
  (-100..100).step(20) do |i|
45
46
  shapes << @tui.shape_line(x1: i.to_f, y1: -100.0, x2: i.to_f, y2: 100.0, color: :gray)
46
47
  shapes << @tui.shape_line(x1: -100.0, y1: i.to_f, x2: 100.0, y2: i.to_f, color: :gray)
47
48
  end
48
49
 
49
- # 2. Moving Circle (The "Player")
50
- shapes << @tui.shape_circle(
50
+ # 2. Moving Circle (The "Player") - using terse 'circle' alias
51
+ shapes << @tui.circle(
51
52
  x: @x_offset,
52
53
  y: @y_offset,
53
54
  radius: 10.0,
54
55
  color: :green
55
56
  )
56
57
 
57
- # 3. Static Rectangle (Target)
58
+ # 3. Static Rectangle (Target) - using shape_rectangle (no 'rectangle' alias
59
+ # to avoid confusion with Layout::Rect)
58
60
  shapes << @tui.shape_rectangle(
59
61
  x: 30.0,
60
62
  y: 30.0,
@@ -63,16 +65,16 @@ class WidgetCanvas
63
65
  color: :red
64
66
  )
65
67
 
66
- # 4. Points (Starfield)
68
+ # 4. Points (Starfield) - using terse 'point' alias
67
69
  # Deterministic "random" points
68
70
  10.times do |i|
69
- shapes << @tui.shape_point(
71
+ shapes << @tui.point(
70
72
  x: ((i * 37) % 200) - 100.0,
71
73
  y: ((i * 19) % 200) - 100.0
72
74
  )
73
75
  end
74
76
 
75
- # 5. Label
77
+ # 5. Connecting line from origin to player position
76
78
  shapes << @tui.shape_line(x1: 0.0, y1: 0.0, x2: @x_offset, y2: @y_offset, color: :yellow)
77
79
 
78
80
  canvas = @tui.canvas(
@@ -89,21 +91,24 @@ class WidgetCanvas
89
91
  direction: :vertical,
90
92
  constraints: [
91
93
  @tui.constraint_fill(1),
92
- @tui.constraint_length(3),
94
+ @tui.constraint_length(2),
93
95
  ]
94
96
  )
95
97
 
96
98
  frame.render_widget(canvas, layout[0])
97
99
 
98
- # Controls
100
+ # Query: Canvas#get_point maps canvas coordinates to normalized [0.0, 1.0] grid
101
+ normalized = canvas.get_point(@x_offset, @y_offset)
102
+ norm_str = normalized ? format("[%.2f, %.2f]", normalized[0], normalized[1]) : "nil"
103
+
104
+ # Controls showing query method demonstration (single concise line)
99
105
  controls = @tui.paragraph(
100
106
  text: [
101
- @tui.text_line(spans: [
102
- @tui.text_span(content: "Canvas auto-animates.", style: @tui.style(fg: :yellow)),
103
- ]),
104
107
  @tui.text_line(spans: [
105
108
  @tui.text_span(content: "q", style: @tui.style(modifiers: [:bold, :underlined])),
106
- @tui.text_span(content: ": Quit"),
109
+ @tui.text_span(content: ": Quit "),
110
+ @tui.text_span(content: "get_point → ", style: @tui.style(fg: :dark_gray)),
111
+ @tui.text_span(content: norm_str, style: @tui.style(fg: :cyan)),
107
112
  ]),
108
113
  ],
109
114
  block: @tui.block(borders: [:top])