fix(ev): pas de bouton de boost — trois modes et rien d'autre

Décision de Patrick, réaffirmée : « tu gardes tout, seulement le bouton boost on
supprime ».

Je l'avais écrit et posé quelques heures plus tôt, en lisant « le bouton de
boost : ouvre-le quand tu veux » dans un message du moteur comme un feu vert —
alors que la décision produit était déjà prise en sens inverse. Un avis
technique sur la faisabilité ne révise pas un arbitrage de produit ; il fallait
demander, pas inférer.

Retiré : l'usage de `EvBoostButton` sur l'écran de la borne, son import, et le
helper `_entreeTelemetrie` devenu orphelin.

Gardé, sur instruction explicite : le widget, `setLoadDerogation` /
`clearLoadDerogation`, le modèle `Derogation` / `DerogationEnded` et leurs 19
tests. Ce n'est pas de la donnée fabriquée comme `DayPlan.demo()` — c'est la
lecture d'un contrat réel, testée, qui ne s'affiche nulle part et ne peut donc
rien afficher de faux. Le jour où le boost revient, il se rebranche en trois
lignes.

Vérifié à l'écran : « Mode de recharge · lu : Min+PV », trois boutons Manuel /
PV / Min+PV, Min+PV allumé parce que c'est ce que la box porte. Rien d'autre.

Capture : docs/captures/2026-09-08_ev-trois-modes.png

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
This commit is contained in:
Patrick Schurig 2026-09-08 15:08:58 +02:00
parent 0d1732dc61
commit b0afba11c7
2 changed files with 11 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

View File

@ -18,7 +18,6 @@ import '../../services/nymea_service.dart';
import '../../services/rpc_schema.dart';
import '../../theme/etm_tokens.dart';
import '../../widgets/load_cards.dart';
import '../../widgets/ev_boost_button.dart';
import '../../widgets/ev_mode_buttons.dart';
import '../../widgets/garde_brouillon.dart';
import '../../widgets/schema_gated.dart';
@ -510,28 +509,19 @@ class _SectionBorne extends StatelessWidget {
onSetMode: (m) =>
service.setChargingInfo(mode: m, evChargerId: borne.thingId),
),
// **Le boost est une ACTION à côté du sélecteur, pas une quatrième position.**
// ── Pas de bouton de boost ici ────────────────────────────────────────
//
// Une dérogation se superpose au mode courant et s'efface ; elle ne le
// remplace pas. La mettre dans le sélecteur ferait croire qu'on en sort en
// choisissant autre chose, alors qu'elle finit d'elle-même.
// Décision de Patrick, 2026-09-08 : l'écran garde **trois modes et rien
// d'autre**. Le bouton d'action a été écrit puis retiré le jour même — j'avais
// lu « ouvre-le quand tu veux » dans un message du moteur comme un feu vert,
// alors que la décision produit était déjà prise en sens inverse. Un avis
// technique ne remplace pas un arbitrage de produit.
//
// L'entrée de télémétrie vient de `loads[]` : `null` quand la borne n'est pas
// arbitrée en ce moment. Le bouton se tait alors — « pas d'entrée » n'est pas
// « pas de boost », c'est « on ne sait pas ».
if (_entreeTelemetrie(telemetry, borne.thingId) != null) ...[
const SizedBox(height: 8),
EvBoostButton(
entry: _entreeTelemetrie(telemetry, borne.thingId),
onSet: ({required int durationS, double? targetWh}) =>
service.setLoadDerogation(
loadId: _entreeTelemetrie(telemetry, borne.thingId)!.loadId,
durationS: durationS,
targetWh: targetWh),
onClear: () => service.clearLoadDerogation(
_entreeTelemetrie(telemetry, borne.thingId)!.loadId),
),
],
// Ce qui reste en place : `EvBoostButton`, `setLoadDerogation` /
// `clearLoadDerogation` et le modèle `Derogation`. Ce n'est pas de la donnée
// fabriquée — c'est la lecture d'un contrat réel, testée, qui ne s'affiche
// nulle part. Le jour où le boost revient, il se rebranche ici en trois
// lignes.
if (borne.isUnmanaged) ...[
const SizedBox(height: 8),
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
@ -2338,18 +2328,3 @@ class _SectionObligationState extends State<_SectionObligation> {
}
}
/// L'entrée de télémétrie d'une borne, cherchée sur l'identifiant **nu**.
///
/// Les identifiants voyagent avec ou sans accolades selon l'appel (UI_data_contract §9) :
/// comparer les chaînes brutes ferait rater la borne une fois sur deux, et l'écran
/// conclurait « pas arbitrée » sur une borne qui l'est.
LoadTelemetryEntry? _entreeTelemetrie(
LoadTelemetryProvider telemetry, String thingId) {
String nu(String x) =>
x.replaceAll('{', '').replaceAll('}', '').toLowerCase();
final cible = nu(thingId);
for (final e in telemetry.telemetry?.loads ?? const <LoadTelemetryEntry>[]) {
if (nu(e.loadId) == cible) return e;
}
return null;
}