From e171b6a0491d8fdac5ea592e6e778225d9fd7cf4 Mon Sep 17 00:00:00 2001 From: Shirone Date: Sun, 11 Jan 2026 12:03:52 +0100 Subject: [PATCH 1/4] feat: add empty state card component and integrate AI suggestion functionality - Introduced the EmptyStateCard component to display contextual guidance when columns are empty. - Enhanced the KanbanBoard and BoardView components to utilize the new EmptyStateCard for improved user experience. - Added AI suggestion functionality to the empty state configuration, allowing users to generate ideas directly from the backlog column. - Updated constants to define empty state configurations for various column types. --- apps/ui/src/components/views/board-view.tsx | 2 + .../components/empty-state-card.tsx | 217 ++++++++++++++++++ .../views/board-view/components/index.ts | 1 + .../components/views/board-view/constants.ts | 87 +++++++ .../views/board-view/kanban-board.tsx | 31 ++- 5 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 apps/ui/src/components/views/board-view/components/empty-state-card.tsx diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 2b1e3591..e325d165 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -1287,6 +1287,8 @@ export function BoardView() { selectedFeatureIds={selectedFeatureIds} onToggleFeatureSelection={toggleFeatureSelection} onToggleSelectionMode={toggleSelectionMode} + isDragging={activeFeature !== null} + onAiSuggest={() => setShowPlanDialog(true)} /> diff --git a/apps/ui/src/components/views/board-view/components/empty-state-card.tsx b/apps/ui/src/components/views/board-view/components/empty-state-card.tsx new file mode 100644 index 00000000..eff9a9be --- /dev/null +++ b/apps/ui/src/components/views/board-view/components/empty-state-card.tsx @@ -0,0 +1,217 @@ +import { memo, useState } from 'react'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Kbd } from '@/components/ui/kbd'; +import { formatShortcut } from '@/store/app-store'; +import { getEmptyStateConfig, type EmptyStateConfig } from '../constants'; +import { + Lightbulb, + Play, + Clock, + CheckCircle2, + Sparkles, + Wand2, + X, + Eye, + EyeOff, +} from 'lucide-react'; + +const ICON_MAP = { + lightbulb: Lightbulb, + play: Play, + clock: Clock, + check: CheckCircle2, + sparkles: Sparkles, +} as const; + +interface EmptyStateCardProps { + columnId: string; + columnTitle?: string; + /** Keyboard shortcut for adding features (from settings) */ + addFeatureShortcut?: string; + /** Whether the column is empty due to active filters */ + isFilteredEmpty?: boolean; + /** Whether we're in read-only mode (hide actions) */ + isReadOnly?: boolean; + /** Called when user clicks "Use AI Suggestions" */ + onAiSuggest?: () => void; + /** Card opacity (matches board settings) */ + opacity?: number; + /** Enable glassmorphism effect */ + glassmorphism?: boolean; + /** Custom config override for pipeline steps */ + customConfig?: Partial; +} + +export const EmptyStateCard = memo(function EmptyStateCard({ + columnId, + columnTitle, + addFeatureShortcut, + isFilteredEmpty = false, + isReadOnly = false, + onAiSuggest, + opacity = 100, + glassmorphism = true, + customConfig, +}: EmptyStateCardProps) { + const [isDismissed, setIsDismissed] = useState(false); + const [isMinimized, setIsMinimized] = useState(false); + + // Get base config and merge with custom overrides + const baseConfig = getEmptyStateConfig(columnId); + const config: EmptyStateConfig = { ...baseConfig, ...customConfig }; + + // Handle dismissal + if (isDismissed) { + return null; + } + + const IconComponent = ICON_MAP[config.icon]; + const showActions = !isReadOnly && !isFilteredEmpty; + const showShortcut = columnId === 'backlog' && addFeatureShortcut && showActions; + + // Minimized state - compact indicator + if (isMinimized) { + return ( + + ); + } + + // Action button handler + const handlePrimaryAction = () => { + if (!config.primaryAction) return; + if (config.primaryAction.actionType === 'ai-suggest') { + onAiSuggest?.(); + } + }; + + return ( +
+ {/* Background with opacity */} +
+ + {/* Dismiss/Minimize controls */} +
+ + +
+ +
+ {/* Header with icon */} +
+
+ +
+
+

+ {isFilteredEmpty ? 'No Matching Items' : config.title} +

+

+ {isFilteredEmpty + ? 'No features match your current filters. Try adjusting your filter criteria.' + : config.description} +

+
+
+ + {/* Keyboard shortcut hint */} + {showShortcut && ( +
+ + {config.shortcutHint || 'Press'} + + + {formatShortcut(addFeatureShortcut, true)} + + to add a feature +
+ )} + + {/* Example card preview */} + {config.exampleCard && ( +
+
+ {config.exampleCard.category} +
+
+ {config.exampleCard.title} +
+
+ )} + + {/* Action buttons */} + {showActions && config.primaryAction && config.primaryAction.actionType !== 'none' && ( + + )} + + {/* Filtered empty state hint */} + {isFilteredEmpty && ( +

+ Clear filters to see all items +

+ )} +
+
+ ); +}); diff --git a/apps/ui/src/components/views/board-view/components/index.ts b/apps/ui/src/components/views/board-view/components/index.ts index 514e407d..0288031d 100644 --- a/apps/ui/src/components/views/board-view/components/index.ts +++ b/apps/ui/src/components/views/board-view/components/index.ts @@ -1,3 +1,4 @@ export { KanbanCard } from './kanban-card/kanban-card'; export { KanbanColumn } from './kanban-column'; export { SelectionActionBar } from './selection-action-bar'; +export { EmptyStateCard } from './empty-state-card'; diff --git a/apps/ui/src/components/views/board-view/constants.ts b/apps/ui/src/components/views/board-view/constants.ts index 9302ea01..5faf4d84 100644 --- a/apps/ui/src/components/views/board-view/constants.ts +++ b/apps/ui/src/components/views/board-view/constants.ts @@ -3,6 +3,93 @@ import type { PipelineConfig, FeatureStatusWithPipeline } from '@automaker/types export type ColumnId = Feature['status']; +/** + * Empty state configuration for each column type + */ +export interface EmptyStateConfig { + title: string; + description: string; + icon: 'lightbulb' | 'play' | 'clock' | 'check' | 'sparkles'; + shortcutKey?: string; // Keyboard shortcut label (e.g., 'N', 'A') + shortcutHint?: string; // Human-readable shortcut hint + primaryAction?: { + label: string; + actionType: 'ai-suggest' | 'none'; + }; + exampleCard?: { + title: string; + category: string; + }; +} + +/** + * Default empty state configurations per column type + */ +export const EMPTY_STATE_CONFIGS: Record = { + backlog: { + title: 'Ready for Ideas', + description: + 'Add your first feature idea to get started using the button below, or let AI help generate ideas.', + icon: 'lightbulb', + shortcutHint: 'Press', + primaryAction: { + label: 'Use AI Suggestions', + actionType: 'ai-suggest', + }, + exampleCard: { + title: 'User Authentication', + category: 'Core Feature', + }, + }, + in_progress: { + title: 'Nothing in Progress', + description: 'Drag a feature from the backlog here or click implement to start working on it.', + icon: 'play', + exampleCard: { + title: 'Implementing feature...', + category: 'In Development', + }, + }, + waiting_approval: { + title: 'No Items Awaiting Approval', + description: 'Features will appear here after implementation is complete and need your review.', + icon: 'clock', + exampleCard: { + title: 'Ready for Review', + category: 'Completed', + }, + }, + verified: { + title: 'No Verified Features', + description: 'Approved features will appear here. They can then be completed and archived.', + icon: 'check', + exampleCard: { + title: 'Approved & Ready', + category: 'Verified', + }, + }, + // Pipeline step default configuration + pipeline_default: { + title: 'Pipeline Step Empty', + description: 'Features will flow through this step during the automated pipeline process.', + icon: 'sparkles', + exampleCard: { + title: 'Processing...', + category: 'Pipeline', + }, + }, +}; + +/** + * Get empty state config for a column, with fallback for pipeline columns + */ +export function getEmptyStateConfig(columnId: string): EmptyStateConfig { + if (columnId.startsWith('pipeline_')) { + return EMPTY_STATE_CONFIGS.pipeline_default; + } + return EMPTY_STATE_CONFIGS[columnId] || EMPTY_STATE_CONFIGS.backlog; +} + export interface Column { id: FeatureStatusWithPipeline; title: string; diff --git a/apps/ui/src/components/views/board-view/kanban-board.tsx b/apps/ui/src/components/views/board-view/kanban-board.tsx index 4601a70c..e6b229ee 100644 --- a/apps/ui/src/components/views/board-view/kanban-board.tsx +++ b/apps/ui/src/components/views/board-view/kanban-board.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { DndContext, DragOverlay } from '@dnd-kit/core'; import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { Button } from '@/components/ui/button'; -import { KanbanColumn, KanbanCard } from './components'; +import { KanbanColumn, KanbanCard, EmptyStateCard } from './components'; import { Feature, useAppStore, formatShortcut } from '@/store/app-store'; import { Archive, Settings2, CheckSquare, GripVertical, Plus } from 'lucide-react'; import { useResponsiveKanban } from '@/hooks/use-responsive-kanban'; @@ -51,6 +51,12 @@ interface KanbanBoardProps { selectedFeatureIds?: Set; onToggleFeatureSelection?: (featureId: string) => void; onToggleSelectionMode?: () => void; + // Empty state action props + onAiSuggest?: () => void; + /** Whether currently dragging (hides empty states during drag) */ + isDragging?: boolean; + /** Whether the board is in read-only mode */ + isReadOnly?: boolean; } export function KanbanBoard({ @@ -86,6 +92,9 @@ export function KanbanBoard({ selectedFeatureIds = new Set(), onToggleFeatureSelection, onToggleSelectionMode, + onAiSuggest, + isDragging = false, + isReadOnly = false, }: KanbanBoardProps) { // Generate columns including pipeline steps const columns = useMemo(() => getColumnsWithPipeline(pipelineConfig), [pipelineConfig]); @@ -211,6 +220,26 @@ export function KanbanBoard({ items={columnFeatures.map((f) => f.id)} strategy={verticalListSortingStrategy} > + {/* Empty state card when column has no features */} + {columnFeatures.length === 0 && !isDragging && ( + + )} {columnFeatures.map((feature, index) => { // Calculate shortcut key for in-progress cards (first 10 get 1-9, 0) let shortcutKey: string | undefined; From f60c18d31a685e3496e9a6299e485805aa06799d Mon Sep 17 00:00:00 2001 From: Shirone Date: Sun, 11 Jan 2026 12:08:33 +0100 Subject: [PATCH 2/4] Update apps/ui/src/components/views/board-view/constants.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- apps/ui/src/components/views/board-view/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ui/src/components/views/board-view/constants.ts b/apps/ui/src/components/views/board-view/constants.ts index 5faf4d84..0ea7ce0a 100644 --- a/apps/ui/src/components/views/board-view/constants.ts +++ b/apps/ui/src/components/views/board-view/constants.ts @@ -87,7 +87,7 @@ export function getEmptyStateConfig(columnId: string): EmptyStateConfig { if (columnId.startsWith('pipeline_')) { return EMPTY_STATE_CONFIGS.pipeline_default; } - return EMPTY_STATE_CONFIGS[columnId] || EMPTY_STATE_CONFIGS.backlog; + return EMPTY_STATE_CONFIGS[columnId] || EMPTY_STATE_CONFIGS.default; } export interface Column { From a48c67d271e2800dd9ca05f61dceed70ba7b633e Mon Sep 17 00:00:00 2001 From: Kacper Date: Sun, 11 Jan 2026 19:59:01 +0100 Subject: [PATCH 3/4] refactor: update EmptyStateCard component for improved layout and functionality - Removed unused props and adjusted styles for a more compact and centered design. - Enhanced the display of the icon, title, and description for better visibility. - Updated keyboard shortcut hint and AI suggestion action for improved user interaction. - Refined dismiss/minimize controls to appear on hover, enhancing the user experience. --- .../components/empty-state-card.tsx | 160 +++++++----------- 1 file changed, 60 insertions(+), 100 deletions(-) diff --git a/apps/ui/src/components/views/board-view/components/empty-state-card.tsx b/apps/ui/src/components/views/board-view/components/empty-state-card.tsx index eff9a9be..26b4fc7a 100644 --- a/apps/ui/src/components/views/board-view/components/empty-state-card.tsx +++ b/apps/ui/src/components/views/board-view/components/empty-state-card.tsx @@ -45,13 +45,10 @@ interface EmptyStateCardProps { export const EmptyStateCard = memo(function EmptyStateCard({ columnId, - columnTitle, addFeatureShortcut, isFilteredEmpty = false, isReadOnly = false, onAiSuggest, - opacity = 100, - glassmorphism = true, customConfig, }: EmptyStateCardProps) { const [isDismissed, setIsDismissed] = useState(false); @@ -70,19 +67,17 @@ export const EmptyStateCard = memo(function EmptyStateCard({ const showActions = !isReadOnly && !isFilteredEmpty; const showShortcut = columnId === 'backlog' && addFeatureShortcut && showActions; - // Minimized state - compact indicator + // Minimized state - compact centered indicator if (isMinimized) { return (
-
- {/* Header with icon */} -
-
- -
-
-

