ratatui_ruby 0.7.4 → 0.9.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 (352) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +2 -2
  3. data/.builds/ruby-3.3.yml +2 -2
  4. data/.builds/ruby-3.4.yml +2 -2
  5. data/.builds/ruby-4.0.0.yml +2 -2
  6. data/.pre-commit-config.yaml +1 -1
  7. data/AGENTS.md +3 -3
  8. data/CHANGELOG.md +70 -1
  9. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  10. data/LICENSES/MIT-0.txt +16 -0
  11. data/README.md +33 -5
  12. data/Rakefile +1 -1
  13. data/doc/concepts/application_architecture.md +44 -3
  14. data/doc/concepts/application_testing.md +43 -1
  15. data/doc/concepts/async.md +32 -2
  16. data/doc/concepts/custom_widgets.md +247 -0
  17. data/doc/concepts/event_handling.md +32 -3
  18. data/doc/concepts/interactive_design.md +32 -2
  19. data/doc/contributors/auditing/parity.md +7 -1
  20. data/doc/contributors/design/ruby_frontend.md +85 -1
  21. data/doc/contributors/design/rust_backend.md +67 -1
  22. data/doc/contributors/developing_examples.md +56 -2
  23. data/doc/contributors/documentation_style.md +20 -3
  24. data/doc/contributors/future_work.md +169 -0
  25. data/doc/contributors/index.md +1 -1
  26. data/doc/contributors/v1.0.0_blockers.md +15 -175
  27. data/doc/getting_started/quickstart.md +35 -9
  28. data/doc/getting_started/why.md +1 -1
  29. data/doc/index.md +2 -1
  30. data/doc/troubleshooting/debugging.md +32 -2
  31. data/doc/troubleshooting/terminal_limitations.md +8 -2
  32. data/doc/troubleshooting/tui_output.md +127 -6
  33. data/examples/app_all_events/README.md +14 -2
  34. data/examples/app_all_events/app.rb +1 -1
  35. data/examples/app_all_events/model/app_model.rb +1 -1
  36. data/examples/app_all_events/model/event_color_cycle.rb +1 -1
  37. data/examples/app_all_events/model/event_entry.rb +1 -1
  38. data/examples/app_all_events/model/msg.rb +1 -1
  39. data/examples/app_all_events/model/timestamp.rb +1 -1
  40. data/examples/app_all_events/update.rb +1 -1
  41. data/examples/app_all_events/view/app_view.rb +1 -1
  42. data/examples/app_all_events/view/controls_view.rb +1 -1
  43. data/examples/app_all_events/view/counts_view.rb +1 -1
  44. data/examples/app_all_events/view/live_view.rb +1 -1
  45. data/examples/app_all_events/view/log_view.rb +1 -1
  46. data/examples/app_all_events/view.rb +1 -1
  47. data/examples/app_color_picker/README.md +20 -2
  48. data/examples/app_color_picker/app.rb +1 -1
  49. data/examples/app_color_picker/clipboard.rb +1 -1
  50. data/examples/app_color_picker/color.rb +1 -1
  51. data/examples/app_color_picker/controls.rb +1 -1
  52. data/examples/app_color_picker/copy_dialog.rb +1 -1
  53. data/examples/app_color_picker/export_pane.rb +1 -1
  54. data/examples/app_color_picker/harmony.rb +1 -1
  55. data/examples/app_color_picker/input.rb +1 -1
  56. data/examples/app_color_picker/main_container.rb +1 -1
  57. data/examples/app_color_picker/palette.rb +1 -1
  58. data/examples/app_login_form/README.md +8 -2
  59. data/examples/app_login_form/app.rb +1 -1
  60. data/examples/app_stateful_interaction/README.md +2 -2
  61. data/examples/app_stateful_interaction/app.rb +71 -17
  62. data/examples/timeout_demo.rb +1 -1
  63. data/examples/verify_quickstart_dsl/README.md +6 -0
  64. data/examples/verify_quickstart_dsl/app.rb +3 -3
  65. data/examples/verify_quickstart_layout/README.md +6 -0
  66. data/examples/verify_quickstart_layout/app.rb +3 -3
  67. data/examples/verify_quickstart_lifecycle/README.md +13 -1
  68. data/examples/verify_quickstart_lifecycle/app.rb +10 -4
  69. data/examples/verify_readme_usage/README.md +6 -0
  70. data/examples/verify_readme_usage/app.rb +3 -3
  71. data/examples/widget_barchart/README.md +6 -0
  72. data/examples/widget_barchart/app.rb +2 -2
  73. data/examples/widget_block/README.md +7 -1
  74. data/examples/widget_block/app.rb +2 -2
  75. data/examples/widget_box/README.md +6 -0
  76. data/examples/widget_box/app.rb +9 -6
  77. data/examples/widget_calendar/README.md +6 -0
  78. data/examples/widget_calendar/app.rb +2 -2
  79. data/examples/widget_canvas/README.md +4 -0
  80. data/examples/widget_canvas/app.rb +2 -2
  81. data/examples/widget_cell/README.md +6 -0
  82. data/examples/widget_cell/app.rb +2 -3
  83. data/examples/widget_center/README.md +4 -0
  84. data/examples/widget_center/app.rb +2 -2
  85. data/examples/widget_chart/README.md +6 -0
  86. data/examples/widget_chart/app.rb +2 -2
  87. data/examples/widget_gauge/README.md +6 -0
  88. data/examples/widget_gauge/app.rb +2 -2
  89. data/examples/widget_layout_split/README.md +6 -0
  90. data/examples/widget_layout_split/app.rb +3 -3
  91. data/examples/widget_line_gauge/README.md +6 -0
  92. data/examples/widget_line_gauge/app.rb +2 -2
  93. data/examples/widget_list/README.md +6 -0
  94. data/examples/widget_list/app.rb +2 -2
  95. data/examples/widget_map/README.md +8 -2
  96. data/examples/widget_map/app.rb +2 -2
  97. data/examples/widget_overlay/README.md +7 -1
  98. data/examples/widget_overlay/app.rb +2 -2
  99. data/examples/widget_popup/README.md +6 -0
  100. data/examples/widget_popup/app.rb +2 -2
  101. data/examples/widget_ratatui_logo/README.md +6 -0
  102. data/examples/widget_ratatui_logo/app.rb +2 -3
  103. data/examples/widget_ratatui_mascot/README.md +6 -0
  104. data/examples/widget_ratatui_mascot/app.rb +2 -2
  105. data/examples/widget_rect/README.md +12 -0
  106. data/examples/widget_rect/app.rb +40 -26
  107. data/examples/widget_render/README.md +6 -0
  108. data/examples/widget_render/app.rb +2 -2
  109. data/examples/widget_render/app.rbs +41 -0
  110. data/examples/widget_rich_text/README.md +6 -0
  111. data/examples/widget_rich_text/app.rb +2 -2
  112. data/examples/widget_scroll_text/README.md +6 -0
  113. data/examples/widget_scroll_text/app.rb +2 -2
  114. data/examples/widget_scrollbar/README.md +6 -0
  115. data/examples/widget_scrollbar/app.rb +2 -2
  116. data/examples/widget_sparkline/README.md +6 -0
  117. data/examples/widget_sparkline/app.rb +2 -2
  118. data/examples/widget_style_colors/README.md +6 -0
  119. data/examples/widget_style_colors/app.rb +2 -2
  120. data/examples/widget_table/README.md +8 -2
  121. data/examples/widget_table/app.rb +2 -2
  122. data/examples/widget_tabs/README.md +6 -0
  123. data/examples/widget_tabs/app.rb +2 -2
  124. data/examples/widget_text_width/README.md +6 -0
  125. data/examples/widget_text_width/app.rb +4 -4
  126. data/ext/ratatui_ruby/Cargo.lock +1 -1
  127. data/ext/ratatui_ruby/Cargo.toml +1 -1
  128. data/ext/ratatui_ruby/extconf.rb +2 -2
  129. data/ext/ratatui_ruby/src/rendering.rs +1 -1
  130. data/ext/ratatui_ruby/src/style.rs +0 -8
  131. data/ext/ratatui_ruby/src/widgets/chart.rs +0 -118
  132. data/ext/ratatui_ruby/src/widgets/list_state.rs +36 -0
  133. data/lib/ratatui_ruby/buffer/cell.rb +34 -2
  134. data/lib/ratatui_ruby/buffer.rb +2 -2
  135. data/lib/ratatui_ruby/cell.rb +34 -2
  136. data/lib/ratatui_ruby/event/focus_gained.rb +26 -2
  137. data/lib/ratatui_ruby/event/focus_lost.rb +26 -2
  138. data/lib/ratatui_ruby/event/key/character.rb +18 -2
  139. data/lib/ratatui_ruby/event/key/media.rb +2 -2
  140. data/lib/ratatui_ruby/event/key/modifier.rb +10 -2
  141. data/lib/ratatui_ruby/event/key/navigation.rb +2 -2
  142. data/lib/ratatui_ruby/event/key/system.rb +2 -2
  143. data/lib/ratatui_ruby/event/key.rb +114 -2
  144. data/lib/ratatui_ruby/event/mouse.rb +42 -2
  145. data/lib/ratatui_ruby/event/none.rb +10 -2
  146. data/lib/ratatui_ruby/event/paste.rb +34 -2
  147. data/lib/ratatui_ruby/event/resize.rb +34 -2
  148. data/lib/ratatui_ruby/event.rb +26 -2
  149. data/lib/ratatui_ruby/frame.rb +74 -2
  150. data/lib/ratatui_ruby/layout/constraint.rb +58 -2
  151. data/lib/ratatui_ruby/layout/layout.rb +47 -2
  152. data/lib/ratatui_ruby/layout/rect.rb +403 -2
  153. data/lib/ratatui_ruby/layout.rb +2 -2
  154. data/lib/ratatui_ruby/list_state.rb +113 -2
  155. data/lib/ratatui_ruby/output_guard.rb +171 -0
  156. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  157. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +2 -2
  158. data/lib/ratatui_ruby/schema/bar_chart.rb +50 -2
  159. data/lib/ratatui_ruby/schema/block.rb +21 -15
  160. data/lib/ratatui_ruby/schema/calendar.rb +2 -2
  161. data/lib/ratatui_ruby/schema/canvas.rb +10 -2
  162. data/lib/ratatui_ruby/schema/center.rb +10 -2
  163. data/lib/ratatui_ruby/schema/chart.rb +2 -28
  164. data/lib/ratatui_ruby/schema/clear.rb +10 -2
  165. data/lib/ratatui_ruby/schema/constraint.rb +58 -2
  166. data/lib/ratatui_ruby/schema/cursor.rb +10 -2
  167. data/lib/ratatui_ruby/schema/draw.rb +10 -2
  168. data/lib/ratatui_ruby/schema/gauge.rb +2 -2
  169. data/lib/ratatui_ruby/schema/layout.rb +18 -2
  170. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  171. data/lib/ratatui_ruby/schema/list.rb +10 -2
  172. data/lib/ratatui_ruby/schema/list_item.rb +10 -2
  173. data/lib/ratatui_ruby/schema/overlay.rb +10 -2
  174. data/lib/ratatui_ruby/schema/paragraph.rb +10 -2
  175. data/lib/ratatui_ruby/schema/ratatui_logo.rb +2 -2
  176. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +2 -2
  177. data/lib/ratatui_ruby/schema/rect.rb +58 -2
  178. data/lib/ratatui_ruby/schema/row.rb +10 -2
  179. data/lib/ratatui_ruby/schema/scrollbar.rb +2 -2
  180. data/lib/ratatui_ruby/schema/shape/label.rb +10 -2
  181. data/lib/ratatui_ruby/schema/sparkline.rb +10 -2
  182. data/lib/ratatui_ruby/schema/style.rb +18 -2
  183. data/lib/ratatui_ruby/schema/table.rb +2 -2
  184. data/lib/ratatui_ruby/schema/tabs.rb +2 -2
  185. data/lib/ratatui_ruby/schema/text.rb +34 -2
  186. data/lib/ratatui_ruby/scrollbar_state.rb +10 -2
  187. data/lib/ratatui_ruby/style/style.rb +18 -2
  188. data/lib/ratatui_ruby/style.rb +2 -2
  189. data/lib/ratatui_ruby/table_state.rb +10 -2
  190. data/lib/ratatui_ruby/terminal_lifecycle.rb +144 -0
  191. data/lib/ratatui_ruby/test_helper/event_injection.rb +34 -2
  192. data/lib/ratatui_ruby/test_helper/snapshot.rb +74 -9
  193. data/lib/ratatui_ruby/test_helper/style_assertions.rb +98 -2
  194. data/lib/ratatui_ruby/test_helper/terminal.rb +50 -2
  195. data/lib/ratatui_ruby/test_helper/test_doubles.rb +18 -2
  196. data/lib/ratatui_ruby/test_helper.rb +10 -2
  197. data/lib/ratatui_ruby/tui/buffer_factories.rb +2 -2
  198. data/lib/ratatui_ruby/tui/canvas_factories.rb +2 -2
  199. data/lib/ratatui_ruby/tui/core.rb +2 -2
  200. data/lib/ratatui_ruby/tui/layout_factories.rb +32 -2
  201. data/lib/ratatui_ruby/tui/state_factories.rb +2 -2
  202. data/lib/ratatui_ruby/tui/style_factories.rb +2 -2
  203. data/lib/ratatui_ruby/tui/text_factories.rb +2 -2
  204. data/lib/ratatui_ruby/tui/widget_factories.rb +2 -2
  205. data/lib/ratatui_ruby/tui.rb +11 -3
  206. data/lib/ratatui_ruby/version.rb +3 -3
  207. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -2
  208. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -2
  209. data/lib/ratatui_ruby/widgets/bar_chart.rb +58 -2
  210. data/lib/ratatui_ruby/widgets/block.rb +37 -15
  211. data/lib/ratatui_ruby/widgets/calendar.rb +2 -2
  212. data/lib/ratatui_ruby/widgets/canvas.rb +10 -2
  213. data/lib/ratatui_ruby/widgets/cell.rb +10 -2
  214. data/lib/ratatui_ruby/widgets/center.rb +10 -2
  215. data/lib/ratatui_ruby/widgets/chart.rb +2 -28
  216. data/lib/ratatui_ruby/widgets/clear.rb +10 -2
  217. data/lib/ratatui_ruby/widgets/cursor.rb +10 -2
  218. data/lib/ratatui_ruby/widgets/gauge.rb +16 -2
  219. data/lib/ratatui_ruby/widgets/line_gauge.rb +16 -2
  220. data/lib/ratatui_ruby/widgets/list.rb +41 -2
  221. data/lib/ratatui_ruby/widgets/list_item.rb +10 -2
  222. data/lib/ratatui_ruby/widgets/overlay.rb +10 -2
  223. data/lib/ratatui_ruby/widgets/paragraph.rb +10 -2
  224. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -2
  225. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -2
  226. data/lib/ratatui_ruby/widgets/row.rb +10 -2
  227. data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -2
  228. data/lib/ratatui_ruby/widgets/shape/label.rb +10 -2
  229. data/lib/ratatui_ruby/widgets/sparkline.rb +10 -2
  230. data/lib/ratatui_ruby/widgets/table.rb +62 -2
  231. data/lib/ratatui_ruby/widgets/tabs.rb +2 -2
  232. data/lib/ratatui_ruby/widgets.rb +2 -2
  233. data/lib/ratatui_ruby.rb +116 -81
  234. data/sig/examples/app_all_events/view.rbs +7 -1
  235. data/sig/examples/app_all_events/view_state.rbs +7 -1
  236. data/sig/examples/app_color_picker/app.rbs +5 -0
  237. data/sig/examples/app_stateful_interaction/app.rbs +7 -1
  238. data/sig/examples/verify_quickstart_dsl/app.rbs +7 -1
  239. data/sig/examples/verify_quickstart_lifecycle/app.rbs +7 -1
  240. data/sig/examples/verify_readme_usage/app.rbs +7 -1
  241. data/sig/examples/widget_block_demo/app.rbs +6 -0
  242. data/sig/examples/widget_box_demo/app.rbs +7 -1
  243. data/sig/examples/widget_calendar_demo/app.rbs +7 -1
  244. data/sig/examples/widget_cell_demo/app.rbs +7 -1
  245. data/sig/examples/widget_chart_demo/app.rbs +7 -1
  246. data/sig/examples/widget_gauge_demo/app.rbs +7 -1
  247. data/sig/examples/widget_layout_split/app.rbs +7 -1
  248. data/sig/examples/widget_line_gauge_demo/app.rbs +7 -1
  249. data/sig/examples/widget_list_demo/app.rbs +5 -0
  250. data/sig/examples/widget_map_demo/app.rbs +7 -1
  251. data/sig/examples/widget_popup_demo/app.rbs +7 -1
  252. data/sig/examples/widget_ratatui_logo_demo/app.rbs +7 -1
  253. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +7 -1
  254. data/sig/examples/widget_rect/app.rbs +7 -1
  255. data/sig/examples/widget_render/app.rbs +7 -1
  256. data/sig/examples/widget_rich_text/app.rbs +7 -1
  257. data/sig/examples/widget_scroll_text/app.rbs +7 -1
  258. data/sig/examples/widget_scrollbar_demo/app.rbs +7 -1
  259. data/sig/examples/widget_sparkline_demo/app.rbs +7 -1
  260. data/sig/examples/widget_style_colors/app.rbs +7 -1
  261. data/sig/examples/widget_table_demo/app.rbs +7 -1
  262. data/sig/examples/widget_text_width/app.rbs +7 -1
  263. data/sig/ratatui_ruby/event.rbs +7 -1
  264. data/sig/ratatui_ruby/frame.rbs +15 -3
  265. data/sig/ratatui_ruby/list_state.rbs +11 -1
  266. data/sig/ratatui_ruby/ratatui_ruby.rbs +8 -2
  267. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +7 -1
  268. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +6 -0
  269. data/sig/ratatui_ruby/schema/bar_chart.rbs +6 -0
  270. data/sig/ratatui_ruby/schema/block.rbs +7 -1
  271. data/sig/ratatui_ruby/schema/calendar.rbs +6 -0
  272. data/sig/ratatui_ruby/schema/canvas.rbs +6 -0
  273. data/sig/ratatui_ruby/schema/center.rbs +6 -0
  274. data/sig/ratatui_ruby/schema/chart.rbs +6 -9
  275. data/sig/ratatui_ruby/schema/constraint.rbs +6 -0
  276. data/sig/ratatui_ruby/schema/cursor.rbs +6 -0
  277. data/sig/ratatui_ruby/schema/draw.rbs +6 -0
  278. data/sig/ratatui_ruby/schema/gauge.rbs +9 -1
  279. data/sig/ratatui_ruby/schema/layout.rbs +6 -0
  280. data/sig/ratatui_ruby/schema/line_gauge.rbs +9 -1
  281. data/sig/ratatui_ruby/schema/list.rbs +9 -1
  282. data/sig/ratatui_ruby/schema/list_item.rbs +7 -1
  283. data/sig/ratatui_ruby/schema/overlay.rbs +6 -0
  284. data/sig/ratatui_ruby/schema/paragraph.rbs +6 -0
  285. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +6 -0
  286. data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +5 -0
  287. data/sig/ratatui_ruby/schema/rect.rbs +30 -0
  288. data/sig/ratatui_ruby/schema/row.rbs +7 -1
  289. data/sig/ratatui_ruby/schema/scrollbar.rbs +6 -0
  290. data/sig/ratatui_ruby/schema/sparkline.rbs +6 -0
  291. data/sig/ratatui_ruby/schema/style.rbs +7 -1
  292. data/sig/ratatui_ruby/schema/table.rbs +11 -1
  293. data/sig/ratatui_ruby/schema/tabs.rbs +6 -0
  294. data/sig/ratatui_ruby/schema/text.rbs +7 -1
  295. data/sig/ratatui_ruby/scrollbar_state.rbs +7 -1
  296. data/sig/ratatui_ruby/session.rbs +7 -1
  297. data/sig/ratatui_ruby/table_state.rbs +7 -1
  298. data/sig/ratatui_ruby/test_helper/event_injection.rbs +7 -1
  299. data/sig/ratatui_ruby/test_helper/snapshot.rbs +7 -1
  300. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +7 -1
  301. data/sig/ratatui_ruby/test_helper/terminal.rbs +7 -1
  302. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -1
  303. data/sig/ratatui_ruby/test_helper.rbs +7 -1
  304. data/sig/ratatui_ruby/tui/buffer_factories.rbs +7 -1
  305. data/sig/ratatui_ruby/tui/canvas_factories.rbs +7 -1
  306. data/sig/ratatui_ruby/tui/core.rbs +7 -1
  307. data/sig/ratatui_ruby/tui/layout_factories.rbs +7 -1
  308. data/sig/ratatui_ruby/tui/state_factories.rbs +7 -1
  309. data/sig/ratatui_ruby/tui/style_factories.rbs +7 -1
  310. data/sig/ratatui_ruby/tui/text_factories.rbs +7 -1
  311. data/sig/ratatui_ruby/tui/widget_factories.rbs +7 -1
  312. data/sig/ratatui_ruby/tui.rbs +7 -1
  313. data/sig/ratatui_ruby/version.rbs +6 -0
  314. data/tasks/autodoc/examples.rb +9 -3
  315. data/tasks/autodoc/member.rb +1 -1
  316. data/tasks/autodoc/name.rb +1 -1
  317. data/tasks/bump/cargo_lockfile.rb +1 -1
  318. data/tasks/bump/changelog.rb +1 -1
  319. data/tasks/bump/header.rb +1 -1
  320. data/tasks/bump/history.rb +1 -1
  321. data/tasks/bump/links.rb +1 -1
  322. data/tasks/bump/manifest.rb +1 -1
  323. data/tasks/bump/ruby_gem.rb +1 -1
  324. data/tasks/bump/sem_ver.rb +1 -1
  325. data/tasks/bump/unreleased_section.rb +1 -1
  326. data/tasks/license/headers_md.rb +223 -0
  327. data/tasks/license/headers_rb.rb +210 -0
  328. data/tasks/license/license_utils.rb +130 -0
  329. data/tasks/license/snippets_md.rb +315 -0
  330. data/tasks/license/snippets_rdoc.rb +150 -0
  331. data/tasks/license.rake +91 -0
  332. data/tasks/rdoc_config.rb +1 -1
  333. data/tasks/resources/build.yml.erb +13 -7
  334. data/tasks/sourcehut.rake +3 -1
  335. data/tasks/terminal_preview/app_screenshot.rb +1 -1
  336. data/tasks/terminal_preview/crash_report.rb +1 -1
  337. data/tasks/terminal_preview/example_app.rb +1 -1
  338. data/tasks/terminal_preview/launcher_script.rb +1 -1
  339. data/tasks/terminal_preview/preview_collection.rb +1 -1
  340. data/tasks/terminal_preview/preview_timing.rb +1 -1
  341. data/tasks/terminal_preview/safety_confirmation.rb +1 -1
  342. data/tasks/terminal_preview/saved_screenshot.rb +1 -1
  343. data/tasks/terminal_preview/system_appearance.rb +1 -1
  344. data/tasks/terminal_preview/terminal_window.rb +1 -1
  345. data/tasks/terminal_preview/window_id.rb +1 -1
  346. data/tasks/website/index_page.rb +1 -1
  347. data/tasks/website/version.rb +1 -1
  348. data/tasks/website/version_menu.rb +1 -1
  349. data/tasks/website/versioned_documentation.rb +1 -1
  350. data/tasks/website/website.rb +1 -1
  351. metadata +15 -3
  352. data/doc/migration/v0_7_0.md +0 -236
