/bug-report était un stub. Un champ libre seul produit « ça ne marche pas », et l'aller-retour
pour obtenir versions, charges et état d'arbitrage coûte des jours. Tout ce que l'app SAIT
est donc joint d'office : version du paquet, hôte et état de connexion, ligne de versions du
schéma, les charges déclarées avec adaptateur / rang / domaine, et le dernier cycle publié.
Deux choix qui ne sont pas cosmétiques :
- LES DEUX IDENTITÉS DU BUDGET sont dans le rapport, avec leur verdict. Elles valent souvent
plus que la description du symptôme : portées fausses, elles désignent le moteur ; portées
vraies, elles désignent l'écran. Sans elles, le premier échange consiste à les demander.
- Aucun secret n'y entre : ni PIN installateur même haché, ni jeton, ni mot de passe. Un
rapport voyage — courriel, capture, fil de discussion — et ce qui y entre en sort. Les
UUID de Things restent : ce sont eux qui permettent de recouper avec le journal de la box,
et ils n'ont aucune valeur hors de l'installation.
Les absences gardent leur sens, comme partout ailleurs : « aucune télémétrie reçue » n'est
pas un arbitrage à zéro, « budget ABSENT » n'est pas un budget de zéro watt, et un
financement omis se dit omis.
Pas d'envoi automatique : il n'existe aucun service de collecte, et un bouton « Envoyer »
sans destinataire donnerait le sentiment que quelqu'un l'a reçu. Le rapport se copie, ce qui
est vérifiable.
package_info_plus est ajouté plutôt qu'une constante de version recopiée à la main : une
constante dérive de pubspec.yaml en silence, et un rapport qui annonce la mauvaise version
envoie chercher un défaut dans un code qui n'est pas celui-là.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq
adb shell input tap est refusé par MIUI (INJECT_EVENTS). integration_test contourne le
problème par construction : le test s'exécute DANS le processus de l'app et tape sur les
widgets depuis l'intérieur, sans injection d'événements Android.
Ce que le harnais a déjà prouvé SUR L'APPAREIL, contre .75 : l'app démarre, se connecte
(« connecté à 1.15.2+202606191336~trixie1 ») et SchemaProvider lit JSONRPC.Introspect
(« NymeaEnergy 0.8 · AirConditioning 1.1 »). Le grisage dérivé du schéma n'est donc pas
une théorie : il est alimenté sur le téléphone par la vraie box.
Le run n'est pas allé au bout. Trois obstacles : GoRouter.of() cherché au-dessus du Router
(corrigé) ; le test supposait une installation déjà enregistrée (corrigé — il enregistre la
box lui-même via connectNew) ; puis le téléphone s'est déconnecté du bus USB, et l'ADB sans
fil n'est pas activé. C'est là que ça s'est arrêté.
EFFET DE BORD ASSUMÉ : flutter test integration_test RÉINSTALLE l'app, ce qui vide
shared_preferences et le stockage sécurisé — les deux installations enregistrées sur le
téléphone ont été effacées. Le harnais ne dépend plus de cet état et restaurera .75 au
premier run mené à terme ; nymea-dev-kutz est à rajouter à la main.
Relevé mis à jour : docs/RELEVE_LOTC.md §2 et §6.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
Lot B côté app, partie NON bloquée par le lot plugin (voir §Limites).
§1 Locale (indépendant) — JSONRPC.Hello portait {} : l'app était en en_US de
fait alors que nymea gère la locale PAR CONNEXION (o:locale). Envoi de la
locale à chaque Hello, donc aussi après reconnexion subie (m_clientLocales
repart au défaut de la box à chaque socket). Locale demandée ET retenue
journalisées : un .qm introuvable rend exactement comme une absence de
traduction, sans cette trace un catalogue mal packagé passe inaperçu.
§2 i18n — flutter_localizations + intl + ARB (français seul). Aucune chaîne
en dur dans le nouvel écran. Les codes reçus de la box (domaine, mécanisme)
sont des clés ARB avec repli explicite sur code brut si l'app ne les connaît
pas : une box en avance sur l'app est un cas normal en parc déployé.
§3 Modèle — LoadConfigEntry est une VUE en lecture au-dessus de la map brute ;
l'écriture repart de la map d'origine (patched()). Pas de couche de filtrage
préventive : l'aller-retour neutre passe contre .75, donc l'écho verbatim est
sûr par construction (§7-1).
§4 Priorité à deux niveaux — groupByDomain / flattenToPayload. Le rang de
domaine se déduit de l'ordre d'apparition à plat ; la box ne sait rien des
groupes. Config non contiguë : signalée, ordre réel affiché, normalisation
seulement sur confirmation explicite. Miroir client de validateSet().
§5 Écritures asynchrones — LoadConfigProvider ne conclut jamais sur
l'acquittement du RPC : pending → LoadConfigChanged → confirmed|diverged.
NymeaEnergy.GetLoadConfig / SetLoadConfig deviennent RÉELS (ils étaient des
stubs journalisés). Conséquence traitée : EnergySetupProvider.persist()
n'émet plus setLoadConfig(buildLoadDescriptors()) — SetLoadConfig remplace
TOUT l'ensemble, et _commit() l'appelait à chaque changement d'UI : une
reconstruction depuis le modèle typé aurait écrasé la config réelle de la
box sans geste explicite.
Écran /energy/loads, gaté installateur (drawer + garde de routeur).
Limites — le lot plugin n'est PAS déployé sur .75 : GetLoadTelemetry,
LoadTelemetryChanged et o:domain sont absents (19 méthodes / 13 notifications
inchangées). Toute la moitié télémétrie du brief est donc hors d'atteinte, et
o:domain n'étant pas au schéma, la box rejetterait la clé (jsonvalidator.cpp:143).
Fixtures multi-domaines synthétiques : .75 n'a qu'une charge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>