Skip to content

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 07 May 13:20
· 122 commits to main since this release
v0.9.0
eea75cf

Added

  • Property-based tests for the :intents runtime opt. New test/ex_ratatui/property/intents_property_test.exs (async: true) with two properties under the 4-tuple {:cell_session, cs, cell_writer_fn, intent_writer_fn} transport tag: (1) for any mount-intent list and any sequence of handle_info-supplied batches, the writer receives every intent in concat order with no reordering, drops, or extras; (2) empty intents: [] batches never fire on the writer regardless of how many sequential empty handle_info calls a TUI makes. Complements the existing scenario unit tests in ExRatatui.Server.IntentsTest (mount-time, handle_event, handle_info, stop-with-intent, drop-without-writer, shape validation) — together they pin the full intent contract. Reuses the existing ExRatatui.Test.ServerApps.Intents fixture.

  • Documented :intents runtime opt across the public surface. ExRatatui.App's moduledoc now has a dedicated Runtime opts section listing every key (:commands, :intents, :render?, :trace?) with types, defaults, scope (callback vs reducer), and the "intents from {:stop, ...} fire before the server exits" guarantee. The t:callback_opts/0 typedoc points at it; a new t:intent/0 typedoc names the opaque-term contract. The c:ExRatatui.App.handle_event/2 and c:ExRatatui.App.handle_info/2 callback typespecs now include the {:noreply | :stop, state, callback_opts} 3-tuple variants — they were always accepted by the runtime but the typespecs declared only the 2-tuple shape, hiding the feature from Dialyzer and HexDocs. Their @doc strings now point at the Runtime opts section. The Callback Runtime guide has a new Runtime opts section between handle_info/2 and Error Handling. The Reducer Runtime guide's existing Runtime Options table grew an intents: row with a follow-up Intents subsection that names the consumer-defined vocabulary contract and the transport-portability story. The Cell Sessions guide gained a Driving an ExRatatui.App over a CellSession section walking through the 3-tuple vs 4-tuple transport shapes, sample cell_writer_fn and intent_writer_fn definitions, and the lifecycle the runtime drives. End-user code is unchanged; this is documentation of the existing feature.

  • ExRatatui.CellSession — non-terminal rendering primitive — sibling of ExRatatui.Session for consumers that aren't terminals (Phoenix LiveView painting <span> cells, embedded framebuffers, screenshot tools, headless tests). Backed by ratatui's TestBackend, it exposes the cell buffer directly instead of ANSI bytes. Same widget tree, input parser, and draw/2 / resize/3 / feed_input/2 / close/1 lifecycle as Session; the only API divergence is take_output/1 being replaced by take_cells/1 (full snapshot) and take_cells_diff/1 (cells that changed since the last diff call). Cells are %{row, col, symbol, fg, bg, modifiers, skip} in row-major order, with colors and modifiers using the same ExRatatui.Style vocabulary as the rest of the library. take_cells_diff/1 returns a full payload on its first call, after resize/3, and after reconstruction; otherwise only cells that differ structurally. Adds 9 NIFs, the ExRatatui.CellSession{,.Cell,.Snapshot,.Diff} modules, a Rendering to Non-Terminal Surfaces guide, and a headless cell_dump.exs example.

  • :intents runtime opt + {:cell_session, cs, cell_writer_fn, intent_writer_fn} 4-tuple transport tagExRatatui.App callbacks can now return {:ok, state, intents: [...]} from mount/1 and {:noreply | :stop, state, intents: [...]} from handle_event/2 / handle_info/2. Intents are opaque to ex_ratatui — they're forwarded verbatim to the transport's intent_writer_fn (the optional 4th element of the cell_session transport tag) in the order they were emitted. phoenix_ex_ratatui consumes this to dispatch inter-page navigation ({:navigate, "/path"}push_navigate, {:patch, "/path"}push_patch, {:redirect, "/url"}redirect); other consumers can define their own intent vocabulary. Transports that don't supply an intent writer (the existing 3-tuple cell_session shape, plus :local / :session / :distributed_server) silently drop intents — apps stay portable across transports. Intents from {:stop, state, intents: ...} transitions fire BEFORE the server exits, so a TUI can return {:stop, state, intents: [{:redirect, "/login"}]} and trust the redirect reaches the consumer before the linked-server EXIT propagates.

  • {:cell_session, cell_session, cell_writer_fn} Server transport tag — ExRatatui.Server now accepts a fourth :transport shape (alongside :local, :session, and :distributed_server) that drives an ExRatatui.App against a CellSession instead of a byte-stream Session. On every render the Server calls CellSession.draw/2, then CellSession.take_cells_diff/1, then hands the resulting %CellSession.Diff{} to the user-supplied cell_writer_fn — same call shape as the byte-stream :session transport, just a different payload type. ExRatatui.Transport.start_server/1 accepts the new shape unchanged via the t:server_transport/0 union; the t:cell_writer_fn/0 type lives next to the existing t:writer_fn/0. Mount opts are augmented identically to the byte-stream path: opts[:transport] = :cell_session, opts[:width] / opts[:height] populated from CellSession.size/1. Telemetry mirrors the existing taxonomy: [:ex_ratatui, :transport, :connect] and [:ex_ratatui, :session, :lifecycle, :open] fire on init with transport: :cell_session; [:ex_ratatui, :session, :lifecycle, :close] and [:ex_ratatui, :transport, :disconnect] fire on terminate/2. Resize semantics match :session exactly — the transport must call CellSession.resize/3 before forwarding {:ex_ratatui_resize, w, h} to the Server, then the Server updates the cached size and dispatches a %Event.Resize{} to the App; the next render's diff payload after a resize is always full (prior baseline at the old area is no longer comparable).