Kleinere Befunde und Dokumentation aus dem Review

Frontend, kleinere Befunde:
- Das Standort-Menue liess sich nur wischen oder ueber den Kartenmarker
  oeffnen. Der Griff ist jetzt ein Knopf mit aria-expanded, damit auch per
  Tastatur erreichbar; der Umschalter Strasse/Satellit hat aria-pressed und
  ein Label, das den Zustand nennt.
- Der Filterschalter "Nur passende Sensoren anzeigen" wirkte nicht, solange
  eine Auswahlliste offen war: der Klick-Handler ersetzte das Overlay-DOM,
  bevor das change-Ereignis des Kontrollkaestchens ausgeliefert wurde. Er
  wird jetzt vor dem Schliessen der Liste behandelt.
- Der Theme-Wechsel zeichnete nicht neu, die Leaflet-Kacheln blieben bis zum
  naechsten Backend-Update im alten Stil - seit der Dauerkarte auf der
  Uebersicht deutlich sichtbar.
- Toter Code entfernt (fahrzeugGlyphPfade, .dot.neutral) und zwei Kommentare
  berichtigt, die Gegenteiliges behaupteten.

Dokumentation:
- AGENTS.md widersprach sich an sechs Stellen. Berichtigt: Fahrterkennung
  laeuft ueber die Zuendung, nicht ueber WLAN; Datenweg ist flespi, nicht
  MQTT; Modulzahl und Zeilenzahl stimmen wieder; Entity-IDs werden im
  Setup-Menue zugeordnet, nicht in einstellungen.py; STANDORT_TRACKER ist
  belegt, nicht leer.
- SPECIFICATION.md beschreibt durchgehend den Stand vor der FMM003-
  Umstellung. Statt es zu Teilen umzuschreiben und dabei Ungenauigkeiten zu
  riskieren, steht jetzt ein datierter Hinweis am Anfang, der die drei
  geaenderten Punkte benennt und auf AGENTS.md verweist. Alles Uebrige des
  Dokuments gilt unveraendert weiter.
- INSTALL.md: Die zirkulaere Schrittfolge (Schritt 4 verweist auf 9, Schritt
  7 auf 4) ist als solche benannt und aufgeloest. Die Override-Datei ist mit
  Pfad genannt, samt dem Hinweis, dass sie gesichert wird. Und die drei mit
  Test-Entitaeten der Entwicklungsinstanz vorbelegten Rollen sind erwaehnt -
  auf einer frischen Installation zeigen sie ins Leere.
- README, Profilvorlage: WLAN-Reste entfernt, zwei Falschaussagen aus dem
  ersten Review nachgezogen (Statistik rechnet echt, die
  97-Prozent-Volltankungsregel wurde entfernt), Dateiuebersicht um die
  fuenf fehlenden pyscript-Dateien und entitaeten.py ergaenzt.
- update.ps1 liefert jetzt auch shell_beleg_parser.py aus. Die Datei liegt in
  data/, ist aber Code - Aenderungen am Belegleser, dem einzigen getesteten
  Teil des Projekts, kamen bisher auf keiner Instanz an.
- design/README: Zwei Dateien der Inhaltstabelle liegen gar nicht in dem
  Ordner, weshalb das Board aus der Repo-Kopie heraus leer bleibt - jetzt
  vermerkt. Ausserdem die Behauptung berichtigt, die Auflage ruehre
  audi-dashboard-app.js nicht an: der Stylesheet-Loader wurde dort ergaenzt.

