Persist workflow choices across dashboard board surfaces so operators return to their selected lane reliably. - Add project-scoped durable storage helpers for board workflow selection with stale-id validation and cleanup. - Share the workflow selection hook across Board, List, Header, and Graph surfaces while preserving cached payloads on transient refresh failures. - Update List quick-create/planning handoffs, cross-surface tests, and dashboard documentation for durable workflow selection behavior. - Add patch changesets for the published Fusion package. Files changed: .changeset/fn-7234-durable-board-workflow-selection.md | 7 ++ .changeset/workflow-selection-board-list.md | 7 ++ docs/dashboard-guide.md | 5 +- .../workflow-selection-cross-surface.test.tsx | 66 ++++++++-- packages/dashboard/app/components/ListView.tsx | 111 ++++------------- .../app/components/__tests__/Board.test.tsx | 45 +++++++ .../__tests__/GraphWorkflowSwitcherSlot.test.tsx | 57 +++++++++ .../__tests__/HeaderWorkflowSwitcherSlot.test.tsx | 42 ++++++- .../app/components/__tests__/ListView.test.tsx | 122 +++++++++++++++++++ .../app/hooks/__tests__/useBoardWorkflows.test.ts | 29 +++++ packages/dashboard/app/hooks/useBoardWorkflows.ts | 64 ++++++++- .../utils/__tests__/boardWorkflowSelection.test.ts | 135 +++++++++++++++++++++ .../app/utils/__tests__/projectStorage.test.ts | 3 +- .../dashboard/app/utils/boardWorkflowSelection.ts | 49 ++++++++ packages/dashboard/app/utils/projectStorage.ts | 1 + 15 files changed, 633 insertions(+), 110 deletions(-) Fusion-Task-Id: FN-7234 Fusion-Task-Lineage: 6ebc000c-19c3-4a8e-9303-4e18a59902ed Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
export const GLOBAL_STORAGE_KEYS: string[] = [
|
|
"kb-dashboard-theme-mode",
|
|
"kb-dashboard-color-theme",
|
|
"kb-dashboard-view-mode",
|
|
"kb-dashboard-current-project",
|
|
"kb-dashboard-recent-projects",
|
|
"fn-agent-log-markdown",
|
|
"fn-agent-log-tool-output",
|
|
];
|
|
|
|
export const PROJECT_STORAGE_KEYS: string[] = [
|
|
"kb-dashboard-task-view",
|
|
"kb-dashboard-list-columns",
|
|
"kb-dashboard-hide-done",
|
|
"kb-dashboard-list-collapsed",
|
|
"kb-dashboard-selected-tasks",
|
|
"kb-dashboard-list-selected-task",
|
|
"kb-dashboard-list-sidebar-width",
|
|
"kb-dashboard-mailbox-sidebar-width",
|
|
"kb-dashboard-agents-sidebar-width",
|
|
"kb-dashboard-github-import-list-width",
|
|
"kb-quick-entry-text",
|
|
"kb-inline-create-text",
|
|
"fn-agent-view",
|
|
"kb-terminal-tabs",
|
|
"kb-planning-last-description",
|
|
"kb-subtask-last-description",
|
|
"kb-mission-last-goal",
|
|
"kb-usage-view-mode",
|
|
"kb-usage-hidden-windows",
|
|
"kb-usage-modal-size",
|
|
"kb-usage-provider-order",
|
|
"kb-chat-active-session",
|
|
"kb-dashboard-working-branch-filter",
|
|
"kb-dashboard-base-branch-filter",
|
|
"kb-capacity-risk-banner-dismissed",
|
|
"kb-files-line-numbers",
|
|
"kb-dashboard-dock-files-current",
|
|
"kb-dashboard-board-workflow-selection",
|
|
"fusion-plugin-dependency-graph:positions",
|
|
];
|
|
|
|
export function scopedKey(baseKey: string, projectId?: string): string {
|
|
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
return baseKey;
|
|
}
|
|
|
|
return `kb:${projectId}:${baseKey}`;
|
|
}
|
|
|
|
export function getScopedItem(baseKey: string, projectId?: string): string | null {
|
|
if (typeof window === "undefined") {
|
|
return null;
|
|
}
|
|
|
|
const getItem = window.localStorage?.getItem;
|
|
if (typeof getItem !== "function") {
|
|
return null;
|
|
}
|
|
|
|
return getItem.call(window.localStorage, scopedKey(baseKey, projectId));
|
|
}
|
|
|
|
export function setScopedItem(baseKey: string, value: string, projectId?: string): void {
|
|
if (typeof window === "undefined") {
|
|
return;
|
|
}
|
|
|
|
const setItem = window.localStorage?.setItem;
|
|
if (typeof setItem !== "function") {
|
|
return;
|
|
}
|
|
|
|
setItem.call(window.localStorage, scopedKey(baseKey, projectId), value);
|
|
}
|
|
|
|
export function removeScopedItem(baseKey: string, projectId?: string): void {
|
|
if (typeof window === "undefined") {
|
|
return;
|
|
}
|
|
|
|
const removeItem = window.localStorage?.removeItem;
|
|
if (typeof removeItem !== "function") {
|
|
return;
|
|
}
|
|
|
|
removeItem.call(window.localStorage, scopedKey(baseKey, projectId));
|
|
}
|