diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e8c24f16..b7c819519 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@ All notable changes to Bambuddy will be documented in this file.
- **Spoolman weight tracking now uses per-print grams for all spools, matching the internal Filament Inventory** ([#1119](https://github.com/maziggy/bambuddy/issues/1119), reported by @Moskito99) — Spoolman previously had two mutually-exclusive weight paths: AMS remain%×tray_weight auto-sync (default; only worked for Bambu Lab spools with valid RFID tray_weight) and per-print 3MF-grams tracking (only enabled when "Disable AMS Weight Sync" was toggled on). Non-BL spools without RFID fell through both paths — AMS auto-sync had no tray_weight to multiply, and the inventory_remaining fallback was wiped because activating Spoolman deletes the internal `spool_assignment` table — so Spoolman never saw a weight update for them. The internal Filament Inventory has no such gap: it always uses per-print 3MF grams as the primary path with AMS-remain% delta as fallback, and it works for every spool type. Spoolman now does the same: per-print tracking runs whenever Spoolman is enabled and is the only writer of `remaining_weight`. AMS auto-sync continues to maintain spool metadata and slot assignments but no longer touches weight (eliminating the double-count that would otherwise occur for BL spools with both paths active). `store_print_data` ([`spoolman_tracking.py:159`](backend/app/services/spoolman_tracking.py)) had its `disable_weight_sync` early-return removed; the three `sync_ams_tray` callsites (`main.py:1450` auto-sync, `spoolman.py:318` per-printer manual, `spoolman.py:517` sync-all) now hard-code `disable_weight_sync=True`. The `spoolman_disable_weight_sync` setting is now deprecated and a no-op — kept in the DB/UI for backwards compat. Behavioral consequence for existing users on the default flag (False): live AMS-based remaining_weight updates between prints stop happening; weight updates now arrive once per print completion with 3MF gram precision. Regression test in `test_spoolman_tracking.py::test_stores_tracking_when_disable_weight_sync_is_false` proves the early-return is gone.
### Fixed
+- **Deleting an SSO user left orphan OIDC/MFA/camera-token rows on SQLite — blocked re-login and leaked auth state** ([#1285](https://github.com/maziggy/bambuddy/issues/1285), [PR #1295](https://github.com/maziggy/bambuddy/pull/1295) by @netscout2001) — On SQLite (default deployment) the `delete_user` route left orphan rows in `user_oidc_links`, `user_totp`, `user_otp_codes`, and `long_lived_tokens` because the project intentionally runs with `PRAGMA foreign_keys=OFF`, so the `ON DELETE CASCADE` declared on those tables never fired. **Reported symptom:** an admin deleted an OIDC-provisioned user, the user tried to re-login via SSO, the OIDC callback found the orphan `UserOIDCLink` pointing at the (now missing) user, failed to resolve it, and redirected to `account_inactive` instead of triggering `auto_create_users`. The same root cause was leaking MFA secrets (`user_totp`), pending email OTP codes (`user_otp_codes`), and per-user camera-stream tokens (`long_lived_tokens` — `verify()` would happily match by `lookup_prefix` even after the owning user was gone). PostgreSQL deployments were unaffected — cascade was firing there. **Fix:** mirrors the existing `APIKey` cleanup pattern in `delete_user` (introduced in PR #1182). `backend/app/api/routes/users.py:delete_user` now explicitly deletes `UserOIDCLink`, `UserTOTP`, `UserOTPCode`, and `LongLivedToken` rows owned by the user; also folds in `PrintBatch.created_by_id` cleanup (same `ondelete=SET NULL` SQLite-FK-off root cause, the `SET NULL` block at `users.py:393-407` was missing it). `backend/app/core/database.py:run_migrations` gains an idempotent startup orphan-cleanup that sweeps the four auth tables (`DELETE FROM
WHERE user_id NOT IN (SELECT id FROM users)`), wrapped in `begin_nested()`, logged at INFO only when rows actually drop — so installations carrying orphans from before the fix are healed automatically without manual DB intervention. No-op on Postgres (cascade already fired) and idempotent on SQLite (second run finds nothing). `backend/app/api/routes/mfa.py:list_oidc_links` returns `""` for `provider_name` when `link.provider` is null instead of raising `AttributeError` — covers the symmetric edge case where a `UserOIDCLink` could reference an orphaned provider. **Tests:** 14 new/extended. `test_users_auth_cleanup.py` (new): 5 tests verify `delete_user` removes OIDC/TOTP/OTP/long-lived-token rows individually + combined-cleanup atomically. `test_oidc_relogin.py` (new): full end-to-end test reproducing the #1285 symptom — mocked IdP, first OIDC login, admin delete, second OIDC login proves `auto_create_users` fires again (and pinned the regression boundary by confirming the test fails without the fix). `test_orphan_auth_cleanup_migration.py` (new): 7 tests for per-table cleanup across all four auth tables, idempotency, no-op on fresh install, and survival of rows belonging to real users. `test_mfa_api.py` adds `TestListOidcLinksDefensiveProviderNull` for the null-check. `test_auth_api.py::test_delete_user` extended to assert all five auth-table side effects (`UserOIDCLink`, `UserTOTP`, `UserOTPCode`, `APIKey`, `LongLivedToken`). All 13 PR-added tests + 194 tests in extended files pass; ruff clean. **Reported and patched by @netscout2001.**
+
- **Slicer bundle import 400/502/503 errors now land in the log so support bundles tell us why** ([#1312](https://github.com/maziggy/bambuddy/issues/1312), reported by @hasmar04) — Reporter hit `400 Bad Request` from `POST /api/v1/slicer/bundles` when uploading a Bambu Studio Printer Preset Bundle (`.bbscfg`); a second contributor had reported the same shape the day before. Same bundle file uploaded fine on Martin's dev machine, which strongly points at sidecar-side differences (image version, write permissions on `DATA_PATH/bundles`, TrueNAS Docker volume perms, etc.) — but triage was blocked because the sidecar's actual reject reason only made it as far as the FE toast. Bambuddy logged just the uvicorn-access line (`POST /api/v1/slicer/bundles HTTP/1.1 400`), with no detail in the support bundle. The route at `backend/app/api/routes/slicer_presets.py:import_slicer_bundle` now emits a `logger.warning` for each of the three failure shapes: **400 (`SlicerInputError`)** — sidecar's reject string is logged alongside the filename and byte count, so we can see "bundle rejected because `manifest.json` is missing" in the next support bundle without asking the reporter to copy the toast text. **503 (`SlicerApiUnavailableError`)** — logs the configured sidecar URL plus the exception detail (separates "URL wrong" from "sidecar offline"). **502 (`SlicerApiError`)** — logs filename + byte count + error string, useful when the sidecar's `DATA_PATH/bundles` write fails (the typical 5xx cause on this path). The 400 case is `WARNING` rather than `INFO` deliberately — it's an unexpected end-user-visible failure, not a routine event. Existing `test_import_bundle_sidecar_400_passes_through` now also asserts the reject reason AND the filename appear in caplog, so the support-bundle-includes-the-diagnostic contract is pinned. Doesn't fix #1312's actual root cause (sidecar-side, still under investigation with reporter) — but the next reporter we get on this code path will produce a bundle that contains the answer.
- **Restarting Bambuddy mid-print triggered plate-check pause + duplicate archive** ([#1304](https://github.com/maziggy/bambuddy/issues/1304), reported by @kleinwareio) — When a P1S print was in progress and the user updated the Bambuddy container (`latest` → `daily` in the report, but the same path fires on any restart), Bambuddy paused the live print with an "Object detected on build plate" warning AND re-archived the in-progress file as a duplicate. Root cause: the print-start detector at `backend/app/services/bambu_mqtt.py:2780` gated on `self._previous_gcode_state != "RUNNING"`, which is true whether we just saw IDLE→RUNNING (a real print start) OR we just constructed a fresh BambuMQTTClient and `_previous_gcode_state` is still its initial `None` (catch-up push from a printer already running). The fresh-client case fired `on_print_start`, which downstream ran the plate-detection-and-pause flow at `main.py` AND the FTP-download-and-archive flow — exactly the two symptoms in the bug report. Fix: added `self._previous_gcode_state is not None` to the `is_new_print` guard, so the first push from the printer in a new process lifetime never counts as a state transition into RUNNING. `_was_running` still flips to `True` via the unconditional "Track RUNNING state" block at `bambu_mqtt.py:2795`, so print-completion detection keeps working — only the start callback is suppressed. Three existing tests that asserted on the old (buggy) behavior were updated to seed `_previous_gcode_state = "IDLE"` first, matching the realistic lifecycle of a print actually starting (Bambuddy has been observing IDLE/FINISH before RUNNING); they now exercise the correct path. New regression test `test_first_running_push_after_bambuddy_restart_does_not_fire_print_start` pins the contract for the reporter's exact scenario — and asserts that `_was_running` still becomes True so completion still fires when the print ends. The `is_file_change` branch was unaffected (it already required `_previous_gcode_file is not None`, so restart-catch-up never reached it anyway).