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
This commit is contained in:
Fusion
2026-04-23 10:33:56 -07:00
committed by gsxdsm
parent 187e5fccf0
commit 86fd24e1d0
6 changed files with 36 additions and 15 deletions

View File

@@ -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.

View File

@@ -70,12 +70,15 @@ interactive TUI with five sections:
| Section | Description | | Section | Description |
|---|---| |---|---|
| **Logs** | Real-time log entries with timestamps and severity levels |
| **System** | Host, port, URL, auth mode, token, engine status, uptime | | **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 | | **Utilities** | Actions: refresh stats, clear logs, toggle engine pause |
| **Stats** | Task counts by column, active task count, agent state counts | | **Stats** | Task counts by column, active task count, agent state counts |
| **Settings** | Key settings from the task store | | **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:** **Keyboard Navigation:**
| Key | Action | | Key | Action |

View File

@@ -166,26 +166,34 @@ describe("renderHeaderToString", () => {
it("renders correctly at wide terminal width (>= 70 cols)", () => { it("renders correctly at wide terminal width (>= 70 cols)", () => {
const header = renderHeaderToString(80); const header = renderHeaderToString(80);
expect(header).toContain("fusion"); expect(header).toContain("fusion");
expect(header).toContain("Logs"); expect(header).toContain("[1] System");
expect(header).toContain("System"); expect(header).toContain("[2] Logs");
}); });
it("renders correctly at medium terminal width (>= 40 cols)", () => { it("renders correctly at medium terminal width (>= 40 cols)", () => {
const header = renderHeaderToString(50); const header = renderHeaderToString(50);
expect(header).toContain("fusion"); 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)", () => { it("renders correctly at narrow terminal width (< 40 cols)", () => {
const header = renderHeaderToString(30); const header = renderHeaderToString(30);
expect(header).toContain("fusion"); expect(header).toContain("fusion");
expect(header).toContain("Logs"); expect(header).toContain("[1]System");
expect(header).toContain("[n/p]nav"); expect(header).toContain("[n/p]nav");
}); });
}); });
// ── Type exports verification ───────────────────────────────────────────── // ── 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", () => { describe("Type exports", () => {
it("exports LogEntry type", () => { it("exports LogEntry type", () => {
const entry = { const entry = {

View File

@@ -2,8 +2,8 @@
* Dashboard TUI Renderer * Dashboard TUI Renderer
* *
* An interactive terminal user interface for `fn dashboard` with five sections: * 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 * - system: host, port, URL, auth mode, token, engine mode
* - logs: real-time log entries with ring buffer
* - utilities: action list with keybindings * - utilities: action list with keybindings
* - stats: task counts, active task count, agent state counts * - stats: task counts, active task count, agent state counts
* - settings: real key/value settings from TaskStore * - settings: real key/value settings from TaskStore
@@ -257,10 +257,10 @@ function visibleTruncate(text: string, maxWidth: number): string {
// ── Dashboard TUI Renderer ─────────────────────────────────────────────────── // ── Dashboard TUI Renderer ───────────────────────────────────────────────────
const SECTION_ORDER: SectionId[] = ["logs", "system", "utilities", "stats", "settings"]; const SECTION_ORDER: SectionId[] = ["system", "logs", "utilities", "stats", "settings"];
export class DashboardTUI { export class DashboardTUI {
private activeSection: SectionId = "logs"; private activeSection: SectionId = "system";
private logBuffer: LogRingBuffer; private logBuffer: LogRingBuffer;
private systemInfo: SystemInfo | null = null; private systemInfo: SystemInfo | null = null;
private taskStats: TaskStats | null = null; private taskStats: TaskStats | null = null;
@@ -684,8 +684,8 @@ export class DashboardTUI {
process.stdout.write(title); process.stdout.write(title);
// Responsive header modes based on terminal width: // Responsive header modes based on terminal width:
// - Wide (>= 70 cols): Full labels "[1] Logs [2] System [3] Utilities [4] Stats [5] Settings" // - Wide (>= 70 cols): Full labels "[1] System [2] Logs [3] Utilities [4] Stats [5] Settings"
// - Medium (>= 40 cols): Short labels "[1] L [2] S [3] U [4] St [5] Se" // - 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" // - Narrow (< 40 cols): Only active tab with navigation hint "[n/p] Previous/Next"
if (cols >= 70) { if (cols >= 70) {
@@ -1331,7 +1331,7 @@ export function renderHeaderToString(cols: number): string {
if (cols >= 70) { if (cols >= 70) {
for (let i = 0; i < SECTION_ORDER.length; i++) { for (let i = 0; i < SECTION_ORDER.length; i++) {
const section = SECTION_ORDER[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 num = (i + 1).toString();
const label = section.charAt(0).toUpperCase() + section.slice(1); const label = section.charAt(0).toUpperCase() + section.slice(1);
const tabText = `[${num}] ${label}`; const tabText = `[${num}] ${label}`;
@@ -1348,7 +1348,7 @@ export function renderHeaderToString(cols: number): string {
}; };
for (let i = 0; i < SECTION_ORDER.length; i++) { for (let i = 0; i < SECTION_ORDER.length; i++) {
const section = SECTION_ORDER[i]; const section = SECTION_ORDER[i];
const isActive = section === "logs"; const isActive = section === "system";
const num = (i + 1).toString(); const num = (i + 1).toString();
const shortLabel = shortLabels[section]; const shortLabel = shortLabels[section];
const tabText = `[${num}]${shortLabel}`; const tabText = `[${num}]${shortLabel}`;
@@ -1356,7 +1356,7 @@ export function renderHeaderToString(cols: number): string {
output += colorize(` ${tabText} `, style); output += colorize(` ${tabText} `, style);
} }
} else { } else {
output += colorize(" [1]Logs ", "brightBlue"); output += colorize(" [1]System ", "brightBlue");
output += colorize(" [n/p]nav ", "dim"); output += colorize(" [n/p]nav ", "dim");
} }

View File

@@ -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 // When both stdout and stdin are TTY, we activate the interactive TUI
// instead of plain console output. The TUI provides 5 sections: // 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 // In non-TTY mode (CI, piped output), we fall back to plain console
// output to maintain compatibility with automated workflows. // output to maintain compatibility with automated workflows.

View File

@@ -236,6 +236,11 @@ export class FirstRunDetector {
* @returns Generated name * @returns Generated name
*/ */
async generateProjectName(projectPath: string): Promise<string> { async generateProjectName(projectPath: string): Promise<string> {
// 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 git remote first
try { try {
const { execFile } = await import("node:child_process"); const { execFile } = await import("node:child_process");
@@ -245,7 +250,7 @@ export class FirstRunDetector {
const { stdout } = await execFileAsync( const { stdout } = await execFileAsync(
"git", "git",
["remote", "get-url", "origin"], ["remote", "get-url", "origin"],
{ cwd: projectPath, timeout: 5000 } { cwd: projectPath, timeout: 1000 }
); );
const remoteUrl = stdout.trim(); const remoteUrl = stdout.trim();