github maziggy/bambuddy v0.2.4b1-daily.20260421
Daily Beta Build v0.2.4b1-daily.20260421

pre-release8 hours ago

Note

This is a daily beta build (2026-04-21). It contains the latest fixes and improvements but may have undiscovered issues.

Docker users: Update by pulling the new image:

docker pull ghcr.io/maziggy/bambuddy:daily

or

docker pull maziggy/bambuddy:daily


**Tip:** Use [Watchtower](https://containrrr.dev/watchtower/) to automatically update when new daily builds are pushed.

Improved

  • Printer Card Shows Plate Name on Multi-Plate Prints (#881) — When two printers were running different plates of the same multi-plate 3MF, the Printers page cards displayed the same file name on both and gave no visual way to tell them apart. The Queue view already showed the plate name by querying the archive's plate list; the Printers page didn't have that linkage. The GET /printers/{id}/status endpoint now returns current_archive_id (resolved by matching the MQTT subtask_id against PrintArchive.subtask_id, the same bridge introduced in #972 for restart-resume) and current_plate_id (parsed from the MQTT gcode_file path by a new shared parse_plate_id helper that's also used by the WebSocket push path, so plate transitions within a running print reflect immediately instead of waiting 30 s for the next REST poll). The card fetches plate metadata via the same api.getArchivePlates() call the Queue page uses — shared React Query cache keeps it cheap across polls — and renders the actual plate name (or a "Plate N" fallback) only when the source 3MF is multi-plate, so single-plate prints stay noise-free. Falls back to the previous plate_(\d+).gcode regex when there's no archive linkage (e.g. prints started directly from the printer LCD). Regression tests cover the plate-id extraction across Bambu Studio path shapes and the label-override precedence in formatPrintName. Thanks to stringham for the follow-up and screenshot.

Fixed

  • AMS Slot Configure: Custom Cloud Preset Resolves to "Generic" in Slicer & Printer LCD (#1053 follow-up) — After configuring any AMS slot (HT or regular) with a user custom Bambu Cloud preset built on top of a Bambu base profile (e.g. "Sting3D ABS" inheriting from "Generic ABS BBL H2D"), OrcaSlicer's Sync Filaments continued to resolve the slot to "Generic ABS" and the custom preset never appeared on the printer's own LCD — independent of the earlier UI fix (commit 87a5aa36) which only corrected Bambuddy's own modal. Root cause: when Bambu Cloud's GET /cloud/settings/{setting_id} returns a user preset with filament_id: null and base_id: "GFSB99_07" (cloud doesn't mint a distinct filament_id for presets that only override fields of a generic base), ConfigureAmsSlotModal.tsx:382-384 fell back to convertToTrayInfoIdx(base_id) which strips the version suffix and the S prefix → "GFB99" — Generic ABS's filament_id. The printer accepted and reported back GFB99, so both the LCD and OrcaSlicer correctly resolved the slot to Generic ABS. The fallback was never right: the preceding default already set tray_info_idx = convertToTrayInfoIdx(selectedPresetId) which for any PFUS*/PFSP* setting_id returns the base setting_id itself (via the helper's startsWith('PFUS') branch added earlier), and the printer + both slicers round-trip that format unchanged — confirmed by existing backend integration tests (test_configure_pfus_sent_directly, test_pfus_slicer_filament_used_directly), by the print scheduler's slot-matching which already expects P* short-form IDs in the printer's reported tray_info_idx (print_scheduler.py:910), and by the inventory Assign Spool flow which has been sending PFUS* preset IDs to the printer for months. The buggy fallback overwrote the correct default with a generic mapping. Fixed by removing the base_id branch: when cloud detail carries a distinct filament_id we still prefer it, otherwise we keep the setting_id-derived default. BambuStudio Sync now resolves the custom preset cleanly; OrcaSlicer (whose user presets don't carry a filament_id field at all, only inherits) will continue to fall back to the inherited generic — that's an OrcaSlicer preset-format limitation, not something Bambuddy can fix on its side, and the behaviour is strictly not worse than before. Regression tests in ConfigureAmsSlotModal.test.tsx pin four paths: (1) cloud detail with filament_id: nulltray_info_idx is the PFUS* setting_id, (2) cloud detail with a concrete filament_id → that filament_id wins over the default, (3) GFS* Bambu presets skip the cloud-detail fetch entirely and still map to the short GF* filament_id, and (4) a 5xx / network error on the cloud-detail fetch degrades gracefully to the PFUS* default instead of aborting the configure flow. An end-to-end backend test (test_configure_pfus_preserves_setting_id_pair) locks in that both tray_info_idx=PFUS… and setting_id=PFUS… survive the HT-slot POST /slots/{ams}/{tray}/configure path untouched. Thanks to mrnoisytiger for the detailed browser-console / network / backend-log diagnostic data that isolated the fallback path, and for sharing the OrcaSlicer preset JSON that showed the missing filament_id field.
  • Single Malformed rgba Bricks the Entire Filaments Inventory Page (#1055) — A user's Filaments page went blank and "Add Spool" became a no-op with no visible error. The backend was returning HTTP 500 from GET /api/v1/inventory/spools with fastapi.exceptions.ResponseValidationError: rgba → 'FFFFFFF' should match pattern '^[0-9A-Fa-f]{8}$' — a single legacy spool row had a 7-char rgba (missing one trailing F) and Pydantic's strict pattern on SpoolResponse refused to serialize the whole list because of it. Root cause spans three layers: (1) SpoolUpdate had no rgba pattern constraint, so PATCH calls could plant malformed values straight into the DB (SpoolCreate did validate, but only on initial create); (2) the ColorSection hex input's onChange ternary val.length <= 6 ? 'FF' : '' silently emitted 7-char strings for 5-char or 7-char typed input (5 chars + FF alpha = 7 chars; 7 chars got no alpha appended at all), which then flowed to the unvalidated PATCH endpoint; (3) SpoolResponse inherited the same pattern as SpoolCreate, so any malformed row already in the DB exploded the entire list endpoint on serialize even though write-side validation was the right place for the check. Fixed on all three layers: SpoolUpdate.rgba now carries the same ^[0-9A-Fa-f]{8}$ pattern as SpoolCreate, so PATCH requests with malformed rgba are rejected with 422 at the boundary. The hex input always emits a fully-formed 8-char RRGGBBAA on every keystroke — 8-char paste passes through, 7-char drops the stray char, shorter input is right-padded with '0' and given FF alpha. SpoolResponse.rgba is now an unconstrained Optional[str]: the pattern belongs on request schemas where Pydantic can reject bad input, not on responses where it turns a single bad row into a total page failure. A legacy malformed row still appears in the UI (the color just renders as whatever browser default applies) but the user can see, edit, and delete it instead of having to hand-edit SQLite. Backend tests cover all three schema contracts (16 cases across SpoolCreate accept/reject, SpoolUpdate accept/reject, SpoolResponse lenient-tolerance on 7-char / null / garbage). Frontend tests cover the hex-input normalization for every input length 0–8 plus non-hex strip-and-pad. Thanks to fdsghy4a for the end-to-end debugging and for locating the exact malformed row in their DB.
  • Printer-Card "Print" Button Leaves Transient Copy in File Manager (#730) — The "Print" button on a printer card (and the equivalent drag-drop-onto-card flow) was silently uploading the chosen file into the Library file manager as a side effect before printing. Root cause is structural: the frontend opened FileUploadModal to persist the file as a LibraryFile, then PrintModal dispatched a library print through POST /library/files/{id}/print, which uses the LibraryFile as the source for both the archive copy and the FTP upload to the printer. When the dispatch finished, both the LibraryFile row and its disk file in data/library/ were left behind, so every one-off Direct-Print accumulated an unwanted File Manager entry that the user had to find and delete manually. The other three print entry points are untouched: Archive "Reprint" never involved the library, and File Manager "Print" / Project Detail "Print" are paths where the user deliberately put the file in the library, so their entries are preserved. POST /library/files/{id}/print now accepts an optional cleanup_library_after_dispatch boolean. When true, _run_print_library_file stages the LibraryFile row for deletion in the same transaction as the archive insert (so a mid-flight FTP or start_print failure rolls back both at once, leaving no orphan), commits together, then unlinks the library disk file and thumbnail from disk after commit succeeds. External library files (is_external = True, pointing at user-managed folders outside Bambuddy's control) are never touched regardless of the flag. The Printers-page Direct-Print flow is the only caller that sends true; every other api.printLibraryFile call site leaves the flag unset so default-False preserves their library entries. Added two unit tests at the enqueue level (default-false + flag-propagates-true), two integration tests at the endpoint level (default-false + forwards-true + cleanup flag never leaks into the MQTT options dict), and two frontend tests on PrintModal guarding that cleanupLibraryAfterDispatch only forwards when explicitly set — so future File Manager / Project Detail entry points can't accidentally inherit the Direct-Print semantics. Thanks to 3823u44238 for flagging the surprising side effect.
  • Direct / File Manager / Library Prints Still Unattributed to User (#730) — The 0.2.3.1 fix (commit f03d0c4c) plumbed the authenticated user from POST /library/files/{id}/print into the background-dispatch job object, but the dispatcher itself never read it back out: _run_print_library_file called ArchiveService.archive_print() without the created_by_id parameter and never called printer_manager.set_current_print_user(). Net effect: direct prints from the printer-card "Print" button, File Manager prints, and Library prints all continued to land archives with created_by_id = NULL (invisible to the per-user stats filter), and the post-print email notification had no user to target. The dispatcher now forwards job.requested_by_user_id to the archive at creation time and registers the current-print user after start_print succeeds — matching the reprint path's behaviour. Reprint-from-Archive attribution is a separate bug (the reprint reuses the source archive row as-is, so a NULL created_by_id stays NULL) and is tracked on #730. Thanks to 3823u44238 for the thorough end-to-end retest.
  • Spoolman Iframe Blocked by CSP on HTTP Instances (#1054) — The Filament tab showed a blank page with a brief Spoolman flash on reload. Browser console reported Content-Security-Policy: The page's settings blocked the loading of a resource (frame-src) at http://<host>:7912/spool because it violates the following directive: "frame-src 'self' https:". Root cause: commit 53a70e37 (#995) tightened the CSP to allow external sidebar iframes but only whitelisted https:, overlooking that self-hosted services on LANs — Spoolman, OctoPrint, etc. — almost always run over plain HTTP. The frame-src directive now allows http: as well (frame-src 'self' http: https:), matching the connect-src 'self' ws: wss: pattern already used for WebSockets. frame-ancestors 'none' still prevents Bambuddy itself from being framed cross-origin. Thanks to saint-hh for reporting.
  • AMS-HT: Custom Filament Preset Reverts to "Generic" in UI After Configure (#1053) — After configuring an AMS-HT slot (HT-A/HT-B) with a custom Bambu Cloud preset (e.g. "Devil Design PLA Basic"), the slot card and Configure modal kept showing "Generic PLA" even though the ams_filament_setting command succeeded and BambuStudio / the printer's LCD both rendered the correct custom preset. Root cause: the GET /api/v1/printers/{id}/slot-presets endpoint keyed its response dict by ams_id * 4 + tray_id, which collapses cleanly to the same integer the frontend uses for regular AMS slots (0 through 15) but produces 128 * 4 + 0 = 512 for HT-A — a key nothing looks up. The frontend's PrintersPage HT render path calls getGlobalTrayId(ams.id, …, false) which returns the ams_id itself (128 for HT-A), and SpoolBuddy's AMS page used a third, unrelated formula ((amsId - 128) * 4 + trayId + 64 = 64). All three agreed for regular AMS so the mismatch only surfaced on HT, where the saved preset name never reached the UI and the render fell through to tray.tray_type → rendered as "Generic PLA". Backend now keys the response via a _slot_preset_key helper that mirrors frontend getGlobalTrayId (HT → ams_id, regular/external → ams_id * 4 + tray_id), and SpoolBuddyAmsPage uses the shared getGlobalTrayId helper instead of its home-grown formula. Regression test covers the key scheme for regular, HT, and external slots. Thanks to mrnoisytiger for the detailed reproduction.
  • ⚠️ Bed-Jog "Home Z" Could Crash the Bed Into the Toolhead (#1052) — Critical safety fix. On H2C (and by extension any Bambu printer where Z-home moves the bed UP toward an endstop — H2D, H2S, and X1 family all share this kinematics) the bed-jog modal's "Home Z" button sent a raw G28 Z over the gcode_line MQTT command. Bare G28 Z skips the toolhead-park step that a full G28 runs first, so the bed raised without stopping at a safe height — in the reporter's case the toolhead happened to be parked on the purge chute and no damage was caused, but hitting the button with a toolhead anywhere else would have driven the bed into it at full Z speed. Root cause was the /api/v1/printers/{id}/home-axes endpoint's per-axis gcode mapping ("z" → "G28 Z", "xy" → "G28 X Y", "all" → "G28"). The endpoint now ignores the axes argument entirely and always sends a bare G28, which Bambu firmware expands into the safe multi-step sequence (park toolhead → home XY → home Z). The MQTT client helper BambuClient.home_axes() has the same change. The bed-jog modal is retitled "Auto Home" and its copy now says "parks the toolhead, then homes X, Y, and Z" so users aren't surprised when X/Y motion happens first. After a successful Auto Home click, the modal no longer re-prompts on the next jog in the same session — the "not homed" warning is gated on a session-scoped acknowledgement flag that was only being set by "Move anyway" and now also fires on successful Auto Home. Regression test covers all three axes arguments producing the same bare G28. Thanks to mikefromdot for catching this with an undamaged retest.
  • AMS: Configure / Assign Spool Hidden on Reset Slots, and Assign Spool Missing Matching-Material Inventory (#1047) — Two separate symptoms from the same report. (1) After resetting an AMS slot from the printer UI, the Bambuddy printer card showed "Empty Slot" with no Configure or Assign Spool actions on hover, while the same slot in SpoolBuddy's AMS page still let the user re-configure it. Root cause: commit c9efa4b8 (#784) added a tray?.state === 10 gate to the EmptySlotHoverCard actions, intended to show the buttons only when a spool was physically present but not loaded (state=10) and hide them on truly empty slots (state=9). In practice, firmware often reports state=9 (or no state field at all) after a user-initiated reset — even when a spool is still physically in the slot — so the actions disappeared exactly when the user needed them. The gate is redundant anyway (EmptySlotHoverCard is only rendered when the slot has no tray_type, so it's definitionally empty from Bambuddy's perspective), and configuring an empty slot is a valid "tell the printer what will be loaded here" operation. The gate is now removed at both the standard-AMS and AMS-HT render paths. (2) After configuring a slot with a Generic profile (e.g. "Devil Design PLA Basic Red"), the Assign Spool modal didn't list the matching inventory spool unless the user enabled the "Show all spools" toggle. Root cause: the filter at AssignSpoolModal.tsx:144 required normalizeValue(spool.slicer_filament_name) === normalizeValue(trayInfo.profile) — manually-added inventory spools typically don't have slicer_filament_name populated, so they failed the exact-profile check even when the material matched. The filter now prefers an exact slicer-profile match when both sides advertise one, and falls back to partial material match in either direction (so e.g. a spool with material="PLA" is selectable for a slot reporting "PLA Basic") when profile info is missing. (3) Once the matching spool was assignable, a "profile mismatch" confirmation dialog still warned on every assignment because Bambu Studio / OrcaSlicer slicer-profile names carry a printer/nozzle/variant qualifier after `` (e.g. "Devil Design PLA Basic Bambu Lab H2D 0.4 nozzle (Custom)") while the tray stores only the bare base name (`"Devil Design PLA Basic"`), and `checkProfileMatch` compared the full strings. Both the filter and the mismatch check now strip the `…` qualifier before comparing, so identical base profiles are treated as a match. Regression test covers a spool with no slicer profile being surfaced for a slot whose profile + material are both set. Thanks to TravisWilder for the report.
  • Skip Objects: Enlarged Preview Image Fails to Load on Auth-Enabled Instances (#1046) — Clicking the mini print-pr

Don't miss a new bambuddy release

NewReleases is sending notifications on new releases.