We build the customizer's interactive UI with a custom panel-based TUI (.vortex/customizer - Tui/ + Widget/ + Input/). This issue tracks whether we should consider replacing it with symfony/tui.
Current assessment: not yet. Its widget set overlaps ours well (SelectList, Input, Editor, SettingsList, focus/keybinding traits), but it is experimental (minimum-stability: dev, no BC promise), needs Symfony 8 (unreleased) + revolt/event-loop, raises the PHP floor to 8.4.1, and its event-loop model conflicts with our synchronous, headless-testable TUI (the basis of our 100% test coverage). It would also only replace the rendering layer, not the engine. Revisit when it leaves experimental status and Symfony 8 ships.
Full assessment
What symfony/tui is
A terminal-UI framework by Fabien Potencier / the Symfony community. Packages: Ansi, Style, Terminal, Input, Focus, Event, Loop, Render, Widget. It is explicitly experimental (README: "This Component is experimental", not covered by Symfony's BC promise), minimum-stability: dev, and "looking for a backer".
Requirements (composer.json):
php: >=8.4.1
revolt/event-loop: ^1.0 (an async event loop)
symfony/event-dispatcher: ^8.0 and symfony/string: ^8.0 (Symfony 8, not yet released)
Widget set: AbstractWidget, ContainerWidget, InputWidget, SelectListWidget, SettingsListWidget (+ SettingItem), EditorWidget, TextWidget, ProgressBarWidget, LoaderWidget, MarkdownWidget, FigletWidget, with Focusable/Quitable/Keybindings/ScheduledTick traits and a WidgetTree/WidgetContext composition model.
What our TUI does (the features to match)
From .vortex/customizer/src/Tui, Widget, Input:
- Panel navigation - a hub of panels and sub-panels with a breadcrumb (
Navigator).
- Five field editors - text, single-select, multi-select (type-to-filter, select-all/none), autocomplete-with-fallback, yes/no.
- A scrolled viewport with pinned header/footer and cursor-follow vs mouse-wheel scroll (
Scroller, Viewport).
- Provenance badges (default / discovered / edited), right-aligned by visible width.
- A theme system: self-contained theme classes owning colours and glyphs and layout, with dark/light built in and config-selectable custom classes.
- A start banner with version.
- Headless testability - the whole interaction is a synchronous, one-key-at-a-time state machine (
PanelController::handle(Key)); tests feed an ArrayKeyStream and assert rendered frames. This is what gets the package to 100 percent coverage without a real terminal.
Feature-by-feature
| Our feature |
symfony/tui equivalent |
Fit |
| Text / select / multiselect / suggest / confirm editors |
InputWidget, SelectListWidget, EditorWidget |
Good - close overlap, though multiselect-with-filter and suggest-with-fallback would need checking/porting |
| Panels of settings |
SettingsListWidget + SettingItem |
Good - conceptually the closest match to our panels |
| Navigation / focus |
Focus package, Focusable trait, WidgetTree |
Good |
| Scrolled viewport, pinned chrome |
Render + ContainerWidget |
Likely, but our exact scroll/badge behaviour would be re-implemented |
| Theme (colour + glyph + layout as a class) |
Style package |
Partial - Style covers colour; our glyph/layout-owning theme class would be reworked |
| Provenance badges |
none specific |
Build on top |
| Key parsing |
Input package |
Good |
| Headless, deterministic tests |
event-loop driven (Loop, revolt/event-loop, Event) |
Poor - architectural mismatch (see below) |
Blockers
- Stability. Experimental,
dev stability, no BC promise, and it needs Symfony 8 (unreleased). A distributable library pinning a dev-stability, BC-unstable dependency is a support liability. The customizer currently requires only symfony/console and alexskrypnyk/str2name.
- PHP floor.
>=8.4.1 would raise the customizer's floor from >=8.2, dropping 8.2/8.3 consumers.
- Architecture mismatch with our test model. symfony/tui is event-loop driven (
revolt/event-loop, an Event dispatcher, a Loop). Our TUI is a synchronous state machine specifically so tests can push one key and assert one frame, with no terminal, no timers and no async - the basis of the 100 percent-covered, deterministic suite. Moving to an event loop would make the interaction harder to test deterministically and would mean rewriting that suite.
- Scope. symfony/tui would only replace
Tui/ + Widget/ + Input/. The customizer's value - the config-driven engine, derive/condition/discovery, handlers, schema - is untouched by it. So this is a rendering-layer swap, not a reduction of what we maintain overall.
Recommendation
Keep the custom TUI for now. It is small, dependency-light, headless-testable and fully covered, and it is coupled to the engine exactly as we need. Reassess symfony/tui when:
- it leaves experimental status and gains a BC promise, and
- Symfony 8 is released (removing the
dev pin), and
- we are comfortable with a PHP 8.4+ floor.
At that point the most valuable pieces to adopt would be its widget layer (SelectListWidget, InputWidget, SettingsListWidget) and Style/Ansi - provided we can preserve deterministic, terminal-free testing. Until then, the cost (unstable deps, higher PHP floor, a rewritten test suite) clearly outweighs the benefit (fewer rendering classes to maintain).
We build the customizer's interactive UI with a custom panel-based TUI (
.vortex/customizer-Tui/+Widget/+Input/). This issue tracks whether we should consider replacing it withsymfony/tui.Current assessment: not yet. Its widget set overlaps ours well (SelectList, Input, Editor, SettingsList, focus/keybinding traits), but it is experimental (
minimum-stability: dev, no BC promise), needs Symfony 8 (unreleased) +revolt/event-loop, raises the PHP floor to 8.4.1, and its event-loop model conflicts with our synchronous, headless-testable TUI (the basis of our 100% test coverage). It would also only replace the rendering layer, not the engine. Revisit when it leaves experimental status and Symfony 8 ships.Full assessment
What symfony/tui is
A terminal-UI framework by Fabien Potencier / the Symfony community. Packages:
Ansi,Style,Terminal,Input,Focus,Event,Loop,Render,Widget. It is explicitly experimental (README: "This Component is experimental", not covered by Symfony's BC promise),minimum-stability: dev, and "looking for a backer".Requirements (composer.json):
php: >=8.4.1revolt/event-loop: ^1.0(an async event loop)symfony/event-dispatcher: ^8.0andsymfony/string: ^8.0(Symfony 8, not yet released)Widget set:
AbstractWidget,ContainerWidget,InputWidget,SelectListWidget,SettingsListWidget(+SettingItem),EditorWidget,TextWidget,ProgressBarWidget,LoaderWidget,MarkdownWidget,FigletWidget, withFocusable/Quitable/Keybindings/ScheduledTicktraits and aWidgetTree/WidgetContextcomposition model.What our TUI does (the features to match)
From
.vortex/customizer/src/Tui,Widget,Input:Navigator).Scroller,Viewport).PanelController::handle(Key)); tests feed anArrayKeyStreamand assert rendered frames. This is what gets the package to 100 percent coverage without a real terminal.Feature-by-feature
InputWidget,SelectListWidget,EditorWidgetSettingsListWidget+SettingItemFocuspackage,Focusabletrait,WidgetTreeRender+ContainerWidgetStylepackageInputpackageLoop,revolt/event-loop,Event)Blockers
devstability, no BC promise, and it needs Symfony 8 (unreleased). A distributable library pinning a dev-stability, BC-unstable dependency is a support liability. The customizer currently requires onlysymfony/consoleandalexskrypnyk/str2name.>=8.4.1would raise the customizer's floor from>=8.2, dropping 8.2/8.3 consumers.revolt/event-loop, anEventdispatcher, aLoop). Our TUI is a synchronous state machine specifically so tests can push one key and assert one frame, with no terminal, no timers and no async - the basis of the 100 percent-covered, deterministic suite. Moving to an event loop would make the interaction harder to test deterministically and would mean rewriting that suite.Tui/+Widget/+Input/. The customizer's value - the config-driven engine, derive/condition/discovery, handlers, schema - is untouched by it. So this is a rendering-layer swap, not a reduction of what we maintain overall.Recommendation
Keep the custom TUI for now. It is small, dependency-light, headless-testable and fully covered, and it is coupled to the engine exactly as we need. Reassess symfony/tui when:
devpin), andAt that point the most valuable pieces to adopt would be its widget layer (
SelectListWidget,InputWidget,SettingsListWidget) andStyle/Ansi- provided we can preserve deterministic, terminal-free testing. Until then, the cost (unstable deps, higher PHP floor, a rewritten test suite) clearly outweighs the benefit (fewer rendering classes to maintain).