67 KiB
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
PROJECT_DESIGN.md— verbindliche Produkt- und Architekturentscheidung.- Code + Tests — Implementierung dieser Entscheidungen.
TASKS.md— daraus abgeleiteter Arbeits-/Migrationsplan; darf keine eigene Architektur erfinden.CLAUDE.md/ andere Agentenhinweise — wenn weiterhin verwendet, nur als kompakte Arbeitsanweisung mit Verweis aufPROJECT_DESIGN.md, nicht als zweite Architekturquelle.- 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
- Lesbarkeit und Orientierung vor Funktionsfülle.
- Mobile Nutzung ist der Normalfall, nicht die abgespeckte Desktop-Version.
- Technisch wenig affine Menschen müssen die App ohne Schulungsbedarf bedienen können.
- Ein Team sieht nur sein Team. Organisationsrechte sind kein Generalschlüssel für Team-Inhalte.
- Module sind optional pro Team. Nicht jedes Team bekommt Funktionen, die es nicht braucht.
- Automatisierung nur bei eindeutigem Ergebnis. Die App unterstützt Entscheidungen; sie entscheidet nicht unnötig für Menschen.
- Dashboard zeigt Zustände, Benachrichtigungen melden Ereignisse.
- Erweiterbarkeit entsteht durch klare Grenzen, nicht durch ein maximal generisches System.
- 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:
- Scheduling / Einsatzplanung
- Documents / Dokumente
- 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:
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:
Organization
├── id
├── name
├── status
└── created_at
5.2 Team
Ein Team ist ein klar abgegrenzter Arbeitsbereich innerhalb der Organisation.
Team
├── id
├── organization_id
├── name
├── slug
├── brand_profile_id
├── status
└── created_at
Status mindestens:
activearchived
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.
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:
activedisabled
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:
TeamMembership
├── id
├── team_id
├── account_id
├── role
├── status
├── joined_at
└── ended_at
Rollen in v1:
membermanager
Status mindestens:
activeinactive
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:
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_admindarf niemals eine fehlendeTeamMembershipfü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:
- Eine Team-Verwaltung kann sich nicht selbst zur normalen Mitgliedschaft herunterstufen.
- Ein aktives Team darf nicht ohne Team-Verwaltung zurückbleiben.
- Beim Entfernen der letzten Team-Verwaltung muss zuerst eine neue bestimmt werden.
- 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:
- Person gibt ihre E-Mail-Adresse ein oder folgt einer Einladung.
- App erzeugt einen zeitlich begrenzten Auth-Challenge.
- E-Mail enthält:
- einen kurzen Einmal-Code,
- zusätzlich einen Direktlink.
- Nach erfolgreicher Verifikation wird eine langlebige, widerrufbare Device Session angelegt.
- Beim normalen erneuten Öffnen der Homescreen-/Browser-App ist die Person weiterhin angemeldet.
Es gibt damit keinen Passwort-Reset-Flow.
10.2 AuthChallenge
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
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
TeamInvitation
├── id
├── team_id
├── email
├── role
├── invited_by
├── status
├── created_at
└── expires_at
Status mindestens:
pendingacceptedrevokedexpired
Normale Team-Verwaltungen dürfen nur role = member einladen.
Ablauf
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:
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:
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:
Andrea
[Profilbild]
Team-Verwaltung
📞 0176 ...
Erreichbar per Anruf / Signal
„Bei kurzfristigen Dienstausfällen gerne anrufen.“
Datenschutzregeln
share_contact_detailsist 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.
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:
- Mitglieder-Dashboard — persönlich, mobil-first, Fokus auf nächste Einsätze, offene Rückmeldungen, Verfügbarkeit und ungelesenen Teamchat.
- Teamverwaltungs-Dashboard — sachlicher und dichter, Fokus auf offene Aufgaben, Rückmeldungen, Freigaben, Einladungen und unbesetzte Positionen.
- 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:
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
Fachmodul → Core erlaubt
Core → Fachmodul verboten
Der Core darf nicht wissen, wie ein Einsatz, Dokument oder Chat fachlich funktioniert.
15.2 Verzeichnisstruktur
Zielstruktur, sinngemäß:
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:
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:
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:
if membership.role == "manager":
sondern fachliche Funktionen wie:
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:
ModuleDefinition
├── key
├── name
├── description
├── version
├── navigation
├── member_dashboard_provider
├── manager_dashboard_provider
└── dependencies
Stabile Modulkeys für v1:
schedulingdocumentschat
Alle installierten Module/Blueprints werden beim App-Start registriert.
TeamModule
Die Datenbank entscheidet nur, ob ein vorhandenes Modul für ein Team aktiv ist:
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:
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:
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:
Shift.team_id
Document.team_id
Channel.team_id
Message.team_id
Grundprüfung:
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:
/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:
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:
/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:
/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.
DashboardItem
├── title
├── text
├── category
├── count optional
├── action_label optional
├── action_endpoint optional
└── module_key
Erlaubte Kategorien:
URGENTACTIONUPCOMINGINFO
Sortierung zentral:
- Dringend
- Handlungsbedarf
- Bevorstehend
- 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:
Notification
├── id
├── account_id
├── team_id optional
├── type
├── priority
├── title
├── text
├── action_endpoint optional
├── created_at
└── read_at optional
Prioritäten:
normalimportanturgent
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
Vorbereitung
→ Verfügbarkeit offen
→ Plan wird erstellt
→ Veröffentlicht
Statuswerte:
draftavailability_openschedulingpublished
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
PlanningPeriod
├── id
├── team_id
├── name
├── start_date
├── end_date
├── status
├── availability_deadline optional
├── created_at
└── archived_at optional
Keine erzwungene Zweimonatsdauer.
21.4 PositionDefinition
PositionDefinition
├── id
├── team_id
├── name
├── description optional
├── sort_order
├── active
└── replacement_for_id optional
Beispiele:
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
ShiftTemplate
├── id
├── team_id
├── name
├── default_title
├── default_start_time
├── default_end_time
├── default_location
├── active
└── Template-Positionen
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
Shift
├── id
├── team_id
├── planning_period_id
├── title
├── date
├── start_time
├── end_time
├── location
├── description optional
└── created_at
21.7 ShiftPosition
ShiftPosition
├── id
├── team_id
├── shift_id
├── position_definition_id
├── required_count
└── sort_order
Beispiel:
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.
Availability
├── id
├── team_id
├── shift_id
├── team_membership_id
├── status
└── updated_at
Status:
availableunavailable
Kein Datensatz = noch nicht beantwortet.
Beim Wechsel des Planungszeitraums zu scheduling werden Verfügbarkeiten eingefroren.
21.9 Assignment
Assignment
├── id
├── team_id
├── shift_id
├── shift_position_id
├── team_membership_id
├── status
├── assigned_at
└── assigned_by optional/system
Status mindestens:
assignedcancelledpromoted
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:
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:
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:
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:
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:
Aktuell
Archiv
25.2 Document
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
pending
approved
rejected
25.4 Bibliotheksstatus
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:
- 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:
/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:
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.:
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
Channel
├── id
├── team_id
├── name
├── description
├── type
├── sort_order
├── status
└── created_at
Typen:
announcementdiscussion
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:
activearchived
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
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
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.
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:
- gültige Account-/Device-Session,
- aktive TeamMembership,
- Teamzugehörigkeit des Objekts,
- Modulaktivierung,
- 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:
/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:
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:
- keine TeamMembership → Zugriff verboten,
- falsches Team → Zugriff verboten,
- Modul deaktiviert → Zugriff verboten,
- normales Mitglied → nur Mitgliederfunktionen,
- Team-Verwaltung → Verwaltungsfunktionen,
- Organisationsadmin ohne TeamMembership → Team-Inhalte verboten,
- 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
unavailableblockiert 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:
~/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:
Dockerfilecompose.yamlbzw. versionierte Stack-Vorlage.env.examplerequirements.txt- Python-Versionsfestlegung
- Migrationen
INSTALL.md/ Recovery-AnleitungPROJECT_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äß:
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:
- SQLite-Datenbank,
- 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:
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:
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
<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.mdals Source of Truth einführen, - alte Konzeptdateien als historisch markieren,
CLAUDE.mdauf Arbeitsregeln + Verweis reduzieren,TASKS.mdnicht weiter nach altem Architekturmodell abarbeiten.
Phase 2 — Plattform-Core
In sinnvoll kleinen Migrationen:
OrganizationAccountTeamOrganizationMembershipTeamMembership- Einladungsmodelle
BrandProfileTeamModuleAuditLog- 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
ModuleDefinitionTeamModule- 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:
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:
- endgültiger Produkt-/Repo-/Containername,
- exakte Auth-Code-Gültigkeit und Device-Session-Dauer,
- konkrete Upload-Größenlimits,
- konkrete Liste blockierter URL-Shortener,
- endgültige mobile Navigation bei vielen aktivierten Modulen,
- konkreter SMTP-Provider und Env-Variablennamen,
- genaue Backup-Aufbewahrungsdauer,
- ob normale Mitglieder in v1 ihre Teammitgliedschaft selbst verlassen dürfen oder ob eine Team-Verwaltung sie beendet,
- 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,
/healthfunktioniert,- 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
- Account ist teamunabhängig.
- Teamzugehörigkeit läuft ausschließlich über TeamMembership.
- Team-Verwaltung ist zusätzliches Recht eines normalen Teammitglieds.
- Nur Organisationsadministration ernennt/entfernt Team-Verwaltungen.
- Org-Admin ist kein Generalschlüssel für Teamdaten.
- Teaminterne Fachobjekte tragen
team_id. - Module dürfen den Core nutzen; der Core kennt keine Fachlogik einzelner Module.
- Code definiert vorhandene Module,
TeamModuledefiniert Aktivierung pro Team. - Org-Verwaltung aktiviert Module; Team-Verwaltung konfiguriert sie.
- Routes bleiben dünn; Businesslogik lebt in Services.
- Permission Helpers statt verstreuter Rollenchecks.
- Dashboard zeigt Zustände; Notifications melden Ereignisse.
- Keine Passwörter in v1 — E-Mail-Code + langlebige widerrufbare Device Session.
- Private Uploads werden nie unkontrolliert statisch ausgeliefert.
- SQLite + Migrationen von Anfang an; PostgreSQL ist kein v1-Projekt.
- Backup bedeutet Datenbank + Uploads.
- Deployment und Restore sind Teil des Produkts, nicht Nacharbeit.
- 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.