Skip to content

Conversation

@dragm83
Copy link
Contributor

@dragm83 dragm83 commented Nov 6, 2025

Adds descale and backflush cleaning intervals.
Creates utility profiles if they don't exist
Shows notification on the standby page of WebUI (provisions for touch UI code)
Running the profile resets the appropriate interval or it can be manually reset in the settings page

image image image

Summary by CodeRabbit

  • New Features
    • Added automated cleaning schedule management with backflush and descaling task tracking.
    • Notifications alert when maintenance is due.
    • Web interface provides controls to start cleaning cycles and configure maintenance intervals.
    • Displays last performed maintenance times with configurable scheduling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

This PR introduces a new CleaningSchedulePlugin to manage espresso machine maintenance schedules. It adds Settings fields to track backflush intervals, descaling intervals, and last-performed timestamps. The plugin integrates with existing components via WebSocket messages and event subscriptions, allowing the web UI to trigger cleaning actions and display maintenance-due notifications on the machine's display and web interface.

Changes

Cohort / File(s) Change Summary
Core Settings Updates
src/display/core/Settings.h, src/display/core/Settings.cpp
Add four public getter/setter methods for cleaning state (backflushIntervalDays, descalingIntervalWeeks, lastBackflushTime, lastDescalingTime). Initialize fields from preferences in constructor with defaults (14 days, 6 weeks, 0). Persist fields to storage in doSave(). Note: duplicate definitions of setters appear in Settings.cpp.
Plugin Registration
src/display/core/Controller.cpp
Include CleaningSchedulePlugin header and register new instance in Controller::setup().
Cleaning Schedule Plugin
src/display/plugins/CleaningSchedulePlugin.h, src/display/plugins/CleaningSchedulePlugin.cpp
New plugin implementing lifecycle (setup, loop), event handlers for backflush/descaling start and reset, due-state queries (isBackflushDue, isDescalingDue), time-elapsed helpers (getDaysSinceLastBackflush, getWeeksSinceLastDescaling), profile creation (createBackflushProfile, createDescalingProfile), and notification triggering.
WebSocket & Settings Integration
src/display/plugins/WebUIPlugin.cpp
Add WebSocket handlers for cleaning actions (req:cleaning:backflush:start, req:cleaning:descaling:start). Process and persist cleaning fields in settings POST. Include cleaning fields in settings GET response.
DefaultUI Event Subscriptions
src/display/ui/default/DefaultUI.h, src/display/ui/default/DefaultUI.cpp
Add private flags (backflushDue, descalingDue) and subscribe to cleaning events (cleaning:backflush:due, cleaning:descaling:due, cleaning:backflush:timer:reset, cleaning:descaling:timer:reset). Add effect to log cleaning notifications during standby.
Web Frontend: Process Controls
web/src/pages/Home/ProcessControls.jsx
Fetch cleaning settings from /api/settings. Add isBackflushDue and isDescalingDue helpers. Replace static "Machine is ready" text with dynamic maintenance-due buttons (faSoap, faLemon icons) when applicable during standby mode.
Web Frontend: Settings Page
web/src/pages/Settings/index.jsx
Add new "Cleaning Schedule" UI block with interval controls and last-run time displays. Include action buttons to start/complete backflush and descaling via API. Persist cleaning fields in form state. Note: duplicated "Cleaning Schedule" card blocks in JSX.

Sequence Diagram

