Compare commits

...

6 Commits

Author SHA1 Message Date
0ca10c7820 docs: add session logs for comprehensive readiness analyses across all domains
All checks were successful
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 7m43s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 7m46s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m5s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m48s
- Added detailed session logs under `docs/99_Journal/` for Backend, Frontend, UI/UX, QA, Documentation, and Architectural readiness.
- Documented findings, recommendations, and next steps for each domain to ensure alignment before starting "Phase 3: Feature Development."
- Captured key architectural decisions and the need for validated domain models and UI/UX specifications.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-03-16 13:03:27 +01:00
38875a1040 refactor(auth): remove redundant semicolons in Sha256.kt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-03-16 11:00:21 +01:00
f7743aa7d9 fix(auth): correct typos, add suppression, and improve documentation clarity
- Fixed minor typos in comments for `LoginViewModel`, `OidcCallback`, and `AuthApiClient`.
- Added `@Suppress("REDUNDANT_CALL_OF_CONVERSION_METHOD")` in `Sha256.kt`.
- Improved phrasing and readability of function documentation across files.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-03-16 10:51:55 +01:00
2c822f8007 refactor: streamline deep link handling and improve sqlite worker initialization
- Simplified `DeepLinkHandler` logic by removing redundant return values and enhancing route parsing with `ifBlank()`.
- Refactored `sqlite.worker.js` for better modularity and error handling.
- Added helper methods for script imports and initialization error management.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-03-16 10:26:41 +01:00
d1fce33716 refactor(network): simplify PlatformConfig logic for URL resolution
- Streamlined `resolveApiBaseUrl` and `resolveKeycloakUrl` by introducing `globalScope` as a reusable property.
- Improved readability and maintainability by reducing redundant code and enhancing structure.
- Removed outdated comments and polished behavior for fallback mechanisms.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-03-16 10:20:28 +01:00
b6fda98c89 fix(web-app): remove unused sqlite.worker.js and wasi-dummy.js, update Config.kt and service worker logic
- Deleted `sqlite.worker.js` and `wasi-dummy.js` to clean up outdated resources.
- Updated `Config.kt` to use a shared `Json` instance for deserialization.
- Revised service worker for cache versioning and to bypass caching of `web-app.js` and `.map` files.
- Enhanced debug logging and improved handling of uncaught errors in new `sqlite.worker.js`.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-03-16 10:14:06 +01:00
21 changed files with 528 additions and 280 deletions

View File

@ -21,12 +21,10 @@ BACKUP_RETENTION_DAYS=7
DOCKER_REGISTRY=git.mo-code.at/mocode-software/meldestelle DOCKER_REGISTRY=git.mo-code.at/mocode-software/meldestelle
DOCKER_TAG=latest DOCKER_TAG=latest
DOCKER_VERSION=1.0.0-SNAPSHOT DOCKER_VERSION=1.0.0-SNAPSHOT
DOCKER_BUILD_DATE=2026-02-02T15:00:00Z DOCKER_BUILD_DATE=2026-03-15T12:00:00Z
DOCKER_GRADLE_VERSION=9.3.1 DOCKER_GRADLE_VERSION=9.3.1
# Java 25 = Early Access; für LTS auf 21 setzen
DOCKER_JAVA_VERSION=25 DOCKER_JAVA_VERSION=25
DOCKER_NODE_VERSION=24.12.0 DOCKER_NODE_VERSION=24.12.0
# Caddy Version für den Web-App Container
DOCKER_CADDY_VERSION=2.11-alpine DOCKER_CADDY_VERSION=2.11-alpine
# ARM64 spezifische JVM-Optionen (leer lassen auf x86/amd64, z.B. auf Apple Silicon) # ARM64 spezifische JVM-Optionen (leer lassen auf x86/amd64, z.B. auf Apple Silicon)
# Beispiel ARM64: JVM_OPTS_ARM64=-XX:UseSVE=0 # Beispiel ARM64: JVM_OPTS_ARM64=-XX:UseSVE=0
@ -43,7 +41,7 @@ POSTGRES_DB_URL=jdbc:postgresql://postgres:5432/pg-meldestelle-db
POSTGRES_SHARED_BUFFERS=256MB POSTGRES_SHARED_BUFFERS=256MB
POSTGRES_EFFECTIVE_CACHE_SIZE=768MB POSTGRES_EFFECTIVE_CACHE_SIZE=768MB
# --- VALKEY (formerly Redis) --- # --- VALKEY ---
VALKEY_IMAGE=valkey/valkey:9-alpine VALKEY_IMAGE=valkey/valkey:9-alpine
VALKEY_PASSWORD=<SICHERES_PASSWORT> VALKEY_PASSWORD=<SICHERES_PASSWORT>
VALKEY_PORT=6379:6379 VALKEY_PORT=6379:6379

View File

@ -0,0 +1,57 @@
# Session Log: Architektonische Gesamtbewertung & Freigabe Phase 3
**Datum:** 16. März 2026
**Verantwortlicher Agent:** 🏗️ [Lead Architect] in Zusammenarbeit mit 🧹 [Curator]
**Typ:** Architektur-Entscheidung & Meilenstein-Review
## 1. Executive Summary
Als Lead Architect habe ich die tiefgehenden Analysen aller Fachbereiche (DevOps, Backend, Frontend, UI/UX, QA, Curator)
evaluiert, um die Bereitschaft für die "Phase 3: Feature Development" der Master Roadmap zu bewerten.
**Das Ergebnis ist eindeutig:**
* 🟢 **Technologie & Infrastruktur:** 100% Ready. Das Fundament ist robust, modern und produktionsnah.
* 🔴 **Fachlichkeit & Domäne:** 0% Ready. Es existieren keine verabschiedeten Spezifikationen, Use-Cases oder UI-Flows.
## 2. Zusammenfassung der Fachberichte
1. **🐧 DevOps:** Infrastruktur, CI/CD und lokale Entwicklungsumgebung stehen stabil bereit. Die Dev/Prod-Parität (
Keycloak) ist gesichert.
2. **👷 Backend:** DDD-Core und Spring Boot Blueprints (KMP-kompatibel) sind lauffähig, aber fachliche Implementierung
würde ohne Domänenmodelle zu massiven Refactorings führen.
3. **🎨 Frontend:** KMP, Compose und Offline-First-Sync-Mechanismen sind exzellent aufgesetzt. Es fehlen jedoch
Datenmodelle und Wireframes.
4. **🖌️ UI/UX:** Das Basis-Design-System steht. Es fehlen jedoch zwingend Wireframes, Benutzer-Flows und Offline-First
UX-Konzepte, um das alte "SuDo"-System sinnvoll abzulösen.
5. **🧐 QA:** Test-Infrastruktur (Testcontainers, BDD-Bereitschaft) ist hervorragend. Es fehlen Akzeptanzkriterien,
Testdaten und "Given-When-Then"-Szenarien für TDD.
6. **🧹 Curator:** Docs-as-Code Struktur ist sauber. Jedoch sind alle Domänen-Dokumente im `docs/03_Domain/` als "Draft"
markiert, was die "Single Source of Truth"-Regel verletzt.
## 3. Architektonische Entscheidung (ADR)
Ich bestätige das einstimmige Veto des Teams gegen einen sofortigen Start der Programmierung. Das blinde Schreiben von
Feature-Code wird hiermit untersagt.
**Wir wenden strikt das "Shift-Left" Prinzip an.** Bevor Code geschrieben wird, muss die fachliche Architektur (Domäne)
geklärt sein.
## 4. Konkreter Fahrplan (Nächste Schritte)
Um die Blockade aufzulösen und Phase 3 aktiv zu schalten, ordne ich folgende Schritte an:
1. **Initiierung des Domain Workshops (Lead Architect & Fachexperte):**
* Fokus auf Kernprozesse (z.B. Nennung, Startliste, Ergebnisse).
* Überführung der Drafts (`Legacy_Spec_Analysis`, `Use_Cases_Draft.md`) in finale Spezifikationen.
2. **Parallele Ausarbeitung (QA & UI/UX):**
* **QA:** Definition der Akzeptanzkriterien und Gherkin-Szenarien aus den Workshop-Ergebnissen.
* **UI/UX:** Erstellung der Wireframes für die Kern-Flows (inkl. Offline-States) basierend auf den neuen
Domänenmodellen.
3. **Dokumenten-Freigabe (Curator):**
* Überführung der `docs/03_Domain/` Dokumente vom Status "DRAFT" auf "ACTIVE".
4. **Roadmap Update:**
* Sobald Schritte 1-3 abgeschlossen sind, wird Phase 3 in der `MASTER_ROADMAP.md` freigegeben und das
Entwicklungs-Team darf mit der Implementierung beginnen (Aktivierung des `entries-service`).
**Fazit:** Der technische Proof of Concept ist abgeschlossen. Wir wechseln nun in den Domänen- und Spezifikations-Modus.

