chore(MP-23): network DI client, frontend architecture guards, detekt & ktlint setup, docs, ping DI factory (#21)

* chore(MP-21): snapshot pre-refactor state (Epic 1)

* chore(MP-22): scaffold new repo structure, relocate Docker Compose, move frontend/backend modules, update Makefile; add docs mapping and env template

* MP-22 Epic 2: Erfolgreich umgesetzt und verifiziert

* MP-23 Epic 3: Gradle/Build Governance zentralisieren
This commit is contained in:
StefanMo
2025-11-30 23:14:00 +01:00
committed by GitHub
parent 89bbd42245
commit 034892e890
101 changed files with 857 additions and 407 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 KiB

@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meldestelle - Web</title>
<link type="text/css" rel="stylesheet" href="styles.css">
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#0f172a">
</head>
<body>
<div id="ComposeTarget">
<div class="loading">Loading...</div>
</div>
<script>
// Prefer explicit query param override (?apiBaseUrl=http://host:port),
// then fall back to same-origin. This avoids Docker secrets and works with Nginx proxy.
(function(){
try {
const params = new URLSearchParams(window.location.search);
const override = params.get('apiBaseUrl');
if (override) {
globalThis.API_BASE_URL = override.replace(/\/$/, '');
} else {
globalThis.API_BASE_URL = window.location.origin.replace(/\/$/, '');
}
} catch (e) {
globalThis.API_BASE_URL = 'http://localhost:8081';
}
})();
// KMP bundle will read globalThis.API_BASE_URL in PlatformConfig.js
</script>
<script src="web-app.js"></script>
<script>
// Register Service Worker only in non-localhost environments
if ('serviceWorker' in navigator && !['localhost', '127.0.0.1', '::1'].includes(location.hostname)) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').catch(function(err){
console.warn('ServiceWorker registration failed:', err);
});
});
}
</script>
</body>
</html>
@@ -0,0 +1,13 @@
{
"name": "Meldestelle",
"short_name": "Melde",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0f172a",
"icons": [
{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
@@ -0,0 +1,22 @@
html, body {
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, sans-serif;
background: #fafafa;
overflow: hidden; /* Verhindert Scrollbalken durch die Canvas */
}
#ComposeTarget {
height: 100vh;
display: flex;
flex-direction: column;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-size: 18px;
color: #666;
}
@@ -0,0 +1,95 @@
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 PRECACHE_URLS = [
'/',
'/index.html',
'/styles.css'
];
self.addEventListener('install', (event) => {
if (IS_DEV) {
// In dev, don't precache. Just activate the SW immediately.
self.skipWaiting();
return;
}
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(PRECACHE_URLS))
.then(() => self.skipWaiting())
);
});
self.addEventListener('activate', (event) => {
if (IS_DEV) {
event.waitUntil(self.clients.claim());
return;
}
event.waitUntil(
caches.keys().then((keys) => Promise.all(
keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))
)).then(() => self.clients.claim())
);
});
self.addEventListener('fetch', (event) => {
if (IS_DEV) {
return; // don't interfere with dev server/HMR
}
const req = event.request;
const url = new URL(req.url);
const isHttp = url.protocol === 'http:' || url.protocol === 'https:';
const sameOrigin = url.origin === self.location.origin;
const isExtension = url.protocol === 'chrome-extension:';
const isHotUpdate = url.pathname.includes('hot-update');
const isWebSocketUpgrade = req.headers.get('upgrade') === 'websocket';
// Ignore non-GET, cross-origin, browser extensions, HMR, and WebSocket upgrade requests
if (req.method !== 'GET' || !isHttp || !sameOrigin || isExtension || isHotUpdate || isWebSocketUpgrade) {
return; // Let the browser handle it
}
if (req.mode === 'navigate') {
// Network-first for navigation
event.respondWith(
fetch(req)
.then((resp) => {
if (resp && resp.status === 200 && resp.type === 'basic') {
const copy = resp.clone();
caches.open(CACHE_NAME).then((cache) => cache.put('/index.html', copy)).catch(() => {
});
}
return resp;
})
.catch(() => caches.match('/index.html'))
);
return;
}
// Avoid noisy errors for favicon during dev/prod when missing
if (url.pathname === '/favicon.ico') {
event.respondWith(
fetch(req).catch(() => caches.match(req))
);
return;
}
// Cache-first for static assets
event.respondWith(
caches.match(req).then((cached) => {
if (cached) return cached;
return fetch(req)
.then((resp) => {
if (resp && resp.status === 200 && resp.type === 'basic') {
const copy = resp.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(req, copy)).catch(() => {
});
}
return resp;
})
.catch(() => caches.match(req));
})
);
});