1import type { OrchestrationV2CommandOutputFrame } from "@t3tools/contracts";
2import {
3 appendTerminalOutput,
4 EMPTY_TERMINAL_OUTPUT,
5 type TerminalOutputState,
6} from "@t3tools/shared/terminalOutput";
7
8/** One command row's output as a client holds it. */
9export interface CommandOutputView {
10 readonly output: TerminalOutputState;
11 /** False once the command settled and `output` is final. */
12 readonly running: boolean;
13}
14
15export const EMPTY_COMMAND_OUTPUT: CommandOutputView = {
16 output: EMPTY_TERMINAL_OUTPUT,
17 running: true,
18};
19
20/**
21 * Applies one server frame. A `replace` carries the whole bounded tail (the
22 * first frame, a viewer that fell behind, or the final output); an `append`
23 * is raw terminal text normalized with the same rules the server uses.
24 */
25export function applyCommandOutputFrame(
26 view: CommandOutputView,
27 frame: OrchestrationV2CommandOutputFrame,
28): CommandOutputView {
29 const base = frame.kind === "replace" ? EMPTY_TERMINAL_OUTPUT : view.output;
30 return {
31 output: appendTerminalOutput(
32 { ...base, truncated: base.truncated || frame.truncated },
33 frame.text,
34 ),
35 running: frame.running,
36 };
37}