@chantier/tui 0.3.0 → 0.5.0

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.
Files changed (3) hide show
  1. package/dist/index.d.mts +488 -22
  2. package/dist/index.mjs +1291 -125
  3. package/package.json +3 -3
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from "react";
1
2
  import { ApprovalDecision, ApprovalRequest } from "@chantier/permissions";
2
3
  //#region ../../node_modules/cli-boxes/index.d.ts
3
4
  /**
@@ -176,6 +177,66 @@ const petWithAutoComplete: Pet2 = '';
176
177
  */
177
178
  type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>);
178
179
  //#endregion
180
+ //#region src/items.d.ts
181
+ /**
182
+ * Frozen v0.5 contract shared by both TUI build workers (spec
183
+ * local://chantier-tui-v05-spec.md §1). Types only — Worker A implements the
184
+ * runtime store against these; Worker B consumes them from widgets/input code.
185
+ */
186
+ /** Finalized transcript item; rendered once via ink Static, never re-rendered. */
187
+ type TuiItem = {
188
+ kind: "markdown";
189
+ text: string;
190
+ } | {
191
+ kind: "tool";
192
+ toolName: string;
193
+ argsSummary: string;
194
+ outcome: "done" | "error";
195
+ durationMs?: number;
196
+ /** 1–2 line output preview (first non-empty line of the result content). */
197
+ detail?: string;
198
+ subagent?: {
199
+ sessionId: string;
200
+ summary: string;
201
+ };
202
+ } | {
203
+ kind: "divider";
204
+ text: string;
205
+ } | {
206
+ kind: "info";
207
+ text: string;
208
+ } | {
209
+ kind: "error";
210
+ text: string;
211
+ };
212
+ /** Status-widget state; null hides the widget. */
213
+ interface RunningState {
214
+ /** Date.now() when the run started (elapsed clock source). */
215
+ sinceMs: number;
216
+ detail?: string;
217
+ }
218
+ interface UsageTotals {
219
+ inputTokens: number;
220
+ outputTokens: number;
221
+ }
222
+ /** Additions to TuiStore for v0.5 (Worker A implements; TuiState gains the fields). */
223
+ interface TuiStoreV5 {
224
+ readonly items: readonly TuiItem[];
225
+ pushItem(item: TuiItem): void;
226
+ setRunning(running: RunningState | null): void;
227
+ readonly running: RunningState | null;
228
+ readonly queued: readonly string[];
229
+ pushQueued(text: string): void;
230
+ /** Replaces the LAST queued text (the ↑-edit path). */
231
+ editQueued(text: string): void;
232
+ dropQueued(): void;
233
+ readonly usage: UsageTotals | undefined;
234
+ setUsage(usage: UsageTotals | undefined): void;
235
+ readonly statusFlash: string;
236
+ /** 4–6s transient status flash that falls back to the persistent status (Hermes restoreStatusAfter). */
237
+ flashStatus(text: string): void;
238
+ }
239
+ //#endregion
179
240
  //#region src/store.d.ts
180
241
  type TuiMode = "input" | "running";
181
242
  /** Optional attachment for an approval ask; pre-wired for diff previews. */
@@ -184,12 +245,20 @@ interface TuiPromptDetail {
184
245
  }
