Chat-authored workflow changes now propagate immediately to workflow selectors and editors. - Emit workflow lifecycle SSE events when chat, planner, or room workflow tools create, update, select, configure, or delete workflows. - Force-refresh board workflow caches and the workflow editor list when workflow lifecycle events arrive. - Add cross-surface tests for chat workflow creation visibility and room/chat workflow tool event behavior. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7383-chat-workflow-authoring.md | 7 +++ .../workflow-selection-cross-surface.test.tsx | 23 +++++++++ packages/dashboard/app/api/legacy.ts | 9 ++-- .../app/components/WorkflowNodeEditor.tsx | 23 ++++++++- .../app/hooks/__tests__/useBoardWorkflows.test.ts | 27 +++++++--- packages/dashboard/app/hooks/useBoardWorkflows.ts | 22 +++++--- .../utils/__tests__/boardWorkflowsCache.test.ts | 12 ++++- .../dashboard/app/utils/boardWorkflowsCache.ts | 14 +++++ .../dashboard/src/__tests__/chat-manager.test.ts | 44 ++++++++++++++-- .../dashboard/src/__tests__/chat.rooms.test.ts | 44 ++++++++++++++++ packages/dashboard/src/chat.ts | 59 +++++++++++++++++++--- 11 files changed, 255 insertions(+), 29 deletions(-) Fusion-Task-Id: FN-7383 Fusion-Task-Lineage: ebe4c982-214b-402f-b829-a58ace19ffe0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import type { BoardWorkflowsPayload } from "../../api";
|
|
import { clearBoardWorkflowsCache, 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("clears only the requested project cache after out-of-component workflow mutations", () => {
|
|
writeBoardWorkflowsCache("project-a", payload);
|
|
writeBoardWorkflowsCache("project-b", { ...payload, defaultWorkflowId: "wf-b" });
|
|
|
|
clearBoardWorkflowsCache("project-a");
|
|
|
|
expect(readBoardWorkflowsCache("project-a")).toBeNull();
|
|
expect(readBoardWorkflowsCache("project-b")).toEqual({ ...payload, defaultWorkflowId: "wf-b" });
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|