Die .ipa war nicht installierbar, plus QR-Scanner (2026.9.4.8)

Der Erweiterung fehlte CFBundleVersion - eine Pflichtangabe, ohne die iOS
die Installation der ganzen App verweigert ("kann nicht installiert werden").
Verursacht von der Versionsangleichung vom Vortag: das Skript reichte den
Platzhalter $(CURRENT_PROJECT_VERSION) statt einer Zahl weiter, und das per
Skript angelegte Ziel der Erweiterung kennt diese Build-Einstellung nicht.
An den entpackten .ipa nachgemessen, 2026.9.3.3 gegen 2026.9.4.5.

ios-signieren.sh reicht nur noch echte Zahlen weiter, das Gegenlesen der
fertigen .ipa bricht ab statt zu warnen, und abgelegt wird erst danach -
eine Datei, die die Pruefung nicht besteht, darf nicht in auslieferung/
landen.

Dazu:
- Kopfmarke statt Firmierung des Betreibers in der Tankstellen-Zeile
- dritter Suchbegriff: der Name ohne fuehrende Marke. Gemessen: Nominatim
  findet "Shell, Pascalstr. 8, Ingolstadt" nicht, "Pascalstr. 8, Ingolstadt"
  schon - daran blieb die Belegkarte der realen Instanz leer
- der Tankstellen-Verweis zeigt die Tankstelle, statt eine Route zu rechnen
- Cache-Brecher auf .ipa, Symbolen und manifest.plist
- QR-Scanner fuer den Zugangs-Token bei der Ersteinrichtung (jsQR statt
  GoogleMLKit, damit der Xcode-Lauf kein pod install riskiert)

