alignment_audit
alignment_audit
-
Symbol Sets
-
line::Set, block::Set, scrollbar::Set
-
Layout
-
Rect methods (~13)
-
Constraint batch constructors (6)
-
Layout margin, spacing (2)
-
Style
-
sub_modifier, underline_color (2)
-
Text
-
Span methods (4)
-
Line methods (6)
MISALIGNED (non-additive, breaking)
alignment_audit_granular_level
v0.7.0 Alignment Audit (Parameter-Level)
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.
[!IMPORTANT] MISSING = Can be added as new features, backwards-compatible. MISALIGNED = Requires breaking changes before v1.0.0 release.
MISSING — Layout Module
Layout::Rect — Missing Methods
| Missing Method | Signature | Notes |
|---|---|---|
area |
rect.area → Integer
|
Returns width * height
|
left |
rect.left → Integer
|
Returns x (alias) |
right |
rect.right → Integer
|
Returns x + width
|
top |
rect.top → Integer
|
Returns y (alias) |
bottom |
rect.bottom → Integer
|
Returns y + height
|
union |
rect.union(other) → Rect
|
Bounding box of both rects |
inner |
rect.inner(margin) → Rect
|
Shrink by margin |
offset |
rect.offset(dx, dy) → Rect
|
Translate position |
clamp |
rect.clamp(other) → Rect
|
Clamp to bounds |
rows |
rect.rows → Iterator
|
Iterate row positions |
columns |
rect.columns → Iterator
|
Iterate column positions |
positions |
rect.positions → Iterator
|
Iterate all positions |
is_empty |
rect.empty? → Boolean
|
True if zero area |
Layout::Constraint — Missing Batch Constructors
| Missing Method | Signature |
|---|---|
from_lengths |
Constraint.from_lengths([10, 20, 30]) → [Constraint]
|
from_percentages |
Constraint.from_percentages([25, 50, 25]) → [Constraint]
|
from_mins |
Constraint.from_mins([5, 10, 15]) → [Constraint]
|
from_maxes |
Constraint.from_maxes([20, 30, 40]) → [Constraint]
|
from_fills |
Constraint.from_fills([1, 2, 1]) → [Constraint]
|
from_ratios |
Constraint.from_ratios([[1,4], [2,4], [1,4]]) → [Constraint]
|
Layout::Layout — Missing Parameters
| Missing Parameter | Ratatui Type | Notes |
|---|---|---|
margin |
Margin { horizontal, vertical } |
Edge margins |
spacing |
u16 |
Gap between segments |
MISSING — Style Module
Style::Style — Missing Parameters/Methods
MISSING — Text Module
Text::Span — Missing Methods
| Missing Method | Signature | Notes |
|---|---|---|
width |
span.width → Integer
|
Display width in terminal cells |
raw |
Span.raw(content) → Span
|
Constructor without style (alias for new(content:)) |
patch_style |
span.patch_style(style) → Span
|
Merge style on top of existing |
reset_style |
span.reset_style → Span
|
Clear style |
Text::Line — Missing Methods
| Missing Method | Signature | Notes |
|---|---|---|
left_aligned |
line.left_aligned → Line
|
Fluent setter for :left alignment |
centered |
line.centered → Line
|
Fluent setter for :center alignment |
right_aligned |
line.right_aligned → Line
|
Fluent setter for :right alignment |
push_span |
line.push_span(span) → Line
|
Append span |
patch_style |
line.patch_style(style) → Line
|
Merge style on all spans |
reset_style |
line.reset_style → Line
|
Clear style on all spans |
Widgets::Table — Missing Row Methods (via Widgets::Row)
| Missing | Ratatui API | Notes |
|---|---|---|
enable_strikethrough |
row.enable_strikethrough() |
Enable strikethrough on row |
Widget Computation Methods — Missing
These are public &self methods on upstream widgets that compute/query values without rendering.
| Widget | Missing Method | Ratatui API | Notes | |——–|—————-|————-|——-|
| List | len | list.len() → usize | Number of items in the list | | List | is_empty | list.is_empty() → bool | True if no items (empty? in Ruby) | | Canvas | get_point | canvas.get_point(x, y) → Option<(usize, usize)> | Map coordinates to grid cell |
[!NOTE]
Paragraph#line_count(width)andParagraph#line_widthARE exposed ✓Tabs#widthIS exposed ✓
State Navigation Methods — Missing
ListState and TableState have navigation helpers that are not exposed.
| State Class | Missing Method | Ratatui API | Notes |
|---|---|---|---|
ListState |
select_next |
state.select_next() |
Move selection to next item |
ListState |
select_previous |
state.select_previous() |
Move selection to previous item |
ListState |
select_first |
state.select_first() |
Jump to first item |
ListState |
select_last |
state.select_last() |
Jump to last item |
| TableState | selected_cell | state.selected_cell() | Get (row, col) tuple | | TableState | with_selected_cell | state.with_selected_cell((r,c)) | Builder pattern | | TableState | select_next_column | state.select_next_column() | Navigate columns | | TableState | select_previous_column | state.select_previous_column() | Navigate columns | | TableState | select_first_column | state.select_first_column() | Jump to first column | | TableState | select_last_column | state.select_last_column() | Jump to last column |
Layout Module — Additional Missing
| Module | Missing | Ratatui API | Notes |
|---|---|---|---|
Rect |
as_position |
rect.as_position() → Position
|
Convert to Position |
Rect |
as_size |
rect.as_size() → Size
|
Convert to Size |
Constraint |
apply |
constraint.apply(length) → u16
|
Compute constrained size |
Layout |
split_with_spacers |
layout.split_with_spacers(area) |
Returns segments AND spacers |
Color — Missing Constructors
| Missing | Ratatui API | Notes |
|---|---|---|
from_u32 |
Color::from_u32(0xRRGGBB) |
Construct from hex integer |
from_hsl |
Color::from_hsl(hsl) |
Construct from HSL (requires palette feature) |
from_hsluv |
Color::from_hsluv(hsluv) |
Construct from HSLuv (requires palette feature) |
Buffer — Missing Query Methods
| Missing Method | Ratatui API | Notes |
|---|---|---|
content |
buffer.content() → &[Cell]
|
Get all cells as slice |
get |
buffer.get(x, y) → &Cell
|
Get cell at position |
index_of |
buffer.index_of(x, y) → usize
|
Position to linear index |
pos_of |
buffer.pos_of(i) → (u16, u16)
|
Linear index to position |
alignment_audit_high_level
v0.7.0 Alignment Audit
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.
[!NOTE] The TUI facade API is explicitly excluded from this audit. It provides ergonomic shortcuts that intentionally diverge from Ratatui naming.
—
| margin | Margin | ❌ Missing | Gap | | spacing | u16 | ❌ Missing | Gap |
Verdict: Core layout aligned. Margin and spacing are gaps.
Gaps Analysis: MISSING vs MISALIGNED
[!IMPORTANT] MISSING = Can be added as new features without breaking backwards compatibility.
MISALIGNED = Requires breaking changes before v1.0.0 to fix API shape.
MISSING Features (Additive, Backwards-Compatible) ✅
These are gaps that can be filled in future minor releases without breaking existing code:
| Component | Missing Feature | Notes |
|---|---|---|
Rect |
area(), left(), right(), top(), bottom()
|
New instance methods |
Rect |
union(other), inner(margin), offset(offset)
|
New instance methods |
Constraint |
from_lengths(), from_percentages(), etc. |
New class methods |
Layout |
margin, spacing
|
New optional constructor args |
Style |
sub_modifier, underline_color
|
New optional constructor args |
Span |
width() instance method |
New instance method |
Span |
raw() constructor |
New class method (alias for new) |
Line |
left_aligned(), centered(), right_aligned()
|
New instance methods (fluent) |
alignment_audit_symbol_sets
Audit of symbol set alignment between Ratatui’s symbols:: module and RatatuiRuby.
[!IMPORTANT] MISSING = Can be added as new features, backwards-compatible. MISALIGNED = Requires breaking changes before v1.0.0.
| scrollbar::Set | 4 predefined sets | ❌ Not exposed | MISSING | | shade constants | 5 constants | ❌ Not exposed | MISSING |
MISSING — Marker Enum
Marker::HalfBlock
| Marker::HalfBlock | ❌ Not exposed | MISSING |
Impact: Users cannot use the HalfBlock marker type, which provides double-resolution square pixels using █, ▄, and ▀ characters.
MISSING — Line Set Customization
Ratatui provides symbols::line::Set with predefined sets: - NORMAL — Standard box-drawing characters - ROUNDED — Rounded corners - DOUBLE — Double-line characters - THICK — Thick line characters
Ruby Status: Not directly exposed. Users cannot customize line symbols for widgets that use them internally.
MISSING — Bar Set Customization
Ratatui provides symbols::bar::Set with predefined sets: - THREE_LEVELS — 3 distinct fill levels - NINE_LEVELS — 9 distinct fill levels (default)
Ruby Status: Not exposed. Used internally by widgets like Sparkline but not configurable.
MISSING — Block Set Customization
Ratatui provides symbols::block::Set with predefined sets: - THREE_LEVELS — 3 distinct fill levels - NINE_LEVELS — 9 distinct fill levels (default)
Ruby Status: Not exposed. Used internally by Gauge widget but not configurable.
MISSING — Scrollbar Set Customization
Ratatui provides symbols::scrollbar::Set with predefined sets: - DOUBLE_VERTICAL — Double-line vertical scrollbar - DOUBLE_HORIZONTAL — Double-line horizontal scrollbar - VERTICAL — Single-line vertical scrollbar - HORIZONTAL — Single-line horizontal scrollbar
Ruby Status: Not exposed. Scrollbar widget not currently implemented in RatatuiRuby.
MISSING — Shade Constants
Ratatui provides symbols::shade constants: - EMPTY — (space) - LIGHT — ░ - MEDIUM — ▒ - DARK — ▓
Recommendations
| Priority | Item | Notes |
|---|---|---|
| Low | Add :half_block marker |
Single symbol addition |
| Low | Expose line::Set customization |
For LineGauge widget |
| Low | Expose bar::Set customization |
For Sparkline widget |
| Low | Expose block::Set customization |
For Gauge widget |
| Medium | Implement Scrollbar widget | Would include scrollbar::Set |
All missing items are additive and do not require breaking changes.
alignment_audit_tui_final
TUI API Alignment Audit
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.
Design Philosophy
The TUI API follows a “Mullet Architecture”: structured namespaces in the library, flat ergonomic DSL for users.
Guiding Principles:
-
Terseness — Fewer keystrokes for common operations
-
DWIM — Do What I Mean; intuitive defaults
-
TIMTOWTDI — Multiple valid ways to express the same thing
-
Big Tent — Aliases for CSS/frontend developers, Ratatui natives, and Ruby purists
-
Two Levels Max —
tui.thingandtui.scope_thing, nevertui.scope.thing, nevertui.ascope_bscope_thing
Breaking Changes: Pre-1.0 with few external users. Rename aggressively for DX. Document in CHANGELOG.
Method Naming Recommendations
Pattern: Base Methods + Aliases + Dispatchers
Base methods align with Ratatui’s module API names. Aliases provide ergonomic shortcuts.
-
Base method (Ratatui-aligned):
tui.shape_circle(...)— matchesWidgets::Shape::Circle -
Aliases (ergonomic):
tui.circle(...),tui.circle_shape(...) -
Dispatcher:
tui.shape(:circle, ...)— errors helpfully on missing type
This pattern applies to: shapes, constraints, text elements.
Canvas Shapes
| Base Method | Add Aliases | Dispatcher |
|---|---|---|
shape_circle |
circle, circle_shape
|
shape(:circle, ...) |
shape_line |
line_shape (not bare line — conflicts with Text::Line)
|
shape(:line, ...) |
shape_point |
point, point_shape
|
shape(:point, ...) |
shape_rectangle |
rectangle, rectangle_shape
|
shape(:rectangle, ...) |
shape_map |
map, map_shape
|
shape(:map, ...) |
shape_label |
label, label_shape
|
shape(:label, ...) |
Dispatcher signature:
def shape(type, **kwargs) case type when :circle then shape_circle(**kwargs) when :line then shape_line(**kwargs) # ... else raise ArgumentError, "Unknown shape type: #{type.inspect}. " \ "Valid types: :circle, :line, :point, :rectangle, :map, :label" end end
Layout Constraints
Ratatui’s constraints map well to CSS layout concepts. Offer aliases for both communities:
| Base Method | Add Aliases (CSS-Friendly) | Dispatcher |
|---|---|---|
constraint_length(n) |
fixed(n), length(n)
|
constraint(:length, n) |
constraint_percentage(n) |
percent(n), percentage(n)
|
constraint(:percentage, n) |
constraint_min(n) |
min(n), min_content(n)
|
constraint(:min, n) |
constraint_max(n) |
max(n), max_content(n)
|
constraint(:max, n) |
constraint_fill(n) |
fill(n), flex(n), fr(n)
|
constraint(:fill, n) |
constraint_ratio(a,b) |
ratio(a,b), aspect(a,b)
|
constraint(:ratio, a, b) |
CSS Flexbox/Grid parallels: - fill(1) ≈ CSS flex: 1 or 1fr - fixed(100) ≈ CSS width: 100px - min(50) ≈ CSS min-width: 50px - percent(25) ≈ CSS width: 25%
Dispatcher signature:
def constraint(type, *args) case type when :length, :fixed then constraint_length(*args) when :percentage, :percent then constraint_percentage(*args) when :min then constraint_min(*args) when :max then constraint_max(*args) when :fill, :flex, :fr then constraint_fill(*args) when :ratio, :aspect then constraint_ratio(*args) else raise ArgumentError, "Unknown constraint type: #{type.inspect}. " \ "Valid types: :length, :percentage, :min, :max, :fill, :ratio" end end
Layout Operations
| Current | Add Alias | Rationale |
|---|---|---|
layout_split |
split |
52 usages in examples; clear in context |
Text Factories
| Current | Status | Notes |
|---|---|---|
text_span |
✓ Has alias span
|
|
text_line |
✓ Has alias line
|
|
text_width |
Keep as-is | Distinct from length (constraint) |
Add dispatcher:
def text(type, **kwargs) case type when :span then text_span(**kwargs) when :line then text_line(**kwargs) else raise ArgumentError, "Unknown text type: #{type.inspect}. Valid types: :span, :line" end end
Widget Factories
| Current | Add Alias | Rationale |
|---|---|---|
| list_item | item |
Clear in list context |
table_row |
Keep alongside row
|
DWIM — both valid mental models |
table_cell |
Keep as-is |
cell means Buffer::Cell
|
bar_chart_bar |
Keep alongside bar
|
DWIM — don’t deprecate |
bar_chart_bar_group |
Keep alongside bar_group | DWIM — don’t deprecate |
Add dispatcher:
def widget(type, **kwargs) case type when :block then block(**kwargs) when :paragraph then paragraph(**kwargs) when :list then list(**kwargs) when :table then table(**kwargs) # ... all widgets else raise ArgumentError, "Unknown widget type: #{type.inspect}." end end
State Factories
| Current | Status |
|---|---|
| list_state | Keep as-is |
| table_state | Keep as-is |
| scrollbar_state | Keep as-is |
Add dispatcher:
def state(type, **kwargs) case type when :list then list_state(**kwargs) when :table then table_state(**kwargs) when :scrollbar then scrollbar_state(**kwargs) else raise ArgumentError, "Unknown state type: #{type.inspect}." end end
Parameter Names
All current parameter names are well-chosen. No changes recommended.
| Widget | Parameter | Status |
|---|---|---|
List |
selected_index, highlight_style, etc. |
✓ |
Table |
row_highlight_style, selected_row, etc. |
✓ |
Scrollbar |
content_length, position, etc. |
✓ |
| All |
block, style, offset
|
✓ Consistent |
Summary of Changes
High Priority (Immediate DX Wins)
-
Add
splitalias forlayout_split -
Add
itemalias for list_item -
Add terse shape aliases:
circle,point,rectangle,map,label -
Add CSS-friendly constraint aliases:
fixed,percent,fill,flex,fr,min,max
Medium Priority (Pattern Completion)
-
Add dispatcher methods:
shape(type, ...),constraint(type, ...),text(type, ...),widget(type, ...),state(type, ...) -
Add bidirectional shape aliases:
circle_shape,point_shape, etc.
Not Changing
-
Don’t deprecate verbose forms (
table_row,bar_chart_bar, etc.) — DWIM -
Don’t rename parameters — already optimal
-
Don’t add third level aliases (
tui.widgets.paragraph) — two levels max
Implementation Checklist
-
[ ] Add
splitalias toLayoutFactories -
[ ] Add
itemalias toWidgetFactories -
[ ] Add terse shape aliases to
CanvasFactories -
[ ] Add CSS-friendly constraint aliases to
LayoutFactories -
[ ] Add
shape(type, ...)dispatcher -
[ ] Add
constraint(type, ...)dispatcher -
[ ] Add bidirectional shape aliases (
*_shape) -
[ ] Add
text(type, ...),widget(type, ...),state(type, ...)dispatchers -
[ ] Update RBS signatures for all new methods
-
[ ] Update RDoc for all new methods
-
[ ] Update CHANGELOG.md
Breaking Changes Analysis
If all recommendations in this audit are adopted, none constitute breaking changes under semver.
| Recommendation | Breaking? | Rationale |
|---|---|---|
Add split alias |
No | Additive; layout_split unchanged |
Add item alias |
No | Additive; list_item unchanged |
Add terse shape aliases (circle, etc.) |
No | Additive; shape_* methods unchanged |
| Add CSS-friendly constraint aliases | No | Additive; constraint_* methods unchanged |
Add bidirectional aliases (*_shape) |
No | Additive; does not remove existing forms |
| Add dispatcher methods | No | Additive; new methods only |
Keep verbose forms (table_row, etc.) |
No | No removal or rename |
Conclusion: This audit recommends only additive changes. All existing code will continue to work unchanged.
[!NOTE] If we later decide to remove verbose forms like
bar_chart_barorbar_chart_bar_group, that would be a breaking change requiring a major version bump. This audit explicitly recommends keeping them (DWIM philosophy).
dwim_dx
dwim_dx
Problem Statement
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.
This proposal identifies DX issues across the widget API and suggests improvements that maintain backward compatibility while providing ergonomic alternatives.
DX Issues Identified
1. Confusing Event Method Names
Current problem: event.char doesn’t exist, but event.code returns things like "enter", "ctrl", not just characters.
What users expect: - event.char should return the printable character (matching the name) - event.ctrl_c?, event.enter?, etc. should work for all key combinations - event.key?, event.mouse? predicates exist but only for broad categories
Solution implemented: Added char method and dynamic predicates via method_missing. See lib/ratatui_ruby/event/key.rb.
2. Dual Parameter APIs Without Predicates
Current problem: Widgets accept both forms but no convenience methods to query the state:
### Both work, but which one does the widget store? gauge1 = Gauge.new(ratio: 0.75) gauge2 = Gauge.new(percent: 75) gauge1.ratio # Works gauge1.percent # Does NOT exist
Similarly with List and Table:
list.selected_index = 2 # Works list.selected? # Does NOT exist list.is_selected? # Does NOT exist
Affected widgets: - Gauge (ratio vs percent) - LineGauge (ratio vs percent) - List (selected_index with no query methods) - Table (selected_row and selected_column with no query methods)
Suggested solutions:
For Gauge and LineGauge:
### Add convenience predicates gauge.percent # => 75 (coerced from ratio internally) gauge.percent = 50 # => Updates ratio to 0.5 ### Or provide explicit accessors gauge.as_percent # => 75 gauge.as_ratio # => 0.75
For List and Table:
list.selected? # => true if selected_index is not nil list.selection # => 2 (alias for selected_index) list.selected_item # => "Item 3" table.selected_row? # => true if selected_row is not nil table.selected_cell? # => true if both row and column selected
3. Symbol Constants for Enum Values
Current problem: Magic symbol values scattered across code:
list = List.new( highlight_spacing: :when_selected, # What are the other options? direction: :top_to_bottom, # Is :bottom_to_top valid? ) layout = Layout.new( flex: :legacy # What does "legacy" mean? ) gauge = Gauge.new( use_unicode: true # Unclear what ASCII fallback looks like )
Users must consult docs or source code to discover valid options.
Suggested solution: Add constants to widget classes:
class List < Data # Highlight spacing modes HIGHLIGHT_ALWAYS = :always HIGHLIGHT_WHEN_SELECTED = :when_selected HIGHLIGHT_NEVER = :never # Direction modes DIRECTION_TOP_TO_BOTTOM = :top_to_bottom DIRECTION_BOTTOM_TO_TOP = :bottom_to_top end list = List.new( highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED, direction: List::DIRECTION_TOP_TO_BOTTOM, )
Benefits: - IDE autocomplete shows valid options - Self-documenting code - Typos caught at runtime (symbol vs constant) - Easy to grep for where these modes are used
Affected widgets and their enum values: - List: highlight_spacing (:always, :when_selected, :never), direction (:top_to_bottom, :bottom_to_top) - Table: highlight_spacing (same as List), flex (:legacy, :default, :fill) - Layout: direction (:vertical, :horizontal), flex (:legacy, :default, :fill) - Gauge/LineGauge: use_unicode (boolean, but could have MODE_UNICODE, MODE_ASCII) - Paragraph: alignment (:left, :center, :right) - Block: border_type (:plain, :rounded, :double, :thick) - Canvas: marker (:braille, :dots, :half_block, :sextant, :octant)
4. Inconsistent Style APIs
Current problem: Different widgets accept styles differently:
### Table accepts both table = Table.new(style: Style.new(fg: :blue)) table = Table.new(style: { fg: :blue }) # Hash shorthand ### But Paragraph doesn't paragraph = Paragraph.new(text: "hi", style: Style.new(fg: :blue)) paragraph = Paragraph.new(text: "hi", style: { fg: :blue }) # Works but undocumented ### And Gauge has separate properties gauge = Gauge.new(style: Style.new(fg: :blue), gauge_style: Style.new(fg: :green))
Suggested solution: Standardize style handling across all widgets:
-
All widgets should accept
Styleobjects andHashshorthand -
Document this clearly in each widget
-
Add a convenience constructor:
class Style def self.with(fg: nil, bg: nil, modifiers: []) Style.new(fg: fg, bg: bg, modifiers: modifiers) end end ### Cleaner than always spelling out keyword args paragraph = Paragraph.new(text: "hi", style: Style.with(fg: :blue))
5. Missing State Query Predicates
Current problem: Widgets store state but provide no query methods:
list.selected_index = 0 ### To check if something is selected, must do: if list.selected_index&.nonzero? # Awkward if list.selected_index.nil? == false # Confusing ### Should be: list.selected? # => true list.empty? # => false (for items array)
Suggested solution: Add predicates to state-holding widgets:
### List list.selected? # => !selected_index.nil? list.empty? # => items.empty? list.selection # => selected_index (alias) list.selected_item # => items[selected_index] (convenience) ### Table table.selected_row? # => !selected_row.nil? table.selected_cell? # => !selected_row.nil? && !selected_column.nil? table.empty? # => rows.empty? ### Gauge gauge.filled? # => ratio > 0 gauge.complete? # => ratio >= 1.0
6. Magic Numeric Coercions
Current problem: Widgets accept Numeric but silently coerce:
### These all work, but behavior is undocumented list = List.new(selected_index: "2") # Coerced to 2 list = List.new(selected_index: 2.7) # Coerced to 2 list = List.new(selected_index: 2.0) # Coerced to 2 gauge = Gauge.new(percent: 150) # Should clamp? gauge = Gauge.new(ratio: 1.5) # Should clamp?
Suggested solution:
-
Document coercion rules explicitly in RDoc
-
Add validation and raise on invalid inputs:
def initialize(percent: nil, ...) if percent raise ArgumentError, "percent must be 0..100, got #{percent}" unless percent.between?(0, 100) ratio = Float(percent) / 100.0 end end
-
Provide clear error messages:
gauge = Gauge.new(percent: 150) ### => ArgumentError: percent must be between 0 and 100 (got 150)
Implementation Strategy
Phase 1: State Query Predicates
-
[ ] Add predicates to
List(selected?, empty?, selected_item) -
[ ] Add predicates to
Table(selected_row?, selected_cell?, empty?) -
[ ] Add predicates to
Gauge(filled?, complete?) -
[ ] Tests for all new predicates
Phase 2: Symbol Constants
-
[ ] Add enum constants to
List,Table,Layout -
[ ] Add enum constants to
Gauge,LineGauge,Paragraph,Block -
[ ] Update all examples to use constants
-
[ ] Document constants in RDoc
Phase 3: Style Consistency
-
[ ] Standardize
Hashshorthand support across all widgets -
[ ] Add
Style.with(fg:, bg:, modifiers:)convenience constructor -
[ ] Update
.rbsfiles to reflect HashStyle support -
[ ] Document in style guide
Phase 4: Numeric Coercion Validation
-
[ ] Add validation to
Gauge,LineGauge,List,Table -
[ ] Raise
ArgumentErroron out-of-range values -
[ ] Provide clear error messages
-
[ ] Update tests
Phase 5: Convenience Accessors
-
[ ] Add
percenttoGaugeandLineGauge -
[ ] Add
selectionalias toListandTable -
[ ] Add
selected_itemtoList -
[ ] Tests and documentation
Example: Before and After
Before (Confusing)
class GameApp def initialize @menu = List.new( items: ["Start Game", "Load Game", "Options", "Quit"], selected_index: 0, highlight_spacing: :when_selected, # What's valid here? direction: :top_to_bottom ) end def handle_input(event) case event when :ctrl_c exit when :up if @menu.selected_index && @menu.selected_index > 0 @menu = @menu.with(selected_index: @menu.selected_index - 1) end end end def render(tui) tui.draw(@menu) end end
After (DWIM)
class GameApp def initialize @menu = List.new( items: ["Start Game", "Load Game", "Options", "Quit"], selected_index: 0, highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED, # IDE autocomplete! direction: List::DIRECTION_TOP_TO_BOTTOM ) end def handle_input(event) return if event.ctrl_c? # Dynamic predicate! if event.up? move_menu_up if @menu.selected? # State predicate! end end def move_menu_up index = @menu.selected_index return if index == 0 @menu = @menu.with(selected_index: index - 1) end def render(tui) tui.draw(@menu) end end
Migration Path
All changes are backward compatible (additive): - Existing code using symbols continues to work - New constants coexist with symbols - New predicates don’t change existing behavior - New methods are additions, not replacements
Apps can migrate at their own pace:
### Old style still works list = List.new(highlight_spacing: :when_selected) ### New style also works list = List.new(highlight_spacing: List::HIGHLIGHT_WHEN_SELECTED) ### Mix and match if list.selected? # New predicate puts list.selected_index # Old accessor end
Metrics for Success
-
Discoverability: New developers can find valid options via IDE autocomplete
-
Clarity: Code self-documents valid states and modes
-
Type safety: Constants and predicates provide type checking
-
Error feedback: Invalid inputs raise with helpful messages
-
Backward compatibility: Zero breaking changes, all existing code works
Related Issues
-
AGENTS.md requirement: All examples must have tests verifying behavior
-
Example improvements: Apply constants and predicates to all example code
-
Documentation: Update style guide with DWIM principles
examples_audit
examples_audit
P1: Moderate (Quality)
-
{Add RDoc Cross-Links}[#1-add-rdoc-cross-links-examples–aliases] (Documentation discoverability)
-
Link library classes/methods to examples
-
Link DWIM/TIMTOWTDI aliases
-
Create consistent pattern across public APIs
p2_moderate
Priority 2: Moderate (Quality Gates)
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.
1. Add RDoc Cross-Links (Examples & Aliases)
Status: Important for API discoverability — Documentation should link library and examples
RDoc should cross-link between: - Library classes/methods ↔ Examples that use them (See also: examples/widget_foo) - Primary methods ↔ DWIM/TIMTOWTDI aliases (See also: tui.foo_bar as alias for tui.foo(:bar))
Current Practice
Done for: - RatatuiRuby::Frame#set_cursor_position ↔ RatatuiRuby::Cursor (cross-linking) - Limited elsewhere
Gaps
-
Most widget classes have no “See also: example_foo_demo” links
-
Aliases/TIMTOWTDI variants are not documented as such
-
Users can’t easily find examples for a given class/method
Action
-
Add
# See also: examples/widget_foo/app.rbto class/method RDoc -
Link DWIM methods to TIMTOWTDI variants:
# Also available as: tui.constraint_length (DWIM) vs tui.constraint(:length) (TIMTOWTDI) -
Create consistent pattern across all public APIs in
lib/ratatui_ruby/
Example Pattern
#### Renders text with styling. # #### See also: examples/widget_paragraph/app.rb (basic paragraph rendering) class Paragraph < Data.define(...) # ... end #### DWIM version of constraint creation #### Also available as: constraint(type, value) for explicit control def constraint_length(length) constraint(:length, length) end
2. Enhance Widget Examples with Functional Context
Status: Recommended — Move beyond “parameter playgrounds” to “real-world patterns”
Current widget_* examples mostly focus on interactive parameter turning (changing colors, borders, etc.). While useful for API discovery, they don’t show how to use the widget in a real application logic flow.
The Standard: widget_tabs
The widget_tabs was enhanced to show conditional rendering of content based on the selected tab in git commit 38ceed39a011d557cc66e11a4598d3341dc7a0cc. It doesn’t just highlight the tab; it changes the screen content. This connects the widget (the tabs) to the problem it solves (view segregation).
Action
Identify other widget examples that could benefit from this “functional context” treatment:
-
widget_popup: Show a multi-step modal flow (e.g., Confirm -> Success) rather than just a static overlay.
-
widget_list: Show a master-detail view where selecting a list item updates a detail pane.
-
widget_input: (If created) Show specific validation logic (email vs number).
Goal: Every widget example should answer “How do I build a feature with this?” not just “What does this parameter do?”