Skip to content

feat(renderer): group consecutive tool outputs under shared tree banners#183

Open
janfeddersen-wq wants to merge 4 commits intompfaffenberger:mainfrom
janfeddersen-wq:main
Open

feat(renderer): group consecutive tool outputs under shared tree banners#183
janfeddersen-wq wants to merge 4 commits intompfaffenberger:mainfrom
janfeddersen-wq:main

Conversation

@janfeddersen-wq
Copy link
Contributor

@janfeddersen-wq janfeddersen-wq commented Feb 16, 2026

image

Problem
Tool output banners were repeated for every event, which made the terminal noisy and hard to scan during rapid tool usage (especially repeated READ FILE/GREP calls).
After initial grouping, GREP sub-results still visually broke the tree structure.

What changed
Implemented immediate (non-buffered) grouping for consecutive tool outputs in RichConsoleRenderer:

• Added renderer grouping state (_last_rendered_type)
• Added group handling helpers:
◦ _GROUPABLE_TYPES
◦ _TRANSPARENT_TYPES
◦ _is_continuation(...)
• Updated grouped rendering for:
◦ READ FILE
◦ GREP
◦ EDIT FILE
◦ DIRECTORY LISTING
◦ SHELL COMMAND

GREP tree fix
Improved GREP formatting so sub-lines remain visually connected to their parent query entry:

• prefixed sub-lines with │ connectors
• removed stray blank line in concise mode
• removed trailing newline that broke consecutive tree flow

Behavior example

Before:
READ FILE /file1
READ FILE /file2
READ FILE /file3

After:
READ FILE
├─ 📂 /file1
├─ 📂 /file2
├─ 📂 /file3

Testing
Expanded renderer tests to verify:

• grouped banner behavior for consecutive same-type messages
• single-item banner+tree rendering
• grouping reset on different message types
• spinner transparency (does not break groups)
• grouped behavior for diff/shell
• cross-type grouping transitions

All targeted suites pass (tests/test_rich_renderer.py and broader messaging/rendering regression run).

Summary by CodeRabbit

  • New Features

    • Consecutive related messages now group under a single banner with tree-like continuation lines for clearer, compact output; grouping works across sync/async rendering paths.
  • Bug Fixes

    • Grouping state is reset on render errors and when message types change to prevent stale group behavior.
  • Style

    • Improved visual grouping for more compact and readable output.
  • Tests

    • Added comprehensive tests covering grouping and rendering behavior.

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Adds grouping infrastructure to the rich renderer so consecutive messages of certain types render with a single banner and subsequent items as continuation (tree-style) lines. Tracks the last rendered message type, treats some messages as transparent, and updates many render methods and the sync/async dispatch to honor grouping.

Changes

Cohort / File(s) Summary
Renderer core
code_puppy/messaging/rich_renderer.py
Adds GROUPABLE_TYPES and TRANSPARENT_TYPES, class _GROUPABLE_TYPES/_TRANSPARENT_TYPES, instance attribute _last_rendered_type, and private _is_continuation(); updates render flow to maintain grouping state, reset on errors, and route sync messages through a shared _render_sync path.
Grouped renderers
code_puppy/messaging/rich_renderer.py
Refactors multiple render methods (FileListing, FileContent, GrepResult, Diff, ShellStart, etc.) to print banners only for first item in a group and render subsequent items with tree-like "├─" continuation prefixes; respects transparent types when updating grouping state.
Tests
tests/test_rich_renderer.py
Adds comprehensive tests covering grouping behavior for FileContentMessage, DiffMessage, ShellStartMessage, GrepResultMessage, FileListingMessage, SpinnerControl, async vs sync paths, grouping resets on type changes, and rendering/error dispatch scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble banners, one by one,

then line up branches when work is done.
A single header, then a tidy tree,
continuations hop along with me.
Hooray — grouped messages, neat as can be!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing grouping of consecutive tool outputs under shared tree banners in the renderer, which is the core focus of both file modifications.
Docstring Coverage ✅ Passed Docstring coverage is 95.24% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
code_puppy/messaging/rich_renderer.py (2)

