Skip to content

fix(core): force full repaint after resume - #1008

Merged
kitlangton merged 1 commit into
anomalyco:mainfrom
altendky:resume-repaint-v0.2.0
May 4, 2026
Merged

kitlangton merged 1 commit into
anomalyco:mainfrom
altendky:resume-repaint-v0.2.0

Conversation

@altendky

@altendky altendky commented May 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Force the next native render after CliRenderer.resume() so terminal contents are fully repainted after process suspend/resume.
  • Replace currentRenderBuffer.clear(this.backgroundColor) with the existing one-shot forceFullRepaintRequested latch.
  • Add regression coverage for main-screen, alternate-screen, and split-footer resume paths.

Problem

After Ctrl+Z / fg, or equivalent SIGTSTP / SIGCONT flows, the terminal can contain stale content while OpenTUI's render buffers still believe cells are current.

This is especially visible for blank or background-only regions: text and other non-background content may repaint, but blank/background cells can remain stale because diff rendering skips cells that compare equal.

The previous resume() behavior tried to invalidate rendering with:

this.currentRenderBuffer.clear(this.backgroundColor)

That was the wrong invalidation model for this bug. It clears OpenTUI's current render buffer to the terminal background/default renderer background, not necessarily to the application's themed background, such as opencode's theme background. For cells that should repaint as blanks or background-only theme cells, this can make the current buffer compare equal to the next frame and skip exactly the cells that need to overwrite stale terminal content.

Fix

resume() now requests a one-shot full repaint:

this.forceFullRepaintRequested = true

The existing renderNative() paths consume that latch and pass force=true to native rendering:

  • main/alternate rendering uses lib.render(..., true)
  • split-footer capture mode forces the next footer repaint path

This keeps repaint recovery inside the normal render loop instead of issuing immediate native renders or raw terminal clear sequences from resume().

Screenshots

Screenshot_20260501_104430 Screenshot_20260501_104515

Fixes

Fixes #922
Fixes anomalyco/opencode#16327
Fixes anomalyco/opencode#22423

Replaces

Closes #928
Closes anomalyco/opencode#16326

The core idea from #928 is retained: use native forced repaint instead of clearing currentRenderBuffer. This PR differs by using the existing one-shot render latch rather than calling native render immediately inside resume().

The repaint portion of anomalyco/opencode#16326 should no longer be needed because stale terminal repaint recovery belongs in OpenTUI rather than opencode app code.

Related But Not Replaced

These address external suspend cleanup / mouse/raw-mode leakage and are complementary:

If OpenTUI later handles external SIGTSTP / SIGCONT directly, that path should call the same suspend() / resume() methods so this repaint recovery still applies.

Testing

  • bun test src/tests/renderer.control.test.ts src/tests/renderer.console-startup.test.ts
  • bunx tsc --noEmit -p tsconfig.build.json
  • Manual opencode verification with Ctrl+Z then fg

kitlangton
kitlangton approved these changes May 3, 2026 •
@kitlangton
kitlangton enabled auto-merge (squash) May 3, 2026 02:44
@kitlangton
kitlangton merged commit 0208119 into anomalyco:main May 4, 2026
17 checks passed
@altendky

altendky commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

thanks!

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

Labels

None yet

Projects

None yet

3 participants