/** * @license * Copyright 2026 Steven Roussey * SPDX-License-Identifier: Apache-2.0 */ import type { RunTaskCounts } from "./runCensus"; /** * How a run row is drawn, independent of what draws it. The terminal renders * these with Ink and the web console renders them with DOM nodes; both must * agree on the glyph, the order, and the one number a row reports, so neither * owns the rules. * * Nothing in this directory may import a renderer. `runRowModel.test.ts` * enforces that. */ /** The shape a row needs to sort — a full task line has more, and none of it matters here. */ export interface RowLike { readonly id: string; readonly status: string; } /** One iteration slot of a Map/Reduce task. */ export interface SlotLike { readonly status: "pending" | "running" | "completed"; } /** Single-character status column: done → active (a spinner replaces this) → waiting → error. */ export declare function cliTaskStatusGlyph(status: string): string; export declare function cliTaskStatusGlyphColor(status: string): string; /** * Sort key: completed (0), then processing-like (1), then pending (2), then * failures, etc. Secondary: graph order index. */ export declare function cliTaskStatusSortOrder(status: string): number; export declare function sortCliTaskLinesForDisplay(tasks: readonly T[], graphOrder: ReadonlyMap): T[]; /** When to draw a numeric progress bar (not just status text). */ export declare function cliTaskShowsProgressBar(status: string): boolean; /** * The one number at the end of a task row: how far along it is while it runs, * and how long it took once it settles. Two states of one column rather than * two columns, because only one of them is ever true. */ export declare function taskDetailText(progress: number | undefined, durationMs: number | undefined, running: boolean): string; /** * The progress a row draws, given what the task reported and what a poll of the * live instance found. * * `Task.progress` initialises to `0` and the runner re-stamps `0` the moment a * task starts. Neither is announced, and neither is a measurement: the graph * needs a number in the denominator of its average and zero is the honest one * to put there. On a row it is a different claim entirely. A determinate bar at * zero reads as "0% and stuck", which is what every task that reports no * progress of its own drew for the whole of its run — a flat empty bar above a * subtree visibly moving. * * So a polled zero is adopted only once the task has said something. Until * then the row draws no bar at all, and the spinner in the status column * carries "working, extent unknown" — which is the claim that is actually * true, and the one that column already exists to make. */ export declare function adoptPolledProgress(polled: number | undefined, reported: number | undefined): number | undefined; /** Settled either way — the work is behind it, whatever the outcome. */ export declare function cliTaskIsSettled(status: string): boolean; /** * The run's own bar. * * `graph_progress` averages the task progresses, and those start at an * unreported zero (see {@link adoptPolledProgress}) — so a run whose tasks * report nothing of their own averages to a flat determinate zero for its * entire life, and the bar heading the whole screen claims the run is stuck at * the gate while the work plainly moves beneath it. * * A zero is therefore only a measurement once something has been measured: a * task reporting a number, or a task landing. Before that the bar is * indeterminate, which is the one row on screen with no spinner of its own to * say so. */ export declare function runAggregateProgress(graphProgress: number | undefined, rows: readonly { readonly status: string; readonly progress?: number | undefined; }[]): number | undefined; /** How the run as a whole ended up, derived from the statuses of its tasks. */ export type RunState = "running" | "completed" | "failed" | "aborted" | ""; /** * A run's own outcome, which no single row carries: one failure decides the * run even when every other task completed, and a run is only `completed` when * nothing is left. Reported from the task statuses rather than tracked * separately so it cannot disagree with the rows above it. */ export declare function deriveRunState(statuses: readonly string[]): RunState; /** * The status a row reports once its own children contradict it. * * `context.own(new Workflow())` puts a wrapper task in the subgraph so the * workflow's tasks have somewhere to live, and the caller then runs the * workflow rather than the wrapper. The wrapper therefore never leaves * PENDING — it draws a `○` beside a subtree that has plainly finished, and * counts as one task that can never land. A parent whose children have started * is not waiting; it is the thing they are doing, and this says so. * * Only ever reads up from PENDING, so a task that genuinely reports its own * status keeps it. */ export declare function ownershipWrapperStatus(status: string, childStatuses: readonly string[]): string; export declare function runStateColor(state: RunState): string | undefined; /** * Wall-clock for a run in progress, as a clock rather than a duration. * * {@link formatCliDuration} answers "how long did that take", and switches * units as it goes — `847ms`, `12.4s`, `2m 15s` — which is right on a row that * settles once and is then read at leisure. A footer timer is read while it * moves, and a field that changes width every few seconds drags everything * beside it back and forth, so this one keeps `M:SS` (and `H:MM:SS` past the * hour) and stays put. */ export declare function formatRunClock(ms: number | undefined): string; /** Everything the run footer reports, in the order it is laid out. */ export interface RunStatusBarInput { /** Directional token counts and cost, already formatted. */ readonly usageLine: string; readonly counts: RunTaskCounts; readonly state: RunState; /** Wall-clock since the run started, or `undefined` before it has. */ readonly elapsedMs: number | undefined; /** Sibling rows the viewport is not drawing. */ readonly hiddenRows: number; } export interface RunStatusBarModel { /** Left-aligned fields, in order. */ readonly fields: readonly string[]; /** Right-aligned wall-clock; empty when the run has not started. */ readonly timer: string; readonly state: RunState; /** False when the bar would say nothing the rows above it do not already. */ readonly visible: boolean; } /** * The run's footer. * * Task counts come from the whole tree, not the top level: a task that owns a * workflow or maps over a worklist does its work in nodes the top level never * mentions, and `1 / 3 tasks` under three hundred running rows is a footer * reporting on the wrong run. * * A single-task run with nothing to spend and nowhere to go gets no bar at all * — a rule and the word "completed" under one row is ceremony, not information. */ export declare function runStatusBarModel(input: RunStatusBarInput): RunStatusBarModel; /** * What the capped iteration rows are not showing. The visible rows are the work * in flight; without this the rest of a large map is invisible. * * Reports only what the slots actually know: above the full-tracking cap the * caller retains running iterations only, so there is no honest done or queued * count to print and the line reduces to the extra running ones. */ export declare function iterationSummaryLine(slots: readonly SlotLike[] | undefined, visibleCount: number): string;