Welcome back! In Chapter 2: Task Detail Dialogs, we built detailed windows to inspect our tasks. Before that, in Chapter 1: Background Task Footer, we built a summary bar.
Imagine you are driving. You see a Red light. You stop. Now imagine you drive to the next town, and there, Blue means stop. You would crash immediately!
In user interfaces, we face a similar problem.
If we hard-code color="green" in the Footer and color="blue" in the Dialog, our app becomes inconsistent and confusing.
We solve this by creating a Visual Status System. This is a collection of helper functions that act as the "Source of Truth" for how things look.
Instead of asking: "What color is a failed task?" The UI asks: "Hey System, here is a task status. Give me the correct color and icon."
We don't talk in hex codes (like #FF0000). We talk in Meanings:
We use symbols to represent state quickly without reading text:
Here is how a raw text status is converted into a visual element:
The heart of this system lives in taskStatusUtils.tsx. Let's look at how it makes decisions.
The function getTaskStatusIcon takes the status and returns a character string (an emoji or symbol).
import figures from 'figures'; // A library for cross-platform symbols
export function getTaskStatusIcon(status, options) {
if (options?.hasError) return figures.cross; // โ
if (status === 'running') {
return figures.play; // โถ
}
if (status === 'completed') return figures.tick; // โ
return figures.bullet; // โข (Default fallback)
}
Similarly, getTaskStatusColor returns the semantic color name recognized by our terminal rendering engine (Ink).
export function getTaskStatusColor(status, options) {
if (options?.hasError) return 'error'; // Red
if (status === 'completed') return 'success'; // Green
if (status === 'killed') return 'warning'; // Yellow
return 'background'; // Default/Dim color
}
AI agents are complex. They don't just "run"; they "think," "search," or "write." We use describeTeammateActivity to generate a one-line summary.
export function describeTeammateActivity(task) {
if (task.shutdownRequested) return 'stopping';
// If the AI has a plan, summarize the recent activity
// e.g., "reading file..." or "searching google..."
const activity = task.progress?.lastActivity?.activityDescription;
return activity ?? 'working';
}
Now, let's look at ShellProgress.tsx. This component is used in both the Footer and the Dialogs. It uses our Visual System to render consistent text.
Instead of manually checking if (status === 'failed'), we use the utility helpers.
import { Text } from 'src/ink.js';
// A reusable sub-component for text coloring
export function TaskStatusText({ status, label }) {
// 1. Determine color based on status
const color = status === 'completed' ? 'success'
: status === 'failed' ? 'error'
: undefined;
// 2. Render text with that color
return (
<Text color={color} dimColor>
({label ?? status})
</Text>
);
}
The main ShellProgress component switches between states but relies on the styling logic above.
export function ShellProgress({ shell }) {
switch (shell.status) {
case 'completed':
return <TaskStatusText status="completed" label="done" />;
case 'failed':
return <TaskStatusText status="failed" label="error" />;
case 'running':
return <TaskStatusText status="running" />;
default:
return null;
}
}
What just happened?
shell object.failed, we ask for the "error" label.TaskStatusText sets the color to 'error' (Red).(error) text.Sometimes, the Visual System decides to show nothing.
In taskStatusUtils.tsx, there is a function shouldHideTasksFooter.
If we are looking at a detailed "Spinner Tree" (a hierarchical view of tasks), we might not want to show the footer at all to save space.
export function shouldHideTasksFooter(tasks, showSpinnerTree) {
// If spinner tree is off, always show footer
if (!showSpinnerTree) return false;
// If we have active background tasks that aren't
// part of the main tree, keep the footer.
// ... (logic to check task types) ...
return true; // Hide footer
}
This ensures the screen doesn't get cluttered with duplicate information.
By moving our colors and icons into a centralized system (taskStatusUtils), we achieved:
if/else chains.โ to โ
, we change it in one place.Now that our local tasks look great, what happens when we connect to a server somewhere else?
Next Chapter: Remote Session Visualization
Generated by Code IQ