Extend the dashboard aggregate workflow view across top-level workflow selectors without leaking its sentinel into task creation. - Add All workflows as an aggregate option for List, Header, Planning/Missions, and Graph selectors.\n- Preserve real workflow handoffs for quick create, planning, missions, and workflow editor entry points.\n- Include aggregate workflow counts and cross-surface regression coverage, plus docs and a changeset.\n\nFiles changed:\n .changeset/fn-7357-all-workflows-selectors.md | 7 +++\n docs/dashboard-guide.md | 9 ++--\n .../workflow-selection-cross-surface.test.tsx | 20 ++++---\n .../app/components/GraphWorkflowSwitcherSlot.tsx | 12 +++--\n .../app/components/HeaderWorkflowSwitcherSlot.tsx | 10 ++--\n packages/dashboard/app/components/ListView.tsx | 63 +++++++++++++++++-----\n .../components/PlanningWorkflowSwitcherSlot.tsx | 5 +-\n .../__tests__/GraphWorkflowSwitcherSlot.test.tsx | 34 ++++++++++++\n .../__tests__/HeaderWorkflowSwitcherSlot.test.tsx | 27 ++++++++++\n .../app/components/__tests__/ListView.test.tsx | 41 +++++++++++++-\n .../app/components/dashboard/MainContent.tsx | 17 ++++--\n .../app/components/workflowStatusCounts.ts | 5 ++\n packages/dashboard/app/hooks/useBoardWorkflows.ts | 20 +++++--\n .../dashboard/app/utils/boardWorkflowSelection.ts | 5 +-\n 14 files changed, 233 insertions(+), 42 deletions(-) Fusion-Task-Id: FN-7357 Fusion-Task-Lineage: b8658cad-7b12-4a06-b627-e2bde7de6951 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
62 lines
2.9 KiB
TypeScript
62 lines
2.9 KiB
TypeScript
import { getScopedItem, removeScopedItem, setScopedItem } from "./projectStorage";
|
|
|
|
export const BOARD_WORKFLOW_SELECTION_STORAGE_KEY = "kb-dashboard-board-workflow-selection";
|
|
export const ALL_WORKFLOWS_BOARD_VIEW_ID = "__all_workflows__";
|
|
|
|
function isValidWorkflowSelection(value: unknown): value is string {
|
|
if (typeof value !== "string") return false;
|
|
|
|
const trimmed = value.trim();
|
|
if (trimmed.length === 0) return false;
|
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return false;
|
|
if (/\p{C}/u.test(trimmed)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* FNXC:BoardWorkflowSelection 2026-06-29-12:00:
|
|
* Persist the last selected Board workflow view in project-scoped localStorage so Board, Header, Graph, and List selectors can restore the operator's workflow after board remounts, task state changes, respecification returns, and browser/server restarts. Storage is best-effort because private-mode, SSR, missing APIs, and quota failures must never block board rendering.
|
|
*
|
|
* FNXC:BoardWorkflowSelection 2026-06-30-00:00:
|
|
* The durable Board view preference may be either a real workflow id or the Board-only All workflows sentinel so a refresh can restore the aggregate board. Shared real-workflow consumers must call `readBoardWorkflowSelection`; it filters the sentinel out so task creation, workflow APIs, and other backend handoffs never receive `__all_workflows__` as a real workflow id.
|
|
*
|
|
* FNXC:WorkflowAggregation 2026-07-01-00:00:
|
|
* Top-level dashboard selectors (Board/List/Planning/Missions/Graph) may display the aggregate sentinel as view state, but any task-creating or workflow-editing path must translate it to a real workflow id or `null` default behavior before crossing component/API boundaries.
|
|
*/
|
|
export function readBoardWorkflowViewSelection(projectId?: string): string | null {
|
|
try {
|
|
const stored = getScopedItem(BOARD_WORKFLOW_SELECTION_STORAGE_KEY, projectId);
|
|
return isValidWorkflowSelection(stored) ? stored.trim() : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function readBoardWorkflowSelection(projectId?: string): string | null {
|
|
const selection = readBoardWorkflowViewSelection(projectId);
|
|
return selection === ALL_WORKFLOWS_BOARD_VIEW_ID ? null : selection;
|
|
}
|
|
|
|
export function writeBoardWorkflowSelection(projectId: string | undefined, workflowId: string): void {
|
|
try {
|
|
const trimmed = workflowId.trim();
|
|
if (!isValidWorkflowSelection(trimmed)) {
|
|
removeScopedItem(BOARD_WORKFLOW_SELECTION_STORAGE_KEY, projectId);
|
|
return;
|
|
}
|
|
|
|
setScopedItem(BOARD_WORKFLOW_SELECTION_STORAGE_KEY, trimmed, projectId);
|
|
} catch {
|
|
// Best-effort preference persistence; board rendering must continue on storage failures.
|
|
}
|
|
}
|
|
|
|
export function removeBoardWorkflowSelection(projectId?: string): void {
|
|
try {
|
|
removeScopedItem(BOARD_WORKFLOW_SELECTION_STORAGE_KEY, projectId);
|
|
} catch {
|
|
// Best-effort preference cleanup; storage failures are non-fatal.
|
|
}
|
|
}
|