From 58accdf8a867f66a45a22c586649e71db8f4e665 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 22 May 2026 23:47:31 -0700 Subject: [PATCH] Revamp onboarding notification setup (#2670) --- resources/notification-sounds/beep.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/blip.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/blop.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/bong.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/clack.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/ding.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/sonar.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/thump.mp3 | Bin 0 -> 12717 bytes resources/notification-sounds/two-tone.mp3 | Bin 0 -> 12717 bytes .../runtime-home-service.test.ts | 1 + src/main/codex-accounts/service.test.ts | 1 + src/main/ipc/notifications.ts | 76 ++- src/main/persistence.ts | 22 + src/renderer/src/assets/main.css | 36 ++ .../onboarding/AgentFeatureSetupStep.test.tsx | 26 + .../onboarding/AgentFeatureSetupStep.tsx | 29 ++ .../components/onboarding/AgentStep.test.tsx | 33 ++ .../src/components/onboarding/AgentStep.tsx | 49 +- .../onboarding/FeatureSetupChecklist.tsx | 6 +- .../onboarding/NotificationStep.test.tsx | 47 +- .../onboarding/NotificationStep.tsx | 493 +++++++++++++++--- .../components/onboarding/OnboardingFlow.tsx | 60 ++- .../use-onboarding-flow-persistence.ts | 26 +- .../onboarding/use-onboarding-flow-types.ts | 11 +- .../onboarding/use-onboarding-flow.ts | 39 +- .../settings/NotificationsPane.test.tsx | 1 + .../components/settings/NotificationsPane.tsx | 21 +- .../use-notification-dispatch.ts | 4 +- .../src/components/ui/collapsible.tsx | 24 + .../src/lib/desktop-notification-sound.ts | 4 +- src/shared/constants.ts | 3 +- src/shared/telemetry-events.ts | 1 + src/shared/types.ts | 12 + tests/e2e/onboarding.spec.ts | 131 +++-- 34 files changed, 868 insertions(+), 288 deletions(-) create mode 100644 resources/notification-sounds/beep.mp3 create mode 100644 resources/notification-sounds/blip.mp3 create mode 100644 resources/notification-sounds/blop.mp3 create mode 100644 resources/notification-sounds/bong.mp3 create mode 100644 resources/notification-sounds/clack.mp3 create mode 100644 resources/notification-sounds/ding.mp3 create mode 100644 resources/notification-sounds/sonar.mp3 create mode 100644 resources/notification-sounds/thump.mp3 create mode 100644 resources/notification-sounds/two-tone.mp3 create mode 100644 src/renderer/src/components/onboarding/AgentFeatureSetupStep.test.tsx create mode 100644 src/renderer/src/components/onboarding/AgentFeatureSetupStep.tsx create mode 100644 src/renderer/src/components/onboarding/AgentStep.test.tsx create mode 100644 src/renderer/src/components/ui/collapsible.tsx diff --git a/resources/notification-sounds/beep.mp3 b/resources/notification-sounds/beep.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8293e0fc04e08f1b0156c86c3ffea909b1190b8d GIT binary patch literal 12717 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 literal 0 HcmV?d00001 diff --git a/resources/notification-sounds/blip.mp3 b/resources/notification-sounds/blip.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..855780537b943387ec4dd0ed438689ba77400f91 GIT binary patch literal 12717 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 G$$tYQcz*=| literal 0 HcmV?d00001 diff --git a/resources/notification-sounds/blop.mp3 b/resources/notification-sounds/blop.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3a617391c78b651123d39c2a9425bfb4fad57d98 GIT binary patch literal 12717 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

Imw<=vYF28!%06yeNrGDi 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 Uqhk(r84U%2`=1#;{6EeA0HsxuDgXcg literal 0 HcmV?d00001 diff --git a/resources/notification-sounds/bong.mp3 b/resources/notification-sounds/bong.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8fc2248c8329b0e3ebe69c442fd45f28ef2f4b30 GIT binary patch literal 12717 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 K|L6aO8Telk?W`97 literal 0 HcmV?d00001 diff --git a/resources/notification-sounds/clack.mp3 b/resources/notification-sounds/clack.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e64b3b35f695ab61edd393d1770d1b4732469227 GIT binary patch literal 12717 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

v1wY*2@}&h5O94DQ7>fmw;xL^k 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 X>zUALE9t08Uj4#f{y+SGF$4b#*D|S% literal 0 HcmV?d00001 diff --git a/resources/notification-sounds/ding.mp3 b/resources/notification-sounds/ding.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..14faceb91d9702ae80e5587d0b93faedc3a720a1 GIT binary patch literal 12717 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 Tg}g>bQCC&$@=J!A|J(Ur^k6`| literal 0 HcmV?d00001 diff --git a/resources/notification-sounds/sonar.mp3 b/resources/notification-sounds/sonar.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b7ddf08111f1fa75cb7ba2796aee44991c750c00 GIT binary patch literal 12717 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 Jl>eX1{{ZAG;4T0F literal 0 HcmV?d00001 diff --git a/resources/notification-sounds/two-tone.mp3 b/resources/notification-sounds/two-tone.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..84f0f004a9b40f29b6bdf9415ee8179dee80cd23 GIT binary patch literal 12717 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 literal 0 HcmV?d00001 diff --git a/src/main/codex-accounts/runtime-home-service.test.ts b/src/main/codex-accounts/runtime-home-service.test.ts index cd31d8f65..a4b595864 100644 --- a/src/main/codex-accounts/runtime-home-service.test.ts +++ b/src/main/codex-accounts/runtime-home-service.test.ts @@ -80,6 +80,7 @@ function createSettings(overrides: Partial = {}): GlobalSettings agentTaskComplete: true, terminalBell: false, suppressWhenFocused: true, + customSoundId: 'system', customSoundPath: null, customSoundVolume: 100 }, diff --git a/src/main/codex-accounts/service.test.ts b/src/main/codex-accounts/service.test.ts index c1c566257..fa13ab326 100644 --- a/src/main/codex-accounts/service.test.ts +++ b/src/main/codex-accounts/service.test.ts @@ -73,6 +73,7 @@ function createSettings(overrides: Partial = {}): GlobalSettings agentTaskComplete: true, terminalBell: false, suppressWhenFocused: true, + customSoundId: 'system', customSoundPath: null, customSoundVolume: 100 }, diff --git a/src/main/ipc/notifications.ts b/src/main/ipc/notifications.ts index 5ab0e19a8..0159d0467 100644 --- a/src/main/ipc/notifications.ts +++ b/src/main/ipc/notifications.ts @@ -1,11 +1,22 @@ +/* eslint-disable max-lines -- Why: notification IPC keeps permission, dispatch, custom sound asset, and sound-loading handlers colocated so renderer/main contracts stay auditable. */ import { app, BrowserWindow, Notification, ipcMain, shell } from 'electron' import { readFile, stat } from 'node:fs/promises' import { extname, isAbsolute, normalize } from 'node:path' +import beepSoundPath from '../../../resources/notification-sounds/beep.mp3?asset' +import blipSoundPath from '../../../resources/notification-sounds/blip.mp3?asset' +import blopSoundPath from '../../../resources/notification-sounds/blop.mp3?asset' +import bongSoundPath from '../../../resources/notification-sounds/bong.mp3?asset' +import clackSoundPath from '../../../resources/notification-sounds/clack.mp3?asset' +import dingSoundPath from '../../../resources/notification-sounds/ding.mp3?asset' +import sonarSoundPath from '../../../resources/notification-sounds/sonar.mp3?asset' +import thumpSoundPath from '../../../resources/notification-sounds/thump.mp3?asset' +import twoToneSoundPath from '../../../resources/notification-sounds/two-tone.mp3?asset' import type { Store } from '../persistence' import type { NotificationDispatchRequest, NotificationDispatchResult, NotificationPermissionStatusResult, + NotificationSettings, NotificationSoundDataResult } from '../../shared/types' import { getRepoIdFromWorktreeId } from '../../shared/worktree-id' @@ -27,6 +38,18 @@ const NOTIFICATION_SOUND_MIME_BY_EXTENSION: ReadonlyMap = new Ma ['.aac', 'audio/aac'], ['.flac', 'audio/flac'] ]) +const BUILT_IN_NOTIFICATION_SOUNDS: ReadonlyMap = new Map([ + ['two-tone', twoToneSoundPath], + ['bong', bongSoundPath], + ['thump', thumpSoundPath], + ['blip', blipSoundPath], + ['sonar', sonarSoundPath], + ['blop', blopSoundPath], + ['ding', dingSoundPath], + ['clack', clackSoundPath], + ['beep', beepSoundPath] +]) +type NotificationSoundId = NotificationSettings['customSoundId'] // Why: Electron Notification objects are normal JS objects — if the only // reference is a local variable inside the ipcMain handler, the GC can @@ -48,6 +71,35 @@ function openNotificationSystemSettings(): void { } } +function getEffectiveNotificationSoundId(settings: NotificationSettings): NotificationSoundId { + return settings.customSoundId ?? (settings.customSoundPath ? 'custom' : 'system') +} + +function getSelectedNotificationSoundPath(settings: NotificationSettings): { + path: string | null + reason?: 'missing-path' | 'invalid-path' | 'unsupported-type' +} { + const customSoundId = getEffectiveNotificationSoundId(settings) + if (customSoundId === 'system') { + return { path: null, reason: 'missing-path' } + } + if (customSoundId !== 'custom') { + const builtInPath = BUILT_IN_NOTIFICATION_SOUNDS.get(customSoundId) + return builtInPath ? { path: builtInPath } : { path: null, reason: 'missing-path' } + } + if (!settings.customSoundPath) { + return { path: null, reason: 'missing-path' } + } + const normalizedPath = normalize(settings.customSoundPath) + if (!isAbsolute(normalizedPath)) { + return { path: null, reason: 'invalid-path' } + } + if (!NOTIFICATION_SOUND_MIME_BY_EXTENSION.has(extname(normalizedPath).toLowerCase())) { + return { path: null, reason: 'unsupported-type' } + } + return { path: normalizedPath } +} + function waitForNotificationDisplay(notification: Notification): Promise { return new Promise((resolve) => { let settled = false @@ -171,7 +223,7 @@ export function registerNotificationHandlers(store: Store, runtime?: OrcaRuntime } const notificationOptions = buildNotificationOptions(args) - if (settings.customSoundPath) { + if (getEffectiveNotificationSoundId(settings) !== 'system') { notificationOptions.silent = true } else if (process.platform === 'darwin') { // Why: macOS treats an unset notification sound as silent. When Orca is @@ -262,14 +314,11 @@ export function registerNotificationHandlers(store: Store, runtime?: OrcaRuntime (): | { ok: true; path: string } | { ok: false; reason: 'missing-path' | 'invalid-path' | 'unsupported-type' } => { - const pathValue = store.getSettings().notifications.customSoundPath - if (!pathValue) { - return { ok: false, reason: 'missing-path' } - } - const normalizedPath = normalize(pathValue) - if (!isAbsolute(normalizedPath)) { - return { ok: false, reason: 'invalid-path' } + const selectedSound = getSelectedNotificationSoundPath(store.getSettings().notifications) + if (!selectedSound.path) { + return { ok: false, reason: selectedSound.reason ?? 'missing-path' } } + const normalizedPath = normalize(selectedSound.path) if (!NOTIFICATION_SOUND_MIME_BY_EXTENSION.has(extname(normalizedPath).toLowerCase())) { return { ok: false, reason: 'unsupported-type' } } @@ -279,15 +328,12 @@ export function registerNotificationHandlers(store: Store, runtime?: OrcaRuntime ipcMain.removeHandler('notifications:loadSound') ipcMain.handle('notifications:loadSound', async (): Promise => { - const pathValue = store.getSettings().notifications.customSoundPath - if (!pathValue) { - return { ok: false, reason: 'missing-path' } + const selectedSound = getSelectedNotificationSoundPath(store.getSettings().notifications) + if (!selectedSound.path) { + return { ok: false, reason: selectedSound.reason ?? 'missing-path' } } - const normalizedPath = normalize(pathValue) - if (!isAbsolute(normalizedPath)) { - return { ok: false, reason: 'invalid-path' } - } + const normalizedPath = normalize(selectedSound.path) const mimeType = NOTIFICATION_SOUND_MIME_BY_EXTENSION.get(extname(normalizedPath).toLowerCase()) if (!mimeType) { diff --git a/src/main/persistence.ts b/src/main/persistence.ts index cf99006a6..e9fffd8b0 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -197,6 +197,27 @@ function normalizeNotificationSettings(value: unknown): NotificationSettings { const defaults = getDefaultNotificationSettings() const candidate = value && typeof value === 'object' ? (value as Partial) : {} + const rawSoundId = (candidate as { customSoundId?: unknown }).customSoundId + const customSoundId = + rawSoundId === 'system' || + rawSoundId === 'two-tone' || + rawSoundId === 'bong' || + rawSoundId === 'thump' || + rawSoundId === 'blip' || + rawSoundId === 'sonar' || + rawSoundId === 'blop' || + rawSoundId === 'ding' || + rawSoundId === 'clack' || + rawSoundId === 'beep' || + rawSoundId === 'custom' + ? rawSoundId + : rawSoundId === 'orca' || rawSoundId === 'chime' + ? 'two-tone' + : rawSoundId === 'pop' + ? 'blop' + : typeof candidate.customSoundPath === 'string' + ? 'custom' + : defaults.customSoundId const rawVolume = candidate.customSoundVolume const customSoundVolume = typeof rawVolume === 'number' && Number.isFinite(rawVolume) @@ -205,6 +226,7 @@ function normalizeNotificationSettings(value: unknown): NotificationSettings { return { ...defaults, ...candidate, + customSoundId, customSoundVolume } } diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index 7e41f45ab..3b1551612 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -818,6 +818,42 @@ animation: settings-shell-enter 180ms ease-out; } +.collapsible-height-content { + overflow: hidden; +} + +.collapsible-height-content[data-state='open'] { + animation: collapsible-down 200ms ease-out; +} + +.collapsible-height-content[data-state='closed'] { + animation: collapsible-up 180ms ease-out; +} + +@keyframes collapsible-down { + from { + height: 0; + opacity: 0; + } + + to { + height: var(--radix-collapsible-content-height); + opacity: 1; + } +} + +@keyframes collapsible-up { + from { + height: var(--radix-collapsible-content-height); + opacity: 1; + } + + to { + height: 0; + opacity: 0; + } +} + .sidebar.collapsed { width: 0; border-right: none; diff --git a/src/renderer/src/components/onboarding/AgentFeatureSetupStep.test.tsx b/src/renderer/src/components/onboarding/AgentFeatureSetupStep.test.tsx new file mode 100644 index 000000000..8a11419d6 --- /dev/null +++ b/src/renderer/src/components/onboarding/AgentFeatureSetupStep.test.tsx @@ -0,0 +1,26 @@ +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it, vi } from 'vitest' +import { AgentFeatureSetupStep } from './AgentFeatureSetupStep' + +describe('AgentFeatureSetupStep', () => { + it('renders the agent feature setup checklist', () => { + const html = renderToStaticMarkup( + + ) + + expect(html).toContain('Set up agent features') + expect(html).toContain('Agent Browser Use') + expect(html).toContain('Computer Use') + expect(html).toContain('Agent Orchestration') + expect(html).toContain('role="checkbox"') + }) +}) diff --git a/src/renderer/src/components/onboarding/AgentFeatureSetupStep.tsx b/src/renderer/src/components/onboarding/AgentFeatureSetupStep.tsx new file mode 100644 index 000000000..e6f4a3e50 --- /dev/null +++ b/src/renderer/src/components/onboarding/AgentFeatureSetupStep.tsx @@ -0,0 +1,29 @@ +import { FeatureSetupChecklist } from './FeatureSetupChecklist' +import { FeatureSetupInlineTerminal } from './FeatureSetupInlineTerminal' +import type { OnboardingFeatureSetupSelection } from './onboarding-feature-setup' + +type AgentFeatureSetupStepProps = { + featureSetup: OnboardingFeatureSetupSelection + onFeatureSetupChange: (value: OnboardingFeatureSetupSelection) => void + featureSetupCommand: string | null + featureSetupCommandSelection: OnboardingFeatureSetupSelection | null +} + +export function AgentFeatureSetupStep({ + featureSetup, + onFeatureSetupChange, + featureSetupCommand, + featureSetupCommandSelection +}: AgentFeatureSetupStepProps): React.JSX.Element { + return ( + <> + + {featureSetupCommand ? ( + + ) : null} + + ) +} diff --git a/src/renderer/src/components/onboarding/AgentStep.test.tsx b/src/renderer/src/components/onboarding/AgentStep.test.tsx new file mode 100644 index 000000000..5bb6bc57d --- /dev/null +++ b/src/renderer/src/components/onboarding/AgentStep.test.tsx @@ -0,0 +1,33 @@ +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it, vi } from 'vitest' +import { AGENT_CATALOG } from '@/lib/agent-catalog' +import { AgentStep } from './AgentStep' + +describe('AgentStep', () => { + it('shows the collapsed fallback agents summary', () => { + const html = renderToStaticMarkup( + + ) + + expect(html).toContain(`Show ${AGENT_CATALOG.length - 1} more agents→`) + }) + + it('labels the fallback agents summary as hide when expanded', () => { + const html = renderToStaticMarkup( + + ) + + expect(html).toContain('Hide agents') + expect(html).not.toContain(`Show ${AGENT_CATALOG.length - 1} more agents→`) + }) +}) diff --git a/src/renderer/src/components/onboarding/AgentStep.tsx b/src/renderer/src/components/onboarding/AgentStep.tsx index 5db0506ad..7fd3bd870 100644 --- a/src/renderer/src/components/onboarding/AgentStep.tsx +++ b/src/renderer/src/components/onboarding/AgentStep.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from 'react' -import { ExternalLink } from 'lucide-react' +import { Check, ExternalLink } from 'lucide-react' import { AGENT_CATALOG, AgentIcon } from '@/lib/agent-catalog' import { cn } from '@/lib/utils' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import type { TuiAgent } from '../../../../shared/types' type AgentStepProps = { @@ -33,6 +34,7 @@ export function AgentStep({ selectedAgent, onSelect, detectedSet, isDetecting }: // disclosure once it's open; controlling `open` directly off the prop would // slam it shut as soon as `selectedEntryIsCollapsed` flips back to false. const [openState, setOpenState] = useState(selectedEntryIsCollapsed) + const fallbackRestLabel = openState ? 'Hide agents' : `Show ${fallbackRest.length} more agents→` useEffect(() => { if (selectedEntryIsCollapsed) { setOpenState(true) @@ -79,25 +81,23 @@ export function AgentStep({ selectedAgent, onSelect, detectedSet, isDetecting }: {fallbackRest.length > 0 && ( -

setOpenState(e.currentTarget.open)} - > - - Show {fallbackRest.length} more {hasDetected ? 'agents' : ''}→ - -
- {fallbackRest.map((agent) => ( - onSelect(agent.id, true)} - /> - ))} -
-
+ + + {fallbackRestLabel} + + +
+ {fallbackRest.map((agent) => ( + onSelect(agent.id, true)} + /> + ))} +
+
+
)} ) @@ -129,12 +129,17 @@ function AgentButton({ className={cn( 'group relative overflow-hidden rounded-xl border p-3.5 text-left transition-all', selected - ? 'border-foreground/50 bg-muted ring-2 ring-foreground/20' + ? 'border-violet-500/60 bg-violet-500/10 ring-2 ring-violet-500/30' : 'border-border bg-muted/30 hover:bg-muted/60' )} onClick={onClick} > -
+ {selected ? ( +
+ +
+ ) : null} +
diff --git a/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx b/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx index cd19cb277..1611979fc 100644 --- a/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx +++ b/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx @@ -70,7 +70,7 @@ export function FeatureSetupChecklist({ 'flex min-h-40 flex-col rounded-lg border px-4 py-3 text-left transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', selected - ? 'border-foreground/40 bg-card text-foreground' + ? 'border-violet-500/60 bg-violet-500/10 text-foreground ring-2 ring-violet-500/30' : 'border-border bg-muted/20 text-muted-foreground hover:bg-muted/40' )} onClick={() => onChange({ ...value, [row.id]: !selected })} @@ -91,11 +91,11 @@ export function FeatureSetupChecklist({ className={cn( 'flex size-5 items-center justify-center rounded-full border transition-colors', selected - ? 'border-primary bg-primary text-primary-foreground' + ? 'border-violet-500 bg-violet-500 text-white' : 'border-border bg-background' )} > - {selected ? : null} + {selected ? : null} {row.title} diff --git a/src/renderer/src/components/onboarding/NotificationStep.test.tsx b/src/renderer/src/components/onboarding/NotificationStep.test.tsx index df2763bb6..044951cd0 100644 --- a/src/renderer/src/components/onboarding/NotificationStep.test.tsx +++ b/src/renderer/src/components/onboarding/NotificationStep.test.tsx @@ -1,33 +1,36 @@ import { renderToStaticMarkup } from 'react-dom/server' import { describe, expect, it, vi } from 'vitest' +import type { GlobalSettings } from '../../../../shared/types' import { NotificationStep } from './NotificationStep' +function createSettings(): GlobalSettings { + return { + notifications: { + enabled: true, + agentTaskComplete: true, + terminalBell: true, + suppressWhenFocused: false, + customSoundId: 'system', + customSoundPath: null, + customSoundVolume: 80 + } + } as GlobalSettings +} + describe('NotificationStep', () => { - it('renders feature setup in the notification step', () => { + it('renders sound setup without the old notification source switches', () => { const html = renderToStaticMarkup( - + ) - expect(html).toContain('Set up agent features') - expect(html).toContain('Agent Browser Use') - expect(html).toContain('Computer Use') - expect(html).toContain('Agent Orchestration') - expect(html).toContain('role="checkbox"') + expect(html).toContain('System Default') + expect(html).toContain('Two Tone') + expect(html).toContain('Sonar') + expect(html).toContain('Ding') + expect(html).toContain('Send Test Notification') + expect(html).not.toContain('Agent task complete') + expect(html).not.toContain('Terminal bell') + expect(html).not.toContain('Set up agent features') expect(html).not.toContain('Connect task sources') }) }) diff --git a/src/renderer/src/components/onboarding/NotificationStep.tsx b/src/renderer/src/components/onboarding/NotificationStep.tsx index 1d21e56e2..93c9b8a64 100644 --- a/src/renderer/src/components/onboarding/NotificationStep.tsx +++ b/src/renderer/src/components/onboarding/NotificationStep.tsx @@ -1,10 +1,36 @@ +/* eslint-disable max-lines -- Why: this onboarding step owns the full notification setup surface, including macOS guidance, sound choices, upload, and volume controls. */ +import { useEffect, useRef, useState } from 'react' +import type { LucideIcon } from 'lucide-react' +import { + Activity, + AudioWaveform, + Bell, + BellRing, + Check, + ChevronDown, + CircleDot, + FileAudio, + Keyboard, + MousePointer2, + Radio, + Radar, + Settings, + Upload, + Volume1, + Volume2, + X, + Zap +} from 'lucide-react' +import { toast } from 'sonner' +import type { GlobalSettings, NotificationPermissionStatusResult } from '../../../../shared/types' import { cn } from '@/lib/utils' -import { FeatureSetupInlineTerminal } from './FeatureSetupInlineTerminal' -import { FeatureSetupChecklist } from './FeatureSetupChecklist' -import type { OnboardingFeatureSetupSelection } from './onboarding-feature-setup' +import { basename } from '@/lib/path' +import { Button } from '@/components/ui/button' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' +import { Slider } from '@/components/ui/slider' +import { sendNotificationSettingsTestNotification } from '@/components/settings/NotificationsPane' +import logo from '../../../../../resources/logo.svg' -// Why: wizard uses positive framing ("notify when focused"); persisted -// setting stays `suppressWhenFocused` and is inverted at the boundary. export type NotificationDraft = { agentTaskComplete: boolean terminalBell: boolean @@ -12,86 +38,393 @@ export type NotificationDraft = { } type NotificationStepProps = { - value: NotificationDraft - onChange: (value: NotificationDraft) => void - featureSetup: OnboardingFeatureSetupSelection - onFeatureSetupChange: (value: OnboardingFeatureSetupSelection) => void - featureSetupCommand: string | null - featureSetupCommandSelection: OnboardingFeatureSetupSelection | null + settings: GlobalSettings | null + updateSettings: (updates: Partial) => Promise | void } +type NotificationSoundOption = { + id: GlobalSettings['notifications']['customSoundId'] + title: string + icon: LucideIcon +} + +const SOUND_OPTIONS: readonly NotificationSoundOption[] = [ + { + id: 'system', + title: 'System Default', + icon: Bell + }, + { + id: 'two-tone', + title: 'Two Tone', + icon: AudioWaveform + }, + { + id: 'bong', + title: 'Bong', + icon: CircleDot + }, + { + id: 'thump', + title: 'Thump', + icon: Volume1 + }, + { + id: 'blip', + title: 'Blip', + icon: Zap + }, + { + id: 'sonar', + title: 'Sonar', + icon: Radar + }, + { + id: 'blop', + title: 'Blop', + icon: Activity + }, + { + id: 'ding', + title: 'Ding', + icon: Radio + }, + { + id: 'clack', + title: 'Clack', + icon: Keyboard + }, + { + id: 'beep', + title: 'Beep', + icon: MousePointer2 + } +] + export function NotificationStep({ - value, - onChange, - featureSetup, - onFeatureSetupChange, - featureSetupCommand, - featureSetupCommandSelection -}: NotificationStepProps) { - const rows: { key: keyof NotificationDraft; title: string; description: string }[] = [ - { - key: 'agentTaskComplete', - title: 'Agent task complete', - description: 'Ping me when an agent finishes its work.' - }, - { - key: 'terminalBell', - title: 'Terminal bell', - description: 'Play a sound when a terminal rings, usually a question waiting on you.' - }, - { - key: 'notifyWhenFocused', - title: 'Notify even when Orca is focused', - description: "Show notifications while you're already in the app." + settings, + updateSettings +}: NotificationStepProps): React.JSX.Element { + const notificationSettings = settings?.notifications + const notificationSettingsRef = useRef(notificationSettings) + const [permissionStatus, setPermissionStatus] = + useState(null) + const [volumeDraft, setVolumeDraft] = useState(notificationSettings?.customSoundVolume ?? 100) + const [advancedOpen, setAdvancedOpen] = useState(false) + const [isPickingSound, setIsPickingSound] = useState(false) + const [showMacSettingsPreview, setShowMacSettingsPreview] = useState(false) + + useEffect(() => { + notificationSettingsRef.current = notificationSettings + setVolumeDraft(notificationSettings?.customSoundVolume ?? 100) + }, [notificationSettings]) + + useEffect(() => { + let cancelled = false + void window.api.notifications.getPermissionStatus().then((status) => { + if (!cancelled) { + setPermissionStatus(status) + } + }) + return () => { + cancelled = true } - ] - return ( - <> -
- {rows.map((row, idx) => ( - - ))} + }, []) + + const updateNotificationSettings = async ( + updates: Partial + ): Promise => { + const current = notificationSettingsRef.current + if (!current) { + return + } + const nextNotifications = { + ...current, + ...updates + } + notificationSettingsRef.current = nextNotifications + await updateSettings({ + notifications: nextNotifications + }) + } + + const handleMacPermission = async (): Promise => { + setShowMacSettingsPreview(true) + const status = await window.api.notifications.requestPermission() + setPermissionStatus(status) + await window.api.notifications.openSystemSettings() + } + + const previewSound = async ( + customSoundId: GlobalSettings['notifications']['customSoundId'] + ): Promise => { + if (customSoundId === 'system') { + return + } + const result = await window.api.notifications.playSound({ + force: true, + volume: volumeDraft + }) + if (!result.played) { + toast.error('Notification sound could not be played') + } + } + + const handleChooseBuiltInSound = async ( + customSoundId: GlobalSettings['notifications']['customSoundId'] + ): Promise => { + await updateNotificationSettings({ customSoundId }) + await previewSound(customSoundId) + } + + const handleChooseCustomSound = async (): Promise => { + setIsPickingSound(true) + try { + const soundPath = await window.api.shell.pickAudio() + if (soundPath) { + await updateNotificationSettings({ customSoundId: 'custom', customSoundPath: soundPath }) + await previewSound('custom') + setAdvancedOpen(true) + } + } finally { + setIsPickingSound(false) + } + } + + const handleVolumeCommit = (value: number): void => { + if (notificationSettingsRef.current?.customSoundVolume !== value) { + void updateNotificationSettings({ customSoundVolume: value }) + } + } + + const handleSendTestNotification = async (): Promise => { + if (!notificationSettings) { + toast.error('Notification settings are still loading') + return + } + await sendNotificationSettingsTestNotification(notificationSettings, volumeDraft) + } + + if (!notificationSettings) { + return ( +
+ Loading notification settings…
-

- Configure other agent status personalization, like custom sounds, under{' '} - Settings → Notifications. -

- - {featureSetupCommand ? ( - + ) + } + + const customPath = notificationSettings.customSoundPath + const selectedSoundId = notificationSettings.customSoundId + const soundOptions = customPath + ? [ + ...SOUND_OPTIONS, + { + id: 'custom' as const, + title: basename(customPath), + icon: FileAudio + } + ] + : SOUND_OPTIONS + const canAdjustVolume = selectedSoundId !== 'system' + const isMac = permissionStatus?.platform === 'darwin' + + return ( +
+ {isMac ? ( +
+
+
+
+ + Allow Orca in macOS +
+

+ macOS controls notifications per app. Open System Settings and make sure Orca is + allowed to show alerts and play sounds. +

+
+ +
+ {showMacSettingsPreview ? ( +
+
+
+
+ +
+
+
Allow notifications
+
Orca
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 9:41 +
+
+
+
+ +
+
+ ) : null} +
) : null} - + +
+
+
+

Choose a sound

+

+ Pick the alert Orca plays after a desktop notification is delivered. +

+
+ +
+ +
+ {soundOptions.map((option) => { + const selected = selectedSoundId === option.id + const OptionIcon = option.icon + return ( + + ) + })} +
+ + {canAdjustVolume ? ( +
+
+ + setVolumeDraft(value)} + onValueCommit={([value]) => handleVolumeCommit(value)} + className="flex-1" + aria-label="Notification sound volume" + /> + + {volumeDraft}% + +
+
+ ) : null} +
+ + + + + + +
+
+
+
+ + Upload a sound +
+

+ MP3, WAV, OGG, M4A, AAC, or FLAC. Orca stores only the local file path. +

+ {customPath ? ( +

+ {customPath} +

+ ) : null} +
+ +
+
+
+
+
) } diff --git a/src/renderer/src/components/onboarding/OnboardingFlow.tsx b/src/renderer/src/components/onboarding/OnboardingFlow.tsx index db72c2d30..b92fa0a7e 100644 --- a/src/renderer/src/components/onboarding/OnboardingFlow.tsx +++ b/src/renderer/src/components/onboarding/OnboardingFlow.tsx @@ -1,5 +1,16 @@ import { useEffect } from 'react' -import { ChevronLeft, CornerDownLeft, Loader2 } from 'lucide-react' +import { + Bell, + Bot, + ChevronLeft, + CornerDownLeft, + FolderOpen, + Loader2, + Palette, + Plug, + Wrench, + type LucideIcon +} from 'lucide-react' import { cn } from '@/lib/utils' import { isEditableTarget } from '@/lib/editable-target' import { getScreenSubmitModifierLabel, isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' @@ -8,6 +19,7 @@ import type { OnboardingState } from '../../../../shared/types' import { AgentStep } from './AgentStep' import { ThemeStep } from './ThemeStep' import { NotificationStep } from './NotificationStep' +import { AgentFeatureSetupStep } from './AgentFeatureSetupStep' import { IntegrationsStep } from './IntegrationsStep' import { RepoStep } from './RepoStep' import { STEPS, useOnboardingFlow } from './use-onboarding-flow' @@ -24,9 +36,12 @@ const stepCopy = { subtitle: 'Pick the look you want to stare at for hours.' }, notifications: { + title: 'Set up notifications', + subtitle: 'Allow desktop alerts and choose the sound Orca uses when work needs attention.' + }, + agentSetup: { title: 'Set up Orca for agents', - subtitle: - 'Get notifications when agents need you, and choose the capabilities Orca should enable on this computer.' + subtitle: 'Choose the capabilities Orca should enable on this computer.' }, integrations: { title: 'Connect your task sources', @@ -41,11 +56,21 @@ const stepCopy = { const stepTooltipLabels = { agent: 'Default Agent', theme: 'Appearance', - notifications: 'Agent tools', + notifications: 'Notifications', + agentSetup: 'Agent setup', integrations: 'Integrations', repo: 'Create project' } as const +const stepIcons = { + agent: Bot, + theme: Palette, + notifications: Bell, + agentSetup: Wrench, + integrations: Plug, + repo: FolderOpen +} satisfies Record + type OnboardingFlowProps = { onboarding: OnboardingState onOnboardingChange: (state: OnboardingState) => void @@ -61,8 +86,9 @@ export default function OnboardingFlow({ const continueShortcutModifierLabel = getScreenSubmitModifierLabel() const { currentStep, stepIndex, busyLabel } = flow const copy = stepCopy[currentStep.id] + const StepIcon = stepIcons[currentStep.id] const shouldShowSetupAction = - currentStep.id === 'notifications' && + currentStep.id === 'agentSetup' && flow.hasSelectedFeatureSetup && !flow.featureSetupTerminalCommand const primaryActionLabel = busyLabel ?? (shouldShowSetupAction ? 'Set up' : 'Continue') @@ -162,12 +188,17 @@ export default function OnboardingFlow({ Welcome to Orca
)} -

- {copy.title} -

-

- {copy.subtitle} -

+
+
+

+ {copy.title} +

+

+ {copy.subtitle} +

+
+ +
@@ -188,9 +219,10 @@ export default function OnboardingFlow({ /> )} {currentStep.id === 'notifications' && ( - + )} + {currentStep.id === 'agentSetup' && ( + ) => Promise | void @@ -134,7 +132,6 @@ export function usePersistCurrentStep({ currentStepId, selectedAgent, theme, - notifications, featureSetupSelection, settings, updateSettings, @@ -171,22 +168,18 @@ export function usePersistCurrentStep({ return { ok: true } } if (currentStepId === 'notifications') { - const enabled = notifications.agentTaskComplete || notifications.terminalBell - if (enabled) { - // Why: triggers macOS first-prompt notification on first call. Only fire - // on Continue; Skip uses the persistence-only path below. - await window.api.notifications.requestPermission() - } await updateSettings({ notifications: { ...settings.notifications, - enabled, - agentTaskComplete: notifications.agentTaskComplete, - terminalBell: notifications.terminalBell, - // Why: invert positive UX framing back to persisted negative field. - suppressWhenFocused: !notifications.notifyWhenFocused + enabled: true, + agentTaskComplete: true, + terminalBell: true } }) + onOnboardingChange(await persistStep(3)) + return { ok: true } + } + if (currentStepId === 'agentSetup') { const setupResult = await runOnboardingFeatureSetup(featureSetupSelection) const featureSetupResult: OnboardingFeatureSetupResult = setupResult track('onboarding_feature_setup_run', { @@ -208,7 +201,7 @@ export function usePersistCurrentStep({ toast.message('Opened Computer Use permissions') } } - onOnboardingChange(await persistStep(3)) + onOnboardingChange(await persistStep(4)) return { ok: true, featureSetupResult } } if (currentStepId === 'integrations') { @@ -216,7 +209,7 @@ export function usePersistCurrentStep({ // store slices when the user actually wires them up. The step itself // is a no-op for settings/onboarding state beyond marking it // completed. - onOnboardingChange(await persistStep(4)) + onOnboardingChange(await persistStep(5)) return { ok: true } } return { ok: false } @@ -227,7 +220,6 @@ export function usePersistCurrentStep({ }, [ currentStepId, featureSetupSelection, - notifications, onboardingChecklist, onOnboardingChange, selectedAgent, diff --git a/src/renderer/src/components/onboarding/use-onboarding-flow-types.ts b/src/renderer/src/components/onboarding/use-onboarding-flow-types.ts index 0db737aa4..832511e97 100644 --- a/src/renderer/src/components/onboarding/use-onboarding-flow-types.ts +++ b/src/renderer/src/components/onboarding/use-onboarding-flow-types.ts @@ -1,14 +1,15 @@ -export type StepNumber = 1 | 2 | 3 | 4 | 5 -export type StepId = 'agent' | 'theme' | 'notifications' | 'integrations' | 'repo' +export type StepNumber = 1 | 2 | 3 | 4 | 5 | 6 +export type StepId = 'agent' | 'theme' | 'notifications' | 'agentSetup' | 'integrations' | 'repo' export const STEPS: readonly { id: StepId stepNumber: StepNumber - valueKind: 'agent' | 'theme' | 'notifications' | 'integrations' | 'repo' + valueKind: 'agent' | 'theme' | 'notifications' | 'agent_setup' | 'integrations' | 'repo' }[] = [ { id: 'agent', stepNumber: 1, valueKind: 'agent' }, { id: 'theme', stepNumber: 2, valueKind: 'theme' }, { id: 'notifications', stepNumber: 3, valueKind: 'notifications' }, - { id: 'integrations', stepNumber: 4, valueKind: 'integrations' }, - { id: 'repo', stepNumber: 5, valueKind: 'repo' } + { id: 'agentSetup', stepNumber: 4, valueKind: 'agent_setup' }, + { id: 'integrations', stepNumber: 5, valueKind: 'integrations' }, + { id: 'repo', stepNumber: 6, valueKind: 'repo' } ] diff --git a/src/renderer/src/components/onboarding/use-onboarding-flow.ts b/src/renderer/src/components/onboarding/use-onboarding-flow.ts index 7e0d9593b..1340098bf 100644 --- a/src/renderer/src/components/onboarding/use-onboarding-flow.ts +++ b/src/renderer/src/components/onboarding/use-onboarding-flow.ts @@ -11,7 +11,6 @@ import { ONBOARDING_FINAL_STEP } from '../../../../shared/constants' import { isGitRepoKind } from '../../../../shared/repo-kind' import type { EventProps } from '../../../../shared/telemetry-events' import type { GlobalSettings, OnboardingState, Repo, TuiAgent } from '../../../../shared/types' -import type { NotificationDraft } from './NotificationStep' import { DEFAULT_ONBOARDING_FEATURE_SETUP_SELECTION, ONBOARDING_FEATURE_SETUP_IDS, @@ -91,15 +90,6 @@ export function useOnboardingFlow( // Why: hydrate theme from saved settings instead of hardcoding 'dark' so users // who already configured a theme see their choice preselected. const [theme, setTheme] = useState(settings?.theme ?? 'dark') - // Why: wizard force-defaults every toggle on (ignoring stored settings) so - // first-run users land in the most attentive state and choose what to dial - // back. Positive framing ("Notify when focused") inverts back to the - // persisted `suppressWhenFocused` field at save time. - const [notifications, setNotifications] = useState({ - agentTaskComplete: true, - terminalBell: true, - notifyWhenFocused: true - }) const [featureSetupSelection, setFeatureSetupSelection] = useState(DEFAULT_ONBOARDING_FEATURE_SETUP_SELECTION) const [featureSetupTerminalCommand, setFeatureSetupTerminalCommand] = useState( @@ -243,12 +233,12 @@ export function useOnboardingFlow( return } startedTrackedRef.current = true - // Why: `resumed_from_step` is the step the user finished (1..3), not the + // Why: `resumed_from_step` is the step the user finished, not the // step we resume into. const lastCompleted = onboarding.lastCompletedStep track( 'onboarding_started', - lastCompleted >= 1 && lastCompleted <= 3 + lastCompleted >= 1 && lastCompleted < ONBOARDING_FINAL_STEP ? { resumed_from_step: lastCompleted as StepNumber } : {} ) @@ -364,7 +354,6 @@ export function useOnboardingFlow( currentStepId: currentStep.id, selectedAgent, theme, - notifications, featureSetupSelection, settings, updateSettings, @@ -396,30 +385,30 @@ export function useOnboardingFlow( // the first call's setStepIndex has run, advancing twice and skipping a // step. A ref flips synchronously so re-entries bail immediately. const nextInFlightRef = useRef(false) - const notificationsStepCompletedTrackedRef = useRef(false) + const featureSetupStepCompletedTrackedRef = useRef(false) const next = useCallback( async (advancedVia: 'button' | 'keyboard' = 'button') => { if (nextInFlightRef.current || busyLabel || currentStep.id === 'repo') { return } - if (currentStep.id === 'notifications' && featureSetupTerminalCommand) { + if (currentStep.id === 'agentSetup' && featureSetupTerminalCommand) { setStepIndex((idx) => Math.min(idx + 1, STEPS.length - 1)) return } nextInFlightRef.current = true - if (currentStep.id === 'notifications' && hasSelectedFeatureSetup) { + if (currentStep.id === 'agentSetup' && hasSelectedFeatureSetup) { setBusyLabel('Setting up features…') } try { const trackCurrentStepCompleted = (): void => { - if (currentStep.id === 'notifications') { - if (notificationsStepCompletedTrackedRef.current) { + if (currentStep.id === 'agentSetup') { + if (featureSetupStepCompletedTrackedRef.current) { return } // Why: feature setup can keep the user on this already-persisted // step to review a terminal command; later checklist edits must // not double-count the same step completion. - notificationsStepCompletedTrackedRef.current = true + featureSetupStepCompletedTrackedRef.current = true } const durationMs = consumeStepDurationMs() track('onboarding_step_completed', { @@ -434,7 +423,7 @@ export function useOnboardingFlow( } const result = await persistCurrentStep() const nextCommand = result.featureSetupResult?.skillInstallCommand ?? null - if (currentStep.id === 'notifications' && nextCommand) { + if (currentStep.id === 'agentSetup' && nextCommand) { trackCurrentStepCompleted() setFeatureSetupTerminalSelection(featureSetupSelection) setFeatureSetupTerminalCommand(nextCommand) @@ -445,7 +434,7 @@ export function useOnboardingFlow( setStepIndex((idx) => Math.min(idx + 1, STEPS.length - 1)) } } finally { - if (currentStep.id === 'notifications') { + if (currentStep.id === 'agentSetup') { setBusyLabel(null) } nextInFlightRef.current = false @@ -623,14 +612,14 @@ export function useOnboardingFlow( ]) const skipAgentSetup = useCallback(async () => { - if (busyLabel || currentStep.id !== 'notifications') { + if (busyLabel || currentStep.id !== 'agentSetup') { return } setError(null) const durationMs = consumeStepDurationMs() try { - // Why: this step's primary action can request notification permission and - // run selected feature setup. Skip is the explicit "not now" path. + // Why: this step's primary action can run selected feature setup. Skip is + // the explicit "not now" path. const nextState = await persistStep(currentStep.stepNumber) onOnboardingChange(nextState) track('onboarding_step_skipped', { @@ -696,8 +685,6 @@ export function useOnboardingFlow( setSelectedAgent: setSelectedAgentInteractive, theme, setTheme: setThemeInteractive, - notifications, - setNotifications, featureSetupSelection, setFeatureSetupSelection: setFeatureSetupSelectionInteractive, featureSetupTerminalCommand, diff --git a/src/renderer/src/components/settings/NotificationsPane.test.tsx b/src/renderer/src/components/settings/NotificationsPane.test.tsx index 0cc38b18a..68eca3763 100644 --- a/src/renderer/src/components/settings/NotificationsPane.test.tsx +++ b/src/renderer/src/components/settings/NotificationsPane.test.tsx @@ -23,6 +23,7 @@ function createSettings(): GlobalSettings { agentTaskComplete: true, terminalBell: true, suppressWhenFocused: true, + customSoundId: 'system', customSoundPath: null, customSoundVolume: 50 } diff --git a/src/renderer/src/components/settings/NotificationsPane.tsx b/src/renderer/src/components/settings/NotificationsPane.tsx index 2d419ccff..4855927fd 100644 --- a/src/renderer/src/components/settings/NotificationsPane.tsx +++ b/src/renderer/src/components/settings/NotificationsPane.tsx @@ -96,13 +96,14 @@ export async function sendNotificationSettingsTestNotification( // Why: the Test button must always play through, even if the user clicks // it twice in quick succession — the in-flight dedupe is for incidental // bursts of real notifications, not for an explicit user action. - const soundResult = notificationSettings.customSoundPath - ? await window.api.notifications.playSound({ - force: true, - volume: volumeDraft - }) - : null - if (notificationSettings.customSoundPath && soundResult && !soundResult.played) { + const soundResult = + notificationSettings.customSoundId !== 'system' + ? await window.api.notifications.playSound({ + force: true, + volume: volumeDraft + }) + : null + if (notificationSettings.customSoundId !== 'system' && soundResult && !soundResult.played) { toast.error('Custom notification sound could not be played') return } @@ -193,7 +194,7 @@ export function NotificationsPane({ try { const soundPath = await window.api.shell.pickAudio() if (soundPath) { - updateNotificationSettings({ customSoundPath: soundPath }) + updateNotificationSettings({ customSoundId: 'custom', customSoundPath: soundPath }) } } finally { setIsPickingSound(false) @@ -287,7 +288,7 @@ export function NotificationsPane({ variant="ghost" size="sm" disabled={!notificationSettings.enabled} - onClick={() => updateNotificationSettings({ customSoundPath: null })} + onClick={() => updateNotificationSettings({ customSoundId: 'system' })} className="gap-2" > @@ -295,7 +296,7 @@ export function NotificationsPane({ ) : null}
- {selectedSoundPath ? ( + {notificationSettings.customSoundId !== 'system' ? (
{ if (result.delivered) { - void playDesktopNotificationSound(customSoundPath, customSoundVolume) + void playDesktopNotificationSound(customSoundId, customSoundVolume) } }) .catch((err) => { diff --git a/src/renderer/src/components/ui/collapsible.tsx b/src/renderer/src/components/ui/collapsible.tsx new file mode 100644 index 000000000..626887752 --- /dev/null +++ b/src/renderer/src/components/ui/collapsible.tsx @@ -0,0 +1,24 @@ +'use client' + +import * as React from 'react' +import { Collapsible as CollapsiblePrimitive } from 'radix-ui' + +function Collapsible({ + ...props +}: React.ComponentProps): React.JSX.Element { + return +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps): React.JSX.Element { + return +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps): React.JSX.Element { + return +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/renderer/src/lib/desktop-notification-sound.ts b/src/renderer/src/lib/desktop-notification-sound.ts index b96e46ecf..0cb386820 100644 --- a/src/renderer/src/lib/desktop-notification-sound.ts +++ b/src/renderer/src/lib/desktop-notification-sound.ts @@ -1,8 +1,8 @@ export async function playDesktopNotificationSound( - customSoundPath: string | null | undefined, + customSoundId: string | null | undefined, customSoundVolume?: number | null ): Promise { - if (!customSoundPath) { + if (!customSoundId || customSoundId === 'system') { return false } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 83cbd6eeb..6ac7f54c9 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -31,7 +31,7 @@ export const DEFAULT_HIDE_SLEEPING_WORKSPACES = false // Why: the onboarding wizard's last step index. Centralized so backfill, // clamps, and UI step references all agree on the same upper bound. -export const ONBOARDING_FINAL_STEP = 5 +export const ONBOARDING_FINAL_STEP = 6 export const ORCA_BROWSER_PARTITION = 'persist:orca-browser' // Why: blank browser tabs must start from an inert guest URL that does not @@ -120,6 +120,7 @@ export function getDefaultNotificationSettings(): NotificationSettings { agentTaskComplete: true, terminalBell: false, suppressWhenFocused: true, + customSoundId: 'system', customSoundPath: null, customSoundVolume: 100 } diff --git a/src/shared/telemetry-events.ts b/src/shared/telemetry-events.ts index 9aaf258ae..af051f2ba 100644 --- a/src/shared/telemetry-events.ts +++ b/src/shared/telemetry-events.ts @@ -441,6 +441,7 @@ const onboardingValueKindSchema = z.enum([ 'agent', 'theme', 'notifications', + 'agent_setup', 'integrations', 'repo' ]) diff --git a/src/shared/types.ts b/src/shared/types.ts index 2d42c7fe8..7d423a5b4 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1354,6 +1354,18 @@ export type NotificationSettings = { agentTaskComplete: boolean terminalBell: boolean suppressWhenFocused: boolean + customSoundId: + | 'system' + | 'two-tone' + | 'bong' + | 'thump' + | 'blip' + | 'sonar' + | 'blop' + | 'ding' + | 'clack' + | 'beep' + | 'custom' customSoundPath: string | null customSoundVolume: number } diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts index 8f6113988..c2a46ca5c 100644 --- a/tests/e2e/onboarding.spec.ts +++ b/tests/e2e/onboarding.spec.ts @@ -125,10 +125,10 @@ async function setupOnboardingFeatures(page: Page): Promise { async function continueFromFeatureSetupToRepo(page: Page): Promise { await continueOnboarding(page) await expect(page.getByRole('heading', { name: TASK_SOURCES_HEADING })).toBeVisible() - await expect(page.getByText('4 of 5')).toBeVisible() + await expect(page.getByText('5 of 6')).toBeVisible() await continueOnboarding(page) await expect(page.getByRole('heading', { name: REPO_STEP_HEADING })).toBeVisible() - await expect(page.getByText('5 of 5')).toBeVisible() + await expect(page.getByText('6 of 6')).toBeVisible() } test.describe('Onboarding flow', () => { @@ -148,7 +148,7 @@ test.describe('Onboarding flow', () => { await expect(orcaPage.getByRole('heading', { name: /Pick your default agent/i })).toBeVisible({ timeout: 15_000 }) - await expect(orcaPage.getByText('1 of 5')).toBeVisible() + await expect(orcaPage.getByText('1 of 6')).toBeVisible() await expect(onboardingFooterButton(orcaPage, /^Continue\b/)).toBeVisible() await expect(onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON)).toBeVisible() // Why: Back is not rendered on the first step (was previously rendered-but- @@ -196,7 +196,7 @@ test.describe('Onboarding flow', () => { await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Make it feel like home/i })).toBeVisible() - await expect(orcaPage.getByText('2 of 5')).toBeVisible() + await expect(orcaPage.getByText('2 of 6')).toBeVisible() await expect .poll(async () => (await getOnboardingState(orcaPage)).lastCompletedStep, { timeout: 5_000, @@ -230,8 +230,8 @@ test.describe('Onboarding flow', () => { .toBe(oppositeTheme) await continueOnboarding(orcaPage) - await expect(orcaPage.getByRole('heading', { name: /Set up Orca for agents/i })).toBeVisible() - await expect(orcaPage.getByText('3 of 5')).toBeVisible() + await expect(orcaPage.getByRole('heading', { name: /Set up notifications/i })).toBeVisible() + await expect(orcaPage.getByText('3 of 6')).toBeVisible() await expect .poll(async () => (await getOnboardingState(orcaPage)).lastCompletedStep, { timeout: 5_000, @@ -243,14 +243,24 @@ test.describe('Onboarding flow', () => { .toBe(oppositeTheme) // --- Step 3: notifications --- - // Why: the wizard force-defaults every toggle ON (use-onboarding-flow.ts), - // which intentionally diverges from the app defaults (terminalBell=false, - // suppressWhenFocused=true). Use the default setup action without touching - // the toggles; the assertions prove the wizard wrote its opt-in defaults - // through IPC, including the inverted suppressWhenFocused. - // Why: the feature checklist also defaults ON; inject safe deps so this - // E2E validates persistence without registering the real CLI or opening - // OS permission prompts. + await expect(orcaPage.getByRole('button', { name: /System Default/i })).toHaveAttribute( + 'aria-pressed', + 'true' + ) + await expect(orcaPage.getByRole('button', { name: /Send Test Notification/i })).toBeVisible() + await expect(orcaPage.getByText(/Advanced sound file/i)).toBeVisible() + await continueOnboarding(orcaPage) + await expect(orcaPage.getByRole('heading', { name: /Set up Orca for agents/i })).toBeVisible() + await expect(orcaPage.getByText('4 of 6')).toBeVisible() + await expect + .poll(async () => (await getOnboardingState(orcaPage)).lastCompletedStep, { + timeout: 5_000, + message: 'lastCompletedStep did not advance to 3 after notifications Continue' + }) + .toBe(3) + + // Why: the feature checklist defaults ON; inject safe deps so this E2E + // validates setup without registering the real CLI or opening OS prompts. await installSafeOnboardingFeatureSetupDeps(orcaPage) const browserUse = orcaPage.getByRole('checkbox', { name: /Agent Browser Use/i }) const computerUse = orcaPage.getByRole('checkbox', { name: /Computer Use/i }) @@ -269,11 +279,10 @@ test.describe('Onboarding flow', () => { .poll(async () => (await getOnboardingState(orcaPage)).lastCompletedStep, { timeout: 5_000 }) - .toBe(4) + .toBe(5) - // Verify all three notification fields landed in settings, including the - // inverted suppressWhenFocused boundary (UI: notifyWhenFocused=true → - // persisted: suppressWhenFocused=false). + // Verify the source defaults land without asking users to configure each + // source in the onboarding UI. await expect .poll( async () => { @@ -281,8 +290,8 @@ test.describe('Onboarding flow', () => { return { agentTaskComplete: s.notifications.agentTaskComplete, terminalBell: s.notifications.terminalBell, - suppressWhenFocused: s.notifications.suppressWhenFocused, - enabled: s.notifications.enabled + enabled: s.notifications.enabled, + customSoundId: s.notifications.customSoundId } }, { timeout: 5_000 } @@ -290,8 +299,8 @@ test.describe('Onboarding flow', () => { .toEqual({ agentTaskComplete: true, terminalBell: true, - suppressWhenFocused: false, - enabled: true + enabled: true, + customSoundId: 'system' }) await expect @@ -355,7 +364,7 @@ test.describe('Onboarding flow', () => { await onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON).click() await expect(orcaPage.getByRole('heading', { name: REPO_STEP_HEADING })).toBeVisible() - await expect(orcaPage.getByText('5 of 5')).toBeVisible() + await expect(orcaPage.getByText('6 of 6')).toBeVisible() await expect(onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON)).toHaveCount(0) await expect(onboardingFooterButton(orcaPage, /Skip all onboarding/i)).toHaveCount(0) await expect(orcaPage.getByRole('button', { name: /Open a folder/i })).toBeVisible() @@ -380,7 +389,7 @@ test.describe('Onboarding flow', () => { closedAt: null, outcome: null, dismissed: false, - lastCompletedStep: 4 + lastCompletedStep: 5 }) await expect .poll(async () => (await getSettings(orcaPage)).defaultTuiAgent, { timeout: 5_000 }) @@ -389,7 +398,7 @@ test.describe('Onboarding flow', () => { await orcaPage.reload() await waitForSessionReady(orcaPage) await expect(orcaPage.getByRole('heading', { name: REPO_STEP_HEADING })).toBeVisible() - await expect(orcaPage.getByText('5 of 5')).toBeVisible() + await expect(orcaPage.getByText('6 of 6')).toBeVisible() await expect(onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON)).toHaveCount(0) expect((await getOnboardingState(orcaPage)).closedAt).toBeNull() }) @@ -430,12 +439,12 @@ test.describe('Onboarding flow', () => { closedAt: null, outcome: null, dismissed: false, - lastCompletedStep: 4 + lastCompletedStep: 5 }) await orcaPage.keyboard.press('Escape') await expect(orcaPage.getByRole('heading', { name: REPO_STEP_HEADING })).toBeVisible() - await expect(orcaPage.getByText('5 of 5')).toBeVisible() + await expect(orcaPage.getByText('6 of 6')).toBeVisible() }) test('Skip from theme reverts preview without saving the skipped choice', async ({ @@ -502,7 +511,7 @@ test.describe('Onboarding flow', () => { expect((await getOnboardingState(orcaPage)).closedAt).toBeNull() }) - test('Skip from notifications does not persist notification or feature setup', async ({ + test('Skip from notifications does not request permission or run feature setup', async ({ orcaPage }) => { await expect(orcaPage.getByRole('heading', { name: /Pick your default agent/i })).toBeVisible({ @@ -511,9 +520,8 @@ test.describe('Onboarding flow', () => { await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Make it feel like home/i })).toBeVisible() await continueOnboarding(orcaPage) - await expect(orcaPage.getByRole('heading', { name: /Set up Orca for agents/i })).toBeVisible() + await expect(orcaPage.getByRole('heading', { name: /Set up notifications/i })).toBeVisible() - const beforeNotifications = (await getSettings(orcaPage)).notifications await orcaPage.evaluate(() => { localStorage.removeItem('orca.e2e.notificationPermissionRequested') window.api.notifications.requestPermission = async () => { @@ -521,33 +529,14 @@ test.describe('Onboarding flow', () => { return { supported: true, platform: 'darwin', requested: true } } }) - const bellSwitch = orcaPage.getByRole('switch', { name: /Terminal bell/i }) - await expect(bellSwitch).toHaveAttribute('aria-checked', 'true') - await bellSwitch.click() - await expect(bellSwitch).toHaveAttribute('aria-checked', 'false') + await expect(orcaPage.getByRole('button', { name: /System Default/i })).toHaveAttribute( + 'aria-pressed', + 'true' + ) await onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON).click() await expect(orcaPage.getByRole('heading', { name: REPO_STEP_HEADING })).toBeVisible() - await expect - .poll( - async () => { - const s = await getSettings(orcaPage) - return { - agentTaskComplete: s.notifications.agentTaskComplete, - terminalBell: s.notifications.terminalBell, - suppressWhenFocused: s.notifications.suppressWhenFocused, - enabled: s.notifications.enabled - } - }, - { timeout: 5_000 } - ) - .toEqual({ - agentTaskComplete: beforeNotifications.agentTaskComplete, - terminalBell: beforeNotifications.terminalBell, - suppressWhenFocused: beforeNotifications.suppressWhenFocused, - enabled: beforeNotifications.enabled - }) await expect .poll( async () => @@ -593,23 +582,21 @@ test.describe('Onboarding flow', () => { await expect(codexButton).toHaveAttribute('aria-pressed', 'true') }) - test('notification toggles flip independently and persist on Continue', async ({ orcaPage }) => { + test('notification sound choice persists on Continue', async ({ orcaPage }) => { await expect(orcaPage.getByRole('heading', { name: /Pick your default agent/i })).toBeVisible({ timeout: 15_000 }) await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Make it feel like home/i })).toBeVisible() + await continueOnboarding(orcaPage) + await expect(orcaPage.getByRole('heading', { name: /Set up notifications/i })).toBeVisible() + + const dingSound = orcaPage.getByRole('button', { name: /^Ding\b/i }) + await dingSound.click() + await expect(dingSound).toHaveAttribute('aria-pressed', 'true') + await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Set up Orca for agents/i })).toBeVisible() - - // Why: NotificationStep buttons expose role="switch" + aria-checked. Flip - // terminalBell off and verify the toggle reflects + persists. The other - // two toggles stay at their wizard-default ON state. - const bellSwitch = orcaPage.getByRole('switch', { name: /Terminal bell/i }) - await expect(bellSwitch).toHaveAttribute('aria-checked', 'true') - await bellSwitch.click() - await expect(bellSwitch).toHaveAttribute('aria-checked', 'false') - await installSafeOnboardingFeatureSetupDeps(orcaPage) await setupOnboardingFeatures(orcaPage) await expect(orcaPage.getByRole('region', { name: /Skill setup command/i })).toBeVisible() @@ -621,12 +608,13 @@ test.describe('Onboarding flow', () => { const s = await getSettings(orcaPage) return { agentTaskComplete: s.notifications.agentTaskComplete, - terminalBell: s.notifications.terminalBell + terminalBell: s.notifications.terminalBell, + customSoundId: s.notifications.customSoundId } }, { timeout: 5_000 } ) - .toEqual({ agentTaskComplete: true, terminalBell: false }) + .toEqual({ agentTaskComplete: true, terminalBell: true, customSoundId: 'ding' }) }) test('can opt into orchestration setup without enabling browser or computer use', async ({ @@ -638,6 +626,8 @@ test.describe('Onboarding flow', () => { await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Make it feel like home/i })).toBeVisible() await continueOnboarding(orcaPage) + await expect(orcaPage.getByRole('heading', { name: /Set up notifications/i })).toBeVisible() + await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Set up Orca for agents/i })).toBeVisible() // Why: this flow validates the orchestration-only setup path without @@ -696,7 +686,7 @@ test.describe('Onboarding flow', () => { .poll(async () => (await getOnboardingState(orcaPage)).lastCompletedStep, { timeout: 5_000 }) - .toBe(4) + .toBe(5) await expect .poll( async () => @@ -724,6 +714,7 @@ test.describe('Onboarding flow', () => { // Advance to the repo step. await continueOnboarding(orcaPage) await continueOnboarding(orcaPage) + await continueOnboarding(orcaPage) await installSafeOnboardingFeatureSetupDeps(orcaPage) await setupOnboardingFeatures(orcaPage) await expectSkillSetupTerminalReady(orcaPage) @@ -763,7 +754,7 @@ test.describe('Onboarding flow', () => { // would otherwise match this regex. await orcaPage.getByRole('button', { name: 'Back', exact: true }).click() await expect(orcaPage.getByRole('heading', { name: /Pick your default agent/i })).toBeVisible() - await expect(orcaPage.getByText('1 of 5')).toBeVisible() + await expect(orcaPage.getByText('1 of 6')).toBeVisible() // Why: "without losing progress" means persisted lastCompletedStep stays // at 1 — Back rewinds the visible step but must not roll persistence back. @@ -780,11 +771,13 @@ test.describe('Onboarding flow', () => { timeout: 15_000 }) - // Advance through the first four steps. The repo step is required setup, + // Advance through the optional setup steps. The repo step is required setup, // so the footer must not offer a dismiss action there. await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Make it feel like home/i })).toBeVisible() await continueOnboarding(orcaPage) + await expect(orcaPage.getByRole('heading', { name: /Set up notifications/i })).toBeVisible() + await continueOnboarding(orcaPage) await expect(orcaPage.getByRole('heading', { name: /Set up Orca for agents/i })).toBeVisible() await installSafeOnboardingFeatureSetupDeps(orcaPage) await setupOnboardingFeatures(orcaPage) @@ -797,6 +790,6 @@ test.describe('Onboarding flow', () => { expect(final.closedAt).toBeNull() expect(final.outcome).toBeNull() expect(final.checklist.dismissed).toBe(false) - expect(final.lastCompletedStep).toBe(4) + expect(final.lastCompletedStep).toBe(5) }) })