@multiclawcli/dashboard 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ import type { EventBus, MultiClawConfig } from "@multiclawcli/core";
2
+ export interface AppProps {
3
+ config: MultiClawConfig;
4
+ /**
5
+ * Called once per turn, after that turn's orchestrator exists. The CLI uses it
6
+ * to attach a file log writer without the TUI depending on the CLI package.
7
+ */
8
+ onTurnStart?: (eventBus: EventBus, logDir: string) => void;
9
+ }
10
+ export declare function App({ config, onTurnStart }: AppProps): import("react").JSX.Element;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The `/` command palette.
3
+ *
4
+ * Slash commands are how every agent CLI in this space exposes operations that
5
+ * are not worth a keybinding. They also solve the console's hardest input
6
+ * problem: a bare key cannot be both "type this" and "do that", but a *prefix*
7
+ * can, because the user has already committed to a mode by typing it.
8
+ */
9
+ /** When a command may be run. */
10
+ export type Availability = "always" | "running" | "idle";
11
+ export interface CommandSpec {
12
+ /** Without the leading slash. */
13
+ name: string;
14
+ /** Argument hint shown next to the name, e.g. `<agent>`. */
15
+ args?: string;
16
+ description: string;
17
+ availability: Availability;
18
+ /**
19
+ * What this command's first argument completes against, if anything.
20
+ *
21
+ * Declared as data rather than matched on the name in `App`, so "this command
22
+ * takes a completer" is one fact in one place: a second such command needs no
23
+ * edit to the console's key handling.
24
+ */
25
+ completes?: "agent";
26
+ }
27
+ export declare const COMMANDS: readonly CommandSpec[];
28
+ export interface ParsedCommand {
29
+ name: string;
30
+ args: string[];
31
+ }
32
+ /** Commands that make sense in the current phase. */
33
+ export declare function availableCommands(running: boolean): CommandSpec[];
34
+ /**
35
+ * Whether a command may run in the current phase.
36
+ *
37
+ * `availableCommands` decides what the palette *offers*; this is the same rule
38
+ * applied to *running*, because typing a command out bypasses the palette
39
+ * entirely. An unknown name is not "unavailable" — it is let through so the
40
+ * caller's `default:` branch can report it.
41
+ */
42
+ export declare function canRun(name: string, running: boolean): boolean;
43
+ /**
44
+ * Why a command may not run in the current phase, or `undefined` when it may.
45
+ *
46
+ * The mirror of {@link canRun}, and the message names the phase the command
47
+ * *wants* rather than just saying it is unavailable: the two directions mean
48
+ * different things to the user. A mid-run `/replay` has to read as "wait for the
49
+ * turn to finish", while an idle `/cancel` has to read as "this only applies to
50
+ * a running turn" — one string for both would leave the user without the fact
51
+ * they need to act on. An unknown name has no reason; it is let through so the
52
+ * caller's `default:` branch can report it.
53
+ */
54
+ export declare function unavailableReason(name: string, running: boolean): string | undefined;
55
+ /**
56
+ * Commands matching what has been typed after the slash.
57
+ *
58
+ * Name prefixes outrank description hits: someone typing `/ag` wants `/agents`,
59
+ * not whichever command happens to mention "agent" in its blurb.
60
+ */
61
+ export declare function matchCommands(query: string, running: boolean): CommandSpec[];
62
+ /** True once the line has committed to command mode. */
63
+ export declare function isCommandLine(text: string): boolean;
64
+ /**
65
+ * The partial name the palette should filter on, or `undefined` when the
66
+ * palette should stay closed.
67
+ *
68
+ * It closes as soon as a space is typed: at that point the user is writing
69
+ * arguments, and a list that keeps re-filtering under their cursor is noise.
70
+ */
71
+ export declare function menuQuery(text: string): string | undefined;
72
+ /** Split a submitted line into a command name and its arguments. */
73
+ export declare function parseCommand(text: string): ParsedCommand | undefined;
74
+ /**
75
+ * One row of the palette, whether it lists commands or their arguments.
76
+ *
77
+ * The label carries the leading slash (commands) or is a bare id (arguments),
78
+ * so the renderer draws one shape for both panels and never needs to know which
79
+ * it is showing.
80
+ */
81
+ export interface PaletteRow {
82
+ key: string;
83
+ label: string;
84
+ description: string;
85
+ }
86
+ /** The palette rows for a command list, in the order given. */
87
+ export declare function paletteRows(commands: readonly CommandSpec[]): PaletteRow[];
88
+ /**
89
+ * The command whose first argument is being typed, and what has been typed so
90
+ * far — or `undefined` when no argument completer applies.
91
+ *
92
+ * The mirror image of {@link menuQuery}: that one closes the moment a space is
93
+ * typed, this one opens there. They are mutually exclusive, so the two palettes
94
+ * can never be open at once.
95
+ *
96
+ * Only the first argument is recognised, which is all any current command needs
97
+ * — a command with two completable arguments would extend this, not the caller.
98
+ */
99
+ export declare function argQuery(text: string): {
100
+ command: CommandSpec;
101
+ partial: string;
102
+ } | undefined;
103
+ /**
104
+ * Agent ids matching a partial id, case-insensitively, capped at `limit`.
105
+ *
106
+ * An empty partial lists the whole roster (up to the cap), so `/focus ` alone is
107
+ * a way to see who is available rather than an empty panel.
108
+ */
109
+ export declare function completeAgents(partial: string, ids: readonly string[], limit?: number): string[];
110
+ /**
111
+ * Turn a typed agent id into the id it names, or the reason it does not.
112
+ *
113
+ * An exact id always wins; otherwise a unique prefix resolves, and an ambiguous
114
+ * prefix reports every candidate so the user can see what to disambiguate
115
+ * between. `/focus` and `/retry` share this so the two commands accept ids the
116
+ * same way.
117
+ */
118
+ export declare function resolveAgentId(input: string, ids: readonly string[]): {
119
+ id: string;
120
+ } | {
121
+ error: string;
122
+ };
@@ -0,0 +1,58 @@
1
+ import type { ReactNode } from "react";
2
+ import type { TurnView } from "../state";
3
+ /**
4
+ * Identity on the left, live context on the right — the same split htop uses.
5
+ *
6
+ * Chrome never shrinks. When the column overflows, Yoga squeezes whichever child
7
+ * it can, and a header that silently disappears is worse than a clipped log.
8
+ */
9
+ export declare function Header({ name, right }: {
10
+ name: string;
11
+ right: string;
12
+ }): import("react").JSX.Element;
13
+ /** A horizontal rule. Cheap separation without spending two rows on a box border. */
14
+ export declare function Rule({ width }: {
15
+ width: number;
16
+ }): import("react").JSX.Element;
17
+ /**
18
+ * The rendered width of each cell the rail draws, in rail order.
19
+ *
20
+ * The rail wraps, so its height is a function of its content — which means the
21
+ * row budget has to be able to see this before anything is rendered. Keeping the
22
+ * measurement next to the component is what stops the two drifting apart: if the
23
+ * rail changes shape, this is the one other place that needs the same change.
24
+ */
25
+ export declare function railCellWidths(turn: TurnView): number[];
26
+ /**
27
+ * The team's shape at a glance: who runs before the pipeline, then each stage in
28
+ * order.
29
+ *
30
+ * The `──▸` connector and the ①②③ markers are borrowed from build tools and CI
31
+ * UIs, where "this happens, then that" is already read as a left-to-right flow.
32
+ */
33
+ export declare function PipelineRail({ turn, spinner }: {
34
+ turn: TurnView;
35
+ spinner: string;
36
+ }): import("react").JSX.Element | null;
37
+ /**
38
+ * The footer.
39
+ *
40
+ * The right side lists only the keys that do something *right now*, which is how
41
+ * a console teaches its own keymap without printing a manual.
42
+ */
43
+ export declare function HintBar({ status, keys }: {
44
+ status: ReactNode;
45
+ keys: string;
46
+ }): import("react").JSX.Element;
47
+ /**
48
+ * Follow-ups typed while the team was still working.
49
+ *
50
+ * It sits directly above the prompt because that is where the text was typed, and
51
+ * the rows are in run order — the first one is what happens next. The overflow
52
+ * count is spelled out rather than implied, since the whole point of the strip is
53
+ * that nothing the user typed gets silently dropped.
54
+ */
55
+ export declare function QueueStrip({ items, max }: {
56
+ items: string[];
57
+ max?: number;
58
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,64 @@
1
+ import { type TurnView } from "../state";
2
+ interface HelpGroup {
3
+ title: string;
4
+ rows: Array<[keys: string, description: string]>;
5
+ }
6
+ /**
7
+ * Grouped the way the user meets them: what the prompt does, then the panes,
8
+ * then the command layer.
9
+ *
10
+ * The command layer lists only keys that are *not* slash commands — `/`, `?`,
11
+ * `ctrl+c` — plus a single derived line counting the commands. It deliberately
12
+ * does not enumerate them: the list would drift from `COMMANDS` the moment a
13
+ * command is added, and it would not fit (see `helpRows`). The `/` palette is
14
+ * built from `COMMANDS` itself, so it can never drift, and that is where the
15
+ * enumeration belongs.
16
+ */
17
+ export declare const HELP_GROUPS: readonly HelpGroup[];
18
+ /** How many turns fit in a body of `maxRows`. Never fewer than one. */
19
+ export declare function historyCapacity(maxRows: number): number;
20
+ /**
21
+ * The first item to draw, given the one the cursor is on.
22
+ *
23
+ * A generic list window — nothing here is specific to history — so the command
24
+ * palette uses it too. The list can be longer than the body, so without this the
25
+ * cursor walks off the bottom and `enter` opens a row the user cannot see —
26
+ * which is exactly when the list is worth opening.
27
+ */
28
+ export declare function listWindowStart(selected: number, total: number, capacity: number): number;
29
+ /** Rows the help screen occupies, with or without the gaps between groups. */
30
+ export declare function helpRows(compact?: boolean): number;
31
+ /**
32
+ * The `?` overlay. Replaces the body rather than floating, which keeps it legible.
33
+ *
34
+ * `flexShrink={0}` so a body smaller than the content clips it rather than
35
+ * folding the rows into each other — the body does the clipping, and overlap is
36
+ * strictly worse than a missing row.
37
+ *
38
+ * When even the compact form does not fit, what gets clipped is the bottom, so
39
+ * the spacing between groups is the first thing to go: on a 24-row terminal the
40
+ * difference is whether "esc or ? to close" survives.
41
+ */
42
+ export declare function HelpOverlay({ width, maxRows }: {
43
+ width: number;
44
+ maxRows: number;
45
+ }): import("react").JSX.Element;
46
+ /**
47
+ * The finished turns, newest first.
48
+ *
49
+ * Two rows per turn on purpose: the requirement is the thing a reader is
50
+ * scanning for, and folding it onto the same line as the outcome would truncate
51
+ * exactly the part that identifies the turn.
52
+ *
53
+ * Only turns a newer turn has superseded are listed — the one being run is
54
+ * already on screen, so listing it would offer to show the user what they are
55
+ * looking at.
56
+ */
57
+ export declare function HistoryOverlay({ turns, selected, width, maxRows, }: {
58
+ turns: TurnView[];
59
+ selected: number;
60
+ width: number;
61
+ /** Rows available in the body. The list is windowed against this. */
62
+ maxRows: number;
63
+ }): import("react").JSX.Element;
64
+ export {};
@@ -0,0 +1,72 @@
1
+ import type { LeaderMessage } from "@multiclawcli/core";
2
+ import { type TurnView } from "../state";
3
+ /** The roster: one row per agent, with the focused one called out by a caret. */
4
+ export declare function TeamPanel({ turn, width, now, spinner, }: {
5
+ turn: TurnView;
6
+ width: number;
7
+ now: number;
8
+ spinner: string;
9
+ }): import("react").JSX.Element;
10
+ /**
11
+ * A one-column vertical rule between the two panes.
12
+ *
13
+ * Drawn as a left border rather than a column of `│` characters: the border is
14
+ * laid out by Yoga and always matches the row's height, whereas a hand-counted
15
+ * column of glyphs overflows into the footer the moment the two disagree.
16
+ */
17
+ export declare function Divider(): import("react").JSX.Element;
18
+ /**
19
+ * The focused agent's output.
20
+ *
21
+ * The header carries the three things a reader needs before the text: whose
22
+ * output this is, what it is running on, and whether the pane is still tailing
23
+ * or parked. Output itself is rendered at normal brightness — the chrome is what
24
+ * should recede, not the agent's work.
25
+ */
26
+ export declare function StreamPanel({ turn, height, width, now, spinner, }: {
27
+ turn: TurnView;
28
+ height: number;
29
+ width: number;
30
+ now: number;
31
+ spinner: string;
32
+ }): import("react").JSX.Element;
33
+ /**
34
+ * A turn the leader answered instead of planning.
35
+ *
36
+ * It takes the body for the same reason the welcome screen does: there is no
37
+ * pipeline to watch, and the words are the entire result. Splitting the space
38
+ * with two panes that have nothing to say would only shrink the one thing worth
39
+ * reading. The rail above still names the leader, so the source is not lost.
40
+ */
41
+ export declare function ReplyPanel({ reply, width }: {
42
+ reply: LeaderMessage;
43
+ width: number;
44
+ }): import("react").JSX.Element;
45
+ /**
46
+ * A turn that stopped with an error, shown in full.
47
+ *
48
+ * The status line has one row and truncates, which is the wrong shape for the
49
+ * errors that matter here: a jiti load failure or an ACP protocol error is
50
+ * several lines, and squeezing it to one leaves the user nothing to act on. So
51
+ * the error takes the body, the same way a reply does, and lets Ink wrap it.
52
+ */
53
+ export declare function ErrorPanel({ message, width }: {
54
+ message: string;
55
+ width: number;
56
+ }): import("react").JSX.Element;
57
+ /**
58
+ * The first screen.
59
+ *
60
+ * A console that opens to a blank pane wastes the one moment where the user has
61
+ * nothing else to read. The roster is already in the config — including the
62
+ * `description` of what each agent is for — so the welcome screen can say who is
63
+ * on the team and what each of them does.
64
+ *
65
+ * It marks the focused agent with the same `›` caret the team panel uses. Without
66
+ * it, `tab` looked broken here: the hint bar advertised `tab switch` while the
67
+ * one roster on screen ignored the focus entirely.
68
+ */
69
+ export declare function WelcomePanel({ turn, width }: {
70
+ turn: TurnView;
71
+ width: number;
72
+ }): import("react").JSX.Element;
@@ -0,0 +1,36 @@
1
+ import type { PaletteRow } from "../commands";
2
+ /**
3
+ * The prompt line.
4
+ *
5
+ * The terminal's own cursor is hidden while the console owns the screen, so the
6
+ * caret has to be drawn. It sits *between* the text before and after it rather
7
+ * than always at the end — that is what makes arrow-key editing legible.
8
+ *
9
+ * The caret cell is always exactly one column wide, in both blink phases. That
10
+ * matters on an empty line: the ghost text starts where the caret is, so a
11
+ * caret that *added* a cell while visible would shove the placeholder one
12
+ * column sideways twice a second. Instead the caret is painted *onto* the ghost
13
+ * text's first character, which is what a real block cursor does.
14
+ */
15
+ export declare function Prompt({ value, cursor, caretVisible, placeholder, }: {
16
+ value: string;
17
+ cursor: number;
18
+ caretVisible: boolean;
19
+ placeholder: string;
20
+ }): import("react").JSX.Element;
21
+ /**
22
+ * The palette, rendered directly above the prompt.
23
+ *
24
+ * Keeping it attached to the line being typed — rather than in a floating box —
25
+ * means the filtered list and the query are always read together, which is how
26
+ * fzf-style pickers work.
27
+ *
28
+ * It takes rows rather than commands so the command list and an argument
29
+ * completer (see `argQuery`) draw through one path: both reduce to a key, a
30
+ * label and a description, and the label already carries whatever prefix it
31
+ * needs.
32
+ */
33
+ export declare function CommandMenu({ rows, selected }: {
34
+ rows: PaletteRow[];
35
+ selected: number;
36
+ }): import("react").JSX.Element;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Truncate to `max` columns, ending in an ellipsis when it does not fit.
3
+ *
4
+ * Counts code units rather than grapheme clusters, which is what Ink's own
5
+ * `wrap="truncate-end"` does; matching it keeps hand-truncated and
6
+ * Ink-truncated strings the same width.
7
+ */
8
+ export declare function truncate(text: string, max: number): string;
9
+ /**
10
+ * How many terminal columns a string occupies.
11
+ *
12
+ * Deliberately not `text.length`: a CJK character is one code unit and *two*
13
+ * columns, so counting code units would size a Chinese line at half the pane
14
+ * and hand it to Ink to cut — the same truncation bug in a new place. Emoji and
15
+ * combining marks are wrong for the same reason.
16
+ */
17
+ export declare function displayWidth(text: string): number;
18
+ /**
19
+ * Break one line of text into the rows a terminal will actually spend on it.
20
+ *
21
+ * Word-wrapped, because the stream pane carries an agent's prose as often as it
22
+ * carries log lines, and breaking mid-word would make the leader's reasoning
23
+ * harder to read than the truncation this replaced. A word with no space inside
24
+ * it — a path, a hash, a minified JSON blob — has nowhere to break, so it is
25
+ * split by width instead.
26
+ *
27
+ * Scripts that use wide characters get the rule they actually have: they put no
28
+ * spaces between words, so a whole Chinese sentence arrives as one "word" and
29
+ * would be pushed onto the next row whole, leaving the row before it mostly
30
+ * empty. They may be broken between any two characters instead, so they fill
31
+ * what is left. Narrow runs — English words, identifiers, numbers — are still
32
+ * atomic, which is what keeps this a no-op for output that has no CJK in it.
33
+ *
34
+ * Every returned row is at most `width` columns, which is what lets the caller
35
+ * count rows and trust the count.
36
+ */
37
+ export declare function wrapLine(line: string, width: number): string[];
38
+ /**
39
+ * Human duration, used for every time column in the console.
40
+ *
41
+ * One formatter everywhere is what makes the columns comparable — the eye can
42
+ * scan a stack of durations only if they share a shape.
43
+ */
44
+ export declare function formatDuration(ms: number): string;
45
+ /** Stage markers read as an ordered sequence at a glance; past twenty, fall back. */
46
+ export declare function circled(n: number): string;
47
+ /** Pad to a fixed column so sibling rows line up. */
48
+ export declare function padRight(text: string, width: number): string;
49
+ /** Elapsed time of a run that is still in flight, or its total once it has ended. */
50
+ export declare function elapsedSince(startedAt: number | undefined, now: number): number | undefined;
51
+ /**
52
+ * A compact token count, for a column that must stay narrow.
53
+ *
54
+ * `45200 → "45.2k"`, `950 → "950"`, `1_200_000 → "1.2M"`. Below a thousand the
55
+ * exact number is short enough to show; above it the decimal is what the eye
56
+ * reads, so the digits past it are dropped.
57
+ */
58
+ export declare function formatTokens(n: number): string;
59
+ /**
60
+ * A USD cost, at the precision a session total is read at.
61
+ *
62
+ * `0.42 → "$0.42"`, `0 → "$0.00"`, and anything under a cent as `"<$0.01"` rather
63
+ * than rounding it away to "$0.00" — a real but tiny cost must not read as free.
64
+ */
65
+ export declare function formatCost(usd: number): string;
@@ -0,0 +1,34 @@
1
+ import { type TurnView } from "./state";
2
+ /** Directory, under a workspace's `.multiclaw/`, that holds console state. */
3
+ export declare const CONSOLE_DIR = "console";
4
+ /** The finished-turn record the console keeps across sessions. */
5
+ export declare const HISTORY_FILE = "history.json";
6
+ /** Where exported turn markdown files are written by default. */
7
+ export declare const EXPORT_DIR = "exports";
8
+ /** Where the console keeps its turn history for a workspace. */
9
+ export declare function historyPath(workDir: string): string;
10
+ /**
11
+ * Read the workspace's turn history.
12
+ *
13
+ * Synchronous on purpose: it runs once, inside the reducer's lazy initialiser,
14
+ * before the first frame. An async load would race the user's first keystroke
15
+ * and force a "history is still arriving" state the console does not need.
16
+ *
17
+ * Never throws. A missing directory, malformed JSON, or a value of the wrong
18
+ * shape all mean "no history": the console can always start from empty, and
19
+ * crashing on startup because a side file got corrupted would be far worse.
20
+ */
21
+ export declare function loadHistory(workDir: string): TurnView[];
22
+ /**
23
+ * Write the workspace's turn history.
24
+ *
25
+ * Written to a temp file and renamed into place: `rename` is atomic within a
26
+ * filesystem, so a crash mid-write leaves the *previous* file intact. Writing
27
+ * `history.json` directly would instead leave half a JSON document, and since
28
+ * `loadHistory` can only drop a file it cannot parse, that would cost the user
29
+ * *all* of their history rather than the last write.
30
+ */
31
+ export declare function saveHistory(workDir: string, turns: readonly TurnView[]): Promise<void>;
32
+ export declare function exportPath(workDir: string, turn: number): string;
33
+ export declare function exportTurn(turn: TurnView, filePath: string): Promise<void>;
34
+ export declare function formatTurnMarkdown(turn: TurnView): string;
@@ -0,0 +1,17 @@
1
+ import type { EventBus, MultiClawConfig } from "@multiclawcli/core";
2
+ export type { AppProps } from "./App";
3
+ export * from "./layout";
4
+ export * from "./state";
5
+ export interface TuiOptions {
6
+ config: MultiClawConfig;
7
+ /** Attach per-turn reporters (e.g. a file log writer) here. */
8
+ onTurnStart?: (eventBus: EventBus, logDir: string) => void;
9
+ }
10
+ /**
11
+ * Render the interactive team console.
12
+ *
13
+ * Runs on the alternate screen buffer so the app owns the viewport and the
14
+ * user's scrollback survives. The buffer is always restored — including when
15
+ * rendering throws — otherwise the terminal is left unusable.
16
+ */
17
+ export declare function startTui(options: TuiOptions): Promise<void>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Terminal geometry.
3
+ *
4
+ * Kept pure and separate from the components because responsive rules are the
5
+ * part of a TUI that is easiest to get subtly wrong and hardest to eyeball. Every
6
+ * row the console spends is budgeted here, so the arithmetic is testable rather
7
+ * than buried in JSX.
8
+ */
9
+ export declare const MIN_COLUMNS = 40;
10
+ export declare const MIN_ROWS = 10;
11
+ /** Below this the roster is dropped so the stream keeps a usable width. */
12
+ export declare const COMPACT_COLUMNS = 72;
13
+ /** The rail costs a row; it only earns one when there is vertical room. */
14
+ export declare const RAIL_MIN_ROWS = 16;
15
+ /**
16
+ * The most rows the pipeline rail is ever allowed to occupy.
17
+ *
18
+ * The rail wraps, so its height depends on how many agents the turn has and how
19
+ * wide the terminal is. Without a cap it could eat the whole screen; with one,
20
+ * the budget stays honest and anything past it is clipped rather than silently
21
+ * swallowed by the body.
22
+ */
23
+ export declare const MAX_RAIL_ROWS = 2;
24
+ /** Enough to see the palette is a list; more would push the stream off screen. */
25
+ export declare const MAX_MENU_ROWS = 7;
26
+ /** Queued follow-ups sit above the prompt; more than a few would crowd the stream. */
27
+ export declare const MAX_QUEUE_ROWS = 3;
28
+ export interface LayoutInput {
29
+ columns: number;
30
+ rows: number;
31
+ /** Zero means no turn has run yet, which swaps the body for the welcome screen. */
32
+ turn: number;
33
+ /** A modal owns the body — help or the history list. */
34
+ overlay: boolean;
35
+ /** Rows the command palette is asking for. */
36
+ menuRows: number;
37
+ /** Rows the queued follow-ups are asking for. */
38
+ queueRows?: number;
39
+ /** Whether a command result is waiting to be shown. */
40
+ notice: boolean;
41
+ /** A past turn is open for reading, which costs a banner row. */
42
+ viewing?: boolean;
43
+ /** Whether there is a leader or any stage to draw. */
44
+ hasRail: boolean;
45
+ /**
46
+ * Rendered widths of the rail's cells, widest first is not required — order
47
+ * is what the rail draws. Needed because the rail wraps: its height is a
48
+ * function of the content, so a flat "one row" charge under-budgets it.
49
+ */
50
+ railCells?: number[];
51
+ }
52
+ export interface Layout {
53
+ columns: number;
54
+ rows: number;
55
+ compact: boolean;
56
+ showTeam: boolean;
57
+ showRail: boolean;
58
+ /** How many rows the rail will actually occupy once it wraps. */
59
+ railRows: number;
60
+ welcome: boolean;
61
+ bodyHeight: number;
62
+ logViewport: number;
63
+ teamWidth: number;
64
+ /**
65
+ * Text columns available to the stream pane's *content* — the roster, the
66
+ * rule between the panes and the stream's own left padding already taken out.
67
+ *
68
+ * The stream pre-wraps its output, so it has to know this before Ink lays
69
+ * anything out; there is no way to measure it back out of Yoga. The arithmetic
70
+ * is therefore deliberate about erring small: a row wrapped a column early
71
+ * costs a few characters of width, while a row wrapped a column late would be
72
+ * cut at the pane edge — the bug this exists to prevent.
73
+ */
74
+ streamWidth: number;
75
+ menuRows: number;
76
+ queueRows: number;
77
+ }
78
+ /**
79
+ * How many rows the rail occupies given the widths of the cells it draws.
80
+ *
81
+ * Approximate on purpose: Yoga's own wrapping depends on margins and on how the
82
+ * terminal renders wide glyphs, and chasing exactness here is not worth it. The
83
+ * number is capped and the rail is clipped, so an under-count costs the last
84
+ * chip rather than collapsing the layout.
85
+ */
86
+ export declare function measureRailRows(cells: number[] | undefined, columns: number): number;
87
+ export declare function computeLayout(input: LayoutInput): Layout;