Geprueft: Panel laedt, alle neun pyscript-Entitaeten werden veroeffentlicht,
Zuordnen und Zuruecksetzen funktionieren, keine neuen Fehler im Protokoll.
This commit is contained in:
Paul Nothaft
2026-08-13 16:10:19 +02:00
parent bc2834e867
commit e1180f8e34
9 changed files with 133 additions and 47 deletions
+17 -13
View File
@@ -91,14 +91,16 @@ Root files: `dashboard-muster*.html` = original static prototype (superseded, re
`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`
**`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/`):
@@ -111,8 +113,10 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac
**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).
- **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).
@@ -317,9 +321,9 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac
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`**left empty/unconfigured**, no live vehicle GPS source
exists yet (same FMM003/flespi gap as section B); the tile shows "Kein GPS-Signal vom
Fahrzeug" until that's wired up. Verified end-to-end in `audi_ha_test` with a manually
`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
+16
View File
@@ -1,5 +1,21 @@
# Audi Dashboard — Technical Specification
> ## ⚠️ Teilweise überholt (Stand 2026-08-13)
>
> Dieses Dokument beschreibt den Stand **vor** der FMM003-Umstellung. Drei Dinge haben sich seither
> geändert und gelten in diesem Dokument durchgehend als veraltet:
>
> 1. **Fahrterkennung** läuft über den Zündungssensor des FMM003, nicht mehr über den
> iPhone-WLAN-Sensor. `sensor.iphone_wifi_connection` und `fahrzeug.wlan_name` sind entfernt.
> 2. **Datenquelle** ist der FMM003 über flespi (Codec8/TCP), nicht mehr die HACS-Integration
> `TommiG1/HA_VAG-EU-Data-Act`.
> 3. **Entity-IDs** werden im Setup-Menü der App zugeordnet (Einstellungen → Fahrzeug einrichten →
> Setup) und in `data/entitaeten.json` abgelegt. `einstellungen.py` enthält nur noch die
> eingebauten Vorgabewerte und ist keine Pflicht-Bearbeitung mehr vor der Installation.
>
> Alles Übrige — Datenmodell, Geschäftsregeln, Frontend-Aufbau, §7 „Known Gaps" — gilt weiterhin.
> Den aktuellen Stand führt `AGENTS.md`, Abschnitt B.
**Audience:** an AI coding agent picking up this codebase with no prior context.
**Scope:** the Home Assistant panel (`homeassistant/`) — the finished, in-use app. A separate sibling project, `design-system/`, is a standalone React component-library replica of this app's visual language, built solely to feed Claude Design (`claude.ai/design`) via the `/design-sync` skill; it is **not** part of this app and deliberately excludes Audi brand assets. `COMPANION_APP_ARCHITECTURE.md` documents a third, **not-yet-built** sibling project — an iOS/Android/HA-iframe companion app fed by a Teltonika FMM003 tracker instead of the WLAN-sensor/VAG-integration data sources described below; its design is decided but no code exists yet. Don't confuse any of the three.
+2 -2
View File
@@ -14,9 +14,9 @@ Das ist der **Entwurf**, nicht der lauffähige Code. Umgesetzt wird er später i
| `DataMetric360.dc.html` | Einstiegsdatei des Entwurfs |
| `DM360.dc.html` | Die eigentlichen Bildschirme und Zustände |
| `DataMetric360 Board.dc.html` | Vergleichsansicht: dieselbe App gleichzeitig als Smartphone- und Großbildschirm-Rahmen nebeneinander, mit Bildschirm-/Theme-Umschalter. Steuert beide Rahmen per `postMessage`. |
| `dm360-host.html` | Lädt `audi-dashboard-app.js` in einem eigenen `<audi-dashboard-panel>`, hängt danach `audi-dashboard-ios.css` in dessen Shadow-Root - der eigentliche Träger hinter dem Board. |
| `dm360-host.html` | Lädt `audi-dashboard-app.js` in einem eigenen `<audi-dashboard-panel>`, hängt danach `audi-dashboard-ios.css` in dessen Shadow-Root - der eigentliche Träger hinter dem Board. **Achtung: beide Dateien liegen nicht in diesem Ordner.** Aus der Repo-Kopie heraus bleibt das Board deshalb leer; es läuft nur im Claude-Design-Projekt, wo sie mit hochgeladen sind. |
| `ios-frame.jsx` | iOS-26-Gerätekachel (Statusleiste, Dynamic Island, Home-Indikator) - reine Optik, keine App-Logik. |
| `audi-dashboard-ios.css` | **Die eigentliche Neuerung dieses Exports.** Optik-Auflage über `audi-dashboard.css`: iOS-natives Erscheinungsbild (44-px-Zeilen, transluzente Tab-Leiste, iOS-Schalter) plus - wichtig - die responsive Großbildschirm-Umsetzung per `@container (min-width:860px)` (Tab-Leiste wird zur seitlichen Navigation). **Seit 2026-08-11 in `homeassistant/www/` scharf geschaltet und im Docker-Testcontainer bei 375px und 1280px verifiziert** - rührt `audi-dashboard-app.js` nicht an, wie behauptet. |
| `audi-dashboard-ios.css` *(liegt in `../homeassistant/www/`, nicht in diesem Ordner)* | **Die eigentliche Neuerung dieses Exports.** Optik-Auflage über `audi-dashboard.css`: iOS-natives Erscheinungsbild (44-px-Zeilen, transluzente Tab-Leiste, iOS-Schalter) plus - wichtig - die responsive Großbildschirm-Umsetzung per `@container (min-width:860px)` (Tab-Leiste wird zur seitlichen Navigation). **Seit 2026-08-11 in `homeassistant/www/` scharf geschaltet und im Docker-Testcontainer bei 375px und 1280px verifiziert.** Die Auflage selbst ist rein additiv; `audi-dashboard-app.js` wurde nur um den Stylesheet-Loader ergänzt, der sie nachlädt (siehe unten). |
| `datametric360-ios.css` | Dieselbe Optik-Auflage, aber für die `@audi-dash/ui`-Komponentenklassen (`.ads-*`) statt für die Panel-eigenen Klassen - für später, wenn `companion-app/` eine UI bekommt. |
| `support.js` | Hilfsfunktionen des Entwurfs |
| `assets/` | Icons, Logos, Typenschilder und Schriften, die der Entwurf verwendet |
+20 -4
View File
@@ -93,8 +93,13 @@ Anders als früher wird dafür **nicht** mehr `pyscript/modules/
einstellungen.py` von Hand bearbeitet — das übernimmt ein grafisches
Setup-Menü direkt in der App: **Mein Audi → Einstellungen → Fahrzeug
einrichten → Einrichten → „Setup — Sensoren zuordnen"** (letzter Punkt,
erscheint erst nach Klick auf „Einrichten"). Dafür muss die App bereits
laufen, siehe Schritt 9 zuerst.
erscheint erst nach Klick auf „Einrichten").
> **Reihenfolge beachten:** Dieser Schritt braucht eine bereits laufende App.
> Arbeite deshalb erst die Schritte 5 bis 9 ab (Token, Neustart, Prüfung,
> Restarbeiten, Frontend) und komm dann hierher zurück. Schritt 7 verweist
> seinerseits auf die hier vorgenommene Zuordnung — das ist kein Widerspruch,
> sondern schlicht die Reihenfolge: erst starten, dann zuordnen, dann prüfen.
Das Setup-Menü listet jede Sensor-Rolle, die die App kennt (Zündung/
Fahrterkennung, Kilometerstand, Tankfüllstand, Standort, Batteriespannung,
@@ -104,6 +109,16 @@ anzeigen" grenzt auf die erwartete Domäne/Einheit ein) und schreibt die
Auswahl direkt in eine Override-Datei — `einstellungen.py` selbst bleibt
unverändert.
Die Override-Datei ist `/config/audi_dashboard/entitaeten.json`. Sie wird von
der automatischen Sicherung und vom Backup-Export mit erfasst; ein
Wiederherstellen spielt die Zuordnung also mit zurück.
**Hinweis zu den Vorgabewerten:** Drei Rollen sind in `einstellungen.py` mit
den Test-Entitäten der Entwicklungsinstanz vorbelegt (Zündung, Batterie-
spannung, Standort — jeweils `*_testzone_fmm003`). Auf einer frischen
Installation zeigen sie ins Leere; das Setup-Menü meldet dann „Entität nicht
gefunden". Einfach die eigenen Entitäten zuordnen, damit ist es erledigt.
**Zwingend, damit die Fahrterkennung läuft:**
- **Zündung/ACC-Status** (`ZUENDUNG_SENSOR`) — ein `binary_sensor`, `on`
= Fahrt läuft. Kommt vom Teltonika FMM003 (z. B.
@@ -226,8 +241,9 @@ Der `panel_custom`-Eintrag aus Schritt 3 zeigt auf
Prototyp. Das Gerät braucht dafür zusätzlich zur Tailscale-Verbindung
normalen Internetzugang — sonst bleibt die Karte auf der Fahrt- und
Tankvorgang-Detailseite leer.
- **Statistik-Seite** zeigt weiterhin Beispielzahlen aus dem Prototyp,
keine echte Auswertung der eigenen Fahrten — eigenes Arbeitspaket.
- **Statistik-Seite** wertet die eigenen Fahrten und Tankvorgänge echt aus
(Zeiträume, Verbrauch, Tag/Nacht, privat/Arbeitsweg). Sie bleibt nur so
lange leer, wie noch keine Fahrten und Tankungen erfasst sind.
## Schritt 10 — künftige Updates einspielen, ohne die App neu zu bauen
+16 -6
View File
@@ -96,22 +96,26 @@ Datendateien — nie App-Code oder -Konfiguration.
`fahrterkennung.py`.
- **Bilder fehlen** (§7a) — Ordner `www/bilder/` ist noch leer. Layout
springt nicht (Maße sind reserviert), aber die Flächen bleiben leer.
- **Statistik-Seite** zeigt weiterhin Beispielzahlen aus dem Prototyp, keine
echte Auswertung der erfassten Fahrten.
- **Leaflet lädt per CDN**, wie im Prototyp — braucht zusätzlich zu
Tailscale echten Internetzugang auf dem Gerät.
## Was schon funktioniert (getestet)
- Fahrterkennung über WLAN mit Pausenregel (`task.unique` + `task.sleep`)
- Fahrterkennung über den Zündungssensor mit Pausenregel (`task.unique` + `task.sleep`);
bis 2026-08-12 lief sie über den WLAN-Sensor des iPhones
- Zweistufiger Fahrtabschluss per Recorder-Historie-Screening, inklusive
Verkettung direkt anschließender Fahrten
- Reifenzähler per direkter Subtraktion, kein `utility_meter`
- Belegverarbeitung inklusive 97-%-Volltankungsregel und Duplikatserkennung
über `receipt_key`
- Belegverarbeitung mit Duplikatserkennung über `receipt_key` (die zunächst
geplante 97-%-Volltankungsregel wurde per Änderungswunsch entfernt, siehe
Kopfkommentar in `pyscript/belegverarbeitung.py`)
- Manuelle Fallback-Wege für Fahrten und Tankvorgänge ohne Beleg
- Frontend: Übersicht, Fahrtenliste, Navigation zwischen allen Ansichten,
Datenadapter Profil→Oberfläche, Schreibaktionen über `hass.callService`
- Statistik-Seite mit echter Auswertung aus Fahrten und Tankvorgängen
(Zeiträume, Verbrauch, Tag/Nacht, privat/Arbeitsweg)
- Setup-Menü für die Sensor-Zuordnung, inklusive Sicherung in
`data/entitaeten.json`
## Dateiübersicht
@@ -129,12 +133,18 @@ homeassistant/
│ ├── modules/
│ │ ├── einstellungen.py zentrale Entity-ID-Konfiguration (Schritt 4)
│ │ ├── profil.py Datenzugriff (§6)
│ │ ├── entitaeten.py Sensor-Zuordnung aus dem Setup-Menü
│ │ ├── fahrtabschluss_logik.py Screening-Logik (§7.2)
│ │ └── frontend_veroeffentlichung.py Zustände fürs Frontend (§10 Punkt 7 Ersatz)
│ ├── fahrterkennung.py §7.1
│ ├── fahrterkennung.py §7.1 (Zündungssensor, früher WLAN)
│ ├── fahrtabschluss.py §7.2 (Trigger-Registrierung)
│ ├── tankerkennung.py §7.4 (automatische Erkennung am Füllstandsanstieg)
│ ├── reifenzaehler.py §7.5
│ ├── belegverarbeitung.py §7.4, §7.7
│ ├── batterieverlauf.py 12-V-Spannung, Tagesminimum/-maximum
│ ├── bilderverwaltung.py Upload/Löschen der Fahrzeugfotos
│ ├── backup.py tägliche Sicherung und Wiederherstellung
│ ├── updateverwaltung.py Update suchen und installieren
│ └── frontend_api.py Lese-/Schreib-Anbindung fürs Frontend
├── www/
│ ├── audi-dashboard-panel.js Lade-Stub (zeigt configuration.yaml hierher), ändert sich kaum
@@ -1,5 +1,5 @@
{
"_hinweis": "Vorlage ohne echte Fahrzeug-/Personendaten. Vor der Installation nach fahrzeugprofil.json kopieren und die Platzhalter unten ersetzen (VIN, Kennzeichen, Erstzulassung, HU-Termin, WLAN-Name, Versicherung, Werkstatt, Servicebuch) - am einfachsten über den Button 'Einrichten' in den Einstellungen der App, sobald sie einmal läuft. Diese Datei selbst bleibt bewusst im Repo (siehe .gitignore), die echte fahrzeugprofil.json nicht.",
"_hinweis": "Vorlage ohne echte Fahrzeug-/Personendaten. Vor der Installation nach fahrzeugprofil.json kopieren und die Platzhalter unten ersetzen (VIN, Kennzeichen, Erstzulassung, HU-Termin, Versicherung, Werkstatt, Servicebuch) - am einfachsten über den Button 'Einrichten' in den Einstellungen der App, sobald sie einmal läuft. Diese Datei selbst bleibt bewusst im Repo (siehe .gitignore), die echte fahrzeugprofil.json nicht.",
"fahrzeug": {
"fin": "",
+17 -4
View File
@@ -21,10 +21,16 @@
ohne Cache und lädt den eigentlichen Code darüber immer frisch nach
(siehe Kopfkommentar in www/audi-dashboard-panel.js).
Absichtlich NICHT kopiert: data/ - das würde ein bereits laufendes
Fahrzeugprofil, die Fahrten- und Tankvorgänge-Archive überschreiben. Die
data/-Dateien sind nur die Erstbefüllung für die einmalige Installation
(siehe INSTALL.md Schritt 2).
Absichtlich NICHT kopiert: die Datenbestände in data/ - das würde ein
bereits laufendes Fahrzeugprofil, die Fahrten- und Tankvorgänge-Archive
sowie die Sensor-Zuordnung überschreiben. Diese Dateien sind nur die
Erstbefüllung für die einmalige Installation (siehe INSTALL.md Schritt 2).
Eine Ausnahme davon ist shell_beleg_parser.py: die Datei liegt zwar in
data/, ist aber Code, kein Datenbestand - belegverarbeitung.py ruft sie
als Unterprozess aus /config/audi_dashboard/ auf. Ohne diese Ausnahme
käme jede Änderung am Belegleser (dem einzigen getesteten Teil des
Projekts) auf keiner Instanz an.
.PARAMETER Ziel
Pfad zum config-Verzeichnis von Home Assistant, z. B. ein per Samba
@@ -55,6 +61,13 @@ Copy-Item "$hier\www\audi-dashboard-panel.js" "$Ziel\www\audi-dashboard-panel.js
Copy-Item "$hier\www\audi-dashboard.css" "$Ziel\www\audi-dashboard.css" -Force
Copy-Item "$hier\www\audi-dashboard-ios.css" "$Ziel\www\audi-dashboard-ios.css" -Force
# Code, der in data/ liegt (siehe Kopfkommentar) - der Ordner selbst wird
# bewusst nicht gespiegelt, diese eine Datei aber schon.
if (Test-Path "$Ziel\audi_dashboard") {
Copy-Item "$hier\data\shell_beleg_parser.py" "$Ziel\audi_dashboard\shell_beleg_parser.py" -Force
Write-Host " Belegleser aktualisiert"
}
if (Test-Path "$hier\www\bilder") {
Write-Host "Kopiere www/bilder/ ..." -ForegroundColor Cyan
New-Item -ItemType Directory -Force -Path "$Ziel\www\bilder" | Out-Null
+31 -15
View File
@@ -500,13 +500,6 @@ const STANDORT_PEEK_PX = 96; // sichtbarer Anteil im geschlossenen Zustand, muss
function standortBekannt() { return CAR.standortLat != null && CAR.standortLon != null; }
/* Fahrzeug-Symbol im myAudi-Stil: einfaches Top-down-Glyph, noch fuer
Vorschauen in Gebrauch; die Kartenmarkierung nutzt poi-car (Audi CI). */
function fahrzeugGlyphPfade() {
return `<path d="M5 15.3 6.4 9.9a2 2 0 0 1 1.9-1.5h7.4a2 2 0 0 1 1.9 1.5l1.4 5.4"/>
<path d="M4.6 15.3h14.8a.9.9 0 0 1 .9.9v1.1a.9.9 0 0 1-.9.9H4.6a.9.9 0 0 1-.9-.9v-1.1a.9.9 0 0 1 .9-.9Z"/>
<path d="M7.4 11.4h9.2"/>`;
}
/* Kartenmarkierung: poi-car aus dem Audi-CI-Satz (assets/icons/poi-car.svg).
Die Spitze sitzt unten, deshalb ankert Leaflet am unteren Rand. */
function fahrzeugMarkerSVG(px = 34) {
@@ -514,12 +507,14 @@ function fahrzeugMarkerSVG(px = 34) {
}
/* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist
(siehe fahrterkennung.py), sonst "steht" - erst ab der in "Fahrten
zusammenfassen" hinterlegten Pausenzeit (CONFIG.pauseMin) gilt der
Stillstand als endgültig genug für "Letzter Parkplatz" statt eines
bloßen Zwischenstopps. Ohne echtes Bewegungssignal vom Fahrzeug (siehe
AGENTS.md, FMM003/flespi offen) ist das die beste Näherung aus den
bereits vorhandenen Fahrtdaten. */
(siehe fahrterkennung.py), sonst "steht". Ohne echtes Bewegungssignal vom
Fahrzeug (siehe AGENTS.md, FMM003/flespi offen) ist das die beste Näherung
aus den bereits vorhandenen Fahrtdaten.
parkplatz unterscheidet einen endgültigen Stillstand (laenger als die in
"Fahrten zusammenfassen" hinterlegte Pausenzeit) von einem blossen
Zwischenstopp. Das Feld ist vorbereitet, wird aber derzeit von keiner
Ansicht gelesen - eine "Letzter Parkplatz"-Anzeige gibt es noch nicht. */
function standortZustand() {
const t = TRIPS[0];
if (t && t.status === "offen") return { faehrt: true, seitTs: null, parkplatz: false };
@@ -812,7 +807,8 @@ function vStandort() {
<div class="standort-vollbild">
<div id="smap" class="standort-karte"></div>
<div class="kartensteuerung">
<button type="button" data-standort-layer aria-label="Kartendarstellung wählen">
<button type="button" data-standort-layer aria-pressed="${SMAP_STIL === "satellit"}"
aria-label="${SMAP_STIL === "satellit" ? "Satellitenansicht, zur Straßenkarte wechseln" : "Straßenkarte, zur Satellitenansicht wechseln"}">
${ciSVG(CI.mapLayer, 22)}
</button>
<button type="button" data-standort-fahrzeug aria-label="Fahrzeug auf der Karte suchen">
@@ -827,7 +823,12 @@ function vStandort() {
</div>
<div class="standortmenu" id="standortmenu">
<div class="standortmenu-griffzone">
<div class="standortmenu-griff"></div>
<!-- Als Knopf, nicht als reines div: sonst laesst sich das Menue
ausschliesslich per Wischgeste oder Klick auf den Kartenmarker
oeffnen und ist mit Tastatur gar nicht erreichbar. -->
<button type="button" class="standortmenu-griff" data-standort-umschalten
aria-expanded="${STANDORTMENU_OFFEN}" aria-controls="standortmenu"
aria-label="Fahrzeugdetails ${STANDORTMENU_OFFEN ? "einklappen" : "aufklappen"}"></button>
<button type="button" class="standortmenu-schliessen" data-standort-schliessen aria-label="Schließen">
<svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M5 5l10 10M15 5 5 15"/></svg>
</button>
@@ -3095,6 +3096,12 @@ function ereignisseVerdrahten() {
ROOT.getElementById("theme").addEventListener("click", () => {
themeSetzen(HOST.dataset.theme === "nacht" ? "tag" : "nacht");
themeIcon();
/* Neu zeichnen, damit auch die Leaflet-Kacheln den Stil wechseln: die
Kachel-URL wird beim Aufbau aus TILES[theme] gewaehlt und nicht
nachtraeglich aktualisiert. Ohne dies blieb die Karte bis zum naechsten
Backend-Update im alten Stil - seit der Dauerkarte auf der Uebersicht
deutlich sichtbar. */
render();
});
// Wischgeste über Pointer Events (nicht Touch Events), damit dieselbe
// Logik auch mit der Maus funktioniert (z. B. beim Testen im Browser).
@@ -3316,6 +3323,14 @@ function ereignisseVerdrahten() {
// Klick irgendwo sonst im Popup (aber nicht auf den Catcher, der schon
// oben behandelt wurde) schließt ein offenes Dropdown, ohne das ganze
// Popup zu verlassen.
/* Den Filterschalter VOR dem Schliessen der Auswahlliste behandeln.
Sonst ersetzt render() unten das Overlay-DOM, bevor das
change-Ereignis des Kontrollkaestchens ausgeliefert wird - der
Schalter sprang dann beim naechsten Zeichnen einfach zurueck. */
if (e.target.closest("[data-setup-nurpassend]") || e.target.closest(".switch")) {
const kasten = ROOT.querySelector("[data-setup-nurpassend]");
if (kasten) { setupNurPassend = !setupNurPassend; render(); return; }
}
if (setupSucheOffen && !e.target.closest(".entitaet-combo")) { setupSucheOffen = null; setupSuchtext = ""; render(); return; }
return;
}
@@ -3590,6 +3605,7 @@ function ereignisseVerdrahten() {
if (art) { const tr = TRIPS.find((x) => x.trip_id === art.dataset.art); tr.art = tr.art === "arbeitsweg" ? "privat" : "arbeitsweg"; render(); return; }
// Standort (Vollbild) - Karten-/Menü-Bedienung manipuliert SMAP/das Menü-
// DOM direkt statt über render() (siehe Kopfkommentar Standort-Abschnitt).
if (e.target.closest("[data-standort-umschalten]")) { standortMenuUmschalten(); return; }
if (e.target.closest("[data-standort-layer]")) { SMAP_STIL = SMAP_STIL === "satellit" ? "strasse" : "satellit"; standortTiles(); return; }
if (e.target.closest("[data-standort-fahrzeug]")) { standortAufFahrzeug(); return; }
if (e.target.closest("[data-standort-user]")) { standortAufUser(); return; }
+13 -2
View File
@@ -749,7 +749,19 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
}
.standortmenu.offen { transform: translateY(0); }
.standortmenu-griffzone { padding: 10px 0 14px; touch-action: none; cursor: grab; position: relative; }
.standortmenu-griff { width: 36px; height: 4px; border-radius: 2px; background: var(--line-strong); margin: 0 auto; }
/* Der Griff ist jetzt ein Knopf (Tastaturzugang zum Menue). Die Knopf-
Voreinstellungen des Browsers werden deshalb zurueckgesetzt; die
Trefferflaeche waechst per Pseudo-Element auf 44px, ohne den sichtbaren
Strich zu vergroessern. */
.standortmenu-griff {
position: relative; display: block; width: 36px; height: 4px;
border: 0; padding: 0; border-radius: 2px;
background: var(--line-strong); margin: 0 auto; cursor: pointer;
}
.standortmenu-griff::after {
content: ""; position: absolute; left: 50%; top: 50%;
transform: translate(-50%, -50%); width: 88px; height: 44px;
}
.standortmenu-schliessen {
position: absolute; top: 14px; right: 0; width: 32px; height: 32px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
@@ -762,7 +774,6 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.standortmenu-adresse { font-size: 13.5px; color: var(--fg2); margin-top: 20px; }
.standortmenu-status { display: flex; align-items: center; gap: 9px; margin-top: 8px; font-size: 13.5px; color: var(--fg2); }
.standortmenu-status svg { color: var(--fg3); flex: 0 0 auto; }
.dot.neutral { background: var(--fg3); }
.standortmenu-werte { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 13.5px; color: var(--fg2); }
.standortmenu-werte svg { color: var(--fg3); flex: 0 0 auto; }
.standortmenu-aktionen { display: flex; gap: 10px; margin-top: 18px; padding-bottom: 4px; }