Reshapes the embedded PrettyGCode viewer (landed in #963) into a focused
archive-preview tool, matching Bambuddy's data model instead of the
OctoPrint-style "connected-printer + library file picker" flow it shipped
with. Reached only from the Archives page 3D-preview button; URL
/gcode-viewer?archive=<id>[&plate=<N>].
Backend:
- /archives/{id}/gcode accepts ?plate=N and resolves the filename by
parsing the suffix as int, so zero-padded names like plate_01.gcode
are found when the plates endpoint reports index 1.
- /archives/{id}/plates gains top-level has_gcode: bool. Source-only
3MFs (PNG/JSON fallback path) surface the flag so the frontend can
skip the picker instead of sending the user into a dead viewer.
- printer_state_to_dict injects name + model into every WS snapshot so
consumers render proper labels on the initial tick without racing a
separate /printers fetch.
- /gcode-viewer (no trailing slash) dropped from the backend so reloads
fall through to the SPA catch-all and keep the layout shell; only
/gcode-viewer/ (trailing slash) and /gcode-viewer/<path> remain for
the iframe + static assets.
Frontend:
- PlatePickerModal shown only for multi-plate archives with sliced
gcode, grid layout with thumbnails matching the Re-print modal.
- Source-only archives show a noGcode toast instead of the empty
viewer.
- ArchivesPage navigate path swapped to /gcode-viewer?archive=<id> with
no trailing slash; GCodeViewerPage iframe forwards
window.location.search so the archive reference survives both the
initial navigate and a full-page reload.
- Viewer iframe's auth path: fetch intercept injects Bearer; a 401
redirects to / so the SPA handles login.
Viewer adapter:
- Stripped the printer selector, WebSocket subscription, library file
picker, tryAutoLoadPrintingFile, BAMBU_BED_SIZES, and updatePrinter-
Selector. The viewer no longer observes live printer state.
- Bed size derived from /archives/{id}/capabilities.build_volume
(extracted from the 3MF's printable_area/printable_height), so H2D,
H-family, and any future printer render on the correct bed without
a hardcoded map.
- loadArchiveById accepts a plate param; fetch intercept rewrites
__bambuddy_archive_<id>[_plate<N>] to /archives/<id>/gcode[?plate=N].
Nav + locale cleanup:
- Sidebar "GCode Viewer" nav entry removed (viewer is archive-scoped
now, not a destination page).
- 32 orphaned gcodeViewer locale keys deleted across all 8 locales.
- platePicker.{title, hint, plateLabel, objectCount, noGcode} keys
added in all 8 locales.
ArchivesPage: the now-unreachable ModelViewerModal render paths + its
showViewer state removed. ModelViewerModal itself stays — File Manager
still uses it for library file previews (plate picker + .3mf 3D model).
pre-commit:
- gcode_viewer/ excluded from trailing-whitespace + end-of-file-fixer
so vendored third-party JS libs don't drift away from upstream.
Incidental sweeps picked up by pre-commit and kept (unrelated but
benign):
- NotificationsPage.tsx: single trailing-whitespace line removed.
- spoolbuddy/scripts/pn5180_diag.py: dead `import gpiod` dropped —
the pn5180 driver module imported at line 27 does its own
`import gpiod` and `gpiod.Chip()` calls, so the diag script's
top-level import was never referenced.
Tests:
- 6 new cases in test_gcode_viewer.py for the backend plate / has_gcode
behaviour (plate=N resolution, zero-padded filenames, missing-plate
404, no-plate fallback, plate=0 rejection, has_gcode true/false).
- 3 new cases in test_printer_manager.py for name/model WS injection.
- PlatePickerModal.test.tsx — 6 frontend cases covering render,
plate-name composition, onSelect payload, backdrop close, and
thumbnail fallback.
PR #939 added the awaiting_plate_clear gate but stored it on
PrinterManager, not on PrinterState. printer_state_to_dict() — which
builds every WebSocket printer_status payload — never emitted the flag,
so the frontend's WS merge preserved the stale false value. The only
path that surfaced true was the 30s HTTP fallback poll, and incoming WS
ticks kept bumping React Query's dataUpdatedAt, pushing the refetch out
further on chatty printers.
Emit awaiting_plate_clear from printer_state_to_dict by reading
printer_manager.is_awaiting_plate_clear(printer_id) directly; returns
False when no id is passed. No frontend change needed — the existing WS
merge carries the flag end-to-end and the button now appears the instant
the printer transitions to FINISH.
Regression tests assert the WS dict always contains the key and surfaces
True when the manager has the flag set for that printer_id.
Affects every printer (A1/H2D/X1C) equally — transport-agnostic path.
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
there was no way to tell them apart. The Queue view already had this
information by cross-referencing the archive's plate list; the card
didn't have the linkage.
Expose `current_archive_id` (resolved by matching the MQTT `subtask_id`
against `PrintArchive.subtask_id` — the bridge introduced in #972 for
restart-resume) and `current_plate_id` (parsed from `gcode_file` by a
new shared `parse_plate_id` helper) on the status endpoint. The helper
is also called from the WebSocket push path so plate transitions
reflect within 100 ms instead of waiting 30 s for the next REST poll;
the archive id itself stays REST-only since it's stable for the life
of a print and shouldn't make the push path touch the DB.
The card fetches plate metadata via the same `api.getArchivePlates()`
call QueuePage uses — shared React Query cache keeps it cheap across
polls — and renders the actual plate name (or a "Plate N" fallback)
only when `is_multi_plate` is true. Single-plate prints stay clean.
Falls back to the previous `plate_N.gcode` regex path when there's no
archive linkage (e.g. prints started directly from the printer LCD).
Tests cover the plate-id extraction across Bambu Studio path shapes
(backend parse_plate_id, printer_state_to_dict wiring) and the label
override precedence in formatPrintName (frontend).
Move H2S from the drying-unsupported blocklist to the firmware-gated
list, requiring minimum firmware 01.02.00.00 (released end of March
2026). Both remote AMS drying and queue auto-drying now work on H2S.
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.
Assigning or unassigning a spool now broadcasts a spool_assignment_changed
event to all connected WebSocket clients. The frontend handles this event
by invalidating the spool-assignments and slotPresets caches, so other
open tabs update automatically without a page reload.
Start, monitor, and stop drying sessions for AMS 2 Pro (n3f) and
AMS-HT (n3s) directly from the Printers page. Flame icon in AMS card
header opens a popover with filament-based temperature/duration presets
from BambuStudio. Live countdown status bar shows time remaining.
Backend: cache module_type from MQTT get_version, expose dry_time and
module_type in both WebSocket and REST paths, add supports_drying()
with firmware gating, add server-side guard on start_drying endpoint.
Frontend: drying button, popover with filament select + temp/duration
sliders, status bar, start/stop mutations. i18n for all 7 locales.
Supported: X1/X1C (fw 01.09+), P1P/P1S (fw 01.08+), H2D (fw 01.02.30+),
H2D Pro, X1E. Not supported: P2S, A1, A1 Mini, H2S, H2C.
Suppress SQL/aiosqlite debug noise (~90% log volume reduction), add
caller-traced PRINT COMMAND logging to start_print(), log scheduler
queue checks, tighten stale archive ilike match to exact match, and
warn on multiple queue items in "printing" status. Includes 18 new
unit tests.
Backend:
- Add dual external spool support for H2D (vt_tray as list: Ext-L/Ext-R)
- Add cloud filament ID map endpoint (/cloud/filament-id-map)
- Fix RFID spool data erased by periodic AMS updates (skip tag matcher
for RFID-tagged trays)
- Fix AMS slot config overwrites RFID spool state
- Fix K-profile selection corrupts existing profiles on X1C/P1S
- Resolve K-profiles filament name via cloud filament ID map
- Update print scheduler and usage tracker for dual external spools
Frontend:
- Add printer model filtering to ConfigureAmsSlotModal (cloud/local/builtin
presets filtered by @BBL model suffix and compatible_printers)
- Add pre-population for configured slots (preset, color, K-profile)
- Add K-Profiles view with accurate filament name resolution
- Internationalize all ConfigureAmsSlotModal strings (en/de/fr/it/ja — 21 keys)
- Add 5 new ConfigureAmsSlotModal tests (model filtering, pre-selection,
color pre-population, i18n)
- Update PrintersPage for dual external spool rendering
Docs:
- Update CHANGELOG, README, website features, and wiki AMS docs
- Remove 28 unused imports across 22 test files
- Prefix 4 unused local variables with _ in app code
(archives, bambu_mqtt, main) and remove 1 dead store
- Consolidate import/import-from in test_plate_detection.py
- Fix unreachable statement in test_archive_service.py
- Simplify redundant comparison in timelapse_processor.py
Resolves ~50 CodeQL py/unused-import, py/unused-local-variable,
py/import-and-import-from, py/unreachable-statement, and
py/redundant-comparison findings.
Track and display who performs key actions in Bambuddy:
- Archives: who uploaded each archive file
- Library: who uploaded each file in File Manager
- Queue: who added each print job to the queue
- Printers: who started the current print (reprint tracking)
Backend changes:
- Add created_by_id column to print_archives, library_files, print_queue tables
- Add database migrations for new columns (auto-run on startup)
- Update archive, library, and queue routes to capture current user
- Add current-print-user endpoint for printer reprint tracking
- Track reprint user in PrinterManager in-memory state
- Fix file uploads not sending auth headers (FormData requires explicit headers)
Frontend changes:
- Display username on archive cards, library files, queue items
- Show "Started by" on printer cards during active prints
- Add auth headers to all 12 FormData upload functions
- Update TypeScript types for user tracking fields
Tests:
- Add unit tests for PrinterManager user tracking methods (7 tests)
- Add integration tests for current-print-user endpoint (3 tests)
- Add integration tests for library file user tracking (3 tests)
Works when authentication is enabled; gracefully hidden when disabled.
Closes#206
Some A1/A1 Mini firmware versions incorrectly report stg_cur=0 (which
maps to "Printing") even when the printer is idle. This is a known
firmware bug also observed in the Home Assistant Bambu Lab integration.
- Add A1_MODELS constant listing affected model variants
- Add has_stg_cur_idle_bug() helper to identify affected models
- Update get_derived_status_name() to check gcode_state before stg_cur
for A1 models: if IDLE + stg_cur=0, return None to show "Idle"
- Fix only applies when all conditions match (A1 model + IDLE + stg_cur=0)
- Non-A1 printers and A1 printers without the bug are unaffected
Closes#168
Root Cause: P2S (and other newer models) may report their model as internal codes (e.g., "N7" for P2S) rather than display names in MQTT/SSDP responses. The camera code
only checked for display names like "P2S", causing it to incorrectly use the chamber image protocol (for A1/P1) instead of RTSP.
Internal Code Mapping:
BL-P001 → X1/X1C (RTSP)
C13 → X1E (RTSP)
O1D → H2D (RTSP)
O1C → H2C (RTSP)
O1S → H2S (RTSP)
O1E → H2D Pro (RTSP)
N7 → P2S (RTSP)
C11 → P1P (Chamber)
C12 → P1S (Chamber)
N2S → A1 (Chamber)
N1 → A1 Mini (Chamber)
Closes#127
Manually configure AMS slots for third-party or generic filaments:
1. Hover over an AMS slot on the printer card
2. Click the menu button (:material-dots-vertical:) that appears
3. Select **Configure Slot**
4. Choose a filament preset from your Bambu Studio cloud presets
5. Select a matching K profile (pressure advance calibration)
6. Optionally set a custom color using the color picker
7. Click **Configure Slot** to apply
**Color Picker Features:**
- Enter custom hex codes or color names (e.g., "brown", "FF8800")
- Live preview of selected color
- Expandable color picker in Configure AMS Slot modal:
- 8 basic colors shown by default
- 24 additional colors available via expand button
- Tests for ConfigureAmsSlotModal component
- Tests for AMS change callback
- Updated README with AMS slot configuration feature
- Wiki documentation for Configure AMS Slot feature
- Multi plate issue where plate names showed incorrect. #93
- Items from Queue end up as "source" files in archive. #107
- Added env variable support to change network port. #108
Docs -> https://wiki.bambuddy.cool/getting-started/docker/?h=port#custom-port
Fix Applied:
1. Added supports_chamber_temp() helper function that returns True only for X1/X1C/X1E, P2S, and H2 series
2. Modified printer_state_to_dict() to filter out chamber, chamber_target, chamber_heating from temperatures for unsupported models
3. Added model caching in PrinterManager so we can look up the model without a database query
4. Updated all callers (main.py, websocket.py) to pass the model
The chamber temperature widget will now simply not appear for P1S/P1P/A1/A1Mini printers since the temperatures.chamber field won't be sent to the frontend.
1. "Unknown Status (255)" bug
File: backend/app/services/printer_manager.py:291-293
Problem: A1/P1 printers send stg_cur=255 when idle (unlike X1 which uses -1). The code treated 255 as a valid stage number and returned "Unknown stage (255)".
Fix: Changed the condition from if state.stg_cur >= 0: to if 0 <= state.stg_cur < 255: so that 255 is now treated as "no calibration stage" (same as -1).
2. AMS humidity/temp displaced on mobile
File: frontend/src/pages/PrintersPage.tsx:1164-1212
Problem: The AMS row used a rigid horizontal flex layout that didn't adapt to narrow mobile screens, causing humidity/temperature indicators to be displaced.
Fix:
- Added flex-wrap to allow wrapping on very narrow screens
- Added ml-auto to humidity/temp section to keep it right-aligned
- Added max-w-[60px] sm:max-w-none to truncate long filament names on mobile
- Changed gap from fixed gap-3 to responsive gap-2 sm:gap-3
Tests Added
4 new unit tests for get_derived_status_name:
- test_stg_cur_255_returns_none - Verifies A1/P1 idle state
- test_stg_cur_negative_one_returns_none_when_idle - Verifies X1 idle state
- test_valid_stage_returns_name - Verifies valid stages work
- test_stg_cur_zero_returns_printing - Verifies printing state
All 382 tests pass.
Backend:
- pytest configuration with async support and coverage
- Unit tests for notification service (23 tests)
- Unit tests for smart plug manager (12 tests)
- Unit tests for archive service (16 tests)
- Integration tests for API endpoints
- Fix: notifications now send immediately (digest is summary only)
Frontend:
- Vitest configuration with jsdom and coverage
- MSW for API mocking
- Component tests for Toggle, Button, Card, ConfirmModal (77 tests)
- Test utilities with custom render wrapper
CI/CD:
- GitHub Actions workflow for automated testing
- Backend lint, unit tests, integration tests
- Frontend lint, type-check, unit tests, build