rooibos 0.5.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.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/.builds/ruby-3.2.yml +51 -0
  3. data/.builds/ruby-3.3.yml +51 -0
  4. data/.builds/ruby-3.4.yml +51 -0
  5. data/.builds/ruby-4.0.0.yml +51 -0
  6. data/.pre-commit-config.yaml +16 -0
  7. data/.rubocop.yml +8 -0
  8. data/AGENTS.md +108 -0
  9. data/CHANGELOG.md +214 -0
  10. data/LICENSE +304 -0
  11. data/LICENSES/AGPL-3.0-or-later.txt +235 -0
  12. data/LICENSES/CC-BY-SA-4.0.txt +170 -0
  13. data/LICENSES/CC0-1.0.txt +121 -0
  14. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  15. data/LICENSES/MIT-0.txt +16 -0
  16. data/LICENSES/MIT.txt +18 -0
  17. data/README.md +183 -0
  18. data/REUSE.toml +24 -0
  19. data/Rakefile +16 -0
  20. data/Steepfile +13 -0
  21. data/doc/concepts/application_architecture.md +197 -0
  22. data/doc/concepts/application_testing.md +49 -0
  23. data/doc/concepts/async_work.md +164 -0
  24. data/doc/concepts/commands.md +530 -0
  25. data/doc/concepts/message_processing.md +51 -0
  26. data/doc/contributors/WIP/decomposition_strategies_analysis.md +258 -0
  27. data/doc/contributors/WIP/implementation_plan.md +409 -0
  28. data/doc/contributors/WIP/init_callable_proposal.md +344 -0
  29. data/doc/contributors/WIP/mvu_tea_implementations_research.md +373 -0
  30. data/doc/contributors/WIP/runtime_refactoring_status.md +47 -0
  31. data/doc/contributors/WIP/task.md +36 -0
  32. data/doc/contributors/WIP/v0.4.0_todo.md +468 -0
  33. data/doc/contributors/design/commands_and_outlets.md +214 -0
  34. data/doc/contributors/kit-no-outlet.md +238 -0
  35. data/doc/contributors/priorities.md +38 -0
  36. data/doc/custom.css +22 -0
  37. data/doc/getting_started/quickstart.md +56 -0
  38. data/doc/images/.gitkeep +0 -0
  39. data/doc/images/verify_readme_usage.png +0 -0
  40. data/doc/images/widget_cmd_exec.png +0 -0
  41. data/doc/index.md +25 -0
  42. data/examples/app_fractal_dashboard/README.md +60 -0
  43. data/examples/app_fractal_dashboard/app.rb +63 -0
  44. data/examples/app_fractal_dashboard/dashboard/base.rb +73 -0
  45. data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +86 -0
  46. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +87 -0
  47. data/examples/app_fractal_dashboard/dashboard/update_router.rb +43 -0
  48. data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +81 -0
  49. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
  50. data/examples/app_fractal_dashboard/fragments/custom_shell_output.rb +90 -0
  51. data/examples/app_fractal_dashboard/fragments/disk_usage.rb +47 -0
  52. data/examples/app_fractal_dashboard/fragments/network_panel.rb +45 -0
  53. data/examples/app_fractal_dashboard/fragments/ping.rb +47 -0
  54. data/examples/app_fractal_dashboard/fragments/stats_panel.rb +45 -0
  55. data/examples/app_fractal_dashboard/fragments/system_info.rb +47 -0
  56. data/examples/app_fractal_dashboard/fragments/uptime.rb +47 -0
  57. data/examples/verify_readme_usage/README.md +54 -0
  58. data/examples/verify_readme_usage/app.rb +47 -0
  59. data/examples/widget_command_system/README.md +70 -0
  60. data/examples/widget_command_system/app.rb +132 -0
  61. data/exe/.gitkeep +0 -0
  62. data/lib/rooibos/command/all.rb +69 -0
  63. data/lib/rooibos/command/batch.rb +77 -0
  64. data/lib/rooibos/command/custom.rb +104 -0
  65. data/lib/rooibos/command/http.rb +192 -0
  66. data/lib/rooibos/command/lifecycle.rb +134 -0
  67. data/lib/rooibos/command/outlet.rb +157 -0
  68. data/lib/rooibos/command/wait.rb +80 -0
  69. data/lib/rooibos/command.rb +546 -0
  70. data/lib/rooibos/error.rb +55 -0
  71. data/lib/rooibos/message/all.rb +45 -0
  72. data/lib/rooibos/message/http_response.rb +61 -0
  73. data/lib/rooibos/message/system/batch.rb +61 -0
  74. data/lib/rooibos/message/system/stream.rb +67 -0
  75. data/lib/rooibos/message/timer.rb +46 -0
  76. data/lib/rooibos/message.rb +38 -0
  77. data/lib/rooibos/router.rb +403 -0
  78. data/lib/rooibos/runtime.rb +396 -0
  79. data/lib/rooibos/shortcuts.rb +49 -0
  80. data/lib/rooibos/test_helper.rb +56 -0
  81. data/lib/rooibos/version.rb +12 -0
  82. data/lib/rooibos.rb +121 -0
  83. data/mise.toml +8 -0
  84. data/rbs_collection.lock.yaml +108 -0
  85. data/rbs_collection.yaml +15 -0
  86. data/sig/concurrent.rbs +72 -0
  87. data/sig/examples/verify_readme_usage/app.rbs +19 -0
  88. data/sig/examples/widget_command_system/app.rbs +26 -0
  89. data/sig/open3.rbs +17 -0
  90. data/sig/rooibos/command.rbs +265 -0
  91. data/sig/rooibos/error.rbs +13 -0
  92. data/sig/rooibos/message.rbs +121 -0
  93. data/sig/rooibos/router.rbs +153 -0
  94. data/sig/rooibos/runtime.rbs +75 -0
  95. data/sig/rooibos/shortcuts.rbs +16 -0
  96. data/sig/rooibos/test_helper.rbs +10 -0
  97. data/sig/rooibos/version.rbs +8 -0
  98. data/sig/rooibos.rbs +46 -0
  99. data/tasks/example_viewer.html.erb +172 -0
  100. data/tasks/resources/build.yml.erb +53 -0
  101. data/tasks/resources/index.html.erb +44 -0
  102. data/tasks/resources/rubies.yml +7 -0
  103. data/tasks/steep.rake +11 -0
  104. data/vendor/goodcop/base.yml +1047 -0
  105. metadata +241 -0
