fix(tui): break infinite re-render loop for images in cmux

This commit is contained in:
Tibsfox 2026-04-05 10:30:13 -07:00
parent 092d1c0a9e
commit 11239140db
2 changed files with 25 additions and 4 deletions

View file

@ -3,6 +3,7 @@ import {
Container,
getCapabilities,
Image,
type ImageDimensions,
imageFallback,
Spacer,
Text,
@ -88,6 +89,9 @@ export class ToolExecutionComponent extends Container {
private editDiffArgsKey?: string; // Track which args the preview is for
// Cached converted images for Kitty protocol (which requires PNG), keyed by index
private convertedImages: Map<number, { data: string; mimeType: string }> = new Map();
// Cached resolved image dimensions to avoid re-triggering async parsing
// when updateDisplay() recreates Image components (#3455).
private resolvedImageDimensions: Map<number, ImageDimensions> = new Map();
// Incremental syntax highlighting cache for write tool call args
private writeHighlightCache?: WriteHighlightCache;
// When true, this component intentionally renders no lines
@ -472,16 +476,28 @@ export class ToolExecutionComponent extends Container {
const spacer = new Spacer(1);
this.addChild(spacer);
this.imageSpacers.push(spacer);
// Pass cached dimensions to avoid re-triggering async parsing
// when updateDisplay() recreates Image components (#3455).
const cachedDims = this.resolvedImageDimensions.get(i);
const imageComponent = new Image(
imageData,
imageMimeType,
{ fallbackColor: (s: string) => theme.fg("toolOutput", s) },
{ maxWidthCells: 60 },
cachedDims,
);
imageComponent.setOnDimensionsResolved(() => {
this.updateDisplay();
this.ui.requestRender();
});
if (!cachedDims) {
const imgIdx = i;
imageComponent.setOnDimensionsResolved(() => {
// Cache resolved dimensions so future updateDisplay() calls
// don't re-trigger async parsing → infinite loop (#3455).
const dims = imageComponent.getDimensions?.();
if (dims) this.resolvedImageDimensions.set(imgIdx, dims);
// Just re-render — don't call updateDisplay() which would
// destroy and recreate all Image components.
this.ui.requestRender();
});
}
this.imageComponents.push(imageComponent);
this.addChild(imageComponent);
}

View file

@ -72,6 +72,11 @@ export class Image implements Component {
return this.imageId;
}
/** Get the resolved image dimensions (for caching across recreations). */
getDimensions(): ImageDimensions | undefined {
return this.dimensionsResolved ? this.dimensions : undefined;
}
invalidate(): void {
this.cachedLines = undefined;
this.cachedWidth = undefined;