From 4366a933da6acd00837c00ea4d2da1d501ac02d9 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 9 Jun 2026 02:09:31 -0700 Subject: [PATCH] FN-6046: add collapsible workflow selectors Add collapsible workflow selectors for board workflow controls and inline task creation. - add a collapsible board workflow toolbar with persisted per-project state - make the inline create workflow selector collapsible and store its expanded state per project - update workflow selector styling, storage key allowlist, and regression coverage for collapse behavior Files changed: packages/dashboard/app/components/Board.tsx | 104 +++++++++++++-------- .../dashboard/app/components/InlineCreateCard.css | 9 ++ .../dashboard/app/components/InlineCreateCard.tsx | 3 + packages/dashboard/app/components/Lane.css | 23 +++++ .../dashboard/app/components/WorkflowSelector.css | 20 +++- .../dashboard/app/components/WorkflowSelector.tsx | 81 ++++++++++++++-- .../app/components/__tests__/Board.test.tsx | 101 ++++++++++++++++++-- .../components/__tests__/InlineCreateCard.test.tsx | 57 ++++++++++- .../components/__tests__/WorkflowSelector.test.tsx | 73 ++++++++++++++- .../app/utils/__tests__/projectStorage.test.ts | 4 +- packages/dashboard/app/utils/projectStorage.ts | 2 + 11 files changed, 420 insertions(+), 57 deletions(-) Fusion-Task-Id: FN-6046 Fusion-Task-Lineage: 07cbe7a8-5740-479d-94a3-b178c72a5273 --- packages/dashboard/app/components/Board.tsx | 104 +++++++++++------- .../app/components/InlineCreateCard.css | 9 ++ .../app/components/InlineCreateCard.tsx | 3 + packages/dashboard/app/components/Lane.css | 23 ++++ .../app/components/WorkflowSelector.css | 20 +++- .../app/components/WorkflowSelector.tsx | 81 ++++++++++++-- .../app/components/__tests__/Board.test.tsx | 101 ++++++++++++++++- .../__tests__/InlineCreateCard.test.tsx | 57 +++++++++- .../__tests__/WorkflowSelector.test.tsx | 73 +++++++++++- .../utils/__tests__/projectStorage.test.ts | 4 +- .../dashboard/app/utils/projectStorage.ts | 2 + 11 files changed, 420 insertions(+), 57 deletions(-) diff --git a/packages/dashboard/app/components/Board.tsx b/packages/dashboard/app/components/Board.tsx index cf7848455b..4f0822c737 100644 --- a/packages/dashboard/app/components/Board.tsx +++ b/packages/dashboard/app/components/Board.tsx @@ -5,11 +5,12 @@ import { Column } from "./Column"; import "./Lane.css"; import type { ToastType } from "../hooks/useToast"; import { useState, useMemo, useEffect, useCallback, useRef } from "react"; -import { Pencil, Plus } from "lucide-react"; +import { ChevronDown, ChevronRight, Pencil, Plus } from "lucide-react"; import { fetchWorkflowSteps, fetchBoardWorkflows, promoteTask, type ModelInfo, type BoardWorkflowDefinition, type BoardWorkflowsPayload } from "../api"; import { useBlockerFanout } from "../hooks/useBlockerFanout"; import { MOBILE_MEDIA_QUERY } from "../hooks/useViewportMode"; import { recordResumeEvent } from "../utils/resumeInstrumentation"; +import { getScopedItem, setScopedItem } from "../utils/projectStorage"; import { subscribeSse } from "../sse-bus"; import { getBoardCanDropTaskRejection } from "./boardCanDropTask"; @@ -92,6 +93,7 @@ function areWorkflowNameLookupsEqual(previous: ReadonlyMap, next export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onPauseTask, onOpenDetail, onOpenGroupModal, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onRetryTask, onArchiveTask, onUnarchiveTask, onDeleteTask, onArchiveAllDone, onLoadArchivedTasks, searchQuery = "", availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, taskStuckTimeoutMs, onOpenMission, staleHighFanoutBlockerAgeThresholdMs, lastFetchTimeMs, prAuthAvailable, onOpenWorkflowEditor, onCreateWorkflow }: BoardProps) { const [archivedCollapsed, setArchivedCollapsed] = useState(true); + const [workflowToolbarCollapsed, setWorkflowToolbarCollapsed] = useState(() => getScopedItem("kb-dashboard-board-workflow-collapsed", projectId) === "1"); const archivedLoadedRef = useRef(false); const [workflowStepNameLookup, setWorkflowStepNameLookup] = useState>(EMPTY_WORKFLOW_STEP_NAME_LOOKUP); const boardRef = useRef(null); @@ -109,6 +111,12 @@ export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onPauseTask archived: [], }); + useEffect(() => { + if (typeof window !== "undefined") { + setScopedItem("kb-dashboard-board-workflow-collapsed", workflowToolbarCollapsed ? "1" : "0", projectId); + } + }, [workflowToolbarCollapsed, projectId]); + useEffect(() => { recordResumeEvent({ view: "Board", @@ -461,45 +469,61 @@ export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onPauseTask return (
{(workflowOptions.length > 1 || onCreateWorkflow || onOpenWorkflowEditor) && ( -
- {workflowOptions.length > 1 && ( - - )} - {onOpenWorkflowEditor && ( - - )} - {onCreateWorkflow && ( - +
+ + {workflowToolbarCollapsed ? ( + Workflow + ) : ( + <> + {workflowOptions.length > 1 && ( + + )} + {onOpenWorkflowEditor && ( + + )} + {onCreateWorkflow && ( + + )} + )}
)} diff --git a/packages/dashboard/app/components/InlineCreateCard.css b/packages/dashboard/app/components/InlineCreateCard.css index 4a64e3695b..505d845d34 100644 --- a/packages/dashboard/app/components/InlineCreateCard.css +++ b/packages/dashboard/app/components/InlineCreateCard.css @@ -141,6 +141,11 @@ min-width: 0; } +.inline-create-controls .workflow-selector { + flex: 0 1 auto; + min-width: 0; +} + .inline-create-actions { display: flex; align-items: center; @@ -288,6 +293,10 @@ gap: var(--space-xs); } + .inline-create-controls .workflow-selector { + width: 100%; + } + .inline-create-controls .btn { min-height: 36px; font-size: 12px; diff --git a/packages/dashboard/app/components/InlineCreateCard.tsx b/packages/dashboard/app/components/InlineCreateCard.tsx index c37923066c..d6365c6e3a 100644 --- a/packages/dashboard/app/components/InlineCreateCard.tsx +++ b/packages/dashboard/app/components/InlineCreateCard.tsx @@ -18,6 +18,7 @@ import { WorkflowSelector } from "./WorkflowSelector"; const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]; const STORAGE_KEY = "kb-inline-create-text"; +const WORKFLOW_SELECTOR_COLLAPSE_STORAGE_KEY = "kb-board-workflow-selector-collapsed"; interface PendingImage { file: File; @@ -1036,6 +1037,8 @@ export function InlineCreateCard({ addToast={addToast} label="Workflow" disabled={submitting} + collapsible + collapseStorageKey={WORKFLOW_SELECTOR_COLLAPSE_STORAGE_KEY} />