/** * Mission Control TUI Header Component * * Displays: * - Title with icon * - Run ID * - Mission timer (elapsed time from run.started_at) * - Compact legend for keyboard navigation */ import type { Theme } from "@mariozechner/pi-coding-agent"; import type { Run } from "../state.js"; import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; export interface HeaderProps { run: Run | null; currentPhase: string; agentsReady: boolean; skillsReady: boolean; } /** * Format elapsed time as HH:MM:SS */ export function formatElapsedTime(startedAt: string): string { const start = new Date(startedAt).getTime(); const now = Date.now(); const elapsed = Math.floor((now - start) / 1000); const hours = Math.floor(elapsed / 3600); const minutes = Math.floor((elapsed % 3600) / 60); const seconds = elapsed % 60; return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; } /** * Render the header component * Returns an array of strings (one per line) */ export function renderHeader( width: number, props: HeaderProps, theme: Theme ): string[] { const title = theme.fg("accent", theme.bold("Mission Control")); const agents = props.agentsReady ? "[√] Agents" : "[ ] Agents"; const skills = props.skillsReady ? "[√] Skills" : "[ ] Skills"; const status = `${theme.fg(props.agentsReady ? "success" : "muted", agents)} ${theme.fg(props.skillsReady ? "success" : "muted", skills)}`; const padding = Math.max(1, width - visibleWidth(title) - visibleWidth(status)); return [truncateToWidth(title + " ".repeat(padding) + status, width)]; } /** * Header component class for dashboard integration */ export class HeaderComponent { private props: HeaderProps; private cachedWidth?: number; private cachedLines?: string[]; constructor(run: Run | null, currentPhase: string, agentsReady = true, skillsReady = true) { this.props = { run, currentPhase, agentsReady, skillsReady }; } update(run: Run | null, currentPhase: string, agentsReady = true, skillsReady = true): void { this.props = { run, currentPhase, agentsReady, skillsReady }; this.invalidate(); } render(width: number, theme: Theme): string[] { if (this.cachedLines && this.cachedWidth === width) { return this.cachedLines; } this.cachedLines = renderHeader(width, this.props, theme); this.cachedWidth = width; return this.cachedLines; } invalidate(): void { this.cachedWidth = undefined; this.cachedLines = undefined; } }