diff --git a/pkg/service/soundtouchweb/frontend_test/deviceDetail.test.mjs b/pkg/service/soundtouchweb/frontend_test/deviceDetail.test.mjs new file mode 100644 index 00000000..4cbae5ad --- /dev/null +++ b/pkg/service/soundtouchweb/frontend_test/deviceDetail.test.mjs @@ -0,0 +1,12 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +const app = await readFile(new URL('../static/js/app.js', import.meta.url), 'utf8'); + +test('explains the documented AirPlay limitation only for ST10 stereo pairs', () => { + assert.match(app, /isSoundTouch10StereoPair\(device\)/); + assert.match(app, /role="note" aria-label="Stereo pair limitation"/); + assert.match(app, /AirPlay unavailable while paired\./); + assert.match(app, /Unpair them to use AirPlay\./); +}); diff --git a/pkg/service/soundtouchweb/frontend_test/stereoPresentation.test.mjs b/pkg/service/soundtouchweb/frontend_test/stereoPresentation.test.mjs new file mode 100644 index 00000000..30337ce4 --- /dev/null +++ b/pkg/service/soundtouchweb/frontend_test/stereoPresentation.test.mjs @@ -0,0 +1,20 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { isSoundTouch10StereoPair } from '../static/js/stereoPresentation.mjs'; + +test('identifies only projected SoundTouch 10 stereo pairs', () => { + assert.equal(isSoundTouch10StereoPair({ + info: { type: 'SoundTouch 10' }, + stereoPair: { masterDeviceId: 'left' }, + }), true); + assert.equal(isSoundTouch10StereoPair({ + info: { type: 'ST10' }, + stereoPair: { masterDeviceId: 'left' }, + }), true); + assert.equal(isSoundTouch10StereoPair({ info: { type: 'SoundTouch 10' } }), false); + assert.equal(isSoundTouch10StereoPair({ + info: { type: 'SoundTouch 20' }, + stereoPair: { masterDeviceId: 'left' }, + }), false); +}); diff --git a/pkg/service/soundtouchweb/static/css/app.css b/pkg/service/soundtouchweb/static/css/app.css index 7a29c8e8..bca70b8d 100644 --- a/pkg/service/soundtouchweb/static/css/app.css +++ b/pkg/service/soundtouchweb/static/css/app.css @@ -693,6 +693,22 @@ img { display: block; max-width: 100%; } .source-icon { font-size: .9rem; line-height: 1; } .source-name { font-weight: 500; } +.stereo-pair-note { + display: flex; + flex-direction: column; + gap: .2rem; + margin-top: 1rem; + padding: .65rem .75rem; + border: 1px solid var(--border); + border-left: 3px solid var(--accent); + border-radius: var(--radius); + background: var(--surface); + color: var(--text-dim); + font-size: .8rem; + line-height: 1.4; +} +.stereo-pair-note strong { color: var(--text); } + /* ── Zone ────────────────────────────────────────────────────────────────── */ .zone-section { margin-top: 1.25rem; } diff --git a/pkg/service/soundtouchweb/static/js/app.js b/pkg/service/soundtouchweb/static/js/app.js index ca8f9461..a75d363f 100644 --- a/pkg/service/soundtouchweb/static/js/app.js +++ b/pkg/service/soundtouchweb/static/js/app.js @@ -16,6 +16,7 @@ import { PlayURL } from './components/PlayURL.js'; import { TTS } from './components/TTS.js'; import { Announcements } from './components/Announcements.js'; import { api } from './api.js'; +import { isSoundTouch10StereoPair } from './stereoPresentation.mjs'; const html = htm.bind(h); @@ -53,6 +54,12 @@ function DeviceDetail({ deviceId, devices, onBack, onDevicesChanged, notify }) { onChanged=${onDevicesChanged} notify=${notify} /> + ${isSoundTouch10StereoPair(device) ? html` + + ` : null} <${Zone} deviceId=${deviceId} devices=${devices} /> <${Recents} deviceId=${deviceId} /> diff --git a/pkg/service/soundtouchweb/static/js/stereoPresentation.mjs b/pkg/service/soundtouchweb/static/js/stereoPresentation.mjs new file mode 100644 index 00000000..737d054c --- /dev/null +++ b/pkg/service/soundtouchweb/static/js/stereoPresentation.mjs @@ -0,0 +1,6 @@ +export function isSoundTouch10StereoPair(device) { + if (!device?.stereoPair) return false; + + const model = String(device?.info?.type || '').toLowerCase().replace(/[^a-z0-9]/g, ''); + return model === 'st10' || model === 'soundtouch10'; +}