sequenceDiagram
    participant User
    participant WebUI as Web UI
    participant API as WebUIPlugin
    participant Plugin as CleaningSchedulePlugin
    participant Settings as Settings
    participant DefaultUI as DefaultUI
    participant Display as Machine Display

    User->>WebUI: Configure cleaning intervals
    WebUI->>API: POST /api/settings (intervals)
    API->>Settings: setBackflushIntervalDays, setDescalingIntervalWeeks
    Settings->>Settings: doSave() to storage

    Plugin->>Plugin: loop() runs periodically
    Plugin->>Plugin: isBackflushDue()?
    alt Due
        Plugin->>DefaultUI: emit cleaning:backflush:due
        DefaultUI->>Display: show "Backflush Due" notification
        Display->>User: render button on standby screen
    end

    User->>WebUI: Click "Start Backflush"
    WebUI->>API: WebSocket req:cleaning:backflush:start
    API->>Plugin: trigger backflush event
    Plugin->>Plugin: loadCleaningProfile(BACKFLUSH_PROFILE_ID)
    Plugin->>Plugin: switch to brew mode
    
    Note over Plugin,Settings: Cleaning cycle runs...
    
    Plugin->>Settings: setLastBackflushTime(now)
    Settings->>Settings: doSave() to storage
    Plugin->>DefaultUI: emit cleaning:backflush:timer:reset
    DefaultUI->>Display: clear notification
    Display->>User: return to normal standby
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Profile creation logic: The CleaningSchedulePlugin implements non-trivial profile construction with multi-phase sequences and parameter configuration that requires verification.
  • Event flow integration: Multiple event subscriptions across DefaultUI and WebUIPlugin that coordinate state changes need tracing to ensure correct triggering and state management.
  • Duplicate definitions: Settings.cpp contains duplicate setter definitions; Settings.h and .cpp need alignment verification.
  • Duplicated UI blocks: The web frontend has two identical "Cleaning Schedule" card blocks in Settings/index.jsx that suggest potential copy-paste and should be consolidated.
  • State synchronization: Verify that Settings persistence, plugin state, and web UI state remain consistent across components.

Possibly related PRs

  • PR #479: Modifies Controller::setup() in Controller.cpp and extends WebUIPlugin.cpp for event handling integration—direct code-level overlap with plugin registration and WebSocket message routing.
  • PR #330: Extends Settings handling for new persistent fields and integrates Settings updates in WebUIPlugin—related pattern for adding and propagating device configuration.
  • PR #347: Modifies web UI pages (ProcessControls.jsx and Settings/index.jsx)—direct code overlap on frontend components for displaying maintenance controls.

Suggested reviewers

  • nayrod485

Poem

🐰 Whiskers twitching with glee

A plugin springs forth, so fine,
With backflush schedules and descaling time,
The machine stays clean, no grime to decline,
Events dance 'cross the UI divine,
Maintenance prompts? Why, they're all yours to design! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/cleaning schedule' is generic and vague, using a branch-naming convention rather than describing the specific changes made. Use a more descriptive title that clearly summarizes the main change, such as 'Add cleaning schedule plugin with backflush and descaling intervals' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 6, 2025

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/display/plugins/CleaningSchedulePlugin.cpp (2)

144-244: Consider refactoring to eliminate code duplication.

The backflush profile creation contains significant duplication. Phases 1, 3, 5, 7, and 9 are identical, as are phases 2, 4, 6, and 8. This makes the code verbose and harder to maintain.

Consider refactoring with a helper function:

Profile CleaningSchedulePlugin::createBackflushProfile() const {
    Profile profile{};
    profile.id = BACKFLUSH_PROFILE_ID;
    profile.label = "[Utility] Backflush";
    profile.utility = true;
    profile.description = "";
    profile.temperature = 93;
    profile.type = "standard";
    
    // Helper lambda to create pressurize/depressurize phases
    auto createPhase = [](const char* name, int valve, int pumpSimple) {
        Phase phase{};
        phase.name = name;
        phase.phase = PhaseType::PHASE_TYPE_BREW;
        phase.valve = valve;
        phase.duration = 10.0f;
        phase.pumpIsSimple = true;
        phase.pumpSimple = pumpSimple;
        return phase;
    };
    
    // Add 5 pressurize/depressurize cycles (9 phases total)
    for (int i = 0; i < 5; i++) {
        profile.phases.push_back(createPhase("Pressurize", 1, 100));
        if (i < 4) { // Last cycle has no depressurize phase
            profile.phases.push_back(createPhase("Depressurize", 0, 0));
        }
    }
    
    return profile;
}

This reduces ~100 lines to ~30 while maintaining the same functionality.


390-394: Consider using static_cast for type safety.

The cast from time_t (typically a signed type) to unsigned long works in practice but could be more explicit.

