ratatui_ruby 0.9.1 → 0.10.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 (268) 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 +113 -0
  8. data/README.md +17 -0
  9. data/REUSE.toml +5 -0
  10. data/Rakefile +1 -1
  11. data/Steepfile +49 -0
  12. data/doc/concepts/debugging.md +401 -0
  13. data/doc/getting_started/quickstart.md +8 -3
  14. data/doc/images/app_all_events.png +0 -0
  15. data/doc/images/app_color_picker.png +0 -0
  16. data/doc/images/app_debugging_showcase.gif +0 -0
  17. data/doc/images/app_debugging_showcase.png +0 -0
  18. data/doc/images/app_login_form.png +0 -0
  19. data/doc/images/app_stateful_interaction.png +0 -0
  20. data/doc/images/verify_quickstart_dsl.png +0 -0
  21. data/doc/images/verify_quickstart_layout.png +0 -0
  22. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  23. data/doc/images/verify_readme_usage.png +0 -0
  24. data/doc/images/widget_barchart.png +0 -0
  25. data/doc/images/widget_block.png +0 -0
  26. data/doc/images/widget_box.png +0 -0
  27. data/doc/images/widget_calendar.png +0 -0
  28. data/doc/images/widget_canvas.png +0 -0
  29. data/doc/images/widget_cell.png +0 -0
  30. data/doc/images/widget_center.png +0 -0
  31. data/doc/images/widget_chart.png +0 -0
  32. data/doc/images/widget_gauge.png +0 -0
  33. data/doc/images/widget_layout_split.png +0 -0
  34. data/doc/images/widget_line_gauge.png +0 -0
  35. data/doc/images/widget_list.png +0 -0
  36. data/doc/images/widget_map.png +0 -0
  37. data/doc/images/widget_overlay.png +0 -0
  38. data/doc/images/widget_popup.png +0 -0
  39. data/doc/images/widget_ratatui_logo.png +0 -0
  40. data/doc/images/widget_ratatui_mascot.png +0 -0
  41. data/doc/images/widget_rect.png +0 -0
  42. data/doc/images/widget_render.png +0 -0
  43. data/doc/images/widget_rich_text.png +0 -0
  44. data/doc/images/widget_scroll_text.png +0 -0
  45. data/doc/images/widget_scrollbar.png +0 -0
  46. data/doc/images/widget_sparkline.png +0 -0
  47. data/doc/images/widget_style_colors.png +0 -0
  48. data/doc/images/widget_table.png +0 -0
  49. data/doc/images/widget_tabs.png +0 -0
  50. data/doc/images/widget_text_width.png +0 -0
  51. data/doc/troubleshooting/async.md +4 -0
  52. data/examples/app_debugging_showcase/README.md +119 -0
  53. data/examples/app_debugging_showcase/app.rb +318 -0
  54. data/examples/widget_canvas/app.rb +19 -14
  55. data/examples/widget_gauge/app.rb +18 -3
  56. data/examples/widget_layout_split/app.rb +10 -4
  57. data/examples/widget_list/app.rb +22 -6
  58. data/examples/widget_rect/app.rb +7 -6
  59. data/examples/widget_rich_text/app.rb +62 -37
  60. data/examples/widget_style_colors/app.rb +26 -47
  61. data/examples/widget_table/app.rb +28 -5
  62. data/examples/widget_text_width/app.rb +6 -4
  63. data/ext/ratatui_ruby/Cargo.lock +48 -1
  64. data/ext/ratatui_ruby/Cargo.toml +6 -2
  65. data/ext/ratatui_ruby/src/color.rs +82 -0
  66. data/ext/ratatui_ruby/src/errors.rs +28 -0
  67. data/ext/ratatui_ruby/src/events.rs +15 -14
  68. data/ext/ratatui_ruby/src/lib.rs +56 -0
  69. data/ext/ratatui_ruby/src/rendering.rs +3 -1
  70. data/ext/ratatui_ruby/src/style.rs +48 -21
  71. data/ext/ratatui_ruby/src/terminal.rs +40 -9
  72. data/ext/ratatui_ruby/src/text.rs +21 -9
  73. data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
  74. data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
  75. data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
  76. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  77. data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
  78. data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
  79. data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
  80. data/lib/ratatui_ruby/buffer/cell.rb +25 -15
  81. data/lib/ratatui_ruby/buffer.rb +134 -2
  82. data/lib/ratatui_ruby/cell.rb +13 -5
  83. data/lib/ratatui_ruby/debug.rb +215 -0
  84. data/lib/ratatui_ruby/event/key.rb +3 -2
  85. data/lib/ratatui_ruby/event.rb +1 -1
  86. data/lib/ratatui_ruby/layout/constraint.rb +49 -0
  87. data/lib/ratatui_ruby/layout/layout.rb +119 -13
  88. data/lib/ratatui_ruby/layout/position.rb +55 -0
  89. data/lib/ratatui_ruby/layout/rect.rb +188 -0
  90. data/lib/ratatui_ruby/layout/size.rb +55 -0
  91. data/lib/ratatui_ruby/layout.rb +4 -0
  92. data/lib/ratatui_ruby/style/color.rb +149 -0
  93. data/lib/ratatui_ruby/style/style.rb +51 -4
  94. data/lib/ratatui_ruby/style.rb +2 -0
  95. data/lib/ratatui_ruby/symbols.rb +435 -0
  96. data/lib/ratatui_ruby/synthetic_events.rb +1 -1
  97. data/lib/ratatui_ruby/table_state.rb +51 -0
  98. data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
  99. data/lib/ratatui_ruby/test_helper/event_injection.rb +6 -1
  100. data/lib/ratatui_ruby/test_helper.rb +9 -0
  101. data/lib/ratatui_ruby/text/line.rb +245 -0
  102. data/lib/ratatui_ruby/text/span.rb +158 -0
  103. data/lib/ratatui_ruby/text.rb +99 -0
  104. data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
  105. data/lib/ratatui_ruby/tui/core.rb +13 -2
  106. data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
  107. data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
  108. data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
  109. data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
  110. data/lib/ratatui_ruby/tui.rb +22 -1
  111. data/lib/ratatui_ruby/version.rb +1 -1
  112. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  113. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  114. data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
  115. data/lib/ratatui_ruby/widgets/block.rb +14 -6
  116. data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
  117. data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
  118. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  119. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  120. data/lib/ratatui_ruby/widgets/chart.rb +6 -0
  121. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  122. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  123. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  124. data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
  125. data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
  126. data/lib/ratatui_ruby/widgets/list.rb +87 -3
  127. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  128. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  129. data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
  130. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
  131. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
  132. data/lib/ratatui_ruby/widgets/row.rb +45 -0
  133. data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
  134. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  135. data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
  136. data/lib/ratatui_ruby/widgets/table.rb +13 -3
  137. data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
  138. data/lib/ratatui_ruby/widgets.rb +1 -0
  139. data/lib/ratatui_ruby.rb +42 -11
  140. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  141. data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
  142. data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
  143. data/sig/examples/app_all_events/view.rbs +1 -1
  144. data/sig/examples/app_stateful_interaction/app.rbs +5 -5
  145. data/sig/examples/widget_block_demo/app.rbs +6 -6
  146. data/sig/manifest.yaml +5 -0
  147. data/sig/patches/data.rbs +26 -0
  148. data/sig/patches/debugger__.rbs +8 -0
  149. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  150. data/sig/ratatui_ruby/buffer.rbs +18 -0
  151. data/sig/ratatui_ruby/cell.rbs +44 -0
  152. data/sig/ratatui_ruby/clear.rbs +18 -0
  153. data/sig/ratatui_ruby/constraint.rbs +26 -0
  154. data/sig/ratatui_ruby/debug.rbs +45 -0
  155. data/sig/ratatui_ruby/draw.rbs +30 -0
  156. data/sig/ratatui_ruby/event.rbs +68 -8
  157. data/sig/ratatui_ruby/frame.rbs +4 -4
  158. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  159. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  160. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  161. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  162. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  163. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  164. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  165. data/sig/ratatui_ruby/ratatui_ruby.rbs +84 -5
  166. data/sig/ratatui_ruby/rect.rbs +17 -0
  167. data/sig/ratatui_ruby/style/color.rbs +22 -0
  168. data/sig/ratatui_ruby/style/style.rbs +29 -0
  169. data/sig/ratatui_ruby/symbols.rbs +141 -0
  170. data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
  171. data/sig/ratatui_ruby/table_state.rbs +6 -0
  172. data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
  173. data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
  174. data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
  175. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
  176. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
  177. data/sig/ratatui_ruby/text/line.rbs +27 -0
  178. data/sig/ratatui_ruby/text/span.rbs +23 -0
  179. data/sig/ratatui_ruby/text.rbs +12 -0
  180. data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
  181. data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
  182. data/sig/ratatui_ruby/tui/core.rbs +2 -2
  183. data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
  184. data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
  185. data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
  186. data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
  187. data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
  188. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  189. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  190. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  191. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  192. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  193. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  194. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  195. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  196. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  197. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  198. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  199. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  200. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  201. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  202. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  203. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  204. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  205. data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
  206. data/tasks/steep.rake +11 -0
  207. metadata +80 -63
  208. data/doc/contributors/v1.0.0_blockers.md +0 -870
  209. data/doc/troubleshooting/debugging.md +0 -101
  210. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
  211. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
  212. data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
  213. data/lib/ratatui_ruby/schema/block.rb +0 -198
  214. data/lib/ratatui_ruby/schema/calendar.rb +0 -84
  215. data/lib/ratatui_ruby/schema/canvas.rb +0 -239
  216. data/lib/ratatui_ruby/schema/center.rb +0 -67
  217. data/lib/ratatui_ruby/schema/chart.rb +0 -159
  218. data/lib/ratatui_ruby/schema/clear.rb +0 -62
  219. data/lib/ratatui_ruby/schema/constraint.rb +0 -151
  220. data/lib/ratatui_ruby/schema/cursor.rb +0 -50
  221. data/lib/ratatui_ruby/schema/gauge.rb +0 -72
  222. data/lib/ratatui_ruby/schema/layout.rb +0 -122
  223. data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
  224. data/lib/ratatui_ruby/schema/list.rb +0 -135
  225. data/lib/ratatui_ruby/schema/list_item.rb +0 -51
  226. data/lib/ratatui_ruby/schema/overlay.rb +0 -51
  227. data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
  228. data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
  229. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
  230. data/lib/ratatui_ruby/schema/rect.rb +0 -174
  231. data/lib/ratatui_ruby/schema/row.rb +0 -76
  232. data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
  233. data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
  234. data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
  235. data/lib/ratatui_ruby/schema/style.rb +0 -97
  236. data/lib/ratatui_ruby/schema/table.rb +0 -141
  237. data/lib/ratatui_ruby/schema/tabs.rb +0 -85
  238. data/lib/ratatui_ruby/schema/text.rb +0 -217
  239. data/sig/examples/app_all_events/model/events.rbs +0 -15
  240. data/sig/examples/app_all_events/view_state.rbs +0 -21
  241. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
  242. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
  243. data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
  244. data/sig/ratatui_ruby/schema/block.rbs +0 -18
  245. data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
  246. data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
  247. data/sig/ratatui_ruby/schema/center.rbs +0 -17
  248. data/sig/ratatui_ruby/schema/chart.rbs +0 -39
  249. data/sig/ratatui_ruby/schema/constraint.rbs +0 -30
  250. data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
  251. data/sig/ratatui_ruby/schema/draw.rbs +0 -33
  252. data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
  253. data/sig/ratatui_ruby/schema/layout.rbs +0 -27
  254. data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
  255. data/sig/ratatui_ruby/schema/list.rbs +0 -28
  256. data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
  257. data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
  258. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
  259. data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
  260. data/sig/ratatui_ruby/schema/rect.rbs +0 -48
  261. data/sig/ratatui_ruby/schema/row.rbs +0 -28
  262. data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
  263. data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
  264. data/sig/ratatui_ruby/schema/style.rbs +0 -19
  265. data/sig/ratatui_ruby/schema/table.rbs +0 -32
  266. data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
  267. data/sig/ratatui_ruby/schema/text.rbs +0 -31
  268. /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
