Portierungsbeschreibung fuer "ueberschritten", Testprofil zurueckgestellt

PORTIERUNG_UEBERSCHRITTEN.md fasst zusammen, was fuer ein Spin-off dieser App
nachgezogen werden muss: Fachregel, Farbschwellen mit den gemessenen Werten,
CSS und Markup beider Codebasen, Tests, und die Fallen, die hier Zeit gekostet
haben.

Das Erstzulassungsdatum im Testcontainer ist von 01.03.2020 auf 01.03.2025
zurueckgestellt, die Sicherung entfernt - OFFEN.md 3b entsprechend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 14:42:48 +02:00
parent 33c2ffa5ff
commit bc04af9c77
2 changed files with 246 additions and 5 deletions
+8 -5
View File
@@ -104,11 +104,14 @@ Beispielzahlen in der Statistik und ein überholter TODO-Kommentar.
(Abschnitt DJ) ist jetzt auch im **Panel** live gesehen und gemessen -
Übersicht und „Mein Audi", Tagmodus, Fassung 2026.9.6.9.
*Offen bleibt allein:* das Erstzulassungsdatum im Testcontainer steht
vorübergehend auf **01.03.2020**, damit der Fall überhaupt auftritt. Das
Original liegt unter `/config/audi_dashboard/fahrzeugprofil.json.ansicht` und
ist zurückzustellen, sobald der Eigentümer sagt, dass er fertig geschaut hat.
Der **Nachtmodus** ist nur gerechnet, nicht gesehen.
Das Erstzulassungsdatum im Testcontainer war dafür vorübergehend auf
01.03.2020 zurückdatiert und ist am 07.09.2026 auf **01.03.2025**
zurückgestellt; die Sicherung `fahrzeugprofil.json.ansicht` ist entfernt.
*Offen bleibt allein:* der **Nachtmodus** ist nur gerechnet, nicht gesehen.
Die Beschreibung zum Nachziehen in einem Spin-off steht in
`PORTIERUNG_UEBERSCHRITTEN.md`.
## 4. Gemeldet, aber nicht reproduzierbar
+238
View File
@@ -0,0 +1,238 @@
# Überschrittene Servicetermine — Beschreibung zum Nachziehen
Stand 07.09.2026, Fassung `2026.9.6.9`, Commit `33c2ffa` (und Vorläufer).
Gedacht für ein Spin-off dieser App: alles, was gebraucht wird, um dieselbe
Markierung dort noch einmal zu bauen — einschließlich der Gründe, damit die
Entscheidungen nicht neu erraten werden müssen.
Betroffen sind **zwei Codebasen**, die nach der Paritätsregel immer denselben
funktionalen Stand haben: das Home-Assistant-Panel
(`custom_components/audi_dashboard/frontend/`) und die Companion-App
(`companion-app/src/`). Wer nur eine davon nachzieht, hat einen latenten Fehler.
---
## 1. Was die Markierung leistet
Ein Servicetermin, dessen Fälligkeitstag erreicht oder überschritten ist, wird
an drei Stellen hervorgehoben:
| Ort | Darstellung |
|---|---|
| Service-Blatt | die betroffene Termingruppe blass gelb hinterlegt, Marke „überschritten" im Kopf |
| „Mein Audi", Service-Kachel | **nur die betroffene Zeile** blass gelb, Marke links vom Wert |
| Übersicht, Kachel „Nächster Service" | Kennzahl in kräftigem Orange, Marke rechts daneben |
Zwei Randbedingungen, die der Eigentümer ausdrücklich gesetzt hat:
* Auf der Übersicht darf die Marke die Kachel **nicht höher machen** — sie steht
deshalb in derselben Zeile wie die Kennzahl, nicht darunter.
* In „Mein Audi" wird **nicht die ganze Kachel** hinterlegt, sondern nur das
überschrittene Feld. Eine Zwischenfassung hatte die Kachel als Ganzes getönt;
das wurde zurückgewiesen.
---
## 2. Die Fachregel: entschieden wird am Datum
`terminUeberschritten(...kandidaten)` — im Panel in `audi-dashboard-app.js`, in
der App exportiert aus `src/daten/service.ts`.
**Regel:** Nimm das **erste lesbare Datum** der übergebenen Kandidaten und melde
`true`, sobald es nicht mehr in der Zukunft liegt. Der Fälligkeitstag selbst
zählt mit.
Drei Punkte, die beim Nachbau leicht falsch gemacht werden:
1. **Nicht an den Restkilometern entscheiden.** Die Kilometerseite trägt je nach
Quelle ein anderes Vorzeichen: die Fahrzeugsensoren melden negativ, solange
noch Strecke bleibt (gemessen: `oil_change_distance` = 28600 bei Fälligkeit
2028), und die Prognosefunktion klemmt ihre Restkilometer mit
`Math.max(0, …)`. Welches Vorzeichen ein überschrittener Fahrzeugwert trägt,
ist an den vorliegenden Daten **nicht beobachtbar**. Der km-Fall fällt ohnehin
unter die Datumsregel, weil die Prognose das Datum auf heute setzt, sobald die
Restkilometer auf 0 laufen.
2. **Das erste lesbare Datum nehmen, nicht das früheste.** Es ist genau das
Datum, das an der Stelle auch angezeigt wird — so kann die Markierung nicht
von dem abweichen, was daneben steht.
3. **Volle ISO-Zeitstempel mitlesen.** Die Fahrzeugmeldung liefert
`"2027-10-21T00:00:00+00:00"`, und sie ist bei Ölwechsel und Inspektion die
Hauptquelle. Der verankerte Textparser lehnt Zeitstempel ab; ohne einen
eigenen Zweig dafür gälte die Hauptquelle stillschweigend **nie** als
überschritten. Im Panel macht das `zeitstempelAlsDatum()` mit einem auf
`JJJJ-MM-TT` plus `T` verankerten Ausdruck; in der App kann `alsZeitpunkt()`
das bereits. Der Fehler wurde vom Test gefangen, nicht im Betrieb — er wäre
still geblieben.
Bewusst eng gefasst statt einem allgemeinen `new Date()`: sonst deutete der
Rückfall auch Zeichenketten, die der Textparser gerade abgelehnt hat.
---
## 3. Die Farben: jedes Element an seiner eigenen WCAG-Schwelle
Das ist der Teil, der zwei Anläufe gebraucht hat, und der Grund dafür ist
übertragbar: **kräftiges Orange ist bei 4,5:1 nicht zu haben.** Apples
systemOrange `#FF9500` kommt auf Weiß auf 2,20:1; jedes Orange, das 4,5:1
schafft, ist so dunkel, dass es als Braun gelesen wird. Zwei Anläufe mit einer
einzigen Textfarbe (`#A15800`, dann Apples `#C55300`) wurden beide als „braun
statt gelb" zurückgewiesen — zu Recht.
Die Lösung ist nicht ein besserer Kompromisston, sondern **die Trennung der
Rollen**, jede an ihrer eigenen Schwelle:
| Rolle | Größe | Schwelle | Tag | Nacht |
|---|---|---|---|---|
| `--warn` (Fläche, 12 % hinterlegt) | — | — | `#FF9F0A` | `#FFD60A` |
| `--warn-fig` (Kennzahl) | 32 px = Großtext | 3:1 | `#DD6000` | `#FF9F0A` |
| `--warn-marke-grund` (Pille) | — | — | `#C55300` | `#FFA056` |
| `--warn-marke-schrift` | 10,5 px = Kleintext | 4,5:1 | `#FFFFFF` | `#171B21` |
| `--warn-text` (Schrift auf hellem Grund) | klein | 4,5:1 | `#C55300` | `#FFA056` |
Gemessen (WCAG 2.1, Hülle = 12 % `--warn` auf dem jeweiligen Kachelgrund):
* `#DD6000` → 3,65:1 auf Weiß, 3,34:1 auf der Hülle (Schwelle 3:1) ✔
* Weiß auf `#C55300` → 4,55:1 (Schwelle 4,5:1) ✔
* `#FF9F0A` auf `#171B21` → 8,41:1 ✔
* `#171B21` auf `#FFA056` → 8,57:1 ✔
**Der entscheidende Kniff bei der Marke:** keine dünne Schrift, sondern eine
**gefüllte Pille**. Derselbe Ton liest sich als Fläche eindeutig als Orange,
während er als 1 px starker Buchstabenstrich bräunlich wirkt. Weiße Schrift auf
`#C55300` erfüllt die 4,5:1 exakt so, wie es umgekehrt nicht ginge.
Tokens liegen in `frontend/audi-dashboard-ios.css` (Panel) und
`design-system/src/tokens/tokens.css` (App) — beide Sätze müssen gleich sein.
**Zur Frage HIG oder Audi CI:** die Fläche ist Apple (iOS-Palette, 2026-08-13
übernommen); das Audi-CI-Gelb `#ffaa00` wurde damals abgelöst und liefert als
Schrift nur 1,91:1. Die Schriftwerte sind **gerechnet, nicht übernommen**
Apples eigener Kontrastwert `#A16A00` liefert auf der 12 %-Hülle nur 4,19:1.
Nebenbefund: Apples HIG-Seite führt inzwischen die neue („unified") Palette;
einen einzelnen Wert daraus zu übernehmen wäre inkonsistent gewesen.
---
## 4. CSS
Gleiche Regeln in beiden Codebasen, nur andere Klassennamen
(Panel `audi-dashboard.css` / App `src/stile/screens.css`):
| Panel | App |
|---|---|
| `.termingruppe.ueberschritten` | `.dm-termingruppe--ueberschritten` |
| `.row.ueberschritten` | `.dm-wertzeile--ueberschritten` |
| `.ueberschritten-marke` | `.dm-ueberschritten-marke` |
| `.serviceblock.ueberschritten .fig` | `.dm-serviceblock--ueberschritten .ads-fig` |
| `.fig-zeile` | `.dm-servicefig` |
Die Tönung:
```css
background: color-mix(in srgb, var(--warn) 12%, transparent);
border-radius: var(--r-tile);
padding-left: 12px; padding-right: 12px;
margin-left: -12px; margin-right: -12px;
```
Die negativen Außenabstände sind der Punkt: sie lassen die Tönung bis an den
Kachelrand laufen, obwohl die Zeile selbst im Innenabstand der Kachel sitzt.
Prüfbar am gemessenen Breitenunterschied — getönte Zeile 752 px gegenüber 728 px
der übrigen, also genau 2 × 12 px.
Die Pille:
```css
font-size: 10.5px; font-weight: 600;
color: var(--warn-marke-schrift); background: var(--warn-marke-grund);
border-radius: var(--r-pill); padding: 3px 8px;
white-space: nowrap; flex: 0 0 auto;
display: inline-block; vertical-align: middle;
```
Platzierung: `.fig-zeile .ueberschritten-marke { margin-left: 10px }` (Übersicht,
rechts der Kennzahl in derselben Zeile) und
`.row .ueberschritten-marke { margin-right: 8px }` („Mein Audi", links des Werts).
Dazu `.termingruppe.ueberschritten, .termingruppe.ueberschritten + .termingruppe
{ border-top: 0 }` — sonst schneidet die Trennlinie durch die getönte Fläche.
---
## 5. Markup
**Übersicht** — die Marke muss in dieselbe Zeile wie die Kennzahl, sonst wächst
die Kachel. Die Zeile wird deshalb **immer** gebaut, auch ohne Marke:
```js
const figMarkup = `<div class="fig-zeile">${artIcon ? ciSVG(artIcon, 31) : ""}`
+ `<div class="fig" style="font-size:${zahlGroesse}px">${hauptwert}</div>`
+ `${ueber ? UEBERSCHRITTEN_MARKE : ""}</div>`;
```
Die Kennzahl steht auf **32 px** (vorher 40 px, auf Rückmeldung „wirkt sehr groß"
reduziert). 32 px liegt über der 24-px-Grenze für Großtext — das ist die
Voraussetzung dafür, dass `--warn-fig` mit 3:1 auskommen darf.
**„Mein Audi"** — Marke **vor** dem Wert, Klasse an der Zeile, **nicht** an der
Kachel:
```js
return `<div class="row${ueber ? " ueberschritten" : ""}" ${letzte}>
<dt>…</dt>
<dd>${ueber ? UEBERSCHRITTEN_MARKE : ""}${haupt}…</dd></div>`;
```
In der App reicht `Wertzeile` dafür eine Eigenschaft `ueberschritten?: boolean`
durch, die die Klasse am Zeilen-`div` setzt.
---
## 6. Tests
* `service.test.ts` — Fälle für `terminUeberschritten()` (Grenztag, Zukunft,
Vergangenheit, leere und unlesbare Kandidaten, Zeitstempel).
* `paritaet_ueberschritten.test.ts` — 14 Fälle, die die Panel-Funktion **aus dem
Quelltext herausschneiden** und gegen die App-Funktion laufen lassen. Das ist
der Mechanismus, der die Paritätsregel überhaupt durchsetzt; ohne ihn driften
die beiden Fassungen auseinander, ohne dass es auffällt.
* `screens.test.tsx` — Render-Tests gegen ein überschrittenes Profil.
* `buendelversion.test.ts` — hält `manifest.json`-Version, `bundle.json` und die
tatsächliche `bundle.zip` zusammen. Nach jeder UI-Änderung Version heben und
Bündel neu bauen, sonst schlägt dieser Test fehl.
---
## 7. Fallen, die Zeit gekostet haben
1. **Stylesheets im Panel greifen asynchron.** Direkt nach dem Neuladen liefert
eine Messung die UA-Vorgabe — die Pille erbt dann `13,3333px` schwarz aus dem
umgebenden `<button>`. Das sieht exakt so aus, als würde die Regel nicht
greifen, und hat hier eine lange Fehlersuche ausgelöst (bis hin zum Einspielen
einer Testregel zur Laufzeit, die aus demselben Grund ebenfalls „nicht
griff"). **Vor dem Messen warten und auf den Zielwert pollen**, nicht einmal
messen und daraus schließen.
2. **Ein Testfall, der den Fall gar nicht auslöst.** Der erste Render-Test war
mit einem Wartungsplan-Datum bestückt, das noch in der Zukunft lag — grün,
aber wertlos. Ebenso: synchrone Zusicherungen gegen asynchron geladene Daten
brauchen `waitFor`.
3. **Der Fall muss erst erzeugt werden.** Zum Ansehen wurde die Erstzulassung im
Testcontainer vorübergehend auf `01.03.2020` zurückdatiert (Sicherung daneben,
hinterher zurückgestellt). Ohne das ist nichts überschritten und es gibt
nichts zu sehen.
4. **Der Nachtmodus ist hier nur gerechnet, nicht gesehen.** Wer ihn im Spin-off
nutzt, sollte ihn einmal ansehen.
---
## 8. Reihenfolge zum Nachziehen
1. `terminUeberschritten()` in beiden Codebasen, mit dem Zeitstempel-Zweig.
2. Tokens `--warn-fig`, `--warn-marke-grund`, `--warn-marke-schrift` in beide
Token-Dateien.
3. CSS-Blöcke in beiden Stildateien.
4. Markup: Übersicht (Zeile immer bauen, 32 px), „Mein Audi" (Klasse an der
Zeile), Service-Blatt (Termingruppe).
5. Tests, **einschließlich** des Paritätstests.
6. Version heben, Design-System bauen, OTA-Bündel bauen.
7. Live ansehen — mit zurückdatiertem Datum, und mit Wartezeit vor der Messung.