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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c543e8590fa79c457dac22abc8c39fc2264026079196ff7d0afc1d083c9cf013
4
- data.tar.gz: 8c044e43009f8ede9c66274809aacae1c7e66a503157afc6756c4890b50d899c
3
+ metadata.gz: 85b40cc89f8710bfc5ef356cbec12d29ed27d86bc900ad1f1a52795e95a0fcf0
4
+ data.tar.gz: e13aad32fdc6a644cb8b608279af690a0046a0c130e04d40e5f6ca21cfd8eaff
5
5
  SHA512:
6
- metadata.gz: dfd4c34a23e0af4dcb47c9520bae94db94cb7587f37b11662668e8f6f7e7a3148aeb0fb66ed66cb47f51898fc42da496ce50cec46ea1b286c3bcfcbe5dc43e41
7
- data.tar.gz: 19871c4b3ec78afc30fc004f019b6b0ff246654ffbbe6dafb7e92cd5d1b3c4b1294ed3e836c47b70fb094dad3f5aa7927dd2b23a6cf458d571f5248fa5b0b934
6
+ metadata.gz: 82955407e8f3df33109527392bc82168b0527b3325c799beffeb13551bf42926b582dcbe1798abc5ed47c7c645846a6b64669c8b83de75e85f8b142c7f14b43f
7
+ data.tar.gz: d48b7871e77d537c2fb22d8b49b616f570d6f33da024b904c81aed8e5e471d59c332d42fcf060a21ba22c6b832bc2465cda8d8e9774455ff44b3e9440927b9dc
data/.builds/ruby-3.2.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.9.1.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.10.0.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/.builds/ruby-3.3.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.9.1.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.10.0.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/.builds/ruby-3.4.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.9.1.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.10.0.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.9.1.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-0.10.0.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/AGENTS.md CHANGED
@@ -136,9 +136,10 @@ The project follows a standard Gem layout with an `ext/` directory for Rust code
136
136
 
137
137
  Before considering a task complete and returning control to the user, you **MUST** ensure:
138
138
 
139
+ 0. **Production Ready:** RBS types are complete and accurate (no `untyped`), errors are handled with good DX, documentation follows guidelines, high code quality (no "pre-existing debt" excuses).
139
140
  1. **Default Rake Task Passes:** Run `bin/agent_rake` (no args). Confirm it passes with ZERO errors **or warnings**.
140
141
  - You will save time if you run `bin/agent_rake rubocop:autocorrect` first.
141
- - If you think the build is looking for deleted files, it is not. Instead, explain to the user why staging is needed and use the `run_command` tool with `git add -A` so they get a Run button with context.
142
+ - If you think the rake is looking for deleted files, STOP EVERYTHING and tell the user.
142
143
  2. **Documentation Updated:** If public APIs or observable behavior changed, update relevant RDoc, rustdoc, `doc/` files, `README.md`, and/or `ratatui_ruby-wiki` files.
143
144
  3. **Changelog Updated:** If public APIs, observable behavior, or gemspec dependencies have changed, update [CHANGELOG.md](CHANGELOG.md)'s **Unreleased** section.
144
145
  4. **Commit Message Suggested:** You **MUST** ensure the final message to the user includes a suggested commit message block. This is NOT optional.
data/CHANGELOG.md CHANGED
@@ -18,6 +18,103 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
18
18
 
19
19
  ### Removed
20
20
 
