Files
audi-app/companion-app
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
..

DataMetric360 — companion app

Private vehicle app for one car. Ships three ways from one codebase: native iOS and Android via Capacitor, and embedded as a plain iframe in the Home Assistant dashboard. Architecture and the decisions behind it: ../COMPANION_APP_ARCHITECTURE.md.

Status

Data layer only. No UI yet — the screens come from the Claude Design draft (../DESIGN_BRIEF_DATAMETRIC360.md), and get implemented on top of design-system/'s React components once that draft settles. This package was written first on purpose: how the app talks to Home Assistant doesn't depend on what the screens look like, so it survives every design iteration untouched.

Not yet added (deliberately, they'd be guesses today): React/Vite, Capacitor, the native secure-storage adapter, and the Audi brand assets (fonts/rings/badges — those come from homeassistant/www/ at implementation time, never into design-system/, see the licence note in the architecture doc).

Layout

src/api/
├── types.ts           HA state shapes + domain types (Fahrt, Tankvorgang, …) + the entity-ID table
├── umgebung.ts        runtime detection (capacitor/iframe/browser), credential storage, URL helpers
├── rest.ts            REST client — replaces hass.states / hass.callService
├── live.ts            WebSocket client — push updates, auto-reconnect with backoff
├── warteschlange.ts   offline queue for writes made without a connection
└── index.ts           DataMetricApi — ties the three together, exposes the domain operations
scripts/smoke.ts       verification against a running HA instance

Comments are in German, matching the rest of the project.

What replaces what

The old panel got a hass object injected by panel_custom. That object only exists inside the HA frontend, which is exactly why the old panel can't run as a standalone app. The mapping:

Old panel Here
hass.states[id].attributes.daten rest.datenLesen(id) / DataMetricApi.profilLesen() etc.
hass.callService(...) warteschlange.einreihen(...) via the DataMetricApi methods
automatic re-render on state push live.aufZustand(...)

Same entities, same pyscript services, same backend files written — only the transport changes.

Every write goes through the queue rather than straight to REST. That's what makes offline edits behave the same as online ones, just delayed: a receipt photographed in a dead zone is persisted and sent when the connection returns, surviving an app restart in between.

Commands

Node is installed at C:\Program Files\nodejs but is not on PATH — prefix it:

$env:Path = "C:\Program Files\nodejs;" + $env:Path
npm run typecheck
npm run smoke

smoke targets http://localhost:18123 (the audi_ha_test Docker container) by default; pass a different base URL as the first argument.

Verification status (2026-08-10)

tsc --noEmit clean under strict plus noUncheckedIndexedAccess and exactOptionalPropertyTypes. All 7 smoke checks pass against the running test container, covering URL normalisation, the http→ws/https→wss mapping, that GET /api/ exists and rejects an unauthenticated request with 401, and that the WebSocket opens with auth_required — which is the message live.ts's whole auth flow is built around.

Not yet verified — needs a token: authenticated reads, service calls, and the queue's real round-trip. Those need a Long-Lived Access Token, which is a deliberate manual step (see the LLAT provisioning decision in the architecture doc). To do it: create a token in the HA profile page, then extend scripts/smoke.ts with authenticated cases.