Description
Summary
A few small quality-of-life additions that came up building an agent-management TUI (master-detail, streaming output, tabbed detail views, a command palette, and modal pickers). Each has a working local workaround — these are ergonomics, not blockers. Grouped into one issue; happy to split into separate issues if you'd prefer.
1. Streaming-friendly transcript in RichLog (grow-then-commit a line)
Use case: streaming an agent/LLM response token-by-token into a log, interleaved with styled status lines (e.g. ● calling weather_lookup, ✓ done).
Gap: RichLog is line-oriented; appending deltas to the same logical line means buffering and re-rendering, or one-token-per-line.
Wish: an append_inline(text) / RichLog.stream() that grows the current line until commit_line(), with auto-scroll that pauses when the user scrolls up and resumes at the bottom (plus a reactive for "user has scrolled away" so apps can show a "jump to latest" affordance). This pattern is increasingly common with agent/LLM TUIs.
Workaround: keep the in-progress text in a reactive str rendered in a Static below a RichLog of committed events; commit on done. Two widgets doing one job.
2. TabbedContent: public activate-by-id + a "first shown" event
Use case: sub-tabs switchable by click, by single-key accelerators from another focused widget, and by the command palette — and lazy-loading a tab's data the first time it's shown (not on every revisit).
Gap: activating a specific tab from a global binding is manual, and there's no event distinguishing "shown for the first time" from "activated" for lazy-loading.
Wish: a documented public activate(id), an on_tab_first_shown / TabPane.first_shown message, and optionally an accelerator= per TabPane that auto-registers a binding.
Workaround: a local active/loaded set + helper bindings. Just boilerplate.
3. Lightweight dynamic palette commands without a full Provider
Use case: a handful of state-dependent palette entries (e.g. "switch account" only when >1 account).
Gap: authoring a full CommandProvider (async search yielding Hits) is heavier than needed for a few simple, conditional actions.
Wish: a decorator/registration for simple commands, e.g. @command("switch account", when=lambda app: len(app.accounts) > 1), that the default palette surfaces — the 80% case without a Provider.
Workaround: write the Provider; not hard, just verbose.
4. A selection-modal helper (SelectionScreen[T])
Use case: "pick one of N" modals — choose an account, a backend, a release environment — with arrow/number select, enter to confirm, esc to cancel (when allowed).
Gap: ModalScreen + OptionList works, but every app re-implements the same select-one-and-return-a-value modal.
Wish: a SelectionScreen[T] helper you push and await for the dismissed value, with number accelerators and a configurable cancel policy — the modal analog of Select.
Workaround: a ~30-line reusable ModalScreen[str] around OptionList.
Context: these surfaced building dtn --tui, a local terminal console for managing agents, on Textual 8.2.8. None block us; filing per your "let us know what would help" spirit.
Activity
github-actions commentedon Sep 18, 2026
We found the following entries in the FAQ which you may find helpful:
Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.
This project is developed and maintained by Will McGugan. Consider sponsoring Will's work on this project (and others).
This is an automated reply, generated by FAQtory
thunderballfists commentedon Sep 18, 2026
Retracting — filed in error; will track this in our own project instead. Apologies for the noise.
github-actions commentedon Sep 18, 2026
I hope I solved your issue!
Consider sponsoring my work to continue to maintain and develop Textual.