340-351: ⚠️ Potential issue | 🟠 Major

Async render path does not update _last_rendered_type — grouping is silently broken.

The sync path (_render_sync, line 243) correctly updates _last_rendered_type after _do_render. However, the async render method calls _do_render directly at line 351 without updating grouping state. If the async path is ever used for rendering (e.g., via start_async or a future async consumer), consecutive groupable messages will each print their own banner instead of grouping.

Proposed fix
     async def render(self, message: AnyMessage) -> None:
         """Render a message asynchronously (supports user input requests)."""
         # Handle async-only message types
         if isinstance(message, UserInputRequest):
             await self._render_user_input_request(message)
         elif isinstance(message, ConfirmationRequest):
             await self._render_confirmation_request(message)
         elif isinstance(message, SelectionRequest):
             await self._render_selection_request(message)
         else:
             # Use sync render for everything else
-            self._do_render(message)
+            self._render_sync(message)
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@code_puppy/messaging/rich_renderer.py` around lines 340 - 351, The async
render method fails to update grouping state: after calling _do_render in render
(async), set self._last_rendered_type to the rendered message's type just like
_render_sync does so grouping works; locate render and, after the branch that
calls self._do_render(message), assign the appropriate type to
self._last_rendered_type (using the same logic/_type value used in _render_sync)
so async-rendered messages update the grouping state.

243-253: ⚠️ Potential issue | 🟡 Minor

Rendering error leaves stale grouping state — next message may incorrectly skip its banner.

If _do_render raises (caught at line 251), _last_rendered_type retains the value from the previous successful render. If the next message happens to be the same groupable type as that stale value, _is_continuation returns True and the banner is suppressed — even though no banner was actually printed in the failed render. Consider resetting _last_rendered_type to None in the except block.

Proposed fix
         except Exception as e:
             safe_error = escape_rich_markup(str(e))
             self._console.print(f"[dim red]Render error: {safe_error}[/dim red]")
+            self._last_rendered_type = None  # Reset grouping on error
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@code_puppy/messaging/rich_renderer.py` around lines 243 - 253, The except
block in _render_sync leaves _last_rendered_type unchanged after a failed
_do_render, causing subsequent messages of the same groupable type to be treated
as continuations and skip their banner; modify the except block in _render_sync
so that after catching the exception you reset self._last_rendered_type = None
(or otherwise clear grouping state), then log the escaped error as before;
ensure you still respect _TRANSPARENT_TYPES logic elsewhere and only clear the
state on failures inside _render_sync.
🤖 Fix all issues with AI agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.

In `@code_puppy/messaging/rich_renderer.py`:
- Around line 114-118: The class-level mutable sets _GROUPABLE_TYPES and
_TRANSPARENT_TYPES should be made immutable: replace their final populated
values with frozenset instances instead of mutating set() after import-time
population; update the comment to remove the misleading "Populated after message
imports are available" note (or move the definitions to module-level constants)
and ensure the population code that currently builds these sets (the
module-level population block) constructs frozenset(...) and assigns it to the
class attributes (or assigns module-level FROZEN_GROUPABLE_TYPES /
FROZEN_TRANSPARENT_TYPES) so callers see immutable containers.
- Around line 1164-1172: The class attributes
RichConsoleRenderer._GROUPABLE_TYPES and _TRANSPARENT_TYPES are being mutated
after class definition; change them to stable declarations instead: define
module-level frozenset constants (e.g., GROUPABLE_TYPES =
frozenset({FileContentMessage, GrepResultMessage, DiffMessage,
FileListingMessage, ShellStartMessage}) and TRANSPARENT_TYPES =
frozenset({SpinnerControl})) or set them directly inside the RichConsoleRenderer
class body (_GROUPABLE_TYPES = GROUPABLE_TYPES, _TRANSPARENT_TYPES =
TRANSPARENT_TYPES) and remove the post-definition mutation block that references
FileContentMessage, GrepResultMessage, DiffMessage, FileListingMessage,
ShellStartMessage, and SpinnerControl so the sets are populated
deterministically at import time.
- Around line 340-351: The async render method fails to update grouping state:
after calling _do_render in render (async), set self._last_rendered_type to the
rendered message's type just like _render_sync does so grouping works; locate
render and, after the branch that calls self._do_render(message), assign the
appropriate type to self._last_rendered_type (using the same logic/_type value
used in _render_sync) so async-rendered messages update the grouping state.
- Around line 243-253: The except block in _render_sync leaves
_last_rendered_type unchanged after a failed _do_render, causing subsequent
messages of the same groupable type to be treated as continuations and skip
their banner; modify the except block in _render_sync so that after catching the
exception you reset self._last_rendered_type = None (or otherwise clear grouping
state), then log the escaped error as before; ensure you still respect
_TRANSPARENT_TYPES logic elsewhere and only clear the state on failures inside
_render_sync.

