Chronik der wichtigsten Architektur- und Produkt-Entscheide, inkl. verworfener Ansätze. Versionsnummern verweisen auf changelog.md.
Kein Django REST Framework (v0.13.0) — alle APIs sind leichtgewichtige JsonResponse-Views mit generischen Helpern (_paginate, _apply_sort). Weniger Abstraktion, weniger Dependencies. Konsequent auch beim Kalender: eigener Allowlist-HTML-Sanitizer statt bleach.
Vue 3 via CDN, keine Build-Pipeline (v0.13.0) — kein npm-Build für Frontend-Logik; nur Tailwind hat einen Watcher.
Person-Lead-Modell (v0.14.60): Person ist die Lead-Tabelle, Member nur das Vertragsverhältnis. Vorname/Adresse/Telefon sind personenbezogen, nicht mitgliedschaftsspezifisch. Kontakt-Entitäten (Address/Phone/Email) als eigene Tabellen mit M:N-Links → echtes Sharing statt String-Kopien (Familienadresse = 1 Datensatz, n Links).
Menü & Addon-Katalog als Code, nicht DB (v0.14.90): Der statische Katalog ist Code-Stand; nur die Aktivierung (ClubAddon) ist persistent. Später kam der DB-Katalog für den Store dazu (Preise, AGB, Popups) — geseedet aus dem Code.
Denormalisierter Sync statt Property (v0.14.96): Member.display_name sollte eine berechnete Property werden. Befund: 20+ Code-Stellen nutzen das Feld direkt → Refactor hiesse Subquery an jeder Stelle. Entscheid: Auto-Sync via Signal — funktional identisch, bei max. ~500 Mitgliedern pro Mandant irrelevant für Performance. Pragmatismus schlägt Purismus.
App-Anker beim Kalender (v0.18.0): Die App ist das führende System, externe Kalender nur Anzeige. Keine Zwei-Wege-Sync → keine Konflikte. Serien werden materialisiert statt als RRULE expandiert (einfaches Einzel-Editieren, Kappe 104).
- Problem: Die Mitgliederliste zeigt die Adresse als Haushalts-Ding, editierbar war sie nur an der Person („Anzeige-Ebene ≠ Editier-Ebene").
- Erster Versuch (v0.16.14): Anzeige-Entscheid statt Modell-Umbau — nur die Darstellung wanderte in die Mitgliederliste.
- Zweiter Versuch (v0.16.60–63): member-owned Adressen/Telefone mit automatischer Vererbung an Personen.
- Rückbau (v0.16.77): Die implizite Vererbung war nirgends sichtbar — kein Screen zeigte einer Person die geerbte Adresse. Mit dem Canvas gibt es EINEN sichtbaren Mechanismus. Endgültig: person-owned, Teilen = explizite Verdrahtung. Die deprecated Link-Tabellen bleiben leer im Schema stehen.
Lehre: Unsichtbare Automatik verliert gegen sichtbare Explizitheit.
- Two-Pass-Detection (v0.14.72): Werte-only-Erkennung nur mit harten Patterns und
tentative-Flag; Header-Match schlägt immer Werte-Match.
- Schweizer-Namen-DB statt Regex (v0.14.69): einzige Möglichkeit, „Müller Hans" von „Hans Müller" zu unterscheiden.
- Verworfen: „Jg."-Substring-Detection (v0.14.81) — False-Positive-Risiko („Jg. Mannschaft") zu hoch; stattdessen Suffix-Routing über die Cluster-Engine.
- 2-Wert-Spalten als Dropdown statt Boolean (v0.15.6): ein Boolean verlöre die Labels „Bezahlt/Offen".
- Stille Auto-Verhalten statt Popups (v0.14.82): fehlende Mitgliedernummer → automatisch weiternummerieren, ohne Rückfrage.
- Checkboxen nie einfrieren (v0.16.9–12): Abwählen immer erlauben, Prüfung erst beim Submit; Hinweis-Popup statt Blockieren.
- Park-Konzept abgeschafft (v0.16.17): Das stille Parken von Spalten in JSON erzeugte unsichtbaren Datenverlust. Neu: jede Spalte wird Mitglieder-Spalte oder Hauptkontakt — fertig.
- M7/M8-Kacheln wieder entfernt (v0.16.23): doppelte Bedienwege verwirren. Grundsatz: „so wenig UI wie nötig". Die Erkennungs-Intelligenz dahinter blieb.
- Wizard-Reduktion (v0.14.82): „Wir verlieren User bei jedem Schritt mehr" — von 6+ Schritten auf 3–4.
- E-Mail vereinsweit eindeutig (v0.16.45): auf DB- UND App-Ebene (CI-Unique-Constraint), inkl. Login-Adressen. E-Mail = Identität.
- «Leitung» statt «Trainer» (v0.28.28–v0.28.38, Entscheid 12./13.10.2026): CluHu spricht Vereine aller Art an (Sport, Musik, Feuerwehr, Kurse). Der neutrale, nicht überschreibbare Begriff «Leitung» ersetzt «Trainer» in Standardrollen, Berechtigungs-Vorlage, Portal und Addon-Store; die Terminart heisst «Training, Kurse & Probe»; im Portal kommt «Training» nicht vor (Termin, Anwesenheitskontrolle, Titel des Termins). Nur Marketing-Texte nennen bewusst gemischte Beispiele (Trainerin, Dirigent, Kursleiterin). Ein Auswahlfeld oder eine Kopplung an die Kategorie wurde verworfen.
- Ein Rollen-System statt drei (v0.16.79/83): Freitext-
membership_role entfernt; Herkunftsmodell mit Referenzzählung („Leitung (Mitglied, Gruppe)"). Kommas/Klammern in Rollennamen verboten — das ist Anzeige-Syntax.
- Team = Gruppe, Organisation = Gruppen-Hierarchie (v0.16.78): ein Addon absorbiert zwei geplante.
- Rollen an Mitgliedschaften abgeschafft (v0.16.95): serverseitig geblockt + Altdaten-Migration. Rollen gehören zu Personen (ggf. mit Kontext), nicht zu Verträgen.
- Verworfen: Namens-Matching als Dedup (v0.16.87→93): „40× Muster AG = 1 Firma" per get-or-create war unkontrollierbar. Neu: jede Zeile = eigene Firma, Anker ist die Firmen-Nr.
- Fallback-Auflösung statt Pflicht-Validierung (v0.16.86, ERP-Muster): explizite Adress-Rolle → einzige Adresse → leer. Nie erzwingen.
- Vererbung „erstgesetzt" (v0.16.92): kein main-Lead; die älteste verknüpfte Person gilt für unbesetzte Slots.
- Haushalts-Mitglieder sind KEINE Teilnehmer (v0.18.6): Mit-Informierte/Stellvertreter ohne eigenen Status, zählen nicht zur 18er-Sichtbarkeitsgrenze.
- Login-Mail-Fallback zurückgebaut (v0.18.4→18.5): „nicht nachvollziehbar" — die Personen-E-Mail ist die einzige Wahrheit, der User spiegelt sie nur.
- Kein Ein-Klick bei Magic-Links: GET zeigt Bestätigungsseite, erst POST setzt den Status („kein Ein-Klick-Unfall" durch Mail-Scanner/versehentliche Klicks).
- «Löschen» in der Oberfläche, Archiv nur intern (v0.28.165, Entscheid Yves 12.11.2026): Einzel-Löschen, «Alle Mitglieder löschen», «Alle Personen-Daten löschen» und «Diesen Import rückgängig» sagen überall «löschen» / «wird gelöscht»; das Bestätigungswort heisst «löschen» (klein, Gross-/Kleinschreibung egal). Vorher standen drei Wörter nebeneinander («archivieren» im Titel, «wird geräumt» als Vermerk, «löschen» in der Rückfrage der Tabelle), und der Fusstext versprach «lassen sich wiederherstellen» und «endgültiges Löschen erst aus dem Archiv», obwohl es dafür keine Oberfläche gibt und Kontaktdaten, Feldwerte und Fotos ohnehin sofort weg sind. Für den Verein ist es ein Löschen, also heisst es so. Warum nicht «archivieren» behalten: Das Wort weckt die Erwartung, man könne zurück; das stimmt nur für den Grundeintrag (Name, Nummer), der im Hintergrund bleibt, damit Rechnungen, Protokolle und Anwesenheiten nachvollziehbar sind. Genau das sagt jetzt der Fusstext. Warum kein hartes Löschen: Der Personenbezug in der Buchhaltung muss bleiben (siehe Mitgliedschaften). Zeilen, die keine Löschung sind, tragen ihren eigenen Vermerk («wird zurückgezogen», «wird neu zugewiesen», «wird deaktiviert»).
- Hinweise statt blockierender Modals: deklarative Hint-Registry (v0.16.97), pro User wegklickbar, Re-Trigger bei erneutem Eintreten der Bedingung.
- Destruktive Aktionen verlangen Texteingabe („löschen", „Tabelle löschen") mit konkreter Aufzählung dessen, was verloren geht.
- Dead-Code bleibt bewusst stehen: alte URLs sollen weiterfunktionieren; Cleanup in eigenen Iterationen (mehrfach dokumentiert).
- Verworfen: SVG-Masken-Typografie im Hero (v0.17.2→3): „Blur-Schrift unter deckenden Icon-Linien ist nicht sauber machbar, jeder Trick macht Icon oder Text matschig" → Layout ohne Überlappung.
- Custom-Feld → Freiheit zum Verdrahten (v0.16.74): nur echte System-Spalten sind im Canvas ausgeblendet; alles andere darf der User verdrahten, auch wenn es selten sinnvoll ist.
Container-Breite zentral (v0.26.7). 36 Clu-Core-Seiten setzten ihre Breite selbst, mit fünf verschiedenen Werten. Folge: ab 1328 px blieb rechts Platz leer (bei 1920 px rund 616 px), die rechte Kante sprang beim Navigieren, Listen waren gedeckelt. Phase 1 verschob jeden Wert unverändert in einen Block der Basis-Vorlage; 78 von 78 Screenshots blieben identisch. Erst danach wurde seitenweise entschieden.
Was fluid wurde (v0.26.8–.12, nur ab 1024 px, Mobile byte-identisch): Mitglieder-, Personen- und Firmen-Listen auf volle Breite; Kachel-Raster mit so vielen Spalten, wie Platz ist (3 bei 1440, 4 bei 1920, 6 bei 2560); Dashboard; Kalender; Gruppen mit Organigramm und Karten ab 2200 px zweispaltig (neuer Breakpoint 3xl, benannt, damit weitere Seiten denselben Wert nutzen). Register-Linien laufen über die volle Breite, der Inhalt bleibt darunter begrenzt (v0.26.11). Zwei-Spalten-Ansichten ab 1280 bzw. 1536 px: Bilanz Aktiven | Passiven, Erfolgsrechnung Aufwand | Ertrag in Kontenform, Geschäftsjahre | MwSt, Kontenplan | Standardkonten, Spesen-Einstellungen, Automationen, Stammdaten (v0.26.12).
Warum nicht alles fluid. Tabellen mit wenigen Spalten (Berechtigungen, Field-Manager, Spalten-Zuordnung) wurden auf Inhaltsbreite gesetzt, sonst zieht die Breite die Spalten auseinander (v0.26.10). Wizards, Formulare und Platzhalter bleiben zentriert und begrenzt: dort hilft Breite nicht beim Lesen.
Portal-Dashboard (v0.26.16–.19). Termine mit Details (Ort als Kartenlink, Organisator, Serie, Beschreibung, Teilnehmerzahl) und zwei Zonen auf Desktop. Dann «eine Kachel, ein Termin» mit Datumsblock, Info-Pills und Kachel-Fuss; Zu- und Absage ohne Neuladen (v0.26.17). Wochenstreifen, Badge «n offen» und Filter «nur offene» flogen wieder raus: sie haben mehr verwirrt als genützt. Module (Meldungen, Dateien, Spesen, Revision, Kalender-Abo, Meine Daten, Konto) stehen aufgeklappt auf der Seite statt als Links; leere Module bleiben sichtbar. Die Schnellzugriff-Kacheln wurden entfernt, weil sie dieselben Module doppelt anboten (v0.26.18). Der Kalender ist eine Sektion mit demselben Kopf wie die anderen Module, die Termine liegen getönt darin: ein Termin ist ein Element im Kalender, nicht ein Modul neben «Meldungen» (v0.26.19). Grosser Clu-Core-Button für Berechtigte. Diese Kachel-Logik trägt seither auch Trainings, Abmelden mit Grund und das Werkzeug «Leitung» (siehe Kalender).
Gemeinsames SVG-Icon-Set (v0.26.20). Ein Inline-Sprite in Portal und Clu-Core ersetzt die Emojis im Portal-Dashboard und im Bereich Berechtigungen. Inline statt externe Datei: keine zweite Anfrage, keine Browser-Eigenheiten bei Verweisen, zuverlässig in der Textfarbe färbbar. Sektions-Köpfe im Portal als dunkler Balken, seit v0.26.21 mit demselben Verlauf wie der Clu-Core-Button.
Dateityp-Symbole (v0.26.21). Vorher trugen alle Nicht-Bilder dasselbe Symbol. Jetzt 13 Symbole (PDF, DOC, XLS, CSV, PPT, TXT, SVG, IMG, ZIP, MP3, MP4, Code, Unbekannt) für rund 60 Endungen, selbst gezeichnet statt Icon-Paket: keine Abhängigkeit, gleiche Optik. Eine einzige Zuordnung bedient Django-Templates (Portal) und den Vue-Datei-Browser (Clu-Core), damit die beiden Listen nicht auseinanderlaufen. Im Detail-Fenster des Datei-Browsers gibt es damit erstmals eine Vorschau für Nicht-Bilder.
«Leitung» statt «Trainer» (v0.28.28–v0.28.38, Entscheid 12./13.10.2026) steht oben unter «Rollen & Gruppen».