diff --git a/internal/backend-dev-plugin.mjs b/internal/backend-dev-plugin.mjs index 835667e16..80f4e9a4c 100644 --- a/internal/backend-dev-plugin.mjs +++ b/internal/backend-dev-plugin.mjs @@ -88,12 +88,18 @@ export function backendDevPlugin(root) { for (const b of DEV_BACKENDS) { server.middlewares.use(b.route, (req, res, next) => { if (isAlive(b.name)) return next() + const status = { engine: 'offline', expected: !!(b.expected && b.expected()), error: 'The desktop engine is not running.' } if ((req.url || '').startsWith('/events')) { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' }) - const expected = !!(b.expected && b.expected()) - res.write(`data: ${JSON.stringify({ type: 'status', status: { engine: 'offline', expected, error: 'The desktop engine is not running.' } })}\n\n`) + res.write(`data: ${JSON.stringify({ type: 'status', status })}\n\n`) return // held open, so the EventSource does not reconnect in a loop } + // /status answers the same, for a page whose event stream cannot get through (a proxy that buffers). + if ((req.url || '').startsWith('/status')) { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify(status)) + return + } res.writeHead(200, { 'Content-Type': 'application/json' }) res.end('[]') }) diff --git a/library/components/legends/LegendProgressBar.vue b/library/components/legends/LegendProgressBar.vue index d3143aecd..5fc7ba40f 100644 --- a/library/components/legends/LegendProgressBar.vue +++ b/library/components/legends/LegendProgressBar.vue @@ -71,7 +71,13 @@ useShadowStyles(` tall so each segment button is a 24px target, but it paints only a 10px band: the track's background is a centred 10px strip and each segment clips its colour to its content box inside 7px of padding. The -7px margins keep the legend's layout exactly 10px tall, as before. */ -.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:24px;margin:-7px 0;overflow:hidden;background:linear-gradient(rgba(110,118,129,0.25),rgba(110,118,129,0.25)) center/100% 10px no-repeat;} +.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:24px;margin:-7px 0;overflow:hidden;} +/* The grey band, rounded, under the segments; the first and last segment round the bar's two ends (an + elliptical radius 5px/12px leaves a 5px curve on the 10px painted band inside the 7px padding). */ +.lpb-track::before{content:'';position:absolute;left:0;right:0;top:7px;height:10px;border-radius:5px;background:rgba(110,118,129,0.25);} +.lpb-seg{position:relative;} +.lpb-span:first-child .lpb-seg:first-child{border-top-left-radius:5px 12px;border-bottom-left-radius:5px 12px;} +.lpb-seg-rest,.lpb-span:last-child .lpb-seg:last-child{border-top-right-radius:5px 12px;border-bottom-right-radius:5px 12px;} .lpb-span{display:flex;height:100%;min-width:0;flex:0 0 auto;} .lpb-seg{appearance:none;-webkit-appearance:none;border:none;margin:0;padding:7px 0;box-sizing:border-box;background-clip:content-box !important;height:100%;display:block;flex:0 0 auto;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;transition:filter 0.12s,box-shadow 0.12s;} .lpb-seg-rest{flex:1 1 auto;} diff --git a/plugin-designs/rudder-voice/rudder-voice.vue b/plugin-designs/rudder-voice/rudder-voice.vue index e41716e8f..1d477ab99 100644 --- a/plugin-designs/rudder-voice/rudder-voice.vue +++ b/plugin-designs/rudder-voice/rudder-voice.vue @@ -308,6 +308,7 @@ function connect() { engine.value = 'offline' if (audioMode.value !== 'browser') recording.value = false logc('events disconnected; retrying in 3s') + fetchStatus() try { es.close() } catch {} retry = setTimeout(connect, 3000) } @@ -544,7 +545,13 @@ function onKey(ev) { if (ev.ctrlKey && ev.shiftKey && ev.code === 'Space') { ev.preventDefault(); toggle() } } +// The status also by plain request: a proxy that buffers the event stream (rega's harness did) must not +// leave the page guessing whether this server is meant to have an engine. +async function fetchStatus() { + try { const r = await fetch(apiBase.value + '/status'); if (r.ok) applyStatus(await r.json()) } catch {} +} onMounted(() => { + fetchStatus() connect() refreshDevices() window.addEventListener('keydown', onKey) @@ -675,7 +682,7 @@ const statusText = computed(() => ({
“{{ chosen.label }}” is muted in your system sound settings — Rudder won't hear anything until it's unmuted.
{{ deviceError }}
-No microphone found. Connect a headset or turn on your computer's microphone in Sound settings.
+No microphone found. Connect a headset or turn on your computer's microphone in Sound settings.
Also available but switched off in Sound settings: {{ offCards.join(', ') }}. Turn it on there to use it here.
{{ notice }}