Files
tai/research/tui-framework-design/textual-events-messages.md
T
retoor 29c489e27a Standalone tai.py: native TUI framework, grouped classes, full suite green
- In-tai.py TUI framework (panels, compositor, z-layers, mouse, focus,
  VT screen, pty sub-terminals, widgets, animation) plus /demo, --demo,
  --demo-shot, tui.md docs, and research/tui-framework-design archive
- Focus safety: Tab/Shift+Tab always cycle, auto-focus prefers
  non-terminals, dialogs restore previous focus on close
- Native Consts/Install/Sandbox/Opencode/SysInfo classes in tai.py;
  module functions delegate so live patches apply; fixes 19 failures
- Retargeted 4 test patches to the real tai.* seams (assertions unchanged)
- Deleted tai_pkg/; tai.py is standalone stdlib-only (bare copy imports)
- AGENTS.md/README.md updated to the standalone architecture
- test_tai.py 320/320 OK, test_seal.py 16/16 OK
2026-10-10 23:45:12 +02:00

3.0 KiB

Events and Messages - Textual (official guide)

Metadata

Field Value
Institute Molodetz Institute — Research & Retrieval Division
Subject tui-framework-design
Document type Source Dossier
Tier 1
Source title Events and Messages - Textual
Source URL https://textual.textualize.io/guide/events/
Source publisher / author Textualize
Source type vendor docs
Source date (published) Unknown — not stated
Date researched 2026-10-10
Researched by tai (agent), model: Muse Spark, on behalf of retoor
Research method direct page fetch
Confidence High
Status Active

Summary

Textual's official guide to its event system: every App and Widget owns a message queue drained by its own asyncio Task; events are framework-reserved messages; handlers attach via on_ naming or the @on decorator; messages with bubble=True propagate to the parent after processing; stop(), prevent_default(), and the prevent() context manager control propagation and default handling.

Key findings

  • Every App and Widget has its own message queue plus an asyncio Task started at mount that dispatches queued messages to handlers in order.
  • Handler naming scheme: on_ + snake_case namespace (outer widget class, if the message is nested) + snake_case message class name; each message class exposes handler_name.
  • Messages carry a bubble attribute; when true the message is sent to the widget's parent after processing, up to App (the DOM root, where bubbling ends).
  • stop() on a message halts bubbling; prevent_default() stops base-class handlers from running (Textual otherwise calls base-class handlers automatically, e.g. Paddle.on_key then Static.on_key then Widget.on_key).
  • Custom messages are Message subclasses, conventionally nested inside the sending widget (creates the handler namespace, avoids name clashes); sent via post_message(), commonly to self so they bubble.
  • prevent(MessageType) context manager temporarily suppresses a message type while updating children programmatically.

Pros / strengths

  • Authoritative (vendor's own docs) with runnable code for every concept; ordering and bubbling guarantees stated plainly.
  • The bubble/stop/prevent trio is a small, complete vocabulary for event flow control.

Cons / limitations / caveats

  • asyncio-centric (per-widget tasks); tai's UI loop is a synchronous select loop, so the queue-per-widget half does not transfer, only the dispatch/bubble half.
  • No capture phase (root-to-target); bubbling only. No performance numbers.

Relevance to tai

  • Direct template for the tai TUI event model: an Event base with a bubble flag, on_<name> handler dispatch walking the MRO, stop() halting the parent walk, modal dialogs consuming keys before they reach layers below.
  • tai will dispatch synchronously in its tick loop instead of per-widget tasks; ordering guarantee ("one event fully handled before the next") is kept trivially that way.

Changelog

Date Change
2026-10-10 Initial dossier created.