From cf28a8c0d59fcdf4a8ce22da768710a95de2b8f7 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:16:30 -0400 Subject: [PATCH] fix(display): restore early-continue guard for mid-loop mode changes (#367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the display loop breaks early because current_display_mode changed (on-demand activation, live priority, etc.), it would fall through to the "honour minimum duration" sleep for the *previous* mode — blocking for up to that mode's full display_duration (default 30s) without polling on-demand requests or re-checking the mode. New modes could sit unrendered for up to 30s, or get clobbered by a queued stop request before ever displaying. This guard was added in #298 to fix #196 (live priority not interrupting long display durations) and was accidentally dropped in #330 as collateral damage of an unrelated time.monotonic() -> time.time() cleanup in the same diff hunk. Restoring it fixes both the original #196 regression and a new symptom found via the on-air MQTT plugin, where ON/OFF toggles could be delayed by up to 30s or missed entirely depending on timing within the previous mode's display cycle. Co-authored-by: Chuck Co-authored-by: Claude Sonnet 4.6 --- src/display_controller.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/display_controller.py b/src/display_controller.py index 7d760953..0a55662d 100644 --- a/src/display_controller.py +++ b/src/display_controller.py @@ -2186,6 +2186,23 @@ class DisplayController: loop_completed = True break + # LOAD-BEARING: if current_display_mode changed mid-loop (on-demand + # activation, live priority, etc.), restart the main loop now instead + # of falling into the "honour minimum duration" sleep below. That sleep + # can run for up to the *previous* mode's full display_duration (default + # 30s) and doesn't poll on-demand requests or re-check the mode, so a + # freshly-requested mode switch would sit invisible for up to 30s — or + # get clobbered by a queued stop request — before ever rendering. + # + # This guard was added in #298 (live priority interrupting long display + # durations) and was accidentally dropped in #330 as collateral damage of + # an unrelated time.monotonic() -> time.time() cleanup in the same hunk. + # Removing it again will silently reintroduce both issues. _activate_on_demand + # already sets force_change=True and clears the display, so the next loop + # iteration renders the new mode immediately. + if self.current_display_mode != active_mode: + continue + # Ensure we honour minimum duration when not dynamic and loop ended early if ( not dynamic_enabled