Merge branch 'main' into random-fixes

This commit is contained in:
Cody Seibert
2025-12-14 14:19:06 -05:00
20 changed files with 1071 additions and 858 deletions

View File

@@ -345,6 +345,7 @@ export function AnalysisView() {
const techStack = detectTechStack();
// Generate the spec content
// Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts
const specContent = `<project_specification>
<project_name>${projectName}</project_name>

View File

@@ -28,6 +28,7 @@ import {
} from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { cn, modelSupportsThinking } from "@/lib/utils";
import type { SpecRegenerationEvent } from "@/types/electron";
import {
Card,
CardDescription,
@@ -179,6 +180,8 @@ export function BoardView() {
kanbanCardDetailLevel,
setKanbanCardDetailLevel,
boardBackgroundByProject,
specCreatingForProject,
setSpecCreatingForProject,
} = useAppStore();
const shortcuts = useKeyboardShortcutsConfig();
const [activeFeature, setActiveFeature] = useState<Feature | null>(null);
@@ -233,6 +236,9 @@ export function BoardView() {
const [searchQuery, setSearchQuery] = useState("");
// Validation state for add feature form
const [descriptionError, setDescriptionError] = useState(false);
// Derive spec creation state from store - check if current project is the one being created
const isCreatingSpec = specCreatingForProject === currentProject?.path;
const creatingSpecProjectPath = specCreatingForProject;
// Make current project available globally for modal
useEffect(() => {
@@ -264,6 +270,37 @@ export function BoardView() {
};
}, []);
// Subscribe to spec regeneration events to clear state on completion
useEffect(() => {
const api = getElectronAPI();
if (!api.specRegeneration) return;
const unsubscribe = api.specRegeneration.onEvent((event) => {
console.log(
"[BoardView] Spec regeneration event:",
event.type,
"for project:",
event.projectPath
);
// Only handle completion/error events for the project being created
// The creating state is set by sidebar when user initiates the action
if (event.projectPath !== specCreatingForProject) {
return;
}
if (event.type === "spec_regeneration_complete") {
setSpecCreatingForProject(null);
} else if (event.type === "spec_regeneration_error") {
setSpecCreatingForProject(null);
}
});
return () => {
unsubscribe();
};
}, [specCreatingForProject, setSpecCreatingForProject]);
// Track previous project to detect switches
const prevProjectPathRef = useRef<string | null>(null);
const isSwitchingProjectRef = useRef<boolean>(false);
@@ -1791,34 +1828,50 @@ export function BoardView() {
<div className="flex-1 flex flex-col overflow-hidden">
{/* Search Bar Row */}
<div className="px-4 pt-4 pb-2 flex items-center justify-between">
<div className="relative max-w-md flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" />
<Input
ref={searchInputRef}
type="text"
placeholder="Search features by keyword..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 pr-12 border-border"
data-testid="kanban-search-input"
/>
{searchQuery ? (
<button
onClick={() => setSearchQuery("")}
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded-sm hover:bg-accent text-muted-foreground hover:text-foreground transition-colors"
data-testid="kanban-search-clear"
aria-label="Clear search"
>
<X className="w-4 h-4" />
</button>
) : (
<span
className="absolute right-2 top-1/2 -translate-y-1/2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-brand-500/10 border border-brand-500/30 text-brand-400/70"
data-testid="kanban-search-hotkey"
>
/
</span>
)}
<div className="relative max-w-md flex-1 flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" />
<Input
ref={searchInputRef}
type="text"
placeholder="Search features by keyword..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 pr-12 border-border"
data-testid="kanban-search-input"
/>
{searchQuery ? (
<button
onClick={() => setSearchQuery("")}
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded-sm hover:bg-accent text-muted-foreground hover:text-foreground transition-colors"
data-testid="kanban-search-clear"
aria-label="Clear search"
>
<X className="w-4 h-4" />
</button>
) : (
<span
className="absolute right-2 top-1/2 -translate-y-1/2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-brand-500/10 border border-brand-500/30 text-brand-400/70"
data-testid="kanban-search-hotkey"
>
/
</span>
)}
</div>
{/* Spec Creation Loading Badge */}
{isCreatingSpec &&
currentProject?.path === creatingSpecProjectPath && (
<div
className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-brand-500/10 border border-brand-500/20 shrink-0"
title="Creating App Specification"
data-testid="spec-creation-badge"
>
<Loader2 className="w-3 h-3 animate-spin text-brand-500 shrink-0" />
<span className="text-xs font-medium text-brand-500 whitespace-nowrap">
Creating spec
</span>
</div>
)}
</div>
{/* Board Background & Detail Level Controls */}

View File

@@ -248,6 +248,7 @@ export function InterviewView() {
.toLowerCase()
.replace(/[^a-z0-9-]/g, "");
// Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts
return `<project_specification>
<project_name>${projectName || "my-project"}</project_name>

View File

@@ -279,7 +279,18 @@ export function SpecView() {
const unsubscribe = api.specRegeneration.onEvent(
(event: SpecRegenerationEvent) => {
console.log("[SpecView] Regeneration event:", event.type);
console.log(
"[SpecView] Regeneration event:",
event.type,
"for project:",
event.projectPath
);
// Only handle events for the current project
if (event.projectPath !== currentProject?.path) {
console.log("[SpecView] Ignoring event - not for current project");
return;
}
if (event.type === "spec_regeneration_progress") {
// Extract phase from content if present

View File

@@ -255,6 +255,7 @@ export function WelcomeView() {
}
// Update the app_spec.txt with the project name
// Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts
await api.writeFile(
`${projectPath}/.automaker/app_spec.txt`,
`<project_specification>
@@ -352,6 +353,7 @@ export function WelcomeView() {
}
// Update the app_spec.txt with template-specific info
// Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts
await api.writeFile(
`${projectPath}/.automaker/app_spec.txt`,
`<project_specification>
@@ -456,6 +458,7 @@ export function WelcomeView() {
}
// Update the app_spec.txt with basic info
// Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts
await api.writeFile(
`${projectPath}/.automaker/app_spec.txt`,
`<project_specification>