Prevent workflow-enabled board surfaces from flashing the legacy layout during first paint. - Hydrate Board and ListView workflow metadata from a per-project session cache before async revalidation. - Gate legacy board/list rendering behind settings and workflow-lane metadata readiness, with neutral skeleton states for cold or empty workflow loads. - Add regression coverage for no-legacy-flash behavior and cache validation, plus docs and changeset notes. Files changed: .changeset/fn-6776-board-flash.md | 5 + docs/dashboard-guide.md | 1 + packages/dashboard/app/App.tsx | 4 + packages/dashboard/app/components/Board.css | 69 +++++ packages/dashboard/app/components/Board.tsx | 53 +++- packages/dashboard/app/components/ListView.css | 53 ++++ packages/dashboard/app/components/ListView.tsx | 52 +++- .../__tests__/board-no-legacy-flash.test.tsx | 326 +++++++++++++++++++++ .../utils/__tests__/boardWorkflowsCache.test.ts | 71 +++++ .../dashboard/app/utils/boardWorkflowsCache.ts | 49 ++++ 10 files changed, 676 insertions(+), 7 deletions(-) Fusion-Task-Id: FN-6776 Fusion-Task-Lineage: caa8e53f-9b6d-49a6-94ca-b694550b43ef
72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import type { BoardWorkflowsPayload } from "../../api";
|
|
import { readBoardWorkflowsCache, writeBoardWorkflowsCache } from "../boardWorkflowsCache";
|
|
|
|
const payload: BoardWorkflowsPayload = {
|
|
flagEnabled: true,
|
|
defaultWorkflowId: "builtin:coding",
|
|
workflows: [
|
|
{
|
|
id: "builtin:coding",
|
|
name: "Coding",
|
|
columns: [{ id: "todo", name: "Todo", flags: {} }],
|
|
},
|
|
],
|
|
taskWorkflowIds: { "FN-1": "builtin:coding" },
|
|
};
|
|
|
|
describe("boardWorkflowsCache", () => {
|
|
afterEach(() => {
|
|
if (typeof window !== "undefined") {
|
|
window.sessionStorage.clear();
|
|
}
|
|
vi.unstubAllGlobals();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("round-trips board-workflows payloads per project", () => {
|
|
writeBoardWorkflowsCache("project-a", payload);
|
|
|
|
expect(readBoardWorkflowsCache("project-a")).toEqual(payload);
|
|
});
|
|
|
|
it("keeps project cache keys isolated", () => {
|
|
writeBoardWorkflowsCache("project-a", payload);
|
|
|
|
expect(readBoardWorkflowsCache("project-b")).toBeNull();
|
|
});
|
|
|
|
it("returns null for missing, corrupt, or invalid entries", () => {
|
|
expect(readBoardWorkflowsCache("missing")).toBeNull();
|
|
|
|
window.sessionStorage.setItem("fusion:board-workflows:corrupt", "{");
|
|
expect(readBoardWorkflowsCache("corrupt")).toBeNull();
|
|
|
|
window.sessionStorage.setItem("fusion:board-workflows:invalid", JSON.stringify({ flagEnabled: true, workflows: {} }));
|
|
expect(readBoardWorkflowsCache("invalid")).toBeNull();
|
|
});
|
|
|
|
it("swallows sessionStorage write failures", () => {
|
|
vi.spyOn(window.sessionStorage.__proto__, "setItem").mockImplementation(() => {
|
|
throw new Error("quota");
|
|
});
|
|
|
|
expect(() => writeBoardWorkflowsCache("project-a", payload)).not.toThrow();
|
|
});
|
|
|
|
it("swallows sessionStorage read failures", () => {
|
|
vi.spyOn(window.sessionStorage.__proto__, "getItem").mockImplementation(() => {
|
|
throw new Error("private mode");
|
|
});
|
|
|
|
expect(readBoardWorkflowsCache("project-a")).toBeNull();
|
|
});
|
|
|
|
it("returns null without window for SSR callers", () => {
|
|
vi.stubGlobal("window", undefined);
|
|
|
|
expect(readBoardWorkflowsCache("project-a")).toBeNull();
|
|
expect(() => writeBoardWorkflowsCache("project-a", payload)).not.toThrow();
|
|
});
|
|
});
|