@@ -0,0 +1,214 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Custom Commands Design (`rooibos`)
7
+
8
+ This document describes the architectural design and guiding principles of custom commands in `rooibos`. It is intended for contributors, architects, and AI agents working on the codebase.
9
+
10
+ ## Core Abstractions
11
+
12
+ Custom commands extend Rooibos with user-defined side effects: WebSockets, gRPC, database polling, background workers. The architecture provides four key components:
13
+
14
+ | Component | Purpose |
15
+ |-----------|---------|
16
+ | `Command::Custom` | Mixin for command identification |
17
+ | `Command::Outlet` | Messaging gateway for result delivery |
18
+ | `Command::CancellationToken` | Cooperative cancellation mechanism |
19
+ | `Command.custom { ... }` | Wrapper giving callables unique identity |
20
+
21
+ ---
22
+
23
+ ## Guiding Design Principles
24
+
25
+ ### 1. Messaging Gateway over Raw Queue
26
+
27
+ Commands produce messages. Those messages cross threads. Without abstraction, queue manipulation scatters across the codebase.
28
+
29
+ The Outlet wraps the internal queue with a domain-specific API. Commands call `out.put(:tag, data)` instead of managing queue details. Debug mode validates Ractor-shareability automatically—commands cannot accidentally push unshareable data.
30
+
31
+ | Aspect | Raw Queue | Outlet |
32
+ |--------|-----------|--------|
33
+ | Ractor safety | Manual | Automatic |
34
+ | Error messages | Generic | Contextual |
35
+ | API | `queue << Ractor.make_shareable([...])` | `out.put(:tag, data)` |
36
+ | Pattern | Implementation detail | **Messaging Gateway** |
37
+
38
+ This implements the **Messaging Gateway** pattern from Enterprise Integration Patterns. The Outlet is a **Facade** (Gang of Four) over the queue, and acts as a **Channel Adapter** translating command results into update function messages.
39
+
40
+ ### 2. Cooperative Cancellation over Thread Termination
41
+
42
+ Long-running commands block the event loop. WebSocket listeners, database pollers, and streaming connections run indefinitely until stopped. Stopping them abruptly corrupts state.
43
+
44
+ `Thread#kill` terminates immediately. Mutexes may deadlock. Resources may leak. Database transactions may abort mid-write.
45
+
46
+ The `CancellationToken` signals cancellation requests. Commands check `token.cancelled?` periodically and stop at safe points. Cleanup code runs. Resources release. Transactions commit.
47
+
48
+ | Aspect | `Thread#kill` | CancellationToken |
49
+ |--------|---------------|------------------|
50
+ | Cleanup | None (immediate termination) | Command controls cleanup |
51
+ | Resource safety | May corrupt state | Clean shutdown |
52
+ | Mutexes | May deadlock | Released properly |
53
+ | Pattern | Forceful | **Cooperative** |
54
+
55
+ **Configurable grace periods** accommodate different cleanup needs:
56
+
57
+ - `0.5` seconds — Quick HTTP abort, minimal cleanup
58
+ - `2.0` seconds — Default, suitable for most commands
59
+ - `5.0` seconds — WebSocket close handshake with remote server
60
+ - `Float::INFINITY` — Never force-kill (database transactions)
61
+
62
+ ### 3. Command Identity via Object Reference
63
+
64
+ The runtime tracks active commands by their object identity. When the update function returns `Command.cancel(handle)`, the runtime looks up that exact object in its registry and signals its token.
65
+
66
+ Class-based commands get unique identity automatically—each `MyCommand.new` produces a distinct object. Reusable lambdas and procs share identity. Dispatch them twice, and cancellation would affect both.
67
+
68
+ `Command.custom(callable)` wraps a callable in a unique container. Each call produces a distinct handle. Store it in your model. Cancel it later by returning `Command.cancel(handle)`.
69
+
70
+ ### 4. Automatic Error Propagation
71
+
72
+ Commands run in threads. Exceptions bubble up silently. The update function never sees them. Backtraces written to STDERR corrupt the TUI display.
73
+
74
+ The runtime catches unhandled exceptions and pushes `Command::Error` to the queue. This is automatic—commands do not rescue exceptions unless they want custom handling. The update function pattern-matches on `Command::Error` and reacts appropriately.
75
+
76
+ This mirrors the sentinel pattern used for `Command::Exit` and `Command::Cancel`.
77
+
78
+ **Error categorization:** We use a single `Command::Error` sentinel for all command exceptions. Distinguishing "framework bugs" vs "user bugs" at the sentinel level adds complexity with marginal benefit. Instead, exception *classes* provide the signal:
79
+
80
+ | Exception Class | Source |
81
+ |-----------------|--------|
82
+ | `RatatuiRuby::Error::Invariant` | Framework validation (debug mode) |
83
+ | `RatatuiRuby::Error::Internal` | Framework bugs |
84
+ | `ArgumentError`, `RuntimeError`, etc. | User code |
85
+
86
+ The update function can pattern-match on `error_msg.exception.class` if it needs to distinguish sources.
87
+
88
+ ### 5. Ractor Readiness
89
+
90
+ This design is forward-compatible with Ruby's Ractor-based parallelism.
91
+
92
+ **What's already shareable:**
93
+
94
+ | Pattern | Shareable? | Why |
95
+ |---------|------------|-----|
96
+ | Module-constant lambda | ✅ Yes | `self` is the frozen module |
97
+ | Closure-free block | ✅ Yes | No captured variables |
98
+ | `Data.define` command | ✅ Yes | Immutable by definition |
99
+ | Frozen class instance | ✅ Yes | Deeply frozen |
100
+
101
+ **Debug mode validation:**
102
+
103
+ In debug mode, Rooibos validates Ractor shareability at dispatch time. `Ractor.shareable?(command)` catches most issues. The Outlet's `put` method validates messages before pushing them to the queue.
104
+
105
+ **Why Thread dispatch is Ractor-safe:**
106
+
107
+ Commands execute in Threads within the **main Ractor**. Only messages (via Outlet) cross the shareability boundary. The `CancellationToken` stays within its Thread—never shared across Ractors. The `@active_commands` hash is local to the Runtime (main Ractor).
108
+
109
+ When Ruby evolves to true Ractor parallelism, this design upgrades transparently: commands are already validated as shareable, so they could be sent to worker Ractors without code changes.
110
+
111
+ ---
112
+
113
+ ## Pattern Lineage
114
+
115
+ This design implements several established patterns from the software architecture literature.
116
+
117
+ ### Design Patterns (Gang of Four)
118
+
119
+ | Component | Pattern |
120
+ |-----------|---------|
121
+ | Custom Mixin | **Command** — Encapsulates a request as an object |
122
+ | Outlet | **Facade** — Simplified interface to a complex subsystem |
123
+ | Runtime | **Mediator** — Coordinates command dispatch and message routing |
124
+ | Update Function | **State** — Behavior changes based on internal state |
125
+
126
+ ### Enterprise Integration Patterns (Hohpe & Woolf)
127
+
128
+ | Component | Pattern |
129
+ |-----------|---------|
130
+ | Outlet | **Messaging Gateway** — Wraps message channel access |
131
+ | Outlet | **Channel Adapter** — Connects applications to messaging systems |
132
+ | Queue | **Point-to-Point Channel** — Single consumer receives each message |
133
+
134
+ ### Cancellation Patterns
135
+
136
+ | Platform | Mechanism | Relationship |
137
+ |----------|-----------|--------------|
138
+ | **.NET** | `CancellationToken` | Direct inspiration for API design |
139
+ | **Go** | `context.Context` | Cooperative cancellation via `ctx.Done()` |
140
+ | **JavaScript** | `AbortController` | Web Fetch API cancellation |
141
+ | **Java** | `Thread.interrupt()` | Flag-based cooperative cancellation |
142
+ | **Kotlin** | `Job.cancel()` | Coroutine cancellation |
143
+
144
+ ---
145
+
146
+ ## Prior Art
147
+
148
+ ### The Elm Architecture (TEA)
149
+
150
+ | Framework | Language | Notes |
151
+ |-----------|----------|-------|
152
+ | **Elm** | Elm | Original TEA; `Cmd` + `Sub` primitives |
153
+ | **BubbleTea** | Go | TUI implementation; "Subscriptions are Loops" philosophy |
154
+ | **Iced** | Rust | GUI TEA with Command + Subscription |
155
+ | **Miso** | Haskell | Web TEA with Effect + Sub |
156
+ | **Bolero** | F# | Web TEA with Cmd + Sub |
157
+
158
+ ### Redux Ecosystem
159
+
160
+ | Library | Pattern | Mapping |
161
+ |---------|---------|---------|
162
+ | **Redux Thunk** | Raw dispatch access | Direct queue access (rejected) |
163
+ | **Redux Saga** | `put()` effect dispatches actions | **Outlet#put** ← adopted |
164
+ | **Redux Observable** | RxJS Observables | RxRuby (rejected for complexity) |
165
+ | **redux-loop** | Elm-style Cmd | Recursive commands |
166
+
167
+ Redux Saga's `put()` is the direct inspiration for `out.put()`. The Saga pattern—long-running processes that listen for actions and dispatch new ones—maps directly to Rooibos's custom commands.
168
+
169
+ ### Ruby Ecosystem
170
+
171
+ | Library | Pattern | Relationship |
172
+ |---------|---------|--------------|
173
+ | **Observable** (stdlib) | Observer pattern | Similar push semantics, lacks thread-safety |
174
+ | **Concurrent Ruby** | Actor, Promises | More complex than needed |
175
+ | **Celluloid** | Actor mailboxes | Inspiration for internal terminology |
176
+ | **Sidekiq** | Background jobs | Similar "fire command, receive result" model |
177
+
178
+ ---
179
+
180
+ ## Key Influences Summary
181
+
182
+ | Influence | What We Adopted |
183
+ |-----------|-----------------|
184
+ | **Elm** | MVU architecture, Cmd/Msg pattern |
185
+ | **BubbleTea** | "Subscriptions are Loops" philosophy |
186
+ | **Redux Saga** | `put()` for message dispatch |
187
+ | **.NET CancellationToken** | Cooperative cancellation with grace periods |
188
+ | **EIP Messaging Gateway** | Outlet as infrastructure abstraction |
189
+ | **Go context.Context** | Cancellation propagation pattern |
190
+
191
+ ---
192
+
193
+ ## Further Reading
194
+
195
+ ### External Resources
196
+
197
+ - [Elm Guide](https://guide.elm-lang.org/) — Official Elm documentation covering commands and effects
198
+ - [BubbleTea](https://github.com/charmbracelet/bubbletea) — Go TUI framework based on The Elm Architecture
199
+ - [Redux Saga](https://redux-saga.js.org/) — Saga pattern and `put()` effect documentation
200
+ - [Enterprise Integration Patterns](https://www.enterpriseintegrationpatterns.com/) — Messaging patterns reference by Hohpe & Woolf
201
+
202
+ ### Academic References
203
+
204
+ 1. **Gamma, Helm, Johnson, Vlissides** (1994). *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley.
205
+ - Command, Facade, Mediator, State patterns
206
+
207
+ 2. **Fowler, M.** (2002). *Patterns of Enterprise Application Architecture*. Addison-Wesley.
208
+ - Gateway pattern
209
+
210
+ 3. **Hohpe, G. & Woolf, B.** (2003). *Enterprise Integration Patterns*. Addison-Wesley.
211
+ - Messaging Gateway, Message Channel, Point-to-Point Channel, Channel Adapter
212
+
213
+ 4. **Joshi, U.** (2023). *Patterns of Distributed Systems*. Addison-Wesley.
214
+ - Write-Ahead Log, Thread Pool
@@ -0,0 +1,238 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ # Why Kit Doesn't Need Outlet
8
+
9
+ > **Context**: The `unified-draft.md` specifies `Rooibos::Command::Outlet` for message passing from custom commands to the Rooibos runtime. This document explains why Kit (the component-based runtime) does not need this abstraction.
10
+
11
+ ---
12
+
13
+ ## The Core Insight: Immediate-Mode Rendering
14
+
15
+ RatatuiRuby's Engine is **immediate-mode**:
16
+
17
+ > "Each frame, your code describes the entire UI from scratch. The Engine draws it and immediately forgets it, holding no application state between renders."
18
+
19
+ This means Kit walks the entire component tree and calls `render` **every frame**. Components don't need to notify anyone of state changes—the next frame automatically sees updated state.
20
+
21
+ ---
22
+
23
+ ## Rooibos vs Kit: Different Problems
24
+
25
+ | Concern | Rooibos | Kit |
26
+ |---------|-----|-----|
27
+ | **State model** | Immutable (Ractor-safe) | Mutable (encapsulated) |
28
+ | **State location** | Single Model in runtime | Distributed in components |
29
+ | **Async results** | Message → Queue → Update | Callback → mutate state |
30
+ | **Render trigger** | After update function | **Every frame** |
31
+ | **Re-render notification** | Implicit (update always renders) | **Not needed** (always renders) |
32
+
33
+ ---
34
+
35
+ ## WebSocket Example: Rooibos vs Kit
36
+
37
+ ### Rooibos: Outlet Required
38
+
39
+ ```ruby
40
+
41
+ class WebSocketCommand
42
+ include Rooibos::Command::Custom
43
+
44
+ def call(out, token)
45
+ ws = WebSocket::Client.new(@url)
46
+ ws.on_message { |msg| out.put(:ws, :message, data: msg) }
47
+ ws.connect
48
+
49
+ until token.cancelled?
50
+ sleep 1
51
+ end
52
+
53
+ ws.close
54
+ end
55
+ end
56
+
57
+ def update(msg, model)
58
+ case msg
59
+ in [:ws, :message, data:]
60
+ model.with(messages: model.messages + [data])
61
+ end
62
+ end
63
+ ```
64
+
65
+ Rooibos needs Outlet because:
66
+ 1. Command runs in a separate thread
67
+ 2. Model is immutable—can't mutate from callback
68
+ 3. Runtime must receive messages to call `update`
69
+
70
+ ### Kit: Direct Mutation
71
+
72
+ ```ruby
73
+ class WebSocketTab
74
+ include Kit::Component
75
+
76
+ def initialize(url:)
77
+ @url = url
78
+ @messages = []
79
+ end
80
+
81
+ def mount
82
+ @ws = WebSocket::Client.new(@url)
83
+ @ws.on_message { |msg| @messages << msg }
84
+ @ws.connect_async
85
+ end
86
+
87
+ def unmount
88
+ @ws&.close
89
+ end
90
+
91
+ def render(frame, area)
92
+ frame.render_widget(tui.list(items: @messages.last(10)), area)
93
+ end
94
+ end
95
+ ```
96
+
97
+ Kit doesn't need Outlet because:
98
+ 1. Component owns the WebSocket directly
99
+ 2. State is mutable—callbacks mutate `@messages`
100
+ 3. Next frame's `render` sees updated state automatically
101
+
102
+ ---
103
+
104
+ ## Why Each Rooibos Concept Is Unnecessary in Kit
105
+
106
+ ### Outlet (Message Gateway)
107
+
108
+ **Rooibos**: Routes messages from command thread → runtime queue → update function.
109
+
110
+ **Kit**: Not needed. Callbacks mutate component state directly. Immediate-mode rendering sees changes next frame.
111
+
112
+ ### CancellationToken
113
+
114
+ **Rooibos**: Runtime signals command to stop cooperatively, since commands run in spawned threads tracked by the runtime.
115
+
116
+ **Kit**: Not needed. Components have `unmount` lifecycle hook. Component stops its own resources:
117
+
118
+ ```ruby
119
+ def unmount
120
+ @ws&.close
121
+ @polling_thread&.kill
122
+ end
123
+ ```
124
+
125
+ ### Ractor Safety
126
+
127
+ **Rooibos**: Messages cross thread boundaries and must be Ractor-shareable for future Ruby 4.0 compatibility.
128
+
129
+ **Kit**: Not needed. Components are mutable by design. State stays within the component. No Ractor isolation required.
130
+
131
+ ### Thread Tracking
132
+
133
+ **Rooibos**: Runtime tracks spawned command threads to ensure clean shutdown.
134
+
135
+ **Kit**: Not needed. Each component tracks its own resources. Tree traversal during shutdown calls `unmount` on each component.
136
+
137
+ ---
138
+
139
+ ## What Kit DOES Need
140
+
141
+ ### 1. Lifecycle Hooks
142
+
143
+ ```ruby
144
+ module Kit::Component
145
+ def mount
146
+ # Called when component enters tree
147
+ end
148
+
149
+ def unmount
150
+ # Called when component leaves tree
151
+ end
152
+ end
153
+ ```
154
+
155
+ These replace Rooibos's command spawning and cancellation.
156
+
157
+ ### 2. Thread Safety for Complex Mutations
158
+
159
+ For simple mutations (array append, boolean toggle), Ruby's GIL is sufficient.
160
+
161
+ For complex mutations:
162
+
163
+ ```ruby
164
+ def mount
165
+ @mutex = Mutex.new
166
+ @ws = WebSocket::Client.new(@url)
167
+ @ws.on_message do |msg|
168
+ @mutex.synchronize { @messages << msg }
169
+ end
170
+ end
171
+
172
+ def render(frame, area)
173
+ messages = @mutex.synchronize { @messages.dup }
174
+ # render with messages
175
+ end
176
+ ```
177
+
178
+ ### 3. Error Handling
179
+
180
+ Components should rescue and surface errors:
181
+
182
+ ```ruby
183
+ def mount
184
+ @ws = WebSocket::Client.new(@url)
185
+ @ws.on_error { |e| @error = e.message }
186
+ rescue => e
187
+ @error = e.message
188
+ end
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Comparison Table
194
+
195
+ | Abstraction | Rooibos | Kit | Why Different |
196
+ |-------------|-----|-----|---------------|
197
+ | **Outlet** | ✓ Required | ✗ Not needed | Kit mutates directly |
198
+ | **CancellationToken** | ✓ Required | ✗ Not needed | Kit has `unmount` |
199
+ | **Ractor safety** | ✓ Required | ✗ Not needed | Kit is mutable |
200
+ | **Thread tracking** | ✓ Runtime tracks | ✗ Not needed | Components self-manage |
201
+ | **Lifecycle hooks** | ✗ Not applicable | ✓ Required | Kit needs mount/unmount |
202
+ | **Mutex** | ✗ Uses Outlet | ⚠ If complex | Kit needs manual locking |
203
+
204
+ ---
205
+
206
+ ## Architectural Summary
207
+
208
+ ```
209
+ ┌─────────────────────────────────────────────────────────────────┐
210
+ │ ENGINE │
211
+ │ (Immediate-mode, renders every frame) │
212
+ ├─────────────────────────────┬───────────────────────────────────┤
213
+ │ ROOIBOS │ KIT │
214
+ │ │ │
215
+ │ Immutable Model │ Mutable Components │
216
+ │ Commands spawn threads │ Components own resources │
217
+ │ Outlet sends messages │ Callbacks mutate state │
218
+ │ Runtime tracks threads │ unmount cleans up │
219
+ │ Ractor-safe required │ GIL + Mutex sufficient │
220
+ │ │ │
221
+ │ NEEDS OUTLET │ DOESN'T NEED OUTLET │
222
+ └─────────────────────────────┴───────────────────────────────────┘
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Conclusion
228
+
229
+ **Outlet is Rooibos-specific.** It solves the problem of getting async results into an immutable, unidirectional data flow.
230
+
231
+ Kit's paradigm—mutable components with immediate-mode rendering—makes Outlet unnecessary:
232
+
233
+ 1. **Callbacks mutate state** → No message routing needed
234
+ 2. **Render every frame** → No change notification needed
235
+ 3. **`unmount` hook** → No external cancellation needed
236
+ 4. **Components own resources** → No runtime tracking needed
237
+
238
+ The Outlet stays in `Rooibos::Command::Outlet`. Kit needs no equivalent.
@@ -0,0 +1,38 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Feature Priorities
7
+
8
+ This document outlines the remaining work before `rooibos` reaches v1.0.0.
9
+
10
+ ## 1. Built-In Commands
11
+
12
+ Six primitives remain unimplemented. See [Command Composition Design](./design/command_composition.md) for full specifications.
13
+
14
+ | Command / Method | Purpose | Complexity |
15
+ |------------------|---------|------------|
16
+ | `Command.wait(seconds, tag)` | One-shot timer | Low |
17
+ | `Command.tick(interval, tag)` | Recurring timer (subscriptions) | Low |
18
+ | `Command.batch([...])` | Parallel execution (fire-and-forget) | Medium |
19
+ | `Command.all([...])` | Parallel execution (aggregating) | Medium |
20
+ | `Command.http(method, url, tag)` | HTTP requests via stdlib | Medium |
21
+ | `Outlet#source(command, token)` | Command composition | Low |
22
+
23
+ Implementation order follows increasing complexity. Each can be tested independently.
24
+
25
+ > [!NOTE]
26
+ > `Command.sequence` was considered but rejected. See [Rejected Alternatives](./design/command_composition.md#rejected-alternatives) for rationale.
27
+
28
+ ## 2. Documentation
29
+
30
+ The README warns: "Because this gem is in pre-release, it lacks documentation."
31
+
32
+ Before v1.0.0:
33
+
34
+ - [ ] Document all public `Command.*` factories with RDoc examples
35
+ - [ ] Write Quickstart guide
36
+ - [ ] Write Fractal Architecture guide
37
+ - [ ] Write Custom Commands guide (including `out.source` composition)
38
+ - [ ] Ensure all examples are copy-pasteable and tested
data/doc/custom.css ADDED
@@ -0,0 +1,22 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ * SPDX-License-Identifier: LGPL-3.0-or-later
4
+ */
5
+
6
+ img {
7
+ max-width: 100%;
8
+ height: auto;
9
+ }
10
+
11
+ .theme-toggle {
12
+ margin-left: 0 !important;
13
+ }
14
+
15
+ /* Terminal Previews (Native PNGs)
16
+ * The images already contain the window chrome and shadows.
17
+ * We just need to center them and ensure they scale down on mobile.
18
+ */
19
+ img[src*="images/"] {
20
+ display: block;
21
+ margin: 2em auto;
22
+ }
@@ -0,0 +1,56 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # Quickstart
6
+
7
+ Welcome to **rooibos**! This guide will help you get up and running with your first Terminal User Interface in Ruby.
8
+
9
+ ## Installation
10
+
11
+ See [Installation in the README](../README.md#installation) for setup instructions.
12
+
13
+
14
+ ## Tutorials
15
+
16
+ ### Basic Application
17
+
18
+ Here is a "Hello World" application that demonstrates the core lifecycle of a **rooibos** app.
19
+
20
+ _Because this gem is in pre-release, it lacks documentation. Please check the source files.
21
+
22
+ #### How it works
23
+
24
+ _Because this gem is in pre-release, it lacks documentation. Please check the source files.
25
+
26
+ ## Examples
27
+
28
+ These examples showcase the full power of **rooibos**. You can find their source code in the [examples directory](../examples).
29
+
30
+ ### Widget Demos
31
+
32
+ Focused examples for individual concepts. Each demonstrates a single concept and ways to interact with it.
33
+
34
+ | Widget | What it demonstrates |
35
+ |--------|---------------------|
36
+ | _Automated Tests_ | _Because this gem is in pre-release, it lacks documentation. Please check the automated tests for details._ |
37
+
38
+ ### Sample Applications
39
+
40
+ These larger examples combine concepts into complete applications, demonstrating real-world TUI patterns and architectures.
41
+
42
+ | Application | Architecture | What you'll learn |
43
+ |-------------|--------------|-------------------|
44
+ | _Automated Tests_ | _Because this gem is in pre-release, it lacks documentation. Please check the automated tests for details._ |
45
+
46
+
47
+ ## Next Steps
48
+
49
+ Now that you've seen what **rooibos** can do:
50
+
51
+ - **Deep dive**: Read the [Application Architecture](../concepts/application_architecture.md) guide for scaling patterns
52
+ - **Test your TUI**: See the [Testing Guide](../concepts/application_testing.md) for snapshot and style assertions
53
+ - **Avoid common mistakes**: See [Terminal Output During TUI Sessions](https://man.sr.ht/~kerrick/ratatui_ruby/troubleshooting/tui_output.md) to prevent screen corruption
54
+ - **Explore the API**: Browse the [full documentation](../index.md)
55
+ - **Learn the philosophy**: Read [Why RatatuiRuby?](https://man.sr.ht/~kerrick/ratatui_ruby/why.md) for comparisons and design decisions
56
+ - **Get help**: Join the [discussion mailing list](https://lists.sr.ht/~kerrick/ratatui_ruby-discuss)
File without changes
Binary file
Binary file
data/doc/index.md ADDED
@@ -0,0 +1,25 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # Start Here
6
+
7
+
8
+ ## Documentation for Users
9
+
10
+ - [README](../README.md): Project overview and installation
11
+
12
+ ### Getting Started
13
+
14
+ - [Why RatatuiRuby?](https://man.sr.ht/~kerrick/ratatui_ruby/why.md): Philosophy, comparisons, and what makes us different
15
+ - [Quickstart](./getting_started/quickstart.md): Build your first TUI app
16
+
17
+ ### Concepts
18
+
19
+ - [Application Architecture](./concepts/application_architecture.md): Lifecycle patterns and API choices
20
+ - [Testing Your Application](./concepts/application_testing.md): Snapshot testing and style assertions
21
+
22
+
23
+ ## Documentation for Contributors
24
+
25
+ - [Contributing Guidelines](https://man.sr.ht/~kerrick/ratatui_ruby/contributing.md): How to contribute patches and features
@@ -0,0 +1,60 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Cmd.map Fractal Dashboard
7
+
8
+ Demonstrates **Fractal Architecture** using `Cmd.map` for component composition.
9
+
10
+ ## Problem
11
+
12
+ Without composition, a complex app needs one giant `case` statement handling every possible message from every child—the "God Reducer" anti-pattern. This doesn't scale.
13
+
14
+ ## Solution
15
+
16
+ `Cmd.map` wraps child commands so their results route through parents:
17
+
18
+ ```ruby
19
+ # Child produces [:system_info, {stdout:, ...}]
20
+ child_cmd = SystemInfoWidget.fetch_cmd
21
+
22
+ # Parent wraps to produce [:stats, :system_info, {...}]
23
+ parent_cmd = Cmd.map(child_cmd) { |m| [:stats, *m] }
24
+ ```
25
+
26
+ Each layer handles only its own messages. Parents pattern-match on the first element to route to the correct child.
27
+
28
+ ## Architecture
29
+
30
+ ```
31
+ Dashboard (root)
32
+ ├── StatsPanel
33
+ │ ├── SystemInfoWidget → Cmd.exec("uname -a", :system_info)
34
+ │ └── DiskUsageWidget → Cmd.exec("df -h", :disk_usage)
35
+ └── NetworkPanel
36
+ ├── PingWidget → Cmd.exec("ping -c 1 localhost", :ping)
37
+ └── UptimeWidget → Cmd.exec("uptime", :uptime)
38
+ ```
39
+
40
+ ## Hotkeys
41
+
42
+ | Key | Action |
43
+ |-----|--------|
44
+ | `s` | Fetch system info |
45
+ | `d` | Fetch disk usage |
46
+ | `p` | Ping localhost |
47
+ | `u` | Fetch uptime |
48
+ | `q` | Quit |
49
+
50
+ ## Key Concepts
51
+
52
+ 1. **Widget isolation**: Each widget has its own `Model`, `UPDATE`, and `fetch_cmd`. It knows nothing about parents.
53
+ 2. **Message routing**: Parents prefix child messages (`:stats`, `:network`) and pattern-match to route.
54
+ 3. **Recursive dispatch**: `Cmd.map` delegates inner command execution to the runtime, then transforms the result.
55
+
56
+ ## Usage
57
+
58
+ ```bash
59
+ ruby examples/widget_cmd_map/app.rb
60
+ ```