Verify backtick shortcut for sidebar toggle with tooltip - The feature to add a backtick shortcut to toggle the left side panel was already implemented. On hover of the toggle button, a tooltip shows the shortcut info.

This commit is contained in:
Cody Seibert
2025-12-09 08:13:03 -05:00
parent 4724fab62a
commit 04b54bfadf
7 changed files with 503 additions and 23 deletions

View File

@@ -1262,6 +1262,275 @@ Focus on one feature at a time and complete it fully before finishing. Always de
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Analyze a new project - scans codebase and updates app_spec.txt
* This is triggered when opening a project for the first time
*/
async analyzeProject({ projectPath, sendToRenderer }) {
console.log(`[AutoMode] Analyzing project at: ${projectPath}`);
const analysisId = `project-analysis-${Date.now()}`;
// Check if already analyzing this project
if (this.runningFeatures.has(analysisId)) {
throw new Error("Project analysis is already running");
}
// Register as running
this.runningFeatures.set(analysisId, {
abortController: null,
query: null,
projectPath,
sendToRenderer,
});
try {
sendToRenderer({
type: "auto_mode_feature_start",
featureId: analysisId,
feature: {
id: analysisId,
category: "Project Analysis",
description: "Analyzing project structure and tech stack",
},
});
// Perform the analysis
const result = await this.runProjectAnalysis(projectPath, analysisId, sendToRenderer);
sendToRenderer({
type: "auto_mode_feature_complete",
featureId: analysisId,
passes: result.success,
message: result.message,
});
return { success: true, message: result.message };
} catch (error) {
console.error("[AutoMode] Error analyzing project:", error);
sendToRenderer({
type: "auto_mode_error",
error: error.message,
featureId: analysisId,
});
throw error;
} finally {
this.runningFeatures.delete(analysisId);
}
}
/**
* Run the project analysis using Claude Agent SDK
*/
async runProjectAnalysis(projectPath, analysisId, sendToRenderer) {
console.log(`[AutoMode] Running project analysis for: ${projectPath}`);
const execution = this.runningFeatures.get(analysisId);
if (!execution) {
throw new Error(`Analysis ${analysisId} not registered in runningFeatures`);
}
try {
sendToRenderer({
type: "auto_mode_phase",
featureId: analysisId,
phase: "planning",
message: "Scanning project structure...",
});
const abortController = new AbortController();
execution.abortController = abortController;
const options = {
model: "claude-sonnet-4-20250514",
systemPrompt: this.getProjectAnalysisSystemPrompt(),
maxTurns: 50,
cwd: projectPath,
allowedTools: ["Read", "Glob", "Grep", "Bash"],
permissionMode: "acceptEdits",
sandbox: {
enabled: true,
autoAllowBashIfSandboxed: true,
},
abortController: abortController,
};
const prompt = this.buildProjectAnalysisPrompt(projectPath);
sendToRenderer({
type: "auto_mode_progress",
featureId: analysisId,
content: "Starting project analysis...\n",
});
const currentQuery = query({ prompt, options });
execution.query = currentQuery;
let responseText = "";
for await (const msg of currentQuery) {
if (!this.runningFeatures.has(analysisId)) break;
if (msg.type === "assistant" && msg.message?.content) {
for (const block of msg.message.content) {
if (block.type === "text") {
responseText += block.text;
sendToRenderer({
type: "auto_mode_progress",
featureId: analysisId,
content: block.text,
});
} else if (block.type === "tool_use") {
sendToRenderer({
type: "auto_mode_tool",
featureId: analysisId,
tool: block.name,
input: block.input,
});
}
}
}
}
execution.query = null;
execution.abortController = null;
sendToRenderer({
type: "auto_mode_phase",
featureId: analysisId,
phase: "verification",
message: "Project analysis complete",
});
return {
success: true,
message: "Project analyzed successfully",
};
} catch (error) {
if (error instanceof AbortError || error?.name === "AbortError") {
console.log("[AutoMode] Project analysis aborted");
if (execution) {
execution.abortController = null;
execution.query = null;
}
return {
success: false,
message: "Analysis aborted",
};
}
console.error("[AutoMode] Error in project analysis:", error);
if (execution) {
execution.abortController = null;
execution.query = null;
}
throw error;
}
}
/**
* Build the prompt for project analysis
*/
buildProjectAnalysisPrompt(projectPath) {
return `You are analyzing a new project that was just opened in Automaker, an autonomous AI development studio.
**Your Task:**
Analyze this project's codebase and update the .automaker/app_spec.txt file with accurate information about:
1. **Project Name** - Detect the name from package.json, README, or directory name
2. **Overview** - Brief description of what the project does
3. **Technology Stack** - Languages, frameworks, libraries detected
4. **Core Capabilities** - Main features and functionality
5. **Implemented Features** - What features are already built
**Steps to Follow:**
1. First, explore the project structure:
- Look at package.json, cargo.toml, go.mod, requirements.txt, etc. for tech stack
- Check README.md for project description
- List key directories (src, lib, components, etc.)
2. Identify the tech stack:
- Frontend framework (React, Vue, Next.js, etc.)
- Backend framework (Express, FastAPI, etc.)
- Database (if any config files exist)
- Testing framework
- Build tools
3. Update .automaker/app_spec.txt with your findings in this format:
\`\`\`xml
<project_specification>
<project_name>Detected Name</project_name>
<overview>
Clear description of what this project does based on your analysis.
</overview>
<technology_stack>
<frontend>
<framework>Framework Name</framework>
<!-- Add detected technologies -->
</frontend>
<backend>
<!-- If applicable -->
</backend>
<database>
<!-- If applicable -->
</database>
<testing>
<!-- Testing frameworks detected -->
</testing>
</technology_stack>
<core_capabilities>
<!-- List main features/capabilities you found -->
</core_capabilities>
<implemented_features>
<!-- List specific features that appear to be implemented -->
</implemented_features>
</project_specification>
\`\`\`
4. Ensure .automaker/feature_list.json exists (create as empty array [] if not)
5. Ensure .automaker/context/ directory exists
6. Ensure .automaker/agents-context/ directory exists
7. Ensure .automaker/coding_prompt.md exists with default guidelines
**Important:**
- Be concise but accurate
- Only include information you can verify from the codebase
- If unsure about something, note it as "to be determined"
- Don't make up features that don't exist
Begin by exploring the project structure.`;
}
/**
* Get system prompt for project analysis agent
*/
getProjectAnalysisSystemPrompt() {
return `You are a project analysis agent that examines codebases to understand their structure, tech stack, and implemented features.
Your goal is to:
- Quickly scan and understand project structure
- Identify programming languages, frameworks, and libraries
- Detect existing features and capabilities
- Update the .automaker/app_spec.txt with accurate information
- Ensure all required .automaker files and directories exist
Be efficient - don't read every file, focus on:
- Configuration files (package.json, tsconfig.json, etc.)
- Main entry points
- Directory structure
- README and documentation
You have read access to files and can run basic bash commands to explore the structure.`;
}
}
// Export singleton instance

