Replace the legacy ANSI-based DashboardTUI with an Ink/React rewrite under packages/cli/src/commands/dashboard-tui/, delete the standalone @fusion/tui package, and make `fn` (no args) launch the dashboard. The new TUI keeps the existing 5-panel status mode (system, logs, utilities, stats, settings) but adds an interactive mode (b/a/g) with three views: a kanban board with project picker and per-task detail, an agents list+detail with state management, and a settings editor. Bordered focus-aware panels, solid-background help overlay, static all-blue FUSION splash that adapts to small terminals. DashboardTUI and DashboardLogSink public API are unchanged so dashboard.ts only needed import-path updates plus interactiveData/loadingStatus wiring. Also adds zod to @fusion/dashboard to satisfy a peer dep introduced by pi-coding-agent 0.70. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { LogRingBuffer } from "../log-ring-buffer.js";
|
|
|
|
describe("LogRingBuffer", () => {
|
|
let buffer: LogRingBuffer;
|
|
|
|
beforeEach(() => {
|
|
buffer = new LogRingBuffer();
|
|
});
|
|
|
|
it("stores entries and reports total count", () => {
|
|
buffer.push({ timestamp: new Date(), level: "info", message: "test1" });
|
|
buffer.push({ timestamp: new Date(), level: "warn", message: "test2" });
|
|
expect(buffer.total).toBe(2);
|
|
});
|
|
|
|
it("returns all entries in chronological order", () => {
|
|
buffer.push({ timestamp: new Date("2026-01-01T10:00:00"), level: "info", message: "first" });
|
|
buffer.push({ timestamp: new Date("2026-01-01T11:00:00"), level: "info", message: "second" });
|
|
const entries = buffer.getAll();
|
|
expect(entries.length).toBe(2);
|
|
expect(entries[0].message).toBe("first");
|
|
expect(entries[1].message).toBe("second");
|
|
});
|
|
|
|
it("caps at MAX_LOG_ENTRIES (1000)", () => {
|
|
for (let i = 0; i < 1500; i++) {
|
|
buffer.push({ timestamp: new Date(), level: "info", message: `entry-${i}` });
|
|
}
|
|
const entries = buffer.getAll();
|
|
expect(entries.length).toBe(1000);
|
|
expect(buffer.total).toBe(1500);
|
|
});
|
|
|
|
it("maintains chronological order when overwriting", () => {
|
|
for (let i = 0; i < 1500; i++) {
|
|
buffer.push({ timestamp: new Date(2026, 0, 1, 0, i), level: "info", message: `entry-${i}` });
|
|
}
|
|
const entries = buffer.getAll();
|
|
expect(entries.length).toBe(1000);
|
|
expect(entries[0].message).toBe("entry-500");
|
|
expect(entries[entries.length - 1].message).toBe("entry-1499");
|
|
});
|
|
|
|
it("clears all entries", () => {
|
|
buffer.push({ timestamp: new Date(), level: "info", message: "test" });
|
|
buffer.clear();
|
|
expect(buffer.getAll().length).toBe(0);
|
|
expect(buffer.total).toBe(0);
|
|
});
|
|
|
|
it("stores entries with different levels", () => {
|
|
buffer.push({ timestamp: new Date(), level: "info", message: "info msg" });
|
|
buffer.push({ timestamp: new Date(), level: "warn", message: "warn msg" });
|
|
buffer.push({ timestamp: new Date(), level: "error", message: "error msg" });
|
|
const entries = buffer.getAll();
|
|
expect(entries[0].level).toBe("info");
|
|
expect(entries[1].level).toBe("warn");
|
|
expect(entries[2].level).toBe("error");
|
|
});
|
|
|
|
it("stores entries with prefix", () => {
|
|
buffer.push({ timestamp: new Date(), level: "info", message: "msg", prefix: "engine" });
|
|
const entries = buffer.getAll();
|
|
expect(entries[0].prefix).toBe("engine");
|
|
});
|
|
});
|