rooibos 0.5.0 → 0.6.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 +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 +46 -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_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 +25 -20
- data/lib/rooibos/command/batch.rb +26 -25
- data/lib/rooibos/command/custom.rb +84 -1
- data/lib/rooibos/command/http.rb +59 -55
- data/lib/rooibos/command/lifecycle.rb +5 -5
- data/lib/rooibos/command/open.rb +86 -0
- data/lib/rooibos/command/outlet.rb +105 -3
- data/lib/rooibos/command/wait.rb +5 -5
- data/lib/rooibos/command.rb +57 -74
- 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 +3 -0
- data/sig/gem.rbs +20 -0
- data/sig/rooibos/cli.rbs +42 -0
- data/sig/rooibos/command.rbs +48 -0
- data/sig/rooibos/message.rbs +60 -0
- 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 +272 -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,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
7
|
+
#++
|
|
8
|
+
|
|
9
|
+
require "json"
|
|
10
|
+
|
|
11
|
+
# Parse stories from file_browser_stories.md
|
|
12
|
+
def parse_stories(file_path)
|
|
13
|
+
content = File.read(file_path)
|
|
14
|
+
stories = []
|
|
15
|
+
|
|
16
|
+
# Split by story headers and capture full content
|
|
17
|
+
sections = content.split(/^## Story (-?\d+): (.+)$/)
|
|
18
|
+
|
|
19
|
+
# sections[0] is preamble, then groups of [number, title, content]
|
|
20
|
+
(1...(sections.length)).step(3) do |i|
|
|
21
|
+
number = sections[i].to_i
|
|
22
|
+
title = sections[i + 1].strip
|
|
23
|
+
story_content = sections[i + 2]
|
|
24
|
+
|
|
25
|
+
# Extract until next story or end
|
|
26
|
+
story_content = story_content.split(/^---$/)[0].strip
|
|
27
|
+
|
|
28
|
+
stories << {
|
|
29
|
+
number:,
|
|
30
|
+
title:,
|
|
31
|
+
content: story_content,
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
stories.sort_by { |s| s[:number] }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Parse tutorial steps from tutorial files
|
|
39
|
+
def parse_tutorial_steps(tutorial_dir)
|
|
40
|
+
steps = []
|
|
41
|
+
|
|
42
|
+
# Get all tutorial files in order
|
|
43
|
+
tutorial_files = Dir.glob("#{tutorial_dir}/*.md")
|
|
44
|
+
|
|
45
|
+
tutorial_files.each do |file|
|
|
46
|
+
content = File.read(file)
|
|
47
|
+
|
|
48
|
+
# Extract H1 title
|
|
49
|
+
title_match = content.match(/^# (.+)$/)
|
|
50
|
+
title = title_match ? title_match[1].strip : File.basename(file, ".md")
|
|
51
|
+
|
|
52
|
+
# Store full content for display
|
|
53
|
+
steps << {
|
|
54
|
+
file: File.basename(file),
|
|
55
|
+
title:,
|
|
56
|
+
content:,
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
steps
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Main interactive mapping logic
|
|
64
|
+
def run_interactive_mapping(stories, tutorial_steps)
|
|
65
|
+
results = {}
|
|
66
|
+
tutorial_steps.each { |step| results[step[:title]] = Set.new }
|
|
67
|
+
|
|
68
|
+
story_index = 0
|
|
69
|
+
|
|
70
|
+
tutorial_steps.each do |step|
|
|
71
|
+
while story_index < stories.length
|
|
72
|
+
story = stories[story_index]
|
|
73
|
+
|
|
74
|
+
# Display tutorial step EVERY time
|
|
75
|
+
puts "\n#{'=' * 80}"
|
|
76
|
+
puts "TUTORIAL STEP: #{step[:title]}"
|
|
77
|
+
puts "=" * 80
|
|
78
|
+
puts step[:content]
|
|
79
|
+
puts "=" * 80
|
|
80
|
+
puts "\n"
|
|
81
|
+
|
|
82
|
+
# Display current story
|
|
83
|
+
puts "-" * 80
|
|
84
|
+
puts "Story #{story[:number]}: #{story[:title]}"
|
|
85
|
+
puts "-" * 80
|
|
86
|
+
puts story[:content]
|
|
87
|
+
puts "-" * 80
|
|
88
|
+
print "\nDoes the learner know enough by this step to implement this story? [y/n]: "
|
|
89
|
+
|
|
90
|
+
answer = gets.chomp.downcase
|
|
91
|
+
|
|
92
|
+
if answer == "y"
|
|
93
|
+
results[step[:title]].add(story)
|
|
94
|
+
puts "✓ Added Story #{story[:number]} to '#{step[:title]}'"
|
|
95
|
+
story_index += 1
|
|
96
|
+
puts ""
|
|
97
|
+
else
|
|
98
|
+
puts "→ Moving to next tutorial step...\n"
|
|
99
|
+
break
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Handle unallocated stories
|
|
105
|
+
if story_index < stories.length
|
|
106
|
+
results[:unallocated] = Set.new
|
|
107
|
+
while story_index < stories.length
|
|
108
|
+
results[:unallocated].add(stories[story_index])
|
|
109
|
+
story_index += 1
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
results
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Pretty print results
|
|
117
|
+
def print_results(results)
|
|
118
|
+
puts "\n\n"
|
|
119
|
+
puts "=" * 80
|
|
120
|
+
puts "FINAL MAPPING RESULTS"
|
|
121
|
+
puts "=" * 80
|
|
122
|
+
|
|
123
|
+
results.each do |step_title, stories|
|
|
124
|
+
puts "\n### #{step_title}"
|
|
125
|
+
|
|
126
|
+
if stories.empty?
|
|
127
|
+
puts " (No stories)"
|
|
128
|
+
else
|
|
129
|
+
stories.to_a.sort_by { |s| s[:number] }.each do |story|
|
|
130
|
+
puts " - Story #{story[:number]}: #{story[:title]}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Summary
|
|
136
|
+
puts "\n#{'=' * 80}"
|
|
137
|
+
puts "SUMMARY"
|
|
138
|
+
puts "=" * 80
|
|
139
|
+
total_allocated = results.except(:unallocated).values.sum(&:size)
|
|
140
|
+
total_unallocated = results[:unallocated]&.size || 0
|
|
141
|
+
puts "Total stories allocated: #{total_allocated}"
|
|
142
|
+
puts "Total stories unallocated: #{total_unallocated}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Main execution
|
|
146
|
+
if __FILE__ == $0
|
|
147
|
+
stories_file = "/Users/kerrick/Developer/ratatui_ruby-tea/doc/contributors/specs/file_browser_stories.md"
|
|
148
|
+
tutorial_dir = "/Users/kerrick/Developer/ratatui_ruby-tea/doc/tutorial"
|
|
149
|
+
|
|
150
|
+
puts "Parsing stories from #{stories_file}..."
|
|
151
|
+
stories = parse_stories(stories_file)
|
|
152
|
+
puts "Found #{stories.length} stories"
|
|
153
|
+
|
|
154
|
+
puts "\nParsing tutorial steps from #{tutorial_dir}..."
|
|
155
|
+
tutorial_steps = parse_tutorial_steps(tutorial_dir)
|
|
156
|
+
puts "Found #{tutorial_steps.length} tutorial steps"
|
|
157
|
+
|
|
158
|
+
puts "\n#{'=' * 80}"
|
|
159
|
+
puts "Starting interactive mapping..."
|
|
160
|
+
puts "=" * 80
|
|
161
|
+
puts "Instructions: For each story, answer 'y' if the learner has enough"
|
|
162
|
+
puts "knowledge from the current tutorial step to implement it, 'n' otherwise."
|
|
163
|
+
puts "=" * 80
|
|
164
|
+
|
|
165
|
+
results = run_interactive_mapping(stories, tutorial_steps)
|
|
166
|
+
print_results(results)
|
|
167
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Scrollbar Widget Support
|
|
7
|
+
|
|
8
|
+
## Status
|
|
9
|
+
|
|
10
|
+
**Blocked.** Scrollbar overlays require frame-level rendering, which MVU abstracts away.
|
|
11
|
+
|
|
12
|
+
## Problem
|
|
13
|
+
|
|
14
|
+
Scrollbar widgets in RatatuiRuby render as overlays on the same area as content:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
# In RatatuiRuby (imperative)
|
|
18
|
+
frame.render_widget(paragraph, frame.area)
|
|
19
|
+
frame.render_widget(scrollbar, frame.area) # Overlays on right edge
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
In Rooibos MVU, View returns a widget tree. The runtime renders all widgets sequentially. When a scrollbar is added as a sibling to a list inside a block:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# In Rooibos (MVU)
|
|
26
|
+
tui.block(children: [list, scrollbar])
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The scrollbar renders **inside** the content area, not **on** the border. It also inherits layout properties (takes up width, changes sibling positioning).
|
|
30
|
+
|
|
31
|
+
## Why This Happens
|
|
32
|
+
|
|
33
|
+
1. **No frame access in View.** View is a pure function that returns widgets. It cannot call `frame.render_widget` for overlay positioning.
|
|
34
|
+
|
|
35
|
+
2. **Children are siblings.** Block's `children` are laid out sequentially, not stacked.
|
|
36
|
+
|
|
37
|
+
3. **Scrollbar is position-aware.** It needs to know the rendered area to position itself at the right edge.
|
|
38
|
+
|
|
39
|
+
## Workarounds Considered
|
|
40
|
+
|
|
41
|
+
### 1. Overlay Widget (Not Implemented)
|
|
42
|
+
|
|
43
|
+
A dedicated overlay widget that renders children on top of each other:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
tui.overlay(base: list, overlay: scrollbar)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Problem:** Would need to track which child gets which area and handle z-ordering.
|
|
50
|
+
|
|
51
|
+
### 2. Block with Scrollbar Option
|
|
52
|
+
|
|
53
|
+
Add scrollbar support directly to Block:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
tui.block(
|
|
57
|
+
children: [list],
|
|
58
|
+
scrollbar: { content_length: 100, position: 10 }
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The Block would render its children, then overlay the scrollbar on the right border.
|
|
63
|
+
|
|
64
|
+
**Advantages:**
|
|
65
|
+
- Scrollbar naturally belongs with the bordered container
|
|
66
|
+
- No new widget type needed
|
|
67
|
+
- Block already handles border rendering
|
|
68
|
+
|
|
69
|
+
**Disadvantages:**
|
|
70
|
+
- Would need to be added to RatatuiRuby, or
|
|
71
|
+
- Would need to intercept TUI facade and Block.new
|
|
72
|
+
|
|
73
|
+
### 3. Custom Widget (Current Pattern)
|
|
74
|
+
|
|
75
|
+
Users implement scrollbar rendering in a custom widget that has frame access:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
class ScrollableList
|
|
79
|
+
def render(area)
|
|
80
|
+
# Split area, render list, then overlay scrollbar
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Problem:** Breaks the pure View pattern. Custom widgets are RatatuiRuby-level, not MVU-level. Duplicates logic from Scrollbar.
|
|
86
|
+
|
|
87
|
+
### 4. Provide `Frame` to View
|
|
88
|
+
|
|
89
|
+
Pass the frame to View, so it can call `render_widget` for overlay positioning.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
module MyFragment
|
|
93
|
+
View = -> (model, tui, frame) {
|
|
94
|
+
tui.block(
|
|
95
|
+
children: [tui.list(items: model.items)],
|
|
96
|
+
scrollbar: { content_length: model.items.length, position: model.offset }
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Problem:** Breaks the pure View-as-returned-tree pattern.
|
|
103
|
+
|
|
104
|
+
## Recommendation
|
|
105
|
+
|
|
106
|
+
Investigate more options.
|
|
107
|
+
|
|
108
|
+
## Open Questions
|
|
109
|
+
|
|
110
|
+
1. Should scrollbar position be `offset` (scroll window position) or `selected_index`?
|
|
111
|
+
2. Should scrollbar automatically infer `content_length` from list children?
|
|
112
|
+
3. How to handle horizontal scrollbars?
|
|
113
|
+
|
|
114
|
+
## See Also
|
|
115
|
+
|
|
116
|
+
- [no_stateful_widgets.md](../best_practices/no_stateful_widgets.md) — MVU scroll offset patterns
|
|
117
|
+
- [RatatuiRuby Scrollbar](https://git.sr.ht/~kerrick/ratatui_ruby/tree/stable/item/lib/ratatui_ruby/widgets/scrollbar.rb)
|
|
118
|
+
- [widget_scrollbar example](https://git.sr.ht/~kerrick/ratatui_ruby/tree/stable/item/examples/widget_scrollbar)
|
|
@@ -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
|
+
# Project Setup
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Create a new Rooibos project with the correct folder structure
|
|
12
|
+
- Configure your Gemfile with required dependencies
|
|
13
|
+
- Run a smoke test to verify your environment works
|
|
14
|
+
- Understand what each file in the project does
|
|
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
|
+
[**Previous:** Tutorial: Build a File Browser](./index.md) | [**Next:** Hello World](./02_hello_world.md)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Hello World
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Write your first VIEW callable that renders text
|
|
12
|
+
- Write your first UPDATE callable that handles keyboard input
|
|
13
|
+
- Handle 'q' and Ctrl+C to quit the application
|
|
14
|
+
- Use predicate helpers (`.q?`, `.ctrl_c?`) for key checks
|
|
15
|
+
- Run your app and see output in the terminal
|
|
16
|
+
- Identify the entry point that starts your application
|
|
17
|
+
- Explain what a VIEW callable returns (RatatuiRuby widgets)
|
|
18
|
+
- Explain what an UPDATE callable returns (new model and command)
|
|
19
|
+
|
|
20
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
[**Previous:** Project Setup](./01_project_setup.md) | [**Next:** Adding State](./03_adding_state.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
|
+
# Adding State
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Define a Model using `Data.define` to hold your app state
|
|
12
|
+
- Explain why state is stored in a struct, not instance variables
|
|
13
|
+
- Pass the Model to your VIEW function
|
|
14
|
+
- Display dynamic content based on state
|
|
15
|
+
- Use `model.with(...)` to create updated state immutably
|
|
16
|
+
- Use predicate helpers (`.up?`, `.down?`, `.j?`, `.k?`) for key checks
|
|
17
|
+
- Use pattern matching (`case message`) for complex message handling
|
|
18
|
+
- Return a new Model from UPDATE with state changes
|
|
19
|
+
- Navigate your file browser with arrow keys and vim keys
|
|
20
|
+
- Understand when to use predicates vs pattern matching
|
|
21
|
+
|
|
22
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
[**Previous:** Hello World](./02_hello_world.md) | [**Next:** Organizing Your Code](./06_organizing_your_code.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
|
+
# Organizing Your Code
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Extract a reusable fragment from your app
|
|
12
|
+
- Define the Init callable that creates initial state
|
|
13
|
+
- Compose multiple fragments into one application
|
|
14
|
+
- Decide when to extract vs. keep code inline
|
|
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
|
+
[**Previous:** Adding State](./03_adding_state.md) | [**Next:** Your First Command](./07_your_first_command.md)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Your First Command
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Understand when Commands are needed (or better) vs. direct model updates
|
|
12
|
+
- Explain why some operations cannot happen inside UPDATE (I/O, async)
|
|
13
|
+
- Use `Command.system` to read a file asynchronously
|
|
14
|
+
- Handle the command result as a message
|
|
15
|
+
- Return `[model, command]` tuples from UPDATE
|
|
16
|
+
|
|
17
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
[**Previous:** Organizing Your Code](./06_organizing_your_code.md) | [**Next:** The Preview Pane](./08_the_preview_pane.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
|
+
# The Preview Pane
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Build a second fragment that displays file contents
|
|
12
|
+
- Use RatatuiRuby layouts to arrange two views side-by-side
|
|
13
|
+
- Coordinate state between the tree view and preview pane
|
|
14
|
+
- Route messages from parent to child 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
|
+
[**Previous:** Your First Command](./07_your_first_command.md) | [**Next:** Loading States](./09_loading_states.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
|
+
# Loading States
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Model three states: loading, success, and error
|
|
12
|
+
- Display a spinner or message while data loads
|
|
13
|
+
- Handle errors gracefully with user feedback
|
|
14
|
+
- Avoid rendering stale data during transitions
|
|
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
|
+
[**Previous:** The Preview Pane](./08_the_preview_pane.md) | [**Next:** Testing Your App](./10_testing_your_app.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
|
+
# Testing Your App
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Set up Rooibos::TestHelper in your test file
|
|
12
|
+
- Write unit tests for UPDATE functions
|
|
13
|
+
- Assert that VIEW renders expected content
|
|
14
|
+
- Simulate keyboard input in tests
|
|
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
|
+
[**Previous:** Loading States](./09_loading_states.md) | [**Next:** Polish and Refine](./11_polish_and_refine.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
|
+
# Polish and Refine
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Handle edge cases (empty directories, permission errors)
|
|
12
|
+
- Add keyboard shortcuts with a help overlay
|
|
13
|
+
- Improve perceived performance with optimistic updates
|
|
14
|
+
- Apply finishing touches that make your app feel professional
|
|
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
|
+
[**Previous:** Testing Your App](./10_testing_your_app.md) | [**Next:** Going Further](./12_going_further.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
|
+
# Going Further
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Know where to find the Essentials for deeper understanding
|
|
12
|
+
- Identify which Scaling Up topics apply to your next project
|
|
13
|
+
- Find Best Practices for common UI patterns
|
|
14
|
+
- Decide what to build next
|
|
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
|
+
[**Previous:** Polish and Refine](./11_polish_and_refine.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
|
+
# Tutorial: Build a File Browser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Build a fully-functional file browser TUI from scratch
|
|
12
|
+
- Apply all core Rooibos concepts (Model, Update, View, Commands)
|
|
13
|
+
- Understand how to structure a real-world Rooibos application
|
|
14
|
+
- Have a working app you can extend and customize
|
|
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
|
+
[**Next:** Project Setup](./01_project_setup.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
|
+
# Commands
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Choose the right built-in command (`system`, `wait`, `http`, `batch`, `all`, `exit`, `cancel`)
|
|
12
|
+
- Explain the command lifecycle: dispatch → execute → message
|
|
13
|
+
- Compare Commands to React useEffect ("Commands are explicit, not magical")
|
|
14
|
+
- Combine multiple commands with `Command.batch`
|
|
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
|
+
[**Previous:** Views](./views.md) | [**Next:** The Runtime](./the_runtime.md)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Essentials
|
|
7
|
+
|
|
8
|
+
Core concepts for building Rooibos applications.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## The Foundation
|
|
13
|
+
|
|
14
|
+
- [**The Elm Architecture**](./the_elm_architecture.md) — Model-View-Update explained
|
|
15
|
+
- [**Models**](./models.md) — Designing state with `Data.define`
|
|
16
|
+
- [**Messages**](./messages.md) — Events, predicates, and pattern matching
|
|
17
|
+
- [**Update Functions**](./update_functions.md) — Pure state transitions
|
|
18
|
+
|
|
19
|
+
## Rendering & Effects
|
|
20
|
+
|
|
21
|
+
- [**Views**](./views.md) — Rendering with RatatuiRuby widgets
|
|
22
|
+
- [**Commands**](./commands.md) — Async operations and side effects
|
|
23
|
+
|
|
24
|
+
## The Glue
|
|
25
|
+
|
|
26
|
+
- [**The Runtime**](./the_runtime.md) — How Rooibos orchestrates your app
|
|
27
|
+
- [**Shortcuts**](./shortcuts.md) — `Cmd` and `Msg` aliases for concise code
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
**Ready for advanced patterns?** Continue to [Scaling Up](../scaling_up/custom_commands.md).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Messages
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Understand message types: RatatuiRuby::Event (user input) and Rooibos::Message (command responses)
|
|
12
|
+
- Define message types that describe what happened in your app
|
|
13
|
+
- Use pattern matching (`case/in`) to route messages to handlers
|
|
14
|
+
- Apply predicate helpers (`.key?`, `.q?`) for keyboard events
|
|
15
|
+
- Design a message vocabulary for your domain
|
|
16
|
+
|
|
17
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
[**Previous:** Models](./models.md) | [**Next:** Update Functions](./update_functions.md)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Design application state with `Data.define`
|
|
12
|
+
- Explain why Rooibos uses immutable structs instead of instance variables
|
|
13
|
+
- Choose between flat and nested state structures
|
|
14
|
+
- Create new state with `.with()` instead of mutation
|
|
15
|
+
- Split a complex Model into nested Models
|
|
16
|
+
|
|
17
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
[**Previous:** The Elm Architecture](./the_elm_architecture.md) | [**Next:** Messages](./messages.md)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Shortcuts
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Use `Cmd` and `Msg` module aliases for concise code
|
|
12
|
+
- Understand the full vs. shorthand forms
|
|
13
|
+
- Know when shorthand improves vs. hurts readability
|
|
14
|
+
|
|
15
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
[**Previous:** The Runtime](./the_runtime.md)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# The Elm Architecture
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
By the end of this guide, you will:
|
|
10
|
+
|
|
11
|
+
- Identify the three callables: Init, Update, and View
|
|
12
|
+
- Explain why UPDATE must be a pure function (no side effects)
|
|
13
|
+
- Understand the separation of concerns in MVU architecture
|
|
14
|
+
- Explain Model-View-Update in your own words
|
|
15
|
+
- Trace data flow through the complete MVU cycle
|
|
16
|
+
- Describe how unidirectional data flow prevents bugs
|
|
17
|
+
- Draw the MVU loop from memory
|
|
18
|
+
- Compare MVU to web server request/response cycle ("unlike a web request, state persists")
|
|
19
|
+
|
|
20
|
+
> ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
[**Next:** Models](./models.md)
|