185
246
  interface TuiState {
186
247
  readonly mode: TuiMode;
187
- /** Finalized transcript lines (rendered once, never re-rendered). */
188
- readonly lines: readonly string[];
189
- /** In-progress model text, replaced by lines once finalized. */
248
+ /** Finalized transcript items (rendered once via Static, never re-rendered). */
249
+ readonly items: readonly TuiItem[];
250
+ /** In-progress model text (the live region), replaced by items once finalized. */
190
251
  readonly streamText: string;
191
252
  /** Transient status line (e.g. "thinking…"); "" hides it. */
192
253
  readonly status: string;
254
+ /** Status-widget state (spinner + elapsed); null hides the widget. */
255
+ readonly running: RunningState | null;
256
+ /** Tasks queued while a run is in flight; drained when the run settles. */
257
+ readonly queued: readonly string[];
258
+ /** Cumulative token usage for the footer; undefined until the first result. */
259
+ readonly usage: UsageTotals | undefined;
260
+ /** Transient flash message that falls back to the persistent status after a few seconds. */
261
+ readonly statusFlash: string;
193
262
  /** Pending approval request; null while the model is streaming. */
194
263
  readonly prompt: ApprovalRequest | null;
195
264
  /** Optional diff attachment for the pending ask; null when absent. */
@@ -201,16 +270,41 @@ interface TuiState {
201
270
  }
202
271
  type TuiStore = {
203
272
  readonly state: TuiState;
273
+ get items(): readonly TuiItem[];
274
+ get running(): RunningState | null;
275
+ get queued(): readonly string[];
276
+ get usage(): UsageTotals | undefined;
277
+ get statusFlash(): string;
204
278
  /** React-side subscription; returns an unsubscribe function. */
205
279
  subscribe(listener: () => void): () => void;
206
- /** Appends one finalized transcript line. */
207
- pushLine(line: string): void;
280
+ /** Appends one finalized transcript item (rendered once, never re-rendered). */
281
+ pushItem(item: TuiItem): void;
208
282
  /** Appends in-flight model text; flushStream() finalizes it. */
209
283
  appendStream(text: string): void;
210
- /** Moves buffered stream text (if any) into finalized lines. */
211
- flushStream(): void;
284
+ /**
285
+ * Finalizes buffered stream text as a markdown item (spec §1). Without
286
+ * options, everything buffered becomes one item. With `{ safe: true }`, the
287
+ * buffer splits at its last safe paragraph boundary (takeSafeFlush): the
288
+ * flushed prefix becomes a markdown item and the remainder stays the live
289
+ * region — a half-open code fence is never finalized mid-stream.
290
+ */
291
+ flushStream(options?: {
292
+ safe?: boolean;
293
+ }): void;
212
294
  /** Shows a transient status (e.g. "thinking…"); "" hides it. */
213
295
  setStatus(status: string): void;
296
+ /** Shows/clears the status-widget running state (spinner + elapsed). */
297
+ setRunning(running: RunningState | null): void;
298
+ /** Queues a task typed while a run is in flight. */
299
+ pushQueued(text: string): void;
300
+ /** Replaces the LAST queued text (the ↑-edit path); no-op when empty. */
301
+ editQueued(text: string): void;
302
+ /** Removes the LAST queued text; no-op when empty. */
303
+ dropQueued(): void;
304
+ /** Replaces the footer usage totals; undefined clears them. */
305
+ setUsage(usage: UsageTotals | undefined): void;
306
+ /** Transient flash message; falls back to the persistent status after ~5s. */
307
+ flashStatus(text: string): void;
214
308
  /** Enters task-input mode; resolves the previous task signal if still open. */
215
309
  awaitTask(defaultText?: string): Promise<string | null>;
216
310
  /** Submits the typed task text; null = user quit. */
@@ -224,7 +318,7 @@ type TuiStore = {
224
318
  decide(decision: ApprovalDecision): void;
225
319
  /**
226
320
  * Esc aborts the current work (deny pending prompt + notify); Ctrl-C quits
227
- * the app with a nonzero exit signal.
321
+ * the app with a nonzero exit signal. The queue survives aborts.
228
322
  */
229
323
  abort(kind: AbortKind): void;
230
324
  /** Marks the store finished; App unmounts on the next render. */
@@ -235,9 +329,85 @@ export declare function createTuiStore(handlers: {
235
329
  onAbort: (kind: AbortKind) => void;
236
330
  }, isQuitCommand?: (text: string) => boolean): TuiStore;
237
331
  //#endregion
332
+ //#region src/keys.d.ts
333
+ /**
334
+ * Namespaced action-id key map (spec §6a): every chord maps to a namespaced
335
+ * action id and components ask `matches(event, "app.interrupt")` instead of
336
+ * comparing raw key fields. This kills the BUG-3 class — any Ctrl-modified
337
+ * key quit (app.ts:23-25 in v0.4) — because ctrl-c is the ONLY quit chord,
338
+ * and it keeps dispatch as pure data + pure functions that unit-test without
339
+ * mounting ink (the Hermes approvalAction pattern, matching the existing
340
+ * keypressToDecision).
341
+ */
342
+ /**
343
+ * Structural subset of ink's `useInput` Key that the matcher needs. Declared
344
+ * here (instead of importing ink) so keys.ts stays a pure, runtime-free
345
+ * module; ink's Key is structurally compatible by construction.
346
+ */
347
+ interface Keychord {
348
+ /** Raw chunk bytes; a PTY can bundle the key with its Enter ("y\r"). */
349
+ readonly input?: string;
350
+ readonly ctrl?: boolean;
351
+ readonly escape?: boolean;
352
+ readonly upArrow?: boolean;
353
+ readonly downArrow?: boolean;
354
+ readonly return?: boolean;
355
+ readonly backspace?: boolean;
356
+ readonly delete?: boolean;
357
+ }
358
+ type ActionId =
359
+ /** Esc while a run streams: interrupt, keep completed work. */
360
+ "app.interrupt" |
361
+ /** Ctrl-C: the ONLY quit chord; the ×2 window lives in the caller. */
362
+ "app.quit" |
363
+ /** Approval card: y / a / n / esc. */
364
+ "app.decide.allow" | "app.decide.always" | "app.decide.deny" | "app.decide.abort" |
365
+ /** Enter at the task prompt (submit or queue while running). */
366
+ "app.submit" |
367
+ /** ↑ / ↓ history recall at an empty-ish editor. */
368
+ "app.history.prev" | "app.history.next" |
369
+ /** ↑ while queued rows exist: pop the last queued row into the editor. */
370
+ "app.queue.edit" |
371
+ /** Emacs set (§6f). */
372
+ "app.editor.home" | "app.editor.end" | "app.editor.char.back" | "app.editor.char.forward" | "app.editor.kill.to-end" | "app.editor.kill.line" | "app.editor.kill.word" | "app.editor.backspace" |
373
+ /** Ctrl-L clears + redraws; ink repaints from state, so this is a no-op
374
+ * hook point today — but it must stay OUT of app.quit's chord set. */
375
+ "app.redraw";
376
+ /**
377
+ * The chord table. "app.queue.edit" and "app.history.prev" share the
378
+ * up-arrow chord on purpose: callers disambiguate by state (queued rows
379
+ * exist vs history recall), which is how OMP treats Alt+Up vs plain Up.
380
+ */
381
+ export declare const ACTION_CHORDS: Readonly<Record<ActionId, readonly Keychord[]>>;
382
+ /**
383
+ * Whether the keypress resolves to the action id. Input chunks are
384
+ * normalized by stripping CR/LF first: a PTY can deliver "y\r" as one chunk
385
+ * while ink sets key.return only for a lone CR.
386
+ */
387
+ export declare function matches(event: Keychord, id: ActionId): boolean;
388
+ /** Hint line for the approval card, verbatim spec §7 mockup. */
389
+ export declare const APPROVAL_HINT_PARTS: readonly ["y allow", "a always", "n deny", "esc abort"];
390
+ /**
391
+ * Maps a prompt keypress to a decision; null = key not handled by the
392
+ * prompt. Moved here unchanged from app.ts (spec §7): this stays the pure
393
+ * dispatch surface, unit-tested without mounting ink.
394
+ */
395
+ export declare function keypressToDecision(key: string): ApprovalDecision | null;
396
+ //#endregion
238
397
  //#region src/app.d.ts
239
- export declare function TuiApp({ store }: {
398
+ /** Footer data computed by the caller per render (context estimate + compact gate). */
399
+ interface FooterData {
400
+ readonly ctxFraction?: number;
401
+ readonly compactSoon?: boolean;
402
+ }
403
+ export declare function TuiApp({ store, footer }: {
240
404
  store: TuiStore;
405
+ footer?: {
406
+ readonly model: string;
407
+ readonly sessionId: string;
408
+ readonly contextWindow?: number;
409
+ readonly data?: () => FooterData;
410
+ };
241
411
  }): import("react").FunctionComponentElement<{
242
412
  readonly position?: "absolute" | "relative" | "static" | undefined;
243
413
  readonly top?: number | string | undefined;
@@ -322,16 +492,20 @@ export declare function TuiApp({ store }: {
322
492
  } & import("react").RefAttributes<import("ink").DOMElement>>;
323
493
  /** Screen-reader label for the approval card (ink serializes it as the button name). */
324
494
  export declare function approvalLabel(tool: string, input: unknown): string;
325
- /** Maps a prompt keypress to a decision; null = key not handled by the prompt. */
326
- export declare function keypressToDecision(key: string): ApprovalDecision | null;
327
495
  interface TuiOptions {
328
496
  /** Opt-in screen-reader rendering (also honored: CHANTIER_SCREEN_READER=1). */
329
497
  readonly screenReader?: boolean;
498
+ /** Footer segments; session id + model come from the CLI, contextWindow gates the ctx segment. */
499
+ readonly footer?: {
500
+ readonly model: string;
501
+ readonly sessionId: string;
502
+ readonly contextWindow?: number;
503
+ readonly data?: () => FooterData;
504
+ };
330
505
  }
331
506
  interface TuiInstance {
332
507
  waitUntilExit(): Promise<void>;
333
508
  }
334
- /** Mounts the TUI. The store drives everything; the caller drives the agent. */
335
509
  export declare function startTui(store: TuiStore, options?: TuiOptions): TuiInstance;
336
510
  //#endregion
337
511
  //#region src/diff.d.ts
@@ -355,15 +529,6 @@ interface DiffPreview {
355
529
  /** Counts add/del lines across the whole diff and caps the displayed lines. */
356
530
  export declare function summarizeUnifiedDiff(diff: string, maxLines?: number): DiffPreview;
357
531
  //#endregion
358
- //#region src/screen-reader.d.ts
359
- /**
360
- * Screen-reader mode: ink serializes the tree to plain linear text (no
361
- * borders, no colors), keeps `<Static>` append-only, and rewrites the dynamic
362
- * region only when it changes. Enabled by the `--screen-reader` flag or the
363
- * CHANTIER_SCREEN_READER=1 environment alias.
364
- */
365
- export declare function resolveScreenReader(flag: boolean | undefined, env: string | undefined): boolean;
366
- //#endregion
367
532
  //#region src/symbols.d.ts
368
533
  /**
369
534
  * Decorative characters and box borders used by the TUI. Screen readers skip
@@ -379,9 +544,310 @@ interface TuiSymbols {
379
544
  readonly hintSeparator: string;
380
545
  /** Truncation ellipsis: "…" normally, "..." in ASCII mode. */
381
546
  readonly ellipsis: string;
547
+ /**
548
+ * Spinner animation frames (v0.5 status widget, spec §4a): the 10-frame
549
+ * braille cycle normally, the 4-glyph `|/-\` cycle in ASCII mode.
550
+ * `spinnerFrame` wraps per set, so no timing code depends on the count.
551
+ */
552
+ readonly spinnerFrames: readonly string[];
553
+ /** Tool/task row bullet: "▸" normally, ">" in ASCII mode (§8 parity). */
554
+ readonly runGlyph: string;
555
+ /** Tree corner for detail and summary blocks: "└" normally, "+" in ASCII. */
556
+ readonly subGlyph: string;
557
+ /** Failed-tool glyph: "✗" normally, "x" in ASCII mode. */
558
+ readonly errorGlyph: string;
559
+ /** Divider rule cell: "─" normally, "-" in ASCII mode (§2d). */
560
+ readonly rule: string;
561
+ /** Context-bar cells: filled "▮" / empty "▯", ASCII "#" / "-" (§5). */
562
+ readonly barFilled: string;
563
+ readonly barEmpty: string;
564
+ /** Forward arrow: "→" normally, "->" in ASCII mode (divider text, §2d). */
565
+ readonly arrow: string;
566
+ /** Recall hint arrow: "↑" normally, "^" in ASCII mode (queue preview §6d). */
567
+ readonly arrowUp: string;
568
+ /** Minus sign in diff counts: "−" normally, "-" in ASCII mode (§7). */
569
+ readonly minus: string;
382
570
  }
383
571
  export declare function resolveSymbols(ascii: boolean): TuiSymbols;
384
572
  /** CHANTIER_ASCII=1 requests the ASCII-safe rendering. */
385
573
  export declare function isAsciiEnv(env: string | undefined): boolean;
386
574
  //#endregion
387
- export type { AbortKind, DiffLineKind, DiffPreview, TuiInstance, TuiOptions, TuiPromptDetail, TuiState, TuiStore, TuiSymbols };
575
+ //#region src/input.d.ts
576
+ /**
577
+ * v0.5 input surface (spec §6c-f): editor keys, paste, history. The emacs
578
+ * transforms and the paste thresholds are pure functions (testable without
579
+ * mounting ink); `TaskInput` is the props-driven ink component the app
580
+ * mounts at integration. Paste travels ink's separate usePaste channel —
581
+ * bracketed paste content never reaches useInput (verified in ink 7.1.1),
582
+ * which is exactly the fix for the v0.4 paste-destruction bug.
583
+ */
584
+ interface EditorState {
585
+ readonly text: string;
586
+ /** Insertion point, 0..text.length. */
587
+ readonly cursor: number;
588
+ }
589
+ export declare function emptyEditor(): EditorState;
590
+ export declare function editorInsert(state: EditorState, insert: string): EditorState;
591
+ export declare function editorBackspace(state: EditorState): EditorState;
592
+ type EditorAction = "home" | "end" | "char.back" | "char.forward" | "kill.to-end" | "kill.line" | "kill.word";
593
+ /**
594
+ * Emacs set (§6f) as pure (text, cursor) transforms: ctrl+a/e home/end,
595
+ * ctrl+b/f char moves, ctrl+k kill to end, ctrl+u clear, ctrl+w kill word
596
+ * back. Single-line editor only; multiline ctrl+j is v0.6.
597
+ */
598
+ export declare function applyEditorAction(state: EditorState, action: EditorAction): EditorState;
599
+ interface PasteResult {
600
+ /** Chip text when the paste exceeds the thresholds; null = insert verbatim. */
601
+ readonly chip: string | null;
602
+ readonly lines: number;
603
+ }
604
+ /**
605
+ * Chip thresholds (§6e): >3 lines or >800 chars chips; under 12 terminal
606
+ * rows the CC narrow rule applies (1 line / 200 chars).
607
+ */
608
+ export declare function pasteChip(text: string, rows?: number): PasteResult;
609
+ /** Restores full paste text from its chip markers before submit. */
610
+ export declare function expandPasteChips(text: string, chunks: ReadonlyMap<string, string>): string;
611
+ /** File-backed history; the path is injectable so tests never touch $HOME. */
612
+ export declare function historyPath(home?: string): string;
613
+ interface HistoryStore {
614
+ readonly entries: readonly string[];
615
+ /** Appends unless it duplicates the last entry; silent on file failure. */
616
+ record(text: string): Promise<void>;
617
+ /** ↑: empty editor → last → older. */
618
+ prev(): string | null;
619
+ /** ↓: forward; past the newest entry → null (empty editor). */
620
+ next(): string | null;
621
+ /** Typed input ends a browse sequence. */
622
+ reset(): void;
623
+ }
624
+ export declare function createHistoryStore(opts?: {
625
+ entries?: readonly string[];
626
+ file?: string;
627
+ }): HistoryStore;
628
+ /** Reads the JSONL history; silent failure yields an empty list. */
629
+ export declare function loadHistory(file: string): Promise<string[]>;
630
+ export declare const QUIT_HINT = "press ctrl-c again to quit";
631
+ interface TaskInputProps {
632
+ /** Controlled editor state; the host owns the single source of truth. */
633
+ readonly editor: EditorState;
634
+ readonly onEditorChange: (next: EditorState) => void;
635
+ /** Enter: the host routes to submitTask (idle) or pushQueued (running). */
636
+ readonly onSubmit: (text: string) => void;
637
+ /** True while a run streams: submit queues, ↑ edits the queue. */
638
+ readonly running: boolean;
639
+ readonly queuedCount: number;
640
+ /** Pops the last queued row back into the editor (§6d, OMP Alt+Up). */
641
+ readonly onQueueEdit: () => void;
642
+ /** Approval pending: every editor key is inert (input lock). */
643
+ readonly locked?: boolean;
644
+ /** Terminal rows for paste thresholds; undefined = standard thresholds. */
645
+ readonly rows?: number;
646
+ /** ↑/↓ recall + esc-saves-draft. */
647
+ readonly history?: HistoryStore;
648
+ /** Ctrl-C: host aborts the store (single-press idle, second stage running). */
649
+ readonly onQuit: () => void;
650
+ /** First ctrl-c while running: host flashes "press ctrl-c again to quit". */
651
+ readonly onQuitArm: () => void;
652
+ /** Two-stage window override for tests. */
653
+ readonly quitWindowMs?: number;
654
+ readonly symbols: TuiSymbols;
655
+ readonly screenReader?: boolean;
656
+ }
657
+ export declare function TaskInput({ editor, onEditorChange, onSubmit, running, queuedCount, onQueueEdit, locked, rows, history, onQuit, onQuitArm, quitWindowMs, symbols, screenReader }: TaskInputProps): ReactNode;
658
+ //#endregion
659
+ //#region src/markdown.d.ts
660
+ /**
661
+ * The v0.5 reading surface (spec local://chantier-tui-v05-spec.md §2): a
662
+ * dependency-free line-based markdown block parser plus the streaming flush
663
+ * splitter. Everything here is pure so the fence/flush rules can be tested
664
+ * without mounting ink.
665
+ */
666
+ /** Result of splitting a stream buffer at its last safe paragraph boundary. */
667
+ interface SafeFlush {
668
+ /** Text that is safe to finalize (balanced blocks only); "" when nothing is. */
669
+ readonly flushed: string;
670
+ /** Text that must stay in the live region (may end inside an open fence). */
671
+ readonly rest: string;
672
+ }
673
+ /**
674
+ * Splits a stream buffer at its last safe split point: the final `\n\n`
675
+ * outside an open fence. When the buffer ends inside an open fence and no
676
+ * outside boundary exists, it splits BEFORE the fence start instead, so a
677
+ * half-open code block is never emitted into the finalized transcript. With
678
+ * neither a boundary nor an open fence, nothing flushes.
679
+ */
680
+ export declare function takeSafeFlush(buffer: string): SafeFlush;
681
+ export declare function hasMarkdownSyntax(text: string): boolean;
682
+ /** A dim rule wrapped around `text`; screen readers get the plain text only. */
683
+ export declare function markdownDivider(text: string, symbols: TuiSymbols, screenReader: boolean): ReactNode;
684
+ /**
685
+ * Renders markdown text into ink elements (spec §2b): headings bold, lists
686
+ * indented, fences dim + 2-space indented, rules as divider rows, tables as
687
+ * space-aligned rows. Screen-reader mode renders the same rows unstyled, with
688
+ * decorative rows replaced by their label, and plain input takes the fast
689
+ * path and skips parsing entirely. Tolerant streaming: an unclosed fence
690
+ * renders as plain text until closed, so the strict styling only ever
691
+ * applies to balanced blocks.
692
+ */
693
+ export declare function markdownToElements(text: string, symbols: TuiSymbols, screenReader: boolean): ReactNode[];
694
+ //#endregion
695
+ //#region src/screen-reader.d.ts
696
+ /**
697
+ * Screen-reader mode: ink serializes the tree to plain linear text (no
698
+ * borders, no colors), keeps `<Static>` append-only, and rewrites the dynamic
699
+ * region only when it changes. Enabled by the `--screen-reader` flag or the
700
+ * CHANTIER_SCREEN_READER=1 environment alias.
701
+ */
702
+ export declare function resolveScreenReader(flag: boolean | undefined, env: string | undefined): boolean;
703
+ //#endregion
704
+ //#region src/widgets.d.ts
705
+ /**
706
+ * v0.5 activity + status widgets (spec §2c/§2d/§4/§5/§7). These are
707
+ * standalone, props-driven components: the parallel worker's app.ts mounts
708
+ * them at integration, and tests feed plain data — nothing here imports the
709
+ * store runtime. Every glyph routes through the provided `symbols`, and
710
+ * screen-reader parity renders finalized labeled lines only (§8 matrix).
711
+ */
712
+ interface RowSpec {
713
+ readonly text: string;
714
+ readonly color?: string;
715
+ readonly bold?: boolean;
716
+ readonly dim?: boolean;
717
+ }
718
+ /** First non-empty line of a tool result, capped for the row preview. */
719
+ export declare function previewLine(content: string, ellipsis: string, cap?: number): string;
720
+ export declare function formatDuration(ms: number | undefined): string;
721
+ /** "24k"-style token counts for the compaction divider (§2d mockup). */
722
+ export declare function formatTokens(tokens: number): string;
723
+ type ToolItem = Extract<TuiItem, {
724
+ kind: "tool";
725
+ }>;
726
+ interface ToolRowProps {
727
+ readonly item: ToolItem;
728
+ readonly symbols: TuiSymbols;
729
+ readonly screenReader?: boolean;
730
+ }
731
+ /**
732
+ * Collapse ladder: task/read/edit render two lines (args + detail preview);
733
+ * everything else renders one line. Errors turn the glyph red (§2c). The
734
+ * full output stays in the transcript log; expand/collapse is v0.6.
735
+ */
736
+ export declare function toolRowLines(item: ToolItem, symbols: TuiSymbols): Array<RowSpec>;
737
+ /** SR parity (§8): `tool: read(src/config.ts) done`. */
738
+ export declare function toolRowSrText(item: ToolItem): string;
739
+ export declare function ToolRow({ item, symbols, screenReader }: ToolRowProps): ReactNode;
740
+ interface SubagentCardProps {
741
+ readonly item: ToolItem & {
742
+ subagent: {
743
+ sessionId: string;
744
+ summary: string;
745
+ };
746
+ };
747
+ readonly symbols: TuiSymbols;
748
+ }
749
+ /** Summary block cap: 8 dim lines, then the tail pointer to the child session. */
750
+ export declare const SUBAGENT_SUMMARY_MAX_LINES = 8;
751
+ export declare function subagentLines(item: SubagentCardProps["item"], symbols: TuiSymbols): Array<RowSpec>;
752
+ export declare function SubagentCard({ item, symbols }: SubagentCardProps): ReactNode;
753
+ interface StatusWidgetProps {
754
+ readonly running: RunningState | null;
755
+ /** Persistent status text (e.g. "thinking…"); "" = none. */
756
+ readonly status: string;
757
+ readonly symbols: TuiSymbols;
758
+ /** Injectable clock source; defaults to Date.now (tests pass a stub). */
759
+ readonly now?: () => number;
760
+ readonly screenReader?: boolean;
761
+ }
762
+ export declare const STATUS_VERB_WIDTH: number;
763
+ /** Elapsed clock with a bounded tail: s → "1m 12s" → "1h 2m" → "99h+". */
764
+ export declare function formatElapsed(ms: number): string;
765
+ export declare function spinnerFrame(frame: number, symbols: TuiSymbols): string;
766
+ /**
767
+ * The one-line running status: spinner + anti-jitter-padded verb + elapsed +
768
+ * interrupt hint, with the detail line underneath. The verb padding is the
769
+ * Hermes trick: the tail never shifts while the spinner cycles because the
770
+ * verb column width is pinned to the widest verb the widget can show.
771
+ */
772
+ export declare function statusLines(frame: number, running: RunningState, status: string, symbols: TuiSymbols, elapsedMs: number): Array<RowSpec>;
773
+ export declare function StatusWidget({ running, status, symbols, now, screenReader }: StatusWidgetProps): ReactNode;
774
+ interface FooterBarProps {
775
+ readonly model: string;
776
+ /** 0..1 context estimate; undefined (no declared window) hides the segment. */
777
+ readonly ctxFraction?: number;
778
+ /** Auto-compact reserve would trigger for this task (§5 "compaction soon"). */
779
+ readonly compactSoon?: boolean;
780
+ readonly usage?: UsageTotals;
781
+ readonly sessionId: string;
782
+ readonly columns: number;
783
+ readonly symbols: TuiSymbols;
784
+ /** Hidden entirely while an approval prompt is pending (§5). */
785
+ readonly hidden?: boolean;
786
+ }
787
+ export declare function formatTokenCount(tokens: number): string;
788
+ /** 8-cell context bar; filled cells round to nearest, clamped to [0, 8]. */
789
+ export declare function ctxBar(fraction: number, symbols: TuiSymbols): string;
790
+ /**
791
+ * Segment list under the width breakpoints: <64 → model + ctx%; <80 → +
792
+ * session; ≥80 → all four. Whole segments only — never mid-truncate.
793
+ */
794
+ export declare function footerSegments(props: Omit<FooterBarProps, "hidden">): Array<RowSpec>;
795
+ export declare function FooterBar({ model, ctxFraction, compactSoon, usage, sessionId, columns, symbols, hidden }: FooterBarProps): ReactNode;
796
+ interface QueuePreviewProps {
797
+ readonly queued: readonly string[];
798
+ readonly symbols: TuiSymbols;
799
+ }
800
+ export declare const QUEUE_PREVIEW_MAX_ROWS = 2;
801
+ /** ≤2 dimmed rows + "+N more"; collapses runs of whitespace per row. */
802
+ export declare function queuePreviewLines(queued: readonly string[], symbols: TuiSymbols): Array<RowSpec>;
803
+ export declare function QueuePreview({ queued, symbols }: QueuePreviewProps): ReactNode;
804
+ interface ApprovalCardDetail {
805
+ readonly diff?: string;
806
+ }
807
+ interface ApprovalCardV2Props {
808
+ readonly request: ApprovalRequest;
809
+ readonly detail: ApprovalCardDetail | null;
810
+ readonly symbols: TuiSymbols;
811
+ /** cwd for the ~-shortened path display (CC label-shortening). */
812
+ readonly cwd?: string;
813
+ readonly screenReader?: boolean;
814
+ }
815
+ /**
816
+ * Humanized subject per tool (§7): edit/write → ~-shortened path + ±counts
817
+ * from the diff detail; bash → command (80 cap); read/grep → the pattern,
818
+ * glob → its pattern; task → first line of the prompt.
819
+ */
820
+ export declare function humanizeApproval(tool: string, input: unknown, detail: ApprovalCardDetail | null, symbols: TuiSymbols, cwd?: string): string;
821
+ /** SR parity (§8): `edit src/x.ts: +3 -1`. */
822
+ export declare function approvalSrLabel(tool: string, input: unknown, detail: ApprovalCardDetail | null, symbols: TuiSymbols, cwd?: string): string;
823
+ interface ApprovalCardV2Spec {
824
+ readonly title: string;
825
+ readonly subject: RowSpec | null;
826
+ readonly diffLines: Array<RowSpec>;
827
+ readonly hiddenLines: number;
828
+ readonly hints: string;
829
+ readonly srLabel: string;
830
+ }
831
+ /** Pure card spec so tests assert content without mounting ink. */
832
+ export declare function approvalCardSpec(props: {
833
+ request: ApprovalRequest;
834
+ detail: ApprovalCardDetail | null;
835
+ symbols: TuiSymbols;
836
+ cwd?: string;
837
+ }): ApprovalCardV2Spec;
838
+ export declare function ApprovalCardV2({ request, detail, symbols, cwd, screenReader }: ApprovalCardV2Props): ReactNode;
839
+ interface DividerProps {
840
+ readonly text: string;
841
+ readonly symbols: TuiSymbols;
842
+ readonly screenReader?: boolean;
843
+ }
844
+ /** `── {text} ──` with the ASCII `--` fallback. */
845
+ export declare function dividerLine(text: string, symbols: TuiSymbols): string;
846
+ export declare function Divider({ text, symbols, screenReader }: DividerProps): ReactNode;
847
+ /**
848
+ * Hermes mechanism: when no tool item survives a visibility filter, the last
849
+ * error item is forced back into view — quiet mode must never hide failures.
850
+ */
851
+ export declare function withErrorBackstop(items: readonly TuiItem[], isVisible: (item: TuiItem) => boolean): readonly TuiItem[];
852
+ //#endregion
853
+ export type { AbortKind, ActionId, ApprovalCardDetail, ApprovalCardV2Props, ApprovalCardV2Spec, DiffLineKind, DiffPreview, DividerProps, EditorAction, EditorState, FooterBarProps, HistoryStore, Keychord, PasteResult, QueuePreviewProps, RowSpec, RunningState, SafeFlush, StatusWidgetProps, SubagentCardProps, TaskInputProps, ToolItem, ToolRowProps, TuiInstance, TuiItem, TuiOptions, TuiPromptDetail, TuiState, TuiStore, TuiStoreV5, TuiSymbols, UsageTotals };