# 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. 360–460 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. 360–460 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.
