Commit Graph

111 Commits

Author SHA1 Message Date
tobias 6f23dfebbf Streckenberechnung: zuletzt davor statt naechstgelegen, keine Rueckwaertsspruenge (2026.8.31.6)
_fahrt_screenen() holte beide Kilometerstaende mit naechster_wert(), also dem
zeitlich naechstgelegenen Wert - gleich ob davor oder danach. Fuer einen
Zaehlerstand ist das falsch, und wert_bei()s eigener Docstring sagt es
woertlich: der Stand bei Fahrtbeginn ist der zuletzt gemeldete, nicht der
naechste, der schon Strecke enthaelt. historienimport.py hat immer wert_bei()
benutzt - dieselbe Fahrt wurde also je nach Weg unterschiedlich bewertet,
genau das, was die gemeinsame Herkunft von UNPLAUSIBLE_KMH und MINDESTDAUER_S
in verlauf.py ausschliesst.

Am echten recorder-Verlauf nachgewiesen: fuer den Fahrtbeginn 16:39:42 UTC
lieferte naechster_wert() 21325 (der Stand von 16:40:53, also 71 s nach dem
Beginn und aus einem anderen Fahrzeug), wert_bei() dagegen 209177.

_vollstaendig() prueft ausserdem nach unten. Bisher galt nur > UNPLAUSIBLE_KMH;
ein Rueckwaertssprung ergab eine negative Strecke und wurde als "vollstaendig"
gespeichert. historienimport.py verlangt odo_end >= odo_start seit jeher. Die
Pruefung sitzt an der Strecke, nicht an der Geschwindigkeit: durchschnitt_kmh()
liefert bei Dauer 0 None, eine Fahrt mit null Sekunden kaeme sonst vorbei.

Dem Fahrzeugwechsel des Dongles waehrend der Testphase steht das nicht im Weg:
verglichen werden Anfang und Ende derselben Fahrt, und jedes Fahrzeug zaehlt
innerhalb seiner eigenen Fahrt aufwaerts. In allen 23 gespeicherten Fahrten
haette die Pruefung nie ausgeloest. Greift sie doch, werden beide Werte
verworfen und die Fahrt bleibt "offen" - der naechste Screening-Lauf versucht
es erneut.

Verifiziert gegen die echte Aufzeichnung in audi_ha_test: sieben Faelle,
darunter drei Regressionen (normale Fahrt, unmoegliches Tempo, echte 0-km-Fahrt
bei stillstehendem Zaehler). py_compile sauber, 2026.8.31.6 sauber gestartet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 23:27:11 +02:00
tobias 9b0e390dfd Audit Panel/iOS: sechs Befunde behoben, Zuendung aufs Trip-Signal (2026.8.31.5)
ZUENDUNG_SENSOR liegt jetzt auf dem Trip-Signal des FMM003 statt auf dem rohen,
prellenden Zuendungseingang. Damit kommt die Pausentoleranz vom Geraet - eine
Fahrt beginnt erst bei Zuendung UND Bewegung UND Start Speed.

MINDESTDAUER_S wandert nach verlauf.py und gilt jetzt auch im Livepfad. Vorher
verwarf nur der Import Zuendphasen unter 60 s; live entstanden daraus am 31.08.
elf Fahrten mit 0 km, zwei davon mit null Sekunden Dauer.

Das Panel erfand einen Literpreis von 0,00 EUR, wenn die Kosten noch nicht
feststanden (null/liter ist 0 in JavaScript) - neue Hilfsfunktion literpreis()
prueft beides, wie die App seit jeher. schnittpreis() teilte ohne Nennerpruefung
und konnte "NaN" oder "unendlich" anzeigen.

Der Setup-Dialog ist wirklich modal: die Tab-Leiste hatte einen eigenen Zuhoerer
vor der Sperre und liess einen Tabwechsel bei offenem Fenster zu.

Verwaister Code entfernt: drei Funktionen und vier Regeln im Panel, 20 Regeln in
der App.

Offen bleibt allein die leere Erstzulassung - eine Angabe, keine Aenderung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:05:59 +02:00
tobias 90efcc5b00 Neustart-Festhaenger, OTA-Pruefsumme, Statistik-Kopfzahl (2026.8.31.4)
Die App blieb nach einem Neustart von Home Assistant auf dem alten Stand
stehen, mit einer 502-Fehlerzeile, die erst ein Beenden der App wegbekam.
Zwei Ursachen: 502/503/504 galten als harter Ladefehler statt als "Server
faehrt gerade hoch", und nach dem Wiederverbinden wurde genau einmal
nachgeladen - schlug das fehl, kam nie wieder ein Versuch. Beides behoben,
vier Regressionstests dazu.

OTA: das Buendel ist von HEAD ueber die Platte bis zu den ausgelieferten
Bytes nachweislich deckungsgleich. Die Fassung haengt jetzt trotzdem als
?v= an der Adresse (der statische Pfad kommt ohne Cache-Control, aber mit
ETag), und ein Pruefsummenfehler wird erklaert statt durchgereicht.

Statistik: der Gesamtwert steht jetzt als grosse Zahl in der Kopfzeile,
darunter bleiben vier Spalten - fuenf passen auf dem Telefon nachweislich
nicht in eine Zeile.

Fahrzeugfoto ohne Beschnitt und ohne Bodenschatten, damit auch ohne den
Schalter dafuer. Setup filtert zusaetzlich nach dem Signalnamen aus dem
Katalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 15:35:56 +02:00
tobias 4af38d7e65 Fahrten, Statistik, Trip-Timer (2026.8.31.3)
Der 15-Minuten-Timer war doppelt: der FMM003 wartet laut Konfiguration
(Trip \ Odometer, Ignition OFF Timeout) selbst 900 s, die App noch einmal
15 Minuten. Zusammen eine halbe Stunde. Die Pausenregel ist damit entfallen -
in der Live-Erkennung, im Historienimport und in beiden Oberflaechen. Der
Schwellwert "ab wann ist es ein Parkplatz" hing an derselben Einstellung, hat
damit aber nichts zu tun und steht jetzt als eigene Zahl.

Hoechst- und Durchschnittsgeschwindigkeit je Fahrt. Der Durchschnitt folgt aus
Strecke und Dauer, vmax aus der neuen Rolle GESCHWINDIGKEIT_SENSOR - laut
Konfiguration liest das Geraet die Geschwindigkeit vom OBD/CAN. Es ist der
groesste gemeldete Wert, nicht die tatsaechliche Spitze: alle zehn Sekunden
ein Datensatz. So ist es auch beschriftet.

Der Langzeitverbrauch war um einen ganzen Tankinhalt zu hoch. Gemessen wird
die Strecke ZWISCHEN erstem und letztem Tankstop; verbraucht wurde darauf,
was ab dem ZWEITEN hineinkam - der erste fuellte die Strecke davor. Bei 13
Tankvorgaengen rund 8 Prozent, im Testfall 16,7 statt 8,3 l/100 km. Der Test
hatte die falsche Rechnung festgeschrieben und ist mitkorrigiert. Eine
negative Strecke (falscher Kilometerstand an einem Tankvorgang) gilt jetzt
als unplausibel statt als Null ohne Erklaerung.

Verbrauch je Fahrt bekommt Grenzen: am Sensor nachgemessen loest
can_fuel_volume 0,1 l auf, ein Schritt entspricht also 10/Strecke l/100 km
Fehler. Unter 3 km sagt die Differenz nichts mehr, ueber 60 l/100 km ist es
kein Verbrauch.

Statistik: zusaetzlich "Absolut", und die Einheit steht jetzt in eckigen
Klammern an der Rubrik statt vier Mal am Zeitraum. Fuenf Spalten passen auf
einem Telefon nicht nebeneinander, das Raster bricht um.