View File

@ -0,0 +1,48 @@
# Session Log: Analyse der Backend-Bereitschaft für die Feature-Entwicklung
**Datum:** 16. März 2026
**Verantwortlicher Agent:** 🧹 [Curator] in Zusammenarbeit mit 👷 [Backend Developer]
**Typ:** Code- & Projekt-Review
## 1. Ausgangslage
Nachdem die Infrastruktur freigegeben wurde, hat der 👷 **[Backend Developer]** die aktuelle Code-Basis (`:backend`,
`:core:core-domain`, `settings.gradle.kts`) auf ihre Bereitschaft für den Start der fachlichen Feature-Entwicklung
geprüft.
## 2. Analyse-Ergebnisse (Backend Developer)
Die Analyse gliedert sich in zwei Kernbereiche: die technische Basis und die fachliche Ausrichtung.
### 2.1 Technische Bereitschaft: 🟢 Exzellent
Das technische Fundament ist hervorragend aufgestellt und bereit für die Produktiventwicklung:
* **Domain-Driven Design (DDD) Core:** Das `:core:core-domain` Modul ist sehr sauber strukturiert. Die konsequente
Nutzung von Kotlin 2.3.0 `Value-Classes` (z.B. für `EntityId`, `ErrorCode` inkl. Regex-Validierung) garantiert
maximale Typsicherheit ohne Performance-Verlust zur Laufzeit.
* **KMP-Kompatibilität:** Die Basis-DTOs und Serializer (wie `KotlinxInstantSerializer`) sind im `commonMain` des
Core-Moduls platziert. Dies ermöglicht eine reibungslose Code-Wiederverwendung für das Kotlin/JS Frontend (KMP-Ready).
* **Service-Blaupause:** Der `ping-service` beweist, dass der aktuelle Technologie-Stack (Spring Boot 3.5.x, Security,
Resilience4j, Persistence, Monitoring via Zipkin/Micrometer) lauffähig verdrahtet ist. Auch die Testumgebung inkl.
`Testcontainers` steht bereit.
### 2.2 Fachliche Bereitschaft: 🔴 Blockiert (Warten auf Architect)
Trotz der technischen Einsatzbereitschaft darf die fachliche Implementierung noch nicht starten.
* **Roadmap-Status:** Die `MASTER_ROADMAP.md` definiert Phase 3 (Feature Development) aktuell ganz klar als **ON HOLD**.
* **Fehlende Domänen-Klarheit:** Es steht noch ein fundamentaler "Domain Analysis Workshop" mit dem Fachexperten aus, um
die Anforderungen, Aggregate und Bounded Contexts neu zu definieren.
* **Pausierte Services:** Der für die Fachlichkeit zentrale `:backend:services:entries:entries-service` ist in der
`settings.gradle.kts` bewusst auskommentiert und deaktiviert.
## 3. Fazit & Empfehlung
Ein unüberlegter Start der Programmierung würde unweigerlich zu Code führen, der nicht der finalen Domänen-Logik
entspricht und später aufwendig refaktorisiert werden müsste.
**Nächster logischer Schritt:**
Bevor Code geschrieben oder der `entries-service` reaktiviert wird, muss der **🏗️ [Lead Architect]** den Domain-Workshop
leiten. Erst wenn die fachliche "Feature Roadmap" und die Kern-Domänenmodelle spezifiziert sind, erfolgt die Freigabe
für das Backend-Team.

View File

@ -0,0 +1,52 @@
# Session Log: Analyse der Dokumentations-Bereitschaft für die Feature-Entwicklung
**Datum:** 16. März 2026
**Verantwortlicher Agent:** 🧹 [Curator]
**Typ:** Projektdokumentations-Review
## 1. Ausgangslage
Als finaler Check vor dem möglichen Start der Phase 3 ("Feature Development") habe ich, der 🧹 **[Curator]**, das Projekt
aus Sicht der Dokumentation, Wissensverwaltung ("Docs-as-Code") und Nachvollziehbarkeit analysiert.
## 2. Analyse-Ergebnisse (Curator)
### 2.1 Technische Dokumentations-Infrastruktur: 🟢 Hervorragend
Die Basis für die Wissensverwaltung ist in einem exzellenten Zustand:
* **Docs-as-Code:** Die Verzeichnisstruktur unter `/docs` ist aufgeräumt (Phase 1 der Roadmap ist abgeschlossen). Alte
Dokumente sind ordnungsgemäß im `_archive` abgelegt.
* **Projekt-Journal:** Die Nachvollziehbarkeit von Architekturentscheidungen und Readiness-Checks funktioniert
fehlerfrei. Alle Agenten dokumentieren ihre Ergebnisse diszipliniert in `docs/99_Journal/`.
* **Playbooks:** Die Rollen und Vorgaben der KI-Agenten unter `docs/04_Agents/Playbooks/` sind präzise und lenken das
Team erfolgreich.
### 2.2 Fachliche Dokumentation (Single Source of Truth): 🔴 Blockiert
Trotz der guten Struktur fehlt der eigentliche Inhalt für die Umsetzung:
* **Domänen-Wissen als Draft:** Der Ordner `docs/03_Domain/` enthält wertvolle Vorarbeiten (z.B.
`Legacy_Spec_Analysis_2026-01.md`, `Use_Cases_Draft.md`), aber diese sind explizit als **"Draft"** markiert. Es gibt
keine final freigegebenen Spezifikationen.
* **Keine verbindlichen Verträge:** Für die Entwickler und die QA gibt es in der Dokumentation keine freigegebenen
API-Contracts, Bounded Contexts oder Aggregat-Definitionen.
* **Verletzung der Projekt-Philosophie:** Unsere Guideline lautet: *"Die Dokumentation ist die Single Source of Truth"*.
Solange diese "Truth" nicht formuliert ist, darf nicht programmiert werden.
## 3. Fazit & Empfehlung
Ich schließe mich aus dokumentarischer Sicht nahtlos allen anderen Fachbereichen an. Ein Start der Implementierung ohne
vorherige Festschreibung der Domäne würde das "Docs-as-Code"-Prinzip untergraben.
**Nächste Schritte & Fahrplan (Empfehlung des Curators):**
1. **Einberufung des Lead Architects:** Der **🏗️ [Lead Architect]** muss nun aktiv werden, um den "Domain Workshop"
durchzuführen.
2. **Spezifikationen finalisieren:** Die Ergebnisse des Workshops müssen von den aktuellen "Drafts" in verbindliche,
aktive Spezifikationen unter `docs/03_Domain/` überführt werden.
3. **Roadmap-Update:** Sobald die fachliche Dokumentation steht, passe ich als Curator die `MASTER_ROADMAP.md` an und
schalte Phase 3 offiziell auf "ACTIVE".
Alle Disziplinen haben nun gesprochen. Das Veto gegen einen verfrühten Code-Start ist einstimmig. Wir warten auf die
Architektur.

