Skip to content

feat(ev): charge now at a chosen current, and boost from the house battery - #60

Merged
frahlg merged 1 commit into
mainfrom
58-charge-now-amps-and-boost
Sep 4, 2026
Merged

feat(ev): charge now at a chosen current, and boost from the house battery#60
frahlg merged 1 commit into
mainfrom
58-charge-now-amps-and-boost

Conversation

@frahlg

@frahlg frahlg commented Sep 4, 2026

Copy link
Copy Markdown
Member

What changed

  • Charge now takes a current. The EV panel draws a slider between the charger's floor and ceiling in whole amps — from min_charge_w, max_charge_w, phases and voltage_v on /api/loadpoints — with the readout "N A · X.X kW". Charge now sends loadpoint.hold with power_w for the chosen amps, hold_s: 0 and phase_mode "1p"/"3p": the same body the box's own page posts to manual_hold. The top of the slider sends exactly max_charge_w (11 000 W rounds to 16 A, and 16 A back would be 11 040 W).
  • A running hold shows its current (manual_charge_w) and offers Update (re-send at the thumb's amps; disabled while nothing changed) and Stop charging. The copy says the hold runs until the car is full, Stop, or an unplug (fix(ev): manual charge runs until the car is full ftw#1056).
  • Battery boost. An owner with a plugged car can start loadpoint.boost with a house-battery reserve (min_battery_soc_pct, default 30 %) and a duration (duration_s: 30 min, 1 h, 2 h, 4 h — the box caps a lease at four hours). While it runs the panel says the reserve and end time from battery_boost.min_battery_soc and expires_at_ms, and offers Stop boost (cancel: true). Afterwards it says why the box stopped it, from battery_boost.stop_reason, in the app's words.
  • The boost offer says up front when the box would refuse — a manual hold running, or spare-solar-only on — from state the box served; that is the box's own preflight and what its page does. A boost the box refuses anyway (E_UNAVAILABLE naming the op) and a lease it will not take (E_UNKNOWN_OP naming lease) get their own sentences instead of "can't reach the charger".
  • The simulator now serves manual_charge_w and the box's three-state battery_boost status, validates a lease by the box's rules, refuses one under a hold, and withdraws a boost when a hold starts (operator_hold), so tests and the demo agree with the box.
  • No new names. Every op and field above is in contract/registry.yaml or is a body field of an op already there; registry.yaml is untouched.

Why

The client asked for full power on every Charge now while the box's own page offers a current, and it drew nothing for a boost the box implements. Both gaps came out of a review of the client against the box today (#58).

Verification

  • npm run verify: svelte-check 0 errors, 940 tests pass (1 skipped, pre-existing), production build ok.
  • New tests:
    • src/lib/format/ev.test.ts — the amp range, the cap at the ceiling, the box page's fallbacks, a single-phase charger, the hold read back in amps, the boost sentences and stop reasons.
    • src/lib/state/loadpoints.test.ts — the exact cmd bodies for a hold at 10 A and for a boost, stopping a boost, a hold ending a boost with operator_hold, and the refusal sentences.
    • src/views/EvPanel.svelte.test.ts — slider defaults, charging at the chosen amps with the 1 Hz stream agreeing, Update, boost start and stop with the stop reason, no boost offer under a hold, and a viewer seeing neither control.
  • Reviewed in Chrome against the dev simulator at a phone viewport: slider and readout, Charge now → Update/Stop, the boost editor, the active sentence with reserve and end time, and the stop reason after Stop boost. One layout fix came out of it (the row label wrapped). The browser pane was hidden for part of the run, so the later states were checked through the live DOM rather than a rendered frame.

Notes for the reviewer:

  • phases and voltage_v are omitempty on the box. When absent the panel falls back to 3 × 230 V and 6–16 A, as the box page does.
  • The protocol carries no default reserve; 30 % is the box page's own default, so a household sees the same number on both surfaces. The task brief suggested 20 % as the fallback — say so if you prefer it.
  • The stop reason is the box's terminal status and stays until the next boost, so "The last boost ended because it was stopped by hand." lingers the way "Last stop" does on the box page.
  • feat(app): native-feel pass #55 also touches EvPanel.svelte (two style lines: scrim and z-index tokens). These hunks do not overlap it.

Closes #58

🤖 Generated with Claude Code


Note

Medium Risk
New dispatch commands (OP_LOADPOINT_HOLD with different payloads, OP_LOADPOINT_BOOST) affect live charging and house-battery use; simulator alignment reduces but does not remove box-side edge-case risk.

Overview
Charge now is no longer fixed at max power: owners get an amp slider (from charger limits on /api/loadpoints), sends loadpoint.hold with power_w, hold_s: 0, and phase_mode, and can Update or Stop an active hold. Battery boost adds loadpoint.boost with reserve % and duration (up to 4 h), Stop boost, and copy for active/stopped state plus boost-specific command errors via boostHelp.

LoadpointsStore generalizes command lifecycle per control (hold / boost); ev.ts decodes richer wire fields and amp/boost formatting; the sim mirrors box boost lease rules (hold cancels boost, refusals). EvPanel wires the UI; tests cover formatting, store commands, and panel behavior.

Reviewed by Cursor Bugbot for commit 3a0e33d. Bugbot is set up for automated code reviews on this repo. Configure here.

…ttery

Charge now sends the slider's amps as watts — the box page's own body:
power_w, hold_s 0, phase_mode from the phase count — instead of the
charger's ceiling every time. A running hold shows its current and offers
Update and Stop. The battery boost the registry and the box already carry
gets a control: a house-battery reserve and a duration, Stop boost while
it runs, and the box's stop reason afterwards.

Refs #58

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 4, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ftw-webapp 3a0e33d Commit Preview URL Sep 04 2026, 06:10 AM

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T06:17:15.130480Z 3a0e33d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@frahlg
frahlg merged commit db53e04 into main Sep 4, 2026
6 checks passed
@frahlg
frahlg deleted the 58-charge-now-amps-and-boost branch September 4, 2026 06:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a0e33d6f7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/sim/box.ts
Comment on lines +815 to +816
this.#evBoost = { expiresAtMs, minBatterySoc }
this.#evBoostStop = null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply active boosts to the simulated power flow

When a demo user starts a boost while the car is plugged in but outside the simulator's normal 17:00–19:30 charging window, this records an active lease and reports success, but SimBox.#sample() only applies #evHold and never consults #evBoost. Consequently EV power remains zero and the battery does not discharge even though the panel says the battery is charging the car. The boost state needs to affect the simulated reading, including the reserve constraint, rather than only the loadpoint status.

Useful? React with 👍 / 👎.

Comment thread src/lib/format/ev.ts
Comment on lines +261 to +262
let maxA = toA(lp.maxChargeW) || 16
if (maxA <= minA) maxA = minA + 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve a single-value charging range

When a charger reports equal minimum and maximum power, or both bounds round to the same whole amp, this invents an additional current above the reported ceiling. Since ampsToWatts() then caps that position back to maxChargeW, the two slider positions submit the same wattage while the UI labels one as a higher current and may enable an Update that changes nothing. Keep the collapsed range instead of manufacturing minA + 1.

Useful? React with 👍 / 👎.

Comment thread src/views/EvPanel.svelte
</button>
{/if}
</div>
{@render outcome('boost')}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope boost outcomes to the affected loadpoint

In a household with multiple plugged-in chargers, this render occurs once for every inactive charger, while store.command records only the control type and not the loadpoint ID. Starting, stopping, or failing a boost on one charger therefore displays the same outcome beneath every other charger's boost control as well, falsely implying that those chargers were changed. Carry the target loadpoint ID in the command state and filter the rendered outcome by it.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3a0e33d. Configure here.

Comment thread src/views/EvPanel.svelte
// The editor closes on the box's yes alone; a refusal stays on screen
// under the values that were refused.
if (store.command.kind === 'applied') boostDraft = null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Boost editor ignores unconfirmed success

Medium Severity

startBoost treats only applied as the box accepting the lease, so an unconfirmed result leaves boostDraft in place. After that boost later ends, the start form reopens on its own with the old reserve and duration still filled in.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3a0e33d. Configure here.

Comment thread src/views/EvPanel.svelte
Ends when the time is up, the house battery reaches the reserve, or you stop it.
</p>
{@render outcome('boost')}
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hold does not close boost editor

Low Severity

The closed boost offer hides itself when a hold is running, but an already-open editor does not. After Charge now, Start boost stays on screen and still sends loadpoint.boost, which the box refuses.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3a0e33d. Configure here.

@cursor
cursor Bot requested review from Leitet and davmoz September 4, 2026 06:20

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Left a non-blocking comment; not approving. Cursor Bugbot reported two unresolved findings on the EV charge/boost controls, so this needs human review. Assigned Leitet and davmoz.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

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.

EV panel: charge now at a chosen current, and battery boost

1 participant