@@ -0,0 +1,247 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Custom Widgets
7
+
8
+ Build anything. Escape the widget library.
9
+
10
+ ## What Terminals Offer
11
+
12
+ Terminals do not have pixels. They have character cells arranged in a grid. Each cell holds one character with foreground color, background color, and text modifiers (bold, italic, underline).
13
+
14
+ This constraint shapes what you can draw:
15
+
16
+ - **Characters**: Any Unicode character fits in a cell
17
+ - **Box-drawing**: Lines, corners, and boxes (`│`, `┌`, `─`, `└`)
18
+ - **Block elements**: Partial fills (`▀`, `▄`, `█`, `░`, `▒`, `▓`)
19
+ - **Braille patterns**: 2×4 "pixel" grids per cell for pseudo-graphics
20
+ - **Nerd Fonts**: Icons and glyphs if the user's font supports them
21
+
22
+ The built-in Canvas widget uses Braille patterns for line graphs and shapes. Custom widgets give you direct control over every cell.
23
+
24
+ ## The Problem
25
+
26
+ Standard widgets handle common needs. Paragraphs display text. Lists show selections. Tables organize data.
27
+
28
+ But terminals can do more. You want a game board, a network graph, or a custom visualization. The built-in widgets cannot help you here.
29
+
30
+ ## The Solution
31
+
32
+ Any Ruby object that implements `render(area)` works as a widget. You are not limited to what the library ships. Define a class. Implement one method. Pass it to `frame.render_widget`.
33
+
34
+ The Engine calls your `render` method with the area where your widget should draw. You return an array of Draw commands. The Engine executes them.
35
+
36
+ ## The Contract
37
+
38
+ Your custom widget implements [the `_CustomWidget` interface](../../sig/ratatui_ruby/frame.rbs). The `area` parameter is a `Rect` with `x`, `y`, `width`, and `height`. It tells you where to draw and how much space you have.
39
+
40
+ ## Draw Commands
41
+
42
+ Two commands describe what to draw:
43
+
44
+ | Command | Purpose |
45
+ |---------|---------|
46
+ | `Draw.string(x, y, text, style)` | Draw a styled string at absolute coordinates |
47
+ | `Draw.cell(x, y, cell)` | Draw a single cell (character + style) |
48
+
49
+ <!-- SPDX-SnippetBegin -->
50
+ <!--
51
+ SPDX-FileCopyrightText: 2026 Kerrick Long
52
+ SPDX-License-Identifier: MIT-0
53
+ -->
54
+ ```ruby
55
+ class HelloWidget
56
+ def render(area)
57
+ [
58
+ RatatuiRuby::Draw.string(
59
+ area.x,
60
+ area.y,
61
+ "Hello, World!",
62
+ RatatuiRuby::Style::Style.new(fg: :green, modifiers: [:bold])
63
+ )
64
+ ]
65
+ end
66
+ end
67
+ ```
68
+ <!-- SPDX-SnippetEnd -->
69
+
70
+ ## Coordinate Offsets
71
+
72
+ The `area.x` and `area.y` values are not always zero. When your widget renders inside a `Block` with borders, or within a nested layout, the area's origin shifts.
73
+
74
+ Always add `area.x` and `area.y` to your drawing coordinates. This pattern ensures your widget works regardless of where it appears on screen.
75
+
76
+ <!-- SPDX-SnippetBegin -->
77
+ <!--
78
+ SPDX-FileCopyrightText: 2026 Kerrick Long
79
+ SPDX-License-Identifier: MIT-0
80
+ -->
81
+ ```ruby
82
+ class DiagonalWidget
83
+ def render(area)
84
+ (0...area.height).filter_map do |i|
85
+ next if i >= area.width # Stay within bounds
86
+
87
+ RatatuiRuby::Draw.string(
88
+ area.x + i, # Offset from area origin
89
+ area.y + i,
90
+ "\\",
91
+ RatatuiRuby::Style::Style.new(fg: :red)
92
+ )
93
+ end
94
+ end
95
+ end
96
+ ```
97
+ <!-- SPDX-SnippetEnd -->
98
+
99
+ ## Composability
100
+
101
+ Custom widgets compose with standard widgets. Wrap them in Blocks. Place them in layouts. Mix them with Paragraphs and Lists.
102
+
103
+ <!-- SPDX-SnippetBegin -->
104
+ <!--
105
+ SPDX-FileCopyrightText: 2026 Kerrick Long
106
+ SPDX-License-Identifier: MIT-0
107
+ -->
108
+ ```ruby
109
+ RatatuiRuby.run do |tui|
110
+ tui.draw do |frame|
111
+ areas = tui.layout_split(
112
+ frame.area,
113
+ direction: :horizontal,
114
+ constraints: [tui.constraint_percentage(50), tui.constraint_percentage(50)]
115
+ )
116
+
117
+ # Standard widget on the left
118
+ frame.render_widget(tui.paragraph(text: "Standard"), areas[0])
119
+
120
+ # Custom widget on the right
121
+ frame.render_widget(DiagonalWidget.new, areas[1])
122
+ end
123
+ end
124
+ ```
125
+ <!-- SPDX-SnippetEnd -->
126
+
127
+ To render inside a bordered Block, calculate the inner area first:
128
+
129
+ <!-- SPDX-SnippetBegin -->
130
+ <!--
131
+ SPDX-FileCopyrightText: 2026 Kerrick Long
132
+ SPDX-License-Identifier: MIT-0
133
+ -->
134
+ ```ruby
135
+ tui.draw do |frame|
136
+ # Render the block frame
137
+ block = tui.block(title: "Custom", borders: [:all])
138
+ frame.render_widget(block, frame.area)
139
+
140
+ # Calculate inner area (1-cell border on all sides)
141
+ inner = tui.rect(
142
+ x: frame.area.x + 1,
143
+ y: frame.area.y + 1,
144
+ width: [frame.area.width - 2, 0].max,
145
+ height: [frame.area.height - 2, 0].max
146
+ )
147
+
148
+ # Render custom widget inside
149
+ frame.render_widget(MyWidget.new, inner)
150
+ end
151
+ ```
152
+ <!-- SPDX-SnippetEnd -->
153
+
154
+ ## Using Custom Widgets in Layouts
155
+
156
+ Custom widgets work as children in Layout trees. The layout system passes the calculated area to your `render` method.
157
+
158
+ <!-- SPDX-SnippetBegin -->
159
+ <!--
160
+ SPDX-FileCopyrightText: 2026 Kerrick Long
161
+ SPDX-License-Identifier: MIT-0
162
+ -->
163
+ ```ruby
164
+ layout = RatatuiRuby::Layout::Layout.new(
165
+ direction: :vertical,
166
+ constraints: [
167
+ RatatuiRuby::Layout::Constraint.length(1),
168
+ RatatuiRuby::Layout::Constraint.fill(1),
169
+ ],
170
+ children: [
171
+ RatatuiRuby::Widgets::Paragraph.new(text: "Header"),
172
+ MyCustomWidget.new, # Your widget here
173
+ ]
174
+ )
175
+
176
+ RatatuiRuby.draw(layout)
177
+ ```
178
+ <!-- SPDX-SnippetEnd -->
179
+
180
+ ## Testing Custom Widgets
181
+
182
+ Custom widgets return arrays. Test them by calling `render` directly and asserting on the result.
183
+
184
+ <!-- SPDX-SnippetBegin -->
185
+ <!--
186
+ SPDX-FileCopyrightText: 2026 Kerrick Long
187
+ SPDX-License-Identifier: MIT-0
188
+ -->
189
+ ```ruby
190
+ def test_hello_widget_output
191
+ area = RatatuiRuby::Rect.new(x: 0, y: 0, width: 20, height: 5)
192
+ widget = HelloWidget.new
193
+ commands = widget.render(area)
194
+
195
+ assert_equal 1, commands.length
196
+ assert_equal 0, commands[0].x
197
+ assert_equal 0, commands[0].y
198
+ assert_equal "Hello, World!", commands[0].string
199
+ end
200
+ ```
201
+ <!-- SPDX-SnippetEnd -->
202
+
203
+ For visual testing, use the test helper to render to a buffer and assert on content:
204
+
205
+ <!-- SPDX-SnippetBegin -->
206
+ <!--
207
+ SPDX-FileCopyrightText: 2026 Kerrick Long
208
+ SPDX-License-Identifier: MIT-0
209
+ -->
210
+ ```ruby
211
+ class TestMyWidget < Minitest::Test
212
+ include RatatuiRuby::TestHelper
213
+
214
+ def test_renders_in_terminal
215
+ with_test_terminal(10, 5) do
216
+ RatatuiRuby.draw(MyWidget.new)
217
+ assert_equal "Expected ", buffer_content[0]
218
+ end
219
+ end
220
+ end
221
+ ```
222
+ <!-- SPDX-SnippetEnd -->
223
+
224
+ ## Typing Your Widgets (RBS)
225
+
226
+ Type your custom widgets by implementing the `_CustomWidget` interface:
227
+
228
+ <!-- SPDX-SnippetBegin -->
229
+ <!--
230
+ SPDX-FileCopyrightText: 2026 Kerrick Long
231
+ SPDX-License-Identifier: MIT-0
232
+ -->
233
+ ```rbs
234
+ # my_widget.rbs
235
+ class MyWidget
236
+ def render: (RatatuiRuby::Rect area) -> Array[RatatuiRuby::Draw::StringCmd | RatatuiRuby::Draw::CellCmd]
237
+ end
238
+ ```
239
+ <!-- SPDX-SnippetEnd -->
240
+
241
+ The interface uses structural typing. Any class with a matching `render` signature satisfies it.
242
+
243
+ ## Related Resources
244
+
245
+ - [Custom Render Example](../examples/widget_render/README.md) — Full working example
246
+ - [Cell Example](../examples/widget_cell/README.md) — Low-level cell drawing
247
+ - [Application Testing](./application_testing.md) — Test helper reference
@@ -1,7 +1,6 @@
1
1
  <!--
