mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
Merge branch 'v0.14.0rc' of github.com:AutoMaker-Org/automaker into v0.14.0rc
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import type { NavigateOptions } from '@tanstack/react-router';
|
import type { NavigateOptions } from '@tanstack/react-router';
|
||||||
import { ChevronDown, Wrench, Github } from 'lucide-react';
|
import { ChevronDown, Wrench, Github } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { formatShortcut } from '@/store/app-store';
|
import { formatShortcut, useAppStore } from '@/store/app-store';
|
||||||
import type { NavSection } from '../types';
|
import type { NavSection } from '../types';
|
||||||
import type { Project } from '@/lib/electron';
|
import type { Project } from '@/lib/electron';
|
||||||
|
import type { SidebarStyle } from '@automaker/types';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -23,6 +24,7 @@ const sectionIcons: Record<string, React.ComponentType<{ className?: string }>>
|
|||||||
interface SidebarNavigationProps {
|
interface SidebarNavigationProps {
|
||||||
currentProject: Project | null;
|
currentProject: Project | null;
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
|
sidebarStyle: SidebarStyle;
|
||||||
navSections: NavSection[];
|
navSections: NavSection[];
|
||||||
isActiveRoute: (id: string) => boolean;
|
isActiveRoute: (id: string) => boolean;
|
||||||
navigate: (opts: NavigateOptions) => void;
|
navigate: (opts: NavigateOptions) => void;
|
||||||
@@ -32,6 +34,7 @@ interface SidebarNavigationProps {
|
|||||||
export function SidebarNavigation({
|
export function SidebarNavigation({
|
||||||
currentProject,
|
currentProject,
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
|
sidebarStyle,
|
||||||
navSections,
|
navSections,
|
||||||
isActiveRoute,
|
isActiveRoute,
|
||||||
navigate,
|
navigate,
|
||||||
@@ -39,21 +42,26 @@ export function SidebarNavigation({
|
|||||||
}: SidebarNavigationProps) {
|
}: SidebarNavigationProps) {
|
||||||
const navRef = useRef<HTMLElement>(null);
|
const navRef = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
// Track collapsed state for each collapsible section
|
// Get collapsed state from store (persisted across restarts)
|
||||||
const [collapsedSections, setCollapsedSections] = useState<Record<string, boolean>>({});
|
const { collapsedNavSections, setCollapsedNavSections, toggleNavSection } = useAppStore();
|
||||||
|
|
||||||
// Initialize collapsed state when sections change (e.g., GitHub section appears)
|
// Initialize collapsed state when sections change (e.g., GitHub section appears)
|
||||||
|
// Only set defaults for sections that don't have a persisted state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCollapsedSections((prev) => {
|
let hasNewSections = false;
|
||||||
const updated = { ...prev };
|
const updated = { ...collapsedNavSections };
|
||||||
|
|
||||||
navSections.forEach((section) => {
|
navSections.forEach((section) => {
|
||||||
if (section.collapsible && section.label && !(section.label in updated)) {
|
if (section.collapsible && section.label && !(section.label in updated)) {
|
||||||
updated[section.label] = section.defaultCollapsed ?? false;
|
updated[section.label] = section.defaultCollapsed ?? false;
|
||||||
|
hasNewSections = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return updated;
|
|
||||||
});
|
if (hasNewSections) {
|
||||||
}, [navSections]);
|
setCollapsedNavSections(updated);
|
||||||
|
}
|
||||||
|
}, [navSections, collapsedNavSections, setCollapsedNavSections]);
|
||||||
|
|
||||||
// Check scroll state
|
// Check scroll state
|
||||||
const checkScrollState = useCallback(() => {
|
const checkScrollState = useCallback(() => {
|
||||||
@@ -77,14 +85,7 @@ export function SidebarNavigation({
|
|||||||
nav.removeEventListener('scroll', checkScrollState);
|
nav.removeEventListener('scroll', checkScrollState);
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
};
|
};
|
||||||
}, [checkScrollState, collapsedSections]);
|
}, [checkScrollState, collapsedNavSections]);
|
||||||
|
|
||||||
const toggleSection = useCallback((label: string) => {
|
|
||||||
setCollapsedSections((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[label]: !prev[label],
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Filter sections: always show non-project sections, only show project sections when project exists
|
// Filter sections: always show non-project sections, only show project sections when project exists
|
||||||
const visibleSections = navSections.filter((section) => {
|
const visibleSections = navSections.filter((section) => {
|
||||||
@@ -97,10 +98,17 @@ export function SidebarNavigation({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav ref={navRef} className={cn('flex-1 overflow-y-auto scrollbar-hide px-3 pb-2 mt-1')}>
|
<nav
|
||||||
|
ref={navRef}
|
||||||
|
className={cn(
|
||||||
|
'flex-1 overflow-y-auto scrollbar-hide px-3 pb-2',
|
||||||
|
// Add top padding in discord mode since there's no header
|
||||||
|
sidebarStyle === 'discord' ? 'pt-3' : 'mt-1'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{/* Navigation sections */}
|
{/* Navigation sections */}
|
||||||
{visibleSections.map((section, sectionIdx) => {
|
{visibleSections.map((section, sectionIdx) => {
|
||||||
const isCollapsed = section.label ? collapsedSections[section.label] : false;
|
const isCollapsed = section.label ? collapsedNavSections[section.label] : false;
|
||||||
const isCollapsible = section.collapsible && section.label && sidebarOpen;
|
const isCollapsible = section.collapsible && section.label && sidebarOpen;
|
||||||
|
|
||||||
const SectionIcon = section.label ? sectionIcons[section.label] : null;
|
const SectionIcon = section.label ? sectionIcons[section.label] : null;
|
||||||
@@ -110,21 +118,37 @@ export function SidebarNavigation({
|
|||||||
{/* Section Label - clickable if collapsible (expanded sidebar) */}
|
{/* Section Label - clickable if collapsible (expanded sidebar) */}
|
||||||
{section.label && sidebarOpen && (
|
{section.label && sidebarOpen && (
|
||||||
<button
|
<button
|
||||||
onClick={() => isCollapsible && toggleSection(section.label!)}
|
onClick={() => isCollapsible && toggleNavSection(section.label!)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center w-full px-3 mb-1.5',
|
'group flex items-center w-full px-3 py-1.5 mb-1 rounded-md',
|
||||||
isCollapsible && 'cursor-pointer hover:text-foreground'
|
'transition-all duration-200 ease-out',
|
||||||
|
isCollapsible
|
||||||
|
? [
|
||||||
|
'cursor-pointer',
|
||||||
|
'hover:bg-accent/50 hover:text-foreground',
|
||||||
|
'border border-transparent hover:border-border/40',
|
||||||
|
]
|
||||||
|
: 'cursor-default'
|
||||||
)}
|
)}
|
||||||
disabled={!isCollapsible}
|
disabled={!isCollapsible}
|
||||||
>
|
>
|
||||||
<span className="text-[10px] font-semibold text-muted-foreground/70 uppercase tracking-widest">
|
<span
|
||||||
|
className={cn(
|
||||||
|
'text-[10px] font-semibold uppercase tracking-widest transition-colors duration-200',
|
||||||
|
isCollapsible
|
||||||
|
? 'text-muted-foreground/70 group-hover:text-foreground'
|
||||||
|
: 'text-muted-foreground/70'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{section.label}
|
{section.label}
|
||||||
</span>
|
</span>
|
||||||
{isCollapsible && (
|
{isCollapsible && (
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-3 h-3 ml-auto text-muted-foreground/50 transition-transform duration-200',
|
'w-3 h-3 ml-auto transition-all duration-200',
|
||||||
isCollapsed && '-rotate-90'
|
isCollapsed
|
||||||
|
? '-rotate-90 text-muted-foreground/50 group-hover:text-muted-foreground'
|
||||||
|
: 'text-muted-foreground/50 group-hover:text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export function Sidebar() {
|
|||||||
trashedProjects,
|
trashedProjects,
|
||||||
currentProject,
|
currentProject,
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
|
sidebarStyle,
|
||||||
mobileSidebarHidden,
|
mobileSidebarHidden,
|
||||||
projectHistory,
|
projectHistory,
|
||||||
upsertAndSetCurrentProject,
|
upsertAndSetCurrentProject,
|
||||||
@@ -381,6 +382,8 @@ export function Sidebar() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
|
{/* Only show header in unified mode - in discord mode, ProjectSwitcher has the logo */}
|
||||||
|
{sidebarStyle === 'unified' && (
|
||||||
<SidebarHeader
|
<SidebarHeader
|
||||||
sidebarOpen={sidebarOpen}
|
sidebarOpen={sidebarOpen}
|
||||||
currentProject={currentProject}
|
currentProject={currentProject}
|
||||||
@@ -388,10 +391,12 @@ export function Sidebar() {
|
|||||||
onOpenFolder={handleOpenFolder}
|
onOpenFolder={handleOpenFolder}
|
||||||
onProjectContextMenu={handleContextMenu}
|
onProjectContextMenu={handleContextMenu}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<SidebarNavigation
|
<SidebarNavigation
|
||||||
currentProject={currentProject}
|
currentProject={currentProject}
|
||||||
sidebarOpen={sidebarOpen}
|
sidebarOpen={sidebarOpen}
|
||||||
|
sidebarStyle={sidebarStyle}
|
||||||
navSections={navSections}
|
navSections={navSections}
|
||||||
isActiveRoute={isActiveRoute}
|
isActiveRoute={isActiveRoute}
|
||||||
navigate={navigate}
|
navigate={navigate}
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
export const commitTemplate = {
|
||||||
|
id: 'commit',
|
||||||
|
name: 'Commit Changes',
|
||||||
|
colorClass: 'bg-purple-500/20',
|
||||||
|
instructions: `## Commit Changes Step
|
||||||
|
|
||||||
|
# ⚠️ CRITICAL REQUIREMENT: YOU MUST COMMIT ALL CHANGES USING CONVENTIONAL COMMIT FORMAT ⚠️
|
||||||
|
|
||||||
|
**THIS IS NOT OPTIONAL. YOU MUST CREATE AND EXECUTE A GIT COMMIT WITH ALL CHANGES.**
|
||||||
|
|
||||||
|
This step requires you to:
|
||||||
|
1. **REVIEW** all changes made in this feature
|
||||||
|
2. **CREATE** a conventional commit message
|
||||||
|
3. **EXECUTE** the git commit command
|
||||||
|
|
||||||
|
**You cannot complete this step by only reviewing changes. You MUST execute the git commit command.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1: Review Phase
|
||||||
|
Review all changes made in this feature:
|
||||||
|
|
||||||
|
- Review all modified files using \`git status\` and \`git diff\`
|
||||||
|
- Identify the scope and nature of changes
|
||||||
|
- Determine the appropriate conventional commit type
|
||||||
|
- Identify any breaking changes that need to be documented
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2: Commit Phase - ⚠️ MANDATORY ACTION REQUIRED ⚠️
|
||||||
|
|
||||||
|
**YOU MUST NOW CREATE AND EXECUTE A GIT COMMIT WITH ALL CHANGES.**
|
||||||
|
|
||||||
|
**This is not optional. You must stage all changes and commit them using conventional commit format.**
|
||||||
|
|
||||||
|
#### Conventional Commit Format
|
||||||
|
|
||||||
|
Follow this format for your commit message:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
#### Commit Types (choose the most appropriate):
|
||||||
|
|
||||||
|
- **feat**: A new feature
|
||||||
|
- **fix**: A bug fix
|
||||||
|
- **docs**: Documentation only changes
|
||||||
|
- **style**: Code style changes (formatting, missing semicolons, etc.)
|
||||||
|
- **refactor**: Code refactoring without changing functionality
|
||||||
|
- **perf**: Performance improvements
|
||||||
|
- **test**: Adding or updating tests
|
||||||
|
- **chore**: Changes to build process, dependencies, or tooling
|
||||||
|
- **ci**: Changes to CI configuration
|
||||||
|
- **build**: Changes to build system or dependencies
|
||||||
|
|
||||||
|
#### Scope (optional but recommended):
|
||||||
|
- Component/module name (e.g., \`ui\`, \`server\`, \`auth\`)
|
||||||
|
- Feature area (e.g., \`board\`, \`pipeline\`, \`agent\`)
|
||||||
|
- Package name (e.g., \`@automaker/types\`)
|
||||||
|
|
||||||
|
#### Subject:
|
||||||
|
- Use imperative mood: "add" not "added" or "adds"
|
||||||
|
- First letter lowercase
|
||||||
|
- No period at the end
|
||||||
|
- Maximum 72 characters
|
||||||
|
|
||||||
|
#### Body (optional but recommended for significant changes):
|
||||||
|
- Explain the "what" and "why" of the change
|
||||||
|
- Reference related issues or PRs
|
||||||
|
- Separate from subject with blank line
|
||||||
|
- Wrap at 72 characters
|
||||||
|
|
||||||
|
#### Footer (optional):
|
||||||
|
- Breaking changes: \`BREAKING CHANGE: <description>\`
|
||||||
|
- Issue references: \`Closes #123\`, \`Fixes #456\`
|
||||||
|
|
||||||
|
#### Action Steps (You MUST complete these):
|
||||||
|
|
||||||
|
1. **Stage All Changes** - PREPARE FOR COMMIT:
|
||||||
|
- ✅ Run \`git add .\` or \`git add -A\` to stage all changes
|
||||||
|
- ✅ Verify staged changes with \`git status\`
|
||||||
|
- ✅ Ensure all relevant changes are staged
|
||||||
|
|
||||||
|
2. **Create Commit Message** - FOLLOW CONVENTIONAL COMMIT FORMAT:
|
||||||
|
- ✅ Determine the appropriate commit type based on changes
|
||||||
|
- ✅ Identify the scope (component/module/feature)
|
||||||
|
- ✅ Write a clear, imperative subject line
|
||||||
|
- ✅ Add a body explaining the changes (if significant)
|
||||||
|
- ✅ Include breaking changes in footer if applicable
|
||||||
|
- ✅ Reference related issues if applicable
|
||||||
|
|
||||||
|
3. **Execute Commit** - COMMIT THE CHANGES:
|
||||||
|
- ✅ Run \`git commit -m "<type>(<scope>): <subject>" -m "<body>"\` or use a multi-line commit message
|
||||||
|
- ✅ Verify the commit was created with \`git log -1\`
|
||||||
|
- ✅ **EXECUTE THE ACTUAL GIT COMMIT COMMAND**
|
||||||
|
|
||||||
|
#### Example Commit Messages:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
feat(ui): add pipeline step commit template
|
||||||
|
|
||||||
|
Add a new pipeline step template for committing changes using
|
||||||
|
conventional commit format. This ensures all commits follow
|
||||||
|
a consistent pattern for better changelog generation.
|
||||||
|
|
||||||
|
Closes #123
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
fix(server): resolve agent session timeout issue
|
||||||
|
|
||||||
|
The agent session was timing out prematurely due to incorrect
|
||||||
|
WebSocket heartbeat configuration. Updated heartbeat interval
|
||||||
|
to match server expectations.
|
||||||
|
|
||||||
|
Fixes #456
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
refactor(pipeline): extract step template logic
|
||||||
|
|
||||||
|
Extract step template loading and validation into separate
|
||||||
|
utility functions to improve code organization and testability.
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Summary Required
|
||||||
|
After completing BOTH review AND commit phases, provide:
|
||||||
|
- A summary of all changes that were committed
|
||||||
|
- **The exact commit message that was used (this proves you executed the commit)**
|
||||||
|
- The commit hash (if available)
|
||||||
|
- Any notes about the commit (breaking changes, related issues, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ⚠️ FINAL REMINDER ⚠️
|
||||||
|
|
||||||
|
**Reviewing changes without committing is INCOMPLETE and UNACCEPTABLE.**
|
||||||
|
|
||||||
|
**You MUST stage all changes and execute a git commit command.**
|
||||||
|
**You MUST use conventional commit format for the commit message.**
|
||||||
|
**You MUST show evidence of the commit execution in your summary.**
|
||||||
|
**This step is only complete when changes have been committed to git.**`,
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { uxReviewTemplate } from './ux-review';
|
|||||||
import { testingTemplate } from './testing';
|
import { testingTemplate } from './testing';
|
||||||
import { documentationTemplate } from './documentation';
|
import { documentationTemplate } from './documentation';
|
||||||
import { optimizationTemplate } from './optimization';
|
import { optimizationTemplate } from './optimization';
|
||||||
|
import { commitTemplate } from './commit';
|
||||||
|
|
||||||
export interface PipelineStepTemplate {
|
export interface PipelineStepTemplate {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -19,6 +20,7 @@ export const STEP_TEMPLATES: PipelineStepTemplate[] = [
|
|||||||
testingTemplate,
|
testingTemplate,
|
||||||
documentationTemplate,
|
documentationTemplate,
|
||||||
optimizationTemplate,
|
optimizationTemplate,
|
||||||
|
commitTemplate,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Helper to get template color class
|
// Helper to get template color class
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Palette, Moon, Sun, Type, Sparkles } from 'lucide-react';
|
import { Palette, Moon, Sun, Type, Sparkles, PanelLeft, Columns2 } from 'lucide-react';
|
||||||
import { darkThemes, lightThemes } from '@/config/theme-options';
|
import { darkThemes, lightThemes } from '@/config/theme-options';
|
||||||
import {
|
import {
|
||||||
UI_SANS_FONT_OPTIONS,
|
UI_SANS_FONT_OPTIONS,
|
||||||
@@ -12,6 +12,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { FontSelector } from '@/components/shared';
|
import { FontSelector } from '@/components/shared';
|
||||||
import type { Theme } from '../shared/types';
|
import type { Theme } from '../shared/types';
|
||||||
|
import type { SidebarStyle } from '@automaker/types';
|
||||||
|
|
||||||
interface AppearanceSectionProps {
|
interface AppearanceSectionProps {
|
||||||
effectiveTheme: Theme;
|
effectiveTheme: Theme;
|
||||||
@@ -26,6 +27,8 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
|
|||||||
setFontMono,
|
setFontMono,
|
||||||
disableSplashScreen,
|
disableSplashScreen,
|
||||||
setDisableSplashScreen,
|
setDisableSplashScreen,
|
||||||
|
sidebarStyle,
|
||||||
|
setSidebarStyle,
|
||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
|
|
||||||
// Determine if current theme is light or dark
|
// Determine if current theme is light or dark
|
||||||
@@ -221,6 +224,94 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar Style Section */}
|
||||||
|
<div className="space-y-4 pt-6 border-t border-border/50">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<PanelLeft className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<Label className="text-foreground font-medium">Sidebar Layout</Label>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground -mt-2 mb-4">
|
||||||
|
Choose between a modern unified sidebar or classic Discord-style layout with a separate
|
||||||
|
project switcher.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{/* Unified Sidebar Option */}
|
||||||
|
<button
|
||||||
|
onClick={() => setSidebarStyle('unified')}
|
||||||
|
className={cn(
|
||||||
|
'group flex flex-col items-center gap-3 p-4 rounded-xl',
|
||||||
|
'text-sm font-medium transition-all duration-200 ease-out',
|
||||||
|
sidebarStyle === 'unified'
|
||||||
|
? [
|
||||||
|
'bg-gradient-to-br from-brand-500/15 to-brand-600/10',
|
||||||
|
'border-2 border-brand-500/40',
|
||||||
|
'text-foreground',
|
||||||
|
'shadow-md shadow-brand-500/10',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'bg-accent/30 hover:bg-accent/50',
|
||||||
|
'border border-border/50 hover:border-border',
|
||||||
|
'text-muted-foreground hover:text-foreground',
|
||||||
|
'hover:shadow-sm',
|
||||||
|
],
|
||||||
|
'hover:scale-[1.02] active:scale-[0.98]'
|
||||||
|
)}
|
||||||
|
data-testid="sidebar-style-unified"
|
||||||
|
>
|
||||||
|
<PanelLeft
|
||||||
|
className={cn(
|
||||||
|
'w-8 h-8 transition-all duration-200',
|
||||||
|
sidebarStyle === 'unified' ? 'text-brand-500' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="font-medium">Unified</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
Single sidebar with project dropdown
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Discord-style Sidebar Option */}
|
||||||
|
<button
|
||||||
|
onClick={() => setSidebarStyle('discord')}
|
||||||
|
className={cn(
|
||||||
|
'group flex flex-col items-center gap-3 p-4 rounded-xl',
|
||||||
|
'text-sm font-medium transition-all duration-200 ease-out',
|
||||||
|
sidebarStyle === 'discord'
|
||||||
|
? [
|
||||||
|
'bg-gradient-to-br from-brand-500/15 to-brand-600/10',
|
||||||
|
'border-2 border-brand-500/40',
|
||||||
|
'text-foreground',
|
||||||
|
'shadow-md shadow-brand-500/10',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'bg-accent/30 hover:bg-accent/50',
|
||||||
|
'border border-border/50 hover:border-border',
|
||||||
|
'text-muted-foreground hover:text-foreground',
|
||||||
|
'hover:shadow-sm',
|
||||||
|
],
|
||||||
|
'hover:scale-[1.02] active:scale-[0.98]'
|
||||||
|
)}
|
||||||
|
data-testid="sidebar-style-discord"
|
||||||
|
>
|
||||||
|
<Columns2
|
||||||
|
className={cn(
|
||||||
|
'w-8 h-8 transition-all duration-200',
|
||||||
|
sidebarStyle === 'discord' ? 'text-brand-500' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="font-medium">Classic</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
Separate project switcher + sidebar
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -699,6 +699,8 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
|
|||||||
fontFamilySans: settings.fontFamilySans ?? null,
|
fontFamilySans: settings.fontFamilySans ?? null,
|
||||||
fontFamilyMono: settings.fontFamilyMono ?? null,
|
fontFamilyMono: settings.fontFamilyMono ?? null,
|
||||||
sidebarOpen: settings.sidebarOpen ?? true,
|
sidebarOpen: settings.sidebarOpen ?? true,
|
||||||
|
sidebarStyle: settings.sidebarStyle ?? 'unified',
|
||||||
|
collapsedNavSections: settings.collapsedNavSections ?? {},
|
||||||
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
chatHistoryOpen: settings.chatHistoryOpen ?? false,
|
||||||
maxConcurrency: settings.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,
|
maxConcurrency: settings.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,
|
||||||
autoModeByWorktree: restoredAutoModeByWorktree,
|
autoModeByWorktree: restoredAutoModeByWorktree,
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ const SETTINGS_FIELDS_TO_SYNC = [
|
|||||||
'terminalFontFamily', // Maps to terminalState.fontFamily
|
'terminalFontFamily', // Maps to terminalState.fontFamily
|
||||||
'openTerminalMode', // Maps to terminalState.openTerminalMode
|
'openTerminalMode', // Maps to terminalState.openTerminalMode
|
||||||
'sidebarOpen',
|
'sidebarOpen',
|
||||||
|
'sidebarStyle',
|
||||||
|
'collapsedNavSections',
|
||||||
'chatHistoryOpen',
|
'chatHistoryOpen',
|
||||||
'maxConcurrency',
|
'maxConcurrency',
|
||||||
'autoModeByWorktree', // Per-worktree auto mode settings (only maxConcurrency is persisted)
|
'autoModeByWorktree', // Per-worktree auto mode settings (only maxConcurrency is persisted)
|
||||||
@@ -698,6 +700,8 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
|
|||||||
useAppStore.setState({
|
useAppStore.setState({
|
||||||
theme: serverSettings.theme as unknown as ThemeMode,
|
theme: serverSettings.theme as unknown as ThemeMode,
|
||||||
sidebarOpen: serverSettings.sidebarOpen,
|
sidebarOpen: serverSettings.sidebarOpen,
|
||||||
|
sidebarStyle: serverSettings.sidebarStyle ?? 'unified',
|
||||||
|
collapsedNavSections: serverSettings.collapsedNavSections ?? {},
|
||||||
chatHistoryOpen: serverSettings.chatHistoryOpen,
|
chatHistoryOpen: serverSettings.chatHistoryOpen,
|
||||||
maxConcurrency: serverSettings.maxConcurrency,
|
maxConcurrency: serverSettings.maxConcurrency,
|
||||||
autoModeByWorktree: restoredAutoModeByWorktree,
|
autoModeByWorktree: restoredAutoModeByWorktree,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { createLogger } from '@automaker/utils/logger';
|
import { createLogger } from '@automaker/utils/logger';
|
||||||
import { Sidebar } from '@/components/layout/sidebar';
|
import { Sidebar } from '@/components/layout/sidebar';
|
||||||
|
import { ProjectSwitcher } from '@/components/layout/project-switcher';
|
||||||
import {
|
import {
|
||||||
FileBrowserProvider,
|
FileBrowserProvider,
|
||||||
useFileBrowser,
|
useFileBrowser,
|
||||||
@@ -167,6 +168,7 @@ function RootLayoutContent() {
|
|||||||
theme,
|
theme,
|
||||||
fontFamilySans,
|
fontFamilySans,
|
||||||
fontFamilyMono,
|
fontFamilyMono,
|
||||||
|
sidebarStyle,
|
||||||
skipSandboxWarning,
|
skipSandboxWarning,
|
||||||
setSkipSandboxWarning,
|
setSkipSandboxWarning,
|
||||||
fetchCodexModels,
|
fetchCodexModels,
|
||||||
@@ -860,6 +862,8 @@ function RootLayoutContent() {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{/* Discord-style layout: narrow project switcher + expandable sidebar */}
|
||||||
|
{sidebarStyle === 'discord' && <ProjectSwitcher />}
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div
|
<div
|
||||||
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import type {
|
|||||||
EventHook,
|
EventHook,
|
||||||
ClaudeApiProfile,
|
ClaudeApiProfile,
|
||||||
ClaudeCompatibleProvider,
|
ClaudeCompatibleProvider,
|
||||||
|
SidebarStyle,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
getAllCursorModelIds,
|
getAllCursorModelIds,
|
||||||
@@ -610,6 +611,8 @@ export interface AppState {
|
|||||||
// View state
|
// View state
|
||||||
currentView: ViewMode;
|
currentView: ViewMode;
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
|
sidebarStyle: SidebarStyle; // 'unified' (modern) or 'discord' (classic two-sidebar layout)
|
||||||
|
collapsedNavSections: Record<string, boolean>; // Collapsed state of nav sections (key: section label)
|
||||||
mobileSidebarHidden: boolean; // Completely hides sidebar on mobile
|
mobileSidebarHidden: boolean; // Completely hides sidebar on mobile
|
||||||
|
|
||||||
// Agent Session state (per-project, keyed by project path)
|
// Agent Session state (per-project, keyed by project path)
|
||||||
@@ -1049,6 +1052,9 @@ export interface AppActions {
|
|||||||
setCurrentView: (view: ViewMode) => void;
|
setCurrentView: (view: ViewMode) => void;
|
||||||
toggleSidebar: () => void;
|
toggleSidebar: () => void;
|
||||||
setSidebarOpen: (open: boolean) => void;
|
setSidebarOpen: (open: boolean) => void;
|
||||||
|
setSidebarStyle: (style: SidebarStyle) => void;
|
||||||
|
setCollapsedNavSections: (sections: Record<string, boolean>) => void;
|
||||||
|
toggleNavSection: (sectionLabel: string) => void;
|
||||||
toggleMobileSidebarHidden: () => void;
|
toggleMobileSidebarHidden: () => void;
|
||||||
setMobileSidebarHidden: (hidden: boolean) => void;
|
setMobileSidebarHidden: (hidden: boolean) => void;
|
||||||
|
|
||||||
@@ -1477,6 +1483,8 @@ const initialState: AppState = {
|
|||||||
projectHistoryIndex: -1,
|
projectHistoryIndex: -1,
|
||||||
currentView: 'welcome',
|
currentView: 'welcome',
|
||||||
sidebarOpen: true,
|
sidebarOpen: true,
|
||||||
|
sidebarStyle: 'unified', // Default to modern unified sidebar
|
||||||
|
collapsedNavSections: {}, // Nav sections expanded by default (sections set their own defaults)
|
||||||
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
mobileSidebarHidden: false, // Sidebar visible by default on mobile
|
||||||
lastSelectedSessionByProject: {},
|
lastSelectedSessionByProject: {},
|
||||||
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
|
||||||
@@ -1936,6 +1944,15 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
setCurrentView: (view) => set({ currentView: view }),
|
setCurrentView: (view) => set({ currentView: view }),
|
||||||
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
|
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
|
||||||
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
||||||
|
setSidebarStyle: (style) => set({ sidebarStyle: style }),
|
||||||
|
setCollapsedNavSections: (sections) => set({ collapsedNavSections: sections }),
|
||||||
|
toggleNavSection: (sectionLabel) =>
|
||||||
|
set((state) => ({
|
||||||
|
collapsedNavSections: {
|
||||||
|
...state.collapsedNavSections,
|
||||||
|
[sectionLabel]: !state.collapsedNavSections[sectionLabel],
|
||||||
|
},
|
||||||
|
})),
|
||||||
toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
|
toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
|
||||||
setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),
|
setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),
|
||||||
|
|
||||||
|
|||||||
431
docs/prd-to-features-guide.md
Normal file
431
docs/prd-to-features-guide.md
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
# PRD to Automaker Features Guide
|
||||||
|
|
||||||
|
This guide helps Claude generate properly structured Automaker features from a Product Requirements Document (PRD). Use this in new projects to create feature folders that Automaker can execute.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. Place your PRD file in the project (e.g., `PRD.md` or `.automaker/context/PRD.md`)
|
||||||
|
2. Create `.automaker/features/` directory
|
||||||
|
3. Use this guide to generate `feature.json` files for each feature phase
|
||||||
|
4. Run features in Automaker sequentially or in parallel based on dependencies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature JSON Schema
|
||||||
|
|
||||||
|
### Minimal Required Fields
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "feature-unique-identifier",
|
||||||
|
"category": "Core",
|
||||||
|
"title": "Feature Title",
|
||||||
|
"description": "Detailed description of what needs to be implemented",
|
||||||
|
"status": "backlog",
|
||||||
|
"priority": 1,
|
||||||
|
"imagePaths": [],
|
||||||
|
"textFilePaths": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complete Feature Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "feature-unique-identifier",
|
||||||
|
"category": "Core | UI/UX | AI Agent | Infrastructure | Testing | From GitHub",
|
||||||
|
"title": "Short descriptive title",
|
||||||
|
"description": "Detailed implementation description with requirements",
|
||||||
|
"status": "backlog | pending | running | completed | failed | verified | waiting_approval",
|
||||||
|
"priority": 1,
|
||||||
|
"complexity": "simple | moderate | complex",
|
||||||
|
"dependencies": ["feature-id-1", "feature-id-2"],
|
||||||
|
"createdAt": "2026-01-23T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-01-23T00:00:00.000Z",
|
||||||
|
"branchName": null,
|
||||||
|
"descriptionHistory": [],
|
||||||
|
"skipTests": false,
|
||||||
|
"model": "claude-sonnet | claude-opus | claude-haiku",
|
||||||
|
"thinkingLevel": "none | low | medium | high | ultrathink",
|
||||||
|
"reasoningEffort": "none | low | medium | high",
|
||||||
|
"imagePaths": [],
|
||||||
|
"textFilePaths": [],
|
||||||
|
"planningMode": "none | spec | full",
|
||||||
|
"requirePlanApproval": false,
|
||||||
|
"workMode": "auto | custom"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field Descriptions
|
||||||
|
|
||||||
|
### Core Fields
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
| --------------- | ------ | -------- | --------------------------------------------------------------------------------- |
|
||||||
|
| `id` | string | Yes | Unique identifier. Use format: `feature-{descriptive-name}` or `phase-{n}-{name}` |
|
||||||
|
| `category` | string | Yes | Grouping category for the feature |
|
||||||
|
| `title` | string | Yes | Short, descriptive title (3-8 words) |
|
||||||
|
| `description` | string | Yes | Detailed implementation requirements |
|
||||||
|
| `status` | string | Yes | Current state. **Must be `backlog` for Automaker to execute** |
|
||||||
|
| `priority` | number | Yes | Execution priority (1 = highest, higher numbers = lower priority) |
|
||||||
|
| `imagePaths` | array | Yes | Must be empty `[]` - Automaker populates this automatically |
|
||||||
|
| `textFilePaths` | array | Yes | Must be empty `[]` - Automaker populates this automatically |
|
||||||
|
|
||||||
|
### Optional Fields
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
| --------------------- | -------- | -------- | -------------------------------------------------------------------------------------- |
|
||||||
|
| `complexity` | string | moderate | `simple` (< 1 hour), `moderate` (1-4 hours), `complex` (> 4 hours) |
|
||||||
|
| `dependencies` | string[] | [] | Array of feature IDs that must complete first |
|
||||||
|
| `skipTests` | boolean | false | Skip test execution during verification |
|
||||||
|
| `model` | string | - | AI model: `claude-sonnet` (balanced), `claude-opus` (complex), `claude-haiku` (simple) |
|
||||||
|
| `thinkingLevel` | string | none | Extended thinking: `none`, `low`, `medium`, `high`, `ultrathink` |
|
||||||
|
| `planningMode` | string | none | `none` (direct), `spec` (generate spec first), `full` (spec + tool exploration) |
|
||||||
|
| `requirePlanApproval` | boolean | false | Pause for human approval before execution |
|
||||||
|
| `workMode` | string | auto | `auto` (continuous), `custom` (step-by-step) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Writing Effective Descriptions
|
||||||
|
|
||||||
|
### Structure for Complex Features
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Brief summary of what this feature accomplishes.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Requirement 1: Specific implementation detail
|
||||||
|
- Requirement 2: Another specific detail
|
||||||
|
- Requirement 3: Edge case to handle
|
||||||
|
|
||||||
|
## Technical Approach
|
||||||
|
|
||||||
|
- Use existing pattern from X
|
||||||
|
- Modify files A, B, C
|
||||||
|
- Follow the Y architectural pattern
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- GIVEN condition, WHEN action, THEN expected result
|
||||||
|
- GIVEN another condition, WHEN action, THEN expected result
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `path/to/file1.ts` - Purpose
|
||||||
|
- `path/to/file2.tsx` - Purpose
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
- Handle empty state
|
||||||
|
- Handle error conditions
|
||||||
|
- Handle concurrent operations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Structure for Simple Features
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Add [feature] to [location].
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Specific requirement 1
|
||||||
|
- Specific requirement 2
|
||||||
|
|
||||||
|
Files: `path/to/main/file.ts`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phasing Strategy
|
||||||
|
|
||||||
|
### When to Create Phases
|
||||||
|
|
||||||
|
Create separate features (phases) when:
|
||||||
|
|
||||||
|
1. Features have clear dependencies (Phase 2 needs Phase 1's types)
|
||||||
|
2. Different complexity levels (separate simple setup from complex logic)
|
||||||
|
3. Different areas of codebase (backend vs frontend)
|
||||||
|
4. Risk isolation (core changes separate from UI changes)
|
||||||
|
|
||||||
|
### Recommended Phase Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Foundation / Types / Schema
|
||||||
|
Phase 2: Backend / Service Layer
|
||||||
|
Phase 3: API Routes / Endpoints
|
||||||
|
Phase 4: Frontend / UI Components
|
||||||
|
Phase 5: Integration / Testing
|
||||||
|
Phase 6: Polish / Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase Naming Convention
|
||||||
|
|
||||||
|
```
|
||||||
|
phase-1-foundation
|
||||||
|
phase-2-backend-service
|
||||||
|
phase-3-api-routes
|
||||||
|
phase-4-frontend-ui
|
||||||
|
phase-5-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Converting PRD to Features
|
||||||
|
|
||||||
|
### Input PRD Section
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## User Authentication Feature
|
||||||
|
|
||||||
|
Users should be able to log in with email/password and OAuth providers.
|
||||||
|
The system should support session management and secure token storage.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Feature Files
|
||||||
|
|
||||||
|
**Phase 1: Types and Schema**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "phase-1-auth-types",
|
||||||
|
"category": "Core",
|
||||||
|
"title": "Authentication Types and Schema",
|
||||||
|
"description": "Define TypeScript types and database schema for authentication.\n\nRequirements:\n- Add User, Session, and AuthToken types to @automaker/types\n- Create database migration for users and sessions tables\n- Define AuthProvider enum (email, google, github)\n\nFiles:\n- libs/types/src/auth.ts\n- libs/types/src/index.ts\n- apps/server/src/db/migrations/",
|
||||||
|
"status": "backlog",
|
||||||
|
"priority": 1,
|
||||||
|
"complexity": "simple",
|
||||||
|
"dependencies": [],
|
||||||
|
"model": "claude-sonnet",
|
||||||
|
"planningMode": "none"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 2: Backend Service**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "phase-2-auth-service",
|
||||||
|
"category": "Core",
|
||||||
|
"title": "Authentication Service Layer",
|
||||||
|
"description": "Implement authentication service with email/password and OAuth support.\n\nRequirements:\n- Create AuthService class with login, logout, register methods\n- Implement password hashing with bcrypt\n- Add OAuth provider integration (Google, GitHub)\n- Session management with secure token generation\n\nAcceptance Criteria:\n- GIVEN valid credentials, WHEN user logs in, THEN session token is returned\n- GIVEN invalid credentials, WHEN user logs in, THEN appropriate error is returned\n- GIVEN OAuth callback, WHEN user authenticates, THEN user is created/updated and session started\n\nFiles:\n- apps/server/src/services/auth-service.ts\n- apps/server/src/services/oauth-service.ts\n- apps/server/src/lib/password.ts",
|
||||||
|
"status": "backlog",
|
||||||
|
"priority": 1,
|
||||||
|
"complexity": "complex",
|
||||||
|
"dependencies": ["phase-1-auth-types"],
|
||||||
|
"model": "claude-opus",
|
||||||
|
"thinkingLevel": "medium",
|
||||||
|
"planningMode": "spec"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 3: API Routes**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "phase-3-auth-routes",
|
||||||
|
"category": "Core",
|
||||||
|
"title": "Authentication API Endpoints",
|
||||||
|
"description": "Create REST API endpoints for authentication.\n\nEndpoints:\n- POST /api/auth/login - Email/password login\n- POST /api/auth/register - New user registration\n- POST /api/auth/logout - End session\n- GET /api/auth/me - Get current user\n- GET /api/auth/oauth/:provider - OAuth initiation\n- GET /api/auth/oauth/:provider/callback - OAuth callback\n\nFiles:\n- apps/server/src/routes/auth/index.ts\n- apps/server/src/routes/auth/routes/*.ts",
|
||||||
|
"status": "backlog",
|
||||||
|
"priority": 1,
|
||||||
|
"complexity": "moderate",
|
||||||
|
"dependencies": ["phase-2-auth-service"],
|
||||||
|
"model": "claude-sonnet",
|
||||||
|
"planningMode": "spec"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 4: Frontend UI**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "phase-4-auth-ui",
|
||||||
|
"category": "UI/UX",
|
||||||
|
"title": "Authentication UI Components",
|
||||||
|
"description": "Create login, register, and profile UI components.\n\nComponents:\n- LoginForm with email/password fields and OAuth buttons\n- RegisterForm with validation\n- UserMenu dropdown showing logged-in user\n- AuthProvider context for app-wide auth state\n\nRoutes:\n- /login - Login page\n- /register - Registration page\n- /profile - User profile page\n\nFiles:\n- apps/ui/src/components/auth/*.tsx\n- apps/ui/src/routes/login.tsx\n- apps/ui/src/routes/register.tsx\n- apps/ui/src/hooks/use-auth.ts\n- apps/ui/src/store/auth-store.ts",
|
||||||
|
"status": "backlog",
|
||||||
|
"priority": 1,
|
||||||
|
"complexity": "moderate",
|
||||||
|
"dependencies": ["phase-3-auth-routes"],
|
||||||
|
"model": "claude-sonnet",
|
||||||
|
"planningMode": "spec"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallel vs Sequential Execution
|
||||||
|
|
||||||
|
### Features that CAN run in parallel
|
||||||
|
|
||||||
|
- Different areas of codebase with no shared files
|
||||||
|
- Independent bug fixes
|
||||||
|
- Documentation updates
|
||||||
|
- UI components that don't share state
|
||||||
|
- Separate service implementations
|
||||||
|
|
||||||
|
### Features that MUST run sequentially
|
||||||
|
|
||||||
|
- Type definitions before implementations
|
||||||
|
- Backend before frontend (if frontend calls backend)
|
||||||
|
- Database schema before data access
|
||||||
|
- Shared utilities before consumers
|
||||||
|
|
||||||
|
### Expressing Dependencies
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "feature-frontend",
|
||||||
|
"dependencies": ["feature-types", "feature-backend"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Features with dependencies won't start until all dependencies are completed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Model Selection Guide
|
||||||
|
|
||||||
|
| Complexity | Recommended Model | Thinking Level | Planning Mode |
|
||||||
|
| --------------------- | ----------------------------- | -------------- | ------------- |
|
||||||
|
| Simple (< 1 hour) | claude-haiku or claude-sonnet | none | none |
|
||||||
|
| Moderate (1-4 hours) | claude-sonnet | none or low | spec |
|
||||||
|
| Complex (> 4 hours) | claude-opus | medium or high | spec or full |
|
||||||
|
| Critical/Architecture | claude-opus | ultrathink | full |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.automaker/
|
||||||
|
└── features/
|
||||||
|
├── phase-1-foundation/
|
||||||
|
│ └── feature.json
|
||||||
|
├── phase-2-backend/
|
||||||
|
│ └── feature.json
|
||||||
|
├── phase-3-api/
|
||||||
|
│ └── feature.json
|
||||||
|
└── phase-4-frontend/
|
||||||
|
└── feature.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Each feature gets its own directory. The directory name should match the feature ID.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Automation Script
|
||||||
|
|
||||||
|
Create features programmatically with this pattern:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# create-feature.sh
|
||||||
|
|
||||||
|
FEATURE_ID=$1
|
||||||
|
TITLE=$2
|
||||||
|
DESCRIPTION=$3
|
||||||
|
PRIORITY=${4:-1}
|
||||||
|
|
||||||
|
mkdir -p ".automaker/features/$FEATURE_ID"
|
||||||
|
cat > ".automaker/features/$FEATURE_ID/feature.json" << EOF
|
||||||
|
{
|
||||||
|
"id": "$FEATURE_ID",
|
||||||
|
"category": "Core",
|
||||||
|
"title": "$TITLE",
|
||||||
|
"description": "$DESCRIPTION",
|
||||||
|
"status": "backlog",
|
||||||
|
"priority": $PRIORITY,
|
||||||
|
"complexity": "moderate",
|
||||||
|
"dependencies": [],
|
||||||
|
"createdAt": "$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")",
|
||||||
|
"updatedAt": "$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")",
|
||||||
|
"model": "claude-sonnet",
|
||||||
|
"planningMode": "spec"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Created feature: $FEATURE_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### DO
|
||||||
|
|
||||||
|
- Keep descriptions detailed but focused
|
||||||
|
- Include specific file paths when known
|
||||||
|
- Use GIVEN/WHEN/THEN format for acceptance criteria
|
||||||
|
- Set realistic complexity estimates
|
||||||
|
- Define clear dependencies between phases
|
||||||
|
- Use `spec` planning mode for moderate+ complexity
|
||||||
|
- Include edge cases in descriptions
|
||||||
|
|
||||||
|
### DON'T
|
||||||
|
|
||||||
|
- Create features that are too large (> 8 hours)
|
||||||
|
- Leave descriptions vague ("make it better")
|
||||||
|
- Skip dependency definitions
|
||||||
|
- Use `ultrathink` for simple tasks (wastes tokens)
|
||||||
|
- Create circular dependencies
|
||||||
|
- Put multiple unrelated changes in one feature
|
||||||
|
- Put values in `imagePaths` or `textFilePaths` (must be empty `[]`, Automaker populates them)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Checklist
|
||||||
|
|
||||||
|
Before running features, verify:
|
||||||
|
|
||||||
|
- [ ] Each feature has a unique ID
|
||||||
|
- [ ] All dependencies exist and are spelled correctly
|
||||||
|
- [ ] No circular dependencies
|
||||||
|
- [ ] Priorities are assigned meaningfully
|
||||||
|
- [ ] Complex features have appropriate model/thinking level
|
||||||
|
- [ ] Descriptions include enough context for implementation
|
||||||
|
- [ ] File paths match actual project structure
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference: Status Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
backlog → pending → running → completed → verified
|
||||||
|
↘ failed
|
||||||
|
↘ waiting_approval → completed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** Features must start in `backlog` status to be executable by Automaker. The system moves them through the pipeline automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template: New Feature
|
||||||
|
|
||||||
|
Copy and customize:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "feature-CHANGE-ME",
|
||||||
|
"category": "Core",
|
||||||
|
"title": "CHANGE ME: Feature Title",
|
||||||
|
"description": "## Overview\nBrief description.\n\n## Requirements\n- Requirement 1\n- Requirement 2\n\n## Files\n- path/to/file.ts",
|
||||||
|
"status": "backlog",
|
||||||
|
"priority": 1,
|
||||||
|
"complexity": "moderate",
|
||||||
|
"dependencies": [],
|
||||||
|
"createdAt": "2026-01-23T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-01-23T00:00:00.000Z",
|
||||||
|
"imagePaths": [],
|
||||||
|
"textFilePaths": [],
|
||||||
|
"model": "claude-sonnet",
|
||||||
|
"planningMode": "spec",
|
||||||
|
"skipTests": false,
|
||||||
|
"workMode": "auto"
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -145,6 +145,7 @@ export { DEFAULT_PROMPT_CUSTOMIZATION } from './prompts.js';
|
|||||||
// Settings types and constants
|
// Settings types and constants
|
||||||
export type {
|
export type {
|
||||||
ThemeMode,
|
ThemeMode,
|
||||||
|
SidebarStyle,
|
||||||
PlanningMode,
|
PlanningMode,
|
||||||
ThinkingLevel,
|
ThinkingLevel,
|
||||||
ServerLogLevel,
|
ServerLogLevel,
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ export type ServerLogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|||||||
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
|
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
|
||||||
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'ultrathink';
|
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'ultrathink';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SidebarStyle - Sidebar layout style options
|
||||||
|
*
|
||||||
|
* - 'unified': Single sidebar with integrated project dropdown (default, modern)
|
||||||
|
* - 'discord': Two sidebars - narrow project switcher + expandable navigation sidebar (classic)
|
||||||
|
*/
|
||||||
|
export type SidebarStyle = 'unified' | 'discord';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thinking token budget mapping based on Claude SDK documentation.
|
* Thinking token budget mapping based on Claude SDK documentation.
|
||||||
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
||||||
@@ -836,6 +844,10 @@ export interface GlobalSettings {
|
|||||||
// UI State Preferences
|
// UI State Preferences
|
||||||
/** Whether sidebar is currently open */
|
/** Whether sidebar is currently open */
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
|
/** Sidebar layout style ('unified' = modern single sidebar, 'discord' = classic two-sidebar layout) */
|
||||||
|
sidebarStyle: SidebarStyle;
|
||||||
|
/** Collapsed state of sidebar navigation sections (key: section label, value: is collapsed) */
|
||||||
|
collapsedNavSections?: Record<string, boolean>;
|
||||||
/** Whether chat history panel is open */
|
/** Whether chat history panel is open */
|
||||||
chatHistoryOpen: boolean;
|
chatHistoryOpen: boolean;
|
||||||
|
|
||||||
@@ -1314,6 +1326,8 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
|||||||
skipClaudeSetup: false,
|
skipClaudeSetup: false,
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
sidebarOpen: true,
|
sidebarOpen: true,
|
||||||
|
sidebarStyle: 'unified',
|
||||||
|
collapsedNavSections: {},
|
||||||
chatHistoryOpen: false,
|
chatHistoryOpen: false,
|
||||||
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
|
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
|
||||||
defaultSkipTests: true,
|
defaultSkipTests: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user