diff --git a/AGENTS.md b/AGENTS.md
index a435313..2fb2cd5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -8690,3 +8690,40 @@ WebP sichern und noch einmal versuchen." — und auf der Platte entsteht **nicht
Verifiziert: 5/5 Backend-Tests (und ohne die Sperre nachweislich rot), `tsc --noEmit` sauber,
173/173 App-Tests, Panel als Modul geparst, `audi_ha_test` auf `2026.9.3.3` sauber gestartet.
+
+---
+
+## AL. Popup beim Öffnen, wenn eine neuere Fassung bereitliegt (2026-09-03)
+
+Wunsch des Besitzers: die App soll beim Start sagen, dass es eine neue Fassung gibt — so wie andere
+Apps es tun, nur ohne App Store. Es gab bisher nur den schmalen Streifen der `Hinweisleiste`
+("Diese App ist älter als der Server"), der dauerhaft mitläuft und deshalb leicht übersehen wird.
+
+Neu: `companion-app/src/screens/Versionshinweis.tsx`, ein `ActionSheet`, eingehängt in `App.tsx`
+neben der bestehenden Leiste. Die beiden ersetzen einander **nicht** — ein neuer Stand ist ein
+Ereignis und gehört einmal nach vorn, danach übernimmt wieder der Streifen für den fortbestehenden
+Zustand.
+
+**Zwei Fälle, zwei Texte, weil zwei verschiedene Dinge zu tun sind.** Liegt ein passendes OTA-Bündel
+bereit (`buendelPasst()`), kann die App sich selbst erneuern — dann trägt das Blatt den Knopf, der
+genau das auslöst, über denselben `buendelAnwenden()`-Weg wie der Knopf in den Einstellungen. Fehlt
+eines, hat sich Natives geändert und es hilft nur neu aufspielen; dann sagt das Blatt genau das und
+bietet keinen Knopf an, der nichts bewirken könnte.
+
+**Nur nativ.** Im Browser und im HA-Panel lädt jeder Aufruf den aktuellen Stand — dort kann die App
+gar nicht veralten, dieselbe Begründung wie in `appVersion.ts`.
+
+**Einmal je Fassung, nicht bei jedem Start.** Weggetippt wird die gesehene Serverfassung in
+`localStorage` gemerkt; erst eine *andere* bringt das Blatt zurück. Ohne das wäre es binnen einer
+Woche unsichtbar geworden. Die Entscheidung steckt in der reinen Funktion `hinweisFaellig()` und ist
+mit sechs Fällen getestet (`Versionshinweis.test.ts`) — ein Fehler dort hat genau zwei Ausprägungen,
+beide schlecht: eine Meldung bei jedem Start, die niemand mehr liest, oder ein Update, von dem nie
+jemand erfährt.
+
+**Geerbte Ungenauigkeit, bewusst nicht angefasst:** der Vergleich ist reine Gleichheit (siehe
+`VERSIONIERUNG.md`), also meldet das Blatt auch dann "neue Version", wenn die App *neuer* ist als das
+Backend — etwa direkt nach einem Xcode-Bau, bevor die Integration nachgezogen wurde. Der
+Hinweisstreifen hat dieselbe Eigenschaft seit jeher. Sortierbar zu vergleichen wäre scheingenau; die
+Alternative wäre ein zweites Signal vom Backend, und das lohnt für diesen Randfall nicht.
+
+Verifiziert: `tsc --noEmit` sauber, 179/179 Tests (6 neue).
diff --git a/companion-app/src/App.tsx b/companion-app/src/App.tsx
index 3dacae3..58d635f 100644
--- a/companion-app/src/App.tsx
+++ b/companion-app/src/App.tsx
@@ -11,6 +11,7 @@ import type { Route, SeitenName } from "./navigation"
import { useTabBeschriftung, useTheme } from "./theme"
import { Einrichtung } from "./screens/Einrichtung"
import { Hinweisleiste } from "./screens/Hinweisleiste"
+import { Versionshinweis } from "./screens/Versionshinweis"
import { SeiteFuer } from "./screens/register"
import { zugangMerken } from "./screens/zugang"
@@ -141,6 +142,13 @@ function AngemeldeteApp({ beiAbmeldung }: { beiAbmeldung: () => void }) {
setzeTabBeschriftung={setzeTabBeschriftung}
beiAbmeldung={beiAbmeldung}
/>
+ {/* Erscheint von selbst, sobald der erste Datenabruf eine neuere
+ Fassung meldet - siehe screens/Versionshinweis.tsx. */}
+
)
}
diff --git a/companion-app/src/screens/Versionshinweis.test.ts b/companion-app/src/screens/Versionshinweis.test.ts
new file mode 100644
index 0000000..454b3eb
--- /dev/null
+++ b/companion-app/src/screens/Versionshinweis.test.ts
@@ -0,0 +1,38 @@
+import { describe, expect, it } from "vitest"
+
+import { hinweisFaellig } from "./Versionshinweis"
+
+/* hinweisFaellig() entscheidet, ob der Nutzer von einer neuen Fassung
+ erfährt. Ein Fehler hier hat genau zwei Ausprägungen, beide schlecht: eine
+ Meldung bei jedem Start, die niemand mehr liest, oder ein Update, von dem
+ nie jemand erfährt. */
+describe("hinweisFaellig", () => {
+ it("meldet, wenn App und Server auseinanderlaufen", () => {
+ expect(hinweisFaellig("abweichend", "2026.9.3.3", null)).toBe(true)
+ })
+
+ it("schweigt bei gleichem Stand", () => {
+ expect(hinweisFaellig("gleich", "2026.9.3.3", null)).toBe(false)
+ })
+
+ it("schweigt, solange kein Vergleich möglich ist", () => {
+ // "unbekannt" heißt offline oder altes Backend - kein Grund für eine
+ // Meldung, sonst erschiene sie bei jedem Start ohne Verbindung.
+ expect(hinweisFaellig("unbekannt", null, null)).toBe(false)
+ expect(hinweisFaellig("unbekannt", "2026.9.3.3", null)).toBe(false)
+ })
+
+ it("schweigt ohne Serverfassung, auch wenn abweichend gemeldet wird", () => {
+ expect(hinweisFaellig("abweichend", null, null)).toBe(false)
+ })
+
+ it("schweigt zur bereits weggetippten Fassung", () => {
+ expect(hinweisFaellig("abweichend", "2026.9.3.3", "2026.9.3.3")).toBe(false)
+ })
+
+ it("meldet erneut, sobald eine andere Fassung bereitliegt", () => {
+ // Der Kern der Merkfunktion: weggetippt gilt für genau eine Fassung,
+ // nicht für "Updates" im Allgemeinen.
+ expect(hinweisFaellig("abweichend", "2026.9.4.1", "2026.9.3.3")).toBe(true)
+ })
+})
diff --git a/companion-app/src/screens/Versionshinweis.tsx b/companion-app/src/screens/Versionshinweis.tsx
new file mode 100644
index 0000000..0f1b1ff
--- /dev/null
+++ b/companion-app/src/screens/Versionshinweis.tsx
@@ -0,0 +1,136 @@
+/**
+ * Meldung beim Öffnen, wenn eine neuere Fassung bereitliegt.
+ *
+ * Warum zusätzlich zur Hinweisleiste: die Leiste ist ein schmaler Streifen,
+ * der dauerhaft mitläuft — richtig für einen Zustand, der bestehen bleibt,
+ * aber leicht zu übersehen. Dass eine neue Fassung *neu* da ist, ist dagegen
+ * ein Ereignis und gehört einmal nach vorn. Danach übernimmt wieder die
+ * Leiste; die beiden ersetzen einander nicht.
+ *
+ * Nur in der nativen Hülle. Im Browser und im HA-Panel lädt jeder Aufruf den
+ * aktuellen Stand, dort kann die App gar nicht veralten (dieselbe Begründung
+ * wie in daten/appVersion.ts).
+ *
+ * ZWEI FÄLLE, ZWEI TEXTE
+ * ----------------------
+ * Liegt ein passendes OTA-Bündel bereit, kann die App sich selbst erneuern —
+ * dann steht hier der Knopf, der genau das tut. Fehlt eines, hat sich Natives
+ * geändert (Plugins, Berechtigungen, die Hülle selbst), und dagegen hilft nur
+ * neu aufspielen. Beides zu vermengen wäre die schlechtere Auskunft: ein
+ * "Jetzt aktualisieren", das nichts ändern kann, oder ein "bitte manuell",
+ * obwohl ein Knopf genügt hätte.
+ *
+ * EINMAL, NICHT BEI JEDEM START
+ * -----------------------------
+ * Weggetippt wird die gesehene Serverfassung gemerkt. Erst eine *andere*
+ * bringt die Meldung zurück — sonst wäre sie bei jedem Start dieselbe und
+ * damit binnen einer Woche unsichtbar.
+ */
+
+import { useState } from "react"
+
+import { ActionSheet } from "@audi-dash/ui"
+
+import { zugangLesen, type Buendelangabe } from "../api"
+import { eigeneVersion, type Versionsstand } from "../daten/appVersion"
+import { OtaFehler, buendelAnwenden, buendelPasst, otaMoeglich } from "../daten/ota"
+
+const SCHLUESSEL = "dm360.version.gesehen"
+
+/**
+ * Soll die Meldung erscheinen?
+ *
+ * Bewusst als reine Funktion: sie entscheidet, ob der Nutzer von einer neuen
+ * Fassung erfährt oder nicht, und das soll prüfbar sein, ohne die Oberfläche
+ * zu bauen.
+ */
+export function hinweisFaellig(
+ versionsstand: Versionsstand,
+ serverVersion: string | null,
+ gesehen: string | null,
+): boolean {
+ // "unbekannt" heißt: kein Vergleich möglich (offline, altes Backend). Das
+ // ist kein Grund für eine Meldung — genauso wenig wie "gleich".
+ if (versionsstand !== "abweichend") return false
+ if (!serverVersion) return false
+ return serverVersion !== gesehen
+}
+
+function gesehenLesen(): string | null {
+ try {
+ return localStorage.getItem(SCHLUESSEL)
+ } catch {
+ // Privater Modus oder gesperrter Speicher: dann eben jedes Mal fragen.
+ return null
+ }
+}
+
+function gesehenSchreiben(version: string): void {
+ try {
+ localStorage.setItem(SCHLUESSEL, version)
+ } catch {
+ /* siehe oben */
+ }
+}
+
+export function Versionshinweis({
+ versionsstand,
+ serverVersion,
+ otaBuendel,
+}: {
+ versionsstand: Versionsstand
+ serverVersion: string | null
+ otaBuendel: Buendelangabe | null
+}) {
+ const [weggetippt, setzeWeggetippt] = useState(false)
+ const [laeuft, setzeLaeuft] = useState(false)
+ const [fehler, setzeFehler] = useState(null)
+
+ const nativ = otaMoeglich()
+ const offen =
+ nativ && !weggetippt && hinweisFaellig(versionsstand, serverVersion, gesehenLesen())
+
+ const perOta = buendelPasst(otaBuendel, eigeneVersion())
+
+ const schliessen = () => {
+ if (laeuft) return
+ if (serverVersion) gesehenSchreiben(serverVersion)
+ setzeWeggetippt(true)
+ }
+
+ const jetztLaden = async () => {
+ if (!otaBuendel) return
+ setzeLaeuft(true)
+ setzeFehler(null)
+ try {
+ const zugang = await zugangLesen()
+ if (!zugang) throw new OtaFehler("Kein Zugang eingerichtet.")
+ // Kehrt im Erfolgsfall nicht zurück — set() lädt die App neu.
+ await buendelAnwenden(otaBuendel, zugang.basisUrl)
+ } catch (f) {
+ setzeFehler(f instanceof Error ? f.message : "Das Update konnte nicht installiert werden.")
+ setzeLaeuft(false)
+ }
+ }
+
+ return (
+ void jetztLaden(), disabled: laeuft }]
+ : []
+ }
+ cancelLabel={perOta ? "Später" : "Verstanden"}
+ >
+ {fehler ? {fehler}
: null}
+
+ )
+}