7.2 KiB
CLAUDE.md
Website des Elternbeirats der IGMH. Blazor Web App mit statischem
Server-Side-Rendering, .NET 10. Läuft als Container auf Unraid hinter dem Nginx
Proxy Manager. Die Inhalte liegen in PocketBase (zweiter Container); die App
liest sie pro Request über einen typisierten PocketBaseClient.
Architekturentscheidungen und deren Begründung: docs/architektur.md. Dort
nachlesen, bevor eine davon in Frage gestellt wird.
Wann welches Dokument
| Thema | Datei |
|---|---|
| Warum SSR statt WASM, warum PocketBase als Datenschicht (AE-1 bis AE-4) | docs/architektur.md |
| Stack lokal starten, Tests, compose-Overlays | docs/entwicklung.md |
| Unraid, compose, NPM-Proxy-Host, Registry, Rollback | docs/deployment.md |
| Inhalte anlegen und ändern (PocketBase-Admin) | docs/redaktion.md |
| Impressum, Datenschutz, Fotos | docs/recht.md |
| Offene Arbeit, Meilensteine, offene Punkte | Gitea-Issues (Milestone „Elternbeirat-Website") |
Offene Aufgaben liegen als Gitea-Issues, nicht als Markdown im Repo. So löst das Anlegen oder Ändern eines Tickets keinen Build aus. Die vier Arbeitsstränge (Redaktionssystem, Deployment, Inhalte, Infrastruktur) sind Parent-Issues mit Sub-Issues als Checkliste im Body, alle am Milestone „Elternbeirat-Website".
Kommandos
# Lokaler Stack (App + PocketBase) über das Dev-Overlay, siehe docs/entwicklung.md:
docker compose -f compose.yaml -f compose.dev.yaml up --build # App :5000, PocketBase-Admin :8090/_/
dotnet test # Smoke-Tests (starten eigene PocketBase) + Unit-Tests
Die App liest ihren Inhalt pro Request aus PocketBase; sie hält selbst keinen
Inhalt und liest nichts beim Start ein. Kein EF Core, keine Migrationen, kein
Seeding im Code. Ein git clone ist nicht der vollständige Stand — der
Inhalt liegt in PocketBase (pb_data), das separat gesichert wird (siehe
docs/deployment.md).
Nicht offensichtlich
- Inhalte liegen in PocketBase (
pb_data-Volume), nicht im Image. Eine Textänderung ist ein Eintrag im PocketBase-Admin und sofort live — kein Commit, kein Rebuild (docs/architektur.md, AE-2/AE-3). Nur die statischen Assets unterwwwroot/(CSS, Fonts, PDFs) sind ins Image gebacken. - Kein
UseHttpsRedirection(), keinUseHsts(). NPM terminiert TLS; beides erzeugt hinter dem Proxy eine Redirect-Schleife.UseForwardedHeadersmit geleertenKnownNetworks/KnownProxiesist korrekt so, weil der Container kein Port-Mapping hat und nur über NPM erreichbar ist. - Das chiseled-Runtime-Image hat keine Shell.
HEALTHCHECKmitcurlodershschlägt dort fehl. - Neue Seite / neuer Beitrag / neuer Termin / neue Frage = Record in der
passenden PocketBase-Collection (
pages/posts/events/faqs), nicht eine Datei und kein.razor.pagesmitslugist sofort unter/<slug>erreichbar;location+ordersteuern das Menü,embedbettet Listen ein (docs/redaktion.md). Eine neue Art von Liste (eigene Route/Logik) ist dagegen Code: neue Collection +PocketBaseClient-Methode + Komponente (docs/entwicklung.md). - Slugs ohne Umlaute (
ueber-uns, nichtüber-uns).
Regeln
- Statisches SSR ist der Default. Kein
@rendermodeohne konkreten Anlass, und wenn, dann an genau der einen Komponente — nie global. - Keine externen Ressourcen. Keine CDN-Skripte, keine Google Fonts, keine
Maps- oder Video-Embeds. Schriften werden selbst ausgeliefert. Grund ist
Datenschutz, nicht Geschmack (
docs/recht.md). - Kein EF Core, kein ORM, keine Migrationen. Inhalte kommen aus PocketBase
über den
PocketBaseClient(REST), nicht aus einer relationalen DB im Code. - Inhalte gehören nach PocketBase, nicht als Markup in Komponenten und nicht in Dateien im Repo.
- Kein eigenes CMS, kein Login und keine Benutzerkonten in der Website — gepflegt wird ausschließlich im PocketBase-Admin (Redakteure sind Superuser).
- UI-Texte auf Deutsch, ohne Anglizismen.
Termine, nichtEvents. - Neue NuGet-Abhängigkeiten vorher begründen. Genutzt wird nur
Markdig(Markdown → HTML).YamlDotNetstammt aus der abgelösten Datei-Schicht, wird nicht mehr verwendet und soll aus demcsprojentfernt werden.
Stil
- Nullable aktiviert,
ImplicitUsingsan, File-scoped Namespaces. - Datenzugriff über den typisierten
PocketBaseClient(registriert viaAddHttpClient), der pro Request liest — kein Start-Cache, kein Singleton mit Inhalten. Komponenten liegen feature-basiert unterFeatures/<Bereich>/. - JSON-Konverter liegen in
Contractsund werden per[JsonConverter]am Feld zugeordnet, nicht global auf denJsonSerializerOptionsim Client registriert. So ist am Contract sichtbar, wie ein Feld behandelt wird (z. B.Event.Startals Zeitpunkt,Post.Dateals Kalendertag), undContractsbekommt keine Referenz aufPocketBase(Zirkel). Datumsfelder:DateTime/DateTime?= Zeitpunkt (Europe/Berlin),DateOnly= Kalendertag. - Code durchgängig auf Englisch — Typen, Member, Variablen, Kommentare,
Skripte und Doku. Das gilt auch für Domänenbegriffe: im Code
Event, nichtTermin;Post, nichtBeitrag. Deutsch bleibt ausschließlich, was ein Besucher liest oder ein Redakteur pflegt: UI-Texte sowie die Werte der PocketBase-Records (z. B.title: Vorstandsteam, der Markdown-body). Die Feldnamen und Slugs bleiben dagegen englisch (title,slug,/board).
Coding Convention
- Ausdruckskörper (
=>) sind Pflicht, wo syntaktisch möglich — Methoden, Properties, Konstruktoren, Operatoren, lokale Funktionen. Einif/else, das einen Wert liefert, wird zum ternären Ausdruck oder zurswitch-Expression, kein Block-Körper mitreturn. Ein Block-Körper nur, wo ein Ausdruck sprachlich nicht geht (mehrere Anweisungen ohne Rückgabe,ref/out,yield). - Ternäre und
switch-Expressions dürfen dafür mehrzeilig umgebrochen werden; Lesbarkeit entsteht durch Einrückung, nicht durch einen Block. - Nullable aktiv nutzen:
?,??,??=statt Nullprüfungen im Block. Ein ungültigernull-Fall wird als Ausdruck geworfen (?? throw new …). - Argumente/Rückgaben früh und knapp validieren, bevorzugt als Ausdruck.
Documentation Convention
Vorbild ist Elternbeirat.PocketBase/LocalDateTimeConverter.cs — daran
ausrichten.
- Jeder öffentliche (
public/protected) Typ und Member bekommt XML-Doc — nicht nurPocketBaseClientundContracts. Interne Helfer, die Teil der fachlichen Erklärung sind (wieWallClock), ebenfalls. Razor-Markup nicht. - Voller Umfang, wo zutreffend:
<summary>, dazu<param>,<returns>,<exception>(jede geworfene Bedingung),<remarks>für Kontext/Fallstricke,<example>mit<code>für nicht offensichtliche Nutzung,<seealso>auf verwandte Typen.<inheritdoc/>bei Interface-/Basis-Implementierungen. - Code im Text als Markup referenzieren, nicht als Prosa:
<see cref="…"/>,<see langword="null"/>/<see langword="false"/>,<c>…</c>für Literale. - Einrückung: der Textinhalt steht mit vier Leerzeichen unter dem
///-Tag (/// Text), Tags sauber verschachtelt. - Englisch, leicht verständlich, erklärt warum, nicht was der Code ohnehin zeigt.