ratatui_ruby-tea 0.3.1 → 0.4.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +42 -2
  3. data/CHANGELOG.md +76 -0
  4. data/README.md +8 -5
  5. data/doc/concepts/async_work.md +164 -0
  6. data/doc/concepts/commands.md +528 -0
  7. data/doc/concepts/message_processing.md +51 -0
  8. data/doc/contributors/WIP/decomposition_strategies_analysis.md +258 -0
  9. data/doc/contributors/WIP/implementation_plan.md +405 -0
  10. data/doc/contributors/WIP/init_callable_proposal.md +341 -0
  11. data/doc/contributors/WIP/mvu_tea_implementations_research.md +372 -0
  12. data/doc/contributors/WIP/runtime_refactoring_status.md +47 -0
  13. data/doc/contributors/WIP/task.md +36 -0
  14. data/doc/contributors/WIP/v0.4.0_todo.md +468 -0
  15. data/doc/contributors/design/commands_and_outlets.md +11 -1
  16. data/doc/contributors/priorities.md +22 -24
  17. data/examples/app_fractal_dashboard/app.rb +3 -7
  18. data/examples/app_fractal_dashboard/dashboard/base.rb +15 -16
  19. data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +8 -8
  20. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +11 -11
  21. data/examples/app_fractal_dashboard/dashboard/update_router.rb +4 -4
  22. data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_input.rb +8 -4
  23. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
  24. data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_output.rb +8 -4
  25. data/examples/app_fractal_dashboard/{bags → fragments}/disk_usage.rb +13 -10
  26. data/examples/app_fractal_dashboard/{bags → fragments}/network_panel.rb +12 -12
  27. data/examples/app_fractal_dashboard/{bags → fragments}/ping.rb +12 -8
  28. data/examples/app_fractal_dashboard/{bags → fragments}/stats_panel.rb +12 -12
  29. data/examples/app_fractal_dashboard/{bags → fragments}/system_info.rb +11 -7
  30. data/examples/app_fractal_dashboard/{bags → fragments}/uptime.rb +11 -7
  31. data/examples/verify_readme_usage/README.md +7 -4
  32. data/examples/verify_readme_usage/app.rb +7 -4
  33. data/lib/ratatui_ruby/tea/command/all.rb +71 -0
  34. data/lib/ratatui_ruby/tea/command/batch.rb +79 -0
  35. data/lib/ratatui_ruby/tea/command/custom.rb +1 -1
  36. data/lib/ratatui_ruby/tea/command/http.rb +194 -0
  37. data/lib/ratatui_ruby/tea/command/lifecycle.rb +136 -0
  38. data/lib/ratatui_ruby/tea/command/outlet.rb +59 -27
  39. data/lib/ratatui_ruby/tea/command/wait.rb +82 -0
  40. data/lib/ratatui_ruby/tea/command.rb +245 -64
  41. data/lib/ratatui_ruby/tea/message/all.rb +47 -0
  42. data/lib/ratatui_ruby/tea/message/http_response.rb +63 -0
  43. data/lib/ratatui_ruby/tea/message/system/batch.rb +63 -0
  44. data/lib/ratatui_ruby/tea/message/system/stream.rb +69 -0
  45. data/lib/ratatui_ruby/tea/message/timer.rb +48 -0
  46. data/lib/ratatui_ruby/tea/message.rb +40 -0
  47. data/lib/ratatui_ruby/tea/router.rb +11 -11
  48. data/lib/ratatui_ruby/tea/runtime.rb +320 -185
  49. data/lib/ratatui_ruby/tea/shortcuts.rb +2 -2
  50. data/lib/ratatui_ruby/tea/test_helper.rb +58 -0
  51. data/lib/ratatui_ruby/tea/version.rb +1 -1
  52. data/lib/ratatui_ruby/tea.rb +44 -10
  53. data/rbs_collection.lock.yaml +1 -17
  54. data/sig/concurrent.rbs +72 -0
  55. data/sig/ratatui_ruby/tea/command.rbs +141 -37
  56. data/sig/ratatui_ruby/tea/message.rbs +123 -0
  57. data/sig/ratatui_ruby/tea/router.rbs +1 -1
  58. data/sig/ratatui_ruby/tea/runtime.rbs +39 -6
  59. data/sig/ratatui_ruby/tea/test_helper.rbs +12 -0
  60. data/sig/ratatui_ruby/tea.rbs +24 -4
  61. metadata +63 -11
  62. data/examples/app_fractal_dashboard/bags/custom_shell_modal.rb +0 -73
  63. data/lib/ratatui_ruby/tea/command/cancellation_token.rb +0 -135
