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.
- Ein Rollen-System statt drei (v0.16.79/83): Freitext-
membership_role entfernt; Herkunftsmodell mit Referenzzählung („Trainer (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).
- Hinweise statt blockierender Modals: deklarative Hint-Registry (v0.16.97), pro User wegklickbar, Re-Trigger bei erneutem Eintreten der Bedingung.
- Destruktive Aktionen verlangen Texteingabe („ALLE 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.