From 8a751fe4417861c59475f422e0c3f970fd6b59e2 Mon Sep 17 00:00:00 2001 From: Tobi G Date: Sun, 16 Aug 2026 23:53:02 +0200 Subject: [PATCH] Run a full Apple HIG audit; fix silent save failures and popup polish Read Tab Bars, Sidebars, Sheets, Alerts, Action Sheets, Toolbars, Buttons, Pickers, Loading, Feedback, Privacy, and Undo/Redo off developer.apple.com and cross-checked each against the actual code. profilSpeichern() (the shared save path for ~25 fields) had no error handling at any call site, so a failed backend write was an unhandled promise rejection with zero user feedback - fixed centrally, plus the same for serviceRufen(). Also added a spinner to the "Ladt ..." bootstrap screen and gave the Anzugsmoment/km-correction popups the same primary-button styling the Setup popup already used. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 46 ++++++++++++++++++- .../www/audi-dashboard-app.js | 29 ++++++++++-- .../www/audi-dashboard-version.json | 2 +- .../installationspaket/www/audi-dashboard.css | 11 +++++ homeassistant/www/audi-dashboard-app.js | 29 ++++++++++-- homeassistant/www/audi-dashboard-version.json | 2 +- homeassistant/www/audi-dashboard.css | 11 +++++ 7 files changed, 117 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6cea913..a3e5c0a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,13 @@ intended 96x96 square, moved the "Montiert" pill to match, added `color-scheme` popups stop rendering in the browser's default light palette against the dark app, root-caused and fixed a JS crash in the "Mein Audi" image-cycle click handler, merged the separate "Fahrzeugbilder" upload grid into "Bild der Übersicht" (pick a view, tap its photo to upload), and turned the back -arrow from red to the neutral headline color — see section C). This file is the entry point for every new agent +arrow from red to the neutral headline color; a tenth round the same day ran a real full Apple HIG +audit (Navigation/Presentation/Selection & Input/Patterns categories, not just the sixth round's +5-page pass) and fixed the three findings that survived: closed a second, larger silent-failure gap +in `profilSpeichern()` itself (the shared save path for ~25 fields, previously an unhandled promise +rejection with zero user feedback), added a spinner to the "Lädt …" bootstrap screen, and gave the +Anzugsmoment/km-correction popups the same primary-button styling the Setup popup already used — +see section C). This file is the entry point for every new agent session: what this repo is, what is finished, what is missing, and how to work here. Detail lives in the linked documents — this file points, it does not duplicate. @@ -1082,6 +1088,44 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed 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. +- [x] Tenth round the same day (2026-08-16) — a real full Apple HIG audit, not just the sixth + round's 5-page pass: read Tab Bars, Sidebars, Sheets, Alerts, Action Sheets, Toolbars/ + Navigation Bars, Buttons, Pickers, Loading, Feedback, Privacy, and Undo/Redo directly off + developer.apple.com and cross-checked each against the actual code (not just CSS) — most of + the app already held up (5-tab bar, destructive-button placement, 44pt tap targets, + `prefers-reduced-motion` support, confirm-sheet button ordering all already correct). Three + real findings surfaced, all fixed and user-approved via `AskUserQuestion` before implementing: + (1) **A second, larger silent-failure gap than the one already fixed for receipt upload.** + `profilSpeichern()` — the shared save path for roughly 25 editable fields across the app + (Anzugsmoment, service book, SmartDeal, tax, insurance, oil change, pause time, backup + interval, start image, ...) — was `async` with no `try`/`catch` at any of its ~25 call sites, + so a failed backend write was an unhandled promise rejection with zero user feedback; the + popup had already closed and the UI already updated optimistically, so the change looked + saved when it silently wasn't. Wrapped the one call inside `profilSpeichern()` itself in + `try`/`catch`, reusing the existing `hinweis()` error-sheet pattern — one central fix instead + of ~25 call-site patches. `serviceRufen()` (the separate wrapper used by km-correction, manual + trip/fuel entry, tire-set switching, deletions, restart, ...) got the identical treatment. + Verified live by monkey-patching `hass.callService` to reject inside the running app (via + `host.hass.callService = () => Promise.reject(...)`), then triggering a real Anzugsmoment save + through the actual UI: before the fix, the popup silently closed with a stale "Uncaught (in + promise)" console entry and no user-visible sign anything went wrong; after the fix, a + "Speichern fehlgeschlagen" sheet appears with the real error text. A follow-up full reload + confirmed the failed test write never reached the backend (value reverted to the real stored + 120 Nm) — the fix only adds feedback, it doesn't change persistence behavior. + (2) **"Lädt …" bootstrap screen got a progress indicator** (new `.lade-spinner` CSS, a plain + rotating ring) instead of bare static text, per HIG Loading guidance to show something moving + while content loads — notable here because this exact screen has a documented history of + genuinely hanging, so a spinner also helps a user tell "still loading" apart from "stuck". + Respects the existing global `prefers-reduced-motion` override (`*, *::before, *::after` + block already forces `animation-duration:.01ms` app-wide) with no extra code. + (3) **Anzugsmoment and km-correction popups now use `.aktion.primaer` on Speichern**, matching + the Setup and SmartDeal popups, which already distinguished their primary action — these two + were the only popups in the app stacking two visually-identical plain buttons. + All three verified live in `audi_ha_test` (version `1786916894`): spinner CSS confirmed via + `getComputedStyle` (16×16px, `border-radius:50%`, `animation-name:lade-spin`) since a genuine + race-condition-timed screenshot proved too fast to catch reliably; Speichern/Abbrechen + hierarchy confirmed via screenshot on both popups; error-sheet fix confirmed via the + monkey-patch test above. Synced to `installationspaket/`. - [ ] 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 8f479e1..852312b 100644 --- a/homeassistant/installationspaket/www/audi-dashboard-app.js +++ b/homeassistant/installationspaket/www/audi-dashboard-app.js @@ -189,7 +189,17 @@ let PROFIL_ROH = null; // letzter vom Backend gelesener Rohstand, für configCar async function profilSpeichern() { const neuesProfil = configCarZuProfil(PROFIL_ROH); PROFIL_ROH = neuesProfil; - await HASS.callService("pyscript", "audi_dashboard_profil_schreiben", { profil_json: JSON.stringify(neuesProfil) }); + // HIG "Feedback": profilSpeichern() ist der Speicherpfad fuer praktisch + // jedes editierbare Feld im Panel (~25 Aufrufstellen) und wurde bisher an + // keiner davon awaited oder abgefangen - ein Fehlschlag war eine unhandled + // promise rejection ohne jede Rueckmeldung. Zentral hier gefangen, aus + // demselben Grund wie serviceRufen() weiter unten. + try { + await HASS.callService("pyscript", "audi_dashboard_profil_schreiben", { profil_json: JSON.stringify(neuesProfil) }); + } catch (err) { + console.error("audi_dashboard: profilSpeichern", err); + hinweis("Speichern fehlgeschlagen", err && err.message ? err.message : "Die Änderung konnte nicht gespeichert werden."); + } } /* ------------------------------------------------------------- Bilder @@ -2376,7 +2386,7 @@ function vReifen() { text-transform:none;font-size:12.5px;line-height:1.6"> Korrigiert nur den bisher gefahrenen Stand - weitere Fahrten mit diesem Satz zählen ab hier weiter dazu. - + ` : ""} @@ -2415,7 +2425,7 @@ function vReifen() {
Nm
- + ` : ""} @@ -3244,7 +3254,16 @@ function randwischenVerdrahten() { Persistenz über hass.callService im Hintergrund - dieselbe Reihenfolge wie im Prototyp, nur dass jetzt zusätzlich gespeichert wird. */ function serviceRufen(dienst, daten) { - HASS.callService("pyscript", dienst, daten).catch((err) => console.error("audi_dashboard:", dienst, err)); + // HIG "Feedback": ein fehlgeschlagener Aufruf darf nicht nur in der Konsole + // landen - die aufrufende Stelle hat ihr Popup meist schon geschlossen und + // die UI optimistisch aktualisiert, sonst würde die Änderung lautlos + // verloren gehen (derselbe Fehler, der für den Beleg-Upload und das Setup- + // Speichern schon einmal einzeln gefixt wurde - hier zentral für alle + // ~20 Aufrufstellen). + HASS.callService("pyscript", dienst, daten).catch((err) => { + console.error("audi_dashboard:", dienst, err); + hinweis("Aktion fehlgeschlagen", err && err.message ? err.message : "Der Dienst konnte nicht ausgeführt werden."); + }); } function ereignisseVerdrahten() { @@ -4254,7 +4273,7 @@ class AudiDashboardPanel extends HTMLElement {
Ziehen zum Aktualisieren
-
Lädt …
+
Lädt …
`; diff --git a/homeassistant/installationspaket/www/audi-dashboard-version.json b/homeassistant/installationspaket/www/audi-dashboard-version.json index 3adbe44..03c0549 100644 --- a/homeassistant/installationspaket/www/audi-dashboard-version.json +++ b/homeassistant/installationspaket/www/audi-dashboard-version.json @@ -1 +1 @@ -{"version": 1786769000} +{"version": 1786916894} diff --git a/homeassistant/installationspaket/www/audi-dashboard.css b/homeassistant/installationspaket/www/audi-dashboard.css index af29d11..cc29dd8 100644 --- a/homeassistant/installationspaket/www/audi-dashboard.css +++ b/homeassistant/installationspaket/www/audi-dashboard.css @@ -329,6 +329,17 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } display: block; line-height: 1.3; } +/* HIG "Loading": statt reinem Text ein sichtbar laufender Indikator, solange + DATEN_GELADEN false ist - hilft auch, "lädt noch" optisch von "hängt fest" + zu unterscheiden (dieser Bildschirm hatte schon ein echtes Hänger-Problem). */ +.lade-spinner { + width: 16px; height: 16px; flex: none; + border: 2px solid var(--line-strong); + border-top-color: var(--fg2); + border-radius: 50%; + animation: lade-spin .8s linear infinite; +} +@keyframes lade-spin { to { transform: rotate(360deg); } } /* Echte Überschrift innerhalb einer Kachel (z.B. "Sommerräder"/"Winterräder") statt des sonst üblichen kleinen, gedämpften .label-Abschnittstitels - selbe Typografie-Stufe wie .title (Seitentitel), nur etwas kleiner, weil diff --git a/homeassistant/www/audi-dashboard-app.js b/homeassistant/www/audi-dashboard-app.js index 8f479e1..852312b 100644 --- a/homeassistant/www/audi-dashboard-app.js +++ b/homeassistant/www/audi-dashboard-app.js @@ -189,7 +189,17 @@ let PROFIL_ROH = null; // letzter vom Backend gelesener Rohstand, für configCar async function profilSpeichern() { const neuesProfil = configCarZuProfil(PROFIL_ROH); PROFIL_ROH = neuesProfil; - await HASS.callService("pyscript", "audi_dashboard_profil_schreiben", { profil_json: JSON.stringify(neuesProfil) }); + // HIG "Feedback": profilSpeichern() ist der Speicherpfad fuer praktisch + // jedes editierbare Feld im Panel (~25 Aufrufstellen) und wurde bisher an + // keiner davon awaited oder abgefangen - ein Fehlschlag war eine unhandled + // promise rejection ohne jede Rueckmeldung. Zentral hier gefangen, aus + // demselben Grund wie serviceRufen() weiter unten. + try { + await HASS.callService("pyscript", "audi_dashboard_profil_schreiben", { profil_json: JSON.stringify(neuesProfil) }); + } catch (err) { + console.error("audi_dashboard: profilSpeichern", err); + hinweis("Speichern fehlgeschlagen", err && err.message ? err.message : "Die Änderung konnte nicht gespeichert werden."); + } } /* ------------------------------------------------------------- Bilder @@ -2376,7 +2386,7 @@ function vReifen() { text-transform:none;font-size:12.5px;line-height:1.6"> Korrigiert nur den bisher gefahrenen Stand - weitere Fahrten mit diesem Satz zählen ab hier weiter dazu. - + ` : ""} @@ -2415,7 +2425,7 @@ function vReifen() {
Nm
- + ` : ""} @@ -3244,7 +3254,16 @@ function randwischenVerdrahten() { Persistenz über hass.callService im Hintergrund - dieselbe Reihenfolge wie im Prototyp, nur dass jetzt zusätzlich gespeichert wird. */ function serviceRufen(dienst, daten) { - HASS.callService("pyscript", dienst, daten).catch((err) => console.error("audi_dashboard:", dienst, err)); + // HIG "Feedback": ein fehlgeschlagener Aufruf darf nicht nur in der Konsole + // landen - die aufrufende Stelle hat ihr Popup meist schon geschlossen und + // die UI optimistisch aktualisiert, sonst würde die Änderung lautlos + // verloren gehen (derselbe Fehler, der für den Beleg-Upload und das Setup- + // Speichern schon einmal einzeln gefixt wurde - hier zentral für alle + // ~20 Aufrufstellen). + HASS.callService("pyscript", dienst, daten).catch((err) => { + console.error("audi_dashboard:", dienst, err); + hinweis("Aktion fehlgeschlagen", err && err.message ? err.message : "Der Dienst konnte nicht ausgeführt werden."); + }); } function ereignisseVerdrahten() { @@ -4254,7 +4273,7 @@ class AudiDashboardPanel extends HTMLElement {
Ziehen zum Aktualisieren
-
Lädt …
+
Lädt …
`; diff --git a/homeassistant/www/audi-dashboard-version.json b/homeassistant/www/audi-dashboard-version.json index 3adbe44..03c0549 100644 --- a/homeassistant/www/audi-dashboard-version.json +++ b/homeassistant/www/audi-dashboard-version.json @@ -1 +1 @@ -{"version": 1786769000} +{"version": 1786916894} diff --git a/homeassistant/www/audi-dashboard.css b/homeassistant/www/audi-dashboard.css index af29d11..cc29dd8 100644 --- a/homeassistant/www/audi-dashboard.css +++ b/homeassistant/www/audi-dashboard.css @@ -329,6 +329,17 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } display: block; line-height: 1.3; } +/* HIG "Loading": statt reinem Text ein sichtbar laufender Indikator, solange + DATEN_GELADEN false ist - hilft auch, "lädt noch" optisch von "hängt fest" + zu unterscheiden (dieser Bildschirm hatte schon ein echtes Hänger-Problem). */ +.lade-spinner { + width: 16px; height: 16px; flex: none; + border: 2px solid var(--line-strong); + border-top-color: var(--fg2); + border-radius: 50%; + animation: lade-spin .8s linear infinite; +} +@keyframes lade-spin { to { transform: rotate(360deg); } } /* Echte Überschrift innerhalb einer Kachel (z.B. "Sommerräder"/"Winterräder") statt des sonst üblichen kleinen, gedämpften .label-Abschnittstitels - selbe Typografie-Stufe wie .title (Seitentitel), nur etwas kleiner, weil