refactor(eve): simplify dev TUI header (#3526)

Signed-off-by: Colton Padden <colton.padden@vercel.com>
This commit is contained in:
Colton Padden
2026-09-18 16:52:10 -04:00
committed by GitHub
parent 9d394faa3a
commit d0d2b5ea01
8 changed files with 33 additions and 95 deletions
@@ -4,7 +4,7 @@ import type { AgentInfoResult } from "#client/index.js";
import { stripAnsi } from "#cli/ui/terminal-text.js";
import { createTestAgentInfoResult } from "#internal/testing/agent-info-fixture.js";
import { AGENT_HEADER_TIPS, buildAgentHeader, pickAgentHeaderTip } from "./agent-header.js";
import { buildAgentHeader } from "./agent-header.js";
import { createTheme } from "./theme.js";
const INFO = createTestAgentInfoResult({
@@ -23,11 +23,13 @@ describe("buildAgentHeader", () => {
const titleIndex = plain.findIndex((line) => line.includes("Weather Agent"));
expect(plain).toHaveLength(1);
expect(plain[titleIndex]).toMatch(/^eve v\d+\.\d+\.\d+ +Weather Agent$/u);
expect(plain[titleIndex]).toMatch(
/^eve v\d+\.\d+\.\d+ · Weather Agent · Run \/help for commands$/u,
);
expect(card).not.toContain("model");
expect(card).not.toContain("instructions");
expect(card).not.toContain("⣿");
expect(lines[0]).toContain(theme.colors.bold("eve"));
expect(lines[0]).toContain(theme.colors.bold("eve"));
});
it("renders only known fields before agent inspection", () => {
@@ -35,27 +37,21 @@ describe("buildAgentHeader", () => {
const card = buildAgentHeader({
name: "weather-agent",
theme,
tip: "Use the /help command to see every command.",
width: 120,
}).join("\n");
expect(card).toContain("weather-agent");
expect(card).toContain("Use the /help command to see every command.");
});
it("renders the /add tip with a blue command", () => {
it("uses ASCII separators and wordmark when Unicode is disabled", () => {
const theme = createTheme({ color: true, unicode: false });
const tip = AGENT_HEADER_TIPS.find((candidate) => candidate.includes("/add"));
const lines = buildAgentHeader({ info: INFO, theme, width: 120 });
expect(tip).toBe("/add to extend your agent · /help for commands");
if (tip === undefined) return;
const line = buildAgentHeader({ info: INFO, theme, width: 120, tip }).find((candidate) =>
candidate.includes("/add"),
expect(stripAnsi(lines[0] ?? "")).toMatch(
/^eve v\d+\.\d+\.\d+ - Weather Agent - Run \/help for commands$/u,
);
expect(stripAnsi(line ?? "")).toContain(tip);
expect(line).toContain(theme.colors.blue("/add"));
expect(lines[0]).toContain(theme.colors.dim("Weather Agent"));
expect(lines[0]).toContain(theme.colors.dim("Run /help for commands"));
});
it("keeps the discovery-diagnostics line when the compiler reported problems", () => {
@@ -70,10 +66,3 @@ describe("buildAgentHeader", () => {
expect(lines.some((line) => line.includes("2 warnings"))).toBe(true);
});
});
describe("pickAgentHeaderTip", () => {
it("maps the random draw across the whole pool", () => {
expect(pickAgentHeaderTip(() => 0)).toBe(AGENT_HEADER_TIPS[0]);
expect(pickAgentHeaderTip(() => 0.999)).toBe(AGENT_HEADER_TIPS.at(-1));
});
});
+7 -35
View File
@@ -3,7 +3,6 @@
import type { AgentInfoResult } from "#client/index.js";
import { resolveInstalledPackageInfo } from "#internal/application/package.js";
import { clipVisible } from "#cli/ui/terminal-text.js";
import { isPromptControlCommand } from "./prompt-commands.js";
import type { Theme } from "./theme.js";
export interface AgentHeaderInput {
@@ -14,37 +13,22 @@ export interface AgentHeaderInput {
theme: Theme;
/** Available terminal width. */
width: number;
/** Message-of-the-day line rendered below the startup card, when present. */
tip?: string;
}
/**
* The header's message-of-the-day pool. All entries reference local-only
* slash commands, so callers only attach a tip to local `eve dev` sessions.
*/
export const AGENT_HEADER_TIPS: readonly string[] = [
"/add to extend your agent · /help for commands",
];
/** Picks one tip; `random` is a test seam over Math.random. */
export function pickAgentHeaderTip(random: () => number = Math.random): string {
const index = Math.min(
AGENT_HEADER_TIPS.length - 1,
Math.floor(random() * AGENT_HEADER_TIPS.length),
);
return AGENT_HEADER_TIPS[index]!;
}
/** Returns the styled rows of the startup card and optional tip. */
/** Returns the styled rows of the startup card. */
export function buildAgentHeader(input: AgentHeaderInput): string[] {
const { theme, info, width } = input;
const c = theme.colors;
const version = resolveInstalledPackageInfo().version;
const available = Math.max(0, width - 1);
const agentName = info?.agent.name ?? input.name;
const title = `${c.bold("eve")} ${c.dim(`v${version}`)}${agentName ? ` ${agentName}` : ""}`;
const metadata = [
...(agentName === undefined ? [] : [c.dim(agentName)]),
c.dim("Run /help for commands"),
].join(c.dim(` ${theme.glyph.dot} `));
const wordmark = theme.unicode ? "☰eve" : "eve";
const title = c.bold(wordmark) + c.dim(` v${version} ${theme.glyph.dot} `) + metadata;
const lines = [clipVisible(title, available)];
if (input.tip) lines.push(renderTip(input.tip, available, theme));
if (info && (info.diagnostics.discoveryErrors > 0 || info.diagnostics.discoveryWarnings > 0)) {
const parts: string[] = [];
@@ -70,18 +54,6 @@ export function buildAgentHeader(input: AgentHeaderInput): string[] {
return lines;
}
function renderTip(tip: string, width: number, theme: Theme): string {
return clipVisible(
tip
.split(/(\/[a-z:-]+)/u)
.map((part) =>
isPromptControlCommand(part) ? theme.colors.blue(part) : theme.colors.dim(part),
)
.join(""),
width,
);
}
function plural(count: number): string {
return count === 1 ? "" : "s";
}
@@ -2068,7 +2068,6 @@ describe("EveTUIRunner initial input", () => {
vi.spyOn(client, "info").mockReturnValue(info.promise);
const startup = {
finish: vi.fn(() => ({ draft: "typed while loading", queuedPrompt: undefined })),
headerTip: "Use the /help command to see every command.",
};
const renderer = fakeRenderer();
const runner = new EveTUIRunner({
@@ -2096,7 +2095,6 @@ describe("EveTUIRunner initial input", () => {
async (result) => {
const login = createDeferred<void>();
const startup = {
headerTip: "/help",
finish: vi.fn(() => ({ draft: "still editing", queuedPrompt: "Hello Alice" })),
};
const handle = vi.fn(async () => {
@@ -2137,7 +2135,6 @@ describe("EveTUIRunner initial input", () => {
draft: "still editing",
queuedPrompt: "first message\n\nsecond message",
})),
headerTip: "Use the /help command to see every command.",
};
const renderer = fakeRenderer();
const runner = new EveTUIRunner({
-12
View File
@@ -53,7 +53,6 @@ import {
localFailureHint,
} from "./errors.js";
import { pickAgentHeaderTip } from "./agent-header.js";
import { probeAgentInfo } from "#services/dev-client/agent-info-probe.js";
import { parseLogDisplayMode } from "./log-display-mode.js";
import {
@@ -253,8 +252,6 @@ export type AgentTUIAgentHeader = {
name: string;
serverUrl: string;
info?: AgentInfoResult;
/** Message-of-the-day line shown below the startup card (local sessions only). */
tip?: string;
};
export type AgentTUIRenderer = {
@@ -428,7 +425,6 @@ export interface PromptCommandHandler {
}
type TuiStartup = {
readonly headerTip: string;
finish(): { draft: string; queuedPrompt: string | undefined };
};
@@ -567,12 +563,6 @@ export class EveTUIRunner {
*/
readonly #vercelStatus?: VercelStatusTracker;
readonly #mcpConnectionStatus?: McpConnectionStatusTracker;
/**
* The header's message-of-the-day, picked once so dev HMR header
* refreshes don't re-roll it mid-session. Local sessions only every
* tip references local-only slash commands.
*/
readonly #headerTip: string;
#agentInfo?: AgentInfoResult;
/**
* approval-id input-request map populated as `input.requested` events
@@ -640,7 +630,6 @@ export class EveTUIRunner {
}
this.#subagentPump = new SubagentPump(pumpOptions);
this.#name = options.name ?? "eve";
this.#headerTip = options.startup?.headerTip ?? pickAgentHeaderTip();
this.#withExclusiveTerminal = options.withExclusiveTerminal;
this.#tools = options.tools ?? "full";
this.#reasoning = options.reasoning ?? "full";
@@ -756,7 +745,6 @@ export class EveTUIRunner {
serverUrl,
};
if (headerInfo !== undefined) header.info = headerInfo;
if (this.#appRoot !== undefined && !this.#onboard) header.tip = this.#headerTip;
this.#renderer.renderAgentHeader?.(header);
return headerInfo;
}
@@ -203,13 +203,11 @@ describe("TerminalRenderer (inline scrollback)", () => {
connected: true,
credential: "api-key",
}),
tip: "Use the /deploy command to deploy your agent.",
});
renderer.shutdown();
const snapshot = screen.snapshot();
expect(snapshot).toMatch(/eve v\d+\.\d+\.\d+.*Weather Agent/u);
expect(snapshot).toContain("Use the /deploy command to deploy your agent.");
expect(snapshot).toMatch(/eve v\d+\.\d+\.\d+ · Weather Agent · Run \/help for commands/u);
expect(snapshot).not.toContain("http://localhost:3000");
});
@@ -238,7 +236,7 @@ describe("TerminalRenderer (inline scrollback)", () => {
const snapshot = screen.snapshot();
expect(snapshot).toContain("new-model");
expect(snapshot).not.toContain("old-model");
expect(snapshot.match(/eve v\d/gu)).toHaveLength(1);
expect(snapshot.match(/eve v\d/gu)).toHaveLength(1);
expect(snapshot).toContain("hello");
expect(snapshot).toContain("still here");
renderer.shutdown();
@@ -2613,11 +2611,10 @@ describe("TerminalRenderer (inline scrollback)", () => {
startupRenderer.beginStartupDraft({
initialDraft: "weather",
tip: "Use the /help command to see every command.",
title: "weather-agent",
});
expect(screen.snapshot()).toContain("weather-agent");
expect(screen.snapshot()).toContain("Use the /help command");
expect(screen.snapshot()).toContain("Run /help for commands");
expect(screen.snapshot()).not.toContain("model");
expect(screen.snapshot()).not.toContain("loading");
expect(screen.snapshot()).toContain("Starting agent");
@@ -2654,7 +2651,7 @@ describe("TerminalRenderer (inline scrollback)", () => {
"defers startup warnings until connection readiness at %i columns",
(columns) => {
const { renderer, screen } = makeRenderer(columns);
renderer.beginStartupDraft({ initialDraft: "Hello Alice", tip: "/help", title: "Agent" });
renderer.beginStartupDraft({ initialDraft: "Hello Alice", title: "Agent" });
renderer.renderSetupWarning("Model disconnected · /login");
expect(screen.snapshot()).not.toContain("Model disconnected");
renderer.setStartupPhase("connecting");
@@ -2674,7 +2671,7 @@ describe("TerminalRenderer (inline scrollback)", () => {
"keeps startup editable across connection work and questions at %i columns",
async (columns) => {
const { renderer, screen, input } = makeRenderer(columns);
renderer.beginStartupDraft({ initialDraft: "Hello", tip: "/help", title: "Agent" });
renderer.beginStartupDraft({ initialDraft: "Hello", title: "Agent" });
const composerRow = screen
.snapshot()
.split("\n")
@@ -2730,7 +2727,7 @@ describe("TerminalRenderer (inline scrollback)", () => {
it("restores the startup draft after a masked key question is cancelled", async () => {
const { renderer, input, screen } = makeRenderer();
renderer.beginStartupDraft({ initialDraft: "My message", tip: "/help", title: "Agent" });
renderer.beginStartupDraft({ initialDraft: "My message", title: "Agent" });
renderer.setupFlow.begin("Connect a model", "pulse");
const answer = renderer.setupFlow.readText({ message: "API key", mask: true });
input.type("private-test-key");
@@ -2756,7 +2753,6 @@ describe("TerminalRenderer (inline scrollback)", () => {
});
renderer.beginStartupDraft({
tip: "Use the /help command to see every command.",
title: "weather-agent",
});
input.ctrlC();
@@ -4086,7 +4082,7 @@ describe("TerminalRenderer (inline scrollback)", () => {
renderer.renderAgentHeader({ name: "Weather Agent", serverUrl: "http://localhost:3000" });
renderer.shutdown();
expect(countOccurrences(screen.snapshot(), "eve v")).toBe(1);
expect(countOccurrences(screen.snapshot(), "eve v")).toBe(1);
});
it("reset clears committed transcript rows", () => {
@@ -302,8 +302,6 @@ export type AgentHeaderOptions = {
name: string;
serverUrl: string;
info?: AgentInfoResult;
/** Message-of-the-day line below the startup card (local sessions only). */
tip?: string;
};
type DisplayModes = {
@@ -414,7 +412,7 @@ export class TerminalRenderer implements AgentTUIRenderer {
#startupEditor?: LineState;
#startupConsumer?: (key: TerminalKey) => void;
#startupStartedAt = 0;
#startupHeader?: { readonly name: string; readonly tip: string };
#startupHeader?: { readonly name: string };
#agentHeaderRendered = false;
/** The last committed header body, to skip re-committing an unchanged banner. */
#agentHeaderBody?: string;
@@ -695,12 +693,12 @@ export class TerminalRenderer implements AgentTUIRenderer {
this.#live.flush(this.#renderAgentHeaderRows(), this.#footerRows(this.#width()));
}
beginStartupDraft(options: { initialDraft?: string; tip: string; title: string }): void {
beginStartupDraft(options: { initialDraft?: string; title: string }): void {
this.#start({ title: options.title });
this.#inputActive = true;
this.#promptPlaceholderActive = true;
this.#startupPhase = "starting";
this.#startupHeader = { name: options.title, tip: options.tip };
this.#startupHeader = { name: options.title };
this.#startupStartedAt = Date.now();
let editor = lineOf(stripPromptControlCharacters(options.initialDraft ?? ""));
this.#startupEditor = editor;
@@ -4237,8 +4235,6 @@ export class TerminalRenderer implements AgentTUIRenderer {
width: this.#width(),
};
if (header?.info !== undefined) input.info = header.info;
const tip = header?.tip ?? startup?.tip;
if (tip !== undefined) input.tip = tip;
return buildAgentHeader(input);
}
-5
View File
@@ -20,7 +20,6 @@ import { createDevDiagnostics, type DevDiagnostics } from "../diagnostics.js";
import { createPromptCommandHandler } from "./prompt-command-handler.js";
import { promptCommandsFor } from "./prompt-commands.js";
import { pickAgentHeaderTip } from "./agent-header.js";
import { formatRemoteAuthChallengeMessage } from "./remote-auth-result.js";
import { probeMcpConnection } from "./mcp-connection-status.js";
import { EveTUIRunner, type EveTUIRunnerOptions } from "./runner.js";
@@ -53,7 +52,6 @@ export interface RunDevelopmentTuiInput extends TuiDisplayOptions {
export interface DevelopmentTuiStartup {
readonly diagnostics: DevDiagnostics | undefined;
readonly headerTip: string;
readonly renderer: TerminalRenderer;
finish(): { draft: string; queuedPrompt: string | undefined };
shutdown(): Promise<void>;
@@ -67,7 +65,6 @@ export async function startDevelopmentTuiStartup(
},
): Promise<DevelopmentTuiStartup> {
const diagnostics = await createDevDiagnostics(input.appRoot).catch(() => undefined);
const headerTip = pickAgentHeaderTip();
const renderer = new TerminalRenderer({
...input,
diagnostics,
@@ -75,12 +72,10 @@ export async function startDevelopmentTuiStartup(
});
renderer.beginStartupDraft({
initialDraft: input.initialInput,
tip: headerTip,
title: input.name ?? "eve",
});
return {
diagnostics,
headerTip,
renderer,
finish: () => renderer.finishStartupDraft(),
async shutdown() {