Vault/VAULT/hinunher/PROJECT_DESIGN.md
2026-08-17 10:54:37 +02:00

2490 lines
67 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PROJECT_DESIGN.md — Ehrenamt-Plattform
> **Status:** Verbindliche Architektur- und Produktgrundlage für v1
> **Stand:** 13.08.2026
> **Arbeitsname:** Ehrenamt-Plattform
> **Ausgangspunkt:** bestehende CheckPoint-Ehrenamt-App, künftig als modulare Plattform für mehrere Ehrenamtsteams der Aidshilfe Köln e.V.
---
## 0. Zweck dieses Dokuments
Dieses Dokument ist die **zentrale Quelle der Wahrheit** für Produktumfang, Architektur, Rollen, Berechtigungen, Datenmodell, Module, UI-Grundsätze, Sicherheit, Deployment und Weiterentwicklung der Ehrenamt-Plattform.
Es ersetzt die bisher verteilten bzw. teilweise widersprüchlichen Architekturentscheidungen aus älteren Konzept-, `CLAUDE.md`- und `TASKS.md`-Ständen.
### Dokument-Hierarchie
1. **`PROJECT_DESIGN.md`** — verbindliche Produkt- und Architekturentscheidung.
2. **Code + Tests** — Implementierung dieser Entscheidungen.
3. **`TASKS.md`** — daraus abgeleiteter Arbeits-/Migrationsplan; darf keine eigene Architektur erfinden.
4. **`CLAUDE.md` / andere Agentenhinweise** — wenn weiterhin verwendet, nur als kompakte Arbeitsanweisung mit Verweis auf `PROJECT_DESIGN.md`, nicht als zweite Architekturquelle.
5. Alte Konzeptdateien werden als historische Referenz archiviert und nicht parallel gepflegt.
Bei einem Widerspruch zwischen Implementierung und diesem Dokument wird **nicht geraten**. Die Abweichung wird bewusst entschieden und anschließend entweder der Code oder dieses Dokument angepasst.
---
# 1. Produktziel
Die Anwendung ist eine **interne, mobile-first Web-App für Ehrenamtsteams**. Sie soll alltägliche Teamorganisation einfacher, klarer und datensparsamer machen als eine Mischung aus WhatsApp-/Signal-Gruppen, Tabellen, Umfragen und verstreuten Dateien.
Die erste Organisation ist:
- **Aidshilfe Köln e.V.**
Die Plattform soll von Anfang an mehrere Teams dieser Organisation unterstützen. Perspektivisch sind ungefähr mehrere unterschiedliche Ehrenamtsbereiche denkbar, die nicht alle dieselben Funktionen benötigen.
### Leitprinzipien
1. **Lesbarkeit und Orientierung vor Funktionsfülle.**
2. **Mobile Nutzung ist der Normalfall**, nicht die abgespeckte Desktop-Version.
3. **Technisch wenig affine Menschen müssen die App ohne Schulungsbedarf bedienen können.**
4. **Ein Team sieht nur sein Team.** Organisationsrechte sind kein Generalschlüssel für Team-Inhalte.
5. **Module sind optional pro Team.** Nicht jedes Team bekommt Funktionen, die es nicht braucht.
6. **Automatisierung nur bei eindeutigem Ergebnis.** Die App unterstützt Entscheidungen; sie entscheidet nicht unnötig für Menschen.
7. **Dashboard zeigt Zustände, Benachrichtigungen melden Ereignisse.**
8. **Erweiterbarkeit entsteht durch klare Grenzen**, nicht durch ein maximal generisches System.
9. **Deployment, Backup und Wiederherstellung werden von Anfang an mitgebaut.**
---
# 2. Scope von v1
## 2.1 Core
Der Core enthält die Plattformfunktionen, ohne die die Anwendung als Mehrteam-System nicht funktionieren kann:
- Passwordless Auth per E-Mail-Code / Direktlink
- Accounts
- Device Sessions
- Organisation
- Teams
- Team-Mitgliedschaften
- Team-Einladungen
- Organisationsadministration
- Rollen und Berechtigungen
- Brand Profiles / Corporate Design
- zentraler Datei-/Storage-Service
- In-App-Benachrichtigungen
- Team-Dashboard und Verwaltungsdashboard
- Team-Mitgliederverzeichnis / freiwillige Teamkontakte
- Profile und Avatare
- Audit Log
- Health-/Deployment-Grundlagen
## 2.2 Fachmodule
v1 enthält genau drei fachliche Module:
1. **Scheduling / Einsatzplanung**
2. **Documents / Dokumente**
3. **Chat / Teamkommunikation**
Nicht jedes Team muss alle drei Module verwenden.
---
# 3. Bewusst nicht Teil von v1
Folgende Funktionen werden nicht in v1 gezogen, nur weil sie technisch möglich wären:
- öffentliche Seiten
- Klient:innen- oder Beratungsdaten
- Bezahlfunktionen
- Kalender-Synchronisierung
- automatische Dienstverteilung
- 1:1-Direktnachrichten
- Bilder oder Dateien im Chat
- GIFs / Sticker / Meme-Previews
- Emoji-Reactions
- `@mentions`
- operative Push-Benachrichtigungen
- operative E-Mail-Benachrichtigungen
- Dokumentordner
- Dokumentversionierung
- vollwertige Dokumentbearbeitung
- Fotogalerie
- Inventar-/Materialverwaltung
- Qualifikations-/Schulungsverwaltung
- feingranulare Spezialrollen
- Passkeys / 2FA als regulärer Loginweg
- PostgreSQL in v1
- dynamisches Installieren/Entfernen von Python-Modulen zur Laufzeit
Diese Punkte dürfen später ergänzt werden, wenn ein realer Bedarf besteht und der Modulvertrag eingehalten wird.
---
# 4. Technischer Rahmen
| Bereich | Entscheidung |
|---|---|
| Backend | Python + Flask |
| App-Struktur | Flask App Factory + Blueprints |
| Rendering | Jinja2, server-rendered Multi-Page-App |
| ORM | SQLAlchemy / Flask-SQLAlchemy |
| Datenbank | SQLite für v1 |
| Schemaänderungen | Flask-Migrate / Alembic ab Beginn der neuen Architektur |
| CSS | eigenes Designsystem, kein CSS-Framework |
| JavaScript | Vanilla JS, nur wo es echten Nutzen bringt |
| Chat-Aktualisierung | kleines Fetch-Polling, keine WebSockets in v1 |
| Produktionsserver | Gunicorn |
| Container | Docker / Compose, non-root |
| Reverse Proxy | Caddy |
| Hosting | eigener VPS in der EU |
| UI-Sprache | Deutsch |
| Zielplattform | Browser-App, mobil zum Homescreen hinzufügbar |
### Architekturregel
Server-rendered HTML bleibt die Basis. JavaScript verbessert Komfort, darf aber nicht unnötig die gesamte App in eine SPA verwandeln.
---
# 5. Organisations- und Teammodell
Die zentrale Hierarchie lautet:
```text
Organization
└── Team
└── TeamMembership
└── Account
```
## 5.1 Organization
Die erste Organisation ist Aidshilfe Köln e.V.
`Organization` existiert trotzdem als eigenes Modell, damit die Anwendung strukturell nicht auf ein einzelnes Team oder eine einzelne Abteilung fest verdrahtet wird.
Konzeptionelle Felder:
```text
Organization
├── id
├── name
├── status
└── created_at
```
## 5.2 Team
Ein Team ist ein klar abgegrenzter Arbeitsbereich innerhalb der Organisation.
```text
Team
├── id
├── organization_id
├── name
├── slug
├── brand_profile_id
├── status
└── created_at
```
Status mindestens:
- `active`
- `archived`
Teams werden **archiviert statt gelöscht**, solange keine zwingende Datenschutz-/Löschanforderung etwas anderes verlangt.
Ein aktives Team muss mindestens eine Team-Verwaltung besitzen.
---
# 6. Accounts, Mitgliedschaften und Rollen
## 6.1 Account ist eine Person, kein Teammitglied
Ein Account gehört **nicht** direkt einem Team.
```text
Account
├── id
├── email
├── display_name
├── status
├── created_at
└── last_login_at
```
Die E-Mail-Adresse ist:
- eindeutig und normalisiert,
- Login-Identität,
- nicht automatisch öffentliche Kontaktadresse,
- nicht Teil des normalen Teamverzeichnisses.
Account-Status mindestens:
- `active`
- `disabled`
Accounts werden nicht leichtfertig hart gelöscht, damit historische Zuteilungen und andere Referenzen erhalten bleiben können.
## 6.2 TeamMembership
Die Beziehung einer Person zu einem Team wird separat modelliert:
```text
TeamMembership
├── id
├── team_id
├── account_id
├── role
├── status
├── joined_at
└── ended_at
```
Rollen in v1:
- `member`
- `manager`
Status mindestens:
- `active`
- `inactive`
Pro Account und Team darf nur eine aktive Mitgliedschaft existieren.
### Wichtige Semantik
`manager` ist **keine getrennte Benutzerart**. Eine Team-Verwaltung ist immer auch normales Mitglied des Teams und kann die Mitgliederoberfläche genauso benutzen wie andere Teammitglieder.
---
# 7. Organisationsadministration
Organisationale Rechte werden separat von Team-Mitgliedschaften modelliert:
```text
OrganizationMembership
├── id
├── organization_id
├── account_id
├── role
└── status
```
Für v1 reicht:
- `organization_admin`
## 7.1 Organisationsadministration darf
- Teams anlegen und archivieren,
- Brand Profiles anlegen/zuordnen,
- Module für Teams aktivieren/deaktivieren,
- Team-Verwalter:innen ernennen und entfernen,
- organisationsweite technische/strukturelle Einstellungen verwalten,
- weitere Organisationsadmins über einen gesicherten Prozess ernennen,
- sicherheitsrelevante Account-Sperren durchführen.
## 7.2 Organisationsadministration darf ausdrücklich nicht automatisch
- Teamchats lesen,
- Dokumente eines Teams sehen,
- Einsatzplanung oder Verfügbarkeiten eines Teams sehen,
- normale Teamkontakte sehen,
- die vollständigen Mitgliederlisten aller Teams durchsuchen,
- allein wegen der Org-Rolle Team-Fachmodule öffnen.
Die Organisationsverwaltung sieht **Teamstruktur und Team-Verwaltungen**, nicht automatisch den internen Teambetrieb.
Wenn eine organisationsadministrierende Person interne Teamdaten sehen soll, braucht sie zusätzlich eine normale `TeamMembership` in diesem Team.
### Sicherheitsinvariante
> `organization_admin` darf niemals eine fehlende `TeamMembership` für Team-Inhalte umgehen.
Diese Regel erhält eigene automatisierte Tests.
---
# 8. Ernennung von Team-Verwaltungen
Nur die **Organisationsverwaltung** darf Team-Verwalter:innen ernennen oder entfernen.
Team-Verwalter:innen dürfen:
- normale Mitglieder einladen,
- normale Teammitgliedschaften verwalten,
- Inhalte und Modulkonfiguration ihres Teams verwalten,
aber **keine weiteren Team-Verwaltungsrechte vergeben**.
Zusätzliche Regeln:
1. Eine Team-Verwaltung kann sich nicht selbst zur normalen Mitgliedschaft herunterstufen.
2. Ein aktives Team darf nicht ohne Team-Verwaltung zurückbleiben.
3. Beim Entfernen der letzten Team-Verwaltung muss zuerst eine neue bestimmt werden.
4. Eine neue Team-Verwaltung kann von der Organisationsverwaltung aus einer bestehenden Teammitgliedschaft befördert oder direkt als Team-Verwaltung eingeladen werden, ohne dass der Org-Admin dazu die komplette Teammitgliederliste sehen muss.
---
# 9. Technische Serveradministration ist keine App-Rolle
Zugriff auf VPS, Docker, Caddy, Backups oder Git verleiht **keine fachliche Rolle innerhalb der App**.
Technische Administration und Anwendungsberechtigungen bleiben konzeptionell getrennt.
---
# 10. Passwordless Auth
## 10.1 Grundprinzip
Es gibt in v1 **keine Benutzerpasswörter**.
Login erfolgt per E-Mail:
1. Person gibt ihre E-Mail-Adresse ein oder folgt einer Einladung.
2. App erzeugt einen zeitlich begrenzten Auth-Challenge.
3. E-Mail enthält:
- einen kurzen Einmal-Code,
- zusätzlich einen Direktlink.
4. Nach erfolgreicher Verifikation wird eine langlebige, widerrufbare Device Session angelegt.
5. Beim normalen erneuten Öffnen der Homescreen-/Browser-App ist die Person weiterhin angemeldet.
Es gibt damit keinen Passwort-Reset-Flow.
## 10.2 AuthChallenge
```text
AuthChallenge
├── id
├── email
├── code_hash
├── link_token_hash
├── created_at
├── expires_at
├── attempts
└── consumed_at
```
Code und Linktoken werden nicht im Klartext gespeichert.
Ablaufzeit, maximale Fehlversuche und Rate Limits sind Konfiguration, nicht Geschäftslogik.
## 10.3 DeviceSession
```text
DeviceSession
├── id
├── account_id
├── token_hash
├── created_at
├── last_seen_at
├── expires_at
├── revoked_at
└── device_label
```
Session-Cookies müssen mindestens:
- `Secure`,
- `HttpOnly`,
- sinnvolle `SameSite`-Einstellung
verwenden.
Gerätesitzungen sind serverseitig widerrufbar. Eine spätere Profilseite „Angemeldete Geräte“ ist mit dem Modell bereits möglich.
## 10.4 Frische Re-Authentifizierung
Besonders sensible Organisationsaktionen, z. B. Änderung von Org-Admin- oder Team-Manager-Rechten, verlangen eine **frische E-Mail-Re-Authentifizierung**, auch wenn eine langlebige Device Session besteht.
Passkeys/2FA können später ergänzt werden, sind nicht Teil von v1.
---
# 11. Einladungen
## 11.1 TeamInvitation
```text
TeamInvitation
├── id
├── team_id
├── email
├── role
├── invited_by
├── status
├── created_at
└── expires_at
```
Status mindestens:
- `pending`
- `accepted`
- `revoked`
- `expired`
Normale Team-Verwaltungen dürfen nur `role = member` einladen.
### Ablauf
```text
Team-Verwaltung trägt E-Mail ein
→ Einladung wird erstellt
→ Empfänger:in verifiziert die E-Mail
→ vorhandenen Account finden oder neuen Account anlegen
→ TeamMembership erstellen
→ bei neuem Account: Onboarding für Anzeigename
```
## 11.2 OrganizationAdminInvitation
Organisationsadministration erhält einen getrennten, sicherheitssensiblen Einladungsflow:
```text
OrganizationAdminInvitation
├── id
├── organization_id
├── email
├── invited_by
├── status
├── created_at
└── expires_at
```
Der erste Organisationsadmin wird über einen CLI-Bootstrap angelegt/eingeladen. Es gibt keine hardcodierte Admin-E-Mail.
Nach vorhandenem Org-Admin soll der Bootstrap standardmäßig nicht einfach weitere globale Administratoren erzeugen.
Operational wird empfohlen, mindestens zwei Org-Admins zu haben, damit die Organisation nicht von einer einzelnen Person abhängig ist.
---
# 12. Profil und Teamkontakte
## 12.1 Anzeigename
Die Login-E-Mail ist nicht der öffentliche Name.
Beim ersten erfolgreichen Login wird, falls noch kein Anzeigename existiert, gefragt:
> Wie sollen wir dich nennen?
`display_name`:
- wird in Chat, Einsatzplanung und Teamverzeichnis verwendet,
- darf von der Person im Profil geändert werden,
- muss nicht organisationsweit eindeutig sein.
Team-spezifische Anzeigenamen werden in v1 nicht gebaut.
## 12.2 Profilbild und Avatare
Ein Account kann optional ein eigenes Profilbild verwenden.
Alternativ stehen drei rein visuelle Standardvarianten zur Auswahl:
- Avatar A
- Avatar B
- Avatar C
Die Avatare werden **nicht geschlechtlich beschriftet**.
Wenn nichts gewählt wurde, kann Avatar A als neutraler technischer Default verwendet werden.
Das Profilbild gilt accountweit und wird z. B. angezeigt in:
- Teamverzeichnis,
- Chat,
- Einsatzansichten.
Uploadbilder werden serverseitig auf geeignete Größen/Thumbnails verarbeitet. Erlaubte Profilbildformate orientieren sich an den Bildformaten des Dokumenten-Uploads, einschließlich moderner Smartphone-Formate.
## 12.3 Freiwilliges TeamMemberProfile
Kontaktdaten sind **teambezogen und freiwillig**:
```text
TeamMemberProfile
├── team_membership_id
├── phone_number optional
├── contact_by_call optional
├── contact_by_sms optional
├── contact_by_signal optional
├── contact_note optional
└── share_contact_details
```
Beispiel:
```text
Andrea
[Profilbild]
Team-Verwaltung
📞 0176 ...
Erreichbar per Anruf / Signal
„Bei kurzfristigen Dienstausfällen gerne anrufen.“
```
### Datenschutzregeln
- `share_contact_details` ist standardmäßig aus.
- Freigegebene Kontaktdaten sehen nur aktive Mitglieder desselben Teams.
- Eine Person kann in einem Team teilen und in einem anderen nicht.
- Nicht freigegebene freiwillige Kontaktdaten werden auch Team-Verwaltungen nicht „heimlich“ angezeigt.
- Beim Ende der Teammitgliedschaft endet der Zugriff für andere Teammitglieder.
- Es gibt in v1 keinen organisationsweiten Kontaktdatenexport.
### Login-E-Mail
Die Login-E-Mail wird **nicht** normalen Teammitgliedern angezeigt.
Team-Verwaltungen dürfen sie für die Mitgliederadministration ihres Teams sehen.
Organisationsadmins sehen nicht automatisch die Login-E-Mails aller normalen Teammitglieder; ihr struktureller Zugriff bleibt auf ihre Aufgaben begrenzt.
Eine spätere freiwillige `contact_email` wäre ein separates Feld und niemals automatisch `Account.email`.
---
# 13. Teammitgliederverzeichnis
Normale Teammitglieder sehen im eigenen Team:
- Anzeigename,
- Profilbild / Avatar,
- Kennzeichnung „Team-Verwaltung“, falls zutreffend,
- freiwillig freigegebene Teamkontaktdaten.
Sie sehen nicht:
- Login-E-Mail,
- Device Sessions,
- Auth-Daten,
- interne Account-/Einladungsmetadaten.
Team-Verwaltungen sehen zusätzlich für die Mitgliederadministration:
- Login-E-Mail,
- Mitgliedsstatus,
- Einladungsstatus,
- Beitrittsdatum.
---
# 14. Brand Profiles / Corporate Design
Branding ist Core-Infrastruktur, kein Fachmodul.
```text
BrandProfile
├── id
├── organization_id
├── name
├── logo
├── primary_color
├── primary_strong
├── accent_color
├── accent_strong
├── heading_font
└── body_font
```
Die genaue Tokenmenge darf sich bei der Implementierung verfeinern. Entscheidend ist:
- Brand Profiles speichern **Design-Eingaben**, kein beliebiges CSS.
- Ein Brand Profile gehört der Organisation.
- Mehrere Teams dürfen dasselbe Brand Profile verwenden.
- In der Startkonfiguration sind mehrere unterschiedliche Profile vorgesehen, z. B. Aidshilfe-Standard, Café Bach, CheckPoint/Vor-Ort.
- Layout und Komponenten bleiben gleich; Farben, Schriften und Logo verändern die Identität.
- Light-/Dark-Darstellung wird über semantische CSS-Tokens abgeleitet.
Der bestehende Ansatz mit getrennten Brand- und semantischen Tokens wird beibehalten, aber auf das neue BrandProfile-Modell ausgerichtet.
## 14.1 Initiales Brand Profile „Vor-Ort-Arbeit“
Für die **Vor-Ort-Arbeit** wird die visuelle Basis aus dem vorhandenen CheckPoint-CSS übernommen. Es werden nur die grundlegenden Markenfarben und Schriften als Ausgangsmaterial verwendet; die Ehrenamt-Plattform übernimmt nicht das gesamte Layout der Quell-App.
### Typografie
| Einsatz | Schrift | Nutzung |
|---|---|---|
| Display / große Überschriften | **Big Shoulders Display** | Seitentitel, große Kennzahlen, markante Abschnittsüberschriften |
| Body / UI | **Noto Sans** | Fließtext, Navigation, Formulare, Buttons, Tabellen, Hilfetexte |
Grundregel: **Big Shoulders Display sparsam und gezielt**, Noto Sans trägt den Großteil der Oberfläche. Das hält die App charakteristisch, ohne sie plakativ oder unruhig wirken zu lassen.
### Basisfarben
| Token / Rolle | Basiswert | Zweck |
|---|---:|---|
| Ink / Navy | `#07182E` | Haupttext, sehr dunkle Flächen, Navigation |
| Deep | `#061126` | tiefster Hintergrund / Sidebar-Basis |
| Teal strong | `#005F61` | dunkle Markenfläche, Sidebar / Header |
| Teal | `#00858A` | primäre Markenfarbe und aktive Navigation |
| Aqua | `#52E0CF` | freundlicher Akzent, Fokus, leichte Highlights |
| Blue | `#2596BE` | sekundärer Informationsakzent |
| Coral | `#F04A67` | wichtig / Aktion; Rot nur gezielt einsetzen |
| Yellow | `#F6E74A` | sparsame Hervorhebung / Hinweis |
| Mist | `#F4FAFC` | ruhiger Seitenhintergrund |
| Paper | `#FFFFFF` | Karten und Arbeitsflächen |
Für helle Arbeitsflächen dürfen kontrastoptimierte semantische Ableitungen verwendet werden, z. B. dunkleres Teal oder Blue für Textlinks und Buttons. Die oben genannten Werte bleiben die **Brand-Basis**, nicht zwangsläufig der direkte CSS-Wert jedes Elements.
### Logo-Nutzung
Das Vor-Ort-Logo darf und soll **sichtbar wiederkehren**, insbesondere:
- im mobilen Header bzw. App-Kopf,
- oben in der Desktop-Sidebar,
- in Team-/Brand-Kontexten,
- auf Login-/Einladungsansichten, sofern das Team bereits feststeht.
Das Logo ist ein Identitätsanker, ersetzt aber keine Überschrift und soll nicht als dekoratives Hintergrundmotiv vervielfacht werden.
## 14.2 Visuelle Referenz — Quick Mockup v1
Die abgestimmte visuelle Richtung ist bewusst **clean, ruhig und informationszentriert**. Bilder und motivierende Illustrationen sind nicht Teil des normalen Dashboard-Rasters. Die Oberfläche soll freundlich wirken, aber primär beim Arbeiten helfen.
Verbindliche Gestaltungsrichtung:
- helle Arbeitsfläche mit viel Weißraum,
- dunkle teal/navy Navigation als klarer Rahmen,
- weiße Cards mit leichter Kontur bzw. sehr zurückhaltendem Schatten,
- wenige, klare Statusfarben,
- Logo gut sichtbar,
- Icons funktional statt dekorativ,
- keine großflächigen Motivbilder im Dashboard,
- keine regelmäßigen Motivationssprüche oder Gamification-Elemente,
- große Display-Schrift nur für Hierarchie und Orientierung, nicht für jeden Kartentitel.
Die drei Dashboard-Profile teilen dieselbe visuelle Sprache, unterscheiden sich aber in Informationsdichte und Aufgabenfokus:
1. **Mitglieder-Dashboard** — persönlich, mobil-first, Fokus auf nächste Einsätze, offene Rückmeldungen, Verfügbarkeit und ungelesenen Teamchat.
2. **Teamverwaltungs-Dashboard** — sachlicher und dichter, Fokus auf offene Aufgaben, Rückmeldungen, Freigaben, Einladungen und unbesetzte Positionen.
3. **Organisationsverwaltungs-Dashboard** — am nüchternsten; Fokus ausschließlich auf Struktur, Teams, Module, Brand Profiles, Einladungen und organisationsweite Verwaltungsaufgaben. Keine teaminternen Fachinhalte.
Quick-Mockup-Referenz im Projektordner:
```text
vor_ort_arbeit_dashboard_mockups.png
```
Diese Grafik ist eine **visuelle Referenz, kein Pixel-Perfect-Vertrag**. Bei der Implementierung haben Barrierearmut, Responsive-Verhalten, reale Inhalte und das zentrale Komponentensystem Vorrang. Die Grundwirkung — Farben, Typografie, Informationsruhe, Logo-Präsenz und klare Dashboard-Hierarchie — soll jedoch erhalten bleiben.
---
# 15. Modularchitektur
## 15.1 Abhängigkeitsregel
```text
Fachmodul → Core erlaubt
Core → Fachmodul verboten
```
Der Core darf nicht wissen, wie ein Einsatz, Dokument oder Chat fachlich funktioniert.
## 15.2 Verzeichnisstruktur
Zielstruktur, sinngemäß:
```text
app/
├── core/
│ ├── auth/
│ ├── accounts/
│ ├── organizations/
│ ├── teams/
│ ├── memberships/
│ ├── invitations/
│ ├── permissions/
│ ├── branding/
│ ├── storage/
│ ├── notifications/
│ ├── dashboard/
│ ├── profiles/
│ └── audit/
└── modules/
├── scheduling/
├── documents/
└── chat/
```
Ein größeres Modul darf intern besitzen:
```text
module/
├── __init__.py
├── models.py
├── routes.py
├── services.py
├── permissions.py
├── forms.py
├── templates/
└── tests/
```
Nicht jedes Modul braucht jede Datei; die Verantwortlichkeiten bleiben aber getrennt.
## 15.3 Routes sind dünn
Eine Route erledigt hauptsächlich:
```text
Request
→ Team-Kontext auflösen
→ Berechtigung prüfen
→ Service aufrufen
→ Response / Redirect / Template
```
Fachlogik gehört in Services, nicht in große Routendateien.
## 15.4 Permission Helpers statt Rollenabfragen überall
Nicht überall:
```python
if membership.role == "manager":
```
sondern fachliche Funktionen wie:
```text
can_manage_schedule()
can_review_document()
can_manage_channel()
```
Dadurch können Rollen später feiner werden, ohne die gesamte Codebasis umzuschreiben.
---
# 16. Modulregistrierung
Die Existenz eines Moduls wird **im Code** definiert, nicht in einer Datenbanktabelle.
Beispiel:
```text
ModuleDefinition
├── key
├── name
├── description
├── version
├── navigation
├── member_dashboard_provider
├── manager_dashboard_provider
└── dependencies
```
Stabile Modulkeys für v1:
- `scheduling`
- `documents`
- `chat`
Alle installierten Module/Blueprints werden beim App-Start registriert.
### TeamModule
Die Datenbank entscheidet nur, ob ein vorhandenes Modul für ein Team aktiv ist:
```text
TeamModule
├── team_id
├── module_key
└── enabled
```
Die Kombination `team_id + module_key` ist eindeutig.
### Verantwortlichkeiten
**Organisationsverwaltung:** aktiviert/deaktiviert Module für Teams.
**Team-Verwaltung:** konfiguriert und nutzt bereits aktivierte Module.
Ein deaktiviertes Modul ist nicht nur aus der Navigation verschwunden. Direkte Routes müssen ebenfalls Zugriff verweigern.
Prüfreihenfolge:
```text
aktive TeamMembership?
→ Modul für Team aktiviert?
→ fachliche Berechtigung vorhanden?
→ Zugriff
```
## 16.1 Modulabhängigkeiten
Abhängigkeiten müssen explizit deklariert sein. v1 soll möglichst ohne Fachmodul-zu-Fachmodul-Abhängigkeiten auskommen:
```text
scheduling → core
documents → core
chat → core
```
Module dürfen nicht ungeordnet gegenseitig interne Modelle abfragen. Falls Modul A Funktionalität von Modul B benötigt, geschieht das über eine definierte Service-Schnittstelle oder die gemeinsame Funktion gehört in den Core.
## 16.2 Moduleinstellungen
Komplexere Moduleinstellungen gehören dem jeweiligen Modul, z. B. `SchedulingSettings`.
Kein vorschnelles generisches JSON-Einstellungsmonster.
---
# 17. Team-Datenbesitz und Scoping
Alle teaminternen Fachobjekte tragen explizit `team_id`, auch wenn die Teamzugehörigkeit theoretisch über eine Beziehung ableitbar wäre.
Beispiele:
```text
Shift.team_id
Document.team_id
Channel.team_id
Message.team_id
```
Grundprüfung:
```text
requested_object.team_id == current_team.id
```
und aktive TeamMembership.
Diese Redundanz ist bewusst: Teamgrenzen sollen in Code, Queries und Tests klar sichtbar bleiben.
---
# 18. Dashboard- und Navigationsarchitektur
## 18.1 Eindeutiger Team-Kontext
Die App arbeitet bei Teamfunktionen immer in einem klaren Teamkontext, vorzugsweise auch sichtbar in der URL:
```text
/team/<slug>/
/team/<slug>/scheduling/
/team/<slug>/documents/
/team/<slug>/chat/
```
Die Session darf das zuletzt verwendete Team merken, aber **keine Berechtigung daraus ableiten**.
### Loginverhalten
- nur ein aktives Team → direkt dort starten,
- mehrere Teams + bekannte letzte Auswahl → dort starten,
- erster Login mit mehreren Teams → einmalige Teamauswahl.
## 18.2 Team-Startseite
Die normale Startseite zeigt persönliche/relevante Informationen des aktuellen Teams.
Beispiel:
```text
Guten Morgen, Patsy
Dringend
Heute wird noch ein Springer gesucht.
Deine nächsten Einsätze
...
Verfügbarkeit
4 Termine noch offen
Teamchat
3 neue Nachrichten
```
Team-Verwalter:innen landen nicht automatisch in einem Backend, sondern benutzen dieselbe Mitgliederstartseite.
## 18.3 Team-Verwaltungsbereich
Zusätzlich existiert:
```text
/team/<slug>/manage/
```
Nur Team-Verwaltungen dürfen ihn öffnen.
Dort erscheinen Aufgaben/Zustände wie:
- unterbesetzte Einsätze,
- fehlende Verfügbarkeiten,
- Dokumentfreigaben,
- offene Einladungen.
## 18.4 Organisationsverwaltung
Separater Bereich:
```text
/org/<slug>/manage/
```
Dieser zeigt ausschließlich strukturelle Organisationsinformationen, z. B.:
- Teams,
- Team-Verwaltungen,
- Brand Profiles,
- Modulaktivierungen,
- Org-Admin-Verwaltung.
Keine Teamchat-, Dokument- oder Scheduling-Inhalte.
---
# 19. Dashboard Extension Contract
Der Core kennt keine Fachmodule, muss deren relevante Hinweise aber darstellen können.
Deshalb registrieren Module strukturierte Dashboard-Beiträge.
```text
DashboardItem
├── title
├── text
├── category
├── count optional
├── action_label optional
├── action_endpoint optional
└── module_key
```
Erlaubte Kategorien:
- `URGENT`
- `ACTION`
- `UPCOMING`
- `INFO`
Sortierung zentral:
1. Dringend
2. Handlungsbedarf
3. Bevorstehend
4. Information
Das Modul liefert Daten, **nicht eigenes Dashboard-HTML oder eigene Farben**. Der Core rendert standardisierte Komponenten und wendet das Brand Profile an.
Es gibt in v1 keine Tabellen wie `DashboardWidget` oder personalisierbare Dashboard-Layouts. Karten werden aus dem aktuellen Systemzustand berechnet.
---
# 20. Core Notifications
Notifications sind Infrastruktur, kein aktivierbares Fachmodul.
Fachmodule rufen einen zentralen Notification-Service auf.
Konzeptionelles Modell:
```text
Notification
├── id
├── account_id
├── team_id optional
├── type
├── priority
├── title
├── text
├── action_endpoint optional
├── created_at
└── read_at optional
```
Prioritäten:
- `normal`
- `important`
- `urgent`
### Grundregel
> Dashboard zeigt Zustände. Benachrichtigungen melden Ereignisse.
Beispiel:
- Benachrichtigung: „Andrea hat ihren Einsatz abgesagt.“
- Dashboard: „1 Position ist aktuell unbesetzt.“
Wenn der Platz besetzt ist, verschwindet der Dashboardzustand; die historische Benachrichtigung darf bestehen bleiben.
v1 nutzt operative Benachrichtigungen **nur in der App**. E-Mail wird in v1 für Auth und Einladungen verwendet, nicht für normalen Teamverkehr.
---
# 21. Modul 1: Scheduling / Einsatzplanung
## 21.1 Ziel
Scheduling bleibt das umfangreichste Fachmodul. Es soll unterschiedliche Teammodelle unterstützen, ohne die Bedienung komplizierter zu machen.
Die bisherige feste Regel „2 Haupt + 1 Springer“ wird **nicht** Plattformregel. Sie wird eine Team-/Vorlagenkonfiguration.
## 21.2 Workflow
```text
Vorbereitung
→ Verfügbarkeit offen
→ Plan wird erstellt
→ Veröffentlicht
```
Statuswerte:
- `draft`
- `availability_open`
- `scheduling`
- `published`
Ein vergangener Zeitraum kann später archiviert werden, ohne dafür einen komplizierten zusätzlichen Arbeitsstatus zu benötigen.
### Semantik
**Vorbereitung:** Verwaltung legt Zeitraum, Einsätze und Positionen an.
**Verfügbarkeit offen:** Mitglieder geben Ja/Nein pro Einsatz ab und dürfen ändern.
**Plan wird erstellt:** Verfügbarkeit ist eingefroren; Verwaltung teilt ein.
**Veröffentlicht:** Plan ist für Mitglieder verbindlich sichtbar; Änderungen laufen über Absage/Nachbesetzung.
## 21.3 PlanningPeriod
```text
PlanningPeriod
├── id
├── team_id
├── name
├── start_date
├── end_date
├── status
├── availability_deadline optional
├── created_at
└── archived_at optional
```
Keine erzwungene Zweimonatsdauer.
## 21.4 PositionDefinition
```text
PositionDefinition
├── id
├── team_id
├── name
├── description optional
├── sort_order
├── active
└── replacement_for_id optional
```
Beispiele:
```text
Vor-Ort:
Haupt
Springer → Ersatz für Haupt
Café Bach:
Theke
Springer → Ersatz für Theke
```
Das Ersatzmodell bleibt absichtlich einfach. Keine komplexen Vertretungsketten in v1.
## 21.5 ShiftTemplate
```text
ShiftTemplate
├── id
├── team_id
├── name
├── default_title
├── default_start_time
├── default_end_time
├── default_location
├── active
└── Template-Positionen
```
```text
ShiftTemplatePosition
├── shift_template_id
├── position_definition_id
├── required_count
└── sort_order
```
Vorlagen sind Komfort, keine Pflicht. Ein konkreter Einsatz darf nach Erzeugung verändert werden.
## 21.6 Shift
```text
Shift
├── id
├── team_id
├── planning_period_id
├── title
├── date
├── start_time
├── end_time
├── location
├── description optional
└── created_at
```
## 21.7 ShiftPosition
```text
ShiftPosition
├── id
├── team_id
├── shift_id
├── position_definition_id
├── required_count
└── sort_order
```
Beispiel:
```text
Haupt required_count = 2
Springer required_count = 1
```
Ein offener Platz wird aus `required_count - aktive Assignments` berechnet. Es braucht keinen künstlichen „offenen Assignment-Datensatz ohne Person“.
## 21.8 Availability
Mitglieder melden nur **Ja oder Nein zum gesamten Einsatz**, keine Positionspräferenzen.
```text
Availability
├── id
├── team_id
├── shift_id
├── team_membership_id
├── status
└── updated_at
```
Status:
- `available`
- `unavailable`
Kein Datensatz = noch nicht beantwortet.
Beim Wechsel des Planungszeitraums zu `scheduling` werden Verfügbarkeiten eingefroren.
## 21.9 Assignment
```text
Assignment
├── id
├── team_id
├── shift_id
├── shift_position_id
├── team_membership_id
├── status
├── assigned_at
└── assigned_by optional/system
```
Status mindestens:
- `assigned`
- `cancelled`
- `promoted`
Die Erstverteilung bleibt **manuell durch Team-Verwaltung**.
## 21.10 Fairness-Hilfe
Bei der Auswahl einer verfügbaren Person zeigt die App mindestens:
- Anzahl bisheriger Einsätze,
- Datum des letzten Einsatzes.
Keine automatische Fairness-Verteilung und keine Gewichtungsformeln in v1.
## 21.11 Unterbesetzung
Unterbesetzung wird aus Daten berechnet:
```text
aktive Assignments < benötigte Positionsanzahl
```
Kein manuell zu pflegender Wahrheitswert, wenn er zuverlässig ableitbar ist.
---
# 22. Scheduling: Ausfall und Nachbesetzung
## 22.1 Grundregel
Nach Veröffentlichung wird eine Zuteilung nicht durch nachträgliches Ändern der ursprünglichen Availability korrigiert.
Wenn jemand ursprünglich `available` war und später absagt:
```text
Availability = available
Assignment = cancelled
```
Damit bleibt die historische Information korrekt.
## 22.2 Automatisches Nachrücken
Wenn eine Zielposition ausfällt und eine passende Ersatzposition definiert und besetzt ist:
```text
Haupt fällt aus
→ Springer rückt auf Haupt
→ Springerplatz wird offen
```
Automatisches Nachrücken findet nur statt, wenn das Ergebnis eindeutig ist. Bei mehreren möglichen Ersatzpersonen oder anderen Mehrdeutigkeiten entscheidet die Team-Verwaltung.
## 22.3 Springer fällt aus
Es wird lediglich die Springerposition offen. Niemand wird künstlich umverteilt.
## 22.4 Kein Springer vorhanden
Die ausgefallene Zielposition selbst wird offen.
## 22.5 Mehrere Ausfälle
Automatisierung endet nach den eindeutig möglichen Nachrückschritten. Die App versucht nicht, selbstständig eine komplette neue Dienstplanung zu errechnen.
## 22.6 Selbstübernahme offener Positionen
Pro Team kann konfiguriert werden, ob offene Positionen nach Veröffentlichung selbst übernommen werden dürfen.
Eine Person darf übernehmen, wenn:
- aktive TeamMembership,
- Scheduling aktiviert,
- Einsatz veröffentlicht,
- Position wirklich offen,
- Person nicht bereits in diesem Einsatz eingeteilt,
- Platz nicht im selben Moment bereits vergeben wurde.
Der letzte Punkt muss **transaktional / durch DB-Constraint bzw. atomare Logik** abgesichert werden, nicht nur durch den Buttonzustand im Browser.
Eine ursprünglich als `unavailable` gemeldete Person darf später trotzdem einen offenen Dienst übernehmen, falls sich ihre Situation geändert hat.
Auch ein übernommener Ersatzdienst kann später wieder abgesagt werden.
## 22.7 Dringlichkeit
Eine offene Position wird in v1 **24 Stunden vor Einsatzbeginn** als `urgent` behandelt.
Das verändert nicht die Fachlogik, nur Priorität und Darstellung.
Beispiel:
> Dringend: Heute 19:00 Springer gesucht.
Der 24-Stunden-Wert ist in v1 ein fester Produktstandard und kann später teamkonfigurierbar werden.
---
# 23. Scheduling: Benachrichtigungen
### Mitglieder
| Ereignis | Empfänger | Priorität |
|---|---|---|
| Verfügbarkeit geöffnet | aktive Teammitglieder | normal |
| Rückmeldung unvollständig, ca. 48 h vor Frist | nur betroffene Mitglieder | normal |
| Plan veröffentlicht / eigene neue Einsätze | betroffene Mitglieder, sinnvoll gebündelt | wichtig |
| eigene Einteilung geändert | betroffene Person | wichtig |
| automatisches Nachrücken | nachrückende Person | wichtig |
| offener Dienst | berechtigte Teammitglieder | normal |
| offener Dienst <24 h | berechtigte Teammitglieder | dringend |
| Einsatzzeit/-ort geändert | eingeteilte Personen | wichtig |
| Einsatz abgesagt | eingeteilte Personen | wichtig |
### Team-Verwaltung
| Ereignis | Priorität |
|---|---|
| Mitglied sagt Zuteilung ab | wichtig |
| Person übernimmt offenen Platz | normal |
| kurzfristige Unterbesetzung | dringend |
Dauerhafte Zustände wie 6 Rückmeldungen fehlen gehören auf das Dashboard, nicht als dauernder Notification-Spam in die Glocke.
Benachrichtigungen sollen bei Veröffentlichung sinnvoll dedupliziert/gebündelt werden, z. B. Der Dienstplan ist da du hast 3 neue Einsätze statt unmittelbar mehrerer redundanter Meldungen.
---
# 24. Scheduling: Verlauf
Das Scheduling-Modul soll fachlich nachvollziehbar machen, wie eine aktuelle Besetzung entstanden ist, ohne den globalen Security-Audit mit jedem Fachereignis zu überladen.
Ein kleiner Scheduling-Verlauf darf Ereignisse wie diese dokumentieren:
```text
Andrea hat abgesagt.
Chris ist automatisch von Springer auf Haupt nachgerückt.
Springerposition wurde geöffnet.
Marco hat die offene Springerposition übernommen.
```
Konkrete Implementierung kann über ein schlankes `SchedulingEvent`-Modell erfolgen.
---
# 25. Modul 2: Documents / Dokumente
## 25.1 Ziel
Das Dokumentenmodul ist **keine Nextcloud** und kein komplexes DMS.
Die sichtbare Struktur bleibt:
```text
Aktuell
Archiv
```
## 25.2 Document
```text
Document
├── id
├── team_id
├── uploader_membership_id
├── title
├── description optional
├── original_filename
├── storage_key
├── mime_type
├── file_size
├── approval_status
├── library_status
├── reviewed_by_membership_id optional
├── rejection_reason optional
├── created_at
├── reviewed_at optional
└── archived_at optional
```
## 25.3 Freigabestatus
```text
pending
approved
rejected
```
## 25.4 Bibliotheksstatus
```text
current
archived
```
Freigabe und Archivierung sind getrennte Dimensionen:
- Freigabe = darf das Team die Datei sehen?
- Archiv = ist die Datei noch aktuell?
## 25.5 Uploadrechte
Normale Mitglieder dürfen hochladen. Diese Uploads starten mit `pending`.
Sichtbarkeit im Pending-Status:
- Uploader:in,
- Team-Verwaltung.
Team-Verwaltungsuploads sind direkt freigegeben.
Normale Mitglieder dürfen eigene noch wartende Uploads zurückziehen.
## 25.6 Dateiformate
Normale Teammitglieder dürfen in v1 mindestens hochladen:
- PDF
- Word (`.doc`, `.docx`)
- JPG / JPEG
- PNG
- HEIC / HEIF
HEIC/HEIF wird aufgenommen, damit Smartphone-Fotos insbesondere aus verbreiteten iPhone-Konfigurationen praktisch nutzbar bleiben.
### HTML-Sonderfall
HTML darf **ausschließlich durch Team-Verwaltungen** hochgeladen/bereitgestellt werden.
Ziel: kleine statische, gut lesbare Anleitungen, z. B. für Café-Abläufe.
HTML wird nicht als vertrauenswürdige Mini-Web-App behandelt. Darstellung erfolgt isoliert und restriktiv:
- kein JavaScript,
- keine Formulare,
- keine frei laufenden aktiven Inhalte,
- keine unkontrollierten externen Embeds,
- sandboxed iframe / restriktive CSP bzw. äquivalente Schutzmechanismen.
HTML ist Top-down-Inhalt; normale Mitglieder können es nicht einreichen.
## 25.7 Dateiprüfung
Serverseitig prüfen:
- erlaubte Erweiterung,
- plausibler MIME-/Dateityp,
- Dateigrößenlimit,
- Dateiname wird nie als Speicherpfad verwendet.
Maximale Uploadgröße ist konfigurierbar, nicht im Fachcode fest verdrahtet.
## 25.8 Storage
Dateien werden nicht als SQLite-BLOB gespeichert.
Beispielstruktur im persistenten App-Volume:
```text
/app/instance/
├── app.db
└── uploads/
├── documents/
└── profiles/
```
Physische Dateinamen/Storage Keys sind opaque (z. B. UUIDs). `original_filename` ist nur Metadatum.
Caddy darf private Uploads nicht einfach öffentlich als statische Dateien ausliefern.
Download/Preview läuft durch kontrollierten Zugriff:
```text
Login
→ TeamMembership
→ team_id prüfen
→ Dokument-/Statusberechtigung
→ Datei ausliefern
```
## 25.9 Bildvorschau
Bilder erhalten serverseitig erzeugte Thumbnails/Vorschauen in einem Browser-kompatiblen Format. Das Original bleibt erhalten.
So können auch HEIC-/HEIF-Ursprungsbilder zuverlässig in der App als Vorschau dargestellt werden.
## 25.10 Freigabe
Team-Verwaltung kann:
- freigeben,
- ablehnen,
- optional Ablehnungsgrund nennen,
- archivieren,
- wiederherstellen,
- dauerhaft löschen.
Dauerhaftes Löschen entfernt Datei und Domain-Datensatz. Im Security-/Admin-Audit kann eine minimale Löschaktion verbleiben, ohne unnötig sensible Dateinamen fortzuschreiben.
## 25.11 Foto-Hinweis
Beim Bild-Upload wird freundlich darauf hingewiesen, nur Fotos hochzuladen, für deren teaminterne Nutzung keine ungeklärten Rechte/Einwilligungen Dritter entgegenstehen.
Keine Checkbox-Orgie; der Freigabeprozess ist die zweite Kontrollstufe.
## 25.12 Benachrichtigungen
- Mitglied lädt hoch Team-Verwaltung erhält Freigabe-Ereignis.
- Dokument freigegeben Uploader:in erhält normale/wichtige Info.
- Dokument abgelehnt Uploader:in erhält Info inkl. Grund, falls vorhanden.
- Nicht jedes neue freigegebene Dokument erzeugt eine Nachricht an das gesamte Team.
Wartende Freigaben erscheinen auf dem Verwaltungsdashboard.
---
# 26. Zukünftige Fotogalerie
Eine echte Galerie wird **nicht** in das Dokumentenmodul hineingebaut.
Sie kann später als eigenes optionales Modul entstehen, z. B.:
```text
gallery/
├── Fotoübersicht
├── Alben / Veranstaltungen
├── große Bildansicht
└── Downloadoptionen
```
Sie darf denselben Core-Storage-Service nutzen. Ob Galerie und Dokumente dieselben Domain-Datensätze verwenden, wird erst entschieden, wenn das Galerie-Modul wirklich gebaut wird.
---
# 27. Modul 3: Chat / Teamkommunikation
## 27.1 Ziel
Der Chat ist **Arbeitskommunikation, kein WhatsApp-Ersatz für Unterhaltung**.
Er soll Teamabsprachen erleichtern, ohne zum Meme-, GIF- oder Dateiaustauschkanal zu werden.
## 27.2 Channel
```text
Channel
├── id
├── team_id
├── name
├── description
├── type
├── sort_order
├── status
└── created_at
```
Typen:
- `announcement`
- `discussion`
Beim Aktivieren des Chat-Moduls entstehen standardmäßig:
- **Ankündigungen**
- **Team**
Team-Verwaltungen dürfen weitere **teamweite Discussion-Channels** anlegen und archivieren.
Keine privaten Untergruppen in v1.
Channel-Status:
- `active`
- `archived`
## 27.3 Ankündigungen
- Team-Verwaltung schreibt.
- Alle aktiven Teammitglieder lesen.
- Mitglieder antworten dort nicht.
- Neue Ankündigung erzeugt eine In-App-Benachrichtigung.
## 27.4 Teamchat / Discussion
Alle aktiven Teammitglieder dürfen:
- lesen,
- schreiben,
- auf einzelne Nachrichten antworten,
- eigene Nachrichten bearbeiten,
- eigene Nachrichten löschen.
Team-Verwaltungen dürfen zusätzlich moderieren/entfernen.
## 27.5 Message
```text
Message
├── id
├── team_id
├── channel_id
├── author_membership_id
├── text
├── reply_to_message_id optional
├── created_at
├── edited_at optional
└── deleted_at optional
```
Gelöschte Nachrichten zeigen in der UI einen neutralen Platzhalter. Bei Moderation darf sichtbar sein, dass die Team-Verwaltung entfernt hat.
Es gibt kein öffentliches Edit-Versionsarchiv in v1.
## 27.6 Keine Medien/Dateien
Im Chat sind ausdrücklich nicht erlaubt:
- Bilder,
- Dokumente,
- Videos,
- Audio,
- GIFs,
- Sticker,
- andere Dateianhänge.
Fotos gehören in das Dokumenten-/spätere Galerie-System, nicht in den Chat.
## 27.7 Links
Externe Links werden bewusst eingeschränkt:
- nur `https://`,
- maximal ein externer Link pro normaler Chatnachricht,
- keine reinen Link-only-Nachrichten; kurzer Kontexttext ist erforderlich,
- keine Link-Vorschau,
- kein Thumbnail,
- kein YouTube-/Social-Embed,
- bekannte URL-Shortener werden blockiert,
- externe Links werden als extern kenntlich gemacht.
Interne Links innerhalb der eigenen App dürfen normal verwendet werden.
Später kann bei echtem Bedarf eine Domain-Allowlist ergänzt werden.
## 27.8 Replies
Ein einfaches `reply_to_message_id` ist Teil von v1.
Threads oder verschachtelte Diskussionen werden nicht gebaut.
Eine direkte Antwort auf die eigene Nachricht darf eine normale In-App-Benachrichtigung auslösen.
## 27.9 Ungelesen / Zustellung / Gelesen
```text
ChannelReadState
├── team_membership_id
├── channel_id
├── last_delivered_message_id
└── last_read_message_id
```
Anzeige für eigene Nachrichten:
- `✓` grau = erfolgreich auf dem Server gespeichert,
- `✓✓` grau = von allen relevanten aktuellen Empfänger:innen technisch abgerufen,
- `✓✓` in der Brand-Akzentfarbe = von allen relevanten Empfänger:innen gelesen.
Mitglieder, die erst später einem Team beitreten, zählen nicht rückwirkend für alte Nachrichten.
Optionaler Detailhinweis darf z. B. Gelesen von 8/10 zeigen, aber keine detaillierte Überwachungsliste mit individuellen Lesezeitpunkten.
## 27.10 Polling
Neue Nachrichten werden mit kleinem Vanilla-JS-`fetch`-Polling nachgeladen.
- kein WebSocket-System in v1,
- Polling-Intervall konfigurierbar,
- Chat bleibt grundsätzlich auch durch normales Neuladen funktional.
## 27.11 Chat-Benachrichtigungen
Normale neue Chatnachrichten erzeugen **keine Glockenbenachrichtigung pro Nachricht**.
Stattdessen:
- Channel zeigt Ungelesen-Zähler,
- Ankündigung Notification,
- direkte Antwort auf eigene Nachricht Notification.
---
# 28. Core Storage Service
Da Dokumente, Profilbilder und spätere Medienmodule Dateien benötigen, gibt es einen gemeinsamen Storage-Service im Core.
Aufgaben:
- sichere opaque Storage Keys erzeugen,
- Pfade kontrollieren,
- Dateien schreiben/lesen/löschen,
- Thumbnails/Varianten verwalten,
- Dateigrößenlimits und erlaubte Speicherbereiche unterstützen,
- keine öffentliche Pfadableitung aus User-Dateinamen zulassen.
Domain-Modelle behalten ihre eigenen Metadaten und Berechtigungen; der Storage-Service entscheidet nicht selbst, wer ein Dokument sehen darf.
---
# 29. Audit Log
Der globale `AuditLog` ist für sicherheits- und administrationsrelevante Änderungen gedacht, **nicht für jeden Klick**.
```text
AuditLog
├── id
├── organization_id
├── team_id optional
├── actor_account_id
├── action
├── target_type
├── target_id
└── created_at
```
Beispiele:
- Team erstellt/archiviert,
- Brand Profile geändert,
- Modul aktiviert/deaktiviert,
- Team-Verwaltung ernannt/entfernt,
- Org-Admin-Rechte geändert,
- Account gesperrt,
- Mitgliedschaft administrativ beendet,
- sensibles Dokument dauerhaft gelöscht.
Chatnachrichten, normale Verfügbarkeitsklicks oder jeder Seitenaufruf gehören nicht in diesen Audit Log.
---
# 30. Berechtigungsmatrix v1
| Funktion | Mitglied | Team-Verwaltung | Organisationsadmin ohne TeamMembership |
|---|:---:|:---:|:---:|
| eigenes Team öffnen | | | |
| Teamverzeichnis sehen | | | |
| freiwillig geteilte Teamkontakte sehen | | | |
| Login-E-Mails normaler Mitglieder sehen | | | |
| Mitglieder einladen | | | |
| normale Mitgliedschaft verwalten | | | |
| Team-Verwaltung ernennen | | | |
| Team-Verwaltung entfernen | | | |
| Team erstellen/archivieren | | | |
| Brand Profile zuordnen | | | |
| Module aktivieren/deaktivieren | | | |
| aktivierte Module konfigurieren | | | |
| Teamchat lesen | | | |
| Dokumente sehen | ✓* | | |
| Scheduling sehen | ✓* | | |
| Teaminterne Inhalte quer über alle Teams sehen | | | |
`*` nur wenn das entsprechende Modul für das Team aktiviert ist und die konkrete Objektberechtigung erfüllt ist.
---
# 31. UI- und UX-Grundsätze
## 31.1 Mobile first
Primärer Testbereich:
- ca. 360460 px App-Breite.
Desktop ist vollständig nutzbar, aber nicht der Ausgangspunkt für die Informationsarchitektur.
## 31.2 Barrierearmut
Pflicht:
- Touch-Ziele mindestens ca. 44 px,
- sichtbarer Tastaturfokus,
- sinnvolle Labels,
- Status nie nur über Farbe,
- ausreichender Kontrast,
- Fehlermeldungen mit konkreter Handlungsanweisung,
- semantisches HTML,
- keine zentrale Funktion ausschließlich per JavaScript.
## 31.3 Sprache
- Deutsch,
- klare aktive Verben,
- ein Begriff bleibt im Flow gleich,
- keine unnötige Verwaltungs-/IT-Sprache in der Mitgliederoberfläche.
## 31.4 Komponenten
Zentrale, wiederverwendbare Komponenten, z. B.:
- Buttons,
- Cards,
- Badges,
- Form Fields,
- Confirmation Pattern,
- Empty State,
- Tabellen,
- Listen,
- Navigation,
- Dashboard Cards.
Fachmodule dürfen fachspezifische Komponenten ergänzen, aber keine eigenen Grund-Buttons oder eigene Designwelt erfinden.
## 31.5 Visuelle Dichte und Bildsprache
Die Ehrenamt-Plattform ist **kein Kampagnenauftritt**, sondern ein Arbeitswerkzeug. Deshalb gilt für alle Brand Profiles, insbesondere für Vor-Ort:
- Illustrationen und Fotos nur dort einsetzen, wo sie inhaltlich helfen.
- Dashboards bleiben grundsätzlich bildarm.
- Motivierende Texte sind optional und selten; sie dürfen keine Arbeitsinformation verdrängen.
- Status, nächste Schritte und Handlungsbedarf stehen visuell vor Dekoration.
- Wiederkehrende Markenidentität entsteht primär durch Logo, Typografie, Farben, Navigation und Komponentenrhythmus.
Für Vor-Ort gilt der Quick Mockup aus Abschnitt 14.2 als Referenz für diese Balance.
## 31.6 Navigation
Die genaue mobile Belegung wird in der UI-Implementierungsphase anhand der aktivierten Module festgelegt, aber:
- **Start**, **Team** und **Profil** sind Core-Ziele,
- Module registrieren Navigationseinträge,
- Navigation muss auch bei mehreren aktiven Modulen übersichtlich bleiben,
- Verwaltungsbereiche bleiben klar von der Mitgliederoberfläche getrennt.
Diese Detailfrage darf bei der UI-Implementierung entschieden werden; sie ändert die Architektur nicht.
---
# 32. Sicherheit
## 32.1 Invite-only
Es gibt keine offene Selbstregistrierung. Accounts entstehen über Einladung bzw. verifizierte E-Mail im Zusammenhang mit einer bestehenden Einladung/Organisation.
## 32.2 Autorisierung
Jeder teaminterne Zugriff prüft serverseitig:
1. gültige Account-/Device-Session,
2. aktive TeamMembership,
3. Teamzugehörigkeit des Objekts,
4. Modulaktivierung,
5. konkrete Permission.
Keine UI-Ausblendung ersetzt eine serverseitige Prüfung.
## 32.3 Auth-Schutz
- Auth-Codes/-Tokens nur gehasht speichern,
- Ablaufzeiten,
- Rate Limits,
- Fehlversuchslimit,
- konsumierte Challenges nicht wiederverwenden,
- generische Antworten vermeiden unnötige E-Mail-Enumeration,
- sichere Cookies.
## 32.4 Web-Sicherheit
Mindestens berücksichtigen:
- CSRF-Schutz für zustandsändernde Requests,
- XSS-Vermeidung durch Jinja Autoescaping und gezielte Sanitization,
- Uploadpfade nie aus ungeprüften Dateinamen bauen,
- restriktive HTML-Einbettung,
- keine direkte öffentliche Auslieferung privater Uploads,
- sichere Redirects,
- Proxy-Headers nur bei aktivierter vertrauenswürdiger Proxy-Konfiguration.
## 32.5 Datenschutz
- keine Klient:innendaten,
- Datensparsamkeit,
- Teamgrenzen strikt,
- freiwillige Kontaktdaten opt-in,
- Login-E-Mail nicht als Teamkontakt missbrauchen,
- Foto-/Einwilligungsregel,
- Datenschutzinformationen und Verarbeitungsverzeichnis müssen vor echtem Betrieb organisatorisch mit der Aidshilfe abgestimmt werden.
---
# 33. Datenbankstrategie
## 33.1 SQLite bleibt v1-Entscheidung
Für die geplante Größenordnung bleibt SQLite bewusst bestehen.
Trotzdem:
- ausschließlich über SQLAlchemy arbeiten,
- Flask-Migrate/Alembic ab Beginn der neuen Architektur,
- keine unnötigen SQLite-Spezialkonstruktionen,
- Datenmodell so halten, dass ein späterer PostgreSQL-Wechsel möglich bleibt.
Ein PostgreSQL-Umstieg wird nicht vorweggebaut.
## 33.2 Persistenter Pfad
Produktion:
```text
/app/instance/app.db
```
und Uploads ebenfalls unter dem persistenten `/app/instance`-Mount.
Der konkrete Host-Volume-/Named-Volume-Name wird im Deployment-Stack dokumentiert.
---
# 34. Migrationen
Es gibt einen gemeinsamen Alembic-/Flask-Migrate-Verlauf für die gesamte App:
```text
migrations/
```
Module besitzen keine separaten, konkurrierenden Migrationssysteme.
Migrationen werden mit der jeweiligen Featureänderung erzeugt, getestet und versioniert.
Deployment führt ausstehende Migrationen bewusst aus; Schemaänderungen werden nicht durch `db.create_all()` in Produktion magisch erledigt.
---
# 35. Tests
Jedes Core-Teil und jedes Fachmodul bringt Tests für seine Regeln mit.
## 35.1 Pflicht-Sicherheitstests für jedes Teammodul
Mindestens:
1. keine TeamMembership Zugriff verboten,
2. falsches Team Zugriff verboten,
3. Modul deaktiviert Zugriff verboten,
4. normales Mitglied nur Mitgliederfunktionen,
5. Team-Verwaltung Verwaltungsfunktionen,
6. Organisationsadmin ohne TeamMembership Team-Inhalte verboten,
7. Objekt aus anderem Team niemals sichtbar/manipulierbar.
## 35.2 Auth-Tests
- Code nur einmal verwendbar,
- abgelaufener Code abgelehnt,
- Fehlversuchslimit,
- widerrufene Device Session ungültig,
- frische Re-Auth für sensible Aktionen.
## 35.3 Scheduling-Tests
- Statusübergänge,
- Verfügbarkeit nach Freeze nicht änderbar,
- manuelle Zuteilung,
- Unterbesetzung,
- eindeutiges Springer-Nachrücken,
- kein automatisches Raten bei Mehrdeutigkeit,
- offene Position übernehmen,
- gleichzeitige Übernahme nur einmal erfolgreich,
- ursprüngliches `unavailable` blockiert spätere Selbstübernahme nicht,
- Dringlichkeit <24 h.
## 35.4 Documents-Tests
- Pending-Sichtbarkeit,
- Freigabe/Ablehnung,
- normale Mitglieder können kein HTML hochladen,
- HTML nur isoliert dargestellt,
- unerlaubte Dateitypen abgelehnt,
- Storage Path Traversal verhindert,
- Teamgrenzen bei Download/Preview,
- Rückzug eigener Pending-Uploads.
## 35.5 Chat-Tests
- Announcement-Schreibrecht,
- Discussion-Schreibrecht,
- Teamgrenzen,
- Reply,
- Edit/Delete nur eigene Nachrichten bzw. Moderation,
- keine Dateianhänge,
- Linkregeln,
- Read/Delivered-State,
- neue Teammitglieder zählen nicht rückwirkend für alte Read Receipts.
## 35.6 UI-/A11y-Tests
Mindestens kritische Flows in mobilen Breiten prüfen:
- Login,
- Teamwechsel,
- Verfügbarkeit,
- Dienstübernahme,
- Dokumentupload,
- Chat,
- Teamverwaltung.
---
# 36. Deployment-Vertrag
Deployment ist Teil der Architektur.
## 36.1 VPS-Struktur
Bestehendes Betriebsprinzip:
```text
~/apps/<name>/ → Git-Arbeitskopie der Anwendung
~/stacks/<name>/ → Compose-/Stack-Konfiguration
Caddy → Reverse Proxy / TLS
```
Der endgültige App-/Repo-/Containername ist noch festzulegen; danach wird **derselbe Name konsistent** für Repo, App-Verzeichnis, Stack-Verzeichnis und Container verwendet.
## 36.2 Versioniert
Mindestens:
- `Dockerfile`
- `compose.yaml` bzw. versionierte Stack-Vorlage
- `.env.example`
- `requirements.txt`
- Python-Versionsfestlegung
- Migrationen
- `INSTALL.md` / Recovery-Anleitung
- `PROJECT_DESIGN.md`
## 36.3 Nicht versioniert
- echte `.env`
- `SECRET_KEY`
- SMTP-Passwort / Credentials
- Device-/Auth-Secrets
- Produktionsdatenbank
- Uploads
- Backups
## 36.4 Docker
- fester Python-Major/Minor-Stand,
- Gunicorn vorhanden,
- Prozess läuft als non-root User,
- persistentes `/app/instance`,
- keine Secrets im Image,
- Healthcheck bzw. `/health`.
## 36.5 Proxy
Caddy terminiert TLS und proxyt zur Flask/Gunicorn-App.
`ProxyFix` wird nur aktiviert, wenn eine explizite Konfiguration wie `TRUST_PROXY_HEADERS=true` dies verlangt und der Proxy vertrauenswürdig ist.
## 36.6 Health Endpoint
`/health`:
- sehr leichtgewichtig,
- ohne personenbezogene oder sensible Daten,
- geeignet für Container-/Deploymentprüfung.
---
# 37. E-Mail als Produktionsabhängigkeit
Passwordless Login macht E-Mail zu einer kritischen Infrastrukturkomponente.
SMTP-/Mailkonfiguration ausschließlich über Environment, z. B. sinngemäß:
```text
MAIL_HOST
MAIL_PORT
MAIL_USERNAME
MAIL_PASSWORD
MAIL_FROM
```
Konkrete Variablennamen können bei Implementierung finalisiert werden, müssen dann in `.env.example` dokumentiert sein.
Keine Provider-Credentials im Code.
Auth-/Einladungsmails sollen funktionieren, bevor v1 als betriebsbereit gilt.
---
# 38. Backup- und Restore-Vertrag
Ein vollständiges Backup besteht mindestens aus einem zusammengehörenden Stand von:
1. SQLite-Datenbank,
2. Upload-/Storage-Verzeichnis.
Nur `app.db` zu sichern ist **kein vollständiges Backup**.
Backups müssen außerhalb des laufenden Container-Dateisystems verfügbar sein.
Ein Restore-Prozess gilt erst als belastbar, wenn er mindestens einmal praktisch getestet wurde.
Restore-Check:
- Account/Org/Teams vorhanden,
- TeamMemberships vorhanden,
- Dokumentdateien funktionieren,
- Profilbilder funktionieren,
- Migrationen auf Zielstand laufen,
- Login-Mail funktioniert,
- Teamgrenzen weiterhin korrekt.
---
# 39. Reinstall-/Recovery-Vertrag
Eine Neuinstallation auf einem frischen Server soll reproduzierbar sein:
```text
1. Host-Voraussetzungen installieren
2. Caddy / Docker vorbereiten
3. App-Repo nach ~/apps/<name> klonen
4. Stack nach ~/stacks/<name> bereitstellen
5. .env aus .env.example erstellen und Secrets eintragen
6. persistentes Datenvolume anlegen bzw. Backup wiederherstellen
7. Image bauen / Compose starten
8. Migrationen ausführen
9. Caddy-Route aktivieren
10. /health prüfen
11. E-Mail-Login testen
12. Team-/Modulzugriff als Smoke Test prüfen
```
Die App soll nicht davon abhängen, dass eine einzelne Person noch weiß, wie das damals eingerichtet war“.
---
# 40. Bootstrap des ersten Org-Admins
Es gibt einen CLI-Befehl bzw. äquivalenten kontrollierten Bootstrap:
```text
flask ... bootstrap-org-admin <email>
```
Der genaue Commandname wird bei Implementierung festgelegt.
Er:
- nimmt eine E-Mail entgegen,
- erzeugt keinen hardcodierten Masteraccount,
- startet einen verifizierten Org-Admin-Einladungsflow,
- verweigert standardmäßig unkontrollierte weitere Bootstrap-Admins, wenn bereits Organisationsadministration existiert.
Nach Restore kommen Org-Admin-Rechte aus der Datenbank zurück; Bootstrap ist kein normaler täglicher Verwaltungsweg.
---
# 41. Vorgeschlagene Projektstruktur
```text
<repo>/
├── PROJECT_DESIGN.md
├── CLAUDE.md # optional: kurze Agentenregeln + Verweis hierher
├── TASKS.md # neuer Build-/Migrationsplan
├── README.md
├── INSTALL.md
├── .env.example
├── .python-version # oder gleichwertige Versionierung
├── requirements.txt
├── Dockerfile
├── compose.yaml # oder Stack-Vorlage
├── migrations/
├── app/
│ ├── __init__.py
│ │
│ ├── core/
│ │ ├── auth/
│ │ ├── accounts/
│ │ ├── organizations/
│ │ ├── teams/
│ │ ├── memberships/
│ │ ├── invitations/
│ │ ├── permissions/
│ │ ├── branding/
│ │ ├── storage/
│ │ ├── notifications/
│ │ ├── dashboard/
│ │ ├── profiles/
│ │ └── audit/
│ │
│ ├── modules/
│ │ ├── scheduling/
│ │ ├── documents/
│ │ └── chat/
│ │
│ ├── templates/
│ │ ├── core/
│ │ └── components/
│ │
│ └── static/
│ ├── css/
│ ├── js/
│ └── avatars/
├── tests/
└── instance/ # lokal; nicht versioniert
```
Die genaue Unterteilung kann pragmatisch angepasst werden. Nicht verhandelbar sind die fachlichen Grenzen Core Module und Services Routes.
---
# 42. Migration vom bestehenden CheckPoint-Prototyp
Der vorhandene Stand hat bereits große Teile des alten Einzelteam-Konzepts implementiert. Deshalb soll der Umbau nicht als unkontrolliertes weiter draufprogrammieren erfolgen.
## Phase 0 — Bestand sichern
- aktuellen funktionierenden Stand committen,
- Legacy-Tag/Branch anlegen,
- aktuelle SQLite-/Uploaddaten sichern, falls reale Daten existieren,
- relevante bestehende Tests/Flows dokumentieren.
## Phase 1 — Dokumente entwirren
- dieses `PROJECT_DESIGN.md` als Source of Truth einführen,
- alte Konzeptdateien als historisch markieren,
- `CLAUDE.md` auf Arbeitsregeln + Verweis reduzieren,
- `TASKS.md` nicht weiter nach altem Architekturmodell abarbeiten.
## Phase 2 — Plattform-Core
In sinnvoll kleinen Migrationen:
1. `Organization`
2. `Account`
3. `Team`
4. `OrganizationMembership`
5. `TeamMembership`
6. Einladungsmodelle
7. `BrandProfile`
8. `TeamModule`
9. `AuditLog`
10. Core Storage / Profile
Bestehende Daten nur migrieren, wenn sie erhaltenswert/real sind. Bei reinem Entwicklungsdatenbestand darf ein sauberer Neu-Seed einfacher und sicherer sein als künstliche Datenrettung.
## Phase 3 — Passwordless Auth
- alten Benutzername-/Passwort-Flow entfernen,
- Mailkonfiguration,
- AuthChallenge,
- DeviceSession,
- Invite-only Onboarding,
- Bootstrap Org Admin,
- Re-Auth für sensible Aktionen.
## Phase 4 — Team-/Org-Oberflächen
- Teamkontext in URLs,
- Teamwechsler,
- Mitgliederstartseite,
- Teamverwaltung,
- Organisationsverwaltung,
- Mitgliederverzeichnis,
- freiwillige Teamkontakte,
- Avatar/Profile.
## Phase 5 — Module Registry + Dashboard
- `ModuleDefinition`
- `TeamModule`
- Navigation Contribution
- Member-/Manager-Dashboard Provider
- standardisierte DashboardItem-Komponente.
## Phase 6 — Scheduling refactor
- flexible Planungszeiträume,
- PositionDefinitions,
- Templates,
- ShiftPositions,
- neuer Statusflow,
- Availability Freeze,
- Assignments,
- Springerlogik,
- dringende Nachbesetzung,
- Scheduling Notifications/History.
## Phase 7 — Documents refactor
- Core Storage,
- Teamscoping,
- Approval/Archive trennen,
- HEIC/HEIF,
- Thumbnails,
- HTML nur für Team-Verwaltung und isoliert,
- Backup-/Restore-Test mit echten Testdateien.
## Phase 8 — Chat refactor
- generische Channels,
- Announcement + Discussion,
- Replies,
- Moderation,
- Linkrestriktionen,
- Polling,
- delivered/read states,
- Notification-Ausnahmen.
## Phase 9 — Produktionshärtung
- CSRF / Cookies / Rate Limits,
- Permission-Invariant-Tests,
- `/health`,
- Gunicorn,
- non-root Docker,
- ProxyFix-Konfiguration,
- Logging,
- Backup-/Restore-Dokumentation,
- SMTP Smoke Test.
## Phase 10 — Deployment
Erst wenn die neue Architektur, Auth und Teamgrenzen getestet sind, erfolgt der produktive VPS-Deploy nach dem Reinstall-Vertrag.
---
# 43. Erweiterung um ein neues Modul
Ein neues Fachmodul folgt immer demselben Rezept:
```text
1. echten Bedarf beschreiben
2. stabilen module_key festlegen
3. ModuleDefinition registrieren
4. Datenmodell mit team_id anlegen
5. Migration erstellen
6. Services implementieren
7. Permissions definieren
8. Routes dünn halten
9. Templates aus zentralen UI-Komponenten bauen
10. Navigation/Dashboard-Beiträge registrieren
11. Team-Scoping- und Permission-Tests schreiben
12. erst dann für ausgewählte Teams aktivieren
```
Ein neues Modul darf nicht verlangen, den Core fachlich umzubauen, nur um sich selbst sichtbar zu machen.
---
# 44. Future Log
Bewusst vorbereitete, aber nicht vorweg implementierte Erweiterungen:
## Galerie
Eigenständiges Modul, ggf. gemeinsamer Core Storage.
## Inventory
Eigenständiges Modul für Material-/Bestandsverwaltung, falls ein Team echten Bedarf meldet.
## Qualifications
Spätere Erweiterung, z. B. erforderliche Schulungen für Positionen. Das Scheduling-Positionsmodell lässt dafür Platz, ohne Qualifikationen heute einzubauen.
## Kalenderintegration
Später denkbar, aber nicht Teil des Scheduling-Kerns von v1.
## Push / operative E-Mail
Kann später den Notification-Service als Ausgabekanal ergänzen.
## Passkeys / 2FA
Vor allem für sensible Organisationsadministration später denkbar.
## feinere Rollen / Permissions
Der Code benutzt schon Permission Helpers, damit später neue Rollen möglich sind, ohne Rollenstrings überall zu ändern.
## PostgreSQL
Falls Last, Parallelität oder Betrieb es später sinnvoll machen. Kein aktuelles Ziel.
---
# 45. Noch bewusst offene Konfigurationsdetails
Diese Punkte sind **keine offenen Architekturfragen**, sondern dürfen bei Implementierung/Deployment konkretisiert werden. Sie sollen nicht stillschweigend erfunden, sondern als kleine Produkt-/Betriebsentscheidung dokumentiert werden:
1. endgültiger Produkt-/Repo-/Containername,
2. exakte Auth-Code-Gültigkeit und Device-Session-Dauer,
3. konkrete Upload-Größenlimits,
4. konkrete Liste blockierter URL-Shortener,
5. endgültige mobile Navigation bei vielen aktivierten Modulen,
6. konkreter SMTP-Provider und Env-Variablennamen,
7. genaue Backup-Aufbewahrungsdauer,
8. ob normale Mitglieder in v1 ihre Teammitgliedschaft selbst verlassen dürfen oder ob eine Team-Verwaltung sie beendet,
9. genaue Regeln für erneutes Senden/Verlängern abgelaufener Einladungen.
Für diese Details gilt: klein entscheiden, dokumentieren, testen keine neue Plattformarchitektur daraus machen.
---
# 46. Definition of Done für v1
v1 ist nicht fertig, nur weil die Seiten funktionieren.
Sie gilt als bereit, wenn:
- Passwordless Login zuverlässig funktioniert,
- Einladung + Onboarding funktionieren,
- mehrere Accounts und mehrere Teams getestet sind,
- dieselbe Person in mehreren Teams funktionieren kann,
- Teamwechsel klar ist,
- Org-Admin ohne TeamMembership keine Team-Inhalte lesen kann,
- Team-Verwaltungen keine weiteren Manager ernennen können,
- Brand Profiles teamweise funktionieren,
- Module pro Team aktivierbar/deaktivierbar sind,
- Scheduling komplett im neuen Positionsmodell funktioniert,
- Documents inkl. Freigabe/Storage/Preview funktionieren,
- Chat die festgelegten Arbeitskommunikationsregeln einhält,
- Dashboard-Providers modulunabhängig funktionieren,
- mobile Hauptflows auf ca. 360460 px gut bedienbar sind,
- kritische A11y-Grundsätze eingehalten sind,
- DB-Migrationen reproduzierbar laufen,
- `/health` funktioniert,
- Docker/Gunicorn/Caddy-Setup dokumentiert ist,
- Backup von DB + Uploads funktioniert,
- ein Restore praktisch getestet wurde,
- SMTP-Loginmail im Produktionssetup getestet wurde,
- Reinstall-Anleitung von einer frischen Umgebung aus nachvollziehbar ist.
---
# 47. Kurzfassung der nicht verhandelbaren Architekturregeln
1. **Account ist teamunabhängig.**
2. **Teamzugehörigkeit läuft ausschließlich über TeamMembership.**
3. **Team-Verwaltung ist zusätzliches Recht eines normalen Teammitglieds.**
4. **Nur Organisationsadministration ernennt/entfernt Team-Verwaltungen.**
5. **Org-Admin ist kein Generalschlüssel für Teamdaten.**
6. **Teaminterne Fachobjekte tragen `team_id`.**
7. **Module dürfen den Core nutzen; der Core kennt keine Fachlogik einzelner Module.**
8. **Code definiert vorhandene Module, `TeamModule` definiert Aktivierung pro Team.**
9. **Org-Verwaltung aktiviert Module; Team-Verwaltung konfiguriert sie.**
10. **Routes bleiben dünn; Businesslogik lebt in Services.**
11. **Permission Helpers statt verstreuter Rollenchecks.**
12. **Dashboard zeigt Zustände; Notifications melden Ereignisse.**
13. **Keine Passwörter in v1 — E-Mail-Code + langlebige widerrufbare Device Session.**
14. **Private Uploads werden nie unkontrolliert statisch ausgeliefert.**
15. **SQLite + Migrationen von Anfang an; PostgreSQL ist kein v1-Projekt.**
16. **Backup bedeutet Datenbank + Uploads.**
17. **Deployment und Restore sind Teil des Produkts, nicht Nacharbeit.**
18. **Neue Funktionen werden als klar begrenzte Module ergänzt, nicht in bestehende Module hineingestopft.**
---
# 48. Nächster Schritt
Aus diesem Dokument wird als nächstes ein **neues `TASKS.md` für den Umbau/Migrationspfad** abgeleitet.
Dabei gilt:
- kleine Sessions,
- jede Session hat ein klar prüfbares Ergebnis,
- zuerst Core und Sicherheitsgrenzen,
- erst danach Fachmodule,
- bestehende funktionierende Teile werden nur übernommen, wenn sie zum neuen Modell passen,
- keine alte Architektur erhalten, nur weil bereits Code dafür existiert.
`PROJECT_DESIGN.md` bleibt während dieses Umbaus die verbindliche Grundlage.