Files
audi-app/VERSIONIERUNG.md
T
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

9.2 KiB

Versionierung — eine Zahl, drei Verbraucher

Kurzfassung für den Alltag: Wenn du etwas änderst, erhöhe version in custom_components/audi_dashboard/manifest.json. Alles Weitere ergibt sich daraus von selbst.


Warum es diese Zahl braucht

Das Panel und die iOS-App werden völlig unterschiedlich ausgeliefert:

  • Panel: gehört zur Integration und wird mit ihr zusammen installiert. Es kann gar nicht hinterherhinken — ein Update der Integration ist automatisch ein Update des Panels.
  • iOS-App: eine Capacitor-Hülle mit fest gebündelten Dateien. Sie bleibt auf dem Stand, der beim Signieren in Xcode eingebaut wurde — unbegrenzt.

Die Paritätsregel in AGENTS.md sichert, dass beide Codebasen in derselben Sitzung geändert werden. Über das, was läuft, sagte lange nichts etwas: die iOS-App konnte wochenlang zurückliegen, ohne dass es irgendwo sichtbar wurde.

Die Versionszahl schließt genau diese Lücke — nicht, indem sie die Abweichung verhindert (das kann keine Zahl), sondern indem sie sie sichtbar macht.

Wo sie steht

In custom_components/audi_dashboard/manifest.json, Feld version. Format 2026.8.23.2 (Datum + laufende Nummer).

Genau dort und nirgends sonst. Home Assistant verlangt das Feld für jede benutzerdefinierte Integration — eine zweite Quelle für dieselbe Angabe wäre eine zu viel. Sie hätten irgendwann auseinandergelegen, und dann wäre der Vergleich still falsch geworden statt laut.

HACS würde dieses Feld ebenfalls lesen und danach entscheiden, ob ein Update bereitliegt — spielt hier aber keine Rolle: HACS kann laut eigener Dokumentation grundsätzlich nicht mit privaten GitHub-Repositories arbeiten, und das Repository ist privat (lizenzierte Audi-Assets). Das Manifest-Feld bleibt trotzdem die einzig richtige Stelle - Home Assistant selbst braucht es unabhängig von HACS.

Bis 2026-08-23 gab es dafür eine eigene Datei VERSION in der Repo-Wurzel, daneben eine Unix-Sekundenzahl in www/audi-dashboard-version.json als Cache-Brecher. Beide sind mit dem Umbau zur Integration entfallen: die Version steht jetzt im Manifest, und die Oberflächen-Dateien tragen sie als ?v=… in ihrer URL — die Integration hängt sie beim Anmelden des Panels an. Ein Cache-Brecher, der sich nur bei echten Änderungen ändert, ist der bessere: er lädt nichts unnötig neu.

Wie sie durchs System läuft

manifest.json  { "version": "2026.8.23.2" }
   │
   ├─► Home Assistant führt sie als Version der Integration
   │
   ├─► die Integration veröffentlicht sie als
   │      sensor.audi_dashboard_app_version
   │        └─► die iOS-App vergleicht sie mit ihrer eigenen, einkompilierten
   │            Zahl und zeigt bei Abweichung einen Hinweis in der
   │            Hinweisleiste
   │
   ├─► sie hängt als ?v=… an der Panel-URL
   │      └─► Browser laden Panel, CSS und Badges nur dann neu, wenn sich
   │          wirklich etwas geändert hat
   │
   └─► companion-app: vite liest das Manifest beim Bauen und setzt sie als
       __APP_VERSION__ ein (bricht ab, wenn das Feld fehlt)

Der Vergleich läuft in eine Richtung: die Integration sagt, welcher Stand installiert ist; die App sagt, welchen sie hat. Stimmen sie nicht überein, ist die App zu alt (oder, seltener, die Integration).

Verglichen wird der Reihe nach, nicht nur auf Gleichheit — geändert am 04.09.2026. Bis dahin galt hier ausdrücklich das Gegenteil (die Version sei eine Kennung und keine Zahl). Der Anlass war ein echter Fall: nach einem Xcode-Lauf lief die App auf 2026.9.4.17, die Instanz stand noch auf 2026.9.4.5 — und die App bot an, sich auf .5 zu erneuern. Ein Rückschritt um zwölf Fassungen, den eine Gleichheitsprüfung gar nicht sehen kann. Vorgabe des Eigentümers: erkannt wird eine neuere Fassung und keine andere.

Der damalige Einwand ist nicht verworfen, sondern eingebaut: sortiert wird nur, was dem Format JJJJ.M.T.N entspricht, und zwar Stelle für Stelle als Zahl — zeichenweise stünde .17 vor .5. Passt eine Seite nicht ins Schema, gilt sie als nicht sortierbar; dann meldet die App eine Abweichung ohne Richtung und bietet nichts an, statt eine Reihenfolge zu erfinden. Fehlt eine der beiden Seiten ganz, wird gar nicht verglichen und nichts gemeldet — ein älteres Backend oder ein Start ohne Netz darf keinen Fehlalarm auslösen.

Daraus folgen vier Verhaltensweisen, entschieden in src/daten/appVersion.ts:

