Files
cleveragents-core/features/tui_materializer.feature
T
freemo a5361a93ae feat(tui): implement TuiMaterializer A2A integration layer
Implement the TuiMaterializer — the glue layer between the Output
Rendering Framework and the Textual widget tree. It maps ElementHandle
events from A2A task updates to Textual widgets.

Key design:
- TuiMaterializer implements the MaterializationStrategy protocol
- Widget registry maps element kinds to Textual widget factories:
  panel → RichLog, table → DataTable, progress/status → Static
- Per-session isolation: each tab gets its own materializer instance
  with independent widget map
- Streaming via on_element_updated pushes incremental deltas directly
  into mounted widgets
- Same widget tree serves TUI, Web (Textual Web), and IDE plugin
- Callbacks (on_widget_created, on_widget_removed) let the host screen
  mount/unmount widgets into the DOM
- Thread-safe via internal lock on widget/snapshot maps

New files:
- src/cleveragents/tui/materializer.py — TuiMaterializer class
- features/tui_materializer.feature — 20 BDD scenarios
- features/steps/tui_materializer_steps.py — Behave step definitions
- robot/tui_materializer.robot — 12 Robot Framework integration tests
- robot/helper_tui_materializer.py — Robot helper script

Quality checks: typecheck (0 errors), lint (clean), format (clean),
unit_tests (20/20 pass), integration_tests (12/12 pass)

ISSUES CLOSED: #696
2026-04-03 08:20:52 +00:00

133 lines
5.9 KiB
Gherkin

Feature: TuiMaterializer A2A integration layer
As a developer
I want a TuiMaterializer that maps ElementHandle events to Textual widgets
So that CLI command producers render in the TUI without modification
# ---------- Instantiation ----------
Scenario: TuiMaterializer can be instantiated with defaults
Given a TuiMaterializer with session id "s1"
Then the materializer session id should be "s1"
And the materializer strategy name should be "tui"
And the materializer should not be closed
And the materializer widget count should be 0
Scenario: TuiMaterializer rejects non-string session id
When a TuiMaterializer is created with a non-string session id
Then a TypeError should be raised for materializer session id
# ---------- Widget registry ----------
Scenario: Default registry maps all element kinds
Given a TuiMaterializer with session id "s1"
Then the materializer registry should contain kind "panel"
And the materializer registry should contain kind "table"
And the materializer registry should contain kind "progress"
And the materializer registry should contain kind "status"
Scenario: Custom factory can be registered
Given a TuiMaterializer with session id "s1"
When a custom widget factory is registered for kind "code"
Then the materializer registry should contain kind "code"
Scenario: Registering empty kind raises ValueError
Given a TuiMaterializer with session id "s1"
When an empty element kind is registered on the materializer
Then a ValueError should be raised for materializer empty kind
Scenario: Registering non-callable factory raises TypeError
Given a TuiMaterializer with session id "s1"
When a non-callable factory is registered on the materializer
Then a TypeError should be raised for materializer non-callable
# ---------- Element creation ----------
Scenario: Panel element creates a RichLog widget
Given a TuiMaterializer with session id "s1"
When a panel element is created on the materializer with title "Details"
Then a materializer widget should exist for the last handle
And the materializer widget should be a RichLog
Scenario: Table element creates a DataTable widget
Given a TuiMaterializer with session id "s1"
When a table element is created on the materializer with columns "Name" and "Value"
Then a materializer widget should exist for the last handle
And the materializer widget should be a DataTable
Scenario: Progress element creates a Static widget
Given a TuiMaterializer with session id "s1"
When a progress element is created on the materializer with label "Loading"
Then a materializer widget should exist for the last handle
And the materializer widget should be a Static widget
Scenario: Status element creates a Static widget
Given a TuiMaterializer with session id "s1"
When a status element is created on the materializer with message "OK"
Then a materializer widget should exist for the last handle
And the materializer widget should be a Static widget
Scenario: Unknown element kind is logged and skipped
Given a TuiMaterializer with session id "s1"
When an element with unknown kind "sparkle" is created on the materializer
Then the materializer widget count should be 0
Scenario: Element without initial state is skipped
Given a TuiMaterializer with session id "s1"
When an element is created on the materializer without initial state
Then the materializer widget count should be 0
# ---------- Widget callbacks ----------
Scenario: on_widget_created callback fires on element creation
Given a TuiMaterializer with a widget-created callback
When a panel element is created on the materializer with title "CB Test"
Then the materializer widget-created callback should have been called
Scenario: on_widget_removed callback fires on element close
Given a TuiMaterializer with a widget-removed callback
When a status element is created on the materializer and then closed
Then the materializer widget-removed callback should have been called
# ---------- Updates ----------
Scenario: Panel entry update refreshes widget snapshot
Given a TuiMaterializer with session id "s1"
When a panel element is created on the materializer with title "Info"
And the materializer panel snapshot is updated with entry "key1" "val1"
Then the materializer stored snapshot should have entry "key1"
Scenario: Status update changes widget text via snapshot
Given a TuiMaterializer with session id "s1"
When a status element is created on the materializer with message "Starting"
And the materializer status snapshot is updated to "Done"
Then the materializer stored status snapshot message should be "Done"
# ---------- Session lifecycle ----------
Scenario: Session end marks materializer closed
Given a TuiMaterializer with session id "s1"
When a materializer session end event is dispatched
Then the materializer should be closed
Scenario: Close releases all widgets
Given a TuiMaterializer with session id "s1"
When a panel element is created on the materializer with title "Temp"
And the materializer is explicitly closed
Then the materializer widget count should be 0
And the materializer should be closed
# ---------- Multi-session isolation ----------
Scenario: Two materializers have independent widget maps
Given two TuiMaterializer instances "a" and "b"
When a panel is created on materializer instance "a"
Then materializer instance "a" should have 1 widget
And materializer instance "b" should have 0 widgets
# ---------- Integration with OutputSession ----------
Scenario: TuiMaterializer works as OutputSession strategy
Given an OutputSession using a TuiMaterializer strategy
When a panel is created via the output session with title "Session Panel"
Then the materializer strategy should have 1 widget