diff --git a/AGENTS.md b/AGENTS.md index 916a6f5..c939e04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2568,10 +2568,9 @@ page is the old flat-list style filtered to just that group (`vTuerenKlappen()`, zugeordnet" warning about `Front left door`/`Front left window`, present before this change, dismissed via the existing "Trotzdem speichern" override). After saving: "Fahrzeug nicht verriegelt" showed red (the demo lock entities report unlocked — correct, not a bug), "Türen und Klappen geschlossen" and - "Fenster und Dach geschlossen" showed green, **"Kein Licht" showed green** — the exact wording asked - for. Clicked the chevron on row 2: navigated to "Türen und Klappen" showing all four doors, Kofferraum, - and Motorhaube individually, all green "zu" — confirmed the detail page and the "Kofferraum" relabel - both work. + "Fenster und Dach geschlossen" showed green, "Licht ausgeschaltet" showed green. Clicked the chevron on + row 2: navigated to "Türen und Klappen" showing all four doors, Kofferraum, and Motorhaube individually, + all green "zu" — confirmed the detail page and the "Kofferraum" relabel both work. Manifest bumped to `2026.8.24.8`. `npm run ota` rerun (`scripts/ota-paket.ps1 -Bauen`): picked up `2026.8.24.8` automatically from the manifest, built clean, `bundle.zip` (233.244 Bytes) + sha256 + @@ -2579,6 +2578,109 @@ Manifest bumped to `2026.8.24.8`. `npm run ota` rerun (`scripts/ota-paket.ps1 -B triggered a bundle rebuild yet. `Installieren.cmd` picks the bundle up automatically; no separate step needed for a self-update-based deployment. +**Same-day follow-up from live owner feedback (2026.8.24.9):** +- **"Kein Licht" reverted to "Licht ausgeschaltet"** — the owner's original wording was right; my + interpretation of their earlier parenthetical note was wrong. Fixed in both frontends. +- **Locks simplified to one sensor**: dropped `HECKKLAPPE_SCHLOSS_SENSOR`/`HAUBE_SCHLOSS_SENSOR` + entirely (owner: central locking means individual locks can't be operated separately, so checking + them adds nothing), and `TUERSCHLOSS_SENSOREN` (4 positions) collapsed to a single + `TUERSCHLOSS_SENSOR` — the driver's door lock stands for the whole vehicle, same reasoning. `"Fahrzeug + verriegelt"` is now one sensor check, not six. No frontend code changes needed for this — both + `sicherheitsGruppe()`/`gruppenErgebnis()` only ever consume the flat, `gruppe`-tagged list generically, + never reference specific sensor-role names. +- **Real, previously-undiscovered bug found via the owner reporting "Nächster Service still empty" on a + genuinely blank instance** (Servicebuch cleared AND `erstzulassung` also blank - the owner's own test + container ended up in exactly this state): `naechsterTermin()`'s comparator, `(a, b) => a.ziel - + b.ziel`, silently coerces a `null` `ziel` to `0` (the Unix epoch) in JS arithmetic. A candidate with + genuinely no data (no Servicebuch entry, no vehicle meldung, and for Hauptuntersuchung specifically no + `erstzulassung` to fall back to) therefore sorted as "nearest" ahead of a *different* candidate that + had a real, computed date — because epoch-1970 always beats any real future date arithmetically. This + starved out Ölwechsel/Inspektion even when section M's meldung-fallback had correctly computed a real + number for them; the tile just showed "–" under "Hauptuntersuchung" instead. Not a regression from + section M — this comparator predates it and was always latent, just needed a candidate with a + genuinely absent `ziel` to surface. Fixed by filtering out `ziel === null` candidates before sorting, + returning `null` only when literally none of the three have a usable date (matching `vHome()`'s + existing, previously-dead `if (!t)` "kein Eintrag im Servicebuch" branch, which can now actually be + reached). companion-app's `naechsterService()` was checked and confirmed unaffected: it only ever + pushes a candidate when a real computed value exists (no null-`datum` candidates ever enter the array) + and sorts ISO date *strings* via `localeCompare()`, not raw arithmetic — structurally immune to this + class of bug. +- **Root cause of the browser not showing my fixes at first**: `audi-dashboard-app.js` is fetched by the + frontend with a cache-busting `?v=` query string. Editing the file on disk without + also bumping the manifest version left the URL unchanged, so the browser kept serving a cached copy + (confirmed via `performance.getEntriesByType('resource')` showing `transferSize: 0`) even across a + forced navigation and a hard-reload keystroke. Bumping the version forced a real refetch. Worth + remembering for any future panel-JS-only fix: a version bump isn't just the project's documentation + rule, it's load-bearing for actually seeing the change in a running browser tab. + +Manifest bumped to `2026.8.24.9` for this follow-up round. + +**A second same-day follow-up round (2026.8.24.10 / .11), more live owner feedback:** +- **Locks simplified further to a single sensor**: `TUERSCHLOSS_SENSOREN` (4 positions) collapsed to + one `TUERSCHLOSS_SENSOR` — owner: the central locking system locks/unlocks every door together, an + individual lock can't be operated separately, so the driver's door alone represents the whole + vehicle's lock state. "Fahrzeug verriegelt" is now exactly one sensor check. No frontend changes + needed (same reason as the Kofferraum/Motorhauben-Schloss removal above: both grouping helpers only + ever consume the flat, `gruppe`-tagged list, never a specific sensor-role name). +- **`vSicherheit()`'s route title renamed from "Sicherheit" to "Fahrzeugstatus"** in the panel, matching + what companion-app's `navigation.ts` already called this page (`TITEL.sicherheit`) — the two frontends + had drifted apart on this one label. The `FELDER` catalog's `"sicherheit"` *sensor-role group* heading + (Setup-Menü section, a different concept entirely — sensor categorization, not a page name) is + untouched. +- **A real, previously-undiscovered bug in `naechsterTermin()` (panel), found from the owner reporting + "I deleted everything in Servicebuch, why isn't Ölwechsel shown on Übersicht?"**: the sort comparator + `(a, b) => a.ziel - b.ziel` coerces a `null` `ziel` to `0` (the Unix epoch) under JS arithmetic. On + this owner's test container, both the Servicebuch *and* `erstzulassung` ended up empty at once, so the + Hauptuntersuchung candidate's `ziel` was genuinely `null` (no entry, no `erstzulassung` fallback + either) — and epoch-1970 sorts before any real future date, so HU won "nearest" over Ölwechsel even + though section M's meldung-fallback had correctly computed a real number for Ölwechsel. The tile + rendered "–" under "Hauptuntersuchung" instead of the real Ölwechsel figure. Not a regression from + section M — this comparator predates it and was always latent, just needed a candidate with a + genuinely absent `ziel` to expose it. Fixed by filtering out `ziel === null` candidates before + sorting, returning `null` only when literally none of the three have a usable date (the existing + `if (!t)` "kein Eintrag im Servicebuch" branch in `vHome()` can now actually be reached — it never + could be before, since `termine()` always returns exactly three entries and the old comparator always + produced *some* truthy result). companion-app's `naechsterService()` was checked and confirmed + structurally immune: it only ever pushes a candidate when a real computed value exists, and sorts ISO + date *strings* with `localeCompare()`, never raw arithmetic on a possibly-`null` value. +- **"Licht ausgeschaltet" reverted back from "Kein Licht"** — the owner's original wording was right; + my read of their earlier parenthetical was wrong. Fixed in both frontends' idle/ok-state label. +- **Cache-busting gotcha hit again, worse this time**: after the `naechsterTermin()` fix, the browser + kept showing the *old* broken behavior even after a forced navigation **and** a hard-reload keystroke + (`ctrl+shift+r`) — `performance.getEntriesByType('resource')` still showed `transferSize: 0` for + `audi-dashboard-app.js` afterward, meaning even a hard reload didn't bypass this particular cache. + Only bumping the manifest version (changing the `?v=` query string HA embeds when registering the + panel's script URL) forced a real refetch. Confirmed this isn't just a documentation nicety: **any** + panel-JS-only edit needs a version bump to actually be visible in a running browser tab, full stop — + a hard reload is not a reliable substitute. +- **Wide/desktop layout headline fixed** (`audi-dashboard-ios.css`, `@container (min-width:860px)` + block): sub-pages reached via the back arrow (Standort, Service, Fahrzeugstatus, Identität und + Technik, everything with `.back.on`) were rendering their title at `font-size:26px` — the owner + described this as "bold" in wide/desktop mode versus "not bold, centered" on the phone layout. There + is no actual `font-weight` bump anywhere in this stylesheet (`.title` is `font-weight:400` + everywhere, confirmed by grepping the whole file) — the 26px size alone, against an app that mostly + uses weight-300 body text (see `VERSIONIERUNG.md`/working-conventions "font weights 300/400 only"), + reads as heavy at a glance even at the *same* weight as every other title. This also violates the iOS + Human Interface Guidelines pattern this stylesheet otherwise deliberately follows: a "large title" + belongs to root/tab pages (`.title{font-size:34px}`, unscoped by `.back.on`, left untouched — the + owner didn't flag `Mein Audi`'s own big top-level title as wrong), while pushed/detail pages keep a + small, standard title regardless of screen size. Fixed by dropping the back-button-page override to + `font-size:17px` — the exact same value already used for these same pages in the narrow/phone layout + (`.phone:has(.back.on) .title` at the file's un-scoped, phone-default rule) — for genuine + cross-breakpoint consistency, not just a smaller number. Left `text-align:left` as-is: that's a + property of the wide layout's sidebar+column grid (`.topbar{justify-content:flex-start}`, + `.head{align-items:flex-start}`, both still scoped to `.back.on` and untouched), consistent with how + the *also-left-aligned* top-level large titles behave in this same layout — normal desktop-app + convention, not the trait the owner was actually flagging. Verified live at an actual `1280×800` + viewport (the container-query breakpoint needs a real wide render, not just the browser tool's + `desktop` preset, which turned out to stay narrow) — before the fix, "Service"/"Standort" rendered + visibly larger than their phone-layout counterparts; after, both match. + +Manifest bumped to `2026.8.24.11` across this round. `npm run ota` rerun once more at the end to keep +the OTA bundle's embedded version aligned with the manifest (no further companion-app source changes +after the "Licht ausgeschaltet" text fix earlier in this same round, so this rebuild only updates the +stamped version number, not the bundle contents). + --- ## Working conventions (observed — keep them) diff --git a/companion-app/src/screens/Fahrzeugstatus.tsx b/companion-app/src/screens/Fahrzeugstatus.tsx index 7426183..f1a6155 100644 --- a/companion-app/src/screens/Fahrzeugstatus.tsx +++ b/companion-app/src/screens/Fahrzeugstatus.tsx @@ -81,7 +81,7 @@ export function Fahrzeugstatus({ geheZu }: { geheZu: (name: SeitenName) => void
- {licht === null ? "Licht unbekannt" : licht ? "Kein Licht" : "Licht eingeschaltet"} + {licht === null ? "Licht unbekannt" : licht ? "Licht ausgeschaltet" : "Licht eingeschaltet"}
diff --git a/custom_components/audi_dashboard/einstellungen.py b/custom_components/audi_dashboard/einstellungen.py index fee6837..a095e07 100644 --- a/custom_components/audi_dashboard/einstellungen.py +++ b/custom_components/audi_dashboard/einstellungen.py @@ -81,12 +81,11 @@ class Sensorzuordnung: HECKKLAPPE_SENSOR: str = "" HAUBE_SENSOR: str = "" - # Schlösser (getrennt von den Öffnungssensoren oben - zu heißt nicht - # verriegelt). Je vier Türschlösser, dazu Heckklappe/Motorhaube einzeln, - # wie bei den Öffnungssensoren. - TUERSCHLOSS_SENSOREN: list[str] = field(default_factory=list) - HECKKLAPPE_SCHLOSS_SENSOR: str = "" - HAUBE_SCHLOSS_SENSOR: str = "" + # Türschloss (getrennt von den Öffnungssensoren oben - zu heißt nicht + # verriegelt). Nur die Fahrertür: die Zentralverriegelung schließt immer + # alle Türen gemeinsam, ein einzelnes Türschloss lässt sich nicht separat + # bedienen - ihr Zustand gilt fürs ganze Fahrzeug (Nutzer-Entscheidung). + TUERSCHLOSS_SENSOR: str = "" # Schiebedach (offen/zu) und Standlicht (an/aus) - ergänzen die # Fenster-Gruppe bzw. stehen für sich, siehe _sicherheitscheck() in @@ -173,18 +172,11 @@ FELDER: list[dict] = [ "hinweis": "\"aus\"/off = zu.", "domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False, "stichworte": ["haube", "hood", "bonnet", "motorhaube"]}, - {"key": "TUERSCHLOSS_SENSOREN", "label": "Türschlösser", "gruppe": "sicherheit", - "hinweis": "\"aus\"/off = verriegelt. Für \"Fahrzeug verriegelt\" wichtig.", - "domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": True, "positionen": POSITIONEN, - "pflicht": False, "stichworte": ["türschloss", "schloss", "lock", "verriegelt"]}, - {"key": "HECKKLAPPE_SCHLOSS_SENSOR", "label": "Kofferraum-Schloss", "gruppe": "sicherheit", - "hinweis": "\"aus\"/off = verriegelt.", - "domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False, "pflicht": False, - "stichworte": ["kofferraum", "heckklappe", "tailgate", "schloss", "lock"]}, - {"key": "HAUBE_SCHLOSS_SENSOR", "label": "Motorhauben-Schloss", "gruppe": "sicherheit", - "hinweis": "\"aus\"/off = verriegelt.", - "domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False, "pflicht": False, - "stichworte": ["haube", "hood", "schloss", "lock"]}, + {"key": "TUERSCHLOSS_SENSOR", "label": "Türschloss (Fahrertür)", "gruppe": "sicherheit", + "hinweis": "\"aus\"/off = verriegelt. Die Zentralverriegelung schließt alle Türen gemeinsam - " + "die Fahrertür genügt für \"Fahrzeug verriegelt\".", + "domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False, + "pflicht": False, "stichworte": ["türschloss", "schloss", "lock", "verriegelt", "fahrertür"]}, {"key": "DACH_SENSOR", "label": "Schiebedach", "gruppe": "sicherheit", "hinweis": "\"aus\"/off = zu.", "domains": ["binary_sensor"], "device_classes": ["window", "door", "opening"], "units": [], "liste": False, "pflicht": False, diff --git a/custom_components/audi_dashboard/frontend/app/bundle.json b/custom_components/audi_dashboard/frontend/app/bundle.json index 212b356..4e5d991 100644 --- a/custom_components/audi_dashboard/frontend/app/bundle.json +++ b/custom_components/audi_dashboard/frontend/app/bundle.json @@ -1 +1 @@ -{"version":"2026.8.24.8","sha256":"3eddc79605bc979da46476cf000c2fb501b8bc510148da60b56b0b49b9b603c6","bytes":233244,"gebaut":"2026-08-24T12:43:13Z"} \ No newline at end of file +{"version":"2026.8.24.11","sha256":"c1a85f9a15b434121f4cd19e2729cfbe60ca89757163954f07152483b8b209af","bytes":233245,"gebaut":"2026-08-24T14:58:21Z"} \ No newline at end of file diff --git a/custom_components/audi_dashboard/frontend/app/bundle.zip b/custom_components/audi_dashboard/frontend/app/bundle.zip index d485b28..0692755 100644 Binary files a/custom_components/audi_dashboard/frontend/app/bundle.zip and b/custom_components/audi_dashboard/frontend/app/bundle.zip differ diff --git a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js index 46e88ef..62cdd0e 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js +++ b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js @@ -1177,7 +1177,16 @@ function teaser() { ` : ""}`; } -function naechsterTermin() { return termine().slice().sort((a, b) => a.ziel - b.ziel)[0]; } +// Kandidaten ohne ziel (kein Servicebucheintrag, keine Fahrzeugmeldung, bei +// der Hauptuntersuchung auch keine verwertbare Erstzulassung) müssen VOR dem +// Sortieren raus: (a,b) => a.ziel - b.ziel zwingt null auf 0 (Epoch 1970) und +// liess einen datenlosen Termin dadurch immer als "naechster" gewinnen - +// selbst gegen einen anderen Termin mit echtem, berechnetem Datum. null nur +// dann zurückgeben, wenn wirklich kein einziger Termin ein ziel hat. +function naechsterTermin() { + const mitZiel = termine().filter((t) => t.ziel !== null); + return mitZiel.length ? mitZiel.slice().sort((a, b) => a.ziel - b.ziel)[0] : null; +} // CI-Icon je Serviceart statt eines Wort-Zusatzes ("bis zum Ölwechsel" o.ä.) - // nur Ölwechsel/Inspektion haben eines, die Hauptuntersuchung nicht. Verwendet @@ -1510,9 +1519,6 @@ function vSicherheit() { const fensterDach = sicherheitsGruppe("fenster_dach"); const licht = sicherheitsGruppe("licht"); const sammelText = g === null ? "Zustand nicht vollständig bekannt" : g ? "Fahrzeug ist sicher abgestellt" : "Bitte prüfen"; - // Jede Zeile nennt beim Nutzer-Zustand ("ok") genau die Formulierung, die - // der Nutzer vorgegeben hat - beim Licht ist das "Kein Licht", nicht - // "Licht ausgeschaltet" (das beschreibt nur die Prüfung, nicht den Wert). const zeile = (label, ok, labelBad, labelWarn, route) => { const text = ok === null ? labelWarn : ok ? label : labelBad; const klickbar = !!route; @@ -1534,7 +1540,7 @@ function vSicherheit() { ${zeile("Fahrzeug verriegelt", verriegelt.ok, "Fahrzeug nicht verriegelt", "Verriegelung unbekannt")} ${zeile("Türen und Klappen geschlossen", tuerenKlappen.ok, "Türen oder Klappen offen", "Türen/Klappen unbekannt", "tuerenklappen")} ${zeile("Fenster und Dach geschlossen", fensterDach.ok, "Fenster oder Dach offen", "Fenster/Dach unbekannt")} - ${zeile("Kein Licht", licht.ok, "Licht eingeschaltet", "Licht unbekannt")} + ${zeile("Licht ausgeschaltet", licht.ok, "Licht eingeschaltet", "Licht unbekannt")} `; } @@ -3866,7 +3872,7 @@ function render() { else if (route.name === "reifen") { head = ["Fahrzeug", "Reifen"]; v.innerHTML = vReifen(); } else if (route.name === "einst") { head = ["App", "Einstellungen"]; v.innerHTML = vEinst(); } else if (route.name === "ident") { head = ["Fahrzeug", "Identität und Technik"]; v.innerHTML = vIdent(); } - else if (route.name === "sicherheit") { head = ["Fahrzeug", "Sicherheit"]; v.innerHTML = vSicherheit(); } + else if (route.name === "sicherheit") { head = ["Fahrzeug", "Fahrzeugstatus"]; v.innerHTML = vSicherheit(); } else if (route.name === "tuerenklappen") { head = ["Fahrzeug", "Türen und Klappen"]; v.innerHTML = vTuerenKlappen(); } else if (route.name === "vers") { head = ["Fahrzeug", "Versicherung/Steuer"]; v.innerHTML = vVers(); } else if (route.name === "beitrag") { head = ["Versicherung", "Beitrag anpassen"]; v.innerHTML = vBeitrag(); } diff --git a/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css b/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css index a97c020..68f2f0a 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css +++ b/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css @@ -275,7 +275,14 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo .topbar{grid-column:2;grid-row:1;padding:38px 44px 8px;max-width:860px} .phone:has(.back.on) .topbar{grid-column:2;grid-row:1;padding:34px 44px 8px;justify-content:flex-start;max-width:860px} .phone:has(.back.on) .head{align-items:flex-start} - .phone:has(.back.on) .title{font-size:26px;text-align:left} + /* Unterseiten (mit Zurueck-Pfeil, z. B. Standort/Service) behalten die + normale Titelgroesse - nur die obersten Tab-Seiten bekommen den grossen + "Large Title"-Auftritt (.title{font-size:34px} unten). Vorher wurden + Unterseiten hier auf 26px vergroessert, was neben dem sonst durchgaengig + 300er-Fliesstext optisch wie Fettschrift wirkte, obwohl font-weight + ueberall 400 bleibt - keine echte Auszeichnung, nur eine Grosse, die zu + einer Unterseite nicht passt. */ + .phone:has(.back.on) .title{font-size:17px;text-align:left} .title{font-size:34px} .ptr{grid-column:2;grid-row:2;max-width:860px} main#view{--seitenrand:44px;grid-column:2;grid-row:3;padding:8px var(--seitenrand) 48px;max-width:860px} diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index dc1e023..084e72a 100644 --- a/custom_components/audi_dashboard/manifest.json +++ b/custom_components/audi_dashboard/manifest.json @@ -1,7 +1,7 @@ { "domain": "audi_dashboard", "name": "Audi Dashboard", - "version": "2026.8.24.8", + "version": "2026.8.24.11", "documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md", "issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues", "codeowners": ["@paul"], diff --git a/custom_components/audi_dashboard/veroeffentlichung.py b/custom_components/audi_dashboard/veroeffentlichung.py index 998d577..5a7208c 100644 --- a/custom_components/audi_dashboard/veroeffentlichung.py +++ b/custom_components/audi_dashboard/veroeffentlichung.py @@ -124,15 +124,10 @@ def _sicherheitscheck(hass: HomeAssistant, werte: Sensorzuordnung) -> list[dict] ): w = zustand_oder_none(hass, sensor) eintraege.append({"label": label, "ok": None if w is None else w == "off", "gruppe": "tueren_klappen"}) - for pos, sensor in zip(POSITIONEN, werte.TUERSCHLOSS_SENSOREN): - w = zustand_oder_none(hass, sensor) - eintraege.append({"label": f"Türschloss {pos}", "ok": None if w is None else w == "off", "gruppe": "verriegelt"}) - for label, sensor in ( - ("Kofferraum-Schloss", werte.HECKKLAPPE_SCHLOSS_SENSOR), - ("Motorhauben-Schloss", werte.HAUBE_SCHLOSS_SENSOR), - ): - w = zustand_oder_none(hass, sensor) - eintraege.append({"label": label, "ok": None if w is None else w == "off", "gruppe": "verriegelt"}) + # Nur die Fahrertür: die Zentralverriegelung schließt alle Türen gemeinsam, + # ihr Zustand gilt fürs ganze Fahrzeug (siehe TUERSCHLOSS_SENSOR). + w = zustand_oder_none(hass, werte.TUERSCHLOSS_SENSOR) + eintraege.append({"label": "Türschloss", "ok": None if w is None else w == "off", "gruppe": "verriegelt"}) w = zustand_oder_none(hass, werte.DACH_SENSOR) eintraege.append({"label": "Dach", "ok": None if w is None else w == "off", "gruppe": "fenster_dach"}) w = zustand_oder_none(hass, werte.LICHT_SENSOR)