In `@tests/test_rich_renderer.py`:
- Around line 675-707: Add an async test that mirrors
test_grouping_resets_across_different_groupable_types but exercises the async
render path: create renderer and console via _make_renderer(), mock
renderer._get_banner_color, construct a FileContentMessage and a
GrepResultMessage, await renderer.render(file_msg) then await
renderer.render(grep_msg), collect printed strings from
console.print.call_args_list, and assert that both "READ FILE" and "GREP"
banners are present; this will exercise render -> _do_render and ensure
_last_rendered_type is handled in the async path.
🧹 Nitpick comments (3)
🤖 Fix all nitpicks with AI agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.

In `@code_puppy/messaging/rich_renderer.py`:
- Around line 114-118: The class-level mutable sets _GROUPABLE_TYPES and
_TRANSPARENT_TYPES should be made immutable: replace their final populated
values with frozenset instances instead of mutating set() after import-time
population; update the comment to remove the misleading "Populated after message
imports are available" note (or move the definitions to module-level constants)
and ensure the population code that currently builds these sets (the
module-level population block) constructs frozenset(...) and assigns it to the
class attributes (or assigns module-level FROZEN_GROUPABLE_TYPES /
FROZEN_TRANSPARENT_TYPES) so callers see immutable containers.
- Around line 1164-1172: The class attributes
RichConsoleRenderer._GROUPABLE_TYPES and _TRANSPARENT_TYPES are being mutated
after class definition; change them to stable declarations instead: define
module-level frozenset constants (e.g., GROUPABLE_TYPES =
frozenset({FileContentMessage, GrepResultMessage, DiffMessage,
FileListingMessage, ShellStartMessage}) and TRANSPARENT_TYPES =
frozenset({SpinnerControl})) or set them directly inside the RichConsoleRenderer
class body (_GROUPABLE_TYPES = GROUPABLE_TYPES, _TRANSPARENT_TYPES =
TRANSPARENT_TYPES) and remove the post-definition mutation block that references
FileContentMessage, GrepResultMessage, DiffMessage, FileListingMessage,
ShellStartMessage, and SpinnerControl so the sets are populated
deterministically at import time.

In `@tests/test_rich_renderer.py`:
- Around line 675-707: Add an async test that mirrors
test_grouping_resets_across_different_groupable_types but exercises the async
render path: create renderer and console via _make_renderer(), mock
renderer._get_banner_color, construct a FileContentMessage and a
GrepResultMessage, await renderer.render(file_msg) then await
renderer.render(grep_msg), collect printed strings from
console.print.call_args_list, and assert that both "READ FILE" and "GREP"
banners are present; this will exercise render -> _do_render and ensure
_last_rendered_type is handled in the async path.
tests/test_rich_renderer.py (1)

675-707: Consider adding a test for async render grouping path.

The async render method (line 340-351 in rich_renderer.py) calls _do_render without updating _last_rendered_type. A test exercising grouping through the async path would catch this gap. Relates to the issue flagged in the renderer file.

🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@tests/test_rich_renderer.py` around lines 675 - 707, Add an async test that
mirrors test_grouping_resets_across_different_groupable_types but exercises the
async render path: create renderer and console via _make_renderer(), mock
renderer._get_banner_color, construct a FileContentMessage and a
GrepResultMessage, await renderer.render(file_msg) then await
renderer.render(grep_msg), collect printed strings from
console.print.call_args_list, and assert that both "READ FILE" and "GREP"
banners are present; this will exercise render -> _do_render and ensure
_last_rendered_type is handled in the async path.
code_puppy/messaging/rich_renderer.py (2)

114-118: Class-level mutable sets could be frozenset for clarity.

Since _GROUPABLE_TYPES and _TRANSPARENT_TYPES are populated once at module level (lines 1164-1172) and never mutated after that, using frozenset at the population site would communicate immutability intent. Also, the comment "Populated after message imports are available" is misleading — the message classes are already imported at lines 31-56 (top of file), so the sets could be defined inline in the class body or as module-level constants.

🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@code_puppy/messaging/rich_renderer.py` around lines 114 - 118, The
class-level mutable sets _GROUPABLE_TYPES and _TRANSPARENT_TYPES should be made
immutable: replace their final populated values with frozenset instances instead
of mutating set() after import-time population; update the comment to remove the
misleading "Populated after message imports are available" note (or move the
definitions to module-level constants) and ensure the population code that
currently builds these sets (the module-level population block) constructs
frozenset(...) and assigns it to the class attributes (or assigns module-level
FROZEN_GROUPABLE_TYPES / FROZEN_TRANSPARENT_TYPES) so callers see immutable
containers.

1164-1172: Consider defining these as module-level constants instead of mutating class attributes post-definition.

This pattern of initializing empty class-level sets and populating them after the class body works, but it's fragile — a subclass or test could inadvertently observe the empty sets if it references the class before this module-level code executes. Since all the message classes are already imported at the top of the file, you could define them inline in the class body or as module-level frozenset constants.

Example alternative
 class RichConsoleRenderer:
-    _GROUPABLE_TYPES: set = set()
-    _TRANSPARENT_TYPES: set = set()
+    _GROUPABLE_TYPES: frozenset = frozenset({
+        FileContentMessage,
+        GrepResultMessage,
+        DiffMessage,
+        FileListingMessage,
+        ShellStartMessage,
+    })
+    _TRANSPARENT_TYPES: frozenset = frozenset({SpinnerControl})

And remove lines 1164-1172.

🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@code_puppy/messaging/rich_renderer.py` around lines 1164 - 1172, The class
attributes RichConsoleRenderer._GROUPABLE_TYPES and _TRANSPARENT_TYPES are being
mutated after class definition; change them to stable declarations instead:
define module-level frozenset constants (e.g., GROUPABLE_TYPES =
frozenset({FileContentMessage, GrepResultMessage, DiffMessage,
FileListingMessage, ShellStartMessage}) and TRANSPARENT_TYPES =
frozenset({SpinnerControl})) or set them directly inside the RichConsoleRenderer
class body (_GROUPABLE_TYPES = GROUPABLE_TYPES, _TRANSPARENT_TYPES =
TRANSPARENT_TYPES) and remove the post-definition mutation block that references
FileContentMessage, GrepResultMessage, DiffMessage, FileListingMessage,
ShellStartMessage, and SpinnerControl so the sets are populated
deterministically at import time.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tests/test_rich_renderer.py (2)

449-672: Consider extracting the repeated printed filter into a test helper.

The pattern of collecting printed strings from mock calls is duplicated across all seven new tests. A small helper would reduce noise and make future tests easier to write.

Example helper
def _get_printed_strings(console: Mock) -> list[str]:
    """Extract all plain string arguments from console.print calls."""
    return [
        call.args[0]
        for call in console.print.call_args_list
        if call.args and isinstance(call.args[0], str)
    ]

Then usage becomes:

printed = _get_printed_strings(console)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rich_renderer.py` around lines 449 - 672, The tests repeat the
same logic to extract printed strings from console.print mocks; add a small
helper function named _get_printed_strings(console: Mock) that returns the list
of plain string args from console.print.call_args_list, then replace the
duplicated list comprehensions in the test functions (e.g.,
test_consecutive_file_content_groups_under_single_banner,
test_single_file_content_still_has_banner_and_tree,
test_different_type_breaks_grouping, test_spinner_does_not_break_grouping,
test_consecutive_diff_groups, test_consecutive_shell_start_groups) with a single
call to _get_printed_strings(console) to reduce duplication and improve
readability.

