Merge branch 'umsetzung-datametric360': Companion-App-Phasen 1-10 uebernehmen

Bringt die vollstaendig gebaute DataMetric360-App zusammen (React/Vite,
21 Screens, Audi-Assets, PWA + native Capacitor-Huelle, 90+9 Tests) sowie
die profil_lesen()-Haertung gegen fehlende/kaputte Profildatei zusammen.

Konfliktaufloesung:
- AGENTS.md, INSTALL.md, README.md: main-Fassung war jeweils die
  chronologisch neuere, uebernommen und um die durch den Merge tatsaechlich
  erledigten Punkte ergaenzt (profil_lesen()-Haertung, Audit-Reste-Entscheidung).
- fahrterkennung.py: toten WLAN-Zweig vom Branch verworfen, Zuendungs-
  basierte Erkennung von main behalten.
- homeassistant/FMM003_MAPPING.md (MQTT/Mosquitto-Ansatz vom 2026-08-11,
  vor der Umstellung auf flespi) bewusst nicht uebernommen - main nutzt seit
  2026-08-12 flespi als alleinigen FMM003-Datenweg. UMSETZUNGSPLAN.md Phase 13
  entsprechend als ueberholt markiert, verweist auf AGENTS.md als massgeblich.
- REVIEW_main_2026-08-13.md, ha_install.md (add/add): main-Fassung war die
  spaetere Revision derselben Dokumente, uebernommen.

Die von REVIEW_main_2026-08-13.md befuerchtete Merge-Falle (profil_lesen()
gibt jetzt None zurueck, main-seitige Aufrufer pruefen das nicht) wurde
verifiziert als bereits entschaerft: alle 5 Aufrufstellen im gemergten Stand
(backup.py x2, fahrterkennung.py, frontend_veroeffentlichung.py,
reifenzaehler.py, tankerkennung.py) sind None-sicher.

