e23a462da9
Die Beschreibung gibt es zusaetzlich als veroeffentlichte Seite mit echt gerenderten Farbmustern fuer Tag und Nacht. Adresse in PORTIERUNG_UEBERSCHRITTEN.md und OFFEN.md nachgetragen, mit dem Hinweis, dass Aenderungen an der Datei dort nachzuziehen sind - sonst laufen die beiden Fassungen auseinander. Die Seite ist markenfrei: keine lizenzierten Schriften, Embleme oder Modellzeichen, nur die Farbwerte und Strukturen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
247 lines
11 KiB
Markdown
247 lines
11 KiB
Markdown
# Ü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.
|
||
|
||
**Teilbare Fassung:** dieselbe Beschreibung als Seite, mit echt gerenderten
|
||
Farbmustern für Tag und Nacht -
|
||
<https://claude.ai/code/artifact/8f2eb15e-1107-45bc-bd04-78b19fa06a4b>
|
||
(standardmäßig privat; teilbar über das Teilen-Menü auf der Seite). Sie ist
|
||
markenfrei gehalten: keine lizenzierten Schriften, Embleme oder Modellzeichen.
|
||
Änderungen an dieser Datei dort nachziehen, sonst laufen die beiden Fassungen
|
||
auseinander.
|
||
|
||
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.
|