Skip to content

feat(tui): extract a typed presentation seam with an opt-in Next renderer - #426

Open
BunsDev wants to merge 1 commit into
mainfrom
feat/408-presentation-seam
Open

BunsDev wants to merge 1 commit into
mainfrom
feat/408-presentation-seam

Conversation

@BunsDev

@BunsDev BunsDev commented Oct 31, 2026

Copy link
Copy Markdown
Member

Summary

Extract the typed presentation seam for TUI v2 (#408): runtime events scoped to session generations, a reducer step that returns its external effects, an interactive-only renderer selector and an opt-in Next renderer over a shared projection. Classic stays the default and renders unchanged.

Context

Changes

  • Session generations. Each interactive query worker now tags its events with the SessionGeneration it was spawned under. Clear, rewind (command and in-App) and resume advance the generation. Presentation discards stale events, so late output from a replaced conversation no longer mutates the new one. Bridge and relay consumers still see every event.
    • Before: one untagged channel served every turn, and events left behind at turn end were applied after /clear or /resume.
  • Reducer split. App::apply_query_event is the reducer step. The completion bell is returned as PresentationEffect::RingBell, and the loop performs it in the same drain step. handle_query_event keeps its existing behavior for other callers.
  • Bounded delivery. A queue of 1024 now sits between the per-turn forwarders and the loop. Forwarders wait for space and never drop events.
    • The query loop's own per-turn buffer stays unbounded, because run_query_loop takes an UnboundedSender. Bounding the producer needs a query-crate change and is documented as such.
  • Selector. COVEN_CODE_UI=classic|next is read only on the interactive route. Unset or empty selects Classic.
  • Next renderer (preview): one transcript, a status row and a composer, drawn from TranscriptView.
    • Any frame where a surface owns input is drawn entirely by Classic, so no choice is ever invisible. That covers permission requests, every overlay/picker/dialog in any_modal_open, the familiar switcher, the effort picker and slash suggestions. An error modal is handled the same way.
    • Next clears Classic's mouse hit-test geometry. It does not implement scrollback (PageUp) yet.

Validation

Failing-first runs:

  • Before implementation, 4 new tests failed for the intended reasons: 2 selector tests, the stale-delta test ("stale delta leaked into new conversation") and the bell-effect test.
  • After independent review, restoring the old Next predicate makes next_hands_every_input_owning_surface_to_classic fail on the familiar switcher and next_draws_permission_requests_through_classic fail. Both pass with the fix.

Commands, run from src-rust/ on the final tree:

  • git diff --check
  • cargo fmt --all -- --check
  • cargo check --workspace --locked
  • cargo clippy --workspace --all-targets --locked -- -D warnings
  • cargo test --workspace --locked: 2789 passed, 0 failed, 52 ignored (--no-fail-fast)
  • Targeted suites:
    • cargo test -p claurst-tui --test presentation_parity: 7 passed. Covers deterministic replay, identical outgoing operations (submitted input, permission decision, draft) for both renderers under the same keys, delegation of every input-owning surface, permission through Classic, cleared mouse geometry, and newest-output visibility at 40x12, 80x24 and 1x4.
    • cargo test -p claurst-tui --lib -- application:: turn_completion_bell: 6 passed.
    • cargo test -p claurst --bin coven-code -- scoped_forwarder goal_dispatch: 14 passed. Covers ordered delivery of 200 events through a capacity-1 queue with a slow consumer, the forwarder stopping when the loop is gone, and, in every goal-dispatch test, the queue closing within 1 s of the turn finishing.
    • cargo test -p claurst --test engine_contract: 18 passed. New: --version, --print and stream-json produce identical stdout and exit codes, and no selector warning, with COVEN_CODE_UI set to next, classic or an unsupported value.
    • cargo test -p claurst --test acp_smoke: 6 passed, with an unsupported COVEN_CODE_UI set for every ACP run.
  • scripts/tui-tests/run.sh with REQUIRE_TMUX=1 and a disposable HOME, Classic default, 120x40: 48 passed, 0 failed, 0 skipped. python3 -m unittest discover -s scripts/tui-tests -p 'test_*.py': OK.
  • Manual tmux check, debug binary, COVEN_CODE_UI=next, 100x24:
    • The preview shell renders and the draft echoes.
    • F2 draws Classic's familiar switcher, and Esc returns to Next.
    • COVEN_CODE_UI=fancy shows the warning notification and starts Classic.
  • Note: in one full-workspace run under load, goal_dispatch_payload_decision_injection_and_spawn_fences hit its fixture's 45 s timeout. In isolation it passes in 24.2 s on this branch versus 31.8 s on the base commit, so it is a pre-existing load-sensitive limit, not a regression.
  • Note: one earlier full run hit the intermittent ACP child-exit timeout tracked in Diagnose ACP smoke startup timeouts and preserve failure diagnostics #379 (large_response_stream_does_not_block_child_exit); acp_smoke then passed 5/5 in isolation and in the final full run.
  • Not run:

PR Readiness

Refs #408

…erer

Add claurst_tui::application, the seam between the interactive loop and
the renderers:

- SessionScope/SessionGeneration: each query worker tags its events with
  the generation it was spawned under; clear, rewind (command and in-App)
  and resume advance the generation, so late events from a replaced
  conversation no longer mutate the new one. Bridge and relay consumers
  still receive every event.
- App::apply_query_event is the reducer step; the completion bell is
  returned as a PresentationEffect and performed by the loop.
- A bounded delivery queue (1024) between per-turn forwarders and the
  loop; forwarders wait for space and never drop events. The query loop's
  own per-turn buffer remains unbounded (query-crate API).
- COVEN_CODE_UI=classic|next, read only on the interactive route. Unset
  or empty selects Classic; unsupported values warn on stderr and in the
  TUI and start Classic.
- An opt-in Next renderer over the TranscriptView projection. Any frame
  where a surface owns input (permission, overlays, pickers, familiar
  switcher, slash suggestions) or an error modal shows is drawn by
  Classic. Next clears Classic mouse hit-test geometry.

Classic remains the default and its rendering is unchanged. Headless,
stream-json and ACP never read the selector.

Refs #408
@vercel

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Oct 31, 2026 3:08am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — 799a8f98 Deployed Oct 31, 2026 by vercel[bot]
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