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

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.