136 Commits

Author SHA1 Message Date
tobias cf06e28e8d Wide Bold fuer die grossen Zahlen, Genauigkeit der Position anzeigen
- Audi Type Wide Bold (700) eingebunden und fuer .fig/.ads-fig gesetzt, in
  beiden Codebasen. Light war zu duenn, Normal ebenfalls (Ansage des
  Eigentuemers). Die alte Notiz "es gibt nur 300 und 400, niemals fetter"
  ist damit ueberholt und entfernt statt ergaenzt - sie stand nur da, weil
  es keine anderen Schnitte gab.
- standort_genauigkeit_m wird jetzt angezeigt ("Position auf +-12 m genau",
  unter der Anschrift im Standortblatt). Der Wert kam seit jeher vom
  Backend, wurde im Panel in eine Variable gelesen und nirgends gezeigt -
  im Audit als totes Schema gefunden. In der App fehlte er ganz und ist
  jetzt durch Typ und Adapter verdrahtet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:58:19 +02:00
tobias f72264c4af Ein Wortlaut fuer den Fahrzustand: "Zuendung an"
Die Sicherheitsseite sagte "Zuendung an - Fahrzeug in Betrieb". Der Zusatz
behauptet mehr, als das Signal deckt: die Zuendungsquelle steht am Geraet
auf Motordrehzahl und meldet auch im Stand mit laufendem Motor "an".
Entscheidung des Eigentuemers ("zuendung an reicht") - jetzt in beiden
Oberflaechen und an allen drei Stellen derselbe Text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:49:33 +02:00
tobias 1a6c8d4740 Audi Type Wide Normal fuer die grossen Zahlen, Audit-Durchgang
- Audi Type Wide gab es hier nur als Light (300). Der Eigentuemer hat den
  vollen Satz aus Audis Styleguide geliefert; Wide Normal (400) ist jetzt
  eingebunden (App und Panel), Wide Bold liegt vor und bleibt bewusst
  ungenutzt. Lizenz wie bei allen Audi-Assets: nur diese eine private
  Installation, deshalb nichts davon in design-system.
- Dabei gefunden: die Aenderung im Design-System allein war wirkungslos.
  audi-schrift.css bringt fuer .ads-fig eine eigene, spaetere Regel mit und
  gewinnt. Im gebauten Stylesheet standen beide - nur die Reihenfolge
  entschied, und sichtbar wurde es erst am gerenderten getComputedStyle.
- Vollstaendiger Durchgang (AGENTS.md Abschnitt DC): Statik, struktureller
  Paritaetstest ueber Routen, Dienste, Sensorrollen, Wortlaut und
  Statusfelder, dazu ein Funktionstest an beiden laufenden Oberflaechen.
  Ein echter Befund: standort_genauigkeit_m ist totes Schema im Panel.
  Drei Meldungen des Eigentuemers waren keine Fehler - das breite Layout,
  der Beschriftungs-Schalter ohne Reiterleiste, und eine Testfahrt von mir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:47:50 +02:00
tobias e76252505f Fehlerspeicher, Amber statt Gruen, Tankbalken rot ab 90 km
- Neue Sensorrolle FEHLERSPEICHER_SENSOR (number_of_diagnostic_trouble_codes
  vom CAN) und eine fuenfte Zeile auf der Sicherheitsseite: "Keine
  Fehlermeldungen". Bewusst NICHT im _sicherheitscheck(): "gesichert"
  rechnet ein UND ueber diese Liste, und ein Eintrag im Fehlerspeicher sagt
  nichts darueber, ob der Wagen verschlossen dasteht.
- "Zuendung an" ist Amber statt Gruen. Gruen las sich als "alles in Ordnung
  und sicher"; unterwegs ist kein guter Zustand, sondern ein anderer.
- Das Modell-Auswahlfeld bekam dm-eingabe statt dm-auswahl - nur letzteres
  setzt appearance:none, deshalb zeichnete der Browser seinen nativen Pfeil
  neben den des Design-Systems. Die Breitenregel heisst jetzt
  dm-feld--fahrzeug und gilt fuer input und select.
- "aktuell" erscheint erst nach einer Pruefung, und fuer beide Abschnitte
  gemeinsam. Der App-Abschnitt zeigte die Marke schon, sobald ein Buendel
  vorlag - also unmittelbar nach einem Update wieder.
