rooibos 0.5.0 → 0.6.1

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +9 -5
  3. data/.builds/ruby-3.3.yml +9 -5
  4. data/.builds/ruby-3.4.yml +9 -5
  5. data/.builds/ruby-4.0.0.yml +9 -5
  6. data/AGENTS.md +1 -1
  7. data/CHANGELOG.md +57 -0
  8. data/README.md +2 -2
  9. data/README.rdoc +374 -0
  10. data/REUSE.toml +5 -0
  11. data/Rakefile +1 -1
  12. data/doc/best_practices/forms_and_validation.md +20 -0
  13. data/doc/best_practices/http_workflows.md +20 -0
  14. data/doc/best_practices/index.md +26 -0
  15. data/doc/best_practices/lists_and_tables.md +20 -0
  16. data/doc/best_practices/modal_dialogs.md +20 -0
  17. data/doc/best_practices/no_stateful_widgets.md +184 -0
  18. data/doc/best_practices/orchestration.md +20 -0
  19. data/doc/best_practices/streaming_data.md +20 -0
  20. data/doc/contributors/design/commands_and_outlets.md +1 -1
  21. data/doc/contributors/documentation_plan.md +616 -0
  22. data/doc/contributors/documentation_stub_audit.md +112 -0
  23. data/doc/contributors/documentation_style.md +275 -0
  24. data/doc/contributors/e2e_pty.md +168 -0
  25. data/doc/contributors/specs/earliest_tutorial_steps_per_story.md +70 -0
  26. data/doc/contributors/specs/file_browser.md +789 -0
  27. data/doc/contributors/specs/file_browser_stories.md +774 -0
  28. data/doc/contributors/specs/tutorials_to_stories.rb +167 -0
  29. data/doc/contributors/todo/scrollbar.md +118 -0
  30. data/doc/contributors/tutorial_old/01_project_setup.md +20 -0
  31. data/doc/contributors/tutorial_old/02_hello_world.md +24 -0
  32. data/doc/contributors/tutorial_old/03_adding_state.md +26 -0
  33. data/doc/contributors/tutorial_old/06_organizing_your_code.md +20 -0
  34. data/doc/contributors/tutorial_old/07_your_first_command.md +21 -0
  35. data/doc/contributors/tutorial_old/08_the_preview_pane.md +20 -0
  36. data/doc/contributors/tutorial_old/09_loading_states.md +20 -0
  37. data/doc/contributors/tutorial_old/10_testing_your_app.md +20 -0
  38. data/doc/contributors/tutorial_old/11_polish_and_refine.md +20 -0
  39. data/doc/contributors/tutorial_old/12_going_further.md +20 -0
  40. data/doc/contributors/tutorial_old/index.md +20 -0
  41. data/doc/essentials/commands.md +20 -0
  42. data/doc/essentials/index.md +31 -0
  43. data/doc/essentials/messages.md +21 -0
  44. data/doc/essentials/models.md +21 -0
  45. data/doc/essentials/shortcuts.md +19 -0
  46. data/doc/essentials/the_elm_architecture.md +24 -0
  47. data/doc/essentials/the_runtime.md +21 -0
  48. data/doc/essentials/update_functions.md +20 -0
  49. data/doc/essentials/views.md +22 -0
  50. data/doc/getting_started/for_go_developers.md +16 -0
  51. data/doc/getting_started/for_python_developers.md +16 -0
  52. data/doc/getting_started/for_rails_developers.md +17 -0
  53. data/doc/getting_started/for_ratatui_ruby_developers.md +17 -0
  54. data/doc/getting_started/for_react_developers.md +17 -0
  55. data/doc/getting_started/index.md +52 -0
  56. data/doc/getting_started/install.md +20 -0
  57. data/doc/getting_started/quickstart.md +9 -45
  58. data/doc/getting_started/ruby_primer.md +19 -0
  59. data/doc/getting_started/why_rooibos.md +20 -0
  60. data/doc/index.md +79 -11
  61. data/doc/scaling_up/async_patterns.md +20 -0
  62. data/doc/scaling_up/command_composition.md +20 -0
  63. data/doc/scaling_up/custom_commands.md +21 -0
  64. data/doc/scaling_up/fractal_architecture.md +20 -0
  65. data/doc/scaling_up/index.md +30 -0
  66. data/doc/scaling_up/message_routing.md +20 -0
  67. data/doc/scaling_up/ractor_safety.md +20 -0
  68. data/doc/scaling_up/testing.md +21 -0
  69. data/doc/troubleshooting/common_errors.md +20 -0
  70. data/doc/troubleshooting/debugging.md +21 -0
  71. data/doc/troubleshooting/index.md +23 -0
  72. data/doc/troubleshooting/performance.md +20 -0
  73. data/doc/tutorial/01_project_setup.md +44 -0
  74. data/doc/tutorial/02_hello_world.md +45 -0
  75. data/doc/tutorial/03_static_file_list.md +44 -0
  76. data/doc/tutorial/04_arrow_navigation.md +47 -0
  77. data/doc/tutorial/05_real_files.md +45 -0
  78. data/doc/tutorial/06_safe_refactoring.md +21 -0
  79. data/doc/tutorial/07_red_first_tdd.md +26 -0
  80. data/doc/tutorial/08_file_metadata.md +42 -0
  81. data/doc/tutorial/09_text_preview.md +44 -0
  82. data/doc/tutorial/10_directory_tree.md +42 -0
  83. data/doc/tutorial/11_pane_focus.md +40 -0
  84. data/doc/tutorial/12_sorting.md +41 -0
  85. data/doc/tutorial/13_filtering.md +43 -0
  86. data/doc/tutorial/14_toggle_hidden.md +41 -0
  87. data/doc/tutorial/15_text_input_widget.md +43 -0
  88. data/doc/tutorial/16_rename_files.md +42 -0
  89. data/doc/tutorial/17_confirmation_dialogs.md +43 -0
  90. data/doc/tutorial/18_progress_indicators.md +43 -0
  91. data/doc/tutorial/19_atomic_operations.md +42 -0
  92. data/doc/tutorial/20_external_editor.md +42 -0
  93. data/doc/tutorial/21_modal_overlays.md +41 -0
  94. data/doc/tutorial/22_error_handling.md +43 -0
  95. data/doc/tutorial/23_terminal_capabilities.md +53 -0
  96. data/doc/tutorial/24_mouse_events.md +43 -0
  97. data/doc/tutorial/25_resize_events.md +43 -0
  98. data/doc/tutorial/26_loading_states.md +42 -0
  99. data/doc/tutorial/27_performance.md +43 -0
  100. data/doc/tutorial/28_color_schemes.md +47 -0
  101. data/doc/tutorial/29_configuration.md +124 -0
  102. data/doc/tutorial/30_going_further.md +17 -0
  103. data/doc/tutorial/index.md +17 -0
  104. data/examples/app_file_browser/app.rb +40 -0
  105. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +7 -7
  106. data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +5 -5
  107. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +1 -1
  108. data/examples/app_fractal_dashboard/fragments/disk_usage.rb +2 -2
  109. data/examples/app_fractal_dashboard/fragments/network_panel.rb +4 -4
  110. data/examples/app_fractal_dashboard/fragments/ping.rb +2 -2
  111. data/examples/app_fractal_dashboard/fragments/stats_panel.rb +4 -4
  112. data/examples/app_fractal_dashboard/fragments/system_info.rb +2 -2
  113. data/examples/app_fractal_dashboard/fragments/uptime.rb +2 -2
  114. data/examples/verify_website_first_app/app.rb +85 -0
  115. data/examples/verify_website_hello_mvu/app.rb +31 -0
  116. data/examples/widget_command_system/app.rb +15 -13
  117. data/exe/rooibos +10 -0
  118. data/generate_tutorial_stubs.rb +126 -0
  119. data/lib/rooibos/cli/commands/new.rb +373 -0
  120. data/lib/rooibos/cli/commands/run.rb +98 -0
  121. data/lib/rooibos/cli.rb +78 -0
  122. data/lib/rooibos/command/all.rb +76 -23
  123. data/lib/rooibos/command/batch.rb +61 -34
  124. data/lib/rooibos/command/custom.rb +84 -1
  125. data/lib/rooibos/command/http.rb +121 -55
  126. data/lib/rooibos/command/lifecycle.rb +5 -5
  127. data/lib/rooibos/command/open.rb +93 -0
  128. data/lib/rooibos/command/outlet.rb +105 -3
  129. data/lib/rooibos/command/wait.rb +9 -6
  130. data/lib/rooibos/command.rb +114 -89
  131. data/lib/rooibos/message/batch.rb +39 -0
  132. data/lib/rooibos/message/canceled.rb +51 -0
  133. data/lib/rooibos/message/error.rb +48 -0
  134. data/lib/rooibos/message/open.rb +30 -0
  135. data/lib/rooibos/message.rb +84 -4
  136. data/lib/rooibos/router.rb +11 -14
  137. data/lib/rooibos/runtime.rb +40 -43
  138. data/lib/rooibos/shortcuts.rb +47 -0
  139. data/lib/rooibos/test_helper.rb +71 -6
  140. data/lib/rooibos/version.rb +1 -1
  141. data/lib/rooibos/welcome.rb +237 -0
  142. data/lib/rooibos.rb +4 -3
  143. data/mise.toml +1 -1
  144. data/rbs_collection.lock.yaml +2 -2
  145. data/sig/concurrent.rbs +4 -0
  146. data/sig/gem.rbs +20 -0
  147. data/sig/rooibos/cli.rbs +42 -0
  148. data/sig/rooibos/command.rbs +59 -7
  149. data/sig/rooibos/message.rbs +66 -2
  150. data/sig/rooibos/shortcuts.rbs +14 -0
  151. data/sig/rooibos/test_helper.rbs +6 -2
  152. data/sig/rooibos/welcome.rbs +75 -0
  153. data/tasks/install.rake +29 -0
  154. data/tasks/resources/build.yml.erb +2 -0
  155. metadata +274 -38
  156. data/doc/concepts/application_architecture.md +0 -197
  157. data/doc/concepts/application_testing.md +0 -49
  158. data/doc/concepts/async_work.md +0 -164
  159. data/doc/concepts/commands.md +0 -530
  160. data/doc/concepts/message_processing.md +0 -51
  161. data/doc/contributors/WIP/decomposition_strategies_analysis.md +0 -258
  162. data/doc/contributors/WIP/implementation_plan.md +0 -409
  163. data/doc/contributors/WIP/init_callable_proposal.md +0 -344
  164. data/doc/contributors/WIP/runtime_refactoring_status.md +0 -47
  165. data/doc/contributors/WIP/task.md +0 -36
  166. data/doc/contributors/WIP/v0.4.0_todo.md +0 -468
  167. data/doc/contributors/kit-no-outlet.md +0 -238
  168. data/doc/contributors/priorities.md +0 -38
  169. data/doc/images/.gitkeep +0 -0
  170. data/exe/.gitkeep +0 -0
  171. /data/doc/contributors/{WIP → design}/mvu_tea_implementations_research.md +0 -0
