Narrowed CSS selector to only target max-w-2xl modals (Assign Spool)
not max-w-sm ones (slot action picker). Removed inner max-h-96 on
the spool grid to eliminate nested scrollbars.
CSS rules scoped via data-spoolbuddy-kiosk force the modal panel to
90vh with flex column layout. Content area scrolls, header/footer
pinned. Uses height (not just max-height) so flex-basis auto works
correctly. No flash — CSS applies before paint.
useEffect in SpoolBuddyAmsPage applies inline styles to the modal
panel after render (requestAnimationFrame + setTimeout) to cap it
at 90vh with scrollable content. Targets the DOM directly to avoid
CSS specificity issues with Tailwind v4.
Used a ref callback wrapper in SpoolBuddyAmsPage to directly apply
inline styles (max-height 90vh, flex column, scrollable content) to
the shared AssignSpoolModal DOM elements without modifying the
shared component.
Added data-spoolbuddy-kiosk attribute to the SpoolBuddy layout and
global CSS rules that cap modals at 90vh with scrollable content on
the small touchscreen, without modifying shared components.
Added scoped CSS in SpoolBuddyAmsPage that constrains the shared
AssignSpoolModal to 90vh with scrollable content and pinned
header/footer, without modifying the shared component.
Added scoped CSS in SpoolBuddyAmsPage that constrains the shared
AssignSpoolModal to 90vh with scrollable content, without modifying
the shared component used by the main Bambuddy frontend.
The NFC Diag and Scale Diag buttons on the Write Tag page's right
card were misplaced — diagnostics are already available on the
Settings page. Removed the buttons and all associated dead code.
When the virtual keyboard opened on the SpoolBuddy device tab, the
API token field and Save button were hidden behind the keyboard.
The keyboard now adds bottom scroll padding and scrolls the parent
card (not just the focused input) to the top of the visible area,
keeping all related fields accessible.
When the virtual keyboard opened on the SpoolBuddy device tab, the
Backend URL and API Token fields were hidden behind the keyboard
overlay. The keyboard now adds temporary bottom padding to the
scrollable container so inputs scroll above it, and removes the
padding on dismiss.
Non-passive touchmove listener with preventDefault() blocks Chromium's
compositor-level back/forward swipe gesture. Once horizontal movement
is detected, the gesture is locked and all further touchmove events
are prevented for that swipe. Removed touch-action: pan-y which was
insufficient on its own.
Swiping left/right on the SpoolBuddy touchscreen now cycles through
online printers instead of triggering browser back/forward navigation.
Uses touch-action: pan-y to suppress Chromium's native swipe history
gesture. Auto-navigates to dashboard when a new NFC tag is detected
on any other page, with edge detection so a persistent tag doesn't
block manual navigation. Also wakes the screen if blanked.
Swiping left/right on the SpoolBuddy touchscreen now cycles through
online printers instead of triggering browser navigation. Auto-navigates
to dashboard when a new NFC tag is detected on any other page, with
edge detection so a persistent tag doesn't block manual navigation.
When an NFC tag is first detected while the UI is on a non-dashboard
page, automatically navigate to the main dashboard. Uses edge detection
(previous ref) so a persistently present tag doesn't block navigation
to other pages. Also wakes the screen if the display was blanked.
When an NFC tag is detected while the UI is on a non-dashboard page
(Settings, AMS, Write Tag, Calibration), automatically navigate back
to the main dashboard to display the scanned spool. Also wakes the
screen if the display blank timeout had kicked in.
usage, OS info, and system uptime every heartbeat using stdlib-only reads
from /proc and /sys. Stats are sent as a JSON blob in the heartbeat
payload, stored in a new system_stats TEXT column, and displayed in a
new "System" tab in SpoolBuddy Settings with color-coded usage bars.
usage, OS info, and system uptime every heartbeat using stdlib-only reads
from /proc and /sys. Stats are sent as a JSON blob in the heartbeat
payload, stored in a new system_stats TEXT column, and displayed in a
new "System" tab in SpoolBuddy Settings with color-coded usage bars.
The daemon now collects CPU temp, core count, load average, memory/disk
usage, OS info, and system uptime every heartbeat using stdlib-only reads
from /proc and /sys. Stats are sent as a JSON blob in the heartbeat
payload, stored in a new system_stats TEXT column, and displayed in a
new "System" tab in SpoolBuddy Settings with color-coded usage bars.
The daemon now collects CPU temp, core count, load average, memory/disk
usage, OS info, and system uptime every heartbeat using stdlib-only reads
from /proc and /sys. Stats are sent as a JSON blob in the heartbeat
payload, stored in a new system_stats TEXT column, and displayed in a
new "System" tab in SpoolBuddy Settings with color-coded usage bars.
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.
The AssignToAmsModal was missing all material/profile validation guards
that exist in the main AssignSpoolModal. Clicking an AMS slot now checks
the spool's material and slicer profile against the target slot's current
filament, showing a confirmation dialog on mismatch. Respects the global
disable_filament_warnings setting. Uses existing i18n keys and ConfirmModal.
When Spoolman is enabled, the sidebar filament item embedded
Spoolman at its root URL. Now navigates to <url>/spool so users
land directly on the spool management page.
When a spool is already assigned/linked, only show Unassign/Unlink.
When no spool is mapped, only show Assign/Link. Previously both
buttons appeared simultaneously.
Clicking an AMS slot now shows a picker with options: Configure AMS
Slot, Assign Spool (inventory) or Link to Spoolman, and Unassign/
Unlink when a mapping exists. Reuses the existing AssignSpoolModal
and LinkSpoolModal components from the main Bambuddy frontend.
Remove branding/about card, fold Device ID into Device Info card,
place Backend/Auth config and diagnostic buttons side by side in a
2-column grid, remove redundant online/offline status from Device
Info, and tighten spacing throughout.
Remove the branding/about card, fold Device ID into the Device Info
card, make diagnostic buttons a horizontal 3-column row, and put the
API token input and Save button on the same line. Tighten spacing
throughout so the tab fits on the small touchscreen without scrolling.
Some printers (e.g. H2D) only send {id, state} in incremental MQTT
tray updates — no tray_type, tray_color, or other fields. When
filament is unloaded (state changes from 11 to 10), the old tray
data persisted indefinitely because the merge logic only updates
fields present in the incoming message.
Backend:
- Detect tray state != 11 without tray_type in incremental updates
and clear stale tray data (bambu_mqtt.py)
- Expose tray `state` field via REST API and WebSocket broadcasts
(printer.py schema, printers.py route, printer_manager.py)
- Include AMS tray content in WebSocket dedup key so load/unload
transitions trigger broadcasts (main.py)
Frontend:
- Add `state` to AMSTray interface (client.ts)
- Show configure/assign buttons for state=10 (spool present, not
loaded) but hide for state=9 (truly empty) on AMS/HT slots
- Hide fill level bar on empty slots
Tests:
- 5 new tests covering state-based clearing, preservation, reload,
and idempotency
Host directories (NAS, USB, network shares) can now be mounted
into the File Manager without copying files. Files are indexed
into the database on scan but read directly from their original
location. Supports read-only mode, hidden file filtering, and
automatic thumbnail extraction for 3MF/STL/gcode.
- POST /library/folders/external — create with path validation
- POST /library/folders/{id}/scan — discover/sync files
- Block uploads, moves, and deletes for read-only external folders
- Never delete actual files from external paths (DB-only removal)
- Purple folder icon + info bar with rescan button in UI
- i18n for all 7 languages
- 19 backend + 11 frontend tests
Just unregistering the SW wasn't enough — the old SW still controlled
the page for that load. Now detects if any SW exists, nukes all SWs
and caches, then reloads once to get a clean fetch from the server.
Subsequent loads skip this since no SW is registered.
The kiosk touchscreen has no way to hard-refresh, and the service worker
served stale cached JS after updates. SpoolBuddy pages now unregister
any existing SW and skip registration entirely. Regular desktop/mobile
users still get the SW. Restored kiosk restart in SSH update flow since
SW is no longer an obstacle.
The SW used stale-while-revalidate for JS/CSS, serving old cached
bundles even after a new build. Changed to network-first (Vite already
content-hashes filenames), bumped cache version v24→v25, and added
Cache-Control: no-cache to the sw.js endpoint so browsers always fetch
the latest service worker.
Rewrote update button states: single `busy` flag stays set from click
through to device pickup — no more gap with no feedback. Buttons hide
while any operation is in progress. Listens for spoolbuddy-online
WebSocket event to reload the page after daemon re-registers, ensuring
fresh version and status. Set staleTime to 0 on update check queries.
Check button had no visual feedback because isFetching doesn't trigger
reliably with cached data — replaced with manual loading state. Removed
kiosk restart via getty; the frontend now detects daemon re-registration
via WebSocket and calls window.location.reload(), keeping the user on
the same page and fetching all fresh data.
- Check button now shows spinner on refetch (isFetching vs isLoading)
- Force Update button shows spinner while triggering
- Reduced staleTime from 4 minutes to 30 seconds so the UI picks up
version changes after an update without a long delay
Merged version, status, and update check into a single card. Buttons
are side by side when up to date. Reduced padding and font sizes.
Removed separate "complete" banner since status is now cleared on
re-registration. SSH Setup section is smaller and more compact.
After an SSH update completed, the UI kept showing "Update complete,
daemon restarting..." and the old "update available" banner because
nothing cleared the stale state. Registration now resets update_status
when the daemon comes back, and WebSocket handlers for spoolbuddy_update
and spoolbuddy_online invalidate the frontend queries immediately.
The daemon's self-update mechanism (git fetch/reset on its own code) was
fragile: .git permission errors, self-modifying code mid-run, hardcoded
main branch. Bambuddy now SSHes into the SpoolBuddy Pi and drives the
update remotely — matching its own branch, with step-by-step progress
via WebSocket. Install script updated with SSH access, sudoers entry,
and --ssh-pubkey flag for pairing.
The SpoolBuddy daemon update endpoint fetched GitHub releases and compared
them against device.firmware_version, which always showed "up to date"
because the comparison source was wrong. Now compares directly against
APP_VERSION from the running backend, so a daemon at 0.2.3b1 correctly
sees 0.2.3 as an available update.
BambuStudio connects to undocumented proprietary ports 2024-2026
on A1/P1S models during the print flow. The proxy wasn't forwarding
these ports, causing connection refused (RST) and triggering the
access code dialog instead of printing. MQTT and FTP worked fine —
port 2024 was the sole blocker.
Added transparent TCP pass-through proxies for ports 2024-2026,
following the same pattern as the existing FileTransfer (6000) and
RTSP (322) proxies. Silently ignored on models that don't use them.
Remove "Assign Spool" and "Configure" buttons from empty AMS slot
hover popups (standard AMS and HT AMS). Assigning a spool to a
physically empty slot created a stuck state — no unassign button
exists for empty slots, so the assignment couldn't be removed.
External spool holders are unaffected.
The SpoolBuddy layout now auto-checks for daemon updates every 5
minutes and shows "Update available: v{version}" in the status bar.
Removed the beta toggle since SpoolBuddy follows Bambuddy's release
channel. The daemon version is now read from backend APP_VERSION
instead of a stale hardcoded string.
SpoolBuddy devices can now be updated from Settings → Updates without
SSH access. The daemon picks up an "update" command via its existing
heartbeat, runs git fetch/reset + pip install, reports progress back
to the backend, then exits for systemd to restart with the new code.
Backend: update_status/update_message fields, trigger + status endpoints
Daemon: _perform_update() handler, report_update_status() API method
Frontend: "Apply Update" button with live progress in UpdatesTab
Multi-plate 3MF files now support selecting a subset of plates to queue
via checkboxes, instead of the binary "one plate" or "all plates" toggle.
In add-to-queue mode, each plate gets a checkbox for multi-select with a
Select All / Deselect All toggle. Reprint and edit modes remain single-select.
The saved preset bypassed the search filter entirely, so when a slot's
DB mapping was stale (e.g. previously Matte, now physically Silk), the
old preset always appeared regardless of search query. Remove the search
bypass — saved/current presets still bypass the printer model filter but
must match the search text like all other presets.