Apply this diff for better type safety:

 unsigned long CleaningSchedulePlugin::getCurrentTimeSeconds() const {
     time_t now;
     time(&now);
-    return (unsigned long)now;
+    return static_cast<unsigned long>(now);
 }

Alternatively, consider keeping the time as time_t throughout and only using it for comparisons, avoiding the cast entirely. However, the current approach works for the use case.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 462db4d and bedbe04.

📒 Files selected for processing (10)
  • src/display/core/Controller.cpp (2 hunks)
  • src/display/core/Settings.cpp (3 hunks)
  • src/display/core/Settings.h (3 hunks)
  • src/display/plugins/CleaningSchedulePlugin.cpp (1 hunks)
  • src/display/plugins/CleaningSchedulePlugin.h (1 hunks)
  • src/display/plugins/WebUIPlugin.cpp (3 hunks)
  • src/display/ui/default/DefaultUI.cpp (2 hunks)
  • src/display/ui/default/DefaultUI.h (1 hunks)
  • web/src/pages/Home/ProcessControls.jsx (6 hunks)
  • web/src/pages/Settings/index.jsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/display/ui/default/DefaultUI.cpp (1)
src/display/core/Controller.h (1)
  • pluginManager (134-134)
src/display/core/Controller.cpp (1)
src/display/core/Controller.h (1)
  • pluginManager (134-134)
src/display/core/Settings.h (1)
src/display/core/Settings.cpp (8)
  • setBackflushIntervalDays (469-472)
  • setBackflushIntervalDays (469-469)
  • setDescalingIntervalWeeks (474-477)
  • setDescalingIntervalWeeks (474-474)
  • setLastBackflushTime (479-482)
  • setLastBackflushTime (479-479)
  • setLastDescalingTime (484-487)
  • setLastDescalingTime (484-484)
src/display/plugins/WebUIPlugin.cpp (2)
src/display/core/Controller.h (1)
  • pluginManager (134-134)
web/src/pages/Home/ProcessControls.jsx (1)
  • settings (179-189)
web/src/pages/Home/ProcessControls.jsx (1)
src/display/plugins/CleaningSchedulePlugin.cpp (8)
  • isBackflushDue (90-103)
  • isBackflushDue (90-90)
  • isDescalingDue (105-118)
  • isDescalingDue (105-105)
  • startBackflush (58-63)
  • startBackflush (58-58)
  • startDescaling (65-70)
  • startDescaling (65-65)
src/display/plugins/CleaningSchedulePlugin.h (4)
src/display/core/Controller.h (2)
  • Controller (21-175)
  • pluginManager (134-134)
src/display/core/PluginManager.h (1)
  • PluginManager (14-33)
src/display/core/Plugin.h (1)
  • Plugin (6-12)
src/display/core/Controller.cpp (4)
  • setup (33-92)
  • setup (33-33)
  • loop (250-333)
  • loop (250-250)
src/display/plugins/CleaningSchedulePlugin.cpp (3)
src/display/plugins/WebUIPlugin.cpp (4)
  • setup (28-60)
  • setup (28-28)
  • loop (62-161)
  • loop (62-62)
src/display/core/Controller.cpp (4)
  • setup (33-92)
  • setup (33-33)
  • loop (250-333)
  • loop (250-250)
src/display/core/Controller.h (2)
  • pluginManager (134-134)
  • profileManager (135-135)
web/src/pages/Settings/index.jsx (2)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/services/ApiService.js (2)
  • ApiServiceContext (202-202)
  • ApiServiceContext (202-202)
🪛 Clang (14.0.6)
src/display/plugins/CleaningSchedulePlugin.h

[error] 4-4: 'display/core/Plugin.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: deploy
🔇 Additional comments (8)
src/display/plugins/CleaningSchedulePlugin.h (1)

1-51: LGTM! Clean plugin header structure.

The header follows the plugin architecture pattern correctly with proper separation of public API and private implementation details. Const correctness is maintained for status query methods, and the interface is well-designed.

src/display/plugins/CleaningSchedulePlugin.cpp (7)

10-11: LGTM!