@@ -0,0 +1,75 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module Rooibos
7
+ # Built-in welcome screen used by scaffolded applications.
8
+ module Welcome
9
+ WEBSITE_URL: String
10
+
11
+ # Internal UI module for styles and widgets.
12
+ module UI
13
+ module Styles
14
+ TEXT: RatatuiRuby::Style::Style
15
+ FILENAME: RatatuiRuby::Style::Style
16
+ COMMAND: RatatuiRuby::Style::Style
17
+ URL: RatatuiRuby::Style::Style
18
+ COMMAND_BUTTON: RatatuiRuby::Style::Style
19
+ COMMAND_BUTTON_FOCUS: RatatuiRuby::Style::Style
20
+ COMMAND_BUTTON_HOVER: RatatuiRuby::Style::Style
21
+ COMMAND_BUTTON_BOTH: RatatuiRuby::Style::Style
22
+ URL_BUTTON: RatatuiRuby::Style::Style
23
+ URL_BUTTON_FOCUS: RatatuiRuby::Style::Style
24
+ URL_BUTTON_HOVER: RatatuiRuby::Style::Style
25
+ URL_BUTTON_BOTH: RatatuiRuby::Style::Style
26
+ end
27
+
28
+ module Widgets
29
+ WELCOME_TEXT: Hash[String, RatatuiRuby::Style::Style]
30
+ PARAGRAPH: RatatuiRuby::Widgets::Paragraph
31
+ def self.website_button: (?focused: bool, ?hovered: bool) -> RatatuiRuby::Text::Span
32
+ def self.exit_button: (?focused: bool, ?hovered: bool) -> RatatuiRuby::Text::Span
33
+ end
34
+
35
+ BUTTON_SLOTS: Hash[Symbol, Integer]
36
+ FOCUS_ORDER: Array[Symbol]
37
+ BUTTON_BAR_CONSTRAINTS: Array[RatatuiRuby::Layout::Constraint]
38
+ CONTENT_CONSTRAINTS: Array[RatatuiRuby::Layout::Constraint]
39
+
40
+ def self.button_bar: (focused: Symbol?, hovered: Symbol?) -> RatatuiRuby::Layout::Layout
41
+ def self.content_layout: (focused: Symbol?, hovered: Symbol?) -> RatatuiRuby::Layout::Layout
42
+ def self.frame: (focused: Symbol?, hovered: Symbol?) -> RatatuiRuby::Widgets::Block
43
+
44
+ # Value object for button hit-test areas.
45
+ class ButtonAreas
46
+ attr_reader website: RatatuiRuby::Layout::Rect?
47
+ attr_reader exit: RatatuiRuby::Layout::Rect?
48
+ def initialize: (website: RatatuiRuby::Layout::Rect?, exit: RatatuiRuby::Layout::Rect?) -> void
49
+ def contains?: (Symbol name, Integer x, Integer y) -> bool
50
+ def button_at: (Integer x, Integer y) -> Symbol?
51
+ def for_viewport: (Integer width, Integer height) -> ButtonAreas
52
+ end
53
+ end
54
+
55
+ # Model for Welcome screen state.
56
+ class Model
57
+ attr_reader button_areas: UI::ButtonAreas
58
+ attr_reader focused: Symbol?
59
+ attr_reader hovered: Symbol?
60
+ def initialize: (button_areas: UI::ButtonAreas, focused: Symbol?, hovered: Symbol?) -> void
61
+ def tree: () -> RatatuiRuby::Widgets::Block
62
+ def active_button: () -> Symbol?
63
+ def with: (?button_areas: UI::ButtonAreas, ?focused: Symbol?, ?hovered: Symbol?) -> Model
64
+ end
65
+
66
+ View: ^(Model, untyped) -> RatatuiRuby::Widgets::Block
67
+ Update: ^(untyped, Model) -> (Model | Command::Exit | [Model, Command::System])
68
+ Init: ^() -> Model
69
+
70
+ def self.handle_click: (untyped message, Model model) -> (Model | Command::Exit | [Model, Command::System])
71
+ def self.handle_hover: (untyped message, Model model) -> Model
72
+ def self.cycle_focus: (Model model, Symbol direction) -> Model
73
+ def self.activate_button: (Symbol? button, Model model) -> (Model | Command::Exit | [Model, Command::System])
74
+ end
75
+ end
@@ -0,0 +1,29 @@
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 "rubygems"
9
+
10
+ namespace :install do
11
+ desc "Force install rooibos gem globally (required for integration tests)"
12
+ task :force do
13
+ require "rooibos/version"
14
+
15
+ # Build the gem first
16
+ Rake::Task["build"].invoke
17
+
18
+ gem_file = "pkg/rooibos-#{Rooibos::VERSION}.gem"
19
+ unless File.exist?(gem_file)
20
+ abort "Gem not found at #{gem_file}. Run 'rake build' first."
21
+ end
22
+
23
+ puts "Installing rooibos #{Rooibos::VERSION} globally..."
24
+ system("gem", "install", gem_file, "--force", "--no-document") ||
25
+ abort("Failed to install gem")
26
+
27
+ puts "✓ rooibos #{Rooibos::VERSION} installed globally"
28
+ end
29
+ end
@@ -40,6 +40,8 @@ tasks:
40
40
  - test: |
41
41
  . ~/.buildenv
42
42
  cd <%= gem_name %>
43
+ echo "Installing rooibos gem globally for integration tests..."
44
+ mise x -- bundle exec rake install:force
43
45
  echo "Testing Ruby <%= ruby_version %>"
44
46
  mise x -- bundle exec rake test
45
47
  - lint: |
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rooibos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kerrick Long
@@ -15,28 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.10.1
18
+ version: '1.2'
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.10.1
26
- - !ruby/object:Gem::Dependency
27
- name: ostruct
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '0.6'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '0.6'
25
+ version: '1.2'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: concurrent-ruby
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -93,11 +79,168 @@ dependencies:
93
79
  - - "~>"
94
80
  - !ruby/object:Gem::Version
95
81
  version: '3.5'
96
- description: Rooibos - part of the RatatuiRuby TUI framework ecosystem
82
+ - !ruby/object:Gem::Dependency
83
+ name: minitest-mock
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '5.27'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '5.27'
96
+ description: "== Confidently Build Terminal Apps\n\nRooibos[https://rooibos.run] helps
97
+ you build interactive terminal applications.\nKeep your code understandable and
98
+ testable as it scales. Rooibos handles\nkeyboard, mouse, and async work so you can
99
+ focus on behavior and user experience.\n\n gem install rooibos\n\n<i>Currently
100
+ in beta. APIs may change before 1.0.</i>\n\n=== Get Started in Seconds\n\n rooibos
101
+ new my_app\n cd my_app\n rooibos run\n\nThat's it. You have a working app with
102
+ keyboard navigation, mouse support,\nand clickable buttons. Open <tt>lib/my_app.rb</tt>
103
+ to make it your own.\n\n\n---\n\n=== The Pattern\n\n\\Rooibos uses Model-View-Update,
104
+ the architecture behind\nElm[https://guide.elm-lang.org/architecture/],\nRedux[https://redux.js.org/],
105
+ and {Bubble\nTea}[https://github.com/charmbracelet/bubbletea].\nState lives in one
106
+ place. Updates flow in one direction. The runtime handles\nrendering and runs background
107
+ work for you.\n\n---\n\n=== Hello, MVU\n\nThe simplest \\Rooibos app. Press any
108
+ key to increment the counter. Press\n<tt>Ctrl</tt>+<tt>C</tt> to quit.\n\n require
109
+ \"rooibos\"\n\n module Counter\n # Init: How do you create the initial model?\n
110
+ \ Init = -> { 0 }\n \n # View: What does the user see?\n View = -> (model,
111
+ tui) { tui.paragraph(text: <<~END) }\n Current count: #{model}.\n Press
112
+ any key to increment.\n Press Ctrl+C to quit.\n END\n \n # Update: What
113
+ happens when things change?\n Update = -> (message, model) {\n if message.ctrl_c?\n
114
+ \ Rooibos::Command.exit\n elsif message.key?\n model + 1\n end\n
115
+ \ }\n end\n\n Rooibos.run(Counter)\n\nThat's the whole pattern: Model holds
116
+ state, Init creates it, View renders it,\nand Update changes it. The runtime handles
117
+ everything else.\n\n\n---\n\n=== Your First Real Application\n\nA file browser in
118
+ sixty lines. It opens files, navigates directories, handles\nerrors, styles directories
119
+ and hidden files differently, and supports vim-style\nkeyboard shortcuts. If you
120
+ can do this much with this little code, imagine how\neasy _your_ app will be to
121
+ build.\n\n require \"rooibos\"\n \n module FileBrowser\n # Model: What state
122
+ does your app need?\n Model = Data.define(:path, :entries, :selected, :error)\n
123
+ \ \n Init = -> {\n path = Dir.pwd\n entries = Entries[path]\n Ractor.make_shareable(
124
+ # Ensures thread safety\n Model.new(path:, entries:, selected: entries.first,
125
+ error: nil))\n }\n \n View = -> (model, tui) {\n tui.block(\n titles:
126
+ [model.error || model.path,\n { content: KEYS, position: :bottom,
127
+ alignment: :right}],\n borders: [:all],\n border_style: if model.error
128
+ then tui.style(fg: :red) else nil end,\n children: [tui.list(items: model.entries.map(&ListItem[model,
129
+ tui]),\n selected_index: model.entries.index(model.selected),\n
130
+ \ highlight_symbol: \"\",\n highlight_style:
131
+ tui.style(modifiers: [:reversed]))]\n )\n }\n \n Update = -> (message,
132
+ model) {\n return model.with(error: ERROR) if message.error?\n model =
133
+ model.with(error: nil) if model.error && message.key?\n \n if message.ctrl_c?
134
+ || message.q? then Rooibos::Command.exit\n elsif message.home? || message.g?
135
+ then model.with(selected: model.entries.first)\n elsif message.end? || message.G?
136
+ then model.with(selected: model.entries.last)\n elsif message.up_arrow? ||
137
+ message.k? then Select[:-, model]\n elsif message.down_arrow? || message.j?
138
+ then Select[:+, model]\n elsif message.enter? then Open[model]\n elsif
139
+ message.escape? then Navigate[File.dirname(model.path), model]\n end\n }\n
140
+ \ \n private # Lines below this are implementation details\n \n KEYS = \"↑/↓/Home/End:
141
+ Select | Enter: Open | Esc: Navigate Up | q: Quit\"\n ERROR = \"Sorry, opening
142
+ the selected file failed.\"\n \n ListItem = -> (model, tui) { -> (name) {\n
143
+ \ modifiers = name.start_with?(\".\") ? [:dim] : []\n fg = :blue
144
+ if name.end_with?(\"/\")\n tui.list_item(content: name, style: tui.style(fg:,
145
+ modifiers:))\n } }\n \n Select = -> (operator, model) {\n new_index
146
+ = model.entries.index(model.selected).public_send(operator, 1)\n model.with(selected:
147
+ model.entries[new_index.clamp(0, model.entries.length - 1)])\n }\n \n Open
148
+ = -> (model) {\n full = File.join(model.path, model.selected.delete_suffix(\"/\"))\n
149
+ \ model.selected.end_with?(\"/\") ? Navigate[full, model] : Rooibos::Command.open(full)\n
150
+ \ }\n \n Navigate = -> (path, model) {\n entries = Entries[path]\n model.with(path:,
151
+ entries:, selected: entries.first, error: nil)\n }\n \n Entries = -> (path)
152
+ {\n Dir.children(path).map { |name|\n File.directory?(File.join(path,
153
+ name)) ? \"#{name}/\" : name\n }.sort_by { |name| [name.end_with?(\"/\") ?
154
+ 0 : 1, name.downcase] }\n }\n end\n\n Rooibos.run(FileBrowser)\n\n\n---\n\n===
155
+ Batteries Included\n\n==== Commands\n\nApplications fetch data, run shell commands,
156
+ and set timers. \\Rooibos Commands\nrun off the main thread and send results back
157
+ as messages.\n\n<b>HTTP requests:</b>\n\n Update = -> (message, model) {\n case
158
+ message\n in :fetch_users\n [model.with(loading: true), Rooibos::Command.http(:get,
159
+ \"/api/users\", :got_users)]\n in { type: :http, envelope: :got_users, status:
160
+ 200, body: }\n model.with(loading: false, users: JSON.parse(body))\n in
161
+ { type: :http, envelope: :got_users, status: }\n model.with(error: \"HTTP #{status}\")\n
162
+ \ end\n }\n\n<b>Shell commands:</b>\n\n Update = -> (message, model) {\n case
163
+ message\n in :list_files\n Rooibos::Command.system(\"ls -la\", :listed_files)\n
164
+ \ in { type: :system, envelope: :listed_files, stdout:, status: 0 }\n model.with(files:
165
+ stdout.lines.map(&:chomp))\n in { type: :system, envelope: :listed_files, stderr:,
166
+ status: }\n model.with(error: stderr)\n end\n }\n\n<b>Timers:</b>\n\n Update
167
+ = -> (message, model) {\n case message\n in { type: :timer, envelope: :tick,
168
+ elapsed: }\n [model.with(frame: model.frame + 1), Rooibos::Command.wait(1.0
169
+ / 24, :tick)]\n end\n }\n\n<b>And more!</b> \\Rooibos includes <tt>all</tt>,
170
+ <tt>batch</tt>, <tt>cancel</tt>,\n<tt>custom</tt>, <tt>exit</tt>, <tt>http</tt>,
171
+ <tt>map</tt>, <tt>open</tt>,\n<tt>system</tt>, <tt>tick</tt>, and <tt>wait</tt>
172
+ commands. You can also define\nyour own custom commands for complex orchestration.
173
+ \n\nEvery command produces a message, and Update handles it the same way.\n\n====
174
+ Testing\n\n\\Rooibos makes TUIs so easy to test, you'll save more time by writing
175
+ tests than\nby not testing.\n\n<b>Unit test Update, View, and Init.</b> No terminal
176
+ needed. Test helpers included.\n\n def test_moves_selection_down_with_j\n model
177
+ = Ractor.make_shareable(FileBrowser::Model.new(\n path: \"/\", entries: %w[bin
178
+ exe lib], selected: \"bin\", error: nil))\n message = RatatuiRuby::Event::Key.new(code:
179
+ \"j\")\n\n result = FileBrowser::Update.call(message, model)\n\n assert_equal
180
+ \"exe\", result.selected\n end\n\n<b>Style assertions.</b> Draw to a headless terminal,
181
+ verify colors and modifiers.\n\n def test_directories_are_blue\n with_test_terminal(60,
182
+ 10) do\n model = Ractor.make_shareable(FileBrowser::Model.new(\n path:
183
+ \"/\", entries: %w[file.txt subdir/], selected: \"file.txt\", error: nil))\n widget
184
+ = FileBrowser::View.call(model, RatatuiRuby::TUI.new)\n\n RatatuiRuby.draw
185
+ { |frame| frame.render_widget(widget, frame.area) }\n\n assert_blue(1, 2) #
186
+ \"subdir/\" at column 1, row 2\n end\n end\n\n<b>System tests.</b> Inject events,
187
+ run the full app, snapshot the result.\n\n def test_selection_moves_down\n with_test_terminal(120,
188
+ 30) do\n Dir.mktmpdir do |dir|\n FileUtils.touch(File.join(dir, \"a\"))\n
189
+ \ FileUtils.touch(File.join(dir, \"b\"))\n FileUtils.touch(File.join(dir,
190
+ \"c\"))\n\n inject_key(:down)\n inject_key(:ctrl_c)\n\n # Tests
191
+ use explicit params to inject deterministic initial state.\n Rooibos.run(\n
192
+ \ model: Ractor.make_shareable(FileBrowser::Model.new(\n path:
193
+ dir, entries: %w[a b c], selected: \"a\", error: nil)),\n view: FileBrowser::View,\n
194
+ \ update: FileBrowser::Update\n )\n\n assert_snapshots(\"selection_moved_down\")
195
+ do |lines|\n title = \"┌/tmp/test#{'─' * 107}┐\"\n lines.map do
196
+ |l|\n l.gsub(/┌#{Regexp.escape(dir)}[^┐]*┐/, title)\n end\n
197
+ \ end\n end\n end\n end\n\nSnapshots record both plain text and ANSI
198
+ colors. Normalization blocks mask\ndynamic content (timestamps, temp paths) for
199
+ cross-platform reproducibility.\nRun <tt>UPDATE_SNAPSHOTS=1 rake test</tt> to regenerate
200
+ baselines.\n\n==== Scale Up\n\nLarge applications decompose into fragments. Each
201
+ fragment has its own Model,\nView, Update, and Init. Parents compose children. The
202
+ pattern scales.\n\nThe Router DSL eliminates boilerplate:\n\n module Dashboard\n
203
+ \ include Rooibos::Router\n\n route :stats, to: StatsPanel\n route :network,
204
+ to: NetworkPanel\n\n keymap do\n key :ctrl_c, -> { Rooibos::Command.exit
205
+ }\n only when: -> (model) { !model.modal_open } do\n key :q, -> { Rooibos::Command.exit
206
+ }\n key :s, -> { StatsPanel.fetch_command }\n key :p, -> { NetworkPanel.ping_command
207
+ }\n end\n end\n\n Update = from_router\n\n # ... Model, Init, View
208
+ below\n end\n\nDeclare routes and keymaps. The router generates Update for you.
209
+ Use guards to\nignore keys when needed.\n\n==== CLI\n\nThe <tt>rooibos</tt> command
210
+ scaffolds projects and runs applications.\n\n rooibos new my_app # Generate
211
+ project structure\n rooibos run # Run the app in current directory\n\nGenerated
212
+ apps include tests, type signatures, and a working welcome\nscreen with keyboard
213
+ and mouse support.\n\n\n---\n\n=== The Ecosystem\n\n\\Rooibos builds on RatatuiRuby[https://www.ratatui-ruby.dev],
214
+ a Rubygem built on\nRatatui[https://ratatui.rs]. You get native performance with
215
+ the joy of Ruby.\n\\Rooibos is one way to manage state and composition. Kit is another.\n\n====
216
+ Rooibos[https://git.sr.ht/~kerrick/rooibos]\n\nModel-View-Update architecture. Inspired
217
+ by Elm, Bubble Tea, and React +\nRedux. Your UI is a pure function of state.\n\n-
218
+ Functional programming with MVU\n- Commands work off the main thread\n- Messages,
219
+ not callbacks, drive updates\n\n==== {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit]
220
+ (Coming Soon)\n\nComponent-based architecture. Encapsulate state, input handling,
221
+ and\nrendering in reusable pieces.\n\n- OOP with stateful components\n- Separate
222
+ UI state from domain logic\n- Built-in focus management & click handling\n\nBoth
223
+ use the same widget library and rendering engine. Pick the paradigm\nthat fits your
224
+ brain.\n\n\n---\n\n=== Links\n\n[Get Started]\n {Getting Started}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/getting_started/index.md],\n
225
+ \ {Tutorial}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/tutorial/index.md],\n
226
+ \ {Examples}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/examples]\n\n[Coming
227
+ From...]\n {React/Redux}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/getting_started/for_react_developers.md],\n
228
+ \ {BubbleTea}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/getting_started/for_go_developers.md],\n
229
+ \ {Textual}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/getting_started/for_python_developers.md]\n\n[Learn
230
+ More]\n {Essentials}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/essentials/index.md],\n
231
+ \ {Scaling Up}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/scaling_up/index.md],\n
232
+ \ {Best Practices}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/best_practices/index.md],\n
233
+ \ {Troubleshooting}[https://git.sr.ht/~kerrick/rooibos/tree/trunk/item/doc/troubleshooting/index.md]\n\n[Community]\n
234
+ \ {Discuss}[https://lists.sr.ht/~kerrick/ratatui_ruby-discuss],\n {Announcements}[https://lists.sr.ht/~kerrick/ratatui_ruby-announce],\n
235
+ \ {Bug Tracker}[https://todo.sr.ht/~kerrick/ratatui_ruby],\n {Contribution Guide}[https://man.sr.ht/~kerrick/ratatui_ruby/contributing.md],\n
236
+ \ {Code of Conduct}[https://man.sr.ht/~kerrick/ratatui_ruby/code_of_conduct.md]\n\n\n---\n\n[Website]
237
+ https://rooibos.run\n[Source] https://git.sr.ht/~kerrick/rooibos\n[RubyGems] https://rubygems.org/gems/rooibos\n\n©
238
+ 2026 Kerrick Long · Library: LGPL-3.0-or-later · Website: CC-BY-NC-ND-4.0 · Snippets:
239
+ MIT-0\n"
97
240
  email:
98
241
  - me@kerricklong.com
99
242
  executables:
100
- - ".gitkeep"
243
+ - rooibos
101
244
  extensions: []
102
245
  extra_rdoc_files: []
103
246
  files:
@@ -117,30 +260,107 @@ files:
117
260
  - LICENSES/MIT-0.txt
118
261
  - LICENSES/MIT.txt
119
262
  - README.md
263
+ - README.rdoc
120
264
  - REUSE.toml
121
265
  - Rakefile
122
266
  - Steepfile
123
- - doc/concepts/application_architecture.md
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
267
+ - doc/best_practices/forms_and_validation.md
268
+ - doc/best_practices/http_workflows.md
269
+ - doc/best_practices/index.md
270
+ - doc/best_practices/lists_and_tables.md
271
+ - doc/best_practices/modal_dialogs.md
272
+ - doc/best_practices/no_stateful_widgets.md
273
+ - doc/best_practices/orchestration.md
274
+ - doc/best_practices/streaming_data.md
135
275
  - doc/contributors/design/commands_and_outlets.md
136
- - doc/contributors/kit-no-outlet.md
137
- - doc/contributors/priorities.md
276
+ - doc/contributors/design/mvu_tea_implementations_research.md
277
+ - doc/contributors/documentation_plan.md
278
+ - doc/contributors/documentation_stub_audit.md
279
+ - doc/contributors/documentation_style.md
280
+ - doc/contributors/e2e_pty.md
281
+ - doc/contributors/specs/earliest_tutorial_steps_per_story.md
282
+ - doc/contributors/specs/file_browser.md
283
+ - doc/contributors/specs/file_browser_stories.md
284
+ - doc/contributors/specs/tutorials_to_stories.rb
285
+ - doc/contributors/todo/scrollbar.md
286
+ - doc/contributors/tutorial_old/01_project_setup.md
287
+ - doc/contributors/tutorial_old/02_hello_world.md
288
+ - doc/contributors/tutorial_old/03_adding_state.md
289
+ - doc/contributors/tutorial_old/06_organizing_your_code.md
290
+ - doc/contributors/tutorial_old/07_your_first_command.md
291
+ - doc/contributors/tutorial_old/08_the_preview_pane.md
292
+ - doc/contributors/tutorial_old/09_loading_states.md
293
+ - doc/contributors/tutorial_old/10_testing_your_app.md
294
+ - doc/contributors/tutorial_old/11_polish_and_refine.md
295
+ - doc/contributors/tutorial_old/12_going_further.md
296
+ - doc/contributors/tutorial_old/index.md
138
297
  - doc/custom.css
298
+ - doc/essentials/commands.md
299
+ - doc/essentials/index.md
300
+ - doc/essentials/messages.md
301
+ - doc/essentials/models.md
302
+ - doc/essentials/shortcuts.md
303
+ - doc/essentials/the_elm_architecture.md
304
+ - doc/essentials/the_runtime.md
305
+ - doc/essentials/update_functions.md
306
+ - doc/essentials/views.md
307
+ - doc/getting_started/for_go_developers.md
308
+ - doc/getting_started/for_python_developers.md
309
+ - doc/getting_started/for_rails_developers.md
310
+ - doc/getting_started/for_ratatui_ruby_developers.md
311
+ - doc/getting_started/for_react_developers.md
312
+ - doc/getting_started/index.md
313
+ - doc/getting_started/install.md
139
314
  - doc/getting_started/quickstart.md
140
- - doc/images/.gitkeep
315
+ - doc/getting_started/ruby_primer.md
316
+ - doc/getting_started/why_rooibos.md
141
317
  - doc/images/verify_readme_usage.png
142
318
  - doc/images/widget_cmd_exec.png
143
319
  - doc/index.md
320
+ - doc/scaling_up/async_patterns.md
321
+ - doc/scaling_up/command_composition.md
322
+ - doc/scaling_up/custom_commands.md
323
+ - doc/scaling_up/fractal_architecture.md
324
+ - doc/scaling_up/index.md
325
+ - doc/scaling_up/message_routing.md
326
+ - doc/scaling_up/ractor_safety.md
327
+ - doc/scaling_up/testing.md
328
+ - doc/troubleshooting/common_errors.md
329
+ - doc/troubleshooting/debugging.md
330
+ - doc/troubleshooting/index.md
331
+ - doc/troubleshooting/performance.md
332
+ - doc/tutorial/01_project_setup.md
333
+ - doc/tutorial/02_hello_world.md
334
+ - doc/tutorial/03_static_file_list.md
335
+ - doc/tutorial/04_arrow_navigation.md
336
+ - doc/tutorial/05_real_files.md
337
+ - doc/tutorial/06_safe_refactoring.md
338
+ - doc/tutorial/07_red_first_tdd.md
339
+ - doc/tutorial/08_file_metadata.md
340
+ - doc/tutorial/09_text_preview.md
341
+ - doc/tutorial/10_directory_tree.md
342
+ - doc/tutorial/11_pane_focus.md
343
+ - doc/tutorial/12_sorting.md
344
+ - doc/tutorial/13_filtering.md
345
+ - doc/tutorial/14_toggle_hidden.md
346
+ - doc/tutorial/15_text_input_widget.md
347
+ - doc/tutorial/16_rename_files.md
348
+ - doc/tutorial/17_confirmation_dialogs.md
349
+ - doc/tutorial/18_progress_indicators.md
350
+ - doc/tutorial/19_atomic_operations.md
351
+ - doc/tutorial/20_external_editor.md
352
+ - doc/tutorial/21_modal_overlays.md
353
+ - doc/tutorial/22_error_handling.md
354
+ - doc/tutorial/23_terminal_capabilities.md
355
+ - doc/tutorial/24_mouse_events.md
356
+ - doc/tutorial/25_resize_events.md
357
+ - doc/tutorial/26_loading_states.md
358
+ - doc/tutorial/27_performance.md
359
+ - doc/tutorial/28_color_schemes.md
360
+ - doc/tutorial/29_configuration.md
361
+ - doc/tutorial/30_going_further.md
362
+ - doc/tutorial/index.md
363
+ - examples/app_file_browser/app.rb
144
364
  - examples/app_fractal_dashboard/README.md
145
365
  - examples/app_fractal_dashboard/app.rb
146
366
  - examples/app_fractal_dashboard/dashboard/base.rb
@@ -158,22 +378,33 @@ files:
158
378
  - examples/app_fractal_dashboard/fragments/uptime.rb
159
379
  - examples/verify_readme_usage/README.md
160
380
  - examples/verify_readme_usage/app.rb
381
+ - examples/verify_website_first_app/app.rb
382
+ - examples/verify_website_hello_mvu/app.rb
161
383
  - examples/widget_command_system/README.md
162
384
  - examples/widget_command_system/app.rb
163
- - exe/.gitkeep
385
+ - exe/rooibos
386
+ - generate_tutorial_stubs.rb
164
387
  - lib/rooibos.rb
388
+ - lib/rooibos/cli.rb
389
+ - lib/rooibos/cli/commands/new.rb
390
+ - lib/rooibos/cli/commands/run.rb
165
391
  - lib/rooibos/command.rb
166
392
  - lib/rooibos/command/all.rb
167
393
  - lib/rooibos/command/batch.rb
168
394
  - lib/rooibos/command/custom.rb
169
395
  - lib/rooibos/command/http.rb
170
396
  - lib/rooibos/command/lifecycle.rb
397
+ - lib/rooibos/command/open.rb
171
398
  - lib/rooibos/command/outlet.rb
172
399
  - lib/rooibos/command/wait.rb
173
400
  - lib/rooibos/error.rb
174
401
  - lib/rooibos/message.rb
175
402
  - lib/rooibos/message/all.rb
403
+ - lib/rooibos/message/batch.rb
404
+ - lib/rooibos/message/canceled.rb
405
+ - lib/rooibos/message/error.rb
176
406
  - lib/rooibos/message/http_response.rb
407
+ - lib/rooibos/message/open.rb
177
408
  - lib/rooibos/message/system/batch.rb
178
409
  - lib/rooibos/message/system/stream.rb
179
410
  - lib/rooibos/message/timer.rb
@@ -182,14 +413,17 @@ files:
182
413
  - lib/rooibos/shortcuts.rb
183
414
  - lib/rooibos/test_helper.rb
184
415
  - lib/rooibos/version.rb
416
+ - lib/rooibos/welcome.rb
185
417
  - mise.toml
186
418
  - rbs_collection.lock.yaml
187
419
  - rbs_collection.yaml
188
420
  - sig/concurrent.rbs
189
421
  - sig/examples/verify_readme_usage/app.rbs
190
422
  - sig/examples/widget_command_system/app.rbs
423
+ - sig/gem.rbs
191
424
  - sig/open3.rbs
192
425
  - sig/rooibos.rbs
426
+ - sig/rooibos/cli.rbs
193
427
  - sig/rooibos/command.rbs
194
428
  - sig/rooibos/error.rbs
195
429
  - sig/rooibos/message.rbs
@@ -198,18 +432,20 @@ files:
198
432
  - sig/rooibos/shortcuts.rbs
199
433
  - sig/rooibos/test_helper.rbs
200
434
  - sig/rooibos/version.rbs
435
+ - sig/rooibos/welcome.rbs
201
436
  - tasks/example_viewer.html.erb
437
+ - tasks/install.rake
202
438
  - tasks/resources/build.yml.erb
203
439
  - tasks/resources/index.html.erb
204
440
  - tasks/resources/rubies.yml
205
441
  - tasks/steep.rake
206
442
  - vendor/goodcop/base.yml
207
- homepage: https://sr.ht/~kerrick/ratatui_ruby/
443
+ homepage: https://rooibos.run
208
444
  licenses:
209
445
  - LGPL-3.0-or-later
210
446
  metadata:
211
447
  allowed_push_host: https://rubygems.org
212
- homepage_uri: https://sr.ht/~kerrick/ratatui_ruby/
448
+ homepage_uri: https://rooibos.run
213
449
  bug_tracker_uri: https://todo.sr.ht/~kerrick/ratatui_ruby
214
450
  mailing_list_uri: https://lists.sr.ht/~kerrick/ratatui_ruby-discuss
215
451
  source_code_uri: https://git.sr.ht/~kerrick/rooibos
@@ -237,5 +473,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
473
  requirements: []
238
474
  rubygems_version: 4.0.3
239
475
  specification_version: 4
240
- summary: Part of the RatatuiRuby ecosystem
476
+ summary: "☕ Confidently Build Terminal Apps"
241
477
  test_files: []