change(workflow): default Plate-Clear Confirmation to off on fresh installs

New users repeatedly reported queued prints "not starting" because the
  confirmation prompt was waiting on an ack they didn't know existed.
  Flip the default in the settings schema and in the frontend fallbacks
  so a missing/unset value reads as disabled. Existing installs keep
  their saved preference.
This commit is contained in:
maziggy
2026-04-14 08:27:00 +02:00
parent 9cc7efdf5f
commit 574b39aee5
6 changed files with 27 additions and 17 deletions
@@ -58,7 +58,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
describe('clear plate button visibility', () => {
it('shows clear plate button when printer state is FINISH', async () => {
render(<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} />);
render(<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} requirePlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
@@ -66,7 +66,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
});
it('shows clear plate button when printer state is FAILED', async () => {
render(<PrinterQueueWidget printerId={1} printerState="FAILED" awaitingPlateClear={true} />);
render(<PrinterQueueWidget printerId={1} printerState="FAILED" awaitingPlateClear={true} requirePlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
@@ -128,7 +128,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
// still awaiting plate-clear ack. The prompt must still show — the ack state, not
// the reported printer state, is the authoritative signal.
it('shows clear plate button in IDLE state when awaitingPlateClear is true (#961)', async () => {
render(<PrinterQueueWidget printerId={1} printerState="IDLE" awaitingPlateClear={true} />);
render(<PrinterQueueWidget printerId={1} printerState="IDLE" awaitingPlateClear={true} requirePlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
@@ -137,7 +137,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
it('shows clear plate button with no printerState when awaitingPlateClear is true', async () => {
// State may be null briefly after a reconnect; the widget must still gate on the flag.
render(<PrinterQueueWidget printerId={1} awaitingPlateClear={true} />);
render(<PrinterQueueWidget printerId={1} awaitingPlateClear={true} requirePlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
@@ -166,7 +166,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
describe('clear plate action', () => {
it('shows confirmation state after clicking clear plate', async () => {
const user = userEvent.setup();
render(<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} />);
render(<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} requirePlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
@@ -192,7 +192,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
);
const user = userEvent.setup();
render(<PrinterQueueWidget printerId={1} printerState="FAILED" awaitingPlateClear={true} />);
render(<PrinterQueueWidget printerId={1} printerState="FAILED" awaitingPlateClear={true} requirePlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
@@ -264,6 +264,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
printerId={1}
printerState="FINISH"
awaitingPlateClear={true}
requirePlateClear={true}
loadedFilamentTypes={new Set(['PLA', 'PETG'])}
/>
);
@@ -281,6 +282,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
printerId={1}
printerState="FINISH"
awaitingPlateClear={true}
requirePlateClear={true}
loadedFilamentTypes={new Set(['PLA'])}
/>
);
@@ -297,7 +299,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
);
render(
<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} />
<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} requirePlateClear={true} />
);
await waitFor(() => {
@@ -378,6 +380,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
printerId={1}
printerState="FINISH"
awaitingPlateClear={true}
requirePlateClear={true}
loadedFilamentTypes={new Set(['PETG'])}
/>
);
@@ -437,6 +440,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
printerId={1}
printerState="FINISH"
awaitingPlateClear={true}
requirePlateClear={true}
loadedFilamentTypes={new Set(['PETG'])}
loadedFilaments={new Set(['PETG:ffffff'])}
/>
@@ -591,12 +595,15 @@ describe('PrinterQueueWidget - Clear Plate', () => {
});
});
it('shows clear plate button when requirePlateClear is not provided (defaults to true)', async () => {
it('shows passive link when requirePlateClear is not provided (defaults to false)', async () => {
render(<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
const link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/queue');
});
expect(screen.queryByText('Clear Plate & Start Next')).not.toBeInTheDocument();
});
it('still shows next item info in passive link when requirePlateClear is false', async () => {
@@ -637,7 +644,7 @@ describe('PrinterQueueWidget - Clear Plate', () => {
http.get('/api/v1/queue/', () => HttpResponse.json(mixedItems)),
);
render(<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} />);
render(<PrinterQueueWidget printerId={1} printerState="FINISH" awaitingPlateClear={true} requirePlateClear={true} />);
await waitFor(() => {
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
@@ -20,7 +20,7 @@ interface PrinterQueueWidgetProps {
loadedFilaments?: Set<string>; // "TYPE:rrggbb" pairs for filament override color matching
}
export function PrinterQueueWidget({ printerId, printerModel, awaitingPlateClear, requirePlateClear = true, loadedFilamentTypes, loadedFilaments }: PrinterQueueWidgetProps) {
export function PrinterQueueWidget({ printerId, printerModel, awaitingPlateClear, requirePlateClear = false, loadedFilamentTypes, loadedFilaments }: PrinterQueueWidgetProps) {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { showToast } = useToast();
+3 -3
View File
@@ -1279,7 +1279,7 @@ function PrinterCard({
onOpenEmbeddedCamera,
checkPrinterFirmware = true,
dryingPresets = DRYING_PRESETS,
requirePlateClear = true,
requirePlateClear = false,
selectionMode = false,
isSelected = false,
onToggleSelect,
@@ -6360,7 +6360,7 @@ export function PrintersPage() {
onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinters(prev => new Map(prev).set(id, { id, name }))}
checkPrinterFirmware={settings?.check_printer_firmware !== false}
dryingPresets={effectiveDryingPresets}
requirePlateClear={settings?.require_plate_clear !== false}
requirePlateClear={settings?.require_plate_clear === true}
selectionMode={selectionMode}
isSelected={selectedPrinterIds.has(printer.id)}
onToggleSelect={toggleSelect}
@@ -6399,7 +6399,7 @@ export function PrintersPage() {
onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinters(prev => new Map(prev).set(id, { id, name }))}
checkPrinterFirmware={settings?.check_printer_firmware !== false}
dryingPresets={effectiveDryingPresets}
requirePlateClear={settings?.require_plate_clear !== false}
requirePlateClear={settings?.require_plate_clear === true}
selectionMode={selectionMode}
isSelected={selectedPrinterIds.has(printer.id)}
onToggleSelect={toggleSelect}
+2 -2
View File
@@ -889,7 +889,7 @@ export function SettingsPage() {
(settings.default_timelapse ?? false) !== (localSettings.default_timelapse ?? false) ||
(settings.stagger_group_size ?? 2) !== (localSettings.stagger_group_size ?? 2) ||
(settings.stagger_interval_minutes ?? 5) !== (localSettings.stagger_interval_minutes ?? 5) ||
(settings.require_plate_clear ?? true) !== (localSettings.require_plate_clear ?? true);
(settings.require_plate_clear ?? false) !== (localSettings.require_plate_clear ?? false);
if (!hasChanges) {
return;
@@ -3730,7 +3730,7 @@ export function SettingsPage() {
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={localSettings.require_plate_clear ?? true}
checked={localSettings.require_plate_clear ?? false}
onChange={(e) => updateSetting('require_plate_clear', e.target.checked)}
className="sr-only peer"
/>