- New APIBrowser component with full OpenAPI schema integration
- Fetches and parses /openapi.json automatically
- Groups endpoints by API tags (printers, archives, settings, etc.)
- Expandable endpoint sections with color-coded method badges
- Path parameter, query parameter, and JSON body editors
- Auto-populates request body with schema examples
- Live API request execution with response display
- Response shows status code, timing, and formatted JSON
- Copy response button with clipboard fallback
- Search to filter endpoints across all categories
- Expand All / Collapse All buttons
- Link to Swagger UI (/docs)
- Two-column layout for API Keys tab
- Left: API key management + webhook documentation
- Right: API Browser with dedicated test key input
- Parameter validation
- Shows warning for missing required parameters
- Validates before sending requests to avoid 422 errors
- UX improvements
- "Use in API Browser" button on newly created keys
- Responsive layout (stacked on mobile, side-by-side on xl+)
- Skip Objects Modal:
- Object ID markers overlaid on preview image (grid layout)
- Large prominent ID badges to match printer display
- Info banner explaining to match IDs with printer screen
- Layer warning when skip unavailable (layer <= 1)
- Red badge on button showing skipped count
- Preview image now shown when paused (not just running)
- Close with ESC key or click outside modal
- Full light/dark theme support
- Backend:
- Extract object positions from 3MF (include_positions=True)
- API returns x/y coordinates for objects (when available)
- Parse s_obj from printer MQTT for skipped state persistence
- Cover URL available in PAUSE/PAUSED states
- Tests:
- Added test for objects with position data
Implement comprehensive theme customization with independent settings for
dark and light modes:
- Style layer: Classic (clean shadows), Glow (accent-colored glow effects),
Vibrant (dramatic deep shadows)
- Background layer: Neutral, Warm, Cool (light mode); plus OLED, Slate,
Forest (dark mode only)
- Accent colors: Green, Teal, Blue, Orange, Purple, Red
All combinations work independently (e.g., Glow + Forest + Teal). Settings
sync across devices via database and show toast confirmations on change.
Backend:
- Add 6 new settings fields (dark_style, dark_background, dark_accent,
light_style, light_background, light_accent)
- Add integration test for theme settings API
Frontend:
- Refactor index.css with 3-layer CSS variable system
- Update ThemeContext for dual-mode theme management
- Add Appearance section to Settings page with 6 dropdowns
- Update components for new ThemeContext API
- Virtual printer appears in Bambu Studio/Orca Slicer via SSDP discovery
- Secure TLS/MQTT communication with auto-generated certificates
- Queue mode (pending uploads) or auto-start mode
- Configurable access code for authentication
- Docker support with network_mode: host and certificate persistence
- Fix backup/restore for virtual printer settings (auto-save no longer overwrites)
- Features:
- Tasmota device discovery: Auto-detect network and scan for Tasmota devices
in Add Smart Plug modal. Supports devices with/without authentication.
- Switchbar: Quick access widget in sidebar footer for controlling smart plugs
from anywhere in the app. Shows real-time status and power consumption.
- Detect Docker environment automatically via /.dockerenv and cgroup
- Show subnet input field in Add Printer dialog when running in Docker
- Scan IP range for Bambu printer ports (8883 MQTT, 990 FTPS)
- Query SSDP to get printer name/serial/model from discovered IPs
- Map raw SSDP model codes to friendly names (BL-P001→X1C, O1D→H2D)
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.
Test Summary:
- Build tests: 3 passed (image build, backend imports, static files)
- Backend unit tests: 378 passed (9 docker tests excluded)
- Frontend unit tests: 137 passed
- Integration tests: 9 passed (health, API endpoints, persistence, WebSocket)
Changes made to fix the Docker test suite:
1. Added curl to the production Dockerfile for integration tests
2. Removed deprecated version attribute from docker-compose.test.yml
3. Added --pull flag to all build commands to ensure fresh images
4. Added explicit build step before starting integration container
5. Fixed WebSocket test to accept 200 as a valid response
6. Excluded docker-marked tests from backend unit test runs (-m "not docker")
- H2D sends timelapse status in ipcam.timelapse field, not xcam.timelapse
- Added parsing for timelapse from ipcam data
- Added test to prevent regression
2. Systemd service updated - /etc/systemd/system/bambuddy.service
- Now runs as claude user instead of root
- Added ExecStartPre commands with + prefix (runs as root) to:
- Create logs and archive directories if missing
- Fix ownership to claude:claude before service starts
The service restarted successfully with the permission fixes running at startup. On the next print with timelapse enabled, both the snapshot and timelapse should be captured automatically.
1. bambu_mqtt.py: Added hms_errors to the completion callback data, which includes the HMS error codes when a print fails
2. archive.py: Updated update_archive_status() to accept an optional failure_reason parameter
3. main.py: Added auto-detection of failure reasons:
- status == "aborted" → failure_reason = "User cancelled"
- status == "failed" with HMS errors → maps module codes to reasons:
- Module 0x07 (Filament) → "Filament runout"
- Module 0x0C (Motion Controller) → "Layer shift"
- Module 0x05 (Nozzle) → "Clogged nozzle"
Frontend changes:
1. ArchivesPage.tsx:
- Badge now shows "cancelled" for aborted prints, "failed" for failed prints
- "Failed Prints" collection and "Hide Failed" filter now include both failed and aborted statuses
2. EditArchiveModal.tsx: Failure reason field now shows for both failed and aborted prints
New tests added:
- test_hms_errors_included_in_failed_completion_callback
- test_aborted_status_when_cancelled
The HMS error module mapping is basic and can be expanded as you observe more failure types. The actual Bambu HMS error codes are documented in their wiki - we can add
more mappings as needed.
Problem: xcam data was parsed BEFORE _was_running was set to True, so timelapse detection failed when both arrived in the same message.
Fix: Added timelapse detection at two critical points:
# Lines 1375-1381: When entering RUNNING state
if self.state.state == "RUNNING" and current_file:
if not self._was_running:
if self.state.timelapse:
self._timelapse_during_print = True
logger.info(f"[{self.serial_number}] Timelapse detected when entering RUNNING state")
# Lines 1391-1399: When print start detected
if self.state.timelapse:
self._timelapse_during_print = True
logger.info(f"[{self.serial_number}] Timelapse detected at print start")
2. Callback Error Handling (main.py + printer_manager.py)
Problem: The on_print_complete callback was hanging silently - no logs after "Found archive".
Fixes:
- Added try/except wrapper around "Update archive status" section (line 749-767)
- Added [CALLBACK] log messages at start and end of callback
- Added exception handler to _schedule_async in printer_manager.py to log any exceptions from async callbacks
3. New Integration Tests (test_bambu_mqtt.py)
Added TestRealisticMessageFlow class with 5 new tests that simulate real MQTT message sequences:
1. test_timelapse_detected_at_print_start_in_same_message - The critical race condition test
2. test_timelapse_not_detected_when_disabled
3. test_timelapse_detected_when_enabled_after_print_start
4. test_print_complete_includes_timelapse_flag_full_flow - Full lifecycle test
5. test_print_failed_includes_timelapse_flag
These tests process messages through _process_message() with proper {"print": {...}} wrapping, matching real MQTT flow.
Files Modified:
- backend/app/services/bambu_mqtt.py - Timelapse race condition fix
- backend/app/main.py - Error handling + logging for on_print_complete
- backend/app/services/printer_manager.py - Exception capture in _schedule_async
- backend/tests/unit/services/test_bambu_mqtt.py - New integration tests
Next Steps
You should restart the application to apply these changes. The next print will show:
- [CALLBACK] on_print_complete started for printer X
- [ARCHIVE] Updating archive X status...
- [ENERGY], [PHOTO], [AUTO-OFF] logs (if those features trigger)
- [CALLBACK] on_print_complete finished for printer X, archive X
Expanded the useWebSocket.test.ts from minimal coverage to 20 comprehensive tests covering:
WebSocket Mock Tests (6 tests)
- Creates WebSocket with correct URL
- Starts in CONNECTING state
- Transitions to OPEN state
- Can receive messages
- Can close connection
- Tracks all instances
Hook Connection Tests (2 tests)
- Connects to WebSocket on mount
- Reports connected state when WebSocket opens
Message Handling Tests (9 tests)
- Updates printer status in query cache on printer_status message
- Preserves wifi_signal when new value is null
- Invalidates archives on print_complete message
- Invalidates archives on archive_created message
- Invalidates archives on archive_updated message (new handler for timelapse auto-assignment)
- Ignores pong messages without error
- Handles malformed JSON gracefully
- Handles unknown message types gracefully
sendMessage Tests (2 tests)
- Sends JSON message when connected
- Does not send when disconnected
Reconnection Tests (2 tests)
- Reconnects after connection closes
- Cleans up on unmount
Key Fixes
- Fixed MSW (Mock Service Worker) conflict by:
a. Adding addEventListener/removeEventListener to MockWebSocket class
b. Updating MSW setup to bypass WebSocket requests
c. Properly managing WebSocket mock lifecycle in each test
Test Results:
- Frontend: 137 tests passed (9 test files)
- Backend: 346 tests passed
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