/** * How much of the screen the chrome is allowed to have. * * ## The problem * * Nine things hang off the TUI root, and seven of them sit *below* the * transcript: queued messages, status rows, two widget containers, the task * ledger, the prompt, and the footer. On a thirty-row terminal that routinely * comes to eight or twelve rows — a third of the screen spent on furniture, * taken from the conversation. Each of those components also decided its own * visibility, in its own way, in its own file: the ledger self-hides when it has * no tasks, the footer always draws, the header is cleared by whoever remembers * to. There was no one place to ask "what is on screen right now", and so no * place to change it. * * ## The shape * * One dial and one table. `ChromeDensity` is the dial — an ordered set of stops * the way the other six are, so `alt+z` steps it and `shift+alt+z` steps back, * and the answer is always reachable in one more press. `resolveChrome` is the * table: a pure function from (dial, what is happening) to what each slot * shows. Adding a stop, or a new reason to hide something, is an edit to that * one function rather than a hunt through seven components. * * The prompt is deliberately not in the table. Everything else can go, but a * screen you can type into and not see is the worst possible failure here, and * a dial that can reach that state will eventually be left in it. * * ## Speed * * Two rules, both about not doing work: * * - A hidden slot returns one frozen array and never renders its child (see * `Slot`). Hiding the footer does not make it cheaper to draw; it makes it * free. * - `apply` compares the resolved layout against the last one and returns * whether anything moved. The inputs change on events that fire *constantly* — * an autocomplete opens and closes on keystrokes — so the callers push state * in on every one of those and this decides whether a frame is owed. Recomputing a layout is a few comparisons; re-rendering * because you did not check is a frame. */ import type { Slot } from "@kolisachint/hoocode-tui"; import { type ChromeDensity } from "../../core/chrome-density.js"; export { CHROME_DENSITIES, type ChromeDensity, isChromeDensity } from "../../core/chrome-density.js"; /** * Below this many rows the dial starts at `compact` rather than `full`. * * A starting point, never a correction: it is read once, when nothing is stored * yet, so a small terminal opens sensibly and the dial still does exactly what * it is told from then on. Re-deciding this on resize would move the layout * under someone dragging a pane divider, which is the kind of thing that makes * a UI feel like it is arguing with you. */ export declare const SMALL_TERMINAL_ROWS = 25; export interface ChromeInputs { density: ChromeDensity; /** The prompt's completion list is open and wants the room. */ autocompleteOpen: boolean; } export interface ChromeLayout { /** `full` is every row it has; `line` is the one-row vitals strip. */ footer: "full" | "line" | "hidden"; /** `summary` is the ledger's header strip alone — the counts, no rows. */ tasks: "full" | "summary" | "hidden"; } /** * The whole policy, in one place. * * Read it as: the dial says what you asked for, and the transient input says * what is happening. Where they disagree the transient one wins, because it is * the one that ends on its own — an autocomplete closes, and the dial's answer * comes back without anyone pressing anything. */ export declare function resolveChrome({ density, autocompleteOpen }: ChromeInputs): ChromeLayout; /** What the controller needs of the footer and the ledger, and nothing more. */ export interface ChromeSurfaces { footerSlot: Slot; tasksSlot: Slot; setFooterDensity(density: "full" | "line"): void; setTasksDensity(density: "full" | "summary"): void; } /** * Holds the dial, takes the transient inputs, and moves the slots. * * Every setter returns nothing and instead reports through `changed`, so a * caller that pushes state in on a hot path (a keystroke, a stream event) can * ask once whether a render is owed. */ export declare class ChromeLayoutController { private readonly surfaces; private inputs; private applied; constructor(surfaces: ChromeSurfaces, density: ChromeDensity); get density(): ChromeDensity; /** The layout currently on screen, for tests and for the hint strip. */ get layout(): ChromeLayout; setDensity(density: ChromeDensity): boolean; /** Step the dial, wrapping, the way every other dial in the app steps. */ cycleDensity(direction: "forward" | "backward"): ChromeDensity; setAutocompleteOpen(open: boolean): boolean; /** Push the current layout onto the slots; true when anything moved. */ apply(): boolean; } //# sourceMappingURL=chrome-layout.d.ts.map