Radfoto in Reifen war ein reines <Bild> - Ersetzen und Loeschen von dort aus
gar nicht erreichbar. Jetzt BildMitMenue wie im Panel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 15:03:28 +02:00
tobias 17dc42e645 App-Symbol: Entwurf 3 bei 54 Prozent
DataMetric oben, das CI-Fahrzeug in der Mitte, 360 in Audi-Rot unten, auf
Nacht-Grund. Ausgewaehlt aus zwoelf Varianten, jede zweimal gezeigt: gross
und bei 60 pt, der Groesse auf dem Homescreen - daran entschied es sich.

Vektorquelle unter native/logo/, erzeugt von scripts/logo-bauen.cjs. Das
Fahrzeug liest das Skript aus ICONS.audi im Panel, damit es nur eine Quelle
fuer diese Form gibt; die Schriften stecken als data-URI in der SVG, sonst
haengt das Ergebnis davon ab, welche Fonts auf dem rasternden Rechner
installiert sind.

Drei Ausfuehrungen fuer iOS 18: hell mit eigenem Grund, dunkel und getoent
ohne - dort legt das System seinen eigenen Grund darunter, ein
mitgeliefertes Schwarz bliebe als Kasten sichtbar. Getoent wertet iOS die
Helligkeit aus, deshalb wird die 360 dort ein helleres Grau statt Rot.

ios-signieren.sh legt die Vorlage ueber die von Capacitor erzeugte - ios/
ist gitignored, ein in Xcode eingesetztes Symbol waere beim naechsten
cap add ios wieder weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 14:43:05 +02:00
tobias b1411625aa Teilen-Blatt, HIG-Blaetter, geteilte Ansichtseinstellungen (2026.8.31.1)
Share-Erweiterung: ein PDF aus Mail landet ueber Teilen -> DataMetric360 als
Tankbeleg. Alles Native liegt versioniert unter companion-app/native/, weil
ios/ gitignored ist und npx cap add ios es sonst wieder frisst;
scripts/ios-teilen-einrichten.mjs haengt es bei jedem Bau ins Xcode-Projekt
und ist in ios-signieren.sh eingehaengt. Der pbxproj-Teil ist auf keinem Mac
erprobt - er bricht vor dem Schreiben ab, und docs/SHARE_EXTENSION.md
beschreibt dieselben Handgriffe fuer Xcode.

"In den Kalender uebernehmen" ging in der App nie: WKWebView ignoriert das
download-Attribut, der Klick lief ins Leere. Auf dem Geraet jetzt
@capacitor/filesystem plus @capacitor/share.

useTheme/useBoden legten je Aufrufer eigenen Zustand an - fuenf Kopien. Der
Schalter aenderte nur seine eigene, data-theme in App.tsx blieb stehen, die
Tag/Nacht-Umschaltung wirkte erst nach einem Neustart. Jetzt ein Speicher je
Einstellung ueber useSyncExternalStore. Zwei Regressionstests, gegen den
alten Stand als fehlschlagend nachgewiesen.

Alle Popups nach Apple HIG: neue Bausteine Sheet und ActionSheet, blickdicht
und mit abgedunkeltem Schleier, in beiden Codebasen. Popup portalierte nach
document.body - ausserhalb von [data-theme] - und war deshalb im Tagmodus
fast unsichtbar; Portal zielt jetzt auf .ads-root.

ImagePlaceholder merkte sich "fehlgeschlagen" ohne die Adresse: nach einem
leeren Galerieplatz zeigten auch die mit Foto nur noch den Platzhalter.
Vier Regressionstests, ebenfalls gegen den alten Stand geprueft.

Bildadressen tragen jetzt einen Cache-Brecher - HA liefert /local/ mit 31
Tagen Cache-Vorgabe aus, im Panel eingefuegte Radfotos erschienen in der App
deshalb nicht.

uebersichtsbild: Panel speichert den Namen, die App verglich gegen den
Dateinamen - der Vergleich traf nie zu. Kanonisch ist der Name.

Weiter: Navigationspfeil statt gleichschenkligem Dreieck auf der Streckenlinie
(die Kerbe unterscheidet Kopf und Ende), CI-Symbol tour-s als Fahrten-Icon,
Datumsfelder zeigen ihren Wert ohne erstes Antippen, feste Beispielnamen im
Setup, Kopf-Gegengewicht fuer mittige Titel, Standort-Blatt mit festem
Fussabstand, NSLocationWhenInUseUsageDescription, watchPosition fuer die
Live-Ortung, Art-Pille wieder als Knopf, Zwischenablage fuer Tankbelege.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 14:24:52 +02:00
tobias 3f1d122538 Werkzeugregel: Panel-Bundle als Modul pruefen, nicht als Script
Haelt fest, warum node --check den Syntaxfehler von .14 nicht finden konnte
(Annex-B-HTML-Kommentare sind in Scripts erlaubt, in Modulen nicht) und wie
die Pruefung ab sofort laufen muss. Dazu der zweite Fehler: der Panel-Tab
wurde nach dem Ausliefern nie neu geladen, alle spaeteren Live-Aussagen
liefen gegen das alte Skript.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:02:16 +02:00
tobias 5461382b8f Flaechen-Token: Regel festgehalten und beide Anwendungen durchgeprueft (2026.8.30.16)
--tile gehoert auf den Seitenhintergrund und sonst nirgends. Was auf einer
Kachel oder einem Blatt liegt, nimmt --tile-deckend (deckende Flaeche) oder
--ios-fill (Bedienelement). Grund: --tile ist nachts absichtlich
durchscheinend und tags deckend weiss - auf einer anderen Flaeche versagt es
deshalb in genau einem Theme, tags unsichtbar, nachts als doppelter Schleier.
Genau daran sind heute drei Fehler entstanden, jeder in nur einem Theme
sichtbar.

Geprueft zur Laufzeit statt per Textsuche, weil es auf die aufgeloeste Farbe
gegen die des naechsten gefuellten Vorfahren ankommt - beides steht nicht im
Quelltext. Panel 21 Routen, App 18 Seiten, beide Themes. Einziger
verbleibender Treffer ist die Listenzeile auf der Kachel im Tagmodus, wo
beide deckend weiss sind und die Zeile bewusst mit der Kachel verschmilzt.

Die Pruefung selbst ist gegengetestet: der heute behobene Schliessen-Knopf
wurde von Hand auf den alten Wert zurueckgesetzt und sofort erkannt. Eine
Pruefung, die man nie hat scheitern sehen, belegt nichts.

Dabei gefunden und angeglichen: die Kartenflaechen der Detailseiten trugen in
der App --tile-2, im Panel --tile-deckend - nachts ein zweiter Schleier auf
der ohnehin durchscheinenden Kachel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 23:47:43 +02:00
tobias 67135da4f7 Layer-Symbol: falscher Pfad in der App, zu klein in beiden (2026.8.30.12)
Drei Anlaeufe, die ersten beiden mein Fehler. Gemeldet war "Icon falsch im
Knopf platziert". Ich habe die Zeichenflaeche gemessen, sie fuellt 20,9x15,6
des 24er-Rasters gegen 20x20 bis 24x22 bei den Nachbarn, und daraus auf "zu
schwer und aussermittig" geschlossen - also verkleinert. Zwei Fehler darin:
gemessen habe ich das Panel und geaendert habe ich beide, ohne das Symbol der
App je selbst zu messen; und "fuellt weniger Flaeche als die Nachbarn" spricht
fuer "wirkt kleiner", nicht dagegen.

Nach der Ruecknahme im Panel zeigte die Messung der App den echten Fehler
dort: ihr mapLayer-Pfad begann mit grossem M statt kleinem m. Beim ersten
Befehl ist das gleichbedeutend, aendert aber alles danach - nach M gelten
folgende Zahlenpaare als absolute Linien, nach m als relative. Der zweite
Punkt sprang damit auf (-8.97,-5.66), weit aus dem Raster: das Symbol war
verzerrt, nicht bloss falsch dimensioniert. Pfad wortgleich vom Panel
uebernommen, alle vier Steuerungssymbole vergleichen sich jetzt zeichengleich.

