feat: implement backlog plan management and UI enhancements

- Added functionality to save, clear, and load backlog plans within the application.
- Introduced a new API endpoint for clearing saved backlog plans.
- Enhanced the backlog plan dialog to allow users to review and apply changes to their features.
- Integrated dependency management features in the UI, allowing users to select parent and child dependencies for features.
- Improved the graph view with options to manage plans and visualize dependencies effectively.
- Updated the sidebar and settings to include provider visibility toggles for better user control over model selection.

These changes aim to enhance the user experience by providing robust backlog management capabilities and improving the overall UI for feature planning.
This commit is contained in:
webdevcody
2026-01-15 22:21:46 -05:00
parent cb544e0011
commit 03436103d1
46 changed files with 1719 additions and 418 deletions

View File

@@ -1,6 +1,6 @@
import { useState, useCallback, useEffect } from 'react';
import { Plus, Bug, FolderOpen } from 'lucide-react';
import { useNavigate } from '@tanstack/react-router';
import { Plus, Bug, FolderOpen, BookOpen } from 'lucide-react';
import { useNavigate, useLocation } from '@tanstack/react-router';
import { cn } from '@/lib/utils';
import { useAppStore, type ThemeMode } from '@/store/app-store';
import { useOSDetection } from '@/hooks/use-os-detection';
@@ -10,6 +10,7 @@ import { EditProjectDialog } from './components/edit-project-dialog';
import { NewProjectModal } from '@/components/dialogs/new-project-modal';
import { OnboardingDialog } from '@/components/layout/sidebar/dialogs';
import { useProjectCreation, useProjectTheme } from '@/components/layout/sidebar/hooks';
import { SIDEBAR_FEATURE_FLAGS } from '@/components/layout/sidebar/constants';
import type { Project } from '@/lib/electron';
import { getElectronAPI } from '@/lib/electron';
import { initializeProject, hasAppSpec, hasAutomakerDir } from '@/lib/project-init';
@@ -31,6 +32,9 @@ function getOSAbbreviation(os: string): string {
export function ProjectSwitcher() {
const navigate = useNavigate();
const location = useLocation();
const { hideWiki } = SIDEBAR_FEATURE_FLAGS;
const isWikiActive = location.pathname === '/wiki';
const {
projects,
currentProject,
@@ -124,6 +128,10 @@ export function ProjectSwitcher() {
api.openExternalLink('https://github.com/AutoMaker-Org/automaker/issues');
}, []);
const handleWikiClick = useCallback(() => {
navigate({ to: '/wiki' });
}, [navigate]);
/**
* Opens the system folder selection dialog and initializes the selected project.
*/
@@ -405,8 +413,37 @@ export function ProjectSwitcher() {
)}
</div>
{/* Bug Report Button at the very bottom */}
<div className="p-2 border-t border-border/40">
{/* Wiki and Bug Report Buttons at the very bottom */}
<div className="p-2 border-t border-border/40 space-y-2">
{/* Wiki Button */}
{!hideWiki && (
<button
onClick={handleWikiClick}
className={cn(
'w-full aspect-square rounded-xl flex items-center justify-center',
'transition-all duration-200 ease-out',
isWikiActive
? [
'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10',
'text-foreground',
'border border-brand-500/30',
'shadow-md shadow-brand-500/10',
]
: [
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50 border border-transparent hover:border-border/40',
'hover:shadow-sm hover:scale-105 active:scale-95',
]
)}
title="Wiki"
data-testid="wiki-button"
>
<BookOpen
className={cn('w-5 h-5', isWikiActive && 'text-brand-500 drop-shadow-sm')}
/>
</button>
)}
{/* Bug Report Button */}
<button
onClick={handleBugReportClick}
className={cn(

View File

@@ -57,8 +57,7 @@ export function Sidebar() {
} = useAppStore();
// Environment variable flags for hiding sidebar items
const { hideTerminal, hideWiki, hideRunningAgents, hideContext, hideSpecEditor } =
SIDEBAR_FEATURE_FLAGS;
const { hideTerminal, hideRunningAgents, hideContext, hideSpecEditor } = SIDEBAR_FEATURE_FLAGS;
// Get customizable keyboard shortcuts
const shortcuts = useKeyboardShortcutsConfig();
@@ -297,7 +296,6 @@ export function Sidebar() {
sidebarOpen={sidebarOpen}
isActiveRoute={isActiveRoute}
navigate={navigate}
hideWiki={hideWiki}
hideRunningAgents={hideRunningAgents}
runningAgentsCount={runningAgentsCount}
shortcuts={{ settings: shortcuts.settings }}

View File

@@ -1,13 +1,12 @@
import type { NavigateOptions } from '@tanstack/react-router';
import { cn } from '@/lib/utils';
import { formatShortcut } from '@/store/app-store';
import { BookOpen, Activity, Settings } from 'lucide-react';
import { Activity, Settings } from 'lucide-react';
interface SidebarFooterProps {
sidebarOpen: boolean;
isActiveRoute: (id: string) => boolean;
navigate: (opts: NavigateOptions) => void;
hideWiki: boolean;
hideRunningAgents: boolean;
runningAgentsCount: number;
shortcuts: {
@@ -19,7 +18,6 @@ export function SidebarFooter({
sidebarOpen,
isActiveRoute,
navigate,
hideWiki,
hideRunningAgents,
runningAgentsCount,
shortcuts,
@@ -34,66 +32,6 @@ export function SidebarFooter({
'bg-gradient-to-t from-background/10 via-sidebar/50 to-transparent'
)}
>
{/* Wiki Link */}
{!hideWiki && (
<div className="p-2 pb-0">
<button
onClick={() => navigate({ to: '/wiki' })}
className={cn(
'group flex items-center w-full px-3 py-2.5 rounded-xl relative overflow-hidden titlebar-no-drag',
'transition-all duration-200 ease-out',
isActiveRoute('wiki')
? [
'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10',
'text-foreground font-medium',
'border border-brand-500/30',
'shadow-md shadow-brand-500/10',
]
: [
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50',
'border border-transparent hover:border-border/40',
'hover:shadow-sm',
],
sidebarOpen ? 'justify-start' : 'justify-center',
'hover:scale-[1.02] active:scale-[0.97]'
)}
title={!sidebarOpen ? 'Wiki' : undefined}
data-testid="wiki-link"
>
<BookOpen
className={cn(
'w-[18px] h-[18px] shrink-0 transition-all duration-200',
isActiveRoute('wiki')
? 'text-brand-500 drop-shadow-sm'
: 'group-hover:text-brand-400 group-hover:scale-110'
)}
/>
<span
className={cn(
'ml-3 font-medium text-sm flex-1 text-left',
sidebarOpen ? 'block' : 'hidden'
)}
>
Wiki
</span>
{!sidebarOpen && (
<span
className={cn(
'absolute left-full ml-3 px-2.5 py-1.5 rounded-lg',
'bg-popover text-popover-foreground text-xs font-medium',
'border border-border shadow-lg',
'opacity-0 group-hover:opacity-100',
'transition-all duration-200 whitespace-nowrap z-50',
'translate-x-1 group-hover:translate-x-0'
)}
>
Wiki
</span>
)}
</button>
</div>
)}
{/* Running Agents Link */}
{!hideRunningAgents && (
<div className="p-2 pb-0">

View File

@@ -12,10 +12,20 @@ export function useRunningAgents() {
try {
const api = getElectronAPI();
if (api.runningAgents) {
logger.debug('Fetching running agents count');
const result = await api.runningAgents.getAll();
if (result.success && result.runningAgents) {
logger.debug('Running agents count fetched', {
count: result.runningAgents.length,
});
setRunningAgentsCount(result.runningAgents.length);
} else {
logger.debug('Running agents count fetch returned empty/failed', {
success: result.success,
});
}
} else {
logger.debug('Running agents API not available');
}
} catch (error) {
logger.error('Error fetching running agents count:', error);
@@ -26,6 +36,7 @@ export function useRunningAgents() {
useEffect(() => {
const api = getElectronAPI();
if (!api.autoMode) {
logger.debug('Auto mode API not available for running agents hook');
// If autoMode is not available, still fetch initial count
fetchRunningAgentsCount();
return;
@@ -35,6 +46,9 @@ export function useRunningAgents() {
fetchRunningAgentsCount();
const unsubscribe = api.autoMode.onEvent((event) => {
logger.debug('Auto mode event for running agents hook', {
type: event.type,
});
// When a feature starts, completes, or errors, refresh the count
if (
event.type === 'auto_mode_feature_complete' ||
@@ -50,6 +64,22 @@ export function useRunningAgents() {
};
}, [fetchRunningAgentsCount]);
// Subscribe to backlog plan events to update running agents count
useEffect(() => {
const api = getElectronAPI();
if (!api.backlogPlan) return;
fetchRunningAgentsCount();
const unsubscribe = api.backlogPlan.onEvent(() => {
fetchRunningAgentsCount();
});
return () => {
unsubscribe();
};
}, [fetchRunningAgentsCount]);
return {
runningAgentsCount,
};