rooibos 0.5.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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/.builds/ruby-3.2.yml +51 -0
  3. data/.builds/ruby-3.3.yml +51 -0
  4. data/.builds/ruby-3.4.yml +51 -0
  5. data/.builds/ruby-4.0.0.yml +51 -0
  6. data/.pre-commit-config.yaml +16 -0
  7. data/.rubocop.yml +8 -0
  8. data/AGENTS.md +108 -0
  9. data/CHANGELOG.md +214 -0
  10. data/LICENSE +304 -0
  11. data/LICENSES/AGPL-3.0-or-later.txt +235 -0
  12. data/LICENSES/CC-BY-SA-4.0.txt +170 -0
  13. data/LICENSES/CC0-1.0.txt +121 -0
  14. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  15. data/LICENSES/MIT-0.txt +16 -0
  16. data/LICENSES/MIT.txt +18 -0
  17. data/README.md +183 -0
  18. data/REUSE.toml +24 -0
  19. data/Rakefile +16 -0
  20. data/Steepfile +13 -0
  21. data/doc/concepts/application_architecture.md +197 -0
  22. data/doc/concepts/application_testing.md +49 -0
  23. data/doc/concepts/async_work.md +164 -0
  24. data/doc/concepts/commands.md +530 -0
  25. data/doc/concepts/message_processing.md +51 -0
  26. data/doc/contributors/WIP/decomposition_strategies_analysis.md +258 -0
  27. data/doc/contributors/WIP/implementation_plan.md +409 -0
  28. data/doc/contributors/WIP/init_callable_proposal.md +344 -0
  29. data/doc/contributors/WIP/mvu_tea_implementations_research.md +373 -0
  30. data/doc/contributors/WIP/runtime_refactoring_status.md +47 -0
  31. data/doc/contributors/WIP/task.md +36 -0
  32. data/doc/contributors/WIP/v0.4.0_todo.md +468 -0
  33. data/doc/contributors/design/commands_and_outlets.md +214 -0
  34. data/doc/contributors/kit-no-outlet.md +238 -0
  35. data/doc/contributors/priorities.md +38 -0
  36. data/doc/custom.css +22 -0
  37. data/doc/getting_started/quickstart.md +56 -0
  38. data/doc/images/.gitkeep +0 -0
  39. data/doc/images/verify_readme_usage.png +0 -0
  40. data/doc/images/widget_cmd_exec.png +0 -0
  41. data/doc/index.md +25 -0
  42. data/examples/app_fractal_dashboard/README.md +60 -0
  43. data/examples/app_fractal_dashboard/app.rb +63 -0
  44. data/examples/app_fractal_dashboard/dashboard/base.rb +73 -0
  45. data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +86 -0
  46. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +87 -0
  47. data/examples/app_fractal_dashboard/dashboard/update_router.rb +43 -0
  48. data/examples/app_fractal_dashboard/fragments/custom_shell_input.rb +81 -0
  49. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
  50. data/examples/app_fractal_dashboard/fragments/custom_shell_output.rb +90 -0
  51. data/examples/app_fractal_dashboard/fragments/disk_usage.rb +47 -0
  52. data/examples/app_fractal_dashboard/fragments/network_panel.rb +45 -0
  53. data/examples/app_fractal_dashboard/fragments/ping.rb +47 -0
  54. data/examples/app_fractal_dashboard/fragments/stats_panel.rb +45 -0
  55. data/examples/app_fractal_dashboard/fragments/system_info.rb +47 -0
  56. data/examples/app_fractal_dashboard/fragments/uptime.rb +47 -0
  57. data/examples/verify_readme_usage/README.md +54 -0
  58. data/examples/verify_readme_usage/app.rb +47 -0
  59. data/examples/widget_command_system/README.md +70 -0
  60. data/examples/widget_command_system/app.rb +132 -0
  61. data/exe/.gitkeep +0 -0
  62. data/lib/rooibos/command/all.rb +69 -0
  63. data/lib/rooibos/command/batch.rb +77 -0
  64. data/lib/rooibos/command/custom.rb +104 -0
  65. data/lib/rooibos/command/http.rb +192 -0
  66. data/lib/rooibos/command/lifecycle.rb +134 -0
  67. data/lib/rooibos/command/outlet.rb +157 -0
  68. data/lib/rooibos/command/wait.rb +80 -0
  69. data/lib/rooibos/command.rb +546 -0
  70. data/lib/rooibos/error.rb +55 -0
  71. data/lib/rooibos/message/all.rb +45 -0
  72. data/lib/rooibos/message/http_response.rb +61 -0
  73. data/lib/rooibos/message/system/batch.rb +61 -0
  74. data/lib/rooibos/message/system/stream.rb +67 -0
  75. data/lib/rooibos/message/timer.rb +46 -0
  76. data/lib/rooibos/message.rb +38 -0
  77. data/lib/rooibos/router.rb +403 -0
  78. data/lib/rooibos/runtime.rb +396 -0
  79. data/lib/rooibos/shortcuts.rb +49 -0
  80. data/lib/rooibos/test_helper.rb +56 -0
  81. data/lib/rooibos/version.rb +12 -0
  82. data/lib/rooibos.rb +121 -0
  83. data/mise.toml +8 -0
  84. data/rbs_collection.lock.yaml +108 -0
  85. data/rbs_collection.yaml +15 -0
  86. data/sig/concurrent.rbs +72 -0
  87. data/sig/examples/verify_readme_usage/app.rbs +19 -0
  88. data/sig/examples/widget_command_system/app.rbs +26 -0
  89. data/sig/open3.rbs +17 -0
  90. data/sig/rooibos/command.rbs +265 -0
  91. data/sig/rooibos/error.rbs +13 -0
  92. data/sig/rooibos/message.rbs +121 -0
  93. data/sig/rooibos/router.rbs +153 -0
  94. data/sig/rooibos/runtime.rbs +75 -0
  95. data/sig/rooibos/shortcuts.rbs +16 -0
  96. data/sig/rooibos/test_helper.rbs +10 -0
  97. data/sig/rooibos/version.rbs +8 -0
  98. data/sig/rooibos.rbs +46 -0
  99. data/tasks/example_viewer.html.erb +172 -0
  100. data/tasks/resources/build.yml.erb +53 -0
  101. data/tasks/resources/index.html.erb +44 -0
  102. data/tasks/resources/rubies.yml +7 -0
  103. data/tasks/steep.rake +11 -0
  104. data/vendor/goodcop/base.yml +1047 -0
  105. metadata +241 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f7a4f2a869c06e70b95a34cac8915f8d8e6543283007fd7c595692cf34bf3329
