Pruefdurchgang ueber alle 26 Bildschirme - fuenf Fehler behoben, HIG nachgezogen

Gemessen statt geschaut: eine Pruefroutine lief auf jedem Bildschirm und
pruefte Ueberlauf, Tippziele, abgeschnittenen Text, Bilder ohne Quelle und
WCAG-Kontrast fuer jeden Textknoten. Dazu die Live-App mit echten Daten, die
Musterseite fuer Leerzustaende und das Panel zum Abgleich.

1. Deutsche Datumsangaben wurden falsch gelesen. new Date("01.03.2025")
   liest amerikanisch: Tage bis 12 wurden still vertauscht, ab Tag 13 fiel
   der Wert ganz weg. Die Erstzulassung stand auf zwei Bildschirmen
   unterschiedlich da. alsZeitpunkt() erkennt jetzt ISO, TT.MM.JJJJ und
   MM/JJJJ, weist unmoegliche Daten ab und fuehrt die Genauigkeit mit -
   aus "08/2026" wird "August 2026", kein erfundener Erster.

2. Die Hauptuntersuchung war in beiden Oberflaechen unerreichbar. Das Panel
   leitete sie aus der Erstzulassung ab, aber nur bei MM/JJJJ, und benutzte
   den eingetragenen Wert gar nicht. Jetzt beide: eingetragene Faelligkeit
   vor Servicebuch vor Erstzulassung + 24 Monate.

3. Die Tankstellenmarke stand zweimal da, wenn der Name kein Komma hat
   ("Shell München Ost"). ohneMarkeVorn() nimmt sie vorn ab.

4. Bilder wurden geholt, obwohl beide wussten, dass es sie nicht gibt -
   fuenf 404 fuer dieselbe Datei in einem Ladevorgang. Jetzt gar keine
   Adresse bei Stand 0; nachgemessen 4 Anfragen, alle 200.

5. Ein Bildelement ohne Quelle zeigte das kaputte Bildsymbol, und onerror
   greift dort nicht: ohne src gibt es keinen Ladeversuch.

Dazu: leere Klammern beim Steuersatz, leere graue Kachel bei leerer
Leistungsgruppe, Umbruch auf dem Einrichtungsbildschirm, drei
Leerdarstellungen in einer Kachel, fehlende Grundschrift.

Apple HIG: Kontrast war an einer Stelle 3,18 statt 4,5, weisse Schrift auf
der Loeschflaeche 3,41/3,55. Rot als Schrift und Rot als Flaeche sind jetzt
getrennte Token (--bad / --bad-flaeche), beide Apples systemRed fuer
erhoehten Kontrast. Tippziele: unsichtbares 44x44-Overlay, wo die sichtbare
Groesse Teil des Bildes ist, echte Mindesthoehe, wo das Element Flaeche ist -
Formulare werden dadurch sichtbar hoeher.

Neu auf Wunsch: Wischen in der Bildergalerie (beide Richtungen) und die
HIG-konforme Loeschgeste - ein voller Wisch loescht ohne zweiten Tipp, ab
55 % der Zeilenbreite, mit wachsender roter Flaeche und erhaltener
Rueckfrage. Dabei fiel auf, dass der Zugwert aus dem React-Zustand gelesen
wurde und ein schneller Wisch dadurch verlorenging; er liegt jetzt in einer
Referenz.

Paritaet: alle 26 Routen und Titel decken sich, Fahrzeugstatus zeilengleich,
zwei ungeplante Abweichungen (Datumsformatierung, Herkunft der
Hauptuntersuchung) geschlossen.

