Skip to content

O3: positron-ratatui — terminal Renderer + Host loop (outlier A)#7

Merged
joelteply merged 2 commits into
mainfrom
feat/positron-ratatui
Jul 3, 2026
Merged

O3: positron-ratatui — terminal Renderer + Host loop (outlier A)#7
joelteply merged 2 commits into
mainfrom
feat/positron-ratatui

Conversation

@joelteply

Copy link
Copy Markdown
Contributor

O3 — the first real stateful Renderer + Host event loop

Validates positron's contract against a genuinely different type Output than counter-cli's String: real terminal cells (any ratatui Widget), drawn into a ratatui Buffer. This is outlier A in the outlier-validation discipline — the wgpu GPU renderer (O4) is outlier B.

What's here (positron-ratatui, three single-purpose pieces)

  • render_to_buffer — project a ViewState through a Renderer into a headless Buffer. The primitive both loops and every test build on.
  • TerminalHost — the Host impl: state-down via on_state, event-up via a boxed key map. Owns no terminal I/O, so the state/event contract is unit-testable without a TTY (render_current renders straight into a Buffer).
  • drive (headless, generic over BackendTestBackend in tests) + run_crossterm (thin live-TTY wrapper). Both share one dispatch step and one redraw — the state-down/event-up decision lives in exactly one place.

Define once, project many

examples/counter_tui.rs renders the same Counter ViewState the tests render headlessly — nothing about the view changes between a TestBackend assertion and a real terminal.

Tests (one mod, each with // what this catches:)

  1. a Renderer whose Output is real cells (not a String) projects into a Buffer at the expected position — the whole reason this is outlier A
  2. TerminalHost honors state-down (store + render) and event-up (key→command; unmapped→None)
  3. drive runs the full state-down/event-up cycle headlessly via TestBackend

Notes

  • ratatui 0.29 pinned in workspace deps; consumers use ratatui::crossterm (its re-export) → no separate-crossterm version-skew.
  • Docs: ARCHITECTURE.md + README mark O1–O3 landed, O4 (positron-wgpu, outlier B) next.

Validated: cargo fmt --check clean, cargo clippy --all-targets -- -D warnings clean, cargo test -p positron-ratatui = 3 passed, example builds.

🤖 Generated with Claude Code

joelteply and others added 2 commits July 2, 2026 22:02
O3 of the define-once roadmap: the first real stateful `Renderer` + `Host`
event loop, validating positron's contract against a genuinely different
`type Output` than counter-cli's `String` — real terminal *cells* (any
ratatui `Widget`), drawn into a ratatui `Buffer`.

Three single-purpose pieces:
- `render_to_buffer` — project a `ViewState` through a `Renderer` into a
  headless `Buffer`; the primitive both loops and every test build on.
- `TerminalHost` — the `Host` impl: state-down via `on_state`, event-up via
  a boxed key map. Owns no terminal I/O, so the state/event contract is
  unit-testable without a TTY.
- `drive` (headless, generic over `Backend` → `TestBackend` in tests) +
  `run_crossterm` (thin live-TTY wrapper). Both share one dispatch `step`
  and one `redraw`, so the state-down/event-up decision lives in one place.

The `examples/counter_tui.rs` demo renders the *same* `Counter` `ViewState`
the tests render headlessly — nothing about the view changes between a
`TestBackend` assertion and a real terminal ("define once, project many").

ratatui 0.29 pinned in workspace deps; consumers use `ratatui::crossterm`
(its re-export) so there's no separate-crossterm version-skew risk.

Docs: ARCHITECTURE.md + README mark O1–O3 landed, O4 (positron-wgpu,
outlier B) next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…nly gating

Two review nits from PR #7, both fail-loud correctness:

- run_crossterm restored the terminal with `?`, so a restore failure
  would mask the loop's root-cause error. Now `result.and(restore)`
  surfaces the loop error first; a restore error only wins when the
  loop succeeded. Fail loud and NAME the cause.
- The live-loop Press/quit/resize gating was untested and the doc
  overclaimed it as exercised by `drive`. Extracted the pure decision
  into `classify(event, quit_on) -> LoopAction` so it is unit-tested
  headlessly (Press dispatches, Release is ignored, quit leaves, resize
  redraws, non-key ignored). Only the blocking `event::read` I/O is now
  genuinely TTY-bound; the doc says exactly that.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply joelteply merged commit 8311514 into main Jul 3, 2026
2 checks passed
@joelteply joelteply deleted the feat/positron-ratatui branch July 3, 2026 03:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant