Files
audi-app/PORTIERUNG_UEBERSCHRITTEN.md
T
tobias e23a462da9 Teilbare Fassung der Portierungsbeschreibung verlinkt
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>
2026-09-07 14:56:19 +02:00

247 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ü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.