diff --git a/client/public/version.js b/client/public/version.js index ed04014..0c47ae3 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.27 R296"; +window.CHGRID_WEB_VERSION = "2026.02.27 R297"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/main.ts b/client/src/main.ts index b337172..f929c4f 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -883,7 +883,7 @@ function classifySystemMessageSound(message: string): keyof typeof SYSTEM_SOUND_ if (normalized.includes(' is now known as ') || normalized.startsWith('you are now known as ')) { return 'notify'; } - if (normalized.startsWith('server rebooting in ') || normalized === 'server reboot already scheduled.') { + if (normalized.startsWith('server rebooting in ')) { return 'notify'; } return null; diff --git a/client/src/network/messageHandlers.ts b/client/src/network/messageHandlers.ts index 38f6d8c..062f779 100644 --- a/client/src/network/messageHandlers.ts +++ b/client/src/network/messageHandlers.ts @@ -231,6 +231,11 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco deps.playSample(deps.ACTION_SOUND_URL, 1); } else if (message.system) { deps.pushChatMessage(message.message); + const normalized = message.message.trim().toLowerCase(); + if (normalized === 'server reboot already in progress.') { + deps.audioUiBlip(); + break; + } const sound = deps.classifySystemMessageSound(message.message); if (sound) { deps.playSample(deps.SYSTEM_SOUND_URLS[sound], 1); diff --git a/server/app/server.py b/server/app/server.py index 829b9c2..71d6c12 100644 --- a/server/app/server.py +++ b/server/app/server.py @@ -1686,11 +1686,10 @@ class SignalingServer: return True reboot_message = remainder if separator else "" if not self._schedule_reboot(client.username or client.nickname, reboot_message): - await self._send( - client.websocket, + await self._broadcast( BroadcastChatMessagePacket( type="chat_message", - message="Server reboot already scheduled.", + message="Server reboot already in progress.", system=True, ), ) diff --git a/server/tests/test_server_message_handling.py b/server/tests/test_server_message_handling.py index 32c9de7..f7835f7 100644 --- a/server/tests/test_server_message_handling.py +++ b/server/tests/test_server_message_handling.py @@ -583,3 +583,41 @@ async def test_chat_reboot_schedules_and_broadcasts_message(monkeypatch: pytest. assert getattr(packet, "type", "") == "chat_message" assert packet.system is True assert packet.message == "Server rebooting in 5 seconds. maintenance" + + +@pytest.mark.asyncio +async def test_chat_reboot_already_in_progress_broadcasts_notice(monkeypatch: pytest.MonkeyPatch) -> None: + server = SignalingServer("127.0.0.1", 8765, None, None) + ws = _fake_ws() + client = ClientConnection( + websocket=ws, + id="u1", + nickname="Tester", + authenticated=True, + user_id="1", + username="tester", + permissions={"chat.send", "server.allow_reboot"}, + ) + server.clients[ws] = client + + broadcast_payloads: list[object] = [] + send_payloads: list[object] = [] + + async def fake_broadcast(packet: object, exclude: ServerConnection | None = None) -> None: + broadcast_payloads.append(packet) + + async def fake_send(websocket: ServerConnection, packet: object) -> None: + send_payloads.append(packet) + + monkeypatch.setattr(server, "_broadcast", fake_broadcast) + monkeypatch.setattr(server, "_send", fake_send) + monkeypatch.setattr(server, "_schedule_reboot", lambda _requested_by, _message: False) + + await server._handle_message(client, json.dumps({"type": "chat_message", "message": "/reboot maintenance"})) + + assert send_payloads == [] + assert len(broadcast_payloads) == 1 + packet = broadcast_payloads[0] + assert getattr(packet, "type", "") == "chat_message" + assert packet.system is True + assert packet.message == "Server reboot already in progress."