Tankstelle zweizeilig und antippbar, Belegkarte ohne toten Knopf (2026.9.4.5)

Sieben Meldungen des Eigentuemers, beide Codebasen.

Karte im Einzelbeleg: "Position laedt nicht" und "der Button funktioniert
nicht" waren dieselbe Stelle - ohne aufloesbaren Ort malte das Panel einen
beliebigen Ausschnitt und liess den Zentrieren-Knopf fuer immer disabled.
Beide zeigen jetzt die Auskunft statt einer Karte und unterscheiden dabei
"steht nicht im Beleg" von "findet niemand". Dazu ein echter Paritaetsmangel:
die App schlug nur station_address nach, das Panel faellt seit jeher auf den
Namen zurueck - von 16 Testbelegen tragen vier eine Anschrift.

Der Suchbegriff darf Name und Anschrift NICHT zusammensetzen: gemessen findet
Nominatim "Pascalstr. 8, 85057 Ingolstadt", mit dem Betreibernamen davor
nichts. Jetzt zwei Kandidaten nacheinander (Anschrift, dann Name); fuer den
Kartendienst dagegen beides zusammen, weil Apple und Google Betriebsnamen
kennen.

Tankstelle steht ueber die volle Kachelbreite, zweizeilig (Tankstelle und
Strasse / Ort) und fuehrt beim Tippen in den Standard-Kartendienst - Apple
Karten auf Apple-Geraeten, sonst Google Maps. Dieselbe Wahl jetzt auch fuer
Fahrzeugstandort und Autohaus.

SmartDeal-Ersparnis brach um, weil die dt-Spalte 150,00px breit war und ihr
Inhalt 150,20px - zwei Zehntel Pixel, Zeile dadurch 44,8 statt 26,4px hoch.

Der Regler "Fahrt beenden" schreibt nicht mehr beim Loslassen: derselbe Wert
stellt den Schlaf-Timeout des Dongles, das soll bewusst ausgeloest werden.
Speichern/Verwerfen erscheinen nur bei echter Aenderung.

Share-Erweiterung: an der ausgelieferten .ipa gemessen trug sie 1.0, die App
2026.9.3.3 - iOS registriert eine Erweiterung mit abweichender Versionsnummer
nicht, sie fehlt dann im Teilen-Blatt. ios-signieren.sh schreibt die Version
jetzt in beide Ziele und liest die fertige .ipa gegen. Zwischenablage: dritter
Weg ueber UIPasteboard.itemProviders, und ein fehlender nativer Teil wird als
solcher gemeldet statt als "Kein Zugriff".

