/** * NotificationsSection - Notification settings including triggers and ignored repositories. */ import { api } from '@renderer/api'; import { RepositoryDropdown, SelectedRepositoryItem, } from '@renderer/components/common/RepositoryDropdown'; import { ExternalLink } from 'lucide-react'; import { SettingRow, SettingsSectionHeader, SettingsSelect, SettingsToggle } from '../components'; import { NotificationTriggerSettings } from '../NotificationTriggerSettings'; import type { RepositoryDropdownItem, SafeConfig } from '../hooks/useSettingsConfig'; import type { NotificationTrigger } from '@renderer/types/data'; import type { TeamReviewState, TeamTaskStatus } from '@shared/types'; /** Notification targets span workflow status plus the explicit review axis. */ type NotifiableStatus = TeamTaskStatus | Extract; // Snooze duration options const SNOOZE_OPTIONS = [ { value: 15, label: '15 minutes' }, { value: 30, label: '30 minutes' }, { value: 60, label: '1 hour' }, { value: 120, label: '2 hours' }, { value: 240, label: '4 hours' }, { value: -1, label: 'Until tomorrow' }, ] as const; interface NotificationsSectionProps { readonly safeConfig: SafeConfig; readonly saving: boolean; readonly isSnoozed: boolean; readonly ignoredRepositoryItems: RepositoryDropdownItem[]; readonly excludedRepositoryIds: string[]; readonly onNotificationToggle: ( key: | 'enabled' | 'soundEnabled' | 'includeSubagentErrors' | 'notifyOnLeadInbox' | 'notifyOnUserInbox' | 'notifyOnClarifications' | 'notifyOnStatusChange' | 'notifyOnTaskComments' | 'statusChangeOnlySolo', value: boolean ) => void; readonly onStatusChangeStatusesUpdate: (statuses: string[]) => void; readonly onSnooze: (minutes: number) => Promise; readonly onClearSnooze: () => Promise; readonly onAddIgnoredRepository: (item: RepositoryDropdownItem) => Promise; readonly onRemoveIgnoredRepository: (repositoryId: string) => Promise; readonly onAddTrigger: (trigger: Omit) => Promise; readonly onUpdateTrigger: ( triggerId: string, updates: Partial ) => Promise; readonly onRemoveTrigger: (triggerId: string) => Promise; } export const NotificationsSection = ({ safeConfig, saving, isSnoozed, ignoredRepositoryItems, excludedRepositoryIds, onNotificationToggle, onSnooze, onClearSnooze, onAddIgnoredRepository, onRemoveIgnoredRepository, onAddTrigger, onUpdateTrigger, onRemoveTrigger, onStatusChangeStatusesUpdate, }: NotificationsSectionProps): React.JSX.Element => { return (
{/* Task Completion Notifications */}

Get native OS notifications when Claude finishes tasks — sounds, banners, and Dock/taskbar badges. Works on macOS, Linux, and Windows.

{/* Notification Settings */} onNotificationToggle('enabled', v)} disabled={saving} /> onNotificationToggle('soundEnabled', v)} disabled={saving || !safeConfig.notifications.enabled} /> onNotificationToggle('includeSubagentErrors', v)} disabled={saving || !safeConfig.notifications.enabled} /> onNotificationToggle('notifyOnLeadInbox', v)} disabled={saving || !safeConfig.notifications.enabled} /> onNotificationToggle('notifyOnUserInbox', v)} disabled={saving || !safeConfig.notifications.enabled} /> onNotificationToggle('notifyOnClarifications', v)} disabled={saving || !safeConfig.notifications.enabled} /> onNotificationToggle('notifyOnTaskComments', v)} disabled={saving || !safeConfig.notifications.enabled} />
{isSnoozed ? ( ) : ( v !== 0 && onSnooze(v)} disabled={saving || !safeConfig.notifications.enabled} dropUp /> )}
{/* Task Status Change Notifications — grouped section */}
Task status change notifications
Show native OS notifications when a task's status changes
onNotificationToggle('notifyOnStatusChange', v)} disabled={saving || !safeConfig.notifications.enabled} />
{safeConfig.notifications.notifyOnStatusChange && safeConfig.notifications.enabled ? (
Only in Solo mode
Notify only when the team has no teammates
onNotificationToggle('statusChangeOnlySolo', v)} disabled={saving} />
Notify on these statuses
Which target statuses trigger a notification
) : null}
{/* Custom Triggers */}

Notifications from these repositories will be ignored

{ignoredRepositoryItems.length > 0 ? (
{ignoredRepositoryItems.map((item) => ( onRemoveIgnoredRepository(item.id)} disabled={saving} /> ))}
) : (

No repositories ignored

)}
); }; const STATUS_OPTIONS: { value: NotifiableStatus; label: string }[] = [ { value: 'in_progress', label: 'Started' }, { value: 'completed', label: 'Completed' }, { value: 'needsFix', label: 'Needs Fixes' }, { value: 'approved', label: 'Approved' }, { value: 'pending', label: 'Pending' }, { value: 'deleted', label: 'Deleted' }, ]; const StatusCheckboxGroup = ({ selected, onChange, disabled, }: { selected: string[]; onChange: (statuses: string[]) => void; disabled: boolean; }) => (
{STATUS_OPTIONS.map((opt) => { const checked = selected.includes(opt.value); return ( ); })}
);