ratatui_ruby 0.9.0 → 0.10.0

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