diff --git a/src/toad/frame_presentation.py b/src/toad/frame_presentation.py index 541be9c76..3e8bbd805 100644 --- a/src/toad/frame_presentation.py +++ b/src/toad/frame_presentation.py @@ -12,6 +12,7 @@ from agent_comms.nominal.declared_family import DeclaredFamily from textual._paint_state import PaintState +from textual.geometry import Region from textual.app import ScreenStackError, UnknownModeError from textual import events from textual.strip import Strip @@ -537,8 +538,21 @@ def frame_moved(self) -> None: def redraw(self) -> None: """A frame paints over the lines (paint_effects in render_line); the content they - show is unchanged, so it is not rendered again.""" - self.repaint_lines() + show is unchanged, so it is not rendered again. Invalidate only the lines the + running effects actually paint; a one-row throbber or a two-row sidebar name + should not flush the styles cache of a 200-line conversation widget.""" + if not self.effects: + return + region = self._effect_region() + if region is not None: + self._invalidate_lines(region) + self._repaint_required = True + else: + self.repaint_lines() + + def _effect_region(self) -> Region | None: + """The region the running effects paint, or None for the whole widget.""" + return None class EffectInContent: diff --git a/src/toad/widgets/conversation.py b/src/toad/widgets/conversation.py index 47029deec..857bb699e 100644 --- a/src/toad/widgets/conversation.py +++ b/src/toad/widgets/conversation.py @@ -375,6 +375,13 @@ def on_mount(self) -> None: def animation_visible(self) -> bool: return super().animation_visible and self.query_ancestor(Window).has_focus + def _effect_region(self) -> Region | None: + cursor = self.query_ancestor(Conversation).navigation.cursor + visible = cursor.visible_region + if visible.height == 0: + return Region(0, 0, 1, 0) + return Region(0, visible.y - self.region.y, 1, visible.height) + def render_lines(self, crop: Region) -> list[Strip]: colours = self.app.colours strips = [colours.paint_mix(self.RAIL, Strip([Segment("▌")], cell_length=1))] * crop.height