21
+ ## [0.10.0] - 2026-01-10
22
+
23
+ ### Added
24
+
25
+ - **Table Integer Width Shorthand**: `Table` `widths:` parameter now accepts plain integers as shorthand for `Constraint.length(n)`. This enables cleaner table definitions like `widths: [40, 16, 10]` instead of verbose constraint arrays. Constraints and integers can be mixed freely.
26
+ - **Error Message Context**: Type errors from the Rust backend now include the `inspect` string of the value that caused the error, making debugging significantly easier. For example, "expected array for rows" now shows "expected array for rows, got {title: \"Processes\", ...}".
27
+ - **Steep Type Checking**: Integrated Steep static type analyzer with a new `rake steep` task. The Steepfile covers `lib/` with comprehensive RBS type definitions for all widgets, layout primitives, events, and interfaces.
28
+ - **RBS Type Definitions**: Added 50+ RBS signature files in `sig/ratatui_ruby/` covering all widget classes, core interfaces (`_RectLike`, `_ToS`), type aliases (`style_input`, `widget`), and a `Data.define` patch for `super()` call compatibility.
29
+ - **Duck Typing Documentation**: New `test/test_duck_typing.rb` documents that `Layout.split` accepts any object responding to `x`, `y`, `width`, `height` (Struct, Data.define, custom classes), not just `Rect`.
30
+ - **Debug Mode**: New `RatatuiRuby::Debug` module controls Rust backtrace visibility for easier debugging. Activate via:
31
+ - `RUST_BACKTRACE=1` — Rust backtraces only
32
+ - `RR_DEBUG=1` — full debug mode (backtraces + future Ruby-side features)
33
+ - `include RatatuiRuby::TestHelper` — auto-enables debug mode in tests
34
+ - `RatatuiRuby.debug_mode!` — programmatic activation
35
+ - **Debug.test_panic!**: New method that intentionally triggers a Rust panic, allowing developers to verify their backtrace setup is working correctly before encountering a real bug.
36
+ - **Deferred Panic Backtraces**: Rust backtraces during TUI sessions are now stored and printed after terminal restoration, preventing output from being lost on the alternate screen. Previously, panic output in raw terminal mode was invisible.
37
+ - **Remote Debugging**: Debug mode now integrates with Ruby's `debug` gem for remote debugging. `RR_DEBUG=1` stops at startup and waits for debugger attachment. `RatatuiRuby.debug_mode!` continues running in nonstop mode. Attach from another terminal with `rdbg --attach`.
38
+ - **Debug.suppress_debug_mode**: New block method temporarily suppresses Ruby-side debug checks within its block. Rust backtraces remain enabled. Useful for testing production behavior in debug mode environments.
39
+ - **DWIM Hash Coercion**: All widget factory methods now accept both `tui.table(hash)` and `tui.table(**hash)` calling styles. When a bare Hash is passed as the first positional argument, it is automatically splatted into keyword arguments. Unknown keys are silently ignored in production mode; in debug mode (`RR_DEBUG=1`), they raise `ArgumentError` for early typo detection.
40
+ - **Ratatui-Aligned Text Methods**: New methods on `Text::Span` and `Text::Line` matching Ratatui's API for style manipulation:
41
+ - `Span#width` — display width in terminal cells (unicode-aware)
42
+ - `Span.raw(content)` — factory for unstyled spans
43
+ - `Span#patch_style(style)` — merge style onto existing style
44
+ - `Span#reset_style` — clear all styling
45
+ - `Line#left_aligned`, `Line#centered`, `Line#right_aligned` — fluent alignment setters
46
+ - `Line#push_span(span)` — append span (returns new Line, immutable)
47
+ - `Line#patch_style(style)`, `Line#reset_style` — style manipulation for all spans
48
+ - **List Query Methods**: New methods on `List` matching Ratatui's API:
49
+ - `List#len` — number of items (with Ruby aliases `length`, `size`)
50
+ - **TableState Navigation Methods**: New methods on `TableState` matching Ratatui's API for column navigation:
51
+ - `selected_cell` — returns `[row, column]` tuple when both are selected
52
+ - `with_selected_cell(cell)` — constructor to create state with both row and column selected
53
+ - `select_next_column` — select the next column (or first if none selected)
54
+ - `select_previous_column` — select the previous column (saturates at 0)
55
+ - `select_first_column` — select column 0
56
+ - `select_last_column` — select the last column (clamped during rendering)
57
+ - **Buffer Query Methods**: New module methods on `Buffer` matching Ratatui's API for buffer inspection:
58
+ - `Buffer.content` — returns all cells as an array
59
+ - `Buffer.get(x, y)` — returns the Cell at the specified position
60
+ - `Buffer.index_of(x, y)` — converts position to linear buffer index
61
+ - `Buffer.pos_of(index)` — converts linear index to position coordinates
62
+ - **Rect Conversion Methods**: New methods on `Rect` for extracting geometry components:
63
+ - `Rect#as_position` — returns a `Position` object containing x and y coordinates
64
+ - `Rect#as_size` — returns a `Size` object containing width and height
65
+ - **Position and Size Classes**: New layout primitives matching Ratatui's API:
66
+ - `Layout::Position` — represents terminal coordinates (x, y)
67
+ - `Layout::Size` — represents terminal dimensions (width, height)
68
+ - **Constraint#apply**: Computes the constrained size for a given available space. For example, `Constraint.percentage(50).apply(100)` returns `50`. Also aliased as `call` for proc-like invocation (`constraint.(100)`).
69
+ - **Color Module**: New `Style::Color` module with constructors matching Ratatui's API:
70
+ - `Color.from_u32(0xRRGGBB)` — creates a color from a hex integer (aliased as `Color.hex`)
71
+ - `Color.from_hsl(h, s, l)` — creates a color from HSL values (aliased as `Color.hsl`)
72
+ - **Ruby-Idiomatic Aliases**: All new APIs include shorter, more Ruby-ish aliases following TIMTOWTDI:
73
+ - `Rect#position` (alias for `as_position`), `Rect#size` (alias for `as_size`)
74
+ - `Buffer[x, y]` (alias for `Buffer.get`)
75
+ - `Constraint#call` (alias for `apply`, enables `constraint.(n)` syntax)
76
+ - **Layout Margin and Spacing**: `Layout` now supports `margin:` and `spacing:` parameters for edge insets and gaps between segments, matching Ratatui's Layout API.
77
+ - **Layout.split_with_spacers**: New class method returns both content segments and spacer Rects, enabling custom rendering of dividers or separators between layout sections.
78
+ - **Canvas#get_point**: Converts canvas coordinates to normalized [0.0, 1.0] grid coordinates for hit testing. Returns `nil` for out-of-bounds coordinates. Also aliased as `point` and `[]` for Ruby-idiomatic access.
79
+ - **Row#enable_strikethrough**: Returns a new Row with `:crossed_out` modifier for indicating cancelled or deleted items. Also aliased as `strikethrough`. Note: Strikethrough (SGR 9) is not supported by all terminals; macOS Terminal.app notably lacks support while Kitty, iTerm2, Alacritty, and WezTerm render it correctly.
80
+ - **Rect Geometry Methods**: New methods on `Rect` for geometry manipulation:
81
+ - `Rect#outer(margin)` — expands a rectangle by a margin (inverse of `inner`)
82
+ - `Rect#resize(size)` — changes dimensions while preserving top-left position
83
+ - `Rect#centered_horizontally(constraint)` — centers horizontally using Layout
84
+ - `Rect#centered_vertically(constraint)` — centers vertically using Layout
85
+ - `Rect#centered(h, v)` — centers on both axes
86
+ - **TUI Shape Aliases (DWIM)**: Canvas shape factories now have terse and bidirectional aliases:
87
+ - Terse: `circle()`, `point()`, `map()`, `label()` (shorter forms of `shape_*`)
88
+ - Bidirectional: `circle_shape()`, `point_shape()`, `rectangle_shape()`, `map_shape()`, `label_shape()` (same as `shape_*`)
89
+ - Note: Terse `rectangle` is intentionally excluded to avoid confusion with `Layout::Rect`; use `shape_rectangle()` or `rectangle_shape()`.
90
+ - **TUI `item` Alias**: `tui.item(...)` is now an alias for `tui.list_item(...)`, providing a terser API when building lists.
91
+ - **Gauge and LineGauge `percent`**: Both widgets now have a `percent` reader that returns the ratio as an integer percentage (0-100). `LineGauge` also now accepts a `percent:` constructor parameter matching `Gauge`.
92
+ - **List#selected_item**: Returns the item at the selected index (or nil if nothing is selected).
93
+ - **Style `underline_color`**: New optional parameter for `Style` that sets a distinct underline color independent of the foreground color. Useful for styling like "white text with red underline". Terminals must support the underline color extension (SGR 58).
94
+ - **Style `remove_modifiers`**: New optional parameter for `Style` that explicitly removes modifiers when styles are patched/inherited. Corresponds to Ratatui's `sub_modifier` field. Use it to prevent inherited bold, italic, or other modifiers from propagating.
95
+ - **Symbols::Shade Constants**: New `RatatuiRuby::Symbols::Shade` module exposes Ratatui's shade block characters as named constants: `EMPTY` (" "), `LIGHT` ("░"), `MEDIUM` ("▒"), `DARK` ("▓"), `FULL` ("█"). Use them for gradients, density fills, or custom progress indicators.
96
+ - **Symbols::Line Constants and Sets**: New `RatatuiRuby::Symbols::Line` module exposes Ratatui's box-drawing characters with 36 individual constants (VERTICAL, HORIZONTAL, corners, T-junctions, cross) and 4 predefined sets: `NORMAL` (standard corners), `ROUNDED` (rounded corners), `DOUBLE` (double-line), `THICK` (heavy lines). Use them for custom borders or drawing.
97
+ - **Symbols::Bar Constants and Sets**: New `RatatuiRuby::Symbols::Bar` module exposes Ratatui's vertical bar characters (lower blocks) with 8 individual constants and 2 predefined sets: `NINE_LEVELS` (full resolution) and `THREE_LEVELS` (simplified). Used by Sparkline widget.
98
+ - **Symbols::Block Constants and Sets**: New `RatatuiRuby::Symbols::Block` module exposes Ratatui's horizontal block characters (left blocks) with 8 individual constants and 2 predefined sets: `NINE_LEVELS` (full resolution) and `THREE_LEVELS` (simplified). Used by Gauge widget.
99
+ - **Symbols::Scrollbar Sets**: New `RatatuiRuby::Symbols::Scrollbar` module exposes 4 predefined scrollbar symbol sets: `VERTICAL`, `DOUBLE_VERTICAL`, `HORIZONTAL`, `DOUBLE_HORIZONTAL`. Each set contains `track`, `thumb`, `begin_char`, and `end_char` symbols.
100
+ - **Cell `underline_color`**: `Buffer::Cell` and top-level `Cell` now expose `underline_color` attribute for introspecting styled underline colors during testing.
101
+
102
+ ### Changed
103
+
104
+ - **`tui.draw` Argument Validation (Breaking)**: `tui.draw` now validates its arguments. Calling `draw` with neither a tree nor a block raises `ArgumentError`, as does calling it with both. Previously this produced undefined behavior.
105
+ - **`Layout.split` Stricter Type Checking (Breaking)**: `Layout.split` now rejects invalid `area` arguments with a clear `ArgumentError` instead of silently misbehaving. Objects must be a `Rect`, `Hash` with `:x/:y/:width/:height` keys, or respond to all four geometry methods.
106
+ - **Schema Directory Removed (Breaking)**: The legacy `lib/ratatui_ruby/schema/` directory has been removed. All widget classes now live exclusively in their proper namespaces (`Widgets::`, `Layout::`, `Text::`, etc.). Direct usage like `RatatuiRuby::Paragraph` (without `Widgets::`) is no longer supported. Use `RatatuiRuby::Widgets::Paragraph` or the TUI facade (`tui.paragraph`).
107
+ - **Buffer::Cell Modifiers (Breaking)**: `Buffer::Cell#modifiers` and `Cell#modifiers` now return an array of `Symbol`s (e.g., `[:bold, :italic]`) instead of `String`s. This unifies the API, as `Style` modifiers were already symbols. Code expecting strings (e.g. `cell.modifiers.include?("bold")`) must be updated to use symbols. This changes behavior established in v0.9.1.
108
+
109
+ ### Fixed
110
+
111
+ - **Tabs Padding Coercion**: `Tabs` `padding_left` and `padding_right` now correctly coerce duck-typed integer values (objects responding to `to_int`/`to_i`) via `Integer()`. Previously these parameters were passed through without coercion, inconsistent with other integer parameters. `Text::Line` values are still accepted as-is for styled padding.
112
+
113
+ ### Removed
114
+
115
+ - **NullIO Re-export (Breaking)**: Removed `RatatuiRuby::NullIO` constant. Use `RatatuiRuby::OutputGuard::NullIO` if you were relying on this internal class.
116
+ - **Legacy Media Keys (Breaking)**: Removed support for legacy unprefixed media keys (e.g. `play`, `stop`) in event parsing. You must now use the canonical `media_`-prefixed keys (e.g. `media_play`, `media_stop`). The `play?`/`stop?` predicates still work for both, checking the correct canonical codes.
117
+
21
118
  ## [0.9.1] - 2026-01-08
