Merge pull request #308 from pedramamini/0.15.0-polish

0.15.0 polish
This commit is contained in:
Pedram Amini
2026-02-05 14:48:57 -06:00
committed by GitHub
25 changed files with 120 additions and 48 deletions

View File

@@ -20,6 +20,44 @@ Settings are organized into tabs:
| **AI Commands** | View and edit slash commands, [Spec-Kit](./speckit-commands), and [OpenSpec](./openspec-commands) prompts |
| **SSH Hosts** | Configure remote hosts for [SSH agent execution](./ssh-remote-execution) |
## Conductor Profile
The **Conductor Profile** (Settings → General → **About Me**) is a short description of yourself that gets injected into every AI agent's system prompt. This helps agents understand your background, preferences, and communication style so they can tailor responses accordingly.
**To configure:**
1. Open **Settings** (`Cmd+,` / `Ctrl+,`) → **General** tab
2. Find the **About Me** text area at the top
3. Write a brief profile describing yourself
### What to Include
A good conductor profile is concise (a few sentences to a short paragraph) and covers:
- **Your role/background**: Developer, researcher, team lead, etc.
- **Technical context**: Languages you work with, tools you prefer, platforms you use
- **Communication preferences**: Direct vs. detailed, level of explanation needed
- **Work style**: Preferences for how agents should approach tasks
### Example Profile
```
Security researcher. macOS desktop. TypeScript and Python for tools.
Direct communication, no fluff. Action over process. Push back on bad ideas.
Generate markdown for Obsidian. CST timezone.
```
### How Agents Use It
When you start a session, Maestro includes your conductor profile in the system prompt sent to the AI agent. This means:
- Agents adapt their response style to match your preferences
- Technical context helps agents make appropriate assumptions
- Communication preferences reduce back-and-forth clarification
### Using in Custom Commands
You can reference your conductor profile in [slash commands](./slash-commands) using the `{{CONDUCTOR_PROFILE}}` template variable. This is useful for commands that need to remind agents of your preferences mid-conversation.
## Checking for Updates
Maestro checks for updates automatically on startup (configurable in Settings → General → **Check for updates on startup**).
@@ -112,7 +150,6 @@ In-app toast notifications appear in the corner when events occur. Configure how
Notifications are sent when:
- An AI task completes (OS notification + optional custom notification)
- A long-running command finishes (OS notification)
- The LLM analysis generates a feedback synopsis (custom notification only, if configured)
## Sleep Prevention

View File

@@ -119,6 +119,7 @@ vi.mock('../../renderer/components/TemplateAutocompleteDropdown', () => ({
vi.mock('../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys) => keys?.join('+') || ''),
isMacOS: vi.fn(() => false),
}));
// Create a mock theme for testing

View File

@@ -126,6 +126,7 @@ vi.mock('../../renderer/components/TemplateAutocompleteDropdown', () => ({
vi.mock('../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys) => keys?.join('+') || ''),
isMacOS: vi.fn(() => false),
}));
vi.mock('../../renderer/hooks/useGitStatusPolling', () => ({

View File

@@ -96,6 +96,7 @@ vi.mock('../../../renderer/components/AutoRun', () => ({
// Mock shortcut formatter
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false),
}));
// Create a mock theme for testing

View File

@@ -38,6 +38,7 @@ vi.mock('../../../renderer/contexts/LayerStackContext', async () => {
// Mock formatShortcutKeys to return predictable output
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: (keys: string[]) => keys.join('+'),
isMacOS: () => false,
}));
// Sample theme for testing

View File

@@ -109,6 +109,7 @@ vi.mock('../../../renderer/utils/tokenCounter', () => ({
// Mock shortcut formatter
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string) => keys),
isMacOS: vi.fn(() => false),
}));
// Mock remarkFileLinks

View File

@@ -333,9 +333,10 @@ describe('WizardInputPanel', () => {
expect(screen.getByText('Enter')).toBeInTheDocument();
});
it('shows "⌘ + Enter" when enterToSend is false', () => {
it('shows "⌘ + Enter" (or "Ctrl + Enter" on non-Mac) when enterToSend is false', () => {
render(<WizardInputPanel {...defaultProps} enterToSend={false} />);
expect(screen.getByText('⌘ + Enter')).toBeInTheDocument();
// Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(screen.getByText(/⌘ \+ Enter|Ctrl \+ Enter/)).toBeInTheDocument();
});
it('calls setEnterToSend when clicked', () => {

View File

@@ -235,17 +235,18 @@ describe('InputArea', () => {
const props = createDefaultProps();
render(<InputArea {...props} />);
const button = screen.getByTitle('Switch to Meta+Enter to send');
const button = screen.getByTitle(/Switch to (Cmd|Ctrl)\+Enter to send/);
expect(button).toBeInTheDocument();
expect(button).toHaveTextContent('Enter');
});
it('renders Cmd+Enter when enterToSend is false', () => {
it('renders Cmd+Enter (or Ctrl+Enter on non-Mac) when enterToSend is false', () => {
const props = createDefaultProps({ enterToSend: false });
render(<InputArea {...props} />);
const button = screen.getByTitle('Switch to Enter to send');
expect(button).toHaveTextContent('⌘ + Enter');
// Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(button).toHaveTextContent(/⌘ \+ Enter|Ctrl \+ Enter/);
});
});
@@ -1505,7 +1506,7 @@ describe('InputArea', () => {
const props = createDefaultProps({ enterToSend: true, setEnterToSend });
render(<InputArea {...props} />);
fireEvent.click(screen.getByTitle('Switch to Meta+Enter to send'));
fireEvent.click(screen.getByTitle(/Switch to (Cmd|Ctrl)\+Enter to send/));
expect(setEnterToSend).toHaveBeenCalledWith(false);
});

View File

@@ -148,6 +148,7 @@ vi.mock('../../../renderer/utils/tabHelpers', () => ({
// Mock shortcut formatter
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys?.join('+') || ''),
isMacOS: vi.fn(() => false),
}));
// Configurable git status data for tests - can be modified in individual tests

View File

@@ -185,8 +185,9 @@ describe('PromptComposerModal', () => {
/>
);
// When enterToSend is false, it shows "⌘ + Enter"
expect(screen.getByText('⌘ + Enter')).toBeInTheDocument();
// When enterToSend is false, it shows "⌘ + Enter" on Mac or "Ctrl + Enter" on other platforms
// Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(screen.getByText(/⌘ \+ Enter|Ctrl \+ Enter/)).toBeInTheDocument();
});
it('should render close button with X icon', () => {

View File

@@ -59,6 +59,7 @@ vi.mock('../../../renderer/services/git', () => ({
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false),
}));
// Mock lucide-react

View File

@@ -21,6 +21,7 @@ vi.mock('../../../renderer/components/AutoRun', () => ({
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys) => keys.join('+')),
isMacOS: vi.fn(() => false),
}));
// Mock lucide-react

View File

@@ -38,6 +38,7 @@ vi.mock('../../../renderer/contexts/LayerStackContext', () => ({
// Mock formatShortcutKeys
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false), // Test environment is not Mac
}));
// Mock AICommandsPanel
@@ -808,14 +809,15 @@ describe('SettingsModal', () => {
expect(setEnterToSendTerminal).toHaveBeenCalledWith(false);
});
it('should display Cmd+Enter when enter-to-send is false', async () => {
it('should display Cmd+Enter (or Ctrl+Enter on non-Mac) when enter-to-send is false', async () => {
render(<SettingsModal {...createDefaultProps({ enterToSendAI: false })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
});
expect(screen.getByText('⌘ + Enter')).toBeInTheDocument();
// Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(screen.getByText(/⌘ \+ Enter|Ctrl \+ Enter/)).toBeInTheDocument();
});
});

View File

@@ -14,6 +14,7 @@ import type { Theme, Shortcut } from '../../../renderer/types';
// Mock the shortcutFormatter module
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false),
}));
// Import after mock to get the mocked version

View File

@@ -271,7 +271,7 @@ describe('ToastContext', () => {
taskDuration: 5000,
agentSessionId: 'test-session-id',
tabName: 'TestTab',
audioNotification: { enabled: false },
audioNotification: { enabled: false, reason: 'disabled' },
});
});

View File

@@ -364,7 +364,10 @@ export function registerNotificationsHandlers(): void {
async (_event, text: string, command?: string): Promise<NotificationCommandResponse> => {
// Skip if there's no content to send
if (!text || text.trim().length === 0) {
logger.debug('Notification skipped - no content to send', 'Notification');
logger.info('Notification skipped - empty or whitespace-only content', 'Notification', {
textLength: text?.length ?? 0,
hasText: !!text,
});
return { success: true }; // Return success since there's nothing to do
}

View File

@@ -4,6 +4,7 @@ Rules:
- 2-4 words max, shorter is better
- Title Case, no punctuation
- Be specific (mention tech/file names if relevant)
- Include ID numbers (PR #, issue #, ticket ID) when mentioned
- Use hyphens for compound concepts
- If too vague, use format: YYYY-MM-DD Topic
@@ -13,4 +14,7 @@ Examples:
"refactor DB queries for pooling" = DB Connection Pooling
"write tests for checkout" = Checkout Tests
"fix TS errors in parser.ts" = Parser TS Errors
"review PR #256" = PR 256 Review
"fix issue #42 memory leak" = Issue 42 Memory Leak
"implement JIRA-1234 feature" = JIRA-1234 Feature
"help with my code" = 2024-01-15 Code Help

View File

@@ -22,7 +22,7 @@ import type {
QueuedItem,
Shortcut,
} from '../types';
import { formatShortcutKeys } from '../utils/shortcutFormatter';
import { formatShortcutKeys, isMacOS } from '../utils/shortcutFormatter';
import { QueuedItemsList } from './QueuedItemsList';
import { normalizeMentionName } from '../utils/participantColors';
@@ -549,10 +549,10 @@ export const GroupChatInput = React.memo(function GroupChatInput({
<button
onClick={() => setEnterToSend(!enterToSend)}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'}
title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
>
<Keyboard className="w-3 h-3" />
{enterToSend ? 'Enter' : '⌘ + Enter'}
{enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</button>
</div>
</div>

View File

@@ -26,6 +26,7 @@ import type { Session, Theme } from '../../types';
import { WizardPill } from './WizardPill';
import { WizardConfidenceGauge } from './WizardConfidenceGauge';
import { WizardExitConfirmDialog } from './WizardExitConfirmDialog';
import { isMacOS } from '../../utils/shortcutFormatter';
interface WizardInputPanelProps {
/** Current session with wizard state */
@@ -322,10 +323,10 @@ export const WizardInputPanel = React.memo(function WizardInputPanel({
<button
onClick={() => setEnterToSend(!enterToSend)}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'}
title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
>
<Keyboard className="w-3 h-3" />
{enterToSend ? 'Enter' : '⌘ + Enter'}
{enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</button>
</div>
</div>

View File

@@ -18,7 +18,7 @@ import {
Pin,
} from 'lucide-react';
import type { Session, Theme, BatchRunState, Shortcut, ThinkingMode } from '../types';
import { formatShortcutKeys } from '../utils/shortcutFormatter';
import { formatShortcutKeys, isMacOS } from '../utils/shortcutFormatter';
import type { TabCompletionSuggestion, TabCompletionFilter } from '../hooks';
import type {
SummarizeProgress,
@@ -1087,10 +1087,10 @@ export const InputArea = React.memo(function InputArea(props: InputAreaProps) {
<button
onClick={() => setEnterToSend(!enterToSend)}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'}
title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
>
<Keyboard className="w-3 h-3" />
{enterToSend ? 'Enter' : '⌘ + Enter'}
{enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</button>
</div>
</div>

View File

@@ -306,10 +306,6 @@ export function NotificationsPanel({
<ul className="text-xs opacity-70 space-y-1" style={{ color: theme.colors.textDim }}>
<li> When an AI task completes</li>
<li> When a long-running command finishes</li>
<li>
When the LLM analysis generates a feedback synopsis (custom notification only, if
configured)
</li>
</ul>
<div
className="text-xs opacity-60 mt-3 pt-3"

View File

@@ -4,6 +4,7 @@ import type { Theme, ThinkingMode } from '../types';
import { useLayerStack } from '../contexts/LayerStackContext';
import { MODAL_PRIORITIES } from '../constants/modalPriorities';
import { estimateTokenCount } from '../../shared/formatters';
import { isMacOS } from '../utils/shortcutFormatter';
interface PromptComposerModalProps {
isOpen: boolean;
@@ -450,11 +451,11 @@ export function PromptComposerModal({
<button
onClick={onToggleEnterToSend}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'}
title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
>
<Keyboard className="w-3 h-3" style={{ color: theme.colors.textDim }} />
<span style={{ color: theme.colors.textDim }}>
{enterToSend ? 'Enter' : '⌘ + Enter'}
{enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</span>
</button>
)}

View File

@@ -49,7 +49,7 @@ import { MODAL_PRIORITIES } from '../constants/modalPriorities';
import { AICommandsPanel } from './AICommandsPanel';
import { SpecKitCommandsPanel } from './SpecKitCommandsPanel';
import { OpenSpecCommandsPanel } from './OpenSpecCommandsPanel';
import { formatShortcutKeys } from '../utils/shortcutFormatter';
import { formatShortcutKeys, isMacOS } from '../utils/shortcutFormatter';
import { ToggleButtonGroup } from './ToggleButtonGroup';
import { SettingCheckbox } from './SettingCheckbox';
import { FontConfigurationPanel } from './FontConfigurationPanel';
@@ -1302,8 +1302,8 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
Input Send Behavior
</label>
<p className="text-xs opacity-50 mb-3">
Configure how to send messages in each mode. Choose between Enter or Command+Enter
for each input type.
Configure how to send messages in each mode. Choose between Enter or{' '}
{isMacOS() ? 'Command' : 'Ctrl'}+Enter for each input type.
</p>
{/* AI Mode Setting */}
@@ -1324,13 +1324,13 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
border: `1px solid ${theme.colors.border}`,
}}
>
{props.enterToSendAI ? 'Enter' : ' + Enter'}
{props.enterToSendAI ? 'Enter' : isMacOS() ? ' + Enter' : 'Ctrl + Enter'}
</button>
</div>
<p className="text-xs opacity-50">
{props.enterToSendAI
? 'Press Enter to send. Use Shift+Enter for new line.'
: 'Press Command+Enter to send. Enter creates new line.'}
: `Press ${isMacOS() ? 'Command' : 'Ctrl'}+Enter to send. Enter creates new line.`}
</p>
</div>
@@ -1352,13 +1352,13 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
border: `1px solid ${theme.colors.border}`,
}}
>
{props.enterToSendTerminal ? 'Enter' : ' + Enter'}
{props.enterToSendTerminal ? 'Enter' : isMacOS() ? ' + Enter' : 'Ctrl + Enter'}
</button>
</div>
<p className="text-xs opacity-50">
{props.enterToSendTerminal
? 'Press Enter to send. Use Shift+Enter for new line.'
: 'Press Command+Enter to send. Enter creates new line.'}
: `Press ${isMacOS() ? 'Command' : 'Ctrl'}+Enter to send. Enter creates new line.`}
</p>
</div>
</div>

View File

@@ -34,6 +34,7 @@ import type { WizardError } from '../services/wizardErrorDetection';
import { AUTO_RUN_FOLDER_NAME, wizardDebugLogger } from '../services/phaseGenerator';
import { getNextFillerPhrase } from '../services/fillerPhrases';
import { ScreenReaderAnnouncement } from '../ScreenReaderAnnouncement';
import { isMacOS } from '../../../utils/shortcutFormatter';
interface ConversationScreenProps {
theme: Theme;
@@ -1613,7 +1614,7 @@ export function ConversationScreen({
className="px-1.5 py-0.5 rounded text-xs"
style={{ backgroundColor: theme.colors.border }}
>
+Enter
{isMacOS() ? '' : 'Ctrl'}+Enter
</kbd>
Send
</span>

View File

@@ -98,7 +98,16 @@ export function ToastProvider({
// Capture audio feedback state for logging
const { enabled: audioEnabled, command: audioCommand } = audioFeedbackRef.current;
// Determine if we have content to send for custom notification
// Also skip if there's no content to send
const hasContent = toast.message && toast.message.trim().length > 0;
// Determine if custom notification will actually be triggered
const willTriggerCustomNotification =
audioEnabled && audioCommand && !toast.skipCustomNotification && hasContent;
// Log toast to system logs (include audio notification info)
// Only log enabled: true when we will actually trigger the notification
window.maestro.logger.toast(toast.title, {
type: toast.type,
message: toast.message,
@@ -107,23 +116,29 @@ export function ToastProvider({
taskDuration: toast.taskDuration,
agentSessionId: toast.agentSessionId,
tabName: toast.tabName,
// Audio/TTS notification info
audioNotification:
audioEnabled && audioCommand
? {
enabled: true,
command: audioCommand,
}
: {
enabled: false,
},
// Audio/TTS notification info - reflects whether notification WILL be triggered
audioNotification: willTriggerCustomNotification
? {
enabled: true,
command: audioCommand,
}
: {
enabled: false,
reason: !audioEnabled
? 'disabled'
: !audioCommand
? 'no-command'
: toast.skipCustomNotification
? 'opted-out'
: !hasContent
? 'no-content'
: 'unknown',
},
});
// Run custom notification command if enabled and configured
// Skip for toasts that explicitly opt out (e.g., synopsis messages)
// Also skip if there's no content to send
const hasContent = toast.message && toast.message.trim().length > 0;
if (audioEnabled && audioCommand && !toast.skipCustomNotification && hasContent) {
if (willTriggerCustomNotification) {
console.log(
'[ToastContext] Running custom notification with message:',
toast.message,