diff --git a/AGENTS.md b/AGENTS.md index d025278..1940ad8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1425,6 +1425,45 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed Überschrift, die nichts mit ihnen zu tun hat) und führten nirgendwohin. Jetzt eigene, antippbare `Tile variant="button" chevron` → `service`. Geprüft: typecheck sauber, 95/95 Tests, Build erfolgreich. +- [x] Übersicht: Kilometerstand/Service statt zwei gleichrangiger Kacheln (2026-08-17) — Nutzer + mochte das Kachelpaar nicht und fragte, wie Apple HIG bzw. die Audi-CI es lösen würden. Beide + Regelwerke wurden dafür belegt, nicht aus dem Gedächtnis zitiert: die HIG-Seiten „Layout“ + („Differentiate controls from content“; Wichtigkeit über Position und Größe) und „Widgets“ + (dynamische statt statischer Inhalte; „sparse layouts can make the widget seem unnecessary“) + live von developer.apple.com gelesen, die CI-Regeln aus dem projekteigenen + `AUDI_CI_ARCHIV_2026-08-13.md` (flache Flächen mit Haarlinien statt gestapelter Karten; + `.fig`/Audi Type Wide als eigene typografische Klasse für Zahlen; Hierarchie nur über Größe + und Farbe, da nur die Schnitte 300/400 existieren). Gemessener Befund: beide Kacheln + 166×113px, gleiche 29px-Zahl, gleiche Farbe — obwohl nur die rechte ein Knopf war (erkennbar + allein an einem 12px-Chevron) und die beiden Zahlen Grundverschiedenes meinen (Ist-Wert vs. + täglich schrumpfender Countdown). Beide Schulen sagen dazu dasselbe, sie unterscheiden sich + nur im Mittel. Ergebnis nach drei Iterationen mit dem Nutzer (Zwischenstände jeweils als + Mockup gezeigt, bevor Code angefasst wurde): das `.duo`-Raster entfällt, stattdessen eine + führende Kennzahl in einer `.tile.flat` (gleiche Ebene wie die Reichweite darüber, kein + eigener Kachelhintergrund) — Zeile „Nächster Service:“, Chevron bündig oben rechts, 40px-Zahl, + Unterzeile „bis zum Ölwechsel · vsl. 15.04.2027“, Haarlinie, darunter der Kilometerstand als + `.servicezeile`. Der Chevron sitzt bewusst nur am `.serviceblock`-Knopf, nicht über der ganzen + Fläche: damit ist der obere Teil das Bedienelement und die Kilometerstand-Zeile reine + Anzeige (live geprüft — Klick auf den Block öffnet „Service“, Klick auf die Zeile navigiert + nicht). „Nächster Service“ meint auf Nutzerwunsch **jede** Serviceart; `naechsterTermin()` + wählte ohnehin schon über Ölwechsel/Inspektion/Hauptuntersuchung hinweg aus, neu ist nur die + Beschriftung samt `ART_BIS`-Zuordnung für den richtigen Artikel („bis zu**m** Ölwechsel“, aber + „bis zu**r** Inspektion“). Fällt kein Restkilometerwert an (Hauptuntersuchung hat kein + km-Ziel), rückt das Datum selbst ins Zahlenfeld, dann bei 30px statt 40px, damit „15.04.2027“ + auf 375px nicht umbricht. Von der Änderung verwaist und deshalb mitentfernt: `mmjjjj()` (nur + hier benutzt) und die `.duo`-Regeln in beiden Stylesheets. **Bewusst offen gelassen:** der + Statuspunkt aus der HIG-Variante („alles ruhig“ vs. „bald fällig“) — der Nutzer hat ihn nicht + übernommen. **Bewusst nicht entschieden von mir:** das volle Tagesdatum statt „04/2027“ war + Nutzerwunsch; angemerkt wurde dabei, dass die Prognose an einem einzigen Arbeitstag von + 12.04. über 14.04. auf 15.04.2027 wanderte (`oelwechselPrognose()` extrapoliert aus km/Tag, + jede Odometer-Aktualisierung verschiebt sie), das Tagesdatum also mehr Genauigkeit verspricht, + als der Wert hat. Live geprüft bei 375px und 1280px: Block und Wertezeile fluchten mit der + Kachelkante (16→359 bzw. 564→1236), Chevron bündig rechts, Tippfläche 88px hoch, keine neuen + Konsolenfehler. Deployed als Version `1787006000`, synchron in `installationspaket/`. + Entwurf und Begründung als Artefakt: „Zwei Kacheln, drei Antworten“. + **Noch offen:** die Änderung existiert nur im Panel, nicht in `companion-app/` — dort steht + die Übersicht weiter mit dem alten Aufbau (siehe den Drift-Eintrag weiter oben; die beiden + Codebasen laufen sonst wieder auseinander). - [ ] Fix remaining documentation drift (statistics claim, README gaps, obsolete TODO comment) — text-only changes; INSTALL.md's WLAN/TommiG1 drift and stale variable names were fixed 2026-08-12 (see section B); `DESIGN_REVIEW_2026-08-13.md` and `REVIEW_main_2026-08-13.md` diff --git a/homeassistant/installationspaket/www/audi-dashboard-app.js b/homeassistant/installationspaket/www/audi-dashboard-app.js index 918b144..12a974a 100644 --- a/homeassistant/installationspaket/www/audi-dashboard-app.js +++ b/homeassistant/installationspaket/www/audi-dashboard-app.js @@ -324,14 +324,9 @@ function heute() { return new Date(); } const dTag = 86400000; /* Ein unlesbares Datum ergibt einen Strich, keine Rechenreste. dat() erwartet TT.MM.JJJJ; steht im Profil etwas anderes (es wird laut INSTALL.md von Hand - gepflegt, z. B. versehentlich im ISO-Format), lieferte mmjjjj() sonst - "NaN/NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */ + gepflegt, z. B. versehentlich im ISO-Format), lieferte dedat() sonst + "NaN.NaN.NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */ const gueltigesDatum = (d) => d instanceof Date && !Number.isNaN(d.getTime()); -/* Volles Jahr, weil das Datum überall als eigene Zeile steht und nicht mehr - in ein Zahlenfeld gequetscht wird (früher mmjj() mit "04/27"). */ -const mmjjjj = (d) => (gueltigesDatum(d) - ? String(d.getMonth() + 1).padStart(2, "0") + "/" + d.getFullYear() - : "–"); const dedat = (d) => (gueltigesDatum(d) ? d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" }) : "–"); @@ -1094,6 +1089,15 @@ function teaser() { function naechsterTermin() { return termine().slice().sort((a, b) => a.ziel - b.ziel)[0]; } +/* "der Ölwechsel", aber "die Inspektion"/"die Hauptuntersuchung" - ohne diese + Zuordnung stünde auf der Übersicht "bis zum Inspektion". */ +const ART_BIS = { + "Ölwechsel": "bis zum Ölwechsel", + "Inspektion": "bis zur Inspektion", + "Hauptuntersuchung": "bis zur Hauptuntersuchung", +}; +const bisText = (art) => ART_BIS[art] || `bis zum nächsten Service (${art})`; + function intervalle(t) { const oel = CAR.oel, b = t.basis; if (!b) return null; @@ -1245,34 +1249,55 @@ function vHome() {
Reichweite -
-
Kilometerstand -
${CAR.odoBekannt ? de(CAR.odo) : "–"}km
- ${(() => { if (!t) return `
Service
kein Eintrag
`; - const artLabel = t.art === "Hauptuntersuchung" ? "Haupt-
untersuchung" : esc(t.art); - const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null; - const datum = t.kmDatum || t.datum; - /* Ein Wert pro Zahlenfeld, der Rest als leisere Zweitzeile darunter. - Vorher stand hier "9,0 tkm/ 04/27" - zwei Einheiten in einem Slot, - eine davon erfunden ("tkm" schreibt niemand, und die Mein-Audi-Kachel - nannte dieselbe Zahl schon immer "9.029 km"), auf 375px zweizeilig - umgebrochen. Apple HIG "Layout" ("don't obscure [essential - information] by crowding it with nonessential details") und - "Typography" (Hierarchie über Größe/Farbe) verlangen die Trennung - - deckungsgleich mit der Audi-CI, wo .fig genau EINE große Zahl trägt. - Kachel ist jetzt ein Knopf: hinter ihr liegt die Service-Seite, das - war bislang eine Sackgasse ohne Hinweis. */ - const hauptwert = restKm !== null - ? `${de(restKm)}km` - : (datum ? mmjjjj(datum) : "–"); - const nebenzeile = restKm !== null && datum ? `vsl. ${mmjjjj(datum)}` : ""; - return ``; })()} -
+ ${(() => { + /* Kilometerstand und Service standen bis 2026-08-17 als zwei gleich große + Kacheln nebeneinander - gleiches Maß, gleiche Zahlengröße, gleiche Farbe, + obwohl nur eine davon ein Knopf war und die beiden Zahlen Grundver- + schiedenes meinen: der Kilometerstand ist ein gemessener Ist-Wert, der + sich kaum ändert, der Service-Wert ein Countdown, der täglich kleiner + wird. Zwei gleich große Zahlen nebeneinander behaupteten damit eine + Gleichrangigkeit, die inhaltlich nicht besteht. + + Jetzt: eine führende Kennzahl, darunter der Kilometerstand als ruhige + Wertezeile, getrennt durch eine Haarlinie - Audi-CI (Flächen und + Haarlinien statt gestapelter Karten, Hierarchie über Größe und Farbe) + kombiniert mit Apple HIGs "differentiate controls from content": der + Chevron sitzt nur am Serviceblock, damit erkennbar bleibt, welcher Teil + auf die Service-Seite führt und welcher reine Anzeige ist. Der Block + liegt bewusst in derselben Ebene wie die Reichweite darüber (.tile flat, + kein eigener Kachelhintergrund). */ + const kmZeile = `
+ Kilometerstand +
${CAR.odoBekannt ? de(CAR.odo) : "–"}km
+
`; + if (!t) return `
+ Nächster Service: +
kein Eintrag im Servicebuch
+ ${kmZeile} +
`; + // naechsterTermin() waehlt bereits ueber alle drei Arten hinweg (Oelwechsel, + // Inspektion, Hauptuntersuchung) den zeitlich naechsten aus. + const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null; + const datum = t.kmDatum || t.datum; + const hauptwert = restKm !== null + ? `${de(restKm)}km` + : (datum ? dedat(datum) : "–"); + // Ohne Restkilometer steht das Datum selbst im Zahlenfeld - dann kleiner, + // damit "15.04.2027" auf 375px nicht umbricht. + const zahlGroesse = restKm !== null ? 40 : 30; + const nebenzeile = restKm !== null && datum + ? `${bisText(t.art)} · vsl. ${dedat(datum)}` + : bisText(t.art); + return `
+ + ${kmZeile} +
`; + })()} ${standortKachel()} ${teaser()}`; } diff --git a/homeassistant/installationspaket/www/audi-dashboard-ios.css b/homeassistant/installationspaket/www/audi-dashboard-ios.css index 2b24bc9..a97c020 100644 --- a/homeassistant/installationspaket/www/audi-dashboard-ios.css +++ b/homeassistant/installationspaket/www/audi-dashboard-ios.css @@ -307,7 +307,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo deshalb hier mit !important. */ .rings{display:none !important} - .duo{grid-template-columns:repeat(3,minmax(0,1fr))} /* Nur die Box selbst hoeher setzen, nicht jedes Element mit .carfix: die Klasse haengt auch am und am Platzhalter-
(siehe bildMitPlatzhalter()). Der Platzhalter ist position:absolute;inset:0 - diff --git a/homeassistant/installationspaket/www/audi-dashboard-version.json b/homeassistant/installationspaket/www/audi-dashboard-version.json index dc1f246..dcb0a42 100644 --- a/homeassistant/installationspaket/www/audi-dashboard-version.json +++ b/homeassistant/installationspaket/www/audi-dashboard-version.json @@ -1 +1 @@ -{"version": 1787003000} +{"version": 1787006000} diff --git a/homeassistant/installationspaket/www/audi-dashboard.css b/homeassistant/installationspaket/www/audi-dashboard.css index 7fced30..dabe736 100644 --- a/homeassistant/installationspaket/www/audi-dashboard.css +++ b/homeassistant/installationspaket/www/audi-dashboard.css @@ -463,8 +463,26 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } /* Der Außenabstand sitzt am Raster, nicht an den Kacheln - sonst wird er im 2×2-Raster (vHome) zum zusätzlichen internen Zeilenabstand und der Abstand innerhalb der Gruppe wäre größer als der zur nächsten. */ -.duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin-bottom: var(--sp-5); } -.duo .tile { margin-bottom: 0; } +/* Übersicht: "Nächster Service" als flache, tippbare Kennzahl, darunter der + Kilometerstand als Wertezeile (siehe Kommentar in vHome()). Der Block sitzt + in einer .tile.flat, hat also keinen eigenen Kachelhintergrund - deshalb + sitzt der Chevron bündig am oberen rechten Rand statt auf --sp-5 eingerückt + wie sonst in einer gepolsterten Kachel. */ +.serviceblock { + position: relative; + display: block; width: 100%; + border: 0; background: none; padding: 0; + font-family: inherit; text-align: left; color: inherit; cursor: pointer; +} +.serviceblock > .go { top: 3px; right: 0; } +/* Beschriftung nie unter dem Chevron hindurchlaufen lassen */ +.serviceblock > .label:first-child { padding-right: 24px; } +.servicezeile { + display: flex; align-items: baseline; justify-content: space-between; + gap: var(--sp-4); + border-top: 1px solid var(--line); + margin-top: 14px; padding-top: 13px; +} .badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .badge img { height: 18px; width: auto; display: none; } diff --git a/homeassistant/www/audi-dashboard-app.js b/homeassistant/www/audi-dashboard-app.js index 918b144..12a974a 100644 --- a/homeassistant/www/audi-dashboard-app.js +++ b/homeassistant/www/audi-dashboard-app.js @@ -324,14 +324,9 @@ function heute() { return new Date(); } const dTag = 86400000; /* Ein unlesbares Datum ergibt einen Strich, keine Rechenreste. dat() erwartet TT.MM.JJJJ; steht im Profil etwas anderes (es wird laut INSTALL.md von Hand - gepflegt, z. B. versehentlich im ISO-Format), lieferte mmjjjj() sonst - "NaN/NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */ + gepflegt, z. B. versehentlich im ISO-Format), lieferte dedat() sonst + "NaN.NaN.NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */ const gueltigesDatum = (d) => d instanceof Date && !Number.isNaN(d.getTime()); -/* Volles Jahr, weil das Datum überall als eigene Zeile steht und nicht mehr - in ein Zahlenfeld gequetscht wird (früher mmjj() mit "04/27"). */ -const mmjjjj = (d) => (gueltigesDatum(d) - ? String(d.getMonth() + 1).padStart(2, "0") + "/" + d.getFullYear() - : "–"); const dedat = (d) => (gueltigesDatum(d) ? d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" }) : "–"); @@ -1094,6 +1089,15 @@ function teaser() { function naechsterTermin() { return termine().slice().sort((a, b) => a.ziel - b.ziel)[0]; } +/* "der Ölwechsel", aber "die Inspektion"/"die Hauptuntersuchung" - ohne diese + Zuordnung stünde auf der Übersicht "bis zum Inspektion". */ +const ART_BIS = { + "Ölwechsel": "bis zum Ölwechsel", + "Inspektion": "bis zur Inspektion", + "Hauptuntersuchung": "bis zur Hauptuntersuchung", +}; +const bisText = (art) => ART_BIS[art] || `bis zum nächsten Service (${art})`; + function intervalle(t) { const oel = CAR.oel, b = t.basis; if (!b) return null; @@ -1245,34 +1249,55 @@ function vHome() {
Reichweite
-
-
Kilometerstand -
${CAR.odoBekannt ? de(CAR.odo) : "–"}km
- ${(() => { if (!t) return `
Service
kein Eintrag
`; - const artLabel = t.art === "Hauptuntersuchung" ? "Haupt-
untersuchung" : esc(t.art); - const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null; - const datum = t.kmDatum || t.datum; - /* Ein Wert pro Zahlenfeld, der Rest als leisere Zweitzeile darunter. - Vorher stand hier "9,0 tkm/ 04/27" - zwei Einheiten in einem Slot, - eine davon erfunden ("tkm" schreibt niemand, und die Mein-Audi-Kachel - nannte dieselbe Zahl schon immer "9.029 km"), auf 375px zweizeilig - umgebrochen. Apple HIG "Layout" ("don't obscure [essential - information] by crowding it with nonessential details") und - "Typography" (Hierarchie über Größe/Farbe) verlangen die Trennung - - deckungsgleich mit der Audi-CI, wo .fig genau EINE große Zahl trägt. - Kachel ist jetzt ein Knopf: hinter ihr liegt die Service-Seite, das - war bislang eine Sackgasse ohne Hinweis. */ - const hauptwert = restKm !== null - ? `${de(restKm)}km` - : (datum ? mmjjjj(datum) : "–"); - const nebenzeile = restKm !== null && datum ? `vsl. ${mmjjjj(datum)}` : ""; - return ``; })()} -
+ ${(() => { + /* Kilometerstand und Service standen bis 2026-08-17 als zwei gleich große + Kacheln nebeneinander - gleiches Maß, gleiche Zahlengröße, gleiche Farbe, + obwohl nur eine davon ein Knopf war und die beiden Zahlen Grundver- + schiedenes meinen: der Kilometerstand ist ein gemessener Ist-Wert, der + sich kaum ändert, der Service-Wert ein Countdown, der täglich kleiner + wird. Zwei gleich große Zahlen nebeneinander behaupteten damit eine + Gleichrangigkeit, die inhaltlich nicht besteht. + + Jetzt: eine führende Kennzahl, darunter der Kilometerstand als ruhige + Wertezeile, getrennt durch eine Haarlinie - Audi-CI (Flächen und + Haarlinien statt gestapelter Karten, Hierarchie über Größe und Farbe) + kombiniert mit Apple HIGs "differentiate controls from content": der + Chevron sitzt nur am Serviceblock, damit erkennbar bleibt, welcher Teil + auf die Service-Seite führt und welcher reine Anzeige ist. Der Block + liegt bewusst in derselben Ebene wie die Reichweite darüber (.tile flat, + kein eigener Kachelhintergrund). */ + const kmZeile = `
+ Kilometerstand +
${CAR.odoBekannt ? de(CAR.odo) : "–"}km
+
`; + if (!t) return `
+ Nächster Service: +
kein Eintrag im Servicebuch
+ ${kmZeile} +
`; + // naechsterTermin() waehlt bereits ueber alle drei Arten hinweg (Oelwechsel, + // Inspektion, Hauptuntersuchung) den zeitlich naechsten aus. + const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null; + const datum = t.kmDatum || t.datum; + const hauptwert = restKm !== null + ? `${de(restKm)}km` + : (datum ? dedat(datum) : "–"); + // Ohne Restkilometer steht das Datum selbst im Zahlenfeld - dann kleiner, + // damit "15.04.2027" auf 375px nicht umbricht. + const zahlGroesse = restKm !== null ? 40 : 30; + const nebenzeile = restKm !== null && datum + ? `${bisText(t.art)} · vsl. ${dedat(datum)}` + : bisText(t.art); + return `
+ + ${kmZeile} +
`; + })()} ${standortKachel()} ${teaser()}`; } diff --git a/homeassistant/www/audi-dashboard-ios.css b/homeassistant/www/audi-dashboard-ios.css index 2b24bc9..a97c020 100644 --- a/homeassistant/www/audi-dashboard-ios.css +++ b/homeassistant/www/audi-dashboard-ios.css @@ -307,7 +307,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo deshalb hier mit !important. */ .rings{display:none !important} - .duo{grid-template-columns:repeat(3,minmax(0,1fr))} /* Nur die Box selbst hoeher setzen, nicht jedes Element mit .carfix: die Klasse haengt auch am und am Platzhalter-
(siehe bildMitPlatzhalter()). Der Platzhalter ist position:absolute;inset:0 - diff --git a/homeassistant/www/audi-dashboard-version.json b/homeassistant/www/audi-dashboard-version.json index dc1f246..dcb0a42 100644 --- a/homeassistant/www/audi-dashboard-version.json +++ b/homeassistant/www/audi-dashboard-version.json @@ -1 +1 @@ -{"version": 1787003000} +{"version": 1787006000} diff --git a/homeassistant/www/audi-dashboard.css b/homeassistant/www/audi-dashboard.css index 7fced30..dabe736 100644 --- a/homeassistant/www/audi-dashboard.css +++ b/homeassistant/www/audi-dashboard.css @@ -463,8 +463,26 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } /* Der Außenabstand sitzt am Raster, nicht an den Kacheln - sonst wird er im 2×2-Raster (vHome) zum zusätzlichen internen Zeilenabstand und der Abstand innerhalb der Gruppe wäre größer als der zur nächsten. */ -.duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin-bottom: var(--sp-5); } -.duo .tile { margin-bottom: 0; } +/* Übersicht: "Nächster Service" als flache, tippbare Kennzahl, darunter der + Kilometerstand als Wertezeile (siehe Kommentar in vHome()). Der Block sitzt + in einer .tile.flat, hat also keinen eigenen Kachelhintergrund - deshalb + sitzt der Chevron bündig am oberen rechten Rand statt auf --sp-5 eingerückt + wie sonst in einer gepolsterten Kachel. */ +.serviceblock { + position: relative; + display: block; width: 100%; + border: 0; background: none; padding: 0; + font-family: inherit; text-align: left; color: inherit; cursor: pointer; +} +.serviceblock > .go { top: 3px; right: 0; } +/* Beschriftung nie unter dem Chevron hindurchlaufen lassen */ +.serviceblock > .label:first-child { padding-right: 24px; } +.servicezeile { + display: flex; align-items: baseline; justify-content: space-between; + gap: var(--sp-4); + border-top: 1px solid var(--line); + margin-top: 14px; padding-top: 13px; +} .badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .badge img { height: 18px; width: auto; display: none; }