diff --git a/bec_widgets/cli/client_utils.py b/bec_widgets/cli/client_utils.py index e3a373b3..6a4f3f27 100644 --- a/bec_widgets/cli/client_utils.py +++ b/bec_widgets/cli/client_utils.py @@ -712,10 +712,13 @@ class BECGuiClient(RPCBase): def _start(self, wait: bool = False) -> None: self._killed = False - self._client.connector.unregister(cb=self._handle_registry_update) - self._client.connector.register( - MessageEndpoints.gui_registry_state(self._gui_id), cb=self._handle_registry_update - ) + endpoint = MessageEndpoints.gui_registry_state(self._gui_id) + # connect_to_gui_server may already have registered this callback with from_start=True; + # replacing that subscription here would lose the replay of the current registry state. + if not self._client.connector.any_stream_is_registered( + endpoint, cb=self._handle_registry_update + ): + self._client.connector.register(endpoint, cb=self._handle_registry_update) return self._start_server(wait=wait) def _handle_registry_update(self, msg: dict[str, GUIRegistryStateMessage]) -> None: diff --git a/tests/unit_tests/test_client_utils.py b/tests/unit_tests/test_client_utils.py index 23200a25..1e310d10 100644 --- a/tests/unit_tests/test_client_utils.py +++ b/tests/unit_tests/test_client_utils.py @@ -309,6 +309,40 @@ def test_client_utils_gui_client_set_rpc_timeout(): assert gui._rpc_timeout == 10 +def test_client_utils_start_preserves_connect_to_gui_server_subscription(): + """ + Regression test for `bec --gui-id `: connect_to_gui_server registers the registry-state + stream with from_start=True so the current state of an already-running server is replayed. + The subsequent show() -> _start() must not replace that subscription with a plain one. + """ + gui = BECGuiClient() + gui._client = mock.MagicMock() + connector = gui._client.connector + + gui.connect_to_gui_server("external_gui") + assert connector.register.call_args.kwargs["from_start"] is True + + connector.any_stream_is_registered.return_value = True + with mock.patch.object(gui, "_start_server"): + gui._start(wait=False) + + connector.register.assert_called_once() + connector.unregister.assert_called_once() # only the one from connect_to_gui_server + + +def test_client_utils_start_registers_registry_state_when_missing(): + gui = BECGuiClient() + gui._client = mock.MagicMock() + connector = gui._client.connector + connector.any_stream_is_registered.return_value = False + + with mock.patch.object(gui, "_start_server") as mock_start_server: + gui._start(wait=False) + + connector.register.assert_called_once() + mock_start_server.assert_called_once_with(wait=False) + + def test_client_utils_kill_server_waits_for_process_before_joining_output_thread(): gui = BECGuiClient() gui._client = mock.MagicMock()