# Bambuddy v0.2.2.1
## Virtual Printer — Breaking Changes
▎ Action required for existing Virtual Printer users. Please follow the migration guide before updating.
The Virtual Printer FTP server now binds directly to port 990 instead of using an iptables redirect from 990 → 9990. This fixes FTP being routed to the wrong VP when running multiple virtual printers on different bind IPs (#735).
What you need to do:
- Native/systemd: Remove old iptables redirect rules (990 → 9990) and verify CAP_NET_BIND_SERVICE is set in the systemd service.
- Docker (bridge network): Change port mapping from 990:9990 to 990:990 in docker-compose.yml.
- Docker (host network): Remove old iptables redirect rules on the host. No other changes needed.
- Unraid/Synology/TrueNAS: Remove any iptables rules you added for 990 → 9990.
Detailed instructions can be found here -> https://github.com/maziggy/bambuddy/blob/0.2.2.1/docs/migration-vp-ftp-port.md
Proxy mode now supports cross-VLAN/subnet printing (#757) with transparent TCP proxying for FTP (990), file transfer (6000), and camera streaming (322). If you use proxy mode behind a firewall, ensure ports 6000 and 322 are open between the slicer and Bambuddy.
X1C/X1 compatibility is fixed — both server mode (corrected SSDP model codes BL-P001/BL-P002) and proxy mode (end-to-end TLS passthrough preserving the printer's real certificate). Existing VPs are automatically migrated on startup.
## Highlights
- Virtual Printer Overhaul (#735, #757) — Major rework of Virtual Printer networking. FTP, proxy mode, and X1C compatibility all significantly improved. Existing VP users: action required — see below.
- Select Plates to Queue (#777) — Multi-plate 3MF files now support selecting a subset of plates to queue, with per-plate checkboxes.
- HMS Error Visibility (#772) — Red "Problem" counter in the status bar, amber/red status pips, and HMS-first sorting for print farms.
- Per-User Email Notifications (#693) — When Advanced Authentication is enabled, individual users can now receive email notifications for their own print jobs. Contributed by @cadtoolbox.
- Spool Rotation During AMS Drying — Added a "Rotate spool during drying" checkbox to the manual drying popover for AMS 2 Pro and AMS-HT units.
## New Features
- SpoolBuddy OTA Updates — SpoolBuddy devices can now be updated directly from the Settings → Updates tab without SSH access. Click "Check for Updates" to see if a newer version is available, then "Apply Update" to trigger the update. The daemon picks up the command via its heartbeat, pulls the latest code from GitHub, installs dependencies, and restarts automatically via systemd. Live progress is shown in the UI with status messages from the device. The status bar at the bottom automatically checks for updates every 5 minutes and shows a prominent message when one is available. Requires the device to be online.
- Select Plates to Queue (#777) — Multi-plate 3MF files now support selecting a subset of plates to queue, instead of only "one plate" or "all plates". In add-to-queue mode, each plate has a checkbox for multi-select, with a "Select All / Deselect All" toggle. Reprint and edit modes remain single-select.
- Camera Image Rotation (#672) — Added per-printer camera rotation (0°, 90°, 180°, 270°) for cameras mounted in portrait or upside-down orientations. Configurable in Settings → Camera for each printer. Rotation applies to live stream, embedded viewer, stream overlay, and notification snapshots.
- Per-User Email Notifications (#693) — When Advanced Authentication is enabled, individual users can now receive email notifications for their own print jobs. Contributed by @cadtoolbox.
- Quick Print Speed Control (#256) — Speed control badge on the printer card with Silent/Standard/Sport/Ludicrous presets.
- Spool Name Column & Filter in Filament Inventory (#740) — Added a "Spool" column and spool name filter dropdown.
- Spool Rotation During AMS Drying — Added a "Rotate spool during drying" checkbox to the manual drying popover for AMS 2 Pro and AMS-HT units.
- Admin Set Default Nav-Menu Order (#761) — Admins can set their sidebar menu order as the default for new users. Contributed by @cadtoolbox.
- Add Total Cost to Projects (#733) — Projects page now shows total cost (material + energy + BOM). Contributed by @Keybored02.
- Material Mismatch & Insufficient Filament Checks (#720) — Warns on filament type/profile mismatch and insufficient material before printing. Contributed by @Keybored02.
- Rework Archive Duplicates Tagging (#718) — Smarter duplicate detection (name + SHA256), reprint counter tags, parent print links. Contributed by @Keybored02.
## Improved
- HMS Error Visibility on Printers Page (#772) — Red "Problem" counter in the status summary bar, red/amber status pips for errors/warnings, amber progress bars for paused prints, and HMS-first sorting.
- Home Assistant Notifications (#750) — Added support for HA notify services. Contributed by @mrtncode.
- Print Command Response Verification (#737) — Monitors whether the printer responds within 15 seconds after sending a print command; logs a warning if silently ignored.
- Compact Assign Spool Modal (#725) — 3-column grid layout showing more spools without scrolling.
- Reformatted AMS Drying Presets Table (#732) — Grouped by AMS type with inline unit labels.
- Redesigned Bug Report Debug Log Flow — Interactive 3-step flow instead of a fixed 30-second timer.
## Fixed
- Queue Print Command Not Reaching Printer (#778) — Fixed repeated MQTT reconnection cycles on printers that reject request topic subscription.
- AMS Slot Search Shows Unrelated Profiles (#681) — Search filter now correctly applies to saved presets.
- AMS Spools Removed After Printer Restart (#765) — Skips slot clearing on shutdown messages.
- Carbon Rod Lubrication Maintenance Task Incorrect (#755) — Removed incorrect lubrication task for carbon rods.
- Spurious "Job Waiting for Filament" Notification (#753) — Skips waiting notification when all printers are just busy.
- File Rename Removes Extension (#751) — Extension is now non-editable.
- Ntfy Notifications Fail With Non-ASCII Characters (#742) — Fixed UTF-8 encoding for header values.
- Print Complete Notification Not Firing (#736) — Added 45-second timeout on photo capture.
- Camera Window Overlapping Modals (#738) — Lowered camera z-index.
- Webhook Notifications Missing Camera Snapshot (#679) — Added base64-encoded image field to webhook payloads.
- White Filament Color Swatches Invisible in Light Theme (#726) — Changed to dark border across all views.
- Mobile Sidebar Not Scrollable — Added overflow scrolling.
- Send Bambu RFID Tags to Spoolman & Manual Mode Unlink (#719) — Proper RFID identifiers sent to Spoolman, unlink button in manual mode, fixed location clearing for generic spools. Contributed by @shrunbr.
- SpoolBuddy Daemon Reports Stale Version — Version now read from backend APP_VERSION instead of hardcoded string.
- Native Install Missing CAP_NET_BIND_SERVICE — Fixed systemd template for VP proxy on native installs.
- UserEmailPreference Model Not Registered — Fixed SQLAlchemy model import order.
## Security
- Bump pyOpenSSL 25.3.0 → 26.0.0 — Fixes CVE-2026-27448 and CVE-2026-27459.
- Bump pyasn1 0.6.2 → 0.6.3 — Fixes CVE-2026-30922.
- Bump flatted 3.4.1 → 3.4.2 — Fixes GHSA-rf6f-7fwh-wjgh (dev dependency).
## Contributors
Thank you to everyone who contributed to this release!
- @cadtoolbox — Admin default nav-menu order (#761), Per-user email notifications (#693)
- @Keybored02 — Total cost in Projects (#733), Material mismatch checks (#720), Archive duplicates rework (#718)
- @mrtncode — Home Assistant notify services (#750)
- @shrunbr — Bambu RFID tags to Spoolman & manual unlink (#719)
And thanks to everyone who reported issues and provided feedback!
The OTA update feature added update_status and update_message to the
SpoolBuddyDevice model but no ALTER TABLE migration, causing
"no such column: spoolbuddy_devices.update_status" on existing databases.
The OTA update feature added update_status and update_message to the
SpoolBuddyDevice model but no ALTER TABLE migration, causing
"no such column: spoolbuddy_devices.update_status" on existing databases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Main had VP #735 hotfix commits that were also on dev. All conflicts
resolved by keeping 0.2.2.1 (superset of main). Verified: 2098 backend
tests pass, frontend builds clean, no conflict markers remain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pyOpenSSL 25.3.0 → 26.0.0 (CVE-2026-27448, CVE-2026-27459)
pyasn1 0.6.2 → 0.6.3 (CVE-2026-30922)
No breaking changes — Python 3.7 drop is irrelevant (we use 3.13),
cryptography >=46.0.0 requirement already satisfied (we have 46.0.5),
and we don't use set_tlsext_servername_callback (the behavioral change).
The SpoolBuddy layout now auto-checks for daemon updates every 5
minutes and shows "Update available: v{version}" in the status bar.
Removed the beta toggle since SpoolBuddy follows Bambuddy's release
channel. The daemon version is now read from backend APP_VERSION
instead of a stale hardcoded string.
The daemon had a hardcoded __version__ = "0.2.2b1" that was never
bumped, causing the update check to always show an update available.
Changed to read APP_VERSION from backend/app/core/config.py at import
time so the daemon version stays in sync automatically.
SpoolBuddy devices can now be updated from Settings → Updates without
SSH access. The daemon picks up an "update" command via its existing
heartbeat, runs git fetch/reset + pip install, reports progress back
to the backend, then exits for systemd to restart with the new code.
Backend: update_status/update_message fields, trigger + status endpoints
Daemon: _perform_update() handler, report_update_status() API method
Frontend: "Apply Update" button with live progress in UpdatesTab
When targeting a specific printer, the scheduler's power-on-wait loop
created new MQTT clients on each attempt. Each new client re-tried the
request topic subscription, which some brokers (e.g. A1) reject by
disconnecting. This caused a 170s thrash loop leaving the connection
fragile, so the eventual print command silently failed to reach the
printer.
Cache request topic support per serial number at the class level so
new client instances inherit the knowledge and skip the subscription.
Multi-plate 3MF files now support selecting a subset of plates to queue
via checkboxes, instead of the binary "one plate" or "all plates" toggle.
In add-to-queue mode, each plate gets a checkbox for multi-select with a
Select All / Deselect All toggle. Reprint and edit modes remain single-select.
The saved preset bypassed the search filter entirely, so when a slot's
DB mapping was stale (e.g. previously Matte, now physically Silk), the
old preset always appeared regardless of search query. Remove the search
bypass — saved/current presets still bypass the printer model filter but
must match the search text like all other presets.
Add visual indicators so printers with HMS errors stand out in large
print farms:
- Red "Problem" counter in the status summary bar
- Status pip turns red (fatal/serious) or amber (warning) for HMS errors
- Progress bar turns amber when a print is paused
- Status sort prioritizes printers with HMS errors at the top
When a printer shuts down it sends a final MQTT message with
tray_exist_bits=0 and power_on_flag=false. The tray_exist_bits
clearing code processed this all-zero value, wiping every AMS
slot's filament data. On reconnect, the auto-unlink check saw
empty tray data (no color, no type) and deleted all spool
assignments as "fingerprint mismatch".
Fix: skip tray_exist_bits slot clearing when power_on_flag is
false. Defaults to true when absent for backwards compatibility.
Adds 3 regression tests covering shutdown preservation, genuine
removal still working, and missing power_on_flag fallback.
Replace fixed 30-second debug log collection with an interactive
3-step flow: start logging, reproduce the issue, stop & submit.
Users now control timing instead of racing a countdown.
Backend: split _collect_debug_logs() into POST /start-logging and
POST /stop-logging endpoints; add debug_logs field to submit request.
Frontend: 3-step progress indicator with elapsed timer, pulsing
active state, and 5-minute auto-stop. Updated all 7 locale files.
Add a "Rotate spool during drying" checkbox to the manual drying popover
for AMS 2 Pro and AMS-HT units. The firmware-level rotate_tray field was
already sent (hardcoded to false) — this makes it user-configurable.
The checkbox defaults to unchecked and resets each time the popover opens.
Firmware silently disables rotation if filament is currently loaded.
When all matching printers were busy and a job was queued with ASAP
timing, the scheduler immediately fired a "Job Waiting for Filament"
notification even though the job was just waiting for a printer to
finish — no user action required.
Added _is_busy_only() check to skip the waiting notification when the
only reason is "Busy". Notifications still fire for actionable reasons
(missing filament, offline, wrong color). Also renamed the default
notification template title to "Queue Job Waiting" and updated
descriptions across all 7 locales.
Renaming a file in the File Manager included the extension in the
editable text, letting users accidentally strip it and break the file.
The rename modal now separates the base name from the extension
(.3mf, .gcode, .gcode.3mf), showing the extension as a non-editable
gray suffix and re-appending it on save.
A1/A1 Mini printers fail to connect through VP proxy mode while
X1C/P2S/H2C work fine with identical transparent TCP proxy code.
Root cause unknown — the proxy is model-agnostic, so the failure
suggests BambuStudio uses a different connection flow for A1 models
that hits ports we don't proxy.
Add diagnostic probe listeners on ports 21, 80, and 443 on each
proxy VP's dedicated bind IP. If the slicer connects to any of
these un-proxied ports, a WARNING is logged so debug logs and
tcpdump can reveal what's missing.
The closed-source bambu_networking DLL validates TLS connection parameters
and rejects connections where the certificate doesn't match the printer's
real BBL CA certificate. The TLS-terminating proxy presented Bambuddy's
own certificate, causing X1C/X1 prints to silently fail after verify_job.
Switch to transparent TCP proxying for FTP, FileTransfer, Camera, and FTP
data — only MQTT remains TLS-terminated (required for IP rewriting). The
slicer now gets end-to-end TLS directly with the printer's real certificate.
Changes:
- SlicerProxyManager uses TCPProxy for FTP (990), FileTransfer (6000),
Camera (322), and pre-listens on FTP data ports (50000-50100)
- Only MQTT (8883) uses TLSProxy for IP rewriting
- Remove debug logging from MQTT and FTP proxy code
- Fix install.sh missing AmbientCapabilities=CAP_NET_BIND_SERVICE
- Update module docstring, migration docs, README proxy description
- Add tests verifying transparent proxy architecture
- Add ports 6000 (file transfer) and 322 (RTSP camera) to Dockerfile
EXPOSE and docker-compose.yml bridge mode port mapping
- Update migration doc with new proxy mode port requirements
- Regenerate proxy-mode-diagram.png with all proxied ports
When the slicer and printer are on different VLANs, Bambu Studio could
not send prints through the proxy because the printer's real IP leaked
through MQTT payloads, the bind protocol forwarded the real printer's
identity, file transfer and camera ports were not proxied, and FTP
data connections raced the TLS handshake on zero-byte uploads.
- Rewrite IP addresses in MQTT PUBLISH payloads (string + integer)
with proper packet framing and cross-chunk buffering
- Respond to bind/detect with VP identity via BindServer
- Add TLS proxies for port 6000 (file transfer) and 322 (RTSP camera)
- Buffer slicer FTP data during printer connection setup
- Advertise configured VP name in SSDP proxy
- Add cross-subnet SSDP wildcard listener for VPN setups
- Register UserEmailPreference model in models/__init__.py
- Add 11 unit tests for MQTT rewrite, IP conversion, SSDP name
When the slicer and printer are on different VLANs, Bambu Studio could
not send prints through the proxy because the printer's real IP leaked
through MQTT payloads, the bind protocol forwarded the real printer's
identity, the port 6000 file transfer tunnel was not proxied, and FTP
data connections raced the TLS handshake on zero-byte uploads.
- Rewrite IP addresses in MQTT PUBLISH payloads (string + integer)
with proper packet framing and cross-chunk buffering
- Respond to bind/detect with VP identity via BindServer
- Add TLS proxy for port 6000 (file transfer tunnel)
- Buffer slicer FTP data during printer connection setup
- Advertise configured VP name in SSDP proxy
- Add cross-subnet SSDP wildcard listener for VPN setups
- Register UserEmailPreference model in models/__init__.py
- Add 11 unit tests for MQTT rewrite, IP conversion, SSDP name
Carbon rods use plain bearings — lubricating them degrades print quality.
Removed the lubrication task from defaults; only "Clean Carbon Rods"
remains. Existing entries are auto-removed on next startup via
ensure_default_types(). Updated wiki link mapping and tests.
When searching for a non-existent profile in the AMS slot config modal,
presets matching the current slot's tray_info_idx bypassed the search
filter, showing e.g. all "Generic PLA" variants instead of no results.
Narrow the search bypass to only the exact saved preset — the broader
trayIdx match still bypasses the model filter as intended.
After sending a print command via MQTT, monitor whether the printer's
gcode_state changes within 15 seconds. If not, log a warning visible in
support packages. Addresses silent command drops observed on P1S firmware
01.09.01.00 where the printer ignores project_file commands while
continuing to send status updates.
Add a speed control badge to the printer monitoring card controls row
that lets users switch between Silent (50%), Standard (100%), Sport
(124%), and Ludicrous (166%) presets during active prints. The badge
displays a gauge icon with the current speed percentage, always visible
but disabled when idle. Includes backend endpoint, optimistic UI
updates, i18n for all 7 locales, and full test coverage.
Add a "Spool" column to the filament inventory table that displays
the spool catalog entry name associated with each spool. The column
is hidden by default and can be enabled via the column visibility
menu. Also add a spool name filter dropdown next to the brand filter,
shown when any spools have catalog entries assigned. No backend
changes needed — catalog data is fetched and joined on the frontend.
Ntfy notifications with camera snapshots failed when the printer name
or filename contained non-ASCII characters. httpx enforces ASCII
encoding on string header values, but the Title and Message headers
can contain printer names with accented letters or CJK characters.
Encode these header values as UTF-8 bytes, which ntfy handles correctly.
Test notifications were unaffected because they use a hardcoded ASCII
title and no image attachment.
When running multiple virtual printers with different access codes on
separate bind IPs, FTP connections were always routed to the wrong VP.
Root cause: the iptables REDIRECT rule (990→9990) rewrites the
destination IP to the incoming interface's primary address. With Linux's
weak host model (arp_filter=0), packets for secondary IPs arrive on the
primary interface, and REDIRECT sends them all to the first VP's FTP
server. MQTT was unaffected because port 8883 had no redirect.
Fix: FTP server now binds directly to port 990 (standard implicit FTPS),
eliminating the iptables redirect entirely. Requires CAP_NET_BIND_SERVICE
(already set in the systemd service file and Docker image).
Also removed a global asyncio set_exception_handler() in the MQTT server
that was overwritten by each VP instance, causing spurious "Unhandled
exception in client_connected_cb" errors on startup.
Changes:
- FTP_PORT: 9990 → 990 (ftp_server.py)
- Removed set_exception_handler() from MQTT server
- Updated Dockerfile, docker-compose.yml port mappings
- Deprecated --redirect-990 in install script
- Updated wiki: removed iptables instructions for all platforms
- Added migration guide (docs/migration-vp-ftp-port.md)
- Added unit tests for port constant and no-global-state invariant
When running multiple virtual printers with different access codes on
separate bind IPs, FTP connections were always routed to the wrong VP.
Root cause: the iptables REDIRECT rule (990→9990) rewrites the
destination IP to the incoming interface's primary address. With Linux's
weak host model (arp_filter=0), packets for secondary IPs arrive on the
primary interface, and REDIRECT sends them all to the first VP's FTP
server. MQTT was unaffected because port 8883 had no redirect.
Fix: FTP server now binds directly to port 990 (standard implicit FTPS),
eliminating the iptables redirect entirely. Requires CAP_NET_BIND_SERVICE
(already set in the systemd service file and Docker image).
Also removed a global asyncio set_exception_handler() in the MQTT server
that was overwritten by each VP instance, causing spurious "Unhandled
exception in client_connected_cb" errors on startup.
Changes:
- FTP_PORT: 9990 → 990 (ftp_server.py)
- Removed set_exception_handler() from MQTT server
- Updated Dockerfile, docker-compose.yml port mappings
- Deprecated --redirect-990 in install script
- Updated wiki: removed iptables instructions for all platforms
- Added migration guide (docs/migration-vp-ftp-port.md)
- Added unit tests for port constant and no-global-state invariant
After assigning a spool to an AMS slot, the Bambuddy UI could show the
wrong filament preset (e.g. "Bambu PLA Matte" instead of "Bambu PLA
Silk") even though the printer was configured correctly.
Two bugs:
1. AssignSpoolModal (PrintersPage hover card path) never saved the slot
preset mapping to the DB, so the display fell back to the old/stale
mapping from a previous manual configuration.
2. AssignToAmsModal (SpoolBuddy path) constructed the preset name from
spool.material + spool.subtype ("PLA Silk") instead of using the
authoritative spool.slicer_filament_name ("Bambu PLA Silk").
Fix: the backend now saves the slot preset mapping in assign_spool()
after successful MQTT configuration, using slicer_filament_name as the
display name. This covers both frontend paths and ensures the correct
name is always stored.
FTP PASS commands were logged with the plaintext password visible in
log files. Since support packages include logs and are shared publicly
on GitHub issues, this exposed user access codes. Now redacted as
PASS ********.
Print complete notifications were chained behind the finish photo
capture task with no timeout. If photo capture hung, the notification
would never send. Added a 45-second timeout so notifications always
fire regardless of photo outcome.
Also added diagnostic logging to MQTT state detection and upgraded
notification error logging to include stack traces for easier
debugging.
Floating camera viewer used z-50, same as all modals, causing it to
render on top of dialogs like Assign Spool. Lowered to z-40 so modals
always stack above the camera window.
X1C and X1 virtual printers used legacy SSDP model codes
(3DPrinter-X1-Carbon, 3DPrinter-X1) that BambuStudio doesn't
recognize, causing "incompatible printer preset" errors when
sending prints. Changed to the correct codes (BL-P001, BL-P002)
that real printers report via SSDP.
Also fixed proxy mode auto-inherit storing printer display names
(e.g. "X1C") instead of SSDP codes, by adding a resolution layer
that maps display names to model codes.
DB migration auto-converts existing VPs on startup.
X1C and X1 virtual printers used legacy SSDP model codes
(3DPrinter-X1-Carbon, 3DPrinter-X1) that BambuStudio doesn't
recognize, causing "incompatible printer preset" errors when
sending prints. Changed to the correct codes (BL-P001, BL-P002)
that real printers report via SSDP.
Also fixed proxy mode auto-inherit storing printer display names
(e.g. "X1C") instead of SSDP codes, by adding a resolution layer
that maps display names to model codes.
DB migration auto-converts existing VPs on startup.
Per-printer camera rotation (0°/90°/180°/270°) for cameras mounted
in portrait or upside-down. CSS rotation for live views, Pillow
rotation for notification snapshots. Setting visible in external
camera config when enabled.
Daily beta build tags (e.g. v0.2.3b1-daily.20260316) were not detected
as prereleases because parse_version() only checked the last
dot-separated segment for letters. The daily date suffix is purely
numeric, so it passed the stable release check. Now checks the entire
version string for prerelease markers.
Changed filament color circle borders from border-white/20 to
border-black/20 across all views so white spools are distinguishable
against light backgrounds.
- Change spool list from vertical to 3-column grid (2-col on small screens)
- Widen modal from max-w-md to max-w-2xl
- Increase scroll area from max-h-64 to max-h-96
- Show compact cards with name, color dot, and remaining/total weight
Add tests, docs, and ruff fixes for per-user email notifications
- Fix missing timezone import in email_service.py (F821)
- Fix unused lambda arg in main.py asyncio done_callback (ARG005)
- Fix E302 blank line spacing for mark_printer_stopped_by_user
- Fix F821/UP037 forward reference in user_email_pref model
- Fix SettingsPage test for duplicate "Notifications" text
- Add backend unit tests for permissions, schemas, and templates
- Add backend integration tests for user-notifications API
- Add frontend tests for NotificationsPage
- Add user_email_pref model import to test conftest
- Update CHANGELOG and README
Webhook providers did not include image data (e.g. camera snapshots
from first layer complete notifications) even though other providers
like Telegram, Pushover, and Discord already attached them. The webhook
payload now includes a base64-encoded "image" field when a snapshot is
available (generic format only, excluded from Slack format).
**# Bambuddy v0.2.2**
**## Highlights**
- **Remote AMS Drying** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — Start, monitor, and stop drying sessions for AMS 2 Pro and AMS-HT directly from the Printers page. Select filament type with official BambuStudio temperature/duration presets, or set temperature manually. Supported on X1/X1C, P1P/P1S, H2D, H2D Pro, and X1E.
- **Queue Auto-Drying** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — Automatically dry filament between scheduled queue prints when AMS humidity exceeds the configured threshold. Uses conservative parameters for mixed filament types, with optional "block queue" mode to delay prints until drying completes.
- **Ambient Drying** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — Automatically keep filament dry on idle printers based on humidity, even without queued prints. Enable "Ambient drying" in Settings → Print Queue.
- **Virtual Printer Queue Auto-Dispatch Toggle** ([#587](https://github.com/maziggy/bambuddy/issues/587)) — Added an "Auto-dispatch" toggle to virtual printers in Queue mode. When disabled, prints are added to the queue but wait for manual dispatch instead of starting automatically.
- **Queue All Plates** ([#530](https://github.com/maziggy/bambuddy/issues/530)) — Multi-plate 3MF files can now be queued in one action. A "Queue All N Plates" toggle adds every plate as a separate queue entry, each individually editable.
- **AMS Info Card & Custom Labels** ([#570](https://github.com/maziggy/bambuddy/pull/570)) — Hovering an AMS label shows serial number, firmware version, and an editable friendly name. Slot numbers now display inside each filament color circle. Contributed by @cadtoolbox.
- **In-App Bug Reporting** — A floating bug report button lets users submit reports directly from the UI, including description, optional screenshot, and automatically collected diagnostic data with full sensitive-data redaction.
**## New Features**
- **First Layer Complete Notification** ([#679](https://github.com/maziggy/bambuddy/issues/679)) — Get notified with a camera snapshot when the first layer finishes printing, so you can check adhesion remotely. Enable the "First Layer Complete" toggle on any notification provider.
- **Remote AMS Drying** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — Start, monitor, and stop drying sessions for AMS 2 Pro and AMS-HT directly from the Printers page. Select filament type with official BambuStudio temperature/duration presets, or set temperature manually. Supported on X1/X1C, P1P/P1S, H2D, H2D Pro, and X1E.
- **Queue Auto-Drying** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — Automatically dry filament between scheduled queue prints when AMS humidity exceeds the configured threshold. Uses conservative parameters for mixed filament types, with optional "block queue" mode to delay prints until drying completes.
- **Configurable Drying Presets** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — Customize temperature and duration for each filament type in Settings → Print Queue. Defaults match BambuStudio presets, with separate presets for AMS 2 Pro and AMS-HT.
- **AMS PSU Detection** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — The drying button is disabled with a tooltip when the AMS lacks sufficient power for drying. Reads `dry_sf_reason` from firmware and surfaces HMS error codes for power issues.
- **Ambient Drying** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — Automatically keep filament dry on idle printers based on humidity, even without queued prints. Enable "Ambient drying" in Settings → Print Queue.
- **Assign Spool to Empty AMS Slot** ([#717](https://github.com/maziggy/bambuddy/issues/717)) — The "Assign Spool" option now appears on empty (unconfigured) AMS slots. Selecting a spool auto-configures the slot with the correct filament profile, color, and K-profile in one step.
- **Home Assistant Notification Provider** ([#656](https://github.com/maziggy/bambuddy/issues/656)) — Send persistent notifications to the HA dashboard. HA automations can forward them to mobile apps, WhatsApp, or any other service.
- **Virtual Printer Queue Auto-Dispatch Toggle** ([#587](https://github.com/maziggy/bambuddy/issues/587)) — Added an "Auto-dispatch" toggle to virtual printers in Queue mode. When disabled, prints are added to the queue but wait for manual dispatch instead of starting automatically.
- **Queue All Plates** ([#530](https://github.com/maziggy/bambuddy/issues/530)) — Multi-plate 3MF files can now be queued in one action. A "Queue All N Plates" toggle adds every plate as a separate queue entry, each individually editable.
- **Malaysian Ringgit Currency** ([#634](https://github.com/maziggy/bambuddy/issues/634)) — Added MYR (RM) to the list of supported currencies for filament cost tracking.
- **ETA Variable in Notifications** ([#638](https://github.com/maziggy/bambuddy/issues/638)) — Added `{eta}` template variable to print start/progress notifications. Shows estimated wall-clock completion time based on the user's configured time format (12h/24h).
- **Bulk Delete Spool and Color Catalog Entries** ([#646](https://github.com/maziggy/bambuddy/issues/646)) — Added checkbox selection and bulk delete to both the Spool Catalog and Color Catalog in Settings > Filament.
- **Force Color Match** ([#625](https://github.com/maziggy/bambuddy/pull/625)) — Added a "Force Color Match" option for "Print to Any" queue scheduling, requiring strict color matching when assigning prints to printers. Contributed by @cadtoolbox.
- **Israeli New Shekel Currency** — Added ILS to the list of supported currencies for filament cost tracking.
- **AMS Info Card & Custom Labels** ([#570](https://github.com/maziggy/bambuddy/pull/570)) — Hovering an AMS label shows serial number, firmware version, and an editable friendly name. Slot numbers now display inside each filament color circle. Contributed by @cadtoolbox.
- **In-App Bug Reporting** — A floating bug report button lets users submit reports directly from the UI, including description, optional screenshot, and automatically collected diagnostic data with full sensitive-data redaction.
- **SpoolBuddy NFC Tag Writing (OpenTag3D)** — Write NFC tags for third-party filament spools using the OpenTag3D format on NTAG213/215/216 stickers. Supports writing for existing spools, creating new spools, or replacing damaged tags.
- **SpoolBuddy On-Screen Keyboard** — Added a virtual QWERTY keyboard for the SpoolBuddy kiosk UI since the Raspberry Pi has no physical keyboard. Auto-shows when text inputs are focused.
- **SpoolBuddy Inline Spool Cards** — Placing an NFC-tagged spool on the reader now shows spool info directly in the dashboard panel instead of a modal overlay, with action buttons for AMS assignment, weight sync, and more.
- **SpoolBuddy AMS Page: External Slots & Slot Configuration** — The SpoolBuddy AMS page now displays external spool slots and AMS-HT units. Clicking any slot opens the configuration modal for filament type and color.
- **SpoolBuddy Dashboard Redesign** — Redesigned with a two-column layout showing device connection status and printer badges on the left, spool card on the right. Removed the separate inventory page in favor of main Bambuddy UI.
- **SpoolBuddy Kiosk Auth Bypass via API Key** — The `/auth/me` endpoint now accepts API keys, and the install script includes the API key in the kiosk URL for automatic authentication on boot.
- **Daily Beta Builds** — Added a release script for daily multi-arch Docker beta builds pushed to GHCR and Docker Hub. Beta images are never tagged as `latest`.
- **Inventory Scale Weight Check Column** — Added a "Weight Check" column (hidden by default) to the inventory table that compares each spool's last scale measurement against its calculated gross weight.
**## Fixed**
- **Library Upload Doesn't Show New File Until Page Reload** ([#704](https://github.com/maziggy/bambuddy/issues/704)) — The upload endpoint used `db.flush()` instead of `db.commit()`, so the file list was stale until page reload. Fixed the same race condition in folder create/update and file update endpoints.
- **Printer File Manager Doesn't Auto-Refresh** ([#704](https://github.com/maziggy/bambuddy/issues/704)) — The printer SD card browser only fetched files once when opened. Now auto-refreshes every 30 seconds.
- **Database Connection Pool Exhaustion Under Load** ([#704](https://github.com/maziggy/bambuddy/issues/704)) — Background tasks held database sessions during slow network I/O, exhausting the pool. Doubled the pool to 30 connections.
- **Block Mode Skips Humidity Auto-Stop** ([#292](https://github.com/maziggy/bambuddy/issues/292)) — When "Wait for drying to complete" was enabled, drying sessions that reached humidity target continued indefinitely. Block mode now only prevents starting new drying, not stopping completed sessions.
- **AMS Fill Level Shows 0% for Non-Viewer Users** ([#676](https://github.com/maziggy/bambuddy/issues/676)) — Stale inventory fill of 0% permanently shadowed the correct real-time AMS remain value due to nullish coalescing not falling through on `0`. Now bypasses inventory when it says 0% but AMS hardware reports positive remain.
- **Virtual Printer Proxy Mode Always Shows X1C Model** — Now auto-inherits the model from the target printer when creating or updating a proxy virtual printer.
- **Cloud Profiles Shared Across All Users** ([#665](https://github.com/maziggy/bambuddy/issues/665)) — Bambu Cloud credentials were stored globally instead of per-user. Each user now logs into their own Cloud account independently. Also fixed cloud endpoints requiring wrong permissions.
- **Local Profiles Not Shown in AMS Slot Configuration** — The `compatible_printers` filter parsed JSON as semicolon-delimited, silently skipping all local presets. Removed the filter entirely.
- **Interface Aliases Not Shown in Virtual Printer Interface Select** — Interface aliases (e.g. `eth0:1`) were invisible because the Docker image lacked `iproute2`. Added it to the image.
- **P2S Camera Stream Disconnects After a Few Seconds** ([#661](https://github.com/maziggy/bambuddy/issues/661)) — GnuTLS rejected TLS behaviors some printer firmwares rely on. Added a local TLS termination proxy using OpenSSL. Also reduced reconnect delay and fixed choppy external camera streams.
- **iOS/iPadOS Cannot Reposition Floating Camera** ([#687](https://github.com/maziggy/bambuddy/issues/687)) — The floating camera viewer only handled mouse events. Added touch event support for drag and resize.
- **PA-CF / PA12-CF / PAHT-CF Not Treated as Compatible** ([#688](https://github.com/maziggy/bambuddy/issues/688)) — Added a filament type equivalence system so PA variants are treated as compatible in scheduler assignment, AMS slot matching, and filament override dropdowns.
- **Force Color Match Toggle Click Target Too Large** ([#688](https://github.com/maziggy/bambuddy/issues/688)) — The click target now covers only the checkbox, icon, and label text instead of the entire row.
- **HA Switch Badge Always Sends Turn On Instead of Toggle** — Now sends `toggle` for non-script entities so the badge click actually toggles the switch state.
- **Multiple Plugs Per Printer Crashes Auto-On/Off** — `scalar_one_or_none()` raised `MultipleResultsFound` when multiple smart plugs were assigned. Now fetches all plugs and returns the main power plug.
- **Multiple HA Switches Per Printer UNIQUE Constraint** — The migration to remove the UNIQUE constraint on `smart_plugs.printer_id` now uses regex matching and also checks standalone UNIQUE indexes.
- **HMS Notifications for Unknown/Phantom Error Codes** — Flipped notification logic from "notify all, suppress specific" to "only notify for errors with known descriptions", matching the frontend behavior.
- **Ethernet Badge Shown on WiFi Printers / MQTT Disconnecting** ([#585](https://github.com/maziggy/bambuddy/issues/585)) — Three bugs: wrong detection method (bit 18 vs wifi_signal heuristic), bad import path crashing MQTT thread, and WiFi-only models not excluded.
- **Inventory Usage Tracker Missing External Spool Mapping** ([#677](https://github.com/maziggy/bambuddy/issues/677)) — Fallback `slot_id - 1` could never reach external spool IDs. Added position-based resolution using sorted available tray IDs. Contributed by @shrunbr.
- **Spool Assignment Applies Wrong Filament Profile** ([#681](https://github.com/maziggy/bambuddy/issues/681)) — Cloud API returns only the base `filament_id` for versioned setting IDs, ignoring variants. Added a cross-check that corrects the filament ID via reverse lookup.
- **Debug Logging Endpoint 500 Error** — Duration calculation mixed timezone-aware and naive datetimes. Now strips timezone info when reading stored timestamps.
- **Bed Cooled Notification Never Fires** ([#497](https://github.com/maziggy/bambuddy/issues/497)) — Partial MQTT updates didn't include `bed_temper`, so cached temperature never dropped. The monitor now sends periodic `pushall` commands to force fresh data.
- **Notification Provider Missing Event Toggles on Create** ([#497](https://github.com/maziggy/bambuddy/issues/497)) — The create endpoint omitted 8 event toggles (`on_bed_cooled` and 7 queue events), so they always defaulted to false regardless of user selection.
- **Clear Plate Prompt Shown for Staged Queue Items** — The prompt now only appears when there are auto-dispatchable items that the scheduler will actually start.
- **Ethernet Badge Shown on WiFi-Only Printers** ([#585](https://github.com/maziggy/bambuddy/issues/585)) — WiFi-only models (A1, P1P, etc.) are now excluded via model-based gating.
- **GitHub Backup Required Cloud Login** ([#655](https://github.com/maziggy/bambuddy/issues/655)) — Removed the cloud auth gate so GitHub backup can be configured and used without Bambu Cloud login.
- **GitHub Backup Log Timestamps Off by 1 Hour** — Timestamps were displayed in UTC instead of the user's local timezone. Now uses `parseUTCDate` for correct conversion.
- **H2D AMS Units Shown on Wrong Nozzle** ([#659](https://github.com/maziggy/bambuddy/issues/659)) — Three bugs in AMS `info` field parsing: decimal instead of hex, wrong bit extraction, and partial updates overwriting full map. All fixed.
- **SD Card Error After FTP Upload** ([#645](https://github.com/maziggy/bambuddy/issues/645)) — The FTP upload skipped the server's 226 "Transfer complete" response, sending the print command before the file was flushed to disk. Now waits for confirmation.
- **P2S Shows Carbon Rod Maintenance Tasks** ([#640](https://github.com/maziggy/bambuddy/issues/640)) — The P2S uses steel shafts, not carbon rods. Added a new `steel_rod` category with appropriate maintenance tasks.
- **Dispatch Toast Stuck After Second Print** — The dedup guard was never reset between batches, leaving the progress toast stuck. Now resets on dismiss and new batch start.
- **Archive Card Buttons Overlapping at Narrow Widths** ([#641](https://github.com/maziggy/bambuddy/issues/641)) — Buttons now clip cleanly with ellipsis instead of overlapping at narrow viewport widths.
- **Debug Logging Banner Timer Shows Negative Time** — Mixed local/UTC timestamps caused negative duration display. Now stores and compares all timestamps in UTC.
- **Non-Bambu Lab Spools Can't Link/Unlink to Spoolman** ([#653](https://github.com/maziggy/bambuddy/pull/653)) — Generates a fallback tag from printer/AMS/tray IDs for spools without RFID identifiers. Also added an "Unlink" button. Contributed by @shrunbr.
- **Spoolman Location Not Updated on Link/Unlink** ([#669](https://github.com/maziggy/bambuddy/pull/669)) — Linking now sets the Spoolman location to printer name, AMS name, and slot number; unlinking clears it. Contributed by @shrunbr.
- **Print Dispatch Toast Disappears Instantly on Fast Uploads** ([#615](https://github.com/maziggy/bambuddy/issues/615)) — The dispatch toast now stays visible for 3 seconds after completion showing a success message before auto-dismissing.
- **Print Modal Shows Busy Printers as Selectable** ([#622](https://github.com/maziggy/bambuddy/issues/622)) — The printer selector now shows live state badges and grays out busy printers in reprint mode. Queue mode still allows selecting busy printers.
- **PWA Install Not Available in Chrome** ([#629](https://github.com/maziggy/bambuddy/issues/629)) — Fixed incorrect icon dimensions, split the `"any maskable"` purpose, and added required screenshots to the manifest.
- **Project Statistics Count Archived Files as Printed** ([#630](https://github.com/maziggy/bambuddy/issues/630)) — Only files with `status="completed"` (actually printed) now count toward completion stats, excluding archived files.
- **Python 3.10 Compatibility** — Added a compatibility shim for `enum.StrEnum` which was added in Python 3.11.
- **Bug Report Bubble Overlapping Toasts** — Moved toast notifications and upload progress above the bug report bubble.
- **Virtual Printer: Bind-TLS Proxy Handshake Failure on OpenSSL 3.x** — Added RSA key exchange ciphers to the client SSL context for compatibility with Bambu printers.
- **Windows: Server Shuts Down After 60 Seconds** ([#605](https://github.com/maziggy/bambuddy/issues/605)) — ffmpeg cleanup broadcast `CTRL_C_EVENT` to the entire process group. Now spawns ffmpeg in its own process group.
- **Multi-Printer Filament Mapping Shows Wrong Nozzle Filaments on Dual-Nozzle Printers** ([#624](https://github.com/maziggy/bambuddy/issues/624)) — The multi-printer filament mapping path was missing the `nozzle_id` filter that the single-printer path already had.
- **Filament Mapping Dropdowns Missing Subtypes** ([#624](https://github.com/maziggy/bambuddy/issues/624)) — Now shows `tray_sub_brands` (e.g. "PLA Basic", "PLA Matte") in all filament dropdowns, with separate dedup entries for different subtypes.
- **Archive Card Shows "Source" Badge for Sliced .3mf Files** — Now checks `total_layers` and `print_time_seconds` metadata to determine if a `.3mf` file is sliced. Also passes the original filename when creating archives.
- **AMS Slot Shows Wrong Material for "Support for" Profiles** — The name parser returned the first material match ("PLA") instead of the one after "Support for" ("PETG"). Both frontend and backend parsers now detect the naming pattern.
- **Firmware Check Shows Wrong Version for H2D Pro** ([#584](https://github.com/maziggy/bambuddy/issues/584)) — Added all known SSDP model codes to the firmware check mapping so raw device codes resolve to the correct firmware track.
- **Spurious Error Notifications During Normal Printing (0300_0002)** — HMS error codes with low 16 bits below `0x4000` are status indicators, not faults. Now skips them in both parsers.
- **Spool Auto-Assign Fails With Greenlet Error** ([#612](https://github.com/maziggy/bambuddy/issues/612)) — SQLAlchemy lazy-loaded `spool.assignments` outside the async greenlet. Now initializes the collection on new spools and eagerly loads it for existing ones.
- **SpoolBuddy Link Tag Missing tag_type** — Now uses the dedicated `linkTagToSpool` endpoint with proper `tag_type` and `data_origin` values.
- **SpoolBuddy AMS Page Missing Fill Levels for Non-BL Spools** — Now fetches inventory assignments and computes fill levels from weight data, falling back to AMS remain when no assignment exists.
- **SpoolBuddy AMS Page Ext-R Slot Falsely Shown as Active When Idle** — Guards against `tray_now=255` (idle sentinel) before ext slot active check.
- **Printer Card Loses Info When Print Is Paused** ([#562](https://github.com/maziggy/bambuddy/issues/562)) — Print progress info now shows for both `RUNNING` and `PAUSE` states. Status label correctly reads "Paused".
- **SpoolBuddy "Assign to AMS" Slot Shows Empty Fields in Slicer** — Fixed version suffix handling in cloud API calls, correct `filament_id` resolution, and `SlotPresetMapping` saving.
- **Virtual Printer Bind Server Fails With TLS-Enabled Slicers** ([#559](https://github.com/maziggy/bambuddy/issues/559)) — Port 3002 now uses TLS for slicer compatibility, while port 3000 remains plain TCP.
- **Queue Returns 500 When Cancelled Print Exists** ([#558](https://github.com/maziggy/bambuddy/issues/558)) — Normalizes `"aborted"` status to `"cancelled"` before storing. A startup fixup converts existing bad rows.
- **Tests Send Real Maintenance Notifications** — Tests now cancel spawned background tasks before mock context exits.
- **Virtual Printer Config Changes Ignored Until Toggle Off/On** — `sync_from_db()` now compares critical fields and restarts VPs when config changes are detected.
- **Sidebar Navigation Ignores User Permissions** — Each nav item is now hidden when the user lacks the corresponding read permission. Also added missing `inventory:*` permissions to the frontend type definition.
- **Camera Button Clickable Without Permission & ffmpeg Process Leak** ([#550](https://github.com/maziggy/bambuddy/issues/550)) — Camera button now requires `camera:view` permission. Added proper process cleanup with background disconnect monitoring and periodic orphan scanning.
- **Windows Install Fails With "Syntax of the Command Is Incorrect"** ([#544](https://github.com/maziggy/bambuddy/issues/544)) — Removed redundant hash verification block that used unparseable multi-line `for /f` syntax.
- **Queue Badge Shows on Incompatible Printers** ([#486](https://github.com/maziggy/bambuddy/issues/486)) — The badge count now applies the same filament compatibility filter as the queue widget.
- **SpoolBuddy Daemon Can't Find Hardware Drivers** — Added `scripts/` to `sys.path` at daemon startup and moved imports inside try/except blocks. Demoted hardware-not-available logs to INFO.
- **SpoolBuddy Scale Tare & Calibration Not Applied** — Fixed five bugs in the tare/calibration chain including missing API endpoints, stale values overwriting new ones, and self-referential factor computation.
- **A1 Mini Shows "Unknown" Status After MQTT Payload Decode Failure** ([#549](https://github.com/maziggy/bambuddy/issues/549)) — Non-UTF-8 MQTT payloads now fall back to `decode(errors="replace")` instead of being silently dropped.
- **H2C Dual Nozzle Variant (O1C2) Not Recognized** ([#489](https://github.com/maziggy/bambuddy/issues/489)) — Added `O1C2` to all model ID maps across backend and frontend, fixing camera protocol, display names, and more.
- **Support Package Leaks Full Subnet IPs and Misdetects Docker Network Mode** — Masks subnet octets, correctly detects Docker network mode, and parses `fun` field from top-level MQTT payload.
- **SpoolBuddy Scale Calibration Lost After Reboot** — Device ID generation now sorts network interfaces alphabetically for deterministic selection across reboots.
- **SpoolBuddy NFC Reader Fails to Detect Tags** — Fixed PN5180 polling state corruption with full hardware reset before each idle poll, and RF cycle before each active poll. Added auto-recovery after consecutive errors.
**## Improved**
- **Shorter Inventory Location Labels** — Location column now shows compact labels like "H2D-1 B3" instead of "H2D-1 AMS-B Slot 3".
- **Higher FTP Timeout Options for Large Files** ([#660](https://github.com/maziggy/bambuddy/issues/660)) — Added 180s and 300s FTP timeout options. The previous 120s max was insufficient for large 3MF files during active printing.
- **Separate Permission for AMS Spool Assignments** ([#635](https://github.com/maziggy/bambuddy/issues/635)) — New `inventory:view_assignments` permission lets users see AMS spool assignments without accessing the full Inventory page.
- **Prometheus Build Info Metric** ([#633](https://github.com/maziggy/bambuddy/pull/633)) — Added `bambuddy_build_info` gauge metric exposing version, Python version, platform, and architecture. Contributed by @sw1nn.
- **i18n: Settings, Smart Plugs, Notifications, Backup/Restore** — Replaced all hardcoded English strings with translation keys across Settings, Smart Plug, Notification, and Backup/Restore components. Added ~600 new keys to all 7 locales.
- **Install Script: Branch Selection** — The native install script now supports a `--branch` option and interactive branch prompt for beta testers.
- **Print Queue Scheduler Diagnostics** ([#616](https://github.com/maziggy/bambuddy/issues/616)) — Added diagnostic logging showing skip summaries and per-printer state details to help diagnose why queued prints aren't starting.
- **SpoolBuddy Settings Page Redesign** — Redesigned with a tabbed layout (Device, Display, Scale, Updates) including brightness slider, screen blank timeout, step-indicator calibration wizard, and update checker.
- **SpoolBuddy Language & Time Format Support** — The kiosk now respects Bambuddy's configured UI language and time format, with full translations for all 6 languages.
- **SpoolBuddy Kiosk Stability** — Disabled swipe-to-navigate gesture and added `video` group for DSI backlight access.
- **SpoolBuddy Touch-Friendly UI** — Enlarged all interactive elements across the kiosk UI for comfortable finger use on the 1024×600 touchscreen, meeting 44px minimum tap targets.
- **Ethernet Connection Indicator** ([#585](https://github.com/maziggy/bambuddy/issues/585)) — Printers on ethernet now show a green "Ethernet" badge with cable icon instead of WiFi signal strength.
- **SpoolBuddy AMS Page Single-Slot Card Layout** — AMS-HT and external spool cards now use a responsive grid aligned with regular AMS card columns.
- **SpoolBuddy Scale Value Stabilization** — Suppresses redundant scale reports (only sends on ≥2g change), increased moving average window, and added frontend 3g display threshold.
- **SpoolBuddy TopBar: Online Printer Selection** — Printer selector now only shows online printers and auto-selects the first available. Auto-switches if selected printer goes offline.
- **SpoolBuddy Assign to AMS Redesign** — Full-screen overlay reusing AMS page components with one-click slot assignment via a single API call.
- **Filament ID Conversion Utility** — Extracted filament_id/setting_id conversion into a shared utility. The `assign_spool` endpoint now correctly sends both `tray_info_idx` and `setting_id`.
- **Updates Card Separates Firmware and Software Settings** — Split into labeled "Printer Firmware" and "Bambuddy Software" sections.
- **SpoolBuddy Test Coverage** — Added 21 backend integration tests and 20 frontend component tests covering all SpoolBuddy API endpoints and main components.
- **Cleanup Obsolete Settings** — Startup migration now deletes orphaned settings keys no longer used by the application.
- **Added HUF Currency** ([#579](https://github.com/maziggy/bambuddy/issues/579)) — Added Hungarian Forint (HUF, Ft) to supported currencies.
- **FTP Upload Progress & Speed** — Reduced chunk size from 1MB to 64KB for smoother progress reporting. Removed post-upload wait that caused 30+ second hangs on H2D. Added transfer speed logging.
- **Wider Print & Schedule Modals** — Increased modal width from 512px to 672px to accommodate long filament profile names.
**## Changed**
- **CI: Node.js 20 → 22** — Updated GitHub Actions workflows to Node.js 22 LTS ahead of GitHub's Node 20 deprecation.
- **Daily Builds Falsely Trigger Update Notification** — The version parser misclassified daily build tags as full releases. Now strips the daily suffix before parsing.
- **License changed from MIT to AGPL-3.0** — To prevent unauthorized redistribution of Bambuddy as a closed-source product. Community contributions and usage are unaffected.
**## Security**
- **Stored XSS via Project Notes** — Project notes rendered with `dangerouslySetInnerHTML` without sanitization. Now sanitized with DOMPurify.
- **Stored XSS via 3MF Description (Sanitizer Bypass)** — Hand-rolled HTML sanitizer allowed attribute injection via quote break-out. Replaced with DOMPurify.
- **Unauthenticated Auth Toggle via Setup Endpoint** — The `/api/v1/auth/setup` endpoint could disable authentication without being authenticated. Now returns 403 when auth is already enabled.
- **PyJWT ≥2.12.0** — Bumped minimum version to address CVE-2026-32597.
- **flatted ≥3.4.0** — Updated transitive ESLint dependency to address GHSA-25h7-pfq9-p65f (unbounded recursion DoS).
- **Access Code Redacted from Support Logs** — Printer access codes in RTSP URLs were not redacted in support bundles. Extended sanitizer to cover `rtsps://` URLs and added access codes to exact-match redaction.
**## Merged Pull Requests**
- [#508](https://github.com/maziggy/bambuddy/pull/508) — feat(queue): show spool grams left in filament slot mapping (@cimdDev)
- [#531](https://github.com/maziggy/bambuddy/pull/531) — Add customizable low stock threshold, add low stock filter (@Keybored02)
- [#561](https://github.com/maziggy/bambuddy/pull/561) — Statistics page: timeframe filtering, new widgets & fix success rate (@aneopsy)
- [#569](https://github.com/maziggy/bambuddy/pull/569) — Printer Page: print button & drop zone on printer card (@aneopsy)
- [#570](https://github.com/maziggy/bambuddy/pull/570) — Feature AMS Info Card (@cadtoolbox)
- [#573](https://github.com/maziggy/bambuddy/pull/573) — Add energy cost to archive card (@Keybored02)
- [#578](https://github.com/maziggy/bambuddy/pull/578) — Fix AMS slot modal infinite scroll loop on Windows (@aneopsy)
- [#580](https://github.com/maziggy/bambuddy/pull/580) — Fix AMS slot modal infinite scroll loop on Windows (@aneopsy)
- [#581](https://github.com/maziggy/bambuddy/pull/581) — Refactor frontend utility functions to reduce duplication (@aneopsy)
- [#625](https://github.com/maziggy/bambuddy/pull/625) — Feature: Force Color Match (@cadtoolbox)
- [#627](https://github.com/maziggy/bambuddy/pull/627) — Sync i18n locales: add 655 translated keys, remove 127 stale keys (@aneopsy)
- [#633](https://github.com/maziggy/bambuddy/pull/633) — feat(metrics): add bambuddy_build_info metric (@sw1nn)
- [#653](https://github.com/maziggy/bambuddy/pull/653) — Non-Bambu Lab Spools: fully link/unlink to Spoolman (@shrunbr)
- [#669](https://github.com/maziggy/bambuddy/pull/669) — Update Spoolman location when linking/unlinking (@shrunbr)
- [#686](https://github.com/maziggy/bambuddy/pull/686) — Properly track filament usage data in Spoolman (@shrunbr)
- [#700](https://github.com/maziggy/bambuddy/pull/700) — Allow SMTP to be configured before auth is enabled (@shrunbr)
- [#710](https://github.com/maziggy/bambuddy/pull/710) — SMTP testing uses saved database settings (@shrunbr)
- [#711](https://github.com/maziggy/bambuddy/pull/711) — Ensures AMS-HT devices are properly identified in Spoolman (@shrunbr)
**## Community Contributors**
- **@shrunbr** — Spoolman integration improvements: non-Bambu spool linking/unlinking, location updates, filament usage tracking, AMS-HT identification, SMTP configuration and testing fixes.
- **@aneopsy** — Statistics page enhancements with timeframe filtering and new widgets, printer card print button and drop zone, AMS slot modal scroll fix, frontend utility refactoring, i18n locale sync, and print dispatch toast fix.
- **@cadtoolbox** — AMS Info Card with custom labels and slot numbers, Force Color Match for queue scheduling, and multiple bug reports for AMS/permission/ethernet issues.
- **@Keybored02** — Customizable low stock threshold with filter, and energy cost display on archive cards.
- **@cimdDev** — Spool grams remaining display in queue filament slot mapping.
- **@sw1nn** — Prometheus `bambuddy_build_info` metric for version tracking and alerting.
Previously the "Assign Spool" button only appeared on configured slots,
forcing users to manually configure first — redundant since assignment
auto-configures the slot. Now shows on empty slots too. Also fixed the
AMS hover card showing generic material type instead of the spool's
slicer preset name after assignment.
- Sanitize project notes with DOMPurify before rendering via
dangerouslySetInnerHTML (ProjectDetailPage.tsx)
- Replace hand-rolled HTML sanitizer with DOMPurify in ProjectPageModal
to prevent attribute injection via crafted 3MF href values
- Block /api/v1/auth/setup when auth is already enabled to prevent
unauthenticated clients from disabling authentication remotely
The Debian ffmpeg package uses GnuTLS, whose hardened defaults reject
TLS renegotiation and legacy ciphers that some Bambu printer firmwares
(notably P2S) rely on — causing RTSP sessions to drop after a few
seconds.
Add a local TLS termination proxy (Python ssl/OpenSSL) that handles
the TLS connection to the printer and exposes a plain RTSP port to
ffmpeg. The proxy rewrites RTSP request-line URLs (rtsp://proxy →
rtsps://printer) while preserving Authorization headers so Digest
auth hashes remain valid.
Also:
- Reduce RTSP reconnect delay from 1.0s to 0.2s
- Add ffmpeg fast-start flags (-probesize 32, -analyzeduration 0,
-fflags nobuffer, -flags low_delay)
- Fix external camera double rate-limiting causing choppy streams
- Apply TLS proxy to external camera rtsps:// URLs and snapshot capture
- Update orphan ffmpeg cleanup to match rtsp:// (proxied) URLs
- Add unit tests for RTSP URL rewriting and proxy lifecycle
The Bambu Cloud API returns the base filament_id for versioned
setting IDs (e.g. GFSL99 → GFL99 for all "Generic PLA" variants),
so assigning a spool with a specific variant like "Generic PLA Silk"
(GFSL99_01) would configure the AMS slot with the base "Generic PLA"
profile (GFL99) instead of the correct one (GFL96).
Added a post-resolution cross-check: if the resolved filament_id maps
to a different name than the spool's stored preset name, reverse-lookup
the correct filament_id from the built-in filament table.
Printer File Manager Doesn't Auto-Refresh ([#704](https://github.com/maziggy/bambuddy/issues/704)) — The printer file manager (SD card browser) only fetched the file list once when opened. Files uploaded from BambuStudio/OrcaSlicer while the modal was open wouldn't appear until the user clicked the refresh button or reopened the modal. Now auto-refreshes every 30 seconds while open. Reported by @shadowjig.
Database Connection Pool Exhaustion Under Load ([#704](https://github.com/maziggy/bambuddy/issues/704)) — Background tasks (print scheduler FTP uploads, camera captures, notification sends, timelapse stitching) held database sessions open during slow network I/O, consuming connection pool slots for seconds at a time. With the default pool of 15 connections (size 5 + overflow 10), concurrent operations during print start/complete events could exhaust the pool, causing `QueuePool limit reached` errors and `greenlet_spawn` failures in RFID spool auto-assignment. Doubled the pool to 30 connections (size 10 + overflow 20). Reported by @shadowjig.
RTSP stream URLs (rtsps://bblp:<code>@<ip>:322/...) were not covered
by the credential sanitizer, leaking access codes in support bundles
and bug report logs. Extended the URL regex to match rtsps:// and added
access codes to the sensitive string collection for exact-match
redaction in both export paths.
Ambient drying: automatically dry filament on idle printers when
humidity exceeds threshold, regardless of queue state. Separate toggle
from queue auto-drying — both can run simultaneously. Uses the same
presets, humidity threshold, and power constraint detection.
Fix: block mode (wait for drying) previously skipped the humidity
auto-stop check for already-drying printers, causing drying to
continue indefinitely. Now only prevents starting new drying.
Queue auto-drying: scheduler automatically starts drying on idle printers
with scheduled queue prints when AMS humidity exceeds the configured
threshold. Uses conservative parameters (lowest temp, longest duration)
for mixed filaments. Drying stops when humidity drops below threshold
(30-minute minimum prevents oscillation), when scheduled items are
removed, or when the feature is disabled. Optional "block queue" mode
delays the next print until drying completes.
Configurable presets: temperature and duration per filament type,
editable in Settings → Print Queue, used by both manual drying popover
and queue auto-drying. Separate presets for AMS 2 Pro (n3f) and AMS-HT
(n3s) reflecting different heating capabilities.
PSU detection: drying button disabled with tooltip when dry_sf_reason
indicates insufficient power. Parses drying status bits and dry_sf_reason
from AMS info hex string via MQTT.
Backend: print_scheduler.py (+316 lines), bambu_mqtt.py, printer_manager,
schemas, settings route. Frontend: PrintersPage drying presets prop,
SettingsPage drying config UI, i18n (7 locales). Tests: 27 new tests in
test_scheduler_auto_drying.py covering conservative params, presets,
state sync, stop logic, minimum drying time, and auto-stop regressions.