feat: add new themes and improve UI components

- Introduced multiple new themes: retro, dracula, nord, monokai, tokyonight, solarized, gruvbox, catppuccin, onedark, and synthwave.
- Updated global CSS to support new themes and added custom variants for theme-specific styles.
- Enhanced layout and sidebar components with improved styling and responsiveness.
- Refactored button and slider components for better visual consistency and added an animated outline variant.
- Improved various views (e.g., settings, welcome, context) with updated styles and better user experience.

This update enhances the overall aesthetic and usability of the application, providing users with more customization options.
This commit is contained in:
Cody Seibert
2025-12-09 18:51:06 -05:00
parent 66951f2b94
commit 6f3bf2f6b6
19 changed files with 2212 additions and 500 deletions

View File

@@ -91,59 +91,65 @@ export function WelcomeView() {
/**
* Initialize project and optionally kick off project analysis agent
*/
const initializeAndOpenProject = useCallback(async (path: string, name: string) => {
setIsOpening(true);
try {
// Initialize the .automaker directory structure
const initResult = await initializeProject(path);
const initializeAndOpenProject = useCallback(
async (path: string, name: string) => {
setIsOpening(true);
try {
// Initialize the .automaker directory structure
const initResult = await initializeProject(path);
if (!initResult.success) {
toast.error("Failed to initialize project", {
description: initResult.error || "Unknown error occurred",
if (!initResult.success) {
toast.error("Failed to initialize project", {
description: initResult.error || "Unknown error occurred",
});
return;
}
const project = {
id: `project-${Date.now()}`,
name,
path,
lastOpened: new Date().toISOString(),
};
addProject(project);
setCurrentProject(project);
// Show initialization dialog if files were created
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
setInitStatus({
isNewProject: initResult.isNewProject,
createdFiles: initResult.createdFiles,
projectName: name,
projectPath: path,
});
setShowInitDialog(true);
// Kick off agent to analyze the project and update app_spec.txt
console.log(
"[Welcome] Project initialized, created files:",
initResult.createdFiles
);
console.log("[Welcome] Kicking off project analysis agent...");
// Start analysis in background (don't await, let it run async)
analyzeProject(path);
} else {
toast.success("Project opened", {
description: `Opened ${name}`,
});
}
} catch (error) {
console.error("[Welcome] Failed to open project:", error);
toast.error("Failed to open project", {
description: error instanceof Error ? error.message : "Unknown error",
});
return;
} finally {
setIsOpening(false);
}
const project = {
id: `project-${Date.now()}`,
name,
path,
lastOpened: new Date().toISOString(),
};
addProject(project);
setCurrentProject(project);
// Show initialization dialog if files were created
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
setInitStatus({
isNewProject: initResult.isNewProject,
createdFiles: initResult.createdFiles,
projectName: name,
projectPath: path,
});
setShowInitDialog(true);
// Kick off agent to analyze the project and update app_spec.txt
console.log("[Welcome] Project initialized, created files:", initResult.createdFiles);
console.log("[Welcome] Kicking off project analysis agent...");
// Start analysis in background (don't await, let it run async)
analyzeProject(path);
} else {
toast.success("Project opened", {
description: `Opened ${name}`,
});
}
} catch (error) {
console.error("[Welcome] Failed to open project:", error);
toast.error("Failed to open project", {
description: error instanceof Error ? error.message : "Unknown error",
});
} finally {
setIsOpening(false);
}
}, [addProject, setCurrentProject, analyzeProject]);
},
[addProject, setCurrentProject, analyzeProject]
);
const handleOpenProject = useCallback(async () => {
const api = getElectronAPI();
@@ -159,9 +165,12 @@ export function WelcomeView() {
/**
* Handle clicking on a recent project
*/
const handleRecentProjectClick = useCallback(async (project: { id: string; name: string; path: string }) => {
await initializeAndOpenProject(project.path, project.name);
}, [initializeAndOpenProject]);
const handleRecentProjectClick = useCallback(
async (project: { id: string; name: string; path: string }) => {
await initializeAndOpenProject(project.path, project.name);
},
[initializeAndOpenProject]
);
const handleNewProject = () => {
setNewProjectName("");
@@ -272,17 +281,17 @@ export function WelcomeView() {
return (
<div className="flex-1 flex flex-col content-bg" data-testid="welcome-view">
{/* Header Section */}
<div className="flex-shrink-0 border-b border-white/10 bg-zinc-950/50 backdrop-blur-md">
<div className="flex-shrink-0 border-b border-border bg-glass backdrop-blur-md">
<div className="px-8 py-6">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-brand-500 to-purple-600 shadow-lg shadow-brand-500/20 flex items-center justify-center">
<Cpu className="w-5 h-5 text-white" />
<div className="w-10 h-10 rounded-xl bg-linear-to-br from-brand-500 to-brand-600 shadow-lg shadow-brand-500/20 flex items-center justify-center">
<Cpu className="w-5 h-5 text-primary-foreground" />
</div>
<div>
<h1 className="text-2xl font-bold text-white">
<h1 className="text-2xl font-bold text-foreground">
Welcome to Automaker
</h1>
<p className="text-sm text-zinc-400">
<p className="text-sm text-muted-foreground">
Your autonomous AI development studio
</p>
</div>
@@ -296,20 +305,20 @@ export function WelcomeView() {
{/* Quick Actions */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
<div
className="group relative overflow-hidden rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md hover:bg-zinc-900/70 hover:border-white/20 transition-all duration-200"
className="group relative overflow-hidden rounded-xl border border-border bg-card backdrop-blur-md hover:bg-card/70 hover:border-border-glass transition-all duration-200"
data-testid="new-project-card"
>
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/5 to-purple-600/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div className="relative p-6">
<div className="flex items-start gap-4 mb-4">
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-brand-500 to-purple-600 shadow-lg shadow-brand-500/20 flex items-center justify-center group-hover:scale-110 transition-transform">
<div className="w-12 h-12 rounded-lg bg-linear-to-br from-brand-500 to-brand-600 shadow-lg shadow-brand-500/20 flex items-center justify-center group-hover:scale-110 transition-transform">
<Plus className="w-6 h-6 text-white" />
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-1">
<h3 className="text-lg font-semibold text-foreground mb-1">
New Project
</h3>
<p className="text-sm text-zinc-400">
<p className="text-sm text-muted-foreground">
Create a new project from scratch with AI-powered
development
</p>
@@ -318,7 +327,7 @@ export function WelcomeView() {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="w-full bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
className="w-full bg-linear-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-primary-foreground border-0"
data-testid="create-new-project"
>
<Plus className="w-4 h-4 mr-2" />
@@ -347,28 +356,28 @@ export function WelcomeView() {
</div>
<div
className="group relative overflow-hidden rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md hover:bg-zinc-900/70 hover:border-white/20 transition-all duration-200 cursor-pointer"
className="group relative overflow-hidden rounded-xl border border-border bg-card backdrop-blur-md hover:bg-card/70 hover:border-border-glass transition-all duration-200 cursor-pointer"
onClick={handleOpenProject}
data-testid="open-project-card"
>
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-cyan-600/5 opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div className="relative p-6">
<div className="flex items-start gap-4 mb-4">
<div className="w-12 h-12 rounded-lg bg-zinc-800 border border-white/10 flex items-center justify-center group-hover:scale-110 transition-transform">
<FolderOpen className="w-6 h-6 text-zinc-400 group-hover:text-white transition-colors" />
<div className="w-12 h-12 rounded-lg bg-muted border border-border flex items-center justify-center group-hover:scale-110 transition-transform">
<FolderOpen className="w-6 h-6 text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-1">
<h3 className="text-lg font-semibold text-foreground mb-1">
Open Project
</h3>
<p className="text-sm text-zinc-400">
<p className="text-sm text-muted-foreground">
Open an existing project folder to continue working
</p>
</div>
</div>
<Button
variant="secondary"
className="w-full bg-white/5 hover:bg-white/10 text-white border border-white/10 hover:border-white/20"
className="w-full bg-secondary hover:bg-secondary/80 text-foreground border border-border hover:border-border-glass"
data-testid="open-existing-project"
>
<FolderOpen className="w-4 h-4 mr-2" />
@@ -382,8 +391,8 @@ export function WelcomeView() {
{recentProjects.length > 0 && (
<div>
<div className="flex items-center gap-2 mb-4">
<Clock className="w-5 h-5 text-zinc-400" />
<h2 className="text-lg font-semibold text-white">
<Clock className="w-5 h-5 text-muted-foreground" />
<h2 className="text-lg font-semibold text-foreground">
Recent Projects
</h2>
</div>
@@ -391,25 +400,25 @@ export function WelcomeView() {
{recentProjects.map((project) => (
<div
key={project.id}
className="group relative overflow-hidden rounded-xl border border-white/10 bg-zinc-900/50 backdrop-blur-md hover:bg-zinc-900/70 hover:border-brand-500/50 transition-all duration-200 cursor-pointer"
className="group relative overflow-hidden rounded-xl border border-border bg-card backdrop-blur-md hover:bg-card/70 hover:border-brand-500/50 transition-all duration-200 cursor-pointer"
onClick={() => handleRecentProjectClick(project)}
data-testid={`recent-project-${project.id}`}
>
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-purple-600/0 group-hover:from-brand-500/5 group-hover:to-purple-600/5 transition-all"></div>
<div className="relative p-4">
<div className="flex items-start gap-3">
<div className="w-10 h-10 rounded-lg bg-zinc-800 border border-white/10 flex items-center justify-center group-hover:border-brand-500/50 transition-colors">
<Folder className="w-5 h-5 text-zinc-400 group-hover:text-brand-500 transition-colors" />
<div className="w-10 h-10 rounded-lg bg-muted border border-border flex items-center justify-center group-hover:border-brand-500/50 transition-colors">
<Folder className="w-5 h-5 text-muted-foreground group-hover:text-brand-500 transition-colors" />
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-white truncate group-hover:text-brand-500 transition-colors">
<p className="font-medium text-foreground truncate group-hover:text-brand-500 transition-colors">
{project.name}
</p>
<p className="text-xs text-zinc-500 truncate mt-0.5">
<p className="text-xs text-muted-foreground/70 truncate mt-0.5">
{project.path}
</p>
{project.lastOpened && (
<p className="text-xs text-zinc-600 mt-1">
<p className="text-xs text-muted-foreground mt-1">
{new Date(
project.lastOpened
).toLocaleDateString()}
@@ -427,10 +436,10 @@ export function WelcomeView() {
{/* Empty State for No Projects */}
{recentProjects.length === 0 && (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="w-16 h-16 rounded-2xl bg-zinc-900/50 border border-white/10 flex items-center justify-center mb-4">
<Sparkles className="w-8 h-8 text-zinc-600" />
<div className="w-16 h-16 rounded-2xl bg-muted border border-border flex items-center justify-center mb-4">
<Sparkles className="w-8 h-8 text-muted-foreground" />
</div>
<h3 className="text-lg font-semibold text-white mb-2">
<h3 className="text-lg font-semibold text-foreground mb-2">
No projects yet
</h3>
<p className="text-sm text-zinc-400 max-w-md">
@@ -447,18 +456,20 @@ export function WelcomeView() {
onOpenChange={setShowNewProjectDialog}
>
<DialogContent
className="bg-zinc-900 border-white/10"
className="bg-card border-border"
data-testid="new-project-dialog"
>
<DialogHeader>
<DialogTitle className="text-white">Create New Project</DialogTitle>
<DialogDescription className="text-zinc-400">
<DialogTitle className="text-foreground">
Create New Project
</DialogTitle>
<DialogDescription className="text-muted-foreground">
Set up a new project directory with initial configuration files.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="project-name" className="text-zinc-300">
<Label htmlFor="project-name" className="text-foreground">
Project Name
</Label>
<Input
@@ -466,12 +477,12 @@ export function WelcomeView() {
placeholder="my-awesome-project"
value={newProjectName}
onChange={(e) => setNewProjectName(e.target.value)}
className="bg-zinc-950/50 border-white/10 text-white placeholder:text-zinc-500"
className="bg-input border-border text-foreground placeholder:text-muted-foreground"
data-testid="project-name-input"
/>
</div>
<div className="space-y-2">
<Label htmlFor="project-path" className="text-zinc-300">
<Label htmlFor="project-path" className="text-foreground">
Parent Directory
</Label>
<div className="flex gap-2">
@@ -480,13 +491,13 @@ export function WelcomeView() {
placeholder="/path/to/projects"
value={newProjectPath}
onChange={(e) => setNewProjectPath(e.target.value)}
className="flex-1 bg-zinc-950/50 border-white/10 text-white placeholder:text-zinc-500"
className="flex-1 bg-input border-border text-foreground placeholder:text-muted-foreground"
data-testid="project-path-input"
/>
<Button
variant="secondary"
onClick={handleSelectDirectory}
className="bg-white/5 hover:bg-white/10 text-white border border-white/10"
className="bg-secondary hover:bg-secondary/80 text-foreground border border-border"
data-testid="browse-directory"
>
Browse
@@ -498,14 +509,14 @@ export function WelcomeView() {
<Button
variant="ghost"
onClick={() => setShowNewProjectDialog(false)}
className="text-zinc-400 hover:text-white hover:bg-white/5"
className="text-muted-foreground hover:text-foreground hover:bg-accent"
>
Cancel
</Button>
<Button
onClick={handleCreateProject}
disabled={!newProjectName || !newProjectPath || isCreating}
className="bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
className="bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-white border-0"
data-testid="confirm-create-project"
>
{isCreating ? "Creating..." : "Create Project"}
@@ -517,15 +528,17 @@ export function WelcomeView() {
{/* Project Initialization Dialog */}
<Dialog open={showInitDialog} onOpenChange={setShowInitDialog}>
<DialogContent
className="bg-zinc-900 border-white/10"
className="bg-card border-border"
data-testid="project-init-dialog"
>
<DialogHeader>
<DialogTitle className="text-white flex items-center gap-2">
<DialogTitle className="text-foreground flex items-center gap-2">
<Sparkles className="w-5 h-5 text-brand-500" />
{initStatus?.isNewProject ? "Project Initialized" : "Project Updated"}
{initStatus?.isNewProject
? "Project Initialized"
: "Project Updated"}
</DialogTitle>
<DialogDescription className="text-zinc-400">
<DialogDescription className="text-muted-foreground">
{initStatus?.isNewProject
? `Created .automaker directory structure for ${initStatus?.projectName}`
: `Updated missing files in .automaker for ${initStatus?.projectName}`}
@@ -533,15 +546,17 @@ export function WelcomeView() {
</DialogHeader>
<div className="py-4">
<div className="space-y-2">
<p className="text-sm text-zinc-300 font-medium">Created files:</p>
<p className="text-sm text-foreground font-medium">
Created files:
</p>
<ul className="space-y-1.5">
{initStatus?.createdFiles.map((file) => (
<li
key={file}
className="flex items-center gap-2 text-sm text-zinc-400"
className="flex items-center gap-2 text-sm text-muted-foreground"
>
<div className="w-1.5 h-1.5 rounded-full bg-green-500" />
<code className="text-xs bg-zinc-800 px-2 py-0.5 rounded">
<code className="text-xs bg-muted px-2 py-0.5 rounded">
{file}
</code>
</li>
@@ -550,7 +565,7 @@ export function WelcomeView() {
</div>
{initStatus?.isNewProject && (
<div className="mt-4 p-3 rounded-lg bg-zinc-800/50 border border-white/5">
<div className="mt-4 p-3 rounded-lg bg-muted/50 border border-border-glass">
{isAnalyzing ? (
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 text-brand-500 animate-spin" />
@@ -559,9 +574,9 @@ export function WelcomeView() {
</p>
</div>
) : (
<p className="text-sm text-zinc-400">
<p className="text-sm text-muted-foreground">
<span className="text-brand-400">Tip:</span> Edit the{" "}
<code className="text-xs bg-zinc-800 px-1.5 py-0.5 rounded">
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
app_spec.txt
</code>{" "}
file to describe your project. The AI agent will use this to
@@ -574,7 +589,7 @@ export function WelcomeView() {
<DialogFooter>
<Button
onClick={() => setShowInitDialog(false)}
className="bg-gradient-to-r from-brand-500 to-purple-600 hover:from-brand-600 hover:to-purple-700 text-white border-0"
className="bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-white border-0"
data-testid="close-init-dialog"
>
Get Started
@@ -586,12 +601,14 @@ export function WelcomeView() {
{/* Loading overlay when opening project */}
{isOpening && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm"
data-testid="project-opening-overlay"
>
<div className="flex flex-col items-center gap-3 p-6 rounded-xl bg-zinc-900 border border-white/10">
<div className="flex flex-col items-center gap-3 p-6 rounded-xl bg-card border border-border">
<Loader2 className="w-8 h-8 text-brand-500 animate-spin" />
<p className="text-white font-medium">Initializing project...</p>
<p className="text-foreground font-medium">
Initializing project...
</p>
</div>
</div>
)}