385 lines
13 KiB
Markdown
385 lines
13 KiB
Markdown
# TASKS.md — Build-Checkliste CheckPoint Ehrenamt App
|
||
|
||
> Eine Aufgabe pro Session. Nach Abhängigkeit geordnet — von oben nach unten abarbeiten.
|
||
> Pro Session: **Tool**, **Ziel**, **Session-Prompt** (zum Einfügen), **Deliverables**.
|
||
> Nach jeder Session: alle Häkchen setzen, committen, erst dann die nächste starten.
|
||
>
|
||
> Tool-Legende: 🛠 Claude Code · 💻 Continue + Qwen · 🏛 Continue + Mistral
|
||
> Modell wechseln: `llama-switch` → [1] Architect / [2] Coder
|
||
|
||
---
|
||
|
||
## Session 1 · Scaffold & Projektgerüst 🛠
|
||
|
||
**Ziel:** Lauffähiges Flask-Grundgerüst mit App-Factory, SQLite, Basis-Layout,
|
||
Static-Einbindung und Team-Scoping-Stub.
|
||
|
||
**Prompt:**
|
||
```
|
||
Lies CLAUDE.md. Erstelle das Projektgerüst gemäß der vorgeschlagenen Struktur:
|
||
Flask-App-Factory, SQLAlchemy mit SQLite (instance/), requirements.txt,
|
||
ein base.html mit eingebundener tokens.css und components.css, sowie ein
|
||
leeres Team-Scoping (Helper, der das aktive Team aus der Session liest).
|
||
Zeig mir zuerst den Plan und die Dateiliste, bevor du schreibst.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] App startet lokal (`flask run`) und zeigt eine leere Startseite
|
||
- [x] `tokens.css` + `components.css` in `base.html` eingebunden
|
||
- [x] SQLite initialisiert, `instance/` in `.gitignore`
|
||
- [x] Team-Scoping-Helper vorhanden (noch ohne echte Logik)
|
||
- [x] `requirements.txt` minimal gehalten
|
||
|
||
---
|
||
|
||
## Session 2 · Datenmodell & Mandanten 💻
|
||
|
||
**Ziel:** Alle Modelle aus CLAUDE.md, jedes mit `team_id`. Seed für das erste Team
|
||
(CheckPoint) inkl. Brand-Tokens und den zwei Chat-Kanälen.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Erst den Ansatz erklären, dann implementieren — ein Modell nach dem anderen.
|
||
Baue die Modelle: team, user, planungszeitraum, einsatz, verfuegbarkeit,
|
||
zuteilung, dokument, kanal, nachricht. Jedes mit team_id. Danach ein Seed-Skript,
|
||
das das CheckPoint-Team mit Brand-Tokens und den Kanälen "ankuendigungen" und
|
||
"team" anlegt. Halte dich an die Feldvorgaben in CLAUDE.md.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Alle Modelle vorhanden, jedes mit `team_id`
|
||
- [x] Beziehungen sauber (Einsatz↔Planungszeitraum, Zuteilung↔User/Einsatz)
|
||
- [x] Seed legt CheckPoint-Team + 2 Kanäle an
|
||
- [x] DB lässt sich anlegen, Seed läuft ohne Fehler
|
||
|
||
---
|
||
|
||
## Session 3 · Auth & Admin-Anlage 💻
|
||
|
||
**Ziel:** Login (Nutzername + Passwort, gehasht), Session, Logout, Team-Scoping aktiv,
|
||
Admin kann Nutzer anlegen. Keine Selbstregistrierung.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/auth.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Baue: Login mit Nutzername+Passwort (werkzeug-Hashing), Session,
|
||
Logout. Aktiviere das Team-Scoping (aktives Team aus der Session). Admin-geschützte
|
||
Route zum Anlegen neuer Nutzer (Rolle wählbar). Geschützte Routen leiten ohne
|
||
Login zur Anmeldung um. Ein Stück nach dem anderen.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Login/Logout funktioniert, Passwörter gehasht
|
||
- [x] Geschützte Routen ohne Login → Redirect
|
||
- [x] Admin kann Nutzer mit Rolle anlegen
|
||
- [x] Alle Queries laufen team-gescoped
|
||
- [ ] 🏛 Mini-Review (Mistral): Auth gegen CLAUDE.md prüfen — als Liste, nicht umschreiben
|
||
|
||
---
|
||
|
||
## Session 4 · Base-Layout, Theming & Dark-Mode-Fix 💻
|
||
|
||
**Ziel:** base.html final (Topbar, Bottom-Nav Start/Termine/Team/Profil, FAB-Slot),
|
||
Brand-Tokens pro Team in den `<head>` injizieren, Mockup-CSS auf Tokens umstellen.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/templates/base.html
|
||
@file app/static/components.css
|
||
@file app/static/tokens.css
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. 1) base.html mit Topbar (Logo, Glocke, Avatar) und Bottom-Nav
|
||
(Start, Termine, Team, Profil). 2) Brand-Tokens des aktiven Teams serverseitig
|
||
in den <head> schreiben. 3) Im components.css alle fest verdrahteten Weißwerte
|
||
(Topbar, Bottom-Nav, .cp-page-Verlauf) auf Tokens umstellen, damit der Dunkelmodus
|
||
greift. Erst die Token-Umstellung erklären, dann umsetzen.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] base.html mit korrekter Bottom-Nav (4 Punkte, „Termine")
|
||
- [x] Brand-Tokens kommen pro Team aus der DB in den `<head>`
|
||
- [x] Keine festen Weiß-/Schwarzwerte mehr im components.css (weiße Button-Schrift jetzt über Token `--cp-on-brand`, themeunabhängig)
|
||
- [x] Hell- und Dunkelmodus optisch sauber (System-Umschaltung testen)
|
||
|
||
---
|
||
|
||
## Session 5 · Planungszeitraum & Einsätze (Admin) 💻
|
||
|
||
**Ziel:** Admin legt Planungszeiträume an und trägt Einsätze ein (Datum, Zeit, Art).
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/planung.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Admin-Ansichten: Planungszeitraum anlegen (Status in_planung) und
|
||
darin Einsätze hinzufügen/bearbeiten/löschen (Datum, Start/Ende, Art/Ort, je 2 Haupt-
|
||
+ 1 Springerplatz). Reine POST-Formulare, kein JS. Liste der Einsätze eines Zeitraums.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Admin kann Zeitraum anlegen
|
||
- [x] Admin kann Einsätze anlegen/bearbeiten/löschen
|
||
- [x] Einsatz kennt seine Platzstruktur (2 + Springer)
|
||
- [x] Alles team-gescoped, ohne JavaScript
|
||
|
||
---
|
||
|
||
## Session 6 · Verfügbarkeit melden (Ehrenamtliche) 💻
|
||
|
||
**Ziel:** Ehrenamtliche melden pro Einsatz „kann / kann nicht", solange `in_planung`.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/planung.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Ansicht für Ehrenamtliche: Liste der Einsätze im laufenden Zeitraum
|
||
mit Umschaltung kann/kann_nicht pro Einsatz (POST-Formular). Änderung nur bei Status
|
||
in_planung. Übersichtlich für mobile.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Ehrenamtliche sehen die Einsätze des aktuellen Zeitraums
|
||
- [x] kann/kann_nicht wird gespeichert und ist änderbar (nur in_planung)
|
||
- [x] Nach Veröffentlichung gesperrt
|
||
- [x] Mobile sauber bedienbar (44px-Ziele)
|
||
|
||
---
|
||
|
||
## Session 7 · Dienstplan bauen (Admin) 💻
|
||
|
||
**Ziel:** Admin verteilt manuell 2 Haupt + 1 Springer pro Einsatz, mit Fairness-Anzeige.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/dienste.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Admin-Ansicht pro Einsatz: verfügbare Personen auswählen und auf
|
||
Haupt/Springer setzen. Pro Person anzeigen: Anzahl bisheriger Einsätze und letzter
|
||
Einsatz (Fairness-Hilfe). Einsatz als „unterbesetzt" markieren, wenn zu wenige.
|
||
Keine automatische Verteilung — nur Anzeige + manuelle Auswahl.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Admin kann Haupt- und Springerplätze besetzen
|
||
- [x] Fairness-Anzeige (Anzahl + letzter Einsatz) pro Person sichtbar
|
||
- [x] Unterbesetzte Einsätze klar markiert
|
||
- [ ] 🏛 Review (Mistral): Zuteilungslogik gegen CLAUDE.md prüfen
|
||
|
||
---
|
||
|
||
## Session 8 · Veröffentlichen & persönliche Terminübersicht 💻
|
||
|
||
**Ziel:** Admin veröffentlicht den Zeitraum; jede:r sieht „Meine Termine" als Liste.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/dienste.py
|
||
@file app/templates/
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. 1) Admin-Aktion „Veröffentlichen" (Status veroeffentlicht, sperrt
|
||
Verfügbarkeit). 2) Ansicht „Meine Termine": Liste der eigenen zugeteilten Einsätze,
|
||
chronologisch, mit Rolle (Haupt/Springer) und Status. Keine Kalenderansicht.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Veröffentlichen funktioniert und sperrt die Verfügbarkeit
|
||
- [x] „Meine Termine" zeigt eigene Einsätze als Liste
|
||
- [x] Haupt/Springer und Status erkennbar (nicht nur über Farbe)
|
||
|
||
---
|
||
|
||
## Session 9 · Absage & Übernahme 💻
|
||
|
||
**Ziel:** Absage einer Hauptperson → Springer rückt automatisch nach → Springerplatz
|
||
wird offen → „Übernehmen" für alle.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/dienste.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Logik laut CLAUDE.md: Sagt eine Hauptperson ab, rückt der Springer
|
||
automatisch nach (status uebernommen) und der Springerplatz wird offen. Offene
|
||
Plätze erscheinen mit „Übernehmen"-Button; wer zuerst klickt, bekommt ihn (sauber
|
||
gegen Doppel-Klicks absichern). Kein Springer → Platz direkt offen.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Absage einer Hauptperson → Springer rückt automatisch nach
|
||
- [x] Frei gewordener Springerplatz wird offen zum Übernehmen
|
||
- [x] „Übernehmen" sicher gegen gleichzeitige Klicks (atomares UPDATE)
|
||
- [x] Fall „kein Springer" korrekt behandelt
|
||
- [x] Review der Übernahme-Logik (Randfälle) — mit Claude Code statt Mistral
|
||
|
||
---
|
||
|
||
## Session 10 · Dokumente & Freigabe 💻
|
||
|
||
**Ziel:** Upload (alle), Freigabe-Warteschlange (Admin), Liste Aktuell/Archiv.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/dokumente.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Upload (PDF/Word/Bild) mit Pflicht-Titel → Status wartet_auf_freigabe
|
||
(nur Admin + Uploader sichtbar). Admin-Ansicht „Freigaben": freigeben/ablehnen
|
||
(optional Grund). Admin-Uploads direkt freigegeben. Liste geteilt in Aktuell/Archiv,
|
||
Verschieben durch Admin. Dateigrößen-Limit und Dateityp-Prüfung serverseitig.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Upload landet in wartet_auf_freigabe (nicht öffentlich sichtbar)
|
||
- [x] Admin kann freigeben/ablehnen, Admin-Uploads direkt sichtbar
|
||
- [x] Liste Aktuell + Archiv, Verschieben durch Admin
|
||
- [x] Server prüft Dateityp und -größe
|
||
|
||
---
|
||
|
||
## Session 11 · Chat (2 Kanäle, Polling) 💻
|
||
|
||
**Ziel:** Ankündigungen (nur Admin postet) + Team (alle). Nur Text. Neue Nachrichten
|
||
per `fetch`-Polling (einzige v1-JS-Ausnahme).
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/chat.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Zwei Kanäle: ankuendigungen (nur Admin darf posten, alle lesen) und
|
||
team (alle posten). Nur Text. Nachrichten chronologisch. Dazu ein kleiner
|
||
JSON-Endpunkt „neue Nachrichten seit X" und ~30 Zeilen Vanilla-JS, das im
|
||
Hintergrund pollt und neue Nachrichten anhängt. Kein Framework, kein WebSocket.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [x] Beide Kanäle funktionieren, Rechte korrekt (Ankündigungen nur Admin)
|
||
- [x] Nachrichten werden gespeichert und angezeigt (nur Text)
|
||
- [x] Polling lädt neue Nachrichten ohne Seiten-Reload
|
||
- [x] JS ist minimal und vanilla
|
||
|
||
---
|
||
|
||
## Session 12 · Profil 💻
|
||
|
||
**Ziel:** Profilbild-Upload + Platzhalter für optionale freiwillige Angaben.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/routes/profil.py
|
||
@file app/models.py
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Profil-Ansicht: Profilbild hochladen/ändern, Anzeigename, optionale
|
||
freiwillige Felder (vorerst ein, zwei Freitextfelder als Platzhalter — Struktur so,
|
||
dass Felder später leicht ergänzt werden). Nichts davon Pflicht.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [ ] Profilbild hochladbar
|
||
- [ ] Optionale Felder vorhanden, nichts Pflicht
|
||
- [ ] Struktur erlaubt späteres Ergänzen von Feldern
|
||
|
||
---
|
||
|
||
## Session 13 · In-App-Benachrichtigungen 💻
|
||
|
||
**Ziel:** Dezente in-App-Hinweise (neuer Dienst, offener Dienst, neue Nachricht).
|
||
|
||
**Prompt:**
|
||
```
|
||
@file app/models.py
|
||
@file app/templates/base.html
|
||
@file CLAUDE.md
|
||
|
||
Ansatz zuerst. Einfaches Benachrichtigungsmodell + Anzeige (Glocke in der Topbar mit
|
||
Zähler, Liste beim Antippen). Auslöser: neue Zuteilung, neuer offener Dienst, neue
|
||
Nachricht. Als gelesen markierbar. Kein Push, keine E-Mail.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [ ] Benachrichtigungen werden bei den drei Auslösern erzeugt
|
||
- [ ] Glocke zeigt ungelesene an, Liste antippbar
|
||
- [ ] Als gelesen markierbar
|
||
|
||
---
|
||
|
||
## Session 14 · Gesamt-Review 🏛
|
||
|
||
**Ziel:** Durchsicht des fertigen Stands gegen CLAUDE.md — Korrektheit, Konsistenz,
|
||
Barrierearmut, Dunkelmodus.
|
||
|
||
**Prompt:**
|
||
```
|
||
@file CLAUDE.md
|
||
@file app/models.py
|
||
@file app/routes/
|
||
|
||
Review den aktuellen Stand gegen CLAUDE.md. Korrektheitsprobleme zuerst, dann
|
||
Konsistenz und Wartbarkeit, dann Barrierearmut und Dunkelmodus-Lücken. Nichts
|
||
umschreiben — alles als nummerierte Liste mit Fundstelle.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [ ] Review-Liste erstellt
|
||
- [ ] Gefundene Korrektheitsprobleme in 💻-Folgeschritten behoben
|
||
- [ ] A11y- und Dark-Mode-Lücken geschlossen
|
||
|
||
---
|
||
|
||
## Session 15 · Deployment auf den VPS 🛠
|
||
|
||
**Ziel:** App läuft auf dem EU-VPS hinter einem produktiven Server.
|
||
|
||
**Prompt:**
|
||
```
|
||
Lies CLAUDE.md. Hilf mir beim Deployment auf einen EU-VPS: WSGI-Server (z. B.
|
||
gunicorn) hinter nginx, Umgebungsvariablen/Secrets, Persistenz für SQLite und
|
||
Uploads, einfaches Backup der DB, HTTPS. Gib mir die Schritte als Fish-Befehle,
|
||
wo Terminal nötig ist. Plan zuerst.
|
||
```
|
||
|
||
**Deliverables:**
|
||
- [ ] App läuft unter gunicorn hinter nginx
|
||
- [ ] HTTPS aktiv
|
||
- [ ] Secrets über Umgebungsvariablen, nicht im Code
|
||
- [ ] DB + Uploads persistent, einfaches DB-Backup eingerichtet
|
||
|
||
---
|
||
|
||
## Future Log (nicht in v1)
|
||
|
||
1:1-Chat · Push-/E-Mail-Benachrichtigungen · E-Mail+Code-Login (Magic Link) ·
|
||
automatische Fairness-Verteilung · weitere Rollen ·
|
||
Laptop-Spiegelung / Desktop-Fallback über LAN ·
|
||
komfortablere Zeit-Auswahl (Rad-/Scroll-Picker für Startzeit & Dauer statt Dropdown).
|
||
|
||
## Härtung (vor Produktiv-Deploy / Session 15)
|
||
|
||
- [x] **CSRF-Schutz für alle POST-Formulare.** Erledigt (nach Session 3): eigenes,
|
||
Session-gebundenes Token in `app/csrf.py`, ohne Flask-WTF. `csrf_token()` als
|
||
Jinja-Global, Prüfung aller unsicheren Methoden via `before_request`.
|
||
**Pflicht für alle künftigen POST-Formulare:** verstecktes Feld
|
||
`<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">` einbauen.
|
||
- [ ] **Brand-Tokens im `<head>` werden mit `|safe` ausgegeben** (`base.html`,
|
||
wegen der Anführungszeichen im Font). Quelle ist aktuell nur Admin/Seed, daher
|
||
für v1 vertretbar. **Sobald eine UI zum Bearbeiten der Brand-Felder entsteht:**
|
||
Werte serverseitig validieren/escapen (CSS-Injection / `</style>`-Ausbruch
|
||
verhindern) – z. B. Hex-Farben per Regex prüfen, Font auf Whitelist begrenzen.
|