# AGENTS.md — Project state, review findings, open items, and working rules

**Last updated: 2026-08-12** (Setup-menu polish batch). 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.

> **Maintenance rule (binding):** whenever you change this repo in a way that affects anything
> recorded here — completing an open item, making or reversing a decision, adding/removing a
> component, discovering a new gap — **update this file in the same session** (tick the checkbox,
> adjust the status table, bump the "Last updated" date). This file must always be written in
> **English**, even though the rest of the project is German.

**How this file is loaded:** Claude Code does not read `AGENTS.md` natively; the root `CLAUDE.md`
imports it via `@AGENTS.md` (official recommended pattern). Other agents (Codex, Cursor, Copilot)
read `AGENTS.md` directly. Edit content here, not in `CLAUDE.md`.

---

## Working rules

### Karpathy guidelines (adopted as project standard)

From [`multica-ai/andrej-karpathy-skills`](https://github.com/multica-ai/andrej-karpathy-skills),
behavioral rules derived from Andrej Karpathy's observations on LLM coding failures. They bias
toward caution over speed — for trivial tasks, use judgment.

1. **Think before coding.** Don't assume. Don't hide confusion. Surface tradeoffs. State
   assumptions explicitly; if multiple interpretations exist, present them — don't pick silently.
   If a simpler approach exists, say so. Push back when warranted. If something is unclear, stop
   and ask.
2. **Simplicity first.** Minimum code that solves the problem. Nothing speculative: no features
   beyond what was asked, no abstractions for single-use code, no unrequested configurability, no
   error handling for impossible scenarios. If 200 lines could be 50, rewrite.
3. **Surgical changes.** Touch only what you must. Don't "improve" adjacent code, comments, or
   formatting; don't refactor what isn't broken; match existing style. Remove only orphans **your**
   change created; mention (don't delete) pre-existing dead code. Every changed line should trace
   directly to the request.
4. **Goal-driven execution.** Define success criteria, loop until verified. "Fix the bug" → write a
   test that reproduces it, then make it pass. For multi-step tasks, state a brief plan with a
   verify step per item.

### Claude Code practices (current guidance, 2026)

- Keep this file **concise and specific** — instructions concrete enough to verify ("run X before
  Y"), markdown headers/bullets, no dense prose. Context files reduce adherence as they grow;
  target roughly ≤200 lines of actual instruction.
- Don't record here what the code or git history already shows (directory dumps, dependency
  lists) — record pitfalls, rationale, decisions, and conventions that differ from defaults.
- Conflicting instructions get picked arbitrarily — when updating, **remove** superseded rules
  rather than stacking corrections.
- Verify loading with `/context` (file must appear under **Memory files**); edit via `/memory`.
- **This file (plus the session's task list) is the source of truth for what's done — not a leftover
  plan-mode file.** A `~/.claude/plans/*.md` file surfaced by the system as "not yet complete" can be
  stale: plan files aren't marked complete when their work finishes. Before claiming a feature is
  unbuilt (e.g. "the Setup menu isn't implemented yet"), check the `[x]`/`[ ]` items below and/or the
  task list — trust those over an old plan file's own claim about its status. (Caused a real mistake
  2026-08-12: told the user the Setup menu wasn't built yet, when it had shipped hours earlier in
  the same project — see the "Setup menu" `[x]` entry further down.)

---

## What this project is

Private replacement for the Audi *connect plug & play* app (discontinued end of 2026), for exactly
one vehicle (**Audi RS 4 Avant competition**), single user, self-hosted on Home Assistant, access
via Tailscale only. Read-only toward the vehicle — no remote control. Covers: vehicle status, trip
log, fuel log with Shell receipt parsing, service forecasting, insurance/tax, tire management.

**License rule (hard):** Audi Type fonts, four-rings SVG, and model-badge SVGs are cleared **only
for this one private, unpublished installation**. Therefore `design-system/` is brand-free (it gets
uploaded to Claude Design), while the HA panel and the future sideload-only app may use the real
assets. Never mix these the other way around.

**Reading order for a new session:**
1. This file (overview + open items)
2. `SPECIFICATION.md` — authoritative for the HA panel, incl. §7 "Known Gaps"
3. `COMPANION_APP_ARCHITECTURE.md` — authoritative for DataMetric360 (decided, barely built)
4. `AUDIT_2026-08-10.md` — accessibility/platform audit of the panel (partly done, rest below)
5. `bauauftrag.md` — **historical only**; code has diverged (see SPECIFICATION.md §7)

## The three projects in this repo

| Area | What | Status |
|---|---|---|
| `homeassistant/` | HA panel (`panel_custom`): pyscript backend + vanilla-JS frontend | ✅ **finished, in use** |
| `design-system/` | React component library `@audi-dash/ui`, brand-free, feeds Claude Design | ✅ done as a kit (20 components, 1,690 lines) |
| `companion-app/` | **DataMetric360** — successor app (Capacitor iOS/Android + HA iframe); will **replace** the panel | 🚧 data layer only (~930 lines TS), **no UI** |
| `design/` | Export of the Claude Design draft for DataMetric360 | 🚧 interim (wrong model, main screens only) |

Root files: `dashboard-muster*.html` = original static prototype (superseded, reference only),
`bauauftrag.md`/`.html` = original build brief (historical), `DESIGN_BRIEF_DATAMETRIC360.md` = the
prompt for the Claude Design project.

**`homeassistant/` in one paragraph:** 10 pyscript scripts + 5 modules (`pyscript/modules/`); trip
detection via the FMM003 ignition sensor with pause tolerance (the iPhone WLAN sensor is gone — see
section B), two-stage trip completion via HA history screening (odometer often updates only on the
next trip), fill-up detection on fuel-level rise, Shell PDF parser (`data/shell_beleg_parser.py`,
subprocess, the only tested part of the repo), tire km counter, backup, self-update, image
management. Frontend: one file `www/audi-dashboard-app.js`
(~4,000 lines, custom element, no framework/bundler), 5 tabs + ~19 detail routes, cache-busting via
`audi-dashboard-version.json` + loader stub. Entity IDs are assigned **in the UI** (Settings →
Fahrzeug einrichten → Setup), stored as overrides in `data/entitaeten.json`;
`pyscript/modules/einstellungen.py` holds only the built-in defaults. Deploy: `update.ps1`
(robocopy to Samba share) or — still inactive — self-update from git.

**`companion-app/` — what exists:** dependency-free, strictly typed TS data layer (`src/api/`):
REST client (`rest.ts`), WebSocket client with auth flow + reconnect backoff (`live.ts`),
persistent offline write queue (`warteschlange.ts`, strict FIFO), environment detection
capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), facade
`DataMetricApi` (`index.ts`). Smoke test ran 7/7 green against Docker HA `audi_ha_test`
(localhost:18123) — unauthenticated only; token-authenticated reads/writes untested.

**DataMetric360 architecture (short — details in `COMPANION_APP_ARCHITECTURE.md`):**
- Future data source: **Teltonika FMM003** on the CAN bus, fully replacing the iPhone WLAN sensor
  and the VAG integration.
- **Current data path (since 2026-08-12): FMM003 → flespi (native Codec8/TCP, IMEI auth) → HA.**
  Both earlier directions are dropped: Traccar first, then the self-hosted MQTT/TLS broker — the
  router's port-forward turned out to be unusable. The decision logs deliberately remain in the
  architecture doc, marked ÜBERHOLT. Details and open steps in section B below.
- Frontend access: HA REST + WebSocket with a long-lived access token (no `hass` object).
- External access: Cloudflare Tunnel + reverse proxy with a path allowlist; HA itself stays
  unreachable. Domain **`datametric360.app`** registered (all-inkl, 2026-08-11).
- Distribution: **sideload only** (license reason) — hence real Audi assets are allowed there.
- Trip detection moves to FMM003 ignition; the pause-tolerance feature
  (`fahrten_pausenzeit_min`) is a deliberate product decision and must be preserved.

---

## Review findings (2026-08-11)

### HA panel — known gaps (most also in SPECIFICATION.md §7)

- **GPS is dead schema for trip records specifically:** `start_lat/lon`, addresses, `route`,
  `avg_speed_kmh` on individual trips never populated. The trip-detail map draws a **fabricated**
  line via `fakeTrack()` (`audi-dashboard-app.js:414`) — not a real track. (Unrelated: the
  Übersicht's live Standort-Kachel is no longer in this state — `STANDORT_TRACKER` now points at
  a real `device_tracker`, see section B/C. This item is only about per-trip route data, which
  nothing currently populates.)
- **No GPS fallback in trip completion:** trips without an odometer match stay `status="offen"`
  forever (`modules/fahrtabschluss_logik.py:16-20`).
- **RAM-only state:** running trip (`_fahrt_start_ts`) and fuel low-water-mark
  (`_tiefststand_pct`) do not survive an HA restart. Deliberately deferred hardening.
- **Inactive features:** `UPDATE_REPO_URL = ""` (self-update inactive) in
  `pyscript/modules/einstellungen.py`. `www/bilder/` has no vehicle photos (all slots show
  placeholders); `steuer.faellig` unset. (`BATTERIE_SENSOR` was the same kind of gap until
  2026-08-12 — now wired to the FMM003's `external_power_voltage`, see section B/C below. Several
  *other* fields are newly inactive since the same date for a different reason — the
  `TommiG1/HA_VAG-EU-Data-Act` integration they depended on was dropped: `KM_SENSOR`,
  `TANK_SENSOR`, `RANGE_SENSOR`, `REFRESH_BUTTON`, all door/window/lock sensors, oil-change/
  inspection sensors. All are configurable again via the new Setup menu, see section C.)
- **Robustness:** `profil_lesen()` in `modules/profil.py` does not handle a missing/corrupt
  `fahrzeugprofil.json` — all callers throw.
- **Tests:** only the receipt parser is tested (`data/tests/test_shell_beleg_parser.py`, 10 real
  receipts) — and its test PDFs are gitignored, so a fresh clone can't run it. Backend and
  frontend: no tests, no CI.
- **Leaflet via CDN:** trip map needs public internet in addition to the Tailscale tunnel.

### Documentation drift (small fixes; align docs to code)

- `homeassistant/README.md:99`, `INSTALL.md:204`, and the header comment
  `audi-dashboard-app.js:13-15` claim the statistics view shows sample numbers — **false**;
  `vStat()` computes real values from `TRIPS`/`FILLS`.
- `homeassistant/README.md:110` mentions the removed 97% full-tank rule (removal documented in
  `belegverarbeitung.py:18-20`); the README file list omits 5 pyscript files.
- Obsolete comment `belegverarbeitung.py:41` ("TODO: Datei ablegen" — file has long existed).

### Audit leftovers (`AUDIT_2026-08-10.md` §4, deliberately left open)

- 🟠 Swipe-to-delete has no gesture-free fallback — screen-reader/switch-control users cannot
  delete trips/fill-ups. Needs a design decision (long-press vs. "…" button vs. action sheet).
- 🟡 Popup close-by-tap-outside is not keyboard-reachable (needs a quick manual check).
- 🟡 Self-host Leaflet JS/CSS (tiles necessarily stay remote).
- Audit's own note: these three may be better done in DataMetric360 than retrofitted — the panel
  gets replaced anyway.

### companion-app / design-system

- **Not wired together:** `companion-app` does not reference `@audi-dash/ui` anywhere (no
  dependency, no import, no workspace root). The link exists only in prose.
- Both packages: no `node_modules`, no `dist` — smoke tests need `npm install` first
  (design-system additionally `npm run build`; `scripts/smoke.mjs` imports from `../dist/`).
- No unit tests, no Storybook (substitute: SSR smoke over 21 cases in design-system).
- `design/` export (2026-08-11) has two known, already-commissioned fixes not yet in the export:
  shows **RS 6 instead of RS 4**, and **16 sub-pages** the HA panel already has are missing
  (list in `design/README.md`).

---

## Open items

### A) Build DataMetric360 (the big block)

- [ ] Fix the Claude Design draft (RS 4, not RS 6) and extend it by the 16 missing sub-pages;
      then re-export to `design/`
- [ ] `companion-app`: set up Vite + React + Capacitor scaffold; wire `@audi-dash/ui` as a real
      dependency (possibly add a workspace/monorepo root)
- [ ] Implement the screens from the design draft on top of the existing `DataMetricApi` layer
- [ ] Add Audi assets (fonts/rings/badges) at implementation time from `homeassistant/www/` —
      **never** into `design-system/`
- [ ] Secure storage for the LLAT (iOS Keychain / Android Keystore via Capacitor plugin; the
      `ablageSetzen()` hook already exists)
- [ ] Onboarding: manual token paste (required); QR scan only if it stays simple (QR generated
      locally under HA `/local/`, architecture §3)
- [ ] Authenticated smoke tests of the data layer (reads, service calls, queue round-trip)
      against `audi_ha_test` with a real token
- [ ] Offline UX per design brief (offline marker, visible pending queue)

### B) Infrastructure / commissioning (partly waits for FMM003 hardware)

- [ ] Switch `datametric360.app` nameservers at all-inkl to Cloudflare ("full setup") —
      prerequisite for the tunnel; domain carries nothing else, so this is consequence-free
- [ ] Decide hostname split (app on apex + API on `api.` subdomain, or vice versa)
- [ ] Choose reverse proxy (Nginx Proxy Manager vs. Traefik) — **can be done before hardware**
      against the existing HA API
- [ ] Define the reverse-proxy path allowlist (depends on final entity/service names)
- [ ] Install/wire the FMM003; record firmware version (Codec JSON is firmware-dependent)
- [x] Generate TLS certificates for Mosquitto + device (small private CA) — done 2026-08-11, 10-year
      validity; Mosquitto configured (`certfile`/`keyfile`/`cafile`/`require_certificate: true`).
      Remaining: upload root/client cert/key to the FMM003 Security tab — filenames must end in
      `.pem`/`.pem.crt`/`.pem.key` (Configurator rejects plain `.crt`/`.key`, content-agnostic check)
- [ ] Decide broker reachability for the vehicle — **reopened 2026-08-11 evening**: port-forward
      8883 on the Speedport Smart 4 Plus looked correctly configured (rule present, right internal
      IP, right port) and internal reachability was confirmed (`homeassistant.local:8883` open from
      the LAN, Mosquitto TLS listener genuinely up, TLS cert chain end-to-end verified byte-for-byte
      against the FMM003's uploaded client cert), but the port stayed **closed from outside**
      (confirmed via external port checker, both before and after a router reboot that changed the
      dynamic WAN IP — DNS/DuckDNS matched correctly each time, so not a DNS or CGNAT issue).
      Community reports (ComputerBase, Telekom Hilft) describe this as a known, Telekom-acknowledged
      firmware bug on this router model; a full disable-the-firewall workaround doesn't exist on this
      model either. Port-forward rule has been removed again. **New direction: route the FMM003
      through flespi instead** (native Teltonika/Codec8 channel, IMEI-based auth, no certs, no
      inbound port needed at all — flespi has a stable public endpoint; HA pulls data back out via
      flespi's REST API or MQTT, outbound-only). Free flespi tier (10 devices/2 channels) is enough
      for one vehicle. Next step: user creates the flespi account + Teltonika channel; a `flespi`
      custom integration is already present in this HA instance (unconfigured) — check what it
      needs once flespi-side setup exists.
      Unrelated but still valid from the same session: DuckDNS hostname
      `datametric360.duckdns.org` reliably updating; Let's Encrypt for HA's own local UI works (root
      cause of the earlier DNS-01 failures was a stray `aliases` entry in the DuckDNS add-on config,
      not DNS/network — see COMPANION_APP_ARCHITECTURE.md §5 item 2a). HA's SSL config now lives in
      Settings → System → Network (UI), not `configuration.yaml`'s old `http:` block, which was
      removed after HA started migrating/ignoring it. The Mosquitto TLS setup itself (cert chain,
      `require_certificate: true`, `certfile`/`keyfile`/`cafile`) is verified correct and can be
      reused as-is if a self-hosted broker is ever revisited.
- [ ] MQTT Client Type on the FMM003 ("Custom server" not selectable in practice, "AWS IoT Custom"
      pointed at a self-hosted broker was confirmed working by two independent community reports) —
      **moot for now** given the flespi pivot above: flespi uses the device's native Codec8/TCP
      channel, not MQTT at all, so Server Settings should switch to Protocol: TCP against the flespi
      channel host/port instead, and Codec set to "Codec 8 Extended". Revisit this item only if a
      self-hosted broker is picked back up later.
- [ ] Capture the first real Codec JSON message (`mosquitto_sub`/MQTT Explorer) and build the
      field mapping from it — **do not guess beforehand** (explicit decision)
- [x] Move trip detection to FMM003 ignition — done 2026-08-12. `fahrterkennung.py` rewritten:
      trigger is now `einstellungen.ZUENDUNG_SENSOR`
      (`binary_sensor.testzone_fmm003_engine_ignition_or_acc_status`, on = trip running) instead
      of the iPhone WLAN sensor; same `task.unique()` pause-tolerance pattern kept unchanged.
      `sensor.iphone_wifi_connection` and the `fahrzeug.wlan_name` profile field are fully removed
      (backend and frontend). At the same time, all entity IDs sourced from the now-abandoned
      `TommiG1/HA_VAG-EU-Data-Act` HACS integration were blanked in `einstellungen.py`
      (`KM_SENSOR`, `TANK_SENSOR`, `RANGE_SENSOR`, `REFRESH_BUTTON`, door/window/lock sensors,
      oil-change/inspection sensors) — the consuming features (Übersicht tiles, Reifenzähler,
      automatic Tankerkennung, Fahrtabschluss-Screening) are untouched and degrade gracefully to
      "unbekannt"/inactive per the existing `zustand_oder_none()` convention, rather than being
      deleted; `fahrtabschluss.py`/`tankerkennung.py`/`reifenzaehler.py` now guard their
      `@state_trigger(f"{einstellungen.KM_SENSOR}")`-style decorators with
      `if einstellungen.KM_SENSOR:` (untested previously whether pyscript tolerates an empty
      trigger string — not worth relying on). `STANDORT_TRACKER` now points at the real
      `device_tracker.testzone_fmm003` and `BATTERIE_SENSOR` at
      `sensor.testzone_fmm003_external_power_voltage` (the vehicle's 12V bus voltage as measured
      by the tracker — NOT `sensor.testzone_fmm003_battery_voltage`, which is the tracker's own
      internal backup cell and unrelated to the car). Real entity list for this device: the user's
      `Entiaeten.csv` export (OneDrive, not in this repo). Verified in `audi_ha_test`: pyscript
      reload (`pyscript.reload`) produced no errors/warnings for any of the changed files; the
      Übersicht correctly shows "Status unbekannt" and em-dash placeholders for the now-blank VAG
      fields instead of crashing (that container itself has no `device_tracker.testzone_fmm003`
      registered, so Standort still shows "Kein GPS-Signal" there — expected, the real HA instance
      has it). `INSTALL.md` step 4 and its troubleshooting table have since been rewritten
      (2026-08-12) to point at the Setup menu instead of manual `einstellungen.py` edits, and to
      use the current FMM003 field names (`ZUENDUNG_SENSOR`/`STANDORT_TRACKER`/`BATTERIE_SENSOR`)
      instead of `WLAN_SENSOR`/TommiG1 — not live-tested against a real install (INSTALL.md itself
      was never run end-to-end in this project, per its own opening note), but consistent with the
      actual code.

### C) Maintain the existing HA panel (low priority — being replaced)

- [x] Wide-screen/desktop layout (`@container (min-width:860px)` in `audi-dashboard-ios.css`,
      turns the same `.tabbar` into a 264px side-nav via CSS only, no JS/markup duplication) — done
      2026-08-11 from the Claude Design "DataMetric360 Board" draft (`claude.ai/design` project
      `c28a8d4d-ec4e-4178-9e49-ab5b90c02097`). Found and fixed a real regression while verifying it
      live in `audi_ha_test`: the overlay's `:host > div{height:100%}` (meant to drop the old
      412px-cap for the sidebar layout) was applied unconditionally instead of only inside the
      `@container` block, which broke the **normal mobile layout too** — HA doesn't reliably
      propagate a real height down through `panel_custom`, so `height:100%` collapsed `main#view` to
      ~0px (tabbar rendered right under the header, content invisible). Fix: kept the outer wrapper
      viewport-anchored (`min(880px, calc(100dvh - 24px))`, matching `audi-dashboard.css`'s original
      reasoning) at all widths, and only drop the 880px cap (`calc(100dvh - 24px)`, still
      viewport-anchored, not parent-relative) inside `@container (min-width:860px)`. Verified live at
      both narrow (mobile tab bar, full content) and wide (1144px container, grid side-nav,
      navigation clicks, settings back-arrow) — screenshots taken, no regressions found.
- [x] Desktop-Sidebar-Markenklon (`.navmarke`) nachgezogen — done 2026-08-12. The user compared the
      deployed app against the live Claude Design "Board" (the two-viewport comparison page, which
      genuinely iframes this project's own `audi-dashboard-app.js`/`-ios.css`, not a static mockup)
      and asked why the desktop Audi-rings placement differed: Design showed the rings pinned above
      the sidebar nav list, ours showed them in the content header next to the title. Root cause,
      confirmed via `DesignSync get_file` + byte-diff: `audi-dashboard-app.js` was already fully in
      sync (identical), but `audi-dashboard-ios.css` had drifted — the Design project had since
      added a `.navmarke` sidebar clone of the rings (only visible in the `@container
      (min-width:860px)` block; `.rings{display:none!important}` hides the header copy there so the
      mark appears once) plus a filled-pill active-tab style (`.tabpille`), neither pulled back
      locally. Diffed the two full files first to confirm every change was additive/CSS-only, then
      replaced the local file wholesale. Verified in `audi_ha_test` at both 402px and 1400px: rings
      now sit above the nav list on desktop, gear icon alone in the content header, mobile view
      unaffected, no console errors beyond the pre-existing missing-vehicle-photo 404 gap.
- [x] Standort-Kachel (live vehicle GPS position on the Übersicht, above "Zuletzt") — done
      2026-08-12. New tile with a real (non-fake) Leaflet mini-map, opens a fullscreen "Standort"
      route on tap; fullscreen has 4 floating controls (map style, center-on-vehicle,
      center-on-user, fit-both) and a draggable myAudi-style bottom sheet (peek/expand/close by
      drag or tap, opens on vehicle-marker click) showing distance-to-user, address (client-side
      reverse geocoding via the public Nominatim API — same "public API, no key, low single-
      vehicle volume" reasoning as the existing Leaflet-CDN gap below), fährt/steht/"Letzter
      Parkplatz" state, fuel/range, and Route (Google Maps deep link) / Teilen (Web Share API)
      actions. Backend: new `STANDORT_TRACKER` entity-ID setting in `einstellungen.py` (device_
      tracker with lat/lon attributes), published via a new `_standort()` helper in
      `frontend_veroeffentlichung.py`. The built-in default points at the author's test device
      (`device_tracker.testzone_fmm003`); until the FMM003 actually delivers positions (section B),
      that entity does not exist and the tile shows "Kein GPS-Signal vom Fahrzeug". Verified end-to-end in `audi_ha_test` with a manually
      created `device_tracker.test_fahrzeug` test entity (tile, fullscreen map, and menu all
      confirmed rendering/populating correctly); the device's own location (browser Geolocation
      API) and the Route/Teilen deep links work independently of the vehicle-GPS gap. Pushed to
      the Claude Design project (`c28a8d4d-ec4e-4178-9e49-ab5b90c02097`, "DataMetric360 Board")
      for visual refinement — the JS/CSS ship functionally complete but visually plain
      (placeholder vehicle-marker glyph, default `.aktion`/`.tile` styling); Claude Design's job is
      polish, not structure. One known rough edge: reverse-geocoded address didn't resolve within
      a few seconds in the `audi_ha_test` container (no crash, stays on "Adresse wird ermittelt …"
      indefinitely) — likely just that container's outbound network to Nominatim specifically;
      unconfirmed whether this reproduces on the real HA instance.
- [x] Setup menu (Einstellungen → Fahrzeug einrichten → last item when expanded) — done
      2026-08-12. Lets the user map every sensor role the app uses (~18 roles, 3 of them 4-item
      door/window/lock lists) to real HA entities from a searchable dropdown, instead of hand-
      editing `einstellungen.py`. New backend module `pyscript/modules/entitaeten.py`: a
      `FELDER` catalog (label, hint, expected domain/device_class/unit, keywords, whether it's
      one of the trigger-bound fields) plus JSON override I/O (`data/entitaeten.json`, same
      atomic-write pattern as `profil.py`) and `overrides_anwenden()`, which does
      `setattr(einstellungen, key, value)` on the already-imported `einstellungen` module — every
      existing consumer does `import einstellungen` + live attribute access (verified: none use
      `from einstellungen import X`), so this needs no changes to any consumer file. New service
      `audi_dashboard_entitaeten_schreiben` (`frontend_api.py`) and publication
      `entitaeten_veroeffentlichen()` (`frontend_veroeffentlichung.py`) follow the project's
      existing read-via-state/write-via-service pattern. Frontend: new popup (`vSetupPopup()`,
      modeled on the existing `.sheet` pattern) with a from-scratch searchable-combobox component
      (none existed in the repo before) — client-side only, built directly from `HASS.states`, no
      new backend read service; a global "Nur passende Sensoren anzeigen" switch hard-filters
      candidates by domain/unit, a keyword+domain+unit score ranks and auto-suggests for empty
      fields. Deliberately does NOT auto-apply changes for the three trigger-bound fields
      (`ZUENDUNG_SENSOR`/`KM_SENSOR`/`TANK_SENSOR` — `@state_trigger` bakes the entity ID in at
      module-load time) — the UI shows an inline "wirkt erst nach Neustart" hint instead of
      attempting a risky self-`pyscript.reload()` from inside a running service call. Two real
      bugs found and fixed during live verification, both now also pushed to the Claude Design
      project: (1) a full popup re-render on every keystroke fought the search input for focus —
      fixed by patching just the candidate-list DOM node on `input`, matching the same fix the
      existing `data-tankpreisfeld` handler already uses for the identical reason; (2) the popup
      background used `--tile-2`, which Design had since changed to a translucent
      `rgba(255,255,255,.1)` in the night theme (for the frosted-glass tile look elsewhere) — made
      the whole popup partially see-through against the app content behind it. Fixed by switching
      to `--tile-deckend` (Design's own opaque-surface token, already used by `.standortmenu` for
      the same reason), with `var(--canvas)` as a defensive fallback.
- [x] Fresh-install packaging + `update.ps1` fix — done 2026-08-12. While preparing a short
      quick-install checklist, found that `update.ps1` never copied `audi-dashboard-ios.css` or
      `www/badges/*` — both are loaded by the running app (the ios overlay is injected by the app
      itself via JS, badges by the model picker since item #51), so every `update.ps1` run since
      those were added silently left the deployed copy stale on those two. Fixed (script now copies
      both). Also removed two stray `*.bak-before-audit-merge` files from `www/` (already gitignored
      at repo root, never committed, just local clutter from an earlier merge). New
      `installationspaket/` — a generated, gitignored bundle (pyscript/, www/ minus the two backup
      files, data/ with only the example profile + receipt parser, `configuration_snippet.yaml`,
      `update.ps1`) plus a short `ANLEITUNG.md` checklist for copying to a fresh HA instance;
      `INSTALL.md` stays the authoritative, detailed reference. Regenerate on demand, don't keep it
      permanently in sync — it's a deployment snapshot, not a second source of truth.
- [x] Setup-menu polish: 5 additions on top of the existing Setup popup — done 2026-08-12.
      Backend (`pyscript/modules/entitaeten.py`): a `_STANDARDWERTE` snapshot taken at module-load
      time (before `overrides_anwenden()` ever runs), exposed via `aktueller_stand()` as
      `standardwerte` — needed because `setattr()` on the live `einstellungen` module is permanent
      for the process, so "reset to default" has to write the original value back explicitly rather
      than just omitting the override. New service `audi_dashboard_neustart` (`frontend_api.py`)
      wrapping `homeassistant.restart()`, called only from an explicit user-confirmed button, never
      automatically. Frontend (`audi-dashboard-app.js`): (1) live current-value preview next to each
      candidate in the search dropdown (`entitaetZeilenMarkup`, reads `HASS.states[e.id].state`);
      (2) inline warning under a field if its currently assigned entity is `unavailable`/`unknown`/
      missing (`entitaetStatusWarnung`); (3) duplicate-assignment check on save
      (`setupDuplikate`) — blocks with a confirm-sheet ("trotzdem speichern?") rather than silently
      allowing the same entity in two roles; (4) per-field reset button (only shown when the current
      value differs from `standardwerte`), reverts to the built-in default; (5) after saving, if any
      of the 3 trigger-bound fields actually changed (`setupGeaenderteTriggerFelder`, compares
      against a snapshot taken when the popup opened), a confirm-sheet offers "Jetzt neu starten"
      calling the new service. Verified live in `audi_ha_test`: value preview and unavailable-warning
      confirmed correct (`ZUENDUNG_SENSOR`'s configured entity genuinely doesn't exist in this test
      container → "Entität nicht gefunden." shown as designed); duplicate-check correctly caught both
      a deliberately-forced KM/TANK collision and pre-existing door/window/lock auto-suggestion
      collisions (a real side effect of this container's limited matching entities, not a bug);
      reset button appeared/disappeared correctly as values diverged from/matched the default;
      save round-tripped through the real service into `data/entitaeten.json` (byte-inspected) and
      into the published `pyscript.audi_dashboard_fahrzeugstatus` state; restart-needed sheet named
      exactly the changed trigger-bound field(s) and no others. Did not actually trigger a restart
      during verification (would have restarted the shared test container) — the service call itself
      was left unexercised beyond confirming it registers without error at pyscript load time.
- [ ] 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)
- [ ] Harden `profil_lesen()` against missing/corrupt `fahrzeugprofil.json`
- [ ] Decide whether the 3 audit leftovers get fixed here or only in DataMetric360
- [ ] Optional: persist the RAM-only states (trip start, fuel low-water-mark) — deliberately
      deferred; may become moot with the FMM003 switch
- [ ] Upload vehicle photos to `www/bilder/`, set `steuer.faellig` (operational data, not code)

### D) Once the panel is superseded

- [ ] **Archive** `audi-dashboard-app.js`, don't delete (settled decision, architecture §1)

---

## Working conventions (observed — keep them)

- German is the project language: identifiers, comments, commits, UI texts. Exceptions:
  `design-system/` uses English props/JSDoc (Claude Design audience) — and **this file**, which is
  English by rule.
- Decisions are logged, including rejected ones (see the Traccar section of the architecture
  doc); superseded sections stay in place marked "ÜBERHOLT" rather than being deleted.
- Numbers in `de-DE` format; font weights 300/400 only, never ≥600 (only those cuts exist).
- Real vehicle/movement data stays local (`data/` contents and receipt PDFs are gitignored).
- Security principle: HA is never publicly exposed; only narrowly scoped surfaces (MQTT broker,
  proxy allowlist) may be exposed, each by explicit decision.
