# 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// /team//scheduling/ /team//documents/ /team//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//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//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// → Git-Arbeitskopie der Anwendung ~/stacks// → 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/ klonen 4. Stack nach ~/stacks/ 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 ``` 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 / ├── 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.