diff --git a/app.py b/app.py index 033f408..50b2d2e 100644 --- a/app.py +++ b/app.py @@ -29,7 +29,7 @@ from ui_builder import install_ui_builder from khl_site.khl_data_center import APP as khl_site_app BASE_DIR = Path(__file__).resolve().parent -BUILD_VERSION = "2026.09.18.1" +BUILD_VERSION = "2026.09.18.2" # compatibility: BUILD_VERSION = "2026.08.26.1" # compatibility: BUILD_VERSION = "2026.08.25.1" # compatibility: BUILD_VERSION = "2026.08.24.15" diff --git a/hockey_data/static/tournament-menu.js b/hockey_data/static/tournament-menu.js index 0d1fc7b..b380fe3 100644 --- a/hockey_data/static/tournament-menu.js +++ b/hockey_data/static/tournament-menu.js @@ -996,6 +996,9 @@ } async function refreshDrawerCache() { + // Do not patch/re-render Runtime data while the operator edits settings. + // The hourly server refresh keeps running; the browser catches up afterwards. + if (operatorSettingsEditingActive()) return; if ((!state.open && !state.selectedId) || state.drawerRefreshBusy) return; state.drawerRefreshBusy = true; try { @@ -1995,6 +1998,17 @@ function selectedGameIsFinished() { return Boolean(state.selectedGame?.is_finished); } +function operatorSettingsEditingActive() { + // BUILD144: settings are operator-owned UI state. Live match/schedule refresh + // may continue on the server, but must not rebuild the Runtime while an + // operator is typing into either Stat2TV settings or a full UI settings modal. + return Boolean( + state.settingsModal + || document.querySelector(".hockey-settings-modal") + || document.querySelector(".modal-settings-full") + ); +} + function scheduleSelectedGamePoll(delay = 1000) { stopSelectedGamePolling(); if (!state.selectedGameId || selectedGameIsFinished()) { @@ -2022,6 +2036,7 @@ async function pollSelectedGame() { || !state.selectedGameId || !state.selectedId || state.open + || operatorSettingsEditingActive() || selectedGameIsFinished() ) { scheduleSelectedGamePoll(1000); @@ -2498,6 +2513,10 @@ document.addEventListener("visibilitychange", () => { const close = () => { modal.remove(); state.settingsModal = null; + if (state.selectedGameId && !selectedGameIsFinished()) { + scheduleSelectedGamePoll(150); + } + if (state.selectedId) scheduleDrawerRefresh(250); }; modal.querySelector("[data-settings-close]").addEventListener("click", close); modal.querySelector(".hockey-settings-backdrop").addEventListener("click", close); diff --git a/ui_builder/static/app.js b/ui_builder/static/app.js index c166fd2..2b37aca 100644 --- a/ui_builder/static/app.js +++ b/ui_builder/static/app.js @@ -13635,15 +13635,14 @@ function renderHockeyPenaltyDashboard(node, component, runtime) { function runtimeVisibleTabs() { const standings = getByPath(state.data, "hockey.standings") || {}; - const schedule = getByPath(state.data, "hockey.schedule") || {}; - const teamSchedule = getByPath(state.data, "hockey.team_schedule") || {}; const selectedGame = getByPath(state.data, "hockey.selected_game") || null; const selectedTournament = getByPath(state.data, "hockey.selected_tournament") || null; - const hasSchedule = (Array.isArray(schedule.items) && schedule.items.length > 0) - || (Array.isArray(teamSchedule.items) && teamSchedule.items.length > 0); const visible = state.config.tabs.filter((tab) => { if (tab.id === "standings") return Boolean(standings.available && Array.isArray(standings.variants) && standings.variants.length); - if (tab.id === "schedule") return hasSchedule; + // BUILD144: Schedule is navigation, not a data-availability indicator. + // Keep the tab visible even when the selected day has zero games or while + // the schedule cache is still loading; the component shows its empty state. + if (tab.id === "schedule") return true; if (tab.id === "shootout") return Boolean(selectedGame && String(selectedTournament?.stage_key || "").toLowerCase() === "regular"); return true; });