fix(tui): repaint fully while scrolling chat
- commit
- a739ef9a8b83cead4c6a083259ccf761c4e6fa72
- author
- patriceckhart · · 2026-04-21 19:29 UTC
- parent
- 43aca0b
- signature
- unsigned
- refs
- tag: v0.0.87
2 files · +17 -0
M internal/agent/modes/interactive.go +10 -0
| @@ -530,6 +530,13 @@ func (i *Interactive) scrollBy(delta int) { | |||
| 530 | 530 | i.parkedTurn = 0 | |
| 531 | 531 | i.parkedTotal = 0 | |
| 532 | 532 | } | |
| 533 | + | if i.rend != nil { | |
| 534 | + | // VS Code's terminal is especially prone to leaving stray | |
| 535 | + | // wrapped-character fragments behind during scroll-driven | |
| 536 | + | // viewport changes. Force a full repaint on scroll, but | |
| 537 | + | // avoid a whole-screen clear because that visibly flickers. | |
| 538 | + | i.rend.Invalidate() | |
| 539 | + | } | |
| 533 | 540 | i.mu.Unlock() | |
| 534 | 541 | i.invalidate() | |
| 535 | 542 | } | |
| @@ -540,6 +547,9 @@ func (i *Interactive) scrollToBottom() { | |||
| 540 | 547 | i.scrollOffset = 0 | |
| 541 | 548 | i.parkedTurn = 0 | |
| 542 | 549 | i.parkedTotal = 0 | |
| 550 | + | if i.rend != nil { | |
| 551 | + | i.rend.Invalidate() | |
| 552 | + | } | |
| 543 | 553 | i.mu.Unlock() | |
| 544 | 554 | i.invalidate() | |
| 545 | 555 | } | |
| @@ -530,6 +530,13 @@ func (i *Interactive) scrollBy(delta int) { | |||||
| 530 | i.parkedTurn = 0 | 530 | i.parkedTurn = 0 | ||
| 531 | i.parkedTotal = 0 | 531 | i.parkedTotal = 0 | ||
| 532 | } | 532 | } | ||
| 533 | + | if i.rend != nil { | |||
| 534 | + | // VS Code's terminal is especially prone to leaving stray | |||
| 535 | + | // wrapped-character fragments behind during scroll-driven | |||
| 536 | + | // viewport changes. Force a full repaint on scroll, but | |||
| 537 | + | // avoid a whole-screen clear because that visibly flickers. | |||
| 538 | + | i.rend.Invalidate() | |||
| 539 | + | } | |||
| 533 | i.mu.Unlock() | 540 | i.mu.Unlock() | ||
| 534 | i.invalidate() | 541 | i.invalidate() | ||
| 535 | } | 542 | } | ||
| @@ -540,6 +547,9 @@ func (i *Interactive) scrollToBottom() { | |||||
| 540 | i.scrollOffset = 0 | 547 | i.scrollOffset = 0 | ||
| 541 | i.parkedTurn = 0 | 548 | i.parkedTurn = 0 | ||
| 542 | i.parkedTotal = 0 | 549 | i.parkedTotal = 0 | ||
| 550 | + | if i.rend != nil { | |||
| 551 | + | i.rend.Invalidate() | |||
| 552 | + | } | |||
| 543 | i.mu.Unlock() | 553 | i.mu.Unlock() | ||
| 544 | i.invalidate() | 554 | i.invalidate() | ||
| 545 | } | 555 | } | ||
M internal/tui/render.go +7 -0
| @@ -62,6 +62,13 @@ func (r *Renderer) Clear() { | |||
| 62 | 62 | _, _ = io.WriteString(r.out, SeqClearScreen) | |
| 63 | 63 | } | |
| 64 | 64 | ||
| 65 | + | // Invalidate forces a full repaint on the next Draw without clearing the | |
| 66 | + | // whole terminal first. Useful when the cached diff is unreliable but a | |
| 67 | + | // visible full-screen flash would be too distracting. | |
| 68 | + | func (r *Renderer) Invalidate() { | |
| 69 | + | r.prev = nil | |
| 70 | + | } | |
| 71 | + | ||
| 65 | 72 | // Draw updates the terminal so that the visible frame ends with the | |
| 66 | 73 | // given lines (bottom-aligned). cursorRow/cursorCol are offsets within | |
| 67 | 74 | // the lines slice indicating where to place the terminal cursor; use | |
| @@ -62,6 +62,13 @@ func (r *Renderer) Clear() { | |||||
| 62 | _, _ = io.WriteString(r.out, SeqClearScreen) | 62 | _, _ = io.WriteString(r.out, SeqClearScreen) | ||
| 63 | } | 63 | } | ||
| 64 | 64 | ||||
| 65 | + | // Invalidate forces a full repaint on the next Draw without clearing the | |||
| 66 | + | // whole terminal first. Useful when the cached diff is unreliable but a | |||
| 67 | + | // visible full-screen flash would be too distracting. | |||
| 68 | + | func (r *Renderer) Invalidate() { | |||
| 69 | + | r.prev = nil | |||
| 70 | + | } | |||
| 71 | + | ||||
| 65 | // Draw updates the terminal so that the visible frame ends with the | 72 | // Draw updates the terminal so that the visible frame ends with the | ||
| 66 | // given lines (bottom-aligned). cursorRow/cursorCol are offsets within | 73 | // given lines (bottom-aligned). cursorRow/cursorCol are offsets within | ||
| 67 | // the lines slice indicating where to place the terminal cursor; use | 74 | // the lines slice indicating where to place the terminal cursor; use | ||