449-742: Consider adding a test for grouping state reset on render error.

The implementation resets _last_rendered_type = None in the except block of _render_sync (rich_renderer.py Line 263). There's no test verifying that an error during rendering of a groupable message correctly breaks the group for the next message of the same type.

Example test
def test_render_error_resets_grouping() -> None:
    """A render error should break grouping for subsequent messages."""
    renderer, console = _make_renderer()
    renderer._get_banner_color = Mock(return_value="blue")

    msg1 = FileContentMessage(
        path="/src/a.py", content="a",
        start_line=None, num_lines=None,
        total_lines=1, num_tokens=1,
    )
    # First renders normally
    renderer._render_sync(msg1)

    # Inject a failure for the next render
    original = renderer._do_render
    def boom(m):
        if getattr(m, 'path', '') == '/src/err.py':
            raise RuntimeError("boom")
        original(m)
    renderer._do_render = boom

    err_msg = FileContentMessage(
        path="/src/err.py", content="x",
        start_line=None, num_lines=None,
        total_lines=1, num_tokens=1,
    )
    renderer._render_sync(err_msg)

    # Restore and render another — should get a new banner
    renderer._do_render = original
    msg3 = FileContentMessage(
        path="/src/b.py", content="b",
        start_line=None, num_lines=None,
        total_lines=1, num_tokens=1,
    )
    renderer._render_sync(msg3)

    printed = [
        call.args[0] for call in console.print.call_args_list
        if call.args and isinstance(call.args[0], str)
    ]
    banner_lines = [p for p in printed if "READ FILE" in p]
    assert len(banner_lines) == 2, f"Expected 2 banners (error should reset group): {banner_lines}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rich_renderer.py` around lines 449 - 742, Add a new test ensuring
that when _render_sync raises an exception the grouping state is reset by
verifying _last_rendered_type becomes None for the next groupable message;
specifically, follow the pattern in the suggested example: render a
FileContentMessage via renderer._render_sync, monkeypatch renderer._do_render to
raise for a second FileContentMessage (path '/src/err.py') so _render_sync hits
the except block that sets _last_rendered_type = None, then restore _do_render
and render a third FileContentMessage and assert there are two "READ FILE"
banners printed; reference _render_sync, _do_render, and _last_rendered_type to
locate the behavior in rich_renderer.py.
code_puppy/messaging/rich_renderer.py (2)

419-425: Trailing \n in tree-child line creates visual inconsistency with other grouped renderers.

Line 424 includes a trailing \n inside the f-string, which adds an extra blank line after the tree-child entry. The other grouped renderers (_render_file_content at Line 550, _render_grep_result at Line 565, _render_diff at Line 655, _render_shell_start at Line 704) do not include this trailing newline. When multiple FileListingMessage items are grouped, this results in extra vertical spacing between entries that doesn't appear for other grouped types.

