diff --git a/AGENTS.md b/AGENTS.md index c0da38a..6cea913 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1037,6 +1037,51 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed left:auto` (added last round to right-align it under its old block-flow parent) was removed since `.radkopf-row`'s flex layout does that job now. Deployed to `audi_ha_test`, confirmed live via screenshot for both Sommerräder (mounted) and Winterräder (unmounted) tiles. +- [x] Ninth round the same day (2026-08-16) — "Mein Audi" image-cycle tap, still broken after the + seventh round's `bildWeiter()` crash fix: reproduced with a genuine mouse click (not a JS + `.click()` call) via screenshot and found the actual cause was one level upstream. This docker + test container has zero uploaded car photos, so every gallery image is in `bild-fehlt` state, + and `.bildbox.bild-fehlt img{display:none}` makes the `` itself un-hit-testable + — a real tap lands on the `.platzhalter` div instead, which the old handler + (`e.target.id === "carimg"`) never matched, so nothing happened. First fix wrapped the gallery + image in a `data-bildgalerie` container that opened the upload picker when empty and advanced + when a photo was loaded; the user then clarified uploads should stay exclusively in Einstellungen + ("Bild der Übersicht") — Mein Audi should only ever cycle. Settled on: the wrapper stays (needed + so taps land on something clickable regardless of `bild-fehlt` state) but always calls + `bildWeiter()`, no upload branch. Verified live with a real click (screenshot → click → recheck + `.dots`): the active dot correctly advances, no console errors beyond this environment's + pre-existing service-worker/404 noise. + Also this round: replaced the "Farbskala Ladezustand (AGM, Ruhespannung)" text-only label above + the battery voltage color scale with a small battery icon + "Ruhespannung" (new `.bv-skala-kopf` + flex row, same geometry as the header's `.sync` icon+text pattern). First attempt hand-drew a + generic outline battery glyph since no "battery" icon exists in this repo's local CI subset + (`design/assets/icons/`); the user then supplied the real `battery-12v-l.svg`/`battery-12v-s.svg` + files from the actual Audi CI icon set (not something this session has fetch access to — asked + the user rather than guessing a URL or fabricating an icon and calling it authentic). Added both + as `CI.battery12vL`/`CI.battery12vS` (verbatim paths, `l` on the 48-grid, `s` on the 24-grid, + matching the existing `poi-l`/`poi-s` size-variant convention); only the `s` variant is used here + via `ciSVG()` since this is a small 15px inline label icon. + Deployed to `audi_ha_test`, confirmed live via screenshot. + Also this round (user then supplied the real `edit-l.svg`/`edit-s.svg` too): added a manual + km-correction control to the Reifen tiles, per explicit request — a small pencil icon (Audi CI + `edit-s`, new `CI.editS`/`CI.editL`) to the left of each Sommerräder/Winterräder km figure, "the + counter shall add continuously to these km even if the user changes the km". The tricky part + was persistence: `configCarZuProfil()` *deliberately* strips `km` before every profile write + (see its own comment - km is "vom Backend fortgeschrieben", a stale browser copy must never + clobber a since-elapsed automatic increment), so simply mutating `CAR.reifen[k].km` and calling + `profilSpeichern()` like every other Reifen field does would silently drop the edit. Added a + dedicated backend service instead, `audi_dashboard_reifen_km_setzen(satz, km)` in + `reifenzaehler.py`, that only overwrites the stored counter itself and leaves + `reifen.referenz_odo_km` (the last-processed odometer reference) untouched - the existing + `kilometerstand_geaendert()` trigger always reads the counter fresh off disk before adding the + next delta, so continuing to accumulate from the corrected value required no other backend + change at all. New `kmOffen` state (which satz's correction field is open, mirroring the + existing `nmOffen`/`bildMenuOffen` single-open-popup pattern) plus `data-kmauf`/ + `data-kmspeichern` handlers, styled like the existing `.montiert`/`.zahnrad` controls (44×44pt + invisible tap target via `::before`). Verified end-to-end: edited Sommerräder from 150→4200 km, + confirmed the pyscript service ran via the container's HA log, then did a full page reload + (forces a real backend re-fetch, not just optimistic local UI state) and the corrected value + was still there. - [ ] 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/pyscript/reifenzaehler.py b/homeassistant/installationspaket/pyscript/reifenzaehler.py index 1615d5c..3ca799a 100644 --- a/homeassistant/installationspaket/pyscript/reifenzaehler.py +++ b/homeassistant/installationspaket/pyscript/reifenzaehler.py @@ -105,3 +105,37 @@ def audi_dashboard_reifen_wechseln(satz=None): return _fortschreiben_und_veroeffentlichen(_aktueller_km(), neuer_aktiv=satz) log.info(f"audi_dashboard: aktiver Reifensatz auf '{satz}' umgeschaltet") + + +@service +def audi_dashboard_reifen_km_setzen(satz=None, km=None): + """Manuelle Korrektur des gefahrenen Kilometerstands eines Reifensatzes + (Editier-Symbol neben der km-Anzeige in der App). Überschreibt nur den + gespeicherten Zähler selbst - referenz_odo_km (der zuletzt verarbeitete + Fahrzeug-Kilometerstand) bleibt unverändert, damit die nächste + Fortschreibung (kilometerstand_geaendert oben) weiterhin korrekt von hier + aus weiterzählt, statt auf den neuen Wert einen erneuten Delta seit dem + alten Stand aufzuschlagen. configCarZuProfil() im Frontend schreibt km nie + selbst mit (siehe Kommentar dort) - genau deshalb braucht es diesen + eigenen Service statt eines einfachen Profil-Überschreibens.""" + if satz not in ("sommer", "winter"): + log.warning(f"audi_dashboard: ungültiger Reifensatz '{satz}'") + return + try: + km = round(float(km), 1) + except (TypeError, ValueError): + log.warning(f"audi_dashboard: ungültiger km-Wert '{km}'") + return + if km < 0: + log.warning(f"audi_dashboard: negativer km-Wert '{km}' abgelehnt") + return + p = profil.profil_lesen() + if p is None: + return + reifen = p.setdefault("reifen", {}) + saetze = reifen.setdefault("saetze", {}) + saetze.setdefault(satz, {})["km"] = km + profil.profil_schreiben(p) + frontend_veroeffentlichung.profil_veroeffentlichen() + _zustaende_veroeffentlichen(p) + log.info(f"audi_dashboard: km-Stand '{satz}' manuell auf {km} gesetzt") diff --git a/homeassistant/installationspaket/www/audi-dashboard-app.js b/homeassistant/installationspaket/www/audi-dashboard-app.js index 9e1cf7a..8f479e1 100644 --- a/homeassistant/installationspaket/www/audi-dashboard-app.js +++ b/homeassistant/installationspaket/www/audi-dashboard-app.js @@ -255,8 +255,17 @@ function carImg(modus) { return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein"); } const info = bildInfo(bild); - return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg") + - `
${BILDER.map((_, i) => ``).join("")}
`; + /* data-bildgalerie: ein Tipp irgendwo auf die Kachel (Foto oder Platzhalter) + schaltet zum naechsten Bild weiter - Hochladen/Ersetzen/Loeschen bleibt + bewusst allein den Einstellungen vorbehalten (Bild der Übersicht), + deshalb hier kein data-bildupload/data-bildklick, nur bildWeiter(). + Der Wrapper ist noetig, weil das selbst bei fehlendem Foto per + .bildbox.bild-fehlt img{display:none} unsichtbar und damit unklickbar + ist - der bisherige Handler (id="carimg") griff dann ins Leere. */ + return `
+ ${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg")} +
+
${BILDER.map((_, i) => ``).join("")}
`; } /* Bildmarke des Kraftstoffanbieters (Shell). Anders als bei den Fahrzeug- fotos ist hier kein generischer Platzhalter-Rahmen sinnvoll, weil das Logo @@ -947,6 +956,14 @@ const CI = { upload: '', fuelRange: '', parking: '', + /* battery-12v-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert, + verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */ + battery12vL: '', + battery12vS: '', + /* edit-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert, + verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */ + editL: '', + editS: '', }; function ciSVG(pfad, px = 16) { return ``; @@ -1481,7 +1498,10 @@ function vBatterieverlauf() { ` : ` ${leerText} `} - Farbskala Ladezustand (AGM, Ruhespannung) +
+ ${ciSVG(CI.battery12vS, 15)} + Ruhespannung +
@@ -2109,6 +2129,9 @@ let nmOffen = false; let einrichtenOffen = false; let bildMenuOffen = null; let smartdealPopupOffen = false; +// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen +// hat (Editier-Symbol neben der km-Anzeige) - null wenn keins offen ist. +let kmOffen = null; /* --------------------------------------------------------- Setup-Popup Sensor-zu-Entität-Zuordnung (Einstellungen -> Fahrzeug einrichten -> @@ -2335,12 +2358,27 @@ function vReifen() {
${satzLabel} -
${t.km != null ? de(t.km) : "–"}km
+
+ + ${t.km != null ? de(t.km) : "–"}km +
${bildMitPlatzhalter(`/local/bilder/${radDatei}?v=${bildVersion}`, radDatei, radLabel, "radbild")}
+ ${kmOffen === k ? ` +
+
+ km
+ + Korrigiert nur den bisher gefahrenen Stand - weitere Fahrten mit diesem + Satz zählen ab hier weiter dazu. + + +
` : ""} ${bildMenuOffen === radDatei ? `
@@ -3583,7 +3621,7 @@ function ereignisseVerdrahten() { smartdealPopupOffen = false; profilSpeichern(); render(); return; } - if (e.target.id === "carimg") { bildWeiter(); return; } + if (e.target.closest("[data-bildgalerie]")) { bildWeiter(); return; } if (e.target.closest("[data-sbneu]")) { CAR.service.buch.unshift({ datum: dedat(heute()), km: CAR.odoBekannt ? CAR.odo : 0, art: SERVICE_ARTEN[0], werkstatt: CAR.service.autohaus.name, kosten: null, arbeiten: "" }); entwurfIndex = 0; go("sbuch", "0"); return; @@ -3598,6 +3636,24 @@ function ereignisseVerdrahten() { if (!isNaN(wert) && wert > 0) { CAR.reifen.anzugsmoment = wert; profilSpeichern(); } nmOffen = false; render(); return; } + const kmauf = e.target.closest("[data-kmauf]"); + if (kmauf) { kmOffen = kmOffen === kmauf.dataset.kmauf ? null : kmauf.dataset.kmauf; render(); return; } + const kmspeichern = e.target.closest("[data-kmspeichern]"); + if (kmspeichern) { + const satz = kmspeichern.dataset.kmspeichern; + const wert = parseFloat($(`#km-${satz}`).value); + if (!isNaN(wert) && wert >= 0) { + const km = Math.round(wert * 10) / 10; + CAR.reifen[satz].km = km; + // km wird bewusst nicht ueber profilSpeichern() geschrieben (siehe + // Kommentar in configCarZuProfil) - eigener Service, der nur den + // Zaehler selbst setzt und referenz_odo_km unangetastet laesst, damit + // die naechste automatische Fortschreibung ab diesem neuen Stand + // weiterzaehlt statt ihn zu ueberschreiben. + serviceRufen("audi_dashboard_reifen_km_setzen", { satz, km }); + } + kmOffen = null; render(); return; + } if (e.target.closest("[data-neu]")) { neuOffen = !neuOffen; if (belegEntwurf && belegEntwurf.praefix === "neuTank") belegEntwurf = null; diff --git a/homeassistant/installationspaket/www/audi-dashboard.css b/homeassistant/installationspaket/www/audi-dashboard.css index 82cb2e6..af29d11 100644 --- a/homeassistant/installationspaket/www/audi-dashboard.css +++ b/homeassistant/installationspaket/www/audi-dashboard.css @@ -340,6 +340,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } color: var(--fg); display: block; } +/* Icon + Text statt reinem Fließtext-Label (z.B. Batterie-Symbol vor + "Ruhespannung") - dieselbe Zeilen-Geometrie wie .sync in der Kopfzeile. */ +.bv-skala-kopf { display: flex; align-items: center; gap: 6px; } + /* A4: Messwerte erben nie mehr das uppercase der Label-Klasse. */ .wert { font-size: 14px; @@ -907,6 +911,21 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } .radkopf-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); } .radkopf { display: flex; flex-direction: column; } +/* Editier-Symbol links neben der km-Anzeige (Audi-CI edit-s) - oeffnet das + Korrekturfeld fuer den manuell gepflegten Kilometerstand des Reifensatzes. + 44x44pt Tippflaeche per unsichtbarem ::before wie bei .montiert/.zahnrad, + optisch bleibt nur das kleine Symbol sichtbar. */ +.km-edit { + position: relative; + display: inline-flex; align-items: center; justify-content: center; + width: 22px; height: 22px; + border: none; background: none; color: var(--fg3); + padding: 0; cursor: pointer; flex: 0 0 auto; +} +.km-edit::before { content: ""; position: absolute; inset: -11px; } +.km-edit:hover { color: var(--fg); } +.km-edit:active { color: var(--fg); transform: scale(.92); } + /* Kompakte Pille statt voller Breite (Apple-HIG-Pull-down-Maß): normale Flusslage unter der Kachel-Überschrift/km, nicht mehr über dem Foto überlagert (das saß nur dort, solange Überschrift+km diesen Platz noch diff --git a/homeassistant/pyscript/reifenzaehler.py b/homeassistant/pyscript/reifenzaehler.py index 1615d5c..3ca799a 100644 --- a/homeassistant/pyscript/reifenzaehler.py +++ b/homeassistant/pyscript/reifenzaehler.py @@ -105,3 +105,37 @@ def audi_dashboard_reifen_wechseln(satz=None): return _fortschreiben_und_veroeffentlichen(_aktueller_km(), neuer_aktiv=satz) log.info(f"audi_dashboard: aktiver Reifensatz auf '{satz}' umgeschaltet") + + +@service +def audi_dashboard_reifen_km_setzen(satz=None, km=None): + """Manuelle Korrektur des gefahrenen Kilometerstands eines Reifensatzes + (Editier-Symbol neben der km-Anzeige in der App). Überschreibt nur den + gespeicherten Zähler selbst - referenz_odo_km (der zuletzt verarbeitete + Fahrzeug-Kilometerstand) bleibt unverändert, damit die nächste + Fortschreibung (kilometerstand_geaendert oben) weiterhin korrekt von hier + aus weiterzählt, statt auf den neuen Wert einen erneuten Delta seit dem + alten Stand aufzuschlagen. configCarZuProfil() im Frontend schreibt km nie + selbst mit (siehe Kommentar dort) - genau deshalb braucht es diesen + eigenen Service statt eines einfachen Profil-Überschreibens.""" + if satz not in ("sommer", "winter"): + log.warning(f"audi_dashboard: ungültiger Reifensatz '{satz}'") + return + try: + km = round(float(km), 1) + except (TypeError, ValueError): + log.warning(f"audi_dashboard: ungültiger km-Wert '{km}'") + return + if km < 0: + log.warning(f"audi_dashboard: negativer km-Wert '{km}' abgelehnt") + return + p = profil.profil_lesen() + if p is None: + return + reifen = p.setdefault("reifen", {}) + saetze = reifen.setdefault("saetze", {}) + saetze.setdefault(satz, {})["km"] = km + profil.profil_schreiben(p) + frontend_veroeffentlichung.profil_veroeffentlichen() + _zustaende_veroeffentlichen(p) + log.info(f"audi_dashboard: km-Stand '{satz}' manuell auf {km} gesetzt") diff --git a/homeassistant/www/audi-dashboard-app.js b/homeassistant/www/audi-dashboard-app.js index 9e1cf7a..8f479e1 100644 --- a/homeassistant/www/audi-dashboard-app.js +++ b/homeassistant/www/audi-dashboard-app.js @@ -255,8 +255,17 @@ function carImg(modus) { return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein"); } const info = bildInfo(bild); - return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg") + - `
${BILDER.map((_, i) => ``).join("")}
`; + /* data-bildgalerie: ein Tipp irgendwo auf die Kachel (Foto oder Platzhalter) + schaltet zum naechsten Bild weiter - Hochladen/Ersetzen/Loeschen bleibt + bewusst allein den Einstellungen vorbehalten (Bild der Übersicht), + deshalb hier kein data-bildupload/data-bildklick, nur bildWeiter(). + Der Wrapper ist noetig, weil das selbst bei fehlendem Foto per + .bildbox.bild-fehlt img{display:none} unsichtbar und damit unklickbar + ist - der bisherige Handler (id="carimg") griff dann ins Leere. */ + return `
+ ${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg")} +
+
${BILDER.map((_, i) => ``).join("")}
`; } /* Bildmarke des Kraftstoffanbieters (Shell). Anders als bei den Fahrzeug- fotos ist hier kein generischer Platzhalter-Rahmen sinnvoll, weil das Logo @@ -947,6 +956,14 @@ const CI = { upload: '', fuelRange: '', parking: '', + /* battery-12v-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert, + verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */ + battery12vL: '', + battery12vS: '', + /* edit-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert, + verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */ + editL: '', + editS: '', }; function ciSVG(pfad, px = 16) { return ``; @@ -1481,7 +1498,10 @@ function vBatterieverlauf() { ` : ` ${leerText} `} - Farbskala Ladezustand (AGM, Ruhespannung) +
+ ${ciSVG(CI.battery12vS, 15)} + Ruhespannung +
@@ -2109,6 +2129,9 @@ let nmOffen = false; let einrichtenOffen = false; let bildMenuOffen = null; let smartdealPopupOffen = false; +// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen +// hat (Editier-Symbol neben der km-Anzeige) - null wenn keins offen ist. +let kmOffen = null; /* --------------------------------------------------------- Setup-Popup Sensor-zu-Entität-Zuordnung (Einstellungen -> Fahrzeug einrichten -> @@ -2335,12 +2358,27 @@ function vReifen() {
${satzLabel} -
${t.km != null ? de(t.km) : "–"}km
+
+ + ${t.km != null ? de(t.km) : "–"}km +
${bildMitPlatzhalter(`/local/bilder/${radDatei}?v=${bildVersion}`, radDatei, radLabel, "radbild")}
+ ${kmOffen === k ? ` +
+
+ km
+ + Korrigiert nur den bisher gefahrenen Stand - weitere Fahrten mit diesem + Satz zählen ab hier weiter dazu. + + +
` : ""} ${bildMenuOffen === radDatei ? `
@@ -3583,7 +3621,7 @@ function ereignisseVerdrahten() { smartdealPopupOffen = false; profilSpeichern(); render(); return; } - if (e.target.id === "carimg") { bildWeiter(); return; } + if (e.target.closest("[data-bildgalerie]")) { bildWeiter(); return; } if (e.target.closest("[data-sbneu]")) { CAR.service.buch.unshift({ datum: dedat(heute()), km: CAR.odoBekannt ? CAR.odo : 0, art: SERVICE_ARTEN[0], werkstatt: CAR.service.autohaus.name, kosten: null, arbeiten: "" }); entwurfIndex = 0; go("sbuch", "0"); return; @@ -3598,6 +3636,24 @@ function ereignisseVerdrahten() { if (!isNaN(wert) && wert > 0) { CAR.reifen.anzugsmoment = wert; profilSpeichern(); } nmOffen = false; render(); return; } + const kmauf = e.target.closest("[data-kmauf]"); + if (kmauf) { kmOffen = kmOffen === kmauf.dataset.kmauf ? null : kmauf.dataset.kmauf; render(); return; } + const kmspeichern = e.target.closest("[data-kmspeichern]"); + if (kmspeichern) { + const satz = kmspeichern.dataset.kmspeichern; + const wert = parseFloat($(`#km-${satz}`).value); + if (!isNaN(wert) && wert >= 0) { + const km = Math.round(wert * 10) / 10; + CAR.reifen[satz].km = km; + // km wird bewusst nicht ueber profilSpeichern() geschrieben (siehe + // Kommentar in configCarZuProfil) - eigener Service, der nur den + // Zaehler selbst setzt und referenz_odo_km unangetastet laesst, damit + // die naechste automatische Fortschreibung ab diesem neuen Stand + // weiterzaehlt statt ihn zu ueberschreiben. + serviceRufen("audi_dashboard_reifen_km_setzen", { satz, km }); + } + kmOffen = null; render(); return; + } if (e.target.closest("[data-neu]")) { neuOffen = !neuOffen; if (belegEntwurf && belegEntwurf.praefix === "neuTank") belegEntwurf = null; diff --git a/homeassistant/www/audi-dashboard.css b/homeassistant/www/audi-dashboard.css index 82cb2e6..af29d11 100644 --- a/homeassistant/www/audi-dashboard.css +++ b/homeassistant/www/audi-dashboard.css @@ -340,6 +340,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } color: var(--fg); display: block; } +/* Icon + Text statt reinem Fließtext-Label (z.B. Batterie-Symbol vor + "Ruhespannung") - dieselbe Zeilen-Geometrie wie .sync in der Kopfzeile. */ +.bv-skala-kopf { display: flex; align-items: center; gap: 6px; } + /* A4: Messwerte erben nie mehr das uppercase der Label-Klasse. */ .wert { font-size: 14px; @@ -907,6 +911,21 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } .radkopf-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); } .radkopf { display: flex; flex-direction: column; } +/* Editier-Symbol links neben der km-Anzeige (Audi-CI edit-s) - oeffnet das + Korrekturfeld fuer den manuell gepflegten Kilometerstand des Reifensatzes. + 44x44pt Tippflaeche per unsichtbarem ::before wie bei .montiert/.zahnrad, + optisch bleibt nur das kleine Symbol sichtbar. */ +.km-edit { + position: relative; + display: inline-flex; align-items: center; justify-content: center; + width: 22px; height: 22px; + border: none; background: none; color: var(--fg3); + padding: 0; cursor: pointer; flex: 0 0 auto; +} +.km-edit::before { content: ""; position: absolute; inset: -11px; } +.km-edit:hover { color: var(--fg); } +.km-edit:active { color: var(--fg); transform: scale(.92); } + /* Kompakte Pille statt voller Breite (Apple-HIG-Pull-down-Maß): normale Flusslage unter der Kachel-Überschrift/km, nicht mehr über dem Foto überlagert (das saß nur dort, solange Überschrift+km diesen Platz noch