tsc sauber, 271 Tests, vite build sauber, Design-System gebaut, Panel als
Modul geparst, audi_ha_test auf 2026.9.4.23 ohne Traceback. Live nachgemessen:
Datum auf beiden Bildschirmen gleich, keine 404 mehr, Zurueck-Pfeil sichtbar
34x34 und treffbar 44x44, Galerie wischt in beide Richtungen, voller Wisch
loest die Rueckfrage aus, keine Kontrast-Unterschreitung mehr.
This commit is contained in:
2026-09-04 18:11:07 +02:00
parent ffaed3f544
commit b0ec8b4982
26 changed files with 1125 additions and 114 deletions
+115 -13
View File
@@ -88,35 +88,137 @@ const UHRZEIT = new Intl.DateTimeFormat("de-DE", { hour: "2-digit", minute: "2-d
const MONAT_JAHR = new Intl.DateTimeFormat("de-DE", { month: "long", year: "numeric" })
function alsDatum(wert: string | number | Date | null | undefined): Date | null {
/**
* Datumsangaben aus dem Bestand kommen in DREI Formen - deshalb reicht
* `new Date(wert)` hier nicht.
*
* WAS AM 04.09.2026 GEMESSEN WURDE
* --------------------------------
* `new Date()` liest einen nicht-ISO-Text nach amerikanischer Lesart, also
* Monat vor Tag. Fuer deutsche Eingaben heisst das:
*
* "01.03.2025" (1. Maerz) -> 3. Januar 2025 - Tag und Monat vertauscht
* "12.11.2024" -> 11. Dezember - dito
* "25.03.2025" -> ungueltig - Wert verschwand als "-"
* "08/2026" (Hauptunters.) -> ungueltig - dito
*
* Tage bis 12 wurden also STILL vertauscht, ab Tag 13 fiel der Wert ganz weg.
* Sichtbar war es an der Erstzulassung: "Mein Audi" zeigte den Rohwert
* richtig, "Identitaet und Technik" formatierte ihn und drehte ihn dabei um -
* derselbe Wert, zwei Ergebnisse. Und die Hauptuntersuchung stand als "-" da,
* obwohl "08/2026" gespeichert war.
*
* WARUM EIGENE MUSTER STATT EINER BIBLIOTHEK
* ------------------------------------------
* Es sind genau drei Formen, alle aus diesem Projekt: ISO vom Backend,
* "TT.MM.JJJJ" aus den Eingabefeldern, "MM/JJJJ" fuer die Hauptuntersuchung
* (so verlangt es das Einrichtungsfeld, siehe Panel `einErstzulassung`).
* Erkannt wird nur, was hier wirklich vorkommt - alles andere bleibt `null`
* und damit "-". Lieber kein Datum als ein erfundenes.
*
* MONATSGENAU IST EINE EIGENE GENAUIGKEIT
* ---------------------------------------
* "08/2026" nennt keinen Tag. Daraus den 1. August zu machen und
* "01.08.2026" anzuzeigen waere eine Erfindung - deshalb traegt das Ergebnis
* seine Genauigkeit mit sich, und `datum()` zeigt dann "August 2026".
*/
type Genauigkeit = "tag" | "monat"
interface Zeitpunkt {
zeit: Date
genau: Genauigkeit
}
/* ISO vom Backend, mit oder ohne Uhrzeit - das kann `new Date` selbst, und
zwar nach Norm statt nach Gutduenken. */
const ISO = /^\d{4}-\d{2}-\d{2}([T ]|$)/
/* "1.3.2025" wie "01.03.2025". */
const DE_TAG = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
/* "08/2026" und "08.2026" - Monat und Jahr, ohne Tag. */
const DE_MONAT = /^(\d{1,2})[./](\d{4})$/
/* "2026-08" - dieselbe Genauigkeit, in ISO-Schreibweise. */
const ISO_MONAT = /^(\d{4})-(\d{1,2})$/
/**
* Baut das Datum und weist die Rollover-Falle ab: `new Date(2025, 1, 31)` ist
* in JavaScript der 3. Maerz, nicht der 31. Februar. Ein unmoegliches Datum
* soll "-" ergeben, nicht klammheimlich ein anderes.
*/
function bauen(jahr: number, monat: number, tag: number): Date | null {
if (monat < 1 || monat > 12 || tag < 1 || tag > 31) return null
const d = new Date(jahr, monat - 1, tag)
if (d.getFullYear() !== jahr || d.getMonth() !== monat - 1 || d.getDate() !== tag) return null
return d
}
function alsZeitpunkt(wert: string | number | Date | null | undefined): Zeitpunkt | null {
if (wert === null || wert === undefined || wert === "") return null
const datum = wert instanceof Date ? wert : new Date(wert)
return Number.isNaN(datum.getTime()) ? null : datum
if (wert instanceof Date) return Number.isNaN(wert.getTime()) ? null : { zeit: wert, genau: "tag" }
if (typeof wert === "number") {
const d = new Date(wert)
return Number.isNaN(d.getTime()) ? null : { zeit: d, genau: "tag" }
}
const text = wert.trim()
if (!text) return null
if (ISO.test(text)) {
const d = new Date(text)
return Number.isNaN(d.getTime()) ? null : { zeit: d, genau: "tag" }
}
const tag = DE_TAG.exec(text)
if (tag) {
const d = bauen(Number(tag[3]), Number(tag[2]), Number(tag[1]))
return d ? { zeit: d, genau: "tag" } : null
}
const monat = DE_MONAT.exec(text)
if (monat) {
const d = bauen(Number(monat[2]), Number(monat[1]), 1)
return d ? { zeit: d, genau: "monat" } : null
}
const isoMonat = ISO_MONAT.exec(text)
if (isoMonat) {
const d = bauen(Number(isoMonat[1]), Number(isoMonat[2]), 1)
return d ? { zeit: d, genau: "monat" } : null
}
return null
}
function alsDatum(wert: string | number | Date | null | undefined): Date | null {
return alsZeitpunkt(wert)?.zeit ?? null
}
export function datum(wert: string | number | Date | null | undefined): string {
const d = alsDatum(wert)
return d ? DATUM_KURZ.format(d) : ""
const z = alsZeitpunkt(wert)
if (!z) return ""
return z.genau === "monat" ? MONAT_JAHR.format(z.zeit) : DATUM_KURZ.format(z.zeit)
}
export function datumLang(wert: string | number | Date | null | undefined): string {
const d = alsDatum(wert)
return d ? DATUM_LANG.format(d) : ""
const z = alsZeitpunkt(wert)
if (!z) return ""
return z.genau === "monat" ? MONAT_JAHR.format(z.zeit) : DATUM_LANG.format(z.zeit)
}
export function uhrzeit(wert: string | number | Date | null | undefined): string {
const d = alsDatum(wert)
return d ? UHRZEIT.format(d) : ""
const z = alsZeitpunkt(wert)
// Eine monatsgenaue Angabe hat keine Uhrzeit - 00:00 waere erfunden.
return z && z.genau === "tag" ? UHRZEIT.format(z.zeit) : ""
}
export function datumZeit(wert: string | number | Date | null | undefined): string {
const d = alsDatum(wert)
return d ? `${DATUM_KURZ.format(d)}, ${UHRZEIT.format(d)} Uhr` : ""
const z = alsZeitpunkt(wert)
if (!z) return ""
if (z.genau === "monat") return MONAT_JAHR.format(z.zeit)
return `${DATUM_KURZ.format(z.zeit)}, ${UHRZEIT.format(z.zeit)} Uhr`
}
export function monatJahr(wert: string | number | Date | null | undefined): string {
const d = alsDatum(wert)
return d ? MONAT_JAHR.format(d) : ""
const z = alsZeitpunkt(wert)
return z ? MONAT_JAHR.format(z.zeit) : ""
}
/** Dauer in Sekunden als „2 h 14 min" bzw. „14 min". */