From adc099d20dddaf5d16d7409aa4097a99f1cf35cb Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 19 Jul 2026 19:58:57 -0400 Subject: [PATCH] Gate player control slot on v3 UI version Add explicit check for `window.feedBack.uiVersion === 'v3'` in _pcSlot() per docs/plugin-v3-ui.md. This prevents the plugin from attempting to mount player controls on non-v3 hosts (e.g., legacy v2 shell). Complements the existing `playerControlSlot` typeof check and improves compatibility robustness. Updated test mocks to include `uiVersion: 'v3'` and added test case verifying that mounting is skipped when uiVersion is not v3, including a guard to ensure the retry loop terminates properly. --- plugins/highway_3d/screen.js | 7 ++++++- .../highway_3d/tests/background_control.test.js | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/highway_3d/screen.js b/plugins/highway_3d/screen.js index b18f22d..03b7465 100644 --- a/plugins/highway_3d/screen.js +++ b/plugins/highway_3d/screen.js @@ -4101,7 +4101,12 @@ // page remains the way in. function _pcSlot() { try { - const fn = window.feedBack && window.feedBack.ui && window.feedBack.ui.playerControlSlot; + // Gate on the v3 shell per docs/plugin-v3-ui.md (matches the tuner + // precedent). The playerControlSlot typeof check below already + // covers the practical case - only v3 exposes it - but the + // documented checklist asks plugins to detect v3 explicitly. + if (!window.feedBack || window.feedBack.uiVersion !== 'v3') return null; + const fn = window.feedBack.ui && window.feedBack.ui.playerControlSlot; return typeof fn === 'function' ? fn() : null; } catch (_) { return null; } } diff --git a/plugins/highway_3d/tests/background_control.test.js b/plugins/highway_3d/tests/background_control.test.js index ce8d910..9648188 100644 --- a/plugins/highway_3d/tests/background_control.test.js +++ b/plugins/highway_3d/tests/background_control.test.js @@ -146,6 +146,7 @@ function load({ store: initialStore } = {}) { }, window: { feedBack: { + uiVersion: 'v3', // _pcSlot gates on this (docs/plugin-v3-ui.md) ui: { playerControlSlot: () => dom.slot }, // The real bus is an EventTarget wrapper exposing on/off. Modelled // here so the screen:changed subscription — and its removal — are @@ -336,6 +337,19 @@ test('re-mounts into a fresh slot when the player chrome is rebuilt', () => { assert.equal(listenerCount(), 1, 'remount must not double-subscribe'); }); +test('a non-v3 host mounts nothing (uiVersion gate)', () => { + const ctl = load(); + ctl.sandbox.window.feedBack.uiVersion = 'v2'; // pre-v3 shell + ctl.api._pcAcquire(); + assert.equal(ctl.api.el, null, 'must not mount when uiVersion is not v3'); + assert.equal(ctl.dom.slot.children.length, 0); + // Retry loop must still terminate rather than spin. + let guard = 0; + while (ctl.timers.length && guard++ < 100) ctl.timers.shift()(); + assert.ok(guard < 100, 'retry loop did not terminate'); + ctl.api._pcRelease(); +}); + test('a host with no player-control slot mounts nothing and does not throw', () => { const { api, dom, sandbox, timers } = load(); sandbox.window.feedBack.ui = {};