- Added performance best-practices guide for React and main process tuning 📘 - Introduced `buildFileTreeIndices` for fast, reusable markdown file lookups ⚡ - Updated `remarkFileLinks` to accept prebuilt indices with backward compatibility 🧩 - Memoized file-tree indices in AutoRun, FilePreview, and MarkdownRenderer for speed 🚀 - Expanded test suite to cover indices building and indexed link resolution 🧪 - Made temp-file cleanup asynchronous to keep the main thread snappy 🧹 - Cached shell path resolution to avoid repeated synchronous filesystem checks 🗄️ - Precompiled leading-emoji regex to speed up session name sorting 🔤 - Consolidated SessionList filtering/grouping/sorting into one efficient memo pass 🧠 - Memoized TabBar tabs, computed labels, and style objects to cut rerenders 🎛️
40 KiB
CLAUDE.md
Essential guidance for working with this codebase. For detailed architecture, see ARCHITECTURE.md. For development setup and processes, see CONTRIBUTING.md.
Standardized Vernacular
Use these terms consistently in code, comments, and documentation:
UI Components
- Left Bar - Left sidebar with session list and groups (
SessionList.tsx) - Right Bar - Right sidebar with Files, History, Auto Run tabs (
RightPanel.tsx) - Main Window - Center workspace (
MainPanel.tsx)- AI Terminal - Main window in AI mode (interacting with AI agents)
- Command Terminal - Main window in terminal/shell mode
- System Log Viewer - Special view for system logs (
LogViewer.tsx)
Session States (color-coded)
- Green - Ready/idle
- Yellow - Agent thinking/busy
- Red - No connection/error
- Pulsing Orange - Connecting
Project Overview
Maestro is an Electron desktop app for managing multiple AI coding assistants (Claude Code, OpenAI Codex, Gemini CLI, Qwen3 Coder) simultaneously with a keyboard-first interface.
Quick Commands
npm run dev # Development with hot reload (isolated data, can run alongside production)
npm run dev:prod-data # Development using production data (close production app first)
npm run dev:web # Web interface development
npm run build # Full production build
npm run clean # Clean build artifacts
npm run lint # TypeScript type checking (all configs)
npm run lint:eslint # ESLint code quality checks
npm run package # Package for all platforms
npm run test # Run test suite
npm run test:watch # Run tests in watch mode
Architecture at a Glance
src/
├── main/ # Electron main process (Node.js)
│ ├── index.ts # Entry point, IPC handlers
│ ├── process-manager.ts # Process spawning (PTY + child_process)
│ ├── preload.ts # Secure IPC bridge
│ ├── agent-detector.ts # Agent detection and configuration
│ ├── agent-capabilities.ts # Agent capability definitions
│ ├── agent-session-storage.ts # Session storage interface
│ ├── parsers/ # Agent output parsers
│ │ ├── agent-output-parser.ts # Parser interface
│ │ ├── claude-output-parser.ts # Claude Code parser
│ │ ├── opencode-output-parser.ts # OpenCode parser
│ │ └── error-patterns.ts # Error detection patterns
│ ├── storage/ # Session storage implementations
│ │ ├── claude-session-storage.ts
│ │ └── opencode-session-storage.ts
│ ├── tunnel-manager.ts # Cloudflare tunnel support
│ ├── web-server.ts # Fastify server for web/mobile interface
│ └── utils/execFile.ts # Safe command execution
│
├── renderer/ # React frontend (desktop)
│ ├── App.tsx # Main coordinator
│ ├── components/ # UI components
│ ├── hooks/ # Custom React hooks
│ ├── services/ # IPC wrappers (git.ts, process.ts)
│ ├── constants/ # Themes, shortcuts, priorities
│ └── contexts/ # Layer stack context
│
├── web/ # Web/mobile interface
│ ├── mobile/ # Mobile-optimized React app
│ ├── components/ # Shared web components
│ └── hooks/ # Web-specific hooks
│
├── cli/ # CLI tooling for batch automation
│ ├── commands/ # CLI command implementations
│ ├── services/ # Playbook and batch processing
│ └── index.ts # CLI entry point
│
├── prompts/ # System prompts (editable .md files)
│ ├── wizard-*.md # Wizard conversation prompts
│ ├── autorun-*.md # Auto Run default prompts
│ └── index.ts # Central exports
│
├── shared/ # Shared types and utilities
│ ├── types.ts # Common type definitions
│ └── templateVariables.ts # Template variable processing
│
└── docs/ # Mintlify documentation (docs.runmaestro.ai)
├── docs.json # Navigation and configuration
├── screenshots/ # All documentation screenshots
└── *.md # Documentation pages
Key Files for Common Tasks
| Task | Primary Files |
|---|---|
| Add IPC handler | src/main/index.ts, src/main/preload.ts |
| Add UI component | src/renderer/components/ |
| Add web/mobile component | src/web/components/, src/web/mobile/ |
| Add keyboard shortcut | src/renderer/constants/shortcuts.ts, App.tsx |
| Add theme | src/renderer/constants/themes.ts |
| Add modal | Component + src/renderer/constants/modalPriorities.ts |
| Add tab overlay menu | See Tab Hover Overlay Menu pattern in src/renderer/components/TabBar.tsx |
| Add setting | src/renderer/hooks/useSettings.ts, src/main/index.ts |
| Add template variable | src/shared/templateVariables.ts, src/renderer/utils/templateVariables.ts |
| Modify system prompts | src/prompts/*.md (wizard, Auto Run, etc.) |
| Add Spec-Kit command | src/prompts/speckit/, src/main/speckit-manager.ts |
| Add OpenSpec command | src/prompts/openspec/, src/main/openspec-manager.ts |
| Add CLI command | src/cli/commands/, src/cli/index.ts |
| Configure agent | src/main/agent-detector.ts, src/main/agent-capabilities.ts |
| Add agent output parser | src/main/parsers/, src/main/parsers/index.ts |
| Add agent session storage | src/main/storage/, src/main/agent-session-storage.ts |
| Add agent error patterns | src/main/parsers/error-patterns.ts |
| Add playbook feature | src/cli/services/playbooks.ts |
| Add marketplace playbook | src/main/ipc/handlers/marketplace.ts (import from GitHub) |
| Playbook import/export | src/main/ipc/handlers/playbooks.ts (ZIP handling with assets) |
| Modify wizard flow | src/renderer/components/Wizard/ (see Onboarding Wizard section) |
| Add tour step | src/renderer/components/Wizard/tour/tourSteps.ts |
| Modify file linking | src/renderer/utils/remarkFileLinks.ts (remark plugin for [[wiki]] and path links) |
| Add documentation page | docs/*.md, docs/docs.json (navigation) |
| Add documentation screenshot | docs/screenshots/ (PNG, kebab-case naming) |
| MCP server integration | See MCP Server docs |
| Add stats/analytics feature | src/main/stats-db.ts, src/main/ipc/handlers/stats.ts |
| Add Usage Dashboard chart | src/renderer/components/UsageDashboard/ |
| Add Document Graph feature | src/renderer/components/DocumentGraph/, src/main/ipc/handlers/documentGraph.ts |
| Add colorblind palette | src/renderer/constants/colorblindPalettes.ts |
| Add performance metrics | src/shared/performance-metrics.ts |
| Add power management | src/main/power-manager.ts, src/main/ipc/handlers/system.ts |
Core Patterns
1. Process Management
Each session runs two processes simultaneously:
- AI agent process (Claude Code, etc.) - spawned with
-aisuffix - Terminal process (PTY shell) - spawned with
-terminalsuffix
// Session stores both PIDs
session.aiPid // AI agent process
session.terminalPid // Terminal process
2. Security Requirements
Always use execFileNoThrow for external commands:
import { execFileNoThrow } from './utils/execFile';
const result = await execFileNoThrow('git', ['status'], cwd);
// Returns: { stdout, stderr, exitCode } - never throws
Never use shell-based command execution - it creates injection vulnerabilities. The execFileNoThrow utility is the safe alternative.
3. Settings Persistence
Add new settings in useSettings.ts:
// 1. Add state with default value
const [mySetting, setMySettingState] = useState(defaultValue);
// 2. Add wrapper that persists
const setMySetting = (value) => {
setMySettingState(value);
window.maestro.settings.set('mySetting', value);
};
// 3. Load from batch response in useEffect (settings use batch loading)
// In the loadSettings useEffect, extract from allSettings object:
const allSettings = await window.maestro.settings.getAll();
const savedMySetting = allSettings['mySetting'];
if (savedMySetting !== undefined) setMySettingState(savedMySetting);
4. Adding Modals
- Create component in
src/renderer/components/ - Add priority in
src/renderer/constants/modalPriorities.ts - Register with layer stack:
import { useLayerStack } from '../contexts/LayerStackContext';
import { MODAL_PRIORITIES } from '../constants/modalPriorities';
const { registerLayer, unregisterLayer } = useLayerStack();
const onCloseRef = useRef(onClose);
onCloseRef.current = onClose;
useEffect(() => {
if (isOpen) {
const id = registerLayer({
type: 'modal',
priority: MODAL_PRIORITIES.YOUR_MODAL,
onEscape: () => onCloseRef.current(),
});
return () => unregisterLayer(id);
}
}, [isOpen, registerLayer, unregisterLayer]);
5. Theme Colors
Themes have 13 required colors. Use inline styles for theme colors:
style={{ color: theme.colors.textMain }} // Correct
className="text-gray-500" // Wrong for themed text
6. Multi-Tab Sessions
Sessions support multiple AI conversation tabs:
// Each session has an array of tabs
session.aiTabs: AITab[]
session.activeTabId: string
// Each tab maintains its own conversation
interface AITab {
id: string;
name: string;
logs: LogEntry[]; // Tab-specific history
agentSessionId?: string; // Agent session continuity
}
// Tab operations
const activeTab = session.aiTabs.find(t => t.id === session.activeTabId);
7. Execution Queue
Messages are queued when the AI is busy:
// Queue items for sequential execution
interface QueuedItem {
type: 'message' | 'slashCommand';
content: string;
timestamp: number;
}
// Add to queue instead of sending directly when busy
session.executionQueue.push({ type: 'message', content, timestamp: Date.now() });
8. Auto Run
File-based document automation system:
// Auto Run state on session
session.autoRunFolderPath?: string; // Document folder path
session.autoRunSelectedFile?: string; // Currently selected document
session.autoRunMode?: 'edit' | 'preview';
// API for Auto Run operations
window.maestro.autorun.listDocuments(folderPath);
window.maestro.autorun.readDocument(folderPath, filename);
window.maestro.autorun.saveDocument(folderPath, filename, content);
Worktree Support: Auto Run can operate in a git worktree, allowing users to continue interactive editing in the main repo while Auto Run processes tasks in the background. When batchRunState.worktreeActive is true, read-only mode is disabled and a git branch icon appears in the UI. See useBatchProcessor.ts for worktree setup logic.
Playbook Assets: Playbooks can include non-markdown assets (config files, YAML, Dockerfiles, scripts) in an assets/ subfolder. When installing playbooks from the marketplace or importing from ZIP files, Maestro copies the entire folder structure including assets. See the Maestro-Playbooks repository for the convention documentation.
playbook-folder/
├── 01_TASK.md
├── 02_TASK.md
├── README.md
└── assets/
├── config.yaml
├── Dockerfile
└── setup.sh
Documents can reference assets using {{AUTORUN_FOLDER}}/assets/filename. The manifest lists assets explicitly:
{
"id": "example-playbook",
"documents": [...],
"assets": ["config.yaml", "Dockerfile", "setup.sh"]
}
9. Tab Hover Overlay Menu
AI conversation tabs display a hover overlay menu after a 400ms delay when hovering over tabs with an established session. The overlay includes tab management and context operations:
Menu Structure:
// Tab operations (always shown)
- Copy Session ID (if session exists)
- Star/Unstar Session (if session exists)
- Rename Tab
- Mark as Unread
// Context management (shown when applicable)
- Context: Compact (if tab has 5+ messages)
- Context: Merge Into (if session exists)
- Context: Send to Agent (if session exists)
// Tab close actions (always shown)
- Close (disabled if only one tab)
- Close Others (disabled if only one tab)
- Close Tabs to the Left (disabled if first tab)
- Close Tabs to the Right (disabled if last tab)
Implementation Pattern:
const [overlayOpen, setOverlayOpen] = useState(false);
const [overlayPosition, setOverlayPosition] = useState<{ top: number; left: number } | null>(null);
const handleMouseEnter = () => {
if (!tab.agentSessionId) return; // Only for established sessions
hoverTimeoutRef.current = setTimeout(() => {
if (tabRef.current) {
const rect = tabRef.current.getBoundingClientRect();
setOverlayPosition({ top: rect.bottom + 4, left: rect.left });
}
setOverlayOpen(true);
}, 400);
};
// Render overlay via portal to escape stacking context
{overlayOpen && overlayPosition && createPortal(
<div style={{ top: overlayPosition.top, left: overlayPosition.left }}>
{/* Overlay menu items */}
</div>,
document.body
)}
Key Features:
- Appears after 400ms hover delay (only for tabs with
agentSessionId) - Fixed positioning at tab bottom
- Mouse can move from tab to overlay without closing
- Disabled states with visual feedback (opacity-40, cursor-default)
- Theme-aware styling
- Dividers separate action groups
See src/renderer/components/TabBar.tsx (Tab component) for implementation details.
10. SSH Remote Sessions
Sessions can execute commands on remote hosts via SSH. Critical: There are two different SSH identifiers with different lifecycles:
// Set AFTER AI agent spawns (via onSshRemote callback)
session.sshRemoteId: string | undefined
// Set BEFORE spawn (user configuration)
session.sessionSshRemoteConfig: {
enabled: boolean;
remoteId: string | null; // The SSH config ID
workingDirOverride?: string;
}
Common pitfall: sshRemoteId is only populated after the AI agent spawns. For terminal-only SSH sessions (no AI agent), it remains undefined. Always use both as fallback:
// WRONG - fails for terminal-only SSH sessions
const sshId = session.sshRemoteId;
// CORRECT - works for all SSH sessions
const sshId = session.sshRemoteId || session.sessionSshRemoteConfig?.remoteId;
This applies to any operation that needs to run on the remote:
window.maestro.fs.readDir(path, sshId)gitService.isRepo(path, sshId)- Directory existence checks for
cdcommand tracking
Similarly for checking if a session is remote:
// WRONG
const isRemote = !!session.sshRemoteId;
// CORRECT
const isRemote = !!session.sshRemoteId || !!session.sessionSshRemoteConfig?.enabled;
Code Conventions
TypeScript
- Strict mode enabled
- Interface definitions for all data structures
- Types exported via
preload.tsfor renderer
React Components
- Functional components with hooks
- Tailwind for layout, inline styles for theme colors
tabIndex={-1}+outline-nonefor programmatic focus
Commit Messages
feat: new feature
fix: bug fix
docs: documentation
refactor: code refactoring
IMPORTANT: Do NOT create a CHANGELOG.md file. This project does not use changelogs - all change documentation goes in commit messages and PR descriptions only.
Session Interface
Key fields on the Session object (abbreviated - see src/renderer/types/index.ts for full definition):
interface Session {
// Identity
id: string;
name: string;
groupId?: string; // Session grouping
toolType: ToolType; // 'claude-code' | 'aider' | 'terminal' | etc.
state: SessionState; // 'idle' | 'busy' | 'error' | 'connecting'
inputMode: 'ai' | 'terminal'; // Which process receives input
bookmarked?: boolean; // Pinned to top
// Paths
cwd: string; // Current working directory (can change via cd)
projectRoot: string; // Initial directory (never changes, used for Claude session storage)
fullPath: string; // Full resolved path
// Processes
aiPid: number; // AI process ID
port: number; // Web server communication port
// Multi-Tab Support (NEW)
aiTabs: AITab[]; // Multiple Claude Code conversation tabs
activeTabId: string; // Currently active tab
closedTabHistory: ClosedTab[]; // Undo stack for closed tabs
// Logs (per-tab)
shellLogs: LogEntry[]; // Terminal output history
// Execution Queue (replaces messageQueue)
executionQueue: QueuedItem[]; // Sequential execution queue
// Usage & Stats
usageStats?: UsageStats; // Token usage and cost
contextUsage: number; // Context window usage percentage
workLog: WorkLogItem[]; // Work tracking
// Git Integration
isGitRepo: boolean; // Git features enabled
changedFiles: FileArtifact[]; // Git change tracking
gitBranches?: string[]; // Branch cache for completion
gitTags?: string[]; // Tag cache for completion
// File Explorer
fileTree: any[]; // File tree structure
fileExplorerExpanded: string[]; // Expanded folder paths
fileExplorerScrollPos: number; // Scroll position
// Web/Live Sessions (NEW)
isLive: boolean; // Accessible via web interface
liveUrl?: string; // Live session URL
// Auto Run (NEW)
autoRunFolderPath?: string; // Auto Run document folder
autoRunSelectedFile?: string; // Selected document
autoRunMode?: 'edit' | 'preview'; // Current mode
// Command History
aiCommandHistory?: string[]; // AI input history
shellCommandHistory?: string[]; // Terminal input history
// Error Handling (NEW)
agentError?: AgentError; // Current agent error (auth, tokens, rate limit, etc.)
agentErrorPaused?: boolean; // Input blocked while error modal shown
}
interface AITab {
id: string;
name: string;
logs: LogEntry[]; // Tab-specific conversation history
agentSessionId?: string; // Agent session for this tab
scrollTop?: number;
draftInput?: string;
}
IPC API Surface
The window.maestro API exposes:
Core APIs
settings- Get/set app settingssessions/groups- Persistenceprocess- Spawn, write, kill, resizefs- readDir, readFiledialog- Folder selectionshells- Detect available shellslogger- System logging
Agent & Agent Sessions
agents- Detect, get, config, refresh, custom paths, getCapabilitiesagentSessions- Generic agent session storage API (list, read, search, delete)agentError- Agent error handling (clearError, retryAfterError)claude- (Deprecated) Claude Code sessions - useagentSessionsinstead
Git Integration
git- Status, diff, isRepo, numstat, branches, tags, infogit- Worktree support: worktreeInfo, getRepoRoot, worktreeSetup, worktreeCheckoutgit- PR creation: createPR, checkGhCli, getDefaultBranch
Web & Live Sessions
web- Broadcast user input, Auto Run state, tab changes to web clientslive- Toggle live sessions, get status, dashboard URL, connected clientswebserver- Get URL, connected client counttunnel- Cloudflare tunnel: isCloudflaredInstalled, start, stop, getStatus
Automation
autorun- Document and image management for Auto Runplaybooks- Batch run configuration managementhistory- Per-session execution history (see History API below)cli- CLI activity detection for playbook runstempfile- Temporary file management for batch processing
Analytics & Visualization
stats- Usage statistics: recordQuery, getAggregatedStats, exportCsv, clearOldData, getDatabaseSizestats- Auto Run tracking: startAutoRun, endAutoRun, recordTask, getAutoRunSessionsstats- Real-time updates viastats:updatedevent broadcastdocumentGraph- File watching: watchFolder, unwatchFolderdocumentGraph- Real-time updates viadocumentGraph:filesChangedevent
History API
Per-session history storage with 5,000 entries per session (up from 1,000 global). Each session's history is stored as a JSON file in ~/Library/Application Support/Maestro/history/{sessionId}.json.
window.maestro.history = {
getAll: (projectPath?, sessionId?) => Promise<HistoryEntry[]>,
add: (entry) => Promise<boolean>,
clear: (projectPath?, sessionId?) => Promise<boolean>,
delete: (entryId, sessionId?) => Promise<boolean>,
update: (entryId, updates, sessionId?) => Promise<boolean>,
// For AI context integration:
getFilePath: (sessionId) => Promise<string | null>,
listSessions: () => Promise<string[]>,
// External change detection:
onExternalChange: (handler) => () => void,
reload: () => Promise<boolean>,
};
AI Context Integration: Use getFilePath(sessionId) to get the path to a session's history file. This file can be passed directly to AI agents as context, giving them visibility into past completed tasks, decisions, and work patterns.
Power Management
power- Sleep prevention: setEnabled, isEnabled, getStatus, addReason, removeReason
Utilities
fonts- Font detectionnotification- Desktop notifications, text-to-speechdevtools- Developer tools: open, close, toggleattachments- Image attachment management
Available Agents
| ID | Name | Status | Notes |
|---|---|---|---|
claude-code |
Claude Code | Active | Primary agent, uses --print --verbose --output-format stream-json |
opencode |
OpenCode | Stub | Output parser implemented, session storage stub ready |
terminal |
Terminal | Internal | Hidden from UI, used for shell sessions |
openai-codex |
OpenAI Codex | Planned | Coming soon |
gemini-cli |
Gemini CLI | Planned | Coming soon |
qwen3-coder |
Qwen3 Coder | Planned | Coming soon |
Additional ToolType values (aider, claude) are defined in types but not yet implemented in agent-detector.ts.
Agent Capabilities
Each agent declares capabilities that control UI feature availability. See src/main/agent-capabilities.ts for the full interface.
| Capability | Description | UI Feature Controlled |
|---|---|---|
supportsResume |
Can resume previous sessions | Resume button |
supportsReadOnlyMode |
Has plan/read-only mode | Read-only toggle |
supportsJsonOutput |
Emits structured JSON | Output parsing |
supportsSessionId |
Emits session ID | Session ID pill |
supportsImageInput |
Accepts image attachments | Attach image button |
supportsSlashCommands |
Has discoverable commands | Slash autocomplete |
supportsSessionStorage |
Persists browsable sessions | Sessions browser |
supportsCostTracking |
Reports token costs | Cost widget |
supportsUsageStats |
Reports token counts | Context window widget |
supportsBatchMode |
Runs per-message | Batch processing |
supportsStreaming |
Streams output | Real-time display |
supportsResultMessages |
Distinguishes final result | Message classification |
For detailed agent integration guide, see AGENT_SUPPORT.md.
Performance Best Practices
React Component Optimization
Use React.memo for list item components:
// Components rendered in arrays (tabs, sessions, list items) should be memoized
const Tab = memo(function Tab({ tab, isActive, ... }: TabProps) {
// Memoize computed values that depend on props
const displayName = useMemo(() => getTabDisplayName(tab), [tab.name, tab.agentSessionId]);
// Memoize style objects to prevent new references on every render
const tabStyle = useMemo(() => ({
borderRadius: '6px',
backgroundColor: isActive ? theme.colors.accent : 'transparent',
} as React.CSSProperties), [isActive, theme.colors.accent]);
return <div style={tabStyle}>{displayName}</div>;
});
Consolidate chained useMemo calls:
// BAD: Multiple dependent useMemo calls create cascade re-computations
const filtered = useMemo(() => sessions.filter(...), [sessions]);
const sorted = useMemo(() => filtered.sort(...), [filtered]);
const grouped = useMemo(() => groupBy(sorted, ...), [sorted]);
// GOOD: Single useMemo with all transformations
const { filtered, sorted, grouped } = useMemo(() => {
const filtered = sessions.filter(...);
const sorted = filtered.sort(...);
const grouped = groupBy(sorted, ...);
return { filtered, sorted, grouped };
}, [sessions]);
Pre-compile regex patterns at module level:
// BAD: Regex compiled on every render
const Component = () => {
const cleaned = text.replace(/^(\p{Emoji})+\s*/u, '');
};
// GOOD: Compile once at module load
const LEADING_EMOJI_REGEX = /^(\p{Emoji})+\s*/u;
const Component = () => {
const cleaned = text.replace(LEADING_EMOJI_REGEX, '');
};
Data Structure Pre-computation
Build indices once, reuse in renders:
// BAD: O(n) tree traversal on every markdown render
const result = remarkFileLinks({ fileTree, cwd });
// GOOD: Build index once when fileTree changes, pass to renders
const indices = useMemo(() => buildFileTreeIndices(fileTree), [fileTree]);
const result = remarkFileLinks({ indices, cwd });
Main Process (Node.js)
Cache expensive lookups:
// BAD: Synchronous file check on every shell spawn
fs.accessSync(shellPath, fs.constants.X_OK);
// GOOD: Cache resolved paths
const shellPathCache = new Map<string, string>();
const cached = shellPathCache.get(shell);
if (cached) return cached;
// ... resolve and cache
shellPathCache.set(shell, resolved);
Use async file operations:
// BAD: Blocking the main process
fs.unlinkSync(tempFile);
// GOOD: Non-blocking cleanup
import * as fsPromises from 'fs/promises';
fsPromises.unlink(tempFile).catch(() => {});
Onboarding Wizard
The wizard (src/renderer/components/Wizard/) guides new users through first-run setup, creating AI sessions with Auto Run documents.
Wizard Architecture
src/renderer/components/Wizard/
├── MaestroWizard.tsx # Main orchestrator, screen transitions
├── WizardContext.tsx # State management (useReducer pattern)
├── WizardResumeModal.tsx # Resume incomplete wizard dialog
├── WizardExitConfirmModal.tsx # Exit confirmation dialog
├── ScreenReaderAnnouncement.tsx # Accessibility announcements
├── screens/ # Individual wizard steps
│ ├── AgentSelectionScreen.tsx # Step 1: Choose AI agent
│ ├── DirectorySelectionScreen.tsx # Step 2: Select project folder
│ ├── ConversationScreen.tsx # Step 3: AI project discovery
│ └── PhaseReviewScreen.tsx # Step 4: Review generated plan
├── services/ # Business logic
│ ├── wizardPrompts.ts # System prompts, response parser
│ ├── conversationManager.ts # AI conversation handling
│ └── phaseGenerator.ts # Document generation
└── tour/ # Post-setup walkthrough
├── TourOverlay.tsx # Spotlight overlay
├── TourStep.tsx # Step tooltip
├── tourSteps.ts # Step definitions
└── useTour.tsx # Tour state management
Wizard Flow
- Agent Selection → Select available AI (Claude Code, etc.) and project name
- Directory Selection → Choose project folder, validates Git repo status
- Conversation → AI asks clarifying questions, builds confidence score (0-100)
- Phase Review → View/edit generated Phase 1 document, choose to start tour
When confidence reaches 80+ and agent signals "ready", user proceeds to Phase Review where Auto Run documents are generated and saved to Auto Run Docs/Initiation/. The Initiation/ subfolder keeps wizard-generated documents separate from user-created playbooks.
Triggering the Wizard
// From anywhere with useWizard hook
const { openWizard } = useWizard();
openWizard();
// Keyboard shortcut (default)
Cmd+Shift+N // Opens wizard
// Also available in:
// - Command K menu: "New Agent Wizard"
// - Hamburger menu: "New Agent Wizard"
State Persistence (Resume)
Wizard state persists to wizardResumeState in settings when user advances past step 1. On next app launch, if incomplete state exists, WizardResumeModal offers "Resume" or "Start Fresh".
// Check for saved state
const hasState = await hasResumeState();
// Load saved state
const savedState = await loadResumeState();
// Clear saved state
clearResumeState();
State Lifecycle
The Wizard maintains two types of state:
-
In-Memory State (React
useReducer)- Managed in
WizardContext.tsx - Includes:
currentStep,isOpen,isComplete, conversation history, etc. - Lives only during the app session
- Must be reset when opening wizard after completion
- Managed in
-
Persisted State (Settings)
- Stored in
wizardResumeStateviawindow.maestro.settings - Enables resume functionality across app restarts
- Automatically saved when advancing past step 1
- Cleared on completion or when user chooses "Just Quit"
- Stored in
State Save Triggers:
- Auto-save: When
currentStepchanges (step > 1) -WizardContext.tsx:791 - Manual save: User clicks "Save & Exit" -
MaestroWizard.tsx:147
State Clear Triggers:
- Wizard completion:
App.tsx:4681+WizardContext.tsx:711 - User quits: "Just Quit" button -
MaestroWizard.tsx:168 - User starts fresh: "Start Fresh" in resume modal -
App.tsxresume handlers
Opening Wizard Logic:
The openWizard() function in WizardContext.tsx:528-535 handles state initialization:
// If previous wizard was completed, reset in-memory state first
if (state.isComplete === true) {
dispatch({ type: 'RESET_WIZARD' }); // Clear stale state
}
dispatch({ type: 'OPEN_WIZARD' }); // Show wizard UI
This ensures:
- Fresh starts: Completed wizards don't contaminate new runs
- Resume works: Abandoned wizards (isComplete: false) preserve state
- No race conditions: Persisted state is checked after wizard opens
Important: The persisted state and in-memory state are independent. Clearing one doesn't automatically clear the other. Both must be managed correctly to prevent state contamination (see Issue #89).
Tour System
The tour highlights UI elements with spotlight cutouts:
// Add data-tour attribute to spotlight elements
<div data-tour="autorun-panel">...</div>
// Tour steps defined in tourSteps.ts
{
id: 'autorun-panel',
title: 'Auto Run in Action',
description: '...',
selector: '[data-tour="autorun-panel"]',
position: 'left', // tooltip position
uiActions: [ // UI state changes before spotlight
{ type: 'setRightTab', value: 'autorun' },
],
}
Customization Points
| What | Where |
|---|---|
| Add wizard step | WizardContext.tsx (WIZARD_TOTAL_STEPS, WizardStep type, STEP_INDEX) |
| Modify wizard prompts | src/prompts/wizard-*.md (content), services/wizardPrompts.ts (logic) |
| Change confidence threshold | READY_CONFIDENCE_THRESHOLD in wizardPrompts.ts (default: 80) |
| Add tour step | tour/tourSteps.ts array |
| Modify Auto Run document format | src/prompts/wizard-document-generation.md |
| Change wizard keyboard shortcut | shortcuts.ts → openWizard |
Related Settings
// In useSettings.ts
wizardCompleted: boolean // First wizard completion
tourCompleted: boolean // First tour completion
firstAutoRunCompleted: boolean // Triggers celebration modal
Inline Wizard (/wizard)
The Inline Wizard creates Auto Run Playbook documents from within an existing agent session. Unlike the full-screen Onboarding Wizard above, it runs inside a single tab.
Prerequisites
- Auto Run document folder must be configured for the session
- If not set,
/wizarderrors with instructions to configure it
User Flow
- Start: Type
/wizardin any AI tab → tab enters wizard mode - Conversation: Back-and-forth with agent, confidence gauge builds (0-100%)
- Generation: At 80%+ confidence, generates docs (Austin Facts shown, cancellable)
- Completion: Tab returns to normal with preserved context, docs in unique subfolder
Key Behaviors
- Multiple wizards can run in different tabs simultaneously
- Wizard state is per-tab (
AITab.wizardState), not per-session - Documents written to unique subfolder under Auto Run folder (e.g.,
Auto Run Docs/Project-Name/) - On completion, tab renamed to "Project: {SubfolderName}"
- Final AI message summarizes generated docs and next steps
- Same
agentSessionIdpreserved for context continuity
Architecture
src/renderer/components/InlineWizard/
├── WizardConversationView.tsx # Conversation phase UI
├── WizardInputPanel.tsx # Input with confidence gauge
├── DocumentGenerationView.tsx # Generation phase with Austin Facts
└── ... (see index.ts for full documentation)
src/renderer/hooks/useInlineWizard.ts # Main hook
src/renderer/contexts/InlineWizardContext.tsx # State provider
Customization Points
| What | Where |
|---|---|
| Modify inline wizard prompts | src/prompts/wizard-*.md |
| Change confidence threshold | READY_CONFIDENCE_THRESHOLD in wizardPrompts.ts |
| Modify generation UI | DocumentGenerationView.tsx, AustinFactsDisplay.tsx |
Usage Dashboard
The Usage Dashboard (src/renderer/components/UsageDashboard/) provides analytics and visualizations for AI agent usage.
Architecture
src/renderer/components/UsageDashboard/
├── UsageDashboardModal.tsx # Main modal with view tabs (Overview, Agents, Activity, AutoRun)
├── SummaryCards.tsx # Metric cards (queries, duration, cost, Auto Runs)
├── AgentComparisonChart.tsx # Bar chart comparing agent usage
├── SourceDistributionChart.tsx # Pie chart for user vs auto queries
├── ActivityHeatmap.tsx # Weekly activity heatmap (GitHub-style)
├── DurationTrendsChart.tsx # Line chart for duration over time
├── AutoRunStats.tsx # Auto Run-specific statistics
├── ChartSkeletons.tsx # Loading skeleton components
├── ChartErrorBoundary.tsx # Error boundary with retry
└── EmptyState.tsx # Empty state when no data
Backend Components
src/main/
├── stats-db.ts # SQLite database (better-sqlite3) with WAL mode
│ ├── query_events table # AI queries with duration, tokens, cost
│ ├── auto_run_sessions table # Auto Run session tracking
│ ├── auto_run_tasks table # Individual task tracking
│ └── _migrations table # Schema migration tracking
├── ipc/handlers/stats.ts # IPC handlers for stats operations
└── utils/statsCache.ts # Query result caching
Key Patterns
Real-time Updates:
// Backend broadcasts after each database write
mainWindow?.webContents.send('stats:updated');
// Frontend subscribes with debouncing
useEffect(() => {
const unsubscribe = window.maestro.stats.onStatsUpdated(() => {
debouncedRefresh();
});
return () => unsubscribe?.();
}, []);
Colorblind-Friendly Palettes:
import { COLORBLIND_AGENT_PALETTE, getColorBlindAgentColor } from '../constants/colorblindPalettes';
// Wong-based palette with high contrast for accessibility
Chart Error Boundaries:
<ChartErrorBoundary chartName="Agent Comparison" onRetry={handleRetry}>
<AgentComparisonChart data={data} colorBlindMode={colorBlindMode} />
</ChartErrorBoundary>
Related Settings
// In useSettings.ts
statsCollectionEnabled: boolean // Enable/disable stats collection (default: true)
defaultStatsTimeRange: 'day' | 'week' | 'month' | 'year' | 'all' // Default time filter
colorBlindMode: boolean // Use accessible color palettes
preventSleepEnabled: boolean // Prevent system sleep while agents are busy (default: false)
Document Graph
The Document Graph (src/renderer/components/DocumentGraph/) visualizes markdown file relationships and wiki-link connections using React Flow.
Architecture
src/renderer/components/DocumentGraph/
├── DocumentGraphView.tsx # Main modal with React Flow canvas
├── DocumentNode.tsx # Document file node component
├── ExternalLinkNode.tsx # External URL domain node
├── NodeContextMenu.tsx # Right-click context menu
├── NodeBreadcrumb.tsx # Path breadcrumb for selected node
├── GraphLegend.tsx # Collapsible legend explaining node/edge types
├── graphDataBuilder.ts # Scans directory, extracts links, builds graph data
└── layoutAlgorithms.ts # Force-directed and hierarchical layout algorithms
src/renderer/utils/
├── markdownLinkParser.ts # Parses [[wiki-links]] and [markdown](links)
└── documentStats.ts # Computes document statistics (word count, etc.)
src/main/ipc/handlers/
└── documentGraph.ts # Chokidar file watcher for real-time updates
Key Patterns
Building Graph Data:
import { buildGraphData } from './graphDataBuilder';
const { nodes, edges, stats } = await buildGraphData(
rootPath,
showExternalLinks,
maxNodes,
offset,
progressCallback
);
Layout Algorithms:
import { applyForceLayout, applyHierarchicalLayout, animateLayoutTransition } from './layoutAlgorithms';
const positionedNodes = layoutMode === 'force'
? applyForceLayout(nodes, edges)
: applyHierarchicalLayout(nodes, edges);
animateLayoutTransition(currentNodes, positionedNodes, setNodes, savePositions);
Node Animation (additions/removals):
import { diffNodes, animateNodesEntering, animateNodesExiting } from './layoutAlgorithms';
const { added, removed, stable } = diffNodes(previousNodes, newNodes);
animateNodesExiting(removed, () => animateNodesEntering(added));
Real-time File Watching:
// Backend watches for .md file changes
window.maestro.documentGraph.watchFolder(rootPath);
window.maestro.documentGraph.onFilesChanged((changes) => {
debouncedRebuildGraph();
});
// Cleanup on modal close
window.maestro.documentGraph.unwatchFolder(rootPath);
Keyboard Navigation:
// Arrow keys navigate to connected nodes (spatial detection)
// Enter opens selected node
// Tab cycles through connected nodes
// Escape closes modal
Large File Handling
Files over 1MB are truncated to first 100KB for link extraction to prevent UI blocking:
const LARGE_FILE_THRESHOLD = 1 * 1024 * 1024; // 1MB
const LARGE_FILE_PARSE_LIMIT = 100 * 1024; // 100KB
Pagination
Default limit of 50 nodes with "Load more" for large directories:
const DEFAULT_MAX_NODES = 50;
const LOAD_MORE_INCREMENT = 25;
Related Settings
// In useSettings.ts
documentGraphShowExternalLinks: boolean // Show external link nodes (default: false)
documentGraphMaxNodes: number // Initial pagination limit (50-1000, default: 50)
Debugging
Focus Not Working
- Add
tabIndex={0}ortabIndex={-1} - Add
outline-noneclass - Use
ref={(el) => el?.focus()}for auto-focus
Settings Not Persisting
- Check wrapper function calls
window.maestro.settings.set() - Check loading code in
useSettings.tsuseEffect
Modal Escape Not Working
- Register with layer stack (don't handle Escape locally)
- Check priority is set correctly
MCP Server
Maestro provides a hosted MCP (Model Context Protocol) server for AI applications to search the documentation.
Server URL: https://docs.runmaestro.ai/mcp
Available Tools:
SearchMaestro- Search the Maestro knowledge base for documentation, code examples, API references, and guides
Connect from Claude Desktop/Code:
{
"mcpServers": {
"maestro": {
"url": "https://docs.runmaestro.ai/mcp"
}
}
}
See MCP Server documentation for full details.