Files
eventmap/docs/adr/0001-vanilla-esm.md
dapelzaandClaude Sonnet 5 2aaff8f680 Seed-Daten per Fetch von externem Server laden, UI-Sprachumschalter (DE/EN) und KI-Übersetzungs-Prompt-Generator ergänzen
- Beispieldaten werden nicht mehr eingebettet (data/seed.js entfernt),
  sondern beim ersten Start per fetch(EventMap.config.SEED_DATA_URL) von
  einem externen, selbst gehosteten Server geladen (siehe ADR 0001,
  Revision 2026-07-28).
- Neuer UI-Sprachumschalter (Deutsch/Englisch) für die Bedienoberfläche:
  js/i18n/strings.js + js/i18n/i18n.js, Sprachwahl im Menü, Persistenz über
  settings.language, Retranslation ohne Reload.
- Neuer Menüpunkt "Ausflugsziel übersetzen (KI)": erzeugt einen Prompt, der
  eine externe KI bittet, Name/Beschreibungen eines bestehenden Ortes ins
  Englische, Spanische oder Französische zu übersetzen (js/ai/translatePromptBuilder.js,
  js/ui/translatePromptGenerator.js).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 15:16:31 +02:00

6.2 KiB
Raw Permalink Blame History

ADR 0001: Kein Build-Tooling, klassische Scripts mit globalem Namespace, Leaflet lokal vendored

Status

Akzeptiert (überarbeitet siehe "Revision" unten; ursprüngliche Entscheidung für native ES-Module wurde revidiert)

Kontext

anforderung.md fordert explizit "möglichst einfache und gut verständliche Technologien" (HTML5, CSS3, JavaScript, eine Open-Source-Kartenbibliothek, LocalStorage) und stellt keinen Backend-Zwang für die erste Version. Die Anwendung soll außerdem vollständig responsiv sein und ohne größere Infrastruktur betrieben und gewartet werden können.

