# CLAUDE.md — CheckPoint Ehrenamt App > Projektanker. Claude Code liest diese Datei automatisch zu Beginn jeder Session. > Sie ist die verbindliche Quelle für Stack, Regeln und Logik. Bei Widerspruch > zwischen Wunsch und dieser Datei: nachfragen, nicht raten. --- ## Zweck Interne, mobile-first Web-App für das Vor-Ort-Team Prävention der AIDS-Hilfe. Sie organisiert Einsätze (Terminabstimmung + Dienstverteilung) und ersetzt schrittweise die WhatsApp/Signal-Zettelwirtschaft. **Das Herzstück ist die Dienstplanung — alles andere ordnet sich dem unter.** --- ## Stack & Rahmenbedingungen (fest) | Bereich | Entscheidung | |---|---| | Sprache/Backend | **Python + Flask** | | Templating | **Jinja2**, server-gerendertes HTML (klassische Multi-Page-App) | | ORM/DB | **SQLAlchemy**, **SQLite** für v1 (ein VPS, 17 Nutzer). Keine Annahmen treffen, die einen späteren Umstieg auf PostgreSQL verbauen. | | Passwörter | `werkzeug.security` (Hashing), nie Klartext | | CSS | **selbst gebaut, kein CSS-Framework**. Basis: `tokens.css` + `components.css` | | JavaScript | **so wenig wie möglich, nur Vanilla, kein Framework**. Einzige Ausnahme v1: Chat-Polling (s. u.) | | Hosting | **eigener VPS in der EU** | | Plattform | mobile-first Web-App, **kein App Store**, „zum Homescreen" möglich | | UI-Sprache | **Deutsch** | **Erlaubte Zusatz-Bibliotheken (klein halten):** Flask, SQLAlchemy/Flask-SQLAlchemy, Werkzeug, optional Flask-Login. Keine schweren Frameworks, kein CSS-/JS-Framework. --- ## Architektur-Prinzipien 1. **Server-rendered first.** Seiten kommen fertig vom Server. Formulare sind echte `POST`-Formulare. Kein clientseitiges Routing, kein SPA-Verhalten. 2. **JavaScript nur, wo es sich beweist.** Einzige JS-Ausnahme in v1: der Chat holt neue Nachrichten per kleinem `fetch`-Polling. Sonst nirgends JS verlangen. 3. **Mandantenfähig ab Tag 1.** Siehe eigener Abschnitt. Jeder Datensatz gehört zu genau einem Team. 4. **Mobile-first.** Getestet in App-Breite 360–460px. 5. **Barrierearmut ist Pflicht, kein Extra.** 44px Touch-Ziele, sichtbarer Fokus, Status nie nur über Farbe, sinnvolle `aria-label`. --- ## Rollen | Rolle | Rechte | |---|---| | **Admin** | Logins vergeben, Planungszeiträume und Einsätze anlegen, Dienstplan bauen und veröffentlichen, Dokumente/Fotos freigeben, im Kanal „Ankündigungen" posten, Team verwalten. | | **Ehrenamtliche:r** | Verfügbarkeit melden, eigenen Dienstplan sehen, freie Dienste übernehmen, Dokumente/Fotos hochladen (Freigabe nötig), im Kanal „Team" schreiben, Profil pflegen. | - **Keine Selbstregistrierung.** Logins legt ausschließlich der Admin an. - **v1:** Nutzername + Passwort. (E-Mail + Code = Future Log.) --- ## Mandantenfähigkeit (Pflichtregeln) - Zentrale Einheit **`team`** (= Mandant). Das CheckPoint-Team ist das erste von perspektivisch mehreren. - **Jeder** Datensatz (user, planungszeitraum, einsatz, verfuegbarkeit, zuteilung, dokument, kanal, nachricht) trägt eine **`team_id`**. - **Jede** Datenbankabfrage filtert nach dem Team des eingeloggten Nutzers. Nie Daten über Teamgrenzen hinweg ausliefern. Das aktive Team wird aus der Session abgeleitet. - Das Corporate Design wird **pro Team** über die Brand-Tokens in `tokens.css` gesetzt (serverseitig in den `` injiziert). Code darf keine Markenfarbe fest verdrahten — immer Tokens verwenden. --- ## Datenmodell (Regeln, konzeptionell) Jede Tabelle hat `team_id`. Namen/Felder sind Richtwerte, keine starre Vorgabe. - **team** — Mandant. Felder u. a.: Name, Brand-Tokens (Primär, Primär-stark, Akzent, Akzent-stark, Schrift), Logo-Pfad. - **user** — `rolle` (admin | ehrenamt), Nutzername, Passwort-Hash, Anzeigename, optionales Profilbild, optionale freiwillige Angaben. - **planungszeitraum** — Block über einen frei wählbaren Zeitraum (kein Mindest­zeitraum). `status`: in_planung | veroeffentlicht. - **einsatz** — gehört **optional** zu einem Planungszeitraum (`planungszeitraum_id` nullbar). Ohne Zeitraum = **Einzeltermin**, der direkt zu besetzen ist. Datum, Uhrzeit (Start/Ende), Art/Ort (z. B. Tour Altstadt, Party, Sonderveranstaltung). Braucht **2 Hauptplätze + 1 Springerplatz**. - **verfuegbarkeit** — (user × einsatz) → kann | kann_nicht. - **zuteilung** — (user × einsatz) mit `platztyp` (haupt | springer) und `status` (zugeteilt | abgesagt | offen | uebernommen). - **dokument** — Datei (PDF/Word/Bild). Felder: Titel (Pflicht), Uploader, `status` (wartet_auf_freigabe | freigegeben | abgelehnt), `ablage` (aktuell | archiv). - **kanal** — pro Team zwei feste: `ankuendigungen`, `team`. - **nachricht** — Kanal, Autor, Text, Zeitstempel. **Nur Text.** --- ## Kern-Business-Logik (verbindlich) ### Planungszyklus 1. Admin legt einen Planungszeitraum (frei wählbare Länge) an und trägt die kuratierten Einsätze ein (Freitags-Touren, Samstags-Einsätze/Partys, Sonderveranstaltungen). Alternativ: einzelne Einzeltermine ohne Zeitraum, direkt zum Besetzen. 2. Status `in_planung` → Ehrenamtliche melden pro Einsatz **kann / kann nicht**. Änderung der Verfügbarkeit nur solange `in_planung`. 3. Admin baut den Plan **manuell**: pro Einsatz **2 Haupt + 1 Springer**. Die App zeigt pro Person **Anzahl bisheriger Einsätze** und **letzten Einsatz** als Hilfe (keine automatische Verteilung in v1). 4. Bei zu wenigen Verfügbaren: Einsatz als **unterbesetzt** markieren. 5. Admin **veröffentlicht** → alle sehen ihren persönlichen Dienstplan. ### Absage & Übernahme - Sagt eine **Hauptperson** ab → **Springer rückt automatisch nach** (Status `uebernommen`). - Der frei gewordene **Springerplatz** wird `offen` → zum **„Übernehmen"** für alle. - Gibt es keinen Springer / sagt er auch ab → offener Platz für alle. - „Übernehmen" dient **nur dem Nachrücken**, nie der Erstverteilung. ### Dokumente - Upload durch alle, aber Status startet **`wartet_auf_freigabe`** (nur Admin + Uploader sichtbar). - Admin gibt frei oder lehnt ab (optional Grund). Erst nach Freigabe für alle sichtbar. - Admin-Uploads gehen direkt durch. - Liste geteilt in **Aktuell** und **Archiv** (Verschieben macht der Admin). - **Foto-Regel:** nur ohne erkennbare Dritte bzw. mit deren Einverständnis. ### Chat - Zwei Kanäle: **Ankündigungen** (nur Admin postet, alle lesen) und **Team** (alle posten). - Nur Text. Neue Nachrichten via Hintergrund-`fetch`-Polling (einzige v1-JS-Ausnahme). ### Benachrichtigungen - **v1: nur in-App** (neuer Dienst, offener Dienst, neue Nachricht). Push/E-Mail = Future Log. --- ## Designsystem - Quelle der Wahrheit: **`tokens.css`** (mandantenfähig, hell + dunkel via `light-dark()`). - **Hell/Dunkel** folgt standardmäßig der Systemeinstellung. Zusätzlich gibt es einen manuellen Umschalter (Topbar-Icon, schaltet System → Hell → Dunkel). Er läuft **serverseitig ohne JS**: ein POST setzt ein `theme`-Cookie, der Server schreibt `data-theme="light|dark"` an `` (bei „System" kein Attribut). „System" ist Default und jederzeit wieder wählbar. - **Nie feste Farben** im Komponenten-CSS — immer Tokens (`var(--cp-...)`). - Erledigt (Session 4 + Nachzug): `components.css` enthält **kein** Farb-Literal mehr. Die früher feste weiße Button-Schrift auf Türkis/Rot läuft jetzt über das Token `--cp-on-brand` (BRAND-Ebene, bewusst themeunabhängig, kein `light-dark()`, da die Flächen `--cp-teal`/`--cp-red` in beiden Themes derselbe Vollton sind). Pro Team überschreibbar. Regel daher ausnahmslos: nie Farb-Literale im Komponenten-CSS. - Status-Mapping: Türkis = offen/bestätigt/verfügbar · Rot = dringend/Vertretung · Neutral = Info/intern/erledigt. - Schrift: DM Sans. --- ## UI-Regeln - Mobile-first bei 360–460px Basisbreite; auf Tablet/Desktop wächst die zentrierte Spalte (inkl. fixierter Topbar/Bottom-Nav) ab 768px auf ~720px. Viel Weißraum, klare Karten (Radius 12px, Innenabstand 16px), keine verschachtelten Karten. - **Bottom-Navigation, 4 Punkte: Start · Termine · Team · Profil.** (Nicht „Kalender".) - Jede Ansicht hat **eine klare Hauptaktion** (Button oder FAB). - Buttons: Primär = Türkis/weiß · Dringend = Rot/weiß · Ghost = Fläche mit feinem Rahmen. Kurze Beschriftungen: „Speichern", „Eintragen", „Übernehmen", „Details". - Formulare: Labels immer sichtbar über dem Feld, Felder ≥ 44px, kurze freundliche Hilfetexte, Fehler klar und nicht nur über Farbe. ## Texte / Copy - **Deutsch**, Satzanfang groß (sentence case), aktive Verben, keine Floskeln. - Eine Aktion heißt im ganzen Flow gleich (Button „Übernehmen" → Bestätigung „Übernommen"). - Fehlermeldungen sagen, was passiert ist und wie es weitergeht — ohne Entschuldigung. --- ## Bewusst raus aus v1 (Out of Scope) Keine öffentlichen Seiten · keine Klient:innendaten · keine Bezahlfunktion · kein Kalender-Sync (Terminübersicht = Liste) · keine Dateien/Bilder im Chat · keine automatische Dienstverteilung · kein 1:1-Chat · kein Push/E-Mail. --- ## Vorgeschlagene Projektstruktur ``` checkpoint-ehrenamt/ ├── CLAUDE.md ├── TASKS.md ├── app/ │ ├── __init__.py # App-Factory, DB-Init, Team-Scoping │ ├── models.py # SQLAlchemy-Modelle (alle mit team_id) │ ├── auth.py # Login, Logout, Admin-Anlage │ ├── routes/ # Blueprints: planung, dienste, dokumente, chat, profil │ ├── templates/ # Jinja2 (base.html + Teilansichten) │ └── static/ │ ├── tokens.css │ └── components.css ├── instance/ # SQLite-DB, Uploads (nicht im Git) └── requirements.txt ``` --- ## Arbeitsweise mit den Modellen (Continue) - **Mistral (Architect):** Planung, Design-Entscheidungen, Reviews. Nie zum Schreiben von Produktivcode. - **Qwen (Coder):** Implementierung, Bugfixes, Inline-Edits (`Ctrl+I`). Immer ein Stück nach dem anderen (Datenmodell → Service → Route → Template). - **Claude Code:** Scaffold, Mehrdateien-Arbeit, übergreifendes Verdrahten. - **Eine Aufgabe pro Session.** Immer `@file`-Kontext. Plan vor Code.