Feature: TUI Materializer As a TUI developer I want a TuiMaterializer that bridges the Output Rendering Framework to Textual widgets So that all CLI command producers can render in the TUI without modification Background: Given the tui materializer module is imported # ── Module exports ──────────────────────────────────────────────────────── Scenario: TuiMaterializer is importable from tui.materializer Then TuiMaterializer should be importable from tui.materializer And TuiWidgetEvent should be importable from tui.materializer And TuiWidgetEventType should be importable from tui.materializer And render_element_for_tui should be importable from tui.materializer # ── TuiMaterializer instantiation ──────────────────────────────────────── Scenario: TuiMaterializer can be instantiated without arguments When I create a TuiMaterializer without a callback Then the materializer strategy_name should be "tui" And the materializer supports_incremental_updates should be True And the materializer events list should be empty Scenario: TuiMaterializer can be instantiated with a callback When I create a TuiMaterializer with a callback Then the materializer strategy_name should be "tui" # ── MaterializationStrategy protocol ───────────────────────────────────── Scenario: TuiMaterializer implements on_session_begin When I create a TuiMaterializer without a callback And I call on_session_begin with a mock session Then the materializer should still be running Scenario: TuiMaterializer emits element_created event for panel When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a panel handle titled "Test Panel" Then an element_created event should be emitted And the event element_kind should be "panel" And the event rendered_text should contain "Test Panel" Scenario: TuiMaterializer emits element_created event for table When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a table handle titled "Results" Then an element_created event should be emitted And the event element_kind should be "table" And the event rendered_text should contain "Results" Scenario: TuiMaterializer emits element_created event for status When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a status handle with message "Operation complete" Then an element_created event should be emitted And the event element_kind should be "status" And the event rendered_text should contain "Operation complete" Scenario: TuiMaterializer emits element_created event for progress When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a progress handle with label "Loading" Then an element_created event should be emitted And the event element_kind should be "progress" And the event rendered_text should contain "Loading" Scenario: TuiMaterializer emits element_created event for code block When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a code handle with content "print('hello')" and language "python" Then an element_created event should be emitted And the event element_kind should be "code" And the event rendered_text should contain "print('hello')" Scenario: TuiMaterializer emits element_created event for separator When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a separator handle Then an element_created event should be emitted And the event element_kind should be "separator" Scenario: TuiMaterializer emits element_created event for action hint When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create an action hint handle with command "agents plan list" Then an element_created event should be emitted And the event element_kind should be "action_hint" And the event rendered_text should contain "agents plan list" Scenario: TuiMaterializer emits element_closed event when handle is closed When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a panel handle titled "Closing Panel" And I close the panel handle Then an element_closed event should be emitted And the closed event rendered_text should contain "Closing Panel" Scenario: TuiMaterializer emits session_end event when session ends When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create a status handle with message "Done" And I close the session Then a session_end event should be emitted # ── Callback invocation ─────────────────────────────────────────────────── Scenario: TuiMaterializer invokes callback on element_created When I create a TuiMaterializer with a callback And I use the materializer with an OutputSession And I create a status handle with message "Callback test" Then the callback should have been called And the callback event type should be "element_created" Scenario: TuiMaterializer invokes callback on element_closed When I create a TuiMaterializer with a callback And I use the materializer with an OutputSession And I create a status handle with message "Close callback test" And I close the status handle Then the callback should have been called with event_type "element_closed" Scenario: TuiMaterializer invokes callback on session_end When I create a TuiMaterializer with a callback And I use the materializer with an OutputSession And I close the session Then the callback should have been called with event_type "session_end" # ── rendered_output property ────────────────────────────────────────────── Scenario: rendered_output returns empty string when no elements When I create a TuiMaterializer without a callback Then the materializer rendered_output should be empty Scenario: rendered_output accumulates closed element text When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create and close a status handle with message "Accumulated" Then the materializer rendered_output should contain "Accumulated" # ── render_element_for_tui ──────────────────────────────────────────────── Scenario: render_element_for_tui renders Panel with title and entries When I render a Panel element with title "Info" and entry "key" "value" Then the render output should contain "Info" And the render output should contain "key" And the render output should contain "value" Scenario: render_element_for_tui renders Table with columns and rows When I render a Table element with title "Data" and column "Name" Then the render output should contain "Data" And the render output should contain "Name" Scenario: render_element_for_tui renders StatusMessage ok level When I render a StatusMessage element with level "ok" and message "Success" Then the render output should contain "ok" And the render output should contain "Success" Scenario: render_element_for_tui renders StatusMessage error level When I render a StatusMessage element with level "error" and message "Failed" Then the render output should contain "error" And the render output should contain "Failed" Scenario: render_element_for_tui renders indeterminate ProgressIndicator When I render an indeterminate ProgressIndicator with label "Thinking" Then the render output should contain "Thinking" Scenario: render_element_for_tui renders determinate ProgressIndicator When I render a determinate ProgressIndicator with label "Loading" current 5 total 10 Then the render output should contain "Loading" And the render output should contain "50%" Scenario: render_element_for_tui renders Tree with root and children When I render a Tree element with root "root" and child "child1" Then the render output should contain "root" And the render output should contain "child1" Scenario: render_element_for_tui renders CodeBlock with language When I render a CodeBlock element with language "python" and content "x = 1" Then the render output should contain "python" And the render output should contain "x = 1" Scenario: render_element_for_tui renders DiffBlock with hunks When I render a DiffBlock element with file_a "old.py" and file_b "new.py" Then the render output should contain "old.py" And the render output should contain "new.py" Scenario: render_element_for_tui renders Separator line style When I render a Separator element with style "line" Then the render output should contain "-" Scenario: render_element_for_tui renders Separator blank style When I render a Separator element with style "blank" Then the render output should be empty Scenario: render_element_for_tui renders Separator double style When I render a Separator element with style "double" Then the render output should contain "=" Scenario: render_element_for_tui renders ActionHint with commands When I render an ActionHint element with command "agents plan list" Then the render output should contain "agents plan list" Scenario: render_element_for_tui renders TextBlock content When I render a TextBlock element with content "Hello world" Then the render output should contain "Hello world" Scenario: render_element_for_tui renders TextBlock with indent When I render a TextBlock element with content "indented" and indent 4 Then the render output should contain " indented" # ── A2A event routing ───────────────────────────────────────────────────── Scenario: route_permission_request emits permission_request event When I create a TuiMaterializer without a callback And I route a permission request for "src/main.py" with type "file_write" Then a permission_request event should be emitted And the permission event extra should be the InlinePermissionQuestion And the permission event rendered_text should contain "src/main.py" Scenario: route_thought_block emits thought_block event When I create a TuiMaterializer without a callback And I route a thought block with content "I am thinking" Then a thought_block event should be emitted And the thought event extra should be the ThoughtBlock And the thought event rendered_text should contain "I am thinking" # ── Thread safety ───────────────────────────────────────────────────────── Scenario: TuiMaterializer events list is thread-safe When I create a TuiMaterializer without a callback And I use the materializer with an OutputSession And I create multiple status handles concurrently Then all events should be recorded without data corruption