diff --git a/.changeset/fn-6690-lazy-view-css.md b/.changeset/fn-6690-lazy-view-css.md
new file mode 100644
index 0000000000..8e4d3f8a44
--- /dev/null
+++ b/.changeset/fn-6690-lazy-view-css.md
@@ -0,0 +1,5 @@
+---
+"@runfusion/fusion": patch
+---
+
+Fix served dashboard lazy-view preloads so persisted Command Center and other lazy views include their extracted CSS chunks.
diff --git a/packages/dashboard/app/components/__tests__/board-mobile-initial-render.test.tsx b/packages/dashboard/app/components/__tests__/board-mobile-initial-render.test.tsx
index e79f87b9c4..68979ac333 100644
--- a/packages/dashboard/app/components/__tests__/board-mobile-initial-render.test.tsx
+++ b/packages/dashboard/app/components/__tests__/board-mobile-initial-render.test.tsx
@@ -74,6 +74,12 @@ function extractRule(content: string, selector: string): string {
return content.match(new RegExp(`${escapedSelector}\\s*\\{[^}]*\\}`))?.[0] ?? "";
}
+function expectLogicalOrPhysicalMinSize(rule: string, axis: "block" | "inline"): void {
+ const logicalProp = axis === "block" ? "min-block-size" : "min-inline-size";
+ const physicalProp = axis === "block" ? "min-height" : "min-width";
+ expect(rule).toSatisfy((value: string) => value.includes(`${logicalProp}: 0`) || value.includes(`${physicalProp}: 0`));
+}
+
const workflowPayload = {
flagEnabled: true,
defaultWorkflowId: "builtin:coding",
@@ -272,8 +278,12 @@ describe("Board mobile initial render stabilization (FN-4574)", () => {
const projectContentRule = extractRule(cssContent, ".project-content");
expect(projectContentRule).toContain("display: flex");
- expect(projectContentRule).toContain("min-height: 0");
- expect(projectContentRule).toContain("min-width: 0");
+ /*
+ * FNXC:BoardMobileCss 2026-06-19-03:16:
+ * The fill-height invariant accepts logical min-size properties because styles.css canonicalizes .project-content to writing-mode-safe min-block-size/min-inline-size declarations.
+ */
+ expectLogicalOrPhysicalMinSize(projectContentRule, "block");
+ expectLogicalOrPhysicalMinSize(projectContentRule, "inline");
expect(baseBoardRule).toContain("box-sizing: border-box");
expect(baseBoardRule).toContain("flex: 1 1 auto");
@@ -324,7 +334,7 @@ describe("Board mobile initial render stabilization (FN-4574)", () => {
expect(mobileProjectContentRule).toContain("display: flex");
expect(mobileProjectContentRule).toContain("align-items: stretch");
expect(mobileProjectContentRule).toContain("width: 100%");
- expect(mobileProjectContentRule).toContain("min-height: 0");
+ expectLogicalOrPhysicalMinSize(mobileProjectContentRule, "block");
expect(mobileProjectContentRule).toContain("overflow: hidden");
expect(mobileWorkflowViewRule).toContain("display: flex");
diff --git a/packages/dashboard/src/__tests__/server-view-preload.test.ts b/packages/dashboard/src/__tests__/server-view-preload.test.ts
index 091828f618..3aabc4a432 100644
--- a/packages/dashboard/src/__tests__/server-view-preload.test.ts
+++ b/packages/dashboard/src/__tests__/server-view-preload.test.ts
@@ -1,11 +1,13 @@
// @vitest-environment node
-import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
-import { afterEach, describe, expect } from "vitest";
+import vm from "node:vm";
+import { JSDOM } from "jsdom";
+import { afterEach, describe, expect, it } from "vitest";
import { TaskStore } from "@fusion/core";
-import { createServer } from "../server.js";
+import { buildViewPreloadInjection, createServer } from "../server.js";
import { createLoopbackIntegrationTest } from "./loopback-integration-test.js";
const serverViewPreloadIntegrationTest = await createLoopbackIntegrationTest("server-view-preload integration");
@@ -18,6 +20,39 @@ function makeTempDir(prefix: string): string {
return dir;
}
+type AppendedLink = {
+ rel?: string;
+ href?: string;
+ crossOrigin?: string;
+};
+
+function runPreloadBootstrap(injection: string, taskView: string, projectId?: string): AppendedLink[] {
+ const script = injection.match(/^`;
+}
+
function parseVersion(version: string): number[] {
return version
.split(".")
@@ -792,11 +801,6 @@ export function createServer(store: TaskStore, options?: ServerOptions): ReturnT
let cachedIndexMtimeMs: number | null = null;
let cachedTemplatedIndexHtml: string | null = null;
- const buildViewPreloadInjection = (chunkMap: Record): string => {
- const serializedChunkMap = JSON.stringify(chunkMap).replace(/<\//g, "<\\/");
- return ``;
- };
-
const renderIndexHtml = (): string => {
const resolvedClientDir = process.env.FUSION_CLIENT_DIR
? process.env.FUSION_CLIENT_DIR
diff --git a/packages/dashboard/src/view-chunk-manifest.ts b/packages/dashboard/src/view-chunk-manifest.ts
index a79b2a08bb..7be00c32fe 100644
--- a/packages/dashboard/src/view-chunk-manifest.ts
+++ b/packages/dashboard/src/view-chunk-manifest.ts
@@ -5,10 +5,20 @@ type TaskViewId = string;
type ManifestEntry = {
file?: string;
+ css?: string[];
+};
+
+export type ViewChunkManifestEntry = {
+ file: string;
+ css: string[];
};
type ViteManifest = Record;
+/*
+FNXC:CommandCenterStyling 2026-06-19-09:42:
+Persisted lazy views need the served index bootstrap to know both the JavaScript chunk and any Vite-emitted CSS chunks. Command Center's co-located styles are split into a dynamic CSS asset, so omitting either the view id or the css array from this manifest map can render the served first load unstyled even though in-app navigation still works through Vite's runtime preload helper.
+*/
// Canonical taskView ids that map to lazy React views in App.tsx.
// Intentionally excluded:
// - nodes: opened by overlay state, not taskView routing
@@ -24,13 +34,14 @@ export const VIEW_SOURCE_MAP: Record = {
memory: "components/MemoryView.tsx",
insights: "components/InsightsView.tsx",
reliability: "components/ReliabilityView.tsx",
+ "command-center": "components/command-center/CommandCenter.tsx",
"dev-server": "components/DevServerView.tsx",
goalsView: "components/GoalsView.tsx",
"stash-recovery": "components/StashRecoveryView.tsx",
};
type ManifestCacheEntry = {
- entries: Record;
+ entries: Record;
mtimeMs: number | null;
};
@@ -46,7 +57,7 @@ function warnOnce(set: Set, key: string, message: string): void {
console.warn(message);
}
-export function loadViewChunkManifest(clientDir: string): Record {
+export function loadViewChunkManifest(clientDir: string): Record {
const cacheKey = resolve(clientDir);
const manifestPath = join(cacheKey, ".vite", "manifest.json");
@@ -67,14 +78,14 @@ export function loadViewChunkManifest(clientDir: string): Record = {};
+ const empty: Record = {};
manifestCache.set(cacheKey, { entries: empty, mtimeMs });
return empty;
}
try {
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as ViteManifest;
- const resolvedEntries: Record = {};
+ const resolvedEntries: Record = {};
for (const [viewId, sourcePath] of Object.entries(VIEW_SOURCE_MAP)) {
const entry = manifest[sourcePath];
@@ -86,14 +97,17 @@ export function loadViewChunkManifest(clientDir: string): Record `/${asset}`),
+ };
}
manifestCache.set(cacheKey, { entries: resolvedEntries, mtimeMs });
return resolvedEntries;
} catch {
warnOnce(warnedMissingManifest, `${cacheKey}:parse`, `[dashboard] Failed to parse view chunk manifest: ${manifestPath}`);
- const empty: Record = {};
+ const empty: Record = {};
manifestCache.set(cacheKey, { entries: empty, mtimeMs });
return empty;
}
diff --git a/packages/dashboard/vitest.config.ts b/packages/dashboard/vitest.config.ts
index f3f8c7ec73..f6654551bf 100644
--- a/packages/dashboard/vitest.config.ts
+++ b/packages/dashboard/vitest.config.ts
@@ -265,8 +265,12 @@ Keep QuickEntryBox out of this list so focus-restoration coverage remains active
FNXC:DashboardTestQuarantine 2026-06-18-09:07:
FN-6642 rescued chat-routes by fixing the shared engine mock to return an iterable chat-task-document tool list during broad API lanes.
Keep chat-routes out of this list so SSE lifecycle coverage remains active and the ledger/config stay in lockstep.
+
+FNXC:DashboardTestQuarantine 2026-06-19-03:22:
+FN-6690 workspace verification observed session-cross-tab fail only during the broad dashboard API backfill shard with temp-directory cleanup ENOTEMPTY, then pass on isolated rerun.
+Quarantine the cleanup-flaky file under the deletion ratchet rather than changing timing or session-locking behavior outside the lazy-view CSS chunk scope.
*/
-const quarantinedDashboardTests: string[] = [];
+const quarantinedDashboardTests: string[] = ["src/__tests__/session-cross-tab.test.ts"];
const qualityApiTests = [
// Critical HTTP/server behavior: auth, task/project/settings mutation,
diff --git a/scripts/lib/test-quarantine.json b/scripts/lib/test-quarantine.json
index d8744f3523..fa7167159a 100644
--- a/scripts/lib/test-quarantine.json
+++ b/scripts/lib/test-quarantine.json
@@ -1,5 +1,10 @@
{
"$comment": "Flaky-test quarantine ledger (deletion ratchet — see AGENTS.md 'Flaky tests: quarantine on sight' and docs/testing.md 'Quarantine ledger and the deletion ratchet'). A test observed failing without a corresponding real bug is quarantined ON SIGHT: add an entry here AND a matching one-line `exclude` entry in that package's vitest config, in the same commit. Every entry needs a non-empty `reason` (link the failing run) and a `quarantinedAt` date — the entry expires 14 days later, at which point the test file is DELETED unless someone rescues it with evidence it catches real regressions plus a root-cause fix (never appeasement). There is deliberately no loader module and no automation around this file: it is a dated record, the vitest config exclude is the mechanism, and the sweep is policy executed by whoever touches the suite.",
"entries": [
+ {
+ "file": "packages/dashboard/src/__tests__/session-cross-tab.test.ts",
+ "reason": "FN-6690 local workspace `pnpm test` observed ENOTEMPTY while removing the test's temp .fusion directory in dashboard-api-quality-backfill shard; isolated rerun passed, indicating cleanup flake rather than a lazy-view CSS regression.",
+ "quarantinedAt": "2026-06-19"
+ }
]
}