Design: Wartungsseite, wenn PocketBase nicht erreichbar ist #47

Closed
opened 2026-10-01 11:23:31 +02:00 by Tom · 0 comments
Owner

Teil von „Design auffrischen“, unabhängig von den Teilen 1–7 umsetzbar (sinnvoll nach Teil 1, damit Schrift und Farben schon stehen). Den Prompt unten komplett an Claude Code geben.

Lies vorher CLAUDE.md und die dort verlinkten Dokumente, die für diese Aufgabe relevant sind
(mindestens docs/architektur.md und docs/redaktion.md). Halte dich an Coding und Documentation
Convention. Erst Plan vorstellen, auf mein OK warten, dann umsetzen.

Design-Leitplanken für alle Design-Aufgaben:
- Bestehende Tokens in wwwroot/app.css erweitern, keine Hardcode-Farben; jede neue Farbe mit
  Dark-Mode-Variante im bestehenden prefers-color-scheme-Block; WCAG AA.
- Keine Hover-Bewegung (transform/translate) – bewusst entfernt in c67c71e. Hover = Farbe,
  Schatten, Rahmen.
- Kommentarstil von app.css beibehalten (erklärt das Warum).
- Layout muss mit beliebigem Redaktions-Markdown und leeren/fehlenden Feldern funktionieren.
- UI-Texte deutsch, ohne Anglizismen.
- Tests: xUnit + Shouldly. Reine Logik als Unit-Test; Darstellung über RouteSmokeTests
  (HTML der gerenderten Route prüfen) gegen den PocketBaseFixture. Keine neuen Test-Pakete
  (bUnit, Playwright …) ohne Begründung und Rückfrage.
- KEINE Änderung am PocketBase-Datenmodell: keine neuen Collections, Felder, Select-Werte
  oder Regeln, collections_schema.json bleibt unverändert. Grund: Es gibt noch keinen Weg,
  Schemaänderungen nach Produktion zu übertragen. Wenn eine Lösung nur mit Schemaänderung
  ginge: anhalten, Alternative ohne Schemaänderung vorschlagen und die Schema-Variante nur als
  Notiz für später ausgeben. Neue Records im Dev-Seed und im Fixture sind erlaubt.

Aufgabe: Eigene Wartungsseite, wenn PocketBase nicht erreichbar ist.

Ist-Zustand: Fällt PocketBase aus, rendert die Startseite (Features/Home/Home.razor.cs)
bewusst nur den festen Kopftext ohne Embeds und mit Status 200; da auch das Menü aus
PocketBase kommt, wirkt die Seite leer. Die übrigen Inhaltsseiten setzen 503 über
Shared/SourceUnavailable und zeigen einen kurzen Hinweis im normalen Layout.

Ziel: Ist PocketBase nicht erreichbar, bekommt jede Seitenanfrage eine eigenständige
statische Wartungsseite mit HTTP 503 (plus Retry-After), statt einer halb leeren Seite.

1. wwwroot/maintenance.html anlegen (Entwurf steht unten im Issue). Eigenständig: keine
   PocketBase-Daten, kein Menü, Styles inline (der Fingerprint-Name von app.css ist der
   statischen Datei nicht bekannt), IGMH-Farben aus app.css übernehmen, Dark Mode,
   keine externen Ressourcen. Nach Prompt 1 Schrift/Farben an die neuen Tokens angleichen.
2. Middleware in Program.cs vor MapRazorComponents: prüft PocketBaseClient.IsHealthyAsync,
   Ergebnis kurz zwischenspeichern (z. B. 5–10 s, IMemoryCache oder ein kleiner
   Singleton-Dienst mit Zeitstempel), damit nicht jede Anfrage PocketBase anpingt.
   Bei „nicht gesund“: maintenance.html mit 503 ausliefern.
   Ausgenommen: /health (muss weiter selbst 503 melden), /events.ics (liefert schon einen
   leeren Kalender), statische Assets (MapStaticAssets), /maintenance.html selbst.
3. Prüfen, ob das bestehende Degradieren in Home/Embeds/Seiten danach noch gebraucht wird;
   als zweite Sicherung (PocketBase fällt zwischen Prüfung und Abruf aus) behalten,
   Kommentare anpassen.
4. Tests in RouteSmokeTests nach dem Muster von
   Health_returns_503_when_PocketBase_is_unreachable: bei nicht erreichbarem PocketBase
   liefern / und eine Inhaltsseite 503 mit dem Wartungstext; /health bleibt 503 mit
   „PocketBase unreachable“; bei erreichbarem PocketBase unverändert 200.