4
+ data.tar.gz: c8c0be8e5059b55671baa49105aa7b470dfdfa17647353f4e81ebcf06ecf7f15
5
+ SHA512:
6
+ metadata.gz: 97556fd7362f24d3d5572da7b11485456b3066bc56ef77994c2448a62085aa62d54c8503c5ffa2cea4472dd556bff1328ececb1dcb0c323cf663fdd1267ac8c9
7
+ data.tar.gz: c02267ab34497516ee6b6fee6daab08b72fcf26f6d4499aaf5d98162df1f063ee7eeec0464361189d3371a77434cf94d72b030289acea29cbc0d4b972691f1aa
@@ -0,0 +1,51 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ image: archlinux
5
+ packages:
6
+ - bash
7
+ - base-devel
8
+ - curl
9
+ - openssl
10
+ - libyaml
11
+ - zlib
12
+ - readline
13
+ - gdbm
14
+ - ncurses
15
+ - libffi
16
+ - clang
17
+ - git
18
+ sources:
19
+ - https://git.sr.ht/~kerrick/ratatui_ruby-tea
20
+ tasks:
21
+ - setup: |
22
+ curl https://mise.jdx.dev/install.sh | sh
23
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
24
+ echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
25
+ echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
26
+ echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
27
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
+ . ~/.buildenv
29
+ export CI="true"
30
+ cd ratatui_ruby-tea
31
+ sed -i 's/ruby = .*/ruby = "3.2"/' mise.toml
32
+ mise install
33
+ mise x -- pip install reuse
34
+ mise x -- gem install bundler:4.0.3
35
+ mise reshim
36
+ mise x -- bundle config set --local frozen 'true'
37
+ mise x -- bundle install
38
+ - test: |
39
+ . ~/.buildenv
40
+ cd ratatui_ruby-tea
41
+ echo "Testing Ruby 3.2"
42
+ mise x -- bundle exec rake test
43
+ - lint: |
44
+ . ~/.buildenv
45
+ cd ratatui_ruby-tea
46
+ echo "Linting Ruby 3.2"
47
+ mise x -- bundle exec rake lint
48
+ - package: |
49
+ . ~/.buildenv
50
+ cd ratatui_ruby-tea
51
+ mise x -- bundle exec rake build
@@ -0,0 +1,51 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ image: archlinux
5
+ packages:
6
+ - bash
7
+ - base-devel
8
+ - curl
9
+ - openssl
10
+ - libyaml
11
+ - zlib
12
+ - readline
13
+ - gdbm
14
+ - ncurses
15
+ - libffi
16
+ - clang
17
+ - git
18
+ sources:
19
+ - https://git.sr.ht/~kerrick/ratatui_ruby-tea
20
+ tasks:
21
+ - setup: |
22
+ curl https://mise.jdx.dev/install.sh | sh
23
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
24
+ echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
25
+ echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
26
+ echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
27
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
+ . ~/.buildenv
29
+ export CI="true"
30
+ cd ratatui_ruby-tea
31
+ sed -i 's/ruby = .*/ruby = "3.3"/' mise.toml
32
+ mise install
33
+ mise x -- pip install reuse
34
+ mise x -- gem install bundler:4.0.3
35
+ mise reshim
36
+ mise x -- bundle config set --local frozen 'true'
37
+ mise x -- bundle install
38
+ - test: |
39
+ . ~/.buildenv
40
+ cd ratatui_ruby-tea
41
+ echo "Testing Ruby 3.3"
42
+ mise x -- bundle exec rake test
43
+ - lint: |
44
+ . ~/.buildenv
45
+ cd ratatui_ruby-tea
46
+ echo "Linting Ruby 3.3"
47
+ mise x -- bundle exec rake lint
48
+ - package: |
49
+ . ~/.buildenv
50
+ cd ratatui_ruby-tea
51
+ mise x -- bundle exec rake build
@@ -0,0 +1,51 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ image: archlinux
5
+ packages:
6
+ - bash
7
+ - base-devel
8
+ - curl
9
+ - openssl
10
+ - libyaml
11
+ - zlib
12
+ - readline
13
+ - gdbm
14
+ - ncurses
15
+ - libffi
16
+ - clang
17
+ - git
18
+ sources:
19
+ - https://git.sr.ht/~kerrick/ratatui_ruby-tea
20
+ tasks:
21
+ - setup: |
22
+ curl https://mise.jdx.dev/install.sh | sh
23
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
24
+ echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
25
+ echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
26
+ echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
27
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
+ . ~/.buildenv
29
+ export CI="true"
30
+ cd ratatui_ruby-tea
31
+ sed -i 's/ruby = .*/ruby = "3.4"/' mise.toml
32
+ mise install
33
+ mise x -- pip install reuse
34
+ mise x -- gem install bundler:4.0.3
35
+ mise reshim
36
+ mise x -- bundle config set --local frozen 'true'
37
+ mise x -- bundle install
38
+ - test: |
39
+ . ~/.buildenv
40
+ cd ratatui_ruby-tea
41
+ echo "Testing Ruby 3.4"
42
+ mise x -- bundle exec rake test
43
+ - lint: |
44
+ . ~/.buildenv
45
+ cd ratatui_ruby-tea
46
+ echo "Linting Ruby 3.4"
47
+ mise x -- bundle exec rake lint
48
+ - package: |
49
+ . ~/.buildenv
50
+ cd ratatui_ruby-tea
51
+ mise x -- bundle exec rake build
@@ -0,0 +1,51 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ image: archlinux
5
+ packages:
6
+ - bash
7
+ - base-devel
8
+ - curl
9
+ - openssl
10
+ - libyaml
11
+ - zlib
12
+ - readline
13
+ - gdbm
14
+ - ncurses
15
+ - libffi
16
+ - clang
17
+ - git
18
+ sources:
19
+ - https://git.sr.ht/~kerrick/ratatui_ruby-tea
20
+ tasks:
21
+ - setup: |
22
+ curl https://mise.jdx.dev/install.sh | sh
23
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
24
+ echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
25
+ echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
26
+ echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
27
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
+ . ~/.buildenv
29
+ export CI="true"
30
+ cd ratatui_ruby-tea
31
+ sed -i 's/ruby = .*/ruby = "4.0.0"/' mise.toml
32
+ mise install
33
+ mise x -- pip install reuse
34
+ mise x -- gem install bundler:4.0.3
35
+ mise reshim
36
+ mise x -- bundle config set --local frozen 'true'
37
+ mise x -- bundle install
38
+ - test: |
39
+ . ~/.buildenv
40
+ cd ratatui_ruby-tea
41
+ echo "Testing Ruby 4.0.0"
42
+ mise x -- bundle exec rake test
43
+ - lint: |
44
+ . ~/.buildenv
45
+ cd ratatui_ruby-tea
46
+ echo "Linting Ruby 4.0.0"
47
+ mise x -- bundle exec rake lint
48
+ - package: |
49
+ . ~/.buildenv
50
+ cd ratatui_ruby-tea
51
+ mise x -- bundle exec rake build
@@ -0,0 +1,16 @@
1
+ # SPDX-FileCopyrightText: 2020 Free Software Foundation Europe e.V.
2
+ # SPDX-License-Identifier: CC0-1.0
3
+ repos:
4
+ - repo: local
5
+ hooks:
6
+ - id: bundle-check
7
+ name: Check Gemfile.lock
8
+ entry: bundle check
9
+ language: system
10
+ files: (Gemfile|Gemfile\.lock|rooibos\.gemspec)
11
+ pass_filenames: false
12
+ - id: rake
13
+ name: rake
14
+ entry: bundle exec rake
15
+ language: system
16
+ pass_filenames: false
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ inherit_from:
5
+ - ./vendor/goodcop/base.yml
6
+
7
+ AllCops:
8
+ TargetRubyVersion: "3.2"
data/AGENTS.md ADDED
@@ -0,0 +1,108 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # AGENTS.md
7
+
8
+ ## Project Identity
9
+
10
+ Project Name: rooibos
11
+
12
+ Description: Part of the RatatuiRuby ecosystem.
13
+
14
+ ## 1. Standards
15
+
16
+ ### STRICT REQUIREMENTS
17
+
18
+ - Every file MUST begin with an SPDX-compliant header. Use `LGPL-3.0-or-later` for code; `CC-BY-SA-4.0` for documentation. `reuse annotate` can help you generate the header. **For Ruby files**, wrap SPDX comments in `#--` / `#++` to hide them from RDoc output.
19
+ - Every line of Ruby MUST be covered by tests that would stand up to mutation testing.
20
+ - Tests must be meaningful and verify specific behavior or rendering output; simply verifying that code "doesn't crash" is insufficient and unacceptable.
21
+ - **Pre-commit:** Use `bundle exec agent_rake` to ensure commit-readiness. See Tools for detailed instructions.
22
+ - **Git Pager:** ALWAYS set `PAGER=cat` for ALL `git` commands (e.g., `PAGER=cat git diff`). This is mandatory.
23
+
24
+ ### Tools
25
+
26
+ - **NEVER** run `bundle exec rake` directly. **NEVER** run `bundle exec ruby -Ilib:test ...` directly.
27
+ - **ALWAYS use `bundle exec agent_rake`** (provided by the `ratatui_ruby-devtools` gem) for running tests, linting, or checking compilation.
28
+ - **Usage:**
29
+ - Runs default task (compile + test + lint): `bundle exec agent_rake`
30
+ - Runs specific task: `bundle exec agent_rake test:ruby` (for example)
31
+ - **Setup:** `bin/setup` must handle Bundler dependencies.
32
+ - **Git:** ALWAYS set `PAGER=cat` with `git`. **THIS IS CRITICAL!**
33
+
34
+ ### Rooibos-Specific Vocabulary
35
+
36
+ - **BANNED WORD: "component"** — Reserved for Kit.
37
+ - **Avoid "widget" for Rooibos units** — "Widget" refers to Engine/Ratatui render primitives. In Rooibos, call them **fragments**.
38
+ - **Fragment:** A module containing `Model`, `INITIAL`, `UPDATE`, and `VIEW` constants. Fragments compose: parent fragments delegate to child fragments.
39
+ - Use "model", "update", "view" for the MVU pattern. Use "message" (not "msg") and "command" (not "cmd").
40
+
41
+ ### Ruby Standards
42
+
43
+ - Use `Data.define` for all value objects (Ruby 3.2+).
44
+ - Define types in `.rbs` files. Don't use `untyped` just because it's easy; be comprehensive and accurate.
45
+ - Every public Ruby class/method must be documented for humans in RDoc.
46
+
47
+ ## 2. Committing
48
+
49
+ - Who commits: DON'T stage (DON'T `git add`) unless explicitly instructed. DON'T commit unless explicitly instructed. DO suggest a commit message when you finish.
50
+ - **Format:**
51
+ - Format: Use [Conventional Commits](https://www.conventionalcommits.org/).
52
+ - Body: Explanation if necessary (wrap at 72 chars).
53
+ - **DON'T list the files changed or the edits made in the body.**
54
+ - **DON'T use markdown syntax** (no backticks, no bolding, no lists, no links).
55
+
56
+ ## 3. Definition of Done (DoD)
57
+
58
+ Before considering a task complete and returning control to the user, you **MUST** ensure:
59
+
60
+ 0. **Production Ready:** RBS types are complete and accurate (no `untyped`), errors are handled with good DX, documentation follows guidelines, high code quality (no "pre-existing debt" excuses).
61
+ 1. **Default Rake Task Passes:** Run `bundle exec agent_rake` (no args). Confirm it passes with ZERO errors **or warnings**.
62
+ - You will save time if you run `bundle exec agent_rake rubocop:autocorrect` first.
63
+ - If you think the rake is looking for deleted files, STOP EVERYTHING and tell the user.
64
+ 2. **Documentation Updated:** If public APIs or observable behavior changed, update relevant RDoc, rustdoc, `doc/` files, `README.md`, and/or `ratatui_ruby-wiki` files.
65
+ 3. **Changelog Updated:** If public APIs, observable behavior, or gemspec dependencies have changed, update [CHANGELOG.md](CHANGELOG.md)'s **Unreleased** section.
66
+ 4. **Commit Message Suggested:** You **MUST** ensure the final message to the user includes a suggested commit message block. This is NOT optional.
67
+ - You MUST also check `git log -n1` to see the current standard AI footer ("Generated with" and "Co-Authored-By") and include it in your suggested message.
68
+
69
+ ## 4. Committing
70
+
71
+ - Who commits: DON'T stage (DON'T `git add`) unless explicitly instructed. DON'T commit unless explicitly instructed. DO suggest a commit message when you finish, even if not instructed..
72
+ - When: Before reporting the task as complete to the user, suggest the commit message.
73
+ - What: Consider not what you remember, but EVERYTHING in the `git diff` and `git diff --cached`.
74
+ - **Format:**
75
+ - Format: Use [Conventional Commits](https://www.conventionalcommits.org/).
76
+ - Body: Explanation if necessary (wrap at 72 chars).
77
+ - Explain why this is the implementation, as opposed to other possible implementations.
78
+ - Skip the body entirely if it's rote, a duplication of the diff, or otherwise unhelpful.
79
+ - **DON'T list the files changed or the edits made in the body.** Don't provide a bulleted list of changes. Use prose to explain the problem and the solution.
80
+ - **DON'T use markdown syntax** (no backticks, no bolding, no lists, no links). The commit message must be plain text.
81
+ - **Type conventions by directory:**
82
+ - `lib/`, `ext/`, `sig/`: Use `feat`, `fix`, `refactor`, `perf` as appropriate.
83
+ - `bin/`, `tasks/`, `.builds/`, CI/CD: Use `chore` for tooling internal to developing this gem. Use `feat`/`fix` for user-facing executables or changes that affect downstream users.
84
+ - `examples/`: Always `docs` (documentation by example).
85
+ - `test/`: Use `test` for new/changed tests, or match the type of the code being tested.
86
+ - `doc/`: Always `docs`.
87
+
88
+ ### 5. Changelog
89
+
90
+ - Follow [Semantic Versioning](https://semver.org/)
91
+ - Follow the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) specification.
92
+ - **What belongs in CHANGELOG:** Only changes that affect **application developers** or **higher-level library developers** who use or depend on `ratatui_ruby`:
93
+ - New public APIs or widget parameters
94
+ - Backwards-incompatible type signature changes, or behavioral additions to type signature changes
95
+ - Observable behavior changes (rendering, styling, layout)
96
+ - Deprecations and removals
97
+ - Breaking changes
98
+ - **What does NOT belong in CHANGELOG:** Internal or non-behavioral changes that don't affect downstream users:
99
+ - Test additions or improvements
100
+ - Documentation updates, RDoc fixes, markdown clarifications
101
+ - Refactors of internal code
102
+ - New or modified example code
103
+ - Internal tooling, CI/CD, or build configuration changes
104
+ - Code style or linting changes
105
+ - Performance improvements that affect applications
106
+ - Changelogs should be useful to downstream developers (both app and library developers), not simple restatements of diffs or commit messages.
107
+ - The Unreleased section MUST be considered "since the last git tag". Therefore, if a change was done in one commit and undone in another (both since the last tag), the second commit should remove its changelog entry.
108
+ - **Location:** New entries ALWAYS go in `## [Unreleased]`. Never edit past version sections (e.g., `## [0.4.0]`)—those are frozen history.
data/CHANGELOG.md ADDED
@@ -0,0 +1,214 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Changelog
7
+
8
+ All notable changes to this project will be documented in this file.
9
+
10
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
11
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
+
13
+
14
+ ## [Unreleased]
15
+
16
+ ### Added
17
+
18
+ ### Changed
19
+
20
+ ### Fixed
21
+
22
+ ### Removed
23
+
24
+ ## [0.5.0] - 2026-01-16
25
+
26
+ ### Added
27
+
28
+ ### Changed
29
+
30
+ - **BREAKING: Rebrand to Rooibos**: The gem is now named `rooibos` and the module is top-level `Rooibos` instead of `RatatuiRuby::Tea`. Update your code, including:
31
+ - `require "ratatui_ruby/tea"` → `require "rooibos"`
32
+ - `RatatuiRuby::Tea::*` → `Rooibos::*`
33
+ - If you need a full migration guide, reach out to [the mailing list](https://lists.sr.ht/~kerrick/ratatui_ruby-discuss)
34
+
35
+ ### Fixed
36
+
37
+ ### Removed
38
+
39
+ ## [0.4.0] - 2026-01-16
40
+
41
+ ### Added
42
+
43
+ - **Timer Commands**: `Command.wait(seconds, tag)` and `Command.tick(seconds, tag)` for timed events. After the duration, sends `tag` to the update function. Responds to cancellation cooperatively — sends `Command.cancel(self)` when cancelled so you can handle it. Uses `Concurrent::Cancellation.timeout` internally. `Command.tick` is an alias for `Command.wait`; the "recurring tick" pattern is achieved by re-dispatching in the update function.
44
+
45
+ - **Command.uncancellable Factory**: Creates a fresh `Concurrent::Cancellation` that never fires. Use for commands wrapping non-cancellable blocking I/O (e.g., `Net::HTTP` requests).
46
+
47
+ - **Command.batch**: Fire-and-forget parallel execution. `Command.batch(cmd1, cmd2)` or `Command.batch([cmds])` runs children in parallel; each child sends its own messages independently. On cancellation, emits `Command.cancel(self)` so you can detect the batch was stopped. Child errors surface as `Command::Error` so you can handle failures in your update function. One failing child does not stop the others. Requires all child commands to be Ractor-shareable.
48
+
49
+ - **Command.all**: Aggregating parallel execution. `Command.all(:tag, cmd1, cmd2)` or `Command.all(:tag, [cmds])` runs children in parallel and waits for all to complete, then sends a single aggregated message. Array syntax produces `[:tag, [results]]`; variadic syntax splats results as `[:tag, result1, result2]`. On cancellation, emits `Command.cancel(self)`. Child errors surface as `Command::Error`. Requires all child commands to be Ractor-shareable.
50
+
51
+ - **Outlet#source**: Command composition for multi-step workflows. `out.source(child_command, token, timeout: 30.0)` runs a child command synchronously within a custom command, returning its result (or `nil` if cancelled/timed out). Exceptions from failed children propagate to the caller. Use this to orchestrate sequential API calls, conditional fetches, or any workflow that needs one result before starting the next.
52
+
53
+ - **Command.http**: Native HTTP client for API calls. Supports GET, POST, PUT, PATCH, DELETE with DWIM syntax: `Command.http(get: 'url')`, `Command.http(:get, 'url', :tag)`, etc. Returns hash-based `HttpResponse` with `deconstruct_keys` for pattern matching: `{ type: :http, envelope:, status:, body:, headers: }` or `{ type: :http, envelope:, error: }`. Optional `parser:` keyword invokes a callable on the response body for JSON, YAML, CSV, or custom parsing. SSL, default 10s timeout, and cancellation-before-request are supported. Parsers and parsed results must be Ractor-shareable.
54
+
55
+ - **Streaming Command Data Loss Fix**: Fixed race condition in `Command.system(stream: true)` where fast commands could lose stdout/stderr data. Reader threads now `join` instead of `kill` to ensure all output is processed before completion.
56
+
57
+ - **Message::Predicates Mixin**: Include in custom message types for safe predicate calls. Returns `false` for any unknown predicate method (ending in `?`). Enables pattern matching workflows where messages can respond to predicates like `msg.http?` or `msg.timer?` without raising `NoMethodError`. Includes `respond_to_missing?` for introspection parity.
58
+
59
+ - **Message::Timer**: Predicate-rich response type for timer commands (`Command.wait`, `Command.tick`). Includes `timer?` predicate and `deconstruct_keys` for pattern matching with `type: :timer` discriminator. Contains `envelope` (routing symbol) and `elapsed` (actual wait time in seconds).
60
+
61
+ - **Message::HttpResponse**: Moved from `Command::HttpResponse` to `Message::HttpResponse` with added predicates. Includes `http?`, `success?`, and `error?` predicates. Implements `deconstruct_keys` for pattern matching with `type: :http` discriminator.
62
+
63
+ - **Message::System::Batch**: Response type for `Command.system` (batch mode). Includes `system?`, `success?` (exit 0), and `error?` (non-zero exit) predicates. Contains `envelope`, `stdout`, `stderr`, and `status`. Implements `deconstruct_keys` for pattern matching.
64
+
65
+ - **Message::System::Stream**: Response type for `Command.system(..., stream: true)`. Includes `system?`, `stdout?`, `stderr?`, and `complete?` predicates. Contains `envelope`, `stream` type (`:stdout`, `:stderr`, `:complete`), `content` (for output lines), and `status` (for completion). Implements conditional `deconstruct_keys` based on stream type.
66
+
67
+ - **Message::All**: Response type for `Command.all` aggregating parallel execution. Includes `all?` predicate. Contains `envelope`, `results` array, and `nested` boolean. Implements `deconstruct_keys` for pattern matching.
68
+
69
+ - **Command Parameter Rename**: Renamed `tag` parameter to `envelope` across all commands for consistency: `Command.wait`, `Command.tick`, `Command.system`, and `Command.all`. These now use `envelope:` in their data definitions. Messages emit with `envelope:` for pattern matching.
70
+
71
+ - **Dependencies**: Added `concurrent-ruby` (~> 1.3) and `concurrent-ruby-edge` (~> 0.7) for robust concurrency primitives.
72
+
73
+ - **Rooibos.normalize_init Helper**: New `Rooibos.normalize_init(result)` normalizes Init callable returns. Accepts the output of a `Fragment.Init` callable and always returns `[model, command]`. Use when composing child fragment initialization in parent fragments.
74
+
75
+ ### Changed
76
+
77
+ - **Terminology: "Bag" → "Fragment"**: Renamed Fractal Architecture units from "bags" to "fragments" throughout the codebase. A fragment is a module containing `Model`, `INITIAL`, `UPDATE`, and `VIEW` constants. Parent fragments compose child fragments via routing. The `examples/app_fractal_dashboard/bags/` directory is now `examples/app_fractal_dashboard/fragments/`. All API documentation, code comments, and examples updated to reflect this terminology change.
78
+
79
+ - **Runtime API Signature (Breaking)**: `Rooibos.run` signature changed:
80
+ - Fragment parameter is now **positional** instead of keyword: `Rooibos.run(MyApp)` instead of `Rooibos.run(fragment: MyApp)`
81
+ - Removed `argv:` and `env:` parameters - Runtime now automatically uses `ARGV` and `ENV` globals
82
+ - Added `fps:` parameter (default 60) for configurable frame rate
83
+ - Renamed `init:` parameter to `command:` in explicit parameters API for clarity
84
+
85
+ - **Fragment Convention Rename (Breaking)**: Fragment constants have new naming conventions:
86
+ - `INITIAL` constant → `Init` callable. The runtime calls `Init.()` to get the initial model.
87
+ - `UPDATE` constant → `Update` callable (capitalized).
88
+ - `VIEW` constant → `View` callable (capitalized).
89
+ - Init is now a lambda/callable instead of a frozen constant. This enables parameterized initialization and returning `[model, command]` tuples for initial commands.
90
+
91
+ - **Internal Method Rename (Breaking)**: `Runtime.normalize_update_result` renamed to `Runtime.normalize_update_return` for clarity. Only affects code calling private Runtime internals.
92
+
93
+ - **Command.custom Ractor Validation (Breaking)**: `Command.custom(callable)` now validates in debug mode that the callable is Ractor-shareable. Callables that capture mutable state will raise `Invariant`. Define callables at module level or use `Ractor.make_shareable`.
94
+
95
+ - **Model Validation Timing (Breaking)**: Runtime now validates model Ractor-shareability **immediately after Init returns**, not just during the Update cycle. This catches mutable models earlier, enforcing immutability at startup. Models must be frozen (`.freeze`) or use immutable data structures (`Data.define`). This is a good breaking change that prevents subtle concurrency bugs.
96
+
97
+ - **CancellationToken Replaced (Breaking)**: Custom commands now receive `Concurrent::Cancellation` instead of `CancellationToken`. The method to check cancellation changes from `token.cancelled?` (British) to `token.canceled?` (American).
98
+
99
+ - **Outlet Accepts Channel (Breaking)**: `Outlet.new` now accepts a `Concurrent::Promises::Channel` instead of `Thread::Queue`.
100
+
101
+ - **Outlet#put (Breaking)**: `out.put(msg)`, the common case now sends `msg` directly instead of `[msg]`; `out.put(a, b, c)` sends `[a, b, c]`. Previously all calls wrapped arguments in an array. Update functions that matched `when Array` may need adjustment.
102
+
103
+ - **Command.all Output (Breaking)**: `Command.all` now emits `Message::All` objects instead of raw arrays. Previously emitted `[:tag, [results]]` (nested) or `[:tag, result1, result2]` (variadic). Now emits `Message::All` with `envelope`, `results`, and `nested` fields. Use hash pattern matching: `in { type: :all, envelope:, results:, nested: }`.
104
+
105
+ ### Fixed
106
+
107
+ - **Streaming Command Data Loss**: Fixed race condition where fast commands (e.g., `echo hello`) could lose stdout/stderr messages. The streaming reader threads were being killed immediately after the child process exited, before they could finish reading buffered output. Now the runtime joins the reader threads to ensure all data is processed before sending `:complete`.
108
+
109
+ ### Removed
110
+
111
+ - **CancellationToken Class**: Removed in favor of `Concurrent::Cancellation` from concurrent-ruby-edge.
112
+ - **CancellationToken::NONE**: Removed. Use `Command.uncancellable` factory instead.
113
+
114
+ ## [0.3.1] - 2026-01-11
115
+
116
+ ### Added
117
+
118
+ - **CancellationToken**: Cooperative cancellation mechanism for long-running custom commands. Commands check `cancelled?` periodically and stop gracefully when `cancel!` is called. Includes `CancellationToken::NONE` null object for commands that ignore cancellation.
119
+
120
+ - **Command::Custom Mixin**: Include in your class to mark it as a custom command. Provides `rooibos_command?` brand predicate and `rooibos_cancellation_grace_period` (default 2.0 seconds) for configuring cleanup time after cancellation.
121
+
122
+ - **Command::Outlet**: Messaging gateway for custom commands. Use `put(tag, *payload)` to send results back to the update function. Validates Ractor-shareability in debug mode.
123
+
124
+ - **Custom Command Dispatch**: Runtime now dispatches custom commands (objects with `rooibos_command?` returning true) in background threads. Commands receive an `Outlet` for messaging and a `CancellationToken` for cooperative shutdown.
125
+
126
+ - **Command.custom Factory**: Wraps lambdas/procs to give them unique identity for dispatch tracking. Each `Command.custom(callable)` call produces a distinct wrapper, enabling targeted cancellation. Accepts optional `grace_period:` to override the default 2.0 second cleanup window.
127
+
128
+ - **Command.cancel Factory**: Request cancellation of a running command. Returns a `Command::Cancel` sentinel that the runtime routes to the appropriate command's CancellationToken.
129
+
130
+ - **Runtime Cancellation Dispatch**: The runtime now handles `Command::Cancel` by signaling the target command's `CancellationToken`, enabling cooperative cancellation of long-running commands. Respects `rooibos_cancellation_grace_period`: waits for the grace period, then force-kills unresponsive threads. Use `Float::INFINITY` to never force-kill.
131
+
132
+ - **Graceful Shutdown**: On exit, runtime signals all active commands then respects each command's grace period. Commands with `Float::INFINITY` grace are waited on indefinitely (user has SIGKILL). Final queue messages are processed before returning.
133
+
134
+ - **Automatic Error Propagation**: Custom commands that raise unhandled exceptions now produce a `Command::Error` message instead of corrupting the TUI display. The runtime catches exceptions and pushes `Command::Error.new(command:, exception:)` to the queue. Pattern match on `Command::Error` in your update function to handle failures uniformly. Factory method `Command.error(command, exception)` is available for testing.
135
+
136
+ - **Command::System Cancellation**: Streaming shell commands now respect cooperative cancellation. When cancelled, sends `SIGTERM` for graceful shutdown, then `SIGKILL` if the child process doesn't exit. Prevents orphaned child processes from lingering after app exit.
137
+
138
+ ### Changed
139
+
140
+ ### Fixed
141
+
142
+ - **Ractor Enforcement is Debug-Only**: The Ractor-shareability check now only runs in debug mode (and automated tests). Production skips this check for performance, matching the original specification. Previously, the check ran unconditionally.
143
+
144
+ ### Removed
145
+
146
+ ## [0.3.0] - 2026-01-08
147
+
148
+ ### Added
149
+
150
+ - **Router DSL**: New `Rooibos::Router` module provides declarative routing for Fractal Architecture:
151
+ - `route :prefix, to: ChildBag` — declares a child bag route
152
+ - `keymap { key "q", -> { Command.exit } }` — declares keyboard handlers
153
+ - `keymap { key "x", handler, when: -> (m) { m.ready? } }` — guards (also: `if:`, `only:`, `guard:`, `unless:`, `except:`, `skip:`)
154
+ - `keymap { only when: guard do ... end }` — nested guard blocks apply to all keys within (also: `skip when: ...`)
155
+ - `mousemap { click -> (x, y) { ... } }` — declares mouse handlers
156
+ - `action :name, handler` — declares reusable actions for key/mouse handlers
157
+ - `from_router` — generates an UPDATE lambda from routes and handlers
158
+
159
+ - **Composition Helpers**: New helper methods for Fractal Architecture reduce boilerplate:
160
+ - `Rooibos.route(command, :prefix)` — wraps a command to route results to a child bag
161
+ - `Rooibos.delegate(message, :prefix, child_update, child_model)` — dispatches prefixed messages to child bags
162
+
163
+ - **Command Mapping**: `Command.map(inner_command, &mapper)` wraps a child command and transforms its result message. Essential for parent bags routing child command results.
164
+
165
+ - **Shortcuts Module**: `require "rooibos/shortcuts"` and `include Rooibos::Shortcuts` for short aliases:
166
+ - `Cmd.exit` — alias for `Command.exit`
167
+ - `Cmd.sh(command, tag)` — alias for `Command.system`
168
+ - `Cmd.map(command, &block)` — alias for `Command.map`
169
+
170
+ - **Sync Event Integration**: Runtime now handles `Event::Sync` from `RatatuiRuby::SyntheticEvents`. When a Sync event is received, the runtime waits for all pending async threads and processes their results before continuing. Use `inject_sync` in tests for deterministic async verification.
171
+
172
+ - **Streaming Command Output**: `Command.system` now accepts a `stream:` keyword argument. When `stream: true`, the runtime sends incremental messages (`[:tag, :stdout, line]`, `[:tag, :stderr, line]`) as output arrives, followed by `[:tag, :complete, {status:}]` when the command finishes. Invalid commands send `[:tag, :error, {message:}]`. Default behavior (`stream: false`) remains unchanged.
173
+
174
+ - **Custom Shell Modal Example**: Added `examples/app_fractal_dashboard/bags/custom_shell_modal.rb` demonstrating a 3-bag fractal architecture for a modal that runs arbitrary shell commands with streaming output. Features interleaved stdout/stderr, exit status indication, and Ractor-safe implementation using `tui.overlay` for opaque rendering.
175
+
176
+ ### Changed
177
+
178
+ - **Command Module Rename (Breaking)**: The `Cmd` module is now `Command` with Rubyish naming:
179
+ - `Cmd::Quit` → `Command::Exit` (use `Command.exit` factory)
180
+ - `Cmd::Exec` → `Command::System` (use `Command.system(cmd, tag)` factory)
181
+
182
+ ### Fixed
183
+
184
+ ### Removed
185
+
186
+ ## [0.2.0] - 2026-01-08
187
+
188
+ ### Added
189
+
190
+ - **The Elm Architecture (TEA)**: Implemented the core Model-View-Update (MVU) runtime. Use `Rooibos.run(model, view: ..., update: ...)` to start an interactive application with predictable state management.
191
+ - **Async Command System**: Side effects (database, HTTP, shell) are executed asynchronously in a thread pool. Results are dispatched back to the main loop as messages, ensuring the UI never freezes.
192
+ - **Ractor Safety Enforcement**: The runtime strictly enforces that all `Model` and `Message` objects are Ractor-shareable (deeply frozen). This guarantees thread safety by design and prepares for future parallelism.
193
+ - **Flexible Update Returns**: The `update` function supports multiple return signatures for developer ergonomics:
194
+ - `[Model, Cmd]` — Standard tuple.
195
+ - `Model` — Implicitly `[Model, Cmd::None]`.
196
+ - `Cmd` — Implicitly `[CurrentModel, Cmd]`.
197
+ - **Startup Commands**: `Rooibos.run` accepts an `init:` parameter to dispatch an initial command immediately after startup, useful for loading initial data without blocking the first render.
198
+ - **View Validation**: The `view` function must return a valid widget. Returning `nil` raises `RatatuiRuby::Error::Invariant` to catch bugs early.
199
+
200
+
201
+ ## [0.1.0] - 2026-01-07
202
+
203
+ ### Added
204
+
205
+ - **First Release**: Empty release of `rooibos`, a Ruby implementation of The Elm Architecture (TEA) for `ratatui_ruby`. Scaffolding generated by `ratatui_ruby-devtools`.
206
+
207
+ [Unreleased]: https://git.sr.ht/~kerrick/rooibos/refs/HEAD
208
+ [0.5.0]: https://git.sr.ht/~kerrick/rooibos/refs/v0.5.0
209
+ [0.4.0]: https://git.sr.ht/~kerrick/rooibos/refs/v0.4.0
210
+ [0.3.1]: https://git.sr.ht/~kerrick/rooibos/refs/v0.3.1
211
+ [0.3.0]: https://git.sr.ht/~kerrick/rooibos/refs/v0.3.0
212
+ [0.2.0]: https://git.sr.ht/~kerrick/rooibos/refs/v0.2.0
213
+ [0.2.0]: https://git.sr.ht/~kerrick/rooibos/refs/v0.2.0
214
+ [0.1.0]: https://git.sr.ht/~kerrick/rooibos/refs/v0.1.0