fix(ui): Change Password modal leaked password affordance onto Printers search field

Opening the sidebar's Change Password modal while on the Printers page
  caused the "Search printers" input to render as a masked password field
  and stay that way after closing the modal.

  Root cause: the modal had three type=password inputs but no accompanying
  username anchor, so password-manager extensions (1Password, Bitwarden,
  browser built-ins) hunted the DOM for a matching text input and latched
  onto the unlabelled Printers-page search bar.

  - Layout.tsx: add hidden autocomplete=username anchor at the top of the
    Change Password modal form. Also ensures saved new passwords are
    correctly keyed to the logged-in user.
  - PrintersPage.tsx: harden the search input with type=search,
    name=printer-search, autoComplete=off, data-1p-ignore, data-lpignore
    so heuristic autofill skips it regardless.
This commit is contained in:
maziggy
2026-04-21 17:22:34 +02:00
parent 28f80f948a
commit 597d961b0c
5 changed files with 19 additions and 4 deletions
+1
View File
@@ -8,6 +8,7 @@ All notable changes to Bambuddy will be documented in this file.
- **Printer Card Shows Plate Name on Multi-Plate Prints** ([#881](https://github.com/maziggy/bambuddy/issues/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
- **Printers-Page Search Turns Into a Password Field After Opening Change-Password Modal** — On the Printers page, clicking the key icon in the sidebar to open the Change Password modal caused the "Search printers" input to render as a password field (masked dots); closing the modal didn't restore it, requiring a full reload. Root cause: the Change Password modal has three `<input type="password">` fields but no accompanying username input, so password-manager browser extensions (1Password, Bitwarden, Chrome/Safari built-in) scanned the current DOM for a matching username anchor and latched onto the nearest `type="text"` input with no `name`/`autoComplete` — which happened to be the Printers-page search bar — and overrode its rendering. Fixed on two levels: (1) added a hidden `<input type="text" name="username" autoComplete="username" value={user.username} readOnly hidden>` at the top of the Change Password modal so password managers have a proper anchor and stop hunting elsewhere — as a bonus, saved new passwords are now correctly keyed to the logged-in user; (2) hardened the Printers-page search input with `type="search"`, `name="printer-search"`, `autoComplete="off"`, and `data-1p-ignore` / `data-lpignore="true"` so any future heuristic-based autofill also skips it.
- **AMS Slot Configure: Custom Cloud Preset Resolves to "Generic" in Slicer & Printer LCD** ([#1053](https://github.com/maziggy/bambuddy/issues/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: null` → `tray_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](https://github.com/maziggy/bambuddy/issues/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](https://github.com/maziggy/bambuddy/issues/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.
+10
View File
@@ -1030,6 +1030,16 @@ export function Layout() {
</CardHeader>
<CardContent>
<div className="space-y-4">
<input
type="text"
name="username"
autoComplete="username"
value={user?.username ?? ''}
readOnly
hidden
aria-hidden="true"
tabIndex={-1}
/>
<div>
<label className="block text-sm font-medium text-white mb-2">
{t('changePassword.currentPassword')}
+5 -1
View File
@@ -6486,7 +6486,11 @@ export function PrintersPage() {
<div className="relative w-full sm:max-w-sm mt-3">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-bambu-gray/50" />
<input
type="text"
type="search"
name="printer-search"
autoComplete="off"
data-1p-ignore
data-lpignore="true"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={t('printers.search')}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -26,7 +26,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-D-cysdnw.js"></script>
<script type="module" crossorigin src="/assets/index-B5tW0juP.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CkAOuJaW.css">
</head>
<body>