#114 — Lokale Docker-Testumgebung im go-loop + Backend-Erreichbarkeit-Anzeige #117

Closed
opened 2026-08-18 13:14:14 +02:00 by lena · 0 comments
lena commented 2026-08-18 13:14:14 +02:00 (Migrated from git.butzei.de)

Story: Lokale Docker-Testumgebung im go-loop + Backend-Erreichbarkeit-Anzeige

As a Mensch, der die Ergebnisse jedes "go"-Zyklus testen will,
I want to (a) dass die aktuelle Version jedes Mal lokal in einem Docker-Container läuft/erreichbar ist,
und (b) dass das Frontend klar anzeigt, wenn das Backend nicht erreichbar ist,
so that ich nicht raten muss, ob ein Fehler ein echter Bug oder nur eine kaputte lokale
Testumgebung ist.

Direkt vom Menschen beauftragt (kein "go"), im Anschluss an eine reale Debugging-Session, in der genau
dieses Fehlerbild auftrat: eine falsch konfigurierte lokale Dev-Umgebung (Vite-Proxy zeigte auf den
falschen Backend-Port) erzeugte 18+ gestapelte, nie verschwindende Toasts ("X failed"), die wie ein
Anwendungsfehler aussahen, aber tatsächlich nur "Backend nicht erreichbar" bedeuteten.

Teil 1 — Lokaler Review-Container (Infra, kein neuer Feature-Code)

docker-compose.dev.yml bekommt einen neuen app-Service, der das echte Produktions-Dockerfile
baut (Frontend in wwwroot gebacken, vom Backend same-origin auf Port 8080 ausgeliefert) statt der
Hot-Reload-dotnet watch/npm run dev-Kombination. Läuft gegen dieselben db/redis-Services,
exponiert auf 127.0.0.1:8090. Kein Vite-Dev-Proxy beteiligt — genau die Fehlerklasse, die die
Debugging-Session auslöste, kann hier gar nicht erst auftreten.

CLAUDE.mds Autonomous Loop und ai/roles/00_team_overview.mds Autonomous-Loop-Beschreibung
bekommen einen neuen Schritt (nach dem Push, vor dem Archivieren): den Container neu bauen/starten
und curl -f http://localhost:8090/api/health verifizieren, bevor der Zyklus endet. Siehe
docs/dev-container.mds neuen Abschnitt "Local review container" für die vollständige Begründung.

Live verifiziert, nachdem Docker Desktop (siehe unten) wieder lief: docker compose up --build app
gegen die bestehenden db/redis-Container, curl http://localhost:8090/api/health200 Healthy,
und ein vollständiger Login+Datenabruf im echten Browser gegen http://localhost:8090/ — same-origin,
kein Dev-Proxy beteiligt, exakt wie vorgesehen.

Zwischenzeitlicher Doppel-Ausfall, mitten in der Session, unabhängig von dieser Story: (1) Docker
Desktop stürzte mit einem defekten Unix-Socket-Reparse-Point unter
%LOCALAPPDATA%\Docker\run\dockerInference ab, der sich auch nach hartem Schließen nicht löschen
ließ — behoben durch den Menschen (Docker Desktop neu gestartet; die tatsächliche Reparatur des
Reparse Points blieb Docker selbst überlassen, danach lief die Engine wieder sauber). (2) Diese
Maschine hat zwei unabhängige lokale Checkouts desselben Repos (...\Git\todo und
...\Git\todo2, letzteres dieser Checkout) — db/redis liefen bereits unter dem
Compose-Projektnamen todo (vom anderen Checkout gestartet), während docker compose aus diesem
Checkout heraus standardmäßig einen eigenen Projektnamen todo2 annimmt und versucht hätte, ein
zweites db/redis-Paar mit kollidierenden Port-Bindings anzulegen — gelöst durch explizites
-p todo, um denselben Projekt-Namespace/Netzwerk zu treffen wie die bereits laufenden Container.
Beide Vorfälle sind Umgebungs-/Setup-Eigenheiten dieser Maschine, keine Bugs in docker-compose.dev.yml
selbst; nicht weiter dokumentiert, da nicht reproduzierbar ohne exakt diesen Zwei-Checkout-Aufbau.

Teil 2 — Backend-Erreichbarkeit-Anzeige (echtes Feature)

Neues, minimales Liveness-Endpoint GET /api/health (Program.cs, MapHealthChecks mit
Predicate = _ => false, also ohne die bestehende smtp-email-Prüfung mitzuziehen) — bewusst unter
/api, nicht unter dem bereits existierenden /health/email, damit Vites Dev-Proxy (der nur /api/*
weiterleitet) es in jeder Dev-Topologie automatisch mitbekommt.

Frontend: neues src/health/backendReachability.ts (Hand-rolled Pub/Sub, gleiches Muster wie
offlineSync.ts) verfolgt einen eigenständigen reachable-Zustand, getrennt von offlineSync.tss
isOnline (das nur navigator.onLine widerspiegelt — ein falsch konfigurierter Dev-Proxy sieht davon
aus wie "online"). callApi (api.tsx) ruft bei jedem fehlgeschlagenen Request reportPossibleOutage()
auf, die einen einzelnen, gebündelten /api/health-Ping macht, bevor sie den bisherigen
Pro-Request-Toast unterdrückt oder zeigt — nur eine bestätigte echte Störung (auch der Ping schlägt
fehl) unterdrückt den Toast zugunsten des einen klaren Banners
(BackendUnreachableBanner.tsx, oben in App.tsx, sichtbar auch vor dem Login). Eine
isolierte, echte App-Fehlermeldung einer einzelnen Anfrage (Ping erfolgreich) zeigt weiterhin ihren
eigenen Toast wie zuvor. Automatisches Wieder-Anpingen alle 5s, plus sofortige Erholung sobald
irgendeine andere Anfrage wieder erfolgreich ist.

Bewusst nicht angefasst: OfflineBanner.tsx/offlineSync.ts (#97) — echtes Browser-Offline bleibt
dessen Aufgabe; reportPossibleOutage gibt sich dort sofort geschlagen (isOnline === false), um
keine zwei sich widersprechenden Banner gleichzeitig zu zeigen.

Live verifiziert, beide Richtungen: im echten Browser, mit tatsächlich unerreichbarem Backend
(ausgelöst durch den oben genannten Docker-Absturz): genau ein Banner ("Cannot reach the server.
Retrying automatically…"), null gestapelte Toasts — der exakte Gegensatz zum ursprünglich gemeldeten
Fehlerbild. Und die automatische Erholung: Backend neu gestartet, während die Seite mit sichtbarem
Banner offen blieb — ohne jede manuelle Aktion verschwand das Banner von selbst, sobald der nächste
5-Sekunden-Poll den wieder erreichbaren /api/health traf (ein reload war danach nur nötig, um den
ursprünglich fehlgeschlagenen Login-Check selbst erneut auszulösen — die Erreichbarkeits-Anzeige
erholt sich automatisch, ein hängengebliebener Login-Versuch nicht, das war nie Teil dieser Story).

Tests

  • backendReachability.test.ts (8 Tests): Ping-Bestätigung, Kurzschluss bei bereits bekanntem Ausfall,
    Bündelung gleichzeitiger Aufrufe, Browser-Offline-Kurzschluss, Benachrichtigung nur bei echter
    Zustandsänderung, automatisches Polling/Erholung (Fake Timers).
  • BackendUnreachableBanner.test.tsx (2 Tests): rendert nichts wenn erreichbar, zeigt die Meldung
    wenn nicht.
  • api.test.ts: 3 bestehende Tests angepasst (differenzierter Fetch-Mock nach URL, damit ein
    isolierter Fehler eines einzelnen Requests nicht versehentlich auch den Health-Ping mitreißt), 1
    neuer Test für die Unterdrückung/Wiederherstellung des Toasts über eine ganze Sequenz.

Frontend-Testsuite grün (11 neue Tests, 3 wegen der neuen Erreichbarkeitsprüfung angepasst); Backend-Build sauber (0 Fehler, nur bereits
bestehende Warnungen); kein Docker-Testlauf dieses Mal möglich (siehe oben).

# Story: Lokale Docker-Testumgebung im go-loop + Backend-Erreichbarkeit-Anzeige **As a** Mensch, der die Ergebnisse jedes "go"-Zyklus testen will, **I want to** (a) dass die aktuelle Version jedes Mal lokal in einem Docker-Container läuft/erreichbar ist, und (b) dass das Frontend klar anzeigt, wenn das Backend nicht erreichbar ist, **so that** ich nicht raten muss, ob ein Fehler ein echter Bug oder nur eine kaputte lokale Testumgebung ist. Direkt vom Menschen beauftragt (kein "go"), im Anschluss an eine reale Debugging-Session, in der genau dieses Fehlerbild auftrat: eine falsch konfigurierte lokale Dev-Umgebung (Vite-Proxy zeigte auf den falschen Backend-Port) erzeugte 18+ gestapelte, nie verschwindende Toasts ("X failed"), die wie ein Anwendungsfehler aussahen, aber tatsächlich nur "Backend nicht erreichbar" bedeuteten. ## Teil 1 — Lokaler Review-Container (Infra, kein neuer Feature-Code) `docker-compose.dev.yml` bekommt einen neuen `app`-Service, der das echte Produktions-`Dockerfile` baut (Frontend in `wwwroot` gebacken, vom Backend same-origin auf Port 8080 ausgeliefert) statt der Hot-Reload-`dotnet watch`/`npm run dev`-Kombination. Läuft gegen dieselben `db`/`redis`-Services, exponiert auf `127.0.0.1:8090`. Kein Vite-Dev-Proxy beteiligt — genau die Fehlerklasse, die die Debugging-Session auslöste, kann hier gar nicht erst auftreten. `CLAUDE.md`s Autonomous Loop und `ai/roles/00_team_overview.md`s Autonomous-Loop-Beschreibung bekommen einen neuen Schritt (nach dem Push, vor dem Archivieren): den Container neu bauen/starten und `curl -f http://localhost:8090/api/health` verifizieren, bevor der Zyklus endet. Siehe `docs/dev-container.md`s neuen Abschnitt "Local review container" für die vollständige Begründung. **Live verifiziert**, nachdem Docker Desktop (siehe unten) wieder lief: `docker compose up --build app` gegen die bestehenden `db`/`redis`-Container, `curl http://localhost:8090/api/health` → `200 Healthy`, und ein vollständiger Login+Datenabruf im echten Browser gegen `http://localhost:8090/` — same-origin, kein Dev-Proxy beteiligt, exakt wie vorgesehen. **Zwischenzeitlicher Doppel-Ausfall, mitten in der Session, unabhängig von dieser Story:** (1) Docker Desktop stürzte mit einem defekten Unix-Socket-Reparse-Point unter `%LOCALAPPDATA%\Docker\run\dockerInference` ab, der sich auch nach hartem Schließen nicht löschen ließ — behoben durch den Menschen (Docker Desktop neu gestartet; die tatsächliche Reparatur des Reparse Points blieb Docker selbst überlassen, danach lief die Engine wieder sauber). (2) Diese Maschine hat **zwei unabhängige lokale Checkouts desselben Repos** (`...\Git\todo` und `...\Git\todo2`, letzteres dieser Checkout) — `db`/`redis` liefen bereits unter dem Compose-Projektnamen `todo` (vom anderen Checkout gestartet), während `docker compose` aus diesem Checkout heraus standardmäßig einen eigenen Projektnamen `todo2` annimmt und versucht hätte, ein zweites `db`/`redis`-Paar mit kollidierenden Port-Bindings anzulegen — gelöst durch explizites `-p todo`, um denselben Projekt-Namespace/Netzwerk zu treffen wie die bereits laufenden Container. Beide Vorfälle sind Umgebungs-/Setup-Eigenheiten dieser Maschine, keine Bugs in `docker-compose.dev.yml` selbst; nicht weiter dokumentiert, da nicht reproduzierbar ohne exakt diesen Zwei-Checkout-Aufbau. ## Teil 2 — Backend-Erreichbarkeit-Anzeige (echtes Feature) Neues, minimales Liveness-Endpoint `GET /api/health` (`Program.cs`, `MapHealthChecks` mit `Predicate = _ => false`, also ohne die bestehende `smtp-email`-Prüfung mitzuziehen) — bewusst unter `/api`, nicht unter dem bereits existierenden `/health/email`, damit Vites Dev-Proxy (der nur `/api/*` weiterleitet) es in jeder Dev-Topologie automatisch mitbekommt. Frontend: neues `src/health/backendReachability.ts` (Hand-rolled Pub/Sub, gleiches Muster wie `offlineSync.ts`) verfolgt einen eigenständigen `reachable`-Zustand, getrennt von `offlineSync.ts`s `isOnline` (das nur `navigator.onLine` widerspiegelt — ein falsch konfigurierter Dev-Proxy sieht davon aus wie "online"). `callApi` (`api.tsx`) ruft bei jedem fehlgeschlagenen Request `reportPossibleOutage()` auf, die einen einzelnen, gebündelten `/api/health`-Ping macht, bevor sie den bisherigen Pro-Request-Toast unterdrückt oder zeigt — nur eine bestätigte echte Störung (auch der Ping schlägt fehl) unterdrückt den Toast zugunsten des einen klaren Banners (`BackendUnreachableBanner.tsx`, oben in `App.tsx`, sichtbar auch vor dem Login). Eine isolierte, echte App-Fehlermeldung einer einzelnen Anfrage (Ping erfolgreich) zeigt weiterhin ihren eigenen Toast wie zuvor. Automatisches Wieder-Anpingen alle 5s, plus sofortige Erholung sobald irgendeine andere Anfrage wieder erfolgreich ist. Bewusst nicht angefasst: `OfflineBanner.tsx`/`offlineSync.ts` (`#97`) — echtes Browser-Offline bleibt dessen Aufgabe; `reportPossibleOutage` gibt sich dort sofort geschlagen (`isOnline === false`), um keine zwei sich widersprechenden Banner gleichzeitig zu zeigen. **Live verifiziert, beide Richtungen:** im echten Browser, mit tatsächlich unerreichbarem Backend (ausgelöst durch den oben genannten Docker-Absturz): genau ein Banner ("Cannot reach the server. Retrying automatically…"), null gestapelte Toasts — der exakte Gegensatz zum ursprünglich gemeldeten Fehlerbild. Und die automatische Erholung: Backend neu gestartet, während die Seite mit sichtbarem Banner offen blieb — ohne jede manuelle Aktion verschwand das Banner von selbst, sobald der nächste 5-Sekunden-Poll den wieder erreichbaren `/api/health` traf (ein `reload` war danach nur nötig, um den ursprünglich fehlgeschlagenen Login-Check selbst erneut auszulösen — die Erreichbarkeits-Anzeige erholt sich automatisch, ein hängengebliebener Login-Versuch nicht, das war nie Teil dieser Story). ## Tests - `backendReachability.test.ts` (8 Tests): Ping-Bestätigung, Kurzschluss bei bereits bekanntem Ausfall, Bündelung gleichzeitiger Aufrufe, Browser-Offline-Kurzschluss, Benachrichtigung nur bei echter Zustandsänderung, automatisches Polling/Erholung (Fake Timers). - `BackendUnreachableBanner.test.tsx` (2 Tests): rendert nichts wenn erreichbar, zeigt die Meldung wenn nicht. - `api.test.ts`: 3 bestehende Tests angepasst (differenzierter Fetch-Mock nach URL, damit ein isolierter Fehler eines einzelnen Requests nicht versehentlich auch den Health-Ping mitreißt), 1 neuer Test für die Unterdrückung/Wiederherstellung des Toasts über eine ganze Sequenz. Frontend-Testsuite grün (11 neue Tests, 3 wegen der neuen Erreichbarkeitsprüfung angepasst); Backend-Build sauber (0 Fehler, nur bereits bestehende Warnungen); kein Docker-Testlauf dieses Mal möglich (siehe oben).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#117
No description provided.