rooibos 0.6.2 → 0.7.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 (217) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSES/BSD-2-Clause.txt +9 -0
  3. data/REUSE.toml +5 -0
  4. data/exe/.gitkeep +0 -0
  5. data/lib/rooibos/cli/commands/new.rb +24 -0
  6. data/lib/rooibos/command/batch.rb +10 -0
  7. data/lib/rooibos/command/bubble.rb +34 -0
  8. data/lib/rooibos/command/custom.rb +3 -2
  9. data/lib/rooibos/command/deliver.rb +50 -0
  10. data/lib/rooibos/command/http.rb +1 -1
  11. data/lib/rooibos/command/lifecycle.rb +3 -1
  12. data/lib/rooibos/command/outlet.rb +19 -9
  13. data/lib/rooibos/command.rb +107 -3
  14. data/lib/rooibos/configuration.rb +29 -0
  15. data/lib/rooibos/message/bubbled.rb +29 -0
  16. data/lib/rooibos/message.rb +24 -6
  17. data/lib/rooibos/router/action.rb +36 -0
  18. data/lib/rooibos/router/flow/dispatch.rb +39 -0
  19. data/lib/rooibos/router/flow/inward.rb +41 -0
  20. data/lib/rooibos/router/flow/outward.rb +44 -0
  21. data/lib/rooibos/router/guard.rb +56 -0
  22. data/lib/rooibos/router/predicate.rb +65 -0
  23. data/lib/rooibos/router/registry/actions.rb +41 -0
  24. data/lib/rooibos/router/registry/forwards.rb +58 -0
  25. data/lib/rooibos/router/registry/observes.rb +57 -0
  26. data/lib/rooibos/router/registry/otherwises.rb +29 -0
  27. data/lib/rooibos/router/registry/receives.rb +57 -0
  28. data/lib/rooibos/router/registry/routes.rb +59 -0
  29. data/lib/rooibos/router/registry.rb +26 -0
  30. data/lib/rooibos/router/route.rb +42 -0
  31. data/lib/rooibos/router/router_update.rb +53 -0
  32. data/lib/rooibos/router/rule/forward.rb +39 -0
  33. data/lib/rooibos/router/rule/observe.rb +22 -0
  34. data/lib/rooibos/router/rule/otherwise.rb +26 -0
  35. data/lib/rooibos/router/rule/receive.rb +22 -0
  36. data/lib/rooibos/router/rule.rb +40 -0
  37. data/lib/rooibos/router.rb +424 -438
  38. data/lib/rooibos/runtime.rb +37 -52
  39. data/lib/rooibos/test_helper.rb +22 -0
  40. data/lib/rooibos/transition.rb +92 -0
  41. data/lib/rooibos/version.rb +1 -1
  42. data/lib/rooibos.rb +2 -57
  43. data/sig/rooibos/cli.rbs +1 -0
  44. data/sig/rooibos/command.rbs +44 -0
  45. data/sig/rooibos/configuration.rbs +20 -0
  46. data/sig/rooibos/message.rbs +12 -0
  47. data/sig/rooibos/router/action.rbs +33 -0
  48. data/sig/rooibos/router/actions.rbs +27 -0
  49. data/sig/rooibos/router/flow/dispatch.rbs +29 -0
  50. data/sig/rooibos/router/flow/inward.rbs +37 -0
  51. data/sig/rooibos/router/flow/outward.rbs +36 -0
  52. data/sig/rooibos/router/forward.rbs +35 -0
  53. data/sig/rooibos/router/forwards.rbs +34 -0
  54. data/sig/rooibos/router/guard.rbs +21 -0
  55. data/sig/rooibos/router/observe.rbs +20 -0
  56. data/sig/rooibos/router/observes.rbs +38 -0
  57. data/sig/rooibos/router/otherwise.rbs +22 -0
  58. data/sig/rooibos/router/otherwises.rbs +20 -0
  59. data/sig/rooibos/router/predicate.rbs +51 -0
  60. data/sig/rooibos/router/receive.rbs +20 -0
  61. data/sig/rooibos/router/receives.rbs +38 -0
  62. data/sig/rooibos/router/registry.rbs +24 -0
  63. data/sig/rooibos/router/route.rbs +46 -0
  64. data/sig/rooibos/router/router_update.rbs +33 -0
  65. data/sig/rooibos/router/routes.rbs +41 -0
  66. data/sig/rooibos/router/rule.rbs +36 -0
  67. data/sig/rooibos/router.rbs +216 -161
  68. data/sig/rooibos/runtime.rbs +0 -1
  69. data/sig/rooibos/test_helper.rbs +6 -0
  70. data/sig/rooibos/transition.rbs +33 -0
  71. data/sig/rooibos.rbs +0 -10
  72. metadata +144 -198
  73. data/.builds/ruby-3.2.yml +0 -55
  74. data/.builds/ruby-3.3.yml +0 -55
  75. data/.builds/ruby-3.4.yml +0 -55
  76. data/.builds/ruby-4.0.0.yml +0 -55
  77. data/.pre-commit-config.yaml +0 -16
  78. data/.rubocop.yml +0 -8
  79. data/AGENTS.md +0 -108
  80. data/CHANGELOG.md +0 -308
  81. data/README.md +0 -183
  82. data/README.rdoc +0 -374
  83. data/Rakefile +0 -16
  84. data/Steepfile +0 -13
  85. data/doc/best_practices/forms_and_validation.md +0 -20
  86. data/doc/best_practices/http_workflows.md +0 -20
  87. data/doc/best_practices/index.md +0 -26
  88. data/doc/best_practices/lists_and_tables.md +0 -20
  89. data/doc/best_practices/modal_dialogs.md +0 -20
  90. data/doc/best_practices/no_stateful_widgets.md +0 -184
  91. data/doc/best_practices/orchestration.md +0 -20
  92. data/doc/best_practices/streaming_data.md +0 -20
  93. data/doc/contributors/design/commands_and_outlets.md +0 -214
  94. data/doc/contributors/design/mvu_tea_implementations_research.md +0 -373
  95. data/doc/contributors/documentation_plan.md +0 -616
  96. data/doc/contributors/documentation_stub_audit.md +0 -112
  97. data/doc/contributors/documentation_style.md +0 -275
  98. data/doc/contributors/e2e_pty.md +0 -168
  99. data/doc/contributors/maybe_stateful_router.md +0 -56
  100. data/doc/contributors/specs/earliest_tutorial_steps_per_story.md +0 -70
  101. data/doc/contributors/specs/file_browser.md +0 -789
  102. data/doc/contributors/specs/file_browser_stories.md +0 -784
  103. data/doc/contributors/specs/tutorials_to_stories.rb +0 -167
  104. data/doc/contributors/todo/scrollbar.md +0 -118
  105. data/doc/contributors/tutorial_old/01_project_setup.md +0 -20
  106. data/doc/contributors/tutorial_old/02_hello_world.md +0 -24
  107. data/doc/contributors/tutorial_old/03_adding_state.md +0 -26
  108. data/doc/contributors/tutorial_old/06_organizing_your_code.md +0 -20
  109. data/doc/contributors/tutorial_old/07_your_first_command.md +0 -21
  110. data/doc/contributors/tutorial_old/08_the_preview_pane.md +0 -20
  111. data/doc/contributors/tutorial_old/09_loading_states.md +0 -20
  112. data/doc/contributors/tutorial_old/10_testing_your_app.md +0 -20
  113. data/doc/contributors/tutorial_old/11_polish_and_refine.md +0 -20
  114. data/doc/contributors/tutorial_old/12_going_further.md +0 -20
  115. data/doc/contributors/tutorial_old/index.md +0 -20
  116. data/doc/custom.css +0 -22
  117. data/doc/essentials/commands.md +0 -20
  118. data/doc/essentials/index.md +0 -31
  119. data/doc/essentials/messages.md +0 -21
  120. data/doc/essentials/models.md +0 -21
  121. data/doc/essentials/shortcuts.md +0 -19
  122. data/doc/essentials/the_elm_architecture.md +0 -24
  123. data/doc/essentials/the_runtime.md +0 -21
  124. data/doc/essentials/update_functions.md +0 -20
  125. data/doc/essentials/views.md +0 -22
  126. data/doc/getting_started/for_go_developers.md +0 -16
  127. data/doc/getting_started/for_python_developers.md +0 -16
  128. data/doc/getting_started/for_rails_developers.md +0 -17
  129. data/doc/getting_started/for_ratatui_ruby_developers.md +0 -17
  130. data/doc/getting_started/for_react_developers.md +0 -17
  131. data/doc/getting_started/index.md +0 -52
  132. data/doc/getting_started/install.md +0 -20
  133. data/doc/getting_started/quickstart.md +0 -20
  134. data/doc/getting_started/ruby_primer.md +0 -19
  135. data/doc/getting_started/why_rooibos.md +0 -20
  136. data/doc/images/verify_readme_usage.png +0 -0
  137. data/doc/images/widget_cmd_exec.png +0 -0
  138. data/doc/index.md +0 -93
  139. data/doc/scaling_up/async_patterns.md +0 -20
  140. data/doc/scaling_up/command_composition.md +0 -20
  141. data/doc/scaling_up/custom_commands.md +0 -21
  142. data/doc/scaling_up/fractal_architecture.md +0 -20
  143. data/doc/scaling_up/index.md +0 -30
  144. data/doc/scaling_up/message_routing.md +0 -20
  145. data/doc/scaling_up/ractor_safety.md +0 -20
  146. data/doc/scaling_up/testing.md +0 -21
  147. data/doc/troubleshooting/common_errors.md +0 -20
  148. data/doc/troubleshooting/debugging.md +0 -21
  149. data/doc/troubleshooting/index.md +0 -23
  150. data/doc/troubleshooting/performance.md +0 -20
  151. data/doc/tutorial/01_project_setup.md +0 -44
  152. data/doc/tutorial/02_hello_world.md +0 -45
  153. data/doc/tutorial/03_static_file_list.md +0 -44
  154. data/doc/tutorial/04_arrow_navigation.md +0 -47
  155. data/doc/tutorial/05_real_files.md +0 -45
  156. data/doc/tutorial/06_safe_refactoring.md +0 -21
  157. data/doc/tutorial/07_red_first_tdd.md +0 -26
  158. data/doc/tutorial/08_file_metadata.md +0 -42
  159. data/doc/tutorial/09_text_preview.md +0 -44
  160. data/doc/tutorial/10_directory_tree.md +0 -42
  161. data/doc/tutorial/11_pane_focus.md +0 -40
  162. data/doc/tutorial/12_sorting.md +0 -41
  163. data/doc/tutorial/13_filtering.md +0 -43
  164. data/doc/tutorial/14_toggle_hidden.md +0 -41
  165. data/doc/tutorial/15_text_input_widget.md +0 -43
  166. data/doc/tutorial/16_rename_files.md +0 -42
  167. data/doc/tutorial/17_confirmation_dialogs.md +0 -43
  168. data/doc/tutorial/18_progress_indicators.md +0 -43
  169. data/doc/tutorial/19_atomic_operations.md +0 -42
  170. data/doc/tutorial/20_external_editor.md +0 -42
  171. data/doc/tutorial/21_modal_overlays.md +0 -41
  172. data/doc/tutorial/22_error_handling.md +0 -43
  173. data/doc/tutorial/23_terminal_capabilities.md +0 -53
  174. data/doc/tutorial/24_mouse_events.md +0 -43
  175. data/doc/tutorial/25_resize_events.md +0 -43
  176. data/doc/tutorial/26_loading_states.md +0 -42
  177. data/doc/tutorial/27_performance.md +0 -43
  178. data/doc/tutorial/28_color_schemes.md +0 -47
  179. data/doc/tutorial/29_configuration.md +0 -124
  180. data/doc/tutorial/30_going_further.md +0 -17
  181. data/doc/tutorial/index.md +0 -17
  182. data/examples/app_fractal_dashboard/README.md +0 -60
  183. data/examples/app_fractal_dashboard/app.rb +0 -63
  184. data/examples/app_fractal_dashboard/dashboard/base.rb +0 -73
  185. data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +0 -86
  186. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +0 -87
  187. data/examples/app_fractal_dashboard/dashboard/update_router.rb +0 -43
  188. data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +0 -81
  189. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +0 -82
  190. data/examples/app_fractal_dashboard/fragments/custom_shell_output.rb +0 -90
  191. data/examples/app_fractal_dashboard/fragments/disk_usage.rb +0 -47
  192. data/examples/app_fractal_dashboard/fragments/network_panel.rb +0 -45
  193. data/examples/app_fractal_dashboard/fragments/ping.rb +0 -47
  194. data/examples/app_fractal_dashboard/fragments/stats_panel.rb +0 -45
  195. data/examples/app_fractal_dashboard/fragments/system_info.rb +0 -47
  196. data/examples/app_fractal_dashboard/fragments/uptime.rb +0 -47
  197. data/examples/tutorial/01/app.rb +0 -50
  198. data/examples/tutorial/02/app.rb +0 -64
  199. data/examples/tutorial/03/app.rb +0 -91
  200. data/examples/tutorial/06_safe_refactoring/app.rb +0 -124
  201. data/examples/verify_readme_usage/README.md +0 -54
  202. data/examples/verify_readme_usage/app.rb +0 -47
  203. data/examples/verify_website_first_app/app.rb +0 -85
  204. data/examples/verify_website_hello_mvu/app.rb +0 -31
  205. data/examples/widget_command_system/README.md +0 -70
  206. data/examples/widget_command_system/app.rb +0 -134
  207. data/generate_tutorial_stubs.rb +0 -126
  208. data/mise.toml +0 -8
  209. data/rbs_collection.lock.yaml +0 -108
  210. data/rbs_collection.yaml +0 -15
  211. data/tasks/example_viewer.html.erb +0 -172
  212. data/tasks/install.rake +0 -29
  213. data/tasks/resources/build.yml.erb +0 -55
  214. data/tasks/resources/index.html.erb +0 -44
  215. data/tasks/resources/rubies.yml +0 -7
  216. data/tasks/steep.rake +0 -11
  217. /data/{vendor/goodcop/base.yml → lib/rooibos/rubocop.yml} +0 -0
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
9
-
10
- require "ratatui_ruby"
11
- require "rooibos"
12
-
13
- class VerifyReadmeUsage
14
- # [SYNC:START:mvu]
15
- Model = Data.define(:text)
16
-
17
- Init = -> do
18
- Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
19
- end
20
-
21
- View = -> (model, tui) do
22
- tui.paragraph(
23
- text: model.text,
24
- alignment: :center,
25
- block: tui.block(
26
- title: "My Ruby TUI App",
27
- borders: [:all],
28
- border_style: { fg: "cyan" }
29
- )
30
- )
31
- end
32
-
33
- Update = -> (msg, model) do
34
- if msg.q? || msg.ctrl_c?
35
- Rooibos::Command.exit
36
- else
37
- model
38
- end
39
- end
40
-
41
- def run
42
- Rooibos.run(VerifyReadmeUsage)
43
- end
44
- # [SYNC:END:mvu]
45
- end
46
-
47
- VerifyReadmeUsage.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
-
10
- module FileBrowser
11
- # Model: What state does your app need?
12
- Model = Data.define(:path, :entries, :selected, :error)
13
-
14
- Init = -> {
15
- path = Dir.pwd
16
- entries = Entries[path]
17
- Ractor.make_shareable( # Ensures thread safety
18
- Model.new(path:, entries:, selected: entries.first, error: nil))
19
- }
20
-
21
- View = -> (model, tui) {
22
- tui.block(
23
- titles: [
24
- model.error || model.path,
25
- { content: KEYS, position: :bottom, alignment: :right },
26
- ],
27
- borders: [:all],
28
- border_style: if model.error then tui.style(fg: :red) else nil end,
29
- children: [
30
- tui.list(items: model.entries.map(&ListItem[model, tui]),
31
- selected_index: model.entries.index(model.selected),
32
- highlight_symbol: "",
33
- highlight_style: tui.style(modifiers: [:reversed])),
34
- ]
35
- )
36
- }
37
-
38
- Update = -> (message, model) {
39
- return model.with(error: ERROR) if message.error?
40
- model = model.with(error: nil) if model.error && message.key?
41
-
42
- if message.ctrl_c? || message.q? then Rooibos::Command.exit
43
- elsif message.home? || message.g? then model.with(selected: model.entries.first)
44
- elsif message.end? || message.G? then model.with(selected: model.entries.last)
45
- elsif message.up_arrow? || message.k? then Select[:-, model]
46
- elsif message.down_arrow? || message.j? then Select[:+, model]
47
- elsif message.enter? then Open[model]
48
- elsif message.escape? then Navigate[File.dirname(model.path), model]
49
- end
50
- }
51
-
52
- KEYS = "↑/↓/Home/End: Select | Enter: Open | Esc: Navigate Up | q: Quit"
53
- ERROR = "Sorry, opening the selected file failed."
54
-
55
- ListItem = -> (model, tui) {
56
- -> (name) {
57
- modifiers = name.start_with?(".") ? [:dim] : []
58
- fg = :blue if name.end_with?("/")
59
- tui.list_item(content: name, style: tui.style(fg:, modifiers:))
60
- }
61
- }
62
-
63
- Select = -> (operator, model) {
64
- new_index = model.entries.index(model.selected).public_send(operator, 1)
65
- model.with(selected: model.entries[new_index.clamp(0, model.entries.length - 1)])
66
- }
67
-
68
- Open = -> (model) {
69
- full = File.join(model.path, model.selected.delete_suffix("/"))
70
- model.selected.end_with?("/") ? Navigate[full, model] : Rooibos::Command.open(full)
71
- }
72
-
73
- Navigate = -> (path, model) {
74
- entries = Entries[path]
75
- model.with(path:, entries:, selected: entries.first, error: nil)
76
- }
77
-
78
- Entries = -> (path) {
79
- Dir.children(path).map { |name|
80
- File.directory?(File.join(path, name)) ? "#{name}/" : name
81
- }.sort_by { |name| [name.end_with?("/") ? 0 : 1, name.downcase] }
82
- }
83
- end
84
-
85
- Rooibos.run(FileBrowser) if __FILE__ == $0
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- require "rooibos"
9
-
10
- module Counter
11
- # Init: How do you create the initial model?
12
- Init = -> { 0 }
13
-
14
- # View: What does the user see?
15
- View = -> (model, tui) { tui.paragraph(text: <<~END) }
16
- Current count: #{model}.
17
- Press any key to increment.
18
- Press Ctrl+C to quit.
19
- END
20
-
21
- # Update: What happens when things change?
22
- Update = -> (message, model) {
23
- if message.ctrl_c?
24
- Rooibos::Command.exit
25
- elsif message.key?
26
- model + 1
27
- end
28
- }
29
- end
30
-
31
- Rooibos.run(Counter) if __FILE__ == $0
@@ -1,70 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
- # Cmd.exec Example
6
-
7
- Demonstrates running shell commands using `Cmd.exec`.
8
-
9
- Commands in TEA produce **messages**, not callbacks. When a command completes, the runtime sends a tagged tuple to your `update` function. Pattern match on the tag to handle success and failure.
10
-
11
- ## Key Concepts
12
-
13
- - **Message Tags:** `Cmd.exec(command, tag)` produces `[tag, {stdout:, stderr:, status:}]`.
14
- - **Non-Blocking:** Commands run in background threads. The UI remains responsive.
15
- - **Success Handling:** Match on `status: 0` to handle successful execution.
16
- - **Error Handling:** Match on non-zero status to handle failures.
17
- - **Ractor-Safe:** No callbacks means no Proc captures. Messages are shareable.
18
-
19
- ## Hotkeys
20
-
21
- - `d`: Run `ls -la` (directory listing)
22
- - `u`: Run `uname -a` (system info)
23
- - `f`: Run a command that fails (demonstrates error handling)
24
- - `q`: **Quit**
25
-
26
- ## Usage
27
-
28
- <!-- SPDX-SnippetBegin -->
29
- <!--
30
- SPDX-FileCopyrightText: 2026 Kerrick Long
31
- SPDX-License-Identifier: MIT-0
32
- -->
33
- ```bash
34
- ruby examples/widget_cmd_exec/app.rb
35
- ```
36
- <!-- SPDX-SnippetEnd -->
37
-
38
- ## How It Works
39
-
40
- The update function handles both key presses and command results:
41
-
42
- <!-- SPDX-SnippetBegin -->
43
- <!--
44
- SPDX-FileCopyrightText: 2026 Kerrick Long
45
- SPDX-License-Identifier: MIT-0
46
- -->
47
- ```ruby
48
- UPDATE = -> (msg, model) do
49
- case msg
50
- # Handle command results
51
- in [:got_output, {stdout:, status: 0}]
52
- [model.with(result: stdout.strip, loading: false), nil]
53
- in [:got_output, {stderr:, status:}]
54
- [model.with(result: "Error (exit #{status}): #{stderr.strip}", loading: false), nil]
55
-
56
- # Handle key presses
57
- in _ if msg.d?
58
- [model.with(loading: true), Cmd.exec("ls -la", :got_output)]
59
- else
60
- model
61
- end
62
- end
63
- ```
64
- <!-- SPDX-SnippetEnd -->
65
-
66
- All logic stays in `update`. The command just runs and produces a message.
67
-
68
- [Read the source code →](app.rb)
69
-
70
- [![widget_cmd_exec](../../doc/images/widget_cmd_exec.png)](app.rb)
@@ -1,134 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
9
-
10
- require "ratatui_ruby"
11
- require "rooibos"
12
-
13
- # Demonstrates the Command.execute command for running shell commands.
14
- #
15
- # This example shows how to execute shell commands and handle both success
16
- # and failure cases using pattern matching in update. The split layout shows
17
- # command output in the main area with controls at the bottom.
18
- #
19
- # === Examples
20
- #
21
- # Run the demo from the terminal:
22
- #
23
- # ruby examples/widget_cmd_exec/app.rb
24
- #
25
- # rdoc-image:/doc/images/widget_cmd_exec.png
26
- class WidgetCommandSystem
27
- Model = Data.define(:result, :loading, :last_command)
28
- Init = -> {
29
- Model.new(
30
- result: "Press a key to run a command...",
31
- loading: false,
32
- last_command: nil
33
- )
34
- }
35
-
36
- View = -> (model, tui) do
37
- hotkey_style = tui.style(modifiers: [:bold, :underlined])
38
- dim_style = tui.style(fg: :dark_gray)
39
-
40
- # Styles
41
- border_color = if model.loading
42
- "yellow"
43
- elsif model.result.start_with?("Error")
44
- "red"
45
- else
46
- "cyan"
47
- end
48
-
49
- title = model.last_command ? "Output: #{model.last_command}" : "Command.execute Demo"
50
- content_text = model.loading ? "Running command..." : model.result
51
-
52
- # 1. Main Output Widget
53
- output_widget = tui.paragraph(
54
- text: content_text,
55
- block: tui.block(
56
- title:,
57
- borders: [:all],
58
- border_style: { fg: border_color },
59
- padding: 1
60
- )
61
- )
62
-
63
- # 2. Control Panel Widget
64
- control_widget = tui.paragraph(
65
- text: [
66
- tui.text_line(spans: [
67
- tui.text_span(content: "d", style: hotkey_style),
68
- tui.text_span(content: ": Directory listing (ls -la) "),
69
- tui.text_span(content: "u", style: hotkey_style),
70
- tui.text_span(content: ": System info (uname -a)"),
71
- ]),
72
- tui.text_line(spans: [
73
- tui.text_span(content: "f", style: hotkey_style),
74
- tui.text_span(content: ": Force failure "),
75
- tui.text_span(content: "s", style: hotkey_style),
76
- tui.text_span(content: ": Sleep (3s) "),
77
- tui.text_span(content: "q", style: hotkey_style),
78
- tui.text_span(content: ": Quit"),
79
- ]),
80
- ],
81
- block: tui.block(
82
- title: "Controls",
83
- borders: [:all],
84
- border_style: dim_style
85
- )
86
- )
87
-
88
- # Return the Root Layout Widget (Blueprint)
89
- tui.layout(
90
- direction: :vertical,
91
- constraints: [
92
- tui.constraint_fill(1),
93
- tui.constraint_length(6),
94
- ],
95
- children: [
96
- output_widget,
97
- control_widget,
98
- ]
99
- )
100
- end
101
-
102
- Update = -> (message, model) do
103
- case message
104
- # Handle command results (hash-based pattern matching)
105
- in { type: :system, envelope: :got_output, stdout:, status: 0 }
106
- [model.with(result: stdout.strip.freeze, loading: false), nil]
107
- in { type: :system, envelope: :got_output, stderr:, status: }
108
- [model.with(result: "Error (exit #{status}): #{stderr.strip}".freeze, loading: false), nil]
109
-
110
- # Handle key presses
111
- in _ if message.q? || message.ctrl_c?
112
- Rooibos::Command.exit
113
- in _ if message.d?
114
- [model.with(loading: true, last_command: "ls -la"), Rooibos::Command.system("ls -la", :got_output)]
115
- in _ if message.u?
116
- [model.with(loading: true, last_command: "uname -a"), Rooibos::Command.system("uname -a", :got_output)]
117
- in _ if message.s?
118
- cmd = "sleep 3 && echo 'Slept for 3s'"
119
- [model.with(loading: true, last_command: cmd.freeze), Rooibos::Command.system(cmd, :got_output)]
120
- in _ if message.f?
121
- # Intentional failure to demonstrate error handling
122
- cmd = "ls /nonexistent_path_12345"
123
- [model.with(loading: true, last_command: cmd.freeze), Rooibos::Command.system(cmd, :got_output)]
124
- else
125
- model
126
- end
127
- end
128
-
129
- def run
130
- Rooibos.run(WidgetCommandSystem)
131
- end
132
- end
133
-
134
- WidgetCommandSystem.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "fileutils"
5
-
6
- # Parse tutorial structure from documentation_plan.md
7
- TUTORIAL_FILES = {
8
- "index.md" => { title: "Tutorial: Build a File Browser", story: nil },
9
- "01_project_setup.md" => { title: "Project Setup", story: "-4" },
10
- "02_hello_world.md" => { title: "Hello World", story: "-3" },
11
- "03_static_file_list.md" => { title: "Static File List", story: "-2" },
12
- "04_arrow_navigation.md" => { title: "Arrow Navigation", story: "-1" },
13
- "05_real_files.md" => { title: "Real Files", story: "0" },
14
- "06_safe_refactoring.md" => { title: "Safe Refactoring", story: "4a" },
15
- "07_red_first_tdd.md" => { title: "Red-First TDD", story: "4b" },
16
- "08_file_metadata.md" => { title: "File Metadata", story: "5" },
17
- "09_text_preview.md" => { title: "Text Preview", story: "6" },
18
- "10_directory_tree.md" => { title: "Directory Tree", story: "7" },
19
- "11_pane_focus.md" => { title: "Pane Focus", story: "8" },
20
- "12_sorting.md" => { title: "Sorting", story: "9" },
21
- "13_filtering.md" => { title: "Filtering", story: "10" },
22
- "14_toggle_hidden.md" => { title: "Toggle Hidden Files", story: "11" },
23
- "15_text_input_widget.md" => { title: "Text Input Widget", story: "12" },
24
- "16_rename_files.md" => { title: "Rename Files", story: "13" },
25
- "17_confirmation_dialogs.md" => { title: "Confirmation Dialogs", story: "14" },
26
- "18_progress_indicators.md" => { title: "Progress Indicators", story: "15" },
27
- "19_atomic_operations.md" => { title: "Atomic Operations", story: "16" },
28
- "20_external_editor.md" => { title: "External Editor", story: "17" },
29
- "21_modal_overlays.md" => { title: "Modal Overlays", story: "18" },
30
- "22_error_handling.md" => { title: "Error Handling", story: "19" },
31
- "23_terminal_capabilities.md" => { title: "Terminal Capabilities", story: "23" },
32
- "24_mouse_events.md" => { title: "Mouse Events", story: "20" },
33
- "25_resize_events.md" => { title: "Resize Events", story: "21" },
34
- "26_loading_states.md" => { title: "Loading States", story: "22" },
35
- "27_performance.md" => { title: "Performance", story: "24" },
36
- "28_color_schemes.md" => { title: "Color Schemes", story: "26" },
37
- "29_configuration.md" => { title: "Configuration", story: "27" },
38
- "30_going_further.md" => { title: "Going Further", story: nil },
39
- }
40
-
41
- # Parse stories from file_browser_stories.md
42
- stories_content = File.read("doc/contributors/specs/file_browser_stories.md")
43
-
44
- # Extract each story
45
- stories = {}
46
- current_story = nil
47
- current_content = []
48
-
49
- stories_content.each_line do |line|
50
- if line =~ /^## Story (-?\d+[ab]?): (.+)$/
51
- # Save previous story
52
- if current_story
53
- stories[current_story] = current_content.join
54
- end
55
-
56
- current_story = $1
57
- current_content = [line]
58
- elsif current_story
59
- current_content << line
60
-
61
- # Stop at the next story or end of stories section
62
- if line =~ /^---$/ && current_content.size > 5
63
- # Check if next section is another story or implementation notes
64
- peek_ahead = stories_content.lines[stories_content.lines.index(line) + 1]
65
- if peek_ahead && !peek_ahead.start_with?("## Story")
66
- stories[current_story] = current_content.join
67
- break if peek_ahead.start_with?("## Implementation Notes")
68
- end
69
- end
70
- end
71
- end
72
-
73
- # Save last story
74
- stories[current_story] = current_content.join if current_story
75
-
76
- # Create tutorial directory
77
- FileUtils.mkdir_p("doc/tutorial")
78
-
79
- # Generate stub files
80
- TUTORIAL_FILES.each do |filename, meta|
81
- filepath = "doc/tutorial/#{filename}"
82
-
83
- # Determine previous/next files
84
- files_list = TUTORIAL_FILES.keys
85
- current_index = files_list.index(filename)
86
- prev_file = (current_index > 0) ? files_list[current_index - 1] : nil
87
- next_file = (current_index < files_list.size - 1) ? files_list[current_index + 1] : nil
88
-
89
- prev_title = prev_file ? TUTORIAL_FILES[prev_file][:title] : nil
90
- next_title = next_file ? TUTORIAL_FILES[next_file][:title] : nil
91
-
92
- # Get story content if applicable
93
- story_section = ""
94
- if meta[:story]
95
- story_key = meta[:story]
96
- if stories[story_key]
97
- story_section = "\n## User Stories\n\n#{stories[story_key]}\n"
98
- end
99
- end
100
-
101
- # Generate stub content
102
- content = <<~MARKDOWN
103
- <!--
104
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
105
- SPDX-License-Identifier: CC-BY-SA-4.0
106
- -->
107
-
108
- # #{meta[:title]}
109
-
110
-
111
- By the end of this guide, you will:
112
-
113
- - TODO: Write learning objectives
114
-
115
- > ⚠️ **This page is a stub.** Help us write it! See the [Documentation Plan](../contributors/documentation_plan.md) and [Style Guide](../contributors/documentation_style.md).
116
- #{story_section}
117
- ---
118
-
119
- #{prev_file ? "[**Previous:** #{prev_title}](./#{prev_file})" : ''} | #{next_file ? "[**Next:** #{next_title}](./#{next_file})" : ''}
120
- MARKDOWN
121
-
122
- File.write(filepath, content)
123
- puts "Created #{filepath}"
124
- end
125
-
126
- puts "\nDone! Created #{TUTORIAL_FILES.size} tutorial stub files."
data/mise.toml DELETED
@@ -1,8 +0,0 @@
1
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: LGPL-3.0-or-later
3
-
4
- [tools]
5
- ruby = "4.0.1"
6
- rust = "1.91.1"
7
- python = "3.12"
8
- pre-commit = "latest"
@@ -1,108 +0,0 @@
1
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- #
3
- # SPDX-License-Identifier: AGPL-3.0-or-later
4
-
5
- ---
6
- path: ".gem_rbs_collection"
7
- gems:
8
- - name: base64
9
- version: 0.3.0
10
- source:
11
- type: rubygems
12
- - name: bigdecimal
13
- version: '0'
14
- source:
15
- type: stdlib
16
- - name: csv
17
- version: '0'
18
- source:
19
- type: stdlib
20
- - name: ffi
21
- version: 1.17.3
22
- source:
23
- type: rubygems
24
- - name: fileutils
25
- version: '0'
26
- source:
27
- type: stdlib
28
- - name: forwardable
29
- version: '0'
30
- source:
31
- type: stdlib
32
- - name: io-console
33
- version: '0'
34
- source:
35
- type: stdlib
36
- - name: json
37
- version: '0'
38
- source:
39
- type: stdlib
40
- - name: logger
41
- version: '0'
42
- source:
43
- type: stdlib
44
- - name: minitest
45
- version: '0'
46
- source:
47
- type: stdlib
48
- - name: monitor
49
- version: '0'
50
- source:
51
- type: stdlib
52
- - name: mutex_m
53
- version: 0.3.0
54
- source:
55
- type: rubygems
56
- - name: optparse
57
- version: '0'
58
- source:
59
- type: stdlib
60
- - name: pp
61
- version: '0'
62
- source:
63
- type: stdlib
64
- - name: prettyprint
65
- version: '0'
66
- source:
67
- type: stdlib
68
- - name: prism
69
- version: 1.8.0
70
- source:
71
- type: rubygems
72
- - name: ratatui_ruby
73
- version: 1.1.0
74
- source:
75
- type: rubygems
76
- - name: ratatui_ruby-devtools
77
- version: 0.1.0
78
- source:
79
- type: rubygems
80
- - name: rbs
81
- version: 3.10.2
82
- source:
83
- type: rubygems
84
- - name: rdoc
85
- version: '0'
86
- source:
87
- type: stdlib
88
- - name: securerandom
89
- version: '0'
90
- source:
91
- type: stdlib
92
- - name: stringio
93
- version: '0'
94
- source:
95
- type: stdlib
96
- - name: strscan
97
- version: '0'
98
- source:
99
- type: stdlib
100
- - name: tsort
101
- version: '0'
102
- source:
103
- type: stdlib
104
- - name: uri
105
- version: '0'
106
- source:
107
- type: stdlib
108
- gemfile_lock_path: Gemfile.lock
data/rbs_collection.yaml DELETED
@@ -1,15 +0,0 @@
1
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- #
3
- # SPDX-License-Identifier: AGPL-3.0-or-later
4
-
5
- # RBS collection configuration
6
- # See: https://github.com/ruby/rbs/blob/master/docs/collection.md
7
-
8
- sources:
9
- - type: stdlib
10
- - type: rubygems
11
-
12
- path: .gem_rbs_collection
13
-
14
- gems:
15
- - name: ratatui_ruby