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:
5
.changeset/dashboard-tui-status-first.md
Normal file
5
.changeset/dashboard-tui-status-first.md
Normal 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.
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -236,6 +236,11 @@ export class FirstRunDetector {
|
||||
* @returns Generated name
|
||||
*/
|
||||
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 {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user