/** * When a tip is allowed to appear. * * `tips.ts` decides *what* to say. This decides *when*, and almost all of it is * about the times it must stay quiet. A tip is the lowest-priority thing on the * screen: it is worth showing only because the moment it uses was going to be * spent anyway, which means the instant that stops being true it must not show * at all. * * ## The two moments * * **Idle.** The prompt is up, nothing is streaming, and the user has not pressed * a key for {@link DEFAULT_IDLE_DELAY_MS}. They are reading the last reply, or * thinking, or have walked away. A tip here is read or it is not; either way it * cost nothing. * * **Streaming.** A turn has been running for {@link DEFAULT_STREAMING_DELAY_MS} * and the user is watching a spinner. This is the better of the two moments, * because the time is definitely being spent and the eye is definitely on the * bottom of the screen. * * ## The times it stays quiet * * - Whenever the band already has something on it. A tip must never delay, * replace, or queue behind a notification the user actually caused — it is * dropped, and the next timer comes round soon enough. * - Inside the cooldown after any tip. Two tips in quick succession reads as a * thing talking at you rather than a thing helping you. * - For the whole first {@link DEFAULT_GRACE_MS} of a session. Startup already * has a banner, a changelog and possibly warnings; adding a tip to that is * noise on top of noise. * - Whenever the user has turned tips off. * * ## Timers * * Every timer is `unref`'d. A pending tip must never be the reason the process * is still alive — a one-shot `hoocode -p` run that happens to take 30 seconds * should exit the moment its work is done, not when a tip timer fires. */ import type { Tip } from "./tips.js"; import { TipRotation } from "./tips.js"; /** Keyboard-quiet time at the prompt before a tip is offered. */ export declare const DEFAULT_IDLE_DELAY_MS = 45000; /** * Turn duration before a tip is offered mid-stream. * * Shorter than the idle delay on purpose: a user watching a spinner is already * waiting, whereas an idle user may be mid-thought and is more easily * interrupted. */ export declare const DEFAULT_STREAMING_DELAY_MS = 20000; /** Minimum gap between two tips, whatever moment they come from. */ export declare const DEFAULT_COOLDOWN_MS = 180000; /** Quiet period after startup, while the banner and changelog are still being read. */ export declare const DEFAULT_GRACE_MS = 60000; export interface TipsControllerOptions { /** Read fresh every time, so turning tips off in `/settings` takes effect at once. */ isEnabled: () => boolean; /** True when the notification band has nothing on it and nothing queued. */ bandIsFree: () => boolean; /** Put the tip on the band. */ show: (tip: Tip) => void; rotation: TipRotation; idleDelayMs?: number; streamingDelayMs?: number; cooldownMs?: number; graceMs?: number; /** Clock, injectable for tests. */ now?: () => number; /** Timer factory, injectable for tests. */ setTimer?: (fn: () => void, ms: number) => unknown; clearTimer?: (handle: unknown) => void; } export declare class TipsController { private readonly opts; private idleTimer; private streamTimer; private lastTipAt; private readonly startedAt; private stopped; constructor(options: TipsControllerOptions); /** * The user did something — a key, a submit, a command. * * Restarts the idle clock. This is deliberately cheap and called from the * input path, so it does nothing but reset a timer. */ onActivity(): void; /** A turn started: the idle moment is over and the streaming one begins. */ onTurnStart(): void; /** A turn ended: back to waiting for the user to go quiet. */ onTurnEnd(): void; /** Teardown. Safe to call more than once. */ stop(): void; private armIdle; private armStreaming; private clearIdle; private clearStreaming; /** * Show a tip, if every reason not to has been ruled out. * * A refused offer is not rescheduled here: the moment that produced it is * over, and the next one will arm its own timer. Retrying would turn "the * band is busy" into a tip that pounces the instant the user's own * notification fades, which is precisely the interruption this avoids. */ private offer; } export { TipRotation }; //# sourceMappingURL=tips-controller.d.ts.map