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
@@ -5,26 +5,25 @@
5
5
  # SPDX-License-Identifier: MIT-0
6
6
  #++
7
7
 
8
- require_relative "../bags/stats_panel"
9
- require_relative "../bags/network_panel"
10
- require_relative "../bags/custom_shell_modal"
8
+ require_relative "../fragments/stats_panel"
9
+ require_relative "../fragments/network_panel"
10
+ require_relative "../fragments/custom_shell_modal"
11
11
 
12
- # Shared Model, INITIAL, and VIEW for the Dashboard.
13
- #
14
- # This module is extended by the three UPDATE variants to demonstrate
15
- # the progression from verbose manual routing to declarative DSL.
12
+ # Shared Model, Init, and View for the Dashboard.
13
+ # Each Dashboard variation (Manual, Router, Helpers) provides its own Update.
16
14
  module DashboardBase
17
15
  Command = RatatuiRuby::Tea::Command
18
16
 
19
17
  Model = Data.define(:stats, :network, :shell_modal)
20
18
 
21
- INITIAL = Model.new(
22
- stats: StatsPanel::INITIAL,
23
- network: NetworkPanel::INITIAL,
24
- shell_modal: CustomShellModal::INITIAL
25
- )
19
+ Init = -> do
20
+ stats, = RatatuiRuby::Tea.normalize_init(StatsPanel::Init.())
21
+ network, = RatatuiRuby::Tea.normalize_init(NetworkPanel::Init.())
22
+ shell_modal, = RatatuiRuby::Tea.normalize_init(CustomShellModal::Init.())
23
+ Model.new(stats:, network:, shell_modal:)
24
+ end
26
25
 
27
- VIEW = lambda do |model, tui|
26
+ View = -> (model, tui) do
28
27
  modal_active = CustomShellModal.active?(model.shell_modal)
29
28
  hotkey, label_style = if modal_active
30
29
  [tui.style(fg: :dark_gray), tui.style(fg: :dark_gray)]
@@ -57,14 +56,14 @@ module DashboardBase
57
56
  direction: :vertical,
58
57
  constraints: [tui.constraint_fill(1), tui.constraint_fill(1), tui.constraint_length(3)],
59
58
  children: [
60
- StatsPanel::VIEW.call(model.stats, tui, disabled: modal_active),
61
- NetworkPanel::VIEW.call(model.network, tui, disabled: modal_active),
59
+ StatsPanel::View.call(model.stats, tui, disabled: modal_active),
60
+ NetworkPanel::View.call(model.network, tui, disabled: modal_active),
62
61
  controls,
63
62
  ]
64
63
  )
65
64
 
66
65
  # Compose modal overlay if active
67
- modal_widget = CustomShellModal::VIEW.call(model.shell_modal, tui)
66
+ modal_widget = CustomShellModal::View.call(model.shell_modal, tui)
68
67
  if modal_widget
69
68
  tui.overlay(layers: [dashboard, modal_widget])
70
69
  else
@@ -17,10 +17,10 @@ module DashboardHelpers
17
17
 
18
18
  # Shared with other UPDATE variants
19
19
  Model = DashboardBase::Model
20
- INITIAL = DashboardBase::INITIAL
21
- VIEW = DashboardBase::VIEW
20
+ Init = DashboardBase::Init
21
+ View = DashboardBase::View
22
22
 
23
- UPDATE = lambda do |message, model|
23
+ Update = -> (message, model) do
24
24
  # Global Force Quit
25
25
  return [model, RatatuiRuby::Tea::Command.exit] if message.respond_to?(:ctrl_c?) && message.ctrl_c?
26
26
 
@@ -29,25 +29,25 @@ module DashboardHelpers
29
29
  # modal is active. Only user input (keys/mouse) should be blocked.
30
30
 
31
31
  # Route streaming command output to modal
32
- if (result = Tea.delegate(message, :shell_output, CustomShellModal::UPDATE, model.shell_modal))
32
+ if (result = Tea.delegate(message, :shell_output, CustomShellModal::Update, model.shell_modal))
33
33
  new_modal, command = result
34
34
  return [model.with(shell_modal: new_modal), command]
35
35
  end
36
36
 
