diff --git a/features/steps/tui_first_run_steps.py b/features/steps/tui_first_run_steps.py index b6e9ca9db..fad2ae17e 100644 --- a/features/steps/tui_first_run_steps.py +++ b/features/steps/tui_first_run_steps.py @@ -488,3 +488,47 @@ def step_persona_bar_reflects_actor(context: object) -> None: assert "anthropic/claude-sonnet-4-20250514" in bar._text, ( f"Expected actor in persona bar, got: {bar._text}" ) + + +# --------------------------------------------------------------------------- +# TDD regression: issue #11039 - no _render() shadowing Textual Widget._render +# --------------------------------------------------------------------------- + + +@then("the overlay should not override Textual Widget._render") +def step_overlay_no_render_override(context: object) -> None: + """Verify that ActorSelectionOverlay does NOT define its own ``_render``. + + The Shadowing Bug (issue #11039): ActorSelectionOverlay once defined + ``def _render(self) -> None`` which returned ``None``, shadowing + ``textual.widgets.Static._render()`` (which returns a + :class:`textual.strip.Strip`). This caused the Textual layout engine to + crash with ``AttributeError: 'NoneType' object has no attribute + 'get_height'``. + + The fix renamed the method to ``_refresh_display`` so that the inherited + base-class ``_render`` is untouched and returns a proper renderable. + """ + from cleveragents.tui.widgets.actor_selection_overlay import ( + ActorSelectionOverlay, + ) + + cls = ActorSelectionOverlay + # The overlay should NOT define its own _render in its __dict__. + # If it did, _render found on the class would resolve to this method + # rather than the Textual base-class implementation. + has_own_render = "_render" in cls.__dict__ + assert not has_own_render, ( + "ActorSelectionOverlay defines its own _render() which shadows " + "Textual Widget._render. This is the bug from issue #11039." + ) + + +@then("the overlay _refresh_display method should be callable") +def step_overlay_refresh_display_callable(context: object) -> None: + """Verify ``_refresh_display`` exists and is callable after show().""" + assert hasattr( + context._overlay, "_refresh_display" + ), "Overlay must have _refresh_display method" + refresh = context._overlay._refresh_display + assert callable(refresh), "_refresh_display must be callable" diff --git a/features/tui_first_run.feature b/features/tui_first_run.feature index 3d82993da..c8646de05 100644 --- a/features/tui_first_run.feature +++ b/features/tui_first_run.feature @@ -194,3 +194,21 @@ Feature: TUI first-run experience with actor selection overlay And I call _complete_first_run with actor "anthropic/claude-sonnet-4-20250514" Then the registry should contain a persona named "default" And the persona bar should reflect the new actor + + # ----------------------------------------------------------------------- + # TDD regression: issue #11039 - no _render() shadowing Textual Widget._render + # ----------------------------------------------------------------------- + # The overlay formerly defined its own _render() returning None, which + # shadowed textual.widgets.Static._render and caused layout-engine crashes. + # This scenario ensures the rename to _refresh_display eliminated that bug. + + @tdd_issue @tdd_issue_11039 + Scenario: ActorSelectionOverlay does not define its own _render method + Given a new ActorSelectionOverlay + Then the overlay should not override Textual Widget._render + + @tdd_issue @tdd_issue_11039 + Scenario: ActorSelectionOverlay has refresh_display callable instead of render + Given a new ActorSelectionOverlay + When I call show on the overlay + Then the overlay _refresh_display method should be callable