@@ -0,0 +1,401 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Debugging Guide
7
+
8
+ TUI applications are harder to debug than typical Ruby programs. The terminal is in raw mode. Standard output corrupts the display. Debuggers that rely on REPL input conflict with the event loop. Rust panics produce cryptic stack traces without symbols.
9
+
10
+ This guide covers what RatatuiRuby offers and what works (and what does not) when debugging TUI apps.
11
+
12
+ ## Debug Mode
13
+
14
+ RatatuiRuby ships with debug symbols in release builds. Call `RatatuiRuby::Debug.enable!` to get Rust backtraces with meaningful stack frames.
15
+
16
+ ### Activation Methods
17
+
18
+ You can turn on debug features in three ways.
19
+
20
+ 1. **Environment variable (Rust only):** `RUST_BACKTRACE=1` turns on Rust backtraces without Ruby-side debug features.
21
+
22
+ 2. **Environment variable (full):** `RR_DEBUG=1` turns on full debug mode at process startup.
23
+
24
+ 3. **Programmatic:** Call `RatatuiRuby.debug_mode!` or `RatatuiRuby::Debug.enable!`.
25
+
26
+ > [!WARNING]
27
+ > Debug mode opens a remote debugging socket. This is a **security vulnerability**. Do not use it in production. See [Remote Debugging](#remote-debugging) for details.
28
+
29
+ Including `RatatuiRuby::TestHelper` auto-enables debug mode. Test authors get backtraces automatically.
30
+
31
+ <!-- SPDX-SnippetBegin -->
32
+ <!--
33
+ SPDX-FileCopyrightText: 2026 Kerrick Long
34
+ SPDX-License-Identifier: MIT-0
35
+ -->
36
+ ```ruby
37
+ # Option 1: Environment variable
38
+ # $ RR_DEBUG=1 ruby my_app.rb
39
+
40
+ # Option 2: Programmatic
41
+ RatatuiRuby.debug_mode!
42
+
43
+ # Now Rust panics show meaningful stack traces
44
+ ```
45
+ <!-- SPDX-SnippetEnd -->
46
+
47
+ ### Panics vs. Exceptions
48
+
49
+ Rust backtraces only appear for **panics** (unrecoverable crashes). When Rust code raises a Ruby exception (like `TypeError`), Ruby handles the backtrace. Rust provides the error message.
50
+
51
+ | Error Type | Backtrace | When It Happens |
52
+ |------------|-----------|-----------------|
53
+ | **Panic** | Rust stack trace | Internal Rust bug, `Debug.test_panic!` |
54
+ | **Exception** | Ruby stack trace | Type mismatch, invalid arguments |
55
+
56
+ The `RUST_BACKTRACE=1` environment variable and `Debug.enable!` affect panic backtraces. Exceptions always show Ruby backtraces, but RatatuiRuby includes **contextual error messages** showing the actual value that caused the error:
57
+
58
+ <!-- SPDX-SnippetBegin -->
59
+ <!--
60
+ SPDX-FileCopyrightText: 2026 Kerrick Long
61
+ SPDX-License-Identifier: MIT-0
62
+ -->
63
+ ```
64
+ # Without context (generic):
65
+ expected array for rows
66
+
67
+ # With context (RatatuiRuby):
68
+ expected array for rows, got 42
69
+ ```
70
+ <!-- SPDX-SnippetEnd -->
71
+
72
+ ## Inspecting the Buffer
73
+
74
+ The following methods help you debug rendering issues from tests or scripts.
75
+
76
+ ### print_buffer
77
+
78
+ Outputs the current terminal buffer to STDOUT with full ANSI colors. Call it inside `with_test_terminal` to see exactly what would render.
79
+
80
+ <!-- SPDX-SnippetBegin -->
81
+ <!--
82
+ SPDX-FileCopyrightText: 2026 Kerrick Long
83
+ SPDX-License-Identifier: MIT-0
84
+ -->
85
+ ```ruby
86
+ with_test_terminal do
87
+ MyApp.new.render
88
+ print_buffer # Outputs the screen with colors
89
+ end
90
+ ```
91
+ <!-- SPDX-SnippetEnd -->
92
+
93
+ ### buffer_content
94
+
95
+ Returns the terminal buffer as an array of strings (one per row). Use it for programmatic inspection.
96
+
97
+ <!-- SPDX-SnippetBegin -->
98
+ <!--
99
+ SPDX-FileCopyrightText: 2026 Kerrick Long
100
+ SPDX-License-Identifier: MIT-0
101
+ -->
102
+ ```ruby
103
+ with_test_terminal do
104
+ MyApp.new.render
105
+ pp buffer_content # ["Line 1: ...", "Line 2: ...", ...]
106
+ end
107
+ ```
108
+ <!-- SPDX-SnippetEnd -->
109
+
110
+ ### get_cell
111
+
112
+ Returns a `Buffer::Cell` with the character, foreground color, background color, and modifiers at specific coordinates.
113
+
114
+ <!-- SPDX-SnippetBegin -->
115
+ <!--
116
+ SPDX-FileCopyrightText: 2026 Kerrick Long
117
+ SPDX-License-Identifier: MIT-0
118
+ -->
119
+ ```ruby
120
+ with_test_terminal do
121
+ MyApp.new.render
122
+ cell = get_cell(0, 0)
123
+ pp cell.symbol # "H"
124
+ pp cell.fg # :red
125
+ pp cell.bold? # true
126
+ end
127
+ ```
128
+ <!-- SPDX-SnippetEnd -->
129
+
130
+ ## Protecting Output
131
+
132
+ During a TUI session, writes to `$stdout` or `$stderr` corrupt the display. Third-party gems often print warnings or debug output unexpectedly.
133
+
134
+ Use `guard_io` to temporarily swallow output from chatty code.
135
+
136
+ <!-- SPDX-SnippetBegin -->
137
+ <!--
138
+ SPDX-FileCopyrightText: 2026 Kerrick Long
139
+ SPDX-License-Identifier: MIT-0
140
+ -->
141
+ ```ruby
142
+ RatatuiRuby.run do |tui|
143
+ RatatuiRuby.guard_io do
144
+ SomeChattyGem.process # Any puts/warn calls are swallowed
145
+ end
146
+ end
147
+ ```
148
+ <!-- SPDX-SnippetEnd -->
149
+
150
+ ## Interactive Debuggers
151
+
152
+ > [!WARNING]
153
+ > This section has not been verified by a human.
154
+
155
+ > [!CAUTION]
156
+ > Traditional interactive debuggers (Pry, IRB, debug.gem) do not work inside an active TUI session. They require terminal input and output, which conflicts with raw mode.
157
+
158
+ ### Workarounds
159
+
160
+ **Temporarily exit TUI mode.** Restore the terminal, run your debugger, then re-initialize.
161
+
162
+ <!-- SPDX-SnippetBegin -->
163
+ <!--
164
+ SPDX-FileCopyrightText: 2026 Kerrick Long
165
+ SPDX-License-Identifier: MIT-0
166
+ -->
167
+ ```ruby
168
+ RatatuiRuby.restore_terminal
169
+ binding.pry # Now Pry works normally
170
+ RatatuiRuby.init_terminal
171
+ ```
172
+ <!-- SPDX-SnippetEnd -->
173
+
174
+ **Use test mode.** Debug rendering logic inside `with_test_terminal` where there is no real terminal conflict.
175
+
176
+ <!-- SPDX-SnippetBegin -->
177
+ <!--
178
+ SPDX-FileCopyrightText: 2026 Kerrick Long
179
+ SPDX-License-Identifier: MIT-0
180
+ -->
181
+ ```ruby
182
+ with_test_terminal do
183
+ binding.pry # Works fine in test mode
184
+ MyApp.new.render
185
+ end
186
+ ```
187
+ <!-- SPDX-SnippetEnd -->
188
+
189
+ ## Remote Debugging
190
+
191
+ Debug mode uses [Ruby's `debug` gem](https://rubygems.org/gems/debug) for [remote debugging](https://github.com/ruby/debug?tab=readme-ov-file#readme). Attach from another terminal (or IDE or Chrome DevTools) while the TUI runs.
192
+
193
+ ![Debugging Showcase](../images/app_debugging_showcase.gif)
194
+
195
+ For a hands-on demo, see the [Debugging Showcase](../../examples/app_debugging_showcase/README.md) example.
196
+
197
+ Debug mode loads the `debug` gem and creates a UNIX domain socket. Debuggers attach from another terminal. This works well for TUI apps since the main terminal is in raw mode.
198
+
199
+ ### How It Works
200
+
201
+ - **`RR_DEBUG=1`**: Loads `debug/open`. The app stops at startup and waits for a debugger to attach.
202
+ - **`RatatuiRuby.debug_mode!`**: Loads `debug/open_nonstop`. The app continues running. Attach whenever you want.
203
+
204
+ Attach from another terminal with `rdbg --attach`.
205
+
206
+ <!-- SPDX-SnippetBegin -->
207
+ <!--
208
+ SPDX-FileCopyrightText: 2026 Kerrick Long
209
+ SPDX-License-Identifier: MIT-0
210
+ -->
211
+ ```sh
212
+ $ rdbg --attach
213
+ ```
214
+ <!-- SPDX-SnippetEnd -->
215
+
216
+ > [!CAUTION]
217
+ > Remote debugging opens a backdoor to your application. This is a **security vulnerability**. The `debug/open_nonstop` mode is particularly dangerous because it allows attachment at any time. Do not run debug mode in production. Anyone who can access the socket can execute arbitrary code.
218
+
219
+ ### Example: Debugging a Running TUI
220
+
221
+ Terminal 1 (your app):
222
+ <!-- SPDX-SnippetBegin -->
223
+ <!--
224
+ SPDX-FileCopyrightText: 2026 Kerrick Long
225
+ SPDX-License-Identifier: MIT-0
226
+ -->
227
+ ```sh
228
+ $ ruby my_tui_app.rb
229
+ # App starts, TUI is running
230
+ # In your code: RatatuiRuby.debug_mode!
231
+ # Console shows: DEBUGGER: Debugger can attach via UNIX domain socket (...)
232
+ ```
233
+ <!-- SPDX-SnippetEnd -->
234
+
235
+ Terminal 2 (debugger):
236
+ <!-- SPDX-SnippetBegin -->
237
+ <!--
238
+ SPDX-FileCopyrightText: 2026 Kerrick Long
239
+ SPDX-License-Identifier: MIT-0
240
+ -->
241
+ ```sh
242
+ $ rdbg --attach
243
+ # Now you have a full debugger REPL
244
+ (rdbg) info locals
245
+ (rdbg) break MyApp#handle_key
246
+ (rdbg) continue
247
+ ```
248
+ <!-- SPDX-SnippetEnd -->
249
+
250
+ ### Requirements
251
+
252
+ Add the `debug` gem to your Gemfile:
253
+
254
+ <!-- SPDX-SnippetBegin -->
255
+ <!--
256
+ SPDX-FileCopyrightText: 2026 Kerrick Long
257
+ SPDX-License-Identifier: MIT-0
258
+ -->
259
+ ```ruby
260
+ gem "debug", ">= 1.0"
261
+ ```
262
+ <!-- SPDX-SnippetEnd -->
263
+
264
+ If `RR_DEBUG=1` is set but the debug gem is missing, RatatuiRuby raises a `LoadError` with installation instructions.
265
+
266
+ ## File Logging
267
+
268
+ You can write debug output to a log file instead of stdout.
269
+
270
+ ### Basic Logging
271
+
272
+ <!-- SPDX-SnippetBegin -->
273
+ <!--
274
+ SPDX-FileCopyrightText: 2026 Kerrick Long
275
+ SPDX-License-Identifier: MIT-0
276
+ -->
277
+ ```ruby
278
+ DEBUG_LOG = File.open("debug.log", "a")
279
+
280
+ def debug(msg)
281
+ DEBUG_LOG.puts("[#{Time.now}] #{msg}")
282
+ DEBUG_LOG.flush
283
+ end
284
+ ```
285
+ <!-- SPDX-SnippetEnd -->
286
+
287
+ Then tail the log in a separate terminal.
288
+
289
+ <!-- SPDX-SnippetBegin -->
290
+ <!--
291
+ SPDX-FileCopyrightText: 2026 Kerrick Long
292
+ SPDX-License-Identifier: MIT-0
293
+ -->
294
+ ```bash
295
+ tail -f debug.log
296
+ ```
297
+ <!-- SPDX-SnippetEnd -->
298
+
299
+ ### Timestamped Logging
300
+
301
+ For high-frequency logging (like inside a render loop), use timestamped files to avoid overwrites:
302
+
303
+ <!-- SPDX-SnippetBegin -->
304
+ <!--
305
+ SPDX-FileCopyrightText: 2026 Kerrick Long
306
+ SPDX-License-Identifier: MIT-0
307
+ -->
308
+ ```ruby
309
+ FileUtils.mkdir_p(File.join(Dir.tmpdir, "my_debug"))
310
+ timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
311
+ File.write(
312
+ File.join(Dir.tmpdir, "my_debug", "#{timestamp}.log"),
313
+ "variable=#{value.inspect}\n"
314
+ )
315
+ ```
316
+ <!-- SPDX-SnippetEnd -->
317
+
318
+ Then tail the directory.
319
+
320
+ <!-- SPDX-SnippetBegin -->
321
+ <!--
322
+ SPDX-FileCopyrightText: 2026 Kerrick Long
323
+ SPDX-License-Identifier: MIT-0
324
+ -->
325
+ ```bash
326
+ watch -n 0.5 'ls -la /tmp/my_debug/ && cat /tmp/my_debug/*.log'
327
+ ```
328
+ <!-- SPDX-SnippetEnd -->
329
+
330
+ ## REPL Without the TUI
331
+
332
+ Unit tests verify correctness, but sometimes you want to poke at objects interactively. Wrap your main execution in a guard:
333
+
334
+ <!-- SPDX-SnippetBegin -->
335
+ <!--
336
+ SPDX-FileCopyrightText: 2026 Kerrick Long
337
+ SPDX-License-Identifier: MIT-0
338
+ -->
339
+ ```ruby
340
+ if __FILE__ == $PROGRAM_NAME
341
+ MyApp.new.run
342
+ end
343
+ ```
344
+ <!-- SPDX-SnippetEnd -->
345
+
346
+ Then load the file without entering raw mode.
347
+
348
+ <!-- SPDX-SnippetBegin -->
349
+ <!--
350
+ SPDX-FileCopyrightText: 2026 Kerrick Long
351
+ SPDX-License-Identifier: MIT-0
352
+ -->
353
+ ```bash
354
+ ruby -e 'load "./bin/my_tui"; obj = MyClass.new; puts obj.result'
355
+ ```
356
+ <!-- SPDX-SnippetEnd -->
357
+
358
+ This exercises domain logic without the terminal conflict. Use it for exploration. Write tests with [TestHelper](application_testing.md) for regression coverage.
359
+
360
+ ## Isolating Terminal Issues
361
+
362
+ Sometimes code works in a `ruby -e` script but fails in the TUI. Here are common causes.
363
+
364
+ 1. **Thread context.** Ruby threads share the process's terminal state.
365
+ 2. **Raw mode.** External commands fail when stdin/stdout are reconfigured.
366
+ 3. **SSH/Git auth.** Commands that prompt for credentials hang or return empty.
367
+
368
+ See [Async Operations](./async.md) for solutions.
369
+
370
+ ## Error Classes
371
+
372
+ RatatuiRuby has semantic exception classes for different failure modes:
373
+
374
+ | Class | Meaning |
375
+ |-------|---------|
376
+ | `RatatuiRuby::Error::Terminal` | I/O failure (backend crashed, terminal unavailable) |
377
+ | `RatatuiRuby::Error::Safety` | Lifetime violation (using Frame after draw block exits) |
378
+ | `RatatuiRuby::Error::Invariant` | Contract violation (double init, headless mode conflict) |
379
+
380
+ Catch these specifically instead of rescuing `StandardError` broadly.
381
+
382
+ <!-- SPDX-SnippetBegin -->
383
+ <!--
384
+ SPDX-FileCopyrightText: 2026 Kerrick Long
385
+ SPDX-License-Identifier: MIT-0
386
+ -->
387
+ ```ruby
388
+ begin
389
+ RatatuiRuby.run { |tui| ... }
390
+ rescue RatatuiRuby::Error::Terminal => e
391
+ puts "Terminal I/O failed: #{e.message}"
392
+ rescue RatatuiRuby::Error::Safety => e
393
+ puts "API misuse: #{e.message}"
394
+ end
395
+ ```
396
+ <!-- SPDX-SnippetEnd -->
397
+
398
+ ## Further Reading
399
+
400
+ - [Application Testing Guide](application_testing.md) — Test helpers, snapshots, event injection
401
+ - [RatatuiRuby::Debug](../../lib/ratatui_ruby/debug.rb) — Debug module source
@@ -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)