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>
11 KiB
Ü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:
- 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 mitMath.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. - 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.
- 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 daszeitstempelAlsDatum()mit einem aufJJJJ-MM-TTplusTverankerten Ausdruck; in der App kannalsZeitpunkt()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) ✔ #FF9F0Aauf#171B21→ 8,41:1 ✔#171B21auf#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:
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:
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:
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:
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ürterminUeberschritten()(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ältmanifest.json-Version,bundle.jsonund die tatsächlichebundle.zipzusammen. Nach jeder UI-Änderung Version heben und Bündel neu bauen, sonst schlägt dieser Test fehl.
7. Fallen, die Zeit gekostet haben
- Stylesheets im Panel greifen asynchron. Direkt nach dem Neuladen liefert
eine Messung die UA-Vorgabe — die Pille erbt dann
13,3333pxschwarz 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. - 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. - Der Fall muss erst erzeugt werden. Zum Ansehen wurde die Erstzulassung im
Testcontainer vorübergehend auf
01.03.2020zurückdatiert (Sicherung daneben, hinterher zurückgestellt). Ohne das ist nichts überschritten und es gibt nichts zu sehen. - Der Nachtmodus ist hier nur gerechnet, nicht gesehen. Wer ihn im Spin-off nutzt, sollte ihn einmal ansehen.
8. Reihenfolge zum Nachziehen
terminUeberschritten()in beiden Codebasen, mit dem Zeitstempel-Zweig.- Tokens
--warn-fig,--warn-marke-grund,--warn-marke-schriftin beide Token-Dateien. - CSS-Blöcke in beiden Stildateien.
- Markup: Übersicht (Zeile immer bauen, 32 px), „Mein Audi" (Klasse an der Zeile), Service-Blatt (Termingruppe).
- Tests, einschließlich des Paritätstests.
- Version heben, Design-System bauen, OTA-Bündel bauen.
- Live ansehen — mit zurückdatiertem Datum, und mit Wartezeit vor der Messung.