Am Geraet gesetzt: 1003 Network Ping Timeout 0 -> 60 s, 1004 ACK Type
TCP/IP -> AVL. Beide liegen bei flespi als pending bereit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 10:10:55 +02:00
parent 7f386d19ea
commit 43f25e2e0d
17 changed files with 1000 additions and 60 deletions
+168 -1
View File
@@ -1,6 +1,9 @@
# AGENTS.md — Project state, review findings, open items, and working rules
**Last updated: 2026-09-04** (Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt:
**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:
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
@@ -9462,3 +9465,167 @@ resting voltage, the SOC, the SOH trend and the fallback shown in "Mein Audi / Z
the same mechanism as `wert_in_naehe()` with its 300 s window for the outside temperature. Without a
vehicle, nothing gets recorded. The 13 existing entries are the owner's data; removal is via the
swipe gesture in the measurement list.
## CC. Tankstelle zweizeilig und antippbar, die Belegkarte sagt die Wahrheit, der Regler bekommt einen Knopf (2026.9.4.5)
Sieben Meldungen des Eigentuemers, dazu die beiden Dongle-Parameter aus dem Vortag.
### Der Dongle steht jetzt auf 1003 = 60 und 1004 = AVL
Am Geraet nachgesehen statt angenommen: `network_ping.timeout` stand auf **0** und
`ack_type.mode` auf **0 (TCP/IP)** — die Werte, die am 03.09. die tote TCP-Sitzung
unbemerkt liessen (Abschnitt CB). Beide ueber `PUT /gw/devices/{id}/settings/{name}`
mit `{"properties": …, "address": "connection"}` geschrieben, dieselbe Form wie
`schlaf_timeout_schreiben()`. flespi haelt sie als **`pending`** vor und stellt sie
zu, sobald sich das Geraet meldet; `current` steht bis dahin unveraendert auf 0/0.
`records_params` (1000 = 60 s, 1001 = 30 s, 1002 = Oldest) bleibt wie es war.
### "Einzelbeleg/Map laedt die Position nicht" und "der Button funktioniert nicht"
**Dieselbe Stelle, ein Befund.** Reproduziert an einem Beleg ohne aufloesbare
Anschrift: das Panel zeichnete dann einen beliebigen Kartenausschnitt und liess den
Zentrieren-Knopf fuer immer `disabled`; die App zeigte ihren Leerzustand — und
suchte gar nicht erst, weil `TankstellenKarte` ausschliesslich `station_address`
nachschlug. Von 16 Tankvorgaengen der Testinstanz tragen **vier** eine Anschrift;
das Panel faellt seit jeher auf den Namen zurueck (`f.station_address ||
f.station_name`), die App nicht.
Beide zeigen jetzt bei fehlender Position **keine Karte**, sondern die Auskunft —
und unterscheiden dabei "steht nicht im Beleg" von "findet niemand". Im Panel
entscheidet das `BELEG_KARTE`, ein Merker, den `initMap()` setzt, wenn der
Nachschlag erfolglos zurueckkommt; `go()` und das Speichern eines Tankvorgangs
raeumen ihn wieder ab, damit eine korrigierte Anschrift erneut gesucht wird.
**Der Suchbegriff war der zweite Fallstrick, und er hat mich einmal erwischt.**
Die erste Fassung setzte Name UND Anschrift zusammen, mit der Begruendung "eine
Hausnummer ohne Betrieb trifft in Gewerbegebieten das Nachbargebaeude". Live
gemessen ist das genau falsch herum: `"Pascalstr. 8, 85057 Ingolstadt"` findet
Nominatim, `"A. Zrenner GmbH, Pascalstr. 8, 85057 Ingolstadt"` **nicht** — der
Betreibername steht in keiner Karte. Sichtbar wurde es sofort als "Keine Position"
bei einem Beleg, dessen Anschrift sich vorher aufloesen liess.
Deshalb jetzt zwei getrennte Begriffe:
* `tankstelleKandidaten()`**erst die Anschrift, dann der Name**, nacheinander
versucht. Der Name allein ist wertvoll (`"Shell, SÖLDEN"` findet Nominatim), nur
eben nicht als Praefix.
* `tankstelleZiel()` — Name UND Anschrift, aber nur fuer den Kartendienst: Apple
Karten und Google Maps kennen Betriebsnamen und sind darin deutlich besser als
Nominatims Freitextsuche.
### Die Tankstelle steht ueber die volle Breite, in zwei Zeilen, und fuehrt in die Karten
Vorgabe des Eigentuemers: erste Zeile Tankstelle und Strasse, zweite Zeile der Ort,
Tippen oeffnet den Standard-Kartendienst. Vorher eine gewoehnliche Wertzeile mit
120px Wertespalte — "Hermann Mogler Mineraloelg. GmbH, Heidenheimerstr.6, 89551
Koenigsbronn" stand dort vier- bis fuenfzeilig.
Geteilt wird am **letzten** Komma (der Belegparser setzt `"<Strasse>, <PLZ Ort>"`
zusammen), damit ein Zusatz bei der Strasse bleibt; ohne Komma bleibt alles in der
ersten Zeile statt einen Ort zu erfinden. `kartendienstUrl()` waehlt nach Geraet:
`maps.apple.com` auf Apple-Geraeten, sonst `google.com/maps/dir`. Dieselbe Wahl
gilt jetzt auch fuer den Fahrzeugstandort und das Autohaus — drei Verweise, ein
Dienst.
**Zwei CSS-Fallen dabei, beide erst am laufenden Panel sichtbar:**
1. `.tankstellenzeile dt` (0,1,1) verliert gegen `.row dt` im **iOS-Overlay**, das
spaeter geladen wird — die Schriftgroesse blieb 16px. Die Regeln heissen deshalb
`.row.tankstellenzeile …` (0,2,1) und gewinnen unabhaengig von der Reihenfolge.
2. `.rows-fest dt { flex: 0 0 150px }` — in einer **Spalte** ist die Flex-Basis die
HOEHE. Der Titel "Tankstelle" war damit 150px hoch, die Zeile 214px. `flex: 0 0
auto` in derselben Regel hebt es auf. Gemessen 82px danach.
**Merkposten, zum dritten Mal in dieser Datei:** eine Regel, die im Overlay einen
Namensvetter hat, braucht den Wert des Overlays oder hoehere Spezifitaet — und eine
Flex-Angabe bedeutet in einer Spalte etwas anderes als in einer Zeile.
### "Wozu der Zeilenumbruch bei SmartDeal-Ersparnis?"
Kein Platzmangel, sondern ein Rundungsfehler, am Einzelbeleg bei 375px gemessen:
die dt-Spalte war **150,00px** breit, ihr eigener Inhalt **150,20px** — der Text
hinter dem Markenlogo rutschte in eine zweite Zeile, die Zeile wurde 44,8px statt
26,4px hoch. Zwei Zehntel Pixel. `.marke { white-space: nowrap }` in beiden
Codebasen, und die Wertespalte gibt in genau dieser Zeile ihre feste Mindestbreite
auf (`.rows-fest .row-marke dd { min-width: 0 }`); ohne das stuende sie um 10px
ueber. Live bestaetigt: 26,4px, eine Zeile.
### Der Regler schreibt nicht mehr beim Loslassen
Vorgabe des Eigentuemers, und sie ist richtig: derselbe Wert stellt den
Schlaf-Timeout des Dongles — eine Aenderung am Fahrzeug ueber zwei Netze, die
bewusst ausgeloest werden soll und nicht als Nebenwirkung einer Wischgeste.
Apples Richtlinien lassen einen Regler sonst sofort wirken; die Ausnahme ist genau
dieser Fall, und das Muster dafuer ist der bestaetigende Knopf. Er erscheint **nur**,
wenn der Regler wirklich woanders steht (ein dauerhaft sichtbarer, meist
wirkungsloser Knopf waere Beiwerk), daneben "Verwerfen" als schlichter Text —
dieselbe Anordnung wie in der Speicherleiste der App am Seitenende.
Panel: `PAUSENZEIT_ENTWURF` neben `CONFIG.pausenzeit`; der `input`-Zweig zieht nur
Fuellung und Anzeige nach (ein `render()` mitten in der Geste erzeugt den Regler neu
und der Daumen springt aus der Hand), der `change`-Zweig setzt den Entwurf und
zeichnet neu, geschrieben wird erst auf Knopfdruck. App: eigener
`pausenzeitEntwurf`-Zustand **neben** dem Seitenentwurf, und `speichern(zusatz)`
nimmt den Wert als Argument entgegen — `setState` wirkt erst beim naechsten
Durchlauf, der Seitenentwurf trueg im selben Rendern noch den alten.
Live in beiden geprueft (Regler bewegt, Knopf erscheint, "Verwerfen" stellt 15 Min.
wieder her). **"Speichern" bewusst NICHT gedrueckt** — das schriebe einen neuen
Schlaf-Timeout an den echten Dongle.
### Teilen-Erweiterung und Zwischenablage am iPhone
Die ausgelieferte `App.ipa` vom 03.09. wurde entpackt und geprueft, statt zu raten.
**Fast alles ist in Ordnung:** die `.appex` traegt ein echtes arm64-Programm
(110.320 Bytes), App und Erweiterung haben beide `group.app.datametric360` in ihren
Entitlements, das URL-Schema `datametric360` steht im Info.plist, und
`BelegZwischenablagePlugin` ist im App-Programm enthalten.
**Ein Befund, und er erklaert genau "die Erweiterung erscheint nicht":** die
Versionsnummern stimmen nicht ueberein — App `2026.9.3.3`, Erweiterung `1.0`.
Apples App Extension Programming Guide verlangt Gleichheit, und iOS registriert
eine Erweiterung mit abweichender Nummer nicht. `ios-signieren.sh` schrieb die
Version bis dahin nur in `ios/App/App/Info.plist`; `native/teilen/Info.plist`
traegt die 1.0 fest. Jetzt bekommen **beide** Ziele dieselbe
`CFBundleShortVersionString` und `CFBundleVersion`.
Dazu, aus der Lehre von Abschnitt AK (von fuenf Fehlern in der nativen Huelle haben
vier einen erfolgreichen Archivlauf erzeugt): das Skript liest die **fertige .ipa**
gegen — Programm in der `.appex` vorhanden, Versionen gleich, App-Gruppe auf beiden
Zielen — und warnt, statt es dem naechsten Fehlerbericht zu ueberlassen.
Fuer die Zwischenablage kommt ein dritter Weg dazu: `UIPasteboard.itemProviders`.
Manche Apps legen eine Datei nur als **Zusage** ab — dann gibt
`data(forPasteboardType:)` nil zurueck und `urls` bleibt leer, nur der Anbieter
kennt sie. Und `zwischenablage.ts` fragt jetzt `Capacitor.isPluginAvailable()`:
fehlt der native Teil (eine App, die nur ueber OTA gewachsen ist), sagt die Meldung
das, statt "Kein Zugriff auf die Zwischenablage" — die schickt in die falsche
Richtung.
**Was das nicht kann:** ohne Mac laesst sich keine dieser Aenderungen ausprobieren.
Der native Teil kommt ausschliesslich ueber einen Xcode-Lauf aufs Geraet, nie ueber
das OTA-Buendel.
**Bewusst nicht angefasst:** die `NSExtensionActivationRule`. Sie ist heute
grosszuegig (jede Datei, jeder Anhang) und laesst die App im Teilen-Blatt
erscheinen; ein strenges Praedikat waere sauberer, koennte sie aber ungeprueft ganz
verschwinden lassen.
### Fahrt `t-66c4209276e7` bleibt bei 3,0 km
Ausdrueckliche Ansage des Eigentuemers ("Fahrt nicht neu rechnen"). Das Screening
fuellt grundsaetzlich nur leere Felder — der gespeicherte Wert bleibt damit von
selbst stehen.
### Testinstanz: was von dieser Sitzung dort liegenbleibt
* **`f-8852746248fa` traegt jetzt `discount: 12.9`** — von Hand gesetzt, um die
SmartDeal-Zeile ueberhaupt sichtbar zu machen (alle Testbelege hatten `null`).
Sinnvoll fuer die naechste Sichtpruefung, aber es ist eine Aenderung an den Daten
des Containers.
* **`/config/www/dmapp/`** haelt das gebaute Webbuendel; unter
`http://localhost:18123/local/dmapp/index.html` laeuft die App **gleichursprunglich**
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.