Skip to content

Commit 417e1fc

Browse files
Fix notification sounds not playing in production
- Add /sounds/* route to serve audio files in production mode - Add mp3/wav/ogg MIME types for audio file serving - Use localStorage debounce to prevent multiple tabs playing same sound - Fix potential double-play when custom sound fallback triggers twice
1 parent 5e3cfb9 commit 417e1fc

6 files changed

Lines changed: 33 additions & 13 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"name": "vibora",
99
"source": "./plugins/vibora",
1010
"description": "Task orchestration for Claude Code",
11-
"version": "2.6.2"
11+
"version": "2.6.3"
1212
}
1313
]
1414
}

desktop/neutralino.config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/neutralinojs/neutralinojs/main/schemas/neutralino.config.schema.json",
33
"applicationId": "io.vibora.desktop",
4-
"version": "2.6.2",
4+
"version": "2.6.3",
55
"defaultMode": "window",
66
"port": 0,
77
"documentRoot": "/resources/",
@@ -26,7 +26,7 @@
2626
],
2727
"globalVariables": {
2828
"APP_NAME": "Vibora",
29-
"APP_VERSION": "2.6.2"
29+
"APP_VERSION": "2.6.3"
3030
},
3131
"modes": {
3232
"window": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vibora",
33
"private": true,
4-
"version": "2.6.2",
4+
"version": "2.6.3",
55
"description": "The Vibe Engineer's Cockpit",
66
"license": "PolyForm-Shield-1.0.0",
77
"type": "module",

plugins/vibora/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vibora",
33
"description": "Vibora task orchestration for Claude Code",
4-
"version": "2.6.2",
4+
"version": "2.6.3",
55
"author": {
66
"name": "Vibora"
77
},

server/app.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ export function createApp() {
111111
svg: 'image/svg+xml',
112112
woff: 'font/woff',
113113
woff2: 'font/woff2',
114+
mp3: 'audio/mpeg',
115+
wav: 'audio/wav',
116+
ogg: 'audio/ogg',
114117
}
115118
const content = await readFile(filePath)
116119
return new Response(content, {
@@ -127,6 +130,15 @@ export function createApp() {
127130
return c.notFound()
128131
})
129132

133+
// Serve sounds
134+
app.get('/sounds/*', async (c) => {
135+
const soundPath = join(distPath, c.req.path)
136+
if (existsSync(soundPath)) {
137+
return serveFile(soundPath)
138+
}
139+
return c.notFound()
140+
})
141+
130142
// Serve specific static files
131143
const staticFiles = ['favicon.ico', 'vibora-icon.png', 'vibora-logo.jpeg', 'vite.svg', 'logo-dark.jpg', 'logo-light.jpg']
132144
for (const file of staticFiles) {

src/hooks/use-task-sync.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,26 @@ export function useTaskSync() {
6262

6363
// Play notification sound if enabled
6464
// Try custom sound first (/api/uploads/sound), fall back to default
65+
// Use localStorage to prevent multiple tabs from playing the same sound
6566
if (playSound) {
66-
const customAudio = new Audio('/api/uploads/sound')
67-
customAudio.onerror = () => {
68-
// Custom sound not available, use default
69-
const defaultAudio = new Audio('/sounds/goat-bleat.mp3')
70-
defaultAudio.play().catch(() => {})
67+
const SOUND_DEBOUNCE_MS = 1000
68+
const lastPlayed = localStorage.getItem('vibora:lastSoundPlayed')
69+
const now = Date.now()
70+
if (lastPlayed && now - parseInt(lastPlayed) < SOUND_DEBOUNCE_MS) {
71+
return // Another tab just played it
7172
}
72-
customAudio.play().catch(() => {
73-
// Custom sound failed, try default
73+
localStorage.setItem('vibora:lastSoundPlayed', String(now))
74+
75+
let fellBack = false
76+
const playDefault = () => {
77+
if (fellBack) return
78+
fellBack = true
7479
const defaultAudio = new Audio('/sounds/goat-bleat.mp3')
7580
defaultAudio.play().catch(() => {})
76-
})
81+
}
82+
const customAudio = new Audio('/api/uploads/sound')
83+
customAudio.onerror = playDefault
84+
customAudio.play().catch(playDefault)
7785
}
7886

7987
// Post to parent window for desktop native notifications

0 commit comments

Comments
 (0)