main ist 18 Commits voraus (FMM003, Setup-Menue, iOS-Auflage). Zusammenfuehren ist bewusst aufgeschoben. Festgehalten ist vor allem die eine Stelle, die ein sauberer Merge nicht auffangen wuerde: profil_lesen() gibt in diesem Branch bei fehlender Profildatei None zurueck, profil.py ist auf main unveraendert und geht damit konfliktfrei durch - aber die dortigen Aufrufstellen pruefen das nicht und wuerden abstuerzen statt eine verstaendliche Meldung zu schreiben.
19 KiB
AGENTS.md — Project state, review findings, open items, and working rules
Last updated: 2026-08-11 (after building the app and the native iOS hull — phases 1, 2, 4–11 of 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.
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,
behavioral rules derived from Andrej Karpathy's observations on LLM coding failures. They bias
toward caution over speed — for trivial tasks, use judgment.
- 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.
- 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.
- 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.
- 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.
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:
- This file (overview + open items)
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 phaseSPECIFICATION.md— authoritative for the HA panel, incl. §7 "Known Gaps"COMPANION_APP_ARCHITECTURE.md— authoritative for DataMetric360 (decided, barely built)AUDIT_2026-08-10.md— accessibility/platform audit of the panel (partly done, rest below)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 — 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 (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
prompt for the Claude Design project.
homeassistant/ in one paragraph: 10 pyscript scripts + 4 modules (pyscript/modules/); trip
detection via the iPhone WLAN sensor with pause tolerance, 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
(3,130 lines, custom element, no framework/bundler), 5 tabs + ~19 detail routes, cache-busting via
audi-dashboard-version.json + loader stub. Entity IDs configured centrally in
pyscript/modules/einstellungen.py (the one file edited before install). Deploy: update.ps1
(robocopy to Samba share) or — still inactive — self-update from git.
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 and the VAG integration.
- Current data path (since 2026-08-11): Codec JSON → MQTT/TLS → Mosquitto add-on → HA. Traccar is dropped (the decision log deliberately remains in the architecture doc, marked ÜBERHOLT).
- Frontend access: HA REST + WebSocket with a long-lived access token (no
hassobject). - External access: Cloudflare Tunnel + reverse proxy with a path allowlist; HA itself stays
unreachable. Domain
datametric360.appregistered (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:
start_lat/lon, addresses,route,avg_speed_kmhnever populated. The trip-detail map draws a fabricated line viafakeTrack()(audi-dashboard-app.js:414) — not a real track. - 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:
BATTERIE_SENSOR = ""(entire battery-history feature is a no-op) andUPDATE_REPO_URL = ""(self-update inactive), both inpyscript/modules/einstellungen.py.www/bilder/has no vehicle photos (all slots show placeholders);steuer.faelligunset. - Robustness:
profil_lesen()inmodules/profil.pydoes not handle a missing/corruptfahrzeugprofil.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 ✅ FIXED 2026-08-11 (kept as a record of what was wrong)
homeassistant/README.md:99,INSTALL.md:204, and the header commentaudi-dashboard-app.js:13-15claim the statistics view shows sample numbers — false;vStat()computes real values fromTRIPS/FILLS.INSTALL.mdstep 4 names variables that no longer exist (DOORS_SENSOR,WINDOWS_SENSOR,LOCK_ENTITY,BATTERY_VOLTAGE_SENSOR) — actual names:TUER_SENSOREN/FENSTER_SENSOREN/TUERSCHLOSS_SENSOREN/BATTERIE_SENSOR.homeassistant/README.md:110mentions the removed 97% full-tank rule (removal documented inbelegverarbeitung.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 — two of three now fixed in the new app
- ✅ 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 ✅ RESOLVED 2026-08-11
- Not wired together — fixed: an npm workspace in the repo root links
@audi-dash/uiinto the app as a real dependency. - Both packages: no
node_modules, nodist— smoke tests neednpm installfirst (design-system additionallynpm run build;scripts/smoke.mjsimports 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 indesign/README.md).
Found while building (2026-08-11) — all fixed
Three defects that only surfaced by running against a real Home Assistant, not by reading code:
- The data layer declared field names the backend never sends.
Fahrzeugstatushadtank_prozent/sicher_abgestellt/sicherheit; the backend writestankprozent/gesichert/sicherheitscheck. Every screen would have readundefinedwithout anything failing.technik/ausstattungwere typed as objects but are arrays. - 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.
design-systemstill 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:
- The vehicle block on the home screen was invisible.
overflow: hiddensets 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 }. - The model name appeared twice — once as the badge image, once as text beside it.
- 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)
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 1–13). 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;
then re-export to
design/ companion-app: set up Vite + React + Capacitor scaffold; wire@audi-dash/uias a real dependency (possibly add a workspace/monorepo root)- Implement the screens from the design draft on top of the existing
DataMetricApilayer - Add Audi assets (fonts/rings/badges) at implementation time from
homeassistant/www/— never intodesign-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_testwith a real token - Offline UX per design brief (offline marker, visible pending queue)
B) Infrastructure / commissioning (partly waits for FMM003 hardware)
- Switch
datametric360.appnameservers 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)
- Generate TLS certificates for Mosquitto + device (small private CA); FMM003 refuses MQTT without them
- Decide broker reachability for the vehicle: port-forward 8883 vs. VPS broker with Mosquitto bridge over Tailscale
- Capture the first real Codec JSON message (
mosquitto_sub/MQTT Explorer) and build the field mapping from it — do not guess beforehand (explicit decision) - Move trip detection to FMM003 ignition (reuse the
fahrterkennung.pypattern, keep pause tolerance); then removesensor.iphone_wifi_connectionfromeinstellungen.py
C) Maintain the existing HA panel (low priority — being replaced)
- Fix documentation drift (statistics claim, INSTALL variable names, README gaps, obsolete TODO comment) — text-only changes
- Harden
profil_lesen()against missing/corruptfahrzeugprofil.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/, setsteuer.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-DEformat; 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.