Files
audi-app/AUDIT_2026-08-10.md
tobias e1d570992e Initialer Import: HA-Panel, Design-System, Companion-App
Drei zusammengehörige Teile in einem Repository:

- homeassistant/  Das fertige, im Einsatz befindliche Home-Assistant-Panel
  (panel_custom Custom Element + pyscript-Backend). Echte Fahrzeug- und
  Personendaten (fahrzeugprofil.json, fahrten.jsonl, tankvorgaenge.jsonl,
  Tankbelege) bleiben per .gitignore außen vor; die anonymisierte Vorlage
  fahrzeugprofil.example.json ist mit dabei.

- design-system/  Eigenständige React-Komponentenbibliothek (@audi-dash/ui),
  die die visuelle Sprache des Panels nachbildet - ohne Audi-Markenzeichen
  und ohne die lizenzierte Hausschrift. Dient als Grundlage für Claude
  Design. War bis hierher ein eigenes Repository und ist in dieses
  eingeschmolzen worden.

- companion-app/  Datenschicht der neuen App DataMetric360 (iOS/Android via
  Capacitor, zusätzlich als Iframe im HA-Dashboard). Noch ohne Oberfläche:
  REST- und WebSocket-Zugriff auf Home Assistant plus Warteschlange für
  Änderungen ohne Netz. Ersetzt das eingespritzte hass-Objekt, das nur
  innerhalb des HA-Frontends existiert.

Dazu die Projektdokumentation: SPECIFICATION.md (Ist-Stand des Panels),
COMPANION_APP_ARCHITECTURE.md (Architekturentscheidungen der neuen App),
AUDIT_2026-08-10.md, DESIGN_BRIEF_DATAMETRIC360.md und der ursprüngliche
Bauauftrag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 00:20:05 +02:00

11 KiB
Raw Permalink Blame History

Audi Dashboard — Full Audit (Code · UI/UX · Cross-Platform)

Date: 2026-08-10 Scope: homeassistant/www/audi-dashboard-app.js (3130 lines), audi-dashboard.css (715 lines), audi-dashboard-panel.js, as currently deployed (post Design-Audit-merge, post scroll-bug fix). Method: static review (Grep/Read across the full source), computed WCAG contrast checks, live verification of prior findings in the audi_ha_test Docker container. Not a full manual screen-reader pass (no VoiceOver/TalkBack device in this environment) — flagged explicitly below where that matters.


1. Platform verdict — can it run on iPhone, Android, Home Assistant?

Environment Verdict Why
Home Assistant (browser, sidebar panel) Works — this is its native environment Built as a panel_custom custom element; hass is injected directly by HA's frontend.
iPhone / Android, via the official Home Assistant Companion App Works today, no changes needed The Companion App is a WebView wrapper around the exact same HA frontend — it opens this panel the identical way a desktop browser does, hass injection included. The app is already tuned for this (safe-area insets, iOS-style pull-to-refresh, 44×44px tap targets, prefers-reduced-motion support). This is very likely how "iPhone" was meant in the original brief (§4.2 already assumes the Companion App).
iPhone / Android, as a standalone app (Capacitor-wrapped or plain browser tab pointed at a public URL) Does not work, and can't with this file as-is The entire data layer is hass.states[...] / hass.callService(...). There is no REST call, no WebSocket client, no token/auth flow, and no Capacitor.* usage anywhere in the file — outside of an HA frontend context, hass is simply undefined and the app never renders past the loading tile. This is exactly the gap the separately-discussed companion-app architecture (FMM003 → Traccar → HA → REST/WebSocket API client) is meant to fill; that work is a different, not-yet-built app, not a mode of this file.
Generic iframe embed (e.g. in a non-HA page) Same reason Needs hass injected the panel_custom way, not a generic postMessage/REST contract.

One-line answer: yes on Home Assistant and yes on iPhone/Android through the HA Companion App; no as an independent native/standalone app — that requires the separate REST/WebSocket-backed companion app already discussed, not a change to this file.


2. Findings

Severity: 🔴 real bug/breakage · 🟠 should fix · 🟡 minor/polish

FIXED — 🔴 iOS Safari/WKWebView auto-zoom on form inputs

Where: audi-dashboard.css:462.feld input, .feld select, .notiz { font-size: 15px; } Issue: iOS Safari (and the Companion App's WKWebView) auto-zooms the viewport when a focused input's computed font-size is under 16px. Every text/number field in the app (trip forms, fill-up forms, settings, service-book entries) is 15px — so tapping any field on an iPhone zooms the page in, and the user has to manually zoom back out after every field. Fix: raise to font-size: 16px (or max(16px, …) if the 15px look matters, add a transform: scale(0.9375) trick — not recommended, adds complexity for no real benefit). Simplest: just bump to 16px; the visual difference is negligible at this size.

RETRACTED (false positive) — Dark-theme label/eyebrow contrast

Originally claimed: --fg3: #657081 on --canvas: #161b23 ≈ 3.45:1, failing WCAG AA. Why it was wrong: that value came from SPECIFICATION.md's §2 color table, not from the actual CSS. The shipped value is --fg3: #8a94a3 — the Design Audit's own finding A2 already fixed exactly this, and audi-dashboard.css documents it inline: "A2: von #657081 (3,0:1 auf --tile) auf 4,6:1 angehoben." Recomputed against the real value: 5.6:1 on --canvas, 4.9:1 on --tile — passes AA on both surfaces. Nothing to fix in the CSS. The real defect this exposed: SPECIFICATION.md's color table was stale (documenting pre-merge values) — the same doc-staleness class as the theming finding below. Fixed: the --fg3 row in SPECIFICATION.md §2 now carries the shipped value. Lesson for future audits of this project: read audi-dashboard.css directly for token values; SPECIFICATION.md predates the Design-Audit merge in several places.

FIXED — 🟠 No visible keyboard-focus indicator outside form fields

Where: audi-dashboard.css:472 is the only :focus rule in the file, and it's scoped to input/select/.notiz. Issue: every button, tile, tab, and swipe-row is focusable (native <button>/interactive elements) but has no :focus/:focus-visible style at all — a keyboard or switch-control user (Bluetooth keyboard + iPad/desktop browser via Tailscale, or iOS Full Keyboard Access) gets no visual indication of where focus is. This is invisible on a touch-only phone, which is presumably why it wasn't caught, but the app is also reachable from a browser tab (desktop, Tailscale). Fix: add a generic :focus-visible rule (e.g. outline: 2px solid var(--red); outline-offset: 2px;) on button, .tile, .tab, [tabindex] — keeps mouse/touch users unaffected (:focus-visible only fires for keyboard focus in modern browsers) while fixing keyboard access.

🟠 Swipe-to-delete has no non-gesture affordance

Where: audi-dashboard-app.js:1872 (.swipe-delete/.swipe-loeschen), used for trip and fill-up rows. Issue: the delete action only becomes reachable (as an actual, labeled <button>Löschen</button>) after a horizontal Pointer Event drag reveals it. A screen-reader user (VoiceOver swipe-navigation, not a raw drag gesture) or anyone using switch control has no way to trigger that reveal, so trip/fill-up rows are not deletable for them at all. Service-book rows already use a plain always-visible delete button (per SPECIFICATION.md §3) — the same pattern doesn't exist for trips/fills. Fix: smallest viable fix is a long-press or a small always-present "…" affordance per row that reveals the same action sheet already built for other destructive actions (sheet/bestaetigen()), rather than requiring the drag gesture as the only path.

🟡 Popup dismiss-by-tapping-outside isn't keyboard-reachable

Where: audi-dashboard-app.js:1460, 1477, 1576.bildmenu-catcher / overlay-close divs. Issue: these are plain <div>s with click handlers, not buttons — fine for touch/mouse, invisible to keyboard/AT navigation. Low severity since these are popups tied to photo management, a small surface, and likely have another (or should have another) explicit close control — worth a quick manual check rather than assumed broken.

🟡 Leaflet map requires public internet, independent of the Tailscale tunnel

Where: SPECIFICATION.md §3 confirms — Leaflet is loaded from a CDN at runtime, not bundled. Issue: on a phone connected only via Tailscale with no other route to the public internet (e.g. cellular data off, only the VPN interface up), the trip-detail map silently fails to load tiles. Not a bug in the code as such, but worth knowing as a real-world failure mode on iPhone/Android specifically (desktop Tailscale setups usually have a normal internet route too). Fix (optional): self-host the Leaflet JS/CSS bundle under /local/, keep tiles CDN-based (tiles are unavoidably remote) — removes the JS/CSS-loading failure mode at least.

FIXED — 🟡 Stale doc: SPECIFICATION.md says theme has "no persistence"

Where: SPECIFICATION.md:136 — "No persistence — theme resets to 'nacht' on every full reload; no localStorage use was found." Issue: this is now false — the Design-Audit merge added localStorage-backed theme persistence (THEME_KEY, audi-dashboard-app.js:2157-2163) and tab-label persistence (SCHRIFT_KEY, line 1377/2856). Not a code bug — just a doc that predates the merge and will mislead the next person/session reading it. Fix: update SPECIFICATION.md §3 "Theming" to reflect the current localStorage behavior.

🟡 Touch targets — no issue found (noted for completeness)

Checked .themebtn, .profilbtn, .zahnrad (all 44×44px, Apple HIG minimum) and the tab bar (icon + label, generous padding). This was clearly already addressed by the Design Audit — nothing to fix here.

🟡 Icon-only buttons — no issue found (noted for completeness)

#back, .profilbtn, and every .zahnrad settings-gear button carry a correct, specific aria-label; decorative SVGs inside them are aria-hidden. Tab bar buttons pair the icon with a visible text <span>. This is solid — better than average for a project this size.


3. What was done (2026-08-10)

Applied and verified live in the audi_ha_test container (CSS version 1786377886):

  1. Form-input font-size 15px → 16px (audi-dashboard.css, .feld input, .feld select, .notiz). Verified: all 14 inputs on the Settings/Einrichten form compute to 16px, none below the iOS auto-zoom threshold.
  2. Global :focus-visible rule addedoutline: 2px solid var(--red); outline-offset: 2px;. Verified: the rule parses into the shadow-root stylesheet (confirmed via CSSOM, not just file content), a focused button reports matches(":focus-visible") === true, and the computed outline resolves to solid 2px rgb(245, 5, 55) at a 2px offset. Using :focus-visible rather than :focus keeps mouse/touch interaction visually unchanged.

Both carry inline German comments explaining why, matching the file's existing convention.

Verification caveat: the browser screenshot tool failed throughout this session ("Browser pane is not displayed"), so confirmation is structural (computed styles, CSSOM, live DOM) rather than visual. The structural evidence is conclusive for both fixes, but nobody has actually looked at the focus ring yet — worth a glance next time the pane cooperates.

Also fixed (documentation): SPECIFICATION.md's stale --fg3 color-table value and its stale "no persistence" theming paragraph — both now match the shipped code.

4. Remaining / not done

  1. Swipe-to-delete accessible fallback (🟠) — the only real feature work in this audit, not a one-liner. Left open deliberately: it needs a decision on which affordance (long-press, a persistent "…" button per row, or routing through the existing Action Sheet).
  2. Popup close-by-tap-outside <div>s (🟡) — needs a quick manual check before deciding whether it's genuinely broken for keyboard/AT users.
  3. Self-hosting Leaflet (🟡) — optional; removes a Tailscale-only-network failure mode.

Note on how much further to invest here: COMPANION_APP_ARCHITECTURE.md records the decision that this panel is eventually superseded by the responsive companion app. These three items may be better carried into that app's component work than retrofitted into this file.

5. Platform targets — unchanged

No changes needed for the Home-Assistant or HA-Companion-App targets; both already work. The "standalone iPhone/Android app" question is answered by COMPANION_APP_ARCHITECTURE.md, not by anything fixable in this file.