research: evaluate Python TUI frameworks
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# Python TUI frameworks for Fenris
|
||||
|
||||
_Research snapshot: 2026-08-31. Planning only; no product implementation._
|
||||
|
||||
## Decision
|
||||
|
||||
Use **Textual** for Fenris's later TUI prototype and, if the prototype checks below pass, for the persistent TUI.
|
||||
|
||||
Textual is the best fit for Fenris's keyboard-first Overview, Usage History, Drive Health, and Settings views because one framework supplies flexible grid/horizontal/vertical layouts, dashboard and form widgets, background workers, and a headless interaction driver ([layout](https://textual.textualize.io/guide/layout/), [widget gallery](https://textual.textualize.io/widget_gallery/), [workers](https://textual.textualize.io/guide/workers/), [testing](https://textual.textualize.io/guide/testing/)). Its principal costs are the largest direct dependency set in this shortlist and only a compact `Sparkline` as built-in charting; detailed historical plots would need a custom widget or the first-party `textual-plotext` integration ([PyPI metadata](https://pypi.org/pypi/textual/json), [Sparkline](https://textual.textualize.io/widgets/sparkline/), [`textual-plotext`](https://github.com/Textualize/textual-plotext)).
|
||||
|
||||
This recommendation is **conditional on raising Fenris's Python floor from its currently documented Python 3.7+ to Python 3.9+**: Textual 8.2.8 and Urwid 4.0.13 require Python 3.9+, while prompt_toolkit 3.0.53 requires Python 3.10+ ([Fenris README](../../README.md#you-need), [Textual metadata](https://pypi.org/pypi/textual/json), [Urwid metadata](https://pypi.org/pypi/urwid/json), [prompt_toolkit metadata](https://pypi.org/pypi/prompt-toolkit/json)). If retaining Python 3.7 is mandatory, none of the current versions evaluated here qualifies.
|
||||
|
||||
**Fallback:** choose **Urwid** if prototype evidence shows that explicit low-level terminal/display control or a smaller direct dependency set matters more than Textual's higher-level layouts, forms, workers, and test driver ([display modules](https://urwid.org/manual/displaymodules.html), [package metadata](https://pypi.org/pypi/urwid/json)).
|
||||
|
||||
## Scope and method
|
||||
|
||||
The shortlist covers maintained, full-screen-capable Python projects with first-party application/widget documentation: Textual, Urwid, and prompt_toolkit. The evaluation uses official documentation, repository release APIs, and PyPI package metadata. Versions, Python floors, dependencies, and release activity are a point-in-time snapshot and should be rechecked when dependencies are locked.
|
||||
|
||||
## Comparison
|
||||
|
||||
| Criterion | Textual 8.2.8 | Urwid 4.0.13 | prompt_toolkit 3.0.53 |
|
||||
|---|---|---|---|
|
||||
| **Terminal compatibility** | PyPI classifies Linux, macOS, and Windows 10/11 support; this exceeds Fenris's Linux-only scope ([metadata](https://pypi.org/pypi/textual/json)). | Offers pure-Python raw and OS curses displays. The manual compares UTF-8, color, mouse, and external-event-loop capabilities; curses is described as broadly terminal-compatible, while raw supports 88/256/24-bit color and external loops ([display modules](https://urwid.org/manual/displaymodules.html)). | Provides full-screen applications and platform-specific POSIX/Windows test input; official input docs describe cross-platform one-key-at-a-time input and Windows behavior ([full-screen apps](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/full_screen_apps.html), [input](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/asking_for_input.html), [testing](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/unit_testing.html)). |
|
||||
| **Responsive layout** | Vertical, horizontal, and grid layouts support fractional, percentage, and automatic sizing, nesting, spans, overflow, docking, and runtime layout changes ([layout](https://textual.textualize.io/guide/layout/)). | `Pile`, `Columns`, `GridFlow`, `Overlay`, `ListBox`, padding, and flow/box/fixed sizing provide procedural composition ([widgets](https://urwid.org/manual/widgets.html)). | `VSplit`, `HSplit`, `FloatContainer`, `ConditionalContainer`, and scrollable panes compose full-screen regions, but the reviewed guide does not document breakpoint-style responsiveness ([full-screen apps](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/full_screen_apps.html)). |
|
||||
| **Dashboard, charts, history** | Built-ins include `DataTable`, `ProgressBar`, `Digits`, and `Sparkline`; Sparkline summarizes reactive numerical data into bars according to available widget width ([gallery](https://textual.textualize.io/widget_gallery/), [Sparkline](https://textual.textualize.io/widgets/sparkline/)). `textual-plotext` adds a `PlotextPlot` wrapper for richer plotting ([repository](https://github.com/Textualize/textual-plotext)). | Includes `BarGraph`, `GraphVScale`, and `ProgressBar`; richer history views still require composition or custom rendering ([graph widgets](https://urwid.org/reference/widget.html#graph-widgets)). | The documented reusable widget set supports text areas, buttons, frames, dialogs, and menus, but does not list a chart widget; Fenris charts would therefore be custom controls/rendering ([widget reference](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/reference.html#module-prompt_toolkit.widgets)). |
|
||||
| **Forms and keyboard use** | Built-ins include `Input`, `MaskedInput`, `Select`, checkbox/radio/selection controls, switches, buttons, and text areas ([gallery](https://textual.textualize.io/widget_gallery/)). `Input` supports validation on change, blur, or submit and emits results with its messages ([Input](https://textual.textualize.io/widgets/input/)). | Supplies `Edit`, `Button`, `CheckBox`, `RadioButton`, focus handling, and keyboard/mouse event support; application code owns more form orchestration ([widgets](https://urwid.org/manual/widgets.html)). | Strong key-binding, editing, completion, history, suggestion, and validation facilities; full-screen reusable components include `TextArea` and `Button` ([input](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/asking_for_input.html), [full-screen apps](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/full_screen_apps.html)). |
|
||||
| **Async/background updates** | Async workers run coroutines concurrently; thread workers cover blocking APIs. Workers support cancellation and exclusivity, while thread-originated UI updates use `call_from_thread()` or thread-safe messages ([workers](https://textual.textualize.io/guide/workers/)). | Supports Select, asyncio, Twisted, GLib, Tornado, and ZMQ event loops plus alarms and watched files; executor use varies by loop ([main loop](https://urwid.org/manual/mainloop.html)). | Uses asyncio natively and supports `Application.run_async()` inside an existing event loop ([asyncio](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/asyncio.html)). |
|
||||
| **Testability** | `App.run_test()` runs headlessly and returns a `Pilot` that can press keys, click, wait for queued messages, and run at specified terminal sizes ([testing](https://textual.textualize.io/guide/testing/)). | The public manuals reviewed do not expose a comparable application pilot; tests would be built around widgets, rendering, callbacks, and event-loop seams ([widgets](https://urwid.org/manual/widgets.html), [main loop](https://urwid.org/manual/mainloop.html)). | Official testing guidance supplies platform-specific pipe input, `DummyOutput`, and app sessions for asserting results or data changes, while warning against brittle stdout-byte assertions ([unit testing](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/unit_testing.html)). |
|
||||
| **Direct dependency cost** | Five required distributions: `markdown-it-py[linkify]`, `mdit-py-plugins`, `platformdirs`, `pygments`, and `rich`; syntax-tree packages are optional-extra dependencies ([metadata](https://pypi.org/pypi/textual/json)). | Two required distributions: `wcwidth` and `typing-extensions`; curses, GLib, Tornado, Trio, Twisted, ZMQ, serial, and LCD integrations are optional extras ([metadata](https://pypi.org/pypi/urwid/json)). | One required distribution: `wcwidth` ([metadata](https://pypi.org/pypi/prompt-toolkit/json)). |
|
||||
| **Python floor** | `>=3.9,<4.0` ([metadata](https://pypi.org/pypi/textual/json)). | `>=3.9.0` ([metadata](https://pypi.org/pypi/urwid/json)). | `>=3.10` ([metadata](https://pypi.org/pypi/prompt-toolkit/json)). |
|
||||
| **Project health signal** | Five non-draft releases from 8.2.4 through 8.2.8 were published between 2026-04-19 and 2026-06-30 ([release API](https://api.github.com/repos/Textualize/textual/releases?per_page=5)). | Five releases from 4.0.9 through 4.0.13 were published between 2026-08-14 and 2026-08-25 ([release API](https://api.github.com/repos/urwid/urwid/releases?per_page=5)). | Release 3.0.53 was published 2026-07-26, following 3.0.52 on 2025-08-27 ([release API](https://api.github.com/repos/prompt-toolkit/python-prompt-toolkit/releases?per_page=5)). |
|
||||
|
||||
All three show recent release activity, so maintenance does not eliminate a candidate. Textual wins on integrated application-level capabilities rather than on release recency alone.
|
||||
|
||||
## Fit to the four views
|
||||
|
||||
- **Overview:** use Textual layout containers with `Digits`, labels, progress indicators, and compact sparklines for monitoring status, usage-adjusted theoretical lifespan, projection confidence, and recent activity ([layout](https://textual.textualize.io/guide/layout/), [gallery](https://textual.textualize.io/widget_gallery/)).
|
||||
- **Usage History:** prototype a `DataTable` plus `Sparkline` first. Add `textual-plotext` only if user testing establishes a need for axes, multiple series, or richer plots, keeping plotting out of the core dependency decision until then ([DataTable](https://textual.textualize.io/widgets/data_table/), [Sparkline](https://textual.textualize.io/widgets/sparkline/), [`textual-plotext`](https://github.com/Textualize/textual-plotext)).
|
||||
- **Drive Health:** use a table and status/progress widgets for NVMe attributes; wording must continue to distinguish endurance projection from a hardware-failure prediction ([gallery](https://textual.textualize.io/widget_gallery/), [parent-map destination and terminology](https://git.bongbetic.com/xavierk/Fenris/issues/1)).
|
||||
- **Settings:** Textual's selection, boolean, and validated text-entry controls cover a keyboard-only form without inventing controls ([gallery](https://textual.textualize.io/widget_gallery/), [Input validation](https://textual.textualize.io/widgets/input/)).
|
||||
|
||||
The TUI should consume snapshots from the independent collector rather than run NVMe collection in its UI loop; the ticket's parent map makes that independent-collector architecture part of the destination ([parent map](https://git.bongbetic.com/xavierk/Fenris/issues/1)). Textual workers remain useful for non-blocking database/IPC reads and cancellable refreshes ([workers](https://textual.textualize.io/guide/workers/)).
|
||||
|
||||
## Prototype acceptance checks
|
||||
|
||||
The later prototype should verify:
|
||||
|
||||
1. Complete keyboard-only operation and predictable focus order across all four views.
|
||||
2. Reflow or deliberate scrolling at 80×24 and a representative wide terminal; Textual's test runner accepts explicit terminal sizes ([testing](https://textual.textualize.io/guide/testing/)).
|
||||
3. Smooth refresh while database/IPC reads are delayed, plus clean cancellation and shutdown ([workers](https://textual.textualize.io/guide/workers/)).
|
||||
4. Headless tests for view switching, settings validation, stale/error states, and collector disappearance ([testing](https://textual.textualize.io/guide/testing/), [Input](https://textual.textualize.io/widgets/input/)).
|
||||
5. Readable behavior in the actual Linux terminal matrix, including SSH and tmux/screen if those are required; framework platform labels alone do not establish every terminal's behavior.
|
||||
6. Usage History performance with realistic observation volume and gaps.
|
||||
7. A table/text equivalent for every chart so observation gaps and projection confidence are not encoded by color or graphics alone.
|
||||
8. Installation with Fenris's chosen packaging mechanism and the agreed Python floor.
|
||||
|
||||
## Newly surfaced questions
|
||||
|
||||
Do not open follow-up tickets during this research resolution; carry these into the next Wayfinder session:
|
||||
|
||||
- May the redesign raise Fenris's minimum Python version from 3.7 to 3.9?
|
||||
- What is the minimum supported terminal contract: modern color terminals only, or also Linux virtual console, monochrome/16-color, SSH, tmux, and screen?
|
||||
- Is a table plus sparkline sufficient for Usage History, or are axes, multiple series, zoom, and interval selection required?
|
||||
- Should richer plotting be an optional install extra?
|
||||
- What is the small-terminal policy: reflow, hide secondary content, scroll, or reject below a documented size?
|
||||
- Which accessibility targets are required, including screen readers, reduced color, high contrast, and reduced animation?
|
||||
- Does the TUI read observation storage directly or use an IPC snapshot API from the collector?
|
||||
- Which settings are user-owned versus system/collector-owned, and which changes require privilege escalation?
|
||||
- Are semantic interaction assertions sufficient, or are deterministic visual snapshots also required?
|
||||
Reference in New Issue
Block a user