Damit stand die urspruengliche Meldung fuer sich und war schlicht richtig: die
flache Form wirkt bei gleicher Kastengroesse kleiner. Beide zeichnen sie jetzt
mit 26px statt 22px - dieselbe Loesung wie beim Oelwechsel-Icon der
Servicebloecke, das aus demselben Grund 26px neben 20px bekommt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 23:29:07 +02:00
tobias 0fd81df700 Ziehen zum Aktualisieren, Nachtmodus-Zeilen, Parkplatznamen (2026.8.30.10)
Ziehen zum Aktualisieren funktionierte auf keinem Geraet, in keiner der
beiden Anwendungen - und der Grund entwertet zugleich, wie es bisher geprueft
wurde. Beide trieben die Geste ueber pointermove und riefen dort
preventDefault(). Das verhindert laut Spezifikation kein Scrollen; darueber
entscheidet allein touch-action. Auf echtem Touch uebernimmt der Browser die
senkrechte Geste, schickt pointercancel und stellt pointermove ein - der Zug
wird zurueckgesetzt, lange bevor er die Schwelle erreicht. Eine Maus kennt
diese Uebernahme nicht, synthetische PointerEvents ebenso wenig: genau
deshalb lief die Geste 2026-08-17 im Test sauber durch und am Geraet nie.

Jetzt laeuft sie auf Touch-Ereignissen, touchmove zwingend mit passive:false
(sonst ist preventDefault() wirkungslos); der Zeigerweg bleibt fuer Maus und
Stift und ignoriert pointerType "touch". Mit echten TouchEvents geprueft,
inklusive defaultPrevented - dem einzigen Signal, das belegt, dass das native
Ueberscrollen wirklich unterbunden ist.

Der zuletzt nicht nachstellbare Befund war nachtmodus-spezifisch: --tile ist
dort ein durchscheinender Schleier, und die Wischzeilen malten ihn ein
zweites Mal auf die Kachel, auf der sie ohnehin liegen. Am Tag sind beide
deckend weiss, deshalb war nichts zu sehen. Das Panel nutzt an dieser Stelle
--tile-deckend; dieses Token fehlte im Design-System und ist jetzt da.

Ausserdem: steht das Auto auf einem benannten Platz, zeigen beide dessen
Namen statt der naechsten Hausnummer - Nominatim liefert ihn mit, die
Pruefung auf die Art der Flaeche verhindert, dass irgendein benanntes
Gebaeude die Anschrift verdraengt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 23:22:52 +02:00
tobias a08922e9a0 Befundliste des Besitzers: drei echte Fehler, dazu Feinschliff (2026.8.30.9)
Fahrzeug-Pin auf der Standortkarte fehlte: die Marker-Effekte lesen Refs,
die erst im asynchronen Leaflet-Import gesetzt werden. Beim ersten Lauf leer,
Effekt steigt aus - und weil die Koordinaten sich danach nicht mehr aendern,
lief er nie wieder. Ein Bereitschaftsschalter in den Abhaengigkeiten behebt
das fuer Marker, Kachelebene und eigene Position zugleich.

Kachelpfeil auf "Mein Audi" sass auf halber Kachelhoehe statt oben neben der
Ueberschrift; Auswahlfelder hatten nach appearance:none ueberhaupt keinen
Aufklapp-Pfeil mehr.

SmartDeal: das Datumsfeld war fehlerhaft und die Abfrage, bis wann der Rabatt
gilt, fehlte ganz. Das Panel fragt beim Einschalten und zeigt danach nur an -
genau daran haengt, dass der Rabatt von selbst auslaufen kann. Portiert.

Adressen statt Koordinaten: Nominatim drosselt uns inzwischen. Beide Apps
fielen korrekt zurueck, merkten sich Treffer aber nur bis zum Neuladen. Jetzt
dauerhaft im selben Cache wie die Tankstellensuche.

Ziehen zum Aktualisieren auf dem Geraet: dem Scrollbehaelter fehlte
overscroll-behavior, das native Overscroll nahm die Geste weg - derselbe
Befund, den das Panel am 2026-08-17 hatte.

Ausserdem: Platzhalter wieder in voller Groesse, FIN statt Fahrgestellnummer,
Details als Hinweiszeile, Chevron an der Batteriespannung, Reifenzeile
"aktuell montiert:", Zahnrad statt Stift am Anzugsmoment, Radkilometer in der
Zahlenschrift, Layer-Icon zentriert, share-s beim Teilen, Schliessen-Knopf nur
bei aufgezogener Karte.

Nicht nachstellbar und zurueckgemeldet: "Fahrten/Tanken, grauer Hintergrund" -
Panel und App sind dort deckungsgleich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 23:13:01 +02:00
tobias 4a58021670 Paritaetsrunden 2 und 3, Designpruefung umgesetzt (2026.8.30.8)
Drei zusammenhaengende Runden, alle live bei 375x812 gegen audi_ha_test
geprueft und in beiden Codebasen angewandt.

Paritaetsrunde 2: der gemeinsame Rahmen war das eigentliche Problem -
Seitenrand, Kachelabstaende, Kopfleiste, Tableiste und vier Bausteine des
Design-Systems trugen noch den Stand vor der iOS-Entscheidung. Dazu sieben
Bildschirme neu aufgebaut. Zwei Panel-Fehler dabei mitbehoben: teaser()
zeigte unter "Letzte Fahrt" den aeltesten Datensatz, und "Daten bearbeiten"
war ein toter Knopf.

Paritaetsrunde 3: das Panel hat nie Audi Type gerendert. @font-face in einem
Shadow Root wird ignoriert - Schriftschnitte registriert der Browser pro
Dokument, nie pro Shadow Tree. Die drei Schnitte liegen jetzt in
audi-dashboard-schriften.css und werden ins Dokument gehaengt. Damit erledigt
sich eine ganze Reihe von "die Schrift sieht anders aus"-Eindruecken: die
beiden Anwendungen zeigten tatsaechlich verschiedene Schriften.
Ausserdem "Daten bearbeiten" im Panel gebaut und portiert, das Zeilenmenue
entfernt und die letzten neun Bildschirme angeglichen.

Designpruefung: die fuenf Punkte der Reihenfolge. Beim vierten hat das Messen
den Befund veraendert - gezaehlt waren die deklarierten Groessen, wirksam war
laengst eine saubere Sieben-Schritt-Skala mit 27 Ausreissern; die sind jetzt
auf den naechsten Schritt gezogen, keiner verschiebt sich um mehr als 1px.

Zuletzt: die Standortvorschau zeichnete die falsche Nadel (das Panel wechselt
den Icon-Satz ab 34px, die App nahm immer den grossen), und der Kopfabstand
der App ist auf den sicheren Bereich reduziert - die 56px des Panels liegen
dort unter der Kopfleiste von Home Assistant, in der nativen Huelle steht
darueber nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 18:31:46 +02:00
tobias e1ebe6d694 Vollständiger Panel-companion-app-Paritätsaudit (11 Kapitel)
Systematischer Bildvergleich der companion-app gegen das HA-Panel (companion-app/AGENTS.md
Kapitel 1-11): Farbtoken/Radien aus dem falschen Stylesheet gelesen (iOS-Overlay statt Basis-CSS,
betraf fast jede Kachel/Farbe/Radius app-weit), vier app-weite @audi-dash/ui-Bugs (Switch/Seg/Feld
rot statt neutral bzw. falsche Feldbreite), Reifen-Seite strukturell neu gebaut (beide Radsätze
gleichzeitig statt Umschalter, editierbare Felder, Anzugsmoment-/km-Korrektur), Standort-Feature
komplett neu (fehlte bisher ganz), sowie diverse Struktur-/Typografie-/Datenlücken in
MeinAudi/Service/Versicherung/Sicherheit/Fahrten/Tanken/Statistik/Batterie/Einstellungen.

