Broadcast spool assignment changes via WebSocket for cross-tab updates

Assigning or unassigning a spool now broadcasts a spool_assignment_changed
  event to all connected WebSocket clients. The frontend handles this event
  by invalidating the spool-assignments and slotPresets caches, so other
  open tabs update automatically without a page reload.
This commit is contained in:
maziggy
2026-03-26 09:34:46 +01:00
parent 45892077ab
commit 1df5130df2
6 changed files with 42 additions and 8 deletions
+1
View File
@@ -18,6 +18,7 @@ All notable changes to Bambuddy will be documented in this file.
- **WiFi Safeguard for SpoolBuddy Pi** — The install script now drops an APT hook (`/etc/apt/apt.conf.d/80-preserve-wifi`) that backs up NetworkManager WiFi connections before every `apt upgrade` and restores them if they get wiped. Prevents headless SpoolBuddy Pis from losing WiFi connectivity after Raspberry Pi OS package upgrades (observed with Bookworm kernel/raspi-config updates that clear `/etc/NetworkManager/system-connections/`).
- **SpoolBuddy Install Script Now Upgrades System Packages** — The install script now runs `apt-get upgrade -y` after installing required packages and the WiFi safeguard. This ensures the Pi is fully up to date before SpoolBuddy is deployed, and the WiFi safeguard protects connectivity during the upgrade.
- **SpoolBuddy Assign-to-AMS Material Mismatch Warnings** — The SpoolBuddy "Assign to AMS" modal now warns when the spool's material or slicer profile doesn't match the target slot's current filament. Shows a confirmation dialog with five warning levels: exact material mismatch, partial material match, profile-only mismatch, and combined material+profile mismatches. Respects the global `disable_filament_warnings` setting. Previously, assigning a spool to an occupied slot proceeded without any validation, matching the behavior already present in the main Assign Spool modal.
- **Spool Assignment Changes Sync Across Tabs** — Assigning or unassigning a spool now broadcasts a WebSocket event to all connected clients. Other open browser tabs and the SpoolBuddy frontend update automatically without requiring a page reload.
### Fixed
- **Delete Tag Leaves Stale Tag Type** — The "Delete Tag" button in the spool edit modal only cleared `tag_uid` but left `tray_uuid`, `tag_type`, and `data_origin` intact. All tag-related fields are now cleared together.
+21
View File
@@ -13,6 +13,7 @@ from backend.app.core.auth import RequirePermissionIfAuthEnabled
from backend.app.core.catalog_defaults import DEFAULT_COLOR_CATALOG, DEFAULT_SPOOL_CATALOG
from backend.app.core.database import get_db
from backend.app.core.permissions import Permission
from backend.app.core.websocket import ws_manager
from backend.app.models.ams_label import AmsLabel
from backend.app.models.color_catalog import ColorCatalogEntry
from backend.app.models.spool import Spool
@@ -1100,6 +1101,16 @@ async def assign_spool(
resp = result.scalar_one()
response = SpoolAssignmentResponse.model_validate(resp)
response.configured = configured
await ws_manager.broadcast(
{
"type": "spool_assignment_changed",
"printer_id": data.printer_id,
"ams_id": data.ams_id,
"tray_id": data.tray_id,
}
)
return response
@@ -1125,6 +1136,16 @@ async def unassign_spool(
await db.delete(assignment)
await db.commit()
await ws_manager.broadcast(
{
"type": "spool_assignment_changed",
"printer_id": printer_id,
"ams_id": ams_id,
"tray_id": tray_id,
}
)
return {"status": "deleted"}
@@ -1163,17 +1163,23 @@ class TestHasStgCurIdleBug:
assert has_stg_cur_idle_bug("a1") is True # case insensitive
assert has_stg_cur_idle_bug("a1 mini") is True
def test_a1_internal_codes_return_true(self):
"""Verify A1 internal model codes return True."""
def test_p1_models_return_true(self):
"""Verify P1P/P1S model variants return True."""
assert has_stg_cur_idle_bug("P1P") is True
assert has_stg_cur_idle_bug("P1S") is True
assert has_stg_cur_idle_bug("p1p") is True # case insensitive
def test_internal_codes_return_true(self):
"""Verify internal model codes return True."""
assert has_stg_cur_idle_bug("N1") is True # A1 Mini
assert has_stg_cur_idle_bug("N2S") is True # A1
assert has_stg_cur_idle_bug("C11") is True # P1P
assert has_stg_cur_idle_bug("C12") is True # P1S
def test_non_a1_models_return_false(self):
"""Verify non-A1 models return False."""
def test_non_affected_models_return_false(self):
"""Verify non-affected models return False."""
assert has_stg_cur_idle_bug("X1C") is False
assert has_stg_cur_idle_bug("X1") is False
assert has_stg_cur_idle_bug("P1P") is False
assert has_stg_cur_idle_bug("P1S") is False
assert has_stg_cur_idle_bug("H2D") is False
def test_none_model_returns_false(self):
+6
View File
@@ -263,6 +263,12 @@ export function useWebSocket() {
}));
break;
case 'spool_assignment_changed':
// Spool assigned/unassigned - refresh assignment data across all tabs
debouncedInvalidate('spool-assignments');
debouncedInvalidate('slotPresets');
break;
case 'spool_auto_assigned':
// RFID tag matched - refresh inventory and assignment data
debouncedInvalidate('inventory-spools');
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -23,7 +23,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-CS1ycNG7.js"></script>
<script type="module" crossorigin src="/assets/index-BKqUb3-A.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CZLTApPU.css">
</head>
<body>