installationspaket/ nicht Teil dieses Commits (gitignored, wird bei Bedarf
neu zusammengestellt).
This commit is contained in:
2026-08-13 19:03:20 +02:00
113 changed files with 17638 additions and 159 deletions
+128 -35
View File
@@ -1,6 +1,7 @@
# AGENTS.md — Project state, review findings, open items, and working rules
**Last updated: 2026-08-13** (design/CI review added, see `DESIGN_REVIEW_2026-08-13.md`). This file is the entry point for every new agent
**Last updated: 2026-08-13** (merged the `umsetzung-datametric360` branch — companion app phases
110 done, see `UMSETZUNGSPLAN.md`). 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.
@@ -73,19 +74,22 @@ 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)
2. `UMSETZUNGSPLAN.md` — the step-by-step execution plan for all open items (13 phases with
commands and acceptance criteria); when working on an open item, follow the plan's phase
3. `SPECIFICATION.md` — authoritative for the HA panel, incl. §7 "Known Gaps"
4. `COMPANION_APP_ARCHITECTURE.md` — authoritative for DataMetric360 (decided, phases 110 built)
5. `AUDIT_2026-08-10.md` — accessibility/platform audit of the panel (partly done, rest below)
6. `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** |
| `homeassistant/` | HA panel (`panel_custom`): pyscript backend + vanilla-JS frontend | ✅ **finished, in use** — to be replaced by the app |
| `testumgebung/` | Script that rebuilds a throwaway Home Assistant with the real backend | ✅ new, reproducible |
| `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) |
| `companion-app/` | **DataMetric360** — successor app (web, PWA, native iOS/Android via Capacitor, HA iframe); will **replace** the panel | **all 21 screens built and tested**; runs natively on iOS with real data |
| `design/` | Export of the Claude Design draft for DataMetric360 | 🚧 interim and now behind the code — screens were derived from the old panel instead (owner's decision) |
Root files: `dashboard-muster*.html` = original static prototype (superseded, reference only),
`bauauftrag.md`/`.html` = original build brief (historical), `DESIGN_BRIEF_DATAMETRIC360.md` = the
@@ -103,12 +107,12 @@ 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.
**`companion-app/` — what exists:** the full app. Data layer (`src/api/`: REST, WebSocket with
reconnect backoff, persistent offline write queue, credential storage), domain logic
(`src/daten/`: profile adapter, statistics, service forecast, data context), all 21 screens
(`src/screens/`), Audi assets (`src/assets/audi/`), PWA manifest and icons. Verified by 90 unit
and render tests plus 9 checks against a live Home Assistant. `npm run dev` in the repo root
starts it; `testumgebung/aufsetzen.sh` provides the server side.
**DataMetric360 architecture (short — details in `COMPANION_APP_ARCHITECTURE.md`):**
- Future data source: **Teltonika FMM003** on the CAN bus, fully replacing the iPhone WLAN sensor
@@ -170,7 +174,7 @@ formatting hold everywhere; `design-system/` is verifiably brand-free.
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)
### Documentation drift ✅ FIXED 2026-08-11 (kept as a record of what was wrong)
- `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**;
@@ -179,19 +183,21 @@ formatting hold everywhere; `design-system/` is verifiably brand-free.
`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)
### Audit leftovers — two of three now fixed in the new app
- 🟠 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).
- Swipe-to-delete without a gesture-free fallback — **fixed in the app**: every list row also
carries an always-visible "…" menu (`companion-app/src/screens/Zeilenmenue.tsx`). Still open in
the old panel, which is being replaced anyway.
- ✅ Leaflet from a CDN — **fixed in the app**: bundled from node_modules as a lazy chunk.
- 🟡 Popup close-by-tap-outside in the old panel: still unchecked (the app uses the library's
Popup, which is keyboard-reachable).
- Audit's own note: these three may be better done in DataMetric360 than retrofitted — the panel
gets replaced anyway.
### companion-app / design-system
### companion-app / design-system ✅ RESOLVED 2026-08-11
- **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.
- **Not wired together** — fixed: an npm workspace in the repo root links `@audi-dash/ui` into the
app as a real dependency.
- 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).
@@ -201,24 +207,100 @@ formatting hold everywhere; `design-system/` is verifiably brand-free.
---
### Found while building (2026-08-11) — all fixed
Three defects that only surfaced by running against a real Home Assistant, not by reading code:
1. **The data layer declared field names the backend never sends.** `Fahrzeugstatus` had
`tank_prozent`/`sicher_abgestellt`/`sicherheit`; the backend writes
`tankprozent`/`gesichert`/`sicherheitscheck`. Every screen would have read `undefined` without
anything failing. `technik`/`ausstattung` were typed as objects but are arrays.
2. **The profile adapter handed out live references into the raw profile.** Editing a form would
have silently mutated the baseline and broken the promise never to overwrite the
backend-maintained tire odometer. Sections are copied now.
3. **`design-system` still carried the pre-audit `--fg3: #657081`** (3.0:1 on `--tile`, fails WCAG
AA) that the panel had already fixed to `#8a94a3`. The new app would have inherited a
already-solved contrast defect.
Also: two TypeScript parameter properties in the data layer broke Node's strip-only mode, which is
what the smoke scripts run on — rewritten as plain fields.
Three more that only screenshots revealed — nothing failed, the pixels were simply wrong:
4. **The vehicle block on the home screen was invisible.** `overflow: hidden` sets a flex item's
automatic minimum size to 0, so once the page was taller than the screen, flex-shrink squashed
the block to zero height and took model name, badge and plate with it. Fixed with
`.dm-inhalt > * { flex: none }`.
5. **The model name appeared twice** — once as the badge image, once as text beside it.
6. **Filenames overflowed the gallery thumbnails.**
And one in the backend, found by watching the log against a current Home Assistant: the odometer
screening called `urlopen` directly, which HA aborts as a blocking call since 2026.8. Every trip
stayed without a distance, visible only as a warning. Now runs through `task.executor`.
**CORS is a real constraint for this app.** `cors_allowed_origins` did not take effect on HA
2026.8 (preflight 403 even same-origin). Two consequences, both handled: the web build is served
from Home Assistant itself (`/local/dm360/`, same origin — the planned deployment anyway), and the
native hull enables `CapacitorHttp`, which routes fetch through native HTTP where CORS does not
apply. Verify this again at commissioning if the app ever moves to a separate hostname.
**A self-written QR encoder was discarded.** It disagreed with a reference implementation on 1239
of 3249 modules — the code would have been unreadable. `homeassistant/www/dm360-qr.html` now uses a
vendored MIT library served from Home Assistant itself, which satisfies the actual requirement
(no network call, token never leaves the local network) and round-trips correctly.
## Pending: this branch has diverged from `main` (noted 2026-08-13)
`ha_install.md` (root) plans the move from pyscript to a **native HA integration** with a config
flow — UI-only setup, no YAML, and updates via a self-reporting `UpdateEntity` against the Gitea
repo (HACS is GitHub-only, so it is not an option). All APIs in it were verified against the
running 2026.8.1 instance. It also records which review findings that move eliminates by design.
A full review of `main`'s 18 new commits is in `REVIEW_main_2026-08-13.md` — 15 findings, the
three most serious in the new setup menu (saving with an unloaded catalogue wipes the whole
mapping; "reset" has no effect on 15 of 17 fields; all four list positions get the same sensor,
which makes "securely parked" report safe while three doors were never checked).
`main` has moved 18 commits ahead of `umsetzung-datametric360` (FMM003 switch, sensor-mapping setup
menu, iOS/large-screen overlay from Claude Design). **Deliberate decision: do not merge yet** — the
owner keeps working on `main` first.
Three files conflict (`AGENTS.md`, `homeassistant/INSTALL.md`,
`homeassistant/pyscript/fahrterkennung.py`); four more are touched by both sides but merge cleanly.
**One hazard that a clean merge will not catch.** This branch changed `profil_lesen()` in
`homeassistant/pyscript/modules/profil.py` to return `None` when the profile file is missing or
corrupt, and guarded all seven call sites that existed here. `profil.py` is untouched on `main`, so
it merges silently — but `main`'s call sites (`fahrterkennung.py`, `tankerkennung.py`,
`reifenzaehler.py`, `modules/frontend_veroeffentlichung.py`) do **not** guard against `None` and
would raise `AttributeError` on a missing profile instead of logging a clear error.
When merging: take `main`'s FMM003 version of `fahrterkennung.py`, then re-apply the `None` guard to
every remaining `profil.profil_lesen()` call site.
## Open items
Execution order, exact steps, and acceptance criteria for every item below live in
`UMSETZUNGSPLAN.md` (phases 113). Additional decisions of 2026-08-11: **no Electron** (Capacitor
wraps the web app for iPhone; a PWA home-screen install is the accepted intermediate step) and
**no separate backend** (the app talks to the HA REST/WebSocket API directly).
### 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;
- [x] 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
- [x] `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/`
- [x] Implement the screens from the design draft on top of the existing `DataMetricApi` layer
- [x] 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
- [x] 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
- [x] 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)
- [x] 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)
- [x] Offline UX per design brief (offline marker, visible pending queue)
### B) Infrastructure / commissioning (partly waits for FMM003 hardware)
@@ -227,7 +309,7 @@ formatting hold everywhere; `design-system/` is verifiably brand-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)
- [x] 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`).
@@ -420,9 +502,20 @@ formatting hold everywhere; `design-system/` is verifiably brand-free.
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
2026-08-12 (see section B); `DESIGN_REVIEW_2026-08-13.md` and `REVIEW_main_2026-08-13.md`
list further drift not yet fixed (WLAN references still in `SPECIFICATION.md`,
`homeassistant/README.md`, the profile template)
- [x] Harden `profil_lesen()` against missing/corrupt `fahrzeugprofil.json` — done on the
`umsetzung-datametric360` branch (2026-08-11), brought over by the 2026-08-13 merge. Returns
`None` on a missing or malformed file instead of raising; all 5 call sites
(`backup.py` ×2, `fahrterkennung.py`, `frontend_veroeffentlichung.py`, `reifenzaehler.py`,
`tankerkennung.py`) already guard for `None` — verified by grep across the merged tree, no
caller left unguarded (this was flagged as the merge's main risk in `REVIEW_main_2026-08-13.md`
§"Was den Branch betrifft").
- [x] Decide whether the 3 audit leftovers get fixed here or only in DataMetric360 — decided
2026-08-11 (`UMSETZUNGSPLAN.md` Phase 2): swipe-delete keyboard fallback, popup keyboard
access, and self-hosting Leaflet stay unfixed in this panel and are addressed 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)