diff --git a/apps/ui/src/components/dialogs/board-background-modal.tsx b/apps/ui/src/components/dialogs/board-background-modal.tsx index bf3ccbd4..3244dfdf 100644 --- a/apps/ui/src/components/dialogs/board-background-modal.tsx +++ b/apps/ui/src/components/dialogs/board-background-modal.tsx @@ -15,6 +15,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import { cn } from "@/lib/utils"; import { useAppStore, defaultBackgroundSettings } from "@/store/app-store"; import { getHttpApiClient } from "@/lib/http-api-client"; +import { useBoardBackgroundSettings } from "@/hooks/use-board-background-settings"; import { toast } from "sonner"; const ACCEPTED_IMAGE_TYPES = [ @@ -35,9 +36,8 @@ export function BoardBackgroundModal({ open, onOpenChange, }: BoardBackgroundModalProps) { + const { currentProject, boardBackgroundByProject } = useAppStore(); const { - currentProject, - boardBackgroundByProject, setBoardBackground, setCardOpacity, setColumnOpacity, @@ -47,7 +47,7 @@ export function BoardBackgroundModal({ setCardBorderOpacity, setHideScrollbar, clearBoardBackground, - } = useAppStore(); + } = useBoardBackgroundSettings(); const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const fileInputRef = useRef(null); @@ -139,8 +139,8 @@ export function BoardBackgroundModal({ ); if (result.success && result.path) { - // Update store with the relative path (live update) - setBoardBackground(currentProject.path, result.path); + // Update store and persist to server + await setBoardBackground(currentProject.path, result.path); toast.success("Background image saved"); } else { toast.error(result.error || "Failed to save background image"); @@ -214,7 +214,7 @@ export function BoardBackgroundModal({ ); if (result.success) { - clearBoardBackground(currentProject.path); + await clearBoardBackground(currentProject.path); setPreviewImage(null); toast.success("Background image cleared"); } else { @@ -228,59 +228,59 @@ export function BoardBackgroundModal({ } }, [currentProject, clearBoardBackground]); - // Live update opacity when sliders change + // Live update opacity when sliders change (with persistence) const handleCardOpacityChange = useCallback( - (value: number[]) => { + async (value: number[]) => { if (!currentProject) return; - setCardOpacity(currentProject.path, value[0]); + await setCardOpacity(currentProject.path, value[0]); }, [currentProject, setCardOpacity] ); const handleColumnOpacityChange = useCallback( - (value: number[]) => { + async (value: number[]) => { if (!currentProject) return; - setColumnOpacity(currentProject.path, value[0]); + await setColumnOpacity(currentProject.path, value[0]); }, [currentProject, setColumnOpacity] ); const handleColumnBorderToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setColumnBorderEnabled(currentProject.path, checked); + await setColumnBorderEnabled(currentProject.path, checked); }, [currentProject, setColumnBorderEnabled] ); const handleCardGlassmorphismToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setCardGlassmorphism(currentProject.path, checked); + await setCardGlassmorphism(currentProject.path, checked); }, [currentProject, setCardGlassmorphism] ); const handleCardBorderToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setCardBorderEnabled(currentProject.path, checked); + await setCardBorderEnabled(currentProject.path, checked); }, [currentProject, setCardBorderEnabled] ); const handleCardBorderOpacityChange = useCallback( - (value: number[]) => { + async (value: number[]) => { if (!currentProject) return; - setCardBorderOpacity(currentProject.path, value[0]); + await setCardBorderOpacity(currentProject.path, value[0]); }, [currentProject, setCardBorderOpacity] ); const handleHideScrollbarToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setHideScrollbar(currentProject.path, checked); + await setHideScrollbar(currentProject.path, checked); }, [currentProject, setHideScrollbar] ); diff --git a/apps/ui/src/components/dialogs/file-browser-dialog.tsx b/apps/ui/src/components/dialogs/file-browser-dialog.tsx index 289ffbfe..1687218a 100644 --- a/apps/ui/src/components/dialogs/file-browser-dialog.tsx +++ b/apps/ui/src/components/dialogs/file-browser-dialog.tsx @@ -208,13 +208,31 @@ export function FileBrowserDialog({ } }; - const handleSelect = () => { + const handleSelect = useCallback(() => { if (currentPath) { addRecentFolder(currentPath); onSelect(currentPath); onOpenChange(false); } - }; + }, [currentPath, onSelect, onOpenChange]); + + // Handle Command/Ctrl+Enter keyboard shortcut to select current folder + useEffect(() => { + if (!open) return; + + const handleKeyDown = (e: KeyboardEvent) => { + // Check for Command+Enter (Mac) or Ctrl+Enter (Windows/Linux) + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + if (currentPath && !loading) { + handleSelect(); + } + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [open, currentPath, loading, handleSelect]); // Helper to get folder name from path const getFolderName = (path: string) => { @@ -399,9 +417,12 @@ export function FileBrowserDialog({ - diff --git a/apps/ui/src/components/ui/card.tsx b/apps/ui/src/components/ui/card.tsx index f36b6678..3e04be89 100644 --- a/apps/ui/src/components/ui/card.tsx +++ b/apps/ui/src/components/ui/card.tsx @@ -11,11 +11,12 @@ function Card({ className, gradient = false, ...props }: CardProps) {
(null); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); const [currentTime, setCurrentTime] = useState(() => Date.now()); - const { kanbanCardDetailLevel, enableDependencyBlocking, features, useWorktrees } = useAppStore(); + const { + kanbanCardDetailLevel, + enableDependencyBlocking, + features, + useWorktrees, + } = useAppStore(); // Calculate blocking dependencies (if feature is in backlog and has incomplete dependencies) const blockingDependencies = useMemo(() => { @@ -287,9 +291,8 @@ export const KanbanCard = memo(function KanbanCard({ (borderStyle as Record).borderColor = "transparent"; } else if (cardBorderOpacity !== 100) { (borderStyle as Record).borderWidth = "1px"; - ( - borderStyle as Record - ).borderColor = `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`; + (borderStyle as Record).borderColor = + `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`; } const cardElement = ( @@ -336,168 +339,220 @@ export const KanbanCard = memo(function KanbanCard({ /> )} - {/* Priority badge */} - {feature.priority && ( - - - -
- {feature.priority === 1 ? "H" : feature.priority === 2 ? "M" : "L"} -
-
- -

- {feature.priority === 1 - ? "High Priority" - : feature.priority === 2 - ? "Medium Priority" - : "Low Priority"} -

-
-
-
- )} - - {/* Category text next to priority badge */} - {feature.priority && ( -
- - {feature.category} - -
- )} - - {/* Skip Tests (Manual) indicator badge - positioned at top right */} - {feature.skipTests && !feature.error && feature.status === "backlog" && ( - - - -
- -
-
- -

Manual verification required

-
-
-
- )} - - {/* Error indicator badge */} - {feature.error && ( - - - -
- -
-
- -

{feature.error}

-
-
-
- )} - - {/* Blocked by dependencies badge - positioned at top right */} - {blockingDependencies.length > 0 && !feature.error && !feature.skipTests && feature.status === "backlog" && ( - - - -
- -
-
- -

Blocked by {blockingDependencies.length} incomplete {blockingDependencies.length === 1 ? 'dependency' : 'dependencies'}

-

- {blockingDependencies.map(depId => { - const dep = features.find(f => f.id === depId); - return dep?.description || depId; - }).join(', ')} -

-
-
-
- )} - - {/* Just Finished indicator badge */} - {isJustFinished && ( -
0 && + !feature.error && + !feature.skipTests && + feature.status === "backlog") || + isJustFinished) && ( +
+ {/* Error badge */} + {feature.error && ( + + + +
+ +
+
+ +

{feature.error}

+
+
+
+ )} + + {/* Blocked badge */} + {blockingDependencies.length > 0 && + !feature.error && + !feature.skipTests && + feature.status === "backlog" && ( + + + +
+ +
+
+ +

+ Blocked by {blockingDependencies.length} incomplete{" "} + {blockingDependencies.length === 1 + ? "dependency" + : "dependencies"} +

+

+ {blockingDependencies + .map((depId) => { + const dep = features.find((f) => f.id === depId); + return dep?.description || depId; + }) + .join(", ")} +

+
+
+
+ )} + + {/* Just Finished badge */} + {isJustFinished && ( +
+ +
)} - data-testid={`just-finished-badge-${feature.id}`} - title="Agent just finished working on this feature" - > -
)} - - {isCurrentAutoTask && ( -
- - - {formatModelName(feature.model ?? DEFAULT_MODEL)} - - {feature.startedAt && ( - + {/* Category row */} +
+ + {feature.category} + +
+ + + {/* Priority and Manual Verification badges - top left, aligned with delete button */} + {(feature.priority || + (feature.skipTests && + !feature.error && + feature.status === "backlog")) && ( +
+ {/* Priority badge */} + {feature.priority && ( + + + +
+ {feature.priority === 1 + ? "H" + : feature.priority === 2 + ? "M" + : "L"} +
+
+ +

+ {feature.priority === 1 + ? "High Priority" + : feature.priority === 2 + ? "Medium Priority" + : "Low Priority"} +

+
+
+
)} + {/* Manual verification badge */} + {feature.skipTests && + !feature.error && + feature.status === "backlog" && ( + + + +
+ +
+
+ +

Manual verification required

+
+
+
+ )} +
+ )} + {isCurrentAutoTask && ( +
+
+ + {feature.startedAt && ( + + )} +
+ + + + + + { + e.stopPropagation(); + onEdit(); + }} + data-testid={`edit-running-${feature.id}`} + className="text-xs" + > + + Edit + + {/* Model info in dropdown */} +
+
+ + + {formatModelName(feature.model ?? DEFAULT_MODEL)} + +
+
+
+
)} {!isCurrentAutoTask && feature.status === "backlog" && ( -
+
)} -
-
-
- -
)}
@@ -650,7 +714,9 @@ export const KanbanCard = memo(function KanbanCard({ {feature.titleGenerating ? (
- Generating title... + + Generating title... +
) : feature.title ? ( @@ -688,16 +754,11 @@ export const KanbanCard = memo(function KanbanCard({ )} )} - {!feature.priority && ( - - {feature.category} - - )}
- + {/* Target Branch Display */} {useWorktrees && feature.branchName && (
@@ -710,8 +771,9 @@ export const KanbanCard = memo(function KanbanCard({ {/* PR URL Display */} {typeof feature.prUrl === "string" && - /^https?:\/\//i.test(feature.prUrl) && (() => { - const prNumber = feature.prUrl.split('/').pop(); + /^https?:\/\//i.test(feature.prUrl) && + (() => { + const prNumber = feature.prUrl.split("/").pop(); return (
- {prNumber ? `Pull Request #${prNumber}` : 'Pull Request'} + {prNumber ? `Pull Request #${prNumber}` : "Pull Request"} @@ -917,11 +979,11 @@ export const KanbanCard = memo(function KanbanCard({ )} {/* Actions */} -
+
{isCurrentAutoTask && ( <> {/* Approve Plan button - PRIORITY: shows even when agent is "running" (paused for approval) */} - {feature.planSpec?.status === 'generated' && onApprovePlan && ( + {feature.planSpec?.status === "generated" && onApprovePlan && (