214/214 Tests, Panel als Modul geparst, live gegen audi_ha_test geprueft.
This commit is contained in:
2026-09-04 11:51:44 +02:00
parent d819f2e285
commit 63a45d4ee2
20 changed files with 788 additions and 56 deletions
+218 -3
View File
@@ -2,8 +2,12 @@
**Last updated: 2026-09-04** (Tankstelle zweizeilig und antippbar, die Belegkarte sagt bei
fehlender Position die Wahrheit statt einen toten Knopf zu zeigen, der Regler bekommt einen
Speichern-Knopf, und die Share-Erweiterung trug eine andere Versionsnummer als die App - `2026.9.4.5`,
Abschnitt CC. Davor: Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt:
Speichern-Knopf, und die Share-Erweiterung trug eine andere Versionsnummer als die App;
dazu die Kopfmarke statt der Firmierung des Betreibers und ein
Cache-Brecher auf der .ipa-Adresse; die .ipa liess sich zuletzt nicht mehr
installieren - der Erweiterung fehlte die CFBundleVersion; dazu der QR-Scanner
fuer den Zugangs-Token, `2026.9.4.8`,
Abschnitte CC bis CE. Davor: Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt:
das Fahrtfenster stand in Geraetezeit, der Verlauf war nach Ankunftszeit sortiert. Dazu die neun
Paritaetsbefunde und die verunreinigte Batteriehistorie, `2026.9.4.1`, Abschnitt CB. Davor: Der Regler stellt den Schlaf-Timeout des Dongles, flespi lesend und
schreibend, die eigene Warteschlange wieder entfallen, `2026.9.3.10`, Abschnitt BZ. Davor: Zugaenge: die Dienste-Token aus App und Panel bedienbar, der Wert kommt nie
@@ -475,7 +479,8 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
- [x] Authenticated smoke tests of the data layer (reads, service calls, queue round-trip)
against `audi_ha_test` with a real token
- [x] Offline UX per design brief (offline marker, visible pending queue)
- [ ] **QR-Code-Scanner fürs Token-Onboarding (angefragt 2026-08-28, noch nicht geplant/gebaut)**
- [x] **QR-Code-Scanner fürs Token-Onboarding — gebaut am 2026-09-04, siehe Abschnitt CE.**
Historie der Anforderung:
2026-08-11 bewusst weggelassen ("nur wenn es einfach bleibt"), der Owner will das jetzt doch als
Alternative zum manuellen Einfügen.
@@ -9629,3 +9634,213 @@ selbst stehen.
zur Testinstanz. Das ist der Weg, die App live zu pruefen, ohne an CORS zu
scheitern: `cors_allowed_origins` in der `configuration.yaml` blieb wirkungslos
(Preflight weiterhin 403), der eingetragene Block ist deshalb wieder entfernt.
### Nachtrag (2026.9.4.6): die Kopfmarke, nicht die Firmierung
Praezisierung des Eigentuemers zur Zeile oben: dort gehoert die **Marke** hin
("Shell, Aral, OMV ..."), nicht der Betreiber. `A. Zrenner GmbH` ist die
Firmierung dessen, der die Tankstelle betreibt - nach aussen sichtbar ist die
Kopfmarke, und nur die sagt dem Leser etwas.
Neu `markeErkennen()` in beiden Codebasen: sucht die Kopfmarke im gespeicherten
`station_name` gegen eine Liste, die **wortgleich** mit `_MARKEN` im
`shell_beleg_parser.py` ist. Der Parser erkennt die Marke beim Einlesen eines
Belegs, diese Liste erkennt sie nachtraeglich in dem, was gespeichert ist -
laufen die beiden auseinander, zeigt die Oberflaeche eine andere Marke als der
Beleg. Gesucht wird mit Wortgrenzen, damit "Star" nicht in "Starnberg" trifft.
Damit steht oben `<Marke>, <Strasse>` und unten `<PLZ> <Ort>`; ohne erkennbare
Marke nur die Strasse - der Betreibername tritt **nicht** an ihre Stelle. Traegt
der Beleg gar keine Anschrift, bleibt der gespeicherte Name unveraendert stehen
("Shell, SÖLDEN"), sonst hiesse eine bekannte Tankstelle ploetzlich "unbekannt".
Gegen die 16 echten Datensaetze der Testinstanz nachgerechnet, mit den Funktionen
des Panels selbst (aus dem Buendel geschnitten und in Node ausgefuehrt, nicht
nachgebaut): drei Betreibernamen fallen korrekt weg (`Hermann Mogler
Mineraloelg. GmbH`, `TC Sengül GmbH`, `A. Zrenner GmbH`), `Shell, SÖLDEN` und
`Testtankstelle` bleiben stehen, leer ergibt "unbekannt". Die App deckt dasselbe
mit 24 Faellen in `src/daten/tankstelle.test.ts` ab.
### Nachtrag (2026.9.4.6): die .ipa-Adresse bekommt einen Cache-Brecher
Gemeldet: die alte App geloescht, neu installiert - "Wolke mit Pfeil" und "kann
nicht installiert werden, versuche es spaeter erneut".
**Das ausgelieferte Artefakt ist in Ordnung**, geprueft statt vermutet: die von
Gitea gelieferte `App.ipa` ist ein gueltiges Zip, App **und** Erweiterung tragen
`2026.9.4.5` (die Versionsangleichung aus Abschnitt CC ist also drin), die
`manifest.plist` passt dazu (`app.datametric360`, `bundle-version 2026.9.4.5`),
beide eingebetteten Profile fuehren beide Geraete-UDIDs und laufen bis
2027-08-29, beide Symbol-Adressen antworten mit HTTP 200 als `image/png`.
Was fehlte, ist die Vorsorge, die dieses Projekt an jeder anderen ausgelieferten
Datei laengst trifft: `ios-luftweg.sh` schrieb `<string>$BASIS/App.ipa</string>`
**ohne** Versionsanhang. Die Adresse ist damit ueber jeden Bau hinweg
zeichengleich, und der Installations-Dienst von iOS haelt eine einmal geholte
Datei fuer schon bekannt - eine bekannte Ursache genau dieser beiden Symptome.
Jetzt tragen `.ipa`, beide Symbole und die `manifest.plist` selbst ein
`?v=<Version>`; die bereits ausgelieferten Dateien unter `auslieferung/` sind
von Hand nachgezogen (das Skript laeuft nur auf dem Mac, es braucht
`PlistBuddy`).
**Ebenfalls geklaert, weil es zusammen gemeldet wurde:** das veraenderte
App-Symbol kommt nicht von hier, sondern vom Commit `d819f2e` ("iOS-App auf
2026.9.4.5"), der vom Mac gepusht wurde - er hat `auslieferung/` erneuert.
**Was bleibt, wenn der Cache-Brecher nicht reicht:** das stecken gebliebene
Platzhalter-Symbol auf dem Startbildschirm loeschen (langes Tippen, "App
entfernen"), Geraet neu starten, dann den `itms-services://`-Link erneut in
Safari oeffnen. Und der `?v=` wirkt erst mit dem naechsten Aufruf des Links,
nicht rueckwirkend auf einen laufenden Versuch.
### Was in dieser Runde NICHT live geprueft werden konnte
Die Testinstanz zeigte in dieser Sitzung die Anmeldemaske, und ein Passwort
einzugeben ist in diesem Projekt ausgeschlossen - auch bei der Wegwerf-Instanz.
Beide Oberflaechen sind deshalb ueber Modul-Parse, `tsc --noEmit`, 206/206 Tests
und die oben beschriebene Rechnung gegen die echten Datensaetze belegt, **nicht**
ueber einen Screenshot. Die Messungen zur Zeilenhoehe (82px) und zur
SmartDeal-Zeile (26,4px) stammen aus dem Teil der Sitzung davor und sind von der
Markenaenderung nicht beruehrt: sie macht die erste Zeile kuerzer, nicht laenger.
## CD. Die .ipa liess sich nicht mehr installieren - eine fehlende Pflichtangabe (2026.9.4.7)
Gemeldet: „bis zum letzten Xcode-Update hatte ich das richtige Logo, jetzt steht
ein anderes auf dem iPhone und die App ist nicht mehr installierbar."
**Beides ist ein Befund, nicht zwei.** Die letzte funktionierende `.ipa`
(`2026.9.3.3`) aus der Historie geholt und gegen die aktuelle gestellt - beide
entpackt, die Info.plists mit `plistlib` gelesen (im Testcontainer, hier gibt es
kein `PlistBuddy`):
| | `2026.9.3.3` (lief) | `2026.9.4.5` (lehnt ab) |
|---|---|---|
| App `CFBundleVersion` | `1` | `1` |
| Erweiterung `CFBundleVersion` | `1` | **fehlt ganz** |
Alles andere ist in beiden gleich und in Ordnung: Bundle-IDs, Profile mit beiden
Geraete-UDIDs bis 2027-08-29, `get-task-allow: false`, App-Gruppe auf beiden
Zielen, `MinimumOSVersion`.
`CFBundleVersion` ist eine Pflichtangabe. Fehlt sie in **einem** der beiden
Buendel, verweigert iOS die Installation der **ganzen** App - mit genau der
nichtssagenden Meldung "kann nicht installiert werden, versuche es spaeter
erneut".
**Verursacht hat es die Versionsangleichung aus Abschnitt CC**, also der Versuch,
einen anderen Fehler zu beheben. Der Schritt liest die Build-Nummer aus der
Info.plist der App - dort steht aber keine Zahl, sondern der Platzhalter einer
Build-Einstellung (`$(CURRENT_PROJECT_VERSION)`, aus dem Capacitor-Geruest).
Fuers App-Ziel loest Xcode ihn zu `1` auf; das per Skript angelegte Ziel der
Erweiterung kennt die Einstellung nicht, dort wurde daraus **leer**, und ein
leerer Wert faellt beim Schreiben der Plist ganz heraus.
Zwei Aenderungen in `ios-signieren.sh`: die Build-Nummer wird nur noch
weitergereicht, wenn sie wirklich eine Zahl ist (sonst `1`), und das
Gegenlesen der fertigen `.ipa` **bricht ab**, wenn einem der beiden Buendel eine
brauchbare `CFBundleVersion` fehlt. Die bisherige Pruefung verglich nur
`CFBundleShortVersionString` - genau daran ist es vorbeigelaufen.
**Das „andere Logo" ist der Platzhalter der haengenden Installation.** iOS zeigt
waehrend des Ladens das `display-image` aus der `manifest.plist` auf dem
Startbildschirm, und das ist `companion-app/public/symbol-192.png` vom 13.08. -
die alte "DM"-Kachel, nicht das App-Symbol vom 31.08. (DataMetric / Auto / 360).
Weil die Installation nie fertig wird, bleibt der Platzhalter stehen. Beide
Dateien nebeneinander angesehen, sie sind nachweislich verschieden.
**Offen und bewusst nicht angefasst:** die beiden Symbole anzugleichen, damit
schon der Platzhalter richtig aussieht - dafuer muessten aus
`native/logo/AppIcon-hell.svg` neue 192er- und 512er-PNG gerastert werden.
### Zusaetzlich: die Adressen der Auslieferung tragen jetzt die Version
`ios-luftweg.sh` schrieb `<string>$BASIS/App.ipa</string>` ohne Anhaengsel. Die
Adresse ist damit ueber jeden Bau hinweg zeichengleich, und der
Installations-Dienst von iOS haelt eine einmal geholte Datei fuer schon bekannt.
Jetzt tragen `.ipa`, beide Symbole und die `manifest.plist` selbst ein
`?v=<Version>`; die bereits ausgelieferten Dateien unter `auslieferung/` sind von
Hand nachgezogen, weil das Skript nur auf dem Mac laeuft.
### Der dritte Suchbegriff: der Name ohne die fuehrende Marke
Gemeldet: dieselbe Tankstelle loest in Docker und auf der realen Instanz
verschieden auf, und auf der realen Instanz bleibt die Belegkarte leer.
Die Ursache steht in den Daten, nicht im Code: seit dem 16.08.2026 setzt der
Belegparser den Stationsnamen als „Marke, Strasse, Ort" zusammen - **ohne
Postleitzahl**. Am 04.09.2026 gegen Nominatim gemessen:
| Suchtext | Antwort |
|---|---|
| `Shell, Pascalstr. 8, Ingolstadt` | leer |
| `Pascalstr. 8, Ingolstadt` | 48.7869/11.3977 |
| `Shell, Pascalstr. 8, 85057 Ingolstadt` | 48.7869/11.3977 |
Die Marke am Anfang kippt die Suche, sobald die Postleitzahl fehlt.
`tankstelleKandidaten()` haengt deshalb einen dritten Versuch an: den Namen ohne
die fuehrende Marke. Er laeuft nur, wenn die ersten beiden nichts gefunden
haben.
**Live nachgewiesen** an genau diesem Beleg in der Testinstanz (deren Datensatz
vom 29.07.2026 dieselbe Form hat): im Zwischenspeicher steht
`"Shell, Pascalstr. 8, Ingolstadt" => null` neben
`"Pascalstr. 8, Ingolstadt" => {lat: 48.7869101, lon: 11.39767}`, und die Karte
zeigt eine Nadel.
### Der Verweis zeigt die Tankstelle, statt eine Route zu rechnen
Vorgabe des Eigentuemers. `kartendienstUrl()` hat dafuer einen Modus bekommen:
`"route"` bleibt die Vorgabe (Standort-Blatt, Autohaus), der Tankstellen-Verweis
im Einzelbeleg nimmt `"zeigen"` - Apple `?q=` bzw. `?ll=&q=`, Google
`/maps/search/`. Live geprueft: der Verweis lautet jetzt
`google.com/maps/search/?api=1&query=...` statt `/maps/dir/`.
Verifiziert: Panel als Modul geparst, `tsc --noEmit` sauber, **210/210** Tests
(vier neue: der markenlose Kandidat, der Fall „Marke steht nicht vorn", und die
beiden Kartendienste im Modus „zeigen"), `audi_ha_test` auf `2026.9.4.7` sauber
gestartet, und beide Belege live im Panel nachgesehen (zweizeilig „Nuernberger
Str.74" / „91522 Ansbach" ohne den Betreibernamen, Nadel auf der Karte).
## CE. QR-Scanner fuer den Zugangs-Token (2026.9.4.8)
Vorgabe des Eigentuemers: ein Knopf unter „Zugangs-Token", und **nur** bei der
Ersteinrichtung - danach gibt es nichts mehr abzutippen. Damit ist der seit dem
28.08.2026 offene Punkt aus Abschnitt A erledigt.
**Kein natives Scanner-Plugin, und das ist die eigentliche Entscheidung.**
`@capacitor-mlkit/barcode-scanning` (8.1.1, passt zu Capacitor 8) waere der
bequemere Weg, zieht aber GoogleMLKit ueber CocoaPods nach - mehrere Dutzend
Megabyte und ein `pod install`, das auf dem Mac gelingen muss. Genau der
Xcode-Lauf, der die App nach Abschnitt CD ueberhaupt erst wieder installierbar
macht, darf daran nicht scheitern. Gewaehlt: **jsQR** (1.4.0, 30 KB, MIT), rein
in JavaScript, dazu `getUserMedia` in der WebView. WKWebView liefert die Kamera
seit iOS 14.3 aus, und die App laeuft unter `capacitor://localhost`, also in
einem sicheren Kontext.
Nativ bleibt genau ein Punkt: **`NSCameraUsageDescription`** in der Info.plist,
gesetzt von `ios-signieren.sh` wie schon der Standort- und die beiden
Kalender-Schluessel (`ios/` ist gitignored, ein in Xcode geklickter Eintrag
waere beim naechsten `cap add ios` wieder weg). Ohne den Schluessel beendet iOS
die App wortlos in dem Moment, in dem sie die Kamera anfordert.
**Was der Code enthaelt, war die zweite offene Frage** (sie stand seit dem
01.09.2026 in Abschnitt A): Home Assistants eigener QR traegt **nur den Token**,
nicht die Adresse der Instanz. `qrFundDeuten()` entscheidet deshalb: eine
http-Adresse wandert in das Adressfeld, ein langer zusammenhaengender Text in
das Tokenfeld, alles andere wird **nicht** uebernommen (ein WLAN-Code oder eine
Visitenkarte sollen kein Feld fuellen). Vier Faelle in
`src/screens/QrScanner.test.ts`.
Der Knopf erscheint nur, wenn das Geraet der App ueberhaupt eine Kamera
durchreicht - am Rechner waere er ein Knopf, der zwangslaeufig in eine
Fehlermeldung laeuft; dieselbe Ueberlegung wie beim Zwischenablage-Knopf des
Beleg-Dialogs (Abschnitt C, 2026-08-17). „Verbinden" ist jetzt `variant`
`primary`, der Scanner-Knopf bleibt die zweite Rolle - die Rangfolge, die Apples
Richtlinien fuer genau diese Paarung vorsehen.
Verifiziert: `tsc --noEmit` sauber, **214/214** Tests, `vite build` sauber,
`audi_ha_test` auf `2026.9.4.8` sauber gestartet, und die Ersteinrichtung live
unter `http://localhost:18123/local/dmapp/index.html` angesehen: der Knopf steht
unter „Zugangs-Token", das Blatt oeffnet mit Videofeld und Zielrahmen, und ohne
Kamerafreigabe steht dort die erklaerende Meldung statt einer leeren Flaeche.
**Nicht geprueft, weil es in dieser Umgebung nicht geht:** ein echter Scan - die
Browser-Vorschau gibt keine Kamera heraus. Das zeigt sich erst auf dem Geraet.