Remove 5-project limit from project dropdown

- Remove .slice(0, 5) to show all projects in dropdown
- Extend keyboard shortcuts to support 1-9 (was 1-5)
- Only show hotkey indicators for first 9 projects
- Update tests to reflect new behavior

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cody Seibert
2025-12-09 12:29:35 -05:00
parent 9bae205312
commit df2c7c36a4
3 changed files with 106 additions and 26 deletions

View File

@@ -11,7 +11,27 @@
"category": "Core", "category": "Core",
"description": "I want the ability to press P which will automatically select my projects drop down and show all my projects. And then for each one, put a hotkey in the left that says 12345 and selecting one of those with my keyboard should automatically select that project.\n", "description": "I want the ability to press P which will automatically select my projects drop down and show all my projects. And then for each one, put a hotkey in the left that says 12345 and selecting one of those with my keyboard should automatically select that project.\n",
"steps": [], "steps": [],
"status": "in_progress", "status": "in_progress"
"startedAt": "2025-12-09T17:11:14.402Z" },
{
"id": "feature-1765301095506-cpy06q9u0",
"category": "Core",
"description": "It seems like there's only a limit of five of how many things show up in the project select drop down. I need to show everything.",
"steps": [],
"status": "verified"
},
{
"id": "feature-1765301127030-a4nnqp0ja",
"category": "Kanban",
"description": "In creating new cards in Kanban, I need the ability to drag and drop images into the description section, which will attach the image as context in store in the temp directory, so that later on when the agent runs, it can know where to fetch that image from.",
"steps": [],
"status": "in_progress"
},
{
"id": "feature-1765301184184-ttvhd8kkt",
"category": "Core",
"description": "-o should actually open the select folder prompt. Right now when you click o it goes to like the overview page. That's not the correct experience I'm looking for. Also just clicking on the top left open folder icon should do the same thing of opening the system prompt so they can select a project.",
"steps": [],
"status": "in_progress"
} }
] ]

View File

