Files
fusion/packages/dashboard/app/utils/boardWorkflowsCache.ts
gsxdsm 869974cd5a FN-7383: refresh workflows after chat authoring
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>
2026-07-01 11:12:59 -07:00

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.
}
}