Skip to content

Commit f04cac8

Browse files
committed
style: refine sidebar and dropdown menu components for improved UI
- Simplified the sidebar button's class structure by removing unnecessary overflow styling. - Enhanced the visual representation of the trashed projects count with updated styling for better visibility. - Wrapped the dropdown menu's subcontent in a portal for improved rendering and performance.
1 parent 25b1789 commit f04cac8

13 files changed

Lines changed: 148 additions & 46 deletions

File tree

.DS_Store

-10 KB
Binary file not shown.

apps/app/src/components/layout/project-setup-dialog.tsx

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { Sparkles } from "lucide-react";
3+
import { Sparkles, Clock } from "lucide-react";
44
import {
55
Dialog,
66
DialogContent,
@@ -11,6 +11,19 @@ import {
1111
} from "@/components/ui/dialog";
1212
import { Button } from "@/components/ui/button";
1313
import { Checkbox } from "@/components/ui/checkbox";
14+
import { cn } from "@/lib/utils";
15+
16+
// Feature count options
17+
export type FeatureCount = 20 | 50 | 100;
18+
const FEATURE_COUNT_OPTIONS: {
19+
value: FeatureCount;
20+
label: string;
21+
warning?: string;
22+
}[] = [
23+
{ value: 20, label: "20" },
24+
{ value: 50, label: "50", warning: "May take up to 5 minutes" },
25+
{ value: 100, label: "100", warning: "May take up to 5 minutes" },
26+
];
1427

1528
interface ProjectSetupDialogProps {
1629
open: boolean;
@@ -19,6 +32,8 @@ interface ProjectSetupDialogProps {
1932
onProjectOverviewChange: (value: string) => void;
2033
generateFeatures: boolean;
2134
onGenerateFeaturesChange: (value: boolean) => void;
35+
featureCount: FeatureCount;
36+
onFeatureCountChange: (value: FeatureCount) => void;
2237
onCreateSpec: () => void;
2338
onSkip: () => void;
2439
isCreatingSpec: boolean;
@@ -31,6 +46,8 @@ export function ProjectSetupDialog({
3146
onProjectOverviewChange,
3247
generateFeatures,
3348
onGenerateFeaturesChange,
49+
featureCount,
50+
onFeatureCountChange,
3451
onCreateSpec,
3552
onSkip,
3653
isCreatingSpec,
@@ -94,16 +111,52 @@ export function ProjectSetupDialog({
94111
</p>
95112
</div>
96113
</div>
114+
115+
{/* Feature Count Selection - only shown when generateFeatures is enabled */}
116+
{generateFeatures && (
117+
<div className="space-y-2 pt-2 pl-7">
118+
<label className="text-sm font-medium">Number of Features</label>
119+
<div className="flex gap-2">
120+
{FEATURE_COUNT_OPTIONS.map((option) => (
121+
<Button
122+
key={option.value}
123+
type="button"
124+
variant={
125+
featureCount === option.value ? "default" : "outline"
126+
}
127+
size="sm"
128+
onClick={() => onFeatureCountChange(option.value)}
129+
className={cn(
130+
"flex-1 transition-all",
131+
featureCount === option.value
132+
? "bg-primary hover:bg-primary/90 text-primary-foreground"
133+
: "bg-muted/30 hover:bg-muted/50 border-border"
134+
)}
135+
data-testid={`feature-count-${option.value}`}
136+
>
137+
{option.label}
138+
</Button>
139+
))}
140+
</div>
141+
{FEATURE_COUNT_OPTIONS.find((o) => o.value === featureCount)
142+
?.warning && (
143+
<p className="text-xs text-amber-500 flex items-center gap-1">
144+
<Clock className="w-3 h-3" />
145+
{
146+
FEATURE_COUNT_OPTIONS.find((o) => o.value === featureCount)
147+
?.warning
148+
}
149+
</p>
150+
)}
151+
</div>
152+
)}
97153
</div>
98154

99155
<DialogFooter>
100156
<Button variant="ghost" onClick={onSkip}>
101157
Skip for now
102158
</Button>
103-
<Button
104-
onClick={onCreateSpec}
105-
disabled={!projectOverview.trim()}
106-
>
159+
<Button onClick={onCreateSpec} disabled={!projectOverview.trim()}>
107160
<Sparkles className="w-4 h-4 mr-2" />
108161
Generate Spec
109162
</Button>
@@ -112,4 +165,3 @@ export function ProjectSetupDialog({
112165
</Dialog>
113166
);
114167
}
115-

apps/app/src/components/layout/sidebar.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ import { themeOptions } from "@/config/theme-options";
8282
import type { SpecRegenerationEvent } from "@/types/electron";
8383
import { DeleteProjectDialog } from "@/components/views/settings-view/components/delete-project-dialog";
8484
import { NewProjectModal } from "@/components/new-project-modal";
85-
import { ProjectSetupDialog } from "@/components/layout/project-setup-dialog";
85+
import {
86+
ProjectSetupDialog,
87+
type FeatureCount,
88+
} from "@/components/layout/project-setup-dialog";
8689
import {
8790
DndContext,
8891
DragEndEvent,
@@ -261,6 +264,7 @@ export function Sidebar() {
261264
const [setupProjectPath, setSetupProjectPath] = useState("");
262265
const [projectOverview, setProjectOverview] = useState("");
263266
const [generateFeatures, setGenerateFeatures] = useState(true);
267+
const [featureCount, setFeatureCount] = useState<FeatureCount>(50);
264268
const [showSpecIndicator, setShowSpecIndicator] = useState(true);
265269

266270
// Derive isCreatingSpec from store state
@@ -466,7 +470,9 @@ export function Sidebar() {
466470
const result = await api.specRegeneration.create(
467471
setupProjectPath,
468472
projectOverview.trim(),
469-
generateFeatures
473+
generateFeatures,
474+
undefined, // analyzeProject - use default
475+
generateFeatures ? featureCount : undefined // only pass maxFeatures if generating features
470476
);
471477

472478
if (!result.success) {
@@ -490,7 +496,13 @@ export function Sidebar() {
490496
description: error instanceof Error ? error.message : "Unknown error",
491497
});
492498
}
493-
}, [setupProjectPath, projectOverview, setSpecCreatingForProject]);
499+
}, [
500+
setupProjectPath,
501+
projectOverview,
502+
generateFeatures,
503+
featureCount,
504+
setSpecCreatingForProject,
505+
]);
494506

495507
// Handle skipping setup
496508
const handleSkipSetup = useCallback(() => {
@@ -1453,7 +1465,7 @@ export function Sidebar() {
14531465
onClick={() => setShowTrashDialog(true)}
14541466
className={cn(
14551467
"group flex items-center justify-center px-3 h-[42px] rounded-xl",
1456-
"relative overflow-hidden",
1468+
"relative",
14571469
"text-muted-foreground hover:text-destructive",
14581470
// Subtle background that turns red on hover
14591471
"bg-accent/20 hover:bg-destructive/15",
@@ -1467,7 +1479,7 @@ export function Sidebar() {
14671479
>
14681480
<Recycle className="size-4 shrink-0 transition-transform duration-200 group-hover:rotate-12" />
14691481
{trashedProjects.length > 0 && (
1470-
<span className="absolute -top-1.5 -right-1.5 flex items-center justify-center min-w-4 h-4 px-1 text-[9px] font-bold rounded-full bg-destructive text-destructive-foreground shadow-sm">
1482+
<span className="absolute -top-1.5 -right-1.5 z-10 flex items-center justify-center min-w-4 h-4 px-1 text-[9px] font-bold rounded-full bg-red-500 text-white shadow-md ring-1 ring-red-600/50">
14711483
{trashedProjects.length > 9 ? "9+" : trashedProjects.length}
14721484
</span>
14731485
)}
@@ -2248,6 +2260,8 @@ export function Sidebar() {
22482260
onProjectOverviewChange={setProjectOverview}
22492261
generateFeatures={generateFeatures}
22502262
onGenerateFeaturesChange={setGenerateFeatures}
2263+
featureCount={featureCount}
2264+
onFeatureCountChange={setFeatureCount}
22512265
onCreateSpec={handleCreateInitialSpec}
22522266
onSkip={handleSkipSetup}
22532267
isCreatingSpec={isCreatingSpec}

apps/app/src/components/ui/dropdown-menu.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,16 @@ const DropdownMenuSubContent = React.forwardRef<
4343
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
4444
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
4545
>(({ className, ...props }, ref) => (
46-
<DropdownMenuPrimitive.SubContent
47-
ref={ref}
48-
className={cn(
49-
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
50-
className
51-
)}
52-
{...props}
53-
/>
46+
<DropdownMenuPrimitive.Portal>
47+
<DropdownMenuPrimitive.SubContent
48+
ref={ref}
49+
className={cn(
50+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
51+
className
52+
)}
53+
{...props}
54+
/>
55+
</DropdownMenuPrimitive.Portal>
5456
))
5557
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
5658

apps/app/src/components/views/board-view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2180,7 +2180,7 @@ export function BoardView() {
21802180
data-testid="start-next-button"
21812181
>
21822182
<FastForward className="w-3 h-3 mr-1" />
2183-
Pull Top
2183+
Make
21842184
</HotkeyButton>
21852185
)}
21862186
</div>

apps/app/src/lib/electron.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,17 @@ export interface SpecRegenerationAPI {
148148
projectPath: string,
149149
projectOverview: string,
150150
generateFeatures?: boolean,
151-
analyzeProject?: boolean
151+
analyzeProject?: boolean,
152+
maxFeatures?: number
152153
) => Promise<{ success: boolean; error?: string }>;
153154
generate: (
154155
projectPath: string,
155156
projectDefinition: string,
156157
generateFeatures?: boolean,
157-
analyzeProject?: boolean
158+
analyzeProject?: boolean,
159+
maxFeatures?: number
158160
) => Promise<{ success: boolean; error?: string }>;
159-
generateFeatures: (projectPath: string) => Promise<{
161+
generateFeatures: (projectPath: string, maxFeatures?: number) => Promise<{
160162
success: boolean;
161163
error?: string;
162164
}>;
@@ -1836,15 +1838,17 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
18361838
create: async (
18371839
projectPath: string,
18381840
projectOverview: string,
1839-
generateFeatures = true
1841+
generateFeatures = true,
1842+
_analyzeProject?: boolean,
1843+
maxFeatures?: number
18401844
) => {
18411845
if (mockSpecRegenerationRunning) {
18421846
return { success: false, error: "Spec creation is already running" };
18431847
}
18441848

18451849
mockSpecRegenerationRunning = true;
18461850
console.log(
1847-
`[Mock] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}`
1851+
`[Mock] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
18481852
);
18491853

18501854
// Simulate async spec creation
@@ -1856,7 +1860,9 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
18561860
generate: async (
18571861
projectPath: string,
18581862
projectDefinition: string,
1859-
generateFeatures = false
1863+
generateFeatures = false,
1864+
_analyzeProject?: boolean,
1865+
maxFeatures?: number
18601866
) => {
18611867
if (mockSpecRegenerationRunning) {
18621868
return {
@@ -1867,7 +1873,7 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
18671873

18681874
mockSpecRegenerationRunning = true;
18691875
console.log(
1870-
`[Mock] Regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}`
1876+
`[Mock] Regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
18711877
);
18721878

18731879
// Simulate async spec regeneration
@@ -1880,7 +1886,7 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
18801886
return { success: true };
18811887
},
18821888

1883-
generateFeatures: async (projectPath: string) => {
1889+
generateFeatures: async (projectPath: string, maxFeatures?: number) => {
18841890
if (mockSpecRegenerationRunning) {
18851891
return {
18861892
success: false,
@@ -1890,7 +1896,7 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
18901896

18911897
mockSpecRegenerationRunning = true;
18921898
console.log(
1893-
`[Mock] Generating features from existing spec for: ${projectPath}`
1899+
`[Mock] Generating features from existing spec for: ${projectPath}, maxFeatures: ${maxFeatures}`
18941900
);
18951901

18961902
// Simulate async feature generation

apps/app/src/lib/http-api-client.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -582,28 +582,32 @@ export class HttpApiClient implements ElectronAPI {
582582
projectPath: string,
583583
projectOverview: string,
584584
generateFeatures?: boolean,
585-
analyzeProject?: boolean
585+
analyzeProject?: boolean,
586+
maxFeatures?: number
586587
) =>
587588
this.post("/api/spec-regeneration/create", {
588589
projectPath,
589590
projectOverview,
590591
generateFeatures,
591592
analyzeProject,
593+
maxFeatures,
592594
}),
593595
generate: (
594596
projectPath: string,
595597
projectDefinition: string,
596598
generateFeatures?: boolean,
597-
analyzeProject?: boolean
599+
analyzeProject?: boolean,
600+
maxFeatures?: number
598601
) =>
599602
this.post("/api/spec-regeneration/generate", {
600603
projectPath,
601604
projectDefinition,
602605
generateFeatures,
603606
analyzeProject,
607+
maxFeatures,
604608
}),
605-
generateFeatures: (projectPath: string) =>
606-
this.post("/api/spec-regeneration/generate-features", { projectPath }),
609+
generateFeatures: (projectPath: string, maxFeatures?: number) =>
610+
this.post("/api/spec-regeneration/generate-features", { projectPath, maxFeatures }),
607611
stop: () => this.post("/api/spec-regeneration/stop"),
608612
status: () => this.get("/api/spec-regeneration/status"),
609613
onEvent: (callback: (event: SpecRegenerationEvent) => void) => {

apps/app/src/types/electron.d.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ export interface SpecRegenerationAPI {
267267
projectPath: string,
268268
projectOverview: string,
269269
generateFeatures?: boolean,
270-
analyzeProject?: boolean
270+
analyzeProject?: boolean,
271+
maxFeatures?: number
271272
) => Promise<{
272273
success: boolean;
273274
error?: string;
@@ -277,13 +278,14 @@ export interface SpecRegenerationAPI {
277278
projectPath: string,
278279
projectDefinition: string,
279280
generateFeatures?: boolean,
280-
analyzeProject?: boolean
281+
analyzeProject?: boolean,
282+
maxFeatures?: number
281283
) => Promise<{
282284
success: boolean;
283285
error?: string;
284286
}>;
285287

286-
generateFeatures: (projectPath: string) => Promise<{
288+
generateFeatures: (projectPath: string, maxFeatures?: number) => Promise<{
287289
success: boolean;
288290
error?: string;
289291
}>;

apps/server/src/routes/app-spec/generate-features-from-spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ import { parseAndCreateFeatures } from "./parse-and-create-features.js";
1313

1414
const logger = createLogger("SpecRegeneration");
1515

16-
const MAX_FEATURES = 100;
16+
const DEFAULT_MAX_FEATURES = 50;
1717

1818
export async function generateFeaturesFromSpec(
1919
projectPath: string,
2020
events: EventEmitter,
21-
abortController: AbortController
21+
abortController: AbortController,
22+
maxFeatures?: number
2223
): Promise<void> {
24+
const featureCount = maxFeatures ?? DEFAULT_MAX_FEATURES;
2325
logger.debug("========== generateFeaturesFromSpec() started ==========");
2426
logger.debug("projectPath:", projectPath);
27+
logger.debug("maxFeatures:", featureCount);
2528

2629
// Read existing spec
2730
const specPath = path.join(projectPath, ".automaker", "app_spec.txt");
@@ -73,7 +76,7 @@ Format as JSON:
7376
]
7477
}
7578
76-
Generate ${MAX_FEATURES} features that build on each other logically.
79+
Generate ${featureCount} features that build on each other logically.
7780
7881
IMPORTANT: Do not ask for clarification. The specification is provided above. Generate the JSON immediately.`;
7982

0 commit comments

Comments
 (0)