Rollende Zeitraeume, zwei Gesten, CI-Symbole und das Screening faellt nach (2026.9.2.8-.12)

Gemeldet vom Eigentuemer an der realen Instanz, in einem Zug abgearbeitet.

STATISTIK. "Monat kleiner als Woche" war kein Rechenfehler, sondern die
Definition: am 02.09. begann die Kalenderwoche am 31.08., der Monat erst am
01.09. Umgestellt auf rollende Fenster (1/7/30/365 Tage), damit 1 ⊂ 7 ⊂ 30 ⊂
365 an jedem Tag gilt. "Jahr" meint damit nicht mehr das Kalenderjahr - so
entschieden. Zwei neue Tests halten die Verschachtelung fest.

Aus dem Langzeitverbrauch sind Herleitung und Erklaerabsatz aus der ANSICHT
genommen, nicht aus der Rechnung; das Datum des letzten Tankstops ebenfalls.

UEBERSICHT. Unter "Letzter Tankvorgang" steht das Datum statt der Tankstelle.

BILDER. Die Draufsicht war der einzige sichtbare Bildplatz ohne Zugang -
bilder.py kannte den Dateinamen, BILDER/BILDPLAETZE nicht. Jetzt ein Platz wie
jeder andere. Die Vorschau in den Einstellungen schneidet Fotos nicht mehr an,
und der Bildbereich der Fahrzeugseite steht eingerueckt statt randlos.

ZAHNRAD. Es lag mit seiner 44px-Trefferflaeche 21px im Kachelinhalt und fing
die obere Haelfte des ersten Knopfes ab (Versicherung, "Inland"). Die Kopfzeile
bekommt jetzt die Hoehe, die es braucht.

SCREENING. Lief ausschliesslich bei einer Aenderung des Kilometerstands, also
nur waehrend der Fahrt - im Stand blieben Ortsnamen und Verbrauch liegen. Jetzt
zusaetzlich einmal nach dem Start, und danach faellt es selbst nach, solange
ein Lauf sein Ortsbudget aufbraucht UND dabei etwas aufloest. Kein Dauertakt:
die Kette endet, sobald ein Lauf mit uebrigem Budget durchkommt.

GESTEN. Zum Loeschen nach links wischen, jetzt auch im Radarchiv (das Panel
hatte die Mechanik, das Archiv war nie angeschlossen; die App hatte sie gar
nicht). Vom linken Rand nach rechts wischen = zurueck, jetzt auch in der App,
mit denselben Zahlen wie im Panel und in beiden mit sichtbarer Rueckmeldung.

CI-SYMBOLE. Das Kachel-Zahnrad ist settings-s - im Panel stand der Pfad
sechsmal wortgleich im Markup, jetzt einmal als CI.settingsS. Das
handgezeichnete Zahnrad der Seitenleiste ist ersetzt und geloescht. Die
Zapfsaeule des Tanken-Reiters ist fuel-station-s, Faktor 0,84 um die
Rastermitte - live per getBBox() gemessen, damit sie auf dieselbe senkrechte
Spanne kommt wie Statistik.

