/** * The band that tells you what just changed, and then stops telling you. * * ## The problem it replaces * * Moving a dial used to write a line into the transcript: `Model: opus-5`, * `Chrome: compact`, `Tool output: peek`. Those lines are true for about a * second and then they are litter — a session where someone found their * thinking level by stepping through it carries five rows of dead settings * chatter forever, interleaved with the conversation that transcript is * supposed to be a record of. Warnings had the same shape from the other end: a * filled block, kept for the life of the session, for something ("No previous * directory to return to") that needs to be seen once and never read again. * * ## The shape * * One band, one notification at a time, directly above the prompt — which is * where the eye already is, and the one place in a bottom-anchored layout that * never moves. * * ## Why a glimpse replaces and a warning waits * * They are different kinds of message and the queue is where that shows. * * A glimpse reports *state*: what the model is now, where the thinking dial * landed. Only the newest one is true, so a glimpse standing in line replaces * itself — holding the dial key down flashes the value it ended on, not five * values in sequence three seconds apart, each of them already wrong. * * A glimpse that names its `topic` goes further and replaces the one *on * screen*, because the two are the same dial. Without that, the second press of * `alt+z` had nowhere to go but the back of a queue of one, and the band went on * showing the stop you had already left until its three seconds were up: a dial * that lags a press behind is a dial you cannot step twice. * * A warning reports an *event*: something happened that you did not ask about. * Every one of them is still true when the next arrives, so warnings queue and * are shown in turn. Collapsing them would mean the last of three startup * warnings silently erasing the two before it. * * ## What comes through here * * Every dial step, every warning, and everything a command has to say for * itself — `Mode set to "build"`, `Cloned to new session`, the plugin * catalogue, what `/learn` read. All of those used to write a dimmed row into * the conversation, and a session where someone ran five commands carried five * rows of dead receipts between the messages the transcript exists to keep. * * ## What does not * * Anything you might want to read later. A share URL, an export path, where * credentials were saved and every error still go to the transcript * (`InteractiveMode.showRecord`), which is the thing that scrolls back. The * rule is: if missing it costs you nothing, it belongs here; if missing it * costs you the information, it does not. */ import type { Component } from "@kolisachint/hoocode-tui"; /** * How long each kind stays up. * * A glimpse confirms something the user just did on purpose, so it only has to * outlast the glance. A warning is telling them something they did not ask * about, and is usually longer, so it gets the time to be read. */ export declare const NOTIFICATION_TTL_MS: { readonly info: 3000; readonly warning: 8000; }; export type NotificationKind = keyof typeof NOTIFICATION_TTL_MS; export interface Notification { kind: NotificationKind; title: string; body: string[]; /** Right-aligned afterword — the key that steps a dial back, usually. */ note?: string; /** Overrides the per-kind default. */ ttlMs?: number; /** * What this is a glimpse *of* — a dial's id, usually. * * Two notifications sharing a topic are two readings of one thing, so the * later one is simply the true one and takes the earlier one's place, on * screen or in the queue. Left unset, the queue rules below apply as they * always have. */ topic?: string; } export declare class NotificationPanel implements Component { private readonly requestRender; private readonly maxBodyRows; /** Shared across every empty frame: identity is all the render caches compare. */ private static readonly EMPTY; /** Head is on screen; the rest are waiting their turn. */ private queue; private timer; private cache?; /** * @param requestRender - the band appears and disappears on its own clock, * so it is the one piece of chrome that has to ask for frames rather than * being drawn into one somebody else asked for. * @param maxBodyRows - how many rows of body the screen can spare right now. * Asked per frame rather than held: the terminal is resized under a * notification as readily as under anything else. */ constructor(requestRender: () => void, maxBodyRows?: () => number); /** What is on the band right now, for tests and for the chrome checks. */ get showing(): Notification | undefined; /** What is waiting behind it. */ get pending(): readonly Notification[]; /** * Put a notification up: a glimpse replaces, a warning queues (see above). */ notify(kind: NotificationKind, title: string, body?: string[], note?: string, options?: { ttlMs?: number; topic?: string; }): void; /** Take the band down now, queue and all; safe when nothing is up. */ dismiss(): void; /** Drop the pending fade without repainting — for teardown. */ stop(): void; invalidate(): void; /** * The band, painted. * * It carries everything a command has to say now — a mode landing, a plugin * installed, a listing — so it has to read at a glance against a transcript * that is already full of text. A fill is what does that: the same block * fill a message sheet takes, so the band is recognisably the app's own * paper rather than one more line of output, and the eye finds its edges * without anything having to be drawn around it. * * Every painted row runs the full width — a fill that stops short of the * margin is a band with a bite out of it — and the one column of lead-in is * what keeps the glyph off the screen's edge. */ render(width: number): string[]; /** The body rows that fit, at whatever the screen can spare this frame. */ private bodyRows; /** Rows of body this frame can spare — never none, so a body always shows. */ private budget; /** * Title on the left, note flush right, the note dropped before the title is. * * The note is a hint about a key; the title is the thing that happened. On a * terminal too narrow for both, losing the hint costs nothing and losing the * headline costs the whole notification. */ private headline; /** * How long this one stays up: the headline's time plus the body's. * * An explicit `ttlMs` still wins — a caller that knows its message is read * at a glance, or not at all, is a better judge than this arithmetic. */ private ttl; private arm; private clearTimer; } //# sourceMappingURL=notification-panel.d.ts.map