ratatui_ruby 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +2 -1
  7. data/CHANGELOG.md +113 -0
  8. data/README.md +17 -0
  9. data/REUSE.toml +5 -0
  10. data/Rakefile +1 -1
  11. data/Steepfile +49 -0
  12. data/doc/concepts/debugging.md +401 -0
  13. data/doc/getting_started/quickstart.md +8 -3
  14. data/doc/images/app_all_events.png +0 -0
  15. data/doc/images/app_color_picker.png +0 -0
  16. data/doc/images/app_debugging_showcase.gif +0 -0
  17. data/doc/images/app_debugging_showcase.png +0 -0
  18. data/doc/images/app_login_form.png +0 -0
  19. data/doc/images/app_stateful_interaction.png +0 -0
  20. data/doc/images/verify_quickstart_dsl.png +0 -0
  21. data/doc/images/verify_quickstart_layout.png +0 -0
  22. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  23. data/doc/images/verify_readme_usage.png +0 -0
  24. data/doc/images/widget_barchart.png +0 -0
  25. data/doc/images/widget_block.png +0 -0
  26. data/doc/images/widget_box.png +0 -0
  27. data/doc/images/widget_calendar.png +0 -0
  28. data/doc/images/widget_canvas.png +0 -0
  29. data/doc/images/widget_cell.png +0 -0
  30. data/doc/images/widget_center.png +0 -0
  31. data/doc/images/widget_chart.png +0 -0
  32. data/doc/images/widget_gauge.png +0 -0
  33. data/doc/images/widget_layout_split.png +0 -0
  34. data/doc/images/widget_line_gauge.png +0 -0
  35. data/doc/images/widget_list.png +0 -0
  36. data/doc/images/widget_map.png +0 -0
  37. data/doc/images/widget_overlay.png +0 -0
  38. data/doc/images/widget_popup.png +0 -0
  39. data/doc/images/widget_ratatui_logo.png +0 -0
  40. data/doc/images/widget_ratatui_mascot.png +0 -0
  41. data/doc/images/widget_rect.png +0 -0
  42. data/doc/images/widget_render.png +0 -0
  43. data/doc/images/widget_rich_text.png +0 -0
  44. data/doc/images/widget_scroll_text.png +0 -0
  45. data/doc/images/widget_scrollbar.png +0 -0
  46. data/doc/images/widget_sparkline.png +0 -0
  47. data/doc/images/widget_style_colors.png +0 -0
  48. data/doc/images/widget_table.png +0 -0
  49. data/doc/images/widget_tabs.png +0 -0
  50. data/doc/images/widget_text_width.png +0 -0
  51. data/doc/troubleshooting/async.md +4 -0
  52. data/examples/app_debugging_showcase/README.md +119 -0
  53. data/examples/app_debugging_showcase/app.rb +318 -0
  54. data/examples/widget_canvas/app.rb +19 -14
  55. data/examples/widget_gauge/app.rb +18 -3
  56. data/examples/widget_layout_split/app.rb +10 -4
  57. data/examples/widget_list/app.rb +22 -6
  58. data/examples/widget_rect/app.rb +7 -6
  59. data/examples/widget_rich_text/app.rb +62 -37
  60. data/examples/widget_style_colors/app.rb +26 -47
  61. data/examples/widget_table/app.rb +28 -5
  62. data/examples/widget_text_width/app.rb +6 -4
  63. data/ext/ratatui_ruby/Cargo.lock +48 -1
  64. data/ext/ratatui_ruby/Cargo.toml +6 -2
  65. data/ext/ratatui_ruby/src/color.rs +82 -0
  66. data/ext/ratatui_ruby/src/errors.rs +28 -0
  67. data/ext/ratatui_ruby/src/events.rs +15 -14
  68. data/ext/ratatui_ruby/src/lib.rs +56 -0
  69. data/ext/ratatui_ruby/src/rendering.rs +3 -1
  70. data/ext/ratatui_ruby/src/style.rs +48 -21
  71. data/ext/ratatui_ruby/src/terminal.rs +40 -9
  72. data/ext/ratatui_ruby/src/text.rs +21 -9
  73. data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
  74. data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
  75. data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
  76. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  77. data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
  78. data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
  79. data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
  80. data/lib/ratatui_ruby/buffer/cell.rb +25 -15
  81. data/lib/ratatui_ruby/buffer.rb +134 -2
  82. data/lib/ratatui_ruby/cell.rb +13 -5
  83. data/lib/ratatui_ruby/debug.rb +215 -0
  84. data/lib/ratatui_ruby/event/key.rb +3 -2
  85. data/lib/ratatui_ruby/event.rb +1 -1
  86. data/lib/ratatui_ruby/layout/constraint.rb +49 -0
  87. data/lib/ratatui_ruby/layout/layout.rb +119 -13
  88. data/lib/ratatui_ruby/layout/position.rb +55 -0
  89. data/lib/ratatui_ruby/layout/rect.rb +188 -0
  90. data/lib/ratatui_ruby/layout/size.rb +55 -0
  91. data/lib/ratatui_ruby/layout.rb +4 -0
  92. data/lib/ratatui_ruby/style/color.rb +149 -0
  93. data/lib/ratatui_ruby/style/style.rb +51 -4
  94. data/lib/ratatui_ruby/style.rb +2 -0
  95. data/lib/ratatui_ruby/symbols.rb +435 -0
  96. data/lib/ratatui_ruby/synthetic_events.rb +1 -1
  97. data/lib/ratatui_ruby/table_state.rb +51 -0
  98. data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
  99. data/lib/ratatui_ruby/test_helper/event_injection.rb +6 -1
  100. data/lib/ratatui_ruby/test_helper.rb +9 -0
  101. data/lib/ratatui_ruby/text/line.rb +245 -0
  102. data/lib/ratatui_ruby/text/span.rb +158 -0
  103. data/lib/ratatui_ruby/text.rb +99 -0
  104. data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
  105. data/lib/ratatui_ruby/tui/core.rb +13 -2
  106. data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
  107. data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
  108. data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
  109. data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
  110. data/lib/ratatui_ruby/tui.rb +22 -1
  111. data/lib/ratatui_ruby/version.rb +1 -1
  112. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  113. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  114. data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
  115. data/lib/ratatui_ruby/widgets/block.rb +14 -6
  116. data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
  117. data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
  118. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  119. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  120. data/lib/ratatui_ruby/widgets/chart.rb +6 -0
  121. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  122. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  123. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  124. data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
  125. data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
  126. data/lib/ratatui_ruby/widgets/list.rb +87 -3
  127. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  128. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  129. data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
  130. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
  131. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
  132. data/lib/ratatui_ruby/widgets/row.rb +45 -0
  133. data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
  134. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  135. data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
  136. data/lib/ratatui_ruby/widgets/table.rb +13 -3
  137. data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
  138. data/lib/ratatui_ruby/widgets.rb +1 -0
  139. data/lib/ratatui_ruby.rb +42 -11
  140. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  141. data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
  142. data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
  143. data/sig/examples/app_all_events/view.rbs +1 -1
  144. data/sig/examples/app_stateful_interaction/app.rbs +5 -5
  145. data/sig/examples/widget_block_demo/app.rbs +6 -6
  146. data/sig/manifest.yaml +5 -0
  147. data/sig/patches/data.rbs +26 -0
  148. data/sig/patches/debugger__.rbs +8 -0
  149. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  150. data/sig/ratatui_ruby/buffer.rbs +18 -0
  151. data/sig/ratatui_ruby/cell.rbs +44 -0
  152. data/sig/ratatui_ruby/clear.rbs +18 -0
  153. data/sig/ratatui_ruby/constraint.rbs +26 -0
  154. data/sig/ratatui_ruby/debug.rbs +45 -0
  155. data/sig/ratatui_ruby/draw.rbs +30 -0
  156. data/sig/ratatui_ruby/event.rbs +68 -8
  157. data/sig/ratatui_ruby/frame.rbs +4 -4
  158. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  159. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  160. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  161. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  162. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  163. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  164. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  165. data/sig/ratatui_ruby/ratatui_ruby.rbs +84 -5
  166. data/sig/ratatui_ruby/rect.rbs +17 -0
  167. data/sig/ratatui_ruby/style/color.rbs +22 -0
  168. data/sig/ratatui_ruby/style/style.rbs +29 -0
  169. data/sig/ratatui_ruby/symbols.rbs +141 -0
  170. data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
  171. data/sig/ratatui_ruby/table_state.rbs +6 -0
  172. data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
  173. data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
  174. data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
  175. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
  176. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
  177. data/sig/ratatui_ruby/text/line.rbs +27 -0
  178. data/sig/ratatui_ruby/text/span.rbs +23 -0
  179. data/sig/ratatui_ruby/text.rbs +12 -0
  180. data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
  181. data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
  182. data/sig/ratatui_ruby/tui/core.rbs +2 -2
  183. data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
  184. data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
  185. data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
  186. data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
  187. data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
  188. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  189. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  190. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  191. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  192. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  193. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  194. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  195. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  196. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  197. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  198. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  199. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  200. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  201. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  202. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  203. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  204. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  205. data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
  206. data/tasks/steep.rake +11 -0
  207. metadata +80 -63
  208. data/doc/contributors/v1.0.0_blockers.md +0 -870
  209. data/doc/troubleshooting/debugging.md +0 -101
  210. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
  211. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
  212. data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
  213. data/lib/ratatui_ruby/schema/block.rb +0 -198
  214. data/lib/ratatui_ruby/schema/calendar.rb +0 -84
  215. data/lib/ratatui_ruby/schema/canvas.rb +0 -239
  216. data/lib/ratatui_ruby/schema/center.rb +0 -67
  217. data/lib/ratatui_ruby/schema/chart.rb +0 -159
  218. data/lib/ratatui_ruby/schema/clear.rb +0 -62
  219. data/lib/ratatui_ruby/schema/constraint.rb +0 -151
  220. data/lib/ratatui_ruby/schema/cursor.rb +0 -50
  221. data/lib/ratatui_ruby/schema/gauge.rb +0 -72
  222. data/lib/ratatui_ruby/schema/layout.rb +0 -122
  223. data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
  224. data/lib/ratatui_ruby/schema/list.rb +0 -135
  225. data/lib/ratatui_ruby/schema/list_item.rb +0 -51
  226. data/lib/ratatui_ruby/schema/overlay.rb +0 -51
  227. data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
  228. data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
  229. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
  230. data/lib/ratatui_ruby/schema/rect.rb +0 -174
  231. data/lib/ratatui_ruby/schema/row.rb +0 -76
  232. data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
  233. data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
  234. data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
  235. data/lib/ratatui_ruby/schema/style.rb +0 -97
  236. data/lib/ratatui_ruby/schema/table.rb +0 -141
  237. data/lib/ratatui_ruby/schema/tabs.rb +0 -85
  238. data/lib/ratatui_ruby/schema/text.rb +0 -217
  239. data/sig/examples/app_all_events/model/events.rbs +0 -15
  240. data/sig/examples/app_all_events/view_state.rbs +0 -21
  241. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
  242. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
  243. data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
  244. data/sig/ratatui_ruby/schema/block.rbs +0 -18
  245. data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
  246. data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
  247. data/sig/ratatui_ruby/schema/center.rbs +0 -17
  248. data/sig/ratatui_ruby/schema/chart.rbs +0 -39
  249. data/sig/ratatui_ruby/schema/constraint.rbs +0 -30
  250. data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
  251. data/sig/ratatui_ruby/schema/draw.rbs +0 -33
  252. data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
  253. data/sig/ratatui_ruby/schema/layout.rbs +0 -27
  254. data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
  255. data/sig/ratatui_ruby/schema/list.rbs +0 -28
  256. data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
  257. data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
  258. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
  259. data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
  260. data/sig/ratatui_ruby/schema/rect.rbs +0 -48
  261. data/sig/ratatui_ruby/schema/row.rbs +0 -28
  262. data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
  263. data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
  264. data/sig/ratatui_ruby/schema/style.rbs +0 -19
  265. data/sig/ratatui_ruby/schema/table.rbs +0 -32
  266. data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
  267. data/sig/ratatui_ruby/schema/text.rbs +0 -31
  268. /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
