diff --git a/CHANGELOG.md b/CHANGELOG.md index a7114d997..4b1081790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ All notable changes to Bambuddy will be documented in this file. ## [1.2.5.1] - 2026-07-27 ### Fixed +- **A rejected K-profile write is now reported as rejected (#2718, reporter @jmoore-skild)** — Saving a K-profile was fire-and-forget: Bambuddy published the command and reported success the moment the bytes left the process. The printer does answer, and the answer was received, matched, and thrown away at debug level — so a write the printer refused for a real reason still told you it was saved. The complication was that the answer itself was wrong: on single-nozzle printers it came back `result: "fail", reason: "invalid tray_id"` on writes that demonstrably applied, which made gating on it look impossible. Measuring against an X1C and an H2D found the cause — the `tray_id: -1` Bambuddy itself put in the payload. The X1C's firmware validates that field and rejects the value while applying the write anyway; the H2D ignores it. Sending `0`, as BambuStudio does, makes the acknowledgement honest, and the printer echoes back the sequence number we sent, so it can be matched to the write that caused it. Saving or deleting a profile now waits for that answer and surfaces a genuine rejection as an error instead of a success toast. A printer that stays silent is still treated as success — no answer is not evidence of refusal. The acknowledgement is also logged at INFO now, so it appears in a support bundle. Covered by backend tests. +- **The K-profile flow type is a real choice again** — On most printers the calibration table comes back with no nozzle identity at all, and Bambuddy had started showing "Not reported by printer" in the Flow Type field as a result. That is not a value you can save, and it isn't what the slicer does: BambuStudio treats a missing nozzle identity as **Standard** and leaves the choice editable. Bambuddy now does the same. The field is hidden only on models sold with a single nozzle variant — the A1, A1 Mini and A2L — using the same rule the slicer applies. This is not the single-versus-dual-nozzle split: the P1P, P1S, P2S, X1, X1 Carbon, X1E and H2S are all single-nozzle and all offer both flows. Editing a profile also no longer strips the nozzle identity from what it writes back. - **Dialogs no longer act after they have closed** — The AMS slot configuration and K-Profile dialogs hold their success state briefly and then close themselves, between 1.5 and 4 seconds after the command is sent so the printer has time to process it. That timer ran whether or not the dialog was still open, so dismissing it — or the printer card refreshing underneath it — within that window left a pending close that fired later, dismissing whatever dialog happened to be open by then. The deferred close is now cancelled when the dialog goes away. Covered by frontend tests. - **A printer with no K-profiles can now be given its first one (#2719, reporter @jmoore-skild)** — **Add K-Profile** built its Filament dropdown out of the profiles already on the printer, so on a printer with none the field was empty, required, and impossible to satisfy — the modal even said so, telling you to go and create the profile in Bambu Studio instead. The filament picker is now populated the way every other one in Bambuddy is, in the same order: **Imported** presets first, then **Orca Cloud**, then **Bambu Cloud**, then Bambuddy's built-in Bambu filament table. That last tier is compiled in, so the list is never empty — a brand-new printer with no cloud account and nothing imported still gets you a profile. The per-printer-model copies a cloud account carries ("Bambu PLA Basic" once for the X1C, once for the P1S, once for the A1) are collapsed into a single row, and the built-in table — a static copy of the same Bambu catalogue — no longer echoes back filaments the groups above already list. Your imported and Orca Cloud libraries are both shown in full even where they overlap by name, because they are usually the same profiles reached two ways and each group is worth seeing under its own heading. The picker is a searchable list with the source heading shown as a real, legible group header — a native dropdown can't do that, since browsers render the group label of a ` f.id === filamentChoice); if (selectedFilament) { - const flowLabel = newNozzleType === 'HH00' ? 'HF' : 'S'; + const flowLabel = newNozzleType === HIGH_FLOW ? 'HF' : 'S'; setName(`${flowLabel} ${selectedFilament.name}`); } } @@ -604,14 +615,8 @@ function KProfileModal({ disabled={!!profile} 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 ${profile ? 'opacity-60 cursor-not-allowed' : ''}`} > - {/* Only reachable when editing a profile the printer - reported without a nozzle_id — the select is disabled - there, so this is a readout, not a choice. */} - {nozzleType === '' && ( - - )} - - + +
@@ -993,21 +998,6 @@ export function KProfilesView() { return builtinFilamentMap.get(profile.filament_id) || extractFilamentName(profile.name); }, [builtinFilamentMap]); - // Whether the printer reports a nozzle_id at all. Single-nozzle models omit - // it from every extrusion_cali_get entry (#1748), so a flow-type filter there - // could only ever match nothing — hide it instead of offering a control that - // silently empties the list. - const hasFlowTypeInfo = React.useMemo( - () => (kprofiles?.profiles ?? []).some((p) => getFlowTypeLabel(p.nozzle_id) !== ''), - [kprofiles?.profiles] - ); - - // Don't strand the list behind a filter whose control just disappeared - // (switching printers, or a refetch that no longer carries nozzle ids). - useEffect(() => { - if (!hasFlowTypeInfo) setFlowTypeFilter('all'); - }, [hasFlowTypeInfo]); - // Filter and sort profiles // Note: nozzle diameter filtering is done server-side via MQTT request const filteredProfiles = React.useMemo(() => { @@ -1054,6 +1044,18 @@ export function KProfilesView() { const selectedPrinterData = printers?.find((p) => p.id === selectedPrinter); const isDualNozzle = selectedPrinterData?.nozzle_count === 2; + // Whether this printer model is sold with both Standard and High Flow + // nozzles. Comes from the model, not from whether the payload happened to + // carry a nozzle_id — most printers omit that field entirely (#1748) while + // still offering both flows. Only the A-series has a single variant. + const supportsFlowType = selectedPrinterData?.supports_nozzle_flow_type ?? true; + + // Don't strand the list behind a filter whose control just disappeared. + useEffect(() => { + if (!supportsFlowType) setFlowTypeFilter('all'); + }, [supportsFlowType]); + + // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -1145,10 +1147,10 @@ export function KProfilesView() { name: p.name, k_value: parseFloat(p.k_value).toFixed(6), filament_id: p.filament_id, - // Keep an absent nozzle_id absent. Exports from single-nozzle - // printers carry none (#1748), and HH00 vs HS00 is a coin flip - // we'd be writing to the printer as if it were fact. - nozzle_id: p.nozzle_id || '', + // An export from a printer that reports no nozzle_id carries + // none; fall back to Standard, the same default the slicer's + // parser uses for a missing field. + nozzle_id: p.nozzle_id || `${STANDARD_FLOW}-${nozzleDiameter}`, nozzle_diameter: p.nozzle_diameter || nozzleDiameter, extruder_id: p.extruder_id ?? 0, slot_id: 0, // Always create new @@ -1394,7 +1396,7 @@ export function KProfilesView() {
)} - {hasFlowTypeInfo && ( + {supportsFlowType && (
without a
`);let s=e.getAttribute(`formaction`)||o.getAttribute(`action`);if(r=s?hn(s,t):null,n=e.getAttribute(`formmethod`)||o.getAttribute(`method`)||zr,i=Qr(e.getAttribute(`formenctype`))||Qr(o.getAttribute(`enctype`))||Br,a=new FormData(o,e),!Xr()){let{name:t,type:n,value:r}=e;if(n===`image`){let e=t?`${t}.`:``;a.append(`${e}x`,`0`),a.append(`${e}y`,`0`)}else t&&a.append(t,r)}}else if(Vr(e))throw Error(`Cannot submit element that is not ,