Static profile ID constants are correctly defined.


13-46: LGTM! Setup method correctly initializes the plugin.

The event listener registrations and initialization sequence are appropriate. The lambda captures using [this] are safe given the plugin's lifecycle.


48-56: LGTM!

The periodic notification check is implemented correctly with proper handling of millis() rollover.


58-88: LGTM!

The cleaning action and timer reset methods are implemented correctly with appropriate logging and event notifications.


246-344: LGTM! Descaling profile is well-structured.

The descaling sequence is logical and clearly defined. While phases 1 and 3 are identical (both 300ml steam flush), extracting them into a helper would provide minimal benefit given they're only duplicated once.


346-388: LGTM! Profile management and timer reset logic are correct.

The helper methods properly handle profile creation, loading, and automatic timer reset upon profile completion.


396-409: LGTM!

The notification event triggering logic correctly checks due status and emits appropriate events for UI updates.

Comment on lines +90 to +118
bool CleaningSchedulePlugin::isBackflushDue() const {
const Settings &settings = controller->getSettings();
int intervalDays = settings.getBackflushIntervalDays();
unsigned long lastBackflushTime = settings.getLastBackflushTime();

if (lastBackflushTime == 0) {
return false; // Never performed, not due yet
}

unsigned long currentTime = getCurrentTimeSeconds();
unsigned long daysSince = (currentTime - lastBackflushTime) / (24 * 60 * 60);

return daysSince >= intervalDays;
}

