Skip to content

Commit 61865e3

Browse files
committed
refactor(ui): improve responsive layout and agent naming consistency
- Update agent names in config files for better clarity and consistency - Add responsive layout support for timeline and output components - Implement width-based truncation and conditional rendering in UI components
1 parent 1a2a3ad commit 61865e3

File tree

7 files changed

+76
-30
lines changed

7 files changed

+76
-30
lines changed

config/main.agents.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module.exports = [
1212
},
1313
{
1414
id: 'principal-analyst',
15-
name: 'Principal Analyst - Checkpoint',
15+
name: 'Analyst Checkpoint',
1616
description: 'Reviews project specifications and identifies critical ambiguities requiring clarification',
1717
promptPath: path.join(promptsDir, 'codemachine', 'main-agents', '01-principal-analyst.md'),
1818
},
@@ -48,13 +48,13 @@ module.exports = [
4848
},
4949
{
5050
id: 'task-sanity-check',
51-
name: 'Task Verification Agent',
51+
name: 'Task Reviewer',
5252
description: 'Verifies generated code against task requirements and acceptance criteria',
5353
promptPath: path.join(promptsDir, 'codemachine', 'main-agents', '07-task-validation-agent.md'),
5454
},
5555
{
5656
id: 'runtime-prep',
57-
name: 'Runtime Preparation Agent',
57+
name: 'Runtime Prep',
5858
description: 'Generates robust shell scripts for project automation (install, run, lint, test)',
5959
promptPath: path.join(promptsDir, 'codemachine', 'main-agents', '08-runtime-preparation-agent.md'),
6060
},