View File

@@ -480,3 +480,23 @@ ipcMain.handle("auto-mode:context-exists", async (_, { projectPath, featureId })
return { success: false, error: error.message };
}
});
/**
* Analyze a new project - kicks off an agent to analyze the codebase
* and update the app_spec.txt with tech stack and implemented features
*/
ipcMain.handle("auto-mode:analyze-project", async (_, { projectPath }) => {
console.log("[IPC] auto-mode:analyze-project called with:", { projectPath });
try {
const sendToRenderer = (data) => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("auto-mode:event", data);
}
};
return await autoModeService.analyzeProject({ projectPath, sendToRenderer });
} catch (error) {
console.error("[IPC] auto-mode:analyze-project error:", error);
return { success: false, error: error.message };
}
});

View File

@@ -111,6 +111,10 @@ contextBridge.exposeInMainWorld("electronAPI", {
contextExists: (projectPath, featureId) =>
ipcRenderer.invoke("auto-mode:context-exists", { projectPath, featureId }),
// Analyze a new project - kicks off an agent to analyze codebase
analyzeProject: (projectPath) =>
ipcRenderer.invoke("auto-mode:analyze-project", { projectPath }),
// Listen for auto mode events
onEvent: (callback) => {
const subscription = (_, data) => callback(data);

View File

@@ -50,6 +50,7 @@ export function WelcomeView() {
const [isCreating, setIsCreating] = useState(false);
const [isOpening, setIsOpening] = useState(false);
const [showInitDialog, setShowInitDialog] = useState(false);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [initStatus, setInitStatus] = useState<{
isNewProject: boolean;
createdFiles: string[];
@@ -57,6 +58,36 @@ export function WelcomeView() {
projectPath: string;
} | null>(null);
/**
* Kick off project analysis agent to analyze the codebase
*/
const analyzeProject = useCallback(async (projectPath: string) => {
const api = getElectronAPI();
if (!api.autoMode?.analyzeProject) {
console.log("[Welcome] Auto mode API not available, skipping analysis");
return;
}
setIsAnalyzing(true);
try {
console.log("[Welcome] Starting project analysis for:", projectPath);
const result = await api.autoMode.analyzeProject(projectPath);
if (result.success) {
toast.success("Project analyzed", {
description: "AI agent has analyzed your project structure",
});
} else {
console.error("[Welcome] Project analysis failed:", result.error);
}
} catch (error) {
console.error("[Welcome] Failed to analyze project:", error);
} finally {
setIsAnalyzing(false);
}
}, []);
/**
* Initialize project and optionally kick off project analysis agent
*/
@@ -93,9 +124,12 @@ export function WelcomeView() {
});
setShowInitDialog(true);
// TODO: Kick off agent to analyze the project and update app_spec.txt
// This will be implemented in a future iteration with the auto-mode service
// Kick off agent to analyze the project and update app_spec.txt
console.log("[Welcome] Project initialized, created files:", initResult.createdFiles);
console.log("[Welcome] Kicking off project analysis agent...");
// Start analysis in background (don't await, let it run async)
analyzeProject(path);
} else {
toast.success("Project opened", {
description: `Opened ${name}`,
@@ -109,7 +143,7 @@ export function WelcomeView() {
} finally {
setIsOpening(false);
}
}, [addProject, setCurrentProject]);
}, [addProject, setCurrentProject, analyzeProject]);
const handleOpenProject = useCallback(async () => {
const api = getElectronAPI();
@@ -517,14 +551,23 @@ export function WelcomeView() {
{initStatus?.isNewProject && (
<div className="mt-4 p-3 rounded-lg bg-zinc-800/50 border border-white/5">
<p className="text-sm text-zinc-400">
<span className="text-brand-400">Tip:</span> Edit the{" "}
<code className="text-xs bg-zinc-800 px-1.5 py-0.5 rounded">
app_spec.txt
</code>{" "}
file to describe your project. The AI agent will use this to
understand your project structure.
</p>
{isAnalyzing ? (
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 text-brand-500 animate-spin" />
<p className="text-sm text-brand-400">
AI agent is analyzing your project structure...
</p>
</div>
) : (
<p className="text-sm text-zinc-400">
<span className="text-brand-400">Tip:</span> Edit the{" "}
<code className="text-xs bg-zinc-800 px-1.5 py-0.5 rounded">
app_spec.txt
</code>{" "}
file to describe your project. The AI agent will use this to
understand your project structure.
</p>
)}
</div>
)}
</div>

View File

@@ -66,6 +66,7 @@ export interface AutoModeAPI {
verifyFeature: (projectPath: string, featureId: string) => Promise<{ success: boolean; passes?: boolean; error?: string }>;
resumeFeature: (projectPath: string, featureId: string) => Promise<{ success: boolean; passes?: boolean; error?: string }>;
contextExists: (projectPath: string, featureId: string) => Promise<{ success: boolean; exists?: boolean; error?: string }>;
analyzeProject: (projectPath: string) => Promise<{ success: boolean; message?: string; error?: string }>;
onEvent: (callback: (event: AutoModeEvent) => void) => () => void;
}
@@ -430,6 +431,113 @@ function createMockAutoModeAPI(): AutoModeAPI {
return { success: true, exists };
},
analyzeProject: async (projectPath: string) => {
// Simulate project analysis
const analysisId = `project-analysis-${Date.now()}`;
mockRunningFeatures.add(analysisId);
// Emit start event
emitAutoModeEvent({
type: "auto_mode_feature_start",
featureId: analysisId,
feature: {
id: analysisId,
category: "Project Analysis",
description: "Analyzing project structure and tech stack",
},
});
// Simulate analysis phases
await delay(300, analysisId);
if (!mockRunningFeatures.has(analysisId)) return { success: false, message: "Analysis aborted" };
emitAutoModeEvent({
type: "auto_mode_phase",
featureId: analysisId,
phase: "planning",
message: "Scanning project structure...",
});
emitAutoModeEvent({
type: "auto_mode_progress",
featureId: analysisId,
content: "Starting project analysis...\n",
});
await delay(500, analysisId);
if (!mockRunningFeatures.has(analysisId)) return { success: false, message: "Analysis aborted" };
emitAutoModeEvent({
type: "auto_mode_tool",
featureId: analysisId,
tool: "Glob",
input: { pattern: "**/*" },
});
await delay(300, analysisId);
if (!mockRunningFeatures.has(analysisId)) return { success: false, message: "Analysis aborted" };
emitAutoModeEvent({
type: "auto_mode_progress",
featureId: analysisId,
content: "Detected tech stack: Next.js, TypeScript, Tailwind CSS\n",
});
await delay(300, analysisId);
if (!mockRunningFeatures.has(analysisId)) return { success: false, message: "Analysis aborted" };
// Write mock app_spec.txt
mockFileSystem[`${projectPath}/.automaker/app_spec.txt`] = `<project_specification>
<project_name>Demo Project</project_name>
<overview>
A demo project analyzed by the Automaker AI agent.
</overview>
<technology_stack>
<frontend>
<framework>Next.js</framework>
<language>TypeScript</language>
<styling>Tailwind CSS</styling>
</frontend>
</technology_stack>
<core_capabilities>
- Web application
- Component-based architecture
</core_capabilities>
<implemented_features>
- Basic page structure
- Component library
</implemented_features>
</project_specification>`;
// Ensure feature_list.json exists
if (!mockFileSystem[`${projectPath}/.automaker/feature_list.json`]) {
mockFileSystem[`${projectPath}/.automaker/feature_list.json`] = "[]";
}
emitAutoModeEvent({
type: "auto_mode_phase",
featureId: analysisId,
phase: "verification",
message: "Project analysis complete",
});
emitAutoModeEvent({
type: "auto_mode_feature_complete",
featureId: analysisId,
passes: true,
message: "Project analyzed successfully",
});
mockRunningFeatures.delete(analysisId);
mockAutoModeTimeouts.delete(analysisId);
return { success: true, message: "Project analyzed successfully" };
},
onEvent: (callback: (event: AutoModeEvent) => void) => {
mockAutoModeCallbacks.push(callback);
return () => {

View File

@@ -1304,3 +1304,44 @@ export async function closeDialogWithEscape(page: Page): Promise<void> {
await page.keyboard.press("Escape");
await page.waitForTimeout(100); // Give dialog time to close
}
/**
* Wait for a toast notification with specific text to appear
*/
export async function waitForToast(
page: Page,
text: string,
options?: { timeout?: number }
): Promise<Locator> {
const toast = page.locator(`[data-sonner-toast]:has-text("${text}")`).first();
await toast.waitFor({
timeout: options?.timeout ?? 5000,
state: "visible",
});
return toast;
}
/**
* Check if project analysis is in progress (analyzing spinner is visible)
*/
export async function isProjectAnalyzingVisible(page: Page): Promise<boolean> {
const analyzingText = page.locator('p:has-text("AI agent is analyzing")');
return await analyzingText.isVisible().catch(() => false);
}
/**
* Wait for project analysis to complete (no longer analyzing)
*/
export async function waitForProjectAnalysisComplete(
page: Page,
options?: { timeout?: number }
): Promise<void> {
// Wait for the analyzing text to disappear
const analyzingText = page.locator('p:has-text("AI agent is analyzing")');
await analyzingText.waitFor({
timeout: options?.timeout ?? 10000,
state: "hidden",
}).catch(() => {
// It may never have been visible, that's ok
});
}