Skip to content

A fade frame repaints an entry's lines; it no longer renders the entry again - #1558

Merged
trissim merged 1 commit into
mainfrom
fade-render-cache
Nov 6, 2026
Merged

trissim merged 1 commit into
mainfrom
fade-render-cache

Conversation

@trissim

@trissim trissim commented Nov 6, 2026

Copy link
Copy Markdown

1 commit, 9 files. Lands after OpenHCSDev/textual#246 (merged).

The bug

ConversationEntry.repaint (block_navigation.py) called widget.refresh() on itself and every descendant on each animation frame. A full refresh() re-renders content, so a fading entry rebuilt every Markdown block and row, with a new rich Style per segment, for text that never changed. In the live profile, rich Style construction was 19% of the main thread.

The change

  • AnimationEffects.repaint and ConversationEntry.repaint use textual#246's refresh_lines(). The effect paints over the lines (paint_effects in render_line), and the content is kept.
  • EffectInContent, a mixin, is the other kind: widgets whose content reads the frame in render or their strips (FutureText, SplashText, the tab Underline, MessageDivider, Throbber). Each declares it in its base list and renders again.
  • on_hide/on_show call self.repaint() instead of refresh(), so each kind keeps its own repaint. SwitchToast reads its frame in render_line, so it repaints lines.

Proof

  • Measured with a probe in the real app over one arrival fade: 9 content renders in 21 frames before, 1 in 27 frames after (the initial render).
  • tests/textual/test_fade_frames_keep_content.py pins that an arrival fade renders its entry once.
  • Arrival fade, goal bar, tab strip and native goal bar tests pass. test_hidden_fades_tick_once_per_loop_turn fails identically on clean main.

🤖 Generated with Claude Code

…y again

ConversationEntry.repaint called widget.refresh() on itself and every descendant each animation
frame, and refresh() re-renders content: a fading entry rebuilt every Markdown block and row,
with a new rich Style per segment, for text that never changed. The live profile had rich Style
construction at 19% of the main thread; a probe over one arrival fade measured 9 content
renders in 21 frames.

With OpenHCSDev/textual#246's refresh_lines, an effect that paints over the lines repaints them:
AnimationEffects.repaint and the entry's walk use it, and on_hide/on_show call repaint, so each
kind keeps its own. EffectInContent is the kind whose content reads the frame (FutureText,
SplashText, the tab Underline, MessageDivider, Throbber): it renders again. SwitchToast reads
its frame in render_line, so it repaints lines. After: 1 render in 27 frames, the initial one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 06:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

debt-src at 65edf97 passes.

@trissim
trissim merged commit 3c408ab into main Nov 6, 2026
3 of 4 checks passed
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