config/modules.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const promptsDir = path.join(__dirname, '..', 'prompts');
55
module.exports = [
66
{
77
id: 'check-task',
8-
name: 'Task Completion Checker',
8+
name: 'Task Loop Checker',
99
description: 'Validates that all tasks are completed and signals whether to repeat workflow steps.',
1010
promptPath: path.join(promptsDir, 'templates', 'codemachine', 'workflows', 'task-verification-workflow.md'),
1111
behavior: {

config/sub.agents.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@ module.exports = [
99
},
1010
{
1111
id: 'structural-data-architect',
12-
name: 'Structural & Data Architect',
12+
name: 'Structure Architect',
1313
description: 'Define the static structure of the system, components hierarchy, and data organization',
1414
mirrorPath: 'prompts/templates/codemachine/sub-agents/architecture/02-structural-data-architect.md',
1515
},
1616
{
1717
id: 'behavior-architect',
18-
name: 'Behavior & Communication Architect',
18+
name: 'Behavior Architect',
1919
description: 'Define dynamic interactions, data flows, and communication patterns between components',
2020
mirrorPath: 'prompts/templates/codemachine/sub-agents/architecture/03-behavior-architect.md',
2121
},
2222
{
2323
id: 'operational-architect',
24-
name: 'Operational & Documentation Architect',
24+
name: 'Ops Architect',
2525
description: 'Handle deployment, operations, security, and documentation architecture',
2626
mirrorPath: 'prompts/templates/codemachine/sub-agents/architecture/04-operational-architect.md',
2727
},
2828
{
2929
id: 'ui-ux-architect',
30-
name: 'UI/UX & Interface Architect',
30+
name: 'UI/UX Architect',
3131
description: 'Define user interface architecture, design systems, component hierarchies, and user experience patterns',
3232
mirrorPath: 'prompts/templates/codemachine/sub-agents/architecture/05-ui-ux-architect.md',
3333
},

src/cli/tui/routes/workflow/components/output/output-window.tsx

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Shows last N lines with auto-scroll for running agents using OpenTUI scrollbox
88
*/
99

10-
import { Show, For } from "solid-js"
10+
import { Show, For, createMemo } from "solid-js"
1111
import { useTheme } from "@tui/shared/context/theme"
1212
import { ShimmerText } from "./shimmer-text"
1313
import { TypingText } from "./typing-text"
@@ -40,22 +40,30 @@ export interface OutputWindowProps {
4040
onPromptSubmit?: (prompt: string) => void
4141
onSkip?: () => void
4242
onPromptBoxFocusExit?: () => void
43+
// Responsive layout
44+
availableWidth?: number
4345
}
4446

4547
/**
4648
* Output window showing current agent's output
4749
* Displays last N lines with syntax highlighting using scrollbox
4850
*/
49-
const OUTPUT_HEADER_HEIGHT = 4 // Side curve header (╭─ + info + thinking + ╰─)
51+
const OUTPUT_HEADER_HEIGHT_WIDE = 4 // Side curve header when wide (╭─ + info + thinking + ╰─)
52+
const OUTPUT_HEADER_HEIGHT_NARROW = 5 // Side curve header when narrow (extra line for status)
53+
const MIN_WIDTH_FOR_INLINE_STATUS = 75 // Minimum width to show status inline with name
5054

5155
export function OutputWindow(props: OutputWindowProps) {
5256
const themeCtx = useTheme()
5357

58+
// Check if we have enough width to show status inline (based on output section width)
59+
const isWideLayout = createMemo(() => (props.availableWidth ?? 80) >= MIN_WIDTH_FOR_INLINE_STATUS)
60+
5461
const effectiveMaxLines = () => props.maxLines ?? 20
5562

5663
// Scrollbox height accounts for the output header + prompt line (3 lines)
5764
const PROMPT_LINE_HEIGHT = 3
58-
const scrollboxHeight = () => Math.max(3, effectiveMaxLines() - OUTPUT_HEADER_HEIGHT - PROMPT_LINE_HEIGHT)
65+
const outputHeaderHeight = () => isWideLayout() ? OUTPUT_HEADER_HEIGHT_WIDE : OUTPUT_HEADER_HEIGHT_NARROW
66+
const scrollboxHeight = () => Math.max(3, effectiveMaxLines() - outputHeaderHeight() - PROMPT_LINE_HEIGHT)
5967

6068
// Check if agent is running
6169
const isRunning = () => props.currentAgent?.status === "running"
@@ -127,24 +135,47 @@ export function OutputWindow(props: OutputWindowProps) {
127135
</box>
128136
}
129137
>
130-
{/* Side Curve Header */}
131-
<box flexDirection="column" paddingLeft={1} height={4} flexShrink={0}>
138+
{/* Side Curve Header - Responsive layout */}
139+
<box flexDirection="column" paddingLeft={1} height={outputHeaderHeight()} flexShrink={0}>
132140
<text fg={themeCtx.theme.border}>╭─</text>
133-
<box flexDirection="row" justifyContent="space-between" paddingRight={2}>
141+
142+
{/* Wide layout: name and status on same line */}
143+
<Show when={isWideLayout()}>
144+
<box flexDirection="row" justifyContent="space-between" paddingRight={2}>
145+
<box flexDirection="row">
146+
<text fg={themeCtx.theme.border}></text>
147+
<text fg={themeCtx.theme.text}>{isRunning() && props.latestThinking ? "(╭ರ_•́)" : "(˶ᵔ ᵕ ᵔ˶)"}</text>
148+
<text> </text>
149+
<text fg={themeCtx.theme.text} attributes={1}>{props.currentAgent!.name}</text>
150+
</box>
151+
<box flexDirection="row" gap={1}>
152+
<text fg={themeCtx.theme.info}>{props.currentAgent!.engine}</text>
153+
<Show when={props.currentAgent!.model}>
154+
<text fg={themeCtx.theme.textMuted}>{props.currentAgent!.model}</text>
155+
</Show>
156+
<text fg={statusColor()}>{props.currentAgent!.status}</text>
157+
</box>
158+
</box>
159+
</Show>
160+
161+
{/* Narrow layout: name on one line, status on next line */}
162+
<Show when={!isWideLayout()}>
134163
<box flexDirection="row">
135164
<text fg={themeCtx.theme.border}></text>
136165
<text fg={themeCtx.theme.text}>{isRunning() && props.latestThinking ? "(╭ರ_•́)" : "(˶ᵔ ᵕ ᵔ˶)"}</text>
137166
<text> </text>
138167
<text fg={themeCtx.theme.text} attributes={1}>{props.currentAgent!.name}</text>
139168
</box>
140-
<box flexDirection="row" gap={1}>
169+
<box flexDirection="row">
170+
<text fg={themeCtx.theme.border}></text>
141171
<text fg={themeCtx.theme.info}>{props.currentAgent!.engine}</text>
142172
<Show when={props.currentAgent!.model}>
143-
<text fg={themeCtx.theme.textMuted}>{props.currentAgent!.model}</text>
173+
<text fg={themeCtx.theme.textMuted}> {props.currentAgent!.model}</text>
144174
</Show>
145-
<text fg={statusColor()}>{props.currentAgent!.status}</text>
175+
<text fg={statusColor()}> {props.currentAgent!.status}</text>
146176
</box>
147-
</box>
177+
</Show>
178+
148179
<box flexDirection="row">
149180
<text fg={themeCtx.theme.border}></text>
150181
<Show when={isRunning() && props.latestThinking} fallback={<text fg={themeCtx.theme.textMuted}>↳ Waiting...</text>}>

src/cli/tui/routes/workflow/components/timeline/agent-timeline.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface AgentTimelineProps {
2020
state: WorkflowState
2121
onToggleExpand: (agentId: string) => void
2222
availableHeight?: number
23+
availableWidth?: number
2324
isPaused?: boolean
2425
isPromptBoxFocused?: boolean
2526
}
@@ -89,7 +90,7 @@ export function AgentTimeline(props: AgentTimelineProps) {
8990

9091
// Main agent
9192
if (item.type === "main") {
92-
return <MainAgentNode agent={item.agent} isSelected={isMainSelected(item.id)} isPaused={props.isPaused} />
93+
return <MainAgentNode agent={item.agent} isSelected={isMainSelected(item.id)} isPaused={props.isPaused} availableWidth={props.availableWidth} />
9394
}
9495

9596
// Sub-agent summary (collapsed)

src/cli/tui/routes/workflow/components/timeline/main-agent-node.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,28 @@ import { useTheme } from "@tui/shared/context/theme"
1111
import { useTick } from "@tui/shared/hooks/tick"
1212
import { Spinner } from "@tui/shared/components/spinner"
1313
import type { AgentState } from "../../state/types"
14-
import { formatDuration } from "../../state/formatters"
14+
import { formatDuration, truncate } from "../../state/formatters"
1515
import { getStatusIcon, getStatusColor } from "./status-utils"
1616

1717
export interface MainAgentNodeProps {
1818
agent: AgentState
1919
isSelected: boolean
2020
isPaused?: boolean
21+
availableWidth?: number
2122
}
2223

24+
// Maximum agent name length before truncation
25+
const MAX_NAME_LENGTH = 22
26+
// Minimum timeline section width to show engine name
27+
const MIN_WIDTH_FOR_ENGINE = 45
28+
2329
export function MainAgentNode(props: MainAgentNodeProps) {
2430
const themeCtx = useTheme()
2531
const now = useTick()
2632

33+
// Only show engine if timeline section is wide enough
34+
const showEngine = () => (props.availableWidth ?? 80) >= MIN_WIDTH_FOR_ENGINE
35+
2736
const color = () => props.agent.error ? themeCtx.theme.error : getStatusColor(props.agent.status, themeCtx.theme)
2837

2938
// Store pause state for timer freeze/resume
@@ -79,27 +88,31 @@ export function MainAgentNode(props: MainAgentNodeProps) {
7988
// Selection indicator
8089
const selectionPrefix = () => (props.isSelected ? "> " : " ")
8190

91+
const displayName = () => truncate(props.agent.name, MAX_NAME_LENGTH)
92+
8293
return (
8394
<box flexDirection="column" paddingLeft={1} paddingRight={1}>
84-
{/* Main line */}
85-
<box flexDirection="row">
86-
<text fg={themeCtx.theme.text}>{selectionPrefix()}</text>
95+
{/* Main line - use wrapMode="none" and overflow="hidden" to prevent text wrapping */}
96+
<box flexDirection="row" overflow="hidden">
97+
<text wrapMode="none" fg={themeCtx.theme.text}>{selectionPrefix()}</text>
8798
<Show when={props.agent.status === "running"} fallback={
88-
<text fg={color()}>{getStatusIcon(props.agent.status)} </text>
99+
<text wrapMode="none" fg={color()}>{getStatusIcon(props.agent.status)} </text>
89100
}>
90101
<Show when={props.isPaused} fallback={
91102
<>
92103
<Spinner color={color()} />
93-
<text> </text>
104+
<text wrapMode="none"> </text>
94105
</>
95106
}>
96-
<text fg={themeCtx.theme.warning}>|| </text>
107+
<text wrapMode="none" fg={themeCtx.theme.warning}>|| </text>
97108
</Show>
98109
</Show>
99-
<text fg={themeCtx.theme.text} attributes={1}>{props.agent.name}</text>
100-
<text fg={themeCtx.theme.textMuted}> ({props.agent.engine})</text>
110+
<text wrapMode="none" fg={themeCtx.theme.text} attributes={1}>{displayName()}</text>
111+
<Show when={showEngine()}>
112+
<text wrapMode="none" fg={themeCtx.theme.textMuted}> ({props.agent.engine})</text>
113+
</Show>
101114
<Show when={duration()}>
102-
<text fg={themeCtx.theme.textMuted}>{duration()}</text>
115+
<text wrapMode="none" fg={themeCtx.theme.textMuted}>{duration()}</text>
103116
</Show>
104117
</box>
105118

src/cli/tui/routes/workflow/workflow-shell.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,14 @@ export function WorkflowShell(props: WorkflowShellProps) {
318318
<box flexDirection="row" flexGrow={1} gap={1}>
319319
<Show when={!isTimelineCollapsed()}>
320320
<box flexDirection="column" width={showOutputPanel() ? "35%" : "100%"}>
321-
<AgentTimeline state={state()} onToggleExpand={(id) => ui.actions.toggleExpand(id)} availableHeight={state().visibleItemCount} isPaused={isWaitingForInput()} isPromptBoxFocused={isPromptBoxFocused()} />
321+
<AgentTimeline state={state()} onToggleExpand={(id) => ui.actions.toggleExpand(id)} availableHeight={state().visibleItemCount} availableWidth={Math.floor((dimensions()?.width ?? 80) * (showOutputPanel() ? 0.35 : 1))} isPaused={isWaitingForInput()} isPromptBoxFocused={isPromptBoxFocused()} />
322322
</box>
323323
</Show>
324324
<Show when={showOutputPanel() || isTimelineCollapsed()}>
325325
<box flexDirection="column" width={isTimelineCollapsed() ? "100%" : "65%"}>
326326
<OutputWindow
327327
currentAgent={currentAgent()}
328+
availableWidth={Math.floor((dimensions()?.width ?? 80) * (isTimelineCollapsed() ? 1 : 0.65))}
328329
lines={logStream.lines}
329330
isLoading={logStream.isLoading}
330331
isConnecting={logStream.isConnecting}

0 commit comments

Comments
 (0)