Manifest auf 2026.8.30.2 angehoben, OTA-Bündel neu gebaut und in audi_ha_test verifiziert
(sauberer Neustart, keine Tracebacks).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 12:21:58 +02:00
Paul Nothaft 4fa5d4bc2d Eingabefelder zoomen nicht mehr: 16px setzen sich jetzt auch durch
Beim Antippen eines Feldes auf dem Willkommensbildschirm zoomte iOS die
ganze Seite heran und machte sie verschiebbar. Ursache war nicht eine
fehlende Regel, sondern eine unterlegene: grundlage.css und .dm-eingabe
setzen laengst 16px, aber @audi-dash/ui setzt ".ads-feld input" auf 13.5px -
Klasse plus Typ schlaegt die blosse Klasse. Unterhalb von 16px zoomt iOS
beim Fokus, das ist der ganze Mechanismus.

Behoben mit einem spezifischeren Selektor im App-CSS, gleicher Wert wie
ohnehin beabsichtigt. @audi-dash/ui bleibt unangetastet, wie schon beim
type=date-Fall darunter.

Bewusst nicht genommen: user-scalable=no im Viewport. Das haette das
Symptom in einer Zeile versteckt, nimmt aber allen das Aufziehen - und
dasselbe Buendel wird auch als Web-App aus Home Assistant ausgeliefert.
2026-08-29 13:34:07 +02:00
Paul Nothaft 4e97656a75 Korrektur: auch Ad-hoc-Apps verlangen seit iOS 16 den Entwicklermodus 2026-08-29 13:17:49 +02:00
Paul Nothaft b6a868df5e Ursache des Integritaetsfehlers festhalten: veraltetes Profil im Xcode-Zwischenspeicher 2026-08-29 13:16:02 +02:00
Paul Nothaft e46bec2e32 Luftweg ueber Gitea dokumentieren und den fertigen Safari-Link ausgeben 2026-08-29 13:04:27 +02:00
Paul Nothaft 959b98c93a Entscheidung des Besitzers festhalten: die .ipa bleibt im oeffentlichen Repo 2026-08-29 13:02:50 +02:00
Paul Nothaft b452c0a284 Befund festhalten: das Repository ist oeffentlich, die Lizenzregel verlangt privat 2026-08-29 13:00:44 +02:00
Paul Nothaft b4d44b2731 Signierte iOS-App gebaut und Luftweg-Installation vorbereitet
Phase 10 Schritt 7 ist damit erledigt. auslieferung/App.ipa, 2,4 MB, Ad-hoc
signiert mit "Apple Distribution: Paul Nothaft", Profil gueltig bis
29.08.2027, genau ein eingetragenes Geraet.

Zwei Annahmen von heute frueh waren falsch und sind korrigiert: die bezahlte
Mitgliedschaft stuft das bestehende Team hoch, statt ein neues anzulegen (die
Kennung bleibt RMACS9VLS4), und der Export als Ad-hoc funktioniert einwandfrei.

Neue Falle festgehalten: beim ersten Signieren fragt der Schluesselbund per
Dialog um Erlaubnis. Bleibt der unbeantwortet, haengt xcodebuild wortlos und
endet mit errSecInternalComponent. Die Diagnose steht in AGENTS.md, weil das
Symptom von sich aus nirgendwohin zeigt.

Neu: scripts/ios-luftweg.sh erzeugt manifest.plist, Installationsseite und
Symbole fuer die Uebertragung ueber die Luft. Es verweigert eine Basis-Adresse
ohne https, weil iOS sonst erst auf dem Telefon still scheitert.

Offen bleibt der Host, der die Dateien ausliefert.
2026-08-29 12:56:42 +02:00
tobias 5fe19e5006 Merge remote-tracking branch 'origin/main' 2026-08-29 11:29:12 +02:00
tobias f9cd8de8fd AGENTS.md: QR-Code-Scanner fürs Token-Onboarding als offenen Punkt aufgenommen
Owner-Anfrage 2026-08-28 - war 2026-08-11 bewusst weggelassen, jetzt wieder
gewünscht. Ursprünglicher Plan aus UMSETZUNGSPLAN.md Phase 10 als Referenz.
2026-08-29 11:28:36 +02:00
Paul Nothaft 0e16f68b4b Korrektur: das Apple-Konto ist ein kostenloses Personal Team
Der vorige Commit ging von einer bezahlten Mitgliedschaft aus und nannte als
Ausweg, die UDID auf developer.apple.com einzutragen. Das ist falsch.

Xcodes eigener Zwischenspeicher belegt das Gegenteil:
isFreeProvisioningTeam = 1, teamType = "Personal Team". Damit gibt es gar
keine Geräteverwaltung im Portal -- ein Gerät wird ausschließlich dadurch
bekannt, dass es angeschlossen und vertraut ist. Zusätzlich verfallen Profil,
App-ID und Geräteeintrag alle 7 Tage.

