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>
This commit is contained in:
@@ -1828,16 +1828,49 @@ about what is *running*. Today those two are wired completely differently:
|
||||
So the iOS app can be weeks behind the panel with nothing anywhere making that visible. That is a
|
||||
real gap, not a hypothetical one, and it gets worse once the app is genuinely in daily use.
|
||||
|
||||
Proposed shape (discussed 2026-08-23, **not yet built** — needs the owner's call on the delivery
|
||||
layer): one shared version stamp fed into both builds instead of today's unrelated
|
||||
`audi-dashboard-version.json` integer and `0.1.0`; the backend publishing the version it shipped
|
||||
with, so both frontends can compare their own build stamp against it and show a visible hint when
|
||||
behind (drift becomes loud instead of silent); and for delivery, OTA web-bundle updates so the iOS
|
||||
shell can follow without an Xcode rebuild. Pointing Capacitor's `server.url` at HA was considered
|
||||
and rejected: it would make the app as current as the panel, but the shell then cannot boot without
|
||||
reaching HA, which guts the deliberately built offline queue (`api/warteschlange.ts`). Note the
|
||||
whole mechanism survives the later integration conversion unchanged — that end state serves panel
|
||||
assets and companion bundle from the same component, so one HACS update moves both.
|
||||
**Built 2026-08-23 — the version identity and the drift warning.** See `VERSIONIERUNG.md` for the
|
||||
owner-facing version.
|
||||
|
||||
The first finding was that the number everyone assumed was "the version" is not one:
|
||||
`audi-dashboard-version.json`'s integer is a **cache-buster**, rewritten with `UtcNow` by
|
||||
`install.ps1`/`update.ps1` on every deploy regardless of whether any code changed. Two builds of
|
||||
identical source get different numbers; the same build deployed twice gets two more. It can never
|
||||
answer "are these the same state?". So the two jobs were separated instead of conflated:
|
||||
|
||||
- **`VERSION`** (new, repo root, e.g. `2026.08.23.1`) — the identity, bumped by hand on change
|
||||
- the existing integer — unchanged, still only breaks the browser cache
|
||||
|
||||
`VERSION` now flows to both sides: into `audi-dashboard-version.json` as a second field `app`
|
||||
(carried over by all three deploy scripts — they previously overwrote the whole file and would have
|
||||
silently destroyed it), and into the companion build via vite `define` → `__APP_VERSION__`. The
|
||||
backend reads the file and publishes `pyscript.audi_dashboard_app_version`; the companion compares
|
||||
that against its own compiled-in stamp and, on mismatch, says so in the existing `Hinweisleiste` —
|
||||
whose stated principle is already "nie eine stille Veraltung", which is exactly this case with the
|
||||
staleness moved from the data to the app itself.
|
||||
|
||||
Deliberate choices worth keeping: the comparison is **equality only**, never greater/less — the
|
||||
version is an identifier, not a number, and ordering it would be false precision that breaks
|
||||
silently on a format change. Either side missing yields `"unbekannt"` and **no** warning, so an
|
||||
older backend or an offline start cannot produce a false alarm. And the vite build **fails hard**
|
||||
if `VERSION` is absent rather than emitting an app that cannot detect its own staleness. The panel
|
||||
needs none of this: it re-fetches on every page load and cannot go stale.
|
||||
|
||||
**OTA delivery: verified viable, not yet built.** `@capgo/capacitor-updater` 8.51.14 checked
|
||||
against the real package: MPL-2.0, peer `@capacitor/core: ^8.0.0` against our `^8.5.0`, and
|
||||
self-hosting is first-class (`updateUrl`, or manual mode entirely). Manual mode is the good fit —
|
||||
`download({version, url})` + `set()` against a plain zip under `/local/`, needing **no** custom
|
||||
endpoint at all, with automatic rollback to the last good bundle via `notifyAppReady()`. It pairs
|
||||
exactly with the version entity above: that entity is already the signal that a newer bundle exists.
|
||||
Owner approved going this route, and a paid Apple developer account is available through Paul (Paul
|
||||
Nothaft, the Gitea repo owner) — which is what makes it worthwhile, since the free account's 7-day
|
||||
signature expiry would otherwise force Xcode weekly anyway.
|
||||
|
||||
Capacitor's `server.url` pointed at HA was considered and **rejected**: it would make the app as
|
||||
current as the panel, but the shell then cannot boot without reaching HA, gutting the deliberately
|
||||
built offline queue (`api/warteschlange.ts`).
|
||||
|
||||
The whole mechanism survives the later integration conversion unchanged — that end state serves
|
||||
panel assets and companion bundle from the same component, so one HACS update moves both.
|
||||
|
||||
### G) Fresh-install audit + installer hardening (2026-08-23, before the first real deployment)
|
||||
|
||||
@@ -1893,6 +1926,35 @@ user to check Einstellungen → System → Speicher first, and recommend `purge_
|
||||
is tight (enough for the "catch up on the past" purpose, since the import moves data into the app's
|
||||
own permanent `.jsonl` files anyway).
|
||||
|
||||
**Two user-reported bugs fixed 2026-08-23, both worse than they looked:**
|
||||
|
||||
*"Fahrzeug fährt" never changed.* `standortZustand()` inferred the driving state from
|
||||
`TRIPS[0].status === "offen"` — two independent errors stacked. First, `"offen"` does not mean
|
||||
"driving"; it means **data incomplete** (distance still missing, see `fahrtabschluss_logik.py`) — a
|
||||
trip created by `_fahrt_beenden()` *when it ends*. A trip that never got its odometer, e.g. because
|
||||
no `KM_SENSOR` was mapped at the time, stays `"offen"` forever. Second, `TRIPS[0]` is the **oldest**
|
||||
trip, not the newest: `fahrten_veroeffentlichen()` passes `profil.fahrten_lesen()` through in file
|
||||
order, which is ascending. Together: the oldest ever-incomplete trip pinned the display to "fährt"
|
||||
permanently. The test instance had one open for 18 days.
|
||||
|
||||
Fixed at the source rather than by patching the index: the backend now publishes `zuendung` in
|
||||
`fahrzeugstatus` from the already-mapped `ZUENDUNG_SENSOR` — the same signal `fahrterkennung.py`
|
||||
treats as authoritative, so display and detection cannot disagree by construction. `null` (no sensor
|
||||
mapped) renders "Fahrzustand unbekannt" rather than claiming "steht".
|
||||
|
||||
The companion had the mirror-image bug: `liveZustandLesen()` read `status["zuendung"]` (never
|
||||
published) and `status["lat"]`/`status["lon"]` (backend publishes `standort_lat`/`standort_lon`), and
|
||||
`Fahrzeug` declared none of those fields — so `LiveFahrt` showed "Das Fahrzeug steht" permanently and
|
||||
never a position. Fields added to `Fahrzeugstatus`/`Fahrzeug` and the adapter, so the type now
|
||||
catches this class of bug — which its own header comment already warned about from a previous
|
||||
occurrence.
|
||||
|
||||
*"Standortzugriff verweigert" was misleading.* It sits directly under the vehicle name where the
|
||||
distance-to-car goes, but describes **this device's** browser geolocation — it read as if the car's
|
||||
location were unavailable. Now "GPS offline", as the owner asked. Permission denial keeps its own
|
||||
string ("GPS-Freigabe fehlt"): it is the one case with a different remedy, and "GPS offline" would
|
||||
send the user looking at signal instead of at a permission toggle.
|
||||
|
||||
**Also fixed this round (panel-only):** the "Teilen" pill on the location sheet was invisible in
|
||||
day mode — `.standort-pille` used `background: var(--tile)` while the sheet under it uses
|
||||
`var(--tile-deckend)`, and the iOS overlay sets *both* to `#FFFFFF` in day mode. Measured contrast
|
||||
|
||||
Reference in New Issue
Block a user