bool CleaningSchedulePlugin::isDescalingDue() const {
const Settings &settings = controller->getSettings();
int intervalWeeks = settings.getDescalingIntervalWeeks();
unsigned long lastDescalingTime = settings.getLastDescalingTime();

if (lastDescalingTime == 0) {
return false; // Never performed, not due yet
}

unsigned long currentTime = getCurrentTimeSeconds();
unsigned long weeksSince = (currentTime - lastDescalingTime) / (7 * 24 * 60 * 60);

return weeksSince >= intervalWeeks;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add validation for interval values and protect against time going backwards.

Two issues need attention:

  1. Missing interval validation: If intervalDays or intervalWeeks is 0 or negative (via corrupted settings), the due check could behave incorrectly. A zero interval would make maintenance always due (except when lastTime == 0).

  2. Time arithmetic underflow risk: If system time goes backwards (RTC adjustment, NTP sync, clock issues), the calculation (currentTime - lastBackflushTime) / (24 * 60 * 60) will underflow due to unsigned arithmetic, making daysSince extremely large and maintenance appear severely overdue.

Apply these diffs to add guards:

 bool CleaningSchedulePlugin::isBackflushDue() const {
     const Settings &settings = controller->getSettings();
     int intervalDays = settings.getBackflushIntervalDays();
     unsigned long lastBackflushTime = settings.getLastBackflushTime();
     
+    if (intervalDays <= 0) {
+        return false; // Invalid interval
+    }
+    
     if (lastBackflushTime == 0) {
         return false; // Never performed, not due yet
     }
     
     unsigned long currentTime = getCurrentTimeSeconds();
+    if (currentTime < lastBackflushTime) {
+        return false; // Time went backwards, skip check
+    }
+    
     unsigned long daysSince = (currentTime - lastBackflushTime) / (24 * 60 * 60);
     
     return daysSince >= intervalDays;
 }
 bool CleaningSchedulePlugin::isDescalingDue() const {
     const Settings &settings = controller->getSettings();
     int intervalWeeks = settings.getDescalingIntervalWeeks();
     unsigned long lastDescalingTime = settings.getLastDescalingTime();
     
+    if (intervalWeeks <= 0) {
+        return false; // Invalid interval
+    }
+    
     if (lastDescalingTime == 0) {
         return false; // Never performed, not due yet
     }
     
     unsigned long currentTime = getCurrentTimeSeconds();
+    if (currentTime < lastDescalingTime) {
+        return false; // Time went backwards, skip check
+    }
+    
     unsigned long weeksSince = (currentTime - lastDescalingTime) / (7 * 24 * 60 * 60);
     
     return weeksSince >= intervalWeeks;
 }
🤖 Prompt for AI Agents
In src/display/plugins/CleaningSchedulePlugin.cpp around lines 90 to 118,
validate interval values and guard against time going backwards: if intervalDays
or intervalWeeks is <= 0 treat the interval as disabled (return false), and
before subtracting check if currentTime < lastBackflushTime/lastDescalingTime —
if so treat as not due (return false) or compute zero elapsed time; implement
these checks prior to any unsigned subtraction to avoid underflow (or use signed
arithmetic after validation) and then perform the usual division and comparison.

Comment on lines +120 to +142
int CleaningSchedulePlugin::getDaysSinceLastBackflush() const {
const Settings &settings = controller->getSettings();
unsigned long lastBackflushTime = settings.getLastBackflushTime();

if (lastBackflushTime == 0) {
return -1; // Never performed
}

unsigned long currentTime = getCurrentTimeSeconds();
return (currentTime - lastBackflushTime) / (24 * 60 * 60);
}

int CleaningSchedulePlugin::getWeeksSinceLastDescaling() const {
const Settings &settings = controller->getSettings();
unsigned long lastDescalingTime = settings.getLastDescalingTime();

if (lastDescalingTime == 0) {
return -1; // Never performed
}

unsigned long currentTime = getCurrentTimeSeconds();
return (currentTime - lastDescalingTime) / (7 * 24 * 60 * 60);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Protect against time going backwards.

Similar to the due check methods, these functions need protection against time underflow when currentTime < lastTime. Without guards, the unsigned arithmetic will wrap around and return an extremely large (but incorrect) value.

Apply these diffs:

 int CleaningSchedulePlugin::getDaysSinceLastBackflush() const {
     const Settings &settings = controller->getSettings();
     unsigned long lastBackflushTime = settings.getLastBackflushTime();
     
     if (lastBackflushTime == 0) {
         return -1; // Never performed
     }
     
     unsigned long currentTime = getCurrentTimeSeconds();
+    if (currentTime < lastBackflushTime) {
+        return 0; // Time went backwards, return 0 to avoid underflow
+    }
+    
     return (currentTime - lastBackflushTime) / (24 * 60 * 60);
 }
 int CleaningSchedulePlugin::getWeeksSinceLastDescaling() const {
     const Settings &settings = controller->getSettings();
     unsigned long lastDescalingTime = settings.getLastDescalingTime();
     
     if (lastDescalingTime == 0) {
         return -1; // Never performed
     }
     
     unsigned long currentTime = getCurrentTimeSeconds();
+    if (currentTime < lastDescalingTime) {
+        return 0; // Time went backwards, return 0 to avoid underflow
+    }
+    
     return (currentTime - lastDescalingTime) / (7 * 24 * 60 * 60);
 }
🤖 Prompt for AI Agents
In src/display/plugins/CleaningSchedulePlugin.cpp around lines 120 to 142, both
getDaysSinceLastBackflush() and getWeeksSinceLastDescaling() can underflow when
currentTime < lastBackflushTime/lastDescalingTime because of unsigned
arithmetic; add a check after obtaining currentTime that if currentTime <
lastBackflushTime (or lastDescalingTime) return -1 (or otherwise treat as "never
performed"/invalid) before computing the difference and dividing by seconds per
day/week so the functions never wrap to a huge value.

Comment on lines +223 to 233
if (!cleaningSettings.lastBackflushTime) return true;
const daysSince = Math.floor((Date.now() / 1000 - cleaningSettings.lastBackflushTime) / (24 * 60 * 60));
return daysSince > cleaningSettings.backflushIntervalDays;
};

const isDescalingDue = () => {
if (!cleaningSettings.lastDescalingTime) return true;
const weeksSince = Math.floor((Date.now() / 1000 - cleaningSettings.lastDescalingTime) / (7 * 24 * 60 * 60));
return weeksSince > cleaningSettings.descalingIntervalWeeks;
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Match cleaning-due logic to CleaningSchedulePlugin.

Lines [223-232] and [522-564] mark backflush/descaling as “due” even when the machine has never been cleaned (last*Time === 0) and require > instead of >=, so the UI lags a full interval behind the backend (which returns false for “never” and true as soon as the threshold is met). This inconsistency confuses users and makes the two UIs disagree. Align the checks with the plugin.

Apply this diff (and mirror it for descaling):

-  const isBackflushDue = () => {
-    if (!cleaningSettings.lastBackflushTime) return true;
-    const daysSince = Math.floor((Date.now() / 1000 - cleaningSettings.lastBackflushTime) / (24 * 60 * 60));
-    return daysSince > cleaningSettings.backflushIntervalDays;
-  };
+  const isBackflushDue = () => {
+    if (!cleaningSettings.lastBackflushTime) {
+      return false;
+    }
+    const daysSince = Math.floor((Date.now() / 1000 - cleaningSettings.lastBackflushTime) / (24 * 60 * 60));
+    return daysSince >= cleaningSettings.backflushIntervalDays;
+  };

Also applies to: 522-565

Comment on lines +751 to +820
if (confirm('Mark backflush as completed? This will reset the backflush timer.')) {
const newTime = Math.floor(Date.now() / 1000);
const formDataToSubmit = new FormData();
formDataToSubmit.set('lastBackflushTime', newTime.toString());

const response = await fetch('/api/settings', {
method: 'post',
body: formDataToSubmit,
});
const data = await response.json();
setFormData(prev => ({...prev, lastBackflushTime: data.lastBackflushTime}));
}
}}
title='Mark as Complete'
>
<FontAwesomeIcon icon={faCheck} />
</button>
</div>
</div>
<div className='flex items-center justify-between rounded-lg bg-base-200 p-3'>
<div>
<div className='text-sm font-medium'>Last Descaling</div>
<div className={`text-xs ${
formData.lastDescalingTime === 0 ||
Math.floor((Date.now() / 1000 - formData.lastDescalingTime) / (7 * 24 * 60 * 60)) > (formData.descalingIntervalWeeks || 6)
? 'text-orange-600 font-medium'
: 'text-base-content/70'
}`}>
{formData.lastDescalingTime > 0
? (() => {
const weeks = Math.floor((Date.now() / 1000 - formData.lastDescalingTime) / (7 * 24 * 60 * 60));
return weeks === 0 ? 'Today' : `${weeks} weeks ago`;
})()
: 'Never performed'
}
</div>
</div>
<div className='flex gap-2'>
<button
type='button'
className='btn btn-warning btn-sm'
onClick={() => {
apiService.send({ tp: 'req:cleaning:descaling:start' });
}}
title='Start Descaling Now'
>
<FontAwesomeIcon icon={faLemon} />
</button>
<button
type='button'
className='btn btn-success btn-sm'
onClick={async () => {
if (confirm('Mark descaling as completed? This will reset the descaling timer.')) {
const newTime = Math.floor(Date.now() / 1000);
const formDataToSubmit = new FormData();
formDataToSubmit.set('lastDescalingTime', newTime.toString());

const response = await fetch('/api/settings', {
method: 'post',
body: formDataToSubmit,
});
const data = await response.json();
setFormData(prev => ({...prev, lastDescalingTime: data.lastDescalingTime}));
}
}}
title='Mark as Complete'
>
<FontAwesomeIcon icon={faCheck} />
</button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Avoid clobbering unrelated settings when marking cleaning complete.

Lines [751-819] post only lastBackflushTime/lastDescalingTime by constructing a fresh FormData() that omits every other field. The backend interprets missing args as unchecked checkboxes, so this request forces HomeKit, boiler fill, auto-wakeup, etc. to false. Users who click “Mark as Complete” will silently lose all boolean settings—a critical regression. Reuse the existing form payload (or hit a dedicated endpoint) so the POST preserves untouched fields.

Apply this diff (and mirror it in the descaling handler):

-const formDataToSubmit = new FormData();
-formDataToSubmit.set('lastBackflushTime', newTime.toString());
+const formDataToSubmit = new FormData(formRef.current);
+formDataToSubmit.set('lastBackflushTime', newTime.toString());

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant