Move the toast container from bottom-4 to bottom-20 so toast
notifications and upload progress stack above the bug report bubble
instead of rendering on top of each other.
* AMS Labels addition to PrintersPage
* Added database reinitialization for schema migrations on database restore
* Bug fixes and AMS Label persistence updates
* Update database.py to resolve PR conflicts
* PR Comment Resolution
* Resolve conflicts in database.py for PR#570
* Updates to address PR#570 additional comments
* Optimize visibility handling for popup component
* Improve serial key handling in printers.py
Refactor serial key assignment to handle empty AMS serial gracefully.
* Implement error handling in serial number mapping
Add error handling for serial number mapping.
* Add migration to drop old ams_labels table
---------
Co-authored-by: MartinNYHC <mz@v8w.de>
* Fix AMS slot modal not scrolling to preselected filament
The scroll useEffect fired before the loading spinner was replaced
by the preset list, so the DOM element didn't exist yet. The ref
guard was then set, preventing any retry once buttons appeared.
- Move isLoading computation before the scroll effect and add it
as a dependency so the effect re-runs when loading completes
- Gate scroll on !isLoading so it only runs when buttons exist
- Only mark scrolledToRef after the element is actually found
- Use requestAnimationFrame to scroll after browser layout
- Use block:'center' so the selected item is clearly visible
Preselect filament for printer-configured AMS slots
When an AMS slot was configured directly on the printer (no
savedPresetId), the modal didn't preselect the filament even though
it was in the list. Add a third fallback that matches trayInfoIdx
against the full filteredPresets list (cloud and builtin sources).
* Restore scrolledToRef lost during merge
The useRef import, scrolledToRef declaration, and its reset were
dropped during the 0.2.2b1 merge, causing a ReferenceError in the
scroll-to-preselected-filament effect.
* Fix preset scroll loop and wire up useEffect scroll logic
- Remove inline ref callbacks with scrollIntoView on both preset
button lists (fullscreen + standard) to stop the infinite scroll
loop on Windows
- Add data-preset-id attribute so the existing useEffect/querySelector
scroll logic can actually find the elements
- Replace filteredPresets dependency with builtinFilaments in the
selection useEffect to prevent search keystrokes from overriding
manual preset selection
* Adjust scroll behavior in ConfigureAmsSlotModal
---------
Co-authored-by: MartinNYHC <mz@v8w.de>
Floating bug report button submits issues via bambuddy.cool relay (no GitHub
token needed locally). Collects 30s debug logs with printer push_all, sanitizes
all sensitive data, uploads logs as files to GitHub. Screenshot upload/paste/drag
with JPEG compression. Translated into all 7 languages. Includes 21 tests.
Parse home_flag bit 18 (0x00040000) from MQTT to detect ethernet
connections. When set, the printer card shows a green "Ethernet"
badge with a cable icon instead of WiFi signal strength in dBm.
The printer info modal also displays "Ethernet" instead of WiFi
signal details.
FTP upload: reduce chunk size from 1MB to 64KB for smooth progress bar
updates (~1s intervals instead of 20+ second gaps). Skip voidresp() for
all printer models — H2D delays the 226 response by 30+ seconds after
data transfer, causing a hang at 100%. Add transfer speed and TLS
handshake timing to logs for diagnosing slow connections.
Print/Schedule modal: widen from max-w-lg (512px) to max-w-2xl (672px)
to accommodate long filament profile names like "PLA Support for PETG
PETG Basic @Bambu Lab H2D 0.4 nozzle".
Profiles like "PLA Support for PETG PETG Basic @Bambu Lab H2D" have
filament_type PETG, but both the frontend and backend name parsers
found "PLA" first (iterating material types in order). The MQTT command
sent tray_type=PLA and tray_info_idx=GFL99 (PLA generic), so the
slicer displayed PLA instead of PETG.
- Frontend parsePresetName(): detect "X Support for Y" pattern, extract
material after "Support for"
- Frontend ConfigureAmsSlotModal: prefer corrected parsed material over
stored localPreset.filament_type for tray_type, tray_info_idx, and
temperature fallback
- Backend _parse_material_from_name(): same "Support for" handling for
future profile imports
- Backend assign_spool: prioritize spool.material over lp.filament_type
for generic filament ID lookup
Inline callback refs on preset buttons called scrollIntoView on every
re-render, creating a scroll → re-render → scrollIntoView loop on
Windows. Replace with a useEffect + data attribute approach that only
scrolls when the selected preset actually changes.
* Add printer card print flow with drag-drop and reusable LibraryUploadModal
Extract inline upload modal from FileManagerPage into a reusable
LibraryUploadModal component. Add a Print button and drag-drop zone
to printer cards that upload files to the library and open PrintModal
with the printer pre-selected. Only .gcode/.gcode.3mf files are
accepted for printing. PrintModal hides printer selector when a
printer is provided via initialSelectedPrinterIds prop.
* Add printer compatibility check for drag-drop and upload-to-print flows
Validates sliced_for_model from file metadata against the target printer model
before opening the print modal, preventing users from printing with incompatible files.
* Improve FileUploadModal: auto-close on success, inline errors, file validation, and i18n
- Close modal automatically after successful upload instead of showing summary
- Show printer compatibility errors inline in the modal instead of closing
- Add validateFile and accept props to restrict file types for print flow
- Add onFileUploaded error return to prevent modal close on incompatible files
- Internationalize all hardcoded error/warning strings across 6 locales
* Clean up incompatible files from library and fix print button i18n
- Delete uploaded file from library when printer compatibility check fails
in both drag-drop and modal upload flows
- Fix print button tooltip to use existing common.print i18n key
- Fix accept attribute to use .gcode,.3mf for proper browser support
* Add printer information modal accessible from card 3-dot menu
Replace inline IP/serial display at bottom of printer card with a
dedicated "Printer Information" modal opened via the card menu. The
modal shows model, status, state, IP address (copyable), serial number
(copyable), WiFi signal, firmware, developer mode, nozzle count, SD
card, auto-archive, total print hours, location, and added date, along
with the printer image. Includes full i18n support across all 6 locales
and accessibility attributes (role="dialog", aria-modal).
* Extract getPrinterImage and getWifiStrength into shared printer utils
---------
Co-authored-by: MartinNYHC <mz@v8w.de>
Write NTAG213/215/216 tags for third-party spools via the SpoolBuddy
kiosk UI. New "Write" page with three workflows: existing spool, new
spool creation, and tag replacement. Backend encodes 133-byte OpenTag3D
NDEF payloads (material, color, brand, weight, temp). Daemon writes
page-by-page via PN5180 NTAG WRITE command with read-back verification.
Write commands flow through heartbeat polling with WebSocket status
updates. Includes 39 new tests and translations for all 6 languages.
- Add printer status badges to dashboard left column (green/gray
online/offline dots, compact wrapping pills, overflow-hidden)
- Remove SpoolBuddy inventory page — inventory management belongs
in the main Bambuddy frontend, not the kiosk
- Remove inventory nav item from bottom nav (now 3 tabs)
- Replace "Add to Inventory" navigate with quick-add modal that
creates a basic PLA spool entry via API, with a hint to use
the main Bambuddy UI for full spool details instead
- Redesign settings page with tabbed layout (Device, Display, Scale, Updates)
- Add screen blank timeout: blanks after touch inactivity, tap to wake
- Add CSS brightness filter for HDMI displays (no sysfs on HDMI)
- Add backend `language` field to app settings for server-side persistence
- Sync UI language from backend on kiosk load (separate Chromium instance)
- Top bar clock respects user's time format setting (system/12h/24h)
- Add SpoolBuddy settings translations for all 6 languages (en/de/fr/ja/it/pt-BR)
- Disable Chromium swipe-to-navigate in kiosk install script
- Add `video` group for DSI backlight access
1. Device tab — repo URL unlinked
Replaced the <a> tag with a <span> so the GitHub URL is displayed as plain text (no clickable link in kiosk mode).
2. Display controls — moved to frontend
Root cause: The daemon runs as a system user without Wayland access, and the kiosk uses an HDMI display (no sysfs backlight). Neither wlopm, vcgencmd, nor sysfs writes
can work in this setup.
Fix — frontend-controlled display in SpoolBuddyLayout.tsx:
- Brightness: CSS filter: brightness(X) applied to the layout root. Immediate visual dimming that works on any display type.
- Screen blanking: Full-screen black overlay (z-[9999]) shown after the configured inactivity timeout. Touch anywhere to wake.
- Activity tracking: Resets on pointerdown/keydown events AND on WebSocket-driven NFC/scale changes (weight update, tag scan).
- Queries device data every 15s to read the current brightness and blank timeout values.
Daemon display_control.py — Simplified:
- Removed vcgencmd/wlopm/subprocess usage entirely
- _blank/_unblank are now state-tracking no-ops (log only)
- DSI backlight brightness via sysfs is preserved as a bonus for DSI displays
- Updated docstring to explain the architecture
3. Scale tab — redesigned with step indicator
Idle state: Same as before — weight card with tare/calibrate buttons.
Calibration wizard: Two-column layout:
- Left column (~64px): Vertical step indicator with numbered circles, checkmark for completed steps, and a connecting line
- Right column: Live weight bar (with stable/settling indicator), step-specific content (instructions or numpad), and action buttons pinned to bottom via mt-auto
The numpad buttons are min-h-[52px] with text-lg — large enough for touch on 1024x600. The weight info card is hidden during the weight entry step to maximize vertical
space.
Root cause: The daemon used wlopm for screen blanking, but wlopm was never installed. Additionally, the daemon runs as the spoolbuddy system user which has no access to
the Wayland socket, so Wayland-based tools can't work.
Fix in display_control.py:
- Replaced wlopm --off/--on with vcgencmd display_power 0/1 — this is a Raspberry Pi firmware-level command that's pre-installed and works without Wayland socket access
- Added shutil.which("vcgencmd") check at init to avoid repeated failures on non-RPi hardware
- Added explicit PermissionError handling for brightness writes with a helpful message about the video group
Fix in install.sh:
- Added video group to the spoolbuddy service user's groups (was: gpio, spi, i2c → now: gpio, spi, i2c, video), which grants access to both vcgencmd and sysfs backlight
files
Scale Tab Numpad
Root cause: On the 1024x600 kiosk screen (~376px available content height), the weight info card + numpad + action buttons exceeded the space, causing tiny buttons and
overlapping.
Fix in SpoolBuddySettingsPage.tsx:
- Hide the weight info card during weight entry step (calStep !== 'weight'), reclaiming ~70px
- Compact inline weight reading in the step header (small dot + monospace text) so users can still see the live scale value
- Larger numpad buttons: min-h-[56px] with text-lg font size (was no min-height, text-sm)
- Added active:scale-95 for tactile touch feedback
- mt-auto on action buttons to push them to the bottom, preventing overlap
- Removed the wrapping card around the calibration flow to save vertical padding
The Raspberry Pi kiosk has no physical keyboard and system-level virtual
keyboards (squeekboard, wvkbd) don't auto-show/hide with labwc/Chromium.
Add a react-simple-keyboard QWERTY keyboard that auto-shows on input
focus, with dark theme, shift/caps/backspace, email keys (@, .), and a
two-phase close that prevents ghost-click passthrough to elements below.
Inputs with data-vkb="false" opt out (e.g. SpoolBuddySettingsPage numpad).
- Show inventory-based fill levels for non-BL spools in AMS slots by
fetching spool assignments and computing fill from weight_used/label_weight,
falling back to AMS remain when no assignment exists
- Fix ext-R slot falsely highlighted as active when idle: tray_now=255
(no tray loaded) matched ext-R's id=255 without a guard
- Improve single-slot card layout: use responsive grid aligned with AMS
card columns, fix vertical stretching with few AMS units
- Remove redundant L/R nozzle badges from ext slots (label already shows
Ext-L/Ext-R)
- Add 16 tests for ext active state logic and fill override fallback
The assign_spool endpoint sent wrong MQTT field values for user presets,
causing the slicer's AMS slot detail card to show all fields empty.
Two bugs: (1) cloud API was called with the raw slicer_filament value
including its version suffix (e.g. PFUS9ac902733670a9_07), returning 404;
the silent fallback sent setting_id as tray_info_idx instead of the real
filament_id; (2) no SlotPresetMapping was saved after assignment.
Now strips version suffixes before cloud lookup, resolves the real
filament_id via cloud API (with local preset and generic fallbacks),
includes brand in tray_sub_brands, and saves slot preset mapping.
Print Activity now shows an hourly heatmap (hours x days) for timeframes
≤7 days and dynamically adjusts calendar months for longer ranges.
Adds hourly granularity to Filament Trends, persists timeframe selection,
fixes optional chaining in QuickStatsWidget, and fixes UTC/local timezone
mismatches in date key generation. Also hardens the /archives/slim limit
param and fixes empty query string handling in API client.
- Add lightweight GET /archives/slim endpoint with column-level SELECT
(13 fields vs 46), skipping duplicate detection for ~70-80% payload
reduction on the stats dashboard
- Add ArchiveSlim Pydantic schema and TypeScript type
- Switch StatsPage and FilamentTrends to use ArchiveSlim
- Fix critical bug in failure_analysis.py: use effective_days for week
count, separate non-date filters, build fresh week_filter per loop
- Fix busiestDay timezone bug: parse YYYY-MM-DD with split() + local
Date constructor instead of new Date() which creates UTC midnight
- Fix success streak ordering: sort by completed_at || created_at
instead of created_at alone
- Add 6 integration tests for /archives/slim endpoint
Use MetricToggle component with new exclude prop instead of inline
buttons, default to weight-first order, and redesign chart layout
with centered donut, total label, and 2-column legend grid.
Add Print Time of Day, Color Distribution, and Records widgets to the
Stats page. Extract shared MetricToggle component for consistent
weight/prints/time toggles. Add date range filtering to archives and
stats API endpoints. Reorganize widgets into parent cards (Printer Stats
and Filament Trends) and fix Dashboard layout persistence for new widgets.
- Replace TagDetectedModal with inline SpoolInfoCard/UnknownTagCard
in dashboard right panel (known spools show assign/sync/close,
unknown tags show add-to-inventory/link/close)
- Rewrite AssignToAmsModal as full-screen overlay reusing AmsUnitCard,
with AMS-HT and external slot support, single assignSpool API call
- Remove printer selector from assign modal (uses top bar selection)
- Extract filament_id <-> setting_id conversion to shared utility
(backend/app/utils/filament_ids.py), used by inventory + cloud routes
- Normalize slicer_filament in assign_spool to derive proper
tray_info_idx and setting_id for MQTT (was sending setting_id="")
- Rename SpoolBuddy top bar status label "Online" -> "Backend"
- Remove weightStable guard from sync weight button
Sidebar nav items (Archives, Queue, Stats, Profiles, Maintenance,
Projects, Inventory, Files) were visible to all users regardless of
role permissions — only Settings was gated. Now each item is hidden
when the user lacks the corresponding read permission. Printers
remains always visible as the home page.
Also adds missing inventory:read|create|update|delete to the frontend
Permission type (existed in backend but was absent from the frontend
type definition).
Frontend: Replace inline SpoolInfoCard/UnknownTagCard with full-screen
TagDetectedModal that auto-opens on NFC tag detection. Known spools show
remaining weight, fill bar, and offer "Assign to AMS" (new sub-modal with
printer selector + AMS slot grid) and "Sync Weight". Unknown tags offer
"Add to Inventory" and "Link to Spool". Modal stays open on tag removal,
won't re-open for dismissed tags, reopens on re-place after removal.
Daemon: Fix PN5180 NFC reader silently stopping tag detection when the
reader drifts into a stuck state. Add auto-recovery (full hardware reset
after 10 consecutive errors), preventive RF cycling every 60s when idle,
and periodic status logging. Heartbeat now reports actual nfc_ok/scale_ok
from reader instances instead of hardcoded True.
Enlarge all interactive elements across 9 SpoolBuddy components to meet
44px minimum tap targets on the RPi touchscreen. Increase nav icons
(20→24px), labels (10→12px), bar heights, section headers, printer
buttons, spool visualizations, fill bars, and status indicators.
Compact the dashboard stats bar and remove the printers card. Add
fullScreen prop to ConfigureAmsSlotModal with two-column layout
(filament list left, K-profile + color right) to eliminate scrolling.
The chart X axis used dataMin/dataMax domain, compressing the view to
only the data present. A 6h view with 1.5h of data looked like 1.5h.
Pin the X axis to the full selected time range, pad data edges so the
line extends to both ends, and connect through nulls for a continuous
line.
When a print was queued to "any printer" with a filament color override
(e.g., white PETG), the "Clear Plate & Start Next" button appeared on
printers that had the right type but wrong color. The backend scheduler
already rejected color mismatches, but the frontend only checked filament
types. Now passes loaded type+color pairs from AMS status to the widget
and filters against filament_overrides, mirroring the backend's
_count_override_color_matches() logic.
Long filament profile names were cut off because inline filament ID
codes consumed horizontal space in the dropdown. Remove the codes from
dropdown items (selected code still shown below the input) and widen
the modal from max-w-lg to max-w-xl.
When scheduling a print to "Any {model}", a redundant "Target Model"
dropdown appeared even though the G-code is already sliced for a
specific printer model. Changing it would lead to print failures.
Hide the dropdown when slicedForModel is known — the tab label already
communicates the target. The dropdown still appears as a fallback for
legacy files without model metadata.
The "Clear Plate & Start Next" button appeared on ALL printers of a
model when a job was queued via "Queue to Any X", even printers without
the required filament loaded. Clicking it started a print that fails.
PrinterQueueWidget now filters queue items by filament compatibility —
each item's required_filament_types must all be present in the printer's
loaded filaments (AMS + external spools). If no compatible items exist,
the widget is hidden entirely. Matching is type-only (not color) and
case-insensitive, consistent with the backend scheduler. When filament
data is unavailable, no filtering is applied (graceful degradation).
All backend timestamps used datetime.now() (server local time) or the
deprecated datetime.utcnow(). The frontend's parseUTCDate() assumes
timestamps without timezone indicators are UTC and appends 'Z', so
stored timestamps were off by the timezone offset when the container's
timezone wasn't UTC.
Backend: replaced datetime.now() and datetime.utcnow() with
datetime.now(timezone.utc) across 16 files (~80 call sites) for all
database fields and DB comparisons. Cosmetic timestamps (filenames,
user-facing local time formatting) intentionally left as local time.
Frontend: replaced 13 new Date(backendTimestamp) calls with
parseUTCDate() across 8 files to correctly interpret UTC timestamps.
The sidebar hid Settings based on hardcoded role === 'user' instead of
the settings:read permission, and login set user state directly from the
response instead of re-fetching full auth status with permissions.
The "Power off printer when done" checkbox in the print modal and the
auto power off tri-state toggle in the bulk edit modal were accessible
to all users regardless of permissions. Users without printers:control
can now no longer enable auto power off — controls are disabled and
visually dimmed.
Previously the Inventory sidebar item was hidden when Spoolman was
enabled. Now it always shows — when Spoolman is active, /inventory
embeds the Spoolman web UI in the content area via iframe; when
disabled, it renders the internal inventory page as before.
When a printer has pending queue items and is in FINISH/FAILED state
awaiting plate clearing, a yellow dot now appears on the Printers
sidebar icon. Uses useQueries to check statuses of printers with
pending queue items from the existing WebSocket-warmed React Query
cache — no new backend endpoints or additional polling needed.