Die tatsächliche Zielumgebung des Nutzers wurde präzisiert: Die App wird per Doppelklick auf index.html direkt aus einem lokal synchronisierten Nextcloud-Ordner geöffnet (file://...). Es steht kein lokaler HTTP-Server zur Verfügung und ein solcher soll vom Endnutzer auch nicht manuell gestartet werden müssen Priorität hat maximale Einfachheit für den Endnutzer, nicht Entwicklerkomfort.

Revision (2026-07-24)

Die ursprüngliche Entscheidung, JavaScript als native ES-Module (<script type="module"> mit import/export) umzusetzen, war für diesen Anwendungsfall falsch: Browser blockieren das Nachladen von ES-Modulen über file:// per CORS-Policy (Access to script ... has been blocked by CORS policy). Die App ließ sich damit nur über einen lokalen HTTP-Server starten was der Anforderung "Doppelklick, kein Server" widerspricht.

Neue Entscheidung: Alle JavaScript-Dateien wurden von ES-Modulen auf klassische, nicht-modulare <script>-Tags umgebaut. Statt import/export hängt jede Datei ihre öffentliche API an ein einziges globales Namespace-Objekt window.EventMap (mit Sub-Namespaces utils, config, models, store, map, ui) an. Jede Datei kapselt ihre internen Hilfsvariablen weiterhin in einer IIFE ((function () { ... })();), sodass nur das explizit an EventMap.* angehängte gemeinsam genutzt wird. Die Ladereihenfolge in index.html (Utils → Config → Models → Store → Map → UI → main.js) stellt sicher, dass jede Datei beim Ausführen bereits auf die Namespaces ihrer Abhängigkeiten zugreifen kann.

Entscheidung

  • Es wird kein Build-Tooling (kein Bundler wie Webpack/Vite, kein Transpiler, kein Package-Manager-Setup für die App selbst) eingesetzt.
  • JavaScript wird als klassische Scripts (<script src="...">, ohne type="module") eingebunden, organisiert über einen gemeinsamen globalen Namespace window.EventMap statt über import/export. Jede Datei ist in eine IIFE gekapselt.
  • Leaflet und Leaflet.markercluster werden lokal vendored unter vendor/leaflet/ bzw. vendor/leaflet.markercluster/ (JS, CSS und Marker-Bildassets), nicht per CDN eingebunden.

Begründung

  • Entspricht direkt der Anforderung nach einfachen, gut verständlichen Technologien ohne zusätzliche Lernkurve (kein Framework, kein Build-Schritt).
  • Ermöglicht das Öffnen der App per Doppelklick aus dem synchronisierten Ordner heraus, ohne dass der Endnutzer einen Server starten oder irgendetwas installieren muss das ist für die Zielgruppe wichtiger als natives Modul-Tooling.
  • Lokal vendorte Bibliotheken machen die App offline-fähig und unabhängig von der Verfügbarkeit externer CDNs; das passt zum Offline-First-Ansatz der LocalStorage-Datenhaltung.
  • Geringere Angriffsfläche/Komplexität: keine Abhängigkeit von Node-Toolchain- Versionen, keine Build-Artefakte, die aktuell gehalten werden müssten.

Konsequenzen

  • Trade-off gegenüber ES-Modulen: Es gibt kein natives Modul-Scoping mehr. Stattdessen dient der globale window.EventMap-Namespace plus IIFE-Kapselung pro Datei als Ersatz. Das erfordert Disziplin beim Vergeben von Namen unter EventMap.* (keine versehentlichen Überschreibungen) und macht Abhängigkeiten weniger explizit als import-Statements sie ergeben sich aus der Script-Reihenfolge in index.html, die entsprechend sorgfältig gepflegt werden muss.
  • Behobenes Restrisiko (2026-07-24): Ursprünglich lud js/store/store.js die Beispieldaten per fetch('data/seed.json'), was unter file:// (insbesondere in Chrome/Chromium) von der Same-Origin-Policy blockiert wird. Die Seed-Daten wurden daher von data/seed.json nach data/seed.js verschoben und lagen dort als eingebettetes <script>-Objekt (EventMap.data.SEED_PLACES), das wie jede andere JS-Datei klassisch eingebunden wurde. initStore() war dadurch synchron. Für spätere Phasen (z.B. KI-JSON-Import) gilt weiterhin: Datei-Uploads über <input type="file"> + FileReader sind unproblematisch unter file://, ein fetch() auf lokale Dateien dagegen nicht.
  • Revision (2026-07-28): data/seed.js wurde aus dem Repo entfernt. Die Beispieldaten werden jetzt beim allerersten Start per fetch(EventMap.config.SEED_DATA_URL) von einem externen, selbst gehosteten Server geladen (siehe js/store/store.js, loadSeedPlaces()/initStore()). Das ist kein Rückfall in das oben beschriebene Problem, da sich die Same-Origin-Blockade unter file:// nur auf lokale Dateien bezieht ein fetch() auf eine entfernte https://-URL funktioniert auch von file:// aus, sofern der Server passende CORS-Header (Access-Control-Allow-Origin) sendet. initStore() ist dadurch wieder asynchron (main.js awaitet es bereits). Schlägt der Download fehl (offline, fehlende CORS-Header, Server nicht erreichbar), startet die App bewusst mit einer leeren Places-Liste und zeigt einen Hinweis im Status-Banner statt die gesamte Initialisierung abzubrechen.
  • Es gibt keinen automatischen Minify-/Tree-Shaking-Schritt; die Dateigröße im Produktivbetrieb ist etwas größer als mit Bundler, was für den Umfang dieser Anwendung akzeptabel ist.
  • Updates der vendorten Bibliotheken müssen manuell durchgeführt werden (Dateien in vendor/ austauschen), es gibt keinen automatisierten Dependency-Update-Mechanismus (z.B. Dependabot) für diese Assets.
  • Sollte die Anwendung später deutlich wachsen (z.B. TypeScript, größere Testinfrastruktur, Bereitstellung über einen echten Webserver), kann diese Entscheidung revidiert und zurück auf ES-Module umgestellt werden.