feat(ev): the charger behind its bubble, and a token gate both surfaces share - #19
Conversation
…es share Round one of bringing the on-box experience into the app: read-only, no box changes, everything a fact the box served. - Tap the EV bubble on Now and a sheet answers the question a thumb brings: what flows now, what this session delivered, what the schedule says (on the kitchen clock, never UTC), and when the optimiser intends to charge next — folded from /api/mpc/plan into windows a person reads. - GET /api/loadpoints and GET /api/mpc/plan ride the passthrough at Read, exactly as the box prices them. The plan is decoration on the panel: a box too old to serve it still shows its charger, with the missing windows said plainly rather than drawn empty. - The panel re-asks each minute while open, the same epoch rule History and Energy follow, and heals through drops with askWhenLive. - The simulator grows the charger the real box has — same generator as the stream, snake_case as the wire, tiers as the box prices them, and a car that honestly does not know its own charge. - scripts/check-token-drift.mjs holds the "kept in step" promise the tokens file has made since it was written: every custom property a vendored component reads must resolve identically in this repo's tokens.css and the box's theme.css, both themes, in CI beside the registry check. It found three missing aliases on its first run — --card-pad, --card-pad-tight, --stat-warn — now pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
ftw-webapp | db0a3f9 | Aug 09 2026, 03:21 PM |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db0a3f9786
ℹ️ 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".
| // The one bubble that opens something: the charger's sheet. The hero | ||
| // makes a clickable planet a button with a name, so the tap target | ||
| // and its announcement come from the component, not from here. | ||
| clickable: true, |
There was a problem hiding this comment.
Disable the EV button until passthrough is available
When the app is showing cached fields, or the connected box lacks api.passthrough, an EV_W field still makes this planet a focusable button announced as “Open EV charger,” while Now.svelte refuses to open the panel unless that capability is present. Cached snapshots deliberately do not restore capabilities, so this produces a dead control during cold starts and indefinitely while offline; derive clickable from the capability gate rather than setting it unconditionally.
Useful? React with 👍 / 👎.
| function clock(ms: number): string { | ||
| return new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }) | ||
| } |
There was a problem hiding this comment.
Include the day in planned charging windows
When a plan contains windows on multiple dates, this formatter discards the date and makes later-day windows appear to occur earlier on the same day. The checked simulator's 24-hour plan demonstrates this at 18:30: the panel can list today's 18:30–19:30 followed by tomorrow's 17:00–18:30 with no indication that the second window is tomorrow, so the displayed charging plan is ambiguous and potentially misleading.
Useful? React with 👍 / 👎.
| is a dialog, so what is behind it is inert to a screen reader. --> | ||
| <div class="backdrop" onclick={onclose} aria-hidden="true"></div> | ||
|
|
||
| <section class="sheet" role="dialog" aria-modal="true" aria-label="EV charger"> |
There was a problem hiding this comment.
Move keyboard focus into the modal dialog
When the sheet is opened with a keyboard, focus remains on the EV button behind it because nothing focuses the dialog or its Close button; aria-modal alone neither moves focus nor makes sibling DOM inert. Keyboard users therefore start outside the advertised modal and can navigate or activate controls behind the backdrop, so opening should move focus into a trapped/inert modal and closing should restore it.
Useful? React with 👍 / 👎.
Round one of the reuse programme: the flagship EV surface, read-only, app-only. No box changes — both routes it reads (
GET /api/loadpoints,GET /api/mpc/plan) are already priced Read on the box's master, verified there and against a live box.What a thumb gets
Tap the EV bubble on Now and a sheet opens with what the box knows: charging now and at what power, what this session has delivered, the schedule on the kitchen clock ("Ready by 08:00 · every day" — the wire's UTC minutes converted at the last moment, nowhere else), and when the optimiser intends to charge next, folded from the MPC plan's quarter-hour slots into windows a person reads ("19:00–21:30 · up to 7.2 kW").
Honesty rules carried through:
/api/mpc/planstill shows its charger, and the missing windows are said plainly ("Charging times aren't readable right now") rather than drawn as an idle week.askWhenLive. The wire's own state stays the freshness band's sentence; the sheet neither repeats it nor clears what it read.api.passthrough; the bubble itself already appears only when the box reports the field.The simulator grows the charger the real box has
Same generator as the 1 Hz stream — the panel's "charging at 7.2 kW" and the hero's bubble read one household. Snake_case as the wire marshals it, tiers exactly as the box prices them (reads Read, every write Actuate and refused over the passthrough — the refusal round 2 exists to lift), and a car that honestly does not know its own charge.
The token gate
src/styles/tokens.csshas claimed since it was written that it is kept in step with the box'stheme.css, and nothing held that claim. Nowscripts/check-token-drift.mjsdoes, in CI beside the registry check: every custom property a vendored component reads must resolve to the same value in both files, both themes. Proven to fail on a genuinely drifted value; found three missing aliases on its first run (--card-pad,--card-pad-tight,--stat-warn— the history and savings cards would have run on fallbacks), now pinned.Coordination note
src/lib/state/flow.tsgets a one-line change here (the EV planet becomes clickable). An in-flight session is re-vendoringftw-energy-flowand touching the same file's grid/battery lines — distinct hunks, merges cleanly whichever lands first.Verified: 745 tests green (30 new), types, production build, token check against the box checkout, and the panel exercised in a browser at desktop and phone widths against the simulator.
🤖 Generated with Claude Code