Merge pull request #7 from AutoMaker-Org/various-improvements

Various improvements
This commit is contained in:
Web Dev Cody
2025-12-10 14:40:53 -05:00
committed by GitHub
27 changed files with 2988 additions and 566 deletions

View File

@@ -593,13 +593,16 @@ export function AgentView() {
<Card
className={cn(
"max-w-[80%]",
message.role === "user" &&
"bg-primary text-primary-foreground"
message.role === "user"
? "bg-primary text-primary-foreground"
: "border-l-4 border-primary bg-card"
)}
>
<CardContent className="p-3">
{message.role === "assistant" ? (
<Markdown className="text-sm">{message.content}</Markdown>
<Markdown className="text-sm text-primary prose-headings:text-primary prose-strong:text-primary prose-code:text-primary">
{message.content}
</Markdown>
) : (
<p className="text-sm whitespace-pre-wrap">
{message.content}
@@ -610,7 +613,7 @@ export function AgentView() {
"text-xs mt-2",
message.role === "user"
? "text-primary-foreground/70"
: "text-muted-foreground"
: "text-primary/70"
)}
>
{new Date(message.timestamp).toLocaleTimeString()}
@@ -625,11 +628,11 @@ export function AgentView() {
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center">
<Bot className="w-4 h-4 text-primary" />
</div>
<Card>
<Card className="border-l-4 border-primary bg-card">
<CardContent className="p-3">
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
<span className="text-sm text-muted-foreground">
<Loader2 className="w-4 h-4 animate-spin text-primary" />
<span className="text-sm text-primary">
Thinking...
</span>
</div>
@@ -642,11 +645,12 @@ export function AgentView() {
{/* Input */}
{currentSessionId && (
<div className="border-t p-4 space-y-3">
<div className="border-t border-border p-4 space-y-3 bg-background">
{/* Image Drop Zone (when visible) */}
{showImageDropZone && (
<ImageDropZone
onImagesSelected={handleImagesSelected}
images={selectedImages}
maxFiles={5}
className="mb-3"
disabled={isProcessing || !isConnected}
@@ -658,7 +662,7 @@ export function AgentView() {
className={cn(
"flex gap-2 transition-all duration-200 rounded-lg",
isDragOver &&
"bg-blue-50 dark:bg-blue-950/20 ring-2 ring-blue-400 ring-offset-2"
"bg-primary/10 ring-2 ring-primary ring-offset-2 ring-offset-background"
)}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
@@ -680,20 +684,21 @@ export function AgentView() {
disabled={isProcessing || !isConnected}
data-testid="agent-input"
className={cn(
"bg-input border-border",
selectedImages.length > 0 &&
"border-blue-200 bg-blue-50/50 dark:bg-blue-950/20",
"border-primary/50 bg-primary/5",
isDragOver &&
"border-blue-400 bg-blue-50/50 dark:bg-blue-950/20"
"border-primary bg-primary/10"
)}
/>
{selectedImages.length > 0 && !isDragOver && (
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 text-xs text-blue-600 bg-blue-100 dark:bg-blue-900 px-2 py-1 rounded">
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 text-xs text-primary-foreground bg-primary px-2 py-1 rounded">
{selectedImages.length} image
{selectedImages.length > 1 ? "s" : ""}
</div>
)}
{isDragOver && (
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 text-xs text-blue-600 bg-blue-100 dark:bg-blue-900 px-2 py-1 rounded flex items-center gap-1">
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 text-xs text-primary-foreground bg-primary px-2 py-1 rounded flex items-center gap-1">
<Paperclip className="w-3 h-3" />
Drop here
</div>
@@ -708,8 +713,8 @@ export function AgentView() {
disabled={isProcessing || !isConnected}
className={cn(
showImageDropZone &&
"bg-blue-100 text-blue-600 dark:bg-blue-900/50 dark:text-blue-400",
selectedImages.length > 0 && "border-blue-400"
"bg-primary/20 text-primary border-primary",
selectedImages.length > 0 && "border-primary"
)}
title="Attach images"
>

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback, useMemo } from "react";
import { useAppStore } from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { Button } from "@/components/ui/button";
import { HotkeyButton } from "@/components/ui/hotkey-button";
import { Card } from "@/components/ui/card";
import {
Plus,
@@ -364,20 +365,16 @@ export function ContextView() {
</div>
</div>
<div className="flex gap-2">
<Button
<HotkeyButton
size="sm"
onClick={() => setIsAddDialogOpen(true)}
hotkey={shortcuts.addContextFile}
hotkeyActive={false}
data-testid="add-context-file"
>
<Plus className="w-4 h-4 mr-2" />
Add File
<span
className="ml-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-secondary border border-border"
data-testid="shortcut-add-context-file"
>
{shortcuts.addContextFile}
</span>
</Button>
</HotkeyButton>
</div>
</div>
@@ -505,7 +502,9 @@ export function ContextView() {
<div className="flex-1 flex items-center justify-center">
<div className="text-center">
<File className="w-12 h-12 text-muted-foreground mx-auto mb-3" />
<p className="text-foreground-secondary">Select a file to view or edit</p>
<p className="text-foreground-secondary">
Select a file to view or edit
</p>
<p className="text-muted-foreground text-sm mt-1">
Or drop files here to add them
</p>
@@ -651,16 +650,18 @@ export function ContextView() {
>
Cancel
</Button>
<Button
<HotkeyButton
onClick={handleAddFile}
disabled={
!newFileName.trim() ||
(newFileType === "image" && !uploadedImageData)
}
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkeyActive={isAddDialogOpen}
data-testid="confirm-add-file"
>
Add File
</Button>
</HotkeyButton>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -10,6 +10,7 @@ import {
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 { Label } from "@/components/ui/label";
import {
@@ -426,9 +427,11 @@ export function FeatureSuggestionsDialog({
<Button variant="ghost" onClick={onClose}>
Cancel
</Button>
<Button
<HotkeyButton
onClick={handleImport}
disabled={selectedIds.size === 0 || isImporting}
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkeyActive={open && hasSuggestions}
>
{isImporting ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
@@ -437,7 +440,7 @@ export function FeatureSuggestionsDialog({
)}
Import {selectedIds.size} Feature
{selectedIds.size !== 1 ? "s" : ""}
</Button>
</HotkeyButton>
</div>
</div>
)}

View File

@@ -431,17 +431,22 @@ export function InterviewView() {
<Card
className={cn(
"max-w-[80%]",
message.role === "user" && "bg-primary text-primary-foreground"
message.role === "user"
? "bg-primary text-primary-foreground"
: "border-l-4 border-primary bg-card"
)}
>
<CardContent className="p-3">
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
<p className={cn(
"text-sm whitespace-pre-wrap",
message.role === "assistant" && "text-primary"
)}>{message.content}</p>
<p
className={cn(
"text-xs mt-2",
message.role === "user"
? "text-primary-foreground/70"
: "text-muted-foreground"
: "text-primary/70"
)}
>
{message.timestamp.toLocaleTimeString()}
@@ -456,11 +461,11 @@ export function InterviewView() {
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center">
<Bot className="w-4 h-4 text-primary" />
</div>
<Card>
<Card className="border-l-4 border-primary bg-card">
<CardContent className="p-3">
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
<span className="text-sm text-muted-foreground">
<Loader2 className="w-4 h-4 animate-spin text-primary" />
<span className="text-sm text-primary">
Generating specification...
</span>
</div>

View File

@@ -188,9 +188,12 @@ export const KanbanCard = memo(function KanbanCard({
// Dragging logic:
// - Backlog items can always be dragged
// - skipTests items can be dragged even when in_progress or verified (unless currently running)
// - waiting_approval items can always be dragged (to allow manual verification via drag)
// - Non-skipTests (TDD) items in progress or verified cannot be dragged
const isDraggable =
feature.status === "backlog" || (feature.skipTests && !isCurrentAutoTask);
feature.status === "backlog" ||
feature.status === "waiting_approval" ||
(feature.skipTests && !isCurrentAutoTask);
const {
attributes,
listeners,
@@ -336,7 +339,7 @@ export const KanbanCard = memo(function KanbanCard({
<Edit className="w-3 h-3 mr-2" />
Edit
</DropdownMenuItem>
{onViewOutput && (
{onViewOutput && feature.status !== "backlog" && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
@@ -737,25 +740,6 @@ export const KanbanCard = memo(function KanbanCard({
)}
</>
)}
{!isCurrentAutoTask && feature.status === "backlog" && (
<>
{onViewOutput && (
<Button
variant="ghost"
size="sm"
className="h-7 text-xs"
onClick={(e) => {
e.stopPropagation();
onViewOutput();
}}
data-testid={`view-logs-backlog-${feature.id}`}
>
<FileText className="w-3 h-3 mr-1" />
Logs
</Button>
)}
</>
)}
</div>
</CardContent>

View File

@@ -1,8 +1,15 @@
"use client";
import { useState, useMemo, useCallback, useEffect } from "react";
import { useAppStore, AIProfile, AgentModel, ThinkingLevel, ModelProvider } from "@/store/app-store";
import {
useAppStore,
AIProfile,
AgentModel,
ThinkingLevel,
ModelProvider,
} from "@/store/app-store";
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 { Textarea } from "@/components/ui/textarea";
@@ -52,7 +59,10 @@ import {
import { CSS } from "@dnd-kit/utilities";
// Icon mapping for profiles
const PROFILE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
const PROFILE_ICONS: Record<
string,
React.ComponentType<{ className?: string }>
> = {
Brain,
Zap,
Scale,
@@ -236,11 +246,13 @@ function ProfileForm({
onSave,
onCancel,
isEditing,
hotkeyActive,
}: {
profile: Partial<AIProfile>;
onSave: (profile: Omit<AIProfile, "id">) => void;
onCancel: () => void;
isEditing: boolean;
hotkeyActive: boolean;
}) {
const [formData, setFormData] = useState({
name: profile.name || "",
@@ -429,17 +441,27 @@ function ProfileForm({
<Button variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button onClick={handleSubmit} data-testid="save-profile-button">
<HotkeyButton
onClick={handleSubmit}
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkeyActive={hotkeyActive}
data-testid="save-profile-button"
>
{isEditing ? "Save Changes" : "Create Profile"}
</Button>
</HotkeyButton>
</DialogFooter>
</div>
);
}
export function ProfilesView() {
const { aiProfiles, addAIProfile, updateAIProfile, removeAIProfile, reorderAIProfiles } =
useAppStore();
const {
aiProfiles,
addAIProfile,
updateAIProfile,
removeAIProfile,
reorderAIProfiles,
} = useAppStore();
const shortcuts = useKeyboardShortcutsConfig();
const [showAddDialog, setShowAddDialog] = useState(false);
@@ -546,13 +568,15 @@ export function ProfilesView() {
</p>
</div>
</div>
<Button onClick={() => setShowAddDialog(true)} data-testid="add-profile-button" className="relative">
<HotkeyButton
onClick={() => setShowAddDialog(true)}
hotkey={shortcuts.addProfile}
hotkeyActive={false}
data-testid="add-profile-button"
>
<Plus className="w-4 h-4 mr-2" />
New Profile
<span className="hidden lg:flex items-center justify-center ml-2 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/20 border border-primary-foreground/30 text-primary-foreground">
{shortcuts.addProfile}
</span>
</Button>
</HotkeyButton>
</div>
</div>
</div>
@@ -663,6 +687,7 @@ export function ProfilesView() {
onSave={handleAddProfile}
onCancel={() => setShowAddDialog(false)}
isEditing={false}
hotkeyActive={showAddDialog}
/>
</DialogContent>
</Dialog>
@@ -683,6 +708,7 @@ export function ProfilesView() {
onSave={handleUpdateProfile}
onCancel={() => setEditingProfile(null)}
isEditing={true}
hotkeyActive={!!editingProfile}
/>
)}
</DialogContent>

View File

@@ -73,6 +73,7 @@ export function SettingsView() {
setCurrentView,
theme,
setTheme,
setProjectTheme,
kanbanCardDetailLevel,
setKanbanCardDetailLevel,
defaultSkipTests,
@@ -87,6 +88,18 @@ export function SettingsView() {
setKeyboardShortcut,
resetKeyboardShortcuts,
} = useAppStore();
// Compute the effective theme for the current project
const effectiveTheme = currentProject?.theme || theme;
// Handler to set theme - saves to project if one is selected, otherwise to global
const handleSetTheme = (newTheme: typeof theme) => {
if (currentProject) {
setProjectTheme(currentProject.id, newTheme);
} else {
setTheme(newTheme);
}
};
const [anthropicKey, setAnthropicKey] = useState(apiKeys.anthropic);
const [googleKey, setGoogleKey] = useState(apiKeys.google);
const [openaiKey, setOpenaiKey] = useState(apiKeys.openai);
@@ -204,13 +217,28 @@ export function SettingsView() {
if (!container) return;
const handleScroll = () => {
const sections = NAV_ITEMS.map((item) => ({
id: item.id,
element: document.getElementById(item.id),
})).filter((s) => s.element);
const sections = NAV_ITEMS.filter(
(item) => item.id !== "danger" || currentProject
)
.map((item) => ({
id: item.id,
element: document.getElementById(item.id),
}))
.filter((s) => s.element);
const containerRect = container.getBoundingClientRect();
const scrollTop = container.scrollTop;
const scrollHeight = container.scrollHeight;
const clientHeight = container.clientHeight;
// Check if scrolled to bottom (within a small threshold)
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
if (isAtBottom && sections.length > 0) {
// If at bottom, highlight the last visible section
setActiveSection(sections[sections.length - 1].id);
return;
}
for (let i = sections.length - 1; i >= 0; i--) {
const section = sections[i];
@@ -227,7 +255,7 @@ export function SettingsView() {
container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}, []);
}, [currentProject]);
const scrollToSection = useCallback((sectionId: string) => {
const element = document.getElementById(sectionId);
@@ -470,7 +498,7 @@ export function SettingsView() {
{/* Scrollable Content */}
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto p-8">
<div className="max-w-4xl mx-auto space-y-6">
<div className="max-w-4xl mx-auto space-y-6 pb-96">
{/* API Keys Section */}
<div
id="api-keys"
@@ -1277,13 +1305,20 @@ export function SettingsView() {
</div>
<div className="p-6 space-y-4">
<div className="space-y-3">
<Label className="text-foreground">Theme</Label>
<Label className="text-foreground">
Theme{" "}
{currentProject
? `(for ${currentProject.name})`
: "(Global)"}
</Label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<Button
variant={theme === "dark" ? "secondary" : "outline"}
onClick={() => setTheme("dark")}
variant={
effectiveTheme === "dark" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("dark")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "dark"
effectiveTheme === "dark"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1293,10 +1328,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Dark</span>
</Button>
<Button
variant={theme === "light" ? "secondary" : "outline"}
onClick={() => setTheme("light")}
variant={
effectiveTheme === "light" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("light")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "light"
effectiveTheme === "light"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1306,10 +1343,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Light</span>
</Button>
<Button
variant={theme === "retro" ? "secondary" : "outline"}
onClick={() => setTheme("retro")}
variant={
effectiveTheme === "retro" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("retro")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "retro"
effectiveTheme === "retro"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1319,10 +1358,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Retro</span>
</Button>
<Button
variant={theme === "dracula" ? "secondary" : "outline"}
onClick={() => setTheme("dracula")}
variant={
effectiveTheme === "dracula" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("dracula")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "dracula"
effectiveTheme === "dracula"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1332,10 +1373,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Dracula</span>
</Button>
<Button
variant={theme === "nord" ? "secondary" : "outline"}
onClick={() => setTheme("nord")}
variant={
effectiveTheme === "nord" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("nord")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "nord"
effectiveTheme === "nord"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1345,10 +1388,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Nord</span>
</Button>
<Button
variant={theme === "monokai" ? "secondary" : "outline"}
onClick={() => setTheme("monokai")}
variant={
effectiveTheme === "monokai" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("monokai")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "monokai"
effectiveTheme === "monokai"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1358,10 +1403,14 @@ export function SettingsView() {
<span className="font-medium text-sm">Monokai</span>
</Button>
<Button
variant={theme === "tokyonight" ? "secondary" : "outline"}
onClick={() => setTheme("tokyonight")}
variant={
effectiveTheme === "tokyonight"
? "secondary"
: "outline"
}
onClick={() => handleSetTheme("tokyonight")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "tokyonight"
effectiveTheme === "tokyonight"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1371,10 +1420,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Tokyo Night</span>
</Button>
<Button
variant={theme === "solarized" ? "secondary" : "outline"}
onClick={() => setTheme("solarized")}
variant={
effectiveTheme === "solarized" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("solarized")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "solarized"
effectiveTheme === "solarized"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1384,10 +1435,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Solarized</span>
</Button>
<Button
variant={theme === "gruvbox" ? "secondary" : "outline"}
onClick={() => setTheme("gruvbox")}
variant={
effectiveTheme === "gruvbox" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("gruvbox")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "gruvbox"
effectiveTheme === "gruvbox"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1397,10 +1450,14 @@ export function SettingsView() {
<span className="font-medium text-sm">Gruvbox</span>
</Button>
<Button
variant={theme === "catppuccin" ? "secondary" : "outline"}
onClick={() => setTheme("catppuccin")}
variant={
effectiveTheme === "catppuccin"
? "secondary"
: "outline"
}
onClick={() => handleSetTheme("catppuccin")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "catppuccin"
effectiveTheme === "catppuccin"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1410,10 +1467,12 @@ export function SettingsView() {
<span className="font-medium text-sm">Catppuccin</span>
</Button>
<Button
variant={theme === "onedark" ? "secondary" : "outline"}
onClick={() => setTheme("onedark")}
variant={
effectiveTheme === "onedark" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("onedark")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "onedark"
effectiveTheme === "onedark"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1423,10 +1482,12 @@ export function SettingsView() {
<span className="font-medium text-sm">One Dark</span>
</Button>
<Button
variant={theme === "synthwave" ? "secondary" : "outline"}
onClick={() => setTheme("synthwave")}
variant={
effectiveTheme === "synthwave" ? "secondary" : "outline"
}
onClick={() => handleSetTheme("synthwave")}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
theme === "synthwave"
effectiveTheme === "synthwave"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
@@ -1959,10 +2020,11 @@ export function SettingsView() {
Show profiles only by default
</Label>
<p className="text-xs text-muted-foreground">
When enabled, the Add Feature dialog will show only AI profiles
and hide advanced model tweaking options (Claude SDK, thinking levels,
and OpenAI Codex CLI). This creates a cleaner, less overwhelming UI.
You can always disable this to access advanced settings.
When enabled, the Add Feature dialog will show only AI
profiles and hide advanced model tweaking options
(Claude SDK, thinking levels, and OpenAI Codex CLI).
This creates a cleaner, less overwhelming UI. You can
always disable this to access advanced settings.
</p>
</div>
</div>

View File

@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback } from "react";
import { useAppStore } from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { Button } from "@/components/ui/button";
import { HotkeyButton } from "@/components/ui/hotkey-button";
import { Card } from "@/components/ui/card";
import {
Dialog,
@@ -15,6 +16,7 @@ import {
} from "@/components/ui/dialog";
import { Save, RefreshCw, FileText, Sparkles, Loader2, FilePlus2 } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { XmlSyntaxEditor } from "@/components/ui/xml-syntax-editor";
import type { SpecRegenerationEvent } from "@/types/electron";
export function SpecView() {
@@ -299,13 +301,15 @@ export function SpecView() {
>
Cancel
</Button>
<Button
<HotkeyButton
onClick={handleCreateSpec}
disabled={!projectOverview.trim()}
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkeyActive={showCreateDialog}
>
<Sparkles className="w-4 h-4 mr-2" />
Generate Spec
</Button>
</HotkeyButton>
</DialogFooter>
</DialogContent>
</Dialog>
@@ -359,12 +363,10 @@ export function SpecView() {
{/* Editor */}
<div className="flex-1 p-4 overflow-hidden">
<Card className="h-full overflow-hidden">
<textarea
className="w-full h-full p-4 font-mono text-sm bg-transparent resize-none focus:outline-none"
<XmlSyntaxEditor
value={appSpec}
onChange={(e) => handleChange(e.target.value)}
onChange={handleChange}
placeholder="Write your app specification here..."
spellCheck={false}
data-testid="spec-editor"
/>
</Card>
@@ -409,9 +411,11 @@ export function SpecView() {
>
Cancel
</Button>
<Button
<HotkeyButton
onClick={handleRegenerate}
disabled={!projectDefinition.trim() || isRegenerating}
hotkey={{ key: "Enter", cmdCtrl: true }}
hotkeyActive={showRegenerateDialog}
>
{isRegenerating ? (
<>
@@ -424,7 +428,7 @@ export function SpecView() {
Regenerate Spec
</>
)}
</Button>
</HotkeyButton>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -2,6 +2,7 @@
import { useState, useCallback } from "react";
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 {
@@ -512,14 +513,16 @@ export function WelcomeView() {
>
Cancel
</Button>
<Button
<HotkeyButton
onClick={handleCreateProject}
disabled={!newProjectName || !newProjectPath || 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 }}
hotkeyActive={showNewProjectDialog}
data-testid="confirm-create-project"
>
{isCreating ? "Creating..." : "Create Project"}
</Button>
</HotkeyButton>
</DialogFooter>
</DialogContent>
</Dialog>