Files
fusion/packages/dashboard/app/utils/boardWorkflowSelection.ts
gsxdsm b3b01bc25d FN-7357: add all workflows to top-level selectors
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>
2026-07-01 07:33:56 -07:00

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