diff --git a/ui_builder/static/app.js b/ui_builder/static/app.js index 84a9ff0..ea7982b 100644 --- a/ui_builder/static/app.js +++ b/ui_builder/static/app.js @@ -5241,10 +5241,19 @@ function startCustomTooltips() { // countdown change. A pure coincidental 4x4 that did not originate from an // advantage still produces no plate. function penaltyDisplayEntriesByTargetSide(step) { - // A fully assigned penalty is already eligible for its plate. Its clock - // remains stopped until Start/Space; strength calculations stay separate. - const home = sortedPenaltyEntries("home", { includePrepared: true }); - const away = sortedPenaltyEntries("away", { includePrepared: true }); + // Prepared penalties may pre-fill the stopped plate, but as soon as at least + // one penalty has actually started they MUST NOT participate in strength + // routing. Otherwise an unstarted draft on the opposite bench can make a + // real 4x5 look like 4x4 locally and suppress the AWAY/HOME StartCountdown. + const activeHome = sortedPenaltyEntries("home"); + const activeAway = sortedPenaltyEntries("away"); + const hasStartedPenalties = Boolean(activeHome.length || activeAway.length); + const home = hasStartedPenalties + ? activeHome + : sortedPenaltyEntries("home", { includePrepared: true }); + const away = hasStartedPenalties + ? activeAway + : sortedPenaltyEntries("away", { includePrepared: true }); const all = [...home, ...away].sort((a, b) => { const runningOrder = Number(Boolean(b.event?.running)) - Number(Boolean(a.event?.running)); if (runningOrder) return runningOrder; @@ -13023,15 +13032,22 @@ function renderHockeyPenaltyDashboard(node, component, runtime) { state.vmixStrengthMappingRefreshPending = true; try { - const response = await fetch(`/api/hockey/agents/devices/${encodeURIComponent(deviceId)}/apply-mapping?only_changed=true`, { - method: "POST", - cache: "no-store", - credentials: "same-origin", - }); + // Strength is a small dedicated Mapping namespace. A strength transition + // is infrequent, so force only this namespace instead of trusting the + // changed-value cache. This guarantees that linked numerical-advantage + // captions are pushed again after a penalty start/finish/reconnect while + // leaving every unrelated Mapping field untouched. + const response = await fetch( + `/api/hockey/agents/devices/${encodeURIComponent(deviceId)}/apply-mapping?source_code=strength`, + { method: "POST", cache: "no-store", credentials: "same-origin" } + ); let payload = {}; try { payload = await response.json(); } catch (_) {} - if (!response.ok) { - const detail = payload?.detail?.message || payload?.detail || `HTTP ${response.status}`; + if (!response.ok || payload?.ok === false) { + const detail = payload?.detail?.message + || payload?.detail + || payload?.errors?.[0]?.reason + || `HTTP ${response.status}`; throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail)); } return true;