@@ -37,6 +37,9 @@ import {
ACTION_SHORTCUTS, ACTION_SHORTCUTS,
KeyboardShortcut, KeyboardShortcut,
} from "@/hooks/use-keyboard-shortcuts"; } from "@/hooks/use-keyboard-shortcuts";
import { getElectronAPI } from "@/lib/electron";
import { initializeProject } from "@/lib/project-init";
import { toast } from "sonner";
interface NavSection { interface NavSection {
label?: string; label?: string;
@@ -56,6 +59,7 @@ export function Sidebar() {
currentProject, currentProject,
currentView, currentView,
sidebarOpen, sidebarOpen,
addProject,
setCurrentProject, setCurrentProject,
setCurrentView, setCurrentView,
toggleSidebar, toggleSidebar,
@@ -65,6 +69,56 @@ export function Sidebar() {
// State for project picker dropdown // State for project picker dropdown
const [isProjectPickerOpen, setIsProjectPickerOpen] = useState(false); const [isProjectPickerOpen, setIsProjectPickerOpen] = useState(false);
/**
* Opens the system folder selection dialog and initializes the selected project.
* Used by both the 'O' keyboard shortcut and the folder icon button.
*/
const handleOpenFolder = useCallback(async () => {
const api = getElectronAPI();
const result = await api.openDirectory();
if (!result.canceled && result.filePaths[0]) {
const path = result.filePaths[0];
const name = path.split("/").pop() || "Untitled Project";
try {
// Initialize the .automaker directory structure
const initResult = await initializeProject(path);
if (!initResult.success) {
toast.error("Failed to initialize project", {
description: initResult.error || "Unknown error occurred",
});
return;
}
const project = {
id: `project-${Date.now()}`,
name,
path,
lastOpened: new Date().toISOString(),
};
addProject(project);
setCurrentProject(project);
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
toast.success(initResult.isNewProject ? "Project initialized" : "Project updated", {
description: `Set up ${initResult.createdFiles.length} file(s) in .automaker`,
});
} else {
toast.success("Project opened", {
description: `Opened ${name}`,
});
}
} catch (error) {
console.error("[Sidebar] Failed to open project:", error);
toast.error("Failed to open project", {
description: error instanceof Error ? error.message : "Unknown error",
});
}
}
}, [addProject, setCurrentProject]);
const navSections: NavSection[] = [ const navSections: NavSection[] = [
{ {
@@ -99,7 +153,7 @@ export function Sidebar() {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
const num = parseInt(event.key, 10); const num = parseInt(event.key, 10);
if (num >= 1 && num <= 5) { if (num >= 1 && num <= 9) {
event.preventDefault(); event.preventDefault();
selectProjectByNumber(num); selectProjectByNumber(num);
} else if (event.key === "Escape") { } else if (event.key === "Escape") {
@@ -122,11 +176,11 @@ export function Sidebar() {
description: "Toggle sidebar", description: "Toggle sidebar",
}); });
// Open project shortcut - always available // Open project shortcut - opens the folder selection dialog directly
shortcuts.push({ shortcuts.push({
key: ACTION_SHORTCUTS.openProject, key: ACTION_SHORTCUTS.openProject,
action: () => setCurrentView("welcome"), action: () => handleOpenFolder(),
description: "Open project (navigate to welcome view)", description: "Open folder selection dialog",
}); });
// Project picker shortcut - only when we have projects // Project picker shortcut - only when we have projects
@@ -161,7 +215,7 @@ export function Sidebar() {
} }
return shortcuts; return shortcuts;
}, [currentProject, setCurrentView, toggleSidebar, projects.length]); }, [currentProject, setCurrentView, toggleSidebar, projects.length, handleOpenFolder]);
// Register keyboard shortcuts // Register keyboard shortcuts
useKeyboardShortcuts(navigationShortcuts); useKeyboardShortcuts(navigationShortcuts);
@@ -241,9 +295,9 @@ export function Sidebar() {
<Plus className="w-4 h-4 flex-shrink-0" /> <Plus className="w-4 h-4 flex-shrink-0" />
</button> </button>
<button <button
onClick={() => setCurrentView("welcome")} onClick={handleOpenFolder}
className="group flex items-center justify-center w-8 h-8 rounded-lg relative overflow-hidden transition-all text-zinc-400 hover:text-white hover:bg-white/5" className="group flex items-center justify-center w-8 h-8 rounded-lg relative overflow-hidden transition-all text-zinc-400 hover:text-white hover:bg-white/5"
title={`Open Project (${ACTION_SHORTCUTS.openProject})`} title={`Open Folder (${ACTION_SHORTCUTS.openProject})`}
data-testid="open-project-button" data-testid="open-project-button"
> >
<FolderOpen className="w-4 h-4 flex-shrink-0" /> <FolderOpen className="w-4 h-4 flex-shrink-0" />
@@ -283,7 +337,7 @@ export function Sidebar() {
align="start" align="start"
data-testid="project-picker-dropdown" data-testid="project-picker-dropdown"
> >
{projects.slice(0, 5).map((project, index) => ( {projects.map((project, index) => (
<DropdownMenuItem <DropdownMenuItem
key={project.id} key={project.id}
onClick={() => { onClick={() => {
@@ -293,12 +347,14 @@ export function Sidebar() {
className="flex items-center gap-2 cursor-pointer text-zinc-300 hover:text-white hover:bg-zinc-700/50" className="flex items-center gap-2 cursor-pointer text-zinc-300 hover:text-white hover:bg-zinc-700/50"
data-testid={`project-option-${project.id}`} data-testid={`project-option-${project.id}`}
> >
<span {index < 9 && (
className="flex items-center justify-center w-5 h-5 text-[10px] font-mono rounded bg-white/5 border border-white/10 text-zinc-400" <span
data-testid={`project-hotkey-${index + 1}`} className="flex items-center justify-center w-5 h-5 text-[10px] font-mono rounded bg-white/5 border border-white/10 text-zinc-400"
> data-testid={`project-hotkey-${index + 1}`}
{index + 1} >
</span> {index + 1}
</span>
)}
<Folder className="h-4 w-4" /> <Folder className="h-4 w-4" />
<span className="flex-1 truncate">{project.name}</span> <span className="flex-1 truncate">{project.name}</span>
{currentProject?.id === project.id && ( {currentProject?.id === project.id && (

View File

@@ -152,9 +152,9 @@ test.describe("Project Picker Keyboard Shortcuts", () => {
await expect(shortcutIndicator).toHaveText("P"); await expect(shortcutIndicator).toHaveText("P");
}); });
test("only first 5 projects are shown with hotkeys", async ({ page }) => { test("all projects are shown, with hotkeys for first 9", async ({ page }) => {
// Setup with 7 projects // Setup with 10 projects
await setupMockMultipleProjects(page, 7); await setupMockMultipleProjects(page, 10);
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("networkidle");
@@ -165,16 +165,20 @@ test.describe("Project Picker Keyboard Shortcuts", () => {
await pressShortcut(page, "p"); await pressShortcut(page, "p");
await waitForProjectPickerDropdown(page); await waitForProjectPickerDropdown(page);
// Only 5 hotkey indicators should be visible (1-5) // All 10 projects should be visible
for (let i = 1; i <= 5; i++) { for (let i = 1; i <= 10; i++) {
const projectOption = page.locator(`[data-testid="project-option-test-project-${i}"]`);
await expect(projectOption).toBeVisible();
}
// First 9 hotkey indicators should be visible (1-9)
for (let i = 1; i <= 9; i++) {
expect(await isProjectHotkeyVisible(page, i)).toBe(true); expect(await isProjectHotkeyVisible(page, i)).toBe(true);
} }
// 6th and 7th should not exist // 10th hotkey should not exist (no keyboard shortcut for it)
const hotkey6 = page.locator('[data-testid="project-hotkey-6"]'); const hotkey10 = page.locator('[data-testid="project-hotkey-10"]');
const hotkey7 = page.locator('[data-testid="project-hotkey-7"]'); await expect(hotkey10).not.toBeVisible();
await expect(hotkey6).not.toBeVisible();
await expect(hotkey7).not.toBeVisible();
}); });
test("clicking a project option also works", async ({ page }) => { test("clicking a project option also works", async ({ page }) => {