ratatui_ruby 0.5.0 → 0.7.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 (311) 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 +10 -4
  7. data/CHANGELOG.md +79 -7
  8. data/README.md +37 -5
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +96 -22
  11. data/doc/application_testing.md +76 -30
  12. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  13. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  14. data/doc/contributors/architectural_overhaul/task.md +37 -0
  15. data/doc/contributors/design/ruby_frontend.md +288 -56
  16. data/doc/contributors/design/rust_backend.md +349 -54
  17. data/doc/contributors/developing_examples.md +134 -49
  18. data/doc/contributors/index.md +7 -5
  19. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  20. data/doc/event_handling.md +11 -3
  21. data/doc/images/app_all_events.png +0 -0
  22. data/doc/images/app_color_picker.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_stateful_interaction.png +0 -0
  25. data/doc/images/verify_quickstart_dsl.png +0 -0
  26. data/doc/images/verify_quickstart_layout.png +0 -0
  27. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  28. data/doc/images/verify_readme_usage.png +0 -0
  29. data/doc/images/widget_barchart_demo.png +0 -0
  30. data/doc/images/widget_block_demo.png +0 -0
  31. data/doc/images/widget_canvas_demo.png +0 -0
  32. data/doc/images/widget_cell_demo.png +0 -0
  33. data/doc/images/widget_center_demo.png +0 -0
  34. data/doc/images/widget_chart_demo.png +0 -0
  35. data/doc/images/widget_list_demo.png +0 -0
  36. data/doc/images/widget_overlay_demo.png +0 -0
  37. data/doc/images/widget_render.png +0 -0
  38. data/doc/images/widget_rich_text.png +0 -0
  39. data/doc/images/widget_scroll_text.png +0 -0
  40. data/doc/images/widget_sparkline_demo.png +0 -0
  41. data/doc/images/widget_table_demo.png +0 -0
  42. data/doc/images/widget_tabs_demo.png +0 -0
  43. data/doc/images/widget_text_width.png +0 -0
  44. data/doc/index.md +11 -6
  45. data/doc/interactive_design.md +2 -2
  46. data/doc/quickstart.md +127 -165
  47. data/doc/terminal_limitations.md +92 -0
  48. data/doc/v0.7.0_migration.md +236 -0
  49. data/doc/why.md +93 -0
  50. data/examples/app_all_events/README.md +47 -27
  51. data/examples/app_all_events/app.rb +38 -35
  52. data/examples/app_all_events/model/app_model.rb +157 -0
  53. data/examples/app_all_events/model/event_entry.rb +17 -0
  54. data/examples/app_all_events/model/msg.rb +37 -0
  55. data/examples/app_all_events/update.rb +73 -0
  56. data/examples/app_all_events/view/app_view.rb +9 -9
  57. data/examples/app_all_events/view/controls_view.rb +9 -7
  58. data/examples/app_all_events/view/counts_view.rb +13 -9
  59. data/examples/app_all_events/view/live_view.rb +9 -8
  60. data/examples/app_all_events/view/log_view.rb +11 -16
  61. data/examples/app_color_picker/README.md +84 -42
  62. data/examples/app_color_picker/app.rb +24 -62
  63. data/examples/app_color_picker/controls.rb +90 -0
  64. data/examples/app_color_picker/copy_dialog.rb +45 -49
  65. data/examples/app_color_picker/export_pane.rb +126 -0
  66. data/examples/app_color_picker/input.rb +99 -67
  67. data/examples/app_color_picker/main_container.rb +178 -0
  68. data/examples/app_color_picker/palette.rb +55 -26
  69. data/examples/app_login_form/README.md +49 -0
  70. data/examples/app_login_form/app.rb +2 -3
  71. data/examples/app_stateful_interaction/README.md +33 -0
  72. data/examples/app_stateful_interaction/app.rb +272 -0
  73. data/examples/timeout_demo.rb +43 -0
  74. data/examples/verify_quickstart_dsl/README.md +49 -0
  75. data/examples/verify_quickstart_dsl/app.rb +2 -0
  76. data/examples/verify_quickstart_layout/README.md +71 -0
  77. data/examples/verify_quickstart_layout/app.rb +2 -0
  78. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  79. data/examples/verify_quickstart_lifecycle/app.rb +10 -4
  80. data/examples/verify_readme_usage/README.md +43 -0
  81. data/examples/verify_readme_usage/app.rb +8 -2
  82. data/examples/widget_barchart_demo/README.md +50 -0
  83. data/examples/widget_barchart_demo/app.rb +5 -5
  84. data/examples/widget_block_demo/README.md +36 -0
  85. data/examples/widget_block_demo/app.rb +256 -0
  86. data/examples/widget_box_demo/README.md +45 -0
  87. data/examples/widget_calendar_demo/README.md +39 -0
  88. data/examples/widget_calendar_demo/app.rb +5 -1
  89. data/examples/widget_canvas_demo/README.md +27 -0
  90. data/examples/widget_canvas_demo/app.rb +123 -0
  91. data/examples/widget_cell_demo/README.md +36 -0
  92. data/examples/widget_cell_demo/app.rb +31 -24
  93. data/examples/widget_center_demo/README.md +29 -0
  94. data/examples/widget_center_demo/app.rb +116 -0
  95. data/examples/widget_chart_demo/README.md +41 -0
  96. data/examples/widget_chart_demo/app.rb +7 -2
  97. data/examples/widget_gauge_demo/README.md +41 -0
  98. data/examples/widget_layout_split/README.md +44 -0
  99. data/examples/widget_line_gauge_demo/README.md +41 -0
  100. data/examples/widget_list_demo/README.md +49 -0
  101. data/examples/widget_list_demo/app.rb +91 -107
  102. data/examples/widget_map_demo/README.md +39 -0
  103. data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
  104. data/examples/widget_overlay_demo/README.md +36 -0
  105. data/examples/widget_overlay_demo/app.rb +248 -0
  106. data/examples/widget_popup_demo/README.md +36 -0
  107. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  108. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  109. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  110. data/examples/widget_rect/README.md +38 -0
  111. data/examples/widget_render/README.md +37 -0
  112. data/examples/widget_render/app.rb +3 -3
  113. data/examples/widget_rich_text/README.md +35 -0
  114. data/examples/widget_rich_text/app.rb +62 -33
  115. data/examples/widget_scroll_text/README.md +37 -0
  116. data/examples/widget_scroll_text/app.rb +0 -1
  117. data/examples/widget_scrollbar_demo/README.md +37 -0
  118. data/examples/widget_sparkline_demo/README.md +42 -0
  119. data/examples/widget_sparkline_demo/app.rb +4 -3
  120. data/examples/widget_style_colors/README.md +34 -0
  121. data/examples/widget_table_demo/README.md +48 -0
  122. data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
  123. data/examples/widget_tabs_demo/README.md +41 -0
  124. data/examples/widget_tabs_demo/app.rb +15 -1
  125. data/examples/widget_text_width/README.md +35 -0
  126. data/examples/widget_text_width/app.rb +113 -0
  127. data/exe/.gitkeep +0 -0
  128. data/ext/ratatui_ruby/Cargo.lock +11 -4
  129. data/ext/ratatui_ruby/Cargo.toml +2 -1
  130. data/ext/ratatui_ruby/src/events.rs +238 -26
  131. data/ext/ratatui_ruby/src/frame.rs +116 -3
  132. data/ext/ratatui_ruby/src/lib.rs +37 -6
  133. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  134. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  135. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  136. data/ext/ratatui_ruby/src/text.rs +13 -4
  137. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  138. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  139. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  140. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  141. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  142. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  143. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  144. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  145. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  146. data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
  147. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  148. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  149. data/lib/ratatui_ruby/buffer.rb +15 -0
  150. data/lib/ratatui_ruby/cell.rb +4 -4
  151. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  152. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  153. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  154. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  155. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  156. data/lib/ratatui_ruby/event/key.rb +111 -51
  157. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  158. data/lib/ratatui_ruby/event/paste.rb +1 -1
  159. data/lib/ratatui_ruby/frame.rb +100 -4
  160. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  161. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  162. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  163. data/lib/ratatui_ruby/layout.rb +19 -0
  164. data/lib/ratatui_ruby/list_state.rb +88 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  166. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  167. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  168. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  169. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  170. data/lib/ratatui_ruby/schema/list.rb +25 -4
  171. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  172. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  173. data/lib/ratatui_ruby/schema/row.rb +66 -0
  174. data/lib/ratatui_ruby/schema/style.rb +24 -4
  175. data/lib/ratatui_ruby/schema/table.rb +29 -11
  176. data/lib/ratatui_ruby/schema/text.rb +96 -3
  177. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  178. data/lib/ratatui_ruby/style/style.rb +81 -0
  179. data/lib/ratatui_ruby/style.rb +15 -0
  180. data/lib/ratatui_ruby/table_state.rb +90 -0
  181. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  182. data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
  183. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  184. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  185. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  186. data/lib/ratatui_ruby/test_helper.rb +65 -358
  187. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  188. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  189. data/lib/ratatui_ruby/tui/core.rb +38 -0
  190. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  191. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  192. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  193. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  194. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  195. data/lib/ratatui_ruby/tui.rb +75 -0
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  198. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  199. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  200. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  201. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  202. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  203. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  204. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  205. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  206. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  207. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  208. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  209. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  210. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  211. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  212. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  213. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  214. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  215. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  216. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  217. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  218. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  219. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  220. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  221. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  222. data/lib/ratatui_ruby/widgets.rb +40 -0
  223. data/lib/ratatui_ruby.rb +64 -57
  224. data/sig/examples/app_all_events/view.rbs +1 -1
  225. data/sig/examples/app_all_events/view_state.rbs +1 -1
  226. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  227. data/sig/examples/widget_block_demo/app.rbs +32 -0
  228. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  229. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  230. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  231. data/sig/ratatui_ruby/event.rbs +11 -1
  232. data/sig/ratatui_ruby/frame.rbs +2 -0
  233. data/sig/ratatui_ruby/list_state.rbs +13 -0
  234. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  235. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  236. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  237. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  238. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  239. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  240. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  241. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  242. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  243. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  244. data/sig/ratatui_ruby/schema/text.rbs +9 -6
  245. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  246. data/sig/ratatui_ruby/session.rbs +41 -48
  247. data/sig/ratatui_ruby/table_state.rbs +15 -0
  248. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  249. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  250. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  251. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  252. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  253. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  254. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  255. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  256. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  257. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  258. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  259. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  260. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  261. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  262. data/sig/ratatui_ruby/tui.rbs +19 -0
  263. data/tasks/autodoc/examples.rb +79 -0
  264. data/tasks/autodoc.rake +7 -35
  265. data/tasks/bump/changelog.rb +3 -3
  266. data/tasks/bump/links.rb +67 -0
  267. data/tasks/sourcehut.rake +64 -21
  268. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  269. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  270. metadata +169 -48
  271. data/doc/contributors/dwim_dx.md +0 -366
  272. data/doc/images/app_analytics.png +0 -0
  273. data/doc/images/app_custom_widget.png +0 -0
  274. data/doc/images/app_mouse_events.png +0 -0
  275. data/doc/images/app_table_select.png +0 -0
  276. data/doc/images/widget_block_padding.png +0 -0
  277. data/doc/images/widget_block_titles.png +0 -0
  278. data/doc/images/widget_list_styles.png +0 -0
  279. data/doc/images/widget_table_flex.png +0 -0
  280. data/examples/app_all_events/model/events.rb +0 -180
  281. data/examples/app_all_events/model/highlight.rb +0 -57
  282. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  283. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  284. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  285. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  286. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  287. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  288. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  289. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  290. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  291. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  292. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  293. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  294. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  295. data/examples/app_all_events/view_state.rb +0 -42
  296. data/examples/app_color_picker/scene.rb +0 -201
  297. data/examples/widget_block_padding/app.rb +0 -67
  298. data/examples/widget_block_titles/app.rb +0 -69
  299. data/examples/widget_list_styles/app.rb +0 -141
  300. data/examples/widget_table_flex/app.rb +0 -95
  301. data/lib/ratatui_ruby/session/autodoc.rb +0 -417
  302. data/lib/ratatui_ruby/session.rb +0 -163
  303. data/sig/examples/widget_block_padding/app.rbs +0 -11
  304. data/sig/examples/widget_block_titles/app.rbs +0 -11
  305. data/sig/examples/widget_list_styles/app.rbs +0 -11
  306. data/tasks/autodoc/inventory.rb +0 -61
  307. data/tasks/autodoc/notice.rb +0 -26
  308. data/tasks/autodoc/rbs.rb +0 -38
  309. data/tasks/autodoc/rdoc.rb +0 -45
  310. data/tasks/bump/comparison_links.rb +0 -41
  311. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -0,0 +1,60 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ # Table Rich Text, Row, Cell, and Line#width
8
+
9
+ ## User Review Required
10
+
11
+ > [!IMPORTANT]
12
+ > **Namespace Decision:** Following existing patterns (`Text::Span`, `BarChart::Bar`), I recommend:
13
+ > - `RatatuiRuby::Table::Cell` — table cell construction (content + style)
14
+ > - `RatatuiRuby::Table::Row` — row construction (cells + style + height)
15
+ > - `RatatuiRuby::Buffer::Cell` — buffer inspection (renamed from current `Cell`)
16
+ >
17
+ > This groups related concepts and mirrors how Ratatui separates `widgets::Cell` from `buffer::Cell`.
18
+
19
+ ### Breaking Changes
20
+
21
+ 1. `RatatuiRuby::Cell` → `RatatuiRuby::Buffer::Cell` (buffer inspection)
22
+ 2. `RatatuiRuby::Row` → `RatatuiRuby::Table::Row` (if we move it)
23
+
24
+ ---
25
+
26
+ ## Proposed Structure
27
+
28
+ ```
29
+ RatatuiRuby::
30
+ ├── Table # Table widget
31
+ │ ├── Cell # NEW: content + style for cells
32
+ │ └── Row # MOVE: cells + style + height
33
+ ├── Buffer::
34
+ │ └── Cell # RENAME: from RatatuiRuby::Cell
35
+ ├── Text::
36
+ │ ├── Span # existing
37
+ │ └── Line # existing (now with #width)
38
+ └── ... other widgets
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Changes Summary
44
+
45
+ | Feature | Status |
46
+ |---------|--------|
47
+ | Table cells accept Text::Span/Line | ✅ Done |
48
+ | Row class with style/height | ✅ Done (needs move to Table::Row) |
49
+ | Line#width method | ✅ Done |
50
+ | Table::Cell class | ⏳ Pending approval |
51
+ | Buffer::Cell rename | ⏳ Pending approval |
52
+
53
+ ---
54
+
55
+ ## Verification
56
+
57
+ ```bash
58
+ bin/agent_rake
59
+ ```
60
+
@@ -0,0 +1,37 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ # Architectural Overhaul: Strict Modularization
8
+
9
+ ## Phase 1: The Great Rename
10
+ - [x] Create `lib/ratatui_ruby/layout/` module with Rect, Constraint, Layout
11
+ - [x] Create `lib/ratatui_ruby/widgets/` module with Table, List, Paragraph, Block, etc.
12
+ - [x] Create `lib/ratatui_ruby/style/` module with Style
13
+ - [x] Create `lib/ratatui_ruby/buffer/` module with Cell (renamed from current)
14
+ - [x] Update `lib/ratatui_ruby.rb` requires
15
+ - [x] Update Rust backend for new class names in rendering.rs
16
+ - [x] Update all tests for new namespaces (0 errors, from 471)
17
+ - [x] Fix RuboCop issues
18
+
19
+ ## Phase 2: Session Hardening
20
+ - [x] Rewrite Session with explicit factory methods (no metaprogramming)
21
+ - [x] Add RDoc to each factory method
22
+ - [x] Ensure IDE autocomplete works
23
+
24
+ ## Phase 3: Table Enhancements
25
+ - [x] Implement `Widgets::Cell` (content + style)
26
+ - [x] Move Row to `Widgets::Row`
27
+ - [x] Add `table_row` and `table_cell` helpers to Session
28
+ - [x] Update table.rs for new types
29
+
30
+ ## Phase 4: Fix Examples and Documentation
31
+ - [x] Update all examples, RDoc, and *.md to use new namespaces and TUI API
32
+ - [x] Update CHANGELOG with migration guide
33
+
34
+ ## Definition of Done
35
+ - [x] `bin/agent_rake` passes
36
+ - [x] CHANGELOG updated with breaking changes
37
+
@@ -5,100 +5,332 @@
5
5
 
6
6
  # Ruby Frontend Design (`ratatui_ruby`)
7
7
 
8
- This document describes the design philosophy and structure of the Ruby layer in `ratatui_ruby`.
8
+ This document describes the architectural design and guiding principles of the Ruby layer in `ratatui_ruby`. It is intended for contributors, architects, and AI agents working on the codebase.
9
9
 
10
- ## Core Philosophy: Data-Driven UI
10
+ ## Guiding Design Principles
11
11
 
12
+ ### 1. Ratatui Alignment
12
13
 
14
+ The Ruby namespace structure mirrors Ratatui's Rust module hierarchy exactly. This is a deliberate architectural choice with specific benefits:
13
15
 
14
- The Ruby frontend is designed as a **thin, declarative layer** over the Rust backend. It uses an **Immediate Mode** paradigm where the user constructs a tree of pure data objects every frame to represent the desired UI state.
16
+ - **Documentation Mapping**: A contributor reading Ratatui's docs for `ratatui::widgets::Table` immediately knows to look at `RatatuiRuby::Widgets::Table`.
17
+ - **Predictability**: No mental translation required between Rust and Ruby codebases.
18
+ - **Scalability**: As Ratatui adds new types, the Ruby placement is deterministic.
15
19
 
16
- ### 1. View Tree as Data
20
+ **Module Mapping:**
17
21
 
18
- Unlike traditional OO GUI toolkits (like Qt or Swing) where widgets are retained objects with internal state, `ratatui_ruby` widgets are immutable value objects.
22
+ | Rust Module | Ruby Module | Purpose |
23
+ |-------------|-------------|---------|
24
+ | `ratatui::layout` | `RatatuiRuby::Layout` | Rect, Constraint, Layout |
25
+ | `ratatui::widgets` | `RatatuiRuby::Widgets` | All widgets (Table, List, Paragraph, Block, etc.) |
26
+ | `ratatui::style` | `RatatuiRuby::Style` | Style, Color |
27
+ | `ratatui::text` | `RatatuiRuby::Text` | Span, Line |
28
+ | `ratatui::buffer` | `RatatuiRuby::Buffer` | Cell (for buffer inspection) |
19
29
 
20
- * Implemented using Ruby 3.2+ `Data` classes.
21
- * Located in `lib/ratatui_ruby/schema/`.
22
- * These objects act as a Schema or Interface Definition Language (IDL) between Ruby and Rust.
30
+ This structure resolves name collisions that would otherwise require arbitrary prefixes. For example, `Buffer::Cell` (terminal cell inspection) vs `Widgets::Cell` (table cell construction) are clearly distinct.
31
+
32
+ ### 2. Two-Layer Architecture
33
+
34
+ The Ruby frontend implements a "Mullet Architecture": structured namespaces in the library, flat ergonomic DSL for users.
35
+
36
+ **Layer 1: Schema Classes (The Library)**
37
+
38
+ Located in `lib/ratatui_ruby/widgets/`, `lib/ratatui_ruby/layout/`, etc.
39
+
40
+ These are the actual `Data.define` classes that the Rust backend expects. They have deep, explicit namespaces that match Ratatui:
23
41
 
24
- **Example:**
25
42
  ```ruby
