ratatui_ruby-tea 0.3.0 → 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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +51 -7
  3. data/CHANGELOG.md +109 -0
  4. data/README.md +25 -5
  5. data/Rakefile +1 -1
  6. data/Steepfile +3 -3
  7. data/doc/concepts/async_work.md +164 -0
  8. data/doc/concepts/commands.md +528 -0
  9. data/doc/concepts/message_processing.md +51 -0
  10. data/doc/contributors/WIP/decomposition_strategies_analysis.md +258 -0
  11. data/doc/contributors/WIP/implementation_plan.md +405 -0
  12. data/doc/contributors/WIP/init_callable_proposal.md +341 -0
  13. data/doc/contributors/WIP/mvu_tea_implementations_research.md +372 -0
  14. data/doc/contributors/WIP/runtime_refactoring_status.md +47 -0
  15. data/doc/contributors/WIP/task.md +36 -0
  16. data/doc/contributors/WIP/v0.4.0_todo.md +468 -0
  17. data/doc/contributors/design/commands_and_outlets.md +214 -0
  18. data/doc/contributors/kit-no-outlet.md +237 -0
  19. data/doc/contributors/priorities.md +22 -24
  20. data/examples/app_fractal_dashboard/app.rb +3 -7
  21. data/examples/app_fractal_dashboard/dashboard/base.rb +15 -16
  22. data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +8 -8
  23. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +11 -11
  24. data/examples/app_fractal_dashboard/dashboard/update_router.rb +4 -4
  25. data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_input.rb +8 -4
  26. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
  27. data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_output.rb +8 -4
  28. data/examples/app_fractal_dashboard/{bags → fragments}/disk_usage.rb +13 -10
  29. data/examples/app_fractal_dashboard/{bags → fragments}/network_panel.rb +12 -12
  30. data/examples/app_fractal_dashboard/{bags → fragments}/ping.rb +12 -8
  31. data/examples/app_fractal_dashboard/{bags → fragments}/stats_panel.rb +12 -12
  32. data/examples/app_fractal_dashboard/{bags → fragments}/system_info.rb +11 -7
  33. data/examples/app_fractal_dashboard/{bags → fragments}/uptime.rb +11 -7
  34. data/examples/verify_readme_usage/README.md +7 -4
  35. data/examples/verify_readme_usage/app.rb +7 -4
  36. data/lib/ratatui_ruby/tea/command/all.rb +71 -0
  37. data/lib/ratatui_ruby/tea/command/batch.rb +79 -0
  38. data/lib/ratatui_ruby/tea/command/custom.rb +106 -0
  39. data/lib/ratatui_ruby/tea/command/http.rb +194 -0
  40. data/lib/ratatui_ruby/tea/command/lifecycle.rb +136 -0
  41. data/lib/ratatui_ruby/tea/command/outlet.rb +159 -0
  42. data/lib/ratatui_ruby/tea/command/wait.rb +82 -0
  43. data/lib/ratatui_ruby/tea/command.rb +416 -13
  44. data/lib/ratatui_ruby/tea/message/all.rb +47 -0
  45. data/lib/ratatui_ruby/tea/message/http_response.rb +63 -0
  46. data/lib/ratatui_ruby/tea/message/system/batch.rb +63 -0
  47. data/lib/ratatui_ruby/tea/message/system/stream.rb +69 -0
  48. data/lib/ratatui_ruby/tea/message/timer.rb +48 -0
  49. data/lib/ratatui_ruby/tea/message.rb +40 -0
  50. data/lib/ratatui_ruby/tea/router.rb +155 -87
  51. data/lib/ratatui_ruby/tea/runtime.rb +329 -150
  52. data/lib/ratatui_ruby/tea/shortcuts.rb +2 -2
  53. data/lib/ratatui_ruby/tea/test_helper.rb +58 -0
  54. data/lib/ratatui_ruby/tea/version.rb +1 -1
  55. data/lib/ratatui_ruby/tea.rb +44 -10
  56. data/rbs_collection.lock.yaml +108 -0
  57. data/rbs_collection.yaml +15 -0
  58. data/sig/concurrent.rbs +72 -0
  59. data/sig/examples/verify_readme_usage/app.rbs +1 -1
  60. data/sig/examples/widget_command_system/app.rbs +1 -1
  61. data/sig/open3.rbs +17 -0
  62. data/sig/ratatui_ruby/tea/command.rbs +226 -6
  63. data/sig/ratatui_ruby/tea/message.rbs +123 -0
  64. data/sig/ratatui_ruby/tea/router.rbs +110 -54
  65. data/sig/ratatui_ruby/tea/runtime.rbs +63 -12
  66. data/sig/ratatui_ruby/tea/shortcuts.rbs +18 -0
  67. data/sig/ratatui_ruby/tea/test_helper.rbs +12 -0
  68. data/sig/ratatui_ruby/tea/version.rbs +10 -0
  69. data/sig/ratatui_ruby/tea.rbs +39 -7
  70. data/tasks/steep.rake +11 -0
  71. metadata +75 -12
  72. data/examples/app_fractal_dashboard/bags/custom_shell_modal.rb +0 -73
@@ -0,0 +1,123 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module RatatuiRuby
7
+ module Tea
8
+ # Messages sent from commands to update functions.
9
+ module Message
10
+ # Fallback predicate mixin.
11
+ module Predicates
12
+ # Returns false for unknown predicate methods.
13
+ def method_missing: (Symbol name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
14
+
15
+ # Responds to all predicate methods.
16
+ def respond_to_missing?: (Symbol name, ?bool include_private) -> bool
17
+ end
18
+
19
+ # System command response types.
20
+ module System
21
+ # Response from a system command (batch mode).
22
+ class Batch
23
+ include Predicates
24
+
25
+ attr_reader envelope: Symbol
26
+ attr_reader stdout: String
27
+ attr_reader stderr: String
28
+ attr_reader status: Integer
29
+
30
+ def initialize: (
31
+ envelope: Symbol,
32
+ stdout: String,
33
+ stderr: String,
34
+ status: Integer
35
+ ) -> void
36
+
37
+ def system?: () -> bool
38
+ def success?: () -> bool
39
+ def error?: () -> bool
40
+ def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
41
+ end
42
+
43
+ # Streaming message from a system command.
44
+ class Stream
45
+ include Predicates
46
+
47
+ attr_reader envelope: Symbol
48
+ attr_reader stream: Symbol
49
+ attr_reader content: String?
50
+ attr_reader status: Integer?
51
+
52
+ def initialize: (
53
+ envelope: Symbol,
54
+ stream: Symbol,
55
+ content: String?,
56
+ status: Integer?
57
+ ) -> void
58
+
59
+ def system?: () -> bool
60
+ def stdout?: () -> bool
61
+ def stderr?: () -> bool
62
+ def complete?: () -> bool
63
+ def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
64
+ end
65
+ end
66
+
67
+ # Response from a timer command.
68
+ class Timer
69
+ include Predicates
70
+
71
+ attr_reader envelope: Symbol
72
+ attr_reader elapsed: Float
73
+
74
+ def initialize: (envelope: Symbol, elapsed: Float) -> void
75
+
76
+ def timer?: () -> bool
77
+ def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
78
+ end
79
+
80
+ # Response from an HTTP command.
81
+ class HttpResponse
82
+ include Predicates
83
+
84
+ attr_reader envelope: Symbol
85
+ attr_reader status: Integer?
86
+ attr_reader body: String?
87
+ attr_reader headers: Hash[String, String]?
88
+ attr_reader error: String?
89
+
90
+ def initialize: (
91
+ envelope: Symbol,
92
+ status: Integer?,
93
+ body: String?,
94
+ headers: Hash[String, String]?,
95
+ error: String?
96
+ ) -> void
97
+
98
+ def http?: () -> bool
99
+ def success?: () -> bool
100
+ def error?: () -> bool
101
+ def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
102
+ end
103
+
104
+ # Response from Command.all aggregating parallel execution.
105
+ class All
106
+ include Predicates
107
+
108
+ attr_reader envelope: Symbol
109
+ attr_reader results: Array[untyped]
110
+ attr_reader nested: bool
111
+
112
+ def initialize: (
113
+ envelope: Symbol,
114
+ results: Array[untyped],
115
+ nested: bool
116
+ ) -> void
117
+
118
+ def all?: () -> bool
119
+ def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
120
+ end
121
+ end
122
+ end
123
+ end
@@ -5,94 +5,150 @@
5
5
 
6
6
  module RatatuiRuby
7
7
  module Tea
8
- # Interface for child bag modules (required by route).
9
- interface _Bag[M]
10
- def self.UPDATE: ^(top, M) -> [M, Command::execution?]
11
- def self.INITIAL: M
12
- end
13
-
14
8
  # Declarative DSL for Fractal Architecture.
15
9
  module Router
16
10
  def self.included: (Class base) -> void
17
11
 
18
- # Guard callable: receives model, returns truthy/falsy.
19
- type guard[M] = ^(M model) -> boolish
12
+ # Duck type for Data-like models that support `with`.
13
+ interface _DataModel
14
+ def public_send: (Symbol, *Object) -> Object
15
+ def with: (**Object) -> self
16
+ end
17
+
18
+ # Interface for fractal child modules (fragments).
19
+ # Routed modules must have an UPDATE constant that handles child messages.
20
+ # NOTE: We use Module here because RBS interfaces can't declare class methods.
20
21
 
21
- # Handler callable: returns a command or nil.
22
- type handler = ^() -> Command::execution?
22
+ # Configuration for key handlers.
23
+ class KeyHandlerConfig < Data
24
+ attr_reader handler: (^() -> Command::execution?)?
25
+ attr_reader action: Symbol?
26
+ attr_reader route: Symbol?
27
+ attr_reader guard: (^(_DataModel) -> bool)?
23
28
 
24
- # Click handler: receives x, y coordinates.
25
- type click_handler = ^(Integer x, Integer y) -> Command::execution?
29
+ def initialize: (?handler: (^() -> Command::execution?)?, ?action: Symbol?, ?route: Symbol?, ?guard: (^(_DataModel) -> bool)?) -> void
30
+ end
26
31
 
27
- # Key handler config stored in handlers hash.
28
- type key_config[M] = {
29
- handler: handler?,
30
- action: Symbol?,
31
- route: Symbol?,
32
- guard: guard[M]?
33
- }
32
+ # Configuration for scroll handlers (no coordinates).
33
+ class ScrollHandlerConfig < Data
34
+ attr_reader handler: (^() -> Command::execution?)?
35
+ attr_reader action: Symbol?
34
36
 
35
- # Mouse handler config.
36
- type mouse_config = {
37
- handler: handler | click_handler,
38
- action: Symbol?
39
- }
37
+ def initialize: (?handler: (^() -> Command::execution?)?, ?action: Symbol?) -> void
38
+ end
39
+
40
+ # Configuration for click handlers (x, y coordinates).
41
+ class ClickHandlerConfig < Data
42
+ attr_reader handler: (^(Integer, Integer) -> Command::execution?)?
43
+ attr_reader action: Symbol?
44
+
45
+ def initialize: (?handler: (^(Integer, Integer) -> Command::execution?)?, ?action: Symbol?) -> void
46
+ end
40
47
 
41
48
  # Class methods added when Router is included.
42
- module ClassMethods
43
- def route: [M] (Symbol | String prefix, to: _Bag[M]) -> void
49
+ module ClassMethods : Module
50
+ @routes: Hash[Symbol, Module]
51
+ @actions: Hash[Symbol, ^() -> Command::execution?]
52
+ @key_handlers: Hash[String, KeyHandlerConfig]
53
+ @scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
54
+ @click_handler: ClickHandlerConfig?
55
+
56
+ def route: (Symbol | String prefix, to: Module) -> void
44
57
  def routes: () -> Hash[Symbol, Module]
45
58
 
46
- def action: (Symbol | String name, handler) -> void
47
- def actions: () -> Hash[Symbol, handler]
59
+ def action: (Symbol | String name, ^() -> Command::execution? handler) -> void
60
+ def actions: () -> Hash[Symbol, ^() -> Command::execution?]
48
61
 
49
- def keymap: () { () -> void } -> void
50
- def key_handlers: [M] () -> Hash[String, key_config[M]]
62
+ def keymap: () { (KeymapBuilder) [self: KeymapBuilder] -> void } -> void
63
+ def key_handlers: () -> Hash[String, KeyHandlerConfig]
51
64
 
52
- def mousemap: () { () -> void } -> void
53
- def mouse_handlers: () -> Hash[Symbol, mouse_config]
65
+ def mousemap: () { (MousemapBuilder) [self: MousemapBuilder] -> void } -> void
66
+ def scroll_handlers: () -> Hash[Symbol, ScrollHandlerConfig]
67
+ def click_handler: () -> ClickHandlerConfig?
54
68
 
55
- # Generates an UPDATE lambda from routes, keymap, and mousemap.
56
- def from_router: [M, Msg] () -> ^(Msg message, M model) -> [M, Command::execution?]
69
+ # Returns UPDATE callable that handles routing.
70
+ # Uses is_a? checks for Event::Key and Event::Mouse type narrowing.
71
+ # Model must be Data-like with `with` and field accessors.
72
+ def from_router: () -> RouterUpdate
73
+ end
74
+
75
+ # UPDATE callable returned by from_router with proper typing.
76
+ # Implements #call to satisfy Proc-like interfaces.
77
+ class RouterUpdate
78
+ @routes: Hash[Symbol, Module]
79
+ @actions: Hash[Symbol, ^() -> Command::execution?]
80
+ @key_handlers: Hash[String, KeyHandlerConfig]
81
+ @scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
82
+ @click_handler: ClickHandlerConfig?
83
+
84
+ def initialize: (
85
+ routes: Hash[Symbol, Module],
86
+ actions: Hash[Symbol, ^() -> Command::execution?],
87
+ key_handlers: Hash[String, KeyHandlerConfig],
88
+ scroll_handlers: Hash[Symbol, ScrollHandlerConfig],
89
+ click_handler: ClickHandlerConfig?
90
+ ) -> void
91
+
92
+ # Process message and return [model, command] tuple.
93
+ def call: (
94
+ (RatatuiRuby::Event::Key | RatatuiRuby::Event::Mouse | Array[Object]) message,
95
+ _DataModel model
96
+ ) -> [_DataModel, Command::execution?]
57
97
  end
58
98
 
59
99
  # Builder for keymap DSL.
60
100
  class KeymapBuilder
61
- @handlers: Hash[String, key_config[top]]
101
+ @handlers: Hash[String, KeyHandlerConfig]
102
+ @guard_stack: Array[^(_DataModel) -> bool]
62
103
 
63
- attr_reader handlers: Hash[String, key_config[top]]
104
+ attr_reader handlers: Hash[String, KeyHandlerConfig]
64
105
 
65
106
  def initialize: () -> void
66
107
 
67
- # Registers a key handler with optional guards.
68
- def key: [M] (
108
+ def key: (
69
109
  String | Symbol key_name,
70
- handler | Symbol handler_or_action,
110
+ (^() -> Command::execution?) | Symbol handler_or_action,
71
111
  ?route: Symbol?,
72
- ?when: guard[M]?,
73
- ?if: guard[M]?,
74
- ?only: guard[M]?,
75
- ?guard: guard[M]?,
76
- ?unless: guard[M]?,
77
- ?except: guard[M]?,
78
- ?skip: guard[M]?
112
+ ?when: (^(_DataModel) -> bool)?,
113
+ ?if: (^(_DataModel) -> bool)?,
114
+ ?only: (^(_DataModel) -> bool)?,
115
+ ?guard: (^(_DataModel) -> bool)?,
116
+ ?unless: (^(_DataModel) -> bool)?,
117
+ ?except: (^(_DataModel) -> bool)?,
118
+ ?skip: (^(_DataModel) -> bool)?
79
119
  ) -> void
120
+
121
+ def only: (
122
+ ?when: (^(_DataModel) -> bool)?,
123
+ ?if: (^(_DataModel) -> bool)?,
124
+ ?only: (^(_DataModel) -> bool)?,
125
+ ?guard: (^(_DataModel) -> bool)?
126
+ ) { () -> void } -> void
127
+
128
+ def skip: (
129
+ ?when: (^(_DataModel) -> bool)?,
130
+ ?if: (^(_DataModel) -> bool)?,
131
+ ?skip: (^(_DataModel) -> bool)?,
132
+ ?guard: (^(_DataModel) -> bool)?
133
+ ) { () -> void } -> void
134
+
135
+ private
136
+
137
+ def with_guard: ((^(_DataModel) -> bool)?) { () -> void } -> void
80
138
  end
81
139
 
82
140
  # Builder for mousemap DSL.
83
141
  class MousemapBuilder
84
- @handlers: Hash[Symbol, mouse_config]
142
+ @scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
143
+ @click_handler: ClickHandlerConfig?
85
144
 
86
- attr_reader handlers: Hash[Symbol, mouse_config]
145
+ attr_reader scroll_handlers: Hash[Symbol, ScrollHandlerConfig]
146
+ attr_reader click_handler: ClickHandlerConfig?
87
147
 
88
148
  def initialize: () -> void
89
149
 
90
- def click: (click_handler | Symbol handler_or_action) -> void
91
- def scroll: (:up | :down direction, handler | Symbol handler_or_action) -> void
92
-
93
- private
94
-
95
- def register: (Symbol key, handler | click_handler | Symbol handler_or_action) -> void
150
+ def click: ((^(Integer, Integer) -> Command::execution?) | Symbol handler_or_action) -> void
151
+ def scroll: (:up | :down direction, (^() -> Command::execution?) | Symbol handler_or_action) -> void
96
152
  end
97
153
  end
98
154
  end
@@ -5,22 +5,73 @@
5
5
 
6
6
  module RatatuiRuby
7
7
  module Tea
8
+ # MVU runtime event loop.
8
9
  class Runtime
9
- # Starts the MVU event loop.
10
- def self.run: [M, Msg] (
11
- model: M,
12
- view: ^(M model, RatatuiRuby::TUI tui) -> untyped,
13
- update: ^(Msg msg, M model) -> (M | [M, Cmd::execution?] | [M, Cmd::Quit] | [M, nil]),
14
- ?init: ^() -> Msg
15
- ) -> M
10
+ # Active command tracking entry.
11
+ type active_entry = { future: Concurrent::Promises::Future[void], origin: Concurrent::Promises::ResolvableEvent }
12
+
13
+ # Widget type accepted by view functions.
14
+ type renderable = RatatuiRuby::_CustomWidget | RatatuiRuby::widget
15
+
16
+ # Duck type for update result that can be normalized.
17
+ # Steep needs a union type (not interface) for is_a? narrowing.
18
+ type update_result = [Object, Command::execution?] | Command::execution | Object
19
+
20
+ # Duck type for init result that can be normalized.
21
+ type init_result = [Object, Command::execution?] | Command::execution | Object
22
+
23
+ # Duck type for values that can be queried for command-ness.
24
+ interface _MaybeCommand
25
+ def nil?: () -> bool
26
+ def class: () -> Class
27
+ def respond_to?: (Symbol, ?bool) -> bool
28
+ def tea_command?: () -> bool
29
+ end
30
+
31
+ # Starts the MVU event loop (positional fragment).
32
+ def self.run: [Model] (
33
+ ?Module? root_fragment,
34
+ ?fps: Integer
35
+ ) -> Model
36
+
37
+ # Starts the MVU event loop (explicit parameters).
38
+ | [Model] (
39
+ ?Module? root_fragment,
40
+ ?fps: Integer,
41
+ model: Model,
42
+ view: ^(Model, RatatuiRuby::TUI) -> renderable,
43
+ update: ^(RatatuiRuby::Event, Model) -> update_result?,
44
+ ?command: Command::execution?
45
+ ) -> Model
46
+
47
+ # Starts the MVU event loop (explicit parameters without fps).
48
+ | [Model] (
49
+ ?Module? root_fragment,
50
+ model: Model,
51
+ view: ^(Model, RatatuiRuby::TUI) -> renderable,
52
+ update: ^(RatatuiRuby::Event, Model) -> update_result?,
53
+ ?command: Command::execution?
54
+ ) -> Model
55
+
56
+ # Normalizes Init callable return value to [model, command] tuple.
57
+ def self.normalize_init: [Model] (init_result result) -> [Model?, Command::execution?]
58
+ QUIT: Object
16
59
 
17
60
  private
18
61
 
19
- def self.validate_view_result!: (untyped widget) -> void
20
- def self.normalize_update_result: [M] (untyped result, M previous_model) -> [M, untyped]
21
- def self.valid_cmd?: (untyped value) -> bool
22
- def self.validate_ractor_shareable!: (untyped object, String name) -> void
23
- def self.dispatch: (untyped cmd, Queue queue) -> void
62
+ def self.validate_view_return!: (renderable? widget) -> void
63
+ def self.normalize_update_return: [Model] (update_result? result, Model? previous_model) -> [Model?, Command::execution?]
64
+ def self.validate_ractor_shareable!: [T] (T object, String name) -> void
65
+ def self.fragment_from_kwargs: (Module? root_fragment, ?model: untyped, ?view: untyped, ?update: untyped, ?command: untyped) -> Module
66
+ def self.fragment_invariant!: (String param) -> void
67
+ def self.init_callable: [Model] () -> ^() -> [Model?, Command::execution?]
68
+ def self.start_runtime: () -> untyped
69
+ def self.draw_view: () -> void
70
+ def self.handle_ratatui_event: () -> void
71
+ def self.handle_sync: () -> void
72
+ QUEUE_EMPTY: Object
73
+ def self.send_pending_messages: (?dispatch: bool) -> void
74
+ def self.dispatch_command: () -> void
24
75
  end
25
76
  end
26
77
  end
@@ -0,0 +1,18 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module RatatuiRuby
7
+ module Tea
8
+ # Convenient short aliases for Tea APIs.
9
+ module Shortcuts
10
+ # Short alias for Command.
11
+ module Cmd
12
+ def self.exit: () -> Command::Exit
13
+ def self.sh: (String command, Symbol | Class tag) -> Command::System
14
+ def self.map: (Command::execution inner_command) { (Array[untyped]) -> Array[untyped] } -> Command::Mapped
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module RatatuiRuby
7
+ module Tea
8
+ module TestHelper
9
+ def validate_tea_command!: (Runtime::_MaybeCommand) -> nil
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module RatatuiRuby
7
+ module Tea
8
+ VERSION: String
9
+ end
10
+ end
@@ -4,13 +4,45 @@
4
4
  #++
5
5
 
6
6
  module RatatuiRuby
7
+ # The Elm Architecture for RatatuiRuby.
7
8
  module Tea
8
- # See RatatuiRuby::Tea::Runtime.run
9
- def self.run: [M, Msg] (
10
- model: M,
11
- view: ^(M model, RatatuiRuby::TUI tui) -> untyped,
12
- update: ^(Msg msg, M model) -> (M | [M, Cmd::execution?] | [M, Cmd::Quit] | [M, nil]),
13
- ?init: ^() -> Msg
14
- ) -> M
9
+ # Starts the MVU event loop (positional fragment).
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,
19
+ model: Model,
20
+ view: ^(Model, RatatuiRuby::TUI) -> Runtime::renderable,
21
+ update: ^(RatatuiRuby::Event, Model) -> Runtime::update_result?,
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?
32
+ ) -> Model
33
+
34
+ # Wraps a command with a routing prefix.
35
+ def self.route: (Command::execution command, Symbol prefix) -> Command::Mapped
36
+
37
+ # Delegates a prefixed message to a child fragment's UPDATE.
38
+ def self.delegate: (
39
+ untyped message,
40
+ Symbol prefix,
41
+ ^(Array[untyped]?, untyped) -> [untyped, Command::execution?] child_update,
42
+ untyped child_model
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?]
15
47
  end
16
48
  end
data/tasks/steep.rake ADDED
@@ -0,0 +1,11 @@
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
+ desc "Run Steep type checker"
9
+ task :steep do
10
+ sh "bundle exec steep check"
11
+ 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.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kerrick Long
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.9.1
18
+ version: 0.10.1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.9.1
25
+ version: 0.10.1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: ostruct
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -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,18 @@ 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
135
+ - doc/contributors/design/commands_and_outlets.md
136
+ - doc/contributors/kit-no-outlet.md
97
137
  - doc/contributors/priorities.md
98
138
  - doc/custom.css
99
139
  - doc/getting_started/quickstart.md
@@ -103,19 +143,19 @@ files:
103
143
  - doc/index.md
104
144
  - examples/app_fractal_dashboard/README.md
105
145
  - examples/app_fractal_dashboard/app.rb
106
- - examples/app_fractal_dashboard/bags/custom_shell_input.rb
107
- - examples/app_fractal_dashboard/bags/custom_shell_modal.rb
108
- - examples/app_fractal_dashboard/bags/custom_shell_output.rb
109
- - examples/app_fractal_dashboard/bags/disk_usage.rb
110
- - examples/app_fractal_dashboard/bags/network_panel.rb
111
- - examples/app_fractal_dashboard/bags/ping.rb
112
- - examples/app_fractal_dashboard/bags/stats_panel.rb
113
- - examples/app_fractal_dashboard/bags/system_info.rb
114
- - examples/app_fractal_dashboard/bags/uptime.rb
115
146
  - examples/app_fractal_dashboard/dashboard/base.rb
116
147
  - examples/app_fractal_dashboard/dashboard/update_helpers.rb
117
148
  - examples/app_fractal_dashboard/dashboard/update_manual.rb
118
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
119
159
  - examples/verify_readme_usage/README.md
120
160
  - examples/verify_readme_usage/app.rb
121
161
  - examples/widget_command_system/README.md
@@ -123,21 +163,44 @@ files:
123
163
  - exe/.gitkeep
124
164
  - lib/ratatui_ruby/tea.rb
125
165
  - lib/ratatui_ruby/tea/command.rb
166
+ - lib/ratatui_ruby/tea/command/all.rb
167
+ - lib/ratatui_ruby/tea/command/batch.rb
168
+ - lib/ratatui_ruby/tea/command/custom.rb
169
+ - lib/ratatui_ruby/tea/command/http.rb
170
+ - lib/ratatui_ruby/tea/command/lifecycle.rb
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
126
179
  - lib/ratatui_ruby/tea/router.rb
127
180
  - lib/ratatui_ruby/tea/runtime.rb
128
181
  - lib/ratatui_ruby/tea/shortcuts.rb
182
+ - lib/ratatui_ruby/tea/test_helper.rb
129
183
  - lib/ratatui_ruby/tea/version.rb
130
184
  - mise.toml
185
+ - rbs_collection.lock.yaml
186
+ - rbs_collection.yaml
187
+ - sig/concurrent.rbs
131
188
  - sig/examples/verify_readme_usage/app.rbs
132
189
  - sig/examples/widget_command_system/app.rbs
190
+ - sig/open3.rbs
133
191
  - sig/ratatui_ruby/tea.rbs
134
192
  - sig/ratatui_ruby/tea/command.rbs
193
+ - sig/ratatui_ruby/tea/message.rbs
135
194
  - sig/ratatui_ruby/tea/router.rbs
136
195
  - sig/ratatui_ruby/tea/runtime.rbs
196
+ - sig/ratatui_ruby/tea/shortcuts.rbs
197
+ - sig/ratatui_ruby/tea/test_helper.rbs
198
+ - sig/ratatui_ruby/tea/version.rbs
137
199
  - tasks/example_viewer.html.erb
138
200
  - tasks/resources/build.yml.erb
139
201
  - tasks/resources/index.html.erb
140
202
  - tasks/resources/rubies.yml
203
+ - tasks/steep.rake
141
204
  - vendor/goodcop/base.yml
142
205
  homepage: https://sr.ht/~kerrick/ratatui_ruby/
143
206
  licenses: