# 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 added** — `outline: 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.
