feat: move "Report Bug / Feature Request" button to header for improved accessibility

- Relocated the button from the bottom sidebar to the header next to the AutoMaker logo.
- Updated the button to be a compact icon-only version with a tooltip on hover.
- Adjusted the header layout to accommodate the new button placement.
- Removed the old button from the sidebar to streamline the UI.
This commit is contained in:
Cody Seibert
2025-12-12 02:43:26 -05:00
parent 8e65f0b338
commit ba24753630
15 changed files with 181 additions and 207 deletions

View File

@@ -53,6 +53,15 @@ class McpServerFactory {
finalStatus = "waiting_approval";
}
// IMPORTANT: Prevent agent from moving an in_progress feature back to backlog
// When a feature is being worked on, the agent should only be able to mark it as verified
// (which may be converted to waiting_approval for skipTests features)
// This prevents the agent from incorrectly putting completed work back in the backlog
if (feature && feature.status === "in_progress" && (args.status === "backlog" || args.status === "todo")) {
console.log(`[McpServerFactory] Feature ${args.featureId} is in_progress - preventing move to ${args.status}, converting to waiting_approval instead`);
finalStatus = "waiting_approval";
}
// Call the provided callback to update feature status
await updateFeatureStatusCallback(
args.featureId,

View File

@@ -211,7 +211,16 @@ async function handleToolsCall(params, id) {
if (status === 'verified' && feature.skipTests === true) {
finalStatus = 'waiting_approval';
}
// IMPORTANT: Prevent agent from moving an in_progress feature back to backlog
// When a feature is being worked on, the agent should only be able to mark it as verified
// (which may be converted to waiting_approval for skipTests features)
// This prevents the agent from incorrectly putting completed work back in the backlog
if (feature.status === 'in_progress' && (status === 'backlog' || status === 'todo')) {
console.log(`[McpServerStdio] Feature ${featureId} is in_progress - preventing move to ${status}, converting to waiting_approval instead`);
finalStatus = 'waiting_approval';
}
// Call the update callback via IPC or direct call
// Since we're in a separate process, we need to use IPC to communicate back
// For now, we'll call the feature loader directly since it has the update method

View File

@@ -2,7 +2,6 @@ import type { Metadata } from "next";
import { GeistSans } from "geist/font/sans";
import { GeistMono } from "geist/font/mono";
import { Toaster } from "sonner";
import { CoursePromoBadge } from "@/components/ui/course-promo-badge";
import "./globals.css";
export const metadata: Metadata = {
title: "Automaker - Autonomous AI Development Studio",
@@ -21,7 +20,6 @@ export default function RootLayout({
>
{children}
<Toaster richColors position="bottom-right" />
<CoursePromoBadge />
</body>
</html>
);

View File

@@ -3,6 +3,7 @@
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
import { cn } from "@/lib/utils";
import { useAppStore, formatShortcut } from "@/store/app-store";
import { CoursePromoBadge } from "@/components/ui/course-promo-badge";
import {
FolderOpen,
Plus,
@@ -1141,6 +1142,8 @@ export function Sidebar() {
{/* Bottom Section - Running Agents / Bug Report / Settings */}
<div className="border-t border-sidebar-border bg-sidebar-accent/10 shrink-0">
{/* Course Promo Badge */}
<CoursePromoBadge />
{/* Running Agents Link */}
<div className="p-2 pb-0">
<button

View File

@@ -1,19 +1,9 @@
"use client";
import * as React from "react";
import { Sparkles, Rocket, X, ExternalLink, Code, MessageSquare, Brain, Terminal } from "lucide-react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "./dialog";
import { Button } from "./button";
import { Sparkles, X } from "lucide-react";
export function CoursePromoBadge() {
const [open, setOpen] = React.useState(false);
const [dismissed, setDismissed] = React.useState(false);
if (dismissed) {
@@ -21,116 +11,29 @@ export function CoursePromoBadge() {
}
return (
<>
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50">
<button
onClick={() => setOpen(true)}
className="group cursor-pointer flex items-center gap-2 pl-4 pr-2 py-2 bg-primary text-primary-foreground rounded-full font-semibold text-sm shadow-lg hover:bg-primary/90 hover:scale-105 transition-all border border-border"
<div className="p-2 pb-0">
<a
href="https://agenticjumpstart.com"
target="_blank"
rel="noopener noreferrer"
className="group cursor-pointer flex items-center justify-between w-full px-2 lg:px-3 py-2.5 bg-primary/10 text-primary rounded-lg font-medium text-sm hover:bg-primary/20 transition-all border border-primary/30"
>
<div className="flex items-center gap-2">
<Sparkles className="size-4 shrink-0" />
<span className="hidden lg:block">Become a 10x Dev</span>
</div>
<span
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setDismissed(true);
}}
className="p-1 rounded-full hover:bg-primary/30 transition-colors cursor-pointer"
aria-label="Dismiss"
>
<Sparkles className="size-4" />
<span>Become a 10x Dev</span>
<span
onClick={(e) => {
e.stopPropagation();
setDismissed(true);
}}
className="p-1 rounded-full hover:bg-primary-foreground/20 transition-colors cursor-pointer"
aria-label="Dismiss"
>
<X className="size-3.5" />
</span>
</button>
</div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-xl">
<Rocket className="size-5 text-primary" />
Learn Agentic AI Development
</DialogTitle>
<DialogDescription className="text-base">
Master the tools and techniques behind modern AI-assisted coding
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="p-3 rounded-lg bg-accent/50 border border-border">
<p className="text-sm text-foreground">
Did you know <span className="font-semibold">Automaker was built entirely through agentic coding</span>?
Want to learn how? Check out the course!
</p>
</div>
<p className="text-muted-foreground">
<span className="font-semibold text-foreground">Agentic Jumpstart</span> teaches you
how to leverage AI tools to build software faster and smarter than ever before.
</p>
<div className="space-y-3">
<div className="flex items-start gap-3">
<div className="p-1.5 rounded-lg bg-primary/10">
<Terminal className="size-4 text-primary" />
</div>
<div>
<p className="font-medium text-sm">Claude Code Mastery</p>
<p className="text-sm text-muted-foreground">
Learn to use Claude Code effectively for autonomous development workflows
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="p-1.5 rounded-lg bg-primary/10">
<Code className="size-4 text-primary" />
</div>
<div>
<p className="font-medium text-sm">Cursor & AI IDEs</p>
<p className="text-sm text-muted-foreground">
Master Cursor and other AI-powered development environments
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="p-1.5 rounded-lg bg-primary/10">
<MessageSquare className="size-4 text-primary" />
</div>
<div>
<p className="font-medium text-sm">Prompting Techniques</p>
<p className="text-sm text-muted-foreground">
Craft effective prompts that get you the results you need
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="p-1.5 rounded-lg bg-primary/10">
<Brain className="size-4 text-primary" />
</div>
<div>
<p className="font-medium text-sm">Context Engineering</p>
<p className="text-sm text-muted-foreground">
Structure your projects and context for optimal AI collaboration
</p>
</div>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
Maybe Later
</Button>
<Button
onClick={() => window.open("https://agenticjumpstart.com", "_blank")}
>
<ExternalLink className="size-4" />
Get Started
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
<X className="size-3.5" />
</span>
</a>
</div>
);
}

View File

@@ -876,17 +876,21 @@ interface SetupAPI {
getClaudeStatus: () => Promise<{
success: boolean;
status?: string;
installed?: boolean;
method?: string;
version?: string;
path?: string;
auth?: {
authenticated: boolean;
method: string;
hasCredentialsFile: boolean;
hasToken: boolean;
hasCredentialsFile?: boolean;
hasToken?: boolean;
hasStoredOAuthToken?: boolean;
hasStoredApiKey?: boolean;
hasEnvApiKey?: boolean;
hasEnvOAuthToken?: boolean;
hasCliAuth?: boolean;
hasRecentActivity?: boolean;
};
error?: string;
}>;
@@ -966,11 +970,14 @@ function createMockSetupAPI(): SetupAPI {
return {
success: true,
status: "not_installed",
installed: false,
auth: {
authenticated: false,
method: "none",
hasCredentialsFile: false,
hasToken: false,
hasCliAuth: false,
hasRecentActivity: false,
},
};
},

View File

@@ -381,6 +381,8 @@ export class HttpApiClient implements ElectronAPI {
hasStoredApiKey?: boolean;
hasEnvApiKey?: boolean;
hasEnvOAuthToken?: boolean;
hasCliAuth?: boolean;
hasRecentActivity?: boolean;
};
error?: string;
}> => this.get("/api/setup/claude-status"),

View File

@@ -10,5 +10,29 @@
"role": "assistant",
"content": "Hello! This is a test response. I'm working correctly and ready to help you with your software development projects.\n\nIs there something specific you'd like to work on today? I can help you with:\n\n- **Planning a new project** - Define requirements, architecture, and technical approach\n- **Writing code** - Create new files, implement features, or build components\n- **Editing existing code** - Modify, refactor, or fix bugs in your codebase\n- **Running commands** - Execute tests, builds, or other terminal commands\n- **Exploring your codebase** - Search for patterns, understand existing code structure\n\nJust let me know what you need!",
"timestamp": "2025-12-12T07:12:14.075Z"
},
{
"id": "msg_1765524006202_3d2cuya6b",
"role": "user",
"content": "test",
"timestamp": "2025-12-12T07:20:06.202Z"
},
{
"id": "msg_1765524011389_sdfbuai3k",
"role": "assistant",
"content": "Hello! I'm here to help you build software. How can I assist you today?\n\nI can help you with:\n\n- **Planning** - Define requirements, discuss architecture, and design your project\n- **Coding** - Write, edit, and modify code files\n- **Debugging** - Analyze issues and fix bugs\n- **Testing** - Run tests and verify functionality\n- **Exploring** - Search and understand existing codebases\n\nWhat would you like to work on?",
"timestamp": "2025-12-12T07:20:11.389Z"
},
{
"id": "msg_1765524491273_7ocghnbb5",
"role": "user",
"content": "test",
"timestamp": "2025-12-12T07:28:11.273Z"
},
{
"id": "msg_1765524498237_x8b4148gc",
"role": "assistant",
"content": "Hello! I'm here to help you build software. How can I assist you today?\n\nI can help you with:\n- **Planning** - Define requirements, architecture, and technical approaches\n- **Coding** - Write, edit, and modify code files\n- **Debugging** - Find and fix issues in your codebase\n- **Testing** - Run tests and help improve test coverage\n- **Exploring** - Search and analyze your existing codebase\n\nWhat would you like to work on?",
"timestamp": "2025-12-12T07:28:18.237Z"
}
]

View File

@@ -5,6 +5,6 @@
"projectPath": "/Users/webdevcody/Workspace/automaker",
"workingDirectory": "/Users/webdevcody/Workspace/automaker",
"createdAt": "2025-12-12T07:12:04.582Z",
"updatedAt": "2025-12-12T07:12:14.382Z"
"updatedAt": "2025-12-12T07:28:18.571Z"
}
}

View File

@@ -80,6 +80,7 @@ export function createSetupRoutes(): Router {
// Not in PATH, try common locations
const commonPaths = [
path.join(os.homedir(), ".local", "bin", "claude"),
path.join(os.homedir(), ".claude", "local", "claude"),
"/usr/local/bin/claude",
path.join(os.homedir(), ".npm-global", "bin", "claude"),
];
@@ -90,6 +91,14 @@ export function createSetupRoutes(): Router {
cliPath = p;
installed = true;
method = "local";
// Get version from this path
try {
const { stdout: versionOut } = await execAsync(`"${p}" --version`);
version = versionOut.trim();
} catch {
// Version command might not be available
}
break;
} catch {
// Not found at this path
@@ -110,10 +119,55 @@ export function createSetupRoutes(): Router {
// Additional fields for detailed status
oauthTokenValid: false,
apiKeyValid: false,
hasCliAuth: false,
hasRecentActivity: false,
};
// Check for credentials file (OAuth tokens from claude login)
const credentialsPath = path.join(os.homedir(), ".claude", "credentials.json");
const claudeDir = path.join(os.homedir(), ".claude");
// Check for recent Claude CLI activity - indicates working authentication
// The stats-cache.json file is only populated when the CLI is working properly
const statsCachePath = path.join(claudeDir, "stats-cache.json");
try {
const statsContent = await fs.readFile(statsCachePath, "utf-8");
const stats = JSON.parse(statsContent);
// Check if there's any activity (which means the CLI is authenticated and working)
if (stats.dailyActivity && stats.dailyActivity.length > 0) {
auth.hasRecentActivity = true;
auth.hasCliAuth = true;
auth.authenticated = true;
auth.method = "cli_authenticated";
}
} catch {
// Stats file doesn't exist or is invalid
}
// Check for settings.json - indicates CLI has been set up
const settingsPath = path.join(claudeDir, "settings.json");
try {
await fs.access(settingsPath);
// If settings exist but no activity, CLI might be set up but not authenticated
if (!auth.hasCliAuth) {
// Try to check for other indicators of auth
const sessionsDir = path.join(claudeDir, "projects");
try {
const sessions = await fs.readdir(sessionsDir);
if (sessions.length > 0) {
auth.hasCliAuth = true;
auth.authenticated = true;
auth.method = "cli_authenticated";
}
} catch {
// Sessions directory doesn't exist
}
}
} catch {
// Settings file doesn't exist
}
// Check for credentials file (OAuth tokens from claude login) - legacy/alternative auth
const credentialsPath = path.join(claudeDir, "credentials.json");
try {
const credentialsContent = await fs.readFile(credentialsPath, "utf-8");
const credentials = JSON.parse(credentialsContent);