mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
style: fix formatting with Prettier
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Sparkles } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { CLAUDE_MODELS } from "@/components/views/board-view/shared/model-constants";
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { CLAUDE_MODELS } from '@/components/views/board-view/shared/model-constants';
|
||||
|
||||
export function AIEnhancementSection() {
|
||||
const { enhancementModel, setEnhancementModel } = useAppStore();
|
||||
@@ -10,10 +10,10 @@ export function AIEnhancementSection() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
@@ -29,9 +29,7 @@ export function AIEnhancementSection() {
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="space-y-4">
|
||||
<Label className="text-foreground font-medium">
|
||||
Enhancement Model
|
||||
</Label>
|
||||
<Label className="text-foreground font-medium">Enhancement Model</Label>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
{CLAUDE_MODELS.map(({ id, label, description, badge }) => {
|
||||
const isActive = enhancementModel === id;
|
||||
@@ -40,46 +38,48 @@ export function AIEnhancementSection() {
|
||||
key={id}
|
||||
onClick={() => setEnhancementModel(id)}
|
||||
className={cn(
|
||||
"group flex flex-col items-start gap-2 px-4 py-4 rounded-xl text-left",
|
||||
"transition-all duration-200 ease-out",
|
||||
'group flex flex-col items-start gap-2 px-4 py-4 rounded-xl text-left',
|
||||
'transition-all duration-200 ease-out',
|
||||
isActive
|
||||
? [
|
||||
"bg-gradient-to-br from-brand-500/15 to-brand-600/10",
|
||||
"border-2 border-brand-500/40",
|
||||
"text-foreground",
|
||||
"shadow-md shadow-brand-500/10",
|
||||
'bg-gradient-to-br from-brand-500/15 to-brand-600/10',
|
||||
'border-2 border-brand-500/40',
|
||||
'text-foreground',
|
||||
'shadow-md shadow-brand-500/10',
|
||||
]
|
||||
: [
|
||||
"bg-accent/30 hover:bg-accent/50",
|
||||
"border border-border/50 hover:border-border",
|
||||
"text-muted-foreground hover:text-foreground",
|
||||
"hover:shadow-sm",
|
||||
'bg-accent/30 hover:bg-accent/50',
|
||||
'border border-border/50 hover:border-border',
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:shadow-sm',
|
||||
],
|
||||
"hover:scale-[1.02] active:scale-[0.98]"
|
||||
'hover:scale-[1.02] active:scale-[0.98]'
|
||||
)}
|
||||
data-testid={`enhancement-model-${id}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<span className={cn(
|
||||
"font-medium text-sm",
|
||||
isActive ? "text-foreground" : "group-hover:text-foreground"
|
||||
)}>
|
||||
<span
|
||||
className={cn(
|
||||
'font-medium text-sm',
|
||||
isActive ? 'text-foreground' : 'group-hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
{badge && (
|
||||
<span className={cn(
|
||||
"ml-auto text-xs px-2 py-0.5 rounded-full",
|
||||
isActive
|
||||
? "bg-brand-500/20 text-brand-500"
|
||||
: "bg-accent text-muted-foreground"
|
||||
)}>
|
||||
<span
|
||||
className={cn(
|
||||
'ml-auto text-xs px-2 py-0.5 rounded-full',
|
||||
isActive
|
||||
? 'bg-brand-500/20 text-brand-500'
|
||||
: 'bg-accent text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground/80">
|
||||
{description}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground/80">{description}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { AIEnhancementSection } from "./ai-enhancement-section";
|
||||
export { AIEnhancementSection } from './ai-enhancement-section';
|
||||
|
||||
@@ -1,8 +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 type { ProviderConfig } from "@/config/api-providers";
|
||||
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 type { ProviderConfig } from '@/config/api-providers';
|
||||
|
||||
interface ApiKeyFieldProps {
|
||||
config: ProviderConfig;
|
||||
@@ -42,7 +42,7 @@ export function ApiKeyField({ config }: ApiKeyFieldProps) {
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
id={inputId}
|
||||
type={showValue ? "text" : "password"}
|
||||
type={showValue ? 'text' : 'password'}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
@@ -82,7 +82,7 @@ export function ApiKeyField({ config }: ApiKeyFieldProps) {
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{descriptionPrefix}{" "}
|
||||
{descriptionPrefix}{' '}
|
||||
<a
|
||||
href={descriptionLinkHref}
|
||||
target="_blank"
|
||||
@@ -97,8 +97,8 @@ export function ApiKeyField({ config }: ApiKeyFieldProps) {
|
||||
<div
|
||||
className={`flex items-center gap-2 p-3 rounded-lg ${
|
||||
result.success
|
||||
? "bg-green-500/10 border border-green-500/20 text-green-400"
|
||||
: "bg-red-500/10 border border-red-500/20 text-red-400"
|
||||
? 'bg-green-500/10 border border-green-500/20 text-green-400'
|
||||
: 'bg-red-500/10 border border-red-500/20 text-red-400'
|
||||
}`}
|
||||
data-testid={resultTestId}
|
||||
>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { useSetupStore } from "@/store/setup-store";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Key, CheckCircle2, Settings, Trash2, Loader2 } from "lucide-react";
|
||||
import { ApiKeyField } from "./api-key-field";
|
||||
import { buildProviderConfigs } from "@/config/api-providers";
|
||||
import { AuthenticationStatusDisplay } from "./authentication-status-display";
|
||||
import { SecurityNotice } from "./security-notice";
|
||||
import { useApiKeyManagement } from "./hooks/use-api-key-management";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState, useCallback } from "react";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
import { toast } from "sonner";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Key, CheckCircle2, Settings, Trash2, Loader2 } from 'lucide-react';
|
||||
import { ApiKeyField } from './api-key-field';
|
||||
import { buildProviderConfigs } from '@/config/api-providers';
|
||||
import { AuthenticationStatusDisplay } from './authentication-status-display';
|
||||
import { SecurityNotice } from './security-notice';
|
||||
import { useApiKeyManagement } from './hooks/use-api-key-management';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
|
||||
export function ApiKeysSection() {
|
||||
const { apiKeys, setApiKeys } = useAppStore();
|
||||
@@ -19,8 +19,7 @@ export function ApiKeysSection() {
|
||||
const [isDeletingAnthropicKey, setIsDeletingAnthropicKey] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { providerConfigParams, apiKeyStatus, handleSave, saved } =
|
||||
useApiKeyManagement();
|
||||
const { providerConfigParams, apiKeyStatus, handleSave, saved } = useApiKeyManagement();
|
||||
|
||||
const providerConfigs = buildProviderConfigs(providerConfigParams);
|
||||
|
||||
@@ -30,24 +29,24 @@ export function ApiKeysSection() {
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api.setup?.deleteApiKey) {
|
||||
toast.error("Delete API not available");
|
||||
toast.error('Delete API not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await api.setup.deleteApiKey("anthropic");
|
||||
const result = await api.setup.deleteApiKey('anthropic');
|
||||
if (result.success) {
|
||||
setApiKeys({ ...apiKeys, anthropic: "" });
|
||||
setApiKeys({ ...apiKeys, anthropic: '' });
|
||||
setClaudeAuthStatus({
|
||||
authenticated: false,
|
||||
method: "none",
|
||||
method: 'none',
|
||||
hasCredentialsFile: claudeAuthStatus?.hasCredentialsFile || false,
|
||||
});
|
||||
toast.success("Anthropic API key deleted");
|
||||
toast.success('Anthropic API key deleted');
|
||||
} else {
|
||||
toast.error(result.error || "Failed to delete API key");
|
||||
toast.error(result.error || 'Failed to delete API key');
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to delete API key");
|
||||
toast.error('Failed to delete API key');
|
||||
} finally {
|
||||
setIsDeletingAnthropicKey(false);
|
||||
}
|
||||
@@ -56,16 +55,16 @@ export function ApiKeysSection() {
|
||||
// Open setup wizard
|
||||
const openSetupWizard = useCallback(() => {
|
||||
setSetupComplete(false);
|
||||
navigate({ to: "/setup" });
|
||||
navigate({ to: '/setup' });
|
||||
}, [setSetupComplete, navigate]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
@@ -101,13 +100,13 @@ export function ApiKeysSection() {
|
||||
onClick={handleSave}
|
||||
data-testid="save-settings"
|
||||
className={cn(
|
||||
"min-w-[140px] h-10",
|
||||
"bg-gradient-to-r from-brand-500 to-brand-600",
|
||||
"hover:from-brand-600 hover:to-brand-600",
|
||||
"text-white font-medium border-0",
|
||||
"shadow-md shadow-brand-500/20 hover:shadow-lg hover:shadow-brand-500/25",
|
||||
"transition-all duration-200 ease-out",
|
||||
"hover:scale-[1.02] active:scale-[0.98]"
|
||||
'min-w-[140px] h-10',
|
||||
'bg-gradient-to-r from-brand-500 to-brand-600',
|
||||
'hover:from-brand-600 hover:to-brand-600',
|
||||
'text-white font-medium border-0',
|
||||
'shadow-md shadow-brand-500/20 hover:shadow-lg hover:shadow-brand-500/25',
|
||||
'transition-all duration-200 ease-out',
|
||||
'hover:scale-[1.02] active:scale-[0.98]'
|
||||
)}
|
||||
>
|
||||
{saved ? (
|
||||
@@ -116,7 +115,7 @@ export function ApiKeysSection() {
|
||||
Saved!
|
||||
</>
|
||||
) : (
|
||||
"Save API Keys"
|
||||
'Save API Keys'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
import type { ProviderConfigParams } from "@/config/api-providers";
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import type { ProviderConfigParams } from '@/config/api-providers';
|
||||
|
||||
interface TestResult {
|
||||
success: boolean;
|
||||
@@ -32,9 +32,7 @@ export function useApiKeyManagement() {
|
||||
const [testingConnection, setTestingConnection] = useState(false);
|
||||
const [testResult, setTestResult] = useState<TestResult | null>(null);
|
||||
const [testingGeminiConnection, setTestingGeminiConnection] = useState(false);
|
||||
const [geminiTestResult, setGeminiTestResult] = useState<TestResult | null>(
|
||||
null
|
||||
);
|
||||
const [geminiTestResult, setGeminiTestResult] = useState<TestResult | null>(null);
|
||||
|
||||
// API key status from environment
|
||||
const [apiKeyStatus, setApiKeyStatus] = useState<ApiKeyStatus | null>(null);
|
||||
@@ -62,7 +60,7 @@ export function useApiKeyManagement() {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to check API key status:", error);
|
||||
console.error('Failed to check API key status:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -76,23 +74,23 @@ export function useApiKeyManagement() {
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const data = await api.setup.verifyClaudeAuth("api_key");
|
||||
const data = await api.setup.verifyClaudeAuth('api_key');
|
||||
|
||||
if (data.success && data.authenticated) {
|
||||
setTestResult({
|
||||
success: true,
|
||||
message: "Connection successful! Claude responded.",
|
||||
message: 'Connection successful! Claude responded.',
|
||||
});
|
||||
} else {
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: data.error || "Failed to connect to Claude API.",
|
||||
message: data.error || 'Failed to connect to Claude API.',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: "Network error. Please check your connection.",
|
||||
message: 'Network error. Please check your connection.',
|
||||
});
|
||||
} finally {
|
||||
setTestingConnection(false);
|
||||
@@ -109,7 +107,7 @@ export function useApiKeyManagement() {
|
||||
if (!googleKey || googleKey.trim().length < 10) {
|
||||
setGeminiTestResult({
|
||||
success: false,
|
||||
message: "Please enter a valid API key.",
|
||||
message: 'Please enter a valid API key.',
|
||||
});
|
||||
setTestingGeminiConnection(false);
|
||||
return;
|
||||
@@ -119,7 +117,7 @@ export function useApiKeyManagement() {
|
||||
// Full verification requires a backend endpoint
|
||||
setGeminiTestResult({
|
||||
success: true,
|
||||
message: "API key saved. Connection test not yet available.",
|
||||
message: 'API key saved. Connection test not yet available.',
|
||||
});
|
||||
setTestingGeminiConnection(false);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
|
||||
interface SecurityNoticeProps {
|
||||
title?: string;
|
||||
@@ -6,7 +6,7 @@ interface SecurityNoticeProps {
|
||||
}
|
||||
|
||||
export function SecurityNotice({
|
||||
title = "Security Notice",
|
||||
title = 'Security Notice',
|
||||
message = "API keys are stored in your browser's local storage. Never share your API keys or commit them to version control.",
|
||||
}: SecurityNoticeProps) {
|
||||
return (
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Volume2, VolumeX } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Volume2, VolumeX } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface AudioSectionProps {
|
||||
muteDoneSound: boolean;
|
||||
onMuteDoneSoundChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function AudioSection({
|
||||
muteDoneSound,
|
||||
onMuteDoneSoundChange,
|
||||
}: AudioSectionProps) {
|
||||
export function AudioSection({ muteDoneSound, onMuteDoneSoundChange }: AudioSectionProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
@@ -26,9 +23,7 @@ export function AudioSection({
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||
<Volume2 className="w-5 h-5 text-brand-500" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">
|
||||
Audio
|
||||
</h2>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">Audio</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Configure audio and notification settings.
|
||||
@@ -52,9 +47,9 @@ export function AudioSection({
|
||||
Mute notification sound when agents complete
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
When enabled, disables the "ding" sound that plays when
|
||||
an agent completes a feature. The feature will still move to the
|
||||
completed column, but without audio notification.
|
||||
When enabled, disables the "ding" sound that plays when an agent completes a
|
||||
feature. The feature will still move to the completed column, but without audio
|
||||
notification.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Terminal,
|
||||
CheckCircle2,
|
||||
AlertCircle,
|
||||
RefreshCw,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { CliStatus } from "../shared/types";
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Terminal, CheckCircle2, AlertCircle, RefreshCw } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CliStatus } from '../shared/types';
|
||||
|
||||
interface CliStatusProps {
|
||||
status: CliStatus | null;
|
||||
@@ -14,20 +9,16 @@ interface CliStatusProps {
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
export function ClaudeCliStatus({
|
||||
status,
|
||||
isChecking,
|
||||
onRefresh,
|
||||
}: CliStatusProps) {
|
||||
export function ClaudeCliStatus({ status, isChecking, onRefresh }: CliStatusProps) {
|
||||
if (!status) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
@@ -48,32 +39,28 @@ export function ClaudeCliStatus({
|
||||
data-testid="refresh-claude-cli"
|
||||
title="Refresh Claude CLI detection"
|
||||
className={cn(
|
||||
"h-9 w-9 rounded-lg",
|
||||
"hover:bg-accent/50 hover:scale-105",
|
||||
"transition-all duration-200"
|
||||
'h-9 w-9 rounded-lg',
|
||||
'hover:bg-accent/50 hover:scale-105',
|
||||
'transition-all duration-200'
|
||||
)}
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("w-4 h-4", isChecking && "animate-spin")}
|
||||
/>
|
||||
<RefreshCw className={cn('w-4 h-4', isChecking && 'animate-spin')} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Claude Code CLI provides better performance for long-running tasks,
|
||||
especially with ultrathink.
|
||||
Claude Code CLI provides better performance for long-running tasks, especially with
|
||||
ultrathink.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
{status.success && status.status === "installed" ? (
|
||||
{status.success && status.status === 'installed' ? (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3 p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div className="w-10 h-10 rounded-xl bg-emerald-500/15 flex items-center justify-center border border-emerald-500/20 shrink-0">
|
||||
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-emerald-400">
|
||||
Claude Code CLI Installed
|
||||
</p>
|
||||
<p className="text-sm font-medium text-emerald-400">Claude Code CLI Installed</p>
|
||||
<div className="text-xs text-emerald-400/70 mt-1.5 space-y-0.5">
|
||||
{status.method && (
|
||||
<p>
|
||||
@@ -94,9 +81,7 @@ export function ClaudeCliStatus({
|
||||
</div>
|
||||
</div>
|
||||
{status.recommendation && (
|
||||
<p className="text-xs text-muted-foreground/70 ml-1">
|
||||
{status.recommendation}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground/70 ml-1">{status.recommendation}</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@@ -106,24 +91,22 @@ export function ClaudeCliStatus({
|
||||
<AlertCircle className="w-5 h-5 text-amber-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-amber-400">
|
||||
Claude Code CLI Not Detected
|
||||
</p>
|
||||
<p className="text-sm font-medium text-amber-400">Claude Code CLI Not Detected</p>
|
||||
<p className="text-xs text-amber-400/70 mt-1">
|
||||
{status.recommendation ||
|
||||
"Consider installing Claude Code CLI for optimal performance with ultrathink."}
|
||||
'Consider installing Claude Code CLI for optimal performance with ultrathink.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{status.installCommands && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-medium text-foreground/80">
|
||||
Installation Commands:
|
||||
</p>
|
||||
<p className="text-xs font-medium text-foreground/80">Installation Commands:</p>
|
||||
<div className="space-y-2">
|
||||
{status.installCommands.npm && (
|
||||
<div className="p-3 rounded-xl bg-accent/30 border border-border/50">
|
||||
<p className="text-[10px] text-muted-foreground mb-1.5 font-medium uppercase tracking-wider">npm</p>
|
||||
<p className="text-[10px] text-muted-foreground mb-1.5 font-medium uppercase tracking-wider">
|
||||
npm
|
||||
</p>
|
||||
<code className="text-xs text-foreground/80 font-mono break-all">
|
||||
{status.installCommands.npm}
|
||||
</code>
|
||||
@@ -131,7 +114,9 @@ export function ClaudeCliStatus({
|
||||
)}
|
||||
{status.installCommands.macos && (
|
||||
<div className="p-3 rounded-xl bg-accent/30 border border-border/50">
|
||||
<p className="text-[10px] text-muted-foreground mb-1.5 font-medium uppercase tracking-wider">macOS/Linux</p>
|
||||
<p className="text-[10px] text-muted-foreground mb-1.5 font-medium uppercase tracking-wider">
|
||||
macOS/Linux
|
||||
</p>
|
||||
<code className="text-xs text-foreground/80 font-mono break-all">
|
||||
{status.installCommands.macos}
|
||||
</code>
|
||||
@@ -139,7 +124,9 @@ export function ClaudeCliStatus({
|
||||
)}
|
||||
{status.installCommands.windows && (
|
||||
<div className="p-3 rounded-xl bg-accent/30 border border-border/50">
|
||||
<p className="text-[10px] text-muted-foreground mb-1.5 font-medium uppercase tracking-wider">Windows (PowerShell)</p>
|
||||
<p className="text-[10px] text-muted-foreground mb-1.5 font-medium uppercase tracking-wider">
|
||||
Windows (PowerShell)
|
||||
</p>
|
||||
<code className="text-xs text-foreground/80 font-mono break-all">
|
||||
{status.installCommands.windows}
|
||||
</code>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Keyboard } from "lucide-react";
|
||||
import { Keyboard } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { KeyboardMap, ShortcutReferencePanel } from "@/components/ui/keyboard-map";
|
||||
} from '@/components/ui/dialog';
|
||||
import { KeyboardMap, ShortcutReferencePanel } from '@/components/ui/keyboard-map';
|
||||
|
||||
interface KeyboardMapDialogProps {
|
||||
open: boolean;
|
||||
@@ -23,8 +23,8 @@ export function KeyboardMapDialog({ open, onOpenChange }: KeyboardMapDialogProps
|
||||
Keyboard Shortcut Map
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-muted-foreground">
|
||||
Visual overview of all keyboard shortcuts. Keys in color are bound to
|
||||
shortcuts. Click on any shortcut below to edit it.
|
||||
Visual overview of all keyboard shortcuts. Keys in color are bound to shortcuts. Click
|
||||
on any shortcut below to edit it.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Settings } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Settings } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface SettingsHeaderProps {
|
||||
title?: string;
|
||||
@@ -7,23 +7,27 @@ interface SettingsHeaderProps {
|
||||
}
|
||||
|
||||
export function SettingsHeader({
|
||||
title = "Settings",
|
||||
description = "Configure your API keys and preferences",
|
||||
title = 'Settings',
|
||||
description = 'Configure your API keys and preferences',
|
||||
}: SettingsHeaderProps) {
|
||||
return (
|
||||
<div className={cn(
|
||||
"shrink-0",
|
||||
"border-b border-border/50",
|
||||
"bg-gradient-to-r from-card/90 via-card/70 to-card/80 backdrop-blur-xl"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
'shrink-0',
|
||||
'border-b border-border/50',
|
||||
'bg-gradient-to-r from-card/90 via-card/70 to-card/80 backdrop-blur-xl'
|
||||
)}
|
||||
>
|
||||
<div className="px-8 py-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={cn(
|
||||
"w-12 h-12 rounded-2xl flex items-center justify-center",
|
||||
"bg-gradient-to-br from-brand-500 to-brand-600",
|
||||
"shadow-lg shadow-brand-500/25",
|
||||
"ring-1 ring-white/10"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
'w-12 h-12 rounded-2xl flex items-center justify-center',
|
||||
'bg-gradient-to-br from-brand-500 to-brand-600',
|
||||
'shadow-lg shadow-brand-500/25',
|
||||
'ring-1 ring-white/10'
|
||||
)}
|
||||
>
|
||||
<Settings className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Project } from "@/lib/electron";
|
||||
import type { NavigationItem } from "../config/navigation";
|
||||
import type { SettingsViewId } from "../hooks/use-settings-view";
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { Project } from '@/lib/electron';
|
||||
import type { NavigationItem } from '../config/navigation';
|
||||
import type { SettingsViewId } from '../hooks/use-settings-view';
|
||||
|
||||
interface SettingsNavigationProps {
|
||||
navItems: NavigationItem[];
|
||||
@@ -17,14 +17,16 @@ export function SettingsNavigation({
|
||||
onNavigate,
|
||||
}: SettingsNavigationProps) {
|
||||
return (
|
||||
<nav className={cn(
|
||||
"hidden lg:block w-52 shrink-0",
|
||||
"border-r border-border/50",
|
||||
"bg-gradient-to-b from-card/80 via-card/60 to-card/40 backdrop-blur-xl"
|
||||
)}>
|
||||
<nav
|
||||
className={cn(
|
||||
'hidden lg:block w-52 shrink-0',
|
||||
'border-r border-border/50',
|
||||
'bg-gradient-to-b from-card/80 via-card/60 to-card/40 backdrop-blur-xl'
|
||||
)}
|
||||
>
|
||||
<div className="sticky top-0 p-4 space-y-1.5">
|
||||
{navItems
|
||||
.filter((item) => item.id !== "danger" || currentProject)
|
||||
.filter((item) => item.id !== 'danger' || currentProject)
|
||||
.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = activeSection === item.id;
|
||||
@@ -33,20 +35,20 @@ export function SettingsNavigation({
|
||||
key={item.id}
|
||||
onClick={() => onNavigate(item.id)}
|
||||
className={cn(
|
||||
"group w-full flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ease-out text-left relative overflow-hidden",
|
||||
'group w-full flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ease-out text-left relative overflow-hidden',
|
||||
isActive
|
||||
? [
|
||||
"bg-gradient-to-r from-brand-500/15 via-brand-500/10 to-brand-600/5",
|
||||
"text-foreground",
|
||||
"border border-brand-500/25",
|
||||
"shadow-sm shadow-brand-500/5",
|
||||
'bg-gradient-to-r from-brand-500/15 via-brand-500/10 to-brand-600/5',
|
||||
'text-foreground',
|
||||
'border border-brand-500/25',
|
||||
'shadow-sm shadow-brand-500/5',
|
||||
]
|
||||
: [
|
||||
"text-muted-foreground hover:text-foreground",
|
||||
"hover:bg-accent/50",
|
||||
"border border-transparent hover:border-border/40",
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:bg-accent/50',
|
||||
'border border-transparent hover:border-border/40',
|
||||
],
|
||||
"hover:scale-[1.01] active:scale-[0.98]"
|
||||
'hover:scale-[1.01] active:scale-[0.98]'
|
||||
)}
|
||||
>
|
||||
{/* Active indicator bar */}
|
||||
@@ -55,10 +57,8 @@ export function SettingsNavigation({
|
||||
)}
|
||||
<Icon
|
||||
className={cn(
|
||||
"w-4 h-4 shrink-0 transition-all duration-200",
|
||||
isActive
|
||||
? "text-brand-500"
|
||||
: "group-hover:text-brand-400 group-hover:scale-110"
|
||||
'w-4 h-4 shrink-0 transition-all duration-200',
|
||||
isActive ? 'text-brand-500' : 'group-hover:text-brand-400 group-hover:scale-110'
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">{item.label}</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import {
|
||||
Key,
|
||||
Terminal,
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
FlaskConical,
|
||||
Trash2,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
import type { SettingsViewId } from "../hooks/use-settings-view";
|
||||
} from 'lucide-react';
|
||||
import type { SettingsViewId } from '../hooks/use-settings-view';
|
||||
|
||||
export interface NavigationItem {
|
||||
id: SettingsViewId;
|
||||
@@ -20,13 +20,13 @@ export interface NavigationItem {
|
||||
|
||||
// Navigation items for the settings side panel
|
||||
export const NAV_ITEMS: NavigationItem[] = [
|
||||
{ id: "api-keys", label: "API Keys", icon: Key },
|
||||
{ id: "claude", label: "Claude", icon: Terminal },
|
||||
{ id: "ai-enhancement", label: "AI Enhancement", icon: Sparkles },
|
||||
{ id: "appearance", label: "Appearance", icon: Palette },
|
||||
{ id: "terminal", label: "Terminal", icon: SquareTerminal },
|
||||
{ id: "keyboard", label: "Keyboard Shortcuts", icon: Settings2 },
|
||||
{ id: "audio", label: "Audio", icon: Volume2 },
|
||||
{ id: "defaults", label: "Feature Defaults", icon: FlaskConical },
|
||||
{ id: "danger", label: "Danger Zone", icon: Trash2 },
|
||||
{ id: 'api-keys', label: 'API Keys', icon: Key },
|
||||
{ id: 'claude', label: 'Claude', icon: Terminal },
|
||||
{ id: 'ai-enhancement', label: 'AI Enhancement', icon: Sparkles },
|
||||
{ id: 'appearance', label: 'Appearance', icon: Palette },
|
||||
{ id: 'terminal', label: 'Terminal', icon: SquareTerminal },
|
||||
{ id: 'keyboard', label: 'Keyboard Shortcuts', icon: Settings2 },
|
||||
{ id: 'audio', label: 'Audio', icon: Volume2 },
|
||||
{ id: 'defaults', label: 'Feature Defaults', icon: FlaskConical },
|
||||
{ id: 'danger', label: 'Danger Zone', icon: Trash2 },
|
||||
];
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Trash2, Folder, AlertTriangle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Project } from "../shared/types";
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Trash2, Folder, AlertTriangle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { Project } from '../shared/types';
|
||||
|
||||
interface DangerZoneSectionProps {
|
||||
project: Project | null;
|
||||
onDeleteClick: () => void;
|
||||
}
|
||||
|
||||
export function DangerZoneSection({
|
||||
project,
|
||||
onDeleteClick,
|
||||
}: DangerZoneSectionProps) {
|
||||
export function DangerZoneSection({ project, onDeleteClick }: DangerZoneSectionProps) {
|
||||
if (!project) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-destructive/30",
|
||||
"bg-gradient-to-br from-destructive/5 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-destructive/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-destructive/30',
|
||||
'bg-gradient-to-br from-destructive/5 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-destructive/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-destructive/20 bg-gradient-to-r from-destructive/5 via-transparent to-transparent">
|
||||
@@ -41,12 +38,8 @@ export function DangerZoneSection({
|
||||
<Folder className="w-5 h-5 text-brand-500" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="font-medium text-foreground truncate">
|
||||
{project.name}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground/70 truncate mt-0.5">
|
||||
{project.path}
|
||||
</p>
|
||||
<p className="font-medium text-foreground truncate">{project.name}</p>
|
||||
<p className="text-xs text-muted-foreground/70 truncate mt-0.5">{project.path}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@@ -54,10 +47,10 @@ export function DangerZoneSection({
|
||||
onClick={onDeleteClick}
|
||||
data-testid="delete-project-button"
|
||||
className={cn(
|
||||
"shrink-0",
|
||||
"shadow-md shadow-destructive/20 hover:shadow-lg hover:shadow-destructive/25",
|
||||
"transition-all duration-200 ease-out",
|
||||
"hover:scale-[1.02] active:scale-[0.98]"
|
||||
'shrink-0',
|
||||
'shadow-md shadow-destructive/20 hover:shadow-lg hover:shadow-destructive/25',
|
||||
'transition-all duration-200 ease-out',
|
||||
'hover:scale-[1.02] active:scale-[0.98]'
|
||||
)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
FlaskConical, Settings2, TestTube, GitBranch, AlertCircle,
|
||||
Zap, ClipboardList, FileText, ScrollText, ShieldCheck, User
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
FlaskConical,
|
||||
Settings2,
|
||||
TestTube,
|
||||
GitBranch,
|
||||
AlertCircle,
|
||||
Zap,
|
||||
ClipboardList,
|
||||
FileText,
|
||||
ScrollText,
|
||||
ShieldCheck,
|
||||
User,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import type { AIProfile } from "@/store/app-store";
|
||||
} from '@/components/ui/select';
|
||||
import type { AIProfile } from '@/store/app-store';
|
||||
|
||||
type PlanningMode = 'skip' | 'lite' | 'spec' | 'full';
|
||||
|
||||
@@ -58,10 +67,10 @@ export function FeatureDefaultsSection({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
@@ -69,9 +78,7 @@ export function FeatureDefaultsSection({
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||
<FlaskConical className="w-5 h-5 text-brand-500" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">
|
||||
Feature Defaults
|
||||
</h2>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">Feature Defaults</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Configure default settings for new features.
|
||||
@@ -80,13 +87,18 @@ export function FeatureDefaultsSection({
|
||||
<div className="p-6 space-y-5">
|
||||
{/* Planning Mode Default */}
|
||||
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||
<div className={cn(
|
||||
"w-10 h-10 mt-0.5 rounded-xl flex items-center justify-center shrink-0",
|
||||
defaultPlanningMode === 'skip' ? "bg-emerald-500/10" :
|
||||
defaultPlanningMode === 'lite' ? "bg-blue-500/10" :
|
||||
defaultPlanningMode === 'spec' ? "bg-purple-500/10" :
|
||||
"bg-amber-500/10"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
'w-10 h-10 mt-0.5 rounded-xl flex items-center justify-center shrink-0',
|
||||
defaultPlanningMode === 'skip'
|
||||
? 'bg-emerald-500/10'
|
||||
: defaultPlanningMode === 'lite'
|
||||
? 'bg-blue-500/10'
|
||||
: defaultPlanningMode === 'spec'
|
||||
? 'bg-purple-500/10'
|
||||
: 'bg-amber-500/10'
|
||||
)}
|
||||
>
|
||||
{defaultPlanningMode === 'skip' && <Zap className="w-5 h-5 text-emerald-500" />}
|
||||
{defaultPlanningMode === 'lite' && <ClipboardList className="w-5 h-5 text-blue-500" />}
|
||||
{defaultPlanningMode === 'spec' && <FileText className="w-5 h-5 text-purple-500" />}
|
||||
@@ -94,17 +106,12 @@ export function FeatureDefaultsSection({
|
||||
</div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-foreground font-medium">
|
||||
Default Planning Mode
|
||||
</Label>
|
||||
<Label className="text-foreground font-medium">Default Planning Mode</Label>
|
||||
<Select
|
||||
value={defaultPlanningMode}
|
||||
onValueChange={(v: string) => onDefaultPlanningModeChange(v as PlanningMode)}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="w-[160px] h-8"
|
||||
data-testid="default-planning-mode-select"
|
||||
>
|
||||
<SelectTrigger className="w-[160px] h-8" data-testid="default-planning-mode-select">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -137,10 +144,14 @@ export function FeatureDefaultsSection({
|
||||
</Select>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
{defaultPlanningMode === 'skip' && "Jump straight to implementation without upfront planning."}
|
||||
{defaultPlanningMode === 'lite' && "Create a quick planning outline with tasks before building."}
|
||||
{defaultPlanningMode === 'spec' && "Generate a specification with acceptance criteria for approval."}
|
||||
{defaultPlanningMode === 'full' && "Create comprehensive spec with phased implementation plan."}
|
||||
{defaultPlanningMode === 'skip' &&
|
||||
'Jump straight to implementation without upfront planning.'}
|
||||
{defaultPlanningMode === 'lite' &&
|
||||
'Create a quick planning outline with tasks before building.'}
|
||||
{defaultPlanningMode === 'spec' &&
|
||||
'Generate a specification with acceptance criteria for approval.'}
|
||||
{defaultPlanningMode === 'full' &&
|
||||
'Create comprehensive spec with phased implementation plan.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,9 +163,7 @@ export function FeatureDefaultsSection({
|
||||
<Checkbox
|
||||
id="default-require-plan-approval"
|
||||
checked={defaultRequirePlanApproval}
|
||||
onCheckedChange={(checked) =>
|
||||
onDefaultRequirePlanApprovalChange(checked === true)
|
||||
}
|
||||
onCheckedChange={(checked) => onDefaultRequirePlanApprovalChange(checked === true)}
|
||||
className="mt-1"
|
||||
data-testid="default-require-plan-approval-checkbox"
|
||||
/>
|
||||
@@ -187,17 +196,12 @@ export function FeatureDefaultsSection({
|
||||
</div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-foreground font-medium">
|
||||
Default AI Profile
|
||||
</Label>
|
||||
<Label className="text-foreground font-medium">Default AI Profile</Label>
|
||||
<Select
|
||||
value={defaultAIProfileId ?? "none"}
|
||||
onValueChange={(v: string) => onDefaultAIProfileIdChange(v === "none" ? null : v)}
|
||||
value={defaultAIProfileId ?? 'none'}
|
||||
onValueChange={(v: string) => onDefaultAIProfileIdChange(v === 'none' ? null : v)}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="w-[180px] h-8"
|
||||
data-testid="default-ai-profile-select"
|
||||
>
|
||||
<SelectTrigger className="w-[180px] h-8" data-testid="default-ai-profile-select">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -215,7 +219,7 @@ export function FeatureDefaultsSection({
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
{selectedProfile
|
||||
? `New features will use the "${selectedProfile.name}" profile (${selectedProfile.model}, ${selectedProfile.thinkingLevel} thinking).`
|
||||
: "Pre-select an AI profile when creating new features. Choose \"None\" to pick manually each time."}
|
||||
: 'Pre-select an AI profile when creating new features. Choose "None" to pick manually each time.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,9 +232,7 @@ export function FeatureDefaultsSection({
|
||||
<Checkbox
|
||||
id="show-profiles-only"
|
||||
checked={showProfilesOnly}
|
||||
onCheckedChange={(checked) =>
|
||||
onShowProfilesOnlyChange(checked === true)
|
||||
}
|
||||
onCheckedChange={(checked) => onShowProfilesOnlyChange(checked === true)}
|
||||
className="mt-1"
|
||||
data-testid="show-profiles-only-checkbox"
|
||||
/>
|
||||
@@ -243,9 +245,8 @@ export function FeatureDefaultsSection({
|
||||
Show profiles only by default
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
When enabled, the Add Feature dialog will show only AI profiles
|
||||
and hide advanced model tweaking options. This creates a cleaner, less
|
||||
overwhelming UI.
|
||||
When enabled, the Add Feature dialog will show only AI profiles and hide advanced
|
||||
model tweaking options. This creates a cleaner, less overwhelming UI.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,9 +259,7 @@ export function FeatureDefaultsSection({
|
||||
<Checkbox
|
||||
id="default-skip-tests"
|
||||
checked={!defaultSkipTests}
|
||||
onCheckedChange={(checked) =>
|
||||
onDefaultSkipTestsChange(checked !== true)
|
||||
}
|
||||
onCheckedChange={(checked) => onDefaultSkipTestsChange(checked !== true)}
|
||||
className="mt-1"
|
||||
data-testid="default-skip-tests-checkbox"
|
||||
/>
|
||||
@@ -273,8 +272,8 @@ export function FeatureDefaultsSection({
|
||||
Enable automated testing by default
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
When enabled, new features will use TDD with automated tests. When disabled, features will
|
||||
require manual verification.
|
||||
When enabled, new features will use TDD with automated tests. When disabled, features
|
||||
will require manual verification.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -287,9 +286,7 @@ export function FeatureDefaultsSection({
|
||||
<Checkbox
|
||||
id="enable-dependency-blocking"
|
||||
checked={enableDependencyBlocking}
|
||||
onCheckedChange={(checked) =>
|
||||
onEnableDependencyBlockingChange(checked === true)
|
||||
}
|
||||
onCheckedChange={(checked) => onEnableDependencyBlockingChange(checked === true)}
|
||||
className="mt-1"
|
||||
data-testid="enable-dependency-blocking-checkbox"
|
||||
/>
|
||||
@@ -302,9 +299,9 @@ export function FeatureDefaultsSection({
|
||||
Enable Dependency Blocking
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
When enabled, features with incomplete dependencies will show blocked badges
|
||||
and warnings. Auto mode and backlog ordering always respect dependencies
|
||||
regardless of this setting.
|
||||
When enabled, features with incomplete dependencies will show blocked badges and
|
||||
warnings. Auto mode and backlog ordering always respect dependencies regardless of
|
||||
this setting.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -317,9 +314,7 @@ export function FeatureDefaultsSection({
|
||||
<Checkbox
|
||||
id="use-worktrees"
|
||||
checked={useWorktrees}
|
||||
onCheckedChange={(checked) =>
|
||||
onUseWorktreesChange(checked === true)
|
||||
}
|
||||
onCheckedChange={(checked) => onUseWorktreesChange(checked === true)}
|
||||
className="mt-1"
|
||||
data-testid="use-worktrees-checkbox"
|
||||
/>
|
||||
@@ -335,8 +330,8 @@ export function FeatureDefaultsSection({
|
||||
</span>
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground/80 leading-relaxed">
|
||||
Creates isolated git branches for each feature. When disabled,
|
||||
agents work directly in the main project directory.
|
||||
Creates isolated git branches for each feature. When disabled, agents work directly in
|
||||
the main project directory.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { useCliStatus } from "./use-cli-status";
|
||||
export { useSettingsView, type SettingsViewId } from "./use-settings-view";
|
||||
export { useCliStatus } from './use-cli-status';
|
||||
export { useSettingsView, type SettingsViewId } from './use-settings-view';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useSetupStore } from "@/store/setup-store";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
|
||||
interface CliStatusResult {
|
||||
success: boolean;
|
||||
@@ -25,8 +25,7 @@ interface CliStatusResult {
|
||||
export function useCliStatus() {
|
||||
const { setClaudeAuthStatus } = useSetupStore();
|
||||
|
||||
const [claudeCliStatus, setClaudeCliStatus] =
|
||||
useState<CliStatusResult | null>(null);
|
||||
const [claudeCliStatus, setClaudeCliStatus] = useState<CliStatusResult | null>(null);
|
||||
|
||||
const [isCheckingClaudeCli, setIsCheckingClaudeCli] = useState(false);
|
||||
|
||||
@@ -41,7 +40,7 @@ export function useCliStatus() {
|
||||
const status = await api.checkClaudeCli();
|
||||
setClaudeCliStatus(status);
|
||||
} catch (error) {
|
||||
console.error("Failed to check Claude CLI status:", error);
|
||||
console.error('Failed to check Claude CLI status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,16 +56,27 @@ export function useCliStatus() {
|
||||
};
|
||||
// Map server method names to client method types
|
||||
// Server returns: oauth_token_env, oauth_token, api_key_env, api_key, credentials_file, cli_authenticated, none
|
||||
const validMethods = ["oauth_token_env", "oauth_token", "api_key", "api_key_env", "credentials_file", "cli_authenticated", "none"] as const;
|
||||
type AuthMethod = typeof validMethods[number];
|
||||
const validMethods = [
|
||||
'oauth_token_env',
|
||||
'oauth_token',
|
||||
'api_key',
|
||||
'api_key_env',
|
||||
'credentials_file',
|
||||
'cli_authenticated',
|
||||
'none',
|
||||
] as const;
|
||||
type AuthMethod = (typeof validMethods)[number];
|
||||
const method: AuthMethod = validMethods.includes(auth.method as AuthMethod)
|
||||
? (auth.method as AuthMethod)
|
||||
: auth.authenticated ? "api_key" : "none"; // Default authenticated to api_key, not none
|
||||
: auth.authenticated
|
||||
? 'api_key'
|
||||
: 'none'; // Default authenticated to api_key, not none
|
||||
const authStatus = {
|
||||
authenticated: auth.authenticated,
|
||||
method,
|
||||
hasCredentialsFile: auth.hasCredentialsFile ?? false,
|
||||
oauthTokenValid: auth.oauthTokenValid || auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
|
||||
oauthTokenValid:
|
||||
auth.oauthTokenValid || auth.hasStoredOAuthToken || auth.hasEnvOAuthToken,
|
||||
apiKeyValid: auth.apiKeyValid || auth.hasStoredApiKey || auth.hasEnvApiKey,
|
||||
hasEnvOAuthToken: auth.hasEnvOAuthToken,
|
||||
hasEnvApiKey: auth.hasEnvApiKey,
|
||||
@@ -74,7 +84,7 @@ export function useCliStatus() {
|
||||
setClaudeAuthStatus(authStatus);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to check Claude auth status:", error);
|
||||
console.error('Failed to check Claude auth status:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -92,7 +102,7 @@ export function useCliStatus() {
|
||||
setClaudeCliStatus(status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to refresh Claude CLI status:", error);
|
||||
console.error('Failed to refresh Claude CLI status:', error);
|
||||
} finally {
|
||||
setIsCheckingClaudeCli(false);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export type SettingsViewId =
|
||||
| "api-keys"
|
||||
| "claude"
|
||||
| "ai-enhancement"
|
||||
| "appearance"
|
||||
| "terminal"
|
||||
| "keyboard"
|
||||
| "audio"
|
||||
| "defaults"
|
||||
| "danger";
|
||||
| 'api-keys'
|
||||
| 'claude'
|
||||
| 'ai-enhancement'
|
||||
| 'appearance'
|
||||
| 'terminal'
|
||||
| 'keyboard'
|
||||
| 'audio'
|
||||
| 'defaults'
|
||||
| 'danger';
|
||||
|
||||
interface UseSettingsViewOptions {
|
||||
initialView?: SettingsViewId;
|
||||
}
|
||||
|
||||
export function useSettingsView({
|
||||
initialView = "api-keys",
|
||||
}: UseSettingsViewOptions = {}) {
|
||||
export function useSettingsView({ initialView = 'api-keys' }: UseSettingsViewOptions = {}) {
|
||||
const [activeView, setActiveView] = useState<SettingsViewId>(initialView);
|
||||
|
||||
const navigateTo = useCallback((viewId: SettingsViewId) => {
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Settings2, Keyboard } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Settings2, Keyboard } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface KeyboardShortcutsSectionProps {
|
||||
onOpenKeyboardMap: () => void;
|
||||
}
|
||||
|
||||
export function KeyboardShortcutsSection({
|
||||
onOpenKeyboardMap,
|
||||
}: KeyboardShortcutsSectionProps) {
|
||||
export function KeyboardShortcutsSection({ onOpenKeyboardMap }: KeyboardShortcutsSectionProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
@@ -28,8 +26,7 @@ export function KeyboardShortcutsSection({
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Customize keyboard shortcuts for navigation and actions using the
|
||||
visual keyboard map.
|
||||
Customize keyboard shortcuts for navigation and actions using the visual keyboard map.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
@@ -42,12 +39,10 @@ export function KeyboardShortcutsSection({
|
||||
<div className="absolute inset-0 bg-brand-500/10 blur-2xl rounded-full -z-10" />
|
||||
</div>
|
||||
<div className="space-y-2 max-w-md">
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
Use the Visual Keyboard Map
|
||||
</h3>
|
||||
<h3 className="text-lg font-semibold text-foreground">Use the Visual Keyboard Map</h3>
|
||||
<p className="text-sm text-muted-foreground/80">
|
||||
Click the button below to customize your keyboard shortcuts. The visual
|
||||
interface shows all available keys and lets you easily edit shortcuts.
|
||||
Click the button below to customize your keyboard shortcuts. The visual interface
|
||||
shows all available keys and lets you easily edit shortcuts.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -55,13 +50,13 @@ export function KeyboardShortcutsSection({
|
||||
size="lg"
|
||||
onClick={onOpenKeyboardMap}
|
||||
className={cn(
|
||||
"gap-2.5 mt-2 h-11 px-6",
|
||||
"bg-gradient-to-r from-brand-500 to-brand-600",
|
||||
"hover:from-brand-600 hover:to-brand-600",
|
||||
"text-white font-medium border-0",
|
||||
"shadow-md shadow-brand-500/20 hover:shadow-lg hover:shadow-brand-500/25",
|
||||
"transition-all duration-200 ease-out",
|
||||
"hover:scale-[1.02] active:scale-[0.98]"
|
||||
'gap-2.5 mt-2 h-11 px-6',
|
||||
'bg-gradient-to-r from-brand-500 to-brand-600',
|
||||
'hover:from-brand-600 hover:to-brand-600',
|
||||
'text-white font-medium border-0',
|
||||
'shadow-md shadow-brand-500/20 hover:shadow-lg hover:shadow-brand-500/25',
|
||||
'transition-all duration-200 ease-out',
|
||||
'hover:scale-[1.02] active:scale-[0.98]'
|
||||
)}
|
||||
>
|
||||
<Keyboard className="w-5 h-5" />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { SquareTerminal } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { toast } from "sonner";
|
||||
import { TERMINAL_FONT_OPTIONS } from "@/config/terminal-themes";
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { SquareTerminal } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { toast } from 'sonner';
|
||||
import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes';
|
||||
|
||||
export function TerminalSection() {
|
||||
const {
|
||||
@@ -31,10 +31,10 @@ export function TerminalSection() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden",
|
||||
"border border-border/50",
|
||||
"bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl",
|
||||
"shadow-sm shadow-black/5"
|
||||
'rounded-2xl overflow-hidden',
|
||||
'border border-border/50',
|
||||
'bg-gradient-to-br from-card/90 via-card/70 to-card/80 backdrop-blur-xl',
|
||||
'shadow-sm shadow-black/5'
|
||||
)}
|
||||
>
|
||||
<div className="p-6 border-b border-border/50 bg-gradient-to-r from-transparent via-accent/5 to-transparent">
|
||||
@@ -45,7 +45,8 @@ export function TerminalSection() {
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">Terminal</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Customize terminal appearance and behavior. Theme follows your app theme in Appearance settings.
|
||||
Customize terminal appearance and behavior. Theme follows your app theme in Appearance
|
||||
settings.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-6">
|
||||
@@ -56,15 +57,15 @@ export function TerminalSection() {
|
||||
value={fontFamily}
|
||||
onChange={(e) => {
|
||||
setTerminalFontFamily(e.target.value);
|
||||
toast.info("Font family changed", {
|
||||
description: "Restart terminal for changes to take effect",
|
||||
toast.info('Font family changed', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
className={cn(
|
||||
"w-full px-3 py-2 rounded-lg",
|
||||
"bg-accent/30 border border-border/50",
|
||||
"text-foreground text-sm",
|
||||
"focus:outline-none focus:ring-2 focus:ring-green-500/30"
|
||||
'w-full px-3 py-2 rounded-lg',
|
||||
'bg-accent/30 border border-border/50',
|
||||
'text-foreground text-sm',
|
||||
'focus:outline-none focus:ring-2 focus:ring-green-500/30'
|
||||
)}
|
||||
>
|
||||
{TERMINAL_FONT_OPTIONS.map((font) => (
|
||||
@@ -106,8 +107,8 @@ export function TerminalSection() {
|
||||
setTerminalLineHeight(value);
|
||||
}}
|
||||
onValueCommit={() => {
|
||||
toast.info("Line height changed", {
|
||||
description: "Restart terminal for changes to take effect",
|
||||
toast.info('Line height changed', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
className="flex-1"
|
||||
@@ -129,8 +130,8 @@ export function TerminalSection() {
|
||||
step={1000}
|
||||
onValueChange={([value]) => setTerminalScrollbackLines(value)}
|
||||
onValueCommit={() => {
|
||||
toast.info("Scrollback changed", {
|
||||
description: "Restart terminal for changes to take effect",
|
||||
toast.info('Scrollback changed', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
className="flex-1"
|
||||
@@ -163,9 +164,12 @@ export function TerminalSection() {
|
||||
checked={screenReaderMode}
|
||||
onCheckedChange={(checked) => {
|
||||
setTerminalScreenReaderMode(checked);
|
||||
toast.success(checked ? "Screen reader mode enabled" : "Screen reader mode disabled", {
|
||||
description: "Restart terminal for changes to take effect",
|
||||
});
|
||||
toast.success(
|
||||
checked ? 'Screen reader mode enabled' : 'Screen reader mode disabled',
|
||||
{
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user