Skip to content

Commit c839a0e

Browse files
committed
refactor: extract TUI components, constants, and utilities into separate modules
1 parent 9141ac1 commit c839a0e

15 files changed

Lines changed: 4823 additions & 4313 deletions

apps/cli/src/tui-components.tsx

Lines changed: 1027 additions & 0 deletions
Large diffs are not rendered by default.

apps/cli/src/tui-constants.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import type { CommandDefinition } from "./tui-types";
2+
3+
export const INPUT_MIN_LINES = 1;
4+
export const INPUT_MAX_LINES = 6;
5+
export const INPUT_PROMPT_PREFIX = "› ";
6+
export const FRAME_RESERVED_COLUMNS = 4;
7+
export const INPUT_BOX_HORIZONTAL_CHROME = 4; // left/right border plus padding
8+
export const INK_RENDER_SAFETY_ROWS = 1;
9+
// The header is rendered as the top of the scrollable transcript region so it
10+
// scrolls away as content grows. It occupies a fixed block: two content rows
11+
// (badges + meta), a top and bottom border, and one bottom margin row.
12+
export const HEADER_ROWS = 5;
13+
export const OVERLAY_SUGGESTION_MIN_ROWS = 4;
14+
export const OVERLAY_SUGGESTION_MAX_ROWS = 12;
15+
export const PET_PANEL_MIN_WIDTH = 28;
16+
export const PET_PANEL_MAX_WIDTH = 42;
17+
export const RUN_SPINNER_FRAMES = [
18+
"⠋",
19+
"⠙",
20+
"⠹",
21+
"⠸",
22+
"⠼",
23+
"⠴",
24+
"⠦",
25+
"⠧",
26+
"⠇",
27+
"⠏",
28+
] as const;
29+
export const COLLAPSED_TEXT_LINE_LIMIT = 10;
30+
export const COLLAPSIBLE_TEXT_LINE_THRESHOLD = 18;
31+
export const COLLAPSIBLE_TEXT_CHAR_THRESHOLD = 2400;
32+
export const RESTORED_TEXT_CHUNK_LINE_LIMIT = 12;
33+
export const RESTORED_TEXT_CHUNK_CHAR_LIMIT = 1800;
34+
export const TOOL_DETAIL_CHAR_LIMIT = 320;
35+
export const TRANSCRIPT_MOUSE_WHEEL_ROWS = 4;
36+
export const IMAGE_PREVIEW_MAX_COLS = 72;
37+
export const IMAGE_PREVIEW_MAX_ROWS = 12;
38+
export const IMAGE_PREVIEW_MIN_COLS = 12;
39+
export const IMAGE_PREVIEW_MIN_ROWS = 4;
40+
export const IMAGE_PREVIEW_VIEWPORT_CHROME_ROWS = 4;
41+
export const RUN_STATUS_ANIMATION_MS = 140;
42+
export const RUN_STATUS_HIGHLIGHT_COLOR = "#ffffff";
43+
export const DEFAULT_STREAM_FLUSH_MS = 1000;
44+
export const STREAM_FLUSH_MIN_CHARS = 600;
45+
export const STREAM_FLUSH_MAX_WAIT_MS = 2500;
46+
export const PET_SNAPSHOT_FLUSH_MS = 1000;
47+
export const TUI_ANIMATIONS_ENABLED =
48+
process.env.BRAINCODE_TUI_ANIMATIONS === "true";
49+
50+
export function isTruthyEnv(value: string | undefined): boolean {
51+
if (!value) return false;
52+
return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
53+
}
54+
55+
// Mouse-wheel scrolling is on by default. In the alternate screen there is no
56+
// native terminal scrollback, so the wheel drives the in-app transcript scroll.
57+
// Set BRAINCODE_TUI_MOUSE=false (or 0) to release the mouse if you prefer your
58+
// terminal's drag-to-select without holding Shift. Unset keeps it enabled.
59+
export const TUI_MOUSE_ENABLED = process.env.BRAINCODE_TUI_MOUSE
60+
? isTruthyEnv(process.env.BRAINCODE_TUI_MOUSE)
61+
: true;
62+
63+
export const BRAIN_LOGO: ReadonlyArray<string> = [
64+
" ██████╗ ██████╗ █████╗ ██╗███╗ ██╗",
65+
" ██╔══██╗██╔══██╗██╔══██╗██║████╗ ██║",
66+
" ██████╔╝██████╔╝███████║██║██╔██╗ ██║",
67+
" ██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║",
68+
" ██████╔╝██║ ██║██║ ██║██║██║ ╚████║",
69+
" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝",
70+
];
71+
72+
export const COMMANDS: CommandDefinition[] = [
73+
{ name: "help", label: "/help", hint: "List all slash commands" },
74+
{
75+
name: "plan",
76+
label: "/plan",
77+
hint: "Preview routeBrain planning; add --heuristic for diagnostics",
78+
insert: "/plan ",
79+
},
80+
{
81+
name: "intent",
82+
label: "/intent",
83+
hint: "Show the current task decomposition and dependency graph",
84+
},
85+
{ name: "mcp", label: "/mcp", hint: "Interactive MCP control panel" },
86+
{ name: "hooks", label: "/hooks", hint: "Interactive hooks control panel" },
87+
{ name: "sessions", label: "/sessions", hint: "Browse recent sessions" },
88+
{
89+
name: "resume",
90+
label: "/resume",
91+
hint: "Resume a session by id",
92+
insert: "/resume ",
93+
},
94+
{
95+
name: "new",
96+
label: "/new",
97+
hint: "Start a fresh session (clears transcript)",
98+
},
99+
{
100+
name: "handoff",
101+
label: "/handoff",
102+
hint: "Fork a new session (or pass <session-id> to summarize another)",
103+
insert: "/handoff ",
104+
},
105+
{
106+
name: "brain",
107+
label: "/brain",
108+
hint: "View Brain catalog and switch default brain",
109+
},
110+
{
111+
name: "mode",
112+
label: "/mode",
113+
hint: "View or switch execution mode: auto/radical",
114+
insert: "/mode ",
115+
},
116+
{ name: "theme", label: "/theme", hint: "Show the system-resolved theme" },
117+
{
118+
name: "image",
119+
label: "/image",
120+
hint: "Preview an image in the terminal (kitty/ghostty or text fallback)",
121+
insert: "/image ",
122+
},
123+
{ name: "auto", label: "/auto", hint: "Switch execution mode to auto" },
124+
{
125+
name: "radical",
126+
label: "/radical",
127+
hint: "Switch execution mode to radical",
128+
},
129+
{
130+
name: "team-test",
131+
label: "/team-test",
132+
hint: "Diagnostic: force every role to run the prompt in parallel",
133+
insert: "/team-test ",
134+
},
135+
{
136+
name: "skill",
137+
label: "/skill",
138+
hint: "List project skills (.agents/skills)",
139+
},
140+
{
141+
name: "agents",
142+
label: "/agents",
143+
hint: "Show AGENTS.md location and length",
144+
},
145+
{ name: "files", label: "/files", hint: "Refresh the @file index" },
146+
{ name: "clear", label: "/clear", hint: "Clear transcript" },
147+
{ name: "exit", label: "/exit", hint: "Quit the TUI" },
148+
];
149+
150+
export const ANSI_RESET = "\x1b[39m";
151+
export const ANSI_INVERSE = "\x1b[7m";
152+
export const ANSI_INVERSE_OFF = "\x1b[27m";
153+
154+
export const INTENT_BIT_UP = 1;
155+
export const INTENT_BIT_RIGHT = 2;
156+
export const INTENT_BIT_DOWN = 4;
157+
export const INTENT_BIT_LEFT = 8;
158+
159+
export const INTENT_BIT_CHARS: Record<number, string> = {
160+
0: " ",
161+
1: "│",
162+
2: "─",
163+
3: "└",
164+
4: "│",
165+
5: "│",
166+
6: "┌",
167+
7: "├",
168+
8: "─",
169+
9: "┘",
170+
10: "─",
171+
11: "┴",
172+
12: "┐",
173+
13: "┤",
174+
14: "┬",
175+
15: "┼",
176+
};

0 commit comments

Comments
 (0)