ratatui_ruby-tea 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98c01a74284a7bef17c45bd1fa409f9821f7a792e7803017dc4b33907bb41cbd
4
- data.tar.gz: 1b850234318bd7a69c06f765661afe659c8d8f7c04b14b7ef36a41031e84965f
3
+ metadata.gz: a1bf31fb6ff8bacdcd6f9c54e794e0c769f5c52afde2c5e4d78cbee48be36b12
4
+ data.tar.gz: 1bd65539c4eace89fc1d58d857d81fb9cc03187ea7ae770d4b5c4a051ae407a0
5
5
  SHA512:
6
- metadata.gz: 50f75f3c72061a42b4349f259974b0d6a0815c401a324726a50e786c573ae4c668b28a06f294c7f6627ab39fcbb7f2218f76b30f67467d6ca2785073182d8b68
7
- data.tar.gz: 7cd6873a151748dec33118de12c056878557648ec1561070a4c5a46c043d2d8b8e380d1657e78bc6f394bf4d73005ad5d5a6bc460b51b4788b4e2cd11fda0bc2
6
+ metadata.gz: 413f42942963ef06433b683b2cf3820db1a8cd679fcf263aaccc4cbd3780fa01b7bcc66842aeaad7606377380530215a9b6a07a145d6df56ecf34a5053c998cb
7
+ data.tar.gz: 8edaf06fbf149723ca70b2f27157c8c2d22536f060c94facfb6d904223b48dc21613daa0a130237548a40d705074ec22f90403e27855b1b578bad0e770f27040
data/.builds/ruby-3.2.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  image: archlinux
5
5
  packages:
@@ -13,9 +13,10 @@ packages:
13
13
  - gdbm
14
14
  - ncurses
15
15
  - libffi
16
+ - clang
16
17
  - git
17
18
  artifacts:
18
- - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.1.0.gem
19
+ - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.2.0.gem
19
20
  sources:
20
21
  - https://git.sr.ht/~kerrick/ratatui_ruby-tea
21
22
  tasks:
@@ -25,6 +26,7 @@ tasks:
25
26
  echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
26
27
  echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
27
28
  echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
29
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
30
  . ~/.buildenv
29
31
  export CI="true"
30
32
  cd ratatui_ruby-tea
data/.builds/ruby-3.3.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  image: archlinux
5
5
  packages:
@@ -13,9 +13,10 @@ packages:
13
13
  - gdbm
14
14
  - ncurses
15
15
  - libffi
16
+ - clang
16
17
  - git
17
18
  artifacts:
18
- - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.1.0.gem
19
+ - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.2.0.gem
19
20
  sources:
20
21
  - https://git.sr.ht/~kerrick/ratatui_ruby-tea
21
22
  tasks:
@@ -25,6 +26,7 @@ tasks:
25
26
  echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
26
27
  echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
27
28
  echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
29
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
30
  . ~/.buildenv
29
31
  export CI="true"
30
32
  cd ratatui_ruby-tea
data/.builds/ruby-3.4.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  image: archlinux
5
5
  packages:
@@ -13,9 +13,10 @@ packages:
13
13
  - gdbm
14
14
  - ncurses
15
15
  - libffi
16
+ - clang
16
17
  - git
17
18
  artifacts:
18
- - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.1.0.gem
19
+ - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.2.0.gem
19
20
  sources:
20
21
  - https://git.sr.ht/~kerrick/ratatui_ruby-tea
21
22
  tasks:
@@ -25,6 +26,7 @@ tasks:
25
26
  echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
26
27
  echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
27
28
  echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
29
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
30
  . ~/.buildenv
29
31
  export CI="true"
30
32
  cd ratatui_ruby-tea
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  image: archlinux
5
5
  packages:
@@ -13,9 +13,10 @@ packages:
13
13
  - gdbm
14
14
  - ncurses
15
15
  - libffi
16
+ - clang
16
17
  - git
17
18
  artifacts:
18
- - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.1.0.gem
19
+ - ratatui_ruby-tea/pkg/ratatui_ruby-tea-0.2.0.gem
19
20
  sources:
20
21
  - https://git.sr.ht/~kerrick/ratatui_ruby-tea
21
22
  tasks:
@@ -25,6 +26,7 @@ tasks:
25
26
  echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
26
27
  echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
27
28
  echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
29
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
28
30
  . ~/.buildenv
29
31
  export CI="true"
30
32
  cd ratatui_ruby-tea
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  inherit_from:
5
5
  - ./vendor/goodcop/base.yml
data/AGENTS.md CHANGED
@@ -15,19 +15,19 @@ Description: Part of the RatatuiRuby ecosystem.
15
15
 
16
16
  ### STRICT REQUIREMENTS
17
17
 
