fix(pi-tui): patch 5 resource leak bugs in TUI components

- Loader: clear existing interval in start() to prevent orphaned timers
  on double-call; add dispose() to stop and null TUI ref
- CancellableLoader: abort the AbortController and clear onAbort in
  dispose() so external signal holders release the controller
- Editor: add public dispose() that clears the autocomplete debounce
  timer to prevent post-removal callbacks
- Input: convert focused to getter/setter that resets isInPaste and
  pasteBuffer on focus loss, preventing paste state corruption
- TUI.stop(): iterate overlayStack calling dispose() on each component
  before clearing, stopping overlay timers (e.g. dashboard refresh)
This commit is contained in:
Flux Labs 2026-03-15 09:56:36 -05:00
parent 2fdcc08eb0
commit ea2efe804f
5 changed files with 34 additions and 1 deletions

View file

@ -35,6 +35,8 @@ export class CancellableLoader extends Loader {
}
dispose(): void {
this.abortController.abort();
this.onAbort = undefined;
this.stop();
}
}

View file

@ -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;
}

View file

@ -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 = "";

View file

@ -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();

View file

@ -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