@@ -0,0 +1,258 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ # MVU/TEA Decomposition Strategies Analysis
8
+
9
+ ## Decomposition Style Categories
10
+
11
+ ### FRACTAL Style (Recursive MVU Triads)
12
+ Each component/module is a complete MVU unit with its own Model, Update, and View. Components nest recursively, parent delegates to children. Child components are self-contained and can be composed.
13
+
14
+ ### SLICE Style (Domain-Segregated State)
15
+ State is partitioned by domain/feature, but update/view logic may be centralized or cross-cutting. Each slice typically has Model + Update, with views potentially pulling from multiple slices.
16
+
17
+ ### HYBRID
18
+ Combines both patterns - some aspects use fractal composition, others use slice partitioning.
19
+
20
+ ### NO DECOMPOSITION
21
+ Framework does not provide explicit support for breaking down complex applications.
22
+
23
+ ---
24
+
25
+ ## Framework Categorization
26
+
27
+ ### 🟢 FRACTAL Decomposition
28
+
29
+ #### **1. Elm (Original TEA)**
30
+ - **Style**: Fractal (historically, with evolution)
31
+ - **Terminology**: "Nested TEA", "Modules", "Translator Pattern" / "OutMsg Pattern"
32
+ - **Details**:
33
+ - Originally promoted recursive MVU triads (child modules with own Model/Msg/update/view)
34
+ - Parent uses `Html.map` and `Cmd.map` to transform child messages
35
+ - **Evolution**: Elm 0.19 de-emphasized deep nesting due to boilerplate
36
+ - Current recommendation: Shallow nesting, reusable view functions over deep component trees
37
+ - "Translator Pattern" for child-to-parent communication (child returns OutMsg for parent)
38
+ - **Composition**: Yes, via `Html.map` / `Cmd.map` and OutMsg pattern
39
+
40
+ #### **2. Bubble Tea (Go)**
41
+ - **Style**: Fractal
42
+ - **Terminology**: "Tree of Models", "Nested Models", "Child Components"
43
+ - **Details**:
44
+ - Each component has its own `Model` struct with `Init()`, `Update()`, `View()` methods
45
+ - Parent embeds child models as fields
46
+ - Parent routes messages to children based on state (often using session/state enum)
47
+ - View composition combines child view strings
48
+ - Uses "Bubbles" library for pre-built components
49
+ - **Composition**: Yes, explicit message routing in parent's Update
50
+
51
+ #### **3. Iced (Rust)**
52
+ - **Style**: Fractal
53
+ - **Terminology**: "Nested components", "Message routing", "`.map()` transformation"
54
+ - **Details**:
55
+ - Each component can have State + Message enum + update + view
56
+ - Parent's Message enum wraps child Message types
57
+ - Uses `.map()` to transform child messages/commands/elements to parent context
58
+ - `Subscription::batch()` for combining child subscriptions
59
+ - Note: `Component` trait deprecated in 0.13+ (violated single source of truth)
60
+ - **Composition**: Yes, via `.map()` transformation
61
+
62
+ #### **4. Elmish (F#/Fable)**
63
+ - **Style**: Fractal
64
+ - **Terminology**: "Nested TEA", "Child modules"
65
+ - **Details**:
66
+ - Direct port of Elm Architecture to F#
67
+ - Each module has `Model * Cmd<Msg>` init, `update`, `view`
68
+ - Parent maps child messages and commands
69
+ - Used with React for rendering
70
+ - **Composition**: Yes, same as Elm
71
+
72
+ #### **5. Hyperapp (JavaScript)**
73
+ - **Style**: Fractal-lite
74
+ - **Terminology**: No specific term (minimal framework)
75
+ - **Details**:
76
+ - Unified global state, but can be structured recursively
77
+ - Effects can be nested in init array `[state, effect1, effect2]`
78
+ - Very lightweight (1KB), minimal opinions
79
+ - **Composition**: Limited, more state-tree focused than component-focused
80
+
81
+ ---
82
+
83
+ ### 🔵 SLICE Decomposition
84
+
85
+ #### **6. Redux (JavaScript/React)**
86
+ - **Style**: PURE SLICE
87
+ - **Terminology**: "Slices", "Reducers", "State combination"
88
+ - **Details**:
89
+ - `createSlice` creates domain-specific state + reducers
90
+ - Each slice has `name`, `initialState`, `reducers`
91
+ - Slices combined via `configureStore({ reducer: { user: userSlice, posts: postsSlice } })`
92
+ - Views (React components) can access any slice via selectors
93
+ - **NOT strictly MVU** (no built-in effects), but pattern is similar
94
+ - **Composition**: Yes, via `combineReducers` / `configureStore`
95
+ - **Notable**: Slices have reducers, but no dedicated views - views are separate React components
96
+
97
+ ---
98
+
99
+ ### 🟡 HYBRID Decomposition
100
+
101
+ #### **7. TCA (Swift - The Composable Architecture)**
102
+ - **Style**: HYBRID (Fractal-ish with powerful scoping)
103
+ - **Terminology**: "Reducers", "Scoping", "Pullback", "Child features", "Composition"
104
+ - **Details**:
105
+ - Each "feature" is a `Reducer` with nested `State` and `Action` types
106
+ - Uses `.scope()` to focus parent state/actions on child domain
107
+ - Can compose reducers horizontally (siblings) or vertically (parent-child)
108
+ - **More sophisticated than pure fractal**: dependency injection, effects management, testing tools
109
+ - Parent doesn't just delegate - uses lenses/scoping to project state
110
+ - **Composition**: Yes, via `Reducer` composition operators and scoping
111
+
112
+ #### **8. Flutter Bloc**
113
+ - **Style**: HYBRID
114
+ - **Terminology**: "BloC per feature/screen", "Nested BloCs", "BlocProvider", "MultiBlocProvider"
115
+ - **Details**:
116
+ - Recommended pattern: one Bloc/Cubit per screen or feature
117
+ - BloCs can be nested via `BlocProvider` (dependency injection)
118
+ - `MultiBlocProvider` for multiple BloCs in widget tree
119
+ - **Not pure fractal**: BloCs don't nest MVU triads, they're separate event-driven state machines
120
+ - **Not pure slice**: Each BloC is feature-specific, views are Flutter widgets
121
+ - **Composition**: Yes, via provider pattern (DI), not via direct delegation
122
+
123
+ #### **9. Android MVI (Kotlin)**
124
+ - **Style**: HYBRID
125
+ - **Terminology**: "ViewModel per feature", "StateFlow", "Intent channels"
126
+ - **Details**:
127
+ - Each ViewModel manages feature state via `StateFlow`
128
+ - Intents sent to ViewModel via `SharedFlow` or `Channel`
129
+ - ViewModels can be scoped to fragments/activities
130
+ - **Not pure fractal**: ViewModels are independent state machines, not nested MVU
131
+ - **Not pure slice**: Each ViewModel is feature-bound
132
+ - **Composition**: Limited, mostly via ViewModel scoping and shared state
133
+
134
+ #### **10. Meiosis (JavaScript)**
135
+ - **Style**: HYBRID
136
+ - **Terminology**: "Services", "Nested components" (optional), "Initial model composition"
137
+ - **Details**:
138
+ - Flexible pattern, not opinionated
139
+ - Can compose via `initialModel` merging
140
+ - "meiosis-setup" library helps with nested components and services
141
+ - View-library agnostic
142
+ - **Composition**: Yes, but flexible/manual
143
+
144
+ ---
145
+
146
+ ### ⚫ NO EXPLICIT DECOMPOSITION
147
+
148
+ #### **11. SAM Pattern (JavaScript)**
149
+ - **Style**: No explicit decomposition support
150
+ - **Terminology**: N/A
151
+ - **Details**:
152
+ - Single Model (state tree)
153
+ - Single State Representation function
154
+ - Actions propose mutations
155
+ - Based on TLA+ formalism
156
+ - **Philosophical**: Focused on temporal logic and correctness, less on composition
157
+ - **Composition**: Not a focus
158
+
159
+ ---
160
+
161
+ ### 🟣 OTHER / MINIMAL DOCUMENTATION
162
+
163
+ #### **12-16. .NET Implementations**
164
+ - **Fabulous (F#/MAUI)**: Likely **FRACTAL** (F# MVU port, similar to Elmish)
165
+ - **BlazorMVU (C#/Blazor)**: Likely **FRACTAL** (Elm-inspired)
166
+ - **MauiReactor (C#/MAUI)**: Likely **FRACTAL** (MVU for .NET MAUI)
167
+ - **MVUX (.NET/Uno)**: **HYBRID** (Model-View-Update eXtended, data binding focus)
168
+ - **ngx-mvu (Angular)**: Likely **HYBRID** (Angular + MVU concepts)
169
+
170
+ ---
171
+
172
+ ## Summary Table
173
+
174
+ | Framework | Style | Terminology |
175
+ |-----------|-------|-------------|
176
+ | **Elm** | Fractal (evolved) | Nested TEA, Modules, Translator/OutMsg Pattern |
177
+ | **Bubble Tea** | Fractal | Tree of Models, Nested Models, Child Components |
178
+ | **Iced** | Fractal | Nested components, Message routing, `.map()` |
179
+ | **Elmish** | Fractal | Nested TEA, Child modules |
180
+ | **Hyperapp** | Fractal-lite | (minimal framework, no specific term) |
181
+ | **Redux** | **SLICE** | **Slices**, Reducers, State combination |
182
+ | **TCA** | **HYBRID** | Reducers, Scoping, Pullback, Child features |
183
+ | **Flutter Bloc** | **HYBRID** | BloC per feature, Nested BloCs, Providers |
184
+ | **Android MVI** | **HYBRID** | ViewModel per feature, StateFlow, Intents |
185
+ | **Meiosis** | **HYBRID** | Services, Nested components (optional) |
186
+ | **SAM** | None | N/A (TLA+ formalism focus) |
187
+ | Fabulous | Fractal (likely) | (F# MVU) |
188
+ | BlazorMVU | Fractal (likely) | (Elm-inspired) |
189
+ | MauiReactor | Fractal (likely) | (MVU for MAUI) |
190
+ | MVUX | Hybrid (likely) | (Extended MVU, data binding) |
191
+ | ngx-mvu | Hybrid (likely) | (Angular + MVU) |
192
+
193
+ ---
194
+
195
+ ## Key Insights
196
+
197
+ ### Fractal Pattern Characteristics:
198
+ 1. ✅ **Complete MVU triads** for each component
199
+ 2. ✅ **Recursive composition** (components contain components)
200
+ 3. ✅ **Message delegation** (parent forwards to child)
201
+ 4. ✅ **View composition** (parent combines child views)
202
+ 5. ❌ **High boilerplate** (Elm community moved away from deep nesting)
203
+
204
+ **Examples**: Elm (original), Bubble Tea, Iced, Elmish
205
+
206
+ ### Slice Pattern Characteristics:
207
+ 1. ✅ **Domain-partitioned state** (slices by feature)
208
+ 2. ✅ **Flat composition** (slices are siblings, not nested)
209
+ 3. ✅ **Centralized view** (views pull from multiple slices)
210
+ 4. ✅ **Low boilerplate** (slices are simple)
211
+ 5. ❌ **No per-slice views** (views are separate layer)
212
+
213
+ **Example**: Redux (pure slice pattern)
214
+
215
+ ### Hybrid Pattern Characteristics:
216
+ 1. ✅ **Feature-scoped state** (like slices)
217
+ 2. ✅ **Independent update logic** (like fractal)
218
+ 3. ✅ **Flexible composition** (dependency injection, scoping)
219
+ 4. ✅ **Reduced boilerplate** (compared to pure fractal)
220
+ 5. ⚠️ **Framework-specific** (each does it differently)
221
+
222
+ **Examples**: TCA (scoping/pullback), Flutter Bloc (providers), Android MVI (ViewModels)
223
+
224
+ ---
225
+
226
+ ## Where Does RatatuiRuby-TEA Fit?
227
+
228
+ ### Current State: **FRACTAL**
229
+ - Fragments have `Model`, `INITIAL`, `UPDATE`, `VIEW`
230
+ - Parents delegate to children via `Tea.delegate`
231
+ - Message routing via `Tea.route`
232
+ - Router DSL for declarative composition
233
+
234
+ ### With Init Callable: **ENHANCED FRACTAL**
235
+ Your `Init` proposal strengthens the fractal pattern by:
236
+ 1. ✅ Enabling **parameterized initialization** (like Iced's `flags`)
237
+ 2. ✅ Supporting **initial commands** (like Elm's `(Model, Cmd)`)
238
+ 3. ✅ Allowing **parent-to-child props** (React-style, but fractal)
239
+ 4. ✅ Maintaining **complete MVU triads** (Model, Init, Update, View)
240
+
241
+ This is **closer to Iced** (Rust) than Elm, as Iced explicitly supports flags for initialization and has evolved beyond Elm's original design.
242
+
243
+ ---
244
+
245
+ ## Recommendation
246
+
247
+ RatatuiRuby-TEA should **embrace the modern fractal pattern** with:
248
+ - **Complete MVU triads per fragment**: `Model`, `Init`, `Update`, `View`
249
+ - **Parameterized initialization**: `Init` accepts flags/props
250
+ - **Explicit composition**: Parent calls child `Init`, routes messages, composes views
251
+
252
+ This positions RatatuiRuby-TEA as:
253
+ - **Functional** (like Elm/Elmish/Iced)
254
+ - **Composable** (fractal pattern)
255
+ - **Modern** (Init callable, not static constants)
256
+ - **Parameterizable** (flags/props support)
257
+
258
+ **NOT** like Redux (slice pattern) or TCA/Bloc (OOP/hybrid patterns).
@@ -0,0 +1,405 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: CC-BY-SA-4.0
5
+ -->
6
+
7
+ # Init Callable Implementation Plan
8
+
9
+ ## Problem Statement
10
+
11
+ The current codebase uses **three different names** for initial state:
12
+ - `MODEL` (simple examples: README, verify_readme_usage)
13
+ - `INITIAL` (fractal fragments: dashboard examples)
14
+ - `init:` (runtime parameter with different semantics)
15
+
16
+ The planned v0.4.0 transition (`INITIAL` → `Initial` PascalCase) would create visual collision with `Model`, where both look like type names despite different purposes (type vs instance).
17
+
18
+ **Critical Finding**: Research of 15+ MVU/TEA frameworks shows **zero** use static constants for initialization. All use callables/functions.
19
+
20
+ ## Solution: Init Callable
21
+
22
+ Replace static `INITIAL`/`MODEL` constants with `Init` callable that:
23
+ 1. Returns `[model, command]` tuple (or DWIM variants)
24
+ 2. Accepts flags/props for parameterization
25
+ 3. Enables parent-to-child initialization data flow
26
+ 4. Supports initial command dispatch alongside state
27
+
28
+ ## Proposed Changes
29
+
30
+ ### Core API
31
+
32
+ #### Fragment Structure (v0.4.0)
33
+ ```ruby
34
+ module MyFragment
35
+ Model = Data.define(:field1, :field2)
36
+
37
+ # NEW: Init callable (optional, defaults to Model.new)
38
+ Init = ->(flags_hash = {}) do
39
+ model = Model.new(...)
40
+ command = some_initial_command # or nil
41
+ [model, command]
42
+ end
43
+
44
+ Update = ->(message, model) { ... }
45
+ View = ->(model, tui) { ... }
46
+ end
47
+ ```
48
+
49
+ #### DWIM Return Values
50
+ ```ruby
51
+ # All valid return formats:
52
+ Init = -> { Model.new(...) } # Just model
53
+ Init = -> { [Model.new(...), nil] } # Explicit tuple, no command
54
+ Init = -> { [Model.new(...), some_cmd] } # With command
55
+ Init = -> { Command.exit } # Just command (edge case)
56
+ ```
57
+
58
+ #### Normalization Helper
59
+ ```ruby
60
+ module Tea
61
+ # Normalize Init return to [model, command] tuple
62
+ # Uses DWIM logic like Update (tea_command? detection)
63
+ def self.normalize_init(result)
64
+ case result
65
+ when Command then [nil, result] # Just command
66
+ in [model, command] then [model, command] # Already tuple
67
+ else
68
+ if result.respond_to?(:tea_command?) && result.tea_command?
69
+ [nil, result] # Command without array wrapper
70
+ else
71
+ [result, nil] # Just model
72
+ end
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ---
79
+
80
+ ### Fractal Composition
81
+
82
+ #### Parent Init Calls Child Init
83
+ ```ruby
84
+ module Dashboard
85
+ Model = Data.define(:stats, :network, :theme)
86
+
87
+ Init = ->(theme: :dark, env: {}) do
88
+ # Call child Inits with props
89
+ stats_model, stats_cmd = StatsPanel::Init.(theme: theme)
90
+ network_model, network_cmd = NetworkPanel::Init.(theme: theme)
91
+
92
+ model = Model.new(
93
+ stats: stats_model,
94
+ network: network_model,
95
+ theme: theme
96
+ )
97
+
98
+ command = Command.batch(
99
+ Tea.route(stats_cmd, :stats),
100
+ Tea.route(network_cmd, :network)
101
+ )
102
+
103
+ [model, command]
104
+ end
105
+
106
+ # View composition - UNCHANGED (still manual)
107
+ View = ->(model, tui) do
108
+ tui.vertical_layout(
109
+ children: [
110
+ StatsPanel::View.call(model.stats, tui),
111
+ NetworkPanel::View.call(model.network, tui)
112
+ ]
113
+ )
114
+ end
115
+
116
+ # Update routing - UNCHANGED (Router DSL)
117
+ include Tea::Router
118
+ route :stats, to: StatsPanel
119
+ route :network, to: NetworkPanel
120
+ Update = from_router
121
+ end
122
+ ```
123
+
124
+ **Key Point**: Only `Update` uses Router DSL. `Init` and `View` remain explicit/manual composition.
125
+
126
+ ---
127
+
128
+ ### Runtime Integration
129
+
130
+ #### Current API (Backward Compatible)
131
+ ```ruby
132
+ # Still works (old style)
133
+ Tea.run(
134
+ model: MyApp::INITIAL,
135
+ view: MyApp::VIEW,
136
+ update: MyApp::UPDATE
137
+ )
138
+ ```
139
+
140
+ #### New API (Fragment-First)
141
+ ```ruby
142
+ # New style (preferred)
143
+ Tea.run(
144
+ fragment: MyApp,
145
+ argv: ARGV,
146
+ env: ENV
147
+ )
148
+
149
+ # Equivalent to:
150
+ # model, init_cmd = MyApp::Init.(argv: ARGV, env: ENV)
151
+ # Tea.run(model: model, view: MyApp::View, update: MyApp::Update, init: init_cmd)
152
+ ```
153
+
154
+ #### Runtime Implementation
155
+ ```ruby
156
+ module RatatuiRuby::Tea
157
+ def self.run(fragment: nil, model: nil, view: nil, update: nil, argv: [], env: {}, init: nil)
158
+ if fragment
159
+ # New style: fragment-first
160
+ init_callable = fragment.const_defined?(:Init) ? fragment::Init : -> { fragment::Model.new }
161
+ init_result = init_callable.call(argv: argv, env: env)
162
+ model, init_cmd = normalize_init(init_result)
163
+ view = fragment::View
164
+ update = fragment::Update
165
+ init = init_cmd # Override init param with Init-generated command
166
+ else
167
+ # Old style: explicit parameters (backward compatible)
168
+ # model, view, update, init already provided
169
+ end
170
+
171
+ Runtime.run(model: model, view: view, update: update, init: init)
172
+ end
173
+ end
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Migration Strategy
179
+
180
+ ### v0.4.0: Breaking Change (Zero Users, Zero Cruft)
181
+
182
+ Since the project has **zero external users**, we will make this a **breaking change in v0.4.0**:
183
+
184
+ - ✅ `Init` callable is the **only** way to define initial state
185
+ - ❌ `INITIAL` and `MODEL` constants are **removed** (no backward compatibility)
186
+ - ✅ `Init` is **optional** (defaults to `-> { Model.new }` if not defined)
187
+ - ✅ All examples and documentation use `Init` pattern
188
+ - ✅ Clean slate, no migration debt
189
+
190
+ **Rationale**: Adding backward compatibility for a non-existent user base creates unnecessary code complexity and testing burden. Break now while we can.
191
+
192
+ ---
193
+
194
+ ## Implementation Checklist
195
+
196
+ ### Core Implementation
197
+ - [ ] **`Tea.normalize_init` helper**
198
+ - [ ] Implement DWIM logic with `tea_command?` detection
199
+ - [ ] Handle all return formats: model, command, `[model, cmd]`
200
+ - [ ] Tests for all DWIM variants
201
+
202
+ - [ ] **Runtime changes**
203
+ - [ ] Add `fragment:`, `argv:`, `env:` parameters to `Tea.run`
204
+ - [ ] Auto-call `fragment::Init` when `fragment:` provided
205
+ - [ ] Default `Init` to `-> { Model.new }` if not defined
206
+ - [ ] Maintain backward compatibility with `model:`/`view:`/`update:` params
207
+ - [ ] Tests for both old and new API styles
208
+
209
+ ### Example Migrations
210
+ - [ ] **Simple example** (verify_readme_usage)
211
+ - [ ] Replace `MODEL` constant with `Init` callable
212
+ - [ ] Update README code samples
213
+ - [ ] Verify tests still pass
214
+
215
+ - [ ] **Fractal example** (app_fractal_dashboard)
216
+ - [ ] Convert all fragment `INITIAL` to `Init`
217
+ - [ ] Update parent Init to call child Inits with props
218
+ - [ ] Compose initial commands with `Command.batch`
219
+ - [ ] Verify all dashboard variants (base, router, manual, helpers) work
220
+
221
+ ### Documentation
222
+ - [ ] **Update AGENTS.md**
223
+ - [ ] Change Fragment definition to use `Init` instead of `INITIAL`
224
+ - [ ] Update terminology section
225
+
226
+ - [ ] **Create concept documentation files**
227
+ - [ ] `doc/init.md` (full documentation)
228
+ - [ ] Follow documentation style guide (Alexandrian Context-Problem-Solution)
229
+ - [ ] Explain what Init is and why it exists (vs static constants)
230
+ - [ ] Document Init at root level (runtime integration)
231
+ - [ ] Document Init at non-root level (fractal composition)
232
+ - [ ] Show parameterization with flags/props
233
+ - [ ] Document DWIM return values
234
+ - [ ] Show Tea.normalize_init usage
235
+ - [ ] Multiple complete examples (simple, fractal, with commands)
236
+ - [ ] Link to related concepts (Fragment, Model, Update, Command)
237
+ - [ ] Create stubs for concept doc series (H1 + "TODO" only):
238
+ - [ ] `doc/fragment.md`
239
+ - [ ] `doc/model.md`
240
+ - [ ] `doc/view.md`
241
+ - [ ] `doc/update.md`
242
+ - [ ] `doc/command.md`
243
+ - [ ] `doc/message.md`
244
+ - [ ] `doc/output.md`
245
+
246
+ - [ ] **Update design_for_v0.4.0.md**
247
+ - [ ] Add Init Callable section
248
+ - [ ] Document DWIM behavior
249
+ - [ ] Document `Tea.normalize_init` helper
250
+ - [ ] Add footnote on Iced pattern inspiration
251
+ - [ ] Add footnote on OutMsg pattern (future consideration)
252
+
253
+ - [ ] **Create migration guide**
254
+ - [ ] Before/after examples
255
+ - [ ] Deprecation timeline
256
+ - [ ] Common patterns
257
+
258
+ - [ ] **Update RDoc**
259
+ - [ ] Document `Tea.normalize_init`
260
+ - [ ] Document new `Tea.run` signature
261
+ - [ ] Update Fragment examples throughout
262
+
263
+ ### RBS Type Signatures
264
+ - [ ] **Update Tea module RBS**
265
+ ```ruby
266
+ module RatatuiRuby::Tea
267
+ def self.run: (
268
+ ?fragment: Module,
269
+ ?model: untyped,
270
+ ?view: ^(untyped, untyped) -> Widget,
271
+ ?update: ^(untyped, untyped) -> untyped,
272
+ ?init: ^() -> untyped,
273
+ ?argv: Array[String],
274
+ ?env: Hash[String, String]
275
+ ) -> void
276
+
277
+ def self.normalize_init: (untyped result) -> [untyped, Command?]
278
+ end
279
+ ```
280
+
281
+ ### Testing
282
+ - [ ] **Unit tests for `Tea.normalize_init`**
283
+ - [ ] Returns `[model, nil]` for model-only
284
+ - [ ] Returns `[nil, cmd]` for command-only
285
+ - [ ] Returns `[model, cmd]` for tuple
286
+ - [ ] Handles `tea_command?` detection
287
+
288
+ - [ ] **Integration tests for `Tea.run`**
289
+ - [ ] Fragment-first API works
290
+ - [ ] Old API still works (backward compat)
291
+ - [ ] Init commands are dispatched
292
+ - [ ] ARGV/ENV passed to Init
293
+ - [ ] Default Init works when not defined
294
+
295
+ - [ ] **Fractal composition tests**
296
+ - [ ] Parent Init calls child Inits
297
+ - [ ] Props passed to children
298
+ - [ ] Commands batched correctly
299
+ - [ ] Router still routes messages correctly
300
+
301
+ ### CHANGELOG
302
+ - [ ] Add to `[UNRELEASED]` section:
303
+ ```markdown
304
+ ### Breaking Changes
305
+ - **REMOVED**: `INITIAL` and `MODEL` constants
306
+ - Use `Init` callable instead: `Init = -> { [Model.new(...), nil] }`
307
+ - `Init` is optional (defaults to `-> { Model.new }`)
308
+
309
+ ### Added
310
+ - **Init Callable Pattern**: Fragments support `Init` callable for parameterized initialization
311
+ - Returns `[model, command]` tuple (DWIM supported)
312
+ - Accepts flags/props for parent-to-child data flow
313
+ - `Tea.normalize_init` helper for composing child Inits
314
+ - **Fragment-First Runtime API**: `Tea.run(fragment: MyApp, argv: ARGV, env: ENV)`
315
+ - **Concept Documentation**: Created `doc/init.md` and series stubs
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Breaking Changes
321
+
322
+ **v0.4.0 is a BREAKING RELEASE**:
323
+
324
+ - ❌ **REMOVED**: `INITIAL` and `MODEL` constants (use `Init` callable instead)
325
+ - ✅ **NEW**: `Init` callable is the only supported initialization pattern
326
+ - ✅ **NEW**: Runtime `fragment:` parameter for fragment-first API
327
+
328
+ **Rationale**: Zero external users means zero migration burden. Ship clean architecture without legacy cruft.
329
+
330
+ ---
331
+
332
+ ## Open Questions
333
+
334
+ None - all design decisions finalized with user.
335
+
336
+ ---
337
+
338
+ ## Design Notes (Contributor Reference)
339
+
340
+ ### Concept Documentation Series
341
+
342
+ `doc/init.md` is the **first** in a series of concept documentation pages, one for each core TEA concept:
343
+ - `doc/init.md` (this implementation)
344
+ - `doc/fragment.md` (future)
345
+ - `doc/model.md` (future)
346
+ - `doc/view.md` (future)
347
+ - `doc/update.md` (future)
348
+ - `doc/command.md` (future)
349
+ - `doc/message.md` (future)
350
+ - `doc/output.md` (future)
351
+
352
+ Each follows the Alexandrian Context-Problem-Solution pattern per `doc/contributors/documentation_style.md`.
353
+
354
+ ### Why Not Auto-Init in Router DSL?
355
+
356
+ The Router DSL is **message-only** by design. It handles `Update` function routing, not initialization or view composition:
357
+
358
+ ```ruby
359
+ route :stats, to: StatsPanel # Only routes messages to StatsPanel::Update
360
+ ```
361
+
362
+ Both `Init` and `View` remain **explicit composition** (manual), which provides:
363
+ - ✅ Full control over initialization order and props
364
+ - ✅ Clear, explicit data flow
365
+ - ✅ Flexibility for conditional initialization
366
+ - ✅ Consistency with View composition pattern
367
+
368
+ ### Terminology Inspirations
369
+
370
+ This pattern draws from multiple proven MVU implementations:
371
+ - **Elm**: `init : () -> (Model, Cmd Msg)` tuple return
372
+ - **Iced (Rust)**: `fn new(flags: Flags) -> (State, Command)` parameterized init
373
+ - **Bubble Tea (Go)**: `func (m Model) Init() tea.Cmd` method-based init
374
+ - **Hyperapp (JS)**: `init: [state, ...effects]` array-based DWIM
375
+
376
+ See `doc/contributors/mvu_research_notes.md` for detailed analysis.
377
+
378
+ ### Future: OutMsg Pattern
379
+
380
+ Elm's "Translator Pattern" / "OutMsg Pattern" allows children to emit events to parents beyond state updates. Not needed for v0.4.0, but may be considered for future:
381
+
382
+ ```ruby
383
+ # Potential future enhancement
384
+ Update = ->(msg, model) do
385
+ case msg
386
+ in [:data_loaded, data]
387
+ [[model.with(data: data), nil], [:parent_event_here]]
388
+ # ^^^^^^^^^^^^^^^^^^^^ OutMsg for parent
389
+ end
390
+ end
391
+ ```
392
+
393
+ See `doc/contributors/outmsg_pattern_notes.md` for details.
394
+
395
+ ---
396
+
397
+ ## Success Criteria
398
+
399
+ - [ ] All existing tests pass
400
+ - [ ] `bundle exec agent_rake` passes (0 warnings)
401
+ - [ ] Both old and new API styles work
402
+ - [ ] Fractal dashboard example uses Init pattern
403
+ - [ ] Simple example uses Init pattern
404
+ - [ ] Documentation is comprehensive and clear
405
+ - [ ] Migration path is documented