Race condition in _update_state: dev mode probe released GIL via MQTT
publish between raw_data overwrite and vt_tray list restoration, letting
the event loop iterate over raw dict keys (strings) instead of spool
dicts. Affects A1, P1, and X1Plus firmware that don't send the fun field.
Fix: normalize vt_tray dict→list before raw_data assignment, restore
preserved fields before any GIL-releasing work, add defensive guard in
printer_state_to_dict.
The test conftest.py model import list was out of sync with database.py,
missing slot_preset, project_bom, spool_k_profile, and spoolbuddy_device.
Base.metadata.create_all() never created those tables in the test DB.
The bottom message bar showed stale warnings from the previous printer
after switching via dropdown or swipe. Cached AMS data was shared across
all printers in a single ref, so switching to a printer whose status
hadn't loaded fell back to the wrong printer's data. The Layout also
unconditionally cleared alerts set by child pages. Fixed by keying the
AMS cache per printer ID and only clearing Layout-owned alerts.
The periodic cleanup of old AMS sensor history entries (every ~24h)
failed with "can't compare offset-naive and offset-aware datetimes".
The cutoff used datetime.now(timezone.utc) but recorded_at stores
naive datetimes via SQLite's func.now(). Use utcnow() instead.
Replace polling-based bed cooldown monitor with event-driven approach.
Some firmware stops sending bed_temper after print completion, causing
the 30-min polling loop to time out on stale cached values. Now reacts
instantly to bed_temper MQTT data via a new on_bed_temp_update callback.
Thread event_type and template variables through the webhook call chain so
all generic webhook payloads include an "event" field and event-specific
data (printer, filename, duration, etc.) as top-level JSON fields. Existing
title/message/timestamp/source fields are unchanged. Slack format unaffected.
GitHub backup can now optionally include spool inventory (with usage
history) and print archive metadata as JSON. Both toggles are off by
default. No binary files (gcode/3MF) are included.
Single-nozzle printers (X1C, P1S, A1) report tray_now=254 for external
spool, but BambuStudio sends ams_id=255 (VIRTUAL_TRAY_MAIN_ID) in the
print command's ams_mapping2 field. Bambuddy was passing 254 as-is,
causing firmware to target AMS tray 0 instead of external spool —
resulting in 07FF_8012 "Failed to get AMS mapping table" or prints stuck
at heatbed heating when an AMS is connected but empty.
Map external spool to ams_id=255 for all non-H2D printers. H2D
dual-nozzle printers retain 254 (deputy) / 255 (main) distinction.
When multiple AMS spools match the same type/color criteria, an optional
setting now prefers the spool with the lowest remaining filament. This
helps consume partial spools before starting new ones. Sorting applies
to all matching paths: queue scheduler, print modal, and multi-printer
mapping. Unknown remain values (-1) sort to end.
Select multiple printer cards and apply bulk actions (stop, pause,
resume, clear notifications, clear bed) from a floating toolbar.
Selection shortcuts: Select All, Select by State (printing/paused/
finished/idle/error/offline), Select by Location. Action buttons are
smart-enabled based on selected printers' current states. Confirmation
modals for destructive actions. The status summary bar now shows all
printer states.
Admins can now filter the Statistics page by user via a new
stats:filter_by_user permission. A user dropdown appears in the stats
header showing all users plus "No User (System)" for prints without
attribution. The filter applies to all stats widgets, failure analysis,
and CSV/Excel exports. Backend validates the permission on all 4 stats
endpoints, returning 403 if the filter is used without authorization.
Stagger option now available when printing directly to multiple printers,
not just in queue mode. Prints are automatically queued with staggered
start times using group size/interval from Settings. New "Require
plate-clear confirmation" setting lets farm users disable per-printer
plate confirmations so queued prints start automatically on finished
printers.
Also fixes settings API type parsing for require_plate_clear (boolean),
stagger_group_size and stagger_interval_minutes (integer) — without this,
saved values returned as strings would cause the settings toggle to
always show enabled and trigger a permanent save loop.
Printers with no AMS hardware (P1S/P1P with only external spool)
rejected print commands with "Failed to get AMS mapping table"
because use_ams was always sent as true. Now auto-sets use_ams=false
when all filament slots map to external spools or are unmapped.
H2D-series excluded since they use use_ams for nozzle routing.
scan_external_folder stored raw 3MF metadata containing _thumbnail_data
bytes directly in the JSON column, causing a serialization error. Applied
the same clean_metadata() pattern used by upload/zip extraction and
removed the call to the non-existent parser.extract_thumbnail().
The archives page only fetched the 50 most recent prints due to a
hardcoded API limit default. Added client-side pagination with
configurable page sizes (25/50/100/200/All) and bumped the fetch
limit to return all archives. Page size is persisted to localStorage.
Queue page redesign: compact stats bar replaces summary cards, color-
coded left borders for status scanning, collapsible history with
condensed rows, and a new production schedule timeline view (#823)
showing estimated completion times grouped by hour with filter tabs
and day navigation. i18n keys added for all 7 languages.
Fix plate thumbnails returning 401 in print modal when auth is enabled
(missing stream token). Fix schedule calendar picker opening off-screen
(hidden input positioned with sr-only instead of near the date field).
on_print_complete returned early when no archive_id was found (auto-
archive off), skipping both internal inventory tracking (AMS remain%
deltas) and Spoolman usage reporting. Moved the filament usage tracking
block to run before the archive_id early-return so consumption is
always recorded regardless of the auto-archive setting.
When adding a print to the queue for multiple printers, users can now
enable "Stagger printer starts" to avoid power spikes from simultaneous
bed heating. Configurable group size and interval — first group starts
immediately (ASAP) or at scheduled time, subsequent groups get offset
scheduled_time values. No backend queue/scheduler changes — leverages
existing scheduled_time field.
Also adds a dedicated Queue tab in Settings (stagger defaults + auto-
drying moved from Filament tab), i18n keys for all 7 locales, frontend
and backend tests.
- Round scale weight to integer before sending to backend (Pydantic
rejects non-whole floats for int fields), move modal close to finally
block, add error toast with actual API message
- Fix null-field crash in SpoolInfoCard prop construction: pick one
source object instead of per-field ?? fallbacks that crash when
displayedSpool has null subtype/brand/rgba and matchedSpool is null
- Add React ErrorBoundary to App so crashes show error instead of
black screen
- Remove --max-old-space-size=128 and --enable-low-end-device-mode
from kiosk Chromium flags (crashed renderer/display)
- Append kiosk flags to Pi GPU defaults instead of resetting them
- Add wlr-randr keep-alive and screenBlankTimeout=0 to prevent
display blanking on labwc 0.9.x
- Fix tests: add ToastProvider to Dashboard test wrapper, update
StatusBar tests for removed animate-pulse class
Corrected left/right nozzle labels in ams_mapping2 comment
(255=main/right, 254=deputy/left). Added [0.2.2.2] release header
and #836 changelog entry noting the nozzle routing fix was already
included via the #797 ams_mapping2 format change.
The _is_docker_environment() fallback assumed Docker when .git/ was
absent, which is also true for native installs in Proxmox LXC
containers. Replace the .git/ fallback with a check of
/run/systemd/container (only matches docker/podman/oci, not lxc).
Frontend: replace expensive idle dashboard animations (3x animate-ping
with scale transforms, blur-2xl glow, continuous animate-pulse on
status dots) with static NFC rings and slow 5s color-cycling spool.
Chromium: add --disable-extensions, --disable-background-timer-throttling,
--memory-pressure-off, --disable-renderer-backgrounding, --disable-breakpad,
and --js-flags=--max-old-space-size=128. Install script: mask stripped
services (not just disable) to prevent socket/dbus reactivation; use
/etc/systemd/user/ global overrides for user services instead of
unreliable su-based systemctl --user. Remove chromium/upower from
strip_packages since kiosk reinstalls them immediately.
Add Chromium flags to cut overhead on Pi: disable extensions, crash
reporter, background timer throttling, renderer backgrounding, and cap
V8 heap at 128MB. Mask (not just disable) stripped system services to
prevent socket/dbus reactivation, and add xdg-permission-store to the
disable list. Remove chromium and upower from strip_packages since the
kiosk needs them — they were being uninstalled then immediately
reinstalled on every run.
reporter, background timer throttling, renderer backgrounding, and cap
V8 heap at 128MB. Also mask (not just disable) stripped system services
to prevent socket/dbus reactivation, and add xdg-permission-store to
the disable list.
Camera streams, snapshots, thumbnails, timelapse videos, photos, QR
codes, and cover images served via <img>/<video> tags were previously
unauthenticated because browser media elements cannot send Authorization
headers. When auth is enabled, these endpoints are now protected by a
reusable stream token (?token=xxx) obtained from POST
/printers/camera/stream-token (requires CAMERA_VIEW permission).
Replace Chromium with cog (WPE WebKit) for the kiosk browser. Cog is
purpose-built for embedded kiosk displays with a fraction of Chromium's
CPU and memory footprint on Pi hardware.
Add React Query `select` to SpoolBuddyLayout and SpoolBuddyDashboard
printer status queries so only `connected` is extracted. Temperature,
fan, and progress changes no longer trigger re-renders on every MQTT
tick.
Expand service/package stripping to disable pipewire audio stack, CUPS
printing, rpcbind, upower, polkit, accounts-daemon, xdg-desktop-portal,
and mpris-proxy. Add user-level service masking for pipewire/portals.
Update SSH update cache clearing to handle both WPE WebKit and legacy
Chromium cache paths.
Chromium: override Debian defaults via /etc/chromium.d/spoolbuddy-kiosk
to disable GPU rasterization, enable low-end device mode, and disable
smooth scrolling/background networking. The system default
--enable-gpu-rasterization conflicted with per-launch flags.
WebSocket: add React Query `select` to SpoolBuddyLayout and
SpoolBuddyDashboard printer status queries so only `connected` is
extracted. Temperature/fan/progress changes no longer trigger
re-renders on every MQTT tick (~10/sec idle).
Services: expand install script to strip pipewire, CUPS, rpcbind,
upower, polkit, accounts-daemon, xdg-desktop-portal, mpris-proxy.
Add user-level service masking for pipewire/portals.
Override Debian's default Chromium flags via /etc/chromium.d/spoolbuddy-kiosk
to disable GPU rasterization, enable low-end device mode, and disable smooth
scrolling/background networking. The system default --enable-gpu-rasterization
conflicted with per-launch flags — the new config replaces all system defaults
so kiosk flags take effect cleanly.
Expand service/package stripping to disable pipewire audio stack, CUPS
printing, rpcbind, upower, polkit, accounts-daemon, xdg-desktop-portal,
and mpris-proxy. Add user-level service masking for pipewire/portals
that system-level disable misses.
Add Chromium performance flags (disable-gpu-rasterization,
enable-low-end-device-mode, disable-smooth-scrolling,
disable-background-networking, disable-dev-shm-usage) to reduce
CPU load from ~54% to manageable levels on Pi 4B.
Expand service/package stripping to disable pipewire audio stack,
CUPS printing, rpcbind, upower, polkit, accounts-daemon,
xdg-desktop-portal, and mpris-proxy. Add user-level service
masking for pipewire/portals that system-level disable misses.