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