- {isFilteredEmpty ? 'No Matching Items' : config.title} -

-

- {isFilteredEmpty - ? 'No features match your current filters. Try adjusting your filter criteria.' - : config.description} -

-
-
- - {/* Keyboard shortcut hint */} - {showShortcut && ( -
- - {config.shortcutHint || 'Press'} - - - {formatShortcut(addFeatureShortcut, true)} - - to add a feature -
- )} - - {/* Example card preview */} - {config.exampleCard && ( -
-
- {config.exampleCard.category} -
-
- {config.exampleCard.title} -
-
- )} - - {/* Action buttons */} - {showActions && config.primaryAction && config.primaryAction.actionType !== 'none' && ( - - )} - - {/* Filtered empty state hint */} - {isFilteredEmpty && ( -

- Clear filters to see all items -

- )} + {/* Icon */} +
+
+ + {/* Title */} +

+ {isFilteredEmpty ? 'No Matching Items' : config.title} +

+ + {/* Description */} +

+ {isFilteredEmpty ? 'No features match your current filters.' : config.description} +

+ + {/* Keyboard shortcut hint for backlog */} + {showShortcut && ( +
+ Press + + {formatShortcut(addFeatureShortcut, true)} + + to add +
+ )} + + {/* AI Suggest action for backlog */} + {showActions && config.primaryAction && config.primaryAction.actionType === 'ai-suggest' && ( + + )} + + {/* Filtered empty state hint */} + {isFilteredEmpty && ( +

+ Clear filters to see all items +

+ )}
); }); From 6842e4c7f7319a3aade9b12faf7956bdebc85198 Mon Sep 17 00:00:00 2001 From: Shirone Date: Sun, 11 Jan 2026 22:35:25 +0100 Subject: [PATCH 4/4] refactor: simplify EmptyStateCard and update empty state configurations - Removed unused properties and state management from the EmptyStateCard component for cleaner code. - Updated the EMPTY_STATE_CONFIGS to remove exampleCard entries, streamlining the empty state configuration. - Enhanced the primary action handling in the EmptyStateCard for improved functionality. --- .../components/empty-state-card.tsx | 61 +------------------ .../components/views/board-view/constants.ts | 26 +------- 2 files changed, 3 insertions(+), 84 deletions(-) diff --git a/apps/ui/src/components/views/board-view/components/empty-state-card.tsx b/apps/ui/src/components/views/board-view/components/empty-state-card.tsx index 26b4fc7a..30ccdefc 100644 --- a/apps/ui/src/components/views/board-view/components/empty-state-card.tsx +++ b/apps/ui/src/components/views/board-view/components/empty-state-card.tsx @@ -1,20 +1,10 @@ -import { memo, useState } from 'react'; +import { memo } from 'react'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Kbd } from '@/components/ui/kbd'; import { formatShortcut } from '@/store/app-store'; import { getEmptyStateConfig, type EmptyStateConfig } from '../constants'; -import { - Lightbulb, - Play, - Clock, - CheckCircle2, - Sparkles, - Wand2, - X, - Eye, - EyeOff, -} from 'lucide-react'; +import { Lightbulb, Play, Clock, CheckCircle2, Sparkles, Wand2 } from 'lucide-react'; const ICON_MAP = { lightbulb: Lightbulb, @@ -51,43 +41,14 @@ export const EmptyStateCard = memo(function EmptyStateCard({ onAiSuggest, customConfig, }: EmptyStateCardProps) { - const [isDismissed, setIsDismissed] = useState(false); - const [isMinimized, setIsMinimized] = useState(false); - // Get base config and merge with custom overrides const baseConfig = getEmptyStateConfig(columnId); const config: EmptyStateConfig = { ...baseConfig, ...customConfig }; - // Handle dismissal - if (isDismissed) { - return null; - } - const IconComponent = ICON_MAP[config.icon]; const showActions = !isReadOnly && !isFilteredEmpty; const showShortcut = columnId === 'backlog' && addFeatureShortcut && showActions; - // Minimized state - compact centered indicator - if (isMinimized) { - return ( - - ); - } - // Action button handler const handlePrimaryAction = () => { if (!config.primaryAction) return; @@ -108,24 +69,6 @@ export const EmptyStateCard = memo(function EmptyStateCard({ )} data-testid={`empty-state-card-${columnId}`} > - {/* Dismiss/Minimize controls - appears on hover */} -
- - -
- {/* Icon */}
diff --git a/apps/ui/src/components/views/board-view/constants.ts b/apps/ui/src/components/views/board-view/constants.ts index 0ea7ce0a..fda19ebf 100644 --- a/apps/ui/src/components/views/board-view/constants.ts +++ b/apps/ui/src/components/views/board-view/constants.ts @@ -16,10 +16,6 @@ export interface EmptyStateConfig { label: string; actionType: 'ai-suggest' | 'none'; }; - exampleCard?: { - title: string; - category: string; - }; } /** @@ -34,49 +30,29 @@ export const EMPTY_STATE_CONFIGS: Record = { shortcutHint: 'Press', primaryAction: { label: 'Use AI Suggestions', - actionType: 'ai-suggest', - }, - exampleCard: { - title: 'User Authentication', - category: 'Core Feature', + actionType: 'none', }, }, in_progress: { title: 'Nothing in Progress', description: 'Drag a feature from the backlog here or click implement to start working on it.', icon: 'play', - exampleCard: { - title: 'Implementing feature...', - category: 'In Development', - }, }, waiting_approval: { title: 'No Items Awaiting Approval', description: 'Features will appear here after implementation is complete and need your review.', icon: 'clock', - exampleCard: { - title: 'Ready for Review', - category: 'Completed', - }, }, verified: { title: 'No Verified Features', description: 'Approved features will appear here. They can then be completed and archived.', icon: 'check', - exampleCard: { - title: 'Approved & Ready', - category: 'Verified', - }, }, // Pipeline step default configuration pipeline_default: { title: 'Pipeline Step Empty', description: 'Features will flow through this step during the automated pipeline process.', icon: 'sparkles', - exampleCard: { - title: 'Processing...', - category: 'Pipeline', - }, }, };