Files
oh-my-pi/packages/coding-agent/src/modes/components/countdown-timer.ts
T
daandden 60086d8a49 fix(ask): restore ask timeout auto-select behavior (#269)
* fix(coding-agent): restore ask timeout auto-select behavior

Fixes #266

* fix(coding-agent): derive ask timeout from ui timeout events

* fix(coding-agent): enforce ask timeout during custom input

* fix(ask): preserve sub-second timeout precision in interactive flow

* fix(ask): reset hook input timeout on user activity

* fix(ask): keep expiration timeout when refreshing tick interval

* fix(ask): auto-select deterministic default on timeout

Fixes #266

---------

Co-authored-by: Vũ Anh Nguyễn <vu.anh.nguyen@mgm-tp.com>
Co-authored-by: can1357 <me@can.ac>
2026-03-03 14:45:06 +01:00

76 lines
1.8 KiB
TypeScript

/**
* Reusable countdown timer for dialog components.
*/
import type { TUI } from "@oh-my-pi/pi-tui";
export class CountdownTimer {
#intervalId: NodeJS.Timeout | undefined;
#expireTimeoutId: NodeJS.Timeout | undefined;
#remainingSeconds: number;
#deadlineMs = 0;
readonly #initialMs: number;
constructor(
timeoutMs: number,
private tui: TUI | undefined,
private onTick: (seconds: number) => void,
private onExpire: () => void,
) {
this.#initialMs = timeoutMs;
this.#remainingSeconds = Math.ceil(timeoutMs / 1000);
this.#start();
}
#calculateRemainingSeconds(now = Date.now()): number {
const remainingMs = Math.max(0, this.#deadlineMs - now);
return Math.ceil(remainingMs / 1000);
}
#start(): void {
const now = Date.now();
this.#deadlineMs = now + this.#initialMs;
this.#remainingSeconds = this.#calculateRemainingSeconds(now);
this.onTick(this.#remainingSeconds);
this.tui?.requestRender();
this.#expireTimeoutId = setTimeout(() => {
this.dispose();
this.onExpire();
}, this.#initialMs);
this.#startInterval();
}
#startInterval(): void {
if (this.#intervalId) {
clearInterval(this.#intervalId);
this.#intervalId = undefined;
}
this.#intervalId = setInterval(() => {
const remainingSeconds = this.#calculateRemainingSeconds();
if (remainingSeconds !== this.#remainingSeconds) {
this.#remainingSeconds = remainingSeconds;
this.onTick(this.#remainingSeconds);
}
this.tui?.requestRender();
}, 1000);
}
/** Reset the countdown to its initial value */
reset(): void {
this.dispose();
this.#start();
}
dispose(): void {
if (this.#intervalId) {
clearInterval(this.#intervalId);
this.#intervalId = undefined;
}
if (this.#expireTimeoutId) {
clearTimeout(this.#expireTimeoutId);
this.#expireTimeoutId = undefined;
}
}
}