- 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>
113 lines
6.2 KiB
Markdown
113 lines
6.2 KiB
Markdown
# 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.
|