22
119
 
23
120
  ### Added
@@ -515,6 +612,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
515
612
  - **Testing Support**: Included `RatatuiRuby::TestHelper` and RSpec integration to make testing your TUI applications possible.
516
613
 
517
614
  [Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/HEAD
615
+ [0.10.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.10.0
518
616
  [0.9.1]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.9.1
519
617
  [0.9.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.9.0
520
618
  [0.8.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.8.0
data/REUSE.toml CHANGED
@@ -16,6 +16,11 @@ path = 'doc/images/*.png'
16
16
  SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
17
17
  SPDX-License-Identifier = "CC-BY-SA-4.0"
18
18
 
19
+ [[annotations]]
20
+ path = 'doc/images/*.gif'
21
+ SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
22
+ SPDX-License-Identifier = "CC-BY-SA-4.0"
23
+
19
24
  [[annotations]]
20
25
  path = 'test/fixtures/*.txt'
21
26
  SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
data/Rakefile CHANGED
@@ -8,4 +8,4 @@ require "bundler/gem_tasks"
8
8
  # Import all tasks from the tasks/ directory
9
9
  Dir.glob("tasks/*.rake").each { |r| import r }
10
10
 
11
- task default: %w[lint:fix sourcehut test lint license:new]
11
+ task default: %w[lint:fix sourcehut test lint license:new steep]
data/Steepfile ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ target :lib do
7
+ signature "sig"
8
+ check "lib"
9
+
10
+ # Legacy schema/ files pending migration - exclude from checking
11
+ # Only schema/text.rb and schema/draw.rb are loaded by the main gem
12
+ # See doc/contributors/v1.0.0_blockers.md
13
+ ignore "lib/ratatui_ruby/schema/bar_chart.rb"
14
+ ignore "lib/ratatui_ruby/schema/bar_chart/"
15
+ ignore "lib/ratatui_ruby/schema/block.rb"
16
+ ignore "lib/ratatui_ruby/schema/calendar.rb"
17
+ ignore "lib/ratatui_ruby/schema/canvas.rb"
18
+ ignore "lib/ratatui_ruby/schema/center.rb"
19
+ ignore "lib/ratatui_ruby/schema/chart.rb"
20
+ ignore "lib/ratatui_ruby/schema/clear.rb"
21
+ ignore "lib/ratatui_ruby/schema/constraint.rb"
22
+ ignore "lib/ratatui_ruby/schema/cursor.rb"
23
+ ignore "lib/ratatui_ruby/schema/gauge.rb"
24
+ ignore "lib/ratatui_ruby/schema/layout.rb"
25
+ ignore "lib/ratatui_ruby/schema/line_gauge.rb"
26
+ ignore "lib/ratatui_ruby/schema/list.rb"
27
+ ignore "lib/ratatui_ruby/schema/list_item.rb"
28
+ ignore "lib/ratatui_ruby/schema/overlay.rb"
29
+ ignore "lib/ratatui_ruby/schema/paragraph.rb"
30
+ ignore "lib/ratatui_ruby/schema/ratatui_logo.rb"
31
+ ignore "lib/ratatui_ruby/schema/ratatui_mascot.rb"
32
+ ignore "lib/ratatui_ruby/schema/rect.rb"
33
+ ignore "lib/ratatui_ruby/schema/row.rb"
34
+ ignore "lib/ratatui_ruby/schema/scrollbar.rb"
35
+ ignore "lib/ratatui_ruby/schema/shape/"
36
+ ignore "lib/ratatui_ruby/schema/sparkline.rb"
37
+ ignore "lib/ratatui_ruby/schema/style.rb"
38
+ ignore "lib/ratatui_ruby/schema/table.rb"
39
+ ignore "lib/ratatui_ruby/schema/tabs.rb"
40
+
41
+ # ClassMethods mixin pattern cannot be typed in RBS
42
+ # (self in ClassMethods refers to the including Class)
43
+ ignore "lib/ratatui_ruby/widgets/coerceable_widget.rb"
44
+
45
+ library "pathname"
46
+ library "fileutils"
47
+ library "minitest"
48
+ library "date"
49
+ end
@@ -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