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
@@ -6,23 +6,43 @@
6
6
  module RatatuiRuby
7
7
  # The Elm Architecture for RatatuiRuby.
8
8
  module Tea
9
- # Starts the MVU event loop.
9
+ # Starts the MVU event loop (positional fragment).
10
10
  def self.run: [Model] (
11
+ ?Module? root_fragment,
12
+ ?fps: Integer
13
+ ) -> Model
14
+
15
+ # Starts the MVU event loop (explicit parameters).
16
+ | [Model] (
17
+ ?Module? root_fragment,
18
+ ?fps: Integer,
11
19
  model: Model,
12
- view: ^(Model, RatatuiRuby::TUI) -> (RatatuiRuby::_CustomWidget | RatatuiRuby::widget),
20
+ view: ^(Model, RatatuiRuby::TUI) -> Runtime::renderable,
13
21
  update: ^(RatatuiRuby::Event, Model) -> Runtime::update_result?,
14
- ?init: (^() -> untyped)?
22
+ ?command: Command::execution?
23
+ ) -> Model
24
+
25
+ # Starts the MVU event loop (explicit parameters without fps).
26
+ | [Model] (
27
+ ?Module? root_fragment,
28
+ model: Model,
29
+ view: ^(Model, RatatuiRuby::TUI) -> Runtime::renderable,
30
+ update: ^(RatatuiRuby::Event, Model) -> Runtime::update_result?,
31
+ ?command: Command::execution?
15
32
  ) -> Model
16
33
 
17
34
  # Wraps a command with a routing prefix.
18
35
  def self.route: (Command::execution command, Symbol prefix) -> Command::Mapped
19
36
 
20
- # Delegates a prefixed message to a child bag's UPDATE.
37
+ # Delegates a prefixed message to a child fragment's UPDATE.
21
38
  def self.delegate: (
22
39
  untyped message,
23
40
  Symbol prefix,
24
41
  ^(Array[untyped]?, untyped) -> [untyped, Command::execution?] child_update,
25
42
  untyped child_model
26
43
  ) -> ([untyped, Command::execution?] | nil)
44
+
45
+ # Normalizes Init callable return value to [model, command] tuple.
46
+ def self.normalize_init: [Model] (Runtime::init_result result) -> [Model?, Command::execution?]
27
47
  end
28
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratatui_ruby-tea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kerrick Long
@@ -37,6 +37,34 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0.6'
40
+ - !ruby/object:Gem::Dependency
41
+ name: concurrent-ruby
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.3'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.3'
54
+ - !ruby/object:Gem::Dependency
55
+ name: concurrent-ruby-edge
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.7'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.7'
40
68
  - !ruby/object:Gem::Dependency
41
69
  name: rdoc
42
70
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +122,16 @@ files:
94
122
  - Steepfile
95
123
  - doc/concepts/application_architecture.md
96
124
  - doc/concepts/application_testing.md
125
+ - doc/concepts/async_work.md
126
+ - doc/concepts/commands.md
127
+ - doc/concepts/message_processing.md
128
+ - doc/contributors/WIP/decomposition_strategies_analysis.md
129
+ - doc/contributors/WIP/implementation_plan.md
130
+ - doc/contributors/WIP/init_callable_proposal.md
131
+ - doc/contributors/WIP/mvu_tea_implementations_research.md
132
+ - doc/contributors/WIP/runtime_refactoring_status.md
133
+ - doc/contributors/WIP/task.md
134
+ - doc/contributors/WIP/v0.4.0_todo.md
97
135
  - doc/contributors/design/commands_and_outlets.md
98
136
  - doc/contributors/kit-no-outlet.md
99
137
  - doc/contributors/priorities.md
@@ -105,19 +143,19 @@ files:
105
143
  - doc/index.md
106
144
  - examples/app_fractal_dashboard/README.md
107
145
  - examples/app_fractal_dashboard/app.rb
108
- - examples/app_fractal_dashboard/bags/custom_shell_input.rb
109
- - examples/app_fractal_dashboard/bags/custom_shell_modal.rb
110
- - examples/app_fractal_dashboard/bags/custom_shell_output.rb
111
- - examples/app_fractal_dashboard/bags/disk_usage.rb
112
- - examples/app_fractal_dashboard/bags/network_panel.rb
113
- - examples/app_fractal_dashboard/bags/ping.rb
114
- - examples/app_fractal_dashboard/bags/stats_panel.rb
115
- - examples/app_fractal_dashboard/bags/system_info.rb
116
- - examples/app_fractal_dashboard/bags/uptime.rb
117
146
  - examples/app_fractal_dashboard/dashboard/base.rb
118
147
  - examples/app_fractal_dashboard/dashboard/update_helpers.rb
119
148
  - examples/app_fractal_dashboard/dashboard/update_manual.rb
120
149
  - examples/app_fractal_dashboard/dashboard/update_router.rb