Lage Streifen Startblatt Update-Knopf
App älter als der Server bitte aktualisieren ja ja
App neuer (frisch signiert) still nein nein
verschieden, nicht sortierbar neutraler Hinweis nein nur bei Serverübereinstimmung
gleich / nicht vergleichbar still nein nein

Was du tun musst

Bei einer Änderung an der Oberfläche oder am Backend:

  1. version in manifest.json erhöhen — bei mehreren Änderungen am selben Tag die laufende Nummer: 2026.8.23.22026.8.23.3, am nächsten Tag 2026.8.24.1.
  2. OTA-Bündel neu bauen: npm run ota (in companion-app/). Baut die Oberfläche und packt sie mit der neuen Versionsnummer — siehe OTA-Updates unten. Ohne diesen Schritt trägt frontend/app/bundle.json noch die alte Nummer: die Hinweisleiste sagt dann „du bist veraltet" (liest die Version live), die Update-Seite sagt „du bist aktuell" (vergleicht gegen das eingefrorene, jetzt falsche Bündel) — ein Widerspruch, aus dem es ohne diesen Schritt keinen Ausweg gibt. install.ps1 prüft das seit 2026-08-24 selbst und warnt, falls vergessen — aber besser, es passiert gar nicht erst.
  3. Integration ausliefern: nach git push entweder in der App selbst unter Einstellungen → Integration-Update auf „Auf Update prüfen" → „Update installieren" (seit 2026-08-24, ersetzt install.ps1 als laufenden Update-Weg — Windows Smart App Control blockiert dessen Ausführung zuverlässig), danach Home Assistant neu starten; oder weiterhin homeassistant\installationspaket\Installieren.cmd von Hand (HACS scheidet aus — siehe Kasten oben, das Repository ist privat).
  4. iOS-App für Xcode neu bauen (npm run build), damit eine über App Store Connect / Sideload signierte Fassung dieselbe Zahl einkompiliert bekommt. Für alle, die die App schon installiert haben, erledigt Schritt 2 das automatisch über OTA — dieser Schritt ist nur für die nächste Neuinstallation oder Signatur-Erneuerung nötig.

Vergisst du Schritt 4, ist das kein stiller Fehler mehr: die App meldet selbst, dass sie älter ist als der Server. Vergisst du Schritt 2, bleibt es leider still — deshalb die Prüfung in install.ps1.

Bei einer reinen Neuinstallation ohne Codeänderung: nichts tun.

OTA-Updates (seit 2026-08-24)

@capgo/capacitor-updater ist eingebaut. Für reine Oberflächen-Änderungen (JavaScript, CSS, Bilder) entfällt Schritt 3 oben — die App lädt sich den neuen Stand selbst, auf einen Tastendruck des Nutzers in Einstellungen → App-Update. Xcode wird nur noch für echte native Änderungen gebraucht (neue Plugins, Berechtigungen, capacitor.config.ts).

Woher das Bündel kommt. companion-app/scripts/ota-paket.ps1 packt dist/ (ohne Sourcemaps) in custom_components/audi_dashboard/frontend/app/bundle.zip, daneben bundle.json mit Version, SHA-256 und Größe. Weil das im Integrationsordner liegt, reist es bei jeder Auslieferung automatisch mit — kein zweiter Weg, den man vergessen könnte. Ausgeliefert wird es unter /audi_dashboard_static/app/bundle.zip, gemeldet über dasselbe Feld, das schon den Versionsvergleich trägt: sensor.audi_dashboard_app_version, Attribut daten.buendel.

Was die App damit macht (companion-app/src/daten/ota.ts): erscheint ein Bündel, dessen Version von der eigenen abweicht, zeigt Einstellungen → App-Update einen Knopf. Ein Tastendruck lädt die Zip, prüft sie clientseitig gegen die mitgelieferte SHA-256 (das Plugin selbst, nicht diese App) und tauscht die Oberfläche aus — die App startet dabei neu.

Die Rückfallebene. notifyAppReady() läuft in App.tsx, sobald React tatsächlich gerendert hat. Bleibt diese Meldung aus, weil das neue Bündel die App zerlegt hat, rollt das Plugin nach der eingestellten Frist (appReadyTimeout in capacitor.config.ts) von selbst auf das vorherige Bündel zurück — ein kaputtes Update kann das Gerät deshalb nicht dauerhaft unbrauchbar machen.

Warum kein Selbstlauf. autoUpdate: false — die App lädt nur auf ausdrücklichen Tastendruck, nie im Hintergrund. Ein Update, das während einer Fahrteintragung ungefragt die Oberfläche austauscht, wäre die falsche Sorte Hilfsbereitschaft.

HACS spielt hier keine Rolle und wird es auch nicht. Das Update-Bündel reist mit jedem Weg, der die Integration ausliefert, automatisch mit — install.ps1 für die Erstinstallation ebenso wie das Selbst-Update seit 2026-08-24 (aktualisierung.py, ersetzt install.ps1 als laufenden Update-Weg, siehe Abschnitt J in AGENTS.md) — der Mechanismus hängt nicht daran, wie die Integration selbst auf die Instanz kommt, nur daran, dass das Bündel im Integrationsordner liegt.