Commit Graph

26 Commits

Author SHA1 Message Date
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
tobias 7276d06a18 Sicherheit-Feinschliff, Nächster-Service-Sortierfehler behoben, Fahrzeugstatus umbenannt
- Türschlösser auf ein Sensor reduziert (Fahrertür genügt, Zentralverriegelung
  schließt alle Türen gemeinsam) - Kofferraum-/Motorhauben-Schloss entfernt.
- "Sicherheit" heißt im Panel jetzt "Fahrzeugstatus", wie in der companion-app.
- naechsterTermin() (Panel): sortierte null als Epoch 1970 und ließ dadurch
  einen datenlosen Termin (Hauptuntersuchung ohne Eintrag/Erstzulassung) immer
  vor einem echten, berechneten Termin (Ölwechsel) gewinnen - Ursache dafür,
  dass Übersicht nach dem Leeren des Servicebuchs nichts mehr zeigte.
- "Licht ausgeschaltet" statt "Kein Licht" (Formulierung zurückgenommen).
- Große Bildschirme: Unterseiten (Standort, Service, ...) zeigten ihren Titel
  in 26px statt der sonst überall genutzten 17px - wirkte neben dem
  durchgängig leichten Fließtext wie Fettschrift, obwohl font-weight nirgends
  wechselt. Jetzt dieselbe Standardgröße wie im Telefon-Layout.

Details und Verifikation in AGENTS.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 17:00:33 +02:00
tobias 5a048488ea Selbst-Update-Sicherung, Service-Prognose ohne Servicebuch und Sicherheit neu gestaltet
- aktualisierung.py: Backup-Ordner verliert sein manifest.json beim Sichern,
  sonst laedt HA ihn nach einem Neustart als zweite audi_dashboard-Integration
  (Ursache des raetselhaften "gleicher Pfad"-Umbenennungs-Fehlers).
- service.ts/audi-dashboard-app.js: Uebersicht faellt jetzt wie Service schon
  auf die Fahrzeugmeldung zurueck, wenn kein Servicebucheintrag existiert;
  eigenes (kuerzeres) Oelwechsel-Intervall wird jetzt auch ohne Servicebuch-
  eintrag naeherungsweise aus der Herstellermeldung hochgerechnet, statt
  weiterhin unveraendert die Herstellervorgabe zu zeigen.
- Sicherheit neu gestaltet: vier Sammelzeilen (Fahrzeug verriegelt / Tueren
  und Klappen geschlossen / Fenster und Dach geschlossen / Kein Licht) statt
  einer flachen Liste, mit neuen Tuerschloss-, Dach- und Standlicht-Sensor-
  rollen, Kreis-Haekchen-Symbolen und einer Detailseite je Tuer/Motorhaube/
  Kofferraum in beiden Frontends.

Details, Verifikation und Sensor-Rollen in AGENTS.md (Abschnitte J/M/N).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:29:37 +02:00
tobias 146f809e9b Selbst-Update: Ladezustand und echter Neustart-Knopf
Der erste echte Live-Test des Selbst-Updates gegen das private Gitea-Repo
lief erfolgreich (mehrfach im Test-Container bestätigt) - deckte aber zwei
UX-Lücken auf, die der Owner direkt gemeldet hat:

- Prüfen/Installieren gaben während der 15-25 Sekunden dauernden
  Netzwerkaktion keine sichtbare Rückmeldung - nicht von einem Hänger zu
  unterscheiden. Jetzt ein Ladeindikator (Panel: wiederverwendetes
  .lade-spinner; companion-app: neues .dm-spinner-Äquivalent, gab es dort
  noch gar nicht).
- Nach erfolgreicher Installation stand nur ein Hinweistext da, kein Weg
  zum eigentlich nötigen nächsten Schritt. Der Knopf wechselt jetzt zu
  "Installation abschließen - Jetzt neu starten" und stößt
  homeassistant.restart direkt an.

Das separat gemeldete "Lädt"-Hängenbleiben war kein Bug: reproduziert durch
Live-Test des neuen Neustart-Knopfs - eine echte HA-Verbindungsunterbrechung
während eines Neustarts zeigt exakt denselben, bereits bestehenden
Bootstrap-Ladebildschirm. Vermutlich derselbe Effekt durch den
Docker-Neustart früher in dieser Sitzung.

Verifiziert im Test-Container per Browser-Automatisierung: Neustart-Knopf
ausgelöst, Verbindungsabbruch beobachtet, per docker logs bestätigt, dass
Home Assistant tatsächlich neu gestartet ist und die neue Version aktiv
wurde.

Details in AGENTS.md, Abschnitt L.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 12:44:47 +02:00
tobias 7365180550 vCard-Import fürs Autohaus, Selbst-Update-Logging und UI-Feinschliff
- Service -> Autohaus -> Zahnrad hat jetzt einen "vCard importieren"-Knopf
  (öffnet den Dateiauswahldialog für .vcf). Liest FN/ORG, TEL, EMAIL, ADR
  und befüllt das Formular - in beiden Frontends, jeweils passend zum
  bestehenden Speicherverhalten (Panel speichert sofort, companion-app
  erst über den vorhandenen "Speichern"-Knopf).
- update_pruefen/update_installieren loggten bisher nur bei einem Fehler -
  ein erfolgreicher Aufruf war im Log nicht von "noch nicht ausgeführt" zu
  unterscheiden. Jetzt auch eine Erfolgsmeldung.
- Panel: der feste Hinweis auf den fehlenden Gitea-Token stand dauerhaft in
  der Integration-Update-Kachel, auch wenn ein Token längst hinterlegt war.
  Erscheint jetzt nur noch als Teil einer echten Fehlermeldung.

Details in AGENTS.md, Abschnitte J (Nachtrag) und K.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 12:30:24 +02:00
tobias fb251154c7 Ölwechsel/Inspektion-Anzeige repariert, Selbst-Update der Integration gebaut
Zwei getrennte Themen in einem Commit, beide in derselben Sitzung entstanden:

