refactor: replace Loader2 with Spinner component across the application

This update standardizes the loading indicators by replacing all instances of Loader2 with the new Spinner component. The Spinner component provides a consistent look and feel for loading states throughout the UI, enhancing the user experience.

Changes include:
- Updated loading indicators in various components such as popovers, modals, and views.
- Ensured that the Spinner component is used with appropriate sizes for different contexts.

No functional changes were made; this is purely a visual and structural improvement.
This commit is contained in:
webdevcody
2026-01-17 17:58:16 -05:00
parent 044c3d50d1
commit 832d10e133
93 changed files with 351 additions and 333 deletions

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react'; import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
@@ -279,7 +280,7 @@ export function ClaudeUsagePopover() {
) : !claudeUsage ? ( ) : !claudeUsage ? (
// Loading state // Loading state
<div className="flex flex-col items-center justify-center py-8 space-y-2"> <div className="flex flex-col items-center justify-center py-8 space-y-2">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground/50" /> <Spinner size="lg" />
<p className="text-xs text-muted-foreground">Loading usage data...</p> <p className="text-xs text-muted-foreground">Loading usage data...</p>
</div> </div>
) : ( ) : (

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react'; import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
@@ -333,7 +334,7 @@ export function CodexUsagePopover() {
) : !codexUsage ? ( ) : !codexUsage ? (
// Loading state // Loading state
<div className="flex flex-col items-center justify-center py-8 space-y-2"> <div className="flex flex-col items-center justify-center py-8 space-y-2">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground/50" /> <Spinner size="lg" />
<p className="text-xs text-muted-foreground">Loading usage data...</p> <p className="text-xs text-muted-foreground">Loading usage data...</p>
</div> </div>
) : codexUsage.rateLimits ? ( ) : codexUsage.rateLimits ? (

View File

@@ -1,6 +1,7 @@
import { useState, useRef, useCallback, useEffect } from 'react'; import { useState, useRef, useCallback, useEffect } from 'react';
import { createLogger } from '@automaker/utils/logger'; import { createLogger } from '@automaker/utils/logger';
import { ImageIcon, Upload, Loader2, Trash2 } from 'lucide-react'; import { ImageIcon, Upload, Trash2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
const logger = createLogger('BoardBackgroundModal'); const logger = createLogger('BoardBackgroundModal');
import { import {
@@ -313,7 +314,7 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa
/> />
{isProcessing && ( {isProcessing && (
<div className="absolute inset-0 flex items-center justify-center bg-background/80"> <div className="absolute inset-0 flex items-center justify-center bg-background/80">
<Loader2 className="w-6 h-6 animate-spin text-brand-500" /> <Spinner size="lg" />
</div> </div>
)} )}
</div> </div>
@@ -353,7 +354,7 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa
)} )}
> >
{isProcessing ? ( {isProcessing ? (
<Upload className="h-6 w-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
) : ( ) : (
<ImageIcon className="h-6 w-6 text-muted-foreground" /> <ImageIcon className="h-6 w-6 text-muted-foreground" />
)} )}

View File

@@ -14,16 +14,8 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { import { FolderPlus, FolderOpen, Rocket, ExternalLink, Check, Link, Folder } from 'lucide-react';
FolderPlus, import { Spinner } from '@/components/ui/spinner';
FolderOpen,
Rocket,
ExternalLink,
Check,
Loader2,
Link,
Folder,
} from 'lucide-react';
import { starterTemplates, type StarterTemplate } from '@/lib/templates'; import { starterTemplates, type StarterTemplate } from '@/lib/templates';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@@ -451,7 +443,7 @@ export function NewProjectModal({
> >
{isCreating ? ( {isCreating ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
{activeTab === 'template' ? 'Cloning...' : 'Creating...'} {activeTab === 'template' ? 'Cloning...' : 'Creating...'}
</> </>
) : ( ) : (

View File

@@ -8,7 +8,8 @@ import {
DialogTitle, DialogTitle,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Folder, Loader2, FolderOpen, AlertCircle } from 'lucide-react'; import { Folder, FolderOpen, AlertCircle } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getHttpApiClient } from '@/lib/http-api-client'; import { getHttpApiClient } from '@/lib/http-api-client';
interface WorkspaceDirectory { interface WorkspaceDirectory {
@@ -74,7 +75,7 @@ export function WorkspacePickerModal({ open, onOpenChange, onSelect }: Workspace
<div className="flex-1 overflow-y-auto py-4 min-h-[200px]"> <div className="flex-1 overflow-y-auto py-4 min-h-[200px]">
{isLoading && ( {isLoading && (
<div className="flex flex-col items-center justify-center h-full gap-3"> <div className="flex flex-col items-center justify-center h-full gap-3">
<Loader2 className="w-8 h-8 text-brand-500 animate-spin" /> <Spinner size="xl" />
<p className="text-sm text-muted-foreground">Loading projects...</p> <p className="text-sm text-muted-foreground">Loading projects...</p>
</div> </div>
)} )}

View File

@@ -1,9 +1,9 @@
import type { NavigateOptions } from '@tanstack/react-router'; import type { NavigateOptions } from '@tanstack/react-router';
import { Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { formatShortcut } from '@/store/app-store'; import { formatShortcut } from '@/store/app-store';
import type { NavSection } from '../types'; import type { NavSection } from '../types';
import type { Project } from '@/lib/electron'; import type { Project } from '@/lib/electron';
import { Spinner } from '@/components/ui/spinner';
interface SidebarNavigationProps { interface SidebarNavigationProps {
currentProject: Project | null; currentProject: Project | null;
@@ -93,9 +93,10 @@ export function SidebarNavigation({
> >
<div className="relative"> <div className="relative">
{item.isLoading ? ( {item.isLoading ? (
<Loader2 <Spinner
size="md"
className={cn( className={cn(
'w-[18px] h-[18px] shrink-0 animate-spin', 'shrink-0',
isActive ? 'text-brand-500' : 'text-muted-foreground' isActive ? 'text-brand-500' : 'text-muted-foreground'
)} )}
/> />

View File

@@ -16,8 +16,8 @@ import {
Check, Check,
X, X,
ArchiveRestore, ArchiveRestore,
Loader2,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { SessionListItem } from '@/types/electron'; import type { SessionListItem } from '@/types/electron';
import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts'; import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts';
@@ -466,7 +466,7 @@ export function SessionManager({
{/* Show loading indicator if this session is running (either current session thinking or any session in runningSessions) */} {/* Show loading indicator if this session is running (either current session thinking or any session in runningSessions) */}
{(currentSessionId === session.id && isCurrentSessionThinking) || {(currentSessionId === session.id && isCurrentSessionThinking) ||
runningSessions.has(session.id) ? ( runningSessions.has(session.id) ? (
<Loader2 className="w-4 h-4 text-primary animate-spin shrink-0" /> <Spinner size="sm" className="shrink-0" />
) : ( ) : (
<MessageSquare className="w-4 h-4 text-muted-foreground shrink-0" /> <MessageSquare className="w-4 h-4 text-muted-foreground shrink-0" />
)} )}

View File

@@ -1,9 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { Slot } from '@radix-ui/react-slot'; import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority'; import { cva, type VariantProps } from 'class-variance-authority';
import { Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Spinner } from '@/components/ui/spinner';
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive active:scale-[0.98]", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive active:scale-[0.98]",
@@ -39,7 +39,7 @@ const buttonVariants = cva(
// Loading spinner component // Loading spinner component
function ButtonSpinner({ className }: { className?: string }) { function ButtonSpinner({ className }: { className?: string }) {
return <Loader2 className={cn('size-4 animate-spin', className)} aria-hidden="true" />; return <Spinner size="sm" className={className} />;
} }
function Button({ function Button({

View File

@@ -3,7 +3,8 @@ import { createLogger } from '@automaker/utils/logger';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const logger = createLogger('DescriptionImageDropZone'); const logger = createLogger('DescriptionImageDropZone');
import { ImageIcon, X, Loader2, FileText } from 'lucide-react'; import { ImageIcon, X, FileText } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { getAuthenticatedImageUrl } from '@/lib/api-fetch'; import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
@@ -431,7 +432,7 @@ export function DescriptionImageDropZone({
{/* Processing indicator */} {/* Processing indicator */}
{isProcessing && ( {isProcessing && (
<div className="flex items-center gap-2 mt-2 text-sm text-muted-foreground"> <div className="flex items-center gap-2 mt-2 text-sm text-muted-foreground">
<Loader2 className="w-4 h-4 animate-spin" /> <Spinner size="sm" />
<span>Processing files...</span> <span>Processing files...</span>
</div> </div>
)} )}

View File

@@ -3,7 +3,8 @@ import { createLogger } from '@automaker/utils/logger';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const logger = createLogger('FeatureImageUpload'); const logger = createLogger('FeatureImageUpload');
import { ImageIcon, X, Upload } from 'lucide-react'; import { ImageIcon, X } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { import {
fileToBase64, fileToBase64,
generateImageId, generateImageId,
@@ -196,7 +197,7 @@ export function FeatureImageUpload({
)} )}
> >
{isProcessing ? ( {isProcessing ? (
<Upload className="h-5 w-5 animate-spin text-muted-foreground" /> <Spinner size="md" />
) : ( ) : (
<ImageIcon className="h-5 w-5 text-muted-foreground" /> <ImageIcon className="h-5 w-5 text-muted-foreground" />
)} )}

View File

@@ -9,11 +9,11 @@ import {
FilePen, FilePen,
ChevronDown, ChevronDown,
ChevronRight, ChevronRight,
Loader2,
RefreshCw, RefreshCw,
GitBranch, GitBranch,
AlertCircle, AlertCircle,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Button } from './button'; import { Button } from './button';
import type { FileStatus } from '@/types/electron'; import type { FileStatus } from '@/types/electron';
@@ -484,7 +484,7 @@ export function GitDiffPanel({
<div className="border-t border-border"> <div className="border-t border-border">
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center gap-2 py-8 text-muted-foreground"> <div className="flex items-center justify-center gap-2 py-8 text-muted-foreground">
<Loader2 className="w-5 h-5 animate-spin" /> <Spinner size="md" />
<span className="text-sm">Loading changes...</span> <span className="text-sm">Loading changes...</span>
</div> </div>
) : error ? ( ) : error ? (

View File

@@ -3,7 +3,8 @@ import { createLogger } from '@automaker/utils/logger';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const logger = createLogger('ImageDropZone'); const logger = createLogger('ImageDropZone');
import { ImageIcon, X, Upload } from 'lucide-react'; import { ImageIcon, X } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import type { ImageAttachment } from '@/store/app-store'; import type { ImageAttachment } from '@/store/app-store';
import { import {
fileToBase64, fileToBase64,
@@ -204,7 +205,7 @@ export function ImageDropZone({
)} )}
> >
{isProcessing ? ( {isProcessing ? (
<Upload className="h-6 w-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
) : ( ) : (
<ImageIcon className="h-6 w-6 text-muted-foreground" /> <ImageIcon className="h-6 w-6 text-muted-foreground" />
)} )}

View File

@@ -1,17 +1,15 @@
import { Loader2 } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner';
interface LoadingStateProps { interface LoadingStateProps {
/** Optional custom message to display below the spinner */ /** Optional custom message to display below the spinner */
message?: string; message?: string;
/** Optional custom size class for the spinner (default: h-8 w-8) */
size?: string;
} }
export function LoadingState({ message, size = 'h-8 w-8' }: LoadingStateProps) { export function LoadingState({ message }: LoadingStateProps) {
return ( return (
<div className="flex-1 flex flex-col items-center justify-center"> <div className="flex-1 flex flex-col items-center justify-center">
<Loader2 className={`${size} animate-spin text-muted-foreground`} /> <Spinner size="xl" />
{message && <p className="mt-4 text-sm text-muted-foreground">{message}</p>} {message && <p className="mt-4 text-sm font-medium text-primary">{message}</p>}
</div> </div>
); );
} }

View File

@@ -22,8 +22,8 @@ import {
Filter, Filter,
Circle, Circle,
Play, Play,
Loader2,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import {
parseLogOutput, parseLogOutput,
@@ -148,7 +148,7 @@ function TodoListRenderer({ todos }: { todos: TodoItem[] }) {
case 'completed': case 'completed':
return <CheckCircle2 className="w-4 h-4 text-emerald-400" />; return <CheckCircle2 className="w-4 h-4 text-emerald-400" />;
case 'in_progress': case 'in_progress':
return <Loader2 className="w-4 h-4 text-amber-400 animate-spin" />; return <Spinner size="sm" />;
case 'pending': case 'pending':
return <Circle className="w-4 h-4 text-muted-foreground/70" />; return <Circle className="w-4 h-4 text-muted-foreground/70" />;
default: default:

View File

@@ -0,0 +1,32 @@
import { Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
const sizeClasses: Record<SpinnerSize, string> = {
xs: 'h-3 w-3',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-6 w-6',
xl: 'h-8 w-8',
};
interface SpinnerProps {
/** Size of the spinner */
size?: SpinnerSize;
/** Additional class names */
className?: string;
}
/**
* Themed spinner component using the primary brand color.
* Use this for all loading indicators throughout the app for consistency.
*/
export function Spinner({ size = 'md', className }: SpinnerProps) {
return (
<Loader2
className={cn(sizeClasses[size], 'animate-spin text-primary', className)}
aria-hidden="true"
/>
);
}

View File

@@ -5,7 +5,8 @@ import { createLogger } from '@automaker/utils/logger';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const logger = createLogger('TaskProgressPanel'); const logger = createLogger('TaskProgressPanel');
import { Check, Loader2, Circle, ChevronDown, ChevronRight, FileCode } from 'lucide-react'; import { Check, Circle, ChevronDown, ChevronRight, FileCode } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import type { AutoModeEvent } from '@/types/electron'; import type { AutoModeEvent } from '@/types/electron';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
@@ -260,7 +261,7 @@ export function TaskProgressPanel({
)} )}
> >
{isCompleted && <Check className="h-3.5 w-3.5" />} {isCompleted && <Check className="h-3.5 w-3.5" />}
{isActive && <Loader2 className="h-3.5 w-3.5 animate-spin" />} {isActive && <Spinner size="xs" />}
{isPending && <Circle className="h-2 w-2 fill-current opacity-50" />} {isPending && <Circle className="h-2 w-2 fill-current opacity-50" />}
</div> </div>

View File

@@ -3,6 +3,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react'; import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
@@ -449,7 +450,7 @@ export function UsagePopover() {
</div> </div>
) : !claudeUsage ? ( ) : !claudeUsage ? (
<div className="flex flex-col items-center justify-center py-8 space-y-2"> <div className="flex flex-col items-center justify-center py-8 space-y-2">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground/50" /> <Spinner size="lg" />
<p className="text-xs text-muted-foreground">Loading usage data...</p> <p className="text-xs text-muted-foreground">Loading usage data...</p>
</div> </div>
) : ( ) : (
@@ -568,7 +569,7 @@ export function UsagePopover() {
</div> </div>
) : !codexUsage ? ( ) : !codexUsage ? (
<div className="flex flex-col items-center justify-center py-8 space-y-2"> <div className="flex flex-col items-center justify-center py-8 space-y-2">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground/50" /> <Spinner size="lg" />
<p className="text-xs text-muted-foreground">Loading usage data...</p> <p className="text-xs text-muted-foreground">Loading usage data...</p>
</div> </div>
) : codexUsage.rateLimits ? ( ) : codexUsage.rateLimits ? (

View File

@@ -11,12 +11,12 @@ import {
Terminal, Terminal,
CheckCircle, CheckCircle,
XCircle, XCircle,
Loader2,
Play, Play,
File, File,
Pencil, Pencil,
Wrench, Wrench,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
@@ -236,7 +236,7 @@ export function AgentToolsView() {
> >
{isReadingFile ? ( {isReadingFile ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Reading... Reading...
</> </>
) : ( ) : (
@@ -315,7 +315,7 @@ export function AgentToolsView() {
> >
{isWritingFile ? ( {isWritingFile ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Writing... Writing...
</> </>
) : ( ) : (
@@ -383,7 +383,7 @@ export function AgentToolsView() {
> >
{isRunningCommand ? ( {isRunningCommand ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Running... Running...
</> </>
) : ( ) : (

View File

@@ -1,4 +1,5 @@
import { Bot } from 'lucide-react'; import { Bot } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
export function ThinkingIndicator() { export function ThinkingIndicator() {
return ( return (
@@ -8,20 +9,7 @@ export function ThinkingIndicator() {
</div> </div>
<div className="bg-card border border-border rounded-2xl px-4 py-3 shadow-sm"> <div className="bg-card border border-border rounded-2xl px-4 py-3 shadow-sm">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="flex items-center gap-1"> <Spinner size="sm" />
<span
className="w-2 h-2 rounded-full bg-primary animate-pulse"
style={{ animationDelay: '0ms' }}
/>
<span
className="w-2 h-2 rounded-full bg-primary animate-pulse"
style={{ animationDelay: '150ms' }}
/>
<span
className="w-2 h-2 rounded-full bg-primary animate-pulse"
style={{ animationDelay: '300ms' }}
/>
</div>
<span className="text-sm text-muted-foreground">Thinking...</span> <span className="text-sm text-muted-foreground">Thinking...</span>
</div> </div>
</div> </div>

View File

@@ -14,12 +14,12 @@ import {
RefreshCw, RefreshCw,
BarChart3, BarChart3,
FileCode, FileCode,
Loader2,
FileText, FileText,
CheckCircle, CheckCircle,
AlertCircle, AlertCircle,
ListChecks, ListChecks,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn, generateUUID } from '@/lib/utils'; import { cn, generateUUID } from '@/lib/utils';
const logger = createLogger('AnalysisView'); const logger = createLogger('AnalysisView');
@@ -742,7 +742,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
<Button onClick={runAnalysis} disabled={isAnalyzing} data-testid="analyze-project-button"> <Button onClick={runAnalysis} disabled={isAnalyzing} data-testid="analyze-project-button">
{isAnalyzing ? ( {isAnalyzing ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Analyzing... Analyzing...
</> </>
) : ( ) : (
@@ -771,7 +771,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
</div> </div>
) : isAnalyzing ? ( ) : isAnalyzing ? (
<div className="flex flex-col items-center justify-center h-full"> <div className="flex flex-col items-center justify-center h-full">
<Loader2 className="w-12 h-12 animate-spin text-primary mb-4" /> <Spinner size="xl" className="mb-4" />
<p className="text-muted-foreground">Scanning project files...</p> <p className="text-muted-foreground">Scanning project files...</p>
</div> </div>
) : projectAnalysis ? ( ) : projectAnalysis ? (
@@ -850,7 +850,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
> >
{isGeneratingSpec ? ( {isGeneratingSpec ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Generating... Generating...
</> </>
) : ( ) : (
@@ -903,7 +903,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
> >
{isGeneratingFeatureList ? ( {isGeneratingFeatureList ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Generating... Generating...
</> </>
) : ( ) : (

View File

@@ -34,7 +34,7 @@ import { pathsEqual } from '@/lib/utils';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { getBlockingDependencies } from '@automaker/dependency-resolver'; import { getBlockingDependencies } from '@automaker/dependency-resolver';
import { BoardBackgroundModal } from '@/components/dialogs/board-background-modal'; import { BoardBackgroundModal } from '@/components/dialogs/board-background-modal';
import { RefreshCw } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner';
import { useAutoMode } from '@/hooks/use-auto-mode'; import { useAutoMode } from '@/hooks/use-auto-mode';
import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts'; import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts';
import { useWindowState } from '@/hooks/use-window-state'; import { useWindowState } from '@/hooks/use-window-state';
@@ -1384,7 +1384,7 @@ export function BoardView() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex-1 flex items-center justify-center" data-testid="board-view-loading"> <div className="flex-1 flex items-center justify-center" data-testid="board-view-loading">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
</div> </div>
); );
} }

View File

@@ -1,6 +1,7 @@
import { useRef, useEffect } from 'react'; import { useRef, useEffect } from 'react';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Search, X, Loader2 } from 'lucide-react'; import { Search, X } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
interface BoardSearchBarProps { interface BoardSearchBarProps {
searchQuery: string; searchQuery: string;
@@ -75,7 +76,7 @@ export function BoardSearchBar({
title="Creating App Specification" title="Creating App Specification"
data-testid="spec-creation-badge" data-testid="spec-creation-badge"
> >
<Loader2 className="w-3 h-3 animate-spin text-brand-500 shrink-0" /> <Spinner size="xs" className="shrink-0" />
<span className="text-xs font-medium text-brand-500 whitespace-nowrap"> <span className="text-xs font-medium text-brand-500 whitespace-nowrap">
Creating spec Creating spec
</span> </span>

View File

@@ -11,16 +11,8 @@ import {
} from '@/lib/agent-context-parser'; } from '@/lib/agent-context-parser';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { AutoModeEvent } from '@/types/electron'; import type { AutoModeEvent } from '@/types/electron';
import { import { Brain, ListTodo, Sparkles, Expand, CheckCircle2, Circle, Wrench } from 'lucide-react';
Brain, import { Spinner } from '@/components/ui/spinner';
ListTodo,
Sparkles,
Expand,
CheckCircle2,
Circle,
Loader2,
Wrench,
} from 'lucide-react';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { SummaryDialog } from './summary-dialog'; import { SummaryDialog } from './summary-dialog';
import { getProviderIconForModel } from '@/components/ui/provider-icon'; import { getProviderIconForModel } from '@/components/ui/provider-icon';
@@ -338,7 +330,7 @@ export function AgentInfoPanel({
{todo.status === 'completed' ? ( {todo.status === 'completed' ? (
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" /> <CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" />
) : todo.status === 'in_progress' ? ( ) : todo.status === 'in_progress' ? (
<Loader2 className="w-2.5 h-2.5 text-[var(--status-warning)] animate-spin shrink-0" /> <Spinner size="xs" className="w-2.5 h-2.5 shrink-0" />
) : ( ) : (
<Circle className="w-2.5 h-2.5 text-muted-foreground/50 shrink-0" /> <Circle className="w-2.5 h-2.5 text-muted-foreground/50 shrink-0" />
)} )}

View File

@@ -13,7 +13,6 @@ import {
import { import {
GripVertical, GripVertical,
Edit, Edit,
Loader2,
Trash2, Trash2,
FileText, FileText,
MoreVertical, MoreVertical,
@@ -21,6 +20,7 @@ import {
ChevronUp, ChevronUp,
GitFork, GitFork,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { CountUpTimer } from '@/components/ui/count-up-timer'; import { CountUpTimer } from '@/components/ui/count-up-timer';
import { formatModelName, DEFAULT_MODEL } from '@/lib/agent-context-parser'; import { formatModelName, DEFAULT_MODEL } from '@/lib/agent-context-parser';
import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog'; import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog';
@@ -65,7 +65,7 @@ export function CardHeaderSection({
{isCurrentAutoTask && !isSelectionMode && ( {isCurrentAutoTask && !isSelectionMode && (
<div className="absolute top-2 right-2 flex items-center gap-1"> <div className="absolute top-2 right-2 flex items-center gap-1">
<div className="flex items-center justify-center gap-2 bg-[var(--status-in-progress)]/15 border border-[var(--status-in-progress)]/50 rounded-md px-2 py-0.5"> <div className="flex items-center justify-center gap-2 bg-[var(--status-in-progress)]/15 border border-[var(--status-in-progress)]/50 rounded-md px-2 py-0.5">
<Loader2 className="w-3.5 h-3.5 text-[var(--status-in-progress)] animate-spin" /> <Spinner size="xs" />
{feature.startedAt && ( {feature.startedAt && (
<CountUpTimer <CountUpTimer
startedAt={feature.startedAt} startedAt={feature.startedAt}
@@ -324,7 +324,7 @@ export function CardHeaderSection({
<div className="flex-1 min-w-0 overflow-hidden"> <div className="flex-1 min-w-0 overflow-hidden">
{feature.titleGenerating ? ( {feature.titleGenerating ? (
<div className="flex items-center gap-1.5 mb-1"> <div className="flex items-center gap-1.5 mb-1">
<Loader2 className="w-3 h-3 animate-spin text-muted-foreground" /> <Spinner size="xs" />
<span className="text-xs text-muted-foreground italic">Generating title...</span> <span className="text-xs text-muted-foreground italic">Generating title...</span>
</div> </div>
) : feature.title ? ( ) : feature.title ? (

View File

@@ -6,7 +6,8 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Loader2, List, FileText, GitBranch, ClipboardList } from 'lucide-react'; import { List, FileText, GitBranch, ClipboardList } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { LogViewer } from '@/components/ui/log-viewer'; import { LogViewer } from '@/components/ui/log-viewer';
import { GitDiffPanel } from '@/components/ui/git-diff-panel'; import { GitDiffPanel } from '@/components/ui/git-diff-panel';
@@ -353,7 +354,7 @@ export function AgentOutputModal({
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 pr-8"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 pr-8">
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
{featureStatus !== 'verified' && featureStatus !== 'waiting_approval' && ( {featureStatus !== 'verified' && featureStatus !== 'waiting_approval' && (
<Loader2 className="w-5 h-5 text-primary animate-spin" /> <Spinner size="md" />
)} )}
Agent Output Agent Output
</DialogTitle> </DialogTitle>
@@ -439,7 +440,7 @@ export function AgentOutputModal({
/> />
) : ( ) : (
<div className="flex items-center justify-center h-full text-muted-foreground"> <div className="flex items-center justify-center h-full text-muted-foreground">
<Loader2 className="w-6 h-6 animate-spin mr-2" /> <Spinner size="lg" className="mr-2" />
Loading... Loading...
</div> </div>
)} )}
@@ -457,7 +458,7 @@ export function AgentOutputModal({
> >
{isLoading && !output ? ( {isLoading && !output ? (
<div className="flex items-center justify-center h-full text-muted-foreground"> <div className="flex items-center justify-center h-full text-muted-foreground">
<Loader2 className="w-6 h-6 animate-spin mr-2" /> <Spinner size="lg" className="mr-2" />
Loading output... Loading output...
</div> </div>
) : !output ? ( ) : !output ? (

View File

@@ -11,16 +11,8 @@ import {
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { import { Wand2, Check, Plus, Pencil, Trash2, ChevronDown, ChevronRight } from 'lucide-react';
Loader2, import { Spinner } from '@/components/ui/spinner';
Wand2,
Check,
Plus,
Pencil,
Trash2,
ChevronDown,
ChevronRight,
} from 'lucide-react';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@@ -287,8 +279,7 @@ export function BacklogPlanDialog({
</div> </div>
{isGeneratingPlan && ( {isGeneratingPlan && (
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 rounded-lg p-3"> <div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 rounded-lg p-3">
<Loader2 className="w-4 h-4 animate-spin" />A plan is currently being generated in <Spinner size="sm" />A plan is currently being generated in the background...
the background...
</div> </div>
)} )}
</div> </div>
@@ -405,7 +396,7 @@ export function BacklogPlanDialog({
case 'applying': case 'applying':
return ( return (
<div className="flex flex-col items-center justify-center py-12"> <div className="flex flex-col items-center justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin text-primary mb-4" /> <Spinner size="xl" className="mb-4" />
<p className="text-muted-foreground">Applying changes...</p> <p className="text-muted-foreground">Applying changes...</p>
</div> </div>
); );
@@ -452,7 +443,7 @@ export function BacklogPlanDialog({
<Button onClick={handleGenerate} disabled={!prompt.trim() || isGeneratingPlan}> <Button onClick={handleGenerate} disabled={!prompt.trim() || isGeneratingPlan}>
{isGeneratingPlan ? ( {isGeneratingPlan ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Generating... Generating...
</> </>
) : ( ) : (

View File

@@ -10,7 +10,8 @@ import {
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { GitCommit, Loader2, Sparkles } from 'lucide-react'; import { GitCommit, Sparkles } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
@@ -209,7 +210,7 @@ export function CommitWorktreeDialog({
<Button onClick={handleCommit} disabled={isLoading || isGenerating || !message.trim()}> <Button onClick={handleCommit} disabled={isLoading || isGenerating || !message.trim()}>
{isLoading ? ( {isLoading ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Committing... Committing...
</> </>
) : ( ) : (

View File

@@ -13,7 +13,8 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { GitBranchPlus, Loader2 } from 'lucide-react'; import { GitBranchPlus } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
interface WorktreeInfo { interface WorktreeInfo {
path: string; path: string;
@@ -133,7 +134,7 @@ export function CreateBranchDialog({
<Button onClick={handleCreate} disabled={!branchName.trim() || isCreating}> <Button onClick={handleCreate} disabled={!branchName.trim() || isCreating}>
{isCreating ? ( {isCreating ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Creating... Creating...
</> </>
) : ( ) : (

View File

@@ -13,7 +13,8 @@ import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { BranchAutocomplete } from '@/components/ui/branch-autocomplete'; import { BranchAutocomplete } from '@/components/ui/branch-autocomplete';
import { GitPullRequest, Loader2, ExternalLink } from 'lucide-react'; import { GitPullRequest, ExternalLink } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -405,7 +406,7 @@ export function CreatePRDialog({
<Button onClick={handleCreate} disabled={isLoading}> <Button onClick={handleCreate} disabled={isLoading}>
{isLoading ? ( {isLoading ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Creating... Creating...
</> </>
) : ( ) : (

View File

@@ -10,7 +10,8 @@ import {
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { GitBranch, Loader2, AlertCircle } from 'lucide-react'; import { GitBranch, AlertCircle } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -216,7 +217,7 @@ export function CreateWorktreeDialog({
<Button onClick={handleCreate} disabled={isLoading || !branchName.trim()}> <Button onClick={handleCreate} disabled={isLoading || !branchName.trim()}>
{isLoading ? ( {isLoading ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Creating... Creating...
</> </>
) : ( ) : (

View File

@@ -10,7 +10,8 @@ import {
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Loader2, Trash2, AlertTriangle, FileWarning } from 'lucide-react'; import { Trash2, AlertTriangle, FileWarning } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -147,7 +148,7 @@ export function DeleteWorktreeDialog({
<Button variant="destructive" onClick={handleDelete} disabled={isLoading}> <Button variant="destructive" onClick={handleDelete} disabled={isLoading}>
{isLoading ? ( {isLoading ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Deleting... Deleting...
</> </>
) : ( ) : (

View File

@@ -10,7 +10,8 @@ import {
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Loader2, GitMerge, AlertTriangle, CheckCircle2 } from 'lucide-react'; import { GitMerge, AlertTriangle, CheckCircle2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -217,7 +218,7 @@ export function MergeWorktreeDialog({
> >
{isLoading ? ( {isLoading ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Merging... Merging...
</> </>
) : ( ) : (

View File

@@ -14,7 +14,8 @@ import { Textarea } from '@/components/ui/textarea';
import { Markdown } from '@/components/ui/markdown'; import { Markdown } from '@/components/ui/markdown';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Feature } from '@/store/app-store'; import { Feature } from '@/store/app-store';
import { Check, RefreshCw, Edit2, Eye, Loader2 } from 'lucide-react'; import { Check, RefreshCw, Edit2, Eye } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
interface PlanApprovalDialogProps { interface PlanApprovalDialogProps {
open: boolean; open: boolean;
@@ -171,7 +172,7 @@ export function PlanApprovalDialog({
</Button> </Button>
<Button variant="secondary" onClick={handleReject} disabled={isLoading}> <Button variant="secondary" onClick={handleReject} disabled={isLoading}>
{isLoading ? ( {isLoading ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
) : ( ) : (
<RefreshCw className="w-4 h-4 mr-2" /> <RefreshCw className="w-4 h-4 mr-2" />
)} )}
@@ -190,7 +191,7 @@ export function PlanApprovalDialog({
className="bg-green-600 hover:bg-green-700 text-white" className="bg-green-600 hover:bg-green-700 text-white"
> >
{isLoading ? ( {isLoading ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
) : ( ) : (
<Check className="w-4 h-4 mr-2" /> <Check className="w-4 h-4 mr-2" />
)} )}

View File

@@ -1,5 +1,6 @@
import { useState, useRef, useEffect, useCallback } from 'react'; import { useState, useRef, useEffect, useCallback } from 'react';
import { Terminal, Check, X, Loader2, ChevronDown, ChevronUp } from 'lucide-react'; import { Terminal, Check, X, ChevronDown, ChevronUp } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useAppStore, type InitScriptState } from '@/store/app-store'; import { useAppStore, type InitScriptState } from '@/store/app-store';
import { AnsiOutput } from '@/components/ui/ansi-output'; import { AnsiOutput } from '@/components/ui/ansi-output';
@@ -65,7 +66,7 @@ function SingleIndicator({
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-3 border-b border-border/50"> <div className="flex items-center justify-between p-3 border-b border-border/50">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{status === 'running' && <Loader2 className="w-4 h-4 animate-spin text-blue-500" />} {status === 'running' && <Spinner size="sm" />}
{status === 'success' && <Check className="w-4 h-4 text-green-500" />} {status === 'success' && <Check className="w-4 h-4 text-green-500" />}
{status === 'failed' && <X className="w-4 h-4 text-red-500" />} {status === 'failed' && <X className="w-4 h-4 text-red-500" />}
<span className="font-medium text-sm"> <span className="font-medium text-sm">

View File

@@ -1,6 +1,7 @@
import { useEffect, useCallback, useState, type ComponentType, type ReactNode } from 'react'; import { useEffect, useCallback, useState, type ComponentType, type ReactNode } from 'react';
import { RefreshCw } from 'lucide-react'; import { RefreshCw } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { AnthropicIcon, OpenAIIcon } from '@/components/ui/provider-icon'; import { AnthropicIcon, OpenAIIcon } from '@/components/ui/provider-icon';
@@ -90,9 +91,11 @@ function UsageItem({
className="p-1 rounded hover:bg-accent/50 transition-colors" className="p-1 rounded hover:bg-accent/50 transition-colors"
title="Refresh usage" title="Refresh usage"
> >
<RefreshCw {isLoading ? (
className={cn('w-3.5 h-3.5 text-muted-foreground', isLoading && 'animate-spin')} <Spinner size="xs" />
/> ) : (
<RefreshCw className="w-3.5 h-3.5 text-muted-foreground" />
)}
</button> </button>
</div> </div>
<div className="pl-6 space-y-2">{children}</div> <div className="pl-6 space-y-2">{children}</div>

View File

@@ -11,7 +11,7 @@ import { getModelProvider, PROVIDER_PREFIXES, stripProviderPrefix } from '@autom
import type { ModelProvider } from '@automaker/types'; import type { ModelProvider } from '@automaker/types';
import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants'; import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { RefreshCw } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner';
interface ModelSelectorProps { interface ModelSelectorProps {
selectedModel: string; // Can be ModelAlias or "cursor-{id}" selectedModel: string; // Can be ModelAlias or "cursor-{id}"
@@ -294,7 +294,7 @@ export function ModelSelector({
{/* Loading state */} {/* Loading state */}
{codexModelsLoading && dynamicCodexModels.length === 0 && ( {codexModelsLoading && dynamicCodexModels.length === 0 && (
<div className="flex items-center justify-center gap-2 p-6 text-sm text-muted-foreground"> <div className="flex items-center justify-center gap-2 p-6 text-sm text-muted-foreground">
<RefreshCw className="w-4 h-4 animate-spin" /> <Spinner size="sm" />
Loading models... Loading models...
</div> </div>
)} )}

View File

@@ -6,12 +6,12 @@ import {
ClipboardList, ClipboardList,
FileText, FileText,
ScrollText, ScrollText,
Loader2,
Check, Check,
Eye, Eye,
RefreshCw, RefreshCw,
Sparkles, Sparkles,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
@@ -236,7 +236,7 @@ export function PlanningModeSelector({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{isGenerating ? ( {isGenerating ? (
<> <>
<Loader2 className="h-4 w-4 animate-spin text-primary" /> <Spinner size="sm" />
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
Generating {mode === 'full' ? 'comprehensive spec' : 'spec'}... Generating {mode === 'full' ? 'comprehensive spec' : 'spec'}...
</span> </span>

View File

@@ -8,7 +8,8 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuLabel, DropdownMenuLabel,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { GitBranch, RefreshCw, GitBranchPlus, Check, Search } from 'lucide-react'; import { GitBranch, GitBranchPlus, Check, Search } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { WorktreeInfo, BranchInfo } from '../types'; import type { WorktreeInfo, BranchInfo } from '../types';
@@ -81,7 +82,7 @@ export function BranchSwitchDropdown({
<div className="max-h-[250px] overflow-y-auto"> <div className="max-h-[250px] overflow-y-auto">
{isLoadingBranches ? ( {isLoadingBranches ? (
<DropdownMenuItem disabled className="text-xs"> <DropdownMenuItem disabled className="text-xs">
<RefreshCw className="w-3.5 h-3.5 mr-2 animate-spin" /> <Spinner size="xs" className="mr-2" />
Loading branches... Loading branches...
</DropdownMenuItem> </DropdownMenuItem>
) : filteredBranches.length === 0 ? ( ) : filteredBranches.length === 0 ? (

View File

@@ -2,7 +2,6 @@ import { useEffect, useRef, useCallback, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
Loader2,
Terminal, Terminal,
ArrowDown, ArrowDown,
ExternalLink, ExternalLink,
@@ -12,6 +11,7 @@ import {
Clock, Clock,
GitBranch, GitBranch,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { XtermLogViewer, type XtermLogViewerRef } from '@/components/ui/xterm-log-viewer'; import { XtermLogViewer, type XtermLogViewerRef } from '@/components/ui/xterm-log-viewer';
import { useDevServerLogs } from '../hooks/use-dev-server-logs'; import { useDevServerLogs } from '../hooks/use-dev-server-logs';
@@ -183,7 +183,7 @@ export function DevServerLogsPanel({
onClick={() => fetchLogs()} onClick={() => fetchLogs()}
title="Refresh logs" title="Refresh logs"
> >
<RefreshCw className={cn('w-3.5 h-3.5', isLoading && 'animate-spin')} /> {isLoading ? <Spinner size="xs" /> : <RefreshCw className="w-3.5 h-3.5" />}
</Button> </Button>
</div> </div>
</div> </div>
@@ -234,7 +234,7 @@ export function DevServerLogsPanel({
> >
{isLoading && !logs ? ( {isLoading && !logs ? (
<div className="flex items-center justify-center h-full min-h-[300px] text-muted-foreground"> <div className="flex items-center justify-center h-full min-h-[300px] text-muted-foreground">
<Loader2 className="w-5 h-5 animate-spin mr-2" /> <Spinner size="md" className="mr-2" />
<span className="text-sm">Loading logs...</span> <span className="text-sm">Loading logs...</span>
</div> </div>
) : !logs && !isRunning ? ( ) : !logs && !isRunning ? (
@@ -245,7 +245,7 @@ export function DevServerLogsPanel({
</div> </div>
) : !logs ? ( ) : !logs ? (
<div className="flex flex-col items-center justify-center h-full min-h-[300px] text-muted-foreground p-8"> <div className="flex flex-col items-center justify-center h-full min-h-[300px] text-muted-foreground p-8">
<div className="w-8 h-8 mb-3 rounded-full border-2 border-muted-foreground/20 border-t-muted-foreground/60 animate-spin" /> <Spinner size="xl" className="mb-3" />
<p className="text-sm">Waiting for output...</p> <p className="text-sm">Waiting for output...</p>
<p className="text-xs mt-1 opacity-60"> <p className="text-xs mt-1 opacity-60">
Logs will appear as the server generates output Logs will appear as the server generates output

View File

@@ -7,7 +7,8 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { GitBranch, ChevronDown, Loader2, CircleDot, Check } from 'lucide-react'; import { GitBranch, ChevronDown, CircleDot, Check } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { WorktreeInfo } from '../types'; import type { WorktreeInfo } from '../types';
@@ -44,7 +45,7 @@ export function WorktreeMobileDropdown({
<GitBranch className="w-3.5 h-3.5 shrink-0" /> <GitBranch className="w-3.5 h-3.5 shrink-0" />
<span className="truncate">{displayBranch}</span> <span className="truncate">{displayBranch}</span>
{isActivating ? ( {isActivating ? (
<Loader2 className="w-3 h-3 animate-spin shrink-0" /> <Spinner size="xs" className="shrink-0" />
) : ( ) : (
<ChevronDown className="w-3 h-3 shrink-0 ml-auto" /> <ChevronDown className="w-3 h-3 shrink-0 ml-auto" />
)} )}
@@ -74,7 +75,7 @@ export function WorktreeMobileDropdown({
) : ( ) : (
<div className="w-3.5 h-3.5 shrink-0" /> <div className="w-3.5 h-3.5 shrink-0" />
)} )}
{isRunning && <Loader2 className="w-3 h-3 animate-spin shrink-0" />} {isRunning && <Spinner size="xs" className="shrink-0" />}
<span className={cn('font-mono text-xs truncate', isSelected && 'font-medium')}> <span className={cn('font-mono text-xs truncate', isSelected && 'font-medium')}>
{worktree.branch} {worktree.branch}
</span> </span>

View File

@@ -1,6 +1,7 @@
import type { JSX } from 'react'; import type { JSX } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { RefreshCw, Globe, Loader2, CircleDot, GitPullRequest } from 'lucide-react'; import { Globe, CircleDot, GitPullRequest } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types'; import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
@@ -197,8 +198,8 @@ export function WorktreeTab({
aria-label={worktree.branch} aria-label={worktree.branch}
data-testid={`worktree-branch-${worktree.branch}`} data-testid={`worktree-branch-${worktree.branch}`}
> >
{isRunning && <Loader2 className="w-3 h-3 animate-spin" />} {isRunning && <Spinner size="xs" />}
{isActivating && !isRunning && <RefreshCw className="w-3 h-3 animate-spin" />} {isActivating && !isRunning && <Spinner size="xs" />}
{worktree.branch} {worktree.branch}
{cardCount !== undefined && cardCount > 0 && ( {cardCount !== undefined && cardCount > 0 && (
<span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border"> <span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border">
@@ -264,8 +265,8 @@ export function WorktreeTab({
: 'Click to switch to this branch' : 'Click to switch to this branch'
} }
> >
{isRunning && <Loader2 className="w-3 h-3 animate-spin" />} {isRunning && <Spinner size="xs" />}
{isActivating && !isRunning && <RefreshCw className="w-3 h-3 animate-spin" />} {isActivating && !isRunning && <Spinner size="xs" />}
{worktree.branch} {worktree.branch}
{cardCount !== undefined && cardCount > 0 && ( {cardCount !== undefined && cardCount > 0 && (
<span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border"> <span className="inline-flex items-center justify-center h-4 min-w-[1rem] px-1 text-[10px] font-medium rounded bg-background/80 text-foreground border border-border">

View File

@@ -1,6 +1,7 @@
import { useEffect, useRef, useCallback, useState } from 'react'; import { useEffect, useRef, useCallback, useState } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { GitBranch, Plus, RefreshCw } from 'lucide-react'; import { GitBranch, Plus, RefreshCw } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn, pathsEqual } from '@/lib/utils'; import { cn, pathsEqual } from '@/lib/utils';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { getHttpApiClient } from '@/lib/http-api-client'; import { getHttpApiClient } from '@/lib/http-api-client';
@@ -285,7 +286,7 @@ export function WorktreePanel({
disabled={isLoading} disabled={isLoading}
title="Refresh worktrees" title="Refresh worktrees"
> >
<RefreshCw className={cn('w-3.5 h-3.5', isLoading && 'animate-spin')} /> {isLoading ? <Spinner size="xs" /> : <RefreshCw className="w-3.5 h-3.5" />}
</Button> </Button>
</> </>
)} )}
@@ -429,7 +430,7 @@ export function WorktreePanel({
disabled={isLoading} disabled={isLoading}
title="Refresh worktrees" title="Refresh worktrees"
> >
<RefreshCw className={cn('w-3.5 h-3.5', isLoading && 'animate-spin')} /> {isLoading ? <Spinner size="xs" /> : <RefreshCw className="w-3.5 h-3.5" />}
</Button> </Button>
</div> </div>
</> </>

View File

@@ -4,7 +4,8 @@ import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { File, Folder, FolderOpen, ChevronRight, ChevronDown, RefreshCw, Code } from 'lucide-react'; import { File, Folder, FolderOpen, ChevronRight, ChevronDown, Code } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const logger = createLogger('CodeView'); const logger = createLogger('CodeView');
@@ -206,7 +207,7 @@ export function CodeView() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex-1 flex items-center justify-center" data-testid="code-view-loading"> <div className="flex-1 flex items-center justify-center" data-testid="code-view-loading">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
</div> </div>
); );
} }

View File

@@ -12,7 +12,6 @@ import {
HeaderActionsPanelTrigger, HeaderActionsPanelTrigger,
} from '@/components/ui/header-actions-panel'; } from '@/components/ui/header-actions-panel';
import { import {
RefreshCw,
FileText, FileText,
Image as ImageIcon, Image as ImageIcon,
Trash2, Trash2,
@@ -24,9 +23,9 @@ import {
Pencil, Pencil,
FilePlus, FilePlus,
FileUp, FileUp,
Loader2,
MoreVertical, MoreVertical,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { import {
useKeyboardShortcuts, useKeyboardShortcuts,
useKeyboardShortcutsConfig, useKeyboardShortcutsConfig,
@@ -670,7 +669,7 @@ export function ContextView() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex-1 flex items-center justify-center" data-testid="context-view-loading"> <div className="flex-1 flex items-center justify-center" data-testid="context-view-loading">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
</div> </div>
); );
} }
@@ -790,7 +789,7 @@ export function ContextView() {
{isUploading && ( {isUploading && (
<div className="absolute inset-0 bg-background/80 z-50 flex items-center justify-center"> <div className="absolute inset-0 bg-background/80 z-50 flex items-center justify-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Loader2 className="w-8 h-8 animate-spin text-primary mb-2" /> <Spinner size="xl" className="mb-2" />
<span className="text-sm font-medium">Uploading {uploadingFileName}...</span> <span className="text-sm font-medium">Uploading {uploadingFileName}...</span>
</div> </div>
</div> </div>
@@ -838,7 +837,7 @@ export function ContextView() {
<span className="truncate text-sm block">{file.name}</span> <span className="truncate text-sm block">{file.name}</span>
{isGenerating ? ( {isGenerating ? (
<span className="flex items-center gap-1 text-xs text-muted-foreground"> <span className="flex items-center gap-1 text-xs text-muted-foreground">
<Loader2 className="w-3 h-3 animate-spin" /> <Spinner size="xs" />
Generating description... Generating description...
</span> </span>
) : file.description ? ( ) : file.description ? (
@@ -955,7 +954,7 @@ export function ContextView() {
</span> </span>
{generatingDescriptions.has(selectedFile.name) ? ( {generatingDescriptions.has(selectedFile.name) ? (
<div className="flex items-center gap-2 mt-1 text-sm text-muted-foreground"> <div className="flex items-center gap-2 mt-1 text-sm text-muted-foreground">
<Loader2 className="w-4 h-4 animate-spin" /> <Spinner size="sm" />
<span>Generating description with AI...</span> <span>Generating description with AI...</span>
</div> </div>
) : selectedFile.description ? ( ) : selectedFile.description ? (

View File

@@ -18,7 +18,6 @@ import {
Folder, Folder,
Star, Star,
Clock, Clock,
Loader2,
ChevronDown, ChevronDown,
MessageSquare, MessageSquare,
MoreVertical, MoreVertical,
@@ -28,6 +27,7 @@ import {
type LucideIcon, type LucideIcon,
} from 'lucide-react'; } from 'lucide-react';
import * as LucideIcons from 'lucide-react'; import * as LucideIcons from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { getAuthenticatedImageUrl } from '@/lib/api-fetch'; import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
import { import {
@@ -992,7 +992,7 @@ export function DashboardView() {
data-testid="project-opening-overlay" data-testid="project-opening-overlay"
> >
<div className="flex flex-col items-center gap-4 p-8 rounded-2xl bg-card border border-border shadow-2xl"> <div className="flex flex-col items-center gap-4 p-8 rounded-2xl bg-card border border-border shadow-2xl">
<Loader2 className="w-10 h-10 text-brand-500 animate-spin" /> <Spinner size="xl" />
<p className="text-foreground font-medium">Opening project...</p> <p className="text-foreground font-medium">Opening project...</p>
</div> </div>
</div> </div>

View File

@@ -4,7 +4,6 @@ import {
X, X,
Wand2, Wand2,
ExternalLink, ExternalLink,
Loader2,
CheckCircle, CheckCircle,
Clock, Clock,
GitPullRequest, GitPullRequest,
@@ -14,6 +13,7 @@ import {
ChevronDown, ChevronDown,
ChevronUp, ChevronUp,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { useState } from 'react'; import { useState } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
@@ -87,7 +87,7 @@ export function IssueDetailPanel({
if (isValidating) { if (isValidating) {
return ( return (
<Button variant="default" size="sm" disabled> <Button variant="default" size="sm" disabled>
<Loader2 className="h-4 w-4 mr-1 animate-spin" /> <Spinner size="sm" className="mr-1" />
Validating... Validating...
</Button> </Button>
); );
@@ -297,9 +297,7 @@ export function IssueDetailPanel({
<span className="text-sm font-medium"> <span className="text-sm font-medium">
Comments {totalCount > 0 && `(${totalCount})`} Comments {totalCount > 0 && `(${totalCount})`}
</span> </span>
{commentsLoading && ( {commentsLoading && <Spinner size="xs" />}
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
)}
{commentsExpanded ? ( {commentsExpanded ? (
<ChevronUp className="h-4 w-4 text-muted-foreground" /> <ChevronUp className="h-4 w-4 text-muted-foreground" />
) : ( ) : (
@@ -340,7 +338,7 @@ export function IssueDetailPanel({
> >
{loadingMore ? ( {loadingMore ? (
<> <>
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Loading... Loading...
</> </>
) : ( ) : (

View File

@@ -2,12 +2,12 @@ import {
Circle, Circle,
CheckCircle2, CheckCircle2,
ExternalLink, ExternalLink,
Loader2,
CheckCircle, CheckCircle,
Sparkles, Sparkles,
GitPullRequest, GitPullRequest,
User, User,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { IssueRowProps } from '../types'; import type { IssueRowProps } from '../types';
@@ -97,7 +97,7 @@ export function IssueRow({
{/* Validating indicator */} {/* Validating indicator */}
{isValidating && ( {isValidating && (
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-primary/10 text-primary border border-primary/20 animate-in fade-in duration-200"> <span className="inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-primary/10 text-primary border border-primary/20 animate-in fade-in duration-200">
<Loader2 className="h-3 w-3 animate-spin" /> <Spinner size="xs" />
Analyzing... Analyzing...
</span> </span>
)} )}

View File

@@ -1,5 +1,6 @@
import { CircleDot, RefreshCw } from 'lucide-react'; import { CircleDot, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { IssuesStateFilter } from '../types'; import type { IssuesStateFilter } from '../types';
import { IssuesFilterControls } from './issues-filter-controls'; import { IssuesFilterControls } from './issues-filter-controls';
@@ -77,7 +78,7 @@ export function IssuesListHeader({
</div> </div>
</div> </div>
<Button variant="outline" size="sm" onClick={onRefresh} disabled={refreshing}> <Button variant="outline" size="sm" onClick={onRefresh} disabled={refreshing}>
<RefreshCw className={cn('h-4 w-4', refreshing && 'animate-spin')} /> {refreshing ? <Spinner size="sm" /> : <RefreshCw className="h-4 w-4" />}
</Button> </Button>
</div> </div>

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { createLogger } from '@automaker/utils/logger'; import { createLogger } from '@automaker/utils/logger';
import { GitPullRequest, Loader2, RefreshCw, ExternalLink, GitMerge, X } from 'lucide-react'; import { GitPullRequest, RefreshCw, ExternalLink, GitMerge, X } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI, GitHubPR } from '@/lib/electron'; import { getElectronAPI, GitHubPR } from '@/lib/electron';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -86,7 +87,7 @@ export function GitHubPRsView() {
if (loading) { if (loading) {
return ( return (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> <Spinner size="xl" />
</div> </div>
); );
} }
@@ -134,7 +135,7 @@ export function GitHubPRsView() {
</div> </div>
</div> </div>
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={refreshing}> <Button variant="outline" size="sm" onClick={handleRefresh} disabled={refreshing}>
<RefreshCw className={cn('h-4 w-4', refreshing && 'animate-spin')} /> {refreshing ? <Spinner size="sm" /> : <RefreshCw className="h-4 w-4" />}
</Button> </Button>
</div> </div>

View File

@@ -17,7 +17,7 @@ import {
import { useWorktrees } from './board-view/worktree-panel/hooks'; import { useWorktrees } from './board-view/worktree-panel/hooks';
import { useAutoMode } from '@/hooks/use-auto-mode'; import { useAutoMode } from '@/hooks/use-auto-mode';
import { pathsEqual } from '@/lib/utils'; import { pathsEqual } from '@/lib/utils';
import { RefreshCw } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { createLogger } from '@automaker/utils/logger'; import { createLogger } from '@automaker/utils/logger';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -330,7 +330,7 @@ export function GraphViewPage() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex-1 flex items-center justify-center" data-testid="graph-view-loading"> <div className="flex-1 flex items-center justify-center" data-testid="graph-view-loading">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
</div> </div>
); );
} }

View File

@@ -4,7 +4,8 @@
*/ */
import { useState, useMemo, useEffect, useCallback } from 'react'; import { useState, useMemo, useEffect, useCallback } from 'react';
import { Loader2, AlertCircle, Plus, X, Sparkles, Lightbulb, Trash2 } from 'lucide-react'; import { AlertCircle, Plus, X, Sparkles, Lightbulb, Trash2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
@@ -109,7 +110,7 @@ function SuggestionCard({
)} )}
> >
{isAdding ? ( {isAdding ? (
<Loader2 className="w-4 h-4 animate-spin" /> <Spinner size="sm" />
) : ( ) : (
<> <>
<Plus className="w-4 h-4" /> <Plus className="w-4 h-4" />
@@ -153,11 +154,7 @@ function GeneratingCard({ job }: { job: GenerationJob }) {
isError ? 'bg-destructive/10 text-destructive' : 'bg-blue-500/10 text-blue-500' isError ? 'bg-destructive/10 text-destructive' : 'bg-blue-500/10 text-blue-500'
)} )}
> >
{isError ? ( {isError ? <AlertCircle className="w-5 h-5" /> : <Spinner size="md" />}
<AlertCircle className="w-5 h-5" />
) : (
<Loader2 className="w-5 h-5 animate-spin" />
)}
</div> </div>
<div> <div>
<p className="font-medium">{job.prompt.title}</p> <p className="font-medium">{job.prompt.title}</p>

View File

@@ -13,8 +13,8 @@ import {
Gauge, Gauge,
Accessibility, Accessibility,
BarChart3, BarChart3,
Loader2,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useGuidedPrompts } from '@/hooks/use-guided-prompts'; import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
import type { IdeaCategory } from '@automaker/types'; import type { IdeaCategory } from '@automaker/types';
@@ -53,7 +53,7 @@ export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps
{isLoading && ( {isLoading && (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
<span className="ml-2 text-muted-foreground">Loading categories...</span> <span className="ml-2 text-muted-foreground">Loading categories...</span>
</div> </div>
)} )}

View File

@@ -3,7 +3,8 @@
*/ */
import { useState, useMemo } from 'react'; import { useState, useMemo } from 'react';
import { ArrowLeft, Lightbulb, Loader2, CheckCircle2 } from 'lucide-react'; import { ArrowLeft, Lightbulb, CheckCircle2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useGuidedPrompts } from '@/hooks/use-guided-prompts'; import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
import { useIdeationStore } from '@/store/ideation-store'; import { useIdeationStore } from '@/store/ideation-store';
@@ -121,7 +122,7 @@ export function PromptList({ category, onBack }: PromptListProps) {
<div className="space-y-3"> <div className="space-y-3">
{isLoadingPrompts && ( {isLoadingPrompts && (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
<span className="ml-2 text-muted-foreground">Loading prompts...</span> <span className="ml-2 text-muted-foreground">Loading prompts...</span>
</div> </div>
)} )}
@@ -162,7 +163,7 @@ export function PromptList({ category, onBack }: PromptListProps) {
}`} }`}
> >
{isLoading || isGenerating ? ( {isLoading || isGenerating ? (
<Loader2 className="w-5 h-5 animate-spin" /> <Spinner size="md" />
) : isStarted ? ( ) : isStarted ? (
<CheckCircle2 className="w-5 h-5" /> <CheckCircle2 className="w-5 h-5" />
) : ( ) : (

View File

@@ -11,7 +11,8 @@ import { PromptList } from './components/prompt-list';
import { IdeationDashboard } from './components/ideation-dashboard'; import { IdeationDashboard } from './components/ideation-dashboard';
import { useGuidedPrompts } from '@/hooks/use-guided-prompts'; import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ArrowLeft, ChevronRight, Lightbulb, CheckCheck, Loader2, Trash2 } from 'lucide-react'; import { ArrowLeft, ChevronRight, Lightbulb, CheckCheck, Trash2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import type { IdeaCategory } from '@automaker/types'; import type { IdeaCategory } from '@automaker/types';
import type { IdeationMode } from '@/store/ideation-store'; import type { IdeationMode } from '@/store/ideation-store';
@@ -152,11 +153,7 @@ function IdeationHeader({
className="gap-2" className="gap-2"
disabled={isAcceptingAll} disabled={isAcceptingAll}
> >
{isAcceptingAll ? ( {isAcceptingAll ? <Spinner size="sm" /> : <CheckCheck className="w-4 h-4" />}
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<CheckCheck className="w-4 h-4" />
)}
Accept All ({acceptAllCount}) Accept All ({acceptAllCount})
</Button> </Button>
)} )}

View File

@@ -5,7 +5,8 @@ import { useAppStore, Feature } from '@/store/app-store';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Bot, Send, User, Loader2, Sparkles, FileText, ArrowLeft, CheckCircle } from 'lucide-react'; import { Bot, Send, User, Sparkles, FileText, ArrowLeft, CheckCircle } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn, generateUUID } from '@/lib/utils'; import { cn, generateUUID } from '@/lib/utils';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { Markdown } from '@/components/ui/markdown'; import { Markdown } from '@/components/ui/markdown';
@@ -491,7 +492,7 @@ export function InterviewView() {
<Card className="border border-primary/30 bg-card"> <Card className="border border-primary/30 bg-card">
<CardContent className="p-3"> <CardContent className="p-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin text-primary" /> <Spinner size="sm" />
<span className="text-sm text-primary">Generating specification...</span> <span className="text-sm text-primary">Generating specification...</span>
</div> </div>
</CardContent> </CardContent>
@@ -571,7 +572,7 @@ export function InterviewView() {
> >
{isGenerating ? ( {isGenerating ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Creating... Creating...
</> </>
) : ( ) : (

View File

@@ -24,7 +24,8 @@ import {
} from '@/lib/http-api-client'; } from '@/lib/http-api-client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { KeyRound, AlertCircle, Loader2, RefreshCw, ServerCrash } from 'lucide-react'; import { KeyRound, AlertCircle, RefreshCw, ServerCrash } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { useAuthStore } from '@/store/auth-store'; import { useAuthStore } from '@/store/auth-store';
import { useSetupStore } from '@/store/setup-store'; import { useSetupStore } from '@/store/setup-store';
@@ -349,7 +350,7 @@ export function LoginView() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background p-4"> <div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" /> <Spinner size="xl" className="mx-auto" />
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Connecting to server Connecting to server
{state.attempt > 1 ? ` (attempt ${state.attempt}/${MAX_RETRIES})` : '...'} {state.attempt > 1 ? ` (attempt ${state.attempt}/${MAX_RETRIES})` : '...'}
@@ -385,7 +386,7 @@ export function LoginView() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background p-4"> <div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" /> <Spinner size="xl" className="mx-auto" />
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{state.phase === 'checking_setup' ? 'Loading settings...' : 'Redirecting...'} {state.phase === 'checking_setup' ? 'Loading settings...' : 'Redirecting...'}
</p> </p>
@@ -447,7 +448,7 @@ export function LoginView() {
> >
{isLoggingIn ? ( {isLoggingIn ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Spinner size="sm" className="mr-2" />
Authenticating... Authenticating...
</> </>
) : ( ) : (

View File

@@ -19,6 +19,7 @@ import {
FilePlus, FilePlus,
MoreVertical, MoreVertical,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -299,7 +300,7 @@ export function MemoryView() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex-1 flex items-center justify-center" data-testid="memory-view-loading"> <div className="flex-1 flex items-center justify-center" data-testid="memory-view-loading">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
</div> </div>
); );
} }

View File

@@ -9,7 +9,8 @@ import { useLoadNotifications, useNotificationEvents } from '@/hooks/use-notific
import { getHttpApiClient } from '@/lib/http-api-client'; import { getHttpApiClient } from '@/lib/http-api-client';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Bell, Check, CheckCheck, Trash2, ExternalLink, Loader2 } from 'lucide-react'; import { Bell, Check, CheckCheck, Trash2, ExternalLink } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { useNavigate } from '@tanstack/react-router'; import { useNavigate } from '@tanstack/react-router';
import type { Notification } from '@automaker/types'; import type { Notification } from '@automaker/types';
@@ -146,7 +147,7 @@ export function NotificationsView() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex flex-1 flex-col items-center justify-center p-8"> <div className="flex flex-1 flex-col items-center justify-center p-8">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> <Spinner size="xl" />
<p className="text-muted-foreground mt-4">Loading notifications...</p> <p className="text-muted-foreground mt-4">Loading notifications...</p>
</div> </div>
); );

View File

@@ -10,9 +10,9 @@ import {
Save, Save,
RotateCcw, RotateCcw,
Trash2, Trash2,
Loader2,
PanelBottomClose, PanelBottomClose,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { apiGet, apiPut, apiDelete } from '@/lib/api-fetch'; import { apiGet, apiPut, apiDelete } from '@/lib/api-fetch';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -409,7 +409,7 @@ export function WorktreePreferencesSection({ project }: WorktreePreferencesSecti
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<Loader2 className="w-5 h-5 animate-spin text-muted-foreground" /> <Spinner size="md" />
</div> </div>
) : ( ) : (
<> <>
@@ -448,11 +448,7 @@ npm install
disabled={!scriptExists || isSaving || isDeleting} disabled={!scriptExists || isSaving || isDeleting}
className="gap-1.5 text-destructive hover:text-destructive hover:bg-destructive/10" className="gap-1.5 text-destructive hover:text-destructive hover:bg-destructive/10"
> >
{isDeleting ? ( {isDeleting ? <Spinner size="xs" /> : <Trash2 className="w-3.5 h-3.5" />}
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : (
<Trash2 className="w-3.5 h-3.5" />
)}
Delete Delete
</Button> </Button>
<Button <Button
@@ -461,11 +457,7 @@ npm install
disabled={!hasChanges || isSaving || isDeleting} disabled={!hasChanges || isSaving || isDeleting}
className="gap-1.5" className="gap-1.5"
> >
{isSaving ? ( {isSaving ? <Spinner size="xs" /> : <Save className="w-3.5 h-3.5" />}
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : (
<Save className="w-3.5 h-3.5" />
)}
Save Save
</Button> </Button>
</div> </div>

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { createLogger } from '@automaker/utils/logger'; import { createLogger } from '@automaker/utils/logger';
import { Bot, Folder, Loader2, RefreshCw, Square, Activity, FileText } from 'lucide-react'; import { Bot, Folder, RefreshCw, Square, Activity, FileText } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI, RunningAgent } from '@/lib/electron'; import { getElectronAPI, RunningAgent } from '@/lib/electron';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -146,7 +147,7 @@ export function RunningAgentsView() {
if (loading) { if (loading) {
return ( return (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> <Spinner size="xl" />
</div> </div>
); );
} }
@@ -169,7 +170,11 @@ export function RunningAgentsView() {
</div> </div>
</div> </div>
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={refreshing}> <Button variant="outline" size="sm" onClick={handleRefresh} disabled={refreshing}>
<RefreshCw className={cn('h-4 w-4 mr-2', refreshing && 'animate-spin')} /> {refreshing ? (
<Spinner size="sm" className="mr-2" />
) : (
<RefreshCw className="h-4 w-4 mr-2" />
)}
Refresh Refresh
</Button> </Button>
</div> </div>

View File

@@ -11,6 +11,7 @@ import {
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { LogOut, User, Code2, RefreshCw } from 'lucide-react'; import { LogOut, User, Code2, RefreshCw } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { logout } from '@/lib/http-api-client'; import { logout } from '@/lib/http-api-client';
import { useAuthStore } from '@/store/auth-store'; import { useAuthStore } from '@/store/auth-store';
@@ -143,7 +144,7 @@ export function AccountSection() {
disabled={isRefreshing || isLoadingEditors} disabled={isRefreshing || isLoadingEditors}
className="shrink-0 h-9 w-9" className="shrink-0 h-9 w-9"
> >
<RefreshCw className={cn('w-4 h-4', isRefreshing && 'animate-spin')} /> {isRefreshing ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>

View File

@@ -1,7 +1,8 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { AlertCircle, CheckCircle2, Eye, EyeOff, Loader2, Zap } from 'lucide-react'; import { AlertCircle, CheckCircle2, Eye, EyeOff, Zap } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import type { ProviderConfig } from '@/config/api-providers'; import type { ProviderConfig } from '@/config/api-providers';
interface ApiKeyFieldProps { interface ApiKeyFieldProps {
@@ -70,7 +71,7 @@ export function ApiKeyField({ config }: ApiKeyFieldProps) {
> >
{testButton.loading ? ( {testButton.loading ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Testing... Testing...
</> </>
) : ( ) : (

View File

@@ -1,7 +1,8 @@
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { useSetupStore } from '@/store/setup-store'; import { useSetupStore } from '@/store/setup-store';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Key, CheckCircle2, Trash2, Loader2 } from 'lucide-react'; import { Key, CheckCircle2, Trash2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { ApiKeyField } from './api-key-field'; import { ApiKeyField } from './api-key-field';
import { buildProviderConfigs } from '@/config/api-providers'; import { buildProviderConfigs } from '@/config/api-providers';
import { SecurityNotice } from './security-notice'; import { SecurityNotice } from './security-notice';
@@ -142,7 +143,7 @@ export function ApiKeysSection() {
data-testid="delete-anthropic-key" data-testid="delete-anthropic-key"
> >
{isDeletingAnthropicKey ? ( {isDeletingAnthropicKey ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
) : ( ) : (
<Trash2 className="w-4 h-4 mr-2" /> <Trash2 className="w-4 h-4 mr-2" />
)} )}
@@ -159,7 +160,7 @@ export function ApiKeysSection() {
data-testid="delete-openai-key" data-testid="delete-openai-key"
> >
{isDeletingOpenaiKey ? ( {isDeletingOpenaiKey ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
) : ( ) : (
<Trash2 className="w-4 h-4 mr-2" /> <Trash2 className="w-4 h-4 mr-2" />
)} )}

View File

@@ -4,6 +4,7 @@ import { getElectronAPI } from '@/lib/electron';
import { useSetupStore } from '@/store/setup-store'; import { useSetupStore } from '@/store/setup-store';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { RefreshCw, AlertCircle } from 'lucide-react'; import { RefreshCw, AlertCircle } from 'lucide-react';
const ERROR_NO_API = 'Claude usage API not available'; const ERROR_NO_API = 'Claude usage API not available';
@@ -178,7 +179,7 @@ export function ClaudeUsageSection() {
data-testid="refresh-claude-usage" data-testid="refresh-claude-usage"
title={CLAUDE_REFRESH_LABEL} title={CLAUDE_REFRESH_LABEL}
> >
<RefreshCw className={cn('w-4 h-4', isLoading && 'animate-spin')} /> {isLoading ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground/80 ml-12">{CLAUDE_USAGE_SUBTITLE}</p> <p className="text-sm text-muted-foreground/80 ml-12">{CLAUDE_USAGE_SUBTITLE}</p>

View File

@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react'; import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { CliStatus } from '../shared/types'; import type { CliStatus } from '../shared/types';
@@ -172,7 +173,7 @@ export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: C
'transition-all duration-200' 'transition-all duration-200'
)} )}
> >
<RefreshCw className={cn('w-4 h-4', isChecking && 'animate-spin')} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground/80 ml-12"> <p className="text-sm text-muted-foreground/80 ml-12">

View File

@@ -1,5 +1,6 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { CheckCircle2, AlertCircle, RefreshCw } from 'lucide-react'; import { CheckCircle2, AlertCircle, RefreshCw } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { CliStatus } from '../shared/types'; import type { CliStatus } from '../shared/types';
@@ -56,7 +57,7 @@ export function CliStatusCard({
'transition-all duration-200' 'transition-all duration-200'
)} )}
> >
<RefreshCw className={cn('w-4 h-4', isChecking && 'animate-spin')} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground/80 ml-12">{description}</p> <p className="text-sm text-muted-foreground/80 ml-12">{description}</p>

View File

@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react'; import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { CliStatus } from '../shared/types'; import type { CliStatus } from '../shared/types';
@@ -165,7 +166,7 @@ export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: Cl
'transition-all duration-200' 'transition-all duration-200'
)} )}
> >
<RefreshCw className={cn('w-4 h-4', isChecking && 'animate-spin')} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground/80 ml-12"> <p className="text-sm text-muted-foreground/80 ml-12">

View File

@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react'; import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { CursorIcon } from '@/components/ui/provider-icon'; import { CursorIcon } from '@/components/ui/provider-icon';
@@ -290,7 +291,7 @@ export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStat
'transition-all duration-200' 'transition-all duration-200'
)} )}
> >
<RefreshCw className={cn('w-4 h-4', isChecking && 'animate-spin')} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground/80 ml-12"> <p className="text-sm text-muted-foreground/80 ml-12">

View File

@@ -1,5 +1,6 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Spinner } from '@/components/ui/spinner';
import { CheckCircle2, AlertCircle, RefreshCw, Bot, Cloud } from 'lucide-react'; import { CheckCircle2, AlertCircle, RefreshCw, Bot, Cloud } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { CliStatus } from '../shared/types'; import type { CliStatus } from '../shared/types';
@@ -221,7 +222,7 @@ export function OpencodeCliStatus({
'transition-all duration-200' 'transition-all duration-200'
)} )}
> >
<RefreshCw className={cn('w-4 h-4', isChecking && 'animate-spin')} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground/80 ml-12"> <p className="text-sm text-muted-foreground/80 ml-12">

View File

@@ -1,6 +1,7 @@
// @ts-nocheck // @ts-nocheck
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { RefreshCw, AlertCircle } from 'lucide-react'; import { RefreshCw, AlertCircle } from 'lucide-react';
import { OpenAIIcon } from '@/components/ui/provider-icon'; import { OpenAIIcon } from '@/components/ui/provider-icon';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@@ -168,7 +169,7 @@ export function CodexUsageSection() {
data-testid="refresh-codex-usage" data-testid="refresh-codex-usage"
title={CODEX_REFRESH_LABEL} title={CODEX_REFRESH_LABEL}
> >
<RefreshCw className={cn('w-4 h-4', isLoading && 'animate-spin')} /> {isLoading ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground/80 ml-12">{CODEX_USAGE_SUBTITLE}</p> <p className="text-sm text-muted-foreground/80 ml-12">{CODEX_USAGE_SUBTITLE}</p>

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import {
History, History,
@@ -184,7 +185,11 @@ export function EventHistoryView() {
</p> </p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={loadEvents} disabled={loading}> <Button variant="outline" size="sm" onClick={loadEvents} disabled={loading}>
<RefreshCw className={cn('w-4 h-4 mr-2', loading && 'animate-spin')} /> {loading ? (
<Spinner size="sm" className="mr-2" />
) : (
<RefreshCw className="w-4 h-4 mr-2" />
)}
Refresh Refresh
</Button> </Button>
{events.length > 0 && ( {events.length > 0 && (

View File

@@ -1,4 +1,5 @@
import { ChevronDown, ChevronRight, Code, Pencil, Trash2, PlayCircle, Loader2 } from 'lucide-react'; import { ChevronDown, ChevronRight, Code, Pencil, Trash2, PlayCircle } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
@@ -111,7 +112,7 @@ export function MCPServerCard({
className="h-8 px-2" className="h-8 px-2"
> >
{testState?.status === 'testing' ? ( {testState?.status === 'testing' ? (
<Loader2 className="w-4 h-4 animate-spin" /> <Spinner size="sm" />
) : ( ) : (
<PlayCircle className="w-4 h-4" /> <PlayCircle className="w-4 h-4" />
)} )}

View File

@@ -1,5 +1,6 @@
import { Plug, RefreshCw, Download, Code, FileJson, Plus } from 'lucide-react'; import { Plug, RefreshCw, Download, Code, FileJson, Plus } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
interface MCPServerHeaderProps { interface MCPServerHeaderProps {
@@ -43,7 +44,7 @@ export function MCPServerHeader({
disabled={isRefreshing} disabled={isRefreshing}
data-testid="refresh-mcp-servers-button" data-testid="refresh-mcp-servers-button"
> >
<RefreshCw className={cn('w-4 h-4', isRefreshing && 'animate-spin')} /> {isRefreshing ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
{hasServers && ( {hasServers && (
<> <>

View File

@@ -1,4 +1,5 @@
import { Terminal, Globe, Loader2, CheckCircle2, XCircle } from 'lucide-react'; import { Terminal, Globe, CheckCircle2, XCircle } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import type { ServerType, ServerTestState } from './types'; import type { ServerType, ServerTestState } from './types';
import { SENSITIVE_PARAM_PATTERNS } from './constants'; import { SENSITIVE_PARAM_PATTERNS } from './constants';
@@ -40,7 +41,7 @@ export function getServerIcon(type: ServerType = 'stdio') {
export function getTestStatusIcon(status: ServerTestState['status']) { export function getTestStatusIcon(status: ServerTestState['status']) {
switch (status) { switch (status) {
case 'testing': case 'testing':
return <Loader2 className="w-4 h-4 animate-spin text-brand-500" />; return <Spinner size="sm" />;
case 'success': case 'success':
return <CheckCircle2 className="w-4 h-4 text-green-500" />; return <CheckCircle2 className="w-4 h-4 text-green-500" />;
case 'error': case 'error':

View File

@@ -14,16 +14,8 @@ import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import { Bot, RefreshCw, Users, ExternalLink, Globe, FolderOpen, Sparkles } from 'lucide-react';
Bot, import { Spinner } from '@/components/ui/spinner';
RefreshCw,
Loader2,
Users,
ExternalLink,
Globe,
FolderOpen,
Sparkles,
} from 'lucide-react';
import { useSubagents } from './hooks/use-subagents'; import { useSubagents } from './hooks/use-subagents';
import { useSubagentsSettings } from './hooks/use-subagents-settings'; import { useSubagentsSettings } from './hooks/use-subagents-settings';
import { SubagentCard } from './subagent-card'; import { SubagentCard } from './subagent-card';
@@ -178,11 +170,7 @@ export function SubagentsSection() {
title="Refresh agents from disk" title="Refresh agents from disk"
className="gap-1.5 h-7 px-2 text-xs" className="gap-1.5 h-7 px-2 text-xs"
> >
{isLoadingAgents ? ( {isLoadingAgents ? <Spinner size="xs" /> : <RefreshCw className="h-3.5 w-3.5" />}
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<RefreshCw className="h-3.5 w-3.5" />
)}
Refresh Refresh
</Button> </Button>
</div> </div>

View File

@@ -4,6 +4,7 @@ import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { Shield, ShieldCheck, ShieldAlert, ChevronDown, Copy, Check } from 'lucide-react'; import { Shield, ShieldCheck, ShieldAlert, ChevronDown, Copy, Check } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { CursorStatus } from '../hooks/use-cursor-status'; import type { CursorStatus } from '../hooks/use-cursor-status';
import type { PermissionsData } from '../hooks/use-cursor-permissions'; import type { PermissionsData } from '../hooks/use-cursor-permissions';
@@ -118,7 +119,7 @@ export function CursorPermissionsSection({
{isLoadingPermissions ? ( {isLoadingPermissions ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<div className="animate-spin w-6 h-6 border-2 border-brand-500 border-t-transparent rounded-full" /> <Spinner size="lg" />
</div> </div>
) : ( ) : (
<> <>

View File

@@ -9,7 +9,8 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Terminal, Cloud, Cpu, Brain, Github, Loader2, KeyRound, ShieldCheck } from 'lucide-react'; import { Terminal, Cloud, Cpu, Brain, Github, KeyRound, ShieldCheck } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import type { import type {
@@ -500,7 +501,7 @@ export function OpencodeModelConfiguration({
</p> </p>
{isLoadingDynamicModels && ( {isLoadingDynamicModels && (
<div className="flex items-center gap-1.5 text-xs text-muted-foreground"> <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<Loader2 className="w-3 h-3 animate-spin" /> <Spinner size="xs" />
<span>Discovering...</span> <span>Discovering...</span>
</div> </div>
)} )}

View File

@@ -1,6 +1,7 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Download, Loader2, AlertCircle } from 'lucide-react'; import { Download, AlertCircle } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { CopyableCommandField } from './copyable-command-field'; import { CopyableCommandField } from './copyable-command-field';
import { TerminalOutput } from './terminal-output'; import { TerminalOutput } from './terminal-output';
@@ -59,7 +60,7 @@ export function CliInstallationCard({
> >
{isInstalling ? ( {isInstalling ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Installing... Installing...
</> </>
) : ( ) : (

View File

@@ -1,4 +1,5 @@
import { CheckCircle2, XCircle, Loader2, AlertCircle } from 'lucide-react'; import { CheckCircle2, XCircle, AlertCircle } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
interface StatusBadgeProps { interface StatusBadgeProps {
status: status:
@@ -34,7 +35,7 @@ export function StatusBadge({ status, label }: StatusBadgeProps) {
}; };
case 'checking': case 'checking':
return { return {
icon: <Loader2 className="w-4 h-4 animate-spin" />, icon: <Spinner size="sm" />,
className: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20', className: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20',
}; };
case 'unverified': case 'unverified':

View File

@@ -14,7 +14,6 @@ import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { import {
CheckCircle2, CheckCircle2,
Loader2,
Key, Key,
ArrowRight, ArrowRight,
ArrowLeft, ArrowLeft,
@@ -27,6 +26,7 @@ import {
XCircle, XCircle,
Trash2, Trash2,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { StatusBadge, TerminalOutput } from '../components'; import { StatusBadge, TerminalOutput } from '../components';
import { useCliStatus, useCliInstallation, useTokenSave } from '../hooks'; import { useCliStatus, useCliInstallation, useTokenSave } from '../hooks';
@@ -330,7 +330,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
Authentication Methods Authentication Methods
</CardTitle> </CardTitle>
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<CardDescription> <CardDescription>
@@ -412,7 +412,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
> >
{isInstalling ? ( {isInstalling ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Installing... Installing...
</> </>
) : ( ) : (
@@ -435,7 +435,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
{/* CLI Verification Status */} {/* CLI Verification Status */}
{cliVerificationStatus === 'verifying' && ( {cliVerificationStatus === 'verifying' && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<div> <div>
<p className="font-medium text-foreground">Verifying CLI authentication...</p> <p className="font-medium text-foreground">Verifying CLI authentication...</p>
<p className="text-sm text-muted-foreground">Running a test query</p> <p className="text-sm text-muted-foreground">Running a test query</p>
@@ -494,7 +494,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
> >
{cliVerificationStatus === 'verifying' ? ( {cliVerificationStatus === 'verifying' ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Verifying... Verifying...
</> </>
) : cliVerificationStatus === 'error' ? ( ) : cliVerificationStatus === 'error' ? (
@@ -574,7 +574,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
> >
{isSavingApiKey ? ( {isSavingApiKey ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Saving... Saving...
</> </>
) : ( ) : (
@@ -589,11 +589,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
className="border-red-500/50 text-red-500 hover:bg-red-500/10 hover:text-red-400" className="border-red-500/50 text-red-500 hover:bg-red-500/10 hover:text-red-400"
data-testid="delete-anthropic-key-button" data-testid="delete-anthropic-key-button"
> >
{isDeletingApiKey ? ( {isDeletingApiKey ? <Spinner size="sm" /> : <Trash2 className="w-4 h-4" />}
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</Button> </Button>
)} )}
</div> </div>
@@ -602,7 +598,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
{/* API Key Verification Status */} {/* API Key Verification Status */}
{apiKeyVerificationStatus === 'verifying' && ( {apiKeyVerificationStatus === 'verifying' && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<div> <div>
<p className="font-medium text-foreground">Verifying API key...</p> <p className="font-medium text-foreground">Verifying API key...</p>
<p className="text-sm text-muted-foreground">Running a test query</p> <p className="text-sm text-muted-foreground">Running a test query</p>
@@ -642,7 +638,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
> >
{apiKeyVerificationStatus === 'verifying' ? ( {apiKeyVerificationStatus === 'verifying' ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Verifying... Verifying...
</> </>
) : apiKeyVerificationStatus === 'error' ? ( ) : apiKeyVerificationStatus === 'error' ? (

View File

@@ -14,7 +14,6 @@ import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { import {
CheckCircle2, CheckCircle2,
Loader2,
Key, Key,
ArrowRight, ArrowRight,
ArrowLeft, ArrowLeft,
@@ -27,6 +26,7 @@ import {
XCircle, XCircle,
Trash2, Trash2,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { StatusBadge, TerminalOutput } from '../components'; import { StatusBadge, TerminalOutput } from '../components';
import { useCliStatus, useCliInstallation, useTokenSave } from '../hooks'; import { useCliStatus, useCliInstallation, useTokenSave } from '../hooks';
@@ -332,7 +332,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
Authentication Methods Authentication Methods
</CardTitle> </CardTitle>
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<CardDescription>Choose one of the following methods to authenticate:</CardDescription> <CardDescription>Choose one of the following methods to authenticate:</CardDescription>
@@ -408,7 +408,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
> >
{isInstalling ? ( {isInstalling ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Installing... Installing...
</> </>
) : ( ) : (
@@ -427,7 +427,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
{cliVerificationStatus === 'verifying' && ( {cliVerificationStatus === 'verifying' && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<div> <div>
<p className="font-medium text-foreground">Verifying CLI authentication...</p> <p className="font-medium text-foreground">Verifying CLI authentication...</p>
<p className="text-sm text-muted-foreground">Running a test query</p> <p className="text-sm text-muted-foreground">Running a test query</p>
@@ -605,7 +605,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
> >
{cliVerificationStatus === 'verifying' ? ( {cliVerificationStatus === 'verifying' ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Verifying... Verifying...
</> </>
) : cliVerificationStatus === 'error' ? ( ) : cliVerificationStatus === 'error' ? (
@@ -681,7 +681,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
> >
{isSavingApiKey ? ( {isSavingApiKey ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Saving... Saving...
</> </>
) : ( ) : (
@@ -696,11 +696,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
className="border-red-500/50 text-red-500 hover:bg-red-500/10 hover:text-red-400" className="border-red-500/50 text-red-500 hover:bg-red-500/10 hover:text-red-400"
data-testid={config.testIds.deleteApiKeyButton} data-testid={config.testIds.deleteApiKeyButton}
> >
{isDeletingApiKey ? ( {isDeletingApiKey ? <Spinner size="sm" /> : <Trash2 className="w-4 h-4" />}
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</Button> </Button>
)} )}
</div> </div>
@@ -708,7 +704,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
{apiKeyVerificationStatus === 'verifying' && ( {apiKeyVerificationStatus === 'verifying' && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<div> <div>
<p className="font-medium text-foreground">Verifying API key...</p> <p className="font-medium text-foreground">Verifying API key...</p>
<p className="text-sm text-muted-foreground">Running a test query</p> <p className="text-sm text-muted-foreground">Running a test query</p>
@@ -767,7 +763,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
> >
{apiKeyVerificationStatus === 'verifying' ? ( {apiKeyVerificationStatus === 'verifying' ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Verifying... Verifying...
</> </>
) : apiKeyVerificationStatus === 'error' ? ( ) : apiKeyVerificationStatus === 'error' ? (

View File

@@ -7,7 +7,6 @@ import { useSetupStore } from '@/store/setup-store';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { import {
CheckCircle2, CheckCircle2,
Loader2,
ArrowRight, ArrowRight,
ArrowLeft, ArrowLeft,
ExternalLink, ExternalLink,
@@ -16,6 +15,7 @@ import {
AlertTriangle, AlertTriangle,
XCircle, XCircle,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { StatusBadge } from '../components'; import { StatusBadge } from '../components';
import { CursorIcon } from '@/components/ui/provider-icon'; import { CursorIcon } from '@/components/ui/provider-icon';
@@ -204,7 +204,7 @@ export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{getStatusBadge()} {getStatusBadge()}
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
</div> </div>
@@ -318,7 +318,7 @@ export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps
> >
{isLoggingIn ? ( {isLoggingIn ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Waiting for login... Waiting for login...
</> </>
) : ( ) : (
@@ -332,7 +332,7 @@ export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps
{/* Loading State */} {/* Loading State */}
{isChecking && ( {isChecking && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<div> <div>
<p className="font-medium text-foreground">Checking Cursor CLI status...</p> <p className="font-medium text-foreground">Checking Cursor CLI status...</p>
</div> </div>

View File

@@ -6,7 +6,6 @@ import { useSetupStore } from '@/store/setup-store';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { import {
CheckCircle2, CheckCircle2,
Loader2,
ArrowRight, ArrowRight,
ArrowLeft, ArrowLeft,
ExternalLink, ExternalLink,
@@ -16,6 +15,7 @@ import {
Github, Github,
XCircle, XCircle,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { StatusBadge } from '../components'; import { StatusBadge } from '../components';
@@ -116,7 +116,7 @@ export function GitHubSetupStep({ onNext, onBack, onSkip }: GitHubSetupStepProps
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{getStatusBadge()} {getStatusBadge()}
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
</div> </div>
@@ -252,7 +252,7 @@ export function GitHubSetupStep({ onNext, onBack, onSkip }: GitHubSetupStepProps
{/* Loading State */} {/* Loading State */}
{isChecking && ( {isChecking && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<div> <div>
<p className="font-medium text-foreground">Checking GitHub CLI status...</p> <p className="font-medium text-foreground">Checking GitHub CLI status...</p>
</div> </div>

View File

@@ -7,7 +7,6 @@ import { useSetupStore } from '@/store/setup-store';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { import {
CheckCircle2, CheckCircle2,
Loader2,
ArrowRight, ArrowRight,
ArrowLeft, ArrowLeft,
ExternalLink, ExternalLink,
@@ -17,6 +16,7 @@ import {
XCircle, XCircle,
Terminal, Terminal,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { StatusBadge } from '../components'; import { StatusBadge } from '../components';
@@ -204,7 +204,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{getStatusBadge()} {getStatusBadge()}
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
</div> </div>
@@ -316,7 +316,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
> >
{isLoggingIn ? ( {isLoggingIn ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Waiting for login... Waiting for login...
</> </>
) : ( ) : (
@@ -330,7 +330,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
{/* Loading State */} {/* Loading State */}
{isChecking && ( {isChecking && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<div> <div>
<p className="font-medium text-foreground">Checking OpenCode CLI status...</p> <p className="font-medium text-foreground">Checking OpenCode CLI status...</p>
</div> </div>

View File

@@ -17,7 +17,6 @@ import {
ArrowRight, ArrowRight,
ArrowLeft, ArrowLeft,
CheckCircle2, CheckCircle2,
Loader2,
Key, Key,
ExternalLink, ExternalLink,
Copy, Copy,
@@ -29,6 +28,7 @@ import {
Terminal, Terminal,
AlertCircle, AlertCircle,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon } from '@/components/ui/provider-icon'; import { AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon } from '@/components/ui/provider-icon';
@@ -240,7 +240,7 @@ function ClaudeContent() {
onClick={checkStatus} onClick={checkStatus}
disabled={isChecking || isVerifying} disabled={isChecking || isVerifying}
> >
<RefreshCw className={`w-4 h-4 ${isChecking || isVerifying ? 'animate-spin' : ''}`} /> {isChecking || isVerifying ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<CardDescription> <CardDescription>
@@ -278,7 +278,7 @@ function ClaudeContent() {
{/* Checking/Verifying State */} {/* Checking/Verifying State */}
{(isChecking || isVerifying) && ( {(isChecking || isVerifying) && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<p className="font-medium text-foreground"> <p className="font-medium text-foreground">
{isChecking ? 'Checking Claude CLI status...' : 'Verifying authentication...'} {isChecking ? 'Checking Claude CLI status...' : 'Verifying authentication...'}
</p> </p>
@@ -322,7 +322,7 @@ function ClaudeContent() {
> >
{isInstalling ? ( {isInstalling ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Installing... Installing...
</> </>
) : ( ) : (
@@ -417,11 +417,7 @@ function ClaudeContent() {
disabled={isSavingApiKey || !apiKey.trim()} disabled={isSavingApiKey || !apiKey.trim()}
className="flex-1 bg-brand-500 hover:bg-brand-600 text-white" className="flex-1 bg-brand-500 hover:bg-brand-600 text-white"
> >
{isSavingApiKey ? ( {isSavingApiKey ? <Spinner size="sm" /> : 'Save API Key'}
<Loader2 className="w-4 h-4 animate-spin" />
) : (
'Save API Key'
)}
</Button> </Button>
{hasApiKey && ( {hasApiKey && (
<Button <Button
@@ -431,7 +427,7 @@ function ClaudeContent() {
className="border-red-500/50 text-red-500 hover:bg-red-500/10" className="border-red-500/50 text-red-500 hover:bg-red-500/10"
> >
{isDeletingApiKey ? ( {isDeletingApiKey ? (
<Loader2 className="w-4 h-4 animate-spin" /> <Spinner size="sm" />
) : ( ) : (
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
)} )}
@@ -553,7 +549,7 @@ function CursorContent() {
Cursor CLI Status Cursor CLI Status
</CardTitle> </CardTitle>
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<CardDescription> <CardDescription>
@@ -658,7 +654,7 @@ function CursorContent() {
> >
{isLoggingIn ? ( {isLoggingIn ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Waiting for login... Waiting for login...
</> </>
) : ( ) : (
@@ -671,7 +667,7 @@ function CursorContent() {
{isChecking && ( {isChecking && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<p className="font-medium text-foreground">Checking Cursor CLI status...</p> <p className="font-medium text-foreground">Checking Cursor CLI status...</p>
</div> </div>
)} )}
@@ -807,7 +803,7 @@ function CodexContent() {
Codex CLI Status Codex CLI Status
</CardTitle> </CardTitle>
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<CardDescription> <CardDescription>
@@ -915,7 +911,7 @@ function CodexContent() {
> >
{isLoggingIn ? ( {isLoggingIn ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Waiting for login... Waiting for login...
</> </>
) : ( ) : (
@@ -958,7 +954,7 @@ function CodexContent() {
disabled={isSaving || !apiKey.trim()} disabled={isSaving || !apiKey.trim()}
className="w-full bg-brand-500 hover:bg-brand-600 text-white" className="w-full bg-brand-500 hover:bg-brand-600 text-white"
> >
{isSaving ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Save API Key'} {isSaving ? <Spinner size="sm" /> : 'Save API Key'}
</Button> </Button>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
@@ -968,7 +964,7 @@ function CodexContent() {
{isChecking && ( {isChecking && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<p className="font-medium text-foreground">Checking Codex CLI status...</p> <p className="font-medium text-foreground">Checking Codex CLI status...</p>
</div> </div>
)} )}
@@ -1082,7 +1078,7 @@ function OpencodeContent() {
OpenCode CLI Status OpenCode CLI Status
</CardTitle> </CardTitle>
<Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}> <Button variant="ghost" size="sm" onClick={checkStatus} disabled={isChecking}>
<RefreshCw className={`w-4 h-4 ${isChecking ? 'animate-spin' : ''}`} /> {isChecking ? <Spinner size="sm" /> : <RefreshCw className="w-4 h-4" />}
</Button> </Button>
</div> </div>
<CardDescription> <CardDescription>
@@ -1191,7 +1187,7 @@ function OpencodeContent() {
> >
{isLoggingIn ? ( {isLoggingIn ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Waiting for login... Waiting for login...
</> </>
) : ( ) : (
@@ -1204,7 +1200,7 @@ function OpencodeContent() {
{isChecking && ( {isChecking && (
<div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center gap-3 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<p className="font-medium text-foreground">Checking OpenCode CLI status...</p> <p className="font-medium text-foreground">Checking OpenCode CLI status...</p>
</div> </div>
)} )}
@@ -1416,7 +1412,7 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
); );
case 'verifying': case 'verifying':
return ( return (
<Loader2 className="w-3 h-3 text-blue-500 absolute -top-1 -right-1.5 bg-background rounded-full animate-spin" /> <Spinner size="xs" className="absolute -top-1 -right-1.5 bg-background rounded-full" />
); );
case 'installed_not_auth': case 'installed_not_auth':
return ( return (
@@ -1436,7 +1432,7 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
{isInitialChecking && ( {isInitialChecking && (
<div className="flex items-center justify-center gap-2 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20"> <div className="flex items-center justify-center gap-2 p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> <Spinner size="md" />
<p className="font-medium text-foreground">Checking provider status...</p> <p className="font-medium text-foreground">Checking provider status...</p>
</div> </div>
)} )}

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { RefreshCw } from 'lucide-react';
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { Spinner } from '@/components/ui/spinner';
// Extracted hooks // Extracted hooks
import { useSpecLoading, useSpecSave, useSpecGeneration } from './spec-view/hooks'; import { useSpecLoading, useSpecSave, useSpecGeneration } from './spec-view/hooks';
@@ -86,7 +86,7 @@ export function SpecView() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex-1 flex items-center justify-center" data-testid="spec-view-loading"> <div className="flex-1 flex items-center justify-center" data-testid="spec-view-loading">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" /> <Spinner size="lg" />
</div> </div>
); );
} }

View File

@@ -1,5 +1,6 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { FileText, FilePlus2, Loader2 } from 'lucide-react'; import { FileText, FilePlus2 } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { PHASE_LABELS } from '../constants'; import { PHASE_LABELS } from '../constants';
interface SpecEmptyStateProps { interface SpecEmptyStateProps {
@@ -36,7 +37,7 @@ export function SpecEmptyState({
{isProcessing && ( {isProcessing && (
<div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-linear-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md"> <div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-linear-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md">
<div className="relative"> <div className="relative">
<Loader2 className="w-5 h-5 animate-spin text-primary shrink-0" /> <Spinner size="md" className="shrink-0" />
<div className="absolute inset-0 w-5 h-5 animate-ping text-primary/20" /> <div className="absolute inset-0 w-5 h-5 animate-ping text-primary/20" />
</div> </div>
<div className="flex flex-col gap-1 min-w-0"> <div className="flex flex-col gap-1 min-w-0">
@@ -64,7 +65,7 @@ export function SpecEmptyState({
<div className="mb-6 flex justify-center"> <div className="mb-6 flex justify-center">
<div className="p-4 rounded-full bg-primary/10"> <div className="p-4 rounded-full bg-primary/10">
{isCreating ? ( {isCreating ? (
<Loader2 className="w-12 h-12 text-primary animate-spin" /> <Spinner size="xl" className="w-12 h-12" />
) : ( ) : (
<FilePlus2 className="w-12 h-12 text-primary" /> <FilePlus2 className="w-12 h-12 text-primary" />
)} )}

View File

@@ -3,7 +3,8 @@ import {
HeaderActionsPanel, HeaderActionsPanel,
HeaderActionsPanelTrigger, HeaderActionsPanelTrigger,
} from '@/components/ui/header-actions-panel'; } from '@/components/ui/header-actions-panel';
import { Save, Sparkles, Loader2, FileText, AlertCircle, ListPlus, RefreshCcw } from 'lucide-react'; import { Save, Sparkles, FileText, AlertCircle, ListPlus, RefreshCcw } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { PHASE_LABELS } from '../constants'; import { PHASE_LABELS } from '../constants';
interface SpecHeaderProps { interface SpecHeaderProps {
@@ -59,7 +60,7 @@ export function SpecHeader({
{isProcessing && ( {isProcessing && (
<div className="hidden lg:flex items-center gap-3 px-6 py-3.5 rounded-xl bg-linear-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md"> <div className="hidden lg:flex items-center gap-3 px-6 py-3.5 rounded-xl bg-linear-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md">
<div className="relative"> <div className="relative">
<Loader2 className="w-5 h-5 animate-spin text-primary shrink-0" /> <Spinner size="md" className="shrink-0" />
<div className="absolute inset-0 w-5 h-5 animate-ping text-primary/20" /> <div className="absolute inset-0 w-5 h-5 animate-ping text-primary/20" />
</div> </div>
<div className="flex flex-col gap-1 min-w-0"> <div className="flex flex-col gap-1 min-w-0">
@@ -83,7 +84,7 @@ export function SpecHeader({
{/* Mobile processing indicator */} {/* Mobile processing indicator */}
{isProcessing && ( {isProcessing && (
<div className="lg:hidden flex items-center gap-2 px-3 py-2 rounded-lg bg-primary/10 border border-primary/20"> <div className="lg:hidden flex items-center gap-2 px-3 py-2 rounded-lg bg-primary/10 border border-primary/20">
<Loader2 className="w-4 h-4 animate-spin text-primary" /> <Spinner size="sm" />
<span className="text-xs font-medium text-primary">Processing...</span> <span className="text-xs font-medium text-primary">Processing...</span>
</div> </div>
)} )}
@@ -157,7 +158,7 @@ export function SpecHeader({
{/* Status messages in panel */} {/* Status messages in panel */}
{isProcessing && ( {isProcessing && (
<div className="flex items-center gap-3 p-3 rounded-lg bg-primary/10 border border-primary/20"> <div className="flex items-center gap-3 p-3 rounded-lg bg-primary/10 border border-primary/20">
<Loader2 className="w-4 h-4 animate-spin text-primary shrink-0" /> <Spinner size="sm" className="shrink-0" />
<div className="flex flex-col gap-0.5 min-w-0"> <div className="flex flex-col gap-0.5 min-w-0">
<span className="text-sm font-medium text-primary"> <span className="text-sm font-medium text-primary">
{isSyncing {isSyncing

View File

@@ -1,4 +1,5 @@
import { Sparkles, Clock, Loader2 } from 'lucide-react'; import { Sparkles, Clock } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -163,7 +164,7 @@ export function CreateSpecDialog({
> >
{isCreatingSpec ? ( {isCreatingSpec ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Generating... Generating...
</> </>
) : ( ) : (

View File

@@ -1,4 +1,5 @@
import { Sparkles, Clock, Loader2 } from 'lucide-react'; import { Sparkles, Clock } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -158,7 +159,7 @@ export function RegenerateSpecDialog({
> >
{isRegenerating ? ( {isRegenerating ? (
<> <>
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
Regenerating... Regenerating...
</> </>
) : ( ) : (

View File

@@ -7,13 +7,13 @@ import {
Unlock, Unlock,
SplitSquareHorizontal, SplitSquareHorizontal,
SplitSquareVertical, SplitSquareVertical,
Loader2,
AlertCircle, AlertCircle,
RefreshCw, RefreshCw,
X, X,
SquarePlus, SquarePlus,
Settings, Settings,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getServerUrlSync } from '@/lib/http-api-client'; import { getServerUrlSync } from '@/lib/http-api-client';
import { import {
useAppStore, useAppStore,
@@ -1279,7 +1279,7 @@ export function TerminalView() {
if (loading) { if (loading) {
return ( return (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> <Spinner size="xl" />
</div> </div>
); );
} }
@@ -1342,7 +1342,7 @@ export function TerminalView() {
{authError && <p className="text-sm text-destructive">{authError}</p>} {authError && <p className="text-sm text-destructive">{authError}</p>}
<Button type="submit" className="w-full" disabled={authLoading || !password}> <Button type="submit" className="w-full" disabled={authLoading || !password}>
{authLoading ? ( {authLoading ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> <Spinner size="sm" className="mr-2" />
) : ( ) : (
<Unlock className="h-4 w-4 mr-2" /> <Unlock className="h-4 w-4 mr-2" />
)} )}

View File

@@ -13,7 +13,6 @@ import {
CheckSquare, CheckSquare,
Trash2, Trash2,
ImageIcon, ImageIcon,
Loader2,
Settings, Settings,
RotateCcw, RotateCcw,
Search, Search,
@@ -24,6 +23,7 @@ import {
ArrowDown, ArrowDown,
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Slider } from '@/components/ui/slider'; import { Slider } from '@/components/ui/slider';
@@ -1743,7 +1743,7 @@ export function TerminalPanel({
<div className="flex flex-col items-center gap-2 px-4 py-3 bg-blue-500/90 rounded-md text-white"> <div className="flex flex-col items-center gap-2 px-4 py-3 bg-blue-500/90 rounded-md text-white">
{isProcessingImage ? ( {isProcessingImage ? (
<> <>
<Loader2 className="h-6 w-6 animate-spin" /> <Spinner size="lg" />
<span className="text-sm font-medium">Processing...</span> <span className="text-sm font-medium">Processing...</span>
</> </>
) : ( ) : (
@@ -1791,7 +1791,7 @@ export function TerminalPanel({
)} )}
{connectionStatus === 'reconnecting' && ( {connectionStatus === 'reconnecting' && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-yellow-500/20 text-yellow-500 flex items-center gap-1"> <span className="text-[10px] px-1.5 py-0.5 rounded bg-yellow-500/20 text-yellow-500 flex items-center gap-1">
<Loader2 className="h-2.5 w-2.5 animate-spin" /> <Spinner size="xs" />
Reconnecting... Reconnecting...
</span> </span>
)} )}

View File

@@ -20,8 +20,8 @@ import {
Sparkles, Sparkles,
MessageSquare, MessageSquare,
ChevronDown, ChevronDown,
Loader2,
} from 'lucide-react'; } from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -758,7 +758,7 @@ export function WelcomeView() {
<div className="mt-5 p-4 rounded-xl bg-muted/50 border border-border"> <div className="mt-5 p-4 rounded-xl bg-muted/50 border border-border">
{isAnalyzing ? ( {isAnalyzing ? (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Loader2 className="w-4 h-4 text-brand-500 animate-spin" /> <Spinner size="sm" />
<p className="text-sm text-brand-500"> <p className="text-sm text-brand-500">
AI agent is analyzing your project structure... AI agent is analyzing your project structure...
</p> </p>
@@ -802,7 +802,7 @@ export function WelcomeView() {
data-testid="project-opening-overlay" data-testid="project-opening-overlay"
> >
<div className="flex flex-col items-center gap-4 p-8 rounded-2xl bg-card border border-border shadow-2xl"> <div className="flex flex-col items-center gap-4 p-8 rounded-2xl bg-card border border-border shadow-2xl">
<Loader2 className="w-10 h-10 text-brand-500 animate-spin" /> <Spinner size="xl" />
<p className="text-foreground font-medium">Initializing project...</p> <p className="text-foreground font-medium">Initializing project...</p>
</div> </div>
</div> </div>