View File

@ -0,0 +1,49 @@
# Session Log: Analyse der Frontend-Bereitschaft für die Feature-Entwicklung
**Datum:** 16. März 2026
**Verantwortlicher Agent:** 🧹 [Curator] in Zusammenarbeit mit 🎨 [Frontend Expert]
**Typ:** Code- & Projekt-Review
## 1. Ausgangslage
Nach dem Backend-Review hat nun auch der 🎨 **[Frontend Expert]** das Kotlin Multiplatform (KMP) Frontend (
`:frontend:core`, `:frontend:shells:meldestelle-portal`) auf seine Bereitschaft für die fachliche Implementierung hin
untersucht.
## 2. Analyse-Ergebnisse (Frontend Expert)
### 2.1 Technische Bereitschaft: 🟢 Sehr gut
Die Architektur ist exzellent auf die Anforderungen (insbesondere "Offline-First") vorbereitet:
* **Architektur:** Saubere Modul-Trennung (`:core:auth`, `:core:network`, `:core:local-db`, `:core:sync`) und
funktionierendes Dependency Injection Setup via Koin im Main-Entrypoint.
* **Offline-First Basis:** Die lokale Datenbank via SQLDelight (inkl. `WebWorkerDriver` für die Kotlin/JS Target) ist
voll funktionsfähig. Der abstrakte `SyncManager` im Modul `:core:sync` bietet genau die richtige Basis für den
Delta-Sync mit dem Backend.
* **UI Foundation:** Das `:core:design-system` etabliert eine professionelle und einheitliche Theming-Grundlage (Farben,
Typografie, Material 3), die sich nahtlos in Compose Multiplatform integriert. Ein funktionierendes Basis-Routing (
`StateNavigationPort`) und Auth-Flows existieren bereits.
### 2.2 Fachliche Bereitschaft: 🔴 Blockiert (Warten auf Architect & UX)
Trotz der technischen Startklarheit fehlt die Grundlage für die eigentliche Feature-Entwicklung:
* **Roadmap-Status:** Gemäß `MASTER_ROADMAP.md` ist die Phase 3 (Feature Development) weiterhin **ON HOLD**.
* **Fehlende Spezifikationen:** Es existieren noch keine definierten Use-Cases, Datenmodelle oder API-Contracts (außer
Ping) für die kommenden Features.
* **UX/UI Design:** Konkrete Wireframes, Screen-Layouts oder Benutzer-Flows für die fachlichen Screens (z.B.
Nennungs-Übersicht) vom 🖌️ **[UI/UX Designer]** fehlen vollständig.
## 3. Fazit & Empfehlung
Ein verfrühter Start im Frontend würde bedeuten, ohne klare UI-Spezifikationen oder abgestimmte Backend-APIs "ins Blaue"
zu programmieren. Dies muss vermieden werden.
**Nächster logischer Schritt:**
Der 🎨 **[Frontend Expert]** schließt sich der Empfehlung des Backends an:
1. Der **🏗️ [Lead Architect]** muss den ausstehenden Domain-Workshop durchführen.
2. Im Anschluss (oder parallel) muss der **🖌️ [UI/UX Designer]** erste Wireframes für die neuen Features entwerfen.
3. Erst wenn Fachlichkeit (Domain Models) und Benutzerführung (UX-Flows) definiert sind, kann die
Frontend-Implementierung starten.

View File

@ -0,0 +1,50 @@
# Session Log: Infrastruktur-Analyse & Freigabe
**Datum:** 16. März 2026
**Verantwortlicher Agent:** 🧹 [Curator] in Zusammenarbeit mit 🐧 [DevOps Engineer]
**Typ:** Infrastruktur-Review / Architektur-Entscheidung
## 1. Ausgangslage
Vor dem Start der fachlichen Implementierung wurde der 🐧 **[DevOps Engineer]** beauftragt, einen tiefgehenden Pre-Flight
Check der lokalen Docker-Infrastruktur (`docker-compose.yaml` und Teil-Dateien) durchzuführen. Ziel war es
sicherzustellen, dass das "Docs-as-Code" Setup robust, fehlerfrei und bereit für den Entwicklungsalltag ist.
## 2. Analyse-Ergebnisse
Das Setup (unterteilt in `dc-infra.yaml`, `dc-backend.yaml`, `dc-gui.yaml` und `dc-ops.yaml`) wurde als sehr robust und
durchdacht bewertet.
Folgende positive Aspekte wurden hervorgehoben:
* **Abhängigkeitsmanagement:** Exzellente Nutzung von `depends_on` mit `condition: service_healthy` (z.B. Backend wartet
auf Postgres und Keycloak).
* **Zentrale Konfiguration:** Erfolgreiche Einbindung von `config/app/base-application.yaml` via Volumes in die Spring
Boot Container.
* **Monitoring & Tools:** Das Ops-Profil bietet ein umfassendes Toolset (Mailpit, pgAdmin, Prometheus, Grafana).
### Identifizierter Klärungsbedarf (DevOps Perspektive)
Der **DevOps Engineer** wies auf eine Abweichung von den dokumentierten Leitlinien für die lokale Entwicklung hin:
* **Keycloak Setup:** Laut Playbook sollte für die lokale Entwicklung das offizielle Image (`quay.io/keycloak/keycloak`)
im schnellen `start-dev` Modus laufen.
* **Ist-Zustand:** Die `dc-infra.yaml` nutzt ein lokales Dockerfile (`config/docker/keycloak/Dockerfile`), führt einen
Build-Step (`kc.sh build`) aus und startet den Container im produktionsnahen `--optimized` Modus.
## 3. Architekturentscheidung (Owner)
Der **Owner** wurde über die Abweichung beim Keycloak-Setup informiert und hat folgende architektonische Entscheidung
getroffen:
* **Entscheidung:** Das aktuelle, produktionsnahe Keycloak-Setup (`build` + `start --optimized`) bleibt unverändert
bestehen. Es wird **nicht** auf den reinen `start-dev` Modus zurückgebaut.
* **Begründung:** Da die Entwicklung direkt auf dem `main`-Branch stattfindet, hat die **Dev/Prod-Parität** höchste
Priorität. Die lokale Umgebung soll so nah wie möglich an der späteren Produktionsumgebung (Zora) bleiben. Eine leicht
erhöhte initiale Build-Zeit des Containers wird zugunsten von maximaler Stabilität und Vorhersehbarkeit (Vermeidung
von "works on my machine" Problemen) in Kauf genommen.
## 4. Fazit & Nächste Schritte
* Die Infrastruktur-Analyse ist abgeschlossen.
* Das aktuelle Setup ist offiziell für die nächste Phase freigegeben.
* Das Team kann nun mit der fachlichen Implementierung (Backend/Frontend) beginnen.

