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>
78 lines
3.3 KiB
TypeScript
78 lines
3.3 KiB
TypeScript
import type { BoardWorkflowDefinition, BoardWorkflowsPayload } from "../api";
|
|
|
|
const BOARD_WORKFLOWS_CACHE_PREFIX = "fusion:board-workflows:";
|
|
const DEFAULT_PROJECT_CACHE_KEY = "default";
|
|
|
|
function cacheKey(projectId?: string): string {
|
|
return `${BOARD_WORKFLOWS_CACHE_PREFIX}${projectId ?? DEFAULT_PROJECT_CACHE_KEY}`;
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function isBoardWorkflowsPayload(value: unknown): value is BoardWorkflowsPayload {
|
|
if (!isRecord(value)) return false;
|
|
if (typeof value.flagEnabled !== "boolean") return false;
|
|
if (!Array.isArray(value.workflows)) return false;
|
|
if (typeof value.defaultWorkflowId !== "string") return false;
|
|
if (!isRecord(value.taskWorkflowIds)) return false;
|
|
// FNXC:BoardWorkflows 2026-06-20-20:10:
|
|
// Validate each cached workflow's shape and the taskWorkflowIds value types,
|
|
// not just the container types. A malformed entry (e.g. `[{}]` from a stale or
|
|
// partially-written cache) would otherwise pass and later throw in Board/ListView
|
|
// when accessing workflow.name/columns — re-introducing the legacy flash this
|
|
// cache exists to prevent. Mirrors the BoardWorkflowDefinition contract (id, name,
|
|
// columns) and the Record<string,string> taskWorkflowIds map.
|
|
if (!value.workflows.every((workflow): workflow is BoardWorkflowDefinition => (
|
|
isRecord(workflow)
|
|
&& typeof workflow.id === "string"
|
|
&& typeof workflow.name === "string"
|
|
&& Array.isArray(workflow.columns)
|
|
))) return false;
|
|
if (!Object.values(value.taskWorkflowIds).every((workflowId) => typeof workflowId === "string")) return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* FNXC:BoardWorkflows 2026-06-20-08:50:
|
|
* Cache the last successful board-workflows payload per project in sessionStorage so Board and ListView can render the correct workflow-lane layout immediately on remount and never flash the legacy single-lane board before the async revalidation finishes.
|
|
*/
|
|
export function readBoardWorkflowsCache(projectId?: string): BoardWorkflowsPayload | null {
|
|
if (typeof window === "undefined") return null;
|
|
|
|
try {
|
|
const raw = window.sessionStorage.getItem(cacheKey(projectId));
|
|
if (!raw) return null;
|
|
|
|
const parsed = JSON.parse(raw) as unknown;
|
|
return isBoardWorkflowsPayload(parsed) ? parsed : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function writeBoardWorkflowsCache(projectId: string | undefined, payload: BoardWorkflowsPayload): void {
|
|
if (typeof window === "undefined") return;
|
|
|
|
try {
|
|
window.sessionStorage.setItem(cacheKey(projectId), JSON.stringify(payload));
|
|
} catch {
|
|
// Private-mode/quota failures should never prevent board rendering.
|
|
}
|
|
}
|
|
|
|
export function clearBoardWorkflowsCache(projectId?: string): void {
|
|
if (typeof window === "undefined") return;
|
|
|
|
try {
|
|
/*
|
|
FNXC:ChatWorkflowAuthoring 2026-07-01-10:55:
|
|
Chat workflow tools create/update/delete definitions outside mounted workflow UI components. Clear only the matching project's board-workflows snapshot before forced refetch so a chat-created workflow cannot be hidden by a pre-mutation session cache or leak into another project.
|
|
*/
|
|
window.sessionStorage.removeItem(cacheKey(projectId));
|
|
} catch {
|
|
// Private-mode/quota failures should never prevent board rendering.
|
|
}
|
|
}
|