# 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.2` → `2026.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](#ota-updates-seit-2026-08-24) 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.