mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Merge remote-tracking branch 'upstream/v0.13.0rc' into patchcraft
This commit is contained in:
@@ -5,18 +5,14 @@
|
||||
|
||||
import * as secureFs from './secure-fs.js';
|
||||
import * as path from 'path';
|
||||
import type { PRState, WorktreePRInfo } from '@automaker/types';
|
||||
|
||||
// Re-export types for backwards compatibility
|
||||
export type { PRState, WorktreePRInfo };
|
||||
|
||||
/** Maximum length for sanitized branch names in filesystem paths */
|
||||
const MAX_SANITIZED_BRANCH_PATH_LENGTH = 200;
|
||||
|
||||
export interface WorktreePRInfo {
|
||||
number: number;
|
||||
url: string;
|
||||
title: string;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface WorktreeMetadata {
|
||||
branch: string;
|
||||
createdAt: string;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '../common.js';
|
||||
import { updateWorktreePRInfo } from '../../../lib/worktree-metadata.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { validatePRState } from '@automaker/types';
|
||||
|
||||
const logger = createLogger('CreatePR');
|
||||
|
||||
@@ -268,11 +269,12 @@ export function createCreatePRHandler() {
|
||||
prAlreadyExisted = true;
|
||||
|
||||
// Store the existing PR info in metadata
|
||||
// GitHub CLI returns uppercase states: OPEN, MERGED, CLOSED
|
||||
await updateWorktreePRInfo(effectiveProjectPath, branchName, {
|
||||
number: existingPr.number,
|
||||
url: existingPr.url,
|
||||
title: existingPr.title || title,
|
||||
state: existingPr.state || 'open',
|
||||
state: validatePRState(existingPr.state),
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
logger.debug(
|
||||
@@ -319,11 +321,12 @@ export function createCreatePRHandler() {
|
||||
|
||||
if (prNumber) {
|
||||
try {
|
||||
// Note: GitHub doesn't have a 'DRAFT' state - drafts still show as 'OPEN'
|
||||
await updateWorktreePRInfo(effectiveProjectPath, branchName, {
|
||||
number: prNumber,
|
||||
url: prUrl,
|
||||
title,
|
||||
state: draft ? 'draft' : 'open',
|
||||
state: 'OPEN',
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
logger.debug(`Stored PR info for branch ${branchName}: PR #${prNumber}`);
|
||||
@@ -352,11 +355,12 @@ export function createCreatePRHandler() {
|
||||
prNumber = existingPr.number;
|
||||
prAlreadyExisted = true;
|
||||
|
||||
// GitHub CLI returns uppercase states: OPEN, MERGED, CLOSED
|
||||
await updateWorktreePRInfo(effectiveProjectPath, branchName, {
|
||||
number: existingPr.number,
|
||||
url: existingPr.url,
|
||||
title: existingPr.title || title,
|
||||
state: existingPr.state || 'open',
|
||||
state: validatePRState(existingPr.state),
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
logger.debug(`Fetched and stored existing PR: #${existingPr.number}`);
|
||||
|
||||
@@ -14,8 +14,13 @@ import path from 'path';
|
||||
import * as secureFs from '../../../lib/secure-fs.js';
|
||||
import { isGitRepo } from '@automaker/git-utils';
|
||||
import { getErrorMessage, logError, normalizePath, execEnv, isGhCliAvailable } from '../common.js';
|
||||
import { readAllWorktreeMetadata, type WorktreePRInfo } from '../../../lib/worktree-metadata.js';
|
||||
import {
|
||||
readAllWorktreeMetadata,
|
||||
updateWorktreePRInfo,
|
||||
type WorktreePRInfo,
|
||||
} from '../../../lib/worktree-metadata.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { validatePRState } from '@automaker/types';
|
||||
import {
|
||||
checkGitHubRemote,
|
||||
type GitHubRemoteStatus,
|
||||
@@ -168,8 +173,11 @@ async function getGitHubRemoteStatus(projectPath: string): Promise<GitHubRemoteS
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch open PRs from GitHub and create a map of branch name to PR info.
|
||||
* This allows detecting PRs that were created outside the app.
|
||||
* Fetch all PRs from GitHub and create a map of branch name to PR info.
|
||||
* Uses --state all to include merged/closed PRs, allowing detection of
|
||||
* state changes (e.g., when a PR is merged on GitHub).
|
||||
*
|
||||
* This also allows detecting PRs that were created outside the app.
|
||||
*
|
||||
* Uses cached GitHub remote status to avoid repeated warnings when the
|
||||
* project doesn't have a GitHub remote configured.
|
||||
@@ -192,9 +200,9 @@ async function fetchGitHubPRs(projectPath: string): Promise<Map<string, Worktree
|
||||
? `-R ${remoteStatus.owner}/${remoteStatus.repo}`
|
||||
: '';
|
||||
|
||||
// Fetch open PRs from GitHub
|
||||
// Fetch all PRs from GitHub (including merged/closed to detect state changes)
|
||||
const { stdout } = await execAsync(
|
||||
`gh pr list ${repoFlag} --state open --json number,title,url,state,headRefName,createdAt --limit 1000`,
|
||||
`gh pr list ${repoFlag} --state all --json number,title,url,state,headRefName,createdAt --limit 1000`,
|
||||
{ cwd: projectPath, env: execEnv, timeout: 15000 }
|
||||
);
|
||||
|
||||
@@ -212,7 +220,8 @@ async function fetchGitHubPRs(projectPath: string): Promise<Map<string, Worktree
|
||||
number: pr.number,
|
||||
url: pr.url,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
// GitHub CLI returns state as uppercase: OPEN, MERGED, CLOSED
|
||||
state: validatePRState(pr.state),
|
||||
createdAt: pr.createdAt,
|
||||
});
|
||||
}
|
||||
@@ -351,23 +360,36 @@ export function createListHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
// Add PR info from metadata or GitHub for each worktree
|
||||
// Only fetch GitHub PRs if includeDetails is requested (performance optimization)
|
||||
// Assign PR info to each worktree, preferring fresh GitHub data over cached metadata.
|
||||
// Only fetch GitHub PRs if includeDetails is requested (performance optimization).
|
||||
// Uses --state all to detect merged/closed PRs, limited to 1000 recent PRs.
|
||||
const githubPRs = includeDetails
|
||||
? await fetchGitHubPRs(projectPath)
|
||||
: new Map<string, WorktreePRInfo>();
|
||||
|
||||
for (const worktree of worktrees) {
|
||||
const metadata = allMetadata.get(worktree.branch);
|
||||
if (metadata?.pr) {
|
||||
// Use stored metadata (more complete info)
|
||||
worktree.pr = metadata.pr;
|
||||
} else if (includeDetails) {
|
||||
// Fall back to GitHub PR detection only when includeDetails is requested
|
||||
const githubPR = githubPRs.get(worktree.branch);
|
||||
if (githubPR) {
|
||||
worktree.pr = githubPR;
|
||||
const githubPR = githubPRs.get(worktree.branch);
|
||||
|
||||
if (githubPR) {
|
||||
// Prefer fresh GitHub data (it has the current state)
|
||||
worktree.pr = githubPR;
|
||||
|
||||
// Sync metadata with GitHub state when:
|
||||
// 1. No metadata exists for this PR (PR created externally)
|
||||
// 2. State has changed (e.g., merged/closed on GitHub)
|
||||
const needsSync = !metadata?.pr || metadata.pr.state !== githubPR.state;
|
||||
if (needsSync) {
|
||||
// Fire and forget - don't block the response
|
||||
updateWorktreePRInfo(projectPath, worktree.branch, githubPR).catch((err) => {
|
||||
logger.warn(
|
||||
`Failed to update PR info for ${worktree.branch}: ${getErrorMessage(err)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
} else if (metadata?.pr) {
|
||||
// Fall back to stored metadata (for PRs not in recent GitHub response)
|
||||
worktree.pr = metadata.pr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('worktree-metadata.ts', () => {
|
||||
number: 123,
|
||||
url: 'https://github.com/owner/repo/pull/123',
|
||||
title: 'Test PR',
|
||||
state: 'open',
|
||||
state: 'OPEN',
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
@@ -158,7 +158,7 @@ describe('worktree-metadata.ts', () => {
|
||||
number: 456,
|
||||
url: 'https://github.com/owner/repo/pull/456',
|
||||
title: 'Updated PR',
|
||||
state: 'closed',
|
||||
state: 'CLOSED',
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
@@ -177,7 +177,7 @@ describe('worktree-metadata.ts', () => {
|
||||
number: 789,
|
||||
url: 'https://github.com/owner/repo/pull/789',
|
||||
title: 'New PR',
|
||||
state: 'open',
|
||||
state: 'OPEN',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -201,7 +201,7 @@ describe('worktree-metadata.ts', () => {
|
||||
number: 999,
|
||||
url: 'https://github.com/owner/repo/pull/999',
|
||||
title: 'Updated PR',
|
||||
state: 'merged',
|
||||
state: 'MERGED',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -224,7 +224,7 @@ describe('worktree-metadata.ts', () => {
|
||||
number: 111,
|
||||
url: 'https://github.com/owner/repo/pull/111',
|
||||
title: 'PR',
|
||||
state: 'open',
|
||||
state: 'OPEN',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -259,7 +259,7 @@ describe('worktree-metadata.ts', () => {
|
||||
number: 222,
|
||||
url: 'https://github.com/owner/repo/pull/222',
|
||||
title: 'Has PR',
|
||||
state: 'open',
|
||||
state: 'OPEN',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -297,7 +297,7 @@ describe('worktree-metadata.ts', () => {
|
||||
number: 333,
|
||||
url: 'https://github.com/owner/repo/pull/333',
|
||||
title: 'PR 3',
|
||||
state: 'open',
|
||||
state: 'OPEN',
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
@@ -279,7 +280,7 @@ export function ClaudeUsagePopover() {
|
||||
) : !claudeUsage ? (
|
||||
// Loading state
|
||||
<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>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
@@ -333,7 +334,7 @@ export function CodexUsagePopover() {
|
||||
) : !codexUsage ? (
|
||||
// Loading state
|
||||
<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>
|
||||
</div>
|
||||
) : codexUsage.rateLimits ? (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
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');
|
||||
import {
|
||||
@@ -313,7 +314,7 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa
|
||||
/>
|
||||
{isProcessing && (
|
||||
<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>
|
||||
@@ -353,7 +354,7 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa
|
||||
)}
|
||||
>
|
||||
{isProcessing ? (
|
||||
<Upload className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
<Spinner size="lg" />
|
||||
) : (
|
||||
<ImageIcon className="h-6 w-6 text-muted-foreground" />
|
||||
)}
|
||||
|
||||
@@ -14,16 +14,8 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
FolderPlus,
|
||||
FolderOpen,
|
||||
Rocket,
|
||||
ExternalLink,
|
||||
Check,
|
||||
Loader2,
|
||||
Link,
|
||||
Folder,
|
||||
} from 'lucide-react';
|
||||
import { FolderPlus, FolderOpen, Rocket, ExternalLink, Check, Link, Folder } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { starterTemplates, type StarterTemplate } from '@/lib/templates';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -451,7 +443,7 @@ export function NewProjectModal({
|
||||
>
|
||||
{isCreating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
{activeTab === 'template' ? 'Cloning...' : 'Creating...'}
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
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';
|
||||
|
||||
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]">
|
||||
{isLoading && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { NavigateOptions } from '@tanstack/react-router';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { formatShortcut } from '@/store/app-store';
|
||||
import type { NavSection } from '../types';
|
||||
import type { Project } from '@/lib/electron';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
interface SidebarNavigationProps {
|
||||
currentProject: Project | null;
|
||||
@@ -93,9 +93,10 @@ export function SidebarNavigation({
|
||||
>
|
||||
<div className="relative">
|
||||
{item.isLoading ? (
|
||||
<Loader2
|
||||
<Spinner
|
||||
size="md"
|
||||
className={cn(
|
||||
'w-[18px] h-[18px] shrink-0 animate-spin',
|
||||
'shrink-0',
|
||||
isActive ? 'text-brand-500' : 'text-muted-foreground'
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
Check,
|
||||
X,
|
||||
ArchiveRestore,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { SessionListItem } from '@/types/electron';
|
||||
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) */}
|
||||
{(currentSessionId === session.id && isCurrentSessionThinking) ||
|
||||
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" />
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
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]",
|
||||
@@ -39,7 +39,7 @@ const buttonVariants = cva(
|
||||
|
||||
// Loading spinner component
|
||||
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({
|
||||
|
||||
@@ -3,7 +3,8 @@ import { createLogger } from '@automaker/utils/logger';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
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 { getElectronAPI } from '@/lib/electron';
|
||||
import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
|
||||
@@ -431,7 +432,7 @@ export function DescriptionImageDropZone({
|
||||
{/* Processing indicator */}
|
||||
{isProcessing && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { createLogger } from '@automaker/utils/logger';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const logger = createLogger('FeatureImageUpload');
|
||||
import { ImageIcon, X, Upload } from 'lucide-react';
|
||||
import { ImageIcon, X } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import {
|
||||
fileToBase64,
|
||||
generateImageId,
|
||||
@@ -196,7 +197,7 @@ export function FeatureImageUpload({
|
||||
)}
|
||||
>
|
||||
{isProcessing ? (
|
||||
<Upload className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
<Spinner size="md" />
|
||||
) : (
|
||||
<ImageIcon className="h-5 w-5 text-muted-foreground" />
|
||||
)}
|
||||
|
||||
@@ -9,11 +9,11 @@ import {
|
||||
FilePen,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
GitBranch,
|
||||
AlertCircle,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Button } from './button';
|
||||
import type { FileStatus } from '@/types/electron';
|
||||
|
||||
@@ -484,7 +484,7 @@ export function GitDiffPanel({
|
||||
<div className="border-t border-border">
|
||||
{isLoading ? (
|
||||
<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>
|
||||
</div>
|
||||
) : error ? (
|
||||
|
||||
@@ -3,7 +3,8 @@ import { createLogger } from '@automaker/utils/logger';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
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 {
|
||||
fileToBase64,
|
||||
@@ -204,7 +205,7 @@ export function ImageDropZone({
|
||||
)}
|
||||
>
|
||||
{isProcessing ? (
|
||||
<Upload className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
<Spinner size="lg" />
|
||||
) : (
|
||||
<ImageIcon className="h-6 w-6 text-muted-foreground" />
|
||||
)}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
interface LoadingStateProps {
|
||||
/** Optional custom message to display below the spinner */
|
||||
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 (
|
||||
<div className="flex-1 flex flex-col items-center justify-center">
|
||||
<Loader2 className={`${size} animate-spin text-muted-foreground`} />
|
||||
{message && <p className="mt-4 text-sm text-muted-foreground">{message}</p>}
|
||||
<Spinner size="xl" />
|
||||
{message && <p className="mt-4 text-sm font-medium text-primary">{message}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import {
|
||||
Filter,
|
||||
Circle,
|
||||
Play,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
parseLogOutput,
|
||||
@@ -148,7 +148,7 @@ function TodoListRenderer({ todos }: { todos: TodoItem[] }) {
|
||||
case 'completed':
|
||||
return <CheckCircle2 className="w-4 h-4 text-emerald-400" />;
|
||||
case 'in_progress':
|
||||
return <Loader2 className="w-4 h-4 text-amber-400 animate-spin" />;
|
||||
return <Spinner size="sm" />;
|
||||
case 'pending':
|
||||
return <Circle className="w-4 h-4 text-muted-foreground/70" />;
|
||||
default:
|
||||
|
||||
32
apps/ui/src/components/ui/spinner.tsx
Normal file
32
apps/ui/src/components/ui/spinner.tsx
Normal 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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import { createLogger } from '@automaker/utils/logger';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
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 type { AutoModeEvent } from '@/types/electron';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -260,7 +261,7 @@ export function TaskProgressPanel({
|
||||
)}
|
||||
>
|
||||
{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" />}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
@@ -449,7 +450,7 @@ export function UsagePopover() {
|
||||
</div>
|
||||
) : !claudeUsage ? (
|
||||
<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>
|
||||
</div>
|
||||
) : (
|
||||
@@ -568,7 +569,7 @@ export function UsagePopover() {
|
||||
</div>
|
||||
) : !codexUsage ? (
|
||||
<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>
|
||||
</div>
|
||||
) : codexUsage.rateLimits ? (
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
Terminal,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Loader2,
|
||||
Play,
|
||||
File,
|
||||
Pencil,
|
||||
Wrench,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
|
||||
@@ -236,7 +236,7 @@ export function AgentToolsView() {
|
||||
>
|
||||
{isReadingFile ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Reading...
|
||||
</>
|
||||
) : (
|
||||
@@ -315,7 +315,7 @@ export function AgentToolsView() {
|
||||
>
|
||||
{isWritingFile ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Writing...
|
||||
</>
|
||||
) : (
|
||||
@@ -383,7 +383,7 @@ export function AgentToolsView() {
|
||||
>
|
||||
{isRunningCommand ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Running...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Bot } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
export function ThinkingIndicator() {
|
||||
return (
|
||||
@@ -8,20 +9,7 @@ export function ThinkingIndicator() {
|
||||
</div>
|
||||
<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-1">
|
||||
<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>
|
||||
<Spinner size="sm" />
|
||||
<span className="text-sm text-muted-foreground">Thinking...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
RefreshCw,
|
||||
BarChart3,
|
||||
FileCode,
|
||||
Loader2,
|
||||
FileText,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
ListChecks,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn, generateUUID } from '@/lib/utils';
|
||||
|
||||
const logger = createLogger('AnalysisView');
|
||||
@@ -742,7 +742,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
<Button onClick={runAnalysis} disabled={isAnalyzing} data-testid="analyze-project-button">
|
||||
{isAnalyzing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Analyzing...
|
||||
</>
|
||||
) : (
|
||||
@@ -771,7 +771,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
</div>
|
||||
) : isAnalyzing ? (
|
||||
<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>
|
||||
</div>
|
||||
) : projectAnalysis ? (
|
||||
@@ -850,7 +850,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
>
|
||||
{isGeneratingSpec ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Generating...
|
||||
</>
|
||||
) : (
|
||||
@@ -903,7 +903,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
|
||||
>
|
||||
{isGeneratingFeatureList ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Generating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -34,7 +34,7 @@ import { pathsEqual } from '@/lib/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { getBlockingDependencies } from '@automaker/dependency-resolver';
|
||||
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 { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts';
|
||||
import { useWindowState } from '@/hooks/use-window-state';
|
||||
@@ -1384,7 +1384,7 @@ export function BoardView() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useRef, useEffect } from 'react';
|
||||
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 {
|
||||
searchQuery: string;
|
||||
@@ -75,7 +76,7 @@ export function BoardSearchBar({
|
||||
title="Creating App Specification"
|
||||
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">
|
||||
Creating spec
|
||||
</span>
|
||||
|
||||
@@ -11,16 +11,8 @@ import {
|
||||
} from '@/lib/agent-context-parser';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { AutoModeEvent } from '@/types/electron';
|
||||
import {
|
||||
Brain,
|
||||
ListTodo,
|
||||
Sparkles,
|
||||
Expand,
|
||||
CheckCircle2,
|
||||
Circle,
|
||||
Loader2,
|
||||
Wrench,
|
||||
} from 'lucide-react';
|
||||
import { Brain, ListTodo, Sparkles, Expand, CheckCircle2, Circle, Wrench } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { SummaryDialog } from './summary-dialog';
|
||||
import { getProviderIconForModel } from '@/components/ui/provider-icon';
|
||||
@@ -338,7 +330,7 @@ export function AgentInfoPanel({
|
||||
{todo.status === 'completed' ? (
|
||||
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" />
|
||||
) : 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" />
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import {
|
||||
GripVertical,
|
||||
Edit,
|
||||
Loader2,
|
||||
Trash2,
|
||||
FileText,
|
||||
MoreVertical,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
ChevronUp,
|
||||
GitFork,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { CountUpTimer } from '@/components/ui/count-up-timer';
|
||||
import { formatModelName, DEFAULT_MODEL } from '@/lib/agent-context-parser';
|
||||
import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog';
|
||||
@@ -65,7 +65,7 @@ export function CardHeaderSection({
|
||||
{isCurrentAutoTask && !isSelectionMode && (
|
||||
<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">
|
||||
<Loader2 className="w-3.5 h-3.5 text-[var(--status-in-progress)] animate-spin" />
|
||||
<Spinner size="xs" />
|
||||
{feature.startedAt && (
|
||||
<CountUpTimer
|
||||
startedAt={feature.startedAt}
|
||||
@@ -324,7 +324,7 @@ export function CardHeaderSection({
|
||||
<div className="flex-1 min-w-0 overflow-hidden">
|
||||
{feature.titleGenerating ? (
|
||||
<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>
|
||||
</div>
|
||||
) : feature.title ? (
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} 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 { LogViewer } from '@/components/ui/log-viewer';
|
||||
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">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
{featureStatus !== 'verified' && featureStatus !== 'waiting_approval' && (
|
||||
<Loader2 className="w-5 h-5 text-primary animate-spin" />
|
||||
<Spinner size="md" />
|
||||
)}
|
||||
Agent Output
|
||||
</DialogTitle>
|
||||
@@ -439,7 +440,7 @@ export function AgentOutputModal({
|
||||
/>
|
||||
) : (
|
||||
<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...
|
||||
</div>
|
||||
)}
|
||||
@@ -457,7 +458,7 @@ export function AgentOutputModal({
|
||||
>
|
||||
{isLoading && !output ? (
|
||||
<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...
|
||||
</div>
|
||||
) : !output ? (
|
||||
|
||||
@@ -11,16 +11,8 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Loader2,
|
||||
Wand2,
|
||||
Check,
|
||||
Plus,
|
||||
Pencil,
|
||||
Trash2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import { Wand2, Check, Plus, Pencil, Trash2, ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -287,8 +279,7 @@ export function BacklogPlanDialog({
|
||||
</div>
|
||||
{isGeneratingPlan && (
|
||||
<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
|
||||
the background...
|
||||
<Spinner size="sm" />A plan is currently being generated in the background...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -405,7 +396,7 @@ export function BacklogPlanDialog({
|
||||
case 'applying':
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
@@ -452,7 +443,7 @@ export function BacklogPlanDialog({
|
||||
<Button onClick={handleGenerate} disabled={!prompt.trim() || isGeneratingPlan}>
|
||||
{isGeneratingPlan ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Generating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
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 { toast } from 'sonner';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
@@ -209,7 +210,7 @@ export function CommitWorktreeDialog({
|
||||
<Button onClick={handleCommit} disabled={isLoading || isGenerating || !message.trim()}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Committing...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -13,7 +13,8 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
import { GitBranchPlus, Loader2 } from 'lucide-react';
|
||||
import { GitBranchPlus } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
interface WorktreeInfo {
|
||||
path: string;
|
||||
@@ -133,7 +134,7 @@ export function CreateBranchDialog({
|
||||
<Button onClick={handleCreate} disabled={!branchName.trim() || isCreating}>
|
||||
{isCreating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -13,7 +13,8 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
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 { toast } from 'sonner';
|
||||
|
||||
@@ -405,7 +406,7 @@ export function CreatePRDialog({
|
||||
<Button onClick={handleCreate} disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
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 { toast } from 'sonner';
|
||||
|
||||
@@ -216,7 +217,7 @@ export function CreateWorktreeDialog({
|
||||
<Button onClick={handleCreate} disabled={isLoading || !branchName.trim()}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
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 { toast } from 'sonner';
|
||||
|
||||
@@ -147,7 +148,7 @@ export function DeleteWorktreeDialog({
|
||||
<Button variant="destructive" onClick={handleDelete} disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
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 { toast } from 'sonner';
|
||||
|
||||
@@ -217,7 +218,7 @@ export function MergeWorktreeDialog({
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Merging...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -14,7 +14,8 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { Markdown } from '@/components/ui/markdown';
|
||||
import { Label } from '@/components/ui/label';
|
||||
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 {
|
||||
open: boolean;
|
||||
@@ -171,7 +172,7 @@ export function PlanApprovalDialog({
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleReject} disabled={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" />
|
||||
)}
|
||||
@@ -190,7 +191,7 @@ export function PlanApprovalDialog({
|
||||
className="bg-green-600 hover:bg-green-700 text-white"
|
||||
>
|
||||
{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" />
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 { useAppStore, type InitScriptState } from '@/store/app-store';
|
||||
import { AnsiOutput } from '@/components/ui/ansi-output';
|
||||
@@ -65,7 +66,7 @@ function SingleIndicator({
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-3 border-b border-border/50">
|
||||
<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 === 'failed' && <X className="w-4 h-4 text-red-500" />}
|
||||
<span className="font-medium text-sm">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useCallback, useState, type ComponentType, type ReactNode } from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { AnthropicIcon, OpenAIIcon } from '@/components/ui/provider-icon';
|
||||
@@ -90,9 +91,11 @@ function UsageItem({
|
||||
className="p-1 rounded hover:bg-accent/50 transition-colors"
|
||||
title="Refresh usage"
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn('w-3.5 h-3.5 text-muted-foreground', isLoading && 'animate-spin')}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<Spinner size="xs" />
|
||||
) : (
|
||||
<RefreshCw className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="pl-6 space-y-2">{children}</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getModelProvider, PROVIDER_PREFIXES, stripProviderPrefix } from '@autom
|
||||
import type { ModelProvider } from '@automaker/types';
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants';
|
||||
import { useEffect } from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
interface ModelSelectorProps {
|
||||
selectedModel: string; // Can be ModelAlias or "cursor-{id}"
|
||||
@@ -294,7 +294,7 @@ export function ModelSelector({
|
||||
{/* Loading state */}
|
||||
{codexModelsLoading && dynamicCodexModels.length === 0 && (
|
||||
<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...
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
ClipboardList,
|
||||
FileText,
|
||||
ScrollText,
|
||||
Loader2,
|
||||
Check,
|
||||
Eye,
|
||||
RefreshCw,
|
||||
Sparkles,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -236,7 +236,7 @@ export function PlanningModeSelector({
|
||||
<div className="flex items-center gap-2">
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
||||
<Spinner size="sm" />
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Generating {mode === 'full' ? 'comprehensive spec' : 'spec'}...
|
||||
</span>
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuLabel,
|
||||
} 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 type { WorktreeInfo, BranchInfo } from '../types';
|
||||
|
||||
@@ -81,7 +82,7 @@ export function BranchSwitchDropdown({
|
||||
<div className="max-h-[250px] overflow-y-auto">
|
||||
{isLoadingBranches ? (
|
||||
<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...
|
||||
</DropdownMenuItem>
|
||||
) : filteredBranches.length === 0 ? (
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Loader2,
|
||||
Terminal,
|
||||
ArrowDown,
|
||||
ExternalLink,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
Clock,
|
||||
GitBranch,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { XtermLogViewer, type XtermLogViewerRef } from '@/components/ui/xterm-log-viewer';
|
||||
import { useDevServerLogs } from '../hooks/use-dev-server-logs';
|
||||
@@ -183,7 +183,7 @@ export function DevServerLogsPanel({
|
||||
onClick={() => fetchLogs()}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,7 +234,7 @@ export function DevServerLogsPanel({
|
||||
>
|
||||
{isLoading && !logs ? (
|
||||
<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>
|
||||
</div>
|
||||
) : !logs && !isRunning ? (
|
||||
@@ -245,7 +245,7 @@ export function DevServerLogsPanel({
|
||||
</div>
|
||||
) : !logs ? (
|
||||
<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-xs mt-1 opacity-60">
|
||||
Logs will appear as the server generates output
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} 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 type { WorktreeInfo } from '../types';
|
||||
|
||||
@@ -44,7 +45,7 @@ export function WorktreeMobileDropdown({
|
||||
<GitBranch className="w-3.5 h-3.5 shrink-0" />
|
||||
<span className="truncate">{displayBranch}</span>
|
||||
{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" />
|
||||
)}
|
||||
@@ -74,7 +75,7 @@ export function WorktreeMobileDropdown({
|
||||
) : (
|
||||
<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')}>
|
||||
{worktree.branch}
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { JSX } from 'react';
|
||||
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 { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
|
||||
@@ -197,8 +198,8 @@ export function WorktreeTab({
|
||||
aria-label={worktree.branch}
|
||||
data-testid={`worktree-branch-${worktree.branch}`}
|
||||
>
|
||||
{isRunning && <Loader2 className="w-3 h-3 animate-spin" />}
|
||||
{isActivating && !isRunning && <RefreshCw className="w-3 h-3 animate-spin" />}
|
||||
{isRunning && <Spinner size="xs" />}
|
||||
{isActivating && !isRunning && <Spinner size="xs" />}
|
||||
{worktree.branch}
|
||||
{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">
|
||||
@@ -264,8 +265,8 @@ export function WorktreeTab({
|
||||
: 'Click to switch to this branch'
|
||||
}
|
||||
>
|
||||
{isRunning && <Loader2 className="w-3 h-3 animate-spin" />}
|
||||
{isActivating && !isRunning && <RefreshCw className="w-3 h-3 animate-spin" />}
|
||||
{isRunning && <Spinner size="xs" />}
|
||||
{isActivating && !isRunning && <Spinner size="xs" />}
|
||||
{worktree.branch}
|
||||
{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">
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
export interface WorktreePRInfo {
|
||||
number: number;
|
||||
url: string;
|
||||
title: string;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
}
|
||||
// Re-export shared types from @automaker/types
|
||||
export type { PRState, WorktreePRInfo } from '@automaker/types';
|
||||
import type { PRState, WorktreePRInfo } from '@automaker/types';
|
||||
|
||||
export interface WorktreeInfo {
|
||||
path: string;
|
||||
@@ -43,7 +39,8 @@ export interface PRInfo {
|
||||
number: number;
|
||||
title: string;
|
||||
url: string;
|
||||
state: string;
|
||||
/** PR state: OPEN, MERGED, or CLOSED */
|
||||
state: PRState;
|
||||
author: string;
|
||||
body: string;
|
||||
comments: Array<{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { GitBranch, Plus, RefreshCw } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn, pathsEqual } from '@/lib/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||
@@ -285,7 +286,7 @@ export function WorktreePanel({
|
||||
disabled={isLoading}
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
@@ -429,7 +430,7 @@ export function WorktreePanel({
|
||||
disabled={isLoading}
|
||||
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>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -4,7 +4,8 @@ import { useAppStore } from '@/store/app-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
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';
|
||||
|
||||
const logger = createLogger('CodeView');
|
||||
@@ -206,7 +207,7 @@ export function CodeView() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
HeaderActionsPanelTrigger,
|
||||
} from '@/components/ui/header-actions-panel';
|
||||
import {
|
||||
RefreshCw,
|
||||
FileText,
|
||||
Image as ImageIcon,
|
||||
Trash2,
|
||||
@@ -24,9 +23,9 @@ import {
|
||||
Pencil,
|
||||
FilePlus,
|
||||
FileUp,
|
||||
Loader2,
|
||||
MoreVertical,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import {
|
||||
useKeyboardShortcuts,
|
||||
useKeyboardShortcutsConfig,
|
||||
@@ -670,7 +669,7 @@ export function ContextView() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -790,7 +789,7 @@ export function ContextView() {
|
||||
{isUploading && (
|
||||
<div className="absolute inset-0 bg-background/80 z-50 flex items-center justify-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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -838,7 +837,7 @@ export function ContextView() {
|
||||
<span className="truncate text-sm block">{file.name}</span>
|
||||
{isGenerating ? (
|
||||
<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...
|
||||
</span>
|
||||
) : file.description ? (
|
||||
@@ -955,7 +954,7 @@ export function ContextView() {
|
||||
</span>
|
||||
{generatingDescriptions.has(selectedFile.name) ? (
|
||||
<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>
|
||||
</div>
|
||||
) : selectedFile.description ? (
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
Folder,
|
||||
Star,
|
||||
Clock,
|
||||
Loader2,
|
||||
ChevronDown,
|
||||
MessageSquare,
|
||||
MoreVertical,
|
||||
@@ -28,6 +27,7 @@ import {
|
||||
type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
|
||||
import {
|
||||
@@ -1013,7 +1013,7 @@ export function DashboardView() {
|
||||
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">
|
||||
<Loader2 className="w-10 h-10 text-brand-500 animate-spin" />
|
||||
<Spinner size="xl" />
|
||||
<p className="text-foreground font-medium">Opening project...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
X,
|
||||
Wand2,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
GitPullRequest,
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -87,7 +87,7 @@ export function IssueDetailPanel({
|
||||
if (isValidating) {
|
||||
return (
|
||||
<Button variant="default" size="sm" disabled>
|
||||
<Loader2 className="h-4 w-4 mr-1 animate-spin" />
|
||||
<Spinner size="sm" className="mr-1" />
|
||||
Validating...
|
||||
</Button>
|
||||
);
|
||||
@@ -297,9 +297,7 @@ export function IssueDetailPanel({
|
||||
<span className="text-sm font-medium">
|
||||
Comments {totalCount > 0 && `(${totalCount})`}
|
||||
</span>
|
||||
{commentsLoading && (
|
||||
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
|
||||
)}
|
||||
{commentsLoading && <Spinner size="xs" />}
|
||||
{commentsExpanded ? (
|
||||
<ChevronUp className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
@@ -340,7 +338,7 @@ export function IssueDetailPanel({
|
||||
>
|
||||
{loadingMore ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Loading...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -2,12 +2,12 @@ import {
|
||||
Circle,
|
||||
CheckCircle2,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
CheckCircle,
|
||||
Sparkles,
|
||||
GitPullRequest,
|
||||
User,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { IssueRowProps } from '../types';
|
||||
@@ -97,7 +97,7 @@ export function IssueRow({
|
||||
{/* Validating indicator */}
|
||||
{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">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
<Spinner size="xs" />
|
||||
Analyzing...
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CircleDot, RefreshCw } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { IssuesStateFilter } from '../types';
|
||||
import { IssuesFilterControls } from './issues-filter-controls';
|
||||
@@ -77,7 +78,7 @@ export function IssuesListHeader({
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
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 { useAppStore } from '@/store/app-store';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -86,7 +87,7 @@ export function GitHubPRsView() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<Spinner size="xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -134,7 +135,7 @@ export function GitHubPRsView() {
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { useWorktrees } from './board-view/worktree-panel/hooks';
|
||||
import { useAutoMode } from '@/hooks/use-auto-mode';
|
||||
import { pathsEqual } from '@/lib/utils';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { toast } from 'sonner';
|
||||
@@ -330,7 +330,7 @@ export function GraphViewPage() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
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 { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -109,7 +110,7 @@ function SuggestionCard({
|
||||
)}
|
||||
>
|
||||
{isAdding ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
<Spinner size="sm" />
|
||||
) : (
|
||||
<>
|
||||
<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 ? (
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
) : (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
)}
|
||||
{isError ? <AlertCircle className="w-5 h-5" /> : <Spinner size="md" />}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{job.prompt.title}</p>
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
Gauge,
|
||||
Accessibility,
|
||||
BarChart3,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
||||
import type { IdeaCategory } from '@automaker/types';
|
||||
@@ -53,7 +53,7 @@ export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps
|
||||
|
||||
{isLoading && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
*/
|
||||
|
||||
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 { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
||||
import { useIdeationStore } from '@/store/ideation-store';
|
||||
@@ -121,7 +122,7 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
<div className="space-y-3">
|
||||
{isLoadingPrompts && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
@@ -162,7 +163,7 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
}`}
|
||||
>
|
||||
{isLoading || isGenerating ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
<Spinner size="md" />
|
||||
) : isStarted ? (
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
) : (
|
||||
|
||||
@@ -11,7 +11,8 @@ import { PromptList } from './components/prompt-list';
|
||||
import { IdeationDashboard } from './components/ideation-dashboard';
|
||||
import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
||||
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 { IdeationMode } from '@/store/ideation-store';
|
||||
|
||||
@@ -152,11 +153,7 @@ function IdeationHeader({
|
||||
className="gap-2"
|
||||
disabled={isAcceptingAll}
|
||||
>
|
||||
{isAcceptingAll ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<CheckCheck className="w-4 h-4" />
|
||||
)}
|
||||
{isAcceptingAll ? <Spinner size="sm" /> : <CheckCheck className="w-4 h-4" />}
|
||||
Accept All ({acceptAllCount})
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,8 @@ import { useAppStore, Feature } from '@/store/app-store';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
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 { getElectronAPI } from '@/lib/electron';
|
||||
import { Markdown } from '@/components/ui/markdown';
|
||||
@@ -491,7 +492,7 @@ export function InterviewView() {
|
||||
<Card className="border border-primary/30 bg-card">
|
||||
<CardContent className="p-3">
|
||||
<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>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -571,7 +572,7 @@ export function InterviewView() {
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -24,7 +24,8 @@ import {
|
||||
} from '@/lib/http-api-client';
|
||||
import { Button } from '@/components/ui/button';
|
||||
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 { useSetupStore } from '@/store/setup-store';
|
||||
|
||||
@@ -349,7 +350,7 @@ export function LoginView() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background p-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">
|
||||
Connecting to server
|
||||
{state.attempt > 1 ? ` (attempt ${state.attempt}/${MAX_RETRIES})` : '...'}
|
||||
@@ -385,7 +386,7 @@ export function LoginView() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background p-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">
|
||||
{state.phase === 'checking_setup' ? 'Loading settings...' : 'Redirecting...'}
|
||||
</p>
|
||||
@@ -447,7 +448,7 @@ export function LoginView() {
|
||||
>
|
||||
{isLoggingIn ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Authenticating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
FilePlus,
|
||||
MoreVertical,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -299,7 +300,7 @@ export function MemoryView() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import { useLoadNotifications, useNotificationEvents } from '@/hooks/use-notific
|
||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||
import { Button } from '@/components/ui/button';
|
||||
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 type { Notification } from '@automaker/types';
|
||||
|
||||
@@ -146,7 +147,7 @@ export function NotificationsView() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
Save,
|
||||
RotateCcw,
|
||||
Trash2,
|
||||
Loader2,
|
||||
PanelBottomClose,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { apiGet, apiPut, apiDelete } from '@/lib/api-fetch';
|
||||
import { toast } from 'sonner';
|
||||
@@ -409,7 +409,7 @@ export function WorktreePreferencesSection({ project }: WorktreePreferencesSecti
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />
|
||||
<Spinner size="md" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -448,11 +448,7 @@ npm install
|
||||
disabled={!scriptExists || isSaving || isDeleting}
|
||||
className="gap-1.5 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
{isDeleting ? (
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
) : (
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
)}
|
||||
{isDeleting ? <Spinner size="xs" /> : <Trash2 className="w-3.5 h-3.5" />}
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
@@ -461,11 +457,7 @@ npm install
|
||||
disabled={!hasChanges || isSaving || isDeleting}
|
||||
className="gap-1.5"
|
||||
>
|
||||
{isSaving ? (
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
) : (
|
||||
<Save className="w-3.5 h-3.5" />
|
||||
)}
|
||||
{isSaving ? <Spinner size="xs" /> : <Save className="w-3.5 h-3.5" />}
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
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 { useAppStore } from '@/store/app-store';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -146,7 +147,7 @@ export function RunningAgentsView() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<Spinner size="xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -169,7 +170,11 @@ export function RunningAgentsView() {
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { toast } from 'sonner';
|
||||
import { LogOut, User, Code2, RefreshCw } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { logout } from '@/lib/http-api-client';
|
||||
import { useAuthStore } from '@/store/auth-store';
|
||||
@@ -143,7 +144,7 @@ export function AccountSection() {
|
||||
disabled={isRefreshing || isLoadingEditors}
|
||||
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>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
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';
|
||||
|
||||
interface ApiKeyFieldProps {
|
||||
@@ -70,7 +71,7 @@ export function ApiKeyField({ config }: ApiKeyFieldProps) {
|
||||
>
|
||||
{testButton.loading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Testing...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
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 { buildProviderConfigs } from '@/config/api-providers';
|
||||
import { SecurityNotice } from './security-notice';
|
||||
@@ -142,7 +143,7 @@ export function ApiKeysSection() {
|
||||
data-testid="delete-anthropic-key"
|
||||
>
|
||||
{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" />
|
||||
)}
|
||||
@@ -159,7 +160,7 @@ export function ApiKeysSection() {
|
||||
data-testid="delete-openai-key"
|
||||
>
|
||||
{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" />
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getElectronAPI } from '@/lib/electron';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { RefreshCw, AlertCircle } from 'lucide-react';
|
||||
|
||||
const ERROR_NO_API = 'Claude usage API not available';
|
||||
@@ -178,7 +179,7 @@ export function ClaudeUsageSection() {
|
||||
data-testid="refresh-claude-usage"
|
||||
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>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">{CLAUDE_USAGE_SUBTITLE}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CliStatus } from '../shared/types';
|
||||
@@ -172,7 +173,7 @@ export function ClaudeCliStatus({ status, authStatus, isChecking, onRefresh }: C
|
||||
'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>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CliStatus } from '../shared/types';
|
||||
|
||||
@@ -56,7 +57,7 @@ export function CliStatusCard({
|
||||
'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>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">{description}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CliStatus } from '../shared/types';
|
||||
@@ -165,7 +166,7 @@ export function CodexCliStatus({ status, authStatus, isChecking, onRefresh }: Cl
|
||||
'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>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, XCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CursorIcon } from '@/components/ui/provider-icon';
|
||||
@@ -290,7 +291,7 @@ export function CursorCliStatus({ status, isChecking, onRefresh }: CursorCliStat
|
||||
'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>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, Bot, Cloud } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CliStatus } from '../shared/types';
|
||||
@@ -221,7 +222,7 @@ export function OpencodeCliStatus({
|
||||
'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>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { RefreshCw, AlertCircle } from 'lucide-react';
|
||||
import { OpenAIIcon } from '@/components/ui/provider-icon';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -168,7 +169,7 @@ export function CodexUsageSection() {
|
||||
data-testid="refresh-codex-usage"
|
||||
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>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">{CODEX_USAGE_SUBTITLE}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
History,
|
||||
@@ -184,7 +185,11 @@ export function EventHistoryView() {
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<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
|
||||
</Button>
|
||||
{events.length > 0 && (
|
||||
|
||||
@@ -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 { Switch } from '@/components/ui/switch';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
@@ -111,7 +112,7 @@ export function MCPServerCard({
|
||||
className="h-8 px-2"
|
||||
>
|
||||
{testState?.status === 'testing' ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
<Spinner size="sm" />
|
||||
) : (
|
||||
<PlayCircle className="w-4 h-4" />
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Plug, RefreshCw, Download, Code, FileJson, Plus } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface MCPServerHeaderProps {
|
||||
@@ -43,7 +44,7 @@ export function MCPServerHeader({
|
||||
disabled={isRefreshing}
|
||||
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>
|
||||
{hasServers && (
|
||||
<>
|
||||
|
||||
@@ -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 { SENSITIVE_PARAM_PATTERNS } from './constants';
|
||||
|
||||
@@ -40,7 +41,7 @@ export function getServerIcon(type: ServerType = 'stdio') {
|
||||
export function getTestStatusIcon(status: ServerTestState['status']) {
|
||||
switch (status) {
|
||||
case 'testing':
|
||||
return <Loader2 className="w-4 h-4 animate-spin text-brand-500" />;
|
||||
return <Spinner size="sm" />;
|
||||
case 'success':
|
||||
return <CheckCircle2 className="w-4 h-4 text-green-500" />;
|
||||
case 'error':
|
||||
|
||||
@@ -14,16 +14,8 @@ import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Bot,
|
||||
RefreshCw,
|
||||
Loader2,
|
||||
Users,
|
||||
ExternalLink,
|
||||
Globe,
|
||||
FolderOpen,
|
||||
Sparkles,
|
||||
} from 'lucide-react';
|
||||
import { Bot, RefreshCw, Users, ExternalLink, Globe, FolderOpen, Sparkles } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { useSubagents } from './hooks/use-subagents';
|
||||
import { useSubagentsSettings } from './hooks/use-subagents-settings';
|
||||
import { SubagentCard } from './subagent-card';
|
||||
@@ -178,11 +170,7 @@ export function SubagentsSection() {
|
||||
title="Refresh agents from disk"
|
||||
className="gap-1.5 h-7 px-2 text-xs"
|
||||
>
|
||||
{isLoadingAgents ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{isLoadingAgents ? <Spinner size="xs" /> : <RefreshCw className="h-3.5 w-3.5" />}
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import { Shield, ShieldCheck, ShieldAlert, ChevronDown, Copy, Check } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CursorStatus } from '../hooks/use-cursor-status';
|
||||
import type { PermissionsData } from '../hooks/use-cursor-permissions';
|
||||
@@ -118,7 +119,7 @@ export function CursorPermissionsSection({
|
||||
|
||||
{isLoadingPermissions ? (
|
||||
<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>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} 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 { Input } from '@/components/ui/input';
|
||||
import type {
|
||||
@@ -500,7 +501,7 @@ export function OpencodeModelConfiguration({
|
||||
</p>
|
||||
{isLoadingDynamicModels && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
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 { TerminalOutput } from './terminal-output';
|
||||
|
||||
@@ -59,7 +60,7 @@ export function CliInstallationCard({
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Installing...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -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 {
|
||||
status:
|
||||
@@ -34,7 +35,7 @@ export function StatusBadge({ status, label }: StatusBadgeProps) {
|
||||
};
|
||||
case 'checking':
|
||||
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',
|
||||
};
|
||||
case 'unverified':
|
||||
|
||||
@@ -14,7 +14,6 @@ import { useAppStore } from '@/store/app-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import {
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
Key,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
XCircle,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { toast } from 'sonner';
|
||||
import { StatusBadge, TerminalOutput } from '../components';
|
||||
import { useCliStatus, useCliInstallation, useTokenSave } from '../hooks';
|
||||
@@ -330,7 +330,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
|
||||
Authentication Methods
|
||||
</CardTitle>
|
||||
<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>
|
||||
</div>
|
||||
<CardDescription>
|
||||
@@ -412,7 +412,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Installing...
|
||||
</>
|
||||
) : (
|
||||
@@ -435,7 +435,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
|
||||
{/* CLI Verification Status */}
|
||||
{cliVerificationStatus === 'verifying' && (
|
||||
<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>
|
||||
<p className="font-medium text-foreground">Verifying CLI authentication...</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' ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Verifying...
|
||||
</>
|
||||
) : cliVerificationStatus === 'error' ? (
|
||||
@@ -574,7 +574,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
|
||||
>
|
||||
{isSavingApiKey ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
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"
|
||||
data-testid="delete-anthropic-key-button"
|
||||
>
|
||||
{isDeletingApiKey ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Trash2 className="w-4 h-4" />
|
||||
)}
|
||||
{isDeletingApiKey ? <Spinner size="sm" /> : <Trash2 className="w-4 h-4" />}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -602,7 +598,7 @@ export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps
|
||||
{/* API Key Verification Status */}
|
||||
{apiKeyVerificationStatus === 'verifying' && (
|
||||
<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>
|
||||
<p className="font-medium text-foreground">Verifying API key...</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' ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Verifying...
|
||||
</>
|
||||
) : apiKeyVerificationStatus === 'error' ? (
|
||||
|
||||
@@ -14,7 +14,6 @@ import { useAppStore } from '@/store/app-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import {
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
Key,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
XCircle,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { toast } from 'sonner';
|
||||
import { StatusBadge, TerminalOutput } from '../components';
|
||||
import { useCliStatus, useCliInstallation, useTokenSave } from '../hooks';
|
||||
@@ -332,7 +332,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
|
||||
Authentication Methods
|
||||
</CardTitle>
|
||||
<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>
|
||||
</div>
|
||||
<CardDescription>Choose one of the following methods to authenticate:</CardDescription>
|
||||
@@ -408,7 +408,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Installing...
|
||||
</>
|
||||
) : (
|
||||
@@ -427,7 +427,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
|
||||
|
||||
{cliVerificationStatus === 'verifying' && (
|
||||
<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>
|
||||
<p className="font-medium text-foreground">Verifying CLI authentication...</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' ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Verifying...
|
||||
</>
|
||||
) : cliVerificationStatus === 'error' ? (
|
||||
@@ -681,7 +681,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
|
||||
>
|
||||
{isSavingApiKey ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
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"
|
||||
data-testid={config.testIds.deleteApiKeyButton}
|
||||
>
|
||||
{isDeletingApiKey ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Trash2 className="w-4 h-4" />
|
||||
)}
|
||||
{isDeletingApiKey ? <Spinner size="sm" /> : <Trash2 className="w-4 h-4" />}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -708,7 +704,7 @@ export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetup
|
||||
|
||||
{apiKeyVerificationStatus === 'verifying' && (
|
||||
<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>
|
||||
<p className="font-medium text-foreground">Verifying API key...</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' ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Verifying...
|
||||
</>
|
||||
) : apiKeyVerificationStatus === 'error' ? (
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useSetupStore } from '@/store/setup-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import {
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
ExternalLink,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
AlertTriangle,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { toast } from 'sonner';
|
||||
import { StatusBadge } from '../components';
|
||||
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">
|
||||
{getStatusBadge()}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -318,7 +318,7 @@ export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps
|
||||
>
|
||||
{isLoggingIn ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Waiting for login...
|
||||
</>
|
||||
) : (
|
||||
@@ -332,7 +332,7 @@ export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps
|
||||
{/* Loading State */}
|
||||
{isChecking && (
|
||||
<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>
|
||||
<p className="font-medium text-foreground">Checking Cursor CLI status...</p>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useSetupStore } from '@/store/setup-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import {
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
ExternalLink,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
Github,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { toast } from 'sonner';
|
||||
import { StatusBadge } from '../components';
|
||||
|
||||
@@ -116,7 +116,7 @@ export function GitHubSetupStep({ onNext, onBack, onSkip }: GitHubSetupStepProps
|
||||
<div className="flex items-center gap-2">
|
||||
{getStatusBadge()}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -252,7 +252,7 @@ export function GitHubSetupStep({ onNext, onBack, onSkip }: GitHubSetupStepProps
|
||||
{/* Loading State */}
|
||||
{isChecking && (
|
||||
<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>
|
||||
<p className="font-medium text-foreground">Checking GitHub CLI status...</p>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useSetupStore } from '@/store/setup-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import {
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
ExternalLink,
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
XCircle,
|
||||
Terminal,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { toast } from 'sonner';
|
||||
import { StatusBadge } from '../components';
|
||||
|
||||
@@ -204,7 +204,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
|
||||
<div className="flex items-center gap-2">
|
||||
{getStatusBadge()}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -316,7 +316,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
|
||||
>
|
||||
{isLoggingIn ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Waiting for login...
|
||||
</>
|
||||
) : (
|
||||
@@ -330,7 +330,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP
|
||||
{/* Loading State */}
|
||||
{isChecking && (
|
||||
<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>
|
||||
<p className="font-medium text-foreground">Checking OpenCode CLI status...</p>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
Key,
|
||||
ExternalLink,
|
||||
Copy,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
Terminal,
|
||||
AlertCircle,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { toast } from 'sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon } from '@/components/ui/provider-icon';
|
||||
@@ -240,7 +240,7 @@ function ClaudeContent() {
|
||||
onClick={checkStatus}
|
||||
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>
|
||||
</div>
|
||||
<CardDescription>
|
||||
@@ -278,7 +278,7 @@ function ClaudeContent() {
|
||||
{/* Checking/Verifying State */}
|
||||
{(isChecking || isVerifying) && (
|
||||
<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">
|
||||
{isChecking ? 'Checking Claude CLI status...' : 'Verifying authentication...'}
|
||||
</p>
|
||||
@@ -322,7 +322,7 @@ function ClaudeContent() {
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Installing...
|
||||
</>
|
||||
) : (
|
||||
@@ -417,11 +417,7 @@ function ClaudeContent() {
|
||||
disabled={isSavingApiKey || !apiKey.trim()}
|
||||
className="flex-1 bg-brand-500 hover:bg-brand-600 text-white"
|
||||
>
|
||||
{isSavingApiKey ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
'Save API Key'
|
||||
)}
|
||||
{isSavingApiKey ? <Spinner size="sm" /> : 'Save API Key'}
|
||||
</Button>
|
||||
{hasApiKey && (
|
||||
<Button
|
||||
@@ -431,7 +427,7 @@ function ClaudeContent() {
|
||||
className="border-red-500/50 text-red-500 hover:bg-red-500/10"
|
||||
>
|
||||
{isDeletingApiKey ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
<Spinner size="sm" />
|
||||
) : (
|
||||
<Trash2 className="w-4 h-4" />
|
||||
)}
|
||||
@@ -553,7 +549,7 @@ function CursorContent() {
|
||||
Cursor CLI Status
|
||||
</CardTitle>
|
||||
<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>
|
||||
</div>
|
||||
<CardDescription>
|
||||
@@ -658,7 +654,7 @@ function CursorContent() {
|
||||
>
|
||||
{isLoggingIn ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Waiting for login...
|
||||
</>
|
||||
) : (
|
||||
@@ -671,7 +667,7 @@ function CursorContent() {
|
||||
|
||||
{isChecking && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
@@ -807,7 +803,7 @@ function CodexContent() {
|
||||
Codex CLI Status
|
||||
</CardTitle>
|
||||
<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>
|
||||
</div>
|
||||
<CardDescription>
|
||||
@@ -915,7 +911,7 @@ function CodexContent() {
|
||||
>
|
||||
{isLoggingIn ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Waiting for login...
|
||||
</>
|
||||
) : (
|
||||
@@ -958,7 +954,7 @@ function CodexContent() {
|
||||
disabled={isSaving || !apiKey.trim()}
|
||||
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>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
@@ -968,7 +964,7 @@ function CodexContent() {
|
||||
|
||||
{isChecking && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
@@ -1082,7 +1078,7 @@ function OpencodeContent() {
|
||||
OpenCode CLI Status
|
||||
</CardTitle>
|
||||
<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>
|
||||
</div>
|
||||
<CardDescription>
|
||||
@@ -1191,7 +1187,7 @@ function OpencodeContent() {
|
||||
>
|
||||
{isLoggingIn ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Waiting for login...
|
||||
</>
|
||||
) : (
|
||||
@@ -1204,7 +1200,7 @@ function OpencodeContent() {
|
||||
|
||||
{isChecking && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
@@ -1416,7 +1412,7 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
|
||||
);
|
||||
case 'verifying':
|
||||
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':
|
||||
return (
|
||||
@@ -1436,7 +1432,7 @@ export function ProvidersSetupStep({ onNext, onBack }: ProvidersSetupStepProps)
|
||||
|
||||
{isInitialChecking && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
// Extracted hooks
|
||||
import { useSpecLoading, useSpecSave, useSpecGeneration } from './spec-view/hooks';
|
||||
@@ -86,7 +86,7 @@ export function SpecView() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
interface SpecEmptyStateProps {
|
||||
@@ -36,7 +37,7 @@ export function SpecEmptyState({
|
||||
{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="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>
|
||||
<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="p-4 rounded-full bg-primary/10">
|
||||
{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" />
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,8 @@ import {
|
||||
HeaderActionsPanel,
|
||||
HeaderActionsPanelTrigger,
|
||||
} 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';
|
||||
|
||||
interface SpecHeaderProps {
|
||||
@@ -59,7 +60,7 @@ export function SpecHeader({
|
||||
{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="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>
|
||||
<div className="flex flex-col gap-1 min-w-0">
|
||||
@@ -83,7 +84,7 @@ export function SpecHeader({
|
||||
{/* Mobile processing indicator */}
|
||||
{isProcessing && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
@@ -157,7 +158,7 @@ export function SpecHeader({
|
||||
{/* Status messages in panel */}
|
||||
{isProcessing && (
|
||||
<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">
|
||||
<span className="text-sm font-medium text-primary">
|
||||
{isSyncing
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Sparkles, Clock, Loader2 } from 'lucide-react';
|
||||
import { Sparkles, Clock } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -163,7 +164,7 @@ export function CreateSpecDialog({
|
||||
>
|
||||
{isCreatingSpec ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Generating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Sparkles, Clock, Loader2 } from 'lucide-react';
|
||||
import { Sparkles, Clock } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -158,7 +159,7 @@ export function RegenerateSpecDialog({
|
||||
>
|
||||
{isRegenerating ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
Regenerating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -7,13 +7,13 @@ import {
|
||||
Unlock,
|
||||
SplitSquareHorizontal,
|
||||
SplitSquareVertical,
|
||||
Loader2,
|
||||
AlertCircle,
|
||||
RefreshCw,
|
||||
X,
|
||||
SquarePlus,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { getServerUrlSync } from '@/lib/http-api-client';
|
||||
import {
|
||||
useAppStore,
|
||||
@@ -1279,7 +1279,7 @@ export function TerminalView() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<Spinner size="xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1342,7 +1342,7 @@ export function TerminalView() {
|
||||
{authError && <p className="text-sm text-destructive">{authError}</p>}
|
||||
<Button type="submit" className="w-full" disabled={authLoading || !password}>
|
||||
{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" />
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
CheckSquare,
|
||||
Trash2,
|
||||
ImageIcon,
|
||||
Loader2,
|
||||
Settings,
|
||||
RotateCcw,
|
||||
Search,
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
ArrowDown,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
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">
|
||||
{isProcessingImage ? (
|
||||
<>
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
<Spinner size="lg" />
|
||||
<span className="text-sm font-medium">Processing...</span>
|
||||
</>
|
||||
) : (
|
||||
@@ -1791,7 +1791,7 @@ export function TerminalPanel({
|
||||
)}
|
||||
{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">
|
||||
<Loader2 className="h-2.5 w-2.5 animate-spin" />
|
||||
<Spinner size="xs" />
|
||||
Reconnecting...
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
Sparkles,
|
||||
MessageSquare,
|
||||
ChevronDown,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -758,7 +758,7 @@ export function WelcomeView() {
|
||||
<div className="mt-5 p-4 rounded-xl bg-muted/50 border border-border">
|
||||
{isAnalyzing ? (
|
||||
<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">
|
||||
AI agent is analyzing your project structure...
|
||||
</p>
|
||||
@@ -802,7 +802,7 @@ export function WelcomeView() {
|
||||
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">
|
||||
<Loader2 className="w-10 h-10 text-brand-500 animate-spin" />
|
||||
<Spinner size="xl" />
|
||||
<p className="text-foreground font-medium">Initializing project...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -292,3 +292,7 @@ export type {
|
||||
EventReplayHookResult,
|
||||
} from './event-history.js';
|
||||
export { EVENT_HISTORY_VERSION, DEFAULT_EVENT_HISTORY_INDEX } from './event-history.js';
|
||||
|
||||
// Worktree and PR types
|
||||
export type { PRState, WorktreePRInfo } from './worktree.js';
|
||||
export { PR_STATES, validatePRState } from './worktree.js';
|
||||
|
||||
32
libs/types/src/worktree.ts
Normal file
32
libs/types/src/worktree.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Worktree and PR-related types
|
||||
* Shared across server and UI components
|
||||
*/
|
||||
|
||||
/** GitHub PR states as returned by the GitHub API (uppercase) */
|
||||
export type PRState = 'OPEN' | 'MERGED' | 'CLOSED';
|
||||
|
||||
/** Valid PR states for validation */
|
||||
export const PR_STATES: readonly PRState[] = ['OPEN', 'MERGED', 'CLOSED'] as const;
|
||||
|
||||
/**
|
||||
* Validates a PR state value from external APIs (e.g., GitHub CLI).
|
||||
* Returns the validated state if it matches a known PRState, otherwise returns 'OPEN' as default.
|
||||
* This is safer than type assertions as it handles unexpected values from external APIs.
|
||||
*
|
||||
* @param state - The state string to validate (can be any string)
|
||||
* @returns A valid PRState value
|
||||
*/
|
||||
export function validatePRState(state: string | undefined | null): PRState {
|
||||
return PR_STATES.find((s) => s === state) ?? 'OPEN';
|
||||
}
|
||||
|
||||
/** PR information stored in worktree metadata */
|
||||
export interface WorktreePRInfo {
|
||||
number: number;
|
||||
url: string;
|
||||
title: string;
|
||||
/** PR state: OPEN, MERGED, or CLOSED */
|
||||
state: PRState;
|
||||
createdAt: string;
|
||||
}
|
||||
Reference in New Issue
Block a user