150
+ - examples/app_fractal_dashboard/fragments/custom_shell_input.rb
151
+ - examples/app_fractal_dashboard/fragments/custom_shell_modal.rb
152
+ - examples/app_fractal_dashboard/fragments/custom_shell_output.rb
153
+ - examples/app_fractal_dashboard/fragments/disk_usage.rb
154
+ - examples/app_fractal_dashboard/fragments/network_panel.rb
155
+ - examples/app_fractal_dashboard/fragments/ping.rb
156
+ - examples/app_fractal_dashboard/fragments/stats_panel.rb
157
+ - examples/app_fractal_dashboard/fragments/system_info.rb
158
+ - examples/app_fractal_dashboard/fragments/uptime.rb
121
159
  - examples/verify_readme_usage/README.md
122
160
  - examples/verify_readme_usage/app.rb
123
161
  - examples/widget_command_system/README.md
@@ -125,24 +163,38 @@ files:
125
163
  - exe/.gitkeep
126
164
  - lib/ratatui_ruby/tea.rb
127
165
  - lib/ratatui_ruby/tea/command.rb
128
- - lib/ratatui_ruby/tea/command/cancellation_token.rb
166
+ - lib/ratatui_ruby/tea/command/all.rb
167
+ - lib/ratatui_ruby/tea/command/batch.rb
129
168
  - lib/ratatui_ruby/tea/command/custom.rb
169
+ - lib/ratatui_ruby/tea/command/http.rb
170
+ - lib/ratatui_ruby/tea/command/lifecycle.rb
130
171
  - lib/ratatui_ruby/tea/command/outlet.rb
172
+ - lib/ratatui_ruby/tea/command/wait.rb
173
+ - lib/ratatui_ruby/tea/message.rb
174
+ - lib/ratatui_ruby/tea/message/all.rb
175
+ - lib/ratatui_ruby/tea/message/http_response.rb
176
+ - lib/ratatui_ruby/tea/message/system/batch.rb
177
+ - lib/ratatui_ruby/tea/message/system/stream.rb
178
+ - lib/ratatui_ruby/tea/message/timer.rb
131
179
  - lib/ratatui_ruby/tea/router.rb
132
180
  - lib/ratatui_ruby/tea/runtime.rb
133
181
  - lib/ratatui_ruby/tea/shortcuts.rb
182
+ - lib/ratatui_ruby/tea/test_helper.rb
134
183
  - lib/ratatui_ruby/tea/version.rb
135
184
  - mise.toml
136
185
  - rbs_collection.lock.yaml
137
186
  - rbs_collection.yaml
187
+ - sig/concurrent.rbs
138
188
  - sig/examples/verify_readme_usage/app.rbs
139
189
  - sig/examples/widget_command_system/app.rbs
140
190
  - sig/open3.rbs
141
191
  - sig/ratatui_ruby/tea.rbs
142
192
  - sig/ratatui_ruby/tea/command.rbs
193
+ - sig/ratatui_ruby/tea/message.rbs
143
194
  - sig/ratatui_ruby/tea/router.rbs
144
195
  - sig/ratatui_ruby/tea/runtime.rbs
145
196
  - sig/ratatui_ruby/tea/shortcuts.rbs
197
+ - sig/ratatui_ruby/tea/test_helper.rbs
146
198
  - sig/ratatui_ruby/tea/version.rbs
147
199
  - tasks/example_viewer.html.erb
