mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
refactor: Enhance project management features and UI components
- Updated create-pr.ts to improve commit error handling and logging. - Enhanced project-switcher.tsx with new folder opening functionality and state management for project setup. - Expanded icon-picker.tsx to include a comprehensive list of icons organized by category. - Replaced dialog components with popover components for auto mode and plan settings, improving UI responsiveness. - Refactored board-view components to streamline feature management and enhance user experience. - Removed outdated dialog components and replaced them with popover alternatives for better accessibility. These changes aim to improve the overall usability and functionality of the project management interface.
This commit is contained in:
@@ -1324,8 +1324,6 @@ export function BoardView() {
|
||||
isCreatingSpec={isCreatingSpec}
|
||||
creatingSpecProjectPath={creatingSpecProjectPath}
|
||||
onShowBoardBackground={() => setShowBoardBackgroundModal(true)}
|
||||
onShowCompletedModal={() => setShowCompletedModal(true)}
|
||||
completedCount={completedFeatures.length}
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
/>
|
||||
@@ -1437,6 +1435,8 @@ export function BoardView() {
|
||||
runningAutoTasks={runningAutoTasks}
|
||||
onArchiveAllVerified={() => setShowArchiveAllVerifiedDialog(true)}
|
||||
onAddFeature={() => setShowAddDialog(true)}
|
||||
onShowCompletedModal={() => setShowCompletedModal(true)}
|
||||
completedCount={completedFeatures.length}
|
||||
pipelineConfig={pipelineConfig}
|
||||
onOpenPipelineSettings={() => setShowPipelineSettings(true)}
|
||||
isSelectionMode={isSelectionMode}
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { ImageIcon, Archive } from 'lucide-react';
|
||||
import { ImageIcon } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface BoardControlsProps {
|
||||
isMounted: boolean;
|
||||
onShowBoardBackground: () => void;
|
||||
onShowCompletedModal: () => void;
|
||||
completedCount: number;
|
||||
}
|
||||
|
||||
export function BoardControls({
|
||||
isMounted,
|
||||
onShowBoardBackground,
|
||||
onShowCompletedModal,
|
||||
completedCount,
|
||||
}: BoardControlsProps) {
|
||||
export function BoardControls({ isMounted, onShowBoardBackground }: BoardControlsProps) {
|
||||
if (!isMounted) return null;
|
||||
|
||||
return (
|
||||
@@ -23,43 +16,23 @@ export function BoardControls({
|
||||
{/* Board Background Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
<button
|
||||
onClick={onShowBoardBackground}
|
||||
className="h-8 px-2"
|
||||
className={cn(
|
||||
'inline-flex h-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-all duration-200 cursor-pointer',
|
||||
'text-muted-foreground hover:text-foreground hover:bg-accent',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
'border border-border'
|
||||
)}
|
||||
data-testid="board-background-button"
|
||||
>
|
||||
<ImageIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Board Background Settings</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{/* Completed/Archived Features Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onShowCompletedModal}
|
||||
className="h-8 px-2 relative"
|
||||
data-testid="completed-features-button"
|
||||
>
|
||||
<Archive className="w-4 h-4" />
|
||||
{completedCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-brand-500 text-white text-[10px] font-bold rounded-full w-4 h-4 flex items-center justify-center">
|
||||
{completedCount > 99 ? '99+' : completedCount}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Completed Features ({completedCount})</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { useCallback } from 'react';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Bot, Wand2, Settings2, GitBranch } from 'lucide-react';
|
||||
import { Wand2, GitBranch } from 'lucide-react';
|
||||
import { UsagePopover } from '@/components/usage-popover';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
import { useIsMobile } from '@/hooks/use-media-query';
|
||||
import { AutoModeSettingsDialog } from './dialogs/auto-mode-settings-dialog';
|
||||
import { WorktreeSettingsDialog } from './dialogs/worktree-settings-dialog';
|
||||
import { PlanSettingsDialog } from './dialogs/plan-settings-dialog';
|
||||
import { AutoModeSettingsPopover } from './dialogs/auto-mode-settings-popover';
|
||||
import { WorktreeSettingsPopover } from './dialogs/worktree-settings-popover';
|
||||
import { PlanSettingsPopover } from './dialogs/plan-settings-popover';
|
||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||
import { BoardSearchBar } from './board-search-bar';
|
||||
import { BoardControls } from './board-controls';
|
||||
@@ -36,8 +33,6 @@ interface BoardHeaderProps {
|
||||
creatingSpecProjectPath?: string;
|
||||
// Board controls props
|
||||
onShowBoardBackground: () => void;
|
||||
onShowCompletedModal: () => void;
|
||||
completedCount: number;
|
||||
// View toggle props
|
||||
viewMode: ViewMode;
|
||||
onViewModeChange: (mode: ViewMode) => void;
|
||||
@@ -61,14 +56,9 @@ export function BoardHeader({
|
||||
isCreatingSpec,
|
||||
creatingSpecProjectPath,
|
||||
onShowBoardBackground,
|
||||
onShowCompletedModal,
|
||||
completedCount,
|
||||
viewMode,
|
||||
onViewModeChange,
|
||||
}: BoardHeaderProps) {
|
||||
const [showAutoModeSettings, setShowAutoModeSettings] = useState(false);
|
||||
const [showWorktreeSettings, setShowWorktreeSettings] = useState(false);
|
||||
const [showPlanSettings, setShowPlanSettings] = useState(false);
|
||||
const claudeAuthStatus = useSetupStore((state) => state.claudeAuthStatus);
|
||||
const skipVerificationInAutoMode = useAppStore((state) => state.skipVerificationInAutoMode);
|
||||
const setSkipVerificationInAutoMode = useAppStore((state) => state.setSkipVerificationInAutoMode);
|
||||
@@ -127,12 +117,7 @@ export function BoardHeader({
|
||||
currentProjectPath={projectPath}
|
||||
/>
|
||||
{isMounted && <ViewToggle viewMode={viewMode} onViewModeChange={onViewModeChange} />}
|
||||
<BoardControls
|
||||
isMounted={isMounted}
|
||||
onShowBoardBackground={onShowBoardBackground}
|
||||
onShowCompletedModal={onShowCompletedModal}
|
||||
completedCount={completedCount}
|
||||
/>
|
||||
<BoardControls isMounted={isMounted} onShowBoardBackground={onShowBoardBackground} />
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
{/* Usage Popover - show if either provider is authenticated, only on desktop */}
|
||||
@@ -148,7 +133,7 @@ export function BoardHeader({
|
||||
onConcurrencyChange={onConcurrencyChange}
|
||||
isAutoModeRunning={isAutoModeRunning}
|
||||
onAutoModeToggle={onAutoModeToggle}
|
||||
onOpenAutoModeSettings={() => setShowAutoModeSettings(true)}
|
||||
onOpenAutoModeSettings={() => {}}
|
||||
onOpenPlanDialog={onOpenPlanDialog}
|
||||
showClaudeUsage={showClaudeUsage}
|
||||
showCodexUsage={showCodexUsage}
|
||||
@@ -160,7 +145,10 @@ export function BoardHeader({
|
||||
{isMounted && !isMobile && (
|
||||
<div className={controlContainerClass} data-testid="worktrees-toggle-container">
|
||||
<GitBranch className="w-4 h-4 text-muted-foreground" />
|
||||
<Label htmlFor="worktrees-toggle" className="text-sm font-medium cursor-pointer">
|
||||
<Label
|
||||
htmlFor="worktrees-toggle"
|
||||
className="text-xs font-medium cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
Worktree Bar
|
||||
</Label>
|
||||
<Switch
|
||||
@@ -169,72 +157,20 @@ export function BoardHeader({
|
||||
onCheckedChange={handleWorktreePanelToggle}
|
||||
data-testid="worktrees-toggle"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setShowWorktreeSettings(true)}
|
||||
className="p-1 rounded hover:bg-accent/50 transition-colors"
|
||||
title="Worktree Settings"
|
||||
data-testid="worktree-settings-button"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
<WorktreeSettingsPopover
|
||||
addFeatureUseSelectedWorktreeBranch={addFeatureUseSelectedWorktreeBranch}
|
||||
onAddFeatureUseSelectedWorktreeBranchChange={setAddFeatureUseSelectedWorktreeBranch}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Worktree Settings Dialog */}
|
||||
<WorktreeSettingsDialog
|
||||
open={showWorktreeSettings}
|
||||
onOpenChange={setShowWorktreeSettings}
|
||||
addFeatureUseSelectedWorktreeBranch={addFeatureUseSelectedWorktreeBranch}
|
||||
onAddFeatureUseSelectedWorktreeBranchChange={setAddFeatureUseSelectedWorktreeBranch}
|
||||
/>
|
||||
|
||||
{/* Concurrency Control - only show after mount to prevent hydration issues */}
|
||||
{isMounted && !isMobile && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className={`${controlContainerClass} cursor-pointer hover:bg-accent/50 transition-colors`}
|
||||
data-testid="concurrency-slider-container"
|
||||
>
|
||||
<Bot className="w-4 h-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Agents</span>
|
||||
<span className="text-sm text-muted-foreground" data-testid="concurrency-value">
|
||||
{runningAgentsCount}/{maxConcurrency}
|
||||
</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-64" align="end">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium text-sm mb-1">Max Concurrent Agents</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Controls how many AI agents can run simultaneously. Higher values process more
|
||||
features in parallel but use more API resources.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Slider
|
||||
value={[maxConcurrency]}
|
||||
onValueChange={(value) => onConcurrencyChange(value[0])}
|
||||
min={1}
|
||||
max={10}
|
||||
step={1}
|
||||
className="flex-1"
|
||||
data-testid="concurrency-slider"
|
||||
/>
|
||||
<span className="text-sm font-medium min-w-[2ch] text-right">
|
||||
{maxConcurrency}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
{/* Auto Mode Toggle - only show after mount to prevent hydration issues */}
|
||||
{isMounted && !isMobile && (
|
||||
<div className={controlContainerClass} data-testid="auto-mode-toggle-container">
|
||||
<Label htmlFor="auto-mode-toggle" className="text-sm font-medium cursor-pointer">
|
||||
<Label
|
||||
htmlFor="auto-mode-toggle"
|
||||
className="text-xs font-medium cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
Auto Mode
|
||||
</Label>
|
||||
<Switch
|
||||
@@ -243,25 +179,16 @@ export function BoardHeader({
|
||||
onCheckedChange={onAutoModeToggle}
|
||||
data-testid="auto-mode-toggle"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setShowAutoModeSettings(true)}
|
||||
className="p-1 rounded hover:bg-accent/50 transition-colors"
|
||||
title="Auto Mode Settings"
|
||||
data-testid="auto-mode-settings-button"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
<AutoModeSettingsPopover
|
||||
skipVerificationInAutoMode={skipVerificationInAutoMode}
|
||||
onSkipVerificationChange={setSkipVerificationInAutoMode}
|
||||
maxConcurrency={maxConcurrency}
|
||||
runningAgentsCount={runningAgentsCount}
|
||||
onConcurrencyChange={onConcurrencyChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Auto Mode Settings Dialog */}
|
||||
<AutoModeSettingsDialog
|
||||
open={showAutoModeSettings}
|
||||
onOpenChange={setShowAutoModeSettings}
|
||||
skipVerificationInAutoMode={skipVerificationInAutoMode}
|
||||
onSkipVerificationChange={setSkipVerificationInAutoMode}
|
||||
/>
|
||||
|
||||
{/* Plan Button with Settings - only show on desktop, mobile has it in the menu */}
|
||||
{isMounted && !isMobile && (
|
||||
<div className={controlContainerClass} data-testid="plan-button-container">
|
||||
@@ -273,24 +200,12 @@ export function BoardHeader({
|
||||
<Wand2 className="w-4 h-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Plan</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowPlanSettings(true)}
|
||||
className="p-1 rounded hover:bg-accent/50 transition-colors"
|
||||
title="Plan Settings"
|
||||
data-testid="plan-settings-button"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
<PlanSettingsPopover
|
||||
planUseSelectedWorktreeBranch={planUseSelectedWorktreeBranch}
|
||||
onPlanUseSelectedWorktreeBranchChange={setPlanUseSelectedWorktreeBranch}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Plan Settings Dialog */}
|
||||
<PlanSettingsDialog
|
||||
open={showPlanSettings}
|
||||
onOpenChange={setShowPlanSettings}
|
||||
planUseSelectedWorktreeBranch={planUseSelectedWorktreeBranch}
|
||||
onPlanUseSelectedWorktreeBranchChange={setPlanUseSelectedWorktreeBranch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,9 +30,7 @@ export function SelectionActionBar({
|
||||
}: SelectionActionBarProps) {
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
|
||||
if (selectedCount === 0) return null;
|
||||
|
||||
const allSelected = selectedCount === totalCount;
|
||||
const allSelected = selectedCount === totalCount && totalCount > 0;
|
||||
|
||||
const handleDeleteClick = () => {
|
||||
setShowDeleteDialog(true);
|
||||
@@ -55,7 +53,9 @@ export function SelectionActionBar({
|
||||
data-testid="selection-action-bar"
|
||||
>
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{selectedCount} feature{selectedCount !== 1 ? 's' : ''} selected
|
||||
{selectedCount === 0
|
||||
? 'Select features to edit'
|
||||
: `${selectedCount} feature${selectedCount !== 1 ? 's' : ''} selected`}
|
||||
</span>
|
||||
|
||||
<div className="h-4 w-px bg-border" />
|
||||
@@ -65,7 +65,8 @@ export function SelectionActionBar({
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={onEdit}
|
||||
className="h-8 bg-brand-500 hover:bg-brand-600"
|
||||
disabled={selectedCount === 0}
|
||||
className="h-8 bg-brand-500 hover:bg-brand-600 disabled:opacity-50"
|
||||
data-testid="selection-edit-button"
|
||||
>
|
||||
<Pencil className="w-4 h-4 mr-1.5" />
|
||||
@@ -76,7 +77,8 @@ export function SelectionActionBar({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleDeleteClick}
|
||||
className="h-8 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
disabled={selectedCount === 0}
|
||||
className="h-8 text-destructive hover:text-destructive hover:bg-destructive/10 disabled:opacity-50"
|
||||
data-testid="selection-delete-button"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-1.5" />
|
||||
|
||||
@@ -38,7 +38,7 @@ export function ViewToggle({ viewMode, onViewModeChange, className }: ViewToggle
|
||||
data-testid="view-toggle-kanban"
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4" />
|
||||
<span className="sr-only sm:not-sr-only">Kanban</span>
|
||||
<span className="sr-only">Kanban</span>
|
||||
</button>
|
||||
<button
|
||||
role="tab"
|
||||
@@ -55,7 +55,7 @@ export function ViewToggle({ viewMode, onViewModeChange, className }: ViewToggle
|
||||
data-testid="view-toggle-list"
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
<span className="sr-only sm:not-sr-only">List</span>
|
||||
<span className="sr-only">List</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { FastForward, Settings2 } from 'lucide-react';
|
||||
|
||||
interface AutoModeSettingsDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
skipVerificationInAutoMode: boolean;
|
||||
onSkipVerificationChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function AutoModeSettingsDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
skipVerificationInAutoMode,
|
||||
onSkipVerificationChange,
|
||||
}: AutoModeSettingsDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md" data-testid="auto-mode-settings-dialog">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings2 className="w-5 h-5" />
|
||||
Auto Mode Settings
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Configure how auto mode handles feature execution and dependencies.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
{/* Skip Verification Setting */}
|
||||
<div className="flex items-start space-x-3 p-3 rounded-lg bg-secondary/50">
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label
|
||||
htmlFor="skip-verification-toggle"
|
||||
className="text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<FastForward className="w-4 h-4 text-brand-500" />
|
||||
Skip verification requirement
|
||||
</Label>
|
||||
<Switch
|
||||
id="skip-verification-toggle"
|
||||
checked={skipVerificationInAutoMode}
|
||||
onCheckedChange={onSkipVerificationChange}
|
||||
data-testid="skip-verification-toggle"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
When enabled, auto mode will grab features even if their dependencies are not
|
||||
verified, as long as they are not currently running. This allows faster pipeline
|
||||
execution without waiting for manual verification.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { FastForward, Bot, Settings2 } from 'lucide-react';
|
||||
|
||||
interface AutoModeSettingsPopoverProps {
|
||||
skipVerificationInAutoMode: boolean;
|
||||
onSkipVerificationChange: (value: boolean) => void;
|
||||
maxConcurrency: number;
|
||||
runningAgentsCount: number;
|
||||
onConcurrencyChange: (value: number) => void;
|
||||
}
|
||||
|
||||
export function AutoModeSettingsPopover({
|
||||
skipVerificationInAutoMode,
|
||||
onSkipVerificationChange,
|
||||
maxConcurrency,
|
||||
runningAgentsCount,
|
||||
onConcurrencyChange,
|
||||
}: AutoModeSettingsPopoverProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className="p-1 rounded hover:bg-accent/50 transition-colors"
|
||||
title="Auto Mode Settings"
|
||||
data-testid="auto-mode-settings-button"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-72" align="end" sideOffset={8}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium text-sm mb-1">Auto Mode Settings</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Configure auto mode execution and agent concurrency.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Max Concurrent Agents */}
|
||||
<div className="space-y-2 p-2 rounded-md bg-secondary/50">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bot className="w-4 h-4 text-brand-500 shrink-0" />
|
||||
<Label className="text-xs font-medium">Max Concurrent Agents</Label>
|
||||
<span className="ml-auto text-xs text-muted-foreground">
|
||||
{runningAgentsCount}/{maxConcurrency}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Slider
|
||||
value={[maxConcurrency]}
|
||||
onValueChange={(value) => onConcurrencyChange(value[0])}
|
||||
min={1}
|
||||
max={10}
|
||||
step={1}
|
||||
className="flex-1"
|
||||
data-testid="concurrency-slider"
|
||||
/>
|
||||
<span className="text-xs font-medium min-w-[2ch] text-right">{maxConcurrency}</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Higher values process more features in parallel but use more API resources.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Skip Verification Setting */}
|
||||
<div className="flex items-center justify-between gap-3 p-2 rounded-md bg-secondary/50">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<FastForward className="w-4 h-4 text-brand-500 shrink-0" />
|
||||
<Label
|
||||
htmlFor="skip-verification-toggle"
|
||||
className="text-xs font-medium cursor-pointer"
|
||||
>
|
||||
Skip verification requirement
|
||||
</Label>
|
||||
</div>
|
||||
<Switch
|
||||
id="skip-verification-toggle"
|
||||
checked={skipVerificationInAutoMode}
|
||||
onCheckedChange={onSkipVerificationChange}
|
||||
data-testid="skip-verification-toggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-[10px] text-muted-foreground leading-relaxed">
|
||||
When enabled, auto mode will grab features even if their dependencies are not verified,
|
||||
as long as they are not currently running.
|
||||
</p>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { GitBranch, Settings2 } from 'lucide-react';
|
||||
|
||||
interface PlanSettingsDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
planUseSelectedWorktreeBranch: boolean;
|
||||
onPlanUseSelectedWorktreeBranchChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function PlanSettingsDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
planUseSelectedWorktreeBranch,
|
||||
onPlanUseSelectedWorktreeBranchChange,
|
||||
}: PlanSettingsDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md" data-testid="plan-settings-dialog">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings2 className="w-5 h-5" />
|
||||
Plan Settings
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Configure how the Plan feature creates and organizes new features.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
{/* Use Selected Worktree Branch Setting */}
|
||||
<div className="flex items-start space-x-3 p-3 rounded-lg bg-secondary/50">
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label
|
||||
htmlFor="plan-worktree-branch-toggle"
|
||||
className="text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<GitBranch className="w-4 h-4 text-brand-500" />
|
||||
Default to worktree mode
|
||||
</Label>
|
||||
<Switch
|
||||
id="plan-worktree-branch-toggle"
|
||||
checked={planUseSelectedWorktreeBranch}
|
||||
onCheckedChange={onPlanUseSelectedWorktreeBranchChange}
|
||||
data-testid="plan-worktree-branch-toggle"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
Planned features will automatically use isolated worktrees, keeping changes separate
|
||||
from your main branch until you're ready to merge.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { GitBranch, Settings2 } from 'lucide-react';
|
||||
|
||||
interface PlanSettingsPopoverProps {
|
||||
planUseSelectedWorktreeBranch: boolean;
|
||||
onPlanUseSelectedWorktreeBranchChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function PlanSettingsPopover({
|
||||
planUseSelectedWorktreeBranch,
|
||||
onPlanUseSelectedWorktreeBranchChange,
|
||||
}: PlanSettingsPopoverProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className="p-1 rounded hover:bg-accent/50 transition-colors"
|
||||
title="Plan Settings"
|
||||
data-testid="plan-settings-button"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-72" align="end" sideOffset={8}>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h4 className="font-medium text-sm mb-1">Plan Settings</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Configure how Plan creates and organizes features.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3 p-2 rounded-md bg-secondary/50">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<GitBranch className="w-4 h-4 text-brand-500 shrink-0" />
|
||||
<Label
|
||||
htmlFor="plan-worktree-branch-toggle"
|
||||
className="text-xs font-medium cursor-pointer"
|
||||
>
|
||||
Default to worktree mode
|
||||
</Label>
|
||||
</div>
|
||||
<Switch
|
||||
id="plan-worktree-branch-toggle"
|
||||
checked={planUseSelectedWorktreeBranch}
|
||||
onCheckedChange={onPlanUseSelectedWorktreeBranchChange}
|
||||
data-testid="plan-worktree-branch-toggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-[10px] text-muted-foreground leading-relaxed">
|
||||
Planned features will automatically use isolated worktrees, keeping changes separate
|
||||
from your main branch until you're ready to merge.
|
||||
</p>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { GitBranch, Settings2 } from 'lucide-react';
|
||||
|
||||
interface WorktreeSettingsDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
addFeatureUseSelectedWorktreeBranch: boolean;
|
||||
onAddFeatureUseSelectedWorktreeBranchChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function WorktreeSettingsDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
addFeatureUseSelectedWorktreeBranch,
|
||||
onAddFeatureUseSelectedWorktreeBranchChange,
|
||||
}: WorktreeSettingsDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md" data-testid="worktree-settings-dialog">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings2 className="w-5 h-5" />
|
||||
Worktree Settings
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Configure how worktrees affect feature creation and organization.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
{/* Use Selected Worktree Branch Setting */}
|
||||
<div className="flex items-start space-x-3 p-3 rounded-lg bg-secondary/50">
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label
|
||||
htmlFor="worktree-branch-toggle"
|
||||
className="text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<GitBranch className="w-4 h-4 text-brand-500" />
|
||||
Default to worktree mode
|
||||
</Label>
|
||||
<Switch
|
||||
id="worktree-branch-toggle"
|
||||
checked={addFeatureUseSelectedWorktreeBranch}
|
||||
onCheckedChange={onAddFeatureUseSelectedWorktreeBranchChange}
|
||||
data-testid="worktree-branch-toggle"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
New features will automatically use isolated worktrees, keeping changes separate
|
||||
from your main branch until you're ready to merge.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { GitBranch, Settings2 } from 'lucide-react';
|
||||
|
||||
interface WorktreeSettingsPopoverProps {
|
||||
addFeatureUseSelectedWorktreeBranch: boolean;
|
||||
onAddFeatureUseSelectedWorktreeBranchChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function WorktreeSettingsPopover({
|
||||
addFeatureUseSelectedWorktreeBranch,
|
||||
onAddFeatureUseSelectedWorktreeBranchChange,
|
||||
}: WorktreeSettingsPopoverProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className="p-1 rounded hover:bg-accent/50 transition-colors"
|
||||
title="Worktree Settings"
|
||||
data-testid="worktree-settings-button"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-72" align="end" sideOffset={8}>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h4 className="font-medium text-sm mb-1">Worktree Settings</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Configure how worktrees affect feature creation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3 p-2 rounded-md bg-secondary/50">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<GitBranch className="w-4 h-4 text-brand-500 shrink-0" />
|
||||
<Label
|
||||
htmlFor="worktree-branch-toggle"
|
||||
className="text-xs font-medium cursor-pointer"
|
||||
>
|
||||
Default to worktree mode
|
||||
</Label>
|
||||
</div>
|
||||
<Switch
|
||||
id="worktree-branch-toggle"
|
||||
checked={addFeatureUseSelectedWorktreeBranch}
|
||||
onCheckedChange={onAddFeatureUseSelectedWorktreeBranchChange}
|
||||
data-testid="worktree-branch-toggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-[10px] text-muted-foreground leading-relaxed">
|
||||
New features will automatically use isolated worktrees, keeping changes separate from
|
||||
your main branch until you're ready to merge.
|
||||
</p>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -44,6 +44,8 @@ interface KanbanBoardProps {
|
||||
runningAutoTasks: string[];
|
||||
onArchiveAllVerified: () => void;
|
||||
onAddFeature: () => void;
|
||||
onShowCompletedModal: () => void;
|
||||
completedCount: number;
|
||||
pipelineConfig: PipelineConfig | null;
|
||||
onOpenPipelineSettings?: () => void;
|
||||
// Selection mode props
|
||||
@@ -88,6 +90,8 @@ export function KanbanBoard({
|
||||
runningAutoTasks,
|
||||
onArchiveAllVerified,
|
||||
onAddFeature,
|
||||
onShowCompletedModal,
|
||||
completedCount,
|
||||
pipelineConfig,
|
||||
onOpenPipelineSettings,
|
||||
isSelectionMode = false,
|
||||
@@ -140,17 +144,36 @@ export function KanbanBoard({
|
||||
showBorder={backgroundSettings.columnBorderEnabled}
|
||||
hideScrollbar={backgroundSettings.hideScrollbar}
|
||||
headerAction={
|
||||
column.id === 'verified' && columnFeatures.length > 0 ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={onArchiveAllVerified}
|
||||
data-testid="archive-all-verified-button"
|
||||
>
|
||||
<Archive className="w-3 h-3 mr-1" />
|
||||
Complete All
|
||||
</Button>
|
||||
column.id === 'verified' ? (
|
||||
<div className="flex items-center gap-1">
|
||||
{columnFeatures.length > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={onArchiveAllVerified}
|
||||
data-testid="archive-all-verified-button"
|
||||
>
|
||||
<Archive className="w-3 h-3 mr-1" />
|
||||
Complete All
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 relative"
|
||||
onClick={onShowCompletedModal}
|
||||
title={`Completed Features (${completedCount})`}
|
||||
data-testid="completed-features-button"
|
||||
>
|
||||
<Archive className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
{completedCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-brand-500 text-white text-[8px] font-bold rounded-full w-3.5 h-3.5 flex items-center justify-center">
|
||||
{completedCount > 99 ? '99+' : completedCount}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
) : column.id === 'backlog' ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
|
||||
@@ -267,6 +267,7 @@ export function GraphViewPage() {
|
||||
setShowAddDialog(true);
|
||||
}}
|
||||
onDeleteTask={(feature) => handleDeleteFeature(feature.id)}
|
||||
onAddFeature={() => setShowAddDialog(true)}
|
||||
/>
|
||||
|
||||
{/* Edit Feature Dialog */}
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
} from './hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useDebounceValue } from 'usehooks-ts';
|
||||
import { SearchX } from 'lucide-react';
|
||||
import { SearchX, Plus } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
// Define custom node and edge types - using any to avoid React Flow's strict typing
|
||||
@@ -64,6 +64,7 @@ interface GraphCanvasProps {
|
||||
onNodeDoubleClick?: (featureId: string) => void;
|
||||
nodeActionCallbacks?: NodeActionCallbacks;
|
||||
onCreateDependency?: (sourceId: string, targetId: string) => Promise<boolean>;
|
||||
onAddFeature?: () => void;
|
||||
backgroundStyle?: React.CSSProperties;
|
||||
backgroundSettings?: BackgroundSettings;
|
||||
className?: string;
|
||||
@@ -77,6 +78,7 @@ function GraphCanvasInner({
|
||||
onNodeDoubleClick,
|
||||
nodeActionCallbacks,
|
||||
onCreateDependency,
|
||||
onAddFeature,
|
||||
backgroundStyle,
|
||||
backgroundSettings,
|
||||
className,
|
||||
@@ -398,6 +400,14 @@ function GraphCanvasInner({
|
||||
|
||||
<GraphLegend />
|
||||
|
||||
{/* Add Feature Button */}
|
||||
<Panel position="top-right">
|
||||
<Button variant="default" size="sm" onClick={onAddFeature} className="gap-1.5">
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Feature
|
||||
</Button>
|
||||
</Panel>
|
||||
|
||||
{/* Empty state when all nodes are filtered out */}
|
||||
{filterResult.hasActiveFilter && filterResult.matchedNodeIds.size === 0 && (
|
||||
<Panel position="top-center" className="mt-20">
|
||||
|
||||
@@ -22,6 +22,7 @@ interface GraphViewProps {
|
||||
onUpdateFeature?: (featureId: string, updates: Partial<Feature>) => void;
|
||||
onSpawnTask?: (feature: Feature) => void;
|
||||
onDeleteTask?: (feature: Feature) => void;
|
||||
onAddFeature?: () => void;
|
||||
}
|
||||
|
||||
export function GraphView({
|
||||
@@ -40,6 +41,7 @@ export function GraphView({
|
||||
onUpdateFeature,
|
||||
onSpawnTask,
|
||||
onDeleteTask,
|
||||
onAddFeature,
|
||||
}: GraphViewProps) {
|
||||
const { currentProject } = useAppStore();
|
||||
|
||||
@@ -212,6 +214,7 @@ export function GraphView({
|
||||
onNodeDoubleClick={handleNodeDoubleClick}
|
||||
nodeActionCallbacks={nodeActionCallbacks}
|
||||
onCreateDependency={handleCreateDependency}
|
||||
onAddFeature={onAddFeature}
|
||||
backgroundStyle={backgroundImageStyle}
|
||||
backgroundSettings={backgroundSettings}
|
||||
className="h-full"
|
||||
|
||||
Reference in New Issue
Block a user