Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- Quick-start app entry points — `Thaum.app do … end` builds and runs an app from a block (an ordinary class body) in place of defining a named class, and `SomeApp.run` builds and runs an instance (forwarding the same options as `Thaum.run`). `Thaum.run(app)` remains the primitive for apps that need constructor arguments.
- Dev log console — `Thaum.log` writes a debug side channel to a file (`Thaum.run(app, log: path)`), so you can log without corrupting the TUI (stdout is the UI). Logger-style API (`debug`/`info`/`warn`/`error`, lazy block form, exception formatting); off by default; truncates on open. While a log is active, framework-internal warnings route to it instead of stderr — `safe_invoke` handler exceptions at ERROR, emit-guard warnings at WARN. Companion examples `log_reader.rb` (tails and colorizes the file live) and `log_generator.rb`. See `docs/logging.md`.

## [0.2.1] - 2026-06-17
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,27 @@ Requires Ruby 3.2 or newer.

## Usage

The `Hello World` example:
The quickest start — `Thaum.app` builds and runs an app from a block, in place
of defining a named class. The block is an ordinary class body:

```ruby
require "thaum"

Thaum.app do
def partition
vertical do
region(height: :fill) { Thaum::Text.new(content: "Hello World!", align: :center) }
end
end

def on_key(event)
quit if event.key == :escape
end
end
```

For a longer-lived app, define a class and start it with `.run`. `include
Thaum::App` adds a `.run` class method:

```ruby
require "thaum"
Expand All @@ -63,16 +83,15 @@ class HelloWorldApp
end

def partition
vertical do
region(height: :fill) { @greeting }
end
vertical { region(height: :fill) { @greeting } }
end
end

Thaum.run(HelloWorldApp.new)

HelloWorldApp.run
```

`Thaum.run(app)` remains the primitive — use it directly when an app needs constructor arguments, e.g. `Thaum.run(LogReaderApp.new(path))`.

## Examples

The `examples/` directory has a runnable demo for every shipped sigil plus a few composed apps:
Expand Down
2 changes: 1 addition & 1 deletion examples/checkbox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ def partition
end
end

Thaum.run(CheckboxApp.new)
CheckboxApp.run
2 changes: 1 addition & 1 deletion examples/counter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ def partition
end
end

Thaum.run(CounterApp.new)
CounterApp.run
23 changes: 8 additions & 15 deletions examples/hello_world.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
# frozen_string_literal: true

# Usage: bundle exec ruby examples/hello_world.rb
# A minimal "Hello World" app. Press esc to quit.
# A minimal "Hello World" app using Thaum.app in place of a named class.
# Press esc to quit.

$LOAD_PATH.unshift File.expand_path("../lib", __dir__)

require "thaum"

class HelloWorldApp
include Thaum::App

def on_key(event)
quit if event.key == :escape
end

def initialize
@greeting = Thaum::Text.new(content: "Hello World!", align: :center)
end

Thaum.app do
def partition
vertical do
region(height: :fill) { @greeting }
region(height: :fill) { Thaum::Text.new(content: "Hello World!", align: :center) }
end
end
end

Thaum.run(HelloWorldApp.new)
def on_key(event)
quit if event.key == :escape
end
end
2 changes: 1 addition & 1 deletion examples/layout_demo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,4 @@ def middle
end
end

Thaum.run(LayoutDemoApp.new)
LayoutDemoApp.run
2 changes: 1 addition & 1 deletion examples/log_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ def partition
end
end

Thaum.run(LogGeneratorApp.new, log: ARGV[0] || "thaum.log") if __FILE__ == $PROGRAM_NAME
LogGeneratorApp.run(log: ARGV[0] || "thaum.log") if __FILE__ == $PROGRAM_NAME
2 changes: 1 addition & 1 deletion examples/modal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ def on_event(event)
end
end

Thaum.run(ModalApp.new)
ModalApp.run
2 changes: 1 addition & 1 deletion examples/mouse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ def partition
end
end

Thaum.run(MouseApp.new)
MouseApp.run
2 changes: 1 addition & 1 deletion examples/progress_bar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,4 @@ def partition
end
end

Thaum.run(ProgressApp.new)
ProgressApp.run
2 changes: 1 addition & 1 deletion examples/scroll_view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ def partition
end
end

Thaum.run(ScrollViewApp.new)
ScrollViewApp.run
2 changes: 1 addition & 1 deletion examples/spinner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ def spinner_row(spinner:, label:)
end
end

Thaum.run(SpinnerApp.new)
SpinnerApp.run
2 changes: 1 addition & 1 deletion examples/status_bar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ def segments
end
end

Thaum.run(StatusBarApp.new)
StatusBarApp.run
2 changes: 1 addition & 1 deletion examples/stopwatch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,4 @@ def cycle_theme
end
end

Thaum.run(StopwatchApp.new, tick: 0.05)
StopwatchApp.run(tick: 0.05)
2 changes: 1 addition & 1 deletion examples/tabs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ def partition
end
end

Thaum.run(TabsApp.new)
TabsApp.run
2 changes: 1 addition & 1 deletion examples/text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,4 @@ def partition
end
end

Thaum.run(TextApp.new)
TextApp.run
2 changes: 1 addition & 1 deletion examples/theme_picker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ def partition
end
end

Thaum.run(ThemePickerApp.new)
ThemePickerApp.run
2 changes: 1 addition & 1 deletion examples/todo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,4 @@ def press(label)
end
end

Thaum.run(TodoApp.new)
TodoApp.run
23 changes: 23 additions & 0 deletions lib/thaum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,27 @@ def self.safe_invoke(label)
def self.run(app, tick: 0.1, threads: 4, log: nil)
RunLoop.run(app:, tick:, threads:, log:)
end

# Quick-start: build an anonymous Thaum::App from the block body and run it,
# in place of defining a named class. The block is an ordinary class body —
# define `partition` and handlers with `def`:
#
# Thaum.app do
# def partition
# vertical { region(height: :fill) { Thaum::Text.new(content: "Hello") } }
# end
#
# def on_key(event)
# quit if event.key == :escape
# end
# end
#
# Forwards the same options as Thaum.run.
def self.app(**, &block)
raise ArgumentError, "Thaum.app requires a block" unless block

klass = Class.new { include App }
klass.class_eval(&block)
klass.run(**)
end
end
9 changes: 9 additions & 0 deletions lib/thaum/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ module App
include Concerns::Modal
include Concerns::TabNavigation

def self.included(base) = base.extend(ClassMethods)

# Class methods added on include.
module ClassMethods
# Build and run an instance: `SomeApp.run(log: "/path")`. Forwards the
# same options as Thaum.run.
def run(**) = Thaum.run(new, **)
end

attr_reader :in_on_update, :modal_sigil, :modal_rect

# --- Quit ---
Expand Down
19 changes: 19 additions & 0 deletions test/test_app_dsl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require "test_helper"

# Quick-start entry points for Thaum::App: the `.run` class method and
# `Thaum.app { }`.
class TestAppDSL < Minitest::Test
def test_include_adds_run_class_method
klass = Class.new { include Thaum::App }
assert_respond_to klass, :run
end

# The happy path enters the blocking run loop (no headless harness yet, and
# the suite doesn't stub), so it's exercised by examples/hello_world.rb. Here
# we only pin the missing-block guard, which needs no terminal.
def test_app_requires_a_block
assert_raises(ArgumentError) { Thaum.app }
end
end