rooibos 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +9 -5
- data/.builds/ruby-3.3.yml +9 -5
- data/.builds/ruby-3.4.yml +9 -5
- data/.builds/ruby-4.0.0.yml +9 -5
- data/AGENTS.md +1 -1
- data/CHANGELOG.md +57 -0
- data/README.md +2 -2
- data/README.rdoc +374 -0
- data/REUSE.toml +5 -0
- data/Rakefile +1 -1
- data/doc/best_practices/forms_and_validation.md +20 -0
- data/doc/best_practices/http_workflows.md +20 -0
- data/doc/best_practices/index.md +26 -0
- data/doc/best_practices/lists_and_tables.md +20 -0
- data/doc/best_practices/modal_dialogs.md +20 -0
- data/doc/best_practices/no_stateful_widgets.md +184 -0
- data/doc/best_practices/orchestration.md +20 -0
- data/doc/best_practices/streaming_data.md +20 -0
- data/doc/contributors/design/commands_and_outlets.md +1 -1
- data/doc/contributors/documentation_plan.md +616 -0
- data/doc/contributors/documentation_stub_audit.md +112 -0
- data/doc/contributors/documentation_style.md +275 -0
- data/doc/contributors/e2e_pty.md +168 -0
- data/doc/contributors/specs/earliest_tutorial_steps_per_story.md +70 -0
- data/doc/contributors/specs/file_browser.md +789 -0
- data/doc/contributors/specs/file_browser_stories.md +774 -0
- data/doc/contributors/specs/tutorials_to_stories.rb +167 -0
- data/doc/contributors/todo/scrollbar.md +118 -0
- data/doc/contributors/tutorial_old/01_project_setup.md +20 -0
- data/doc/contributors/tutorial_old/02_hello_world.md +24 -0
- data/doc/contributors/tutorial_old/03_adding_state.md +26 -0
- data/doc/contributors/tutorial_old/06_organizing_your_code.md +20 -0
- data/doc/contributors/tutorial_old/07_your_first_command.md +21 -0
- data/doc/contributors/tutorial_old/08_the_preview_pane.md +20 -0
- data/doc/contributors/tutorial_old/09_loading_states.md +20 -0
- data/doc/contributors/tutorial_old/10_testing_your_app.md +20 -0
- data/doc/contributors/tutorial_old/11_polish_and_refine.md +20 -0
- data/doc/contributors/tutorial_old/12_going_further.md +20 -0
- data/doc/contributors/tutorial_old/index.md +20 -0
- data/doc/essentials/commands.md +20 -0
- data/doc/essentials/index.md +31 -0
- data/doc/essentials/messages.md +21 -0
- data/doc/essentials/models.md +21 -0
- data/doc/essentials/shortcuts.md +19 -0
- data/doc/essentials/the_elm_architecture.md +24 -0
- data/doc/essentials/the_runtime.md +21 -0
- data/doc/essentials/update_functions.md +20 -0
- data/doc/essentials/views.md +22 -0
- data/doc/getting_started/for_go_developers.md +16 -0
- data/doc/getting_started/for_python_developers.md +16 -0
- data/doc/getting_started/for_rails_developers.md +17 -0
- data/doc/getting_started/for_ratatui_ruby_developers.md +17 -0
- data/doc/getting_started/for_react_developers.md +17 -0
- data/doc/getting_started/index.md +52 -0
- data/doc/getting_started/install.md +20 -0
- data/doc/getting_started/quickstart.md +9 -45
- data/doc/getting_started/ruby_primer.md +19 -0
- data/doc/getting_started/why_rooibos.md +20 -0
- data/doc/index.md +79 -11
- data/doc/scaling_up/async_patterns.md +20 -0
- data/doc/scaling_up/command_composition.md +20 -0
- data/doc/scaling_up/custom_commands.md +21 -0
- data/doc/scaling_up/fractal_architecture.md +20 -0
- data/doc/scaling_up/index.md +30 -0
- data/doc/scaling_up/message_routing.md +20 -0
- data/doc/scaling_up/ractor_safety.md +20 -0
- data/doc/scaling_up/testing.md +21 -0
- data/doc/troubleshooting/common_errors.md +20 -0
- data/doc/troubleshooting/debugging.md +21 -0
- data/doc/troubleshooting/index.md +23 -0
- data/doc/troubleshooting/performance.md +20 -0
- data/doc/tutorial/01_project_setup.md +44 -0
- data/doc/tutorial/02_hello_world.md +45 -0
- data/doc/tutorial/03_static_file_list.md +44 -0
- data/doc/tutorial/04_arrow_navigation.md +47 -0
- data/doc/tutorial/05_real_files.md +45 -0
- data/doc/tutorial/06_safe_refactoring.md +21 -0
- data/doc/tutorial/07_red_first_tdd.md +26 -0
- data/doc/tutorial/08_file_metadata.md +42 -0
- data/doc/tutorial/09_text_preview.md +44 -0
- data/doc/tutorial/10_directory_tree.md +42 -0
- data/doc/tutorial/11_pane_focus.md +40 -0
- data/doc/tutorial/12_sorting.md +41 -0
- data/doc/tutorial/13_filtering.md +43 -0
- data/doc/tutorial/14_toggle_hidden.md +41 -0
- data/doc/tutorial/15_text_input_widget.md +43 -0
- data/doc/tutorial/16_rename_files.md +42 -0
- data/doc/tutorial/17_confirmation_dialogs.md +43 -0
- data/doc/tutorial/18_progress_indicators.md +43 -0
- data/doc/tutorial/19_atomic_operations.md +42 -0
- data/doc/tutorial/20_external_editor.md +42 -0
- data/doc/tutorial/21_modal_overlays.md +41 -0
- data/doc/tutorial/22_error_handling.md +43 -0
- data/doc/tutorial/23_terminal_capabilities.md +53 -0
- data/doc/tutorial/24_mouse_events.md +43 -0
- data/doc/tutorial/25_resize_events.md +43 -0
- data/doc/tutorial/26_loading_states.md +42 -0
- data/doc/tutorial/27_performance.md +43 -0
- data/doc/tutorial/28_color_schemes.md +47 -0
- data/doc/tutorial/29_configuration.md +124 -0
- data/doc/tutorial/30_going_further.md +17 -0
- data/doc/tutorial/index.md +17 -0
- data/examples/app_file_browser/app.rb +40 -0
- data/examples/app_fractal_dashboard/dashboard/update_manual.rb +7 -7
- data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +5 -5
- data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +1 -1
- data/examples/app_fractal_dashboard/fragments/disk_usage.rb +2 -2
- data/examples/app_fractal_dashboard/fragments/network_panel.rb +4 -4
- data/examples/app_fractal_dashboard/fragments/ping.rb +2 -2
- data/examples/app_fractal_dashboard/fragments/stats_panel.rb +4 -4
- data/examples/app_fractal_dashboard/fragments/system_info.rb +2 -2
- data/examples/app_fractal_dashboard/fragments/uptime.rb +2 -2
- data/examples/verify_website_first_app/app.rb +85 -0
- data/examples/verify_website_hello_mvu/app.rb +31 -0
- data/examples/widget_command_system/app.rb +15 -13
- data/exe/rooibos +10 -0
- data/generate_tutorial_stubs.rb +126 -0
- data/lib/rooibos/cli/commands/new.rb +373 -0
- data/lib/rooibos/cli/commands/run.rb +98 -0
- data/lib/rooibos/cli.rb +78 -0
- data/lib/rooibos/command/all.rb +76 -23
- data/lib/rooibos/command/batch.rb +61 -34
- data/lib/rooibos/command/custom.rb +84 -1
- data/lib/rooibos/command/http.rb +121 -55
- data/lib/rooibos/command/lifecycle.rb +5 -5
- data/lib/rooibos/command/open.rb +93 -0
- data/lib/rooibos/command/outlet.rb +105 -3
- data/lib/rooibos/command/wait.rb +9 -6
- data/lib/rooibos/command.rb +114 -89
- data/lib/rooibos/message/batch.rb +39 -0
- data/lib/rooibos/message/canceled.rb +51 -0
- data/lib/rooibos/message/error.rb +48 -0
- data/lib/rooibos/message/open.rb +30 -0
- data/lib/rooibos/message.rb +84 -4
- data/lib/rooibos/router.rb +11 -14
- data/lib/rooibos/runtime.rb +40 -43
- data/lib/rooibos/shortcuts.rb +47 -0
- data/lib/rooibos/test_helper.rb +71 -6
- data/lib/rooibos/version.rb +1 -1
- data/lib/rooibos/welcome.rb +237 -0
- data/lib/rooibos.rb +4 -3
- data/mise.toml +1 -1
- data/rbs_collection.lock.yaml +2 -2
- data/sig/concurrent.rbs +4 -0
- data/sig/gem.rbs +20 -0
- data/sig/rooibos/cli.rbs +42 -0
- data/sig/rooibos/command.rbs +59 -7
- data/sig/rooibos/message.rbs +66 -2
- data/sig/rooibos/shortcuts.rbs +14 -0
- data/sig/rooibos/test_helper.rbs +6 -2
- data/sig/rooibos/welcome.rbs +75 -0
- data/tasks/install.rake +29 -0
- data/tasks/resources/build.yml.erb +2 -0
- metadata +274 -38
- data/doc/concepts/application_architecture.md +0 -197
- data/doc/concepts/application_testing.md +0 -49
- data/doc/concepts/async_work.md +0 -164
- data/doc/concepts/commands.md +0 -530
- data/doc/concepts/message_processing.md +0 -51
- data/doc/contributors/WIP/decomposition_strategies_analysis.md +0 -258
- data/doc/contributors/WIP/implementation_plan.md +0 -409
- data/doc/contributors/WIP/init_callable_proposal.md +0 -344
- data/doc/contributors/WIP/runtime_refactoring_status.md +0 -47
- data/doc/contributors/WIP/task.md +0 -36
- data/doc/contributors/WIP/v0.4.0_todo.md +0 -468
- data/doc/contributors/kit-no-outlet.md +0 -238
- data/doc/contributors/priorities.md +0 -38
- data/doc/images/.gitkeep +0 -0
- data/exe/.gitkeep +0 -0
- /data/doc/contributors/{WIP → design}/mvu_tea_implementations_research.md +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Forms and Validation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Collect multi-field user input in your Model
|
|
12
|
+
- Validate input and store error messages
|
|
13
|
+
- Display inline validation feedback
|
|
14
|
+
- Submit form data via Commands
|
|
15
|
+
|
|
16
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
**Best Practices:** [Modal Dialogs](./modal_dialogs.md) | [Lists and Tables](./lists_and_tables.md) | [HTTP Workflows](./http_workflows.md) | [Streaming Data](./streaming_data.md) | [Orchestration](./orchestration.md)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# HTTP Workflows
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Model loading/success/error states for HTTP requests
|
|
12
|
+
- Implement retry logic with exponential backoff
|
|
13
|
+
- Cache responses to avoid redundant fetches
|
|
14
|
+
- Handle offline/error states gracefully
|
|
15
|
+
|
|
16
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
**Best Practices:** [Modal Dialogs](./modal_dialogs.md) | [Forms and Validation](./forms_and_validation.md) | [Lists and Tables](./lists_and_tables.md) | [Streaming Data](./streaming_data.md) | [Orchestration](./orchestration.md)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Best Practices
|
|
7
|
+
|
|
8
|
+
Common UI patterns and recipes for Rooibos applications.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## UI Patterns
|
|
13
|
+
|
|
14
|
+
- [**Modal Dialogs**](./modal_dialogs.md) — Overlays, focus capture, result routing
|
|
15
|
+
- [**Forms and Validation**](./forms_and_validation.md) — Multi-field input, error display
|
|
16
|
+
- [**Lists and Tables**](./lists_and_tables.md) — Scrolling, selection, pagination
|
|
17
|
+
|
|
18
|
+
## Data Patterns
|
|
19
|
+
|
|
20
|
+
- [**HTTP Workflows**](./http_workflows.md) — Loading states, retries, caching
|
|
21
|
+
- [**Streaming Data**](./streaming_data.md) — SSE, websockets, incremental updates
|
|
22
|
+
- [**Orchestration**](./orchestration.md) — Parallel commands, sequential flows
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
**Having trouble?** Check out [Troubleshooting](../troubleshooting/common_errors.md).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Lists and Tables
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Implement keyboard-navigable scrolling lists
|
|
12
|
+
- Track selection state in your Model
|
|
13
|
+
- Support pagination for large datasets
|
|
14
|
+
- Link to RatatuiRuby List and Table widgets
|
|
15
|
+
|
|
16
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
**Best Practices:** [Modal Dialogs](./modal_dialogs.md) | [Forms and Validation](./forms_and_validation.md) | [HTTP Workflows](./http_workflows.md) | [Streaming Data](./streaming_data.md) | [Orchestration](./orchestration.md)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Modal Dialogs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Implement a modal overlay that captures focus
|
|
12
|
+
- Route the dialog result (confirm/cancel) back to the parent
|
|
13
|
+
- Handle the "escape to close" pattern
|
|
14
|
+
- Design reusable modal fragments
|
|
15
|
+
|
|
16
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
**Best Practices:** [Forms and Validation](./forms_and_validation.md) | [Lists and Tables](./lists_and_tables.md) | [HTTP Workflows](./http_workflows.md) | [Streaming Data](./streaming_data.md) | [Orchestration](./orchestration.md)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Stateless Widgets in MVU
|
|
7
|
+
|
|
8
|
+
After reading this guide, you will know:
|
|
9
|
+
|
|
10
|
+
- Why Rooibos uses stateless widgets instead of stateful widgets
|
|
11
|
+
- How to achieve selection highlighting, scrolling, and bounds clamping
|
|
12
|
+
- When to handle Resize events for viewport-aware layouts
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Context
|
|
17
|
+
|
|
18
|
+
RatatuiRuby offers two widget rendering modes: stateless (`render_widget`) and stateful (`render_stateful_widget`). Stateful widgets accept a mutable state object during render, updating scroll offsets and clamping selections based on viewport size.
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# Stateful approach (imperative, inside draw block)
|
|
22
|
+
RatatuiRuby.draw do |frame|
|
|
23
|
+
frame.render_stateful_widget(list, frame.area, @list_state)
|
|
24
|
+
# @list_state is mutated during render
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Problem
|
|
29
|
+
|
|
30
|
+
MVU separates *what to display* (View) from *when to render* (Runtime). The View callable builds a widget tree. The Runtime renders it later.
|
|
31
|
+
|
|
32
|
+
Stateful widgets blur this boundary. They mutate state during render, creating hidden side effects. This breaks the MVU guarantee: **state only changes via Update**.
|
|
33
|
+
|
|
34
|
+
Passing mutable state during render means:
|
|
35
|
+
|
|
36
|
+
1. State changes happen outside Update
|
|
37
|
+
2. State mutations are invisible to the message flow
|
|
38
|
+
3. Testing becomes harder—you can't predict state after render
|
|
39
|
+
|
|
40
|
+
## Solution
|
|
41
|
+
|
|
42
|
+
Rooibos uses stateless widgets exclusively. All state management happens in your Model, computed by your Update function.
|
|
43
|
+
|
|
44
|
+
The "magic" that stateful widgets perform is simple arithmetic. You can do it yourself.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## What Stateful Widgets Actually Do
|
|
49
|
+
|
|
50
|
+
Stateful widgets perform two render-time operations:
|
|
51
|
+
|
|
52
|
+
### 1. Selection Bounds Clamping
|
|
53
|
+
|
|
54
|
+
If `selected >= items.length`, clamp to the last item:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# In Update, after any selection change
|
|
58
|
+
def clamp_selection(model)
|
|
59
|
+
max_index = model.items.length - 1
|
|
60
|
+
if model.selected && model.selected > max_index
|
|
61
|
+
model.with(selected: [max_index, 0].max)
|
|
62
|
+
else
|
|
63
|
+
model
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Scroll Offset Calculation
|
|
69
|
+
|
|
70
|
+
Compute which item should be at the top of the visible window:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# In Update, after selection or viewport changes
|
|
74
|
+
def calculate_offset(model)
|
|
75
|
+
return model unless model.selected
|
|
76
|
+
|
|
77
|
+
visible_count = model.viewport.height
|
|
78
|
+
selected = model.selected
|
|
79
|
+
offset = model.offset
|
|
80
|
+
|
|
81
|
+
# If selected is above current view, scroll up
|
|
82
|
+
if selected < offset
|
|
83
|
+
model.with(offset: selected)
|
|
84
|
+
# If selected is below current view, scroll down
|
|
85
|
+
elsif selected >= offset + visible_count
|
|
86
|
+
model.with(offset: selected - visible_count + 1)
|
|
87
|
+
else
|
|
88
|
+
model
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Storing Viewport Dimensions
|
|
96
|
+
|
|
97
|
+
Your Model stores the terminal dimensions. Query them in `Init` and update on `Resize` events.
|
|
98
|
+
|
|
99
|
+
### Init
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Init = -> {
|
|
103
|
+
[
|
|
104
|
+
Model.new(
|
|
105
|
+
items: [],
|
|
106
|
+
selected: nil,
|
|
107
|
+
offset: 0,
|
|
108
|
+
viewport: RatatuiRuby.viewport_area
|
|
109
|
+
),
|
|
110
|
+
fetch_items_command
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Update
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
Update = ->(model, message) {
|
|
119
|
+
case message
|
|
120
|
+
in Rooibos::Message::Resize(width:, height:)
|
|
121
|
+
new_viewport = RatatuiRuby::Layout::Rect.new(x: 0, y: 0, width:, height:)
|
|
122
|
+
new_model = model.with(viewport: new_viewport)
|
|
123
|
+
[calculate_offset(new_model), nil]
|
|
124
|
+
in SelectNext
|
|
125
|
+
new_selected = [(model.selected || -1) + 1, model.items.length - 1].min
|
|
126
|
+
new_model = model.with(selected: new_selected)
|
|
127
|
+
[calculate_offset(new_model), nil]
|
|
128
|
+
# ...
|
|
129
|
+
end
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### View
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
View = ->(model, tui) {
|
|
137
|
+
visible_items = model.items[model.offset, model.viewport.height] || []
|
|
138
|
+
|
|
139
|
+
list = tui.list(
|
|
140
|
+
items: visible_items,
|
|
141
|
+
highlight_style: tui.style(fg: :yellow)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Highlighting uses the offset-adjusted index
|
|
145
|
+
if model.selected
|
|
146
|
+
visible_selected = model.selected - model.offset
|
|
147
|
+
list = list.with(selected_index: visible_selected)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
list
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Benefits
|
|
157
|
+
|
|
158
|
+
### Testable State
|
|
159
|
+
|
|
160
|
+
All state changes happen in Update. Test your logic without rendering:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
def test_selection_clamps_to_bounds
|
|
164
|
+
viewport = RatatuiRuby::Layout::Rect.new(x: 0, y: 0, width: 80, height: 10)
|
|
165
|
+
model = Model.new(items: ["a", "b"], selected: 5, offset: 0, viewport:)
|
|
166
|
+
result, _ = Update.call(model, ClampSelection.new)
|
|
167
|
+
assert_equal 1, result.selected
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Predictable Rendering
|
|
172
|
+
|
|
173
|
+
View is a pure function. Given the same Model, it returns the same widget tree. No hidden mutations.
|
|
174
|
+
|
|
175
|
+
### No Frame Dependency
|
|
176
|
+
|
|
177
|
+
View runs before the draw callback. Terminal queries work normally.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## See Also
|
|
182
|
+
|
|
183
|
+
- [The Elm Architecture](../essentials/the_elm_architecture.md) — Core MVU concepts
|
|
184
|
+
- [Lists and Tables](lists_and_tables.md) — Patterns for scrollable collections
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Orchestration
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Coordinate parallel commands with `Command.all`
|
|
12
|
+
- Sequence dependent operations (A then B then C)
|
|
13
|
+
- Handle partial failures in multi-step workflows
|
|
14
|
+
- Design robust async pipelines
|
|
15
|
+
|
|
16
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
**Best Practices:** [Modal Dialogs](./modal_dialogs.md) | [Forms and Validation](./forms_and_validation.md) | [Lists and Tables](./lists_and_tables.md) | [HTTP Workflows](./http_workflows.md) | [Streaming Data](./streaming_data.md)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Streaming Data
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Handle SSE (Server-Sent Events) streams
|
|
12
|
+
- Process websocket messages as they arrive
|
|
13
|
+
- Update UI incrementally without blocking
|
|
14
|
+
- Clean up streaming connections on app exit
|
|
15
|
+
|
|
16
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
**Best Practices:** [Modal Dialogs](./modal_dialogs.md) | [Forms and Validation](./forms_and_validation.md) | [Lists and Tables](./lists_and_tables.md) | [HTTP Workflows](./http_workflows.md) | [Orchestration](./orchestration.md)
|
|
@@ -43,7 +43,7 @@ Long-running commands block the event loop. WebSocket listeners, database poller
|
|
|
43
43
|
|
|
44
44
|
`Thread#kill` terminates immediately. Mutexes may deadlock. Resources may leak. Database transactions may abort mid-write.
|
|
45
45
|
|
|
46
|
-
The `CancellationToken` signals cancellation requests. Commands check `token.
|
|
46
|
+
The `CancellationToken` signals cancellation requests. Commands check `token.canceled?` periodically and stop at safe points. Cleanup code runs. Resources release. Transactions commit.
|
|
47
47
|
|
|
48
48
|
| Aspect | `Thread#kill` | CancellationToken |
|
|
49
49
|
|--------|---------------|------------------|
|