18
- - Every file MUST begin with an SPDX-compliant header. Use `AGPL-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.
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
19
  - Every line of Ruby MUST be covered by tests that would stand up to mutation testing.
20
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 `agent_rake` to ensure commit-readiness. See Tools for detailed instructions.
21
+ - **Pre-commit:** Use `bundle exec agent_rake` to ensure commit-readiness. See Tools for detailed instructions.
22
22
  - **Git Pager:** ALWAYS set `PAGER=cat` for ALL `git` commands (e.g., `PAGER=cat git diff`). This is mandatory.
23
23
 
24
24
  ### Tools
25
25
 
26
26
  - **NEVER** run `bundle exec rake` directly. **NEVER** run `bundle exec ruby -Ilib:test ...` directly.
27
- - **ALWAYS use `agent_rake`** (provided by the `ratatui_ruby-devtools` gem) for running tests, linting, or checking compilation.
27
+ - **ALWAYS use `bundle exec agent_rake`** (provided by the `ratatui_ruby-devtools` gem) for running tests, linting, or checking compilation.
28
28
  - **Usage:**
29
- - Runs default task (compile + test + lint): `agent_rake`
30
- - Runs specific task: `agent_rake test:ruby` (for example)
29
+ - Runs default task (compile + test + lint): `bundle exec agent_rake`
30
+ - Runs specific task: `bundle exec agent_rake test:ruby` (for example)
31
31
  - **Setup:** `bin/setup` must handle Bundler dependencies.
32
32
  - **Git:** ALWAYS set `PAGER=cat` with `git`. **THIS IS CRITICAL!**
33
33
 
@@ -50,7 +50,7 @@ Description: Part of the RatatuiRuby ecosystem.
50
50
 
51
51
  Before considering a task complete:
52
52
 
53
- 1. **Default Rake Task Passes:** Run `agent_rake` (no args). Confirm it passes with ZERO errors.
53
+ 1. **Default Rake Task Passes:** Run `bundle exec agent_rake` (no args). Confirm it passes with ZERO errors.
54
54
  2. **Documentation Updated:** If public APIs changed, update relevant docs.
55
55
  3. **Changelog Updated:** If public APIs changed, update CHANGELOG.md's **Unreleased** section.
56
56
  4. **Commit Message Suggested:** Include a suggested commit message block.
data/CHANGELOG.md CHANGED
@@ -15,4 +15,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
 
16
16
  ### Added
17
17
 
18
- - Initial release of ratatui_ruby-tea
18
+ ### Changed
19
+
20
+ ### Fixed
21
+
22
+ ### Removed
23
+
24
+ ## [0.2.0] - 2026-01-08
25
+
26
+ ### Added
27
+
28
+ - **The Elm Architecture (TEA)**: Implemented the core Model-View-Update (MVU) runtime. Use `RatatuiRuby::Tea.run(model, view: ..., update: ...)` to start an interactive application with predictable state management.
29
+ - **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.
30
+ - **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.
31
+ - **Flexible Update Returns**: The `update` function supports multiple return signatures for developer ergonomics:
32
+ - `[Model, Cmd]` — Standard tuple.
33
+ - `Model` — Implicitly `[Model, Cmd::None]`.
34
+ - `Cmd` — Implicitly `[CurrentModel, Cmd]`.
35
+ - **Startup Commands**: `RatatuiRuby::Tea.run` accepts an `init:` parameter to dispatch an initial command immediately after startup, useful for loading initial data without blocking the first render.
36
+ - **View Validation**: The `view` function must return a valid widget. Returning `nil` raises `RatatuiRuby::Error::Invariant` to catch bugs early.
37
+
38
+
39
+ ## [0.1.0] - 2026-01-07
40
+
41
+ ### Added
42
+
43
+ - **First Release**: Empty release of `ratatui_ruby-tea`, a Ruby implementation of The Elm Architecture (TEA) for `ratatui_ruby`. Scaffolding generated by `ratatui_ruby-devtools`.
44
+
45
+ [Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby-tea/refs/HEAD
46
+ [0.2.0]: https://git.sr.ht/~kerrick/ratatui_ruby-tea/refs/v0.2.0
47
+ [0.2.0]: https://git.sr.ht/~kerrick/ratatui_ruby-tea/refs/v0.2.0
48
+ [0.1.0]: https://git.sr.ht/~kerrick/ratatui_ruby-tea/refs/v0.1.0
data/README.md CHANGED
@@ -80,7 +80,46 @@ gem install ratatui_ruby-tea
80
80
 
81
81
  ## Usage
82
82
 
83
- _Because this gem is in pre-release, it lacks documentation. Please check the source files.
83
+ **ratatui_ruby-tea** uses the Model-View-Update (MVU) pattern. You provide an immutable model, a view function, and an update function.
84
+
85
+ <!-- SPDX-SnippetBegin -->
86
+ <!--
87
+ SPDX-FileCopyrightText: 2026 Kerrick Long
88
+ SPDX-License-Identifier: MIT-0
89
+ -->
90
+ <!-- SYNC:START:examples/verify_readme_usage/app.rb:mvu -->
91
+ ```ruby
92
+ Model = Data.define(:text)
93
+ MODEL = Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
94
+
95
+ VIEW = -> (model, tui) do
96
+ tui.paragraph(
97
+ text: model.text,
98
+ alignment: :center,
99
+ block: tui.block(
100
+ title: "My Ruby TUI App",
101
+ borders: [:all],
102
+ border_style: { fg: "cyan" }
103
+ )
104
+ )
105
+ end
106
+
107
+ UPDATE = -> (msg, model) do
108
+ if msg.q? || msg.ctrl_c?
109
+ RatatuiRuby::Tea::Cmd.quit
110
+ else
111
+ model
112
+ end
113
+ end
114
+
115
+ def run
116
+ RatatuiRuby::Tea.run(model: MODEL, view: VIEW, update: UPDATE)
117
+ end
118
+ ```
119
+ <!-- SYNC:END -->
120
+ <!-- SPDX-SnippetEnd -->
121
+
122
+ ![Hello Ratatui](./doc/images/verify_readme_usage.png)
84
123
 
85
124
  For a full tutorial, see [the Quickstart](./doc/getting_started/quickstart.md). For an explanation of the application architecture, see [Application Architecture](./doc/concepts/application_architecture.md).
86
125
 
@@ -117,7 +156,7 @@ Want to help develop **ratatui_ruby-tea**? Check out the [contribution guide on
117
156
 
118
157
  The library is [LGPL-3.0-or-later](./LICENSES/LGPL-3.0-or-later.txt): you can use it in proprietary applications, but you must share changes you make to **ratatui_ruby-tea** itself. Documentation snippets and widget examples are [MIT-0](./LICENSES/MIT-0.txt): copy and use them without attribution.
119
158
 
120
- Documentation is [CC-BY-SA-4.0](./LICENSES/CC-BY-SA-4.0.txt). Build tooling and full app examples are [AGPL-3.0-or-later](./LICENSES/AGPL-3.0-or-later.txt). See each file's SPDX comment for specifics.
159
+ Documentation is [CC-BY-SA-4.0](./LICENSES/CC-BY-SA-4.0.txt). Build tooling and full app examples are [LGPL-3.0-or-later](./LICENSES/LGPL-3.0-or-later.txt). See each file's SPDX comment for specifics.
121
160
 
122
161
  Some parts of this program are copied from other sources under appropriate reuse licenses, and the copyright belongs to their respective owners. See the [REUSE Specification – Version 3.3](https://reuse.software/spec-3.3/) for details.
123
162
 
data/REUSE.toml CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  version = 1
5
5
 
@@ -11,12 +11,12 @@ SPDX-License-Identifier = "CC0-1.0"
11
11
  [[annotations]]
12
12
  path = '**/snapshots/*.txt'
13
13
  SPDX-FileCopyrightText = "2026 Kerrick Long <me@kerricklong.com>"
14
- SPDX-License-Identifier = "AGPL-3.0-or-later"
14
+ SPDX-License-Identifier = "LGPL-3.0-or-later"
15
15
 
16
16
  [[annotations]]
17
17
  path = '**/snapshots/*.ansi'
18
18
  SPDX-FileCopyrightText = "2026 Kerrick Long <me@kerricklong.com>"
19
- SPDX-License-Identifier = "AGPL-3.0-or-later"
19
+ SPDX-License-Identifier = "LGPL-3.0-or-later"
20
20
 
21
21
  [[annotations]]
22
22
  path = 'doc/images/*'
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  #--
4
4
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
6
  #++
7
7
 
8
8
  require "bundler/gem_tasks"
data/Steepfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: LGPL-3.0-or-later
5
+
6
+ target :lib do
7
+ signature "sig"
8
+ check "lib"
9
+
10
+ library "pathname"
11
+ library "fileutils"
12
+ library "minitest"
13
+ end
@@ -0,0 +1,40 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Feature Priorities
7
+
8
+ This document outlines the critical next steps for `ratatui_ruby-tea`. Each item explains the context, the problem, and the solution, following our [Documentation Style](../ratatui_ruby/doc/contributors/documentation_style.md).
9
+
10
+ ## 1. Composition (Cmd.map)
11
+
12
+ **Context:** Real applications grow. You start with a file picker. Then a modal. Then a sidebar. Each component has its own model and update function.
13
+
14
+ **Problem:** The Tea architecture naturally isolates components. A parent model holds a child model. But when the child update function returns a message (like `:selected`), the parent `update` function only understands its own messages. It cannot "hear" the child. The architecture breaks at the boundary of the first file.
15
+
16
+ **Solution:** Implement `Cmd.map(cmd) { |child_msg| ... }`. This wraps the child's effect. When the effect completes, the runtime passes the result through the block, transforming the child's message into a parent's message. This restores the flow of data up the tree.
17
+
18
+ ## 2. Parallelism (Cmd.batch)
19
+
20
+ **Context:** Applications often need to do two things at once. You initialize the app. You need to load the config *and* fetch the latest data *and* start the tick timer.
21
+
22
+ **Problem:** The `update` function returns a single tuple `[Model, Cmd]`. It cannot return `[Model, Cmd1, Cmd2]`. Without a way to group them, you are forced to sequence independent operations, making the UI feel slow and linear.
23
+
24
+ **Solution:** Implement `Cmd.batch([cmd1, cmd2, ...])`. This command takes an array of commands and submits them all to the runtime. The runtime executes them in parallel (where possible) or concurrently.
25
+
26
+ ## 3. Serial Execution (Cmd.sequence)
27
+
28
+ **Context:** Some effects depend on others. You cannot read a file until you have downloaded it. You cannot query the database until you have opened the connection.
29
+
30
+ **Problem:** The Tea architecture relies on async messages. You send a command, and *eventually* you get a message. To chain actions, you must handle the first success message in `update`, then return the second command. This smears a single logical transaction across multiple independent `case` clauses, creating "callback hell" but in the shape of a state machine.
31
+
32
+ **Solution:** Implement `Cmd.sequence([cmd1, cmd2, ...])`. This command executes the first command. If successful, it runs the next. If any fail, it stops. Note: This assumes commands have a standard "success/failure" result shape, or simply runs them blindly. (Design decision required: does `sequence` wait for the message, or just the execution?)
33
+
34
+ ## 4. Time (Cmd.tick)
35
+
36
+ **Context:** Animations and real-time updates. A spinner rotating. A clock ticking. Evaluation metrics updating live.
37
+
38
+ **Problem:** The runtime blocks on input. If the user doesn't type or click, the screen stays frozen. You cannot implement a simple "Loading..." spinner because the frame never updates.
39
+
40
+ **Solution:** Implement `Cmd.tick(interval, tag)`. This command sleeps for the interval and then sends a message. The `update` function handles the message and returns the *same* tick command again. This creates a recursive loop, driving the frame rate independent of user input.
data/doc/custom.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
3
+ * SPDX-License-Identifier: LGPL-3.0-or-later
4
4
  */
5
5
 
6
6
  img {
Binary file
Binary file
@@ -0,0 +1,51 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # README Usage Verification
7
+
8
+ Verifies the primary usage example for the Tea gem.
9
+
10
+ This example exists as a documentation regression test. It ensures that the very first MVU pattern a user sees actually works.
11
+
12
+ ## Usage
13
+
14
+ <!-- SPDX-SnippetBegin -->
15
+ <!--
16
+ SPDX-FileCopyrightText: 2026 Kerrick Long
17
+ SPDX-License-Identifier: MIT-0
18
+ -->
19
+ <!-- SYNC:START:./app.rb:mvu -->
20
+ ```ruby
21
+ Model = Data.define(:text)
22
+ MODEL = Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
23
+
24
+ VIEW = -> (model, tui) do
25
+ tui.paragraph(
26
+ text: model.text,
27
+ alignment: :center,
28
+ block: tui.block(
29
+ title: "My Ruby TUI App",
30
+ borders: [:all],
31
+ border_style: { fg: "cyan" }
32
+ )
33
+ )
34
+ end
35
+
36
+ UPDATE = -> (msg, model) do
37
+ if msg.q? || msg.ctrl_c?
38
+ RatatuiRuby::Tea::Cmd.quit
39
+ else
40
+ model
41
+ end
42
+ end
43
+
44
+ def run
45
+ RatatuiRuby::Tea.run(model: MODEL, view: VIEW, update: UPDATE)
46
+ end
47
+ ```
48
+ <!-- SYNC:END -->
49
+ <!-- SPDX-SnippetEnd -->
50
+
51
+ [![verify_readme_usage](../../doc/images/verify_readme_usage.png)](../../README.md#usage)
@@ -0,0 +1,44 @@
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 "ratatui_ruby/tea"
12
+
13
+ class VerifyReadmeUsage
14
+ # [SYNC:START:mvu]
15
+ Model = Data.define(:text)
16
+ MODEL = Model.new(text: "Hello, Ratatui! Press 'q' to quit.")
17
+
18
+ VIEW = -> (model, tui) do
19
+ tui.paragraph(
20
+ text: model.text,
21
+ alignment: :center,
22
+ block: tui.block(
23
+ title: "My Ruby TUI App",
24
+ borders: [:all],
25
+ border_style: { fg: "cyan" }
26
+ )
27
+ )
28
+ end
29
+
30
+ UPDATE = -> (msg, model) do
31
+ if msg.q? || msg.ctrl_c?
32
+ RatatuiRuby::Tea::Cmd.quit
33
+ else
34
+ model
35
+ end
36
+ end
37
+
38
+ def run
39
+ RatatuiRuby::Tea.run(model: MODEL, view: VIEW, update: UPDATE)
40
+ end
41
+ # [SYNC:END:mvu]
42
+ end
43
+
44
+ VerifyReadmeUsage.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,70 @@
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)
@@ -0,0 +1,132 @@
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 "ratatui_ruby/tea"
12
+
13
+ # Demonstrates the Cmd.exec 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 WidgetCmdExec
27
+ Model = Data.define(:result, :loading, :last_command)
28
+ INITIAL = Model.new(
29
+ result: "Press a key to run a command...",
30
+ loading: false,
31
+ last_command: nil
32
+ )
33
+
34
+ VIEW = -> (model, tui) do
35
+ hotkey_style = tui.style(modifiers: [:bold, :underlined])
36
+ dim_style = tui.style(fg: :dark_gray)
37
+
38
+ # Styles
39
+ border_color = if model.loading
40
+ "yellow"
41
+ elsif model.result.start_with?("Error")
42
+ "red"
43
+ else
44
+ "cyan"
45
+ end
46
+
47
+ title = model.last_command ? "Output: #{model.last_command}" : "Cmd.exec Demo"
48
+ content_text = model.loading ? "Running command..." : model.result
49
+
50
+ # 1. Main Output Widget
51
+ output_widget = tui.paragraph(
52
+ text: content_text,
53
+ block: tui.block(
54
+ title:,
55
+ borders: [:all],
56
+ border_style: { fg: border_color },
57
+ padding: 1
58
+ )
59
+ )
60
+
61
+ # 2. Control Panel Widget
62
+ control_widget = tui.paragraph(
63
+ text: [
64
+ tui.text_line(spans: [
65
+ tui.text_span(content: "d", style: hotkey_style),
66
+ tui.text_span(content: ": Directory listing (ls -la) "),
67
+ tui.text_span(content: "u", style: hotkey_style),
68
+ tui.text_span(content: ": System info (uname -a)"),
69
+ ]),
70
+ tui.text_line(spans: [
71
+ tui.text_span(content: "f", style: hotkey_style),
72
+ tui.text_span(content: ": Force failure "),
73
+ tui.text_span(content: "s", style: hotkey_style),
74
+ tui.text_span(content: ": Sleep (3s) "),
75
+ tui.text_span(content: "q", style: hotkey_style),
76
+ tui.text_span(content: ": Quit"),
77
+ ]),
78
+ ],
79
+ block: tui.block(
80
+ title: "Controls",
81
+ borders: [:all],
82
+ border_style: dim_style
83
+ )
84
+ )
85
+
86
+ # Return the Root Layout Widget (Blueprint)
87
+ tui.layout(
88
+ direction: :vertical,
89
+ constraints: [
90
+ tui.constraint_fill(1),
91
+ tui.constraint_length(6),
92
+ ],
93
+ children: [
94
+ output_widget,
95
+ control_widget,
96
+ ]
97
+ )
98
+ end
99
+
100
+ UPDATE = -> (msg, model) do
101
+ case msg
102
+ # Handle command results
103
+ in [:got_output, { stdout:, status: 0 }]
104
+ [model.with(result: stdout.strip.freeze, loading: false), nil]
105
+ in [:got_output, { stderr:, status: }]
106
+ [model.with(result: "Error (exit #{status}): #{stderr.strip}".freeze, loading: false), nil]
107
+
108
+ # Handle key presses
109
+ in _ if msg.q? || msg.ctrl_c?
110
+ RatatuiRuby::Tea::Cmd.quit
111
+ in _ if msg.d?
112
+ [model.with(loading: true, last_command: "ls -la"), RatatuiRuby::Tea::Cmd.exec("ls -la", :got_output)]
113
+ in _ if msg.u?
114
+ [model.with(loading: true, last_command: "uname -a"), RatatuiRuby::Tea::Cmd.exec("uname -a", :got_output)]
115
+ in _ if msg.s?
116
+ cmd = "sleep 3 && echo 'Slept for 3s'"
117
+ [model.with(loading: true, last_command: cmd.freeze), RatatuiRuby::Tea::Cmd.exec(cmd, :got_output)]
118
+ in _ if msg.f?
119
+ # Intentional failure to demonstrate error handling
120
+ cmd = "ls /nonexistent_path_12345"
121
+ [model.with(loading: true, last_command: cmd.freeze), RatatuiRuby::Tea::Cmd.exec(cmd, :got_output)]
122
+ else
123
+ model
124
+ end
125
+ end
126
+
127
+ def run
128
+ RatatuiRuby::Tea.run(model: INITIAL, view: VIEW, update: UPDATE)
129
+ end
130
+ end
131
+
132
+ WidgetCmdExec.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,88 @@
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
+ module RatatuiRuby
9
+ module Tea
10
+ # Commands represent side effects.
11
+ #
12
+ # The MVU pattern separates logic from effects. Your update function returns a pure
13
+ # model transformation. Side effects go in commands. The runtime executes them.
14
+ #
15
+ # Commands produce **messages**, not callbacks. The +tag+ argument names the message
16
+ # so your update function can pattern-match on it. This keeps all logic in +update+
17
+ # and ensures messages are Ractor-shareable.
18
+ #
19
+ # === Examples
20
+ #
21
+ # # Terminate the application
22
+ # [model, Cmd.quit]
23
+ #
24
+ # # Run a shell command; produces [:got_files, {stdout:, stderr:, status:}]
25
+ # [model, Cmd.exec("ls -la", :got_files)]
26
+ #
27
+ # # No side effect
28
+ # [model, nil]
29
+ module Cmd
30
+ # Sentinel value for application termination.
31
+ #
32
+ # The runtime detects this before dispatching. It breaks the loop immediately.
33
+ Quit = Data.define
34
+
35
+ # Creates a quit command.
36
+ #
37
+ # Returns a sentinel the runtime detects to terminate the application.
38
+ #
39
+ # === Example
40
+ #
41
+ # def update(msg, model)
42
+ # case msg
43
+ # in { type: :key, code: "q" }
44
+ # [model, Cmd.quit]
45
+ # else
46
+ # [model, nil]
47
+ # end
48
+ # end
49
+ def self.quit
50
+ Quit.new
51
+ end
52
+
53
+ # Command to run a shell command via Open3.
54
+ #
55
+ # The runtime executes the command and produces a message:
56
+ # <tt>[tag, {stdout:, stderr:, status:}]</tt>
57
+ #
58
+ # The +status+ is the integer exit code (0 = success).
59
+ Exec = Data.define(:command, :tag)
60
+
61
+ # Creates a shell execution command.
62
+ #
63
+ # [command] Shell command string to execute.
64
+ # [tag] Symbol or class to tag the result message.
65
+ #
66
+ # When the command completes, the runtime sends
67
+ # <tt>[tag, {stdout:, stderr:, status:}]</tt> to update.
68
+ #
69
+ # === Example
70
+ #
71
+ # # Return this from update:
72
+ # [model.with(loading: true), Cmd.exec("ls -la", :got_files)]
73
+ #
74
+ # # Then handle it later:
75
+ # def update(msg, model)
76
+ # case msg
77
+ # in [:got_files, {stdout:, status: 0}]
78
+ # [model.with(files: stdout.lines), nil]
79
+ # in [:got_files, {stderr:, status:}]
80
+ # [model.with(error: stderr), nil]
81
+ # end
82
+ # end
83
+ def self.exec(command, tag)
84
+ Exec.new(command:, tag:)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,159 @@
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 "ratatui_ruby"
9
+
10
+ module RatatuiRuby
11
+ module Tea
12
+ # Runs the Model-View-Update event loop.
13
+ #
14
+ # Applications need a render loop. You poll events, update state, redraw. Every frame.
15
+ # The boilerplate is tedious and error-prone.
16
+ #
17
+ # This class handles the loop. You provide the model, view, and update. It handles the rest.
18
+ #
19
+ # Use it to build applications with predictable state.
20
+ #
21
+ # === Example
22
+ #
23
+ #--
24
+ # SPDX-SnippetBegin
25
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
26
+ # SPDX-License-Identifier: MIT-0
27
+ #++
28
+ # RatatuiRuby::Tea.run(
29
+ # model: { count: 0 }.freeze,
30
+ # view: ->(m, tui) { tui.paragraph(text: m[:count].to_s) },
31
+ # update: ->(msg, m) { msg.q? ? [m, Cmd.quit] : [m, nil] }
32
+ # )
33
+ #--
34
+ # SPDX-SnippetEnd
35
+ #++
36
+ class Runtime
37
+ # Starts the MVU event loop.
38
+ #
39
+ # Runs until the update function returns a <tt>Cmd.quit</tt> command.
40
+ #
41
+ # [model] Initial application state (immutable).
42
+ # [view] Callable receiving <tt>(model, tui)</tt>, returns a widget.
43
+ # [update] Callable receiving <tt>(msg, model)</tt>, returns <tt>[new_model, cmd]</tt> or just <tt>new_model</tt>.
44
+ # [init] Optional callable to run at startup. Returns a message for update.
45
+ def self.run(model:, view:, update:, init: nil)
46
+ validate_ractor_shareable!(model, "model")
47
+
48
+ # Execute init command synchronously if provided
49
+ if init
50
+ init_msg = init.call
51
+ result = update.call(init_msg, model)
52
+ model, _cmd = normalize_update_result(result, model)
53
+ validate_ractor_shareable!(model, "model")
54
+ end
55
+
56
+ queue = Queue.new
57
+
58
+ catch(:quit) do
59
+ RatatuiRuby.run do |tui|
60
+ loop do
61
+ tui.draw do |frame|
62
+ widget = view.call(model, tui)
63
+ validate_view_result!(widget)
64
+ frame.render_widget(widget, frame.area)
65
+ end
66
+
67
+ # 1. Handle user input (blocks up to 16ms)
68
+ msg = tui.poll_event
69
+
70
+ # If provided, handle the event
71
+ unless msg.is_a?(RatatuiRuby::Event::None)
72
+ result = update.call(msg, model)
73
+ model, cmd = normalize_update_result(result, model)
74
+ validate_ractor_shareable!(model, "model")
75
+ throw :quit if cmd.is_a?(Cmd::Quit)
76
+
77
+ dispatch(cmd, queue) if cmd
78
+ end
79
+
80
+ # 2. Check for background outcomes
81
+ until queue.empty?
82
+ begin
83
+ bg_msg = queue.pop(true)
84
+ result = update.call(bg_msg, model)
85
+ model, cmd = normalize_update_result(result, model)
86
+ validate_ractor_shareable!(model, "model")
87
+ throw :quit if cmd.is_a?(Cmd::Quit)
88
+
89
+ dispatch(cmd, queue) if cmd
90
+ rescue ThreadError
91
+ break
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ model
99
+ end
100
+
101
+ # Validates the view returned a widget.
102
+ #
103
+ # Views return widget trees. Returning +nil+ is a bug—you forgot to
104
+ # return something. For an intentionally empty screen, use TUI#clear.
105
+ private_class_method def self.validate_view_result!(widget)
106
+ return unless widget.nil?
107
+
108
+ raise RatatuiRuby::Error::Invariant,
109
+ "View returned nil. Return a widget, or use TUI#clear for an empty screen."
110
+ end
111
+
112
+ # Detects whether +result+ is a +[model, cmd]+ tuple, a plain model, or a Cmd alone.
113
+ #
114
+ # Returns +[model, cmd]+ in all cases.
115
+ private_class_method def self.normalize_update_result(result, previous_model)
116
+ return result if result.is_a?(Array) && result.size == 2 && valid_cmd?(result[1])
117
+ return [previous_model, result] if valid_cmd?(result)
118
+
119
+ [result, nil]
120
+ end
121
+
122
+ # Returns +true+ if +value+ is a valid command (+nil+ or a +Cmd+ type).
123
+ private_class_method def self.valid_cmd?(value)
124
+ value.nil? || value.class.name&.start_with?("RatatuiRuby::Tea::Cmd::")
125
+ end
126
+
127
+ # Validates an object is Ractor-shareable (deeply frozen).
128
+ #
129
+ # Models and messages must be shareable for future Ractor support.
130
+ # Mutable objects cause race conditions. Freeze your data.
131
+ private_class_method def self.validate_ractor_shareable!(object, name)
132
+ return if Ractor.shareable?(object)
133
+
134
+ raise RatatuiRuby::Error::Invariant,
135
+ "#{name.capitalize} is not Ractor-shareable. Use Ractor.make_shareable or Object#freeze."
136
+ end
137
+
138
+ # Dispatches a command to the worker pool.
139
+ #
140
+ # Spawns a thread for async commands. Pushes result to +queue+.
141
+ # Handles nested commands (Batch, Sequence) recursively.
142
+ private_class_method def self.dispatch(cmd, queue)
143
+ case cmd
144
+ when Cmd::Exec
145
+ Thread.new do
146
+ require "open3"
147
+ stdout, stderr, status = Open3.capture3(cmd.command)
148
+ msg = [cmd.tag, { stdout:, stderr:, status: status.exitstatus }]
149
+ queue << Ractor.make_shareable(msg)
150
+ rescue => e
151
+ # Should we send an error message? For now, crash in debug, ignore in prod?
152
+ # Better to rely on Open3 not raising for standard execution.
153
+ end
154
+ # TODO: Add Batch, Sequence, NetHttp
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -9,6 +9,6 @@ module RatatuiRuby # :nodoc: Documented in the ratatui_ruby gem.
9
9
  module Tea
10
10
  # The version of this gem.
11
11
  # See https://semver.org/spec/v2.0.0.html
12
- VERSION = "0.1.0"
12
+ VERSION = "0.2.0"
13
13
  end
14
14
  end
@@ -6,16 +6,25 @@
6
6
  #++
7
7
 
8
8
  require_relative "tea/version"
9
+ require_relative "tea/cmd"
10
+ require_relative "tea/runtime"
9
11
 
10
- # Entry point for the ratatui_ruby-tea gem.
11
- #
12
- # Ruby libraries benefit from a clear namespace. Gems need a central module.
13
- #
14
- # This module serves as the namespace root. All classes and utilities live here.
15
- #
16
- # Require this file to load the library.
17
12
  module RatatuiRuby # :nodoc: Documented in the ratatui_ruby gem.
18
- # Namespace for library functionality.
13
+ # The Elm Architecture for RatatuiRuby.
14
+ #
15
+ # Building TUI applications means managing state, events, and rendering. Mixing them leads to
16
+ # spaghetti code. Bugs hide in the tangles.
17
+ #
18
+ # This module implements The Elm Architecture (TEA). It separates your application into three
19
+ # pure functions: model, view, and update. The runtime handles the rest.
20
+ #
21
+ # Use it to build applications with predictable, testable state management.
19
22
  module Tea
23
+ # Starts the MVU event loop.
24
+ #
25
+ # Convenience delegator to Runtime.run. See Runtime for full documentation.
26
+ def self.run(...)
27
+ Runtime.run(...)
28
+ end
20
29
  end
21
30
  end
data/mise.toml CHANGED
@@ -1,7 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  [tools]
5
5
  ruby = "4.0.0"
6
+ rust = "1.91.1"
6
7
  python = "3.12"
7
8
  pre-commit = "latest"
@@ -0,0 +1,19 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: MIT-0
4
+ #++
5
+
6
+ class VerifyReadmeUsage
7
+ class Model < Data
8
+ attr_reader text: String
9
+ def self.new: (text: String) -> instance
10
+ end
11
+
12
+ MODEL: Model
13
+
14
+ VIEW: ^(Model, RatatuiRuby::TUI) -> untyped
15
+
16
+ UPDATE: ^(RatatuiRuby::Event, Model) -> (Model | [Model, RatatuiRuby::Tea::Cmd::Quit])
17
+
18
+ def run: () -> void
19
+ end
@@ -0,0 +1,26 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: MIT-0
4
+ #++
5
+
6
+ class WidgetCmdExec
7
+ class Model < Data
8
+ attr_reader result: String
9
+ attr_reader loading: bool
10
+ attr_reader last_command: String?
11
+
12
+ def self.new: (result: String, loading: bool, last_command: String?) -> instance
13
+ def with: (**untyped) -> instance
14
+ end
15
+
16
+ INITIAL: Model
17
+
18
+ VIEW: ^(Model, RatatuiRuby::TUI) -> untyped
19
+
20
+ # Msg can be Event or [:got_output, Hash]
21
+ type msg = RatatuiRuby::Event | [Symbol, Hash[Symbol, untyped]]
22
+
23
+ UPDATE: ^(msg, Model) -> (Model | [Model, RatatuiRuby::Tea::Cmd::execution?])
24
+
25
+ def run: () -> void
26
+ end
@@ -0,0 +1,32 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module RatatuiRuby
7
+ module Tea
8
+ module Cmd
9
+ # Sentinel value for application termination.
10
+ class Quit < Data
11
+ def self.new: () -> instance
12
+ end
13
+
14
+ # Command to run a shell command via Open3.
15
+ class Exec < Data
16
+ attr_reader command: String
17
+ attr_reader tag: Symbol | Class
18
+
19
+ def self.new: (command: String, tag: (Symbol | Class)) -> instance
20
+ end
21
+
22
+ # Union type for all valid commands
23
+ type execution = Exec
24
+
25
+ # Creates a quit command.
26
+ def self.quit: () -> Quit
27
+
28
+ # Creates a shell execution command.
29
+ def self.exec: (String command, (Symbol | Class) tag) -> Exec
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module RatatuiRuby
7
+ module Tea
8
+ class Runtime
9
+ # Starts the MVU event loop.
10
+ def self.run: [M, Msg] (
11
+ model: M,
12
+ view: ^(M model, RatatuiRuby::TUI tui) -> untyped,
13
+ update: ^(Msg msg, M model) -> (M | [M, Cmd::execution?] | [M, Cmd::Quit] | [M, nil]),
14
+ ?init: ^() -> Msg
15
+ ) -> M
16
+
17
+ private
18
+
19
+ def self.validate_view_result!: (untyped widget) -> void
20
+ def self.normalize_update_result: [M] (untyped result, M previous_model) -> [M, untyped]
21
+ def self.valid_cmd?: (untyped value) -> bool
22
+ def self.validate_ractor_shareable!: (untyped object, String name) -> void
23
+ def self.dispatch: (untyped cmd, Queue queue) -> void
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ module RatatuiRuby
7
+ module Tea
8
+ # See RatatuiRuby::Tea::Runtime.run
9
+ def self.run: [M, Msg] (
10
+ model: M,
11
+ view: ^(M model, RatatuiRuby::TUI tui) -> untyped,
12
+ update: ^(Msg msg, M model) -> (M | [M, Cmd::execution?] | [M, Cmd::Quit] | [M, nil]),
13
+ ?init: ^() -> Msg
14
+ ) -> M
15
+ end
16
+ end
@@ -1,6 +1,6 @@
1
1
  <%#
2
2
  SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: AGPL-3.0-or-later
3
+ SPDX-License-Identifier: LGPL-3.0-or-later
4
4
  %>
5
5
  <!DOCTYPE html>
6
6
  <html lang="en">
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  image: archlinux
5
5
  packages:
@@ -13,9 +13,7 @@ packages:
13
13
  - gdbm
14
14
  - ncurses
15
15
  - libffi
16
- <%- if has_rust -%>
17
16
  - clang
18
- <%- end -%>
19
17
  - git
20
18
  artifacts:
21
19
  - <%= gem_name %>/pkg/<%= gem_filename %>
@@ -28,9 +26,7 @@ tasks:
28
26
  echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
29
27
  echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
30
28
  echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
31
- <%- if has_rust -%>
32
29
  echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
33
- <%- end -%>
34
30
  . ~/.buildenv
35
31
  export CI="true"
36
32
  cd <%= gem_name %>
@@ -41,9 +37,6 @@ tasks:
41
37
  mise reshim
42
38
  mise x -- bundle config set --local frozen 'true'
43
39
  mise x -- bundle install
44
- <%- if has_rust -%>
45
- mise x -- bundle exec rake compile
46
- <%- end -%>
47
40
  - test: |
48
41
  . ~/.buildenv
49
42
  cd <%= gem_name %>
@@ -1,7 +1,7 @@
1
1
  <!--
2
2
  SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
3
 
4
- SPDX-License-Identifier: AGPL-3.0-or-later
4
+ SPDX-License-Identifier: LGPL-3.0-or-later
5
5
  -->
6
6
 
7
7
  <!DOCTYPE html>
@@ -1,5 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  - "3.2"
5
5
  - "3.3"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratatui_ruby-tea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kerrick Long
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ratatui_ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.9'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.9'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: ostruct
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -77,16 +91,31 @@ files:
77
91
  - README.md
78
92
  - REUSE.toml
79
93
  - Rakefile
94
+ - Steepfile
80
95
  - doc/concepts/application_architecture.md
81
96
  - doc/concepts/application_testing.md
97
+ - doc/contributors/priorities.md
82
98
  - doc/custom.css
83
99
  - doc/getting_started/quickstart.md
84
100
  - doc/images/.gitkeep
101
+ - doc/images/verify_readme_usage.png
102
+ - doc/images/widget_cmd_exec.png
85
103
  - doc/index.md
104
+ - examples/verify_readme_usage/README.md
105
+ - examples/verify_readme_usage/app.rb
106
+ - examples/widget_cmd_exec/README.md
107
+ - examples/widget_cmd_exec/app.rb
86
108
  - exe/.gitkeep
87
109
  - lib/ratatui_ruby/tea.rb
110
+ - lib/ratatui_ruby/tea/cmd.rb
111
+ - lib/ratatui_ruby/tea/runtime.rb
88
112
  - lib/ratatui_ruby/tea/version.rb
89
113
  - mise.toml
114
+ - sig/examples/verify_readme_usage/app.rbs
115
+ - sig/examples/widget_cmd_exec/app.rbs
116
+ - sig/ratatui_ruby/tea.rbs
117
+ - sig/ratatui_ruby/tea/cmd.rbs
118
+ - sig/ratatui_ruby/tea/runtime.rbs
90
119
  - tasks/example_viewer.html.erb
91
120
  - tasks/resources/build.yml.erb
92
121
  - tasks/resources/index.html.erb