37
- # Route to child bags
38
- if (result = Tea.delegate(message, :stats, StatsPanel::UPDATE, model.stats))
37
+ # Route to child fragments
38
+ if (result = Tea.delegate(message, :stats, StatsPanel::Update, model.stats))
39
39
  new_child, command = result
40
40
  return [model.with(stats: new_child), command && Tea.route(command, :stats)]
41
41
  end
42
42
 
43
- if (result = Tea.delegate(message, :network, NetworkPanel::UPDATE, model.network))
43
+ if (result = Tea.delegate(message, :network, NetworkPanel::Update, model.network))
44
44
  new_child, command = result
45
45
  return [model.with(network: new_child), command && Tea.route(command, :network)]
46
46
  end
47
47
 
48
48
  # Modal intercepts user input (not command results)
49
49
  if CustomShellModal.active?(model.shell_modal)
50
- new_modal, command = CustomShellModal::UPDATE.call(message, model.shell_modal)
50
+ new_modal, command = CustomShellModal::Update.call(message, model.shell_modal)
51
51
  return [model.with(shell_modal: new_modal), command]
52
52
  end
53
53
 
@@ -16,10 +16,10 @@ module DashboardManual
16
16
 
17
17
  # Shared with other UPDATE variants
18
18
  Model = DashboardBase::Model
19
- INITIAL = DashboardBase::INITIAL
20
- VIEW = DashboardBase::VIEW
19
+ Init = DashboardBase::Init
20
+ View = DashboardBase::View
21
21
 
22
- UPDATE = lambda do |message, model|
22
+ Update = -> (message, model) do
23
23
  # Global Force Quit
24
24
  return [model, RatatuiRuby::Tea::Command.exit] if message.respond_to?(:ctrl_c?) && message.ctrl_c?
25
25
 
@@ -29,18 +29,18 @@ module DashboardManual
29
29
  case message
30
30
  # Route command results to panels
31
31
  in [:stats, *rest]
32
- new_panel, command = StatsPanel::UPDATE.call(rest, model.stats)
32
+ new_panel, command = StatsPanel::Update.call(rest, model.stats)
33
33
  mapped_command = command ? Command.map(command) { |child_result| [:stats, *child_result] } : nil
34
34
  return [model.with(stats: new_panel), mapped_command]
35
35
 
36
36
  in [:network, *rest]
37
- new_panel, command = NetworkPanel::UPDATE.call(rest, model.network)
37
+ new_panel, command = NetworkPanel::Update.call(rest, model.network)
38
38
  mapped_command = command ? Command.map(command) { |child_result| [:network, *child_result] } : nil
39
39
  return [model.with(network: new_panel), mapped_command]
40
40
 
41
41
  in [:shell_output, *rest]
42
42
  # Route streaming command output to modal
43
- new_modal, command = CustomShellModal::UPDATE.call(message, model.shell_modal)
43
+ new_modal, command = CustomShellModal::Update.call(message, model.shell_modal)
44
44
  return [model.with(shell_modal: new_modal), command]
45
45
  else
46
46
  nil # Fall through to input handling
@@ -48,7 +48,7 @@ module DashboardManual
48
48
 
49
49
  # Modal intercepts user input (not command results)
50
50
  if CustomShellModal.active?(model.shell_modal)
51
- new_modal, command = CustomShellModal::UPDATE.call(message, model.shell_modal)
51
+ new_modal, command = CustomShellModal::Update.call(message, model.shell_modal)
52
52
  return [model.with(shell_modal: new_modal), command]
53
53
  end
54
54
 
@@ -61,22 +61,22 @@ module DashboardManual
61
61
  [model.with(shell_modal: CustomShellModal.open), nil]
62
62
 
63
63
  in _ if message.s?
64
- command = Command.map(SystemInfo.fetch_command) { |r| [:stats, *r] }
64
+ command = Command.map(SystemInfo.fetch_command) { |batch| [:stats, batch.envelope, batch] }
65
65
  new_stats = model.stats.with(system_info: model.stats.system_info.with(loading: true))
66
66
  [model.with(stats: new_stats), command]
67
67
 
68
68
  in _ if message.d?