View File

@ -0,0 +1,51 @@
# Session Log: Analyse der QA-Bereitschaft für die Feature-Entwicklung
**Datum:** 16. März 2026
**Verantwortlicher Agent:** 🧹 [Curator] in Zusammenarbeit mit 🧐 [QA Specialist]
**Typ:** Code- & Projekt-Review
## 1. Ausgangslage
Nach den Analysen der Bereiche DevOps, Backend, Frontend und UI/UX hat der 🧐 **[QA Specialist]** das Projekt
hinsichtlich Testbarkeit, Test-Infrastruktur und "Shift-Left"-Qualitätssicherung bewertet.
## 2. Analyse-Ergebnisse (QA Specialist)
### 2.1 Technische Test-Infrastruktur: 🟢 Hervorragend
Das Projekt bietet eine exzellente Basis für eine moderne Test-Pyramide:
* **Zentrales Test-Modul:** Das Modul `:platform:platform-testing` bündelt alle wichtigen Test-Abhängigkeiten (JUnit 5,
AssertJ, MockK). Das sorgt für Konsistenz und vermeidet Versionskonflikte.
* **Integration Testing Setup:** Die Einbindung von `Testcontainers` (PostgreSQL, Keycloak, Kafka) ist vorhanden. Dies
ermöglicht deterministische Integrationstests gegen echte Infrastruktur-Komponenten, statt wackeliger Mocks.
* **Multiplatform Ready:** Die Test-SourceSets im `:core-domain` Modul sind für Kotlin Multiplatform (JVM, JS) korrekt
vorbereitet.
### 2.2 Fachliche Test-Spezifikation (Shift-Left): 🔴 Blockiert
Wie in den anderen Disziplinen fehlt die fachliche Grundlage:
* **Fehlende Akzeptanzkriterien:** Es existieren keine Use-Cases oder konkreten "Definition of Done" Kriterien für die
anstehenden Features.
* **Keine BDD-Szenarien:** Es gibt keine "Given-When-Then" (Gherkin) Spezifikationen, gegen die die Entwickler fachliche
Tests (TDD) schreiben könnten.
* **Testdaten-Mangel:** Da das finale Domain-Modell noch unklar ist, können noch keine realistischen Testdaten (
Mock-Objekte für Turniere, Nennungen etc.) definiert werden.
## 3. Fazit & Empfehlung
Ein sofortiger Implementierungsstart würde zu "technisch grünen", aber fachlich nutzlosen Tests führen. Die
Qualitätssicherung muss "Shift-Left" betrieben werden also weit vor der ersten Zeile Feature-Code.
**Nächste Schritte & Fahrplan (Empfehlung des QA Specialists):**
1. **Teilnahme am Domain Workshop:** Der QA Specialist muss in den anstehenden Workshop des **🏗️ [Lead Architect]**
integriert werden.
2. **Szenario-Definition:** Parallel zur Definition der Use-Cases durch den Architekten müssen die Akzeptanzkriterien
als Gherkin-Szenarien (Given-When-Then) dokumentiert werden.
3. **Edge-Cases aufdecken:** Im Workshop sollen Randfälle (z.B. "Was passiert bei einer Nennung ohne gültige Lizenz?")
identifiziert und als Test-Szenarien festgehalten werden.
Erst wenn diese fachlichen Test-Szenarien als Verträge existieren, dürfen die Entwickler mit der Implementierung
beginnen, um diese Verträge technisch zu erfüllen (Test-Driven Development Ansatz).

View File

@ -0,0 +1,54 @@
# Session Log: Analyse der UI/UX-Bereitschaft für die Feature-Entwicklung
**Datum:** 16. März 2026
**Verantwortlicher Agent:** 🧹 [Curator] in Zusammenarbeit mit 🖌️ [UI/UX Designer]
**Typ:** Code- & Projekt-Review
## 1. Ausgangslage
Nach den positiven technischen Reviews durch das Backend- und Frontend-Team hat der 🖌️ **[UI/UX Designer]** das Projekt
hinsichtlich der User Experience und Design-Bereitschaft analysiert. Der Fokus lag auf dem Zusammenspiel zwischen den
technischen Möglichkeiten, den alten System-Referenzen und den zukünftigen Anforderungen.
## 2. Analyse-Ergebnisse (UI/UX Designer)
### 2.1 Technische Design-Basis: 🟢 Gut
Die technische Umsetzung der grundlegenden Design-Sprache ist bereits im Frontend-Code verankert:
* **Design System (`AppTheme.kt`):** Ein starkes, "Enterprise-taugliches" Theme (Material 3) ist implementiert. Es
definiert Farbpaletten, Kontraste (inkl. Dark Mode) und Typografie-Hierarchien. Dies stellt sicher, dass neue
Komponenten konsistent und professionell aussehen werden.
* **Technologie (Compose):** Compose Multiplatform bietet die perfekte Basis, um hochgradig reaktive und responsive
Interfaces schnell zu entwickeln.
### 2.2 Fachliche UI/UX-Spezifikation: 🔴 Blockiert (Wireframes & Flows fehlen)
Die visuelle und konzeptionelle Ausarbeitung der eigentlichen Features fehlt komplett:
* **Fehlende UX-Flows:** Es gibt keine definierten Benutzerpfade. Es ist unklar, wie Nutzer durch die Kernprozesse (z.B.
Nennungen anlegen, Startlisten prüfen) navigieren sollen.
* **Umgang mit Legacy-Screenshots:** Die Bilder des alten "SuDo"-Systems (unter `docs/BilderSuDo/`) sind wertvolle
Referenzen für die Datenpunkte, **dürfen aber nicht 1:1 nachgebaut werden**. Das neue UI muss entschlackt, fokussiert
und intuitiver gestaltet werden ("Fokus auf den Sport").
* **Offline-First UX:** Ein entscheidender Aspekt fehlt: Das Interface-Design muss klar kommunizieren, ob der Nutzer
offline ist, ob Daten synchronisiert werden oder ob es Sync-Konflikte gibt. Diese speziellen UI-Muster müssen erst
gestaltet werden.
## 3. Fazit & Empfehlung
Ein Start der UI-Programmierung ohne vorherige Design-Spezifikationen würde unweigerlich zu einer unstrukturierten
Benutzeroberfläche ("Datenbank-Felder aufs UI klatschen") und damit zu einer schlechten User Experience führen.
**Nächste Schritte & Fahrplan (Empfehlung des UI/UX Designers):**
1. **Domain Workshop:** Teilnahme (passiv) am ausstehenden Workshop des **🏗️ [Lead Architect]**, um die Fachlichkeit und
die notwendigen Datenpunkte zu verstehen.
2. **Wireframing:** Erstellung von aufgeräumten, responsiven Wireframes (Mobile & Desktop) für die Kern-Flows (z.B.
Nennung, Startliste) basierend auf den neuen Modellen und unter zur Hilfenahme der SuDo-Screenshots als fachliche
Orientierung.
3. **Frontend-Abstimmung:** Review der Wireframes mit dem **🎨 [Frontend Expert]**, insbesondere hinsichtlich der
Offline-UX-Konzepte.
Erst nach der Freigabe der Wireframes für einen spezifischen Bounded Context sollte das Frontend-Team mit der
Implementierung der Screens beginnen.