- Der Tankbalken wird rot ab 90 km Restreichweite, in Kilometern und nicht
  in Prozent: die Reichweite ist die Aussage, um die es geht, und das
  Fahrzeug rechnet die Fahrweise darin bereits ein. Farbe --red (Audi-CI,
  #F50537), nicht --bad-flaeche - letzteres ist fuer weisse Schrift darauf
  gewaehlt, und hier steht keine drauf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:31:52 +02:00
tobias 255a593b50 Ein Update-Knopf, Modell als Auswahlfeld, abgestellt schliesst fahrend aus
- Die Update-Kachel hat genau einen Knopf am Ende. Er traegt in jedem
  Zustand den naechsten faelligen Schritt (suchen, installieren, Neustart,
  Neuladen); Beschriftung und Fuellung wechseln mit. Die Abschnitte
  darueber behalten ihre Werte und verlieren nur ihre Knoepfe.
- Abstand unter der Trennlinie von 28 auf 16 px: das padding-top verhindert
  das Kollabieren des margin der Werteliste, also zaehlt jetzt nur noch das
  padding.
- Modell ist in der App ein Auswahlfeld statt eines Textfelds, wie im
  Panel. Die Liste liegt als Zweitschrift in daten/fahrzeugmodelle.ts;
  fahrzeugmodelle.test.ts liest die Panel-Quelle ein und vergleicht sie
  Eintrag fuer Eintrag, damit die beiden nicht auseinanderlaufen.
- Faehrt der Wagen, sagen Uebersicht und Sicherheitsseite das, statt
  "sicher abgestellt" zu behaupten. Die Zuendung meldet der Dongle in
  Sekunden, Tuer- und Schlossmeldungen kommen verzoegert - die schnellere
  Quelle hat Vorrang, weil sonst das Wort nicht stimmt.
- tankerkennung: der Tiefststand wird vor dem Anlegen gesetzt. Ein
  gepufferter Schwung Datensaetze legte sonst denselben Tankvorgang
  zweimal an, weil der zweite Zustandswechsel waehrend des Wartens noch
  den alten Tiefststand las (04.09.2026, 29,8 l und 29,7 l).
- flespi: nach dem Leeren des Zwischenspeichers wird nur abgeglichen, wenn
  ein Geraetewert vorliegt. Sonst ueberschrieb der Waechterfehler den
  frisch gelesenen Stand in der Kachel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:12:51 +02:00
tobias 1838ab0401 Pruefzeitpunkt gehoert unter den Suchknopf, nicht an die Integration
Der Zeitstempel stand an der gruenen Marke des Integrations-Abschnitts und sah
dort aus, als gaelte er nur fuer sie - obwohl der Suchknopf seit dem Umbau fuer
die ganze Kachel steht. Vom Eigentuemer bemerkt.

Jetzt steht "Zuletzt geprueft <Zeitpunkt>" unter dem Knopf, die gruene Marke
sagt nur noch "aktuell". In beiden Codebasen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 16:44:39 +02:00
tobias 0268d507d4 Update-Kachel aufgeraeumt: ein Suchknopf unten, weniger Text
Auf Vorgabe des Eigentuemers:

* Die Kachel heisst "Update" statt "Version" - sie zeigt beide Update-Wege.
* Der Suchknopf steht ganz unten unter beiden Abschnitten und ist in JEDEM
  Zustand da. Das legt nahe, was er tut: nach Neuem fuer Integration und App
  sehen. Vorher sass er nur im Zweig "ist aktuell", und daneben brauchte es
  ein zweites "Erneut pruefen", damit sich eine womoeglich wochenalte Zahl
  ueberhaupt auffrischen liess, solange ein Update angeboten wurde. Beides
  eruebrigt sich.
* Der Erklaertext "Laedt die neueste Fassung von Gitea ..." ist entfallen.
* Im App-Abschnitt steht statt eines Satzes nur noch "Update <Fassung>
  vorhanden" - wer ohnehin auf Update klickt, braucht nicht mehr.
* Der Pruef-Spinner mitten in der Kachel ist weg; der Knopf sagt selbst
  "Pruefe ...".

In beiden Codebasen. Am laufenden Panel nachgesehen: "Update | Integration
2026.9.5.33 | Auf Update pruefen", ein einziger Knopf. 300 Tests gruen,
0 Tracebacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 16:34:45 +02:00
tobias 45259ae9ba Buendel und Manifest muessen dieselbe Fassung tragen - Test dagegen
Am iPhone gesehen: der Server meldete 2026.9.5.29, das mitgelieferte Buendel
war noch .28. buendelPasst() verlangt Gleichheit, die App hielt sich deshalb
fuer nicht erneuerbar - und die Meldung schickte den Eigentuemer zu Xcode,
obwohl nur meine Veroeffentlichung unvollstaendig war: ich hatte die
Versionsnummer erhoeht, ohne npm run ota laufen zu lassen. Betraf .29 und .30.

Zwei Aenderungen:

* buendelversion.test.ts vergleicht die Version in bundle.json mit der in
  manifest.json. Gegengeprueft: der Test faellt, wenn sie auseinanderlaufen.
  Damit wird daraus ein Fehlschlag vor der Auslieferung statt einer
  irrefuehrenden Meldung auf dem Telefon.
* Der Hinweis unterscheidet jetzt zwei Faelle. Liegt ein Buendel, das nur
  nicht zur Serverfassung passt, heisst es, die App koenne sich erneuern,
  sobald eine Veroeffentlichung ein passendes mitbringt. Nach Xcode wird nur
  noch verwiesen, wenn gar kein Buendel da ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 16:23:47 +02:00
tobias 2a4337e030 App meldet nicht mehr "aktuell", wenn ein neueres Buendel danebenliegt
Vom Eigentuemer am iPhone bemerkt: nach "Update installiert, Neustart noetig"
zeigte der App-Abschnitt "5.22 aktuell", obwohl auf der Platte bereits das
Buendel 5.29 lag.

Ursache ist kein Fehler, sondern eine bewusste Bedingung: buendelPasst()
verlangt, dass das Buendel der Fassung entspricht, die diese Installation
gerade ausliefert. Zwischen Installation und Neustart meldet der laufende
Server aber noch seine alte Nummer - das Buendel wird deshalb zurecht nicht
angeboten. Nur "aktuell" zu schreiben war dabei falsch.

Jetzt steht dort, dass die Fassung bereitliegt und nach dem Neustart abrufbar
ist. Kein Knopf: zu tun ist erst danach etwas.

Dazu auf Wunsch des Eigentuemers der Knopftext in beiden Oberflaechen:
"Installation abschliessen und neu starten" statt "... - Jetzt neu starten".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 16:20:19 +02:00
tobias 8b213c540e flespi: kein sleep_mode-Schreiben ohne bekannten Modus
Am laufenden System ausgeloest: nach einem "Jetzt lesen" - das leert flespis
Zwischenspeicher - kannte der Schreibweg das mode-Objekt nicht mehr und
schickte einen timeout ohne type. flespi lehnt das mit 400 ab (the following
properties are missing: type).

Der else-Zweig ist ersatzlos weg. Ist das mode-Objekt samt type unbekannt,
wird gar nicht geschrieben und der Grund gemeldet. Der zweite Grund wiegt
schwerer als der 400er: type IST der Schlafmodus (2 = Deep Sleep, 4 = Ultra
Deep Sleep). Ihn zu erraten hiesse, den Schlafmodus des Dongles zu verstellen
- das darf ohne ausdrueckliche Freigabe nie passieren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 16:03:55 +02:00
tobias 356a0bdf76 Nur Versionsnummer - Nachweis, dass die Meldung durch das Neuladen verschwindet
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 15:26:55 +02:00
tobias 462970251e Meldung verschwindet durch das Neuladen, zu dem sie auffordert
Vorgabe des Eigentuemers: klarer Zustand statt einer Meldung, die auch nach
dem Neuladen stehenbleibt.

Panel: nach einem Update ohne Neustart wird der Sidebar-Eintrag mit der neuen
Versionsnummer neu angemeldet (koordinator.panel_neu_anmelden) - ohne das
traegt die Modul-URL weiter die alte Nummer. Das Panel vergleicht jetzt die
Version aus seiner Modul-URL mit der von der Platte gelesenen: sind sie
gleich, laeuft es bereits auf der installierten Fassung und die Meldung
entfaellt. Sie verschwindet damit genau durch das Neuladen, zu dem sie
auffordert.

App: fuer ein Update ohne Neustart erscheint gar nichts mehr. Geaendert wurden
Dateien des Panels; die App hat hier nichts zu tun. Damit gilt durchgehend:
keine Meldung = alles in Ordnung, Neustart-Knopf = handeln.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 15:26:19 +02:00
tobias 0e6e7fa9ba App: kein irrefuehrender Neuladen-Knopf beim Integrations-Update
Der Zweig fuer ein Update ohne Neustart bot in der App 'App neu laden' an.
Das versprach etwas, das es nicht einloesen kann: geaendert wurden Dateien
unter frontend/, und das sind die des PANELS. Die App laedt ihren eigenen Code
aus /local/dmapp bzw. der nativen Huelle, ihr Update laeuft ueber das
OTA-Buendel im Abschnitt darunter. Vom Eigentuemer im Bild bemerkt.

Jetzt steht dort nur noch der Hinweis, dass kein Neustart noetig ist und das
Panel die Dateien beim naechsten Oeffnen laedt. Im Panel bleibt 'Seite neu
laden' - dort stimmt es.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 15:14:49 +02:00
tobias 4bed48d69e Probeaenderung entfernt - reine Oberflaechen-Veroeffentlichung
Aendert nur audi-dashboard-app.js und die Versionsnummer. Damit muss die
Neustart-Erkennung 'kein Neustart noetig' melden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 13:51:41 +02:00
tobias 7c96bc8304 Neustart-Erkennung: Zeilenenden zaehlen nicht als Aenderung
Am echten Update-Weg gemessen: die installierte Fassung lag mit CRLF auf der
Platte, das Gitea-Archiv liefert LF. Drei .py-Dateien galten dadurch als
geaendert, obwohl sie nach Entfernen der CR byteweise identisch waren - die
Erkennung verlangte einen Neustart fuer einen rein kosmetischen Unterschied.

Textdateien werden jetzt vor dem Hashen auf LF normalisiert. Binaerdateien
(Schriften, Bilder, das OTA-Zip) bleiben roh, dort waere 0x0D0A echter Inhalt.
Drei neue Testfaelle: reiner Zeilenenden-Unterschied, echte Aenderung trotz
verschiedener Zeilenenden, Binaerdatei. 26 Tests der Suite gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 13:51:02 +02:00
tobias 189dff5255 Probeaenderung: nur Oberflaeche, fuer den Nachweis der Neustart-Erkennung
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 13:30:46 +02:00
tobias 81808dbc10 Update ohne Neustart, wenn nur Oberflaechendateien wechseln
Bisher verlangte jedes Update einen Home-Assistant-Neustart. Noetig ist er
aber nur fuer Python-Code: HA importiert die Module einmal beim Start und
haengt danach mit lebenden Objekten daran. frontend/ dagegen wird direkt von
der Platte ausgeliefert (StaticPathConfig, cache_headers=False) - eine
ersetzte .js ist sofort wirksam, es braucht nur ein Neuladen im Browser.

aktualisierung.neustart_noetig() vergleicht die alte gegen die neue Fassung
ueber sha256 je Datei und meldet "kein Neustart" nur, wenn JEDE Abweichung
unter frontend/ liegt oder die manifest.json ist. Alles andere - .py,
services.yaml, translations/, vorlage/ - gilt als neustartpflichtig, auch wo
es das im Einzelfall nicht waere. Die Schieflage ist Absicht: ein
faelschlich ausgelassener Neustart laesst neuen Python-Code nie anlaufen, und
der Fehler wird woanders gesucht.

manifest.json ist ausgenommen, weil sich seine Versionsnummer bei jeder
Veroeffentlichung aendert - sonst waere die Unterscheidung wertlos. Weil HA
das Manifest fuer die Laufzeit festhaelt (loader.py: hass.data[
DATA_INTEGRATIONS]), liest version_von_platte() die Nummer direkt von der
Platte; koordinator.version_neu_lesen() zieht sie nach einem Update ohne
Neustart nach, damit Panel und App die neue Fassung auch anzeigen.

Panel und App zeigen im Neustart-freien Fall "Seite neu laden" statt
"Installation abschliessen - Jetzt neu starten".

Neun neue Testfaelle fuer die Unterscheidung, 23 Tests in der
Aktualisierungs-Suite gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 13:30:25 +02:00
tobias 4dad3d21a8 App-Abschnitt zeigt gruenes "aktuell" statt eines Satzes
Statt "Diese App ist aktuell." dieselbe gruene Marke wie bei der Integration -
Vorgabe des Eigentuemers. Ohne Zeitpunkt: anders als bei der Integration wird
hier nicht auf Knopfdruck geprueft, das Buendel meldet der Server beim Laden
mit. Der Fall "kein Buendel hinterlegt" bleibt ein Satz.

Nicht im Bild geprueft: der Abschnitt erscheint nur in der nativen Huelle,
otaMoeglich() ist im Browser falsch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 12:48:40 +02:00
tobias 71403efafe Speicherleiste ersatzlos gestrichen, Felder speichern beim Verlassen
Auf Entscheidung des Eigentuemers nach der HIG-Pruefung. Gegen die Leiste
sprach viererlei: iOS kennt kein solches Muster auf einer geschobenen
Einstellungsseite; sie schwebte ueber der Tab-Leiste und deckte einen Knopf
der Kachel dahinter zu (gemessen); sie war die Ursache des Datenverlusts bei
der Erstzulassung; und das Panel hatte sie nie - dort schreibt jede Aenderung
sofort ins Profil.

Jetzt speichern Textfelder beim Verlassen, Auswahlfelder sofort. Die Meldung
"Gespeichert." entfaellt mit. Der Regler "Fahrt beenden" behaelt seinen
eigenen Bestaetigungsschritt - ihn bei jedem Zwischenschritt zu sichern hiesse
ein Dutzend Schreibvorgaenge je Bedienung.

Mit echten Eingabeereignissen am laufenden System geprueft: getippt, ins
naechste Feld geklickt, Wert steht in fahrzeugprofil.json; danach der
urspruengliche Wert wiederhergestellt. 299 Tests gruen, 0 Tracebacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 12:42:50 +02:00
tobias b22a0eca5b Beschriftung auf "Integration" gekuerzt
"Home-Assistant-Integration" brach auf Telefonbreite in zwei Zeilen um. Auf
Entscheidung des Eigentuemers gekuerzt; nachgemessen einzeilig, 20px hoch.
In beiden Codebasen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 11:40:18 +02:00
tobias 5a65ec9590 Kein Rueckschritt mehr als Update, Version-Kachel schlanker
aktualisierung.py pruefte mit `remote_version != eigene_version` - reine
Ungleichheit ohne Richtung. Lief die Instanz einer Veroeffentlichung voraus,
bot die Integration an, sich auf die aeltere Fassung zu aktualisieren. Vom
Eigentuemer gemeldet. ist_neuer() ist jetzt das Gegenstueck zu
versionOrdnung() in appVersion.ts, wo derselbe Fehler am 04.09. behoben wurde:
fehlende Stellen zaehlen als 0, kein int() mit Vorabschnitt, und was sich
nicht in eine Reihenfolge bringen laesst, gilt als verfuegbar statt
verschwiegen. Sechs neue Testfaelle, 79 Python-Tests gruen.

Dazu auf Vorgabe des Eigentuemers: die Zeile benennt jetzt selbst, was sie
zeigt - "Home-Assistant-Integration | 2026.9.5.19" statt "Installiert" mit dem
Gegenstand klein darunter. Damit entfallen die Abschnitts-Ueberschriften aus
dem vorigen Commit, sie sagten dasselbe doppelt.

Live in beiden Oberflaechen gegengeprueft: Gitea auf .18, Instanz auf .19 -
gemeldet wird gruenes "aktuell" (--ok) mit Pruefzeitpunkt statt eines
Rueckschritts. 0 Tracebacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 11:33:56 +02:00
tobias f693a41692 Version-Kachel: Klassenkollision behoben, "aktuell" statt Satz
Die zusammengefuehrte Kachel war zerlegt: ich hatte den Wrapper
.dm-abschnitt genannt, den Namen gibt es aber schon - als Flex-Zeile mit
space-between (bausteine/FahrtDetail/TankDetail). Der Kachelinhalt lag dadurch
nebeneinander statt untereinander, der Knopf lief aus der Kachel. Der neue
Wrapper heisst .dm-kachelabschnitt, die bestehende Klasse bleibt unberuehrt.

Gefunden hat es der Eigentuemer, nicht ich: ich hatte nach dem Umbau die
Abstaende der eingesetzten Probe gemessen, aber nie die Kachel selbst
angesehen.

Dazu auf seine Vorgabe: statt "Integration ist aktuell (Zeitpunkt)." steht
jetzt ein gruenes "aktuell" direkt unter dem installierten Stand, der
Zeitpunkt gedaempft daneben. Wurde NIE geprueft, steht dort gar nichts mehr -
gruen "aktuell" waere ohne Pruefung eine Behauptung ohne Grundlage, und der
Knopf "Auf Update pruefen" sagt den Zustand ohnehin. In beiden Codebasen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 11:29:04 +02:00
tobias 80a3eef6ec Update-Kacheln zu einer zusammengefuehrt
Die App hatte "App-Update" (OTA-Buendel) und "Version" (Integration) als zwei
Kacheln nebeneinander - zweimal dieselbe Frage. Jetzt eine Kachel "Version"
mit zwei benannten, durch eine Haarlinie getrennten Abschnitten; ohne das
staenden zwei gleich aussehende "Update installieren"-Knoepfe untereinander,
ohne dass man ihnen ansieht, was sie erneuern.

Die Unterueberschrift "Integration" erscheint nur, wenn wirklich ein zweiter
Abschnitt folgt (otaMoeglich()) - sonst waere es eine Unterteilung ohne
zweiten Teil. Wo es kein OTA gibt, also im Panel und in der App im Browser,
sieht die Kachel damit aus wie zuvor.

Das Panel bekommt den App-Abschnitt nicht: die App traegt ihre Dateien fest
gebuendelt und kann veralten, das Panel laedt sie bei jedem Aufruf neu. Eine
geplante Abweichung.

Nachgemessen: Panel 216px, App im Browser 203px, gleicher Inhalt; die
Gestaltung des zweiten Abschnitts durch Einsetzen gegengeprueft. 299 Tests
gruen, 0 Tracebacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 11:20:49 +02:00
tobias 52daaf8611 Knopfpaar von der Speicherleiste getrennt, Modell "RS 6 Avant performance" raus
Das QR-Blatt benutzte dm-speicherleiste als reine Gestaltung eines Knopfpaares,
obwohl dort nichts gespeichert wird - vom Eigentuemer richtiggestellt. Der
Name machte eine Pruefung unbrauchbar, die genau danach suchte. Jetzt trennt
.dm-knopfpaar (zwei gleich breite Knoepfe) von .dm-speicherleiste (dasselbe
Paar als klebende Leiste fuer ein Formular mit Speichern-Schritt).

"RS 6 Avant performance" ist aus der Modellauswahl entfernt; "RS 6 Avant"
stand bereits darin, die Ausfuehrung traegt der Nutzer selbst ein. Es war der
einzige Eintrag, der in der auf 180px vereinheitlichten Auswahl abgeschnitten
wurde. Ein Profil mit dem alten Wert behaelt ihn - die Auswahl stellt einen
nicht gelisteten Titel als eigene Option voran.

Nachgemessen am laufenden System: .dm-knopfpaar flex mit 12px Abstand und zwei
gleich breiten Knoepfen, .dm-speicherleiste weiterhin klebend; zehn Modelle,
laengster 115px bei 142px Platz.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 11:00:28 +02:00
tobias 063446239c Felder unter "Fahrzeug einrichten" gleich lang
Ohne feste Breite bemisst sich jedes Feld nach seinem eigenen Inhalt, die
rechte Kante steht fest, und die linken Kanten fransen aus: gemessen
216/202/195/206 in der App und 220/209/203/213 im Panel.

Jetzt 180px in beiden. Der Wert ist die engste Grenze beider Oberflaechen -
das laengste Label ("Erstzulassung") laesst in der App nur 183px fuers Feld,
im Panel 196px.

Vorbehalt: von elf Modell-Eintraegen ist einer laenger als der verbleibende
Textplatz ("RS 6 Avant performance", 182px gegen 142px) und wird ausgewaehlt
abgeschnitten. In der aufgeklappten Liste steht er vollstaendig.

Nachgemessen in beiden: vier Felder je 180px, gleiche linke Kante, kein Label
bricht um.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 10:39:40 +02:00
tobias 44dcb3c9d9 Erstzulassung war in der App nicht speicherbar
Einstellungen.tsx fuehrt fuer die Erstzulassung einen eigenen Entwurf neben
`entwurf`. Zwei Stellen kannten ihn nicht: speichern() stieg in der ersten
Zeile aus, und die Speicherleiste haengt allein an `entwurf` - wer nur die
Erstzulassung aenderte, bekam also gar keinen Speichern-Knopf zu sehen und
verlor den Wert beim Verlassen der Seite. Damit blieb auch die
Hauptuntersuchung leer, die daraus abgeleitet wird. Das Panel speichert an
dieser Stelle sofort und war nie betroffen.

Dazu:

* Platzhalter beider Felder war MM/JJJJ, obwohl ein voller Tag gemeint ist -
  jetzt TT.MM.JJJJ. Die Monatsform bleibt lesbar, damit gespeicherte Werte
  wie "08/2026" gueltig bleiben.
* Das Tagesmuster nimmt jetzt Punkt oder Bindestrich, in beiden Codebasen
  wortgleich. "01-03-2025" ergab vorher still nichts. Unmoegliche Daten
  werden weiterhin abgewiesen, die ISO-Form nicht verwechselt.

Geprueft am laufenden System ueber die ganze Kette: eintragen, speichern,
Profildatei, dann App-Service, Panel-Service und Panel-Mein-Audi mit
uebereinstimmend 15.06.2027. 299 Tests gruen, 0 Tracebacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 10:31:53 +02:00
tobias ddcd9396b0 Kachelueberschriften bekommen Luft, Profil raeumt ausgemusterte Felder auf
Der Abstand zwischen Kachelueberschrift und erster Inhaltszeile war 0, 10 oder
12px, je nachdem ob das folgende Element einen eigenen oberen Rand mitbrachte.
Bei 0 klebte die Ueberschrift an der Zeile und las sich als deren Beischrift.
Jetzt einheitlich 14px, gesetzt am Kopf selbst - benachbarte Raender fallen
zusammen, vorhandene 10/12 werden also angehoben statt addiert, und absolut
gesetzte Nachbarn wie das Zahnrad bleiben unberuehrt. Nur die erste
Beschriftung einer Kachel: .label/.ads-eyebrow tragen auch Inhaltszeilen wie
die Anschrift des Autohauses, die bleiben unveraendert.

Der urspruengliche Auftrag lautete "Panel wie App setzen" und beruhte auf einer
Falschaussage von mir: ich hatte die 10px-Grossschrift aus dem Design-System
gelesen statt die App zu messen. Die App ueberschreibt sie dort absichtlich auf
den Panel-Wert; beide waren laengst identisch.

Dazu: _profil_aufraeumen() entfernt ausgemusterte Profilfelder einmalig beim
Start, Anlass ist fahrzeug.hauptuntersuchung_faellig. Ohne das bliebe der tote
Schluessel fuer immer stehen, weil beide Oberflaechen das Profil vollstaendig
zurueckschreiben. An der Testinstanz geprueft, einschliesslich zweitem Start
ohne Schreibvorgang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 02:36:16 +02:00
tobias e9c16325cc Musterseite verworfen, Zahnrad-Regression behoben, Fotomenue als Aktionsblatt
Musterseite entfernt (companion-app/muster, vite.muster.config.ts,
/config/www/dmmuster). Ansage des Eigentuemers: sie fuehrt nur zu neuen
Fehlerquellen. Ausloeser war ein Befund, den ich als App-Fehler gemeldet hatte
- Ringe und Typenschild schwarz auf schwarz - und der nur dort bestand.

Behoben:

* Das Zahnrad im Autohaus-Kasten hatte seine Ecke verlassen (58/288 statt
  8/8). Ursache war mein HIG-Durchgang von gestern: .zahnrad kam in die
  Gruppe fuer die 44x44-Trefferflaeche und bekam damit position:relative,
  was das absolute aus dem Basisblatt ueberschrieb. Die Auflage war
  ueberfluessig - .zahnrad ist dort schon 44x44.
* Das Fotomenue hing mit position:absolute an der Kachel statt am
  Bildschirm und lag bei der hohen Raeder-Kachel ausserhalb des Sichtfelds;
  einen Abbrechen-Knopf hatte es nie. Das Panel benutzt jetzt dasselbe
  Aktionsblatt wie die App. .bildmenu ist samt Zustand, Markup, totem
  Handler und CSS entfernt.
* Der Blattkopf las sich als dritte Auswahl (linksbuendig, 15px, --fg).
  Jetzt zentriert, 13px, gedaempft - Apples Muster, in beiden Codebasen.
* "Anstehende Termine" stand 5px ueber "Oelwechsel" und wirkte als dessen
  Beischrift. Entfernt.
* Wartungsplan-Formular: Art nach oben, Datum 124px statt Browser-Vorgabe,
  Werkstatt ueber die volle Zeile (218 -> 306px), Kosten 116px. Zwei
  Erklaertexte entfernt.
* Thema folgt jetzt einer Systemumstellung im laufenden Betrieb. Vorher las
  es prefers-color-scheme genau einmal beim Laden. Drei Tests, die ohne die
  Behebung nachweislich fallen.

Geprueft: 295 App-Tests, Panel node --check, 0 Tracebacks, alle Aenderungen
live an der Testinstanz gemessen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 02:24:08 +02:00
tobias 863284e540 Hauptuntersuchung: Ableitung aus Erstzulassung und Wartungsplan, drittes Feld entfernt
Gemeldet: Wartungsplan leer, Erstzulassung 01.03.2025, erwartet 01.03.2028 -
gezeigt wurden drei verschiedene Antworten (Uebersicht 01.08.2026, Mein Audi
"August 2026", Service "kein Eintrag im Wartungsplan").

Ursache war das Profilfeld hauptuntersuchung_faellig ("08/2026"), das die
Ableitung ueberstimmte, in keiner Oberflaeche ein Eingabefeld hatte und im
Backend kein Schema. Drei Bildschirme verarbeiteten denselben Wert
verschieden. Das Feld ist samt Lese- und Schreibweg entfernt; es gibt jetzt
zwei Quellen: Wartungsplan + 24 Monate, sonst Erstzulassung + 36 Monate.

Dabei mitbehoben:

* Die Service-Kachel verschluckte abgeleitete Termine, sobald kein
  Wartungsplan-Eintrag dahinterstand.
* monatePlus() in der App verlor einen Tag ueber die Zeitumstellung (drei von
  fuenf gemessenen Faellen) - betraf Oelwechsel- und Inspektionsprognose
  genauso. Das Panel rechnete dort seit jeher richtig.
* Die App verlangte fuer jeden Wartungsplan-Eintrag eine Kilometerangabe. Eine
  Hauptuntersuchung ist rein datumsbasiert; ohne km waere der erste HU-Eintrag
  ignoriert worden. Ausserdem nahm das Panel den ersten Treffer der Liste, die
  App den juengsten - beide nehmen jetzt den juengsten.
* Monatsgenauigkeit ("03/2025") bleibt auf beiden Seiten erhalten.

Neu: paritaet_hu.test.ts vergleicht die App gegen die aus der Panel-Quelle
herausgeschnittenen Originalfunktionen, 15 Faelle. Gesamt 292 App-Tests, 73
Python-Tests mit 231 Untertests, 0 Tracebacks. Live an der Testinstanz auf
allen drei gemeldeten Bildschirmen geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 01:56:11 +02:00
tobias 5621b818cc flespi: echtes Lesen statt Zwischenspeicher, und kein Gleichstand ohne Grundlage
Der Waechter meldete am 04.09.2026 "stimmt ueberein" fuer
trip_scenario.ign_off_timeout: 900 gegen unsere Konstante NACHLAUF_S = 900.
Das Geraet hat dort 0 - das Trip-Szenario ist abgeschaltet. flespis
Zwischenspeicher trug den Stand vom 01.09. 13:15, dreieinhalb Tage alt.
Der Eigentuemer hat es aufgedeckt: "die gelesenen werte sind veraltet".

Zwei Aenderungen:

1. "Jetzt lesen" leert erst den Zwischenspeicher der ueberwachten
   Einstellungen (DELETE /gw/devices/{id}/settings/{name} - die
   API-Entsprechung des Panel-Knopfes "clear cache and synchronize"; loescht
   den gespeicherten Wert, nicht den im Geraet) und holt dann neu.
   Einstellungen mit einem AUSSTEHENDEN Wert werden uebersprungen: dasselbe
   DELETE wuerde ihn verwerfen, und die Aenderung kaeme nie an - wie
   1003/1004, die am 04.09. acht Stunden in der Warteschlange standen.

2. `abweichung` ist dreiwertig: true, false und **null (kein Urteil)**. Null
   steht dort, wo ein Vergleich nichts aussagt - der Wert wurde nie vom
   Geraet bestaetigt, oder es ist gerade eine Aenderung unterwegs. Dazu
   traegt jede Zeile `gemeldet_am` (flespis `updated`), und beide
   Oberflaechen zeigen es an: "bestaetigt 01.09.2026 (4 T. alt)" bzw.
   "nie bestaetigt".

Ein stiller Gleichstand mit einem veralteten Wert ist schlimmer als gar kein
Vergleich: er behauptet Sicherheit, wo keine ist.

tsc sauber, 271 Tests, vite build sauber, Panel als Modul geparst,
py_compile sauber, audi_ha_test auf 2026.9.4.26 ohne Traceback.
2026-09-05 01:11:23 +02:00
tobias 9ac69cdf44 Eine Neuschreibung ist kein Wechsel - Wartezeit startet nicht mehr neu
Home Assistant schreibt denselben Zustandswert auch neu, wenn sich nur ein
Attribut geruehrt hat. Am 04.09.2026 gezaehlt: von 22 Zustandszeilen der
Zuendung waren nur 6 echte Wechsel, die uebrigen 16 Neuschreibungen - und
alle 6 Wechsel lagen ausserhalb der Fahrten. Das Zuendungssignal ist
unterwegs stabil.

Der Beobachter unterschied das nicht. Fiel eine Neuschreibung von "aus" in
die laufende Wartezeit, brach Zweig 2 die wartende Bestaetigung ab und
begann die 15 Minuten von vorn. Am 04.09. ist es nicht passiert - die
Neuschreibungen lagen hinter dem Fenster -, aber das Loch war da.

_signalwechsel() bekommt jetzt mit, ob es ein echter Wechsel war. Eine
Neuschreibung, waehrend fuer diese Fahrt schon eine Bestaetigung wartet,
laesst sie stehen.

Bewusst NICHT als Filter ganz oben: _spaetes_aus() verlaesst sich darauf,
ein Zuendungs-Aus auch als off -> off zu sehen - daran haengt das
Nachtragen vorlaeufiger Enden. Die Pruefung sitzt nur in Zweig 2, und ein
eigener Test belegt, dass der andere Weg weiter offen ist.

21 Tests (von 18), Gegenprobe mit ausgeschalteter Pruefung scheitert wie
erwartet an einem. py_compile sauber, audi_ha_test auf 2026.9.4.25 ohne
Traceback.

AGENTS.md haelt ausserdem fest: die Beschreibung des Beenden-Knopfes war zu
weit gefasst (die Korrektur zieht nur nach hinten, nie nach vorn), und der
flespi-Cache laesst sich ueber DELETE auf die Einstellung zum Neulesen
zwingen - mit dem Haken, dass dabei ausstehende Werte verloren gingen.
2026-09-05 01:02:11 +02:00
tobias 242312441d Pausenregel rechnet in Geraetezeit - zwei Fahrten bleiben zwei
Am 04.09.2026 war der Dongle von 18:25 bis 23:36 ohne Verbindung. Als er
sich meldete, kamen das Zuendungs-Aus der ersten Fahrt und das Ein der
zweiten in DERSELBEN Sekunde bei Home Assistant an - ihre Geraetezeiten
lagen 5 Stunden 12 auseinander (18:23:32 und 23:35:43).

Die Pausenregel mass an der Ankunft: 0 Sekunden, also dieselbe Fahrt.
Ergebnis war ein Eintrag von 18:10 bis 23:42 ueber 5 h 33 ohne Strecke,
und die Fahrt um 23:30 gab es gar nicht.

Zwei Stellen im Code trugen dazu bei:
- zuendung_geaendert() rief bedingungslos warte_ende_ab_abbrechen(); das
  "an" loeschte damit das wartende Ende des "aus" derselben Sekunde.
- Der Fall "an, waehrend eine Fahrt laeuft" fiel wortlos durch alle drei
  Zweige von _signalwechsel(). Dazu steht seit dem 02.09. ein Kommentar im
  Code - damals wurde eine Sperre eingebaut, die die Reihenfolge richtig
  stellt, aber nicht den Ausgang.

Neu entscheidet _zuendung_kehrt_zurueck() in Geraetezeit. Der Koordinator
fuehrt mit, welches Ende gerade seine Pausenzeit absitzt (ende_wartet).
Liegt zwischen dem echten Ende und dem neuen "an" mehr als die Pausenzeit,
wird die alte Fahrt auf ihr berechnetes Ende geschlossen und eine neue
begonnen. Gemessen wird ab dem echten Ende, nicht ab dem Signal. Ohne
wartendes Ende passiert nichts - ein Schnitt auf Verdacht waere schlimmer
als eine zu lange Fahrt.

Das behebt die Verspaetung nicht, es macht sie harmlos.

Die Tests mussten zwei Dinge trennen, die vorher eine Zahl waren: PAUSE_S
(900, das Fenster der Regel) und WARTEN (0,15 s, die Wanduhrzeit des Tests).

18 Tests in test_zuendungspause.py (von 14), py_compile sauber,
audi_ha_test auf 2026.9.4.24 ohne Traceback. Gegenprobe am alten Stand:
2 der 4 neuen Tests scheitern dort, mit der Korrektur laufen alle 18.
2026-09-05 00:40:59 +02:00
tobias b0ec8b4982 Pruefdurchgang ueber alle 26 Bildschirme - fuenf Fehler behoben, HIG nachgezogen
Gemessen statt geschaut: eine Pruefroutine lief auf jedem Bildschirm und
pruefte Ueberlauf, Tippziele, abgeschnittenen Text, Bilder ohne Quelle und
WCAG-Kontrast fuer jeden Textknoten. Dazu die Live-App mit echten Daten, die
Musterseite fuer Leerzustaende und das Panel zum Abgleich.

1. Deutsche Datumsangaben wurden falsch gelesen. new Date("01.03.2025")
   liest amerikanisch: Tage bis 12 wurden still vertauscht, ab Tag 13 fiel
   der Wert ganz weg. Die Erstzulassung stand auf zwei Bildschirmen
   unterschiedlich da. alsZeitpunkt() erkennt jetzt ISO, TT.MM.JJJJ und
   MM/JJJJ, weist unmoegliche Daten ab und fuehrt die Genauigkeit mit -
   aus "08/2026" wird "August 2026", kein erfundener Erster.

2. Die Hauptuntersuchung war in beiden Oberflaechen unerreichbar. Das Panel
   leitete sie aus der Erstzulassung ab, aber nur bei MM/JJJJ, und benutzte
   den eingetragenen Wert gar nicht. Jetzt beide: eingetragene Faelligkeit
   vor Servicebuch vor Erstzulassung + 24 Monate.

3. Die Tankstellenmarke stand zweimal da, wenn der Name kein Komma hat
   ("Shell München Ost"). ohneMarkeVorn() nimmt sie vorn ab.

4. Bilder wurden geholt, obwohl beide wussten, dass es sie nicht gibt -
   fuenf 404 fuer dieselbe Datei in einem Ladevorgang. Jetzt gar keine
   Adresse bei Stand 0; nachgemessen 4 Anfragen, alle 200.

5. Ein Bildelement ohne Quelle zeigte das kaputte Bildsymbol, und onerror
   greift dort nicht: ohne src gibt es keinen Ladeversuch.

Dazu: leere Klammern beim Steuersatz, leere graue Kachel bei leerer
Leistungsgruppe, Umbruch auf dem Einrichtungsbildschirm, drei
Leerdarstellungen in einer Kachel, fehlende Grundschrift.

Apple HIG: Kontrast war an einer Stelle 3,18 statt 4,5, weisse Schrift auf
der Loeschflaeche 3,41/3,55. Rot als Schrift und Rot als Flaeche sind jetzt
getrennte Token (--bad / --bad-flaeche), beide Apples systemRed fuer
erhoehten Kontrast. Tippziele: unsichtbares 44x44-Overlay, wo die sichtbare
Groesse Teil des Bildes ist, echte Mindesthoehe, wo das Element Flaeche ist -
Formulare werden dadurch sichtbar hoeher.

Neu auf Wunsch: Wischen in der Bildergalerie (beide Richtungen) und die
HIG-konforme Loeschgeste - ein voller Wisch loescht ohne zweiten Tipp, ab
55 % der Zeilenbreite, mit wachsender roter Flaeche und erhaltener
Rueckfrage. Dabei fiel auf, dass der Zugwert aus dem React-Zustand gelesen
wurde und ein schneller Wisch dadurch verlorenging; er liegt jetzt in einer
Referenz.

Paritaet: alle 26 Routen und Titel decken sich, Fahrzeugstatus zeilengleich,
zwei ungeplante Abweichungen (Datumsformatierung, Herkunft der
Hauptuntersuchung) geschlossen.

tsc sauber, 271 Tests, vite build sauber, Design-System gebaut, Panel als
Modul geparst, audi_ha_test auf 2026.9.4.23 ohne Traceback. Live nachgemessen:
Datum auf beiden Bildschirmen gleich, keine 404 mehr, Zurueck-Pfeil sichtbar
34x34 und treffbar 44x44, Galerie wischt in beide Richtungen, voller Wisch
loest die Rueckfrage aus, keine Kontrast-Unterschreitung mehr.
2026-09-04 18:11:07 +02:00
tobias 8557bfe230 Markenerkennung: Grenzen gemessen, Summenzeile im Kopf ist keine Marke
Auf die Frage, ob Belege einheitlich gestaltet sind: nein. Die Erkennung
haengt an drei Annahmen - Marke in den ersten acht Zeilen, Marke in der
festen Liste, Marke ueberhaupt vorhanden. Alle drei sind jetzt gemessen und
in Tests festgehalten.

Dabei ein echter Defekt gefunden: "Total" ist Kopfmarke UND Summenwort. Ein
Beleg mit Betrag in den ersten acht Zeilen lieferte faelschlich die Marke
"Total" (an einem echten Beleg reproduziert). _betragszeile() filtert
Summenzeilen jetzt aus dem Kopf - verlangt werden beide Kennzeichen zugleich,
Summenwort und Geldangabe, damit "SHELL TANKSTELLE, 6450 SOELDEN" nicht
mitfaellt. Gegenprobe: ein echter "TOTAL TANKSTELLE"-Kopf ergibt weiter Total.

Der Ausfall beschaedigt nichts: mit unbekannter Marke im Kopf bleiben Liter,
Betrag, Rabatt und Anschrift unveraendert richtig, nur die Marke fehlt.

Parser-Suite 17 Tests (von 11), alle zwoelf abgelegten Belege liefern weiter
Shell, audi_ha_test auf 2026.9.4.22 ohne Traceback.
2026-09-04 17:09:12 +02:00
tobias bdbdb4a010 Tankstellenmarke als eigenes Feld, Belegfelder in der App richtig zugeordnet
Die Marke steht auf jedem Beleg, war aber nirgends gespeichert: der Parser
erkennt sie seit dem 16.08.2026, faltete sie aber nur in station_name.
Fuer jeden Beleg, den eine aeltere Fassung eingelesen hat, blieb dort der
Betreibername stehen und die Marke war danach nicht mehr zu holen.

- shell_beleg_parser: station_brand als eigenes Feld aus beiden Parserwegen,
  dazu marke_aus_text()/marke_aus_pdf() - sie lesen nur den Belegkopf und
  haengen nicht am vollstaendigen Einlesen.
- belege.marken_nachtragen(): traegt die Marke einmal beim Start aus der
  abgelegten Belegdatei nach. Nur wo das Feld gar nicht vorkommt und die
  Datei liegt; ein einmal eingetragenes Feld wird nie ueberschrieben.
- Panel und App: die gespeicherte Marke geht vor, geraten wird nur, wo keine
  da ist. Ein Teil, der fuer sich genommen eine Kopfmarke ist, taugt zudem
  nie als Ort.

Dazu Punkt 5 der Meldung vom 04.09.2026: ein eingelesener Beleg fuellte nur
Datum und Uhrzeit. Der Parser las ihn immer vollstaendig - die App fragte
liter/kosten/station/ersparnis/kraftstoff ab, veroeffentlicht werden aber die
Namen des Belegs (liters/fuel_total_eur/station_name/discount/fuel_type).
Uebereingestimmt hat allein ts. Die Zuordnung steht jetzt als
belegFormularwerte() in daten/belegentwurf.ts und wird gegen die echte
Nutzlast dieses Belegs geprueft.

tsc sauber, 258 Tests (die drei neuen Marken-Tests gegen den alten Stand als
scheiternd nachgewiesen), vite build sauber, Panel als Modul geparst,
Parser-Suite 11 Tests gegen elf echte Belege. Panel und App liefern fuer elf
Faelle byteweise dasselbe. Live in audi_ha_test auf 2026.9.4.21 ohne
Traceback: Uebersicht "Shell, Nuernberger Str., Ansbach", Einzelbeleg
zweizeilig mit Marke, und der echte geteilte Beleg fuellt durchs Backend das
ganze Formular.
2026-09-04 17:00:22 +02:00
tobias a99ae432fc Tankstellen-Nadel, doppeltes Tanken-Symbol, Tankstellenname, packageClassList
Fuenf Meldungen des Eigentuemers, vier davon behoben:

1. Tankstellen-Nadel war bei 32 px ein dunkler Fleck (poi zeichnet nur
   einen Ring). Gefuellter Punkt in currentColor zwischen Silhouette und
   Symbol - die Geometrie stand im Pfad. Rot wurde ausdruecklich
   abgelehnt, die Umsetzung aendert keine Farbe.

2. Tanken-Symbol im Standort-Blatt wurde gefuellt UND umrandet, weil das
   CI-Symbol zusaetzlich stroke-Attribute mitbekam. Jetzt ueber ciSVG
   wie das Parkplatz-Symbol daneben; in der App dafuer imRaster an
   SymbolTanken (der 0,84-Faktor gilt nur in der Reiterleiste).

3. Tankstellenname: der Bestand hat zwei Bauformen - aeltere Belege
   tragen den Betreiber in station_name und die Anschrift daneben.
   Neu tankstelleTeile() liest beide Felder, entfernt Firmierungen,
   kuerzt Strassen und trennt die Hausnummer auch ohne Leerzeichen.
   Uebersicht: Marke, Strasse, Ort. Einzelbeleg: zwei Zeilen.
   adresseTeilen() hat keinen Aufrufer mehr und ist entfernt.

4. Zwischenablage und Kalendereintrag hatten denselben Grund: Capacitor 8
   registriert nur, was in packageClassList steht, und cap sync traegt
   dort ausschliesslich npm-Pakete ein. Beide app-eigenen Plugins lagen
   im Programm und wurden nie registriert. ios-teilen-einrichten.mjs
   traegt sie jetzt nach, ios-signieren.sh bricht ab, wenn sie im
   fertigen .ipa fehlen.

5. Geteilter Beleg nur mit Datum und Uhrzeit bleibt offen - der Weg
   nutzt denselben belegLesen()-Pfad wie eine gewaehlte Datei, es fehlt
   der konkrete Beleg oder die Protokollzeile.

tsc sauber, 246 Tests, vite build sauber, Panel als Modul geparst,
audi_ha_test auf 2026.9.4.20 ohne Traceback. Panel und App liefern fuer
die Tankstelle byteweise dasselbe.
2026-09-04 16:34:46 +02:00
tobias cefb235f6d Versionsvergleich: nur eine neuere Fassung zaehlt
Nach dem Xcode-Lauf lief die App auf 2026.9.4.17, die Instanz auf
2026.9.4.5 - und die App bot an, sich auf .5 zu erneuern. Die
Gleichheitspruefung konnte den Rueckschritt nicht sehen.

versionOrdnung() sortiert jetzt Stelle fuer Stelle als Zahl, aber nur was
dem Format JJJJ.M.T.N entspricht; sonst gibt es eine Abweichung ohne
Richtung. Streifen, Startblatt und OTA-Knopf schlagen nur noch an, wenn
die App nachweislich aelter ist. Damit ist die frueher dokumentierte
Entscheidung "nur auf Gleichheit" umgedreht - Begruendung in
VERSIONIERUNG.md und AGENTS.md Abschnitt CL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:10:47 +02:00
tobias e237bf1e8b Server-Adresse: das Schema richtet sich nach dem Ziel
Beim Neuverbinden stand "localhost:5173" im Feld, die App meldete "nicht
erreichbar". Ursache war basisUrlNormalisieren(): ohne Schema setzte es
blind https:// davor - womit die eigene Vorlage der App unbrauchbar war,
denn im Feld steht als Beispiel 192.168.1.20:8123, und im Heimnetz gibt
es kein Zertifikat.

Jetzt nach dem Ziel: localhost, 127.x, 10.x, 192.168.x, 172.16-31.x, ::1
und .local/.home.arpa/.localhost bekommen http, alles andere weiter https
(datametric360.de laeuft ueber Cloudflare). Steht ein Schema da, wird
nichts geraten.

Vier Regressionstests, darunter der gemeldete Fall, die Vorlage aus dem
Feld und 172.32.0.1 als Gegenprobe zum privaten Bereich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:16:32 +02:00
tobias 461151cfec Rueckfrage vor loeschenden Aktionen: eigenes Blatt statt window.confirm
Gemeldet als "Verbindung trennen tut nichts". Kein Fehler der App: in
eingebetteten Ansichten sind Dialoge abgeschaltet, window.confirm kehrt
sofort mit false zurueck (gemessen: 1 ms), und abmelden() beginnt damit.

Die Suche danach brachte den eigentlichen Befund: elf Stellen hingen an
window.confirm - acht direkt, drei im Zeilenmenue (Wischgeste in Fahrten,
Tankvorgaengen, Messwerten). Jede davon eine loeschende Aktion, jede in
eingebetteten Ansichten still wirkungslos. Das Panel zeigt an denselben
Stellen seit jeher sein eigenes Blatt.

screens/bestaetigung.tsx: ein kleiner Speicher plus ein ActionSheet, das
einmal in App.tsx haengt. Kein Hook je Aufrufstelle - der haette jede der
elf Stellen gezwungen, das Blatt auch selbst zu rendern, elf
Gelegenheiten es zu vergessen, ohne dass etwas auffaellt. Ohne gehaengtes
Blatt (Testlauf, Fremdeinbettung) gilt weiter window.confirm.

LOESCH_TEXT und LOESCH_HINWEIS wortgleich aus dem Panel, samt der
Trennung in Frage, erklaerenden Satz und Knopfbeschriftung.

225 Tests (vier neue), live an der gemeldeten Stelle geprueft: das Blatt
erscheint, Abbrechen schliesst es, die Sitzung bleibt. Bestaetigt wurde
bewusst nicht - das haette den Token vom Geraet geloescht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:07:49 +02:00
tobias ae276fb518 Leerzustand der Karte deckend: 2,45:1 waren zu wenig
Der Kasten "Kein GPS-Signal vom Fahrzeug" liegt auf Leaflets eigenem
Hellgrau, und das ist in beiden Themen hell. --tile ist im Nachtmodus ein
durchscheinender weisser Schleier - er macht die Flaeche dort noch heller,
waehrend der Text der Nacht-Palette folgt: gemessene 2,45:1, deutlich
unter den 4,5:1 dieses Projekts.

Deckender Kasten plus Zweitfarbe, in beiden Codebasen. Live nachgemessen,
identische Werte: 9,86:1 nachts, 10,94:1 tags.

Kein Gleichlauf-Befund, sondern eine gemeinsame Schwaeche - deshalb auf
Nachfrage vorgelegt und erst nach Freigabe geaendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 13:57:09 +02:00
tobias 9e9ca9ca28 Audit: Wisch-Zeile und Auswahlliste der App nachgezogen
Gleichlauf, Funktion und Fehler geprueft. Sauber: 28/28 Dienste ueber
const/Registrierung/services.yaml/Klarnamen, 27/27 Sensorrollen, Routen
deckungsgleich, 47 Panel-Seiten live ohne einen Konsolenfehler, App
ebenso, Fahrzeugstatus textgleich, Bestand ohne Befund ausser den drei
bekannten Altbelegen mit odometer_km als Zeichenkette.

Zwei echte Befunde, beide in der App und beide dort, wo ein juengerer
Zwilling die Korrekturen des aelteren nie bekommen hat:

WischLoeschen (Raeder-Archiv) liess den roten Loeschknopf dauerhaft
sichtbar unter der Zeile stehen - genau der Haarlinien-Fehler, den das
Panel am 16.08.2026 mit visibility:hidden behoben hat - und malte den
Zeileninhalt mit dem durchscheinenden --tile auf eine Kachel, wodurch im
Nachtmodus der Streifen heller wird und das Rot durchscheint (Regel aus
Abschnitt AP). Beides behoben und im Nachtmodus nachgemessen.

.dm-auswahl option stand auf --tile; die aufgeklappte Liste eines <select>
malt der Browser ausserhalb der Seite, ein durchscheinender Wert laesst
dort den hellen Systemgrund durch. Im Panel steht dafuer seit dem
16.08.2026 --canvas.

Zur ausgelieferten .ipa: entpackt und gelesen - Erweiterung mit echtem
Programm, beide Plugins samt Methoden im App-Programm, App-Gruppe in
beiden Profilen, Kalender- und Standortschluessel da. Fehlt nur die
CFBundleVersion der Erweiterung (der Grund fuer die abgelehnte
Installation) und der Kamera-Schluessel - beides im Skript behoben und
wirksam beim naechsten Xcode-Lauf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 13:49:40 +02:00
tobias 68732aa10f Bilder: der Cache-Brecher haengt am Foto statt an der Startzeit
Beide Oberflaechen haengten Date.now() an jede Bildadresse - richtig
gegen ein veraltetes Foto (31 Tage Cache-Vorgabe unter /local/), aber die
Adresse aendert sich damit bei JEDEM Start, der Zwischenspeicher greift
zwischen zwei Starts also nie. Mit dem Vorladen waeren das 5,3 MB je
Start gewesen.

bilder.staende() liefert jetzt den Zeitstempel je Datei, veroeffentlicht
an sensor.audi_dashboard_app_version; eine Bildadresse aendert sich damit
genau dann, wenn das Foto ein anderes ist. Ein fehlendes Foto bekommt 0
(dann loest sich auch ein gespeicherter 404 von selbst auf), ein Backend
ohne Staende faellt auf das bisherige Verhalten zurueck, und Upload wie
Loeschen veroeffentlichen sofort neu.

Die App merkt sich den letzten Stand im Browserspeicher: sie malt ihren
ersten Bildschirm, bevor die Versionsangabe eintrifft - ohne das
Gedaechtnis trug genau dieser Durchlauf noch die Startzeit und holte das
Uebersichtsfoto doch wieder bei jedem Start.

Live gemessen: Neuladen ohne Aenderung 5.328.771 Bytes Inhalt bei 0 Bytes
uebertragen; ein per touch geaendertes Foto wird neu geholt (513.403
Bytes), die anderen nicht; App-Start ohne eine einzige Adresse mit
Startzeit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 13:29:45 +02:00
tobias d6f7993504 QR-Code aus gespeichertem Bild, stiller Debug-Knopf, HA-Zugang bei den Zugaengen
Der Scanner liest den QR jetzt auch aus einer gespeicherten Bilddatei -
in der Testumgebung ohne Kamera der einzige Weg, auf dem Telefon der
kuerzere (der Token entsteht am Rechner, das Bildschirmfoto liegt ohnehin
auf dem Geraet). Gleicher Leser, gleiche Deutung, nur eine andere Quelle;
ohne Kamera entfaellt das schwarze Quadrat.

Dabei ein echter Fehler in der ersten Fassung, gefunden an der
Musterseite: img.decode() loest bei einem GIF in Chromium nie auf - das
Blatt haette ohne Meldung ewig gewartet. Jetzt createImageBitmap, als
Rueckfall bewusst das load-Ereignis statt decode().

Debug-Mode ist nur noch ein stiller Textknopf am Seitenende, ohne Kachel
und ohne Erlaeuterung; der Zugang zu Home Assistant steht als Akkordeon in
der Kachel "Zugaenge" darin. Beides auf Vorgabe des Eigentuemers.

Die Musterseite kann jetzt auch die Ersteinrichtung (?seite=einrichtung) -
damit ist der ganze Weg ohne echten Token nachgewiesen: selbst erzeugter
QR mit Token-Text landet zeichengleich im Feld, einer mit http-Adresse im
Adressfeld, ein Bild ohne Code meldet das sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 13:08:10 +02:00
tobias 4ab6b9fca5 Debug-Mode, eine Version-Kachel, Knoepfe nach HIG, Bilder ohne Verzoegerung (2026.9.4.11)
Debug-Mode: Zugaenge und Dongle stehen nicht mehr verstreut oben in den
Einstellungen, sondern zusammen hinter einem Schalter am Seitenende - dazu
neu der letzte Datensatz des Dongles (geraet.py). "Letzter Datensatz" heisst
dabei: alle Entitaeten des Geraets, deren Aktualisierung hoechstens zwei
Sekunden nach der juengsten liegt - die flespi-Integration setzt einen
Datensatz in Millisekunden. Angezeigt mit Ankunft, Geraetezeit und dem
Rueckstand zwischen beiden.

Beim Verifizieren gefunden: app_version_veroeffentlichen() lief nur beim
Start und nach einer Aktion, und beim Start haben die Entitaeten des Geraets
noch keinen Zustand - die Kachel waere dauerhaft leer geblieben. Laeuft
jetzt im Minutentakt mit, ohne Netzaufruf.

Ausserdem:
- "Version" und "Integration-Update" sind eine Kachel
- Speichern/Verwerfen als zwei gleich breite Knoepfe, der bestaetigende
  gefuellt und rechts; "Verwerfen" war in der App blosser Text
- Bilder werden nach dem ersten Zeichnen in der Leerlaufzeit vorgeladen UND
  dekodiert. Die Verzoegerung beim Seitenwechsel kam nicht vom Netz (31 Tage
  Cache), sondern vom asynchronen Dekodieren eines frisch erzeugten <img>.

214/214 Tests, Panel als Modul geparst, live gegen audi_ha_test geprueft.
2026-09-04 12:15:33 +02:00
tobias 63a45d4ee2 Die .ipa war nicht installierbar, plus QR-Scanner (2026.9.4.8)
Der Erweiterung fehlte CFBundleVersion - eine Pflichtangabe, ohne die iOS
die Installation der ganzen App verweigert ("kann nicht installiert werden").
Verursacht von der Versionsangleichung vom Vortag: das Skript reichte den
Platzhalter $(CURRENT_PROJECT_VERSION) statt einer Zahl weiter, und das per
Skript angelegte Ziel der Erweiterung kennt diese Build-Einstellung nicht.
An den entpackten .ipa nachgemessen, 2026.9.3.3 gegen 2026.9.4.5.

ios-signieren.sh reicht nur noch echte Zahlen weiter, das Gegenlesen der
fertigen .ipa bricht ab statt zu warnen, und abgelegt wird erst danach -
eine Datei, die die Pruefung nicht besteht, darf nicht in auslieferung/
landen.

Dazu:
- Kopfmarke statt Firmierung des Betreibers in der Tankstellen-Zeile
- dritter Suchbegriff: der Name ohne fuehrende Marke. Gemessen: Nominatim
  findet "Shell, Pascalstr. 8, Ingolstadt" nicht, "Pascalstr. 8, Ingolstadt"
  schon - daran blieb die Belegkarte der realen Instanz leer
- der Tankstellen-Verweis zeigt die Tankstelle, statt eine Route zu rechnen
- Cache-Brecher auf .ipa, Symbolen und manifest.plist
- QR-Scanner fuer den Zugangs-Token bei der Ersteinrichtung (jsQR statt
  GoogleMLKit, damit der Xcode-Lauf kein pod install riskiert)

214/214 Tests, Panel als Modul geparst, live gegen audi_ha_test geprueft.
2026-09-04 11:51:44 +02:00
tobias 43f25e2e0d Tankstelle zweizeilig und antippbar, Belegkarte ohne toten Knopf (2026.9.4.5)
Sieben Meldungen des Eigentuemers, beide Codebasen.

Karte im Einzelbeleg: "Position laedt nicht" und "der Button funktioniert
nicht" waren dieselbe Stelle - ohne aufloesbaren Ort malte das Panel einen
beliebigen Ausschnitt und liess den Zentrieren-Knopf fuer immer disabled.
Beide zeigen jetzt die Auskunft statt einer Karte und unterscheiden dabei
"steht nicht im Beleg" von "findet niemand". Dazu ein echter Paritaetsmangel:
die App schlug nur station_address nach, das Panel faellt seit jeher auf den
Namen zurueck - von 16 Testbelegen tragen vier eine Anschrift.

Der Suchbegriff darf Name und Anschrift NICHT zusammensetzen: gemessen findet
Nominatim "Pascalstr. 8, 85057 Ingolstadt", mit dem Betreibernamen davor
nichts. Jetzt zwei Kandidaten nacheinander (Anschrift, dann Name); fuer den
Kartendienst dagegen beides zusammen, weil Apple und Google Betriebsnamen
kennen.

Tankstelle steht ueber die volle Kachelbreite, zweizeilig (Tankstelle und
Strasse / Ort) und fuehrt beim Tippen in den Standard-Kartendienst - Apple
Karten auf Apple-Geraeten, sonst Google Maps. Dieselbe Wahl jetzt auch fuer
Fahrzeugstandort und Autohaus.

SmartDeal-Ersparnis brach um, weil die dt-Spalte 150,00px breit war und ihr
Inhalt 150,20px - zwei Zehntel Pixel, Zeile dadurch 44,8 statt 26,4px hoch.

Der Regler "Fahrt beenden" schreibt nicht mehr beim Loslassen: derselbe Wert
stellt den Schlaf-Timeout des Dongles, das soll bewusst ausgeloest werden.
Speichern/Verwerfen erscheinen nur bei echter Aenderung.

Share-Erweiterung: an der ausgelieferten .ipa gemessen trug sie 1.0, die App
2026.9.3.3 - iOS registriert eine Erweiterung mit abweichender Versionsnummer
nicht, sie fehlt dann im Teilen-Blatt. ios-signieren.sh schreibt die Version
jetzt in beide Ziele und liest die fertige .ipa gegen. Zwischenablage: dritter
Weg ueber UIPasteboard.itemProviders, und ein fehlender nativer Teil wird als
solcher gemeldet statt als "Kein Zugriff".

Am Geraet gesetzt: 1003 Network Ping Timeout 0 -> 60 s, 1004 ACK Type
TCP/IP -> AVL. Beide liegen bei flespi als pending bereit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 10:10:55 +02:00
tobias 7f386d19ea Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt (2026.9.4.1)
Das Fahrtfenster stand seit dem 01.09. in der Gerätezeit, der Verlauf war nach
Ankunftszeit sortiert. Zwei Uhren, und das Gerät puffert: was verspätet ankam,
fiel aus einem Fenster heraus, in dem es der Sache nach lag. Am 03.09. hat das
eine Fahrt gekostet - 26 von 48 GPS-Punkten und 3,0 statt 7 km.

verlauf_lesen() stempelt jeden Punkt jetzt mit der Gerätezeit seines
Datensatzes. Eine Stelle, an der alle Verbraucher vorbeikommen. Dazu zwei
Riegel aus derselben Untersuchung: ein Rückschritt von 43 Metern ist keine
Zählerrücksetzung mehr (aus 87 Metern wurden vorher 13,7 km), und das
Fahrtende wird auf das letzte Lebenszeichen des Geräts geklemmt - der Nachlauf
ist eine Rechnung, kein Messwert.

Am echten Recorder nachgerechnet: Tacho 3,0 -> 7,0 km, GNSS 2,952 -> 6,877 km,
Route 26 -> 47 Punkte, Ende 23:21:57 -> 23:17:48. Beim ersten Lauf haben zwei
Fahrten eine Strecke bekommen, die vorher gar keine hatten.

Nicht das Gerät war schuld: es hat lückenlos aufgezeichnet. Die zehn Minuten
Funkstille waren eine offene, aber tote TCP-Sitzung (flespi-Log: 703 s, 16
Nachrichten) - kein Funkloch. Begründung und die Empfehlung fürs Gerät stehen
in AGENTS.md, Abschnitt CB.

Dazu die neun Paritätsbefunde, alle Richtung Panel gelöst - darunter ein
unmaskierter Punkt in beiden Codebasen, der aus "vor 3 Tage" ein "vor 3 Tag"
machte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 02:18:12 +02:00
tobias 4a6c48e81c Zwei Textfehler der Dongle-Kachel, an der Sichtpruefung gefunden (2026.9.3.15)
"Gelesen vor 1 Std.." - der Punkt der Abkuerzung und der Satzpunkt standen
doppelt. Und im Panel stand noch der Halbsatz "Lesen und Schreiben gehen nur bei
laufender Zuendung" vor dem neuen Text - ausgerechnet die Behauptung, die am
selben Tag widerlegt wurde; meine Ersetzung hatte nur den hinteren Teil
getroffen.

Gefunden, weil die Kachel bis dahin nur an den Daten belegt war, nie angesehen.
Der erste Blick zeigte dabei eine sechs Fassungen alte Datei aus dem
Tab-Zwischenspeicher - die Lehre aus Abschnitt AQ, jetzt dort mit dem
Einzeiler zum Mitlesen der geladenen Fassung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 21:22:00 +02:00
tobias 6ec31b5275 Audit, zweite Runde: der Knopf schrieb ein erfundenes Ende (2026.9.3.14)
_letztes_lebenszeichen() fiel auf "jetzt" zurueck, sobald verlauf_lesen() eine
leere Liste lieferte - und die bedeutet sowohl "nichts aufgezeichnet" als auch
"nicht lesbar". Am 03.09. trat der zweite Fall ein: der Knopf traf einen
Neustart, der recorder war noch nicht bereit, die Abfrage lief 2:47 und kam leer
zurueck. Die Fahrt bekam 18:43 statt 17:59 als Ende, 74 Minuten statt 30.

Drei Quellen in fester Reihenfolge statt einer: Aufzeichnung, dann hass.states
(last_reported/last_updated - weiss dasselbe ohne Datenbank), dann "jetzt", und
das nur noch mit Warnung. Vier Regressionstests, drei davon gegen den alten
Stand nachweislich rot.

Dazu aus der Bestandspruefung: odometer_km liegt in drei alten Tankvorgaengen
als Zeichenkette vor, ablage.distanz_seit_letzter_tankung() rechnete damit
float - str. Laeuft jetzt durch als_kilometerstand(). Der Eingang war schon
zu; das ist Altbestand.

Und acht Dienste hatten keinen Klarnamen in translations/de.json - jetzt 28
Dienste, 28 Klarnamen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 21:15:42 +02:00
tobias 2736c3d6e0 Audit nach dem Dongle-Umbau: vier Befunde (2026.9.3.13)
1. profil_schreiben awaitete wunsch_uebernehmen und damit bis zu drei
   HTTP-Runden zu flespi - bei 39 Aufrufstellen von profilSpeichern() im Panel
   je Feldaenderung. Laeuft jetzt als eigene Aufgabe.
2. Ein gescheiterter Schreibversuch wurde bei jedem Speichern wiederholt, weil
   der Fehlerstand kein "werte" hat. Er merkt sich jetzt unter "versucht", was
   gescheitert ist. Dazu: kein zweites Schreiben, wenn der Wert schon als
   pending bereitliegt.
3. buendelPasst() versprach im Kommentar, ein aelteres Buendel abzulehnen,
   pruefte aber nur Ungleichheit - deshalb meldete die App "diese Fassung
   aendert auch Natives", obwohl nur das Buendel nach einem Versionssprung
   nicht neu gebaut war. Vergleicht jetzt gegen die Serverfassung, drei
   Regressionstests (der entscheidende gegen den alten Stand rot). Der Text
   behauptet keine Ursache mehr, die die App nicht kennen kann.
4. Das Regler-Minimum ging heute von 0 auf 1, ein bereits gespeicherter Wert
   darunter lief ungeprueft durch. Beide Oberflaechen klemmen jetzt auf 1-60.

182/182 Tests, 28 Backend-Dateien py_compile, beide Frontends als Modul
geparst, Dienst- und Katalog-Konsistenz in beide Richtungen geprueft, Buendel
auf derselben Fassung wie das Manifest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 21:03:17 +02:00