5. Verhalten kurz in docs/architektur.md oder docs/deployment.md (Abschnitt Ausfall)
   beschreiben.

Hintergrund: Ein früherer Versuch funktionierte nur lokal, weil Datei und Middleware nie
committet wurden; Produktion (gebaut aus main) kannte beides nicht.
Entwurf wwwroot/maintenance.html aus dem früheren Versuch (nie committet)
<!DOCTYPE html>
<!--
  Static maintenance page shown when PocketBase is unreachable. A gate in Program.cs
  serves this file with status 503 before any component renders, so it must stand on
  its own: no menu, no PocketBase, and its styles are inlined rather than linked to
  the fingerprinted app.css (whose hashed name this static file cannot know). The
  IGMH colours are copied from app.css so the page still looks like the site.
-->
<html lang="de">

<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Elternbeirat der IGMH – gerade nicht erreichbar</title>
    <style>
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #f4f6f4;
            color: #222;
            font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
            line-height: 1.6;
            padding: 1.5rem;
        }

        main {
            max-width: 32rem;
            background: #fff;
            border-radius: 12px;
            box-shadow: 0 1px 3px rgba(31, 58, 95, 0.08), 0 4px 12px rgba(31, 58, 95, 0.06);
            padding: 2rem;
        }

        h1 {
            color: #2b6f9c;
            margin-top: 0;
            font-size: 1.5rem;
        }

        a {
            color: #1f5477;
            font-weight: 600;
        }

        .contact {
            margin-top: 1.5rem;
            padding-top: 1.5rem;
            border-top: 1px solid #e0e0e0;
            color: #555;
        }
    </style>
</head>

<body>
<main>
    <h1>Die Seite ist gerade nicht erreichbar</h1>
    <p>
        Wir bitten um Entschuldigung. Die Website des Elternbeirats der IGMH ist im
        Moment nicht verfügbar. Wir arbeiten daran – bitte versuchen Sie es in Kürze
        noch einmal.
    </p>
    <p class="contact">
        In dringenden Fällen erreichen Sie den Elternbeirat per E-Mail:<br/>
        <a href="mailto:kontakt@elternbeirat-igmh.info">kontakt@elternbeirat-igmh.info</a>
    </p>
</main>
</body>

</html>

Gehört zu #37.