Remove trailing `\n` for consistency
         self._console.print(
-            f"  ├─ 📂 [bold cyan]{msg.directory}[/bold cyan] [dim]{rec_flag}[/dim]\n"
+            f"  ├─ 📂 [bold cyan]{msg.directory}[/bold cyan] [dim]{rec_flag}[/dim]"
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/messaging/rich_renderer.py` around lines 419 - 425, The tree-child
print in _render for FileListingMessage is appending a trailing "\n" in the
f-string which causes extra blank lines when grouped; locate the block that
checks self._is_continuation(FileListingMessage) and prints via
self._console.print(f"  ├─ 📂 [bold cyan]{msg.directory}[/bold cyan]
[dim]{rec_flag}[/dim]\n") and remove the trailing "\n" so the call becomes
consistent with other grouped renderers (keep the rest of the string and
surrounding logic unchanged).

125-129: Consider exporting GROUPABLE_TYPES and TRANSPARENT_TYPES in __all__.

The module-level GROUPABLE_TYPES and TRANSPARENT_TYPES (Lines 101–110) are public-facing constants (no underscore prefix), but they're not listed in __all__. If they're meant to be internal, the convention would be to prefix them with _. If external consumers (plugins, tests) should reference them, add them to __all__.

Also applies to: 1180-1184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/messaging/rich_renderer.py` around lines 125 - 129, The public
constants GROUPABLE_TYPES and TRANSPARENT_TYPES are not exported via __all__
while their underscored aliases _GROUPABLE_TYPES and _TRANSPARENT_TYPES are used
internally; decide whether they are public or private and make it consistent: if
they should be public, add "GROUPABLE_TYPES" and "TRANSPARENT_TYPES" to the
module's __all__ list; if they should be private, rename the top-level symbols
to _GROUPABLE_TYPES and _TRANSPARENT_TYPES (or remove the non-underscored names)
and update any references to use the underscored names (e.g., where
_GROUPABLE_TYPES and _TRANSPARENT_TYPES are assigned) so the module export
surface is correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code_puppy/messaging/rich_renderer.py`:
- Around line 419-425: The tree-child print in _render for FileListingMessage is
appending a trailing "\n" in the f-string which causes extra blank lines when
grouped; locate the block that checks self._is_continuation(FileListingMessage)
and prints via self._console.print(f"  ├─ 📂 [bold cyan]{msg.directory}[/bold
cyan] [dim]{rec_flag}[/dim]\n") and remove the trailing "\n" so the call becomes
consistent with other grouped renderers (keep the rest of the string and
surrounding logic unchanged).
- Around line 125-129: The public constants GROUPABLE_TYPES and
TRANSPARENT_TYPES are not exported via __all__ while their underscored aliases
_GROUPABLE_TYPES and _TRANSPARENT_TYPES are used internally; decide whether they
are public or private and make it consistent: if they should be public, add
"GROUPABLE_TYPES" and "TRANSPARENT_TYPES" to the module's __all__ list; if they
should be private, rename the top-level symbols to _GROUPABLE_TYPES and
_TRANSPARENT_TYPES (or remove the non-underscored names) and update any
references to use the underscored names (e.g., where _GROUPABLE_TYPES and
_TRANSPARENT_TYPES are assigned) so the module export surface is correct.

In `@tests/test_rich_renderer.py`:
- Around line 449-672: The tests repeat the same logic to extract printed
strings from console.print mocks; add a small helper function named
_get_printed_strings(console: Mock) that returns the list of plain string args
from console.print.call_args_list, then replace the duplicated list
comprehensions in the test functions (e.g.,
test_consecutive_file_content_groups_under_single_banner,
test_single_file_content_still_has_banner_and_tree,
test_different_type_breaks_grouping, test_spinner_does_not_break_grouping,
test_consecutive_diff_groups, test_consecutive_shell_start_groups) with a single
call to _get_printed_strings(console) to reduce duplication and improve
readability.
- Around line 449-742: Add a new test ensuring that when _render_sync raises an
exception the grouping state is reset by verifying _last_rendered_type becomes
None for the next groupable message; specifically, follow the pattern in the
suggested example: render a FileContentMessage via renderer._render_sync,
monkeypatch renderer._do_render to raise for a second FileContentMessage (path
'/src/err.py') so _render_sync hits the except block that sets
_last_rendered_type = None, then restore _do_render and render a third
FileContentMessage and assert there are two "READ FILE" banners printed;
reference _render_sync, _do_render, and _last_rendered_type to locate the
behavior in rich_renderer.py.

@mpfaffenberger
Copy link
Owner

@nhicks00 - Wanna try this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments