fix(notifications): skip custom notification for synopsis messages

Custom notification commands (e.g., TTS, fabric pipelines) should only
run for regular AI conversation flow toasts, not for synopsis messages.

Added skipCustomNotification flag to Toast interface and set it to true
for synopsis toasts. This prevents the notification command from being
executed when a synopsis completes, while still showing OS notifications
and toast UI.
This commit is contained in:
Pedram Amini
2026-02-01 23:09:04 -06:00
parent 13f68c15a7
commit 65af8a23c7
3 changed files with 87 additions and 1 deletions

View File

@@ -361,6 +361,85 @@ describe('ToastContext', () => {
expect(window.maestro.notification.speak).not.toHaveBeenCalled();
});
it('does not trigger custom notification when skipCustomNotification is true', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
let contextValue: ReturnType<typeof useToast> | null = null;
renderWithProvider(
<ToastConsumer
onMount={(ctx) => {
contextValue = ctx;
}}
/>
);
// Enable audio feedback
await act(async () => {
contextValue!.setAudioFeedback(true, 'say -v Alex');
});
vi.clearAllMocks();
// Add toast with skipCustomNotification - should NOT trigger speak
await act(async () => {
contextValue!.addToast({
type: 'info',
title: 'Synopsis',
message: 'Synopsis message that should not be spoken',
skipCustomNotification: true,
});
});
expect(window.maestro.notification.speak).not.toHaveBeenCalled();
// But OS notification should still work
expect(window.maestro.notification.show).toHaveBeenCalled();
});
it('triggers custom notification for regular toasts but not synopsis', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
let contextValue: ReturnType<typeof useToast> | null = null;
renderWithProvider(
<ToastConsumer
onMount={(ctx) => {
contextValue = ctx;
}}
/>
);
// Enable audio feedback
await act(async () => {
contextValue!.setAudioFeedback(true, 'say');
});
vi.clearAllMocks();
// Add regular task completion toast - SHOULD trigger speak
await act(async () => {
contextValue!.addToast({
type: 'success',
title: 'Task Complete',
message: 'Regular task completed',
});
});
expect(window.maestro.notification.speak).toHaveBeenCalledWith('Regular task completed', 'say');
vi.clearAllMocks();
// Add synopsis toast - should NOT trigger speak
await act(async () => {
contextValue!.addToast({
type: 'info',
title: 'Synopsis',
message: 'Synopsis message',
skipCustomNotification: true,
});
});
expect(window.maestro.notification.speak).not.toHaveBeenCalled();
});
it('shows OS notification when enabled', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
let contextValue: ReturnType<typeof useToast> | null = null;

View File

@@ -2549,6 +2549,7 @@ function MaestroConsoleInner() {
);
// Show toast for synopsis completion
// Skip custom notification - synopsis is not part of regular AI conversation flow
addToastRef.current({
type: 'info',
title: 'Synopsis',
@@ -2559,6 +2560,7 @@ function MaestroConsoleInner() {
sessionId: synopsisData!.sessionId,
tabId: synopsisData!.tabId,
tabName: synopsisData!.tabName,
skipCustomNotification: true,
});
// Refresh history panel if available

View File

@@ -18,6 +18,8 @@ export interface Toast {
// Action link - clickable URL shown below message (e.g., PR URL)
actionUrl?: string; // URL to open when clicked
actionLabel?: string; // Label for the action link (defaults to URL)
// Skip custom notification command for this toast (used for synopsis messages)
skipCustomNotification?: boolean;
}
interface ToastContextType {
@@ -118,7 +120,8 @@ export function ToastProvider({
});
// Run custom notification command if enabled and configured
if (audioEnabled && audioCommand) {
// Skip for toasts that explicitly opt out (e.g., synopsis messages)
if (audioEnabled && audioCommand && !toast.skipCustomNotification) {
console.log(
'[ToastContext] Running custom notification with message:',
toast.message,
@@ -128,6 +131,8 @@ export function ToastProvider({
window.maestro.notification.speak(toast.message, audioCommand).catch((err) => {
console.error('[ToastContext] Custom notification failed:', err);
});
} else if (toast.skipCustomNotification) {
console.log('[ToastContext] Custom notification skipped - toast opted out');
} else {
console.log('[ToastContext] Custom notification skipped - enabled:', audioEnabled, 'command:', audioCommand);
}