🗂️ refactor: implement Phase 2 folder-pattern compliance

- Move dialogs to src/components/dialogs/ folder:
  - delete-session-dialog.tsx
  - delete-all-archived-sessions-dialog.tsx
  - new-project-modal.tsx
  - workspace-picker-modal.tsx
- Update all imports to reference new dialog locations
- Create barrel export (index.ts) for board-view/components/kanban-card/
- Create barrel exports (index.ts) for all 11 settings-view subfolders:
  - api-keys/, api-keys/hooks/, appearance/, audio/, cli-status/
  - components/, config/, danger-zone/, feature-defaults/
  - keyboard-shortcuts/, shared/

This is Phase 2 of folder-pattern.md compliance: organizing dialogs
and establishing consistent barrel export patterns across all view subfolders.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-21 19:43:17 +01:00
parent 6365cc137c
commit e47b34288b
20 changed files with 304 additions and 406 deletions

View File

@@ -1,4 +1,3 @@
import {
Dialog,
DialogContent,
@@ -6,9 +5,9 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Trash2 } from "lucide-react";
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Trash2 } from 'lucide-react';
interface DeleteAllArchivedSessionsDialogProps {
open: boolean;
@@ -29,8 +28,7 @@ export function DeleteAllArchivedSessionsDialog({
<DialogHeader>
<DialogTitle>Delete All Archived Sessions</DialogTitle>
<DialogDescription>
Are you sure you want to delete all archived sessions? This action
cannot be undone.
Are you sure you want to delete all archived sessions? This action cannot be undone.
{archivedCount > 0 && (
<span className="block mt-2 text-yellow-500">
{archivedCount} session(s) will be deleted.

View File

@@ -1,6 +1,6 @@
import { MessageSquare } from "lucide-react";
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
import type { SessionListItem } from "@/types/electron";
import { MessageSquare } from 'lucide-react';
import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog';
import type { SessionListItem } from '@/types/electron';
interface DeleteSessionDialogProps {
open: boolean;
@@ -38,12 +38,8 @@ export function DeleteSessionDialog({
<MessageSquare className="w-5 h-5 text-brand-500" />
</div>
<div className="min-w-0">
<p className="font-medium text-foreground truncate">
{session.name}
</p>
<p className="text-xs text-muted-foreground">
{session.messageCount} messages
</p>
<p className="font-medium text-foreground truncate">{session.name}</p>
<p className="text-xs text-muted-foreground">{session.messageCount} messages</p>
</div>
</div>
)}

View File

@@ -1,2 +1,6 @@
export { BoardBackgroundModal } from './board-background-modal';
export { DeleteAllArchivedSessionsDialog } from './delete-all-archived-sessions-dialog';
export { DeleteSessionDialog } from './delete-session-dialog';
export { FileBrowserDialog } from './file-browser-dialog';
export { NewProjectModal } from './new-project-modal';
export { WorkspacePickerModal } from './workspace-picker-modal';

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect } from 'react';
import {
Dialog,
DialogContent,
@@ -6,13 +6,13 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { HotkeyButton } from "@/components/ui/hotkey-button";
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";
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { HotkeyButton } from '@/components/ui/hotkey-button';
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,
@@ -22,15 +22,12 @@ import {
Loader2,
Link,
Folder,
} from "lucide-react";
import { starterTemplates, type StarterTemplate } from "@/lib/templates";
import { getElectronAPI } from "@/lib/electron";
import { cn } from "@/lib/utils";
import { useFileBrowser } from "@/contexts/file-browser-context";
import {
getDefaultWorkspaceDirectory,
saveLastProjectDirectory,
} from "@/lib/workspace-config";
} from 'lucide-react';
import { starterTemplates, type StarterTemplate } from '@/lib/templates';
import { getElectronAPI } from '@/lib/electron';
import { cn } from '@/lib/utils';
import { useFileBrowser } from '@/contexts/file-browser-context';
import { getDefaultWorkspaceDirectory, saveLastProjectDirectory } from '@/lib/workspace-config';
interface ValidationErrors {
projectName?: boolean;
@@ -42,20 +39,13 @@ interface ValidationErrors {
interface NewProjectModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onCreateBlankProject: (
projectName: string,
parentDir: string
) => Promise<void>;
onCreateBlankProject: (projectName: string, parentDir: string) => Promise<void>;
onCreateFromTemplate: (
template: StarterTemplate,
projectName: string,
parentDir: string
) => Promise<void>;
onCreateFromCustomUrl: (
repoUrl: string,
projectName: string,
parentDir: string
) => Promise<void>;
onCreateFromCustomUrl: (repoUrl: string, projectName: string, parentDir: string) => Promise<void>;
isCreating: boolean;
}
@@ -67,14 +57,13 @@ export function NewProjectModal({
onCreateFromCustomUrl,
isCreating,
}: NewProjectModalProps) {
const [activeTab, setActiveTab] = useState<"blank" | "template">("blank");
const [projectName, setProjectName] = useState("");
const [workspaceDir, setWorkspaceDir] = useState<string>("");
const [activeTab, setActiveTab] = useState<'blank' | 'template'>('blank');
const [projectName, setProjectName] = useState('');
const [workspaceDir, setWorkspaceDir] = useState<string>('');
const [isLoadingWorkspace, setIsLoadingWorkspace] = useState(false);
const [selectedTemplate, setSelectedTemplate] =
useState<StarterTemplate | null>(null);
const [selectedTemplate, setSelectedTemplate] = useState<StarterTemplate | null>(null);
const [useCustomUrl, setUseCustomUrl] = useState(false);
const [customUrl, setCustomUrl] = useState("");
const [customUrl, setCustomUrl] = useState('');
const [errors, setErrors] = useState<ValidationErrors>({});
const { openFileBrowser } = useFileBrowser();
@@ -89,7 +78,7 @@ export function NewProjectModal({
}
})
.catch((error) => {
console.error("Failed to get default workspace directory:", error);
console.error('Failed to get default workspace directory:', error);
})
.finally(() => {
setIsLoadingWorkspace(false);
@@ -100,11 +89,11 @@ export function NewProjectModal({
// Reset form when modal closes
useEffect(() => {
if (!open) {
setProjectName("");
setProjectName('');
setSelectedTemplate(null);
setUseCustomUrl(false);
setCustomUrl("");
setActiveTab("blank");
setCustomUrl('');
setActiveTab('blank');
setErrors({});
}
}, [open]);
@@ -117,10 +106,7 @@ export function NewProjectModal({
}, [projectName, errors.projectName]);
useEffect(() => {
if (
(selectedTemplate || (useCustomUrl && customUrl)) &&
errors.templateSelection
) {
if ((selectedTemplate || (useCustomUrl && customUrl)) && errors.templateSelection) {
setErrors((prev) => ({ ...prev, templateSelection: false }));
}
}, [selectedTemplate, useCustomUrl, customUrl, errors.templateSelection]);
@@ -145,7 +131,7 @@ export function NewProjectModal({
}
// Check template selection (only for template tab)
if (activeTab === "template") {
if (activeTab === 'template') {
if (useCustomUrl) {
if (!customUrl.trim()) {
newErrors.customUrl = true;
@@ -164,7 +150,7 @@ export function NewProjectModal({
// Clear errors and proceed
setErrors({});
if (activeTab === "blank") {
if (activeTab === 'blank') {
await onCreateBlankProject(projectName, workspaceDir);
} else if (useCustomUrl && customUrl) {
await onCreateFromCustomUrl(customUrl, projectName, workspaceDir);
@@ -181,7 +167,7 @@ export function NewProjectModal({
const handleSelectTemplate = (template: StarterTemplate) => {
setSelectedTemplate(template);
setUseCustomUrl(false);
setCustomUrl("");
setCustomUrl('');
};
const handleToggleCustomUrl = () => {
@@ -193,9 +179,8 @@ export function NewProjectModal({
const handleBrowseDirectory = async () => {
const selectedPath = await openFileBrowser({
title: "Select Base Project Directory",
description:
"Choose the parent directory where your project will be created",
title: 'Select Base Project Directory',
description: 'Choose the parent directory where your project will be created',
initialPath: workspaceDir || undefined,
});
if (selectedPath) {
@@ -211,15 +196,12 @@ export function NewProjectModal({
// Use platform-specific path separator
const pathSep =
typeof window !== "undefined" && (window as any).electronAPI
? navigator.platform.indexOf("Win") !== -1
? "\\"
: "/"
: "/";
const projectPath =
workspaceDir && projectName
? `${workspaceDir}${pathSep}${projectName}`
: "";
typeof window !== 'undefined' && (window as any).electronAPI
? navigator.platform.indexOf('Win') !== -1
? '\\'
: '/'
: '/';
const projectPath = workspaceDir && projectName ? `${workspaceDir}${pathSep}${projectName}` : '';
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -228,9 +210,7 @@ export function NewProjectModal({
data-testid="new-project-modal"
>
<DialogHeader className="pb-2">
<DialogTitle className="text-foreground">
Create New Project
</DialogTitle>
<DialogTitle className="text-foreground">Create New Project</DialogTitle>
<DialogDescription className="text-muted-foreground">
Start with a blank project or choose from a starter template.
</DialogDescription>
@@ -241,13 +221,9 @@ export function NewProjectModal({
<div className="space-y-2">
<Label
htmlFor="project-name"
className={cn(
"text-foreground",
errors.projectName && "text-red-500"
)}
className={cn('text-foreground', errors.projectName && 'text-red-500')}
>
Project Name{" "}
{errors.projectName && <span className="text-red-500">*</span>}
Project Name {errors.projectName && <span className="text-red-500">*</span>}
</Label>
<Input
id="project-name"
@@ -255,33 +231,31 @@ export function NewProjectModal({
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
className={cn(
"bg-input text-foreground placeholder:text-muted-foreground",
'bg-input text-foreground placeholder:text-muted-foreground',
errors.projectName
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-border"
? 'border-red-500 focus:border-red-500 focus:ring-red-500/20'
: 'border-border'
)}
data-testid="project-name-input"
autoFocus
/>
{errors.projectName && (
<p className="text-xs text-red-500">Project name is required</p>
)}
{errors.projectName && <p className="text-xs text-red-500">Project name is required</p>}
</div>
{/* Workspace Directory Display */}
<div
className={cn(
"flex items-center gap-2 text-sm",
errors.workspaceDir ? "text-red-500" : "text-muted-foreground"
'flex items-center gap-2 text-sm',
errors.workspaceDir ? 'text-red-500' : 'text-muted-foreground'
)}
>
<Folder className="w-4 h-4 shrink-0" />
<span className="flex-1 min-w-0">
{isLoadingWorkspace ? (
"Loading workspace..."
'Loading workspace...'
) : workspaceDir ? (
<>
Will be created at:{" "}
Will be created at:{' '}
<code className="text-xs bg-muted px-1.5 py-0.5 rounded truncate">
{projectPath || workspaceDir}
</code>
@@ -305,7 +279,7 @@ export function NewProjectModal({
<Tabs
value={activeTab}
onValueChange={(v) => setActiveTab(v as "blank" | "template")}
onValueChange={(v) => setActiveTab(v as 'blank' | 'template')}
className="flex-1 flex flex-col overflow-hidden"
>
<TabsList className="w-full justify-start">
@@ -323,9 +297,8 @@ export function NewProjectModal({
<TabsContent value="blank" className="mt-0">
<div className="p-4 rounded-lg bg-muted/50 border border-border">
<p className="text-sm text-muted-foreground">
Create an empty project with the standard .automaker directory
structure. Perfect for starting from scratch or importing an
existing codebase.
Create an empty project with the standard .automaker directory structure. Perfect
for starting from scratch or importing an existing codebase.
</p>
</div>
</TabsContent>
@@ -342,18 +315,18 @@ export function NewProjectModal({
{/* Preset Templates */}
<div
className={cn(
"space-y-3 rounded-lg p-1 -m-1",
errors.templateSelection && "ring-2 ring-red-500/50"
'space-y-3 rounded-lg p-1 -m-1',
errors.templateSelection && 'ring-2 ring-red-500/50'
)}
>
{starterTemplates.map((template) => (
<div
key={template.id}
className={cn(
"p-4 rounded-lg border cursor-pointer transition-all",
'p-4 rounded-lg border cursor-pointer transition-all',
selectedTemplate?.id === template.id && !useCustomUrl
? "border-brand-500 bg-brand-500/10"
: "border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50"
? 'border-brand-500 bg-brand-500/10'
: 'border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50'
)}
onClick={() => handleSelectTemplate(template)}
data-testid={`template-${template.id}`}
@@ -361,13 +334,10 @@ export function NewProjectModal({
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-medium text-foreground">
{template.name}
</h4>
{selectedTemplate?.id === template.id &&
!useCustomUrl && (
<Check className="w-4 h-4 text-brand-500" />
)}
<h4 className="font-medium text-foreground">{template.name}</h4>
{selectedTemplate?.id === template.id && !useCustomUrl && (
<Check className="w-4 h-4 text-brand-500" />
)}
</div>
<p className="text-sm text-muted-foreground mb-3">
{template.description}
@@ -376,11 +346,7 @@ export function NewProjectModal({
{/* Tech Stack */}
<div className="flex flex-wrap gap-1.5 mb-3">
{template.techStack.slice(0, 6).map((tech) => (
<Badge
key={tech}
variant="secondary"
className="text-xs"
>
<Badge key={tech} variant="secondary" className="text-xs">
{tech}
</Badge>
))}
@@ -394,7 +360,7 @@ export function NewProjectModal({
{/* Key Features */}
<div className="text-xs text-muted-foreground">
<span className="font-medium">Features: </span>
{template.features.slice(0, 3).join(" · ")}
{template.features.slice(0, 3).join(' · ')}
{template.features.length > 3 &&
` · +${template.features.length - 3} more`}
</div>
@@ -419,47 +385,38 @@ export function NewProjectModal({
{/* Custom URL Option */}
<div
className={cn(
"p-4 rounded-lg border cursor-pointer transition-all",
'p-4 rounded-lg border cursor-pointer transition-all',
useCustomUrl
? "border-brand-500 bg-brand-500/10"
: "border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50"
? 'border-brand-500 bg-brand-500/10'
: 'border-border bg-muted/30 hover:border-border-glass hover:bg-muted/50'
)}
onClick={handleToggleCustomUrl}
>
<div className="flex items-center gap-2 mb-2">
<Link className="w-4 h-4 text-muted-foreground" />
<h4 className="font-medium text-foreground">
Custom GitHub URL
</h4>
{useCustomUrl && (
<Check className="w-4 h-4 text-brand-500" />
)}
<h4 className="font-medium text-foreground">Custom GitHub URL</h4>
{useCustomUrl && <Check className="w-4 h-4 text-brand-500" />}
</div>
<p className="text-sm text-muted-foreground mb-3">
Clone any public GitHub repository as a starting point.
</p>
{useCustomUrl && (
<div
onClick={(e) => e.stopPropagation()}
className="space-y-1"
>
<div onClick={(e) => e.stopPropagation()} className="space-y-1">
<Input
placeholder="https://github.com/username/repository"
value={customUrl}
onChange={(e) => setCustomUrl(e.target.value)}
className={cn(
"bg-input text-foreground placeholder:text-muted-foreground",
'bg-input text-foreground placeholder:text-muted-foreground',
errors.customUrl
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-border"
? 'border-red-500 focus:border-red-500 focus:ring-red-500/20'
: 'border-border'
)}
data-testid="custom-url-input"
/>
{errors.customUrl && (
<p className="text-xs text-red-500">
GitHub URL is required
</p>
<p className="text-xs text-red-500">GitHub URL is required</p>
)}
</div>
)}
@@ -482,14 +439,14 @@ export function NewProjectModal({
onClick={validateAndCreate}
disabled={isCreating}
className="bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-600 hover:to-brand-600 text-white border-0"
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkey={{ key: 'Enter', cmdCtrl: true }}
hotkeyActive={open}
data-testid="confirm-create-project"
>
{isCreating ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
{activeTab === "template" ? "Cloning..." : "Creating..."}
{activeTab === 'template' ? 'Cloning...' : 'Creating...'}
</>
) : (
<>Create Project</>

View File

@@ -1,5 +1,4 @@
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback } from 'react';
import {
Dialog,
DialogContent,
@@ -7,10 +6,10 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Folder, Loader2, FolderOpen, AlertCircle } from "lucide-react";
import { getHttpApiClient } from "@/lib/http-api-client";
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Folder, Loader2, FolderOpen, AlertCircle } from 'lucide-react';
import { getHttpApiClient } from '@/lib/http-api-client';
interface WorkspaceDirectory {
name: string;
@@ -23,11 +22,7 @@ interface WorkspacePickerModalProps {
onSelect: (path: string, name: string) => void;
}
export function WorkspacePickerModal({
open,
onOpenChange,
onSelect,
}: WorkspacePickerModalProps) {
export function WorkspacePickerModal({ open, onOpenChange, onSelect }: WorkspacePickerModalProps) {
const [isLoading, setIsLoading] = useState(false);
const [directories, setDirectories] = useState<WorkspaceDirectory[]>([]);
const [error, setError] = useState<string | null>(null);
@@ -43,10 +38,10 @@ export function WorkspacePickerModal({
if (result.success && result.directories) {
setDirectories(result.directories);
} else {
setError(result.error || "Failed to load directories");
setError(result.error || 'Failed to load directories');
}
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load directories");
setError(err instanceof Error ? err.message : 'Failed to load directories');
} finally {
setIsLoading(false);
}
@@ -90,12 +85,7 @@ export function WorkspacePickerModal({
<AlertCircle className="w-6 h-6 text-destructive" />
</div>
<p className="text-sm text-destructive">{error}</p>
<Button
variant="secondary"
size="sm"
onClick={loadDirectories}
className="mt-2"
>
<Button variant="secondary" size="sm" onClick={loadDirectories} className="mt-2">
Try Again
</Button>
</div>
@@ -128,9 +118,7 @@ export function WorkspacePickerModal({
<p className="font-medium text-foreground truncate group-hover:text-brand-500 transition-colors">
{dir.name}
</p>
<p className="text-xs text-muted-foreground/70 truncate">
{dir.path}
</p>
<p className="text-xs text-muted-foreground/70 truncate">{dir.path}</p>
</div>
</button>
))}

View File

@@ -72,7 +72,7 @@ import { toast } from 'sonner';
import { themeOptions } from '@/config/theme-options';
import type { SpecRegenerationEvent } from '@/types/electron';
import { DeleteProjectDialog } from '@/components/views/settings-view/components/delete-project-dialog';
import { NewProjectModal } from '@/components/new-project-modal';
import { NewProjectModal } from '@/components/dialogs/new-project-modal';
import { CreateSpecDialog } from '@/components/views/spec-view/dialogs';
import type { FeatureCount } from '@/components/views/spec-view/types';
import {

View File

@@ -1,10 +1,9 @@
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { HotkeyButton } from "@/components/ui/hotkey-button";
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { HotkeyButton } from '@/components/ui/hotkey-button';
import { Input } from '@/components/ui/input';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
Plus,
MessageSquare,
@@ -15,66 +14,66 @@ import {
X,
ArchiveRestore,
Loader2,
} from "lucide-react";
import { cn } from "@/lib/utils";
import type { SessionListItem } from "@/types/electron";
import { useKeyboardShortcutsConfig } from "@/hooks/use-keyboard-shortcuts";
import { getElectronAPI } from "@/lib/electron";
import { DeleteSessionDialog } from "@/components/delete-session-dialog";
import { DeleteAllArchivedSessionsDialog } from "@/components/delete-all-archived-sessions-dialog";
} from 'lucide-react';
import { cn } from '@/lib/utils';
import type { SessionListItem } from '@/types/electron';
import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts';
import { getElectronAPI } from '@/lib/electron';
import { DeleteSessionDialog } from '@/components/dialogs/delete-session-dialog';
import { DeleteAllArchivedSessionsDialog } from '@/components/dialogs/delete-all-archived-sessions-dialog';
// Random session name generator
const adjectives = [
"Swift",
"Bright",
"Clever",
"Dynamic",
"Eager",
"Focused",
"Gentle",
"Happy",
"Inventive",
"Jolly",
"Keen",
"Lively",
"Mighty",
"Noble",
"Optimal",
"Peaceful",
"Quick",
"Radiant",
"Smart",
"Tranquil",
"Unique",
"Vibrant",
"Wise",
"Zealous",
'Swift',
'Bright',
'Clever',
'Dynamic',
'Eager',
'Focused',
'Gentle',
'Happy',
'Inventive',
'Jolly',
'Keen',
'Lively',
'Mighty',
'Noble',
'Optimal',
'Peaceful',
'Quick',
'Radiant',
'Smart',
'Tranquil',
'Unique',
'Vibrant',
'Wise',
'Zealous',
];
const nouns = [
"Agent",
"Builder",
"Coder",
"Developer",
"Explorer",
"Forge",
"Garden",
"Helper",
"Innovator",
"Journey",
"Kernel",
"Lighthouse",
"Mission",
"Navigator",
"Oracle",
"Project",
"Quest",
"Runner",
"Spark",
"Task",
"Unicorn",
"Voyage",
"Workshop",
'Agent',
'Builder',
'Coder',
'Developer',
'Explorer',
'Forge',
'Garden',
'Helper',
'Innovator',
'Journey',
'Kernel',
'Lighthouse',
'Mission',
'Navigator',
'Oracle',
'Project',
'Quest',
'Runner',
'Spark',
'Task',
'Unicorn',
'Voyage',
'Workshop',
];
function generateRandomSessionName(): string {
@@ -101,19 +100,15 @@ export function SessionManager({
}: SessionManagerProps) {
const shortcuts = useKeyboardShortcutsConfig();
const [sessions, setSessions] = useState<SessionListItem[]>([]);
const [activeTab, setActiveTab] = useState<"active" | "archived">("active");
const [activeTab, setActiveTab] = useState<'active' | 'archived'>('active');
const [editingSessionId, setEditingSessionId] = useState<string | null>(null);
const [editingName, setEditingName] = useState("");
const [editingName, setEditingName] = useState('');
const [isCreating, setIsCreating] = useState(false);
const [newSessionName, setNewSessionName] = useState("");
const [runningSessions, setRunningSessions] = useState<Set<string>>(
new Set()
);
const [newSessionName, setNewSessionName] = useState('');
const [runningSessions, setRunningSessions] = useState<Set<string>>(new Set());
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [sessionToDelete, setSessionToDelete] =
useState<SessionListItem | null>(null);
const [isDeleteAllArchivedDialogOpen, setIsDeleteAllArchivedDialogOpen] =
useState(false);
const [sessionToDelete, setSessionToDelete] = useState<SessionListItem | null>(null);
const [isDeleteAllArchivedDialogOpen, setIsDeleteAllArchivedDialogOpen] = useState(false);
// Check running state for all sessions
const checkRunningSessions = async (sessionList: SessionListItem[]) => {
@@ -131,10 +126,7 @@ export function SessionManager({
}
} catch (err) {
// Ignore errors for individual session checks
console.warn(
`[SessionManager] Failed to check running state for ${session.id}:`,
err
);
console.warn(`[SessionManager] Failed to check running state for ${session.id}:`, err);
}
}
@@ -180,14 +172,10 @@ export function SessionManager({
const sessionName = newSessionName.trim() || generateRandomSessionName();
const result = await api.sessions.create(
sessionName,
projectPath,
projectPath
);
const result = await api.sessions.create(sessionName, projectPath, projectPath);
if (result.success && result.session?.id) {
setNewSessionName("");
setNewSessionName('');
setIsCreating(false);
await loadSessions();
onSelectSession(result.session.id);
@@ -201,11 +189,7 @@ export function SessionManager({
const sessionName = generateRandomSessionName();
const result = await api.sessions.create(
sessionName,
projectPath,
projectPath
);
const result = await api.sessions.create(sessionName, projectPath, projectPath);
if (result.success && result.session?.id) {
await loadSessions();
@@ -234,7 +218,7 @@ export function SessionManager({
if (result.success) {
setEditingSessionId(null);
setEditingName("");
setEditingName('');
await loadSessions();
}
};
@@ -243,7 +227,7 @@ export function SessionManager({
const handleArchiveSession = async (sessionId: string) => {
const api = getElectronAPI();
if (!api?.sessions) {
console.error("[SessionManager] Sessions API not available");
console.error('[SessionManager] Sessions API not available');
return;
}
@@ -256,10 +240,10 @@ export function SessionManager({
}
await loadSessions();
} else {
console.error("[SessionManager] Archive failed:", result.error);
console.error('[SessionManager] Archive failed:', result.error);
}
} catch (error) {
console.error("[SessionManager] Archive error:", error);
console.error('[SessionManager] Archive error:', error);
}
};
@@ -267,7 +251,7 @@ export function SessionManager({
const handleUnarchiveSession = async (sessionId: string) => {
const api = getElectronAPI();
if (!api?.sessions) {
console.error("[SessionManager] Sessions API not available");
console.error('[SessionManager] Sessions API not available');
return;
}
@@ -276,10 +260,10 @@ export function SessionManager({
if (result.success) {
await loadSessions();
} else {
console.error("[SessionManager] Unarchive failed:", result.error);
console.error('[SessionManager] Unarchive failed:', result.error);
}
} catch (error) {
console.error("[SessionManager] Unarchive error:", error);
console.error('[SessionManager] Unarchive error:', error);
}
};
@@ -324,8 +308,7 @@ export function SessionManager({
const activeSessions = sessions.filter((s) => !s.isArchived);
const archivedSessions = sessions.filter((s) => s.isArchived);
const displayedSessions =
activeTab === "active" ? activeSessions : archivedSessions;
const displayedSessions = activeTab === 'active' ? activeSessions : archivedSessions;
return (
<Card className="h-full flex flex-col rounded-none">
@@ -337,8 +320,8 @@ export function SessionManager({
size="sm"
onClick={() => {
// Switch to active tab if on archived tab
if (activeTab === "archived") {
setActiveTab("active");
if (activeTab === 'archived') {
setActiveTab('active');
}
handleQuickCreateSession();
}}
@@ -354,9 +337,7 @@ export function SessionManager({
<Tabs
value={activeTab}
onValueChange={(value) =>
setActiveTab(value as "active" | "archived")
}
onValueChange={(value) => setActiveTab(value as 'active' | 'archived')}
className="w-full"
>
<TabsList className="w-full">
@@ -372,10 +353,7 @@ export function SessionManager({
</Tabs>
</CardHeader>
<CardContent
className="flex-1 overflow-y-auto space-y-2"
data-testid="session-list"
>
<CardContent className="flex-1 overflow-y-auto space-y-2" data-testid="session-list">
{/* Create new session */}
{isCreating && (
<div className="p-3 border rounded-lg bg-muted/50">
@@ -385,10 +363,10 @@ export function SessionManager({
value={newSessionName}
onChange={(e) => setNewSessionName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleCreateSession();
if (e.key === "Escape") {
if (e.key === 'Enter') handleCreateSession();
if (e.key === 'Escape') {
setIsCreating(false);
setNewSessionName("");
setNewSessionName('');
}
}}
autoFocus
@@ -401,7 +379,7 @@ export function SessionManager({
variant="ghost"
onClick={() => {
setIsCreating(false);
setNewSessionName("");
setNewSessionName('');
}}
>
<X className="w-4 h-4" />
@@ -411,7 +389,7 @@ export function SessionManager({
)}
{/* Delete All Archived button - shown at the top of archived sessions */}
{activeTab === "archived" && archivedSessions.length > 0 && (
{activeTab === 'archived' && archivedSessions.length > 0 && (
<div className="pb-2 border-b mb-2">
<Button
variant="destructive"
@@ -431,9 +409,9 @@ export function SessionManager({
<div
key={session.id}
className={cn(
"p-3 border rounded-lg cursor-pointer transition-colors hover:bg-accent/50",
currentSessionId === session.id && "bg-primary/10 border-primary",
session.isArchived && "opacity-60"
'p-3 border rounded-lg cursor-pointer transition-colors hover:bg-accent/50',
currentSessionId === session.id && 'bg-primary/10 border-primary',
session.isArchived && 'opacity-60'
)}
onClick={() => !session.isArchived && onSelectSession(session.id)}
data-testid={`session-item-${session.id}`}
@@ -446,10 +424,10 @@ export function SessionManager({
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleRenameSession(session.id);
if (e.key === "Escape") {
if (e.key === 'Enter') handleRenameSession(session.id);
if (e.key === 'Escape') {
setEditingSessionId(null);
setEditingName("");
setEditingName('');
}
}}
onClick={(e) => e.stopPropagation()}
@@ -472,7 +450,7 @@ export function SessionManager({
onClick={(e) => {
e.stopPropagation();
setEditingSessionId(null);
setEditingName("");
setEditingName('');
}}
className="h-7"
>
@@ -483,16 +461,14 @@ export function SessionManager({
<>
<div className="flex items-center gap-2 mb-1">
{/* Show loading indicator if this session is running (either current session thinking or any session in runningSessions) */}
{(currentSessionId === session.id &&
isCurrentSessionThinking) ||
{(currentSessionId === session.id && isCurrentSessionThinking) ||
runningSessions.has(session.id) ? (
<Loader2 className="w-4 h-4 text-primary animate-spin shrink-0" />
) : (
<MessageSquare className="w-4 h-4 text-muted-foreground shrink-0" />
)}
<h3 className="font-medium truncate">{session.name}</h3>
{((currentSessionId === session.id &&
isCurrentSessionThinking) ||
{((currentSessionId === session.id && isCurrentSessionThinking) ||
runningSessions.has(session.id)) && (
<span className="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full">
thinking...
@@ -500,9 +476,7 @@ export function SessionManager({
)}
</div>
{session.preview && (
<p className="text-xs text-muted-foreground truncate">
{session.preview}
</p>
<p className="text-xs text-muted-foreground truncate">{session.preview}</p>
)}
<div className="flex items-center gap-2 mt-2">
<span className="text-xs text-muted-foreground">
@@ -519,10 +493,7 @@ export function SessionManager({
{/* Actions */}
{!session.isArchived && (
<div
className="flex gap-1"
onClick={(e) => e.stopPropagation()}
>
<div className="flex gap-1" onClick={(e) => e.stopPropagation()}>
<Button
size="sm"
variant="ghost"
@@ -547,10 +518,7 @@ export function SessionManager({
)}
{session.isArchived && (
<div
className="flex gap-1"
onClick={(e) => e.stopPropagation()}
>
<div className="flex gap-1" onClick={(e) => e.stopPropagation()}>
<Button
size="sm"
variant="ghost"
@@ -578,14 +546,12 @@ export function SessionManager({
<div className="text-center py-8 text-muted-foreground">
<MessageSquare className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm">
{activeTab === "active"
? "No active sessions"
: "No archived sessions"}
{activeTab === 'active' ? 'No active sessions' : 'No archived sessions'}
</p>
<p className="text-xs">
{activeTab === "active"
? "Create your first session to get started"
: "Archive sessions to see them here"}
{activeTab === 'active'
? 'Create your first session to get started'
: 'Archive sessions to see them here'}
</p>
</div>
)}

View File

@@ -0,0 +1,7 @@
export { AgentInfoPanel } from './agent-info-panel';
export { CardActions } from './card-actions';
export { CardBadges, PriorityBadges } from './card-badges';
export { CardContentSections } from './card-content-sections';
export { CardHeaderSection } from './card-header';
export { KanbanCard } from './kanban-card';
export { SummaryDialog } from './summary-dialog';

View File

@@ -0,0 +1 @@
export { useApiKeyManagement } from './use-api-key-management';

View File

@@ -0,0 +1,4 @@
export { ApiKeyField } from './api-key-field';
export { ApiKeysSection } from './api-keys-section';
export { AuthenticationStatusDisplay } from './authentication-status-display';
export { SecurityNotice } from './security-notice';

View File

@@ -0,0 +1 @@
export { AppearanceSection } from './appearance-section';

View File

@@ -0,0 +1 @@
export { AudioSection } from './audio-section';

View File

@@ -0,0 +1 @@
export { ClaudeCliStatus } from './claude-cli-status';

View File

@@ -0,0 +1,4 @@
export { DeleteProjectDialog } from './delete-project-dialog';
export { KeyboardMapDialog } from './keyboard-map-dialog';
export { SettingsHeader } from './settings-header';
export { SettingsNavigation } from './settings-navigation';

View File

@@ -0,0 +1,2 @@
export { NAV_ITEMS } from './navigation';
export type { NavigationItem } from './navigation';

View File

@@ -0,0 +1 @@
export { DangerZoneSection } from './danger-zone-section';

View File

@@ -0,0 +1 @@
export { FeatureDefaultsSection } from './feature-defaults-section';

View File

@@ -0,0 +1 @@
export { KeyboardShortcutsSection } from './keyboard-shortcuts-section';

View File

@@ -0,0 +1,2 @@
export type { Theme } from '@/config/theme-options';
export type { CliStatus, KanbanDetailLevel, Project, ApiKeys } from './types';

View File

@@ -1,6 +1,5 @@
import { useState, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { useState, useCallback } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
@@ -8,10 +7,10 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useAppStore, type ThemeMode } from "@/store/app-store";
import { getElectronAPI, type Project } from "@/lib/electron";
import { initializeProject } from "@/lib/project-init";
} from '@/components/ui/dialog';
import { useAppStore, type ThemeMode } from '@/store/app-store';
import { getElectronAPI, type Project } from '@/lib/electron';
import { initializeProject } from '@/lib/project-init';
import {
FolderOpen,
Plus,
@@ -21,19 +20,19 @@ import {
MessageSquare,
ChevronDown,
Loader2,
} from "lucide-react";
} from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { toast } from "sonner";
import { WorkspacePickerModal } from "@/components/workspace-picker-modal";
import { NewProjectModal } from "@/components/new-project-modal";
import { getHttpApiClient } from "@/lib/http-api-client";
import type { StarterTemplate } from "@/lib/templates";
import { useNavigate } from "@tanstack/react-router";
} from '@/components/ui/dropdown-menu';
import { toast } from 'sonner';
import { WorkspacePickerModal } from '@/components/dialogs/workspace-picker-modal';
import { NewProjectModal } from '@/components/dialogs/new-project-modal';
import { getHttpApiClient } from '@/lib/http-api-client';
import type { StarterTemplate } from '@/lib/templates';
import { useNavigate } from '@tanstack/react-router';
export function WelcomeView() {
const {
@@ -66,24 +65,24 @@ export function WelcomeView() {
const api = getElectronAPI();
if (!api.autoMode?.analyzeProject) {
console.log("[Welcome] Auto mode API not available, skipping analysis");
console.log('[Welcome] Auto mode API not available, skipping analysis');
return;
}
setIsAnalyzing(true);
try {
console.log("[Welcome] Starting project analysis for:", projectPath);
console.log('[Welcome] Starting project analysis for:', projectPath);
const result = await api.autoMode.analyzeProject(projectPath);
if (result.success) {
toast.success("Project analyzed", {
description: "AI agent has analyzed your project structure",
toast.success('Project analyzed', {
description: 'AI agent has analyzed your project structure',
});
} else {
console.error("[Welcome] Project analysis failed:", result.error);
console.error('[Welcome] Project analysis failed:', result.error);
}
} catch (error) {
console.error("[Welcome] Failed to analyze project:", error);
console.error('[Welcome] Failed to analyze project:', error);
} finally {
setIsAnalyzing(false);
}
@@ -100,8 +99,8 @@ export function WelcomeView() {
const initResult = await initializeProject(path);
if (!initResult.success) {
toast.error("Failed to initialize project", {
description: initResult.error || "Unknown error occurred",
toast.error('Failed to initialize project', {
description: initResult.error || 'Unknown error occurred',
});
return;
}
@@ -126,26 +125,23 @@ export function WelcomeView() {
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...");
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", {
toast.success('Project opened', {
description: `Opened ${name}`,
});
}
// Navigate to the board view
navigate({ to: "/board" });
navigate({ to: '/board' });
} catch (error) {
console.error("[Welcome] Failed to open project:", error);
toast.error("Failed to open project", {
description: error instanceof Error ? error.message : "Unknown 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);
@@ -178,21 +174,19 @@ export function WelcomeView() {
if (!result.canceled && result.filePaths[0]) {
const path = result.filePaths[0];
// Extract folder name from path (works on both Windows and Mac/Linux)
const name =
path.split(/[/\\]/).filter(Boolean).pop() || "Untitled Project";
const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project';
await initializeAndOpenProject(path, name);
}
}
} catch (error) {
console.error("[Welcome] Failed to check workspace config:", error);
console.error('[Welcome] Failed to check workspace config:', error);
// Fall back to current behavior on error
const api = getElectronAPI();
const result = await api.openDirectory();
if (!result.canceled && result.filePaths[0]) {
const path = result.filePaths[0];
const name =
path.split(/[/\\]/).filter(Boolean).pop() || "Untitled Project";
const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project';
await initializeAndOpenProject(path, name);
}
}
@@ -224,16 +218,13 @@ export function WelcomeView() {
};
const handleInteractiveMode = () => {
navigate({ to: "/interview" });
navigate({ to: '/interview' });
};
/**
* Create a blank project with just .automaker directory structure
*/
const handleCreateBlankProject = async (
projectName: string,
parentDir: string
) => {
const handleCreateBlankProject = async (projectName: string, parentDir: string) => {
setIsCreating(true);
try {
const api = getElectronAPI();
@@ -242,7 +233,7 @@ export function WelcomeView() {
// Validate that parent directory exists
const parentExists = await api.exists(parentDir);
if (!parentExists) {
toast.error("Parent directory does not exist", {
toast.error('Parent directory does not exist', {
description: `Cannot create project in non-existent directory: ${parentDir}`,
});
return;
@@ -251,7 +242,7 @@ export function WelcomeView() {
// Verify parent is actually a directory
const parentStat = await api.stat(parentDir);
if (parentStat && !parentStat.isDirectory) {
toast.error("Parent path is not a directory", {
toast.error('Parent path is not a directory', {
description: `${parentDir} is not a directory`,
});
return;
@@ -260,8 +251,8 @@ export function WelcomeView() {
// Create project directory
const mkdirResult = await api.mkdir(projectPath);
if (!mkdirResult.success) {
toast.error("Failed to create project directory", {
description: mkdirResult.error || "Unknown error occurred",
toast.error('Failed to create project directory', {
description: mkdirResult.error || 'Unknown error occurred',
});
return;
}
@@ -270,8 +261,8 @@ export function WelcomeView() {
const initResult = await initializeProject(projectPath);
if (!initResult.success) {
toast.error("Failed to initialize project", {
description: initResult.error || "Unknown error occurred",
toast.error('Failed to initialize project', {
description: initResult.error || 'Unknown error occurred',
});
return;
}
@@ -313,7 +304,7 @@ export function WelcomeView() {
setCurrentProject(project);
setShowNewProjectModal(false);
toast.success("Project created", {
toast.success('Project created', {
description: `Created ${projectName} with .automaker directory`,
});
@@ -326,9 +317,9 @@ export function WelcomeView() {
});
setShowInitDialog(true);
} catch (error) {
console.error("Failed to create project:", error);
toast.error("Failed to create project", {
description: error instanceof Error ? error.message : "Unknown error",
console.error('Failed to create project:', error);
toast.error('Failed to create project', {
description: error instanceof Error ? error.message : 'Unknown error',
});
} finally {
setIsCreating(false);
@@ -356,8 +347,8 @@ export function WelcomeView() {
);
if (!cloneResult.success || !cloneResult.projectPath) {
toast.error("Failed to clone template", {
description: cloneResult.error || "Unknown error occurred",
toast.error('Failed to clone template', {
description: cloneResult.error || 'Unknown error occurred',
});
return;
}
@@ -368,8 +359,8 @@ export function WelcomeView() {
const initResult = await initializeProject(projectPath);
if (!initResult.success) {
toast.error("Failed to initialize project", {
description: initResult.error || "Unknown error occurred",
toast.error('Failed to initialize project', {
description: initResult.error || 'Unknown error occurred',
});
return;
}
@@ -387,15 +378,11 @@ export function WelcomeView() {
</overview>
<technology_stack>
${template.techStack
.map((tech) => `<technology>${tech}</technology>`)
.join("\n ")}
${template.techStack.map((tech) => `<technology>${tech}</technology>`).join('\n ')}
</technology_stack>
<core_capabilities>
${template.features
.map((feature) => `<capability>${feature}</capability>`)
.join("\n ")}
${template.features.map((feature) => `<capability>${feature}</capability>`).join('\n ')}
</core_capabilities>
<implemented_features>
@@ -415,7 +402,7 @@ export function WelcomeView() {
setCurrentProject(project);
setShowNewProjectModal(false);
toast.success("Project created from template", {
toast.success('Project created from template', {
description: `Created ${projectName} from ${template.name}`,
});
@@ -431,9 +418,9 @@ export function WelcomeView() {
// Kick off project analysis
analyzeProject(projectPath);
} catch (error) {
console.error("Failed to create project from template:", error);
toast.error("Failed to create project", {
description: error instanceof Error ? error.message : "Unknown error",
console.error('Failed to create project from template:', error);
toast.error('Failed to create project', {
description: error instanceof Error ? error.message : 'Unknown error',
});
} finally {
setIsCreating(false);
@@ -454,15 +441,11 @@ export function WelcomeView() {
const api = getElectronAPI();
// Clone the repository
const cloneResult = await httpClient.templates.clone(
repoUrl,
projectName,
parentDir
);
const cloneResult = await httpClient.templates.clone(repoUrl, projectName, parentDir);
if (!cloneResult.success || !cloneResult.projectPath) {
toast.error("Failed to clone repository", {
description: cloneResult.error || "Unknown error occurred",
toast.error('Failed to clone repository', {
description: cloneResult.error || 'Unknown error occurred',
});
return;
}
@@ -473,8 +456,8 @@ export function WelcomeView() {
const initResult = await initializeProject(projectPath);
if (!initResult.success) {
toast.error("Failed to initialize project", {
description: initResult.error || "Unknown error occurred",
toast.error('Failed to initialize project', {
description: initResult.error || 'Unknown error occurred',
});
return;
}
@@ -516,7 +499,7 @@ export function WelcomeView() {
setCurrentProject(project);
setShowNewProjectModal(false);
toast.success("Project created from repository", {
toast.success('Project created from repository', {
description: `Created ${projectName} from ${repoUrl}`,
});
@@ -532,9 +515,9 @@ export function WelcomeView() {
// Kick off project analysis
analyzeProject(projectPath);
} catch (error) {
console.error("Failed to create project from custom URL:", error);
toast.error("Failed to create project", {
description: error instanceof Error ? error.message : "Unknown error",
console.error('Failed to create project from custom URL:', error);
toast.error('Failed to create project', {
description: error instanceof Error ? error.message : 'Unknown error',
});
} finally {
setIsCreating(false);
@@ -587,12 +570,9 @@ export function WelcomeView() {
<Plus className="w-6 h-6 text-white" />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-lg font-semibold text-foreground mb-1.5">
New Project
</h3>
<h3 className="text-lg font-semibold text-foreground mb-1.5">New Project</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
Create a new project from scratch with AI-powered
development
Create a new project from scratch with AI-powered development
</p>
</div>
</div>
@@ -608,10 +588,7 @@ export function WelcomeView() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuItem
onClick={handleNewProject}
data-testid="quick-setup-option"
>
<DropdownMenuItem onClick={handleNewProject} data-testid="quick-setup-option">
<Plus className="w-4 h-4 mr-2" />
Quick Setup
</DropdownMenuItem>
@@ -640,9 +617,7 @@ export function WelcomeView() {
<FolderOpen className="w-6 h-6 text-muted-foreground group-hover:text-blue-500 transition-colors duration-300" />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-lg font-semibold text-foreground mb-1.5">
Open Project
</h3>
<h3 className="text-lg font-semibold text-foreground mb-1.5">Open Project</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
Open an existing project folder to continue working
</p>
@@ -667,9 +642,7 @@ export function WelcomeView() {
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center">
<Clock className="w-4 h-4 text-muted-foreground" />
</div>
<h2 className="text-lg font-semibold text-foreground">
Recent Projects
</h2>
<h2 className="text-lg font-semibold text-foreground">Recent Projects</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{recentProjects.map((project, index) => (
@@ -695,9 +668,7 @@ export function WelcomeView() {
</p>
{project.lastOpened && (
<p className="text-xs text-muted-foreground mt-1.5">
{new Date(
project.lastOpened
).toLocaleDateString()}
{new Date(project.lastOpened).toLocaleDateString()}
</p>
)}
</div>
@@ -715,9 +686,7 @@ export function WelcomeView() {
<div className="w-20 h-20 rounded-2xl bg-muted/50 border border-border flex items-center justify-center mb-5">
<Sparkles className="w-10 h-10 text-muted-foreground/50" />
</div>
<h3 className="text-xl font-semibold text-foreground mb-2">
No projects yet
</h3>
<h3 className="text-xl font-semibold text-foreground mb-2">No projects yet</h3>
<p className="text-sm text-muted-foreground max-w-md leading-relaxed">
Get started by creating a new project or opening an existing one
</p>
@@ -747,9 +716,7 @@ export function WelcomeView() {
<div className="w-8 h-8 rounded-lg bg-brand-500/10 flex items-center justify-center">
<Sparkles className="w-4 h-4 text-brand-500" />
</div>
{initStatus?.isNewProject
? "Project Initialized"
: "Project Updated"}
{initStatus?.isNewProject ? 'Project Initialized' : 'Project Updated'}
</DialogTitle>
<DialogDescription className="text-muted-foreground mt-1">
{initStatus?.isNewProject
@@ -759,9 +726,7 @@ export function WelcomeView() {
</DialogHeader>
<div className="py-4">
<div className="space-y-3">
<p className="text-sm text-foreground font-medium">
Created files:
</p>
<p className="text-sm text-foreground font-medium">Created files:</p>
<ul className="space-y-2">
{initStatus?.createdFiles.map((file) => (
<li
@@ -788,12 +753,12 @@ export function WelcomeView() {
</div>
) : (
<p className="text-sm text-muted-foreground leading-relaxed">
<span className="text-brand-500 font-medium">Tip:</span> Edit the{" "}
<span className="text-brand-500 font-medium">Tip:</span> Edit the{' '}
<code className="text-xs bg-muted px-1.5 py-0.5 rounded font-mono">
app_spec.txt
</code>{" "}
file to describe your project. The AI agent will use this to
understand your project structure.
</code>{' '}
file to describe your project. The AI agent will use this to understand your
project structure.
</p>
)}
</div>
@@ -826,9 +791,7 @@ export function WelcomeView() {
>
<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" />
<p className="text-foreground font-medium">
Initializing project...
</p>
<p className="text-foreground font-medium">Initializing project...</p>
</div>
</div>
)}