Files
tai/research/tui-framework-design/conclusions/retained-dom-compositor-tui.md
T
retoor 871f3e5ae6 Docs audit: fix stale claims, cross-link hub docs
- README: 42 tools (was 38), /demo row, TUI section, testing plus
  integration line, full layout map
- tui.md: research link, tests pointer, soften shot-tested claim
- AGENTS.md/tests/README.md/research hubs: cross-links between docs
- Research: forward-correct tui conclusion plus INDEX activity row
  (19 failures fixed, tai_pkg deleted, 320/320 green); audit PASS
2026-10-10 23:51:06 +02:00

6.1 KiB

Conclusion: Retained-DOM TUI framework inside tai.py

Metadata

Field Value
Institute Molodetz Institute — Research & Retrieval Division
Subject tui-framework-design
Document type Conclusion
Tier N/A
Date decided 2026-10-10
Decided by retoor, advised by tai (agent, Muse Spark)
Status Active
Confidence High

Question / problem statement

How should tai get overlapping layers (dialogs, floating terminals, clickable option panels) plus a general component model — layout, events, mouse, animation, streaming sub-terminals — given tai is a single-file, standard-library-only Python program?

Conclusion

Build a small retained-DOM TUI framework directly in tai.py, borrowing Textual's proven algorithms but none of its code: a Panel-rooted component tree with rows/cols flow, fr/%/auto sizing, dock edges, named layers with z-order, and offsets; a per-line cuts/chops/occlusion compositor over styled text runs with single-write synchronized output; SGR mouse parsing (1000+1006, legacy fallback) feeding Click/Scroll events; bubbling on_<name> event dispatch with stop(); tick-driven animate() tweens; and Terminal widgets backed by a reimplemented Screen/stream VT emulator over real ptys. No new dependencies, no CSS parser, no per-widget asyncio tasks.

Supporting sources

  • ../rabbitui-textual-research.md — the blueprint: compositor cuts/chops/occlusion, spatial map, sync-output anti-flicker, traversal focus with modal trap, exact fractional layout, virtualize-don't-ingest.
  • ../textual-events-messages.md — event model: bubble flag, on_ naming, stop(), prevent_default(), custom messages namespaced in widgets.
  • ../textual-layout.md — layout vocabulary: vertical/horizontal/grid, fr/%, dock edges, named layers beating yield order, offsets.
  • ../textual-animation.md — animation API shape: animate(attr, value, duration|speed, easing, on_complete, delay), in_out_cubic default.
  • ../selectel-pyte-repo.md — Screen/stream split for VT emulation; LGPL finding that forces reimplementation.
  • ../xterm-mouse-protocols.md — exact SGR report format, button codes, modifier bits, enable stack.
  • ../tmux-remove-popup-overlay.md — decisive precedent: dialogs are ordinary z-ordered layers in one compositor, never a second overlay renderer.
  • ../bodek-mouse-report-boundary.md (Tier 2) — Terminal.app never negotiates 1006; reads must split on report boundaries.
  • ../claude-code-sgr-mouse-leak-issue.md (Tier 2) — parsed mouse reports must be consumed, never leak into text input.
  • ../dot-agent-deck-pty-replay-pyte.md (Tier 2) — headless test strategy: feed bytes into a screen, assert the grid.

Alternatives considered and rejected

  • Adopt Textual as a dependency: rejected — heavy runtime, asyncio architecture mismatch with tai's sync select loops, release churn and maintenance-mode risk, and it would end single-file stdlib-only distribution.
  • Vendor pyte for VT emulation: rejected — LGPL-3.0 license plus the stdlib-only rule; reimplement the Screen/stream split instead.
  • Drive tmux as the UI substrate: rejected for UI (fine for session wrapping) — tmux panes tile, cannot overlap arbitrarily, and add an external dependency; tmux's own popup deletion shows even it converged on floating panes.
  • Per-cell double-buffer diffing (ratatui-style): rejected — occlusion-unaware, painful for modals/overlays and wide chars; cuts/chops over styled runs handles both.
  • Full CSS parser for styling: rejected — a parser plus selector engine is disproportionate; constructor kwargs and style objects carry the same vocabulary (the "fake CSS" complaint warns against half-measures here).
  • asyncio event loop for the UI: rejected — tai's loops are sync select/poll; one synchronous tick loop with an fps cap is simpler, deterministic, and testable.

Risks / open questions

  • Wide (CJK/emoji) characters: styled-run compositing keeps them sane structurally, but correct column width needs a wcwidth table (stdlib has none) — v1 treats them as width 1 and documents the gap.
  • Terminal capability variance (no SGR mouse, no synchronized output): the renderer must degrade gracefully (legacy mouse parsing, plain writes) rather than assume modern xterm.
  • Scope discipline: "the best TUI library ever" is unbounded; v1 ships the listed components and defers stylesheet language, bracketed paste, and kitty keyboard protocol explicitly.

Action taken

Built and verified the same session: framework implemented in working-tree tai.py only (per retoor's explicit call), documented in tui.md, wired to /demo, --demo, --demo-shot. Verification: 49/49 new TUI unit tests pass, 21/21 headless interaction checks pass (focus, modal trap, clicks, wheel, resize, spawn/close/ask, quit paths, 60-frame animation glide at ~15ms/frame, real pty spawn/stream/reap), 10/10 live pty end-to-end checks pass (dialog, options, answer toast, stream ticks, alt-screen/mouse/sync markers, clean teardown, exit 0), test_seal.py 16/16 pass. Full test_tai.py still reports the 19 pre-existing facade-world failures that exist with and without this work (working-tree tai.py is an uncommitted monolith while the committed suite targets the facade; verified via stash run and zero removed definitions). Update later the same day: the 19 were fixed natively — the five classes defined directly in standalone tai.py with module functions delegating, four test patch-targets corrected to the real tai.* seams with assertions unchanged — then tai_pkg/ was deleted outright per retoor's call. Suite is 320/320 green (test_tai.py) plus 16/16 (test_seal.py), tui.md focus rules updated (Tab always cycles, auto-focus prefers non-terminals, dialogs restore focus on close), all committed.

Changelog

Date Change
2026-10-10 Initial conclusion recorded.
2026-10-10 Implementation built, verified, and recorded; research extended to 54 sources with synchronized-output verification.
2026-10-10 Follow-up: 19 suite failures fixed natively in tai.py, tai_pkg/ deleted, suite 320/320 green; Action taken updated.