26
- # This is just a piece of data, not a "live" widget
27
- paragraph = RatatuiRuby::Paragraph.new(
28
- text: "Hello World",
29
- style: RatatuiRuby::Style.new(fg: :red),
30
- block: nil
31
- )
43
+ RatatuiRuby::Widgets::Paragraph.new(text: "Hello")
44
+ RatatuiRuby::Layout::Constraint.length(20)
45
+ RatatuiRuby::Style::Style.new(fg: :red)
46
+ ```
47
+
48
+ **Layer 2: TUI Facade (The DSL)**
49
+
50
+ Located in `lib/ratatui_ruby/tui.rb` and `lib/ratatui_ruby/tui/*.rb` mixins.
51
+
52
+ The `TUI` class provides shorthand factory methods that hide namespace verbosity:
53
+
54
+ ```ruby
55
+ RatatuiRuby.run do |tui|
56
+ tui.paragraph(text: "Hello")
57
+ tui.constraint_length(20)
58
+ tui.style(fg: :red)
59
+ end
60
+ ```
61
+
62
+ **Why This Matters:**
63
+
64
+ Users write application code using the TUI API and rarely touch deep namespaces. Contributors maintaining the library work with explicit, documentable, IDE-friendly classes. Both audiences are served without compromise.
65
+
66
+ ### 3. Explicit Over Magic
67
+
68
+ The TUI facade uses explicit factory method definitions, not runtime metaprogramming.
69
+
70
+ **What We Do:**
71
+
72
+ ```ruby
73
+ # lib/ratatui_ruby/tui/widget_factories.rb
74
+ module RatatuiRuby
75
+ class TUI
76
+ module WidgetFactories
77
+ def paragraph(**kwargs)
78
+ Widgets::Paragraph.new(**kwargs)
79
+ end
80
+
81
+ def table(**kwargs)
82
+ Widgets::Table.new(**kwargs)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ **What We Don't Do:**
90
+
91
+ ```ruby
92
+ # NO: Dynamic method generation
93
+ RatatuiRuby.constants.each do |const|
94
+ define_method(const.underscore) { |**kw| RatatuiRuby.const_get(const).new(**kw) }
95
+ end
32
96
  ```
33
97
 
34
- ### 2. Immediate Mode Rendering
98
+ **Benefits of Explicit Definitions:**
99
+
100
+ 1. **IDE Support**: Solargraph and Ruby LSP provide autocomplete because methods exist at parse time.
101
+ 2. **RDoc**: Each method can have its own documentation with examples.
102
+ 3. **RBS Types**: Each method has an explicit type signature.
103
+ 4. **Debugging**: Stack traces show real method names, not `define_method` closures.
104
+ 5. **Decoupling**: Internal class names can change without breaking the public TUI API.
105
+
106
+ ### 4. Data-Driven UI (Immediate Mode)
107
+
108
+ All UI components are pure, immutable `Data.define` value objects. They describe *desired appearance* for a single frame, not live stateful objects.
109
+
110
+ **Widgets Are Inputs:**
111
+
112
+ ```ruby
113
+ # This is just data. It has no behavior, no side effects.
114
+ paragraph = RatatuiRuby::Widgets::Paragraph.new(
115
+ text: "Hello",
116
+ style: RatatuiRuby::Style::Style.new(fg: :red)
117
+ )
118
+
119
+ # Pass to renderer as input
120
+ frame.render_widget(paragraph, area)
121
+ ```
35
122
 
36
- The application loop typically looks like this:
123
+ **Immediate Mode Loop:**
37
124
 
38
- 1. **Poll Event**: Ruby asks Rust for the next event.
39
- 2. **Update State**: Ruby application code updates its own domain state (e.g., `counter += 1`).
40
- 3. **Render**: Ruby constructs a fresh View Tree based on the current domain state and passes the root node to `RatatuiRuby.draw`.
125
+ Every frame, the application constructs a fresh view tree and passes it to `draw`. No widget state persists between frames. This is Ratatui's core paradigm.
41
126
 
42
127
  ```ruby
43
128
  loop do
44
- # 1. & 2. Handle events and update state
45
- event = RatatuiRuby.poll_event
46
- break if event == :esc
47
-
48
- # 3. Construct View Tree & Draw
49
- RatatuiRuby.draw do |frame|
50
- frame.render_widget(
51
- RatatuiRuby::Paragraph.new(text: "Time: #{Time.now}"),
52
- frame.area
53
- )
129
+ tui.draw do |frame|
130
+ # Fresh tree every frame
131
+ frame.render_widget(tui.paragraph(text: "Time: #{Time.now}"), frame.area)
54
132
  end
133
+ break if tui.poll_event.key? && tui.poll_event.code == "q"
55
134
  end
56
135
  ```
57
136
 
58
- ### 3. No render logic in Ruby
137
+ ### 5. Separation of Configuration and Status
138
+
139
+ Widgets (Configuration) and State (Status) are strictly separated.
140
+
141
+ **Configuration (Input):**
142
+
143
+ Widgets define *what* to render. They are created, rendered, and discarded.
144
+
145
+ ```ruby
146
+ list = tui.list(items: ["A", "B", "C", "D", "E"])
147
+ ```
148
+
149
+ **Status (Output):**
150
+
151
+ State objects track *runtime metrics* computed by the Rust backend: scroll offsets, selection positions, etc. They persist across frames.
152
+
153
+ ```ruby
154
+ # Created once
155
+ @list_state = RatatuiRuby::ListState.new
156
+
157
+ # Used every frame
158
+ frame.render_stateful_widget(list, area, @list_state)
159
+
160
+ # Read back computed values
161
+ puts "Scroll offset: #{@list_state.offset}"
162
+ ```
163
+
164
+ **Precedence Rule:**
165
+
166
+ When using `render_stateful_widget`, the State object is the source of truth. Widget properties like `selected_index` are ignored.
167
+
168
+ ### 6. No Render Logic in Ruby
169
+
170
+ Ruby defines data structures. Rust renders them.
171
+
172
+ The classes in `lib/ratatui_ruby/widgets/` contain no rendering code. They are pure structural definitions that the Rust extension walks and converts to Ratatui primitives.
173
+
174
+ **Ruby's Job:**
175
+ - Define `Data.define` classes with attributes
176
+ - Validate inputs (types, ranges)
177
+ - Provide convenience constructors
178
+
179
+ **Rust's Job:**
180
+ - Walk the Ruby object tree
181
+ - Extract attributes via `funcall`
182
+ - Construct Ratatui widgets
183
+ - Render to the terminal buffer
184
+
185
+ This separation ensures rendering performance remains in Rust while Ruby handles the ergonomic API layer.
59
186
 
60
- The Ruby classes in `lib/ratatui_ruby/schema/` should **not** contain rendering logic. They are strictly for structural definition and validation. All rendering logic resides in the Rust extension (`ext/ratatui_ruby/`), which walks this Ruby object tree and produces Ratatui primitives.
187
+ ---
188
+
189
+ ## Directory Structure
190
+
191
+ ```
192
+ lib/ratatui_ruby/
193
+ ├── tui.rb # TUI class, includes all mixins
194
+ ├── tui/ # TUI facade mixins
195
+ │ ├── core.rb # draw, poll_event, get_cell_at
196
+ │ ├── layout_factories.rb # rect, constraint_*, layout_split
197
+ │ ├── style_factories.rb # style
198
+ │ ├── widget_factories.rb # paragraph, block, table, list, etc.
199
+ │ ├── text_factories.rb # span, line, text_width
200
+ │ ├── state_factories.rb # list_state, table_state, scrollbar_state
201
+ │ ├── canvas_factories.rb # shape_map, shape_line, etc.
202
+ │ └── buffer_factories.rb # cell (for buffer inspection)
203
+ ├── layout/ # ratatui::layout
204
+ │ ├── rect.rb
205
+ │ ├── constraint.rb
206
+ │ └── layout.rb
207
+ ├── widgets/ # ratatui::widgets
208
+ │ ├── paragraph.rb
209
+ │ ├── block.rb
210
+ │ ├── table.rb
211
+ │ ├── list.rb
212
+ │ ├── row.rb # Table row wrapper
213
+ │ ├── cell.rb # Table cell wrapper (NOT buffer cell)
214
+ │ └── ...
215
+ ├── style/ # ratatui::style
216
+ │ └── style.rb
217
+ ├── text/ # ratatui::text
218
+ │ ├── span.rb
219
+ │ └── line.rb
220
+ ├── buffer/ # ratatui::buffer
221
+ │ └── cell.rb # For get_cell_at inspection
222
+ └── schema/ # Legacy location (being migrated)
223
+ ```
224
+
225
+ ---
61
226
 
62
227
  ## Adding a New Widget
63
228
 
64
- To add a new widget to the Ruby frontend:
229
+ ### Step 1: Create the Schema Class
65
230
 
66
- 1. Define the class in `lib/ratatui_ruby/schema/`.
67
- 2. Use `Data.define`.
68
- 3. Ensure attribute names match what the Rust rendering logic expects (see `ext/ratatui_ruby/src/widgets/`).
231
+ Define the Data class in the appropriate namespace directory:
69
232
 
70
233
  ```ruby
71
- # lib/ratatui_ruby/schema/my_widget.rb
234
+ # lib/ratatui_ruby/widgets/my_widget.rb
72
235
  module RatatuiRuby
73
- # A widget that does something specific.
74
- #
75
- # [some_property] The description of the property.
76
- # [style] The style to apply.
77
- # [block] Optional block widget.
78
- class MyWidget < Data.define(:some_property, :style, :block)
79
- # Creates a new MyWidget.
236
+ module Widgets
237
+ # A widget that displays foo with optional styling.
80
238
  #
81
- # [some_property] The description of the property.
82
- # [style] The style to apply.
83
- # [block] Optional block widget.
84
- def initialize(some_property:, style: nil, block: nil)
85
- super
239
+ # [content] The text content to display.
240
+ # [style] Optional styling for the content.
241
+ # [block] Optional block border wrapper.
242
+ class MyWidget < Data.define(:content, :style, :block)
243
+ def initialize(content:, style: nil, block: nil)
244
+ super
245
+ end
86
246
  end
87
247
  end
88
248
  end
89
249
  ```
90
250
 
91
- And define the types in the corresponding `.rbs` file:
251
+ ### Step 2: Add the RBS Type
92
252
 
93
253
  ```rbs
94
- # sig/ratatui_ruby/schema/my_widget.rbs
254
+ # sig/ratatui_ruby/widgets/my_widget.rbs
95
255
  module RatatuiRuby
96
- class MyWidget < Data
97
- attr_reader some_property: String
98
- attr_reader style: Style?
99
- attr_reader block: Block?
256
+ module Widgets
257
+ class MyWidget < Data
258
+ attr_reader content: String
259
+ attr_reader style: Style::Style?
260
+ attr_reader block: Block?
100
261
 
101
- def self.new: (some_property: String, ?style: Style?, ?block: Block?) -> MyWidget
262
+ def self.new: (content: String, ?style: Style::Style?, ?block: Block?) -> MyWidget
263
+ end
102
264
  end
103
265
  end
104
266
  ```
267
+
268
+ ### Step 3: Add the TUI Factory Method
269
+
270
+ ```ruby
271
+ # lib/ratatui_ruby/tui/widget_factories.rb
272
+ def my_widget(**kwargs)
273
+ Widgets::MyWidget.new(**kwargs)
274
+ end
275
+ ```
276
+
277
+ ### Step 4: Implement Rust Rendering
278
+
279
+ See `rust_backend.md` for the Rust implementation steps.
280
+
281
+ ### Step 5: Register in Requires
282
+
283
+ Add to `lib/ratatui_ruby.rb`:
284
+
285
+ ```ruby
286
+ require_relative "ratatui_ruby/widgets/my_widget"
287
+ ```
288
+
289
+ ---
290
+
291
+ ## TUI Mixin Architecture
292
+
293
+ The `TUI` class is composed of 8 focused mixins, each with a single responsibility:
294
+
295
+ | Mixin | Methods | Purpose |
296
+ |-------|---------|---------|
297
+ | `Core` | `draw`, `poll_event`, `get_cell_at`, `draw_cell` | Terminal I/O operations |
298
+ | `LayoutFactories` | `rect`, `constraint_*`, `layout`, `layout_split` | Layout construction |
299
+ | `StyleFactories` | `style` | Style construction |
300
+ | `WidgetFactories` | `paragraph`, `block`, `table`, `list`, etc. | Widget construction |
301
+ | `TextFactories` | `span`, `line`, `text_width` | Text construction |
302
+ | `StateFactories` | `list_state`, `table_state`, `scrollbar_state` | State object construction |
303
+ | `CanvasFactories` | `shape_map`, `shape_line`, `shape_circle`, etc. | Canvas shape construction |
304
+ | `BufferFactories` | `cell` | Buffer cell construction (for testing) |
305
+
306
+ This modular structure keeps each file focused (~20-50 lines) and makes it easy to locate and modify factory methods.
307
+
308
+ ---
309
+
310
+ ## Thread and Ractor Safety
311
+
312
+ ### Shareable (Frozen Data Objects)
313
+
314
+ These are deeply frozen and `Ractor.shareable?`:
315
+
316
+ - `Event::*` objects from `poll_event`
317
+ - `Buffer::Cell` objects from `get_cell_at`
318
+ - `Layout::Rect` objects from `Layout.split`
319
+
320
+ ### Not Shareable (I/O Handles)
321
+
322
+ These have side effects and are intentionally not Ractor-safe:
323
+
324
+ - `TUI` — Has terminal I/O methods
325
+ - `Frame` — Valid only during the `draw` block; invalid after
326
+
327
+ ```ruby
328
+ # OK: Cache TUI during run loop
329
+ RatatuiRuby.run do |tui|
330
+ @tui = tui
331
+ loop { render; handle_input }
332
+ end
333
+
334
+ # NOT OK: Include in immutable Model
335
+ Model = Data.define(:tui, :count) # Don't do this
336
+ ```