From 0fc72b481d7a33beaa7d75c0d867d2bd4fc95373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Mon, 28 Sep 2026 15:29:51 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui_builder/static/app.js | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) 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;