From 1af8c39926a80dab1c5969ce431ae40f41502088 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Mon, 22 Jun 2026 13:01:01 +0200 Subject: [PATCH] Surface Sud's Load rejection as a server-driven error, not GUI guesswork Sud.load() already refuses a new schedule while a run is in progress (state not IDLE/DONE) - but tasks/sud.py's SudTask.recv() silently dropped that refusal, and an earlier attempt to handle it client-side by pre-emptively greying out the File menu based on the GUI's own (replicated, laggy) view of sud_state was reverted: the intelligence belongs on the server, which already knows definitively whether it's running, not on the client guessing from a mirrored state that can lag or miss the message that changed it. tasks/sud.py: when Sud.load() returns False, send {'Sud': {'Error': ...}} explaining why, instead of doing nothing. client/brewpi_gui.py: New/Save/Load Sud stay enabled unconditionally; on_sud_changed() shows whatever 'Error' the server sends via QMessageBox.warning(). on_action_sud_new()'s comment updated to match (it goes through the same Load path, so gets the same rejection/error for free). Verified live: triggering Load while a run is in progress on an isolated test server round-trips the Error over the wire and pops the expected dialog ("Cannot load a new schedule while a run is in progress - stop it first."). Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01LhiQe64F74uHV8jzuoSa5K --- client/brewpi_gui.py | 9 +++++++-- tasks/sud.py | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/brewpi_gui.py b/client/brewpi_gui.py index c9ef99f..7d8e349 100755 --- a/client/brewpi_gui.py +++ b/client/brewpi_gui.py @@ -659,8 +659,11 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.msg_sud.send({'Confirm': True}) def on_action_sud_new(self): - # Replaces the running Sud's schedule with an empty one, same as - # Load but with a built-in empty document instead of a file dialog. + # Sends an empty schedule via the same Load path Load Sud uses, with + # a built-in empty document instead of a file dialog - the server + # itself refuses this (like any Load) while a run is in progress, + # surfacing an error (see on_sud_changed()'s 'Error' handling) + # rather than the GUI trying to guess that here. self.msg_sud.send({'Load': {'Name': '', 'Description': '', 'pot_mass': 0, 'pot_material': None, 'steps': []}}) def on_action_sud_save(self): @@ -876,6 +879,8 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow): self.server_remaining_forecast = (anchor_min, forecast['T'], forecast['Theta']) elif "Forecast" in key: self.on_sud_forecast_received(msg['Forecast']) + elif "Error" in key: + QtWidgets.QMessageBox.warning(self, "Sud", msg['Error']) self.update_sud_actions() def on_sud_forecast_received(self, forecast): diff --git a/tasks/sud.py b/tasks/sud.py index ec38653..d9a3a43 100644 --- a/tasks/sud.py +++ b/tasks/sud.py @@ -196,6 +196,13 @@ class SudTask(ATask): await self.send({'Name': self.sud.name, 'Description': self.sud.description}) await self.send({'Json': pair[1]}) await self.send_forecast(pair[1]) + else: + # Sud.load() refuses while a run is in progress (state + # not IDLE/DONE) - tell the client why instead of + # silently dropping the request. The client has no + # business pre-emptively guessing this itself from its + # own (replicated, laggy) view of the state. + await self.send({'Error': 'Cannot load a new schedule while a run is in progress - stop it first.'}) async def send(self, data): await self.msg_handler.send(data)