Auch die ältere Behauptung weiter oben in AGENTS.md ("Paul has an Apple
Developer Program") ist damit als falsch markiert.

Offen und bewusst als ungeprüft vermerkt: ob -exportArchive mit einem
Personal Team überhaupt eine brauchbare .ipa liefert. Der direkte Weg aufs
angeschlossene Gerät steht als Rückfallebene im Skriptkopf.
2026-08-29 11:22:21 +02:00
Paul Nothaft da85a2c0d6 iOS-Signierung vorbereiten: Skript, Nachweis des Gerätebaus, offener Punkt
Phase 10 Schritt 7 lässt sich nicht abschließen, aber alles bis zur Signatur
ist gebaut und belegt: Simulator- und Gerätebau (arm64, Release) laufen
fehlerfrei durch, 146/146 Tests grün, Typecheck sauber.

Die Signatur scheitert allein daran, dass dem Entwicklerteam kein Gerät
bekannt ist -- Apple erzeugt ein Development-Profil nur für konkrete UDIDs.
Kein iPhone angeschlossen, keins je mit diesem Mac gepaart, kein
App-Store-Connect-Schlüssel zum Nachtragen.

Neu: companion-app/scripts/ios-signieren.sh macht Bauen, Synchronisieren,
Signieren und den .ipa-Export zu einem Befehl. Nötig, weil ios/ absichtlich
gitignored ist und jede in Xcode geklickte Signatureinstellung beim nächsten
npx cap add ios wieder verschwinden würde -- die Team-Kennung braucht eine
versionierte Heimat.

Bewusst nicht getan: eine unsignierte .ipa als Platzhalter einchecken. Sie
wäre nicht installierbar und läge als Binärdatei dauerhaft in der Historie.
2026-08-29 11:17:46 +02:00
tobias 137d32d28d Domain auf datametric360.de umgestellt (war .app)
Alle Referenzen in Doku und Code aktualisiert (REVERSE_PROXY.md,
INTERNET_ZUGRIFF_EINRICHTEN.md, COMPANION_APP_ARCHITECTURE.md, AGENTS.md,
UMSETZUNGSPLAN.md, companion-app/src/api/umgebung.ts-Kommentar). Dabei den
.app-spezifischen HSTS-Preload-Hinweis in COMPANION_APP_ARCHITECTURE.md §5.4
korrigiert - gilt für .de nicht, Force-SSL in Nginx Proxy Manager deckt das
weiterhin ab. UMSETZUNGSPLAN.md Phase 12 zusätzlich mit einem
Aktualisierungshinweis versehen (zwei-Hostnamen-Plan und pyscript-Namen dort
waren ohnehin schon überholt, jetzt klar auf REVERSE_PROXY.md/
INTERNET_ZUGRIFF_EINRICHTEN.md als maßgeblich verwiesen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 15:06:07 +02:00
tobias 3ad1809af6 Internetzugriff: fehlenden Einrichtungs-Runbook nachgereicht, Pfad-Freigabeliste korrigiert
REVERSE_PROXY.md verwies auf INTERNET_ZUGRIFF_EINRICHTEN.md, das nie
geschrieben wurde - jetzt vorhanden (Cloudflare-Konto, Nameserver-Umstellung,
Cloudflared/NPM-Add-ons, Pfad-Freigabeliste eintragen, Prüfung).

Dabei einen echten Fehler in der Freigabeliste gefunden: sie ging noch von
einem companion-app-Web-Build unter /local/dm360/ aus (Planungsstand
2026-08-11) - das wurde nie gebaut, die App ist nativ/sideload-only. Ersetzt
durch den tatsächlichen Fernbedarf: das OTA-Bündel unter
/audi_dashboard_static/app/* (@capgo/capacitor-updater). Damit genügt auch
ein einziger Hostname statt der ursprünglich erwogenen App-/API-Trennung.
COMPANION_APP_ARCHITECTURE.md §5 und AGENTS.md entsprechend nachgezogen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 14:41:10 +02:00
tobias 6b35de458d Fahrgestellnummer: expliziter "automatisch"-Schalter statt Nur-wenn-leer-Heuristik
- Neues Profilfeld fahrzeug.fin_automatisch (Standard: an).
- identitaet.py: bei "aus" läuft die Ableitung (samt Mehrfach-Sensor-Abgleich)
  gar nicht erst; bei "an" überschreibt der abgeleitete Wert auch einen
  vorher von Hand eingetragenen - der Schalter selbst entscheidet jetzt, statt
  aus einem leeren Feld zu raten.
- dienste.py: profil_schreiben löst die Ableitung jetzt auch aus, damit das
  Umschalten des Schalters selbst sofort wirkt.
- Panel: Schalter "automatisch" zwischen Label und Textfeld in der
  Fahrgestellnummer-Zeile, Feld wird bei "an" deaktiviert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 12:01:10 +02:00
tobias c0acc836f0 Fahrgestellnummer (FIN) automatisch aus den zugeordneten Sensoren ableiten
Neues identitaet.py: durchsucht das Geräte-Register der aktuell im Setup
zugeordneten Sensoren (cupra_eu_data_act, FMM003, ...) nach einem
VIN-förmigen Geräte-Identifier/Seriennummer, statt ein eigenes Setup-Feld zu
verlangen. Mehrere übereinstimmende Quellen werden akzeptiert, abweichende nur
geloggt statt geraten; eine bereits von Hand eingetragene FIN wird nie
überschrieben. Läuft nach jedem Start und nach jeder Setup-Änderung
(koordinator.py/dienste.py).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 11:47:08 +02:00
tobias 8f88ccf581 Zuletzt-Kachel: Werte jetzt wirklich spaltenbündig
- .teaser-Klasse auf die Kachel gesetzt (teaser()).
- .teaser .leaf .v: min-width:72px (gleiche Spaltenbreite je Zeile).
- .teaser .leaf .k: flex:1 1 auto, damit unterschiedlich lange Labels nicht
  mehr über justify-content:space-between den ganzen Rest der Zeile
  verschieben - vorher blieben .v-Boxen zwar gleich breit, saßen aber an
  unterschiedlichen X-Positionen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 11:31:07 +02:00
tobias dd8d62e3b3 Tanken: Distanz-Vorschlag bevorzugt TANK_DISTANZ_SENSOR; Zuletzt-Kachel: Zeilenabstand ergänzt
- distanzVorschlag(): liest jetzt zuerst den Live-Wert von TANK_DISTANZ_SENSOR
  (wie backendseitig schon in distanz_seit_tankung()), fällt ohne Zuordnung
  auf die bisherige FILLS-Berechnung zurück (jetzt distanzVorschlagBerechnet()).
- .leaf .v: gap:3px ergänzt (fehlte im Gegensatz zu .leaf .k) - die zweizeiligen
  Werte in der "Zuletzt"-Kachel (Übersicht) berührten sich sonst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 11:26:23 +02:00
tobias ff9aaeb338 Setup-Menü: runde Klammern aus Feld-Labels entfernt, Popup verbreitert
- FELDER-Katalog: "(Datum)"/"(Restkilometer)"/"(Liter)"/"(Knopf)"/"(Fahrertür)"
  aus sieben Labels entfernt - die Unterscheidung übernimmt bereits das
  [Einheit]-Label neben der Überschrift (feldEinheitAnzeige()).
- .setup-popup auf max-width:760px verbreitert (eigener Wert, getrennt von
  .sdpopup/.sheet/.beleg-popup), damit lange Rohsensornamen nicht mehr
  umbrechen müssen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 11:19:28 +02:00
tobias d1cc742a59 Setup-Menü: Sensorkennung per Mehrheitsentscheid statt nächstem Nachbarn; Ellipsis entfernt; Filter/Tanken-Fixes
- entitaetIdKurz(): Präfix per Stimmenmehrheit über alle zugeordneten Sensoren statt
  des einzelnen nächsten Nachbarn - verhindert Über-Kürzung durch zufällig geteilte
  Namensteile zwischen verwandten Feldern (can_fuel_volume->fuel_volume,
  oil_change_due->due waren falsch).
- .setup-feld-hinweis: Ellipsis-Kürzung entfernt, voller Sensorname sichtbar.
- entitaetKandidaten(): "Nur passende Sensoren anzeigen" filtert jetzt auch nach
  device_class für Datum/Timestamp-Felder, nicht nur nach Domain/Einheit.
- tankFelder(): Kilometerstand/Getankte Liter in .mitEinheit gewrappt (wie alle
  anderen km/l-Felder im Rest der App).
- distanzVorschlag(): negative Vorschläge (fehlerhafte km-Basis eines vorherigen
  Tankvorgangs) werden nicht mehr angezeigt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 11:15:12 +02:00
tobias 4f85a179cf Setup-Menue: Sensorkennung per naechstem Verwandten statt globalem Praefix
Zwei weitere Korrekturrunden, beide vom Nutzer live erkannt:

- .6: paarweise Abstimmung statt fortlaufender Verengung - robuster gegen
  einen einzelnen Ausreisser, aber grundsaetzlich noch falsch.
- .7: die eigentliche Ursache war, dass diese Installation zwei etwa
  gleich grosse Sensorquellen gleichzeitig hat (9 FMM003-Rollen, 18 Rollen
  einer zweiten Integration mit "audi_rs_4_avant_"-Praefix) - ein
  EINZELNES globales "Sieger"-Praefix kann immer nur eine der beiden
  Quellen richtig kuerzen. entitaetIdKurz() sucht jetzt pro Feld den
  naechsten Verwandten unter allen zugeordneten Sensoren und kuerzt nur
  dessen gemeinsames Praefix - unabhaengig von Anzahl und Groesse der
  Quellen.

Diesmal vor dem Deploy anhand der ECHTEN sensor.audi_dashboard_entitaeten-
Zuordnung durchgerechnet statt mit erfundenen Test-IDs (das hatte den
.6-Fehler faelschlich als behoben erscheinen lassen). Version 2026.8.28.7,
live verifiziert: alle 20 Einzelwert-Felder zeigen die richtige, kurze
Kennung, keine installationsspezifischen Reste aus beiden Quellen mehr.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:54:16 +02:00
tobias af64fc39e3 Setup-Menue: Sensorkennung ohne installationsspezifisches Praefix, Einheit statt Live-Wert
Korrektur zur letzten Aenderung, vom Nutzer live erkannt:
- "fmm003_" war hart einprogrammiert und liess trotzdem noch
  installationsspezifisches Rauschen stehen (Geraete-Slug, Instanzname der
  Integration). Jetzt wird das laengste gemeinsame Praefix ueber alle
  aktuell zugeordneten Sensoren berechnet (setupGemeinsamesPraefix()) -
  nichts mehr hartkodiert. Faellt bei einer einzelnen Entitaet aus
  anderer Quelle korrekt auf die volle ID zurueck, statt falsch zu kuerzen
  (live bestaetigt: TANK_DISTANZ_SENSOR zeigte den vollen Namen, weil er
  auf eine andere Integration zeigte).
- Der Live-Wert des Sensors gehoert nicht mehr neben die ID - stattdessen
  zeigt das Label selbst jetzt die ERWARTETE Art des Werts an
  ("Tankfuellstand [%]", "12V-Batteriespannung [V]"), statisch aus dem
  FELDER-Katalog abgeleitet, unabhaengig von einer aktuellen Zuordnung.

Version 2026.8.28.5, live im Testcontainer verifiziert (acht echte
Setup-Zeilen gegen die Nutzerbeispiele geprueft).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:45:45 +02:00
tobias b1cd03be51 Wartungsplan-Schema companion-app<->Panel angeglichen; Setup-Feldkopf zeigt zugeordneten Sensor
companion-app nannte Servicebuch-Feldnamen betrieb/notiz, waehrend Panel und
Backend (profil["service"]["buch"]) werkstatt/kosten/arbeiten verwenden -
ohne Uebersetzung dazwischen zeigten in einer Oberflaeche angelegte
Eintraege in der anderen leere Werkstatt-/Kosten-Werte. werkstatt/kosten/
arbeiten als kanonisch uebernommen (aeltere, bereits etablierte Panel-
Konvention); ServicebuchEintrag, das Bearbeitungsformular (inkl. neuem
Kosten-Feld) und die Ansicht in Service.tsx angepasst, den Workaround-Cast
im CSV-Export (DatensatzPopup.tsx) sowie die Testfixture entsprechend
bereinigt.

Setup-Popup (Panel, Item 11 aus der letzten Sammel-Rueckmeldung): die
statische Beschreibung unter jedem Feldnamen ist jetzt die Kennung und der
aktuelle Wert des tatsaechlich zugeordneten Sensors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:38:27 +02:00
tobias 2be6b99667 companion-app: Datensatz sichern/laden portiert (Item 9 Paritaet)
- "Daten ausgeben" zu "Fahrzeugprofil" umgebaut, gleiche zwei Knoepfe wie
  im Panel: Datensatz sichern/laden, popup mit vier Aktionen
- CSV-Spaltennamen jetzt byte-identisch zum Panel-Export (gemeinsamer
  Backend-Parser, csv_import.py) - vorher eigene, abweichende Kopfzeilen
  ohne Reimport-Moeglichkeit
- neue api.csvImportieren(), Fahrzeugprofil-Import ueber das bestehende
  api.profilSchreiben() (volles Profil, nicht die teilweise
  zusammenfuehrende profilSpeichern()-Bequemlichkeitsfunktion)
- Blinden Fleck geprueft: importStatusLesen()/zustandLesen() lesen per
  REST, nicht aus lokalem Cache - dieselbe Racebedingung wie im Panel kann
  hier strukturell nicht auftreten
- Toten dateiwahl-Scaffolding entfernt (nie verdrahtet)

Beim Bauen echten, vorbestehenden Fehler gefunden: companion-app und Panel
verwenden unterschiedliche Feldnamen fuer Wartungsplan-Eintraege
(betrieb/notiz vs. werkstatt/kosten) - als eigene Aufgabe geflaggt statt
hier mitgefixt.

tsc --noEmit sauber, 146/146 Tests, vite build erfolgreich. Nicht live
getestet (kein laufender companion-app-Dev-Server mit Backend-Zugang in
dieser Sitzung).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:31:10 +02:00
tobias bfb7dec1e9 TANK_DISTANZ_SENSOR + Datensatz sichern/laden mit echtem CSV-Import (Panel)
- Neuer optionaler Sensor TANK_DISTANZ_SENSOR ersetzt die eigene
  Kilometerstand-Subtraktion fuer die Tankvorgang-Strecke, wo zugeordnet
  (Live-Erkennung, manuelle Erfassung, historischer Import)
- "Fahrzeugprofil" und "Daten ausgeben" zu einer Kachel zusammengelegt:
  "Datensatz sichern" (4 Exporte wie bisher) / "Datensatz laden" (neu:
  echter CSV-Import fuer Fahrten/Tankvorgaenge/Wartungsplan)
- Import gleicht per ID ab (Fahrten/Tankvorgaenge) bzw. Datum+Art
  (Wartungsplan, hat keine eigene ID) und aktualisiert nur die in der CSV
  enthaltenen Spalten - alles andere am Datensatz bleibt unangetastet
- Nebenbei gefunden und behoben: belege.tankvorgang_aktualisieren()
  ueberschrieb bisher immer alle Felder, auch mit None, wenn irgendeins
  geaendert wurde - fuer den CSV-Import gefaehrlich, jetzt nur noch
  tatsaechlich uebergebene Felder
- Ebenfalls gefunden und behoben: Panel las das Import-Ergebnis per
  HASS.states direkt nach dem Dienstaufruf - ein Wettlauf mit dem
  state_changed-Push. Nutzt jetzt denselben HASS.callWS(get_states)-Weg
  wie der bestehende historie_importieren-Ablauf.

companion-app-Portierung von "Datensatz sichern/laden" steht noch aus.

Version 2026.8.28.3, live im Testcontainer verifiziert (alle drei
CSV-Datensatztypen: anlegen + aktualisieren per ID/Datum+Art getestet,
Testdaten danach geloescht).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 10:19:06 +02:00
tobias edefdd66d8 Reifen/Version/Wartungsplan: erste Runde des Sammel-Feedbacks behoben
- Archiv-Knopf jetzt in Zeile mit "Montiert" (stale margin-top entfernt),
  bekommt einen sichtbaren Hintergrund
- companion-app: Wechseltermin-Datumsfeld (appearance-Reset gegen
  natives Safari-Chrome, Breite auf Inhalt geschrumpft)
- Mein Audi/Reifen-Box: "montiert: Sommerräder" links statt Marke/Modell,
  "offen" durch "-" ersetzt
- "Servicebuch" ueberall in "Wartungsplan" umbenannt (nur Anzeigetext,
  beide Codebasen)
- Version-Kachel: tote Fahrzeugdaten-/Position-/Dashboard-Zeilen entfernt
  (inkl. der nie aktualisierten CONFIG.version-Konstante)

Version 2026.8.28.1, live im Testcontainer verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 09:43:40 +02:00
tobias bdd8cb89a3 Batterie-Messwertliste: Zeilen sehen nicht mehr klickbar aus (Cursor/Presshighlight)
.leaf (Panel) und .dm-listenzeile (companion-app) galten fuer echte
Navigations-Buttons UND fuer die reine <div>-Messwertliste gleichermassen -
Hand-Cursor und Tipp-Rueckmeldung suggerierten dort faelschlich Klickbarkeit,
obwohl die Liste nur Wischen-zum-Loeschen unterstuetzt. Beide Regeln jetzt
auf button.leaf/button.dm-listenzeile beschraenkt - echte Zeilen-Buttons
(Fahrten, Tankvorgaenge, Servicebuch) bleiben unveraendert klickbar.

Version 2026.8.27.22, live verifiziert (Cursor auto statt pointer bei der
Messwertliste, pointer weiterhin bei echten Zeilen-Buttons).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 00:47:57 +02:00
tobias f4222b715f Batteriespannung-Fallback: zeigt letzte Ruhespannungsmessung ueber Neustarts hinweg
Der RAM-only Fallback fuer die Zustand-Kachel wurde bei jedem Neustart
geleert und zeigte "unbekannt", solange seit dem Neustart keine echte
Live-Messung eintraf (Dongle bei ausgeschaltetem Fahrzeug erwartungsgemaess
immer offline). spannung_cache_vorladen() laedt ihn jetzt beim Start aus
der gespeicherten Historie vor, begrenzt auf den vereinbarten Bereich
10,0-13,0 V (SPANNUNG_MIN_V bis AGM_RUHE_MAX_V) - eine Generatorspannung
(z.B. 15,4 V) soll dort nie als Batteriespannung erscheinen.

Version 2026.8.27.21, im Testcontainer verifiziert (12,149 V statt
"unbekannt", trotz Dongle weiterhin 0 V live).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 00:23:16 +02:00
tobias 90fff7b305 Vollaudit: sechs echte Fehler in Backend, Panel und companion-app behoben
- companion-app Batterie.tsx: Generatorspannung zeigte sich faelschlich als
  Ruhespannung/verzerrte den SOH-Trend, Panel-Filter (AGM_RUHE_MAX_V) fehlte
- Manuell angelegte Fahrten/Tankvorgaenge trugen naive Zeitstempel und wurden
  von der Import-Dublettenpruefung stillschweigend uebersprungen; neue
  gemeinsame zeit_normalisiert() in verlauf.py schliesst die Luecke
- Batteriespannungsverlauf fehlte im Backup (sicherung.py)
- Tankvorgangs-Import verlor stillschweigend aeltere Tankvorgaenge vor Beginn
  der Litersensor-Historie; laeuft jetzt zweigleisig (Prozent + Liter)
- Panel-Statistikseite behauptete faelschlich, der Verbrauch je Fahrt komme
  vom Fahrzeug (OBD) - ist eine Naeherung aus dem Tankfuellstand
- companion-app Statistik.tsx: Arbeitsweg-Segment war noch rot statt neutral

Version 2026.8.27.19, OTA-Buendel neu gebaut, im Testcontainer verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 00:05:58 +02:00
tobias e1ddf2cda1 AGENTS.md: dokumentiert den Update-Vorfall - Selbst-Update hat unveroeffentlichte Sitzungsarbeit ueberschrieben
Der Nutzer fuehrte ein Selbst-Update aus, das Gitea's letzten committeten Stand
(80d73e8) zog - alle Fixes dieser Sitzung waren bis dahin nur per docker cp an
audi_ha_test ausgeliefert, nie committet. Manifest fiel auf 2026.8.27.8 zurueck,
alle Symptome (Batteriespannung "unbekannt", Diagramm-Maximalwert 13,99V) passten
exakt zum Vor-Sitzungs-Stand. Container wiederhergestellt, jetzt committet -
Lehre fuer kuenftige Sitzungen im Dokument festgehalten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:24:24 +02:00
tobias 172db54e7a Statistik-Icon-Groesse, Batteriediagramm-Ueberarbeitung, Temperatur-Kopplung mit 300s-Fenster, letzte gueltige Spannung als Fallback
- Statistik-Tab-Icon per zentrierter Stauchung an die Fuellflaeche der
  anderen Tab-Icons angeglichen (Panel + companion-app), auf Nutzerwunsch
  anschliessend 10% groesser.
- Batteriediagramm neu gezeichnet: dynamische viewBox (1 Einheit = 1 Pixel,
  kein preserveAspectRatio-Verzerren mehr), ein Punkt je Ruhespannungsmessung
  statt Datumsbeschriftung, Tageshoechstwert nur noch im Tooltip. Generator-
  spannungen (> AGM_RUHE_MAX_V = 13,0 V, vom Nutzer festgelegt) bleiben aus
  dem Diagramm aussen vor; der SOC-Kurve-Eckwert "100% / voll" bleibt bei den
  ursprünglich recherchierten 12,8 V. companion-app auf dieselbe lineare
  SOC-Interpolation umgestellt (vorher eine abweichende Stufenkurve).
- Außentemperatur wird jetzt auch beim Batterieverlauf-Import nachgetragen
  (vorher nur live) - Kopplung an die naechstgelegene Messung innerhalb
  NEBENWERT_MAX_ABSTAND_S (verlauf.py), vom Nutzer nach Live-Daten-Analyse
  auf 300s festgelegt. Merge-Logik in ablage.py ergaenzt: ein erneuter Import
  kann eine zuvor fehlende Temperatur jetzt tatsaechlich nachtragen, auch
  wenn sich der Tagesminimalwert selbst nicht aendert.
- "Mein Audi"/Zustand zeigt bei unplausibler Live-Messung (z. B. Dongle
  offline) die zuletzt gemessene Spannung statt "unbekannt".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:23:12 +02:00
tobias 80d73e8afd Fahrten: Verbrauch-Näherung, 0V-Zustandsanzeige gefiltert, Plausibilitätsgrenze für die Live-Vervollständigung
Verbrauch (l/100km) war bislang totes Schema - kein Codepfad füllte
verbrauch_l_100km je. Jetzt aus der Literstand-Differenz (TANK_LITER_SENSOR)
über die Distanz genähert, live und beim Import, in beiden Frontends.

Die "Zustand"-Kachel zeigte die Batteriespannung ungefiltert direkt vom
Sensor, unabhängig von der Plausibilitätsgrenze der Verlaufsaufzeichnung -
ein Sensorausreißer (0V) zeigte sich dort weiterhin, obwohl die Messwertliste
ihn längst verwarf. Dieselbe Grenze gilt jetzt auch für diesen Anzeigepfad.

Reale Fahrtendaten zeigten eine Fahrt mit 22km in 67s (~1180 km/h) - die
Live-Vervollständigung (screening.py) hatte anders als der Import keine
Plausibilitätsprüfung der Durchschnittsgeschwindigkeit. Jetzt gemeinsam in
verlauf.py (UNPLAUSIBLE_KMH/durchschnitt_kmh) für beide Pfade. Dabei einen
zweiten echten Bug gefunden: _fahrt_screenen() zog sein Ergebnis nie ins
In-Memory-Objekt nach (nur in die Ablage) - eine im selben Durchlauf gerade
erst ermittelte Distanz blieb für spätere Schritte (z. B. Verbrauch) bis zum
nächsten Screening unsichtbar. Beide Stellen jetzt behoben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 21:01:57 +02:00
tobias cf57c5f1a5 Batteriespannungs-Diagramm: feste 170px-Höhe statt aspect-ratio, Y-Achse geklemmt
Die aspect-ratio-Lösung für die volle Spaltenbreite ließ auf großen Bildschirmen
die gesamte Koordinatenfläche mitwachsen - Achsenbeschriftungen (fester font-size
in SVG-Einheiten) wurden dadurch auf ~26px zu groß, das Diagramm unnötig hoch.
Zurück auf eine feste 170px-Höhe: der Y-Maßstab bleibt immer 1:1, nur die
X-Achse dehnt sich auf die volle Breite - normale Schriftgröße, kompaktes
Diagramm, trotzdem volle Spaltenbreite.

bvSkalaY()/y() klemmen jetzt auf [0,1] statt roh zu extrapolieren - ein Punkt
außerhalb von yMin/yMax (z. B. ein Ausreißer auf einer noch nicht aktualisierten
Instanz) zeichnet sich sonst weit außerhalb der Fläche und reißt die Linie über
den Rand hinaus, statt sichtbar am Achsenrand zu liegen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 18:33:31 +02:00
tobias b8e560e75b Batteriespannung: Diagramm-Y-Achse fest 10-15V, Plausibilitätsgrenze auf 10V angehoben
Die Y-Achse beider Batteriespannungs-Diagramme (Panel und companion-app) war bisher
aus den sichtbaren Punkten berechnet - jetzt fest 10-15V, auf Nutzerwunsch (zunächst
8-15V, dann auf 10-15V korrigiert). SPANNUNG_MIN_V (batterie.py) auf denselben Wert
angehoben, damit die Plausibilitätsgrenze zur Achse passt - historienimport.py
übernimmt den Wert automatisch, da er von dort importiert statt dupliziert wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 18:06:12 +02:00
tobias f337820fc8 Batteriespannung: 8V-Plausibilitätsgrenze; Fahrten: echte Start-/Zielposition und Streckenlinie
Batteriespannung: Werte unter 8V (Sensor-/Verbindungsfehler, nicht physikalisch
plausibel für eine 12V-Bleibatterie) werden nicht mehr erfasst - live und beim
Import. "Neue Räder anlegen"-Knopf sitzt jetzt auf derselben Zeile wie "Montiert".

Fahrten: start_lat/-lon, end_lat/-lon und route wurden bisher nur beim
rückwirkenden Import befüllt, nie bei live erkannten Fahrten (dem Normalfall) -
Screening trägt sie jetzt aus dem GPS-Verlauf nach, unabhängig vom
Kilometerstand-Status. fakeTrack() (eine erfundene Linie) ist entfernt, die
Karte zeichnet jetzt die echte Route oder eine ehrliche Gerade zwischen den
bekannten Punkten. CARTOs anonyme Kartenkacheln verlangen inzwischen einen
API-Schlüssel ("API key required" auf jeder Karte in beiden Apps) - auf
schlüssellose OpenStreetMap-Kacheln umgestellt. Start/Ziel zeigen jetzt Datum
und Uhrzeit über der Adresse; die fehlerhafte "Status"-Zeile ist entfernt.
Batteriespannungs-Diagramm nutzt auf großen Bildschirmen die volle
Spaltenbreite statt einer festen 400px-Deckelung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 17:23:32 +02:00
tobias 17149937a6 Batteriespannungs-Messwertliste, Wisch-Löschen, Reifensatz-Archiv, CI-Icons
Fünf vom Nutzer angeforderte Erweiterungen in einer Runde:

- Batteriespannungs-Kachel bekommt eine Messwertliste (Datum, Uhrzeit,
  Spannung, optional Außentemperatur über den neuen AUSSENTEMP_SENSOR),
  erreichbar über ein neues list-s-Symbol in der Diagramm-Kachel. Werte
  über AGM_RUHE_MAX_V (12,8 V) sind keine Ruhespannung, sondern
  Lichtmaschinenspannung - die Zeile markiert das jetzt mit
  "Generatorspannung" statt es unkommentiert als Messwert auszugeben.
- Jede Zeile der neuen Liste wischbar zum Löschen (neuer Dienst
  batterieverlauf_loeschen), über dieselbe Wisch-Mechanik wie Fahrten/
  Tankvorgänge in beiden Oberflächen.
- Batteriespannungs-Diagramm im Panel war auf großen Bildschirmen (≥860px,
  Spaltenlayout) stark in die Breite gezogen (preserveAspectRatio="none"
  bei fester Höhe) - gedeckelt wie die dort bereits vorhandenen Popups.
- Statistik-Tab-Symbol in beiden Oberflächen durch das Audi-CI-Symbol
  polls-s ersetzt.
- Neue Möglichkeit, einen Reifensatz zu archivieren ("Neue Räder
  anlegen"): der aktuelle Stand (km, Marke, Modell, DOT, Maße, Solldruck,
  Kommentar) wandert in ein Archiv, der laufende Satz beginnt bei 0 km neu.
  Archiv als aufklappbare Übersicht mit voller Bearbeitungsmöglichkeit,
  über drei neue, bewusst nicht über profilSchreiben laufende Dienste
  (reifen_archivieren/-aktualisieren/-loeschen) - aus demselben Grund wie
  reifen_wechseln: kein im Browser gehaltener Stand darf einen
  zwischenzeitlich fortgeschriebenen km-Wert überschreiben.

Live im Testcontainer geprüft (erstmals über :18123 statt :8123 erreichbar)
und dabei zwei echte Layout-Fehler gefunden, die kein Compile-/Testlauf
sehen konnte: die Archiv-Eingabefelder waren durch ein fälschlich
verwendetes .mitEinheit (feste 96px-Breite, eigentlich für Zahl+Einheit
gedacht) abgeschnitten ("Continenta" statt "Continental"), und die
Kilometerzahl in der eingeklappten Archiv-Zeile war klein an das Datum
gequetscht statt wie der Satzname lesbar. Beides behoben, live erneut
bestätigt.

Backend: py_compile clean, services.yaml ergänzt. Panel: node --check
clean. companion-app: tsc/Testsuite (146/146)/Build/OTA-Bündel alle grün.
Manifest 2026.8.25.2 → .8, jeder Neustart im Testcontainer sauber.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 11:44:24 +02:00
tobias 70253e83a5 Fahrterkennung: FMM003-Funklöcher reißen keine Fahrt mehr auseinander
Die Zündungs-Entität stammt vom FMM003-Tracker; verliert er unterwegs
kurz die Verbindung, meldet Home Assistant "unavailable" statt eines
echten Zündungszustands. Die Live-Erkennung wertete das bisher wie
"aus" - eine Funklücke über der Pausenzeit teilte eine durchgehende
Fahrt in zwei. zuendung_geaendert() ignoriert "unavailable"/"unknown"
jetzt vollständig und leitet "fährt gerade" aus dem eigenen
Zwischenstand (fahrt_start_ts) statt aus dem letzten Rohwert ab.

historienimport.py bekommt zusätzlich eine Plausibilitätsprüfung: eine
errechnete Durchschnittsgeschwindigkeit über 300 km/h deutet auf einen
Kilometerstand-Ausreißer an der Fahrtgrenze hin, nicht auf eine echte
Fahrt - die Strecke wird dann verworfen statt eine unmögliche Fahrt
anzuzeigen.

Tankerkennung: optionaler zweiter Sensor für das Tankvolumen in Litern
(TANK_LITER_SENSOR, direkt vom CAN) neben dem bisherigen
Prozent-Füllstand. Ist er zugeordnet, übernimmt er die automatische
Tankerkennung vollständig - genauer als der Umweg über das im
Fahrzeugprofil hinterlegte Tankvolumen, und der erkannte Anstieg
liefert gleich eine grobe Vorbelegung für die getankte Menge statt
eines leeren Feldes. Ohne den Sensor bleibt alles beim Alten. Der
historische Import zieht mit derselben Präferenz nach.

Manifest auf 2026.8.25.2, beide Änderungsrunden live im Testcontainer
verifiziert (py_compile, Neustart, sauberes Setup-Log).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 22:04:26 +02:00
tobias 4fccf21676 Selbst-Update: Ergebnis erscheint ohne Neuladen, Panel übersteht Neustart
Zwei Meldungen des Besitzers ("Update installieren" braucht manuelles
Neuladen, um den Neustart-Knopf zu zeigen; die "HA startet neu..."-
Anzeige ebenso) auf denselben Grund zurückgeführt: datenLaden() rief
render() nur bei Änderungen an profil/fahrten/tank/status/batt auf,
nie bei einer reinen Änderung des Versions-Sensors. Dazu ein bereits
im Code kommentiertes, bisher nur per Tab-Klick umgangenes Rennen
beim echten HA-Neustart gefunden und behoben: panel_custom baut das
Element neu auf, aber nichts zeichnete es automatisch, bis der Nutzer
irgendwo klickte. companion-app erhielt den passenden Fix für die
Neustart-Anzeige (Panel-Bug 1 betraf sie nicht, siehe AGENTS.md).

Alle drei Fixes live im Testcontainer über einen echten
homeassistant.restart bestätigt, nicht nur simuliert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 19:39:40 +02:00