diff --git a/packages/pi-tui/src/components/cancellable-loader.ts b/packages/pi-tui/src/components/cancellable-loader.ts index 506b763de..e790659e1 100644 --- a/packages/pi-tui/src/components/cancellable-loader.ts +++ b/packages/pi-tui/src/components/cancellable-loader.ts @@ -35,6 +35,8 @@ export class CancellableLoader extends Loader { } dispose(): void { + this.abortController.abort(); + this.onAbort = undefined; this.stop(); } } diff --git a/packages/pi-tui/src/components/editor.ts b/packages/pi-tui/src/components/editor.ts index 768439289..fdcf402ea 100644 --- a/packages/pi-tui/src/components/editor.ts +++ b/packages/pi-tui/src/components/editor.ts @@ -2055,6 +2055,10 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/ this.lastAutocompleteLookupPrefix = null; } + public dispose(): void { + this.clearAutocompleteDebounce(); + } + public isShowingAutocomplete(): boolean { return this.autocompleteState !== null; } diff --git a/packages/pi-tui/src/components/input.ts b/packages/pi-tui/src/components/input.ts index e5c3b4f7f..4feb81912 100644 --- a/packages/pi-tui/src/components/input.ts +++ b/packages/pi-tui/src/components/input.ts @@ -22,7 +22,17 @@ export class Input implements Component, Focusable { public onEscape?: () => void; /** Focusable interface - set by TUI when focus changes */ - focused: boolean = false; + private _focused: boolean = false; + get focused(): boolean { + return this._focused; + } + set focused(value: boolean) { + this._focused = value; + if (!value) { + this.isInPaste = false; + this.pasteBuffer = ""; + } + } // Bracketed paste mode buffering private pasteBuffer: string = ""; diff --git a/packages/pi-tui/src/components/loader.ts b/packages/pi-tui/src/components/loader.ts index b071e8ee2..a55a2570c 100644 --- a/packages/pi-tui/src/components/loader.ts +++ b/packages/pi-tui/src/components/loader.ts @@ -26,6 +26,9 @@ export class Loader extends Text { } start() { + if (this.intervalId) { + clearInterval(this.intervalId); + } this.updateDisplay(); this.intervalId = setInterval(() => { this.currentFrame = (this.currentFrame + 1) % this.frames.length; @@ -40,6 +43,11 @@ export class Loader extends Text { } } + dispose() { + this.stop(); + this.ui = null; + } + setMessage(message: string) { this.message = message; this.updateDisplay(); diff --git a/packages/pi-tui/src/tui.ts b/packages/pi-tui/src/tui.ts index 89537f1b3..c3e39acc5 100644 --- a/packages/pi-tui/src/tui.ts +++ b/packages/pi-tui/src/tui.ts @@ -441,6 +441,15 @@ export class TUI extends Container { stop(): void { this.stopped = true; + + // Dispose all overlays to stop any running timers + for (const entry of this.overlayStack) { + if ("dispose" in entry.component && typeof (entry.component as any).dispose === "function") { + (entry.component as any).dispose(); + } + } + this.overlayStack = []; + // Move cursor to the end of the content to prevent overwriting/artifacts on exit if (this.previousLines.length > 0) { const targetRow = this.previousLines.length; // Line after the last content