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:
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user