@@ -1,870 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # alignment_audit
7
- ## Legacy Migrations
8
-
9
- ### Outstanding
10
-
11
- - Migrate away from `schema/` folder structure as mentioned in ruby_frontend.md
12
-
13
- ## alignment_audit
14
- - Symbol Sets
15
- - line::Set, block::Set, scrollbar::Set
16
- - Layout
17
- - ~~Constraint batch constructors (6)~~ ✅ DONE
18
- - Layout margin, spacing (2)
19
- - Style
20
- - sub_modifier, underline_color (2)
21
- - Text
22
- - Span methods (4)
23
- - Line methods (6)
24
-
25
- ### alignment_audit_granular_level
26
- ##### v0.7.0 Alignment Audit (Parameter-Level)
27
-
28
- This document audits alignment between RatatuiRuby v0.7.0 and the upstream Ratatui/Crossterm Rust libraries at the **parameter and enum value level**. Only gaps are listed.
29
-
30
- ---
31
-
32
-
33
- ###### MISSING — Layout Module
34
-
35
-
36
-
37
- ###### `Layout::Constraint` — Batch Constructors ✅ DONE
38
-
39
- All 6 batch constructors implemented in v0.8.0:
40
- - `from_lengths`, `from_percentages`, `from_mins`, `from_maxes`, `from_fills`, `from_ratios`
41
-
42
- ---
43
-
44
- ###### `Layout::Layout` — Missing Parameters
45
-
46
- | Missing Parameter | Ratatui Type | Notes |
47
- |-------------------|--------------|-------|
48
- | `margin` | `Margin { horizontal, vertical }` | Edge margins |
49
- | `spacing` | `u16` | Gap between segments |
50
-
51
- ---
52
-
53
- ###### MISSING — Style Module
54
-
55
- ###### `Style::Style` — Missing Parameters/Methods
56
-
57
- ###### MISSING — Text Module
58
-
59
- ###### `Text::Span` — Missing Methods
60
-
61
- | Missing Method | Signature | Notes |
62
- |----------------|-----------|-------|
63
- | `width` | `span.width` → `Integer` | Display width in terminal cells |
64
- | `raw` | `Span.raw(content)` → `Span` | Constructor without style (alias for `new(content:)`) |
65
- | `patch_style` | `span.patch_style(style)` → `Span` | Merge style on top of existing |
66
- | `reset_style` | `span.reset_style` → `Span` | Clear style |
67
-
68
- ---
69
-
70
- ###### `Text::Line` — Missing Methods
71
-
72
- | Missing Method | Signature | Notes |
73
- |----------------|-----------|-------|
74
- | `left_aligned` | `line.left_aligned` → `Line` | Fluent setter for `:left` alignment |
75
- | `centered` | `line.centered` → `Line` | Fluent setter for `:center` alignment |
76
- | `right_aligned` | `line.right_aligned` → `Line` | Fluent setter for `:right` alignment |
77
- | `push_span` | `line.push_span(span)` → `Line` | Append span |
78
- | `patch_style` | `line.patch_style(style)` → `Line` | Merge style on all spans |
79
- | `reset_style` | `line.reset_style` → `Line` | Clear style on all spans |
80
-
81
- ---
82
-
83
- ---
84
-
85
- ###### `Widgets::Table` — Missing Row Methods (via `Widgets::Row`)
86
-
87
- | Missing | Ratatui API | Notes |
88
- |---------|-------------|-------|
89
- | `enable_strikethrough` | `row.enable_strikethrough()` | Enable strikethrough on row |
90
-
91
- ---
92
-
93
-
94
-
95
- ###### Widget Computation Methods — Missing
96
-
97
- These are public `&self` methods on upstream widgets that compute/query values without rendering.
98
-
99
- | Widget | Missing Method | Ratatui API | Notes |
100
- |--------|----------------|-------------|-------|
101
-
102
- | `List` | `len` | `list.len()` → `usize` | Number of items in the list |
103
- | `List` | `is_empty` | `list.is_empty()` → `bool` | True if no items (`empty?` in Ruby) |
104
- | `Canvas` | `get_point` | `canvas.get_point(x, y)` → `Option<(usize, usize)>` | Map coordinates to grid cell |
105
-
106
- > [!NOTE]
107
- > `Paragraph#line_count(width)` and `Paragraph#line_width` ARE exposed ✓
108
- > `Tabs#width` IS exposed ✓
109
-
110
- ---
111
-
112
- ###### State Navigation Methods — Missing
113
-
114
- TableState has navigation helpers that are not exposed.
115
-
116
- | State Class | Missing Method | Ratatui API | Notes |
117
- |-------------|----------------|-------------|-------|
118
- | `TableState` | `selected_cell` | `state.selected_cell()` | Get (row, col) tuple |
119
- | `TableState` | `with_selected_cell` | `state.with_selected_cell((r,c))` | Builder pattern |
120
- | `TableState` | `select_next_column` | `state.select_next_column()` | Navigate columns |
121
- | `TableState` | `select_previous_column` | `state.select_previous_column()` | Navigate columns |
122
- | `TableState` | `select_first_column` | `state.select_first_column()` | Jump to first column |
123
- | `TableState` | `select_last_column` | `state.select_last_column()` | Jump to last column |
124
-
125
- ---
126
-
127
- ###### Layout Module — Additional Missing
128
-
129
- | Module | Missing | Ratatui API | Notes |
130
- |--------|---------|-------------|-------|
131
- | `Rect` | `as_position` | `rect.as_position()` → `Position` | Convert to Position |
132
- | `Rect` | `as_size` | `rect.as_size()` → `Size` | Convert to Size |
133
- | `Rect` | `outer` | `rect.outer(margin)` → `Rect` | Expand by margin (inverse of `inner`) |
134
- | `Rect` | `resize` | `rect.resize(size)` → `Rect` | Change dimensions, preserve position |
135
- | `Rect` | `centered_horizontally` | `rect.centered_horizontally(constraint)` → `Rect` | Center horizontally via Layout |
136
- | `Rect` | `centered_vertically` | `rect.centered_vertically(constraint)` → `Rect` | Center vertically via Layout |
137
- | `Rect` | `centered` | `rect.centered(h, v)` → `Rect` | Center both axes |
138
- | `Constraint` | `apply` | `constraint.apply(length)` → `u16` | Compute constrained size |
139
- | `Layout` | `split_with_spacers` | `layout.split_with_spacers(area)` | Returns segments AND spacers |
140
-
141
- ---
142
-
143
-
144
-
145
- ###### Color — Missing Constructors
146
-
147
- | Missing | Ratatui API | Notes |
148
- |---------|-------------|-------|
149
- | `from_u32` | `Color::from_u32(0xRRGGBB)` | Construct from hex integer |
150
- | `from_hsl` | `Color::from_hsl(hsl)` | Construct from HSL (requires palette feature) |
151
- | `from_hsluv` | `Color::from_hsluv(hsluv)` | Construct from HSLuv (requires palette feature) |
152
-
153
- ---
154
-
155
- ###### Buffer — Missing Query Methods
156
-
157
- | Missing Method | Ratatui API | Notes |
158
- |----------------|-------------|-------|
159
- | `content` | `buffer.content()` → `&[Cell]` | Get all cells as slice |
160
- | `get` | `buffer.get(x, y)` → `&Cell` | Get cell at position |
161
- | `index_of` | `buffer.index_of(x, y)` → `usize` | Position to linear index |
162
- | `pos_of` | `buffer.pos_of(i)` → `(u16, u16)` | Linear index to position |
163
-
164
- ---
165
-
166
-
167
- ### alignment_audit_high_level
168
- ##### v0.7.0 Alignment Audit
169
-
170
- This document audits strict alignment between RatatuiRuby v0.7.0 and the upstream Ratatui/Crossterm Rust libraries. The audit covers modules, classes, static methods, and constructor arguments as specified in the [Ruby Frontend Design](../design/ruby_frontend.md#1-ratatui-alignment).
171
-
172
- > [!NOTE]
173
- > The TUI facade API is explicitly excluded from this audit. It provides ergonomic shortcuts that intentionally diverge from Ratatui naming.
174
-
175
- ---
176
- ---
177
- | `margin` | `Margin` | ❌ Missing | Gap |
178
- | `spacing` | `u16` | ❌ Missing | Gap |
179
-
180
- **Verdict**: Core layout aligned. Margin and spacing are gaps.
181
-
182
- ---
183
-
184
-
185
- ---
186
-
187
- ###### Gaps Analysis: MISSING vs MISALIGNED
188
-
189
- > [!IMPORTANT]
190
- > **MISSING** = Can be added as new features without breaking backwards compatibility.
191
- > **MISALIGNED** = Requires breaking changes before v1.0.0 to fix API shape.
192
-
193
- ###### MISSING Features (Additive, Backwards-Compatible) ✅
194
-
195
- These are gaps that can be filled in future minor releases without breaking existing code:
196
-
197
- | Component | Missing Feature | Notes |
198
- |-----------|-----------------|-------|
199
- | `Constraint` | `from_lengths()`, `from_percentages()`, etc. | New class methods |
200
- | `Layout` | `margin`, `spacing` | New optional constructor args |
201
- | `Style` | `sub_modifier`, `underline_color` | New optional constructor args |
202
- | `Span` | `width()` instance method | New instance method |
203
- | `Span` | `raw()` constructor | New class method (alias for `new`) |
204
- | `Line` | `left_aligned()`, `centered()`, `right_aligned()` | New instance methods (fluent) |
205
-
206
-
207
-
208
- ### alignment_audit_symbol_sets
209
- Audit of symbol set alignment between Ratatui's `symbols::` module and RatatuiRuby.
210
-
211
- > [!IMPORTANT]
212
- > **MISSING** = Can be added as new features, backwards-compatible.
213
- > **MISALIGNED** = Requires breaking changes before v1.0.0.
214
-
215
- | `scrollbar::Set` | 4 predefined sets | ❌ Not exposed | MISSING |
216
- | `shade` constants | 5 constants | ❌ Not exposed | MISSING |
217
-
218
-
219
- ###### MISSING — Marker Enum
220
-
221
- ###### `Marker::HalfBlock`
222
-
223
- | `Marker::HalfBlock` | ❌ Not exposed | MISSING |
224
-
225
- **Impact**: Users cannot use the `HalfBlock` marker type, which provides double-resolution square pixels using `█`, `▄`, and `▀` characters.
226
-
227
- ---
228
-
229
- ###### MISSING — Line Set Customization
230
-
231
- Ratatui provides `symbols::line::Set` with predefined sets:
232
- - `NORMAL` — Standard box-drawing characters
233
- - `ROUNDED` — Rounded corners
234
- - `DOUBLE` — Double-line characters
235
- - `THICK` — Thick line characters
236
-
237
- **Ruby Status**: Not directly exposed. Users cannot customize line symbols for widgets that use them internally.
238
-
239
- ---
240
-
241
- ###### MISSING — Bar Set Customization
242
-
243
- Ratatui provides `symbols::bar::Set` with predefined sets:
244
- - `THREE_LEVELS` — 3 distinct fill levels
245
- - `NINE_LEVELS` — 9 distinct fill levels (default)
246
-
247
- **Ruby Status**: Not exposed. Used internally by widgets like `Sparkline` but not configurable.
248
-
249
- ---
250
-
251
- ###### MISSING — Block Set Customization
252
-
253
- Ratatui provides `symbols::block::Set` with predefined sets:
254
- - `THREE_LEVELS` — 3 distinct fill levels
255
- - `NINE_LEVELS` — 9 distinct fill levels (default)
256
-
257
- **Ruby Status**: Not exposed. Used internally by `Gauge` widget but not configurable.
258
-
259
- ---
260
-
261
- ###### MISSING — Scrollbar Set Customization
262
-
263
- Ratatui provides `symbols::scrollbar::Set` with predefined sets:
264
- - `DOUBLE_VERTICAL` — Double-line vertical scrollbar
265
- - `DOUBLE_HORIZONTAL` — Double-line horizontal scrollbar
266
- - `VERTICAL` — Single-line vertical scrollbar
267
- - `HORIZONTAL` — Single-line horizontal scrollbar
268
-
269
- **Ruby Status**: Not exposed. Scrollbar widget not currently implemented in RatatuiRuby.
270
-
271
- ---
272
-
273
- ###### MISSING — Shade Constants
274
-
275
- Ratatui provides `symbols::shade` constants:
276
- - `EMPTY` — ` ` (space)
277
- - `LIGHT` — `░`
278
- - `MEDIUM` — `▒`
279
- - `DARK` — `▓`
280
-
281
-
282
- ---
283
-
284
- ###### Recommendations
285
-
286
- | Priority | Item | Notes |
287
- |----------|------|-------|
288
- | Low | Add `:half_block` marker | Single symbol addition |
289
- | Low | Expose `line::Set` customization | For LineGauge widget |
290
- | Low | Expose `bar::Set` customization | For Sparkline widget |
291
- | Low | Expose `block::Set` customization | For Gauge widget |
292
- | Medium | Implement Scrollbar widget | Would include scrollbar::Set |
293
-
294
- All missing items are **additive** and do not require breaking changes.
295
-
296
-
297
- ---
298
-
299
-
300
- ### alignment_audit_tui_final
301
- ##### TUI API Alignment Audit
302
-
303
- This document audits the `RatatuiRuby::TUI` facade API for method and parameter naming, with a focus on **Developer Experience (DX)** before the v1.0.0 release.
304
-
305
- ###### Design Philosophy
306
-
307
- The TUI API follows a "Mullet Architecture": structured namespaces in the library, flat ergonomic DSL for users.
308
-
309
- **Guiding Principles:**
310
-
311
- 1. **Terseness** — Fewer keystrokes for common operations
312
- 2. **DWIM** — Do What I Mean; intuitive defaults
313
- 3. **TIMTOWTDI** — Multiple valid ways to express the same thing
314
- 4. **Big Tent** — Aliases for CSS/frontend developers, Ratatui natives, and Ruby purists
315
- 5. **Two Levels Max** — `tui.thing` and `tui.scope_thing`, never `tui.scope.thing`, never `tui.ascope_bscope_thing`
316
-
317
- **Breaking Changes:** Pre-1.0 with few external users. Rename aggressively for DX. Document in CHANGELOG.
318
-
319
- ---
320
-
321
- ###### Method Naming Recommendations
322
-
323
- ###### Pattern: Base Methods + Aliases + Dispatchers
324
-
325
- Base methods align with Ratatui's module API names. Aliases provide ergonomic shortcuts.
326
-
327
- 1. **Base method** (Ratatui-aligned): `tui.shape_circle(...)` — matches `Widgets::Shape::Circle`
328
- 2. **Aliases** (ergonomic): `tui.circle(...)`, `tui.circle_shape(...)`
329
- 3. **Dispatcher**: `tui.shape(:circle, ...)` — errors helpfully on missing type
330
-
331
- This pattern applies to: shapes, constraints, text elements.
332
-
333
- ---
334
-
335
- ###### Canvas Shapes
336
-
337
- | Base Method | Add Aliases | Dispatcher |
338
- |-------------|-------------|------------|
339
- | `shape_circle` | `circle`, `circle_shape` | `shape(:circle, ...)` |
340
- | `shape_line` | `line_shape` *(not bare `line` — conflicts with `Text::Line`)* | `shape(:line, ...)` |
341
- | `shape_point` | `point`, `point_shape` | `shape(:point, ...)` |
342
- | `shape_rectangle` | `rectangle`, `rectangle_shape` | `shape(:rectangle, ...)` |
343
- | `shape_map` | `map`, `map_shape` | `shape(:map, ...)` |
344
- | `shape_label` | `label`, `label_shape` | `shape(:label, ...)` |
345
-
346
- **Dispatcher signature:**
347
- ```ruby
348
- def shape(type, **kwargs)
349
- case type
350
- when :circle then shape_circle(**kwargs)
351
- when :line then shape_line(**kwargs)
352
- # ...
353
- else
354
- raise ArgumentError, "Unknown shape type: #{type.inspect}. " \
355
- "Valid types: :circle, :line, :point, :rectangle, :map, :label"
356
- end
357
- end
358
- ```
359
-
360
- ---
361
-
362
- ###### Layout Constraints
363
-
364
- Ratatui's constraints map well to CSS layout concepts. Offer aliases for both communities:
365
-
366
- | Base Method | Add Aliases (CSS-Friendly) | Dispatcher |
367
- |-------------|---------------------------|------------|
368
- | `constraint_length(n)` | `fixed(n)`, `length(n)` | `constraint(:length, n)` |
369
- | `constraint_percentage(n)` | `percent(n)`, `percentage(n)` | `constraint(:percentage, n)` |
370
- | `constraint_min(n)` | `min(n)`, `min_content(n)` | `constraint(:min, n)` |
371
- | `constraint_max(n)` | `max(n)`, `max_content(n)` | `constraint(:max, n)` |
372
- | `constraint_fill(n)` | `fill(n)`, `flex(n)`, `fr(n)` | `constraint(:fill, n)` |
373
- | `constraint_ratio(a,b)` | `ratio(a,b)`, `aspect(a,b)` | `constraint(:ratio, a, b)` |
374
-
375
- **CSS Flexbox/Grid parallels:**
376
- - `fill(1)` ≈ CSS `flex: 1` or `1fr`
377
- - `fixed(100)` ≈ CSS `width: 100px`
378
- - `min(50)` ≈ CSS `min-width: 50px`
379
- - `percent(25)` ≈ CSS `width: 25%`
380
-
381
- **Dispatcher signature:**
382
- ```ruby
383
- def constraint(type, *args)
384
- case type
385
- when :length, :fixed then constraint_length(*args)
386
- when :percentage, :percent then constraint_percentage(*args)
387
- when :min then constraint_min(*args)
388
- when :max then constraint_max(*args)
389
- when :fill, :flex, :fr then constraint_fill(*args)
390
- when :ratio, :aspect then constraint_ratio(*args)
391
- else
392
- raise ArgumentError, "Unknown constraint type: #{type.inspect}. " \
393
- "Valid types: :length, :percentage, :min, :max, :fill, :ratio"
394
- end
395
- end
396
- ```
397
-
398
- ---
399
-
400
- ###### Layout Operations
401
-
402
- | Current | Add Alias | Rationale |
403
- |---------|-----------|-----------|
404
- | `layout_split` | `split` | 52 usages in examples; clear in context |
405
-
406
- ---
407
-
408
- ###### Text Factories
409
-
410
- | Current | Status | Notes |
411
- |---------|--------|-------|
412
- | `text_span` | ✓ Has alias `span` | |
413
- | `text_line` | ✓ Has alias `line` | |
414
- | `text_width` | Keep as-is | Distinct from `length` (constraint) |
415
-
416
- Add dispatcher:
417
- ```ruby
418
- def text(type, **kwargs)
419
- case type
420
- when :span then text_span(**kwargs)
421
- when :line then text_line(**kwargs)
422
- else
423
- raise ArgumentError, "Unknown text type: #{type.inspect}. Valid types: :span, :line"
424
- end
425
- end
426
- ```
427
-
428
- ---
429
-
430
- ###### Widget Factories
431
-
432
- | Current | Add Alias | Rationale |
433
- |---------|-----------|-----------|
434
- | `list_item` | `item` | Clear in list context |
435
- | `table_row` | Keep alongside `row` | DWIM — both valid mental models |
436
- | `table_cell` | Keep as-is | `cell` means `Buffer::Cell` |
437
- | `bar_chart_bar` | Keep alongside `bar` | DWIM — don't deprecate |
438
- | `bar_chart_bar_group` | Keep alongside `bar_group` | DWIM — don't deprecate |
439
-
440
- Add dispatcher:
441
- ```ruby
442
- def widget(type, **kwargs)
443
- case type
444
- when :block then block(**kwargs)
445
- when :paragraph then paragraph(**kwargs)
446
- when :list then list(**kwargs)
447
- when :table then table(**kwargs)
448
- # ... all widgets
449
- else
450
- raise ArgumentError, "Unknown widget type: #{type.inspect}."
451
- end
452
- end
453
- ```
454
-
455
- ---
456
-
457
- ###### State Factories
458
-
459
- | Current | Status |
460
- |---------|--------|
461
- | `list_state` | Keep as-is |
462
- | `table_state` | Keep as-is |
463
- | `scrollbar_state` | Keep as-is |
464
-
465
- Add dispatcher:
466
- ```ruby
467
- def state(type, **kwargs)
468
- case type
469
- when :list then list_state(**kwargs)
470
- when :table then table_state(**kwargs)
471
- when :scrollbar then scrollbar_state(**kwargs)
472
- else
473
- raise ArgumentError, "Unknown state type: #{type.inspect}."
474
- end
475
- end
476
- ```
477
-
478
- ---
479
-
480
- ###### Parameter Names
481
-
482
- All current parameter names are well-chosen. No changes recommended.
483
-
484
- | Widget | Parameter | Status |
485
- |--------|-----------|--------|
486
- | `List` | `selected_index`, `highlight_style`, etc. | ✓ |
487
- | `Table` | `row_highlight_style`, `selected_row`, etc. | ✓ |
488
- | `Scrollbar` | `content_length`, `position`, etc. | ✓ |
489
- | All | `block`, `style`, `offset` | ✓ Consistent |
490
-
491
- ---
492
-
493
- ###### Summary of Changes
494
-
495
- ###### High Priority (Immediate DX Wins)
496
-
497
- 1. **Add `item` alias** for `list_item`
498
- 2. **Add terse shape aliases**: `circle`, `point`, `rectangle`, `map`, `label`
499
-
500
- ###### Medium Priority (Pattern Completion)
501
-
502
- 5. **Add dispatcher methods**: `shape(type, ...)`, `constraint(type, ...)`, `text(type, ...)`, `widget(type, ...)`, `state(type, ...)`
503
- 6. **Add bidirectional shape aliases**: `circle_shape`, `point_shape`, etc.
504
-
505
- ###### Not Changing
506
-
507
- - Don't deprecate verbose forms (`table_row`, `bar_chart_bar`, etc.) — DWIM
508
- - Don't rename parameters — already optimal
509
- - Don't add third level aliases (`tui.widgets.paragraph`) — two levels max
510
-
511
- ---
512
-
513
- ###### Implementation Checklist
514
-
515
- - [ ] Add `item` alias to `WidgetFactories`
516
- - [ ] Add terse shape aliases to `CanvasFactories`
517
- - [ ] Add `shape(type, ...)` dispatcher
518
- - [ ] Add `constraint(type, ...)` dispatcher
519
- - [ ] Add bidirectional shape aliases (`*_shape`)
520
- - [ ] Add `text(type, ...)`, `widget(type, ...)`, `state(type, ...)` dispatchers
521
- - [ ] Update RBS signatures for all new methods
522
- - [ ] Update RDoc for all new methods
523
- - [ ] Update CHANGELOG.md
524
-
525
- ---
526
-
527
- ###### Breaking Changes Analysis
528
-
529
- If all recommendations in this audit are adopted, **none constitute breaking changes** under semver.
530
-
531
- | Recommendation | Breaking? | Rationale |
532
- |----------------|-----------|-----------|
533
- | Add `item` alias | No | Additive; `list_item` unchanged |
534
- | Add terse shape aliases (`circle`, etc.) | No | Additive; `shape_*` methods unchanged |
535
- | Add bidirectional aliases (`*_shape`) | No | Additive; does not remove existing forms |
536
- | Add dispatcher methods | No | Additive; new methods only |
537
- | Keep verbose forms (`table_row`, etc.) | No | No removal or rename |
538
-
539
- **Conclusion:** This audit recommends only additive changes. All existing code will continue to work unchanged.
540
-
541
- > [!NOTE]
542
- > If we later decide to **remove** verbose forms like `bar_chart_bar` or `bar_chart_bar_group`, that would be a breaking change requiring a major version bump. This audit explicitly recommends **keeping** them (DWIM philosophy).
543
-
544
-
545
-
546
- ---
547
-
548
-
549
- # dwim_dx
550
- ## dwim_dx
551
- #### Problem Statement
552
-
553
- Ruby's philosophy of "Do What I Mean" (DWIM) and human-centric design should extend to ratatui_ruby's API. Currently, app developers encounter friction points that force them to remember non-obvious conventions, use overly verbose code, or pattern-match when simple predicates would suffice.
554
-
555
- This proposal identifies DX issues across the widget API and suggests improvements that maintain backward compatibility while providing ergonomic alternatives.
556
-
557
- #### DX Issues Identified
558
-
559
- ##### 1. Confusing Event Method Names
560
-
561
- **Current problem**: `event.char` doesn't exist, but `event.code` returns things like `"enter"`, `"ctrl"`, not just characters.
562
-
563
- **What users expect**:
564
- - `event.char` should return the printable character (matching the name)
565
- - `event.ctrl_c?`, `event.enter?`, etc. should work for all key combinations
566
- - `event.key?`, `event.mouse?` predicates exist but only for broad categories
567
-
568
- **Solution implemented**: Added `char` method and dynamic predicates via `method_missing`. See `lib/ratatui_ruby/event/key.rb`.
569
-
570
- ##### 2. Dual Parameter APIs Without Predicates
571
-
572
- **Current problem**: Widgets accept both forms but no convenience methods to query the state:
573
-
574
- ```ruby
575
- ### Both work, but which one does the widget store?
576
- gauge1 = Gauge.new(ratio: 0.75)
577
- gauge2 = Gauge.new(percent: 75)
578
- gauge1.ratio # Works
579
- gauge1.percent # Does NOT exist
580
- ```
581
-
582
- Similarly with List and Table:
583
- ```ruby
584
- list.selected_index = 2 # Works
585
- list.selected? # Does NOT exist
586
- list.is_selected? # Does NOT exist
587
- ```
588
-
589
- **Affected widgets**:
590
- - `Gauge` (ratio vs percent)
591
- - `LineGauge` (ratio vs percent)
592
- - `List` (selected_index with no query methods)
593
- - `Table` (selected_row and selected_column with no query methods)
594
-
595
- **Suggested solutions**:
596
-
597
- For `Gauge` and `LineGauge`:
598
- ```ruby
599
- ### Add convenience predicates
600
- gauge.percent # => 75 (coerced from ratio internally)
601
- gauge.percent = 50 # => Updates ratio to 0.5
602
-
603
- ### Or provide explicit accessors
604
- gauge.as_percent # => 75
605
- gauge.as_ratio # => 0.75
606
- ```
607
-
608
- For `List` and `Table`:
609
- ```ruby
610
- list.selected? # => true if selected_index is not nil
611
- list.selection # => 2 (alias for selected_index)
612
- list.selected_item # => "Item 3"
613
-
614
- table.selected_row? # => true if selected_row is not nil
615
- table.selected_cell? # => true if both row and column selected
616
- ```
617
-
618
- ##### 4. Inconsistent Style APIs
619
-
620
- **Current problem**: Different widgets accept styles differently:
621
-
622
- ```ruby
623
- ### Table accepts both
624
- table = Table.new(style: Style.new(fg: :blue))
625
- table = Table.new(style: { fg: :blue }) # Hash shorthand
626
-
627
- ### But Paragraph doesn't
628
- paragraph = Paragraph.new(text: "hi", style: Style.new(fg: :blue))
629
- paragraph = Paragraph.new(text: "hi", style: { fg: :blue }) # Works but undocumented
630
-
631
- ### And Gauge has separate properties
632
- gauge = Gauge.new(style: Style.new(fg: :blue), gauge_style: Style.new(fg: :green))
633
- ```
634
-
635
- **Suggested solution**: Standardize style handling across all widgets:
636
-
637
- 1. All widgets should accept `Style` objects and `Hash` shorthand
638
- 2. Document this clearly in each widget
639
- 3. Add a convenience constructor:
640
-
641
- ```ruby
642
- class Style
643
- def self.with(fg: nil, bg: nil, modifiers: [])
644
- Style.new(fg: fg, bg: bg, modifiers: modifiers)
645
- end
646
- end
647
-
648
- ### Cleaner than always spelling out keyword args
649
- paragraph = Paragraph.new(text: "hi", style: Style.with(fg: :blue))
650
- ```
651
-
652
- ##### 6. Magic Numeric Coercions
653
-
654
- **Current problem**: Widgets accept `Numeric` but silently coerce:
655
-
656
- ```ruby
657
- ### These all work, but behavior is undocumented
658
- list = List.new(selected_index: "2") # Coerced to 2
659
- list = List.new(selected_index: 2.7) # Coerced to 2
660
- list = List.new(selected_index: 2.0) # Coerced to 2
661
-
662
- gauge = Gauge.new(percent: 150) # Should clamp?
663
- gauge = Gauge.new(ratio: 1.5) # Should clamp?
664
- ```
665
-
666
- **Suggested solution**:
667
-
668
- 1. Document coercion rules explicitly in RDoc
669
- 2. Add validation and raise on invalid inputs:
670
-
671
- ```ruby
672
- def initialize(percent: nil, ...)
673
- if percent
674
- raise ArgumentError, "percent must be 0..100, got #{percent}" unless percent.between?(0, 100)
675
- ratio = Float(percent) / 100.0
676
- end
677
- end
678
- ```
679
-
680
- 3. Provide clear error messages:
681
- ```ruby
682
- gauge = Gauge.new(percent: 150)
683
- ### => ArgumentError: percent must be between 0 and 100 (got 150)
684
- ```
685
-
686
- #### Implementation Strategy
687
-
688
- ##### Phase 3: Style Consistency
689
- - [ ] Standardize `Hash` shorthand support across all widgets
690
- - [ ] Add `Style.with(fg:, bg:, modifiers:)` convenience constructor
691
- - [ ] Update `.rbs` files to reflect HashStyle support
692
- - [ ] Document in style guide
693
-
694
- ##### Phase 4: Numeric Coercion Validation
695
- - [ ] Add validation to `Gauge`, `LineGauge`, `List`, `Table`
696
- - [ ] Raise `ArgumentError` on out-of-range values
697
- - [ ] Provide clear error messages
698
- - [ ] Update tests
699
-
700
- ##### Phase 5: Convenience Accessors
701
- - [ ] Add `percent` to `Gauge` and `LineGauge`
702
- - [ ] Add `selection` alias to `List` and `Table`
703
- - [ ] Add `selected_item` to `List`
704
- - [ ] Tests and documentation
705
-
706
- #### Example: Before and After
707
-
708
- ##### Before (Confusing)
709
- ```ruby
710
- class GameApp
711
- def initialize
712
- @menu = List.new(
713
- items: ["Start Game", "Load Game", "Options", "Quit"],
714
- selected_index: 0,
715
- highlight_spacing: :when_selected, # What's valid here?
716
- direction: :top_to_bottom
717
- )
718
- end
719
-
720
- def handle_input(event)
721
- case event
722
- when :ctrl_c
723
- exit
724
- when :up
725
- if @menu.selected_index && @menu.selected_index > 0
726
- @menu = @menu.with(selected_index: @menu.selected_index - 1)
727
- end
728
- end
729
- end
730
-
731
- def render(tui)
732
- tui.draw(@menu)
733
- end
734
- end
735
- ```
736
-
737
- ##### After (DWIM)
738
- ```ruby
739
- class GameApp
740
- def initialize
741
- @menu = List.new(
742
- items: ["Start Game", "Load Game", "Options", "Quit"],
743
- selected_index: 0,
744
- highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED, # IDE autocomplete!
745
- direction: List::DIRECTION_TOP_TO_BOTTOM
746
- )
747
- end
748
-
749
- def handle_input(event)
750
- return if event.ctrl_c? # Dynamic predicate!
751
-
752
- if event.up?
753
- move_menu_up if @menu.selected? # State predicate!
754
- end
755
- end
756
-
757
- def move_menu_up
758
- index = @menu.selected_index
759
- return if index == 0
760
- @menu = @menu.with(selected_index: index - 1)
761
- end
762
-
763
- def render(tui)
764
- tui.draw(@menu)
765
- end
766
- end
767
- ```
768
-
769
- #### Migration Path
770
-
771
- All changes are backward compatible (additive):
772
- - Existing code using symbols continues to work
773
- - New constants coexist with symbols
774
- - New predicates don't change existing behavior
775
- - New methods are additions, not replacements
776
-
777
- Apps can migrate at their own pace:
778
- ```ruby
779
- ### Old style still works
780
- list = List.new(highlight_spacing: :when_selected)
781
-
782
- ### New style also works
783
- list = List.new(highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED)
784
-
785
- ### Mix and match
786
- if list.selected? # New predicate
787
- puts list.selected_index # Old accessor
788
- end
789
- ```
790
-
791
- #### Metrics for Success
792
-
793
- 1. **Discoverability**: New developers can find valid options via IDE autocomplete
794
- 2. **Clarity**: Code self-documents valid states and modes
795
- 3. **Type safety**: Constants and predicates provide type checking
796
- 4. **Error feedback**: Invalid inputs raise with helpful messages
797
- 5. **Backward compatibility**: Zero breaking changes, all existing code works
798
-
799
- #### Related Issues
800
-
801
- - AGENTS.md requirement: All examples must have tests verifying behavior
802
- - Example improvements: Apply constants and predicates to all example code
803
- - Documentation: Update style guide with DWIM principles
804
-
805
-
806
- ---
807
-
808
-
809
- # examples_audit
810
- ## examples_audit
811
- ##### [P1: Moderate (Quality)](#p2_moderate)
812
-
813
- 1. **[Add RDoc Cross-Links](#1-add-rdoc-cross-links-examples--aliases)** (Documentation discoverability)
814
- - Link library classes/methods to examples
815
- - Link DWIM/TIMTOWTDI aliases
816
- - Create consistent pattern across public APIs
817
-
818
-
819
- ---
820
-
821
-
822
- ### p2_moderate
823
- #### Priority 2: Moderate (Quality Gates)
824
-
825
- These are v1.0.0 quality improvements that refine the example suite after P0 is complete. Not blocking, but recommended for maintainability and API consistency.
826
-
827
- ---
828
-
829
- ##### 1. Add RDoc Cross-Links (Examples & Aliases)
830
-
831
- **Status:** Important for API discoverability — Documentation should link library and examples
832
-
833
- RDoc should cross-link between:
834
- - **Library classes/methods** ↔ **Examples that use them** (See also: examples/widget_foo)
835
- - **Primary methods** ↔ **DWIM/TIMTOWTDI aliases** (See also: tui.foo_bar as alias for tui.foo(:bar))
836
-
837
- ###### Current Practice
838
-
839
- Done for:
840
- - `RatatuiRuby::Frame#set_cursor_position` ↔ `RatatuiRuby::Cursor` (cross-linking)
841
- - Limited elsewhere
842
-
843
- ###### Gaps
844
-
845
- - Most widget classes have no "See also: example_foo_demo" links
846
- - Aliases/TIMTOWTDI variants are not documented as such
847
- - Users can't easily find examples for a given class/method
848
-
849
- ###### Action
850
-
851
- 1. Add `# See also: examples/widget_foo/app.rb` to class/method RDoc
852
- 2. Link DWIM methods to TIMTOWTDI variants: `# Also available as: tui.constraint_length (DWIM) vs tui.constraint(:length) (TIMTOWTDI)`
853
- 3. Create consistent pattern across all public APIs in `lib/ratatui_ruby/`
854
-
855
- ###### Example Pattern
856
-
857
- ```ruby
858
- #### Renders text with styling.
859
- #
860
- #### See also: examples/widget_paragraph/app.rb (basic paragraph rendering)
861
- class Paragraph < Data.define(...)
862
- # ...
863
- end
864
-
865
- #### DWIM version of constraint creation
866
- #### Also available as: constraint(type, value) for explicit control
867
- def constraint_length(length)
868
- constraint(:length, length)
869
- end
870
- ```