From e59adc51256528015962aa9a1d43049c47322b3b Mon Sep 17 00:00:00 2001 From: Fusion Date: Thu, 23 Apr 2026 10:33:56 -0700 Subject: [PATCH] feat(FN-2311): default dashboard TUI to system section - Reorder dashboard TUI sections so System is tab [1] and the initial active view - Update header rendering across wide, medium, and narrow terminal layouts to reflect the new tab order - Expand dashboard TUI tests to assert system-first labels and default active section behavior - Document system-first startup behavior in CLI reference and add a patch changeset for @runfusion/fusion --- .changeset/dashboard-tui-status-first.md | 5 +++++ docs/cli-reference.md | 5 ++++- packages/cli/src/commands/dashboard-tui.test.ts | 16 ++++++++++++---- packages/cli/src/commands/dashboard-tui.ts | 16 ++++++++-------- packages/cli/src/commands/dashboard.ts | 2 +- packages/core/src/migration.ts | 7 ++++++- 6 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 .changeset/dashboard-tui-status-first.md diff --git a/.changeset/dashboard-tui-status-first.md b/.changeset/dashboard-tui-status-first.md new file mode 100644 index 000000000..273463cae --- /dev/null +++ b/.changeset/dashboard-tui-status-first.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +`fn dashboard` TTY mode now opens on the System tab first so users immediately see host, port, URL, and auth token access details. diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 513c286ce..c1b4349e5 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -70,12 +70,15 @@ interactive TUI with five sections: | Section | Description | |---|---| -| **Logs** | Real-time log entries with timestamps and severity levels | | **System** | Host, port, URL, auth mode, token, engine status, uptime | +| **Logs** | Real-time log entries with timestamps and severity levels | | **Utilities** | Actions: refresh stats, clear logs, toggle engine pause | | **Stats** | Task counts by column, active task count, agent state counts | | **Settings** | Key settings from the task store | +On startup, the TUI opens on the **System** section by default so you can +immediately see host/port and access-token details. + **Keyboard Navigation:** | Key | Action | diff --git a/packages/cli/src/commands/dashboard-tui.test.ts b/packages/cli/src/commands/dashboard-tui.test.ts index 673840317..f9769ed10 100644 --- a/packages/cli/src/commands/dashboard-tui.test.ts +++ b/packages/cli/src/commands/dashboard-tui.test.ts @@ -166,26 +166,34 @@ describe("renderHeaderToString", () => { it("renders correctly at wide terminal width (>= 70 cols)", () => { const header = renderHeaderToString(80); expect(header).toContain("fusion"); - expect(header).toContain("Logs"); - expect(header).toContain("System"); + expect(header).toContain("[1] System"); + expect(header).toContain("[2] Logs"); }); it("renders correctly at medium terminal width (>= 40 cols)", () => { const header = renderHeaderToString(50); expect(header).toContain("fusion"); - expect(header).toContain("[1]L"); // Short label for Logs + expect(header).toContain("[1]S"); // Short label for System + expect(header).toContain("[2]L"); // Short label for Logs }); it("renders correctly at narrow terminal width (< 40 cols)", () => { const header = renderHeaderToString(30); expect(header).toContain("fusion"); - expect(header).toContain("Logs"); + expect(header).toContain("[1]System"); expect(header).toContain("[n/p]nav"); }); }); // ── Type exports verification ───────────────────────────────────────────── +describe("DashboardTUI default section", () => { + it("starts on the system section by default", () => { + const tui = new DashboardTUI(); + expect((tui as any).activeSection).toBe("system"); + }); +}); + describe("Type exports", () => { it("exports LogEntry type", () => { const entry = { diff --git a/packages/cli/src/commands/dashboard-tui.ts b/packages/cli/src/commands/dashboard-tui.ts index dcefc084e..9d81afb58 100644 --- a/packages/cli/src/commands/dashboard-tui.ts +++ b/packages/cli/src/commands/dashboard-tui.ts @@ -2,8 +2,8 @@ * Dashboard TUI Renderer * * An interactive terminal user interface for `fn dashboard` with five sections: - * - logs: real-time log entries with ring buffer * - system: host, port, URL, auth mode, token, engine mode + * - logs: real-time log entries with ring buffer * - utilities: action list with keybindings * - stats: task counts, active task count, agent state counts * - settings: real key/value settings from TaskStore @@ -257,10 +257,10 @@ function visibleTruncate(text: string, maxWidth: number): string { // ── Dashboard TUI Renderer ─────────────────────────────────────────────────── -const SECTION_ORDER: SectionId[] = ["logs", "system", "utilities", "stats", "settings"]; +const SECTION_ORDER: SectionId[] = ["system", "logs", "utilities", "stats", "settings"]; export class DashboardTUI { - private activeSection: SectionId = "logs"; + private activeSection: SectionId = "system"; private logBuffer: LogRingBuffer; private systemInfo: SystemInfo | null = null; private taskStats: TaskStats | null = null; @@ -684,8 +684,8 @@ export class DashboardTUI { process.stdout.write(title); // Responsive header modes based on terminal width: - // - Wide (>= 70 cols): Full labels "[1] Logs [2] System [3] Utilities [4] Stats [5] Settings" - // - Medium (>= 40 cols): Short labels "[1] L [2] S [3] U [4] St [5] Se" + // - Wide (>= 70 cols): Full labels "[1] System [2] Logs [3] Utilities [4] Stats [5] Settings" + // - Medium (>= 40 cols): Short labels "[1] S [2] L [3] U [4] St [5] Se" // - Narrow (< 40 cols): Only active tab with navigation hint "[n/p] Previous/Next" if (cols >= 70) { @@ -1331,7 +1331,7 @@ export function renderHeaderToString(cols: number): string { if (cols >= 70) { for (let i = 0; i < SECTION_ORDER.length; i++) { const section = SECTION_ORDER[i]; - const isActive = section === "logs"; // Default active section for test + const isActive = section === "system"; // Default active section for test const num = (i + 1).toString(); const label = section.charAt(0).toUpperCase() + section.slice(1); const tabText = `[${num}] ${label}`; @@ -1348,7 +1348,7 @@ export function renderHeaderToString(cols: number): string { }; for (let i = 0; i < SECTION_ORDER.length; i++) { const section = SECTION_ORDER[i]; - const isActive = section === "logs"; + const isActive = section === "system"; const num = (i + 1).toString(); const shortLabel = shortLabels[section]; const tabText = `[${num}]${shortLabel}`; @@ -1356,7 +1356,7 @@ export function renderHeaderToString(cols: number): string { output += colorize(` ${tabText} `, style); } } else { - output += colorize(" [1]Logs ", "brightBlue"); + output += colorize(" [1]System ", "brightBlue"); output += colorize(" [n/p]nav ", "dim"); } diff --git a/packages/cli/src/commands/dashboard.ts b/packages/cli/src/commands/dashboard.ts index 773efa544..6272ee7f0 100644 --- a/packages/cli/src/commands/dashboard.ts +++ b/packages/cli/src/commands/dashboard.ts @@ -240,7 +240,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?: // // When both stdout and stdin are TTY, we activate the interactive TUI // instead of plain console output. The TUI provides 5 sections: - // logs, system, utilities, stats, settings with keyboard navigation. + // system, logs, utilities, stats, settings with keyboard navigation. // // In non-TTY mode (CI, piped output), we fall back to plain console // output to maintain compatibility with automated workflows. diff --git a/packages/core/src/migration.ts b/packages/core/src/migration.ts index 6f578c730..a0e763741 100644 --- a/packages/core/src/migration.ts +++ b/packages/core/src/migration.ts @@ -236,6 +236,11 @@ export class FirstRunDetector { * @returns Generated name */ async generateProjectName(projectPath: string): Promise { + // Fast path: avoid invoking git for non-repositories (prevents unnecessary delays in tests/startup). + if (!existsSync(join(projectPath, ".git"))) { + return basename(projectPath); + } + // Try git remote first try { const { execFile } = await import("node:child_process"); @@ -245,7 +250,7 @@ export class FirstRunDetector { const { stdout } = await execFileAsync( "git", ["remote", "get-url", "origin"], - { cwd: projectPath, timeout: 5000 } + { cwd: projectPath, timeout: 1000 } ); const remoteUrl = stdout.trim();