148
200
  - tasks/resources/build.yml.erb
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: LGPL-3.0-or-later
6
- #++
7
-
8
- require_relative "custom_shell_input"
9
- require_relative "custom_shell_output"
10
-
11
- # Parent coordinator bag for custom shell modal.
12
- #
13
- # Routes to active child (input or output). Checks child model state for transitions.
14
- module CustomShellModal
15
- Command = RatatuiRuby::Tea::Command
16
-
17
- Model = Data.define(:mode, :input, :output)
18
- INITIAL = Ractor.make_shareable(Model.new(mode: :none, input: CustomShellInput::INITIAL, output: CustomShellOutput::INITIAL))
19
-
20
- VIEW = lambda do |model, tui|
21
- case model.mode
22
- when :none then nil
23
- when :input then CustomShellInput::VIEW.call(model.input, tui)
24
- when :output then CustomShellOutput::VIEW.call(model.output, tui)
25
- end
26
- end
27
-
28
- UPDATE = lambda do |message, model|
29
- case model.mode
30
- when :input
31
- new_input, cmd = CustomShellInput::UPDATE.call(message, model.input)
32
-
33
- if new_input.cancelled
34
- [INITIAL, nil]
35
- elsif new_input.submitted
36
- shell_cmd = new_input.text
37
- new_output = CustomShellOutput::INITIAL.with(command: shell_cmd, running: true)
38
- [
39
- model.with(mode: :output, input: CustomShellInput::INITIAL, output: new_output),
40
- Command.system(shell_cmd, :shell_output, stream: true),
41
- ]
42
- else
43
- [model.with(input: new_input), cmd]
44
- end
45
-
46
- when :output
47
- # Route streaming messages (strip :shell_output prefix)
48
- routed = case message
49
- in [:shell_output, *rest] then rest
50
- else message
51
- end
52
-
53
- new_output, cmd = CustomShellOutput::UPDATE.call(routed, model.output)
54
-
55
- if new_output.dismissed
56
- [INITIAL, nil]
57
- else
58
- [model.with(output: new_output), cmd]
59
- end
60
-
61
- else
62
- [model, nil]
63
- end
64
- end
65
-
66
- def self.open
67
- INITIAL.with(mode: :input, input: CustomShellInput::INITIAL)
68
- end
69
-
70
- def self.active?(model)
71
- model.mode != :none
72
- end
73
- end
@@ -1,135 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: LGPL-3.0-or-later
6
- #++
7
-
8
- module RatatuiRuby
9
- module Tea
10
- module Command
11
- # Cooperative cancellation mechanism for long-running commands.
12
- #
13
- # Long-running commands block the event loop. Commands that poll, stream, or wait
14
- # indefinitely prevent clean shutdown. Killing threads mid-operation corrupts state.
15
- #
16
- # This class signals cancellation requests. Commands check +cancelled?+ periodically
17
- # and stop gracefully. The runtime calls +cancel!+ when shutdown begins.
18
- #
19
- # Use it to implement WebSocket handlers, database pollers, or any command that loops.
20
- #
21
- # === Example
22
- #
23
- #--
24
- # SPDX-SnippetBegin
25
- # SPDX-FileCopyrightText: 2026 Kerrick Long
26
- # SPDX-License-Identifier: MIT-0
27
- #++
28
- # class PollerCommand
29
- # include Tea::Command::Custom
30
- #
31
- # def call(out, token)
32
- # until token.cancelled?
33
- # data = fetch_batch
34
- # out.put(:batch, data)
35
- # sleep 5
36
- # end
37
- # out.put(:poller_stopped)
38
- # end
39
- # end
40
- #--
41
- # SPDX-SnippetEnd
42
- #++
43
- class CancellationToken
44
- # Number of times +cancel!+ has been called. :nodoc:
45
- #
46
- # Exposed for testing thread-safety. Not part of the public API.
47
- attr_reader :cancel_count
48
-
49
- # Creates a new cancellation token in the non-cancelled state.
50
- def initialize
51
- @cancel_count = 0
52
- @mutex = Mutex.new
53
- end
54
-
55
- # Signals cancellation. Thread-safe.
56
- #
57
- # Call this to request the command stop. The command checks +cancelled?+
58
- # and stops at the next safe point.
59
- #
60
- # === Example
61
- #
62
- #--
63
- # SPDX-SnippetBegin
64
- # SPDX-FileCopyrightText: 2026 Kerrick Long
65
- # SPDX-License-Identifier: MIT-0
66
- #++
67
- # token = CancellationToken.new
68
- # token.cancel!
69
- # token.cancelled? # => true
70
- #--
71
- # SPDX-SnippetEnd
72
- #++
73
- def cancel!
74
- @mutex.synchronize do
75
- current = @cancel_count
76
- sleep 0 # Force context switch (enables thread-safety testing)
77
- @cancel_count = current + 1
78
- end
79
- end
80
-
81
- # Checks if cancellation was requested. Thread-safe.
82
- #
83
- # Commands call this periodically in their main loop. When it returns
84
- # <tt>true</tt>, the command should clean up and exit.
85
- #
86
- # === Example
87
- #
88
- #--
89
- # SPDX-SnippetBegin
90
- # SPDX-FileCopyrightText: 2026 Kerrick Long
91
- # SPDX-License-Identifier: MIT-0
92
- #++
93
- # until token.cancelled?
94
- # do_work
95
- # sleep 1
96
- # end
97
- #--
98
- # SPDX-SnippetEnd
99
- #++
100
- def cancelled?
101
- @cancel_count > 0
102
- end
103
-
104
- # Null object for commands that ignore cancellation.
105
- #
106
- # Some commands complete quickly and do not check for cancellation.
107
- # Pass this when the command signature requires a token but the
108
- # command does not use it.
109
- #
110
- # Ractor-shareable. Calling <tt>cancel!</tt> does nothing.
111
- class NoneToken < Data.define(:cancelled?)
112
- # Does nothing. Ignores cancellation requests.
113
- #
114
- # === Example
115
- #
116
- #--
117
- # SPDX-SnippetBegin
118
- # SPDX-FileCopyrightText: 2026 Kerrick Long
119
- # SPDX-License-Identifier: MIT-0
120
- #++
121
- # CancellationToken::NONE.cancel! # => nil (no effect)
122
- #--
123
- # SPDX-SnippetEnd
124
- #++
125
- def cancel!
126
- nil
127
- end
128
- end
129
-
130
- # Singleton null token. Always returns <tt>cancelled? == false</tt>.
131
- NONE = NoneToken.new(cancelled?: false)
132
- end
133
- end
134
- end
135
- end