Files
eventmap/docs/plan.md
T
dapelza ee55fb1cf9 Phase 0+1: Scaffold, Datenmodell, Karte und Liste
Reines HTML5/CSS3/JavaScript ohne Build-Tooling, lauffaehig direkt per
Doppelklick auf index.html (file://, kein Server noetig):

- Datenmodell fuer Ausflugsziele mit allen Feldern aus der Anforderung,
  15 feste Kategorien inkl. Farbe/Icon
- Zentraler Pub/Sub-Store mit LocalStorage-Persistenz und
  Migrations-Grundgeruest
- Interaktive Karte (Leaflet + Leaflet.markercluster, lokal vendored)
  mit kategoriefarbenen Markern, Clustering, Popups, Geolocation-Button
- Kartengrundlage ueber CARTO-Basemaps statt tile.openstreetmap.org, da
  dessen Referer-Pflicht file://-Aufrufe blockiert
- Seed-Daten als eingebettetes Script (data/seed.js) statt JSON-Datei,
  da fetch() auf lokale Dateien unter file:// nicht zuverlaessig
  funktioniert
- Ergebnisliste mit 10 Beispiel-Ausflugszielen, sicher gerendert ueber
  textContent/DOM-APIs statt innerHTML (Vorbereitung fuer spaeteren
  KI-JSON-Import mit Fremddaten)
- Responsives Layout (Desktop nebeneinander, Mobile gestapelt)
2026-07-24 11:24:47 +02:00

8.3 KiB
Raw Blame History

EventMap Gesamtplan

Dieses Dokument fasst den vom architect-Agenten erarbeiteten Plan zusammen und wird phasenweise fortgeschrieben. Aktuell umgesetzt: Phase 0 (Scaffold) und Phase 1 (Datenmodell + Karte + Liste, read-only mit Beispieldaten).

Tech-Stack

  • Vanilla HTML5/CSS3/JavaScript, klassische Scripts (kein type="module"); Module-Ersatz über einen globalen Namespace window.EventMap + IIFE-Kapselung pro Datei, siehe docs/adr/0001-vanilla-esm.md. Grund: die App wird per Doppelklick direkt aus file:// geöffnet, native ES-Module würden dort per CORS-Policy blockiert.
  • Kein Build-Tool, kein Bundler, kein Framework
  • Karte: Leaflet 1.9.4 + Leaflet.markercluster 1.5.3, lokal vendored unter vendor/
  • Kartengrundlage: OpenStreetMap-Daten, ausgeliefert über CARTO-Basemap-Tiles (basemaps.cartocdn.com, Stil "light_all") statt des offiziellen tile.openstreetmap.org. Grund: dessen Tile Usage Policy verlangt seit 2024 einen gültigen HTTP-Referer, den file://-Seiten (Doppelklick-Start) grundsätzlich nicht mitsenden können ("Access blocked: Referer is required"). CARTO liefert dieselben OSM-Daten kartographisch auf, ohne diese Einschränkung; Attribution zeigt weiterhin OpenStreetMap + CARTO.
  • Datenhaltung: LocalStorage (Offline-first), vorbereitet für spätere Migration auf Backend/API

Ordnerstruktur

index.html
css/            reset, variables (Design-Tokens), layout, components, responsive
js/config/      constants.js, categories.js
js/store/       store.js (Pub/Sub-State), storage.js (LocalStorage-IO), migrations.js
js/models/      place.js (Normalisierung), schema.js (Feldstruktur/Defaults)
js/map/         mapController.js, markers.js, geolocation.js
js/ui/          list.js (Ergebnisliste; weitere UI-Module folgen in späteren Phasen)
js/utils/       dom.js, geo.js, id.js
data/seed.js     Beispieldaten (10 Ausflugsziele, wird nur beim allerersten Start geladen)
vendor/         lokal vendorte Leaflet-Bibliotheken (JS/CSS/Bildassets)
docs/           dieser Plan, ADRs

Datenmodell (Ausflugsziel / Place)

Das interne Place-Objekt orientiert sich eng am verbindlichen KI-Import-JSON-Format aus anforderung.md (siehe dortiger Abschnitt "Vorgabe für das KI-JSON"):

  • Basisfelder: name, category, shortDescription, description, latitude, longitude, address, city, region
  • ageRecommendation: { min, max }
  • cost: { type: 'free'|'paid'|'mixed'|'unknown', description }
  • openingHours, durationMinutes
  • facilities: { strollerAccessible, wheelchairAccessible, toilets, parking, restaurant }
  • environment: 'indoor'|'outdoor'|'mixed'
  • website, image, rating, tags[], source, sourceVerified
  • Intern generiert: id (UUID), createdAt, updatedAt (ISO-Strings)

js/models/place.js normalisiert rohe Objekte (aus data/seed.js oder späteren Importen) zu diesem vollständigen internen Format; die Kategorie wird dabei immer auf eine gültige Kategorie-id (Slug, siehe js/config/categories.js) aufgelöst entweder direkt (falls schon ein Slug vorliegt) oder über das Label (z.B. "Spielplatz"spielplatz), wie es die KI liefern würde.

Kategorien

14 Kategorien aus der Anforderung + "Sonstige" als generischer Fallback (insgesamt 15), jeweils mit id (Slug), label, color (Hex) und icon (Emoji-Glyph) siehe js/config/categories.js.

Store-Architektur

js/store/store.js ist ein zentraler Pub/Sub-Store:

state = { places, filters: null, userLocation: null, selectedPlaceId: null }
  • subscribe(fn) registriert Listener, setState(patch) merged den State und benachrichtigt alle Subscriber.
  • initStore() lädt beim Start persistierte Daten aus LocalStorage (js/store/storage.js). Ist noch nichts gespeichert, wird data/seed.js geladen, normalisiert und einmalig persistiert.
  • Einstellungen (lastFilters, lastMapView, userConsentGeo, defaultCenter) werden getrennt über getSettings()/updateSettings() verwaltet und im Key eventmap.settings.v1 persistiert.

LocalStorage-Schema

  • eventmap.data.v1{ schemaVersion: 1, places: [...], updatedAt }
  • eventmap.settings.v1{ lastFilters, lastMapView, userConsentGeo, defaultCenter }

js/store/migrations.js stellt eine schlanke, sequenziell anwendbare Migrations-Infrastruktur bereit (migrate(data)). Aktuell existiert nur Schema-Version 1, das Migrationsarray ist entsprechend leer.

defaultCenter (frei wählbares Fallback-Kartenzentrum) ist als Datenfeld vorbereitet, aber in Phase 1 noch nicht über die UI einstellbar. Solange kein defaultCenter gesetzt ist, verwendet die Karte den hartcodierten Default aus js/config/constants.js (geografische Mitte Deutschlands, lat 51.1657, lng 10.4515, zoom 6).

Karte

  • js/map/mapController.js: initialisiert Leaflet-Karte + OSM-Tile-Layer (inkl. korrekter Attribution), verwaltet ein L.markerClusterGroup, reagiert per subscribe() auf Änderungen an state.places (Marker neu rendern) und state.selectedPlaceId (Karte zentrieren + Popup öffnen). fitToPlaces() passt die Ansicht auf alle vorhandenen Places an, mit Fallback auf das Default-Center falls keine Places mit gültigen Koordinaten vorhanden sind.
  • js/map/markers.js: erzeugt je Kategorie ein L.divIcon (Farbe + Emoji, kein PNG-Sprite nötig) und den Popup-Inhalt (Name, Kategorie, Kurzbeschreibung) als DOM-Node (nicht als HTML-String, siehe Sicherheitshinweis unten).
  • js/map/geolocation.js: fragt den Standort nur auf explizite Nutzeraktion (Button "Meinen Standort verwenden") ab, setzt bei Erfolg userLocation im Store und zentriert die Karte; bei Ablehnung/Fehler bleibt das Default-Center erhalten und ein Hinweis wird angezeigt.

Ergebnisliste

js/ui/list.js rendert state.places als Cards (Name, Kategorie mit Farbe/Icon, Kurzbeschreibung). Ein Klick auf eine Card setzt selectedPlaceId im Store; mapController.js reagiert darauf und zentriert die Karte auf den zugehörigen Marker. Filterung (Phase 4) ist hier noch nicht implementiert aktuell werden immer alle Places gezeigt.

Sicherheitshinweis: Da künftig Fremddaten per KI-Import in die App gelangen, wird an keiner Stelle innerHTML mit Place-Daten befüllt. js/utils/dom.js stellt dafür el() (DOM-Erzeugung über textContent/ Properties) sowie escapeHtml() als zusätzliches Sicherheitsnetz bereit.

Layout

  • Header mit Titel "EventMap".
  • Mobile/Tablet (< 1024px): Karte oben (fixe Höhe ca. 50vh), Liste darunter, normal scrollbar.
  • Desktop (≥ 1024px): Grid mit Karte (~65%) und Liste-Sidebar (~35%) nebeneinander, siehe css/responsive.css.

Kein Dev-Server nötig

Die App verwendet klassische <script>-Tags statt ES-Module (siehe docs/adr/0001-vanilla-esm.md) und lässt sich daher per Doppelklick auf index.html direkt aus dem lokal synchronisierten Ordner öffnen (file://...) ein lokaler HTTP-Server ist dafür nicht mehr nötig.

Die Beispieldaten liegen in data/seed.js als eingebettetes <script>-Objekt (EventMap.data.SEED_PLACES) statt als .json-Datei, da fetch() auf lokale Dateien über file:// in manchen Browsern (insbesondere Chrome/Chromium) blockiert wird. initStore() liest die Seed-Daten dadurch synchron direkt aus dem globalen Namespace, ganz ohne fetch().

Phasenübersicht

Phase Inhalt Status
0 Scaffold (Ordnerstruktur, vendored Leaflet, Grundgerüst) umgesetzt
1 Datenmodell, Karte, Liste (read-only, Seed-Daten) umgesetzt
2 Detailansicht (Titelbild, vollständige Beschreibung, Mini-Karte, Navigation-Button, Bearbeiten/Löschen) offen
3 Formular zum manuellen Anlegen/Bearbeiten (Validierung, Koordinatenprüfung) offen
4 Filter- und Suchfunktion (Kategorie, Entfernung, Alter, Kosten, Indoor/Outdoor, Ausstattung, Bewertung, Region) inkl. Anwendung auf Karte + Liste offen
5 Route/Navigation-Integration (z.B. Link zu externem Kartendienst) offen
6 KI-Prompt-Generator (Eingabeformular, Promptgenerierung, Copy/Download) offen
7 KI-JSON-Import (Validierung, Vorschau, Übernahme ins LocalStorage) offen

Nicht Teil von Phase 0/1 und daher bewusst noch nicht angelegt: Filter-Modul, Detail-Modul, Formular-Modul, Prompt-Generator-Modul, Import-Modul.