feat(dashboard): Brew-mode quick-adjust steppers + over-pressure alert#712
feat(dashboard): Brew-mode quick-adjust steppers + over-pressure alert#712FR4NKLN wants to merge 6 commits into
Conversation
…e after brew Two coupled changes that back the new web dashboard "quick adjust" +/- stepper for Brew mode. WebUIPlugin::handleWebSocketData Add handlers for `req:raise-brew-target` and `req:lower-brew-target`. The web client has been sending these messages via its existing `raiseTarget()` / `lowerTarget()` callbacks (mirroring how the Grind +/- buttons already work), but the firmware had no matching handler so the messages were silently dropped. The new handlers call `Controller::raiseBrewTarget` / `lowerBrewTarget` — the exact same methods the device's gear-icon BrewScreen invokes — so behaviour is identical whether the user adjusts from the web UI or the on-device display. Controller::deactivate After a brew finishes, reload the selected profile from disk. Any in-memory target temp / target weight adjustments made via the dashboard stepper or the display's gear-icon BrewScreen Settings panel revert back to the profile's on-disk values. This is what makes the steppers behave as transient "for this next brew only" adjustments rather than silent, persistent modifications to the active profile. For permanent changes the user goes to the Profiles page (web) or hits Save on the display's BrewScreen Settings panel.
Two related additions to the Process Controls card on the Dashboard,
applied to both the full `ProcessControls` and the responsive
`CompactProcessControls` so behaviour is consistent across desktop /
tablet / phone viewports.
Brew-mode +/- steppers (TEMP + WEIGHT)
When in Brew mode with no process running, show TEMP and TARGET
WEIGHT steppers under the profile chart (full view) or under the
profile/toggle block (compact view). Each tap calls the same
Controller method (`raiseTemp` / `lowerTemp` / `raiseBrewTarget` /
`lowerBrewTarget`) that the device's gear-icon BrewScreen invokes,
so the web and the on-device display behave identically. The weight
stepper appears only when the volumetric scale is connected and the
profile has a weight target (matches the existing condition used by
the header weight metric).
These are intentionally transient "next-brew-only" adjustments,
not a profile editor: the firmware reverts the in-memory profile to
its saved on-disk values after each brew completes (see the matching
Controller::deactivate change). The Profiles page remains the place
for permanent edits. This mirrors the existing display-side gear
icon flow where users can tweak a shot without dirtying the saved
profile.
Compact view uses a slimmer inline `MiniStepper` so two steppers
fit side-by-side in the narrow card without wrapping; the full view
uses the standard styling consistent with the existing Steam / Water
temp steppers.
Over-pressure red alert
The current pressure value in the metric header turns text-error
(bright red, semibold) when above 1.0 bar in Brew mode. This is a
visual cue that the boiler/group is still pressurised from a prior
shot or heat-up cycle and should be released — typically by a
short flush or by opening the steam wand — before the next brew
is started.
Two reasons this matters and why it's worth flagging proactively:
1. Pressure-aware profiles depend on a clean baseline. Adaptive /
pressure-driven profiles read the current pressure to drive
phase transitions, predictive delay, and decline behaviour. If
residual pressure is already above the profile's preinfusion or
comparison thresholds at process start, those phases can advance
incorrectly or skip altogether, producing a shot that doesn't
follow the intended curve.
2. Brewing into a back-pressurised group head is a common physical
cause of channeling, blown pucks, and uneven extraction —
especially on the first shot of a session or after steaming.
The colour change is intentionally localised to the *current*
pressure value only: the target value and the unit stay normal
colour so the visual change is precise (it draws attention to the
reading itself, not the whole metric block), and it only triggers
in Brew mode — >1 bar in Steam mode is normal and expected.
The responsive switch between `CompactProcessControls` and the full `ProcessControls` in `Home/index.jsx` uses `landscape:hmd:...` and `portrait:md:...` Tailwind variants. The portrait classes were misspelled `potrait:md:hidden` and `potrait:md:contents` (introduced in e95b844 "fix: Fix some responsive things"). Tailwind silently ignores undefined variants, so these classes never applied. Result: in portrait orientation, `ProcessControls` was hidden by its base `hidden` class and `CompactProcessControls` was shown instead — at every width, even on a tablet in portrait where the full layout would fit comfortably (iPad portrait, tablet split-screen, resized desktop window in portrait aspect, etc.). Renaming `potrait` -> `portrait` activates the intended responsive behaviour: at portrait + md width (>= 768px) the full `ProcessControls` renders; below that, `CompactProcessControls` does, matching what was originally intended.
|
We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds compact and full UI steppers for brew-mode TEMP/WEIGHT/TIME adjustments, routes two new WebSocket brew-target messages to the Controller, reloads the selected profile after a brew completes, and applies conditional pressure error styling and a responsive classname fix. ChangesBrew-mode Idle Control Flow
Sequence Diagram(s)sequenceDiagram
participant WebClient
participant WebUIPlugin
participant Controller
participant ProfileManager
WebClient->>WebUIPlugin: req:raise-brew-target / req:lower-brew-target
WebUIPlugin->>Controller: raiseBrewTarget() / lowerBrewTarget()
Note over Controller: brew completes
Controller->>Controller: deactivate()
Controller->>ProfileManager: loadSelectedProfile()
ProfileManager-->>Controller: persisted profile
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…pressure trigger Refines the Brew-mode quick-adjust UI to mirror the device's gear-icon BrewScreen Settings panel exactly, and narrows the over-pressure red alert to the state where it is actually actionable. ### Morphing TARGET WEIGHT / TARGET TIME stepper Previously the Brew-mode card showed a fixed TEMP + TARGET WEIGHT pair. When no scale was connected or the profile wasn't volumetric, the weight stepper was hidden and the second slot collapsed — leaving a lone TEMP stepper and no way to surface the time-based target. The second slot now morphs based on the firmware status flag \`bt = isVolumetricAvailable && profile.isVolumetric\`: - \`bt=true\` → label "TARGET WEIGHT", value \`Xg\`, +/- adjusts volumetric - \`bt=false\` → label "TARGET TIME", value \`M:SS\`, +/- adjusts duration Both modes send the same \`req:raise-brew-target\` / \`req:lower-brew-target\` WS messages; \`Controller::raiseBrewTarget\` / \`lowerBrewTarget\` decide internally whether to mutate the profile's volumetric value or its phase durations based on the same condition. Format is the only visible difference. This is the exact logic the display's BrewScreen Settings panel already uses. Added a \`fmtDuration(seconds)\` helper alongside \`fmtElapsed(ms)\` in \`utils.js\` since the firmware \`btd\` field reports seconds whereas \`processInfo.e\` reports milliseconds. Applied symmetrically to \`ProcessControls\` (full layout) and \`CompactProcessControls\` (via the existing \`MiniStepper\` component). ### Tightened over-pressure red alert The red \`text-error\` styling on the current pressure value now requires \`brew && !active && !finished\`. Original behaviour fired red whenever pressure > 1 bar in Brew mode, including during the shot itself — where 8+ bar is the expected state and the red noise was actively misleading. Narrowed to the only state where the cue is actionable: sitting idle in Brew mode before the next shot, with residual boiler pressure that should be released by flushing or opening the steam wand. During an active brew and immediately after (while the user reviews the post-brew stats), the value renders in normal colour regardless of pressure; the cue returns automatically once the finished view is cleared and the card is back to idle.
|
We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added. |
…ET WEIGHT/TIME) CompactProcessControls' MiniStepper for the morphing second slot was labelled bare \`WEIGHT\` / \`TIME\`, which reads like a current measurement rather than the profile-driven stop condition the +/- buttons actually mutate. ProcessControls (full layout) already uses \`TARGET WEIGHT\` / \`TARGET TIME\` for the same control. Aligned the Compact label set so both layouts describe the same thing the same way, and the user can tell at a glance that the value below the label is the brew-stopping condition for the current profile + scale state — not a live reading of cup weight or elapsed shot time. \`TEMP\` (vs full layout's \`TEMPERATURE\`) kept short since temperature has no read/target ambiguity at this position in the card.
|
We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added. |
Why "TARGET" rather than just "weight" or "time"The value is the setpoint the firmware is aiming for, not a guarantee of brew outcome. Under common conditions on certain profile shapes, the actual cup weight or brew time can land dramatically far from the value shown. Two concrete cases that come up in normal use:
type: "standard" profiles (e.g., the bundled 9 Bar Espresso) are the structural exception: Phase::isFinished has an early-return at profile.h:109-111 that makes the volumetric target the only escape (besides a 5-minute safety cap). Standard profiles do stop at the target weight reliably. Worth flagging for the maintainer concerning uniformity and intention |
…o-revert The auto-revert added in d02a961 passed the already-populated in-memory selectedProfile by reference into loadProfile / parseProfile. parseProfile appends to profile.phases rather than replacing, so every brew completion duplicated the phase set. Reset the selectedProfile in place before reload (p = Profile{}), mirroring the pattern ProfileManager::selectProfile already uses.
|
We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added. |
|
|
@cla-bot check |
|
The cla-bot has been summoned, and re-checked this pull request! |



Summary
Adds a Brew-mode quick-adjust workflow to the web Dashboard plus a visual cue when the boiler is still pressurised before a shot. Behaviour is consistent with the device's on-screen gear-icon BrewScreen Settings panel — no firmware semantics change beyond wiring up two missing WebSocket handlers.
1. Brew-mode +/- steppers (TEMP + WEIGHT/TIME)
When in Brew mode with no process running, the Process Controls card shows a TEMP stepper plus a second slot that morphs based on the firmware status flag
bt = isVolumetricAvailable && profile.isVolumetric:bt=true→ label TARGET WEIGHT, valueXg,+/−adjusts the profile's volumetric targetbt=false→ label TARGET TIME, valueM:SS,+/−adjusts the profile's phase durationBoth modes call the same
req:raise-brew-target/req:lower-brew-targetWS messages;Controller::raiseBrewTarget/lowerBrewTargetdecide internally whether to adjust the profile's volumetric value or its phase durations based on the same condition. This is the exact logic the device's gear-icon BrewScreen Settings panel already uses — users get consistent affordances across both interfaces, and time-based profiles (or any brew without a connected scale) are no longer second-class citizens in the dashboard.These are transient "next-brew-only" adjustments, not a profile editor. The firmware reloads the selected profile from disk after each brew completes (
Controller::deactivate), so values revert. Permanent edits still go through the Profiles page.Implemented in both
ProcessControls(full layout) andCompactProcessControls(responsive layout, slimmerMiniStepperso two fit side-by-side).2. Over-pressure red alert (idle only)
In Brew mode, the current pressure value turns
text-error(red, semibold) when above 1.0 bar and the brew is idle — i.e. no shot active and no finished view shown. The cue is suppressed during the brew itself and the immediate post-brew finished view, since 8+ bar is the expected state during the shot and pressure takes a moment to bleed off after. The cue returns automatically once the user clears the finished view and the card is back to idle.Visual purpose: warn the user that the boiler/group is still pressurised from a prior shot or heat-up before starting the next brew, without adding red noise during the periods when high pressure is normal.
Two reasons this matters:
Localised to the current pressure reading only (target + unit stay normal colour), and only triggers in Brew mode (>1 bar is normal in Steam).
3. Bonus fix:
potraittypo in responsive variantWhile working on the dashboard I noticed
Home/index.jsxhaspotrait:md:hiddenandpotrait:md:contents. Tailwind silently ignores undefined variants, so the portrait switch between Compact and full layouts never worked — Compact was always shown in portrait orientation, even on tablets at widths where the full layout fits comfortably. Renamed toportrait:so the intended responsive behaviour activates.Firmware changes (minimal)
WebUIPlugin::handleWebSocketData: addreq:raise-brew-target/req:lower-brew-targethandlers. The web client already sent these via the existingraiseTarget()callback (the Grind +/- buttons use the same pattern) but firmware had no matching handler — they were silently dropped.Controller::deactivate: reload the selected profile from disk on brew end, so stepper adjustments are transient.Test plan
−/+adjusts TEMP — value updates immediatelyTARGET WEIGHT,−/+adjusts weightTARGET TIME(M:SS),−/+adjusts durationProcessControlsrenders (not Compact)MiniStepperSummary by CodeRabbit
New Features
Bug Fixes
Improvements