Browse Source

Hide unnecessary target model selector on "Any" tab (#528)

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.
maziggy 3 months ago
parent
commit
f4f09ccc2d

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
 ## [0.2.1] - Unreleased
 
 ### Fixed
+- **Unnecessary Target Model Selector on "Any" Tab** ([#528](https://github.com/maziggy/bambuddy/issues/528)) — 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 the target model would lead to print failures. The dropdown is now hidden when the sliced model is known (the tab label already shows "Any {model}"). It still appears as a fallback for legacy files without model metadata.
 - **"Clear Plate & Start Next" Button Shown on Printers Without Correct Filament** ([#527](https://github.com/maziggy/bambuddy/issues/527)) — When a print job was queued for "any printer" of a model (e.g., "any H2S"), the "Clear Plate & Start Next" button appeared on ALL printers of that model, including those without the required filament loaded. Clicking it on a printer without the right filament would start a print that fails. The `PrinterQueueWidget` now filters queue items by filament compatibility — it checks the printer's loaded filament types (from AMS and external spools) against the queue item's `required_filament_types` and only shows items the printer can actually print. If no compatible items exist, the widget is hidden.
 - **Manual Spool Weight Overwritten by AMS Auto-Sync** ([#525](https://github.com/maziggy/bambuddy/issues/525)) — When a user manually entered a spool weight (via UI or API), the value was overwritten by the automatic AMS remain% sync that runs on every MQTT update. The AMS remain% is integer-only (~10g resolution for 1kg spool) and can't match precise manual entries. Added a `weight_locked` flag that is automatically set when `weight_used` is explicitly updated via the API. Locked spools are skipped by both the automatic AMS remain% sync and the manual force-sync endpoint. The usage tracker (3MF/gcode delta tracking) is unaffected. Users can re-enable AMS sync by setting `weight_locked: false`.
 - **Inconsistent Print Cost on Reprints** ([#505](https://github.com/maziggy/bambuddy/issues/505)) — Reprinting the same model produced different costs each time (e.g., £0.77, £1.54, £2.03 for the same print). Three independent code paths wrote to `archive.cost` with conflicting strategies: the usage tracker summed ALL historical `SpoolUsageHistory` rows for the archive (including rows from previous reprints), and a separate `add_reprint_cost` method added yet another full print's cost on top. Removed the redundant `add_reprint_cost` path entirely and changed the usage tracker to compute cost only from the current print session's results instead of querying all historical rows. `archive.cost` now always reflects the cost of a single print.

+ 2 - 2
backend/tests/conftest.py

@@ -51,7 +51,7 @@ def event_loop():
     loop = asyncio.get_event_loop_policy().new_event_loop()
     yield loop
     # Drain pending callbacks so aiosqlite threads can finish before loop closes
-    loop.run_until_complete(asyncio.sleep(0.05))
+    loop.run_until_complete(asyncio.sleep(0.1))
     loop.close()
 
 
@@ -94,7 +94,7 @@ async def test_engine():
     # Allow aiosqlite's background thread to finish processing the close
     # response before the per-function event loop shuts down, preventing
     # "RuntimeError: Event loop is closed" in call_soon_threadsafe.
-    await asyncio.sleep(0.05)
+    await asyncio.sleep(0.1)
 
 
 @pytest.fixture

+ 24 - 22
frontend/src/components/PrintModal/PrinterSelector.tsx

@@ -389,28 +389,30 @@ export function PrinterSelector({
       {/* Model selection and location filter (when in model mode) */}
       {assignmentMode === 'model' && modelAssignmentAvailable && (
         <div className="space-y-3 mb-4">
-          {/* Model selector */}
-          <div>
-            <label className="block text-xs text-bambu-gray mb-1">Target Model</label>
-            <select
-              value={targetModel || ''}
-              onChange={(e) => {
-                onTargetModelChange!(e.target.value || null);
-                // Clear location when model changes
-                if (onTargetLocationChange) {
-                  onTargetLocationChange(null);
-                }
-              }}
-              className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none text-sm"
-            >
-              <option value="">Select a model...</option>
-              {uniqueModels.map((model) => (
-                <option key={model} value={model}>
-                  {model}
-                </option>
-              ))}
-            </select>
-          </div>
+          {/* Model selector — only show when sliced model is unknown */}
+          {!slicedForModel && (
+            <div>
+              <label className="block text-xs text-bambu-gray mb-1">Target Model</label>
+              <select
+                value={targetModel || ''}
+                onChange={(e) => {
+                  onTargetModelChange!(e.target.value || null);
+                  // Clear location when model changes
+                  if (onTargetLocationChange) {
+                    onTargetLocationChange(null);
+                  }
+                }}
+                className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none text-sm"
+              >
+                <option value="">Select a model...</option>
+                {uniqueModels.map((model) => (
+                  <option key={model} value={model}>
+                    {model}
+                  </option>
+                ))}
+              </select>
+            </div>
+          )}
 
           {/* Location filter (only show when target model is selected and locations exist) */}
           {targetModel && uniqueLocations.length > 0 && onTargetLocationChange && (

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BXXkRrQK.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-D6vc8KCS.js"></script>
+    <script type="module" crossorigin src="/assets/index-BXXkRrQK.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-1Ts9jjQl.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff