diff --git a/.changeset/fn-7877-system-tab-spacing.md b/.changeset/fn-7877-system-tab-spacing.md
new file mode 100644
index 0000000000..eba8d7731c
--- /dev/null
+++ b/.changeset/fn-7877-system-tab-spacing.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Fix cramped spacing on the Command Center System tab (logs and controls).
+category: fix
+dev: System tab now wraps SystemControlsArea + SystemStatsArea in a flex container so all sections share the --space-lg rhythm; adds a gap after Server logs.
diff --git a/packages/dashboard/app/components/command-center/CommandCenter.css b/packages/dashboard/app/components/command-center/CommandCenter.css
index 4f67b19c19..4f7674b956 100644
--- a/packages/dashboard/app/components/command-center/CommandCenter.css
+++ b/packages/dashboard/app/components/command-center/CommandCenter.css
@@ -123,10 +123,25 @@ Dashboard follows the global header rule: surface background, canonical height/p
-webkit-overflow-scrolling: touch;
}
+/*
+FNXC:SystemPanel 2026-07-12-16:10:
+The System tab combines a controls fragment with a stats .cc-area. Keep the tabpanel as the only vertical scroll owner, and put the shared --space-lg section rhythm on this lightweight wrapper so the Server logs section has breathing room before Live system health.
+*/
+.cc-system-tab {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-lg);
+ min-inline-size: 0;
+}
+
@media (max-width: 768px) {
.cc-tabpanel {
padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0) + var(--standalone-bottom-gap));
}
+
+ .cc-system-tab {
+ gap: var(--space-lg);
+ }
}
/* ---- Overview ---- */
diff --git a/packages/dashboard/app/components/command-center/CommandCenter.tsx b/packages/dashboard/app/components/command-center/CommandCenter.tsx
index fc6e1170d4..c346819f35 100644
--- a/packages/dashboard/app/components/command-center/CommandCenter.tsx
+++ b/packages/dashboard/app/components/command-center/CommandCenter.tsx
@@ -595,10 +595,14 @@ export function CommandCenter({
live "on the dashboard under System where we have runtime metrics".
*/
return (
- <>
+
+ {/*
+ FNXC:SystemPanel 2026-07-12-16:10:
+ The System tab renders a bare SystemControlsArea section fragment followed by SystemStatsArea's own .cc-area wrapper. The tab-level flex container owns the inter-area rhythm so controls, rebuild output, Server logs, and Live system health all share the same --space-lg gap without adding another scroll owner.
+ */}
- >
+
);
case "plugins":
/*
diff --git a/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx b/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx
new file mode 100644
index 0000000000..2e56a096e3
--- /dev/null
+++ b/packages/dashboard/app/components/command-center/__tests__/SystemControlsArea.test.tsx
@@ -0,0 +1,157 @@
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { CommandCenter } from "../CommandCenter";
+
+const apiMock = vi.fn();
+const mockFetchSystemInfo = vi.fn();
+const mockFetchCurrentSystemRebuild = vi.fn();
+const mockFetchSystemStats = vi.fn();
+const mockFetchNodeSystemStats = vi.fn();
+const mockFetchGlobalSettings = vi.fn();
+const mockFetchNodes = vi.fn();
+
+vi.mock("../../../api/legacy", () => ({
+ api: (path: string, opts?: RequestInit) => apiMock(path, opts),
+ withProjectId: (path: string, projectId?: string) =>
+ projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
+ fetchOrgTree: vi.fn().mockResolvedValue([]),
+ fetchExecutorStats: vi.fn().mockResolvedValue({ globalPause: false, enginePaused: false, maxConcurrent: 2 }),
+ fetchSettings: vi.fn().mockResolvedValue({ maxConcurrent: 2, maxTriageConcurrent: 1, maxWorktrees: 5 }),
+ fetchConfig: vi.fn().mockResolvedValue({ maxConcurrent: 2, rootDir: "/" }),
+ updateSettings: vi.fn().mockResolvedValue({}),
+ createBackup: vi.fn().mockResolvedValue({ ok: true }),
+ fetchDashboardHealth: vi.fn().mockResolvedValue({ ok: true }),
+ fetchCurrentSystemRebuild: (...args: unknown[]) => mockFetchCurrentSystemRebuild(...args),
+ fetchSystemInfo: (...args: unknown[]) => mockFetchSystemInfo(...args),
+ fetchSystemLogs: vi.fn().mockResolvedValue({ entries: [] }),
+ reloadAllSystemPlugins: vi.fn().mockResolvedValue({ ok: true }),
+ requestSystemRestart: vi.fn().mockResolvedValue({ ok: true }),
+ restartAllSystemAgents: vi.fn().mockResolvedValue({ ok: true }),
+ restartSystemEngines: vi.fn().mockResolvedValue({ ok: true }),
+ startSystemRebuild: vi.fn().mockResolvedValue({ id: "job-1", status: "running", lines: [] }),
+}));
+
+vi.mock("../../../api", () => ({
+ fetchSystemStats: (...args: unknown[]) => mockFetchSystemStats(...args),
+ fetchNodeSystemStats: (...args: unknown[]) => mockFetchNodeSystemStats(...args),
+ fetchGlobalSettings: (...args: unknown[]) => mockFetchGlobalSettings(...args),
+ fetchNodes: (...args: unknown[]) => mockFetchNodes(...args),
+ killVitestProcesses: vi.fn().mockResolvedValue({ killed: 0, pids: [] }),
+ updateGlobalSettings: vi.fn().mockResolvedValue({}),
+}));
+
+vi.mock("../../../sse-bus", () => ({
+ subscribeSse: vi.fn(() => () => undefined),
+}));
+
+vi.mock("../../../hooks/useAppSettings", () => ({
+ useAppSettings: () => ({
+ globalPaused: false,
+ enginePaused: false,
+ toggleGlobalPause: vi.fn(),
+ toggleEnginePause: vi.fn(),
+ refresh: vi.fn().mockResolvedValue(undefined),
+ }),
+}));
+
+function emptyOverviewResponse(path: string) {
+ if (path.includes("/command-center/tokens")) {
+ return { totals: { totalTokens: 0, inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, nTasks: 0 }, cost: { usd: null, unavailable: true, stale: false }, groups: [] };
+ }
+ if (path.includes("/command-center/tools")) return { totals: { calls: 0, errors: 0 }, groups: [] };
+ if (path.includes("/command-center/activity")) return { totals: { agentRuns: 0, tasksDone: 0 }, daily: [] };
+ if (path.includes("/command-center/signals")) return { totals: { open: 0, closed: 0 }, groups: [] };
+ if (path.includes("/command-center/live")) return { activeTasks: 0, activeAgents: 0, openSignals: 0, tokensPerMinute: 0, tasksByColumn: [] };
+ return {};
+}
+
+function systemInfoFixture() {
+ return {
+ pid: 12345,
+ nodeVersion: "v22.0.0",
+ platform: "darwin",
+ arch: "arm64",
+ sourceCheckout: true,
+ supervised: true,
+ engineRestartSupported: true,
+ agentRestartSupported: true,
+ pluginReloadSupported: true,
+ logsSupported: true,
+ activeRebuild: null,
+ };
+}
+
+function systemStatsFixture() {
+ return {
+ systemStats: {
+ rss: 1024,
+ heapUsed: 512,
+ heapTotal: 1024,
+ heapLimit: 2048,
+ external: 0,
+ arrayBuffers: 0,
+ cpuPercent: 10,
+ loadAvg: [0.1, 0.2, 0.3] as [number, number, number],
+ cpuCount: 8,
+ systemTotalMem: 4096,
+ systemFreeMem: 2048,
+ pid: 12345,
+ nodeVersion: "v22.0.0",
+ platform: "darwin/arm64",
+ },
+ taskStats: {
+ total: 0,
+ byColumn: {},
+ active: 0,
+ agents: { idle: 0, active: 0, running: 0, error: 0 },
+ },
+ vitestProcessCount: 0,
+ vitestLastAutoKillAt: null,
+ };
+}
+
+describe("SystemControlsArea layout integration", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ apiMock.mockImplementation((path: string) => Promise.resolve(emptyOverviewResponse(path)));
+ mockFetchSystemInfo.mockResolvedValue(systemInfoFixture());
+ mockFetchCurrentSystemRebuild.mockResolvedValue({ job: null });
+ mockFetchSystemStats.mockResolvedValue(systemStatsFixture());
+ mockFetchNodeSystemStats.mockResolvedValue(systemStatsFixture());
+ mockFetchGlobalSettings.mockResolvedValue({ vitestAutoKillEnabled: true, vitestKillThresholdPct: 90 });
+ mockFetchNodes.mockResolvedValue([]);
+ });
+
+ it("wraps System controls, Server logs, and Live system health in the shared gap owner", async () => {
+ render();
+
+ fireEvent.click(screen.getByTestId("command-center-tab-system"));
+
+ const systemTab = await screen.findByTestId("cc-system-tab");
+ const controls = await screen.findByTestId("cc-system-controls");
+ const logs = await screen.findByTestId("cc-system-logs");
+ const stats = await screen.findByTestId("cc-area-system");
+
+ expect(systemTab).toHaveClass("cc-system-tab");
+ expect(systemTab).toContainElement(controls);
+ expect(systemTab).toContainElement(logs);
+ expect(systemTab).toContainElement(stats);
+ expect(controls.parentElement).toBe(systemTab);
+ expect(logs.parentElement).toBe(systemTab);
+ expect(stats.parentElement).toBe(systemTab);
+ expect(screen.getByTestId("cc-system-logs-toggle")).toBeInTheDocument();
+ await waitFor(() => expect(mockFetchSystemInfo).toHaveBeenCalled());
+ });
+
+ it("keeps the System tab gap and mobile scroll-owner CSS contracts tokenized", () => {
+ const css = readFileSync(join(process.cwd(), "app/components/command-center/CommandCenter.css"), "utf8");
+
+ expect(css).toMatch(/\.cc-system-tab\s*{[^}]*display:\s*flex;[^}]*flex-direction:\s*column;[^}]*gap:\s*var\(--space-lg\);/s);
+ expect(css).toMatch(/@media\s*\(max-width:\s*768px\)\s*{[\s\S]*\.cc-tabpanel\s*{[^}]*padding-bottom:\s*calc\(var\(--space-lg\) \+ env\(safe-area-inset-bottom, 0\) \+ var\(--standalone-bottom-gap\)\);/);
+ expect(css).toMatch(/@media\s*\(max-width:\s*768px\)\s*{[\s\S]*\.cc-system-tab\s*{[^}]*gap:\s*var\(--space-lg\);/);
+ expect(css).not.toMatch(/\.cc-system-tab\s*{[^}]*overflow-y:\s*auto;/s);
+ });
+});
diff --git a/packages/dashboard/app/components/command-center/areas/SystemControlsArea.css b/packages/dashboard/app/components/command-center/areas/SystemControlsArea.css
index a464194ac3..dcda183d6a 100644
--- a/packages/dashboard/app/components/command-center/areas/SystemControlsArea.css
+++ b/packages/dashboard/app/components/command-center/areas/SystemControlsArea.css
@@ -71,7 +71,7 @@ never grows the page; the page must not scroll horizontally (pre wraps).
.cc-syscontrols-banner--error {
justify-content: space-between;
- border-color: var(--danger, #f87171);
+ border-color: var(--color-error);
}
.cc-syscontrols-job-status {
@@ -84,7 +84,7 @@ never grows the page; the page must not scroll horizontally (pre wraps).
}
.cc-syscontrols-job-status--failed {
- color: var(--danger, #f87171);
+ color: var(--color-error);
}
.cc-syscontrols-output {
@@ -115,11 +115,11 @@ never grows the page; the page must not scroll horizontally (pre wraps).
}
.cc-syscontrols-log-line--warn .cc-syscontrols-log-message {
- color: var(--warning, #facc15);
+ color: var(--color-warning);
}
.cc-syscontrols-log-line--error .cc-syscontrols-log-message {
- color: var(--danger, #f87171);
+ color: var(--color-error);
}
.cc-syscontrols-log-time {