Zurueckgenommen: die Titel der Unterseiten stehen wieder mittig. Zentriert ist
Apples HIG fuer Navigationsleisten; die linksbuendigen Titel sind die der
Hauptbereiche und folgen der anderen Regel.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-02 15:48:24 +02:00
co-authored by Claude Opus 5
parent e28abdce25
commit 38e7731a3d
23 changed files with 911 additions and 135 deletions
+246
View File
@@ -7941,3 +7941,249 @@ matching SHA-256 and `bundle-version 2026.9.2.7` in the manifest.
build result.** Four of the five defects produced either a successful archive or a silent
misconfiguration; only unzipping the `.ipa` and reading entitlements, binaries and plists exposed
them.
---
## BP. Rollende Zeiträume, Gesten, CI-Symbole und das Screening fasst nach (2026.9.2.8-.12)
Vier Meldungen des Eigentümers an der realen Instanz, alle vom 02.09.2026.
### „Monat kleiner als Woche" — die Kalendergrenzen waren nicht ineinander enthalten
Gemeldet für **Gefahrene Distanz** und **Fahrzeit**: die Monatsspalte stand unter der Wochenspalte.
Kein Rechenfehler, sondern die Definition. Am 02.09.2026 begann die Kalenderwoche am Montag, dem
**31.08.**, der Kalendermonat aber erst am **01.09.** — die Woche zählte damit zwei Tage mit, die
der Monat nicht kennt. Zwischen Monat und Jahr passiert dasselbe an jedem Jahreswechsel: eine Fahrt
vom 30.12. gehört zur Woche, nicht zum Jahr.
Umgestellt auf **rollende Fenster**: Tag = 1, Woche = 7, Monat = 30, Jahr = 365 Tage zurück. So gilt
1 ⊂ 7 ⊂ 30 ⊂ 365 an jedem Tag des Jahres, ein unmöglicher Vergleich kann gar nicht mehr entstehen.
Der Preis, vom Eigentümer so entschieden: **„Jahr" ist nicht mehr das Kalenderjahr**, sondern die
letzten 365 Tage — das gilt auch für die Prozentwerte in „Tag und Nacht" und „Art der Fahrten", die
auf derselben Menge rechnen. „Absolut" bleibt unverändert alles je Erfasste.
`ZEITRAUM_TAGE` steht wortgleich in beiden Codebasen (`daten/statistik.ts`, `periodStart()` im
Panel). Zwei neue Tests halten die Regel fest: die Verschachtelung an drei kritischen Tagen
(02.09., 01.01., Monatserster) und die Fahrt vom Monatsletzten, die in Woche **und** Monat zählt.
### Langzeitverbrauch: Herleitung aus der Ansicht, nicht aus der Rechnung
Aus der Kachel entfernt: „Getankt insgesamt", „Strecke bis zum letzten Tankstop" samt Basiszeile
„Kilometerstand an den Tankvorgängen", „Letzter Tankstop", der Unplausibel-Hinweis und der
Erklärabsatz. **`langzeitverbrauch()` rechnet unverändert weiter** — `liter`, `km`, `basis` und
`unplausibel` stehen weiter im Rückgabewert, nur zeigt sie niemand mehr an. Es bleiben der Wert
selbst in der Kopfzeile und die Vierergruppe „Näherung je Fahrt, je Zeitraum".
### Die Draufsicht war der einzige Bildplatz ohne Zugang
Sie steht auf der Fahrzeugseite über den Sicherheitszeilen, `bilder.py` kennt `draufsicht.webp`
seit jeher in `ERLAUBTE_DATEINAMEN` — aber in `BILDER`/`BILDPLAETZE` fehlte sie, und genau diese
Liste baut die Auswahl in **Einstellungen → Bild der Übersicht**. Hochladen ließ sich das Foto
damit über keine der beiden Oberflächen. Jetzt ein Platz wie jeder andere (achter Eintrag), womit
er zugleich in der Galerie unter „Mein Audi" auftaucht — dieselbe Liste, dasselbe Verhalten.
### „Zuletzt": das Datum statt der Tankstelle
Unter „Letzter Tankvorgang" stand der Name der Tankstelle. Dort steht jetzt das **Datum** — die
Angabe, die auf der Startseite zählt; der Name steht auf der Detailseite. Der Gesamtpreis unter den
Litern war in beiden Oberflächen bereits da (`fuel_total_eur`) und erscheint, sobald der Beleg eine
Summe trägt.
### Das Screening lief nur, während das Fahrzeug fuhr
Der eigentliche Fund hinter „Start/Ziel: wird ermittelt …" auf der realen Instanz.
`screening.durchfuehren()` hing an **genau einem** Auslöser: `_kilometerstand_geaendert()`. Steht das
Fahrzeug, ändert sich der Kilometerstand nicht, und alles, was das Screening nachträgt — Ortsnamen,
Durchschnitt, Höchstgeschwindigkeit, Verbrauch, Radzähler — bleibt bis zur nächsten Fahrt liegen.
Nach einem Update ist das am deutlichsten: die neuen Felder der frisch installierten Fassung füllen
sich erst, wenn jemand losfährt.
Zwei neue Auslöser, beide klein: **einmal in `_nach_start()`** (dort kommt eine frisch
aktualisierte Integration an) und ein **Zehn-Minuten-Takt** (`TAKT_SCREENING`). Beides ist billig —
das Screening füllt nur leere Felder, und das Ortsbudget von acht Anfragen je Durchlauf
(`geokodierung.py`) bleibt weit unter Nominatims einer Anfrage je Sekunde. Nachgewiesen im
Testcontainer: Ortsfelder einer Fahrt geleert, ohne jede Fahrzeugbewegung vom Takt wieder gefüllt.
### Was an der gemeldeten Fahrt vom 01.09. NICHT zu reparieren ist
Dieselbe Fahrt in beiden Instanzen, gemessen:
| | reale Instanz | Testinstanz |
|---|---|---|
| Strecke | 7,0 km | 6,896 km |
| Dauer | 28 Min | 12,9 Min |
| Durchschnitt | 15,1 km/h | 32,6 km/h |
| Verbrauch | 11,4 l/100 km | 8,7 l/100 km |
Die Dauerdifferenz beträgt **15,1 Minuten** — der **Ignition-OFF-Timeout von 900 s** des FMM003
(Abschnitt AS). Die reale Instanz hat die Fahrt am 01.09. um 16:20 mit einer Fassung **vor**
`cef11b2`/`0124254` aufgezeichnet, die den Nachlauf noch mitzählte. Alles Weitere folgt daraus: der
Durchschnitt ist 7,0 km ÷ 28 Min, und der Verbrauch misst die Tankstandsdifferenz über ein 15
Minuten längeres Fenster.
**Ein Update repariert das nicht rückwirkend.** `ts_end` und `duration_s` stehen so in der
`fahrten.jsonl`, wie die damals laufende Fassung sie geschrieben hat; das Screening füllt nur leere
Felder und rechnet keine Fahrtgrenzen neu. Wer den Tag richtig haben will, muss ihn über den
Rückblick (`historienimport`) neu aufbauen.
### Nachtrag .9: kein Dauertakt, sondern Nachfassen
Der Zehn-Minuten-Takt aus .8 ist auf Rückfrage des Eigentümers wieder entfallen — zu Recht. Im
Normalbetrieb bringt er nichts: beim Fahrtende hat der Lauf das volle Budget von acht Anfragen, die
neue Fahrt braucht zwei, danach stehen Start und Ziel fest in der Fahrt und `_orte_screenen()` füllt
ohnehin nur leere Felder. Jeder weitere Durchlauf über dieselbe Fahrt ist Leerlauf.
Aufgebraucht wird das Budget in genau zwei Lagen: nach einem **Rückblick-Import**, der viele Fahrten
auf einmal anlegt, und **einmalig nach einem Update**, das ein neues Feld einführt. Dafür jetzt
`screening._nachfassen_planen()`: Ist am Ende eines Laufs das Ortsbudget leer **und** ist mindestens
ein Ort dazugekommen, läuft in 60 Sekunden ein weiterer. Beides muss gelten — Budget leer heißt „es
liegt noch Arbeit an", Fortschritt heißt „die Gegenstelle antwortet". Kommt ein Lauf mit übrigem
Budget durch, wird nichts eingeplant und die Kette endet von selbst; antwortet Nominatim gerade
nicht, endet sie ebenfalls, statt im Minutentakt gegen eine Wand zu laufen. `beenden()` bestellt
einen offenen Nachlauf ab.
`Geokodierung.neu` zählt dafür die in einem Lauf **gespeicherten** Ergebnisse — auch das ehrliche
„dort ist nichts", aber keinen Netzfehler, denn der wird bewusst nicht gemerkt.
**Live nachgewiesen** im Testcontainer: alle 13 Koordinaten und der Zwischenspeicher geleert, dann
Neustart. Lauf 1 löste genau 8 auf (12:49:5812:50:18), 60 s Pause, Lauf 2 die restlichen 5
(12:51:14) — danach keine weitere Zeile im Log. Ohne jede Fahrzeugbewegung.
### Nachtrag .9: die Draufsicht steht schmaler, das Tankdatum ist weg
Der Bildbereich auf der Fahrzeugseite lief randlos über die volle Breite (auf einem Telefon rund
374 px, mit 16:8 also 187 px hoch) und drückte die Sicherheitszeilen nach unten. Auf Ansage des
Eigentümers **schmaler statt flacher**: 60 % Breite, mittig, die Höhe geht im selben Verhältnis auf
rund 112 px zurück (`.szene.bleed.schmal` / `.dm-szene--schmal`). Flacher hätte bei einem Foto, das
nicht sehr breit ist, oben und unten geschnitten.
Aus der Kopfzeile des Langzeitverbrauchs ist zusätzlich das „bis 01.09.2026" verschwunden — der
letzte Rest der Herleitung, die aus dem Rumpf schon raus war.
### Nachtrag .9: zwei Gesten, und was davon es schon gab
**Zum Löschen nach links wischen, Räder → Archiv.** Der Löschknopf saß in der aufgeklappten Zeile,
hinter sieben Eingabefeldern. Das Panel hatte die Mechanik längst (`swipeZeile()`, seit den Fahrten-
und Tankvorgangslisten), nur war das Radarchiv nie angeschlossen — jetzt mit `rfarch:<id>` samt
eigenem `LOESCH_TEXT`/`LOESCH_HINWEIS` und einem Zweig in `eintragLoeschen()`. Umschlossen ist nur
die **Kopfzeile**, nicht der aufgeklappte Rumpf: sonst verschöbe ein waagerechtes Ziehen in einem
Eingabefeld die ganze Zeile.
Der App fehlte die Mechanik ganz; sie ist als `WischLoeschen` in `bausteine.tsx` nachgebaut.
Bewusst **nicht** die volle iOS-Mechanik: kein Löschen durch Durchwischen, die Geste legt nur den
Knopf frei, und die Sicherheitsabfrage bleibt — ein archivierter Satz ist unwiederbringlich. Der
Löschknopf in der aufgeklappten Zeile bleibt ebenfalls, am Rechner ist er der kürzere Weg.
**Vom linken Rand nach rechts wischen = zurück.** Auch das hatte das Panel schon
(`randwischenVerdrahten()`, 26 px Rand, 62 px Auslösung); der App fehlte es, dort war der Pfeil oben
links der einzige Weg zurück. `useWischZurueck()` in `Shell.tsx` bringt sie mit **denselben Zahlen**.
Zwei Dinge, die dabei nicht offensichtlich sind:
- Die App hat keinen Verlaufsstapel (siehe `navigation.ts`), und iOS' eigene Zurück-Geste hängt am
Stapel eines `UINavigationController`. In einer einzelnen WebView gibt es sie deshalb nicht
geschenkt — sie muss von Hand gebaut werden.
- Ziehen zum Aktualisieren und Randwischen greifen auf **dasselbe** `<main>`. Eine schräge Geste am
oberen Rand löste sonst beides gleichzeitig aus. Ein gemeinsames `gesteBelegt`-Ref entscheidet:
läuft der Randwisch, hält sich das Ziehen heraus.
Neu in **beiden**: die Seite folgt beim Randwischen dem Finger und federt unter der Schwelle zurück.
Vorher passierte bis zum Loslassen nichts Sichtbares — wer zu kurz zog, sah nicht einmal, dass er
die Geste begonnen hatte.
### Nachtrag .10: vier Befunde vom Bildschirmfoto des Eigentümers (02.09.2026)
**Das Zahnrad lag auf dem ersten Knopf.** Nachgerechnet an den Zahlen der Kachel: Innenabstand 22px,
Überschrift (`.ads-eyebrow`/`.label`) 12px hoch, danach folgt der erste Knopf ohne eigenen Abstand —
also ab y≈34. Das Zahnrad ist mit `top:11px` und 44px Trefferfläche von y=11 bis y=55 da. Es ragte
damit **21px in den Kachelinhalt** und fing die obere Hälfte von „Inland · <Nummer>" ab, sichtbar
wie funktional. Beide Oberflächen hatten dieselbe Geometrie, also denselben Fehler.
Nicht das Zahnrad verkleinert — 44px sind die Mindestgröße einer Trefferfläche —, sondern der
Kopfzeile die Höhe gegeben, die es braucht: `min-height:34px` und `padding-right:48px` auf der
ersten Überschrift einer Zahnrad-Kachel, Zahnrad auf `top/right:8px`. Damit endet es bei y=48, der
Inhalt beginnt bei y=56.
**Die Vorschau in den Einstellungen schnitt Fotos an.** Sie stand im festen Kasten (Panel 16:9, App
4:3) mit `object-fit:cover`; ein Foto anderen Formats verlor oben und unten je ein Stück, bei der
Frontansicht am deutlichsten. Wer dort prüft, ob das richtige Bild hochgeladen ist, muss es ganz
sehen — jetzt bestimmt das Bild die Höhe (`carfix klein` im Panel, `.dm-einstellbild` in der App),
dieselbe Entscheidung wie beim Fahrzeugfoto der Übersicht am 31.08.
**Der Titel einer Unterseite stand mittig**, als einziger Titel der ganzen Oberfläche — iOS' Regel
für Navigationsleisten, aber der Sprung fällt auf, wenn alles andere links steht. `text-align:center`
ist in beiden Oberflächen entfallen; der Zurück-Pfeil ist ein eigenes Flex-Element davor, der Titel
läuft ihm also nicht unter.
**Die Draufsicht ist freigestellt.** Das Foto lag auf einem gleichmäßigen Studiogrund (#DDDDDD) mit
Schatten — auf dunklem Grund ein heller Kasten mitten auf der Seite. Freigestellt mit einem
Flutfüller vom Bildrand her über „farblos und hell" (Sättigung ≤16, Helligkeit ≥150), zwei
Saumdurchgängen gegen den grauen Rand an der Kante, und anschließendem engen Zuschnitt:
1451×710 → **1405×618**, 15,7 % der Fläche durchsichtig. Der Zuschnitt ist der eigentliche Gewinn
für die Abstände — die kommen jetzt aus dem Layout (`padding: 22px 0 26px`), nicht aus leerem
Bildrand.
Das Werkzeug dafür steht **nicht** im Repo: es war ein einmaliger Lauf über ein Nutzerfoto (Skripte
im Scratchpad, ein PNG-Leser/-Schreiber auf `zlib` plus Flutfüller). Fotos sind Nutzerdaten und
liegen unter `/config/www/bilder/`, nie im Repository.
### Nachtrag .11: der zentrierte Titel war richtig, und das Zahnrad heißt settings-s
**Zurückgenommen: die Titel der Unterseiten stehen wieder mittig.** Die Änderung aus .10 („wie alle
anderen auch") war ein Fehlschluss. Apples HIG kennt **zwei** Titelformen, und beide sind hier im
Einsatz: den großen, linksbündigen auf oberster Ebene (Hauptbereiche) und den kleinen, **zentrierten**
in der Navigationsleiste einer Detailseite. Die „anderen" Titel waren die der Hauptbereiche — sie
folgen der anderen Regel. Nicht erneut ändern, ohne die beiden Formen auseinanderzuhalten; der
Kommentar an `.dm-kopf--unterseite` sagt das jetzt.
**Einheitlichkeit geprüft, nicht behauptet:** alle **21** Unterseiten der App haben einen Eintrag in
`ZURUECK` *und* in `TITEL` (Skript über `navigation.ts`, keine Lücke). Die Kopfzeile entsteht in
`Shell.tsx` aus `unterseite = !istHauptbereich(route.name)` — eine Seite kann also gar nicht
abweichen. Im Flächenlayout gibt es die Kopfzeile nicht, dort steht eine Brotkrume; der zentrierte
Titel erscheint nur auf dem Telefon, wo der Zurück-Pfeil immer da ist.
**Das Kachel-Zahnrad ist `settings-s` aus der Audi-Icon-Bibliothek** (24er-Raster) — nachgewiesen,
nicht angenommen: der Pfad in `SymbolZahnradVoll` ist zeichengleich mit
`design/assets/icons/settings.svg`. Im Panel stand derselbe Pfad **sechsmal wortgleich im Markup**,
obwohl es für genau diesen Zweck den `CI`-Satz gibt; er ist jetzt `CI.settingsS` und wird über
`ciSVG(CI.settingsS, 17)` gesetzt. Die 48er-Variante hieße `settings-l` und wird nirgends gebraucht,
das Zahnrad steht immer auf 17px.
**Offen und bewusst nicht angefasst:** `SymbolZahnrad` (Seitenleiste des Flächenlayouts) ist ein
selbst gezeichnetes Kontursymbol und stammt **nicht** aus dem CI-Satz. Es auf `settings-s`
umzustellen würde es neben die übrigen Leistensymbole stellen, die alle Kontur sind — das CI-Symbol
ist flächiger und wöge dort schwerer. Entscheidung des Eigentümers.
### Nachtrag .12: das gezeichnete Zahnrad ist weg, die Zapfsäule ist fuel-station-s
**Das gezeichnete Zahnrad war noch in Gebrauch** — nur nicht dort, wo man hinsieht. In der App
stand es in der Seitenleiste des Flächenlayouts (`Shell.tsx`), im Panel als `.profilbtn` in der
Kopfzeile, per CSS auf Telefonbreite `display:none` und erst ab dem großen Umbruch `inline-flex`.
Auf einem Telefon also nie sichtbar — daher der Eindruck, es sei längst raus. Beide zeigen jetzt
`settings-s`, `SymbolZahnrad` ist gelöscht. Im Panel musste die Regel `.profilbtn svg` mit: sie
stand auf `fill:none; stroke:currentColor`, was um jede Fläche des CI-Symbols eine Kontur gezogen
hätte — dieselbe Falle, die `.voll`/`RahmenFlaeche` in der App vermeidet.
**Die Zapfsäule des Tanken-Reiters ist jetzt `fuel-station-s`** (aus der Bibliothek des Eigentümers
nachgereicht; im Repo lag sie nicht — `design/assets/icons/gas-station.svg` ist eine andere
Variante mit Buchstaben unter der Säule). Damit ist von den fünf Reitersymbolen nur noch
„Übersicht" gezeichnet.
**Größe live gemessen statt geschätzt** (`getBBox()` im Browser, wie schon bei `polls-s`):
| Symbol | Breite | Höhe |
|---|---|---|
| Übersicht | 21,78 | 12,46 |
| Mein Audi | 24,00 | 17,22 |
| Fahrten | 22,00 | 21,44 |
| Statistik (CI, Faktor 0,77) | 16,94 | 18,48 |
| Tanken, gezeichnet (vorher) | 14,00 | 15,50 |
| **fuel-station-s roh** | **19,00** | **22,00** |
| **fuel-station-s, Faktor 0,84** | **15,96** | **18,48** |
Roh wäre es das höchste Symbol der Leiste gewesen — höher als „Fahrten". Faktor **0,84** um die
Rastermitte (`translate(1.5,1.92) scale(0.84)`) bringt es auf **genau dieselbe senkrechte Spanne wie
Statistik**: y 2,76 bis 21,24. Ränder nachgemessen, nicht nur die Größe — die Lehre aus dem
Übersicht-Symbol vom selben Tag.
`fuel-station-l` (48er-Raster) wird nicht gebraucht: die Reiter zeichnen mit 21 bzw. 26px.