Teil von „Design auffrischen“, unabhängig von den Teilen 1–7 umsetzbar (sinnvoll nach Teil 1, damit Schrift und Farben schon stehen). Den Prompt unten komplett an Claude Code geben. ``` Lies vorher CLAUDE.md und die dort verlinkten Dokumente, die für diese Aufgabe relevant sind (mindestens docs/architektur.md und docs/redaktion.md). Halte dich an Coding und Documentation Convention. Erst Plan vorstellen, auf mein OK warten, dann umsetzen. Design-Leitplanken für alle Design-Aufgaben: - Bestehende Tokens in wwwroot/app.css erweitern, keine Hardcode-Farben; jede neue Farbe mit Dark-Mode-Variante im bestehenden prefers-color-scheme-Block; WCAG AA. - Keine Hover-Bewegung (transform/translate) – bewusst entfernt in c67c71e. Hover = Farbe, Schatten, Rahmen. - Kommentarstil von app.css beibehalten (erklärt das Warum). - Layout muss mit beliebigem Redaktions-Markdown und leeren/fehlenden Feldern funktionieren. - UI-Texte deutsch, ohne Anglizismen. - Tests: xUnit + Shouldly. Reine Logik als Unit-Test; Darstellung über RouteSmokeTests (HTML der gerenderten Route prüfen) gegen den PocketBaseFixture. Keine neuen Test-Pakete (bUnit, Playwright …) ohne Begründung und Rückfrage. - KEINE Änderung am PocketBase-Datenmodell: keine neuen Collections, Felder, Select-Werte oder Regeln, collections_schema.json bleibt unverändert. Grund: Es gibt noch keinen Weg, Schemaänderungen nach Produktion zu übertragen. Wenn eine Lösung nur mit Schemaänderung ginge: anhalten, Alternative ohne Schemaänderung vorschlagen und die Schema-Variante nur als Notiz für später ausgeben. Neue Records im Dev-Seed und im Fixture sind erlaubt. Aufgabe: Eigene Wartungsseite, wenn PocketBase nicht erreichbar ist. Ist-Zustand: Fällt PocketBase aus, rendert die Startseite (Features/Home/Home.razor.cs) bewusst nur den festen Kopftext ohne Embeds und mit Status 200; da auch das Menü aus PocketBase kommt, wirkt die Seite leer. Die übrigen Inhaltsseiten setzen 503 über Shared/SourceUnavailable und zeigen einen kurzen Hinweis im normalen Layout. Ziel: Ist PocketBase nicht erreichbar, bekommt jede Seitenanfrage eine eigenständige statische Wartungsseite mit HTTP 503 (plus Retry-After), statt einer halb leeren Seite. 1. wwwroot/maintenance.html anlegen (Entwurf steht unten im Issue). Eigenständig: keine PocketBase-Daten, kein Menü, Styles inline (der Fingerprint-Name von app.css ist der statischen Datei nicht bekannt), IGMH-Farben aus app.css übernehmen, Dark Mode, keine externen Ressourcen. Nach Prompt 1 Schrift/Farben an die neuen Tokens angleichen. 2. Middleware in Program.cs vor MapRazorComponents: prüft PocketBaseClient.IsHealthyAsync, Ergebnis kurz zwischenspeichern (z. B. 5–10 s, IMemoryCache oder ein kleiner Singleton-Dienst mit Zeitstempel), damit nicht jede Anfrage PocketBase anpingt. Bei „nicht gesund“: maintenance.html mit 503 ausliefern. Ausgenommen: /health (muss weiter selbst 503 melden), /events.ics (liefert schon einen leeren Kalender), statische Assets (MapStaticAssets), /maintenance.html selbst. 3. Prüfen, ob das bestehende Degradieren in Home/Embeds/Seiten danach noch gebraucht wird; als zweite Sicherung (PocketBase fällt zwischen Prüfung und Abruf aus) behalten, Kommentare anpassen. 4. Tests in RouteSmokeTests nach dem Muster von Health_returns_503_when_PocketBase_is_unreachable: bei nicht erreichbarem PocketBase liefern / und eine Inhaltsseite 503 mit dem Wartungstext; /health bleibt 503 mit „PocketBase unreachable“; bei erreichbarem PocketBase unverändert 200. 5. Verhalten kurz in docs/architektur.md oder docs/deployment.md (Abschnitt Ausfall) beschreiben. Hintergrund: Ein früherer Versuch funktionierte nur lokal, weil Datei und Middleware nie committet wurden; Produktion (gebaut aus main) kannte beides nicht. ``` <details> <summary>Entwurf <code>wwwroot/maintenance.html</code> aus dem früheren Versuch (nie committet)</summary> ```html <!DOCTYPE html> <!-- Static maintenance page shown when PocketBase is unreachable. A gate in Program.cs serves this file with status 503 before any component renders, so it must stand on its own: no menu, no PocketBase, and its styles are inlined rather than linked to the fingerprinted app.css (whose hashed name this static file cannot know). The IGMH colours are copied from app.css so the page still looks like the site. --> <html lang="de"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>Elternbeirat der IGMH – gerade nicht erreichbar</title> <style> body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #f4f6f4; color: #222; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; line-height: 1.6; padding: 1.5rem; } main { max-width: 32rem; background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(31, 58, 95, 0.08), 0 4px 12px rgba(31, 58, 95, 0.06); padding: 2rem; } h1 { color: #2b6f9c; margin-top: 0; font-size: 1.5rem; } a { color: #1f5477; font-weight: 600; } .contact { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid #e0e0e0; color: #555; } </style> </head> <body> <main> <h1>Die Seite ist gerade nicht erreichbar</h1> <p> Wir bitten um Entschuldigung. Die Website des Elternbeirats der IGMH ist im Moment nicht verfügbar. Wir arbeiten daran – bitte versuchen Sie es in Kürze noch einmal. </p> <p class="contact"> In dringenden Fällen erreichen Sie den Elternbeirat per E-Mail:<br/> <a href="mailto:kontakt@elternbeirat-igmh.info">kontakt@elternbeirat-igmh.info</a> </p> </main> </body> </html> ``` </details> Gehört zu #37.
Tom added this to the Elternbeirat-Website milestone 2026-10-01 11:23:31 +02:00
Tom closed this issue 2026-10-01 17:13:00 +02:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Tom/Elternbeirat#47