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,112 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Documentation Stub Audit
|
|
7
|
+
|
|
8
|
+
Audit of learning objectives against the actual Rooibos codebase.
|
|
9
|
+
|
|
10
|
+
**Legend:**
|
|
11
|
+
- ⚠️ Minor issue or suggestion
|
|
12
|
+
- ❌ Major issue, needs revision
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Essentials
|
|
17
|
+
|
|
18
|
+
## Scaling Up
|
|
19
|
+
|
|
20
|
+
## Best Practices
|
|
21
|
+
|
|
22
|
+
### `forms_and_validation.md`
|
|
23
|
+
|
|
24
|
+
**Audit:**
|
|
25
|
+
- ❌ **MAJOR:** Need `examples/app_form/` — multi-field form with validation
|
|
26
|
+
- ❌ **MAJOR:** Need validation error display example in the form app
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### `lists_and_tables.md`
|
|
31
|
+
|
|
32
|
+
**Audit:**
|
|
33
|
+
- ❌ **MAJOR:** Need `examples/app_master_detail/` — list sidebar + table top-right + detail pane bottom-right (list chooses table, table chooses detail)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### `http_workflows.md`
|
|
38
|
+
|
|
39
|
+
**Audit:**
|
|
40
|
+
- ❌ **MAJOR:** Need `examples/app_web_api/` — retry logic with exponential backoff
|
|
41
|
+
- ❌ **MAJOR:** Need caching example in the HTTP app
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### `streaming_data.md`
|
|
46
|
+
|
|
47
|
+
**Audit:**
|
|
48
|
+
- ❌ **MAJOR:** Need `examples/app_websocket/` — simplified version of official third-party WebSocket gem to teach SSE/websocket patterns
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### `orchestration.md`
|
|
53
|
+
|
|
54
|
+
**Audit:**
|
|
55
|
+
- ❌ **MAJOR:** Need `examples/app_command_orchestration/` — partial failure handling and multi-step pipeline patterns
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Troubleshooting
|
|
60
|
+
|
|
61
|
+
## Tutorial
|
|
62
|
+
|
|
63
|
+
### `index.md`
|
|
64
|
+
|
|
65
|
+
**Audit:**
|
|
66
|
+
- ❌ **MAJOR:** Need `examples/app_file_browser/` — complete example that tutorial chapters will walk through building
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### `04_handling_input.md`
|
|
71
|
+
|
|
72
|
+
**Audit:**
|
|
73
|
+
- ℹ️ **Objective 5:** References the file browser from `examples/app_file_browser/` — tutorial walks through building it
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### `08_the_preview_pane.md`
|
|
78
|
+
|
|
79
|
+
**Audit:**
|
|
80
|
+
- ℹ️ **Objective 1:** References the file browser from `examples/app_file_browser/` — tutorial walks through building it
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### `11_polish_and_refine.md`
|
|
85
|
+
|
|
86
|
+
**Audit:**
|
|
87
|
+
- ℹ️ **Objective 1:** Edge case handling is application logic — teach patterns, not framework features
|
|
88
|
+
- ℹ️ **Objective 2:** Help overlay is application-level UI — teach implementation patterns
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `12_going_further.md`
|
|
93
|
+
|
|
94
|
+
**Audit:**
|
|
95
|
+
- ℹ️ All objectives are purely meta/navigational — appropriate for "Going Further" chapter
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Getting Started
|
|
100
|
+
|
|
101
|
+
### `for_go_developers.md`
|
|
102
|
+
|
|
103
|
+
**Audit:**
|
|
104
|
+
- ℹ️ **Objective 3:** Rooibos Cmd pattern differs — uses `Command::Custom` protocol vs BubbleTea's `tea.Cmd` function (teach the difference)
|
|
105
|
+
- ❌ **MAJOR:** Need `examples/app_bubbletea_counter/` — side-by-side comparison with equivalent BubbleTea code
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### `ruby_primer.md`
|
|
110
|
+
|
|
111
|
+
**Audit:**
|
|
112
|
+
- ℹ️ All objectives are Ruby language education — appropriate for onboarding non-Rubyists
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Documentation Style Guide
|
|
7
|
+
|
|
8
|
+
This guide defines how to write Rooibos user documentation. It applies to all markdown files in `doc/` (except `doc/contributors/`, which follows its own standards).
|
|
9
|
+
|
|
10
|
+
**All docs must be reviewed against this guide before merging.**
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Document Types
|
|
15
|
+
|
|
16
|
+
Rooibos has three distinct document types. Each follows different patterns:
|
|
17
|
+
|
|
18
|
+
| Type | Location | Pattern | Purpose |
|
|
19
|
+
|------|----------|---------|---------|
|
|
20
|
+
| **Tutorial** | `tutorial/` | Progressive build | Hands-on learning |
|
|
21
|
+
| **Concept** | `essentials/`, `scaling_up/`, `best_practices/` | Context-Problem-Solution | Understanding why |
|
|
22
|
+
| **Troubleshooting** | `troubleshooting/` | Error → Cause → Fix | Solving problems |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Tutorial Chapters
|
|
27
|
+
|
|
28
|
+
Tutorials teach through action. Readers learn by doing, then understanding.
|
|
29
|
+
|
|
30
|
+
### Structure
|
|
31
|
+
|
|
32
|
+
1. **Chapter title** — What you'll accomplish
|
|
33
|
+
2. **Learning objectives** — What you'll know by the end (bulleted list)
|
|
34
|
+
3. **Prerequisites** — What you should have completed first
|
|
35
|
+
4. **Steps** — Numbered actions: "Type this → Run this → See this"
|
|
36
|
+
5. **Explanation** — Why it works (after the reader has seen it work)
|
|
37
|
+
6. **Summary** — What you learned
|
|
38
|
+
|
|
39
|
+
### Example
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
# Adding State to Your App
|
|
43
|
+
|
|
44
|
+
By the end of this chapter, you will:
|
|
45
|
+
- Understand what a Model is
|
|
46
|
+
- Create your first `Data.define` struct
|
|
47
|
+
- See how state flows through the MVU loop
|
|
48
|
+
|
|
49
|
+
## Prerequisites
|
|
50
|
+
|
|
51
|
+
Complete [Your First View](02_hello_world.md) before starting.
|
|
52
|
+
|
|
53
|
+
## Step 1: Define Your Model
|
|
54
|
+
|
|
55
|
+
Create a new file called `model.rb`:
|
|
56
|
+
|
|
57
|
+
Model = Data.define(:current_path, :entries)
|
|
58
|
+
|
|
59
|
+
Run your app:
|
|
60
|
+
|
|
61
|
+
ruby app.rb
|
|
62
|
+
|
|
63
|
+
You should see...
|
|
64
|
+
|
|
65
|
+
## Why This Works
|
|
66
|
+
|
|
67
|
+
The `Model` holds all of your application state...
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Guidelines
|
|
71
|
+
|
|
72
|
+
- **Do first, explain after** — Show the code, let them run it, then explain
|
|
73
|
+
- **Small steps** — Each step should produce a visible result
|
|
74
|
+
- **No surprises** — If something can go wrong, warn them first
|
|
75
|
+
- **Screenshots** — Show what success looks like
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Concept Documents
|
|
80
|
+
|
|
81
|
+
Concept docs explain ideas. They answer "why" questions.
|
|
82
|
+
|
|
83
|
+
### Structure: Context-Problem-Solution (Alexandrian Form)
|
|
84
|
+
|
|
85
|
+
Every concept doc follows this pattern:
|
|
86
|
+
|
|
87
|
+
1. **Title** — The concept name
|
|
88
|
+
2. **Learning objectives** — "After reading this guide, you will know:" (Rails pattern)
|
|
89
|
+
3. **Context** — Set the scene. What situation is the reader in?
|
|
90
|
+
4. **Problem** — What difficulty or pain exists without this?
|
|
91
|
+
5. **Solution** — How Rooibos solves it
|
|
92
|
+
6. **Deep dive** — Detailed explanation with examples
|
|
93
|
+
7. **See also** — Cross-references to related docs
|
|
94
|
+
|
|
95
|
+
### Example
|
|
96
|
+
|
|
97
|
+
```markdown
|
|
98
|
+
# The Elm Architecture
|
|
99
|
+
|
|
100
|
+
After reading this guide, you will know:
|
|
101
|
+
- What Model-View-Update means
|
|
102
|
+
- Why unidirectional data flow prevents bugs
|
|
103
|
+
- How the runtime coordinates your app
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Context
|
|
108
|
+
|
|
109
|
+
Applications have state. A counter has a count. A file browser has a current directory. A chat app has messages.
|
|
110
|
+
|
|
111
|
+
State changes over time. Users click. Servers respond. Timers fire.
|
|
112
|
+
|
|
113
|
+
## Problem
|
|
114
|
+
|
|
115
|
+
Coordinating state changes is hard. Callbacks create spaghetti. Shared mutable state causes race conditions. Event emitters become untraceable.
|
|
116
|
+
|
|
117
|
+
## Solution
|
|
118
|
+
|
|
119
|
+
The Elm Architecture solves this with **unidirectional data flow**:
|
|
120
|
+
|
|
121
|
+
1. **Model** — A single source of truth for state
|
|
122
|
+
2. **Update** — A pure function that computes the next state
|
|
123
|
+
3. **View** — A function that renders the model
|
|
124
|
+
|
|
125
|
+
...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Guidelines
|
|
129
|
+
|
|
130
|
+
- **Start with why** — Don't explain what a Command is; explain why Commands exist
|
|
131
|
+
- **Use diagrams** — ASCII or mermaid for data flow
|
|
132
|
+
- **Show bad then good** — Contrast the painful way with the elegant way
|
|
133
|
+
- **Link out to RatatuiRuby** — Don't re-document widgets; link to their guides
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Callouts
|
|
138
|
+
|
|
139
|
+
Use callouts to highlight important information. Standard types:
|
|
140
|
+
|
|
141
|
+
### Syntax
|
|
142
|
+
|
|
143
|
+
```markdown
|
|
144
|
+
> **Note**: Background context that's helpful but not critical.
|
|
145
|
+
|
|
146
|
+
> **Tip**: A shortcut, best practice, or efficiency suggestion.
|
|
147
|
+
|
|
148
|
+
> **Warning**: Something that could cause confusion or bugs if ignored.
|
|
149
|
+
|
|
150
|
+
> **Caution**: High-risk actions that could cause data loss or crashes.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### When to Use
|
|
154
|
+
|
|
155
|
+
| Callout | Use for |
|
|
156
|
+
|---------|---------|
|
|
157
|
+
| **Note** | "By the way..." — Additional context |
|
|
158
|
+
| **Tip** | "Pro tip..." — Efficiency improvements |
|
|
159
|
+
| **Warning** | "Watch out..." — Common mistakes |
|
|
160
|
+
| **Caution** | "Danger..." — Breaking changes, data loss |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Cross-References
|
|
165
|
+
|
|
166
|
+
Every document should have **3+ cross-references** to related docs.
|
|
167
|
+
|
|
168
|
+
### Related Topic Links
|
|
169
|
+
|
|
170
|
+
```markdown
|
|
171
|
+
See [The Elm Architecture](../essentials/the_elm_architecture.md) for background.
|
|
172
|
+
|
|
173
|
+
For advanced patterns, see [Fractal Architecture](../scaling_up/fractal_architecture.md).
|
|
174
|
+
|
|
175
|
+
Related: [Commands](commands.md) | [Messages](messages.md) | [The Runtime](the_runtime.md)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Next/Previous Navigation
|
|
179
|
+
|
|
180
|
+
Tutorial chapters and sequential docs should end with navigation links:
|
|
181
|
+
|
|
182
|
+
```markdown
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
← Previous: [Handling Input](04_handling_input.md) | Next: [Organizing Your Code](06_organizing_your_code.md) →
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Link to RatatuiRuby
|
|
189
|
+
|
|
190
|
+
Rooibos docs focus on architecture. Link to RatatuiRuby for rendering:
|
|
191
|
+
|
|
192
|
+
```markdown
|
|
193
|
+
For widget details, see the [RatatuiRuby List docs](https://ratatui-ruby.dev/docs).
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Prose Style
|
|
199
|
+
|
|
200
|
+
Based on Zinsser's *On Writing Well* and Plain Language Guidelines.
|
|
201
|
+
|
|
202
|
+
### Do
|
|
203
|
+
|
|
204
|
+
- **Use active voice** — "The runtime dispatches messages" not "Messages are dispatched"
|
|
205
|
+
- **Use short sentences** — One idea per sentence
|
|
206
|
+
- **Address the reader** — "You" not "the developer"
|
|
207
|
+
- **Use imperative mood** — "Create a model" not "You should create a model"
|
|
208
|
+
|
|
209
|
+
### Avoid
|
|
210
|
+
|
|
211
|
+
| Avoid | Use instead |
|
|
212
|
+
|-------|-------------|
|
|
213
|
+
| "allows you to" | (just state what happens) |
|
|
214
|
+
| "provides functionality for" | (state what it does) |
|
|
215
|
+
| "in order to" | "to" |
|
|
216
|
+
| "utilize" | "use" |
|
|
217
|
+
| "prior to" | "before" |
|
|
218
|
+
| "You must" / "You need to" | (imperative: "Do X") |
|
|
219
|
+
|
|
220
|
+
### Examples
|
|
221
|
+
|
|
222
|
+
**Bad:**
|
|
223
|
+
> The Command module provides functionality that allows the user to perform asynchronous operations.
|
|
224
|
+
|
|
225
|
+
**Good:**
|
|
226
|
+
> Commands run async work. Use them to fetch data, wait for timers, or perform I/O.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Code Examples
|
|
231
|
+
|
|
232
|
+
### Format
|
|
233
|
+
|
|
234
|
+
Use fenced code blocks with language identifier:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
Model = Data.define(:count)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Guidelines
|
|
241
|
+
|
|
242
|
+
- **Complete and runnable** — Every example should work if copy-pasted
|
|
243
|
+
- **Show output** — Include comments showing what prints or renders
|
|
244
|
+
- **Progressive complexity** — Start simple, add complexity gradually
|
|
245
|
+
- **Highlight changes** — When building on previous code, comment new lines
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Learning Objectives (Rails Pattern)
|
|
250
|
+
|
|
251
|
+
Every concept doc starts with learning objectives:
|
|
252
|
+
|
|
253
|
+
```markdown
|
|
254
|
+
After reading this guide, you will know:
|
|
255
|
+
|
|
256
|
+
- How to define a Model using `Data.define`
|
|
257
|
+
- Why immutability matters in MVU
|
|
258
|
+
- When to use nested structs vs. flat state
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
This pattern comes from Rails Guides and sets reader expectations.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Checklist
|
|
266
|
+
|
|
267
|
+
Before submitting documentation, verify:
|
|
268
|
+
|
|
269
|
+
- [ ] **Type identified** — Is this a tutorial, concept, or troubleshooting doc?
|
|
270
|
+
- [ ] **Pattern followed** — Does it follow the correct structure for its type?
|
|
271
|
+
- [ ] **Learning objectives** — Does it start with what the reader will learn?
|
|
272
|
+
- [ ] **Cross-references** — Are there 3+ links to related docs?
|
|
273
|
+
- [ ] **Code examples** — Are all examples complete and runnable?
|
|
274
|
+
- [ ] **Prose style** — Active voice? Short sentences? No "allows you to"?
|
|
275
|
+
- [ ] **RatatuiRuby links** — Did you link out for widget/layout details?
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# End-to-End PTY Testing for TUI Applications
|
|
8
|
+
|
|
9
|
+
This document explains how to write end-to-end tests for Rooibos TUI applications
|
|
10
|
+
using Ruby's PTY module. These tests verify that `rooibos new` generated apps
|
|
11
|
+
can be launched via `rooibos run` and respond correctly to user input.
|
|
12
|
+
|
|
13
|
+
## Background
|
|
14
|
+
|
|
15
|
+
Testing TUI applications is challenging because:
|
|
16
|
+
|
|
17
|
+
1. **TUI apps require a real terminal** - They use raw mode, escape sequences,
|
|
18
|
+
and read from the controlling terminal (`/dev/tty`), not just stdin.
|
|
19
|
+
2. **Crossterm reads from the controlling terminal** - Via `isatty()` check on
|
|
20
|
+
stdin, falling back to `/dev/tty` if stdin isn't a tty.
|
|
21
|
+
3. **Bundler context matters** - Tests running via `bundle exec` inherit that
|
|
22
|
+
Bundler context, which can interfere with the generated app's own dependencies.
|
|
23
|
+
|
|
24
|
+
## Key Learnings
|
|
25
|
+
|
|
26
|
+
### 1. Use `PTY.spawn` for Proper Terminal Emulation
|
|
27
|
+
|
|
28
|
+
`PTY.spawn` properly establishes a controlling terminal for the child process by:
|
|
29
|
+
- Calling `setsid()` to create a new session
|
|
30
|
+
- Setting the PTY as the controlling terminal via `TIOCSCTTY`
|
|
31
|
+
- Connecting the child's stdin/stdout/stderr to the PTY
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require "pty"
|
|
35
|
+
|
|
36
|
+
# Ruby's PTY.spawn returns [r, w, pid] where:
|
|
37
|
+
# - r = readable IO (receives child's stdout/stderr)
|
|
38
|
+
# - w = writable IO (sends to child's stdin)
|
|
39
|
+
# These are the host's view of the PTY - what the child writes, we read from r;
|
|
40
|
+
# what we write to w, the child reads from stdin.
|
|
41
|
+
pty_out, pty_in, pid = PTY.spawn("rooibos", "run", chdir: app_dir)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Escape the Parent's Bundler Context
|
|
45
|
+
|
|
46
|
+
When tests run via `bundle exec`, child processes inherit that Bundler context.
|
|
47
|
+
For generated apps to use their own `Gemfile`, wrap spawning in:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
Bundler.with_unbundled_env do
|
|
51
|
+
pty_out, pty_in, pid = PTY.spawn("rooibos", "run", chdir: app_dir)
|
|
52
|
+
# ... all PTY interaction must happen inside this block
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Drain the PTY Output Buffer
|
|
57
|
+
|
|
58
|
+
**Critical**: TUI apps write escape sequences and screen content to stdout.
|
|
59
|
+
If you don't read from the PTY, the buffer fills up and the app blocks waiting
|
|
60
|
+
to write, never reading your input.
|
|
61
|
+
|
|
62
|
+
Use a background thread to continuously drain output:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
reader = Thread.new do
|
|
66
|
+
loop do
|
|
67
|
+
pty_out.read_nonblock(4096)
|
|
68
|
+
rescue IO::WaitReadable
|
|
69
|
+
pty_out.wait_readable(0.1)
|
|
70
|
+
rescue EOFError, Errno::EIO
|
|
71
|
+
break
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# ... send input, wait for exit ...
|
|
76
|
+
|
|
77
|
+
reader.kill rescue nil
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 5. Set the PTY Window Size
|
|
81
|
+
|
|
82
|
+
**Critical**: Ruby's `PTY.spawn` creates terminals with 0×0 dimensions by default.
|
|
83
|
+
TUI apps that query terminal size will get zero and may crash (e.g., negative width
|
|
84
|
+
calculations). Set standard 80×24 dimensions after spawning:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
require "io/console"
|
|
88
|
+
|
|
89
|
+
pty_out, pty_in, pid = PTY.spawn("rooibos", "run", chdir: app_dir)
|
|
90
|
+
pty_out.winsize = [24, 80] # rows, columns
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 6. Send Key Events as Raw Bytes
|
|
94
|
+
|
|
95
|
+
Ctrl+C is sent as the ETX byte (0x03), not as a signal:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
pty_in.sync = true # Disable buffering
|
|
99
|
+
pty_in.write("\x03") # ETX = Ctrl+C
|
|
100
|
+
pty_in.flush
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Crossterm parses bytes 0x01-0x1A as Ctrl+A through Ctrl+Z:
|
|
104
|
+
|
|
105
|
+
<!-- SPDX-SnippetBegin -->
|
|
106
|
+
<!-- SPDX-License-Identifier: MIT -->
|
|
107
|
+
<!-- SPDX-SnippetCopyrightText: 2019 Timon -->
|
|
108
|
+
```rust
|
|
109
|
+
// From crossterm/src/event/sys/unix/parse.rs
|
|
110
|
+
c @ b'\x01'..=b'\x1A' => KeyEvent::new(
|
|
111
|
+
KeyCode::Char((c - 0x1 + b'a') as char),
|
|
112
|
+
KeyModifiers::CONTROL,
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
<!-- SPDX-SnippetEnd -->
|
|
116
|
+
|
|
117
|
+
### 5. Require Global Gem Installation
|
|
118
|
+
|
|
119
|
+
Generated apps expect `rooibos` to be installed as a gem. For tests:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
def require_global_rooibos!
|
|
123
|
+
require "rooibos/version"
|
|
124
|
+
installed = Gem::Specification.find_all_by_name("rooibos", Rooibos::VERSION)
|
|
125
|
+
if installed.empty?
|
|
126
|
+
flunk "Rooibos #{Rooibos::VERSION} not installed globally. Run: rake install:force"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Run `rake install:force` before tests (CI does this automatically).
|
|
132
|
+
|
|
133
|
+
## Complete Example
|
|
134
|
+
|
|
135
|
+
See [test/cli/test_new_integration.rb](../../test/cli/test_new_integration.rb),
|
|
136
|
+
specifically the `test_new_app_runs_and_exits_on_ctrl_c` method.
|
|
137
|
+
|
|
138
|
+
## Common Pitfalls
|
|
139
|
+
|
|
140
|
+
| Problem | Cause | Solution |
|
|
141
|
+
|---------|-------|----------|
|
|
142
|
+
| App can't load its own gem | Wrong Bundler context | Use `Bundler.with_unbundled_env` |
|
|
143
|
+
| Test hangs forever | PTY buffer full | Add background reader thread |
|
|
144
|
+
| `LoadError: cannot load such file` | Gem not installed globally | Run `rake install:force` |
|
|
145
|
+
| Ctrl+C doesn't work | Sending SIGINT instead of byte | Write `"\x03"` to PTY |
|
|
146
|
+
| `bundle exec rooibos run` fails | Inherits parent's Bundler | Use global `rooibos` command |
|
|
147
|
+
|
|
148
|
+
## References
|
|
149
|
+
|
|
150
|
+
- `ruby/ext/pty/pty.c` - How PTY.spawn sets up the controlling terminal
|
|
151
|
+
- `crossterm/src/event/sys/unix/parse.rs` - How key events are parsed from raw bytes
|
|
152
|
+
- `crossterm/src/terminal/sys/file_descriptor.rs` - How crossterm decides where to read input from
|
|
153
|
+
|
|
154
|
+
## Want a Helper API?
|
|
155
|
+
|
|
156
|
+
This pattern isn't currently exposed in `Rooibos::TestHelper` because most
|
|
157
|
+
testing needs are met by `with_test_terminal`. However, reach out if you need
|
|
158
|
+
PTY-based E2E testing regularly, and you would use a helper like the following.
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
Rooibos::TestHelper.with_pty("rooibos", "run") do |pty_in, pty_out, pid|
|
|
162
|
+
# ...
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If you'd use this, please email the
|
|
167
|
+
[~kerrick/ratatui_ruby-discuss](https://lists.sr.ht/~kerrick/ratatui_ruby-discuss)
|
|
168
|
+
mailing list to request it as a feature.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
> [!NOTE]
|
|
8
|
+
> This is not *where* each story should go, but
|
|
9
|
+
> *when* the pre-requisites for each story are met.
|
|
10
|
+
|
|
11
|
+
## FINAL MAPPING RESULTS
|
|
12
|
+
|
|
13
|
+
### Project Setup
|
|
14
|
+
- Story -4: Project Setup
|
|
15
|
+
|
|
16
|
+
### Hello World
|
|
17
|
+
- Story -3: Hello World + Quit
|
|
18
|
+
|
|
19
|
+
### Adding State
|
|
20
|
+
- Story -2: Static File List
|
|
21
|
+
- Story -1: Arrow Key Navigation
|
|
22
|
+
- Story 0: Real Files <!-- FAKE DATA STOPS HERE; ALL DATA FETCHING UNTIL COMMANDS IS VIA RUBY STDLIB SYNCHRONOUS CALLS IN UPDATE -->
|
|
23
|
+
- Story 1: Walking Skeleton - View Current Directory
|
|
24
|
+
- Story 2: Basic Navigation - Move Through List
|
|
25
|
+
- Story 3: Enter Directories
|
|
26
|
+
|
|
27
|
+
### Organizing Your Code
|
|
28
|
+
- Story 4: Three-Pane Layout
|
|
29
|
+
- Story 5: File Metadata Display
|
|
30
|
+
- Story 6: Text File Preview
|
|
31
|
+
- Story 7: Directory Tree Expansion
|
|
32
|
+
- Story 8: Pane Focus Switching
|
|
33
|
+
- Story 9: Sort Files
|
|
34
|
+
- Story 10: Filter Files by Name
|
|
35
|
+
- Story 11: Toggle Hidden Files
|
|
36
|
+
- Story 12: Create New Directory
|
|
37
|
+
- Story 13: Rename Files and Directories
|
|
38
|
+
|
|
39
|
+
### Your First Command
|
|
40
|
+
- Story 14: Delete Files and Directories
|
|
41
|
+
- Story 15: Copy Files and Directories
|
|
42
|
+
- Story 16: Move Files and Directories
|
|
43
|
+
- Story 17: Open in External Editor
|
|
44
|
+
- Story 18: Help Overlay
|
|
45
|
+
- Story 19: Error Handling and Messages
|
|
46
|
+
- Story 20: Mouse Support
|
|
47
|
+
- Story 21: Terminal Resize Handling
|
|
48
|
+
- Story 22: Loading States for Large Directories
|
|
49
|
+
- Story 23: Visual Polish and Theming
|
|
50
|
+
- Story 24: Performance Optimization
|
|
51
|
+
|
|
52
|
+
### The Preview Pane
|
|
53
|
+
(No stories)
|
|
54
|
+
|
|
55
|
+
### Loading States
|
|
56
|
+
(No stories)
|
|
57
|
+
|
|
58
|
+
### Testing Your App
|
|
59
|
+
- Story 25: Comprehensive Test Coverage
|
|
60
|
+
- Story 26: Configurable Color Schemes
|
|
61
|
+
- Story 27: Configuration Management
|
|
62
|
+
|
|
63
|
+
### Polish and Refine
|
|
64
|
+
(No stories)
|
|
65
|
+
|
|
66
|
+
### Going Further
|
|
67
|
+
(No stories)
|
|
68
|
+
|
|
69
|
+
### Tutorial: Build a File Browser
|
|
70
|
+
(No stories)
|