From df1d5c40eaf2847073b42f0ed534d36cbb6a8d68 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 22 Jan 2026 04:39:58 +0100 Subject: [PATCH] fix(tui): proper Kitty image ID management and cleanup - Add allocateImageId() to generate unique image IDs - Add deleteKittyImage() and deleteAllKittyImages() functions - Image component now tracks its ID and has dispose() method - renderImage() returns imageId for tracking - DOSBox: reuse single image ID for all frames, delete on dispose Fixes image accumulation hitting terminal quota and lingering images after component close. --- .../pi-dosbox/src/dosbox-component.ts | 19 +++++++++-- packages/tui/src/components/image.ts | 31 ++++++++++++++++- packages/tui/src/index.ts | 3 ++ packages/tui/src/terminal-image.ts | 34 +++++++++++++++++-- 4 files changed, 81 insertions(+), 6 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 65ec1313..37b91cbb 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 @@ -8,7 +8,16 @@ import { createRequire } from "node:module"; import { dirname } from "node:path"; import { deflateSync } from "node:zlib"; import type { Component } from "@mariozechner/pi-tui"; -import { Image, type ImageTheme, isKeyRelease, Key, matchesKey, truncateToWidth } from "@mariozechner/pi-tui"; +import { + allocateImageId, + deleteKittyImage, + Image, + type ImageTheme, + isKeyRelease, + Key, + matchesKey, + truncateToWidth, +} from "@mariozechner/pi-tui"; import type { CommandInterface, Emulators } from "emulators"; const MAX_WIDTH_CELLS = 120; @@ -47,6 +56,7 @@ export class DosboxComponent implements Component { private disposed = false; private bundleData?: Uint8Array; private kittyPushed = false; + private imageId: number; wantsKeyRelease = true; @@ -60,6 +70,7 @@ export class DosboxComponent implements Component { this.onClose = onClose; this.bundleData = bundleData; this.imageTheme = { fallbackColor }; + this.imageId = allocateImageId(); void this.init(); } @@ -124,7 +135,7 @@ export class DosboxComponent implements Component { base64, "image/png", this.imageTheme, - { maxWidthCells: MAX_WIDTH_CELLS }, + { maxWidthCells: MAX_WIDTH_CELLS, imageId: this.imageId }, { widthPx: this.frameWidth, heightPx: this.frameHeight }, ); this.version++; @@ -186,6 +197,10 @@ export class DosboxComponent implements Component { dispose(): void { if (this.disposed) return; this.disposed = true; + + // Delete the terminal image + process.stdout.write(deleteKittyImage(this.imageId)); + if (this.kittyPushed) { process.stdout.write("\x1b[