View File

@ -36,7 +36,7 @@ class AuthApiClient(
) { ) {
/** /**
* Authenticate user with username and password * Authenticate a user with a username and password
*/ */
suspend fun login(username: String, password: String): LoginResponse { suspend fun login(username: String, password: String): LoginResponse {
val tokenEndpoint = "$keycloakBaseUrl/realms/$realm/protocol/openid-connect/token" val tokenEndpoint = "$keycloakBaseUrl/realms/$realm/protocol/openid-connect/token"

View File

@ -29,8 +29,8 @@ expect suspend fun launchOidcFlow(
/** /**
* Prüft beim App-Start, ob in der aktuellen URL ein OIDC-Callback steckt. * Prüft beim App-Start, ob in der aktuellen URL ein OIDC-Callback steckt.
* Relevant für JS/Browser: nach Keycloak-Redirect enthält die URL code= und state=. * Relevant für JS/Browser: Nach Keycloak-Redirect enthält die URL code= und state=.
* Gibt null zurück, wenn kein Callback vorhanden. * Gibt null zurück, wenn kein Callback vorhanden ist.
*/ */
expect fun consumePendingOidcCallback(): OidcCallbackResult? expect fun consumePendingOidcCallback(): OidcCallbackResult?

View File

@ -1,3 +1,5 @@
@file:Suppress("REDUNDANT_CALL_OF_CONVERSION_METHOD")
package at.mocode.frontend.core.auth.data package at.mocode.frontend.core.auth.data
/** /**
@ -60,13 +62,13 @@ internal object Sha256 {
w[i] = w[i - 16] + s0 + w[i - 7] + s1 w[i] = w[i - 16] + s0 + w[i - 7] + s1
} }
var a = h0; var a = h0
var b = h1; var b = h1
var c = h2; var c = h2
var d = h3 var d = h3
var e = h4; var e = h4
var f = h5; var f = h5
var g = h6; var g = h6
var h = h7 var h = h7
for (i in 0..63) { for (i in 0..63) {

View File

@ -160,7 +160,7 @@ class LoginViewModel(
} }
} }
/** Verarbeitet das Ergebnis des OIDC-Callbacks (intern + von JS-Startup). */ /** Verarbeitet das Ergebnis des OIDC-Callbacks (intern + von JS-Start-up). */
private suspend fun handleOidcCallbackResult(result: OidcCallbackResult) { private suspend fun handleOidcCallbackResult(result: OidcCallbackResult) {
when (result) { when (result) {
is OidcCallbackResult.Success -> { is OidcCallbackResult.Success -> {

View File

@ -1,122 +1,146 @@
// Minimal debug worker 'use strict';
console.log("Worker: sqlite.worker.js loaded. Starting initialization...");
try { // --- State ---
// We do NOT import from node_modules anymore to avoid Webpack bundling issues. let db = null;
// import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; let isReady = false;
let initError = null;
const messageQueue = [];
// Message buffer for messages arriving before DB is ready // --- Message Handler ---
let messageQueue = []; self.onmessage = (event) => {
let db = null;
let isReady = false;
// Minimal worker protocol compatible with SQLDelight's `web-worker-driver`.
self.onmessage = (event) => {
if (!isReady) { if (!isReady) {
console.log("Worker: Buffering message (DB not ready)", event.data); if (initError) {
postMessage({id: event.data?.id, error: `Worker not initialized: ${initError}`});
} else {
messageQueue.push(event); messageQueue.push(event);
}
return; return;
} }
processMessage(event); processMessage(event);
}; };
function processMessage(event) { self.onerror = (event) => {
console.error('[sqlite.worker] Uncaught error:', event.message, `${event.filename}:${event.lineno}`);
};
// --- Message Processor ---
function processMessage(event) {
const data = event.data; const data = event.data;
if (!data) return;
let result;
try { try {
switch (data && data.action) { result = executeAction(data);
} catch (err) {
console.error('[sqlite.worker] Error processing message:', err);
postMessage({id: data.id, error: err?.message ?? String(err)});
return;
}
postMessage({id: data.id, ...result});
}
function executeAction(data) {
switch (data.action) {
case 'exec': { case 'exec': {
if (!data.sql) throw new Error('exec: Missing query string'); if (!data.sql) {
postMessage({id: data.id, error: 'exec: Missing sql string'});
return null;
}
const rows = []; const rows = [];
db.exec({ db.exec({
sql: data.sql, sql: data.sql,
bind: data.params ?? [], bind: data.params ?? [],
rowMode: 'array', rowMode: 'array',
callback: (row) => rows.push(row) callback: (row) => rows.push(row),
}); });
return postMessage({id: data.id, results: {values: rows}}); return {results: {values: rows}};
} }
case 'begin_transaction': case 'begin_transaction':
db.exec('BEGIN TRANSACTION;'); db.exec('BEGIN TRANSACTION;');
return postMessage({id: data.id, results: []}); return {results: []};
case 'end_transaction': case 'end_transaction':
db.exec('END TRANSACTION;'); db.exec('END TRANSACTION;');
return postMessage({id: data.id, results: []}); return {results: []};
case 'rollback_transaction': case 'rollback_transaction':
db.exec('ROLLBACK TRANSACTION;'); db.exec('ROLLBACK TRANSACTION;');
return postMessage({id: data.id, results: []}); return {results: []};
default: default:
throw new Error(`Unsupported action: ${data && data.action}`); postMessage({id: data.id, error: `Unsupported action: ${data.action}`});
} return null;
} catch (err) {
console.error("Worker: Error processing message", err);
return postMessage({id: data && data.id, error: err?.message ?? String(err)});
}
} }
}
self.onerror = function (event) { // --- Initialization ---
console.error("Error in Web Worker (onerror):", event.message, event.filename, event.lineno); async function init() {
// Don't postMessage here as it might confuse the driver if it expects a response to a query // Load sqlite3.js via importScripts (avoids Webpack bundling issues)
}; const loadResult = tryImportScripts();
if (loadResult !== null) {
async function init() { handleInitError(loadResult);
try { return;
// 1. Load the sqlite3.js library manually via importScripts.
console.log("Worker: Loading sqlite3.js via importScripts...");
try {
importScripts('sqlite3.js');
} catch (e) {
throw new Error("Failed to importScripts('sqlite3.js'). Check if file exists at root. Error: " + e.message);
} }
if (typeof self.sqlite3InitModule !== 'function') { if (typeof self.sqlite3InitModule !== 'function') {
throw new Error("sqlite3InitModule is not defined after importScripts. Check if sqlite3.js was loaded correctly."); handleInitError('sqlite3InitModule not found after importScripts sqlite3.js may be corrupt or wrong version.');
return;
} }
console.log("Worker: Fetching sqlite3.wasm manually..."); // Fetch WASM binary manually so we control the URL and can detect failures early
const response = await fetch('sqlite3.wasm'); let wasmBinary;
if (!response.ok) { try {
throw new Error(`Failed to fetch sqlite3.wasm: ${response.status} ${response.statusText}`); const wasmResponse = await fetch('sqlite3.wasm');
if (!wasmResponse.ok) {
handleInitError(`Failed to fetch sqlite3.wasm: ${wasmResponse.status} ${wasmResponse.statusText}`);
return;
}
wasmBinary = await wasmResponse.arrayBuffer();
} catch (e) {
handleInitError(`Failed to fetch sqlite3.wasm: ${e.message}`);
return;
} }
const wasmBinary = await response.arrayBuffer();
console.log("Worker: sqlite3.wasm fetched successfully, size:", wasmBinary.byteLength);
console.log("Worker: Calling sqlite3InitModule with wasmBinary..."); try {
const sqlite3 = await self.sqlite3InitModule({ const sqlite3 = await self.sqlite3InitModule({
print: console.log, print: console.log,
printErr: console.error, printErr: console.error,
wasmBinary: wasmBinary wasmBinary,
}); });
console.log("Worker: sqlite3InitModule resolved successfully");
const opfsAvailable = 'opfs' in sqlite3;
console.log("Worker: OPFS available:", opfsAvailable);
// Initialize DB
const dbName = 'app.db'; const dbName = 'app.db';
if (opfsAvailable) { if ('opfs' in sqlite3) {
console.log("Initialisiere persistente OPFS Datenbank: " + dbName); console.log(`[sqlite.worker] Using persistent OPFS database: ${dbName}`);
db = new sqlite3.oo1.OpfsDb(dbName); db = new sqlite3.oo1.OpfsDb(dbName);
} else { } else {
console.warn("OPFS nicht verfügbar, Fallback auf In-Memory"); console.warn('[sqlite.worker] OPFS not available falling back to in-memory database');
db = new sqlite3.oo1.DB(dbName); db = new sqlite3.oo1.DB(dbName);
} }
// Mark as ready and process queue // Flush buffered messages
isReady = true; isReady = true;
console.log("Worker: DB Ready. Processing " + messageQueue.length + " buffered messages."); console.log(`[sqlite.worker] Ready. Flushing ${messageQueue.length} buffered message(s).`);
while (messageQueue.length > 0) { while (messageQueue.length > 0) {
processMessage(messageQueue.shift()); processMessage(messageQueue.shift());
} }
} catch (e) { } catch (e) {
console.error("Database initialization error in worker:", e); handleInitError(e?.message ?? String(e));
// We can't easily communicate this back to the driver during init,
// but console.error should show up.
} }
}
init();
} catch (e) {
console.error("Critical Worker Error:", e);
} }
function tryImportScripts() {
try {
importScripts('sqlite3.js');
return null;
} catch (e) {
return `importScripts('sqlite3.js') failed is the file served at root? ${e.message}`;
}
}
function handleInitError(message) {
initError = message;
console.error('[sqlite.worker] Initialization failed:', message);
while (messageQueue.length > 0) {
const queued = messageQueue.shift();
postMessage({id: queued.data?.id, error: `Worker initialization failed: ${initError}`});
}
}
init();

View File

@ -3,7 +3,7 @@ package at.mocode.frontend.core.navigation
import at.mocode.frontend.core.domain.models.User import at.mocode.frontend.core.domain.models.User
/** /**
* Abstraction to obtain the current authenticated user (or null if guest). * Abstraction to get the current authenticated user (or null if guest).
* Implementations live in shells/apps and provide access to the actual auth state. * Implementations live in shells/apps and provide access to the actual auth state.
*/ */
interface CurrentUserProvider { interface CurrentUserProvider {

View File

@ -4,7 +4,7 @@ import at.mocode.frontend.core.domain.models.AppRoles
/** /**
* Deep link handling with minimal auth-aware guard via CurrentUserProvider. * Deep link handling with minimal auth-aware guard via CurrentUserProvider.
* This version is self-contained in core:navigation and has no dependency on shared app store. * This version is self-contained in core:navigation and has no dependency on the shared app store.
*/ */
class DeepLinkHandler( class DeepLinkHandler(
private val navigation: NavigationPort, private val navigation: NavigationPort,
@ -22,11 +22,11 @@ class DeepLinkHandler(
fun handleDeepLink(url: String): Boolean { fun handleDeepLink(url: String): Boolean {
val parsed = parseDeepLink(url) ?: return false val parsed = parseDeepLink(url) ?: return false
return processDeepLink(parsed) processDeepLink(parsed)
return true
} }
// TODO: Implement deep link processing logic private fun processDeepLink(deepLink: DeepLink) {
private fun processDeepLink(deepLink: DeepLink): Boolean {
val route = cleanRoute(deepLink.route) val route = cleanRoute(deepLink.route)
// If the route requires auth and the user is missing → redirect to log in // If the route requires auth and the user is missing → redirect to log in
@ -34,20 +34,19 @@ class DeepLinkHandler(
val user = currentUserProvider.getCurrentUser() val user = currentUserProvider.getCurrentUser()
if (user == null) { if (user == null) {
navigation.navigateTo(config.loginRoute) navigation.navigateTo(config.loginRoute)
return true return
} }
// Admin section guard: requires ADMIN role // Admin section guard: requires ADMIN role
if (requiresAdmin(route)) { if (requiresAdmin(route)) {
val isAdmin = user.roles.contains(AppRoles.ADMIN) val isAdmin = user.roles.contains(AppRoles.ADMIN)
if (!isAdmin) { if (!isAdmin) {
navigation.navigateTo(Routes.HOME) navigation.navigateTo(Routes.HOME)
return true return
} }
} }
} }
navigation.navigateTo(deepLink.route) navigation.navigateTo(deepLink.route)
return true
} }
private fun parseDeepLink(url: String): DeepLink? { private fun parseDeepLink(url: String): DeepLink? {
@ -63,7 +62,7 @@ class DeepLinkHandler(
val parts = withoutScheme.split("/") val parts = withoutScheme.split("/")
if (parts.isEmpty() || parts[0] != config.host) return null if (parts.isEmpty() || parts[0] != config.host) return null
val path = "/" + parts.drop(1).joinToString("/") val path = "/" + parts.drop(1).joinToString("/")
val route = if (path.isBlank()) Routes.HOME else path val route = path.ifBlank { Routes.HOME }
return DeepLink(DeepLinkType.CUSTOM_SCHEME, route, url) return DeepLink(DeepLinkType.CUSTOM_SCHEME, route, url)
} }
@ -87,7 +86,4 @@ class DeepLinkHandler(
} }
private fun requiresAdmin(route: String): Boolean = route.startsWith("${Routes.Admin.ROOT}/") private fun requiresAdmin(route: String): Boolean = route.startsWith("${Routes.Admin.ROOT}/")
fun generateDeepLink(route: String, useCustomScheme: Boolean = true): String =
if (useCustomScheme) "${config.scheme}://${config.host}$route" else "https://${config.allowedDomains.first()}$route"
} }

View File

@ -4,12 +4,14 @@ import kotlinx.browser.window
@Suppress("UnsafeCastFromDynamic", "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") @Suppress("UnsafeCastFromDynamic", "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual object PlatformConfig { actual object PlatformConfig {
private val globalScope: dynamic
get() = js("typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {}))")
actual fun resolveApiBaseUrl(): String { actual fun resolveApiBaseUrl(): String {
// 1) Prefer a global JS variable (can be injected by index.html or nginx) // 1) Prefer a global JS variable (injected by main.kt via AppConfig)
val global =
js("typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {}))")
val fromGlobal = try { val fromGlobal = try {
(global.API_BASE_URL as? String)?.trim().orEmpty() (globalScope.API_BASE_URL as? String)?.trim().orEmpty()
} catch (_: dynamic) { } catch (_: dynamic) {
"" ""
} }
@ -17,32 +19,27 @@ actual object PlatformConfig {
console.log("[PlatformConfig] Resolved API_BASE_URL from global: $fromGlobal") console.log("[PlatformConfig] Resolved API_BASE_URL from global: $fromGlobal")
return fromGlobal.removeSuffix("/") return fromGlobal.removeSuffix("/")
} }
// 2) Try window location origin (same origin gateway/proxy setup) // 2) Try window location origin (same origin gateway/proxy setup)
val origin = try { val origin = try {
window.location.origin window.location.origin
} catch (_: dynamic) { } catch (_: dynamic) {
null null
} }
if (!origin.isNullOrBlank()) { if (!origin.isNullOrBlank()) {
val resolvedUrl = origin.removeSuffix("/") + "/api" val resolvedUrl = origin.removeSuffix("/") + "/api"
console.log("[PlatformConfig] Resolved API_BASE_URL from window.location.origin: $resolvedUrl") console.log("[PlatformConfig] Resolved API_BASE_URL from window.location.origin: $resolvedUrl")
return resolvedUrl return resolvedUrl
} }
// 3) Fallback to the local gateway directly (e.g., for tests without a window)
// 3) Fallback to the local gateway directly (e.g. for tests without window)
val fallbackUrl = "http://localhost:8081/api" val fallbackUrl = "http://localhost:8081/api"
console.log("[PlatformConfig] Fallback API_BASE_URL: $fallbackUrl") console.log("[PlatformConfig] Fallback API_BASE_URL: $fallbackUrl")
return fallbackUrl return fallbackUrl
} }
actual fun resolveKeycloakUrl(): String { actual fun resolveKeycloakUrl(): String {
// 1) Prefer a global JS variable injected by index.html at runtime // 1) Prefer a global JS variable (injected by main.kt via AppConfig)
val global =
js("typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {}))")
val fromGlobal = try { val fromGlobal = try {
(global.KEYCLOAK_URL as? String)?.trim().orEmpty() (globalScope.KEYCLOAK_URL as? String)?.trim().orEmpty()
} catch (_: dynamic) { } catch (_: dynamic) {
"" ""
} }
@ -50,7 +47,6 @@ actual object PlatformConfig {
console.log("[PlatformConfig] Resolved KEYCLOAK_URL from global: $fromGlobal") console.log("[PlatformConfig] Resolved KEYCLOAK_URL from global: $fromGlobal")
return fromGlobal.removeSuffix("/") return fromGlobal.removeSuffix("/")
} }
// 2) Derive from window.location.hostname with default Keycloak port // 2) Derive from window.location.hostname with default Keycloak port
val hostname = try { val hostname = try {
window.location.hostname window.location.hostname
@ -62,7 +58,6 @@ actual object PlatformConfig {
console.log("[PlatformConfig] Resolved KEYCLOAK_URL from window.location.hostname: $resolvedUrl") console.log("[PlatformConfig] Resolved KEYCLOAK_URL from window.location.hostname: $resolvedUrl")
return resolvedUrl return resolvedUrl
} }
// 3) Fallback for local development // 3) Fallback for local development
val fallbackUrl = "http://localhost:8180" val fallbackUrl = "http://localhost:8180"
console.log("[PlatformConfig] Fallback KEYCLOAK_URL: $fallbackUrl") console.log("[PlatformConfig] Fallback KEYCLOAK_URL: $fallbackUrl")

View File

@ -3,6 +3,8 @@ import kotlinx.coroutines.await
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
private val AppJson = Json { ignoreUnknownKeys = true }
@Serializable @Serializable
data class AppConfig( data class AppConfig(
val apiBaseUrl: String, val apiBaseUrl: String,
@ -18,7 +20,7 @@ suspend fun loadAppConfig(): AppConfig {
return fallbackFromGlobal() return fallbackFromGlobal()
} }
val text = response.text().await() val text = response.text().await()
Json.decodeFromString(AppConfig.serializer(), text) AppJson.decodeFromString(AppConfig.serializer(), text)
} catch (e: dynamic) { } catch (e: dynamic) {
console.error("[Config] Error loading config:", e) console.error("[Config] Error loading config:", e)
// Fallback: Caddy-injizierte Werte aus index.html (globalThis.API_BASE_URL / KEYCLOAK_URL) // Fallback: Caddy-injizierte Werte aus index.html (globalThis.API_BASE_URL / KEYCLOAK_URL)
@ -28,10 +30,10 @@ suspend fun loadAppConfig(): AppConfig {
private fun fallbackFromGlobal(): AppConfig { private fun fallbackFromGlobal(): AppConfig {
val apiBase = (js("globalThis.API_BASE_URL") as? String) val apiBase = (js("globalThis.API_BASE_URL") as? String)
?.takeIf { it.isNotBlank() && !it.startsWith("\${") } ?.takeIf { it.isNotBlank() && !it.startsWith($$"${") }
?: window.location.origin ?: window.location.origin
val kcUrl = (js("globalThis.KEYCLOAK_URL") as? String) val kcUrl = (js("globalThis.KEYCLOAK_URL") as? String)
?.takeIf { it.isNotBlank() && !it.startsWith("\${") } ?.takeIf { it.isNotBlank() && !it.startsWith($$"${") }
console.log("[Config] Fallback: apiBaseUrl=$apiBase, keycloakUrl=$kcUrl") console.log("[Config] Fallback: apiBaseUrl=$apiBase, keycloakUrl=$kcUrl")
return AppConfig(apiBaseUrl = apiBase, keycloakUrl = kcUrl) return AppConfig(apiBaseUrl = apiBase, keycloakUrl = kcUrl)
} }

View File

@ -28,7 +28,7 @@ fun main() {
val config = loadAppConfig() val config = loadAppConfig()
console.log("[WebApp] Configuration loaded: apiBaseUrl=${config.apiBaseUrl}, keycloakUrl=${config.keycloakUrl}") console.log("[WebApp] Configuration loaded: apiBaseUrl=${config.apiBaseUrl}, keycloakUrl=${config.keycloakUrl}")
// Inject config into global JS scope for PlatformConfig to find // Inject config into the global JS scope for PlatformConfig to find
val global = js("typeof globalThis !== 'undefined' ? globalThis : window") val global = js("typeof globalThis !== 'undefined' ? globalThis : window")
global.API_BASE_URL = config.apiBaseUrl global.API_BASE_URL = config.apiBaseUrl
config.keycloakUrl?.let { global.KEYCLOAK_URL = it } config.keycloakUrl?.let { global.KEYCLOAK_URL = it }

View File

@ -1,122 +0,0 @@
// Minimal debug worker
console.log("Worker: sqlite.worker.js loaded. Starting initialization...");
try {
// We do NOT import from node_modules anymore to avoid Webpack bundling issues.
// import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
// Message buffer for messages arriving before DB is ready
let messageQueue = [];
let db = null;
let isReady = false;
// Minimal worker protocol compatible with SQLDelight's `web-worker-driver`.
self.onmessage = (event) => {
if (!isReady) {
console.log("Worker: Buffering message (DB not ready)", event.data);
messageQueue.push(event);
return;
}
processMessage(event);
};
function processMessage(event) {
const data = event.data;
try {
switch (data && data.action) {
case 'exec': {
if (!data.sql) throw new Error('exec: Missing query string');
const rows = [];
db.exec({
sql: data.sql,
bind: data.params ?? [],
rowMode: 'array',
callback: (row) => rows.push(row)
});
return postMessage({ id: data.id, results: { values: rows } });
}
case 'begin_transaction':
db.exec('BEGIN TRANSACTION;');
return postMessage({ id: data.id, results: [] });
case 'end_transaction':
db.exec('END TRANSACTION;');
return postMessage({ id: data.id, results: [] });
case 'rollback_transaction':
db.exec('ROLLBACK TRANSACTION;');
return postMessage({ id: data.id, results: [] });
default:
throw new Error(`Unsupported action: ${data && data.action}`);
}
} catch (err) {
console.error("Worker: Error processing message", err);
return postMessage({ id: data && data.id, error: err?.message ?? String(err) });
}
}
self.onerror = function(event) {
console.error("Error in Web Worker (onerror):", event.message, event.filename, event.lineno);
// Don't postMessage here as it might confuse the driver if it expects a response to a query
};
async function init() {
try {
// 1. Load the sqlite3.js library manually via importScripts.
console.log("Worker: Loading sqlite3.js via importScripts...");
try {
importScripts('sqlite3.js');
} catch (e) {
throw new Error("Failed to importScripts('sqlite3.js'). Check if file exists at root. Error: " + e.message);
}
if (typeof self.sqlite3InitModule !== 'function') {
throw new Error("sqlite3InitModule is not defined after importScripts. Check if sqlite3.js was loaded correctly.");
}
console.log("Worker: Fetching sqlite3.wasm manually...");
const response = await fetch('sqlite3.wasm');
if (!response.ok) {
throw new Error(`Failed to fetch sqlite3.wasm: ${response.status} ${response.statusText}`);
}
const wasmBinary = await response.arrayBuffer();
console.log("Worker: sqlite3.wasm fetched successfully, size:", wasmBinary.byteLength);
console.log("Worker: Calling sqlite3InitModule with wasmBinary...");
const sqlite3 = await self.sqlite3InitModule({
print: console.log,
printErr: console.error,
wasmBinary: wasmBinary
});
console.log("Worker: sqlite3InitModule resolved successfully");
const opfsAvailable = 'opfs' in sqlite3;
console.log("Worker: OPFS available:", opfsAvailable);
// Initialize DB
const dbName = 'app.db';
if (opfsAvailable) {
console.log("Initialisiere persistente OPFS Datenbank: " + dbName);
db = new sqlite3.oo1.OpfsDb(dbName);
} else {
console.warn("OPFS nicht verfügbar, Fallback auf In-Memory");
db = new sqlite3.oo1.DB(dbName);
}
// Mark as ready and process queue
isReady = true;
console.log("Worker: DB Ready. Processing " + messageQueue.length + " buffered messages.");
while (messageQueue.length > 0) {
processMessage(messageQueue.shift());
}
} catch (e) {
console.error("Database initialization error in worker:", e);
// We can't easily communicate this back to the driver during init,
// but console.error should show up.
}
}
init();
} catch (e) {
console.error("Critical Worker Error:", e);
}

View File

@ -1,6 +1,6 @@
const IS_DEV = self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1' || self.location.hostname === '::1'; const IS_DEV = self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1' || self.location.hostname === '::1';
const CACHE_NAME = 'meldestelle-cache-v2'; const CACHE_NAME = 'meldestelle-cache-v3';
const PRECACHE_URLS = [ const PRECACHE_URLS = [
'/', '/',
'/index.html', '/index.html',
@ -10,7 +10,8 @@ const PRECACHE_URLS = [
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
if (IS_DEV) { if (IS_DEV) {
// In dev, don't precache. Just activate the SW immediately. // In dev, don't precache. Just activate the SW immediately.
self.skipWaiting(); self.skipWaiting().then(_ => {
});
return; return;
} }
event.waitUntil( event.waitUntil(
@ -74,6 +75,12 @@ self.addEventListener('fetch', (event) => {
return; return;
} }
// App-Bundle immer vom Netzwerk niemals aus dem Cache (verhindert veraltete JS-Versionen)
if (url.pathname.endsWith('web-app.js') || url.pathname.endsWith('web-app.js.map')) {
event.respondWith(fetch(req));
return;
}
// Avoid noisy errors for favicon during dev/prod when missing // Avoid noisy errors for favicon during dev/prod when missing
if (url.pathname === '/favicon.ico') { if (url.pathname === '/favicon.ico') {
event.respondWith( event.respondWith(

View File

@ -1,15 +0,0 @@
// Dummy module to satisfy WASI imports in Webpack
// Used for skiko.wasm and potentially others
export function abort() {
console.error("WASI abort called");
}
// Some WASM modules might look for these
export function emscripten_notify_memory_growth() {
}
export default {
abort,
emscripten_notify_memory_growth
};