From 1668a59bffd8a2f91187a92d5d9f4259ea9715c9 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 22 Jan 2026 05:30:57 +0100 Subject: [PATCH] fix(pi-dosbox): force image re-render to prevent ghosting during streaming The TUI only re-renders changed lines. During agent streaming, the conversation lines above DOSBox change but DOSBox lines stay the same. This caused the image to stay at its old position while text scrolled. Fix: Add invisible timestamp to image line to force TUI to see it as changed every render, ensuring the image is always re-rendered at the correct position. --- .../pi-dosbox/src/dosbox-component.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-component.ts b/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-component.ts index 995c7249..c909b297 100644 --- a/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-component.ts +++ b/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-component.ts @@ -65,10 +65,6 @@ export class DosboxComponent implements Component { private imageTheme: ImageTheme; private loadingMessage = "Connecting to DOSBox..."; private errorMessage: string | null = null; - private cachedLines: string[] = []; - private cachedWidth = 0; - private cachedVersion = -1; - private version = 0; private disposed = false; private imageId: number; private kittyPushed = false; @@ -121,7 +117,6 @@ export class DosboxComponent implements Component { { maxWidthCells: MAX_WIDTH_CELLS, imageId: this.imageId }, { widthPx: width, heightPx: height }, ); - this.version++; this.tui.requestRender(); } @@ -195,7 +190,8 @@ export class DosboxComponent implements Component { } invalidate(): void { - this.cachedWidth = 0; + // No caching, so nothing to invalidate + // Image component handles its own caching } render(width: number): string[] { @@ -208,17 +204,18 @@ export class DosboxComponent implements Component { if (!this.image) { return [truncateToWidth("Waiting for DOSBox frame...", width)]; } - if (width === this.cachedWidth && this.cachedVersion === this.version) { - return this.cachedLines; - } + // Always render fresh - never cache. This ensures the image is re-rendered + // at the correct position even during streaming when lines above change. + // The cache in Image component handles avoiding redundant Kitty transmissions. const imageLines = this.image.render(width); const footer = truncateToWidth("\x1b[2mCtrl+Q to detach (DOSBox keeps running)\x1b[22m", width); - const lines = [...imageLines, footer]; - this.cachedLines = lines; - this.cachedWidth = width; - this.cachedVersion = this.version; + // Add a unique suffix to force TUI to see these lines as "changed" + // This ensures the image is always re-rendered at correct position + const timestamp = `\x1b[0m\x1b[8m${Date.now()}\x1b[28m`; + const lines = imageLines.map((line, i) => (i === imageLines.length - 1 ? line + timestamp : line)); + lines.push(footer); return lines; }