69
- command = Command.map(DiskUsage.fetch_command) { |r| [:stats, *r] }
69
+ command = Command.map(DiskUsage.fetch_command) { |batch| [:stats, batch.envelope, batch] }
70
70
  new_stats = model.stats.with(disk_usage: model.stats.disk_usage.with(loading: true))
71
71
  [model.with(stats: new_stats), command]
72
72
 
73
73
  in _ if message.p?
74
- command = Command.map(Ping.fetch_command) { |r| [:network, *r] }
74
+ command = Command.map(Ping.fetch_command) { |batch| [:network, batch.envelope, batch] }
75
75
  new_network = model.network.with(ping: model.network.ping.with(loading: true))
76
76
  [model.with(network: new_network), command]
77
77
 
78
78
  in _ if message.u?
79
- command = Command.map(Uptime.fetch_command) { |r| [:network, *r] }
79
+ command = Command.map(Uptime.fetch_command) { |batch| [:network, batch.envelope, batch] }
80
80
  new_network = model.network.with(uptime: model.network.uptime.with(loading: true))
81
81
  [model.with(network: new_network), command]
82
82
 
@@ -16,10 +16,10 @@ module DashboardRouter
16
16
 
17
17
  Command = RatatuiRuby::Tea::Command
18
18
 
19
- # Shared with other UPDATE variants
19
+ # Shared with other Update variants
20
20
  Model = DashboardBase::Model
21
- INITIAL = DashboardBase::INITIAL
22
- VIEW = DashboardBase::VIEW
21
+ Init = DashboardBase::Init
22
+ View = DashboardBase::View
23
23
 
24
24
  route :stats, to: StatsPanel
25
25
  route :network, to: NetworkPanel
@@ -39,5 +39,5 @@ module DashboardRouter
39
39
  end
40
40
  end
41
41
 
42
- UPDATE = from_router
42
+ Update = from_router
43
43
  end
@@ -5,14 +5,18 @@
5
5
  # SPDX-License-Identifier: LGPL-3.0-or-later
6
6
  #++
7
7
 
8
- # Text input bag for custom shell command modal.
8
+ require "ratatui_ruby/tea"
9
+ # Text input fragment for custom shell command modal.
9
10
  #
10
11
  # Handles text entry. Sets cancelled: or submitted: in model for parent to detect.
11
12
  module CustomShellInput
12
13
  Model = Data.define(:text, :cancelled, :submitted)
13
- INITIAL = Ractor.make_shareable(Model.new(text: "", cancelled: false, submitted: false))
14
14
 
15
- VIEW = lambda do |model, tui|
15
+ Init = -> do
16
+ Ractor.make_shareable(Model.new(text: "", cancelled: false, submitted: false))
17
+ end
18
+
19
+ View = -> (model, tui) do
16
20
  content = if model.text.empty?
17
21
  tui.paragraph(text: tui.text_span(content: "Type a command...", style: { fg: :dark_gray }))
18
22
  else
@@ -50,7 +54,7 @@ module CustomShellInput
50
54
  )
51
55
  end
52
56
 
53
- UPDATE = lambda do |message, model|
57
+ Update = -> (message, model) do
54
58
  case message
55
59
  in _ if message.respond_to?(:esc?) && message.esc?
56
60
  [model.with(cancelled: true), nil]