2
- SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
-
4
- SPDX-License-Identifier: AGPL-3.0-or-later
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
4
  -->
6
5
 
7
6
  # Event Handling
@@ -30,6 +29,11 @@ For simple key events, `RatatuiRuby::Event::Key` objects can be compared directl
30
29
 
31
30
  For a complete list of supported keys, modifiers, and event types, please refer to the [API Documentation for RatatuiRuby::Event](file:///Users/kerrick/Developer/ratatui_ruby/lib/ratatui_ruby/event.rb).
32
31
 
32
+ <!-- SPDX-SnippetBegin -->
33
+ <!--
34
+ SPDX-FileCopyrightText: 2025 Kerrick Long
35
+ SPDX-License-Identifier: MIT-0
36
+ -->
33
37
  ```ruby
34
38
  event = RatatuiRuby.poll_event
35
39
 
@@ -43,6 +47,7 @@ if event == :enter
43
47
  submit_form
44
48
  end
45
49
  ```
50
+ <!-- SPDX-SnippetEnd -->
46
51
 
47
52
  ## 3. Predicate Methods (Intermediate)
48
53
 
@@ -54,6 +59,11 @@ Safe to call on *any* event object. They return `true` only for the matching eve
54
59
 
55
60
  Available: `key?`, `mouse?`, `resize?`, `paste?`, `focus_gained?`, `focus_lost?`.
56
61
 
62
+ <!-- SPDX-SnippetBegin -->
63
+ <!--
64
+ SPDX-FileCopyrightText: 2025 Kerrick Long
65
+ SPDX-License-Identifier: MIT-0
66
+ -->
57
67
  ```ruby
58
68
  event = RatatuiRuby.poll_event
59
69
 
@@ -65,6 +75,7 @@ elsif event.resize?
65
75
  resize_layout(event.width, event.height)
66
76
  end
67
77
  ```
78
+ <!-- SPDX-SnippetEnd -->
68
79
 
69
80
  ### Helper Predicates
70
81
 
@@ -74,26 +85,43 @@ Specific to certain event classes to simplify checks.
74
85
  * `ctrl?`, `alt?`, `shift?`: Check if modifier is held.
75
86
  * `text?`: Returns `true` if the event is a printable character (length == 1).
76
87
 
88
+ <!-- SPDX-SnippetBegin -->
89
+ <!--
90
+ SPDX-FileCopyrightText: 2025 Kerrick Long
91
+ SPDX-License-Identifier: MIT-0
92
+ -->
77
93
  ```ruby
78
94
  if event.key? && event.ctrl? && event.code == "s"
79
95
  save_file
80
96
  end
81
97
  ```
98
+ <!-- SPDX-SnippetEnd -->
82
99
 
83
100
  #### `RatatuiRuby::Event::Mouse`
84
101
  * `down?`, `up?`, `drag?`: Check mouse action.
85
102
  * `scroll_up?`, `scroll_down?`: Check scroll direction.
86
103
 
104
+ <!-- SPDX-SnippetBegin -->
105
+ <!--
106
+ SPDX-FileCopyrightText: 2025 Kerrick Long
107
+ SPDX-License-Identifier: MIT-0
108
+ -->
87
109
  ```ruby
88
110
  if event.mouse? && event.scroll_up?
89
111
  scroll_view(-1)
90
112
  end
91
113
  ```
114
+ <!-- SPDX-SnippetEnd -->
92
115
 
93
116
  ## 4. Pattern Matching (Powerful)
94
117
 
95
118
  For complex applications, Ruby 3.0+ Pattern Matching with the `type:` discriminator is the most idiomatic and concise approach.
96
119
 
120
+ <!-- SPDX-SnippetBegin -->
121
+ <!--
122
+ SPDX-FileCopyrightText: 2025 Kerrick Long
123
+ SPDX-License-Identifier: MIT-0
124
+ -->
97
125
  ```ruby
98
126
  loop do
99
127
  case RatatuiRuby.poll_event
@@ -119,6 +147,7 @@ loop do
119
147
  end
120
148
  end
121
149
  ```
150
+ <!-- SPDX-SnippetEnd -->
122
151
 
123
152
  ## Summary of Event Classes
124
153
 
@@ -1,6 +1,6 @@
1
1
  <!--
2
- SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
4
  -->
5
5
 
6
6
  # Interactive TUI Design Patterns
@@ -19,6 +19,11 @@ Canonical patterns for building responsive, interactive terminal user interfaces
19
19
 
20
20
  Structure your event loop into three clear phases:
21
21
 
22
+ <!-- SPDX-SnippetBegin -->
23
+ <!--
24
+ SPDX-FileCopyrightText: 2026 Kerrick Long
25
+ SPDX-License-Identifier: MIT-0
26
+ -->
22
27
  ```ruby
23
28
  def run
24
29
  RatatuiRuby.run do |tui|
@@ -33,11 +38,17 @@ def run
33
38
  end
34
39
  end
35
40
  ```
41
+ <!-- SPDX-SnippetEnd -->
36
42
 
37
43
  **Phase 1: Layout Calculation**
38
44
 
39
45
  Call this inside your `draw` block. It uses the current terminal area provided by the frame:
40
46
 
47
+ <!-- SPDX-SnippetBegin -->
48
+ <!--
49
+ SPDX-FileCopyrightText: 2026 Kerrick Long
50
+ SPDX-License-Identifier: MIT-0
51
+ -->
41
52
  ```ruby
42
53
  def calculate_layout(area)
43
54
  # Main area vs sidebar (70% / 30%)
@@ -61,22 +72,34 @@ def calculate_layout(area)
61
72
  )
62
73
  end
63
74
  ```
75
+ <!-- SPDX-SnippetEnd -->
64
76
 
65
77
  **Phase 2: Rendering**
66
78
 
67
79
  Reuse the cached rects. Build and draw:
68
80
 
81
+ <!-- SPDX-SnippetBegin -->
82
+ <!--
83
+ SPDX-FileCopyrightText: 2026 Kerrick Long
84
+ SPDX-License-Identifier: MIT-0
85
+ -->
69
86
  ```ruby
70
87
  def render(frame)
71
88
  frame.render_widget(build_widget(@left_rect), @left_rect)
72
89
  frame.render_widget(build_widget(@right_rect), @right_rect)
73
90
  end
74
91
  ```
92
+ <!-- SPDX-SnippetEnd -->
75
93
 
76
94
  **Phase 3: Event Handling**
77
95
 
78
96
  Reuse the cached rects. Test clicks:
79
97
 
98
+ <!-- SPDX-SnippetBegin -->
99
+ <!--
100
+ SPDX-FileCopyrightText: 2025 Kerrick Long
101
+ SPDX-License-Identifier: MIT-0
102
+ -->
80
103
  ```ruby
81
104
  def handle_input
82
105
  event = RatatuiRuby.poll_event
@@ -93,6 +116,7 @@ def handle_input
93
116
  end
94
117
  end
95
118
  ```
119
+ <!-- SPDX-SnippetEnd -->
96
120
 
97
121
  ### Why This Matters
98
122
 
@@ -105,6 +129,11 @@ end
105
129
 
106
130
  `Layout.split` computes layout geometry without rendering. It returns an array of `Rect` objects. While you can call `RatatuiRuby::Layout.split` directly, we recommend using the `TUI` helper (`tui.layout_split`) for cleaner application code.
107
131
 
132
+ <!-- SPDX-SnippetBegin -->
133
+ <!--
134
+ SPDX-FileCopyrightText: 2026 Kerrick Long
135
+ SPDX-License-Identifier: MIT-0
136
+ -->
108
137
  ```ruby
109
138
  # Preferred (TUI API)
110
139
  left, right = tui.layout_split(area, constraints: [...])
@@ -112,5 +141,6 @@ left, right = tui.layout_split(area, constraints: [...])
112
141
  # Manual (Core API)
113
142
  left, right = RatatuiRuby::Layout.split(area, constraints: [...])
114
143
  ```
144
+ <!-- SPDX-SnippetEnd -->
115
145
 
116
146
  Use it to establish the single source of truth inside your `draw` block. Store the results in instance variables and reuse them in both `render` and `handle_input`.
@@ -1,5 +1,5 @@
1
1
  <!--
2
- SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
3
  SPDX-License-Identifier: CC-BY-SA-4.0
4
4
  -->
5
5
 
@@ -32,6 +32,11 @@ Every audit begins with a question:
32
32
 
33
33
  Every RatatuiRuby feature has three layers. Gaps can occur at any layer:
34
34
 
35
+ <!-- SPDX-SnippetBegin -->
36
+ <!--
37
+ SPDX-FileCopyrightText: 2026 Kerrick Long
38
+ SPDX-License-Identifier: MIT-0
39
+ -->
35
40
  ```
36
41
  ┌─────────────────────────────┐
37
42
  │ Ruby API (lib/**/*.rb) │ ← What users see
@@ -41,6 +46,7 @@ Every RatatuiRuby feature has three layers. Gaps can occur at any layer:
41
46
  │ Upstream Ratatui │ ← Source of truth
42
47
  └─────────────────────────────┘
43
48
  ```
49
+ <!-- SPDX-SnippetEnd -->
44
50
 
45
51
  ### Layer 1: Ruby API Gaps
46
52
  Ruby doesn't expose a parameter that upstream supports.
@@ -1,5 +1,5 @@
1
1
  <!--
2
- SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
3
  SPDX-License-Identifier: CC-BY-SA-4.0
4
4
  -->
5
5
 
@@ -39,11 +39,17 @@ Located in `lib/ratatui_ruby/widgets/`, `lib/ratatui_ruby/layout/`, etc.
39
39
 
40
40
  These are the actual `Data.define` classes that the Rust backend expects. They have deep, explicit namespaces that match Ratatui:
41
41
 
42
+ <!-- SPDX-SnippetBegin -->
43
+ <!--
44
+ SPDX-FileCopyrightText: 2026 Kerrick Long
45
+ SPDX-License-Identifier: MIT-0
46
+ -->
42
47
  ```ruby
43
48
  RatatuiRuby::Widgets::Paragraph.new(text: "Hello")
44
49
  RatatuiRuby::Layout::Constraint.length(20)
45
50
  RatatuiRuby::Style::Style.new(fg: :red)
46
51
  ```
52
+ <!-- SPDX-SnippetEnd -->
47
53
 
48
54
  **Layer 2: TUI Facade (The DSL)**
49
55
 
@@ -51,6 +57,11 @@ Located in `lib/ratatui_ruby/tui.rb` and `lib/ratatui_ruby/tui/*.rb` mixins.
51
57
 
52
58
  The `TUI` class provides shorthand factory methods that hide namespace verbosity:
53
59
 
60
+ <!-- SPDX-SnippetBegin -->
61
+ <!--
62
+ SPDX-FileCopyrightText: 2026 Kerrick Long
63
+ SPDX-License-Identifier: MIT-0
64
+ -->
54
65
  ```ruby
55
66
  RatatuiRuby.run do |tui|
56
67
  tui.paragraph(text: "Hello")
@@ -58,6 +69,7 @@ RatatuiRuby.run do |tui|
58
69
  tui.style(fg: :red)
59
70
  end
60
71
  ```
72
+ <!-- SPDX-SnippetEnd -->
61
73
 
62
74
  **Why This Matters:**
63
75
 
@@ -69,6 +81,11 @@ The TUI facade uses explicit factory method definitions, not runtime metaprogram
69
81
 
70
82
  **What We Do:**
71
83
 
84
+ <!-- SPDX-SnippetBegin -->
85
+ <!--
86
+ SPDX-FileCopyrightText: 2026 Kerrick Long
87
+ SPDX-License-Identifier: MIT-0
88
+ -->
72
89
  ```ruby
73
90
  # lib/ratatui_ruby/tui/widget_factories.rb
74
91
  module RatatuiRuby
@@ -85,15 +102,22 @@ module RatatuiRuby
85
102
  end
86
103
  end
87
104
  ```
105
+ <!-- SPDX-SnippetEnd -->
88
106
 
89
107
  **What We Don't Do:**
90
108
 
109
+ <!-- SPDX-SnippetBegin -->
110
+ <!--
111
+ SPDX-FileCopyrightText: 2026 Kerrick Long
112
+ SPDX-License-Identifier: MIT-0
113
+ -->
91
114
  ```ruby
92
115
  # NO: Dynamic method generation
93
116
  RatatuiRuby.constants.each do |const|
94
117
  define_method(const.underscore) { |**kw| RatatuiRuby.const_get(const).new(**kw) }
95
118
  end
96
119
  ```
120
+ <!-- SPDX-SnippetEnd -->
97
121
 
98
122
  **Benefits of Explicit Definitions:**
99
123
 
@@ -109,6 +133,11 @@ All UI components are pure, immutable `Data.define` value objects. They describe
109
133
 
110
134
  **Widgets Are Inputs:**
111
135
 
136
+ <!-- SPDX-SnippetBegin -->
137
+ <!--
138
+ SPDX-FileCopyrightText: 2026 Kerrick Long
139
+ SPDX-License-Identifier: MIT-0
140
+ -->
112
141
  ```ruby
113
142
  # This is just data. It has no behavior, no side effects.
114
143
  paragraph = RatatuiRuby::Widgets::Paragraph.new(
@@ -119,11 +148,17 @@ paragraph = RatatuiRuby::Widgets::Paragraph.new(
119
148
  # Pass to renderer as input
120
149
  frame.render_widget(paragraph, area)
121
150
  ```
151
+ <!-- SPDX-SnippetEnd -->
122
152
 
123
153
  **Immediate Mode Loop:**
124
154
 
125
155
  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.
126
156
 
157
+ <!-- SPDX-SnippetBegin -->
158
+ <!--
159
+ SPDX-FileCopyrightText: 2026 Kerrick Long
160
+ SPDX-License-Identifier: MIT-0
161
+ -->
127
162
  ```ruby
128
163
  loop do
129
164
  tui.draw do |frame|
@@ -133,6 +168,7 @@ loop do
133
168
  break if tui.poll_event.key? && tui.poll_event.code == "q"
134
169
  end
135
170
  ```
171
+ <!-- SPDX-SnippetEnd -->
136
172
 
137
173
  ### 5. Separation of Configuration and Status
138
174
 
@@ -142,14 +178,25 @@ Widgets (Configuration) and State (Status) are strictly separated.
142
178
 
143
179
  Widgets define *what* to render. They are created, rendered, and discarded.
144
180
 
181
+ <!-- SPDX-SnippetBegin -->
182
+ <!--
183
+ SPDX-FileCopyrightText: 2026 Kerrick Long
184
+ SPDX-License-Identifier: MIT-0
185
+ -->
145
186
  ```ruby
146
187
  list = tui.list(items: ["A", "B", "C", "D", "E"])
147
188
  ```
189
+ <!-- SPDX-SnippetEnd -->
148
190
 
149
191
  **Status (Output):**
150
192
 
151
193
  State objects track *runtime metrics* computed by the Rust backend: scroll offsets, selection positions, etc. They persist across frames.
152
194
 
195
+ <!-- SPDX-SnippetBegin -->
196
+ <!--
197
+ SPDX-FileCopyrightText: 2026 Kerrick Long
198
+ SPDX-License-Identifier: MIT-0
199
+ -->
153
200
  ```ruby
154
201
  # Created once
155
202
  @list_state = RatatuiRuby::ListState.new
@@ -160,6 +207,7 @@ frame.render_stateful_widget(list, area, @list_state)
160
207
  # Read back computed values
161
208
  puts "Scroll offset: #{@list_state.offset}"
162
209
  ```
210
+ <!-- SPDX-SnippetEnd -->
163
211
 
164
212
  **Precedence Rule:**
165
213
 
@@ -188,6 +236,11 @@ This separation ensures rendering performance remains in Rust while Ruby handles
188
236
 
189
237
  ## Directory Structure
190
238
 
239
+ <!-- SPDX-SnippetBegin -->
240
+ <!--
241
+ SPDX-FileCopyrightText: 2026 Kerrick Long
242
+ SPDX-License-Identifier: MIT-0
243
+ -->
191
244
  ```
192
245
  lib/ratatui_ruby/
193
246
  ├── tui.rb # TUI class, includes all mixins
@@ -221,6 +274,7 @@ lib/ratatui_ruby/
221
274
  │ └── cell.rb # For get_cell_at inspection
222
275
  └── schema/ # Legacy location (being migrated)
223
276
  ```
277
+ <!-- SPDX-SnippetEnd -->
224
278
 
225
279
  ---
226
280
 
@@ -230,6 +284,11 @@ lib/ratatui_ruby/
230
284
 
231
285
  Define the Data class in the appropriate namespace directory:
232
286
 
287
+ <!-- SPDX-SnippetBegin -->
288
+ <!--
289
+ SPDX-FileCopyrightText: 2026 Kerrick Long
290
+ SPDX-License-Identifier: MIT-0
291
+ -->
233
292
  ```ruby
234
293
  # lib/ratatui_ruby/widgets/my_widget.rb
235
294
  module RatatuiRuby
@@ -247,9 +306,15 @@ module RatatuiRuby
247
306
  end
248
307
  end
249
308
  ```
309
+ <!-- SPDX-SnippetEnd -->
250
310
 
251
311
  ### Step 2: Add the RBS Type
252
312
 
313
+ <!-- SPDX-SnippetBegin -->
314
+ <!--
315
+ SPDX-FileCopyrightText: 2026 Kerrick Long
316
+ SPDX-License-Identifier: MIT-0
317
+ -->
253
318
  ```rbs
254
319
  # sig/ratatui_ruby/widgets/my_widget.rbs
255
320
  module RatatuiRuby
@@ -264,15 +329,22 @@ module RatatuiRuby
264
329
  end
265
330
  end
266
331
  ```
332
+ <!-- SPDX-SnippetEnd -->
267
333
 
268
334
  ### Step 3: Add the TUI Factory Method
269
335
 
336
+ <!-- SPDX-SnippetBegin -->
337
+ <!--
338
+ SPDX-FileCopyrightText: 2026 Kerrick Long
339
+ SPDX-License-Identifier: MIT-0
340
+ -->
270
341
  ```ruby
271
342
  # lib/ratatui_ruby/tui/widget_factories.rb
272
343
  def my_widget(**kwargs)
273
344
  Widgets::MyWidget.new(**kwargs)
274
345
  end
275
346
  ```
347
+ <!-- SPDX-SnippetEnd -->
276
348
 
277
349
  ### Step 4: Implement Rust Rendering
278
350
 
@@ -282,9 +354,15 @@ See `rust_backend.md` for the Rust implementation steps.
282
354
 
283
355
  Add to `lib/ratatui_ruby.rb`:
284
356
 
357
+ <!-- SPDX-SnippetBegin -->
358
+ <!--
359
+ SPDX-FileCopyrightText: 2026 Kerrick Long
360
+ SPDX-License-Identifier: MIT-0
361
+ -->
285
362
  ```ruby
286
363
  require_relative "ratatui_ruby/widgets/my_widget"
287
364
  ```
365
+ <!-- SPDX-SnippetEnd -->
288
366
 
289
367
  ---
290
368
 
@@ -324,6 +402,11 @@ These have side effects and are intentionally not Ractor-safe:
324
402
  - `TUI` — Has terminal I/O methods
325
403
  - `Frame` — Valid only during the `draw` block; invalid after
326
404
 
405
+ <!-- SPDX-SnippetBegin -->
406
+ <!--
407
+ SPDX-FileCopyrightText: 2026 Kerrick Long
408
+ SPDX-License-Identifier: MIT-0
409
+ -->
327
410
  ```ruby
328
411
  # OK: Cache TUI during run loop
329
412
  RatatuiRuby.run do |tui|
@@ -334,3 +417,4 @@ end
334
417
  # NOT OK: Include in immutable Model
335
418
  Model = Data.define(:tui, :count) # Don't do this
336
419
  ```
420
+ <!-- SPDX-SnippetEnd -->