1. Ölwechsel/Inspektion wurden komplett ausgeblendet ("kein Eintrag im
   Servicebuch"), sobald kein Servicebucheintrag vorlag - selbst wenn der
   zugeordnete Sensor eine gültige Fälligkeit meldete. In beiden Frontends
   prüfte die Anzeige nur den Servicebuch-Zweig, bevor sie die
   Fahrzeugmeldung überhaupt las. Jetzt steht die Fahrzeugmeldung für sich;
   fehlt zusätzlich ein Servicebucheintrag, übernimmt eine neue,
   fahrtenlog-basierte Prognose (kmProTagAusFahrten()/meldungsPrognose())
   die Hochrechnung statt der Servicebuch-Rate - deckelt auf die vom
   Fahrzeug selbst gemeldete Zeitgrenze, falls zu wenig gefahren wird.

2. install.ps1 als Update-Weg wird von Windows Smart App Control blockiert,
   ohne Umgehungsmöglichkeit. Die Integration lädt sich jetzt auf
   Tastendruck selbst von Gitea (aktualisierung.py), verifiziert das
   Manifest vor jedem Tausch und tauscht per os.rename mit automatischem
   Rollback bei Fehlern - install.ps1 bleibt nur noch für die
   Erstinstallation nötig. Zugangstoken über einen neuen OptionsFlow in
   entry.options, nie in configuration.yaml.

Nebenbei: mehrere seit der HACS-Ausschluss-Entscheidung liegen gebliebene
falsche HACS-Referenzen in Code-Kommentaren und einem UI-Text korrigiert.

Details, Sicherheitsbegründung und Verifikationsstand in AGENTS.md,
Abschnitte I und J.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 12:01:01 +02:00
tobias 1354ca6b06 OTA-Updates für die iOS-App; HACS-Fehlannahme korrigiert
HACS kann laut eigener Dokumentation grundsätzlich nicht mit privaten
GitHub-Repositories arbeiten (hacs.xyz/docs/faq/private_repositories) - keine
Ausnahme für Tokens oder verbundene Konten. Meine frühere Annahme, HACS käme
damit zurecht, wenn es unter dem richtigen Konto angemeldet ist, war falsch.
Da das Repository aus Lizenzgründen privat bleiben muss (Audi-Hausschrift,
Typenschilder), ist install.ps1 damit nicht die Rückfallebene, sondern der
einzige Installationsweg - README, INSTALL.md, ANLEITUNG.md, install.ps1 und
VERSIONIERUNG.md korrigiert.

Oberflächen-Updates für die iOS-App laufen jetzt ohne Xcode:
@capgo/capacitor-updater eingebaut, ein Update-Abschnitt in den
Einstellungen lädt ein neues Bündel und tauscht die Oberfläche aus. Kein
Selbstlauf (autoUpdate: false) - nur auf Tastendruck, nie während der
Benutzung.

Das Bündel liegt in der Integration selbst
(custom_components/audi_dashboard/frontend/app/), nicht unter /local/: so
reist es bei jeder Installation automatisch mit, ohne zweiten
Auslieferungsweg. Gebaut von companion-app/scripts/ota-paket.ps1 (neuer
Befehl: npm run ota), gemeldet über sensor.audi_dashboard_app_version
(neues Feld daten.buendel).

Ein echter Bug beim Bauen gefunden: [IO.Compression.ZipFile]::CreateFrom-
Directory schreibt unter Windows PowerShell 5.1 Backslashes in die
Zip-Einträge - iOS hätte das Archiv falsch entpackt. Behoben, indem die
Einträge von Hand mit "/" geschrieben werden.

Rückfallebene: notifyAppReady() läuft erst, wenn React nachweislich
gerendert hat (App.tsx). Kommt diese Meldung nicht, rollt das Plugin nach
20 Sekunden von selbst auf das vorherige Bündel zurück.

Am laufenden Testcontainer verifiziert: die ausgelieferte Zip hasht exakt
auf den in bundle.json hinterlegten Wert, 13 Einträge, index.html in der
Wurzel, keine Backslashes, keine Beschädigung. tsc sauber, 117/117 Tests
(5 davon neu für buendelPasst() - dabei eine echte Lücke gefunden: die
Funktion hätte bei unbekannter eigener Version fälschlich ein Update
angeboten, jetzt genauso vorsichtig wie versionVergleichen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 09:20:14 +02:00
tobias d8b12da36d pyscript-Backend zur echten HA-Integration umgebaut (HACS-fähig)
Das Backend liegt jetzt als custom_components/audi_dashboard/ vor - eine
normale Home-Assistant-Integration mit Config-Flow, einer sensor-Plattform
und 18 Diensten. Damit ist die App über HACS installierbar; bis das Repo auf
GitHub gespiegelt ist (HACS spricht ausschließlich mit GitHub), installiert
homeassistant/installationspaket/install.ps1 denselben Ordner ohne HACS.

Fünf Installationsschritte entfallen ersatzlos: der pyscript:-Block, der
panel_custom:-Block, das Kopieren der Oberfläche nach www/, das langlebige
Zugriffstoken (der Verlauf wird direkt über die recorder-API gelesen) und
"pip install pypdf" (steht in manifest.json). Das Fahrzeugprofil legt die
Integration beim ersten Start aus ihrer Vorlage an.

Drei alte Schwächen sind dabei mit erledigt:
- Die Nutzlast landet nicht mehr in der Recorder-Datenbank
  (_unrecorded_attributes - das kann nur eine echte Entität).
- Eine laufende Fahrt überlebt einen Neustart (Store statt Arbeitsspeicher);
  fiel sie während eines Ausfalls ins Ende, schließt
  nach_neustart_fortsetzen() sie beim letzten aufgezeichneten Zeitpunkt.
- Sensor-Zuordnungen wirken sofort - die Zustandsbeobachter werden neu
  gebunden, der Neustart-Hinweis und der Neustart-Dienst sind weg.

Namensvertrag geändert, beide Oberflächen mitgezogen:
pyscript.audi_dashboard_x -> sensor.audi_dashboard_x,
pyscript.audi_dashboard_y -> audi_dashboard.y. Eine Companion-App vom alten
Stand findet nach dem Umstieg nichts mehr und muss neu gebaut werden; das
Panel liegt in der Integration und kann nicht driften.

Der selbstgebaute Updater entfällt - HACS ist die Update-Mechanik, die Home
Assistant kennt. Die Versionierung schrumpft auf eine Quelle: manifest.json.

Geprüft am laufenden Testcontainer (Container byteweise identisch mit dem
Repo): alle 18 Dienste, Panel, Config-Entry neu laden, Historienimport,
echter Shell-Beleg in-process, Neuinstallation im Wegwerf-Container blank mit
automatisch nachinstalliertem pypdf. Companion-App: tsc sauber, 112/112
Tests, beide Rauchtests gegen das laufende Backend grün. Belegparser 8/8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 23:53:56 +02:00
tobias 99cef7c393 Fahrzustand aus der Zuendung statt aus dem Fahrtstatus, App-Version vergleichbar
BUG "Fahrzeug faehrt" aenderte sich nie. standortZustand() leitete den
Fahrzustand aus TRIPS[0].status === "offen" ab - zwei Fehler
uebereinander, die sich gegenseitig verstaerkt haben.

Erstens heisst "offen" nicht "unterwegs", sondern "Daten noch
unvollstaendig": _fahrt_beenden() legt die Fahrt mit diesem Status an,
wenn sie ENDET, und das Kilometerstand-Screening fuellt sie spaeter. Eine
Fahrt, die nie eine Strecke bekam - etwa weil damals kein KM_SENSOR
zugeordnet war -, bleibt fuer immer "offen". Zweitens ist TRIPS[0] die
AELTESTE Fahrt, nicht die neueste: fahrten_veroeffentlichen() reicht
profil.fahrten_lesen() unveraendert in Dateireihenfolge weiter, und die
ist aufsteigend. Zusammen hat die aelteste jemals unvollstaendig
gebliebene Fahrt das Fahrzeug dauerhaft als fahrend angezeigt; an der
Testinstanz war das eine seit 18 Tagen beendete Fahrt.

Nicht den Index geflickt, sondern die Quelle korrigiert: das Backend
veroeffentlicht jetzt "zuendung" im Fahrzeugstatus, gelesen aus dem
ohnehin zugeordneten ZUENDUNG_SENSOR - demselben Signal, das auch
fahrterkennung.py als massgeblich nimmt. Anzeige und Erfassung koennen
dadurch gar nicht mehr auseinanderlaufen. Ohne zugeordneten Sensor
(null) steht "Fahrzustand unbekannt" statt einer Behauptung.

companion-app hatte denselben Fehler spiegelverkehrt: liveZustandLesen()
las "zuendung" (gab es nie) und "lat"/"lon" (das Backend liefert
standort_lat/standort_lon), und der Typ Fahrzeug kannte keines dieser
Felder. Die Live-Ansicht meldete deshalb dauerhaft "Das Fahrzeug steht"
und zeigte nie eine Position. Felder in Fahrzeugstatus/Fahrzeug und im
Adapter ergaenzt, damit der Typ diese Fehlerklasse kuenftig faengt -
wovor sein eigener Kopfkommentar seit einem frueheren Vorfall warnt.

BUG "Standortzugriff verweigert" war irrefuehrend. Die Zeile steht
direkt unter dem Fahrzeugnamen, wo sonst der Abstand zum Auto steht,
handelt aber vom Standort DIESES Geraets - sie las sich, als sei der
Standort des Autos nicht abrufbar. Jetzt "GPS offline" wie gewuenscht.
Die verweigerte Freigabe behaelt eine eigene Meldung ("GPS-Freigabe
fehlt"): sie ist der einzige Fall mit anderer Abhilfe, und "GPS offline"
wuerde dort zur Signalsuche statt zum Freigabeschalter schicken.

VERSIONIERUNG: die Zahl, die alle fuer "die Version" hielten, ist keine.
Der Integer in audi-dashboard-version.json ist ein Cache-Brecher, den
install.ps1/update.ps1 bei jedem Deploy mit UtcNow neu setzen -
unabhaengig davon, ob sich Code geaendert hat. Zwei Builds derselben
Quelle bekommen verschiedene Zahlen. Er kann die Frage "ist das derselbe
Stand?" grundsaetzlich nicht beantworten.

Deshalb beide Aufgaben getrennt: neue Datei VERSION im Projektstamm
(2026.08.23.1) als Identitaet, von Hand erhoeht; der Integer bleibt
unveraendert der Cache-Brecher. VERSION fliesst in beide Seiten - als
zweites Feld "app" in audi-dashboard-version.json (alle drei
Deploy-Skripte uebernehmen es jetzt; sie haben die Datei bisher komplett
ueberschrieben und haetten es still zerstoert) und ueber vite define als
__APP_VERSION__ in den Companion-Build. Das Backend veroeffentlicht
pyscript.audi_dashboard_app_version, die App vergleicht und meldet eine
Abweichung in der Hinweisleiste - deren erklaerter Grundsatz "nie eine
stille Veraltung" genau dieser Fall ist, nur dass hier nicht die Anzeige
veraltet, sondern die App selbst.

Bewusst nur Gleichheitsvergleich, nie groesser/kleiner: die Version ist
eine Kennung, keine Zahl; Sortieren waere scheingenau und wuerde bei
einem Formatwechsel still falsch antworten. Fehlt eine der beiden
Seiten, wird nicht verglichen und nichts gemeldet - ein aelteres Backend
oder ein Start ohne Netz darf keinen Fehlalarm ausloesen. Der
vite-Build bricht dagegen hart ab, wenn VERSION fehlt, statt eine App zu
erzeugen, die ihre eigene Veraltung nicht erkennen kann. Das Panel
braucht nichts davon: es laedt bei jedem Seitenaufruf neu.

Geprueft: Backend meldet zuendung: False und app_version 2026.08.23.1 im
Testcontainer, Panel zeigt statt "Fahrzeug faehrt" jetzt "Geparkt seit
13 Tg. 13 Std." und statt der alten Meldung "GPS-Freigabe fehlt";
VERSION landet nachweislich im Build (im Bundle gegriffen) und der Build
bricht ohne die Datei ab (gegengeprueft); tsc sauber, Tests 112/112,
vite build sauber, HA-Start ohne Fehler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 21:59:14 +02:00
tobias 3b99f1f332 Vergangene Daten aus dem HA-Verlauf importierbar, Auslieferstand bereinigt
Datenaufbewahrung (recorder_snippet.yaml, neu): Home Assistant loescht
Sensor-Verlaeufe standardmaessig nach 10 Tagen - das war die stille
Obergrenze dafuer, wie weit sich ueberhaupt je etwas rekonstruieren
laesst, denn geloeschte recorder-Zeilen sind endgueltig weg. Der Block
hebt das auf 365 Tage. Die Bestaende der App selbst (fahrten.jsonl,
tankvorgaenge.jsonl, batteriespannung.jsonl, fahrzeugprofil.json) waren
davon nie betroffen, die kennen ohnehin keine Purge-Logik. Platzbedarf
an der Testinstanz gemessen statt geschaetzt: 90.858 Zeilen in 17 Tagen,
hochgerechnet grob 1,9 Mio. Zeilen und 1-1,5 GB im Jahr, davon 96 % aus
der EU-Data-Act-Integration - dafuer liegt eine auskommentierte
exclude:-Liste bei. Bewusst kein include:-Block, der wuerde jeder
anderen Integration den Verlauf nehmen und dabei kaum Platz sparen.

Historienimport (pyscript/historienimport.py, neu): neuer Dienst
audi_dashboard_historie_importieren(start, ende) liest denselben
Verlauf, den die Live-Trigger in Echtzeit sehen, und leitet rueckwirkend
dieselben Datensaetze ab - Fahrten aus dem Zuendungsverlauf (inklusive
der Pausenregel, kurze Unterbrechungen verschmelzen zu einer Fahrt),
Tankvorgaenge nach derselben Tiefststand-Logik und denselben Schwellen
wie tankerkennung.py, Tagesmin/-max der Batteriespannung. Mehrfach
ausfuehrbar: ueberschneidet sich eine Fahrt mit einer bereits erfassten,
wird sie uebersprungen statt doppelt angelegt. Erzeugte Datensaetze
tragen source: "import".

Gelesen wird ueber HAs eigene recorder-API (get_significant_states),
nicht per direktem SQL auf home-assistant_v2.db: das Schema ist
HA-intern und aendert sich zwischen Versionen, die Funktion ist die
stabile Schnittstelle. Preis dafuer ist hass_is_global: true im
pyscript-Block; ohne die Zeile laeuft alles andere unveraendert weiter,
nur der Import meldet, dass er den Verlauf nicht lesen kann.

Oberflaeche in beiden Codebasen: Knopf "Daten importieren aus Home
Assistant" unter Einstellungen -> Einrichten, dahinter ein Fenster mit
Von/Bis (Vorbelegung: letzte 30 Tage), "Importieren" als Hauptaktion und
"Abbrechen" als zweite Wahl. Das Fenster bleibt offen und zeigt das
Ergebnis, statt optimistisch zu schliessen - hier ist das Ergebnis der
Zweck des Aufrufs. Fortschritt ueber die Entitaet
pyscript.audi_dashboard_import_status, weil pyscript-Dienste sofort
zurueckkehren. In companion-app bewusst NICHT ueber die
Offline-Warteschlange: ein spaeter aus dem Nichts abgefeuerter
Importlauf waere fuer den Nutzer nicht nachvollziehbar.

Beim Testen gefunden und mitgefixt: profil.batterieverlauf_tageswert_
aktualisieren() haengte in Einfuegereihenfolge an. Solange nur die
Live-Aufzeichnung schrieb, war das dasselbe wie sortiert (sie traegt
immer den heutigen Tag ein) - der Import traegt vergangene Tage nach,
die waeren hinter den neueren gelandet und das Diagramm haette zeitlich
rueckwaerts gelaufen. Sortiert jetzt nach Datum, wie der eigene
Docstring es ohnehin versprach.

Auslieferstand bereinigt: einstellungen.py brachte zwei Entity-IDs einer
laengst abgeraeumten Testinstanz mit (testzone_fmm003_...). Beim Testen
unsichtbar, weil die Testinstanz sie ueber entitaeten.json ueberschreibt
- auf einer Neuinstallation waeren sie die wirksamen Werte gewesen. Und
schlimmer als nur wirkungslos: fahrterkennung.py registriert seinen
@state_trigger nur, wenn das Feld belegt ist, ausdruecklich als Schutz
gegen eine leere Entity-ID. Ein gesetzter, aber nicht existierender Wert
hebelt genau diesen Schutz aus. Beide Felder jetzt leer wie alle
anderen; Kopfkommentar der Datei ueberarbeitet, er behauptete noch, die
EU-Data-Act-Integration werde nicht mehr verwendet.

install.ps1 abgesichert, da die erste echte Installation auf HA OS
ansteht: die feste configuration.yaml.bak wurde bei jedem Lauf
ueberschrieben, ausgerechnet das Original war nach dem zweiten Lauf weg
- Sicherungen jetzt zeitgestempelt. Nach dem Schreiben wird die Datei
zurueckgelesen und geprueft (Laenge, genau ein Markerblock, bisheriger
Inhalt unveraendert); bei der kleinsten Abweichung rollt das Skript
automatisch zurueck. Die Sicherheitseigenschaften stehen jetzt im Kopf
der Datei, statt dass man ihnen glauben muss.

Standort-Blatt: "Teilen" war im Tagmodus unsichtbar - .standort-pille
nutzte var(--tile), das Blatt darunter var(--tile-deckend), und die
iOS-Auflage setzt im Tagmodus beide auf #FFFFFF. Gemessener Kontrast
1,00:1, weiss auf weiss. Beide Pillen folgen jetzt der Knopfsprache des
Panels (.aktion / .aktion.primaer): Umriss fuer die Nebenaktion, var(--fg)
gefuellt fuer "Route". Danach 17-21:1 Textkontrast in beiden Themes.
Nebenbei ist damit --line-strong - eine Linienfarbe - nicht laenger als
Knopffuellung im Einsatz.

Geprueft: Import zweimal ueber die echte Oberflaeche im Browser gegen
eine in die recorder-DB eingespielte Kunsthistorie (der Testcontainer
laeuft nicht, waehrend das Auto faehrt, echte Fahrten liegen dort also
nicht vor) - drei Fahrten wie erwartet, die 5-Minuten-Unterbrechung
korrekt zu einer 50-Minuten-Fahrt verschmolzen, die 30-Sekunden-Zuendung
verworfen, Strecken kilometergenau; zweiter Lauf legte 0 an und meldete
3 als vorhanden. Neuinstallation in einem Wegwerf-Container: nur
Profilvorlage und Parser, keine Fahrten-/Tank-/Batteriedatei, null
Fehler im Log. install.ps1 gegen Attrappen: -Pruefen schreibt nichts,
echter Lauf laesst automations.yaml bytegleich (SHA-256) und fremde
Bloecke stehen, Ergebnis parst als gueltiges HA-YAML, zweiter Lauf
idempotent, bei fremdem pyscript:/panel_custom: bleibt die Datei
bytegleich. tsc --noEmit sauber, companion-app-Tests 106/106, vite build
sauber, HA-Configcheck und Start ohne Fehler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 20:42:54 +02:00
tobias f115af50ab Versicherung/Steuer: Wortlaut-Feinschliff und Vertrag komplett editierbar
Mein-Audi-Kachel "Versicherung/Steuer" (vAudi()) und Kfz-Steuer-Detail
(vSteuer()): "Zusammen" heisst jetzt "Summe", die Fusszeile "feste
Kosten im Jahr" darunter entfaellt ersatzlos. Die Kfz-Steuer-Zeile
zeigt "im Jahr" statt des rohen Datenwerts "jaehrlich", deckungsgleich
mit der Versicherungs-Zeile darueber - nur wenn steuer.zeitraum
tatsaechlich "jaehrlich" ist. Der gespeicherte Wert und die
"jaehrlich"/"halbjaehrlich"-Auswahl in vSteuerBearbeiten() bleiben
unveraendert, das war ein reiner Anzeige-Fix, keine Datenmodell-
Aenderung. companion-app hatte an keiner der beiden Stellen ein
Gegenstueck (kein "Zusammen"-Tile, keine Nur-Lese-Anzeige des
Zeitraums) - dokumentiert als vorbestehende strukturelle Luecke statt
kommentarlos uebersprungen.

Vertrag-Kachel: war komplett nur lesbar (Gesellschaft, Umfang,
Vertragsnummer, Selbstbeteiligung, Schadenfreiheitsklasse). Neue
vVertragBearbeiten()-Ansicht (Panel, Route "vertrag") ueber ein neues
Zahnrad auf der Vertrag-Kachel - alle Felder editierbar,
Selbstbeteiligung zusaetzlich mit Hinzufuegen/Loeschen pro Zeile (neue
.zeile-loeschen-Klasse, 44x44pt-Tippflaeche wie .km-edit). Die
aktuelle Schadenfreiheitsklasse bleibt bewusst nur ueber "Beitrag
anpassen" editierbar (Querverweis statt Duplikat), nur "zuvor" (sfAlt)
ist neu in der Vertrag-Ansicht.

companion-app: neue Vertrag()-Seite (Route "vertrag", eigene
NaviKachel auf der Versicherungs-Seite) nach dem dortigen etablierten
Muster (lokaler Entwurf-State + expliziter Speichern-Knopf statt
Panel-Autosave pro Feld - gleiches Verhalten, eigenes Idiom). Die
bisherige Nur-Lese-Selbstbeteiligung in Vertragsdetails() entfaellt,
da sie jetzt (editierbar) in Vertrag() lebt. Schadenfreiheitsklasse
wird in companion-app bewusst nicht ergaenzt: das Feld war dort noch
nie sichtbar, auch nicht lesend - eine komplette neue UI-Sektion dafuer
waere kein "Zeile editierbar machen" mehr, sondern ein neues Feature;
als Luecke dokumentiert statt still uebergangen.

tsc --noEmit sauber, companion-app-Tests 100/100 (inkl. dem
Alle-Seiten-Rendertest, der jetzt auch "vertrag" abdeckt), vite build
erfolgreich. Panel live im Docker-Testcontainer geprueft: Zeile
hinzufuegen/bearbeiten/loeschen, Daten bleiben nach Re-Render erhalten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 16:50:58 +02:00
tobias 23b6defa68 Service-Termine: CI-Icons statt Wortlaut, Anstehende-Termine neu gegliedert
Naechster-Service-Kachel (Uebersicht): das "bis zum Oelwechsel"/
"bis zur Inspektion" vor der Kennzahl entfaellt zugunsten der echten
Audi-CI-Icons oil-change/inspection (vom Nutzer als SVG geliefert,
verbatim uebernommen wie die uebrigen CI-Icons). Hauptuntersuchung hat
kein eigenes Icon (kein Restkilometer-Ziel) und zeigt stattdessen ihr
Datum in derselben 40px-Groesse wie die km-Zahlen, mit dem Wort
"Hauptuntersuchung" darunter statt "bis zur Hauptuntersuchung" - vorher
war das Datumsfeld kleiner (30px), um Umbruch zu vermeiden; am
laufenden Panel nachgemessen, dass 40px keinen Umbruch verursacht.
"vsl." wird ausgeschrieben zu "voraussichtlich am". bisText()/ART_BIS
(Panel) und bisText()/BIS_TEXT (companion-app) wurden durch die
Aenderung ueberfluessig und als Orphans entfernt.

Anstehende-Termine-Box (Service-Seite): aus der dt/dd-Liste werden drei
sichtbar getrennte Bloecke (Kopfzeile mit Icon + Name + optionalem
Werkstatt-Chip, optionale Herstellervorgabe-Zeile, grosse Kennzahl,
optionale Prognose-Fusszeile) - Apple HIG (Hierarchie ueber
Position/Groesse, Inhalt nicht mit Nebensaechlichem ueberladen) plus
Audi-CI (Flaeche mit Haarlinien statt Karten). Durchlief drei
Mockup-Runden als Artefakt vor der Umsetzung, zwei davon vom Nutzer
zurueckgewiesen ("Major Information is missing" bzw. Kommentare zu
Icon-Groesse/Herstellervorgabe/Ausrichtung).

Herstellervorgabe-Logik korrigiert: eine Fahrzeugmeldung stammt immer
aus dem werkseigenen Wartungsprogramm des Bordcomputers, unabhaengig
vom in "Einrichten" gewaehlten App-Intervall - zaehlt also immer als
Herstellervorgabe, nicht nur wenn der App-Modus zufaellig "hersteller"
ist. Die Prognose-Fusszeile zeigt die Restkilometerzahl nur noch beim
eigenen (kuerzeren) Intervall, bei Herstellervorgabe nur noch das
Datum. "Eigenes" in der Ölwechsel-Intervall-Auswahl umbenannt zu
"Individuell".

companion-app-Portierung ist eine echte Neustrukturierung: die
bisherige "Naechster Oelwechsel"-Kachel mischte Oelwechsel-,
Inspektions- und Hauptuntersuchungs-Zeilen in einer gemeinsamen
Werteliste, jetzt "Anstehende Termine" mit denselben drei Bloecken wie
im Panel. Neuer Export letzterInspektion() in service.ts. Zwei
dokumentierte, vorbestehende Luecken bleiben bestehen statt neu
kaschiert zu werden: kein Werkstatt-Chip (companion-app hat keine
"Termin vereinbaren"-Funktion), Herstellervorgabe kommt aus
fahrzeug.oel.modus statt einem intervalle()-Aequivalent (das es dort
nicht gibt).

npm run typecheck sauber, npm run test 99/99, npm run build erfolgreich.
Panel deployed als Version 1787013003, synchron in installationspaket/,
live im Docker-Testcontainer per DOM-Abfrage geprueft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 00:03:16 +02:00
tobias 2848b5286f Parity-Regel: Uebersicht-Service-Block in companion-app portiert
Nutzer stellte klar: Panel und companion-app sollen immer denselben
funktionalen Stand haben, auch wenn eine Anfrage nur das Panel nennt -
das ist reiner Testkomfort (Docker-Instanz laesst sich am schnellsten
ansehen), keine Scope-Entscheidung. Regel als verbindlicher Absatz in
AGENTS.md verankert, direkt unter der bestehenden Maintenance-Regel.

Erste Anwendung im selben Zug: die eben im Panel gebaute
"Naechster Service"-Kachel nach companion-app/ portiert.

- daten/service.ts: neues naechsterService() als Gegenstueck zu
  naechsterTermin() im Panel - waehlt ueber Oelwechsel-/Inspektions-
  Prognose und die von Hand gepflegte Hauptuntersuchung hinweg den
  zeitlich naechsten Termin. bisText() liefert denselben Artikel wie
  ART_BIS im Panel ("bis zum Oelwechsel" / "bis zur Inspektion").
- screens/Uebersicht.tsx: die bisherigen Kacheln "Reichweite"/
  "Kilometerstand" nebeneinander plus eine separate "Service"-Kachel
  mit Werteliste weichen einer Reichweiten-Kachel plus einer
  Service-Kachel mit .dm-serviceblock (Knopf, nur der obere Teil) und
  .dm-servicezeile (reine Anzeige) darunter - dieselbe Control/Content-
  Trennung wie im Panel.
- stile/screens.css: .dm-serviceblock/.dm-servicezeile ergaenzt.

Tests: 4 neue in service.test.ts (naechsterService waehlt das frueher
faellige Datum, nimmt die Hauptuntersuchung auf, liefert nichts ohne
Servicebuch/HU, bisText-Artikel je Art), 1 neuer in screens.test.tsx,
der mit einem vi.fn() als geheZu wirklich belegt, dass ein Klick auf
den Serviceblock navigiert und ein Klick auf die Kilometerstand-Zeile
es nicht tut - dafuer bekam zeige() in screens.test.tsx erst einen
injizierbaren geheZu-Parameter (vorher hart auf () => {} verdrahtet).

npm run typecheck sauber, npm run test 100/100 (von 95), npm run build
erfolgreich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 20:44:31 +02:00
tobias 7fc378f427 Übersicht: Wert und Kontext trennen statt "9,0 tkm/ 04/27"
Auf Nachfrage, wie Apple HIG und die Audi-CI die beiden Kacheln loesen
wuerden - beide laufen auf dieselbe Antwort hinaus.

Die Oelwechsel-Kachel presste zwei verschiedene Groessen in ein
Zahlenfeld: Restkilometer und Prognosedatum, getrennt durch einen
Schraegstrich. Zwei Einheiten, davon eine erfundene ("tkm" schreibt
niemand - die Mein-Audi-Servicebox nannte dieselbe Zahl schon immer
"9.029 km"), zwei Schraegstriche mit zwei Bedeutungen, und auf 375px
zweizeilig umgebrochen.

Apple HIG "Layout" ("don't obscure essential information by crowding it
with nonessential details") und "Typography" (Hierarchie ueber Groesse
und Farbe) verlangen die Trennung; die Audi-CI definiert .fig
ausdruecklich als "large numeric figures" - eine Zahl, nicht zwei.

- Wert gross (9.029 km), Kontext leise darunter (vsl. 04/2027)
- Echte Einheit statt "tkm"
- Kachel ist jetzt ein Knopf mit Chevron zur Service-Seite; vorher eine
  Sackgasse, obwohl es die Seite gibt
- mmjj() dadurch verwaist, ersetzt durch mmjjjj() mit vollem Jahr
- HU-/Inspektionszweig faellt mit dem Oelwechselzweig zusammen, weil sie
  nach der Trennung dieselbe Form haben

companion-app hatte diesen Fehler nicht (dort standen die Termine schon
als Label/Wert/Zusatz). Dort stattdessen der benachbarte
Gruppierungsfehler behoben: die Service-Zeilen hingen innerhalb der
Kilometerstand-Kachel und fuehrten nirgendwohin - jetzt eigene,
antippbare Kachel zur Service-Seite.

Live geprueft im Panel (Tag und Nacht, 375px): beide Kacheln einzeilig,
Tippen oeffnet Service. companion-app: typecheck sauber, 95/95 Tests,
Build erfolgreich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 13:54:27 +02:00
tobias edf3845375 companion-app: catch up six days of panel drift before the native build
The phone-app codebase (companion-app/) hadn't been touched since
2026-08-11 - every panel fix and feature since then (HIG audit rounds,
receipt upload, trip editing, Inspektion forecast, ...) existed only in
the HA panel. Found while auditing what's needed for a real iPhone
build; user decision was to port everything now rather than ship a
stale app.

Six real, verified gaps (not blind copies of panel CSS/markup, which
doesn't transfer to the @audi-dash/ui component set):

- Battery voltage cutoff was still 13.2V, not the panel's 12.8V fix.
- Dead wlan_name field (WLAN trip detection was fully removed from the
  backend 2026-08-12) - removed from the adapter and the settings screen
  instead of leaving a form field that silently does nothing.
- No Inspektion forecast - added inspektionPrognose() alongside the
  existing oelwechselPrognose(), sharing a refactored core.
- Arbeitsweg pill used the design-system's "work" variant, which is
  documented as recoloring to red - stopped passing it, same fix as the
  panel.
- Trip creation only took Beginn/Ende/Art; extended with Startort/
  Zielort/Kilometerstand/Distanz via a new shared FahrtFelder.tsx.
- FahrtDetail.tsx was read-only - added an edit mode using the same
  shared fields, backed by a new DataMetricApi.fahrtAktualisieren()
  calling the backend service built earlier this session.

Explicitly checked and found not applicable: price rounding (already
2 decimals here), pull-to-refresh CSS (no native gesture to fix),
the panel's drag/paste receipt dialog (solves a desktop-browser problem
this native app doesn't have - the plain file picker already gets iOS's
native Files integration), and the purely cosmetic panel CSS fixes.

npm install run at the repo root (node_modules was incomplete/stale),
package-lock.json reflects the real dependency tree. Verified with
npm run typecheck (clean), npm run test (95/95, up from 90 - added
interaction tests for the new form and inspektionPrognose), and
npm run build (succeeds).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 12:16:40 +02:00
Paul Nothaft 4395ff6ac6 Native iOS-Huelle in Betrieb, drei Darstellungsfehler behoben
Xcode ist vorhanden, die Huelle liess sich also wirklich bauen statt nur
vorzubereiten: Capacitor 8 fuer iOS und Android, Build fuer den Simulator
erfolgreich, App laeuft und zeigt echte Daten vom Server.

CapacitorHttp eingeschaltet. Das ist keine Feinheit: die native Huelle
liefert die Oberflaeche unter eigenem Ursprung aus, jede Anfrage an Home
Assistant ist damit ursprungsuebergreifend, und die WebView lehnt sie ohne
CORS-Freigabe ab. Nativ gestellte Anfragen kennen keine CORS-Pruefung -
die App braucht dadurch keine CORS-Einstellung am Server.

Drei Fehler, die erst die Bildschirmfotos zeigten:

1. Der Fahrzeug-Block auf der Uebersicht war unsichtbar. Ursache war der
   Flexbox-Fallstrick: "overflow: hidden" setzt die automatische Mindesthoehe
   auf 0, und sobald die Seite laenger ist als der Bildschirm, quetscht
   flex-shrink den Block auf Hoehe 0. Modellname, Typenschild und Kennzeichen
   waren damit schlicht weg.
2. Neben dem Typenschild stand der Modellname noch einmal komplett, obwohl
   das Schild ihn bereits zeigt. Das alte Panel loest das laengst richtig -
   nur der Zusatz gehoert daneben. Nachgezogen, samt Entdopplung, wenn die
   Ausfuehrung schon im Namen steckt.
3. In der Fotogalerie liefen die Dateinamen ueber den Rand der Miniaturen.

Dazu: Datumszeile in Listen bricht um statt abzuschneiden, Statistik nutzt
auf der Flaeche mehrere Spalten.

Im Backend war das Kilometerstand-Screening kaputt: es rief urlopen direkt
auf, was Home Assistant seit 2026.8 als blockierenden Aufruf abbricht. Jede
Fahrt blieb dadurch ohne Strecke - sichtbar nur als Warnung im Protokoll.
Laeuft jetzt ueber task.executor und rechnet an der Testinstanz wieder echte
Strecken aus dem Verlauf.

Die CORS-Freigabe fuer die Web-Fassung ist in der Testkonfiguration
hinterlegt. Sie greift in Home Assistant 2026.8 allerdings nicht - deshalb
wird die App aus Home Assistant selbst ausgeliefert (gleicher Ursprung, kein
CORS), was ohnehin der geplante Weg ist.

Die erzeugten Ordner ios/ und android/ bleiben ungetrackt; sie entstehen
jederzeit neu aus dem Webbuendel.
2026-08-11 12:44:11 +02:00
Paul Nothaft f8b877d555 Phase 10-13: PWA fertig, blockierte Phasen vorbereitet
PWA laeuft: Manifest, Symbole in allen noetigen Groessen und die
iOS-Angaben, die Apple statt des Manifests auswertet. Das Startsymbol ist
bewusst neutral - die Vier Ringe auf einem Homescreen waeren nach aussen
sichtbar und fielen nicht mehr unter die private Nutzung.

Vorbereitet, aber nicht ausgefuehrt: Capacitor-Konfiguration, ein
Ablage-Adapter fuer Schluesselbund und Keystore (greift defensiv auf die
Laufzeit zu, damit die App ohne das Plugin unveraendert weiterlaeuft), die
Pfad-Freigabeliste fuer den Reverse Proxy und das Geruest der
FMM003-Zuordnungstabelle.

Die QR-Seite fuer die Einrichtung war zunaechst ein Fehlgriff: Ich hatte den
QR-Erzeuger selbst geschrieben. Der Vergleich gegen eine erprobte
Implementierung zeigte 1239 abweichende Module von 3249 - der Code waere
unlesbar gewesen. Jetzt liegt eine bewaehrte Bibliothek (MIT, 57 KB) neben
der Seite im eigenen Home Assistant. Das erfuellt die eigentliche
Anforderung genauso: kein Netzzugriff, der Token verlaesst das eigene Netz
nicht. Geprueft ueber einen Umlauf - 233 Zeichen hinein, identisch wieder
heraus.
2026-08-11 11:27:20 +02:00
Paul Nothaft a68817065b Phase 9: Tests fuer die portierte Rechenlogik
41 weitere Tests fuer Statistik, Service-Prognose und Formatierung. Die
Faelle sind entlang der Regeln gewaehlt, die beim Portieren wichtig waren:
Wochenbeginn Montag, Nachtspanne ueber Mitternacht, mengengewichteter
Durchschnittspreis statt Mittel der Einzelpreise, Plausibilitaetsfenster beim
Langzeitverbrauch, Neustart der Oelprognose ab dem letzten Wechsel samt
Zeitlimit-Deckelung, BOM und Semikolon in der CSV-Ausgabe.

Ein Test war zunaechst falsch: er verglich das UTC-Datum gegen eine
Ortszeit-Erwartung. Der Code rechnet bewusst lokal - sonst begaenne der
Monat in unserer Zeitzone einen Tag zu frueh.

companion-app/README.md auf den erreichten Stand gebracht.

Gesamt: 90 Unit- und Rendertests plus 9 Pruefungen gegen die laufende
Home-Assistant-Instanz.
2026-08-11 11:21:56 +02:00
Paul Nothaft 571d12310f Phase 8: Audi-Schrift, Vier-Ringe und Typenschilder
Die drei Schriftschnitte stammen aus dem Panel, wo sie als base64 im
Stylesheet lagen - als eigene woff2-Dateien sind sie zwischenspeicherbar
statt bei jedem Laden erneut uebertragen zu werden. Typenschilder und Ringe
wechseln mit dem Erscheinungsbild.

Lizenzgrenze eingehalten und geprueft: design-system bleibt unveraendert und
enthaelt keine Markendateien, weil es nach aussen hochgeladen wird. Die App
darf sie nutzen, weil sie nur seitlich installiert wird.

Dabei noch eine Typungenauigkeit korrigiert: technik und ausstattung im
Profil sind Listen von Gruppen, waren aber wie die uebrigen Abschnitte als
Objekt deklariert.
2026-08-11 11:19:38 +02:00
Paul Nothaft 8e6e5bc2ee Phase 7c: restliche Screens - alle 21 Seiten stehen
Fahrzeugdaten, Batterieverlauf, Service mit Werkstatt und Servicebuch,
Reifen, die ganze Versicherungs- und Steuergruppe, Einstellungen in voller
Tiefe und die Live-Ansicht.

Portiert dazu: die eigene Oelwechsel-Prognose (rechnet ab dem letzten
Wechsel im Servicebuch neu, weil der Langzeitschnitt dafuer zu traege ist),
Kalenderdateien fuer Termine und die CSV-Ausgabe im deutschen Format.

Die Live-Ansicht ist gebaut, aber ueber einen Schalter abgeschaltet: die
Werte dafuer liefert erst der FMM003. Ein Menuepunkt mit lauter Nullen waere
schlechter als keiner.

Zwei Stellen, an denen die App bewusst zurueckhaltend ist: ein unbekannter
Pruefpunkt erscheint als hohler Ring statt als geratenes Gruen, und der
Reifen-Kilometerstand wird nie zurueckgeschrieben, weil ihn der Server
fortschreibt.

49 Tests, darunter ein Rendertest ueber alle 21 Seiten in beiden Layouts
mit Beispieldaten in der Form, die das Backend wirklich liefert.
2026-08-11 11:17:47 +02:00
Paul Nothaft 8b64a808ed Phase 7b: Fahrten, Tanken und Statistik
Listen mit Jahr-Monat-Aufklappung, Detailseiten, manuelles Eintragen und
Beleg-Upload. Statistik rechnet ueber die portierten Regeln.

Zwei offene Audit-Befunde des alten Panels sind hier gleich mit erledigt:

1. Loeschen war ausschliesslich per Wischgeste erreichbar. Wer mit
   Sprachausgabe oder Schaltersteuerung bedient, wischt nicht - Fahrten und
   Tankvorgaenge waren fuer diese Nutzer gar nicht loeschbar. Jede Zeile hat
   jetzt zusaetzlich ein sichtbares Menue, ein richtiger Knopf mit Tastatur.
2. Leaflet kam per CDN und blieb auf einem Handy ohne eigenen Internetzugang
   still leer. Es liegt jetzt im Buendel (eigener Abschnitt, erst bei Bedarf
   geladen).

Bewusst NICHT uebernommen: fakeTrack() aus dem alten Panel, das eine
erfundene Linie zwischen zwei Punkten zeichnete, wenn keine Route vorlag.
Solange das Fahrzeug keine Positionen liefert, sagt die Seite das - statt
eine glaubwuerdig aussehende Erfindung zu zeigen.
2026-08-11 11:11:00 +02:00
Paul Nothaft 4dbf3cb173 Phase 7a: Uebersicht, Fahrzeugstatus und Mein Audi
Erste drei Screens auf der fertigen Datenschicht. Uebersicht mit Fahrzeugbild,
Sicherungsstatus, Reichweite und Tankfuellstand, Kilometerstand samt
Service-Faelligkeit und Vorschau auf letzte Fahrt und Tankung. Fahrzeugstatus
zeigt die 16 Einzelpruefungen, wobei ein unbekannter Sensor als hohler Ring
erscheint statt als gruen geratener Punkt - dieselbe Vorsicht, die schon das
Backend walten laesst. Mein Audi buendelt Fotogalerie und Wege zu den
Unterseiten.

Dazu die geteilte Rechenlogik der Statistik portiert (Zeitraeume,
Langzeitverbrauch mit Plausibilitaetsfenster, Gruppierung nach Jahr und
Monat) und Formatierung im deutschen Format.

Bild-Komponente kapselt eine Reibung zwischen App und Bibliothek: die App
laeuft mit exactOptionalPropertyTypes, ein undefined an einem optionalen Prop
waere sonst an jeder Aufrufstelle einzeln zu behandeln.
2026-08-11 11:07:03 +02:00
Paul Nothaft 0103508cea Phase 6: Datenanbindung, Ersteinrichtung und Offline-Anzeige
Die App spricht jetzt echt mit Home Assistant. Ein Datenkontext buendelt die
vier Datentoepfe, haengt sich an den WebSocket-Ereignisstrom und reicht
Verbindungszustand und Warteschlange nach aussen; die Screens kennen weder
REST noch WebSocket. Ersteinrichtung prueft die Zugangsdaten, bevor sie sie
speichert, und unterscheidet in der Fehlermeldung zwischen abgelehntem Token
und nicht erreichbarem Server - zwei Faelle, die voellig verschiedene
Reaktionen verlangen.

Zwei echte Fehler, die erst der Test gegen die laufende Instanz zutage
brachte:

1. Die Typen der Datenschicht deklarierten tank_prozent, sicher_abgestellt
   und sicherheit. Das Backend schreibt tankprozent, gesichert und
   sicherheitscheck - die Oberflaeche haette still ueberall undefined
   gelesen, ohne dass irgendetwas fehlgeschlagen waere.
2. Der Profil-Adapter reichte lebende Verweise ins Rohprofil durch. Ein
   Formular haette damit den Rohstand mitveraendert und die Zusage gebrochen,
   den vom Backend fortgeschriebenen Reifen-Kilometerstand nie zu
   ueberschreiben. Abschnitte werden jetzt kopiert.

Ausserdem zwei Parameter-Eigenschaften in der Datenschicht aufgeloest, weil
Node sie im Strip-Modus nicht uebersetzt und genau darueber die Rauchtests
laufen.

Belegt durch neun Pruefungen gegen die laufende Instanz (Lesen, Dienstaufruf,
vollstaendiger Warteschlangenumlauf, WebSocket-Anmeldung) und 21 Unit-Tests.
2026-08-11 11:02:44 +02:00
Paul Nothaft 5c0c2d1898 Phase 5: App-Shell mit responsivem Layout, Navigation und Theme
Zwei Layouts aus derselben Zusammensetzung: unter 1000px Tab-Leiste unten,
darueber Seitenleiste links mit Brotkrume statt Zurueck-Pfeil. Navigation
ist wie im alten Panel ein flacher Zustand mit fester ZURUECK-Tabelle statt
eines Router-Pakets - die Verschachtelung ist hoechstens dreistufig.

Theme und Tab-Beschriftung ueberleben einen Neustart (localStorage,
abgesichert fuer den Privatmodus), Startwert folgt der Systemeinstellung.
Symbole aus dem alten Panel uebernommen, damit App und Panel gleich aussehen.

Neun Rendertests belegen die Abnahmekriterien: Layoutwechsel in beide
Richtungen, Zurueck-Ziel ueber die Tabelle, Zahnrad statt Tab-Leiste fuer
die Einstellungen, Theme-Persistenz.
2026-08-11 10:55:10 +02:00
Paul Nothaft 2fe3576682 Phase 4: Vite-React-Geruest und Workspace-Anbindung
npm-Workspace im Repo-Root verbindet companion-app mit @audi-dash/ui, so
dass die App die Komponentenbibliothek als echte Abhaengigkeit nutzt statt
nur in der Doku. Dazu Vite, React 18, Einstiegsdateien und eine schlanke
Grundlage-CSS, die nur enthaelt was eine App braucht und eine Bibliothek
nicht mitbringt: Vollbild, sichere Bereiche, Scrollverhalten.

Zwei Audit-Befunde des alten Panels sind hier von Anfang an beruecksichtigt:
Formularfelder mit 16px (sonst zoomt iOS Safari beim Fokus) und ein
globaler :focus-visible-Ring.

Nebenbei im design-system den Kontrastwert --fg3 nachgezogen: dort stand
noch #657081 mit 3,0:1 auf --tile, das Panel hatte den Wert laengst auf
#8a94a3 korrigiert. Ohne das haette die neue App den bereits behobenen
WCAG-Verstoss geerbt.
2026-08-11 10:50:51 +02:00
tobias e1d570992e Initialer Import: HA-Panel, Design-System, Companion-App
Drei zusammengehörige Teile in einem Repository:

- homeassistant/  Das fertige, im Einsatz befindliche Home-Assistant-Panel
  (panel_custom Custom Element + pyscript-Backend). Echte Fahrzeug- und
  Personendaten (fahrzeugprofil.json, fahrten.jsonl, tankvorgaenge.jsonl,
  Tankbelege) bleiben per .gitignore außen vor; die anonymisierte Vorlage
  fahrzeugprofil.example.json ist mit dabei.

- design-system/  Eigenständige React-Komponentenbibliothek (@audi-dash/ui),
  die die visuelle Sprache des Panels nachbildet - ohne Audi-Markenzeichen
  und ohne die lizenzierte Hausschrift. Dient als Grundlage für Claude
  Design. War bis hierher ein eigenes Repository und ist in dieses
  eingeschmolzen worden.

- companion-app/  Datenschicht der neuen App DataMetric360 (iOS/Android via
  Capacitor, zusätzlich als Iframe im HA-Dashboard). Noch ohne Oberfläche:
  REST- und WebSocket-Zugriff auf Home Assistant plus Warteschlange für
  Änderungen ohne Netz. Ersetzt das eingespritzte hass-Objekt, das nur
  innerhalb des HA-Frontends existiert.

Dazu die Projektdokumentation: SPECIFICATION.md (Ist-Stand des Panels),
COMPANION_APP_ARCHITECTURE.md (Architekturentscheidungen der neuen App),
AUDIT_2026-08-10.md, DESIGN_BRIEF_DATAMETRIC360.md und der ursprüngliche
Bauauftrag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 00:20:05 +02:00