@@ -0,0 +1,82 @@
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
+ # Modal overlay for custom shell command execution.
12
+ module CustomShellModal
13
+ Command = RatatuiRuby::Tea::Command
14
+
15
+ Model = Data.define(:mode, :input, :output)
16
+
17
+ Init = -> do
18
+ input, = RatatuiRuby::Tea.normalize_init(CustomShellInput::Init.())
19
+ output, = RatatuiRuby::Tea.normalize_init(CustomShellOutput::Init.())
20
+ Ractor.make_shareable(Model.new(mode: :none, input:, output:))
21
+ end
22
+
23
+ View = -> (model, tui) do
24
+ case model.mode
25
+ when :input
26
+ CustomShellInput::View.call(model.input, tui)
27
+ when :output
28
+ CustomShellOutput::View.call(model.output, tui)
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+ Update = -> (message, model) do
35
+ case [model.mode, message]
36
+ in [:input, _]
37
+ # Delegate first, then check if user wants to close
38
+ new_input, _cmd = CustomShellInput::Update.call(message, model.input)
39
+
40
+ if new_input.cancelled
41
+ [Init.(), nil]
42
+ elsif new_input.submitted
43
+ shell_cmd = new_input.text
44
+ new_output = CustomShellOutput::Init.().with(command: shell_cmd, running: true)
45
+ reset_input, = RatatuiRuby::Tea.normalize_init(CustomShellInput::Init.())
46
+ [
47
+ model.with(mode: :output, input: reset_input, output: new_output),
48
+ Command.system(shell_cmd, :shell_output, stream: true),
49
+ ]
50
+ else
51
+ [model.with(input: new_input), nil]
52
+ end
53
+
54
+ in [:output, _]
55
+ # Delegate first, then check if user wants to close
56
+ case message
57
+ in RatatuiRuby::Event::Key if message.ctrl_c?
58
+ [Init.(), nil]
59
+ else
60
+ new_output, _cmd = CustomShellOutput::Update.call(message, model.output)
61
+
62
+ if new_output.dismissed
63
+ [Init.(), nil]
64
+ else
65
+ [model.with(output: new_output), nil]
66
+ end
67
+ end
68
+
69
+ else
70
+ [model, nil]
71
+ end
72
+ end
73
+
74
+ def self.open
75
+ input, = RatatuiRuby::Tea.normalize_init(CustomShellInput::Init.())
76
+ Init.().with(mode: :input, input:)
77
+ end
78
+
79
+ def self.active?(model)
80
+ model.mode != :none
81
+ end
82
+ end
@@ -5,16 +5,20 @@
5
5
  # SPDX-License-Identifier: LGPL-3.0-or-later
6
6
  #++
7
7
 
8
- # Streaming output bag for custom shell command modal.
8
+ require "ratatui_ruby/tea"
9
+ # Streaming output fragment for custom shell command modal.
9
10
  #
10
11
  # Displays interleaved stdout/stderr. Border color reflects exit status.
11
12
  # Sets dismissed: in model for parent to detect.
12
13
  module CustomShellOutput
13
14
  Chunk = Data.define(:stream, :text)
14
15
  Model = Data.define(:command, :chunks, :running, :exit_status, :dismissed)
15
- INITIAL = Ractor.make_shareable(Model.new(command: "", chunks: [].freeze, running: false, exit_status: nil, dismissed: false))
16
16
 
17
- VIEW = lambda do |model, tui|
17
+ Init = -> do
18
+ Ractor.make_shareable(Model.new(command: "", chunks: [].freeze, running: false, exit_status: nil, dismissed: false))
19
+ end
20
+
21
+ View = -> (model, tui) do
18
22
  # Build styled spans from chunks
19
23
  spans = if model.chunks.empty? && model.running
20
24
  [tui.text_span(content: "Running...", style: tui.style(fg: :dark_gray))]
@@ -56,7 +60,7 @@ module CustomShellOutput
56
60
  )
57
61
  end
58
62
 
59
- UPDATE = lambda do |message, model|
63
+ Update = -> (message, model) do
60
64
  case message
61
65
  in [:stdout, chunk]
62
66
  new_chunks = Ractor.make_shareable([*model.chunks, Chunk.new(stream: :stdout, text: chunk)].freeze)
@@ -5,16 +5,20 @@
5
5
  # SPDX-License-Identifier: MIT-0
6
6
  #++
7
7
 
8
+ require "ratatui_ruby/tea"
8
9
  # Fetches and displays disk usage via +df -h+.
9
- # A bag for fetching and displaying disk usage.
10
+ # A fragment for fetching and displaying disk usage.
10
11
  module DiskUsage
11
12
  Command = RatatuiRuby::Tea::Command
12
13
 
13
14
  Model = Data.define(:output, :loading)
14
- INITIAL = Model.new(output: "Press 'd' for disk usage", loading: false)
15
15
 
16
- VIEW = lambda do |model, tui, disabled: false|
17
- text_style = if disabled && model.output == INITIAL.output
16
+ Init = -> do
17
+ Model.new(output: "Press 'd' for disk usage", loading: false)
18
+ end
19
+
20
+ View = -> (model, tui, disabled: false) do
21
+ text_style = if disabled && model.output == Init.().output
18
22
  tui.style(fg: :dark_gray)
19
23
  else
20
24
  nil
@@ -26,12 +30,11 @@ module DiskUsage
26
30
  )
27
31
  end
28
32
 
29
- UPDATE = lambda do |message, model|
33
+ Update = -> (message, model) do
30
34
  case message
31
- in [:disk_usage, { stdout:, status: 0 }]
32
- lines = Ractor.make_shareable(stdout.lines.first(4).join.strip)
33
- [model.with(output: lines, loading: false), nil]
34
- in [:disk_usage, { stderr:, _status: }]
35
+ in [{ type: :system, envelope: :disk_usage, status: 0, stdout: }]
36
+ [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
37
+ in [{ type: :system, envelope: :disk_usage, stderr: }]
35
38
  [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
36
39
  else
37
40
  [model, nil]
@@ -39,6 +42,6 @@ module DiskUsage
39
42
  end
40
43
 
41
44
  def self.fetch_command
42
- Command.system("df -h", :disk_usage)
45
+ Command.system("df -h /", :disk_usage)
43
46
  end
44
47
  end
@@ -5,6 +5,7 @@
5
5
  # SPDX-License-Identifier: MIT-0
6
6
  #++
7
7
 
8
+ require "ratatui_ruby/tea"
8
9
  require_relative "ping"
9
10
  require_relative "uptime"
10
11
 
@@ -12,31 +13,30 @@ require_relative "uptime"
12
13
  module NetworkPanel
13
14
  Model = Data.define(:ping, :uptime)
14
15
 
15
- INITIAL = Model.new(
16
- ping: Ping::INITIAL,
17
- uptime: Uptime::INITIAL
18
- )
16
+ Init = -> do
17
+ ping, = RatatuiRuby::Tea.normalize_init(Ping::Init.())
18
+ uptime, = RatatuiRuby::Tea.normalize_init(Uptime::Init.())
19
+ Model.new(ping:, uptime:)
20
+ end
19
21
 
20
- VIEW = lambda do |model, tui, disabled: false|
22
+ View = -> (model, tui, disabled: false) do
21
23
  tui.layout(
22
24
  direction: :horizontal,
23
25
  constraints: [tui.constraint_percentage(50), tui.constraint_percentage(50)],
24
26
  children: [
25
- Ping::VIEW.call(model.ping, tui, disabled:),
26
- Uptime::VIEW.call(model.uptime, tui, disabled:),
27
+ Ping::View.call(model.ping, tui, disabled:),
28
+ Uptime::View.call(model.uptime, tui, disabled:),
27
29
  ]
28
30
  )
29
31
  end
30
32
 
31
- UPDATE = lambda do |message, model|
33
+ Update = -> (message, model) do
32
34
  case message
33
35
  in [:ping, *rest]
34
- child_message = [:ping, *rest]
35
- new_child, command = Ping::UPDATE.call(child_message, model.ping)
36
+ new_child, command = Ping::Update.call(rest, model.ping)
36
37
  [model.with(ping: new_child), command]
37
38
  in [:uptime, *rest]
38
- child_message = [:uptime, *rest]
39
- new_child, command = Uptime::UPDATE.call(child_message, model.uptime)
39
+ new_child, command = Uptime::Update.call(rest, model.uptime)
40
40
  [model.with(uptime: new_child), command]
41
41
  else
42
42
  [model, nil]
@@ -5,16 +5,20 @@
5
5
  # SPDX-License-Identifier: MIT-0
6
6
  #++
7
7
 
8
+ require "ratatui_ruby/tea"
8
9
  # Pings localhost to check network connectivity.
9
- # A bag for pinging localhost.
10
+ # A fragment for pinging localhost.
10
11
  module Ping
11
12
  Command = RatatuiRuby::Tea::Command
12
13
 
13
14
  Model = Data.define(:output, :loading)
14
- INITIAL = Model.new(output: "Press 'p' for ping", loading: false)
15
15
 
16
- VIEW = lambda do |model, tui, disabled: false|
17
- text_style = if disabled && model.output == INITIAL.output
16
+ Init = -> do
17
+ Model.new(output: "Press 'p' for ping", loading: false)
18
+ end
19
+
20
+ View = -> (model, tui, disabled: false) do
21
+ text_style = if disabled && model.output == Init.().output
18
22
  tui.style(fg: :dark_gray)
19
23
  else
20
24
  nil
@@ -26,11 +30,11 @@ module Ping
26
30
  )
27
31
  end
28
32
 
29
- UPDATE = lambda do |message, model|
33
+ Update = -> (message, model) do
30
34
  case message
31
- in [:ping, { stdout:, status: 0 }]
35
+ in [{ type: :system, envelope: :ping, status: 0, stdout: }]
32
36
  [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
33
- in [:ping, { stderr:, _status: }]
37
+ in [{ type: :system, envelope: :ping, stderr: }]
34
38
  [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
35
39
  else
36
40
  [model, nil]
@@ -38,6 +42,6 @@ module Ping
38
42
  end
39
43
 
40
44
  def self.fetch_command
41
- Command.system("ping -c 1 localhost", :ping)
45
+ Command.system("ping -c 3 8.8.8.8", :ping)
42
46
  end
43
47
  end
@@ -5,6 +5,7 @@
5
5
  # SPDX-License-Identifier: MIT-0
6
6
  #++
7
7
 
8
+ require "ratatui_ruby/tea"
8
9
  require_relative "system_info"
9
10
  require_relative "disk_usage"
10
11
 
@@ -12,31 +13,30 @@ require_relative "disk_usage"
12
13
  module StatsPanel
13
14
  Model = Data.define(:system_info, :disk_usage)
14
15
 
15
- INITIAL = Model.new(
16
- system_info: SystemInfo::INITIAL,
17
- disk_usage: DiskUsage::INITIAL
18
- )
16
+ Init = -> do
17
+ system_info, = RatatuiRuby::Tea.normalize_init(SystemInfo::Init.())
18
+ disk_usage, = RatatuiRuby::Tea.normalize_init(DiskUsage::Init.())
19
+ Model.new(system_info:, disk_usage:)
20
+ end
19
21
 
20
- VIEW = lambda do |model, tui, disabled: false|
22
+ View = -> (model, tui, disabled: false) do
21
23
  tui.layout(
22
24
  direction: :horizontal,
23
25
  constraints: [tui.constraint_percentage(50), tui.constraint_percentage(50)],
24
26
  children: [
25
- SystemInfo::VIEW.call(model.system_info, tui, disabled:),
26
- DiskUsage::VIEW.call(model.disk_usage, tui, disabled:),
27
+ SystemInfo::View.call(model.system_info, tui, disabled:),
28
+ DiskUsage::View.call(model.disk_usage, tui, disabled:),
27
29
  ]
28
30
  )
29
31
  end
30
32
 
31
- UPDATE = lambda do |message, model|
33
+ Update = -> (message, model) do
32
34
  case message
33
35
  in [:system_info, *rest]
34
- child_message = [:system_info, *rest]
35
- new_child, command = SystemInfo::UPDATE.call(child_message, model.system_info)
36
+ new_child, command = SystemInfo::Update.call(rest, model.system_info)
36
37
  [model.with(system_info: new_child), command]
37
38
  in [:disk_usage, *rest]
38
- child_message = [:disk_usage, *rest]
39
- new_child, command = DiskUsage::UPDATE.call(child_message, model.disk_usage)
39
+ new_child, command = DiskUsage::Update.call(rest, model.disk_usage)
40
40
  [model.with(disk_usage: new_child), command]
41
41
  else
42
42
  [model, nil]
@@ -5,16 +5,20 @@
5
5
  # SPDX-License-Identifier: MIT-0
6
6
  #++
7
7
 
8
+ require "ratatui_ruby/tea"
8
9
  # Fetches and displays system information via +uname -a+.
9
- # A bag for fetching and displaying system information.
10
+ # A fragment for fetching and displaying system information.
10
11
  module SystemInfo
11
12
  Command = RatatuiRuby::Tea::Command
12
13
 
13
14
  Model = Data.define(:output, :loading)
14
- INITIAL = Model.new(output: "Press 's' for system info", loading: false)
15
15
 
16
- VIEW = lambda do |model, tui, disabled: false|
17
- text_style = if disabled && model.output == INITIAL.output
16
+ Init = -> do
17
+ Model.new(output: "Press 's' for system info", loading: false)
18
+ end
19
+
20
+ View = -> (model, tui, disabled: false) do
21
+ text_style = if disabled && model.output == Init.().output
18
22
  tui.style(fg: :dark_gray)
19
23
  else
20
24
  nil
@@ -26,11 +30,11 @@ module SystemInfo
26
30
  )
27
31
  end
28
32
 
29
- UPDATE = lambda do |message, model|
33
+ Update = -> (message, model) do
30
34
  case message
31
- in [:system_info, { stdout:, status: 0 }]
35
+ in [{ type: :system, envelope: :system_info, status: 0, stdout: }]
32
36
  [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
33
- in [:system_info, { stderr:, _status: }]
37
+ in [{ type: :system, envelope: :system_info, stderr: }]
34
38
  [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
35
39
  else
36
40
  [model, nil]
@@ -5,16 +5,20 @@
5
5
  # SPDX-License-Identifier: MIT-0
6
6
  #++
7
7
 
8
+ require "ratatui_ruby/tea"
8
9
  # Displays system uptime.
9
- # A bag for displaying system uptime.
10
+ # A fragment for displaying system uptime.
10
11
  module Uptime
11
12
  Command = RatatuiRuby::Tea::Command
12
13
 
13
14
  Model = Data.define(:output, :loading)
14
- INITIAL = Model.new(output: "Press 'u' for uptime", loading: false)
15
15
 
16
- VIEW = lambda do |model, tui, disabled: false|
17
- text_style = if disabled && model.output == INITIAL.output
16
+ Init = -> do
17
+ Model.new(output: "Press 'u' for uptime", loading: false)
18
+ end
19
+
20
+ View = -> (model, tui, disabled: false) do
21
+ text_style = if disabled && model.output == Init.().output
18
22
  tui.style(fg: :dark_gray)
19
23
  else
20
24
  nil
@@ -26,11 +30,11 @@ module Uptime
26
30
  )
27
31
  end
28
32
 
29
- UPDATE = lambda do |message, model|
33
+ Update = -> (message, model) do
30
34
  case message
31
- in [:uptime, { stdout:, status: 0 }]
35
+ in [{ type: :system, envelope: :uptime, status: 0, stdout: }]
32
36
  [model.with(output: Ractor.make_shareable(stdout.strip), loading: false), nil]
33
- in [:uptime, { stderr:, _status: }]
37
+ in [{ type: :system, envelope: :uptime, stderr: }]
34
38
  [model.with(output: Ractor.make_shareable("Error: #{stderr.strip}"), loading: false), nil]
35
39
  else
36
40
  [model, nil]
@@ -19,9 +19,12 @@ This example exists as a documentation regression test. It ensures that the very
19
19
  <!-- SYNC:START:./app.rb:mvu -->
20
20
  ```ruby
21
21
  Model = Data.define(:text)
22
- MODEL = Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
23
22
 
24
- VIEW = -> (model, tui) do
23
+ Init = -> do
24
+ Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
25
+ end
26
+
27
+ View = -> (model, tui) do
25
28
  tui.paragraph(
26
29
  text: model.text,
27
30
  alignment: :center,
@@ -33,7 +36,7 @@ VIEW = -> (model, tui) do
33
36
  )
34
37
  end
35
38
 
36
- UPDATE = -> (msg, model) do
39
+ Update = -> (msg, model) do
37
40
  if msg.q? || msg.ctrl_c?
38
41
  RatatuiRuby::Tea::Command.exit
39
42
  else
@@ -42,7 +45,7 @@ UPDATE = -> (msg, model) do
42
45
  end
43
46
 
44
47
  def run
45
- RatatuiRuby::Tea.run(model: MODEL, view: VIEW, update: UPDATE)
48
+ RatatuiRuby::Tea.run(VerifyReadmeUsage)
46
49
  end
47
50
  ```
48
51
  <!-- SYNC:END -->