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:
SuperComboGamer
2025-12-21 20:31:57 -05:00
parent 584f5a3426
commit 8d578558ff
295 changed files with 9088 additions and 10546 deletions

View File

@@ -1,3 +1,3 @@
export { SpecHeader } from "./spec-header";
export { SpecEditor } from "./spec-editor";
export { SpecEmptyState } from "./spec-empty-state";
export { SpecHeader } from './spec-header';
export { SpecEditor } from './spec-editor';
export { SpecEmptyState } from './spec-empty-state';

View File

@@ -1,5 +1,5 @@
import { Card } from "@/components/ui/card";
import { XmlSyntaxEditor } from "@/components/ui/xml-syntax-editor";
import { Card } from '@/components/ui/card';
import { XmlSyntaxEditor } from '@/components/ui/xml-syntax-editor';
interface SpecEditorProps {
value: string;

View File

@@ -1,10 +1,6 @@
import { Button } from "@/components/ui/button";
import {
FileText,
FilePlus2,
Loader2,
} from "lucide-react";
import { PHASE_LABELS } from "../constants";
import { Button } from '@/components/ui/button';
import { FileText, FilePlus2, Loader2 } from 'lucide-react';
import { PHASE_LABELS } from '../constants';
interface SpecEmptyStateProps {
projectPath: string;
@@ -27,19 +23,14 @@ export function SpecEmptyState({
const phaseLabel = PHASE_LABELS[currentPhase] || currentPhase;
return (
<div
className="flex-1 flex flex-col overflow-hidden content-bg"
data-testid="spec-view-empty"
>
<div className="flex-1 flex flex-col overflow-hidden content-bg" data-testid="spec-view-empty">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border bg-glass backdrop-blur-md">
<div className="flex items-center gap-3">
<FileText className="w-5 h-5 text-muted-foreground" />
<div>
<h1 className="text-xl font-bold">App Specification</h1>
<p className="text-sm text-muted-foreground">
{projectPath}/.automaker/app_spec.txt
</p>
<p className="text-sm text-muted-foreground">{projectPath}/.automaker/app_spec.txt</p>
</div>
</div>
{isProcessing && (
@@ -50,9 +41,7 @@ export function SpecEmptyState({
</div>
<div className="flex flex-col gap-1 min-w-0">
<span className="text-sm font-semibold text-primary leading-tight tracking-tight">
{isCreating
? "Generating Specification"
: "Regenerating Specification"}
{isCreating ? 'Generating Specification' : 'Regenerating Specification'}
</span>
{currentPhase && (
<span className="text-xs text-muted-foreground/90 leading-tight font-medium">
@@ -96,13 +85,13 @@ export function SpecEmptyState({
)}
</>
) : (
"No App Specification Found"
'No App Specification Found'
)}
</h2>
<p className="text-muted-foreground mb-6">
{isCreating
? currentPhase === "feature_generation"
? "The app specification has been created! Now generating features from the implementation roadmap..."
? currentPhase === 'feature_generation'
? 'The app specification has been created! Now generating features from the implementation roadmap...'
: "We're analyzing your project and generating a comprehensive specification. This may take a few moments..."
: "Create an app specification to help our system understand your project. We'll analyze your codebase and generate a comprehensive spec based on your description."}
</p>

View File

@@ -1,12 +1,6 @@
import { Button } from "@/components/ui/button";
import {
Save,
Sparkles,
Loader2,
FileText,
AlertCircle,
} from "lucide-react";
import { PHASE_LABELS } from "../constants";
import { Button } from '@/components/ui/button';
import { Save, Sparkles, Loader2, FileText, AlertCircle } from 'lucide-react';
import { PHASE_LABELS } from '../constants';
interface SpecHeaderProps {
projectPath: string;
@@ -42,9 +36,7 @@ export function SpecHeader({
<FileText className="w-5 h-5 text-muted-foreground" />
<div>
<h1 className="text-xl font-bold">App Specification</h1>
<p className="text-sm text-muted-foreground">
{projectPath}/.automaker/app_spec.txt
</p>
<p className="text-sm text-muted-foreground">{projectPath}/.automaker/app_spec.txt</p>
</div>
</div>
<div className="flex items-center gap-3">
@@ -57,10 +49,10 @@ export function SpecHeader({
<div className="flex flex-col gap-1 min-w-0">
<span className="text-sm font-semibold text-primary leading-tight tracking-tight">
{isGeneratingFeatures
? "Generating Features"
? 'Generating Features'
: isCreating
? "Generating Specification"
: "Regenerating Specification"}
? 'Generating Specification'
: 'Regenerating Specification'}
</span>
{currentPhase && (
<span className="text-xs text-muted-foreground/90 leading-tight font-medium">
@@ -96,7 +88,7 @@ export function SpecHeader({
) : (
<Sparkles className="w-4 h-4 mr-2" />
)}
{isRegenerating ? "Regenerating..." : "Regenerate"}
{isRegenerating ? 'Regenerating...' : 'Regenerate'}
</Button>
<Button
size="sm"
@@ -105,7 +97,7 @@ export function SpecHeader({
data-testid="save-spec"
>
<Save className="w-4 h-4 mr-2" />
{isSaving ? "Saving..." : hasChanges ? "Save Changes" : "Saved"}
{isSaving ? 'Saving...' : hasChanges ? 'Save Changes' : 'Saved'}
</Button>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import type { FeatureCount } from "./types";
import type { FeatureCount } from './types';
// Delay before reloading spec file to ensure it's written to disk
export const SPEC_FILE_WRITE_DELAY = 500;
@@ -12,18 +12,18 @@ export const FEATURE_COUNT_OPTIONS: {
label: string;
warning?: string;
}[] = [
{ value: 20, label: "20" },
{ value: 50, label: "50", warning: "May take up to 5 minutes" },
{ value: 100, label: "100", warning: "May take up to 5 minutes" },
{ value: 20, label: '20' },
{ value: 50, label: '50', warning: 'May take up to 5 minutes' },
{ value: 100, label: '100', warning: 'May take up to 5 minutes' },
];
// Phase display labels for UI
export const PHASE_LABELS: Record<string, string> = {
initialization: "Initializing...",
setup: "Setting up tools...",
analysis: "Analyzing project structure...",
spec_complete: "Spec created! Generating features...",
feature_generation: "Creating features from roadmap...",
complete: "Complete!",
error: "Error occurred",
initialization: 'Initializing...',
setup: 'Setting up tools...',
analysis: 'Analyzing project structure...',
spec_complete: 'Spec created! Generating features...',
feature_generation: 'Creating features from roadmap...',
complete: 'Complete!',
error: 'Error occurred',
};

View File

@@ -1,4 +1,4 @@
import { Sparkles, Clock, Loader2 } from "lucide-react";
import { Sparkles, Clock, Loader2 } from 'lucide-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 { Checkbox } from "@/components/ui/checkbox";
import { cn } from "@/lib/utils";
import { FEATURE_COUNT_OPTIONS } from "../constants";
import type { CreateSpecDialogProps, FeatureCount } from "../types";
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { HotkeyButton } from '@/components/ui/hotkey-button';
import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils';
import { FEATURE_COUNT_OPTIONS } from '../constants';
import type { CreateSpecDialogProps, FeatureCount } from '../types';
export function CreateSpecDialog({
open,
@@ -29,12 +29,10 @@ export function CreateSpecDialog({
onSkip,
isCreatingSpec,
showSkipButton = false,
title = "Create App Specification",
title = 'Create App Specification',
description = "We didn't find an app_spec.txt file. Let us help you generate your app_spec.txt to help describe your project for our system. We'll analyze your project's tech stack and create a comprehensive specification.",
}: CreateSpecDialogProps) {
const selectedOption = FEATURE_COUNT_OPTIONS.find(
(o) => o.value === featureCount
);
const selectedOption = FEATURE_COUNT_OPTIONS.find((o) => o.value === featureCount);
return (
<Dialog
@@ -49,18 +47,15 @@ export function CreateSpecDialog({
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription className="text-muted-foreground">
{description}
</DialogDescription>
<DialogDescription className="text-muted-foreground">{description}</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label className="text-sm font-medium">Project Overview</label>
<p className="text-xs text-muted-foreground">
Describe what your project does and what features you want to
build. Be as detailed as you want - this will help us create a
better specification.
Describe what your project does and what features you want to build. Be as detailed as
you want - this will help us create a better specification.
</p>
<textarea
className="w-full h-48 p-3 rounded-md border border-border bg-background font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-ring"
@@ -76,25 +71,20 @@ export function CreateSpecDialog({
<Checkbox
id="create-analyze-project"
checked={analyzeProject}
onCheckedChange={(checked) =>
onAnalyzeProjectChange(checked === true)
}
onCheckedChange={(checked) => onAnalyzeProjectChange(checked === true)}
disabled={isCreatingSpec}
/>
<div className="space-y-1">
<label
htmlFor="create-analyze-project"
className={`text-sm font-medium ${
isCreatingSpec ? "" : "cursor-pointer"
}`}
className={`text-sm font-medium ${isCreatingSpec ? '' : 'cursor-pointer'}`}
>
Analyze current project for additional context
</label>
<p className="text-xs text-muted-foreground">
If checked, the agent will research your existing codebase to
understand the tech stack. If unchecked, defaults to TanStack
Start, Drizzle ORM, PostgreSQL, shadcn/ui, Tailwind CSS, and
React.
If checked, the agent will research your existing codebase to understand the tech
stack. If unchecked, defaults to TanStack Start, Drizzle ORM, PostgreSQL, shadcn/ui,
Tailwind CSS, and React.
</p>
</div>
</div>
@@ -103,23 +93,19 @@ export function CreateSpecDialog({
<Checkbox
id="create-generate-features"
checked={generateFeatures}
onCheckedChange={(checked) =>
onGenerateFeaturesChange(checked === true)
}
onCheckedChange={(checked) => onGenerateFeaturesChange(checked === true)}
disabled={isCreatingSpec}
/>
<div className="space-y-1">
<label
htmlFor="create-generate-features"
className={`text-sm font-medium ${
isCreatingSpec ? "" : "cursor-pointer"
}`}
className={`text-sm font-medium ${isCreatingSpec ? '' : 'cursor-pointer'}`}
>
Generate feature list
</label>
<p className="text-xs text-muted-foreground">
Automatically create features in the features folder from the
implementation roadmap after the spec is generated.
Automatically create features in the features folder from the implementation roadmap
after the spec is generated.
</p>
</div>
</div>
@@ -133,19 +119,15 @@ export function CreateSpecDialog({
<Button
key={option.value}
type="button"
variant={
featureCount === option.value ? "default" : "outline"
}
variant={featureCount === option.value ? 'default' : 'outline'}
size="sm"
onClick={() =>
onFeatureCountChange(option.value as FeatureCount)
}
onClick={() => onFeatureCountChange(option.value as FeatureCount)}
disabled={isCreatingSpec}
className={cn(
"flex-1 transition-all",
'flex-1 transition-all',
featureCount === option.value
? "bg-primary hover:bg-primary/90 text-primary-foreground"
: "bg-muted/30 hover:bg-muted/50 border-border"
? 'bg-primary hover:bg-primary/90 text-primary-foreground'
: 'bg-muted/30 hover:bg-muted/50 border-border'
)}
data-testid={`feature-count-${option.value}`}
>
@@ -169,18 +151,14 @@ export function CreateSpecDialog({
Skip for now
</Button>
) : (
<Button
variant="ghost"
onClick={() => onOpenChange(false)}
disabled={isCreatingSpec}
>
<Button variant="ghost" onClick={() => onOpenChange(false)} disabled={isCreatingSpec}>
Cancel
</Button>
)}
<HotkeyButton
onClick={onCreateSpec}
disabled={!projectOverview.trim() || isCreatingSpec}
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkey={{ key: 'Enter', cmdCtrl: true }}
hotkeyActive={open && !isCreatingSpec}
>
{isCreatingSpec ? (

View File

@@ -1,2 +1,2 @@
export { CreateSpecDialog } from "./create-spec-dialog";
export { RegenerateSpecDialog } from "./regenerate-spec-dialog";
export { CreateSpecDialog } from './create-spec-dialog';
export { RegenerateSpecDialog } from './regenerate-spec-dialog';

View File

@@ -1,4 +1,4 @@
import { Sparkles, Clock, Loader2 } from "lucide-react";
import { Sparkles, Clock, Loader2 } from 'lucide-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 { Checkbox } from "@/components/ui/checkbox";
import { cn } from "@/lib/utils";
import { FEATURE_COUNT_OPTIONS } from "../constants";
import type { RegenerateSpecDialogProps, FeatureCount } from "../types";
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { HotkeyButton } from '@/components/ui/hotkey-button';
import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils';
import { FEATURE_COUNT_OPTIONS } from '../constants';
import type { RegenerateSpecDialogProps, FeatureCount } from '../types';
export function RegenerateSpecDialog({
open,
@@ -29,9 +29,7 @@ export function RegenerateSpecDialog({
isRegenerating,
isGeneratingFeatures = false,
}: RegenerateSpecDialogProps) {
const selectedOption = FEATURE_COUNT_OPTIONS.find(
(o) => o.value === featureCount
);
const selectedOption = FEATURE_COUNT_OPTIONS.find((o) => o.value === featureCount);
const isDisabled = isRegenerating || isGeneratingFeatures;
return (
@@ -47,10 +45,9 @@ export function RegenerateSpecDialog({
<DialogHeader>
<DialogTitle>Regenerate App Specification</DialogTitle>
<DialogDescription className="text-muted-foreground">
We will regenerate your app spec based on a short project definition
and the current tech stack found in your project. The agent will
analyze your codebase to understand your existing technologies and
create a comprehensive specification.
We will regenerate your app spec based on a short project definition and the current
tech stack found in your project. The agent will analyze your codebase to understand
your existing technologies and create a comprehensive specification.
</DialogDescription>
</DialogHeader>
@@ -58,9 +55,8 @@ export function RegenerateSpecDialog({
<div className="space-y-2">
<label className="text-sm font-medium">Describe your project</label>
<p className="text-xs text-muted-foreground">
Provide a clear description of what your app should do. Be as
detailed as you want - the more context you provide, the more
comprehensive the spec will be.
Provide a clear description of what your app should do. Be as detailed as you want -
the more context you provide, the more comprehensive the spec will be.
</p>
<textarea
className="w-full h-40 p-3 rounded-md border border-border bg-background font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-ring"
@@ -75,25 +71,20 @@ export function RegenerateSpecDialog({
<Checkbox
id="regenerate-analyze-project"
checked={analyzeProject}
onCheckedChange={(checked) =>
onAnalyzeProjectChange(checked === true)
}
onCheckedChange={(checked) => onAnalyzeProjectChange(checked === true)}
disabled={isDisabled}
/>
<div className="space-y-1">
<label
htmlFor="regenerate-analyze-project"
className={`text-sm font-medium ${
isDisabled ? "" : "cursor-pointer"
}`}
className={`text-sm font-medium ${isDisabled ? '' : 'cursor-pointer'}`}
>
Analyze current project for additional context
</label>
<p className="text-xs text-muted-foreground">
If checked, the agent will research your existing codebase to
understand the tech stack. If unchecked, defaults to TanStack
Start, Drizzle ORM, PostgreSQL, shadcn/ui, Tailwind CSS, and
React.
If checked, the agent will research your existing codebase to understand the tech
stack. If unchecked, defaults to TanStack Start, Drizzle ORM, PostgreSQL, shadcn/ui,
Tailwind CSS, and React.
</p>
</div>
</div>
@@ -102,23 +93,19 @@ export function RegenerateSpecDialog({
<Checkbox
id="regenerate-generate-features"
checked={generateFeatures}
onCheckedChange={(checked) =>
onGenerateFeaturesChange(checked === true)
}
onCheckedChange={(checked) => onGenerateFeaturesChange(checked === true)}
disabled={isDisabled}
/>
<div className="space-y-1">
<label
htmlFor="regenerate-generate-features"
className={`text-sm font-medium ${
isDisabled ? "" : "cursor-pointer"
}`}
className={`text-sm font-medium ${isDisabled ? '' : 'cursor-pointer'}`}
>
Generate feature list
</label>
<p className="text-xs text-muted-foreground">
Automatically create features in the features folder from the
implementation roadmap after the spec is regenerated.
Automatically create features in the features folder from the implementation roadmap
after the spec is regenerated.
</p>
</div>
</div>
@@ -132,19 +119,15 @@ export function RegenerateSpecDialog({
<Button
key={option.value}
type="button"
variant={
featureCount === option.value ? "default" : "outline"
}
variant={featureCount === option.value ? 'default' : 'outline'}
size="sm"
onClick={() =>
onFeatureCountChange(option.value as FeatureCount)
}
onClick={() => onFeatureCountChange(option.value as FeatureCount)}
disabled={isDisabled}
className={cn(
"flex-1 transition-all",
'flex-1 transition-all',
featureCount === option.value
? "bg-primary hover:bg-primary/90 text-primary-foreground"
: "bg-muted/30 hover:bg-muted/50 border-border"
? 'bg-primary hover:bg-primary/90 text-primary-foreground'
: 'bg-muted/30 hover:bg-muted/50 border-border'
)}
data-testid={`regenerate-feature-count-${option.value}`}
>
@@ -164,17 +147,13 @@ export function RegenerateSpecDialog({
<DialogFooter>
<div className="flex gap-2">
<Button
variant="ghost"
onClick={() => onOpenChange(false)}
disabled={isDisabled}
>
<Button variant="ghost" onClick={() => onOpenChange(false)} disabled={isDisabled}>
Cancel
</Button>
<HotkeyButton
onClick={onRegenerate}
disabled={!projectDefinition.trim() || isDisabled}
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkey={{ key: 'Enter', cmdCtrl: true }}
hotkeyActive={open && !isDisabled}
>
{isRegenerating ? (

View File

@@ -1,3 +1,3 @@
export { useSpecLoading } from "./use-spec-loading";
export { useSpecSave } from "./use-spec-save";
export { useSpecGeneration } from "./use-spec-generation";
export { useSpecLoading } from './use-spec-loading';
export { useSpecSave } from './use-spec-save';
export { useSpecGeneration } from './use-spec-generation';

View File

@@ -1,12 +1,12 @@
import { useEffect, useState, useCallback, useRef } from "react";
import { useAppStore } from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { toast } from "sonner";
import { CheckCircle2 } from "lucide-react";
import { createElement } from "react";
import { SPEC_FILE_WRITE_DELAY, STATUS_CHECK_INTERVAL_MS } from "../constants";
import type { FeatureCount } from "../types";
import type { SpecRegenerationEvent } from "@/types/electron";
import { useEffect, useState, useCallback, useRef } from 'react';
import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner';
import { CheckCircle2 } from 'lucide-react';
import { createElement } from 'react';
import { SPEC_FILE_WRITE_DELAY, STATUS_CHECK_INTERVAL_MS } from '../constants';
import type { FeatureCount } from '../types';
import type { SpecRegenerationEvent } from '@/types/electron';
interface UseSpecGenerationOptions {
loadSpec: () => Promise<void>;
@@ -20,33 +20,29 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
const [showRegenerateDialog, setShowRegenerateDialog] = useState(false);
// Create spec state
const [projectOverview, setProjectOverview] = useState("");
const [projectOverview, setProjectOverview] = useState('');
const [isCreating, setIsCreating] = useState(false);
const [generateFeatures, setGenerateFeatures] = useState(true);
const [analyzeProjectOnCreate, setAnalyzeProjectOnCreate] = useState(true);
const [featureCountOnCreate, setFeatureCountOnCreate] =
useState<FeatureCount>(50);
const [featureCountOnCreate, setFeatureCountOnCreate] = useState<FeatureCount>(50);
// Regenerate spec state
const [projectDefinition, setProjectDefinition] = useState("");
const [projectDefinition, setProjectDefinition] = useState('');
const [isRegenerating, setIsRegenerating] = useState(false);
const [generateFeaturesOnRegenerate, setGenerateFeaturesOnRegenerate] =
useState(true);
const [analyzeProjectOnRegenerate, setAnalyzeProjectOnRegenerate] =
useState(true);
const [featureCountOnRegenerate, setFeatureCountOnRegenerate] =
useState<FeatureCount>(50);
const [generateFeaturesOnRegenerate, setGenerateFeaturesOnRegenerate] = useState(true);
const [analyzeProjectOnRegenerate, setAnalyzeProjectOnRegenerate] = useState(true);
const [featureCountOnRegenerate, setFeatureCountOnRegenerate] = useState<FeatureCount>(50);
// Generate features only state
const [isGeneratingFeatures, setIsGeneratingFeatures] = useState(false);
// Logs state (kept for internal tracking)
const [logs, setLogs] = useState<string>("");
const logsRef = useRef<string>("");
const [logs, setLogs] = useState<string>('');
const logsRef = useRef<string>('');
// Phase tracking and status
const [currentPhase, setCurrentPhase] = useState<string>("");
const [errorMessage, setErrorMessage] = useState<string>("");
const [currentPhase, setCurrentPhase] = useState<string>('');
const [errorMessage, setErrorMessage] = useState<string>('');
const statusCheckRef = useRef<boolean>(false);
const stateRestoredRef = useRef<boolean>(false);
const pendingStatusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -56,10 +52,10 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
setIsCreating(false);
setIsRegenerating(false);
setIsGeneratingFeatures(false);
setCurrentPhase("");
setErrorMessage("");
setLogs("");
logsRef.current = "";
setCurrentPhase('');
setErrorMessage('');
setLogs('');
logsRef.current = '';
stateRestoredRef.current = false;
statusCheckRef.current = false;
@@ -84,15 +80,15 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
const status = await api.specRegeneration.status();
console.log(
"[useSpecGeneration] Status check on mount:",
'[useSpecGeneration] Status check on mount:',
status,
"for project:",
'for project:',
currentProject.path
);
if (status.success && status.isRunning) {
console.log(
"[useSpecGeneration] Spec generation is running globally. Tentatively showing loader."
'[useSpecGeneration] Spec generation is running globally. Tentatively showing loader.'
);
setIsCreating(true);
@@ -100,7 +96,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
if (status.currentPhase) {
setCurrentPhase(status.currentPhase);
} else {
setCurrentPhase("initialization");
setCurrentPhase('initialization');
}
if (pendingStatusTimeoutRef.current) {
@@ -108,21 +104,21 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
}
pendingStatusTimeoutRef.current = setTimeout(() => {
console.log(
"[useSpecGeneration] No events received for current project - clearing tentative state"
'[useSpecGeneration] No events received for current project - clearing tentative state'
);
setIsCreating(false);
setIsRegenerating(false);
setCurrentPhase("");
setCurrentPhase('');
pendingStatusTimeoutRef.current = null;
}, 3000);
} else if (status.success && !status.isRunning) {
setIsCreating(false);
setIsRegenerating(false);
setCurrentPhase("");
setCurrentPhase('');
stateRestoredRef.current = false;
}
} catch (error) {
console.error("[useSpecGeneration] Failed to check status:", error);
console.error('[useSpecGeneration] Failed to check status:', error);
} finally {
statusCheckRef.current = false;
}
@@ -145,52 +141,36 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
if (!api.specRegeneration) return;
const status = await api.specRegeneration.status();
console.log(
"[useSpecGeneration] Visibility change - status check:",
status
);
console.log('[useSpecGeneration] Visibility change - status check:', status);
if (!status.isRunning) {
console.log(
"[useSpecGeneration] Visibility change: Backend indicates generation complete - clearing state"
'[useSpecGeneration] Visibility change: Backend indicates generation complete - clearing state'
);
setIsCreating(false);
setIsRegenerating(false);
setIsGeneratingFeatures(false);
setCurrentPhase("");
setCurrentPhase('');
stateRestoredRef.current = false;
loadSpec();
} else if (status.currentPhase) {
setCurrentPhase(status.currentPhase);
}
} catch (error) {
console.error(
"[useSpecGeneration] Failed to check status on visibility change:",
error
);
console.error('[useSpecGeneration] Failed to check status on visibility change:', error);
}
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [
currentProject,
isCreating,
isRegenerating,
isGeneratingFeatures,
loadSpec,
]);
}, [currentProject, isCreating, isRegenerating, isGeneratingFeatures, loadSpec]);
// Periodic status check
useEffect(() => {
if (
!currentProject ||
(!isCreating && !isRegenerating && !isGeneratingFeatures)
)
return;
if (!currentProject || (!isCreating && !isRegenerating && !isGeneratingFeatures)) return;
const intervalId = setInterval(async () => {
try {
@@ -201,46 +181,30 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
if (!status.isRunning) {
console.log(
"[useSpecGeneration] Periodic check: Backend indicates generation complete - clearing state"
'[useSpecGeneration] Periodic check: Backend indicates generation complete - clearing state'
);
setIsCreating(false);
setIsRegenerating(false);
setIsGeneratingFeatures(false);
setCurrentPhase("");
setCurrentPhase('');
stateRestoredRef.current = false;
loadSpec();
} else if (
status.currentPhase &&
status.currentPhase !== currentPhase
) {
console.log(
"[useSpecGeneration] Periodic check: Phase updated from backend",
{
old: currentPhase,
new: status.currentPhase,
}
);
} else if (status.currentPhase && status.currentPhase !== currentPhase) {
console.log('[useSpecGeneration] Periodic check: Phase updated from backend', {
old: currentPhase,
new: status.currentPhase,
});
setCurrentPhase(status.currentPhase);
}
} catch (error) {
console.error(
"[useSpecGeneration] Periodic status check error:",
error
);
console.error('[useSpecGeneration] Periodic status check error:', error);
}
}, STATUS_CHECK_INTERVAL_MS);
return () => {
clearInterval(intervalId);
};
}, [
currentProject,
isCreating,
isRegenerating,
isGeneratingFeatures,
currentPhase,
loadSpec,
]);
}, [currentProject, isCreating, isRegenerating, isGeneratingFeatures, currentPhase, loadSpec]);
// Subscribe to spec regeneration events
useEffect(() => {
@@ -249,198 +213,179 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
const api = getElectronAPI();
if (!api.specRegeneration) return;
const unsubscribe = api.specRegeneration.onEvent(
(event: SpecRegenerationEvent) => {
const unsubscribe = api.specRegeneration.onEvent((event: SpecRegenerationEvent) => {
console.log(
'[useSpecGeneration] Regeneration event:',
event.type,
'for project:',
event.projectPath,
'current project:',
currentProject?.path
);
if (event.projectPath !== currentProject?.path) {
console.log('[useSpecGeneration] Ignoring event - not for current project');
return;
}
if (pendingStatusTimeoutRef.current) {
clearTimeout(pendingStatusTimeoutRef.current);
pendingStatusTimeoutRef.current = null;
console.log(
"[useSpecGeneration] Regeneration event:",
event.type,
"for project:",
event.projectPath,
"current project:",
currentProject?.path
'[useSpecGeneration] Event confirmed this is for current project - clearing timeout'
);
}
if (event.projectPath !== currentProject?.path) {
console.log(
"[useSpecGeneration] Ignoring event - not for current project"
);
return;
}
if (event.type === 'spec_regeneration_progress') {
setIsCreating(true);
setIsRegenerating(true);
if (pendingStatusTimeoutRef.current) {
clearTimeout(pendingStatusTimeoutRef.current);
pendingStatusTimeoutRef.current = null;
console.log(
"[useSpecGeneration] Event confirmed this is for current project - clearing timeout"
);
}
const phaseMatch = event.content.match(/\[Phase:\s*([^\]]+)\]/);
if (phaseMatch) {
const phase = phaseMatch[1];
setCurrentPhase(phase);
console.log(`[useSpecGeneration] Phase updated: ${phase}`);
if (event.type === "spec_regeneration_progress") {
setIsCreating(true);
setIsRegenerating(true);
const phaseMatch = event.content.match(/\[Phase:\s*([^\]]+)\]/);
if (phaseMatch) {
const phase = phaseMatch[1];
setCurrentPhase(phase);
console.log(`[useSpecGeneration] Phase updated: ${phase}`);
if (phase === "complete") {
console.log(
"[useSpecGeneration] Phase is complete - clearing state"
);
setIsCreating(false);
setIsRegenerating(false);
stateRestoredRef.current = false;
setTimeout(() => {
loadSpec();
}, SPEC_FILE_WRITE_DELAY);
}
}
if (
event.content.includes("All tasks completed") ||
event.content.includes("✓ All tasks completed")
) {
console.log(
"[useSpecGeneration] Detected completion in progress message - clearing state"
);
if (phase === 'complete') {
console.log('[useSpecGeneration] Phase is complete - clearing state');
setIsCreating(false);
setIsRegenerating(false);
setCurrentPhase("");
stateRestoredRef.current = false;
setTimeout(() => {
loadSpec();
}, SPEC_FILE_WRITE_DELAY);
}
}
const newLog = logsRef.current + event.content;
logsRef.current = newLog;
setLogs(newLog);
if (
event.content.includes('All tasks completed') ||
event.content.includes('✓ All tasks completed')
) {
console.log(
"[useSpecGeneration] Progress:",
event.content.substring(0, 100)
'[useSpecGeneration] Detected completion in progress message - clearing state'
);
setIsCreating(false);
setIsRegenerating(false);
setCurrentPhase('');
stateRestoredRef.current = false;
setTimeout(() => {
loadSpec();
}, SPEC_FILE_WRITE_DELAY);
}
if (errorMessage) {
setErrorMessage("");
}
} else if (event.type === "spec_regeneration_tool") {
const isFeatureTool =
event.tool === "mcp__automaker-tools__UpdateFeatureStatus" ||
event.tool === "UpdateFeatureStatus" ||
event.tool?.includes("Feature");
const newLog = logsRef.current + event.content;
logsRef.current = newLog;
setLogs(newLog);
console.log('[useSpecGeneration] Progress:', event.content.substring(0, 100));
if (isFeatureTool) {
if (currentPhase !== "feature_generation") {
setCurrentPhase("feature_generation");
setIsCreating(true);
setIsRegenerating(true);
console.log(
"[useSpecGeneration] Detected feature creation tool - setting phase to feature_generation"
);
}
}
if (errorMessage) {
setErrorMessage('');
}
} else if (event.type === 'spec_regeneration_tool') {
const isFeatureTool =
event.tool === 'mcp__automaker-tools__UpdateFeatureStatus' ||
event.tool === 'UpdateFeatureStatus' ||
event.tool?.includes('Feature');
const toolInput = event.input
? ` (${JSON.stringify(event.input).substring(0, 100)}...)`
: "";
const toolLog = `\n[Tool] ${event.tool}${toolInput}\n`;
const newLog = logsRef.current + toolLog;
logsRef.current = newLog;
setLogs(newLog);
console.log("[useSpecGeneration] Tool:", event.tool, event.input);
} else if (event.type === "spec_regeneration_complete") {
const completionLog =
logsRef.current + `\n[Complete] ${event.message}\n`;
logsRef.current = completionLog;
setLogs(completionLog);
const isFinalCompletionMessage =
event.message?.includes("All tasks completed") ||
event.message === "All tasks completed!" ||
event.message === "All tasks completed" ||
event.message === "Spec regeneration complete!" ||
event.message === "Initial spec creation complete!";
const hasCompletePhase =
logsRef.current.includes("[Phase: complete]");
const isIntermediateCompletion =
event.message?.includes("Features are being generated") ||
event.message?.includes("features are being generated");
const shouldComplete =
(isFinalCompletionMessage || hasCompletePhase) &&
!isIntermediateCompletion;
if (shouldComplete) {
console.log(
"[useSpecGeneration] Final completion detected - clearing state",
{
isFinalCompletionMessage,
hasCompletePhase,
message: event.message,
}
);
setIsRegenerating(false);
setIsCreating(false);
setIsGeneratingFeatures(false);
setCurrentPhase("");
setShowRegenerateDialog(false);
setShowCreateDialog(false);
setProjectDefinition("");
setProjectOverview("");
setErrorMessage("");
stateRestoredRef.current = false;
setTimeout(() => {
loadSpec();
}, SPEC_FILE_WRITE_DELAY);
const isRegeneration = event.message?.includes("regeneration");
const isFeatureGeneration =
event.message?.includes("Feature generation");
toast.success(
isFeatureGeneration
? "Feature Generation Complete"
: isRegeneration
? "Spec Regeneration Complete"
: "Spec Creation Complete",
{
description: isFeatureGeneration
? "Features have been created from the app specification."
: "Your app specification has been saved.",
icon: createElement(CheckCircle2, { className: "w-4 h-4" }),
}
);
} else if (isIntermediateCompletion) {
if (isFeatureTool) {
if (currentPhase !== 'feature_generation') {
setCurrentPhase('feature_generation');
setIsCreating(true);
setIsRegenerating(true);
setCurrentPhase("feature_generation");
console.log(
"[useSpecGeneration] Intermediate completion, continuing with feature generation"
'[useSpecGeneration] Detected feature creation tool - setting phase to feature_generation'
);
}
}
console.log(
"[useSpecGeneration] Spec generation event:",
event.message
);
} else if (event.type === "spec_regeneration_error") {
const toolInput = event.input
? ` (${JSON.stringify(event.input).substring(0, 100)}...)`
: '';
const toolLog = `\n[Tool] ${event.tool}${toolInput}\n`;
const newLog = logsRef.current + toolLog;
logsRef.current = newLog;
setLogs(newLog);
console.log('[useSpecGeneration] Tool:', event.tool, event.input);
} else if (event.type === 'spec_regeneration_complete') {
const completionLog = logsRef.current + `\n[Complete] ${event.message}\n`;
logsRef.current = completionLog;
setLogs(completionLog);
const isFinalCompletionMessage =
event.message?.includes('All tasks completed') ||
event.message === 'All tasks completed!' ||
event.message === 'All tasks completed' ||
event.message === 'Spec regeneration complete!' ||
event.message === 'Initial spec creation complete!';
const hasCompletePhase = logsRef.current.includes('[Phase: complete]');
const isIntermediateCompletion =
event.message?.includes('Features are being generated') ||
event.message?.includes('features are being generated');
const shouldComplete =
(isFinalCompletionMessage || hasCompletePhase) && !isIntermediateCompletion;
if (shouldComplete) {
console.log('[useSpecGeneration] Final completion detected - clearing state', {
isFinalCompletionMessage,
hasCompletePhase,
message: event.message,
});
setIsRegenerating(false);
setIsCreating(false);
setIsGeneratingFeatures(false);
setCurrentPhase("error");
setErrorMessage(event.error);
setCurrentPhase('');
setShowRegenerateDialog(false);
setShowCreateDialog(false);
setProjectDefinition('');
setProjectOverview('');
setErrorMessage('');
stateRestoredRef.current = false;
const errorLog = logsRef.current + `\n\n[ERROR] ${event.error}\n`;
logsRef.current = errorLog;
setLogs(errorLog);
console.error("[useSpecGeneration] Regeneration error:", event.error);
setTimeout(() => {
loadSpec();
}, SPEC_FILE_WRITE_DELAY);
const isRegeneration = event.message?.includes('regeneration');
const isFeatureGeneration = event.message?.includes('Feature generation');
toast.success(
isFeatureGeneration
? 'Feature Generation Complete'
: isRegeneration
? 'Spec Regeneration Complete'
: 'Spec Creation Complete',
{
description: isFeatureGeneration
? 'Features have been created from the app specification.'
: 'Your app specification has been saved.',
icon: createElement(CheckCircle2, { className: 'w-4 h-4' }),
}
);
} else if (isIntermediateCompletion) {
setIsCreating(true);
setIsRegenerating(true);
setCurrentPhase('feature_generation');
console.log(
'[useSpecGeneration] Intermediate completion, continuing with feature generation'
);
}
console.log('[useSpecGeneration] Spec generation event:', event.message);
} else if (event.type === 'spec_regeneration_error') {
setIsRegenerating(false);
setIsCreating(false);
setIsGeneratingFeatures(false);
setCurrentPhase('error');
setErrorMessage(event.error);
stateRestoredRef.current = false;
const errorLog = logsRef.current + `\n\n[ERROR] ${event.error}\n`;
logsRef.current = errorLog;
setLogs(errorLog);
console.error('[useSpecGeneration] Regeneration error:', event.error);
}
);
});
return () => {
unsubscribe();
@@ -453,18 +398,15 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
setIsCreating(true);
setShowCreateDialog(false);
setCurrentPhase("initialization");
setErrorMessage("");
logsRef.current = "";
setLogs("");
console.log(
"[useSpecGeneration] Starting spec creation, generateFeatures:",
generateFeatures
);
setCurrentPhase('initialization');
setErrorMessage('');
logsRef.current = '';
setLogs('');
console.log('[useSpecGeneration] Starting spec creation, generateFeatures:', generateFeatures);
try {
const api = getElectronAPI();
if (!api.specRegeneration) {
console.error("[useSpecGeneration] Spec regeneration not available");
console.error('[useSpecGeneration] Spec regeneration not available');
setIsCreating(false);
return;
}
@@ -477,13 +419,10 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
);
if (!result.success) {
const errorMsg = result.error || "Unknown error";
console.error(
"[useSpecGeneration] Failed to start spec creation:",
errorMsg
);
const errorMsg = result.error || 'Unknown error';
console.error('[useSpecGeneration] Failed to start spec creation:', errorMsg);
setIsCreating(false);
setCurrentPhase("error");
setCurrentPhase('error');
setErrorMessage(errorMsg);
const errorLog = `[Error] Failed to start spec creation: ${errorMsg}\n`;
logsRef.current = errorLog;
@@ -491,9 +430,9 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.error("[useSpecGeneration] Failed to create spec:", errorMsg);
console.error('[useSpecGeneration] Failed to create spec:', errorMsg);
setIsCreating(false);
setCurrentPhase("error");
setCurrentPhase('error');
setErrorMessage(errorMsg);
const errorLog = `[Error] Failed to create spec: ${errorMsg}\n`;
logsRef.current = errorLog;
@@ -512,18 +451,18 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
setIsRegenerating(true);
setShowRegenerateDialog(false);
setCurrentPhase("initialization");
setErrorMessage("");
logsRef.current = "";
setLogs("");
setCurrentPhase('initialization');
setErrorMessage('');
logsRef.current = '';
setLogs('');
console.log(
"[useSpecGeneration] Starting spec regeneration, generateFeatures:",
'[useSpecGeneration] Starting spec regeneration, generateFeatures:',
generateFeaturesOnRegenerate
);
try {
const api = getElectronAPI();
if (!api.specRegeneration) {
console.error("[useSpecGeneration] Spec regeneration not available");
console.error('[useSpecGeneration] Spec regeneration not available');
setIsRegenerating(false);
return;
}
@@ -536,13 +475,10 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
);
if (!result.success) {
const errorMsg = result.error || "Unknown error";
console.error(
"[useSpecGeneration] Failed to start regeneration:",
errorMsg
);
const errorMsg = result.error || 'Unknown error';
console.error('[useSpecGeneration] Failed to start regeneration:', errorMsg);
setIsRegenerating(false);
setCurrentPhase("error");
setCurrentPhase('error');
setErrorMessage(errorMsg);
const errorLog = `[Error] Failed to start regeneration: ${errorMsg}\n`;
logsRef.current = errorLog;
@@ -550,9 +486,9 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.error("[useSpecGeneration] Failed to regenerate spec:", errorMsg);
console.error('[useSpecGeneration] Failed to regenerate spec:', errorMsg);
setIsRegenerating(false);
setCurrentPhase("error");
setCurrentPhase('error');
setErrorMessage(errorMsg);
const errorLog = `[Error] Failed to regenerate spec: ${errorMsg}\n`;
logsRef.current = errorLog;
@@ -571,32 +507,25 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
setIsGeneratingFeatures(true);
setShowRegenerateDialog(false);
setCurrentPhase("initialization");
setErrorMessage("");
logsRef.current = "";
setLogs("");
console.log(
"[useSpecGeneration] Starting feature generation from existing spec"
);
setCurrentPhase('initialization');
setErrorMessage('');
logsRef.current = '';
setLogs('');
console.log('[useSpecGeneration] Starting feature generation from existing spec');
try {
const api = getElectronAPI();
if (!api.specRegeneration) {
console.error("[useSpecGeneration] Spec regeneration not available");
console.error('[useSpecGeneration] Spec regeneration not available');
setIsGeneratingFeatures(false);
return;
}
const result = await api.specRegeneration.generateFeatures(
currentProject.path
);
const result = await api.specRegeneration.generateFeatures(currentProject.path);
if (!result.success) {
const errorMsg = result.error || "Unknown error";
console.error(
"[useSpecGeneration] Failed to start feature generation:",
errorMsg
);
const errorMsg = result.error || 'Unknown error';
console.error('[useSpecGeneration] Failed to start feature generation:', errorMsg);
setIsGeneratingFeatures(false);
setCurrentPhase("error");
setCurrentPhase('error');
setErrorMessage(errorMsg);
const errorLog = `[Error] Failed to start feature generation: ${errorMsg}\n`;
logsRef.current = errorLog;
@@ -604,12 +533,9 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) {
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.error(
"[useSpecGeneration] Failed to generate features:",
errorMsg
);
console.error('[useSpecGeneration] Failed to generate features:', errorMsg);
setIsGeneratingFeatures(false);
setCurrentPhase("error");
setCurrentPhase('error');
setErrorMessage(errorMsg);
const errorLog = `[Error] Failed to generate features: ${errorMsg}\n`;
logsRef.current = errorLog;

View File

@@ -1,6 +1,6 @@
import { useEffect, useState, useCallback } from "react";
import { useAppStore } from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { useEffect, useState, useCallback } from 'react';
import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
export function useSpecLoading() {
const { currentProject, setAppSpec } = useAppStore();
@@ -13,20 +13,18 @@ export function useSpecLoading() {
setIsLoading(true);
try {
const api = getElectronAPI();
const result = await api.readFile(
`${currentProject.path}/.automaker/app_spec.txt`
);
const result = await api.readFile(`${currentProject.path}/.automaker/app_spec.txt`);
if (result.success && result.content) {
setAppSpec(result.content);
setSpecExists(true);
} else {
// File doesn't exist
setAppSpec("");
setAppSpec('');
setSpecExists(false);
}
} catch (error) {
console.error("Failed to load spec:", error);
console.error('Failed to load spec:', error);
setSpecExists(false);
} finally {
setIsLoading(false);

View File

@@ -1,6 +1,6 @@
import { useState } from "react";
import { useAppStore } from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { useState } from 'react';
import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
export function useSpecSave() {
const { currentProject, appSpec, setAppSpec } = useAppStore();
@@ -13,13 +13,10 @@ export function useSpecSave() {
setIsSaving(true);
try {
const api = getElectronAPI();
await api.writeFile(
`${currentProject.path}/.automaker/app_spec.txt`,
appSpec
);
await api.writeFile(`${currentProject.path}/.automaker/app_spec.txt`, appSpec);
setHasChanges(false);
} catch (error) {
console.error("Failed to save spec:", error);
console.error('Failed to save spec:', error);
} finally {
setIsSaving(false);
}

View File

@@ -3,13 +3,13 @@ export type FeatureCount = 20 | 50 | 100;
// Generation phases for UI display
export type GenerationPhase =
| "initialization"
| "setup"
| "analysis"
| "spec_complete"
| "feature_generation"
| "complete"
| "error";
| 'initialization'
| 'setup'
| 'analysis'
| 'spec_complete'
| 'feature_generation'
| 'complete'
| 'error';
// Props for the unified create spec dialog
export interface CreateSpecDialogProps {