# kulibri-rewrite — Paritäts-Lücken gegenüber Legacy

Stand: 6. Oktober 2026 (erste Fassung 1. Oktober) · Erstellt von: Mitko (mit Claude Code) · Pflege: PO + Bolt-Team

Ziel: Alle Funktionen der bestehenden kulibri-App (Laravel-Backend + Vue-2-SPA im Repo `kulibri-laravel`, Ordner `frontend/`) stehen im neuen Frontend (Vue 3, dieses Repo) zur Verfügung — gegen dasselbe Backend (`test.kulibri.com`).

## So lesen und pflegen

- **Priorität** (Spalte `Prio`) ist ein **Vorschlag** und gehört dem PO. Bitte direkt in dieser Datei ändern.
  - `P1` Launch-Blocker: Ohne das kann Legacy nicht abgelöst werden.
  - `P2` Wichtig: Soll zeitnah nach dem Launch folgen oder wird von aktiven Nutzern erwartet.
  - `P3` Später / selten genutzt.
  - `ENTSCHEIDUNG` Der PO muss entscheiden, ob die Funktion überhaupt übernommen wird (Konflikt mit Produktregeln).
  - `DROP` Bewusst nicht übernommen (nur mit Begründung eintragen).
- **Status:** `fehlt` = nichts gefunden · `teilweise` = es gibt etwas, aber nicht gleichwertig · `prüfen` = unklar, muss im Code oder im Browser verifiziert werden.
- **Methode / Grenzen:** Erste Runde = Abgleich der Legacy-Routen, Komponenten und API-Aufrufe gegen Router, `api.ts` und Views dieses Repos (Suche im Code, noch kein Durchklicken im Browser). Zeilen mit `prüfen` sind Hinweise, keine Befunde. Das Repo war beim Abgleich 29 Commits hinter `origin/main`; vor dem Nacharbeiten bitte `git pull`.
- Legacy-Pfade beziehen sich auf `kulibri-laravel/frontend/src/…` (Frontend) bzw. `kulibri-laravel/app/…` und `routes/api.php` (Backend).

---

## 1 · Konto, Anmeldung, Rechtliches

| # | Funktion | Legacy | Rewrite-Status | Prio | Anmerkung |
|---|---|---|---|---|---|
| 1.1 | Registrierung (Formular, Fehlerfälle) | `Register.vue`, `POST /register` | vorhanden (auf `main`): der Reiter „Registrieren“ legt das Konto über `POST /api/register` an (Backend-Anforderung A1, seit dem 2. Oktober 2026 im Backend); eine echte Registrierung mit Test-Adresse (registrieren, bestätigen, anmelden) ist noch nicht geprüft | P1 | **Korrektur:** der frühere Stand „Registrieren im Login-Dialog vorhanden“ stimmte nicht. Der Rewrite rief `POST /api/register` auf, diese Route gibt es nicht (405 am 1. Oktober 2026); die Web-Route `POST /register` braucht ein CSRF-Cookie (419) und liefert kein Token. Das Formular im Rewrite ist deshalb entfernt. **Entscheidung (PO/Mitko):** bis nach dem MVP über das alte Frontend; danach `POST /api/register` im Backend (validieren, Konto anlegen, Bestätigungs-Mail, Token wie bei `/api/login`), siehe Befund G10. Fehlertexte, Hinweis „E-Mail bestätigen“ nach der Registrierung und Gast-Übergang hängen daran. **Stand 2. Oktober 2026 (Branch `feat/auth-classic-ui`):** der Reiter „Registrieren“ ist vollständig gebaut (Name, E-Mail, Passwort 8 bis 128 Zeichen, Hinweis auf AGB und Datenschutz, Erfolgsseite „Email Adresse bestätigen“, Legacy-Texte) und stand zuerst hinter `VITE_REGISTER_ENABLED` (aus, bis das Backend `POST /api/register` hat, Anforderung A1), mit einem Hinweis und einem Link zur Registrierung der alten Oberfläche (`VITE_LEGACY_BASE_URL`). **Stand 3. Oktober 2026:** der Schalter, der Hinweis, der Link und `VITE_LEGACY_BASE_URL` sind entfernt, die Registrierung ist immer an. Voraussetzung: der Server, gegen den das Rewrite läuft, hat `POST /api/register` (Test: ja; Produktion: vor dem Ausliefern prüfen). |
| 1.2 | E-Mail-Verifizierung | Link der Mail: `/signin/:id/:hash` (`Login.vue`, ruft `GET /email/verify/{id}/{hash}`); `email/resend` | teilweise — vorhanden (auf `main`; Browser-Prüfung offen, Zustellung der Bestätigungsmail auf der Testumgebung war am 2. Oktober ungeklärt): Seite `/signin/:id/:hash` bestätigt die Adresse und führt zur Anmeldung; Anmeldung ohne bestätigte Adresse zeigt „E-Mail noch nicht bestätigt“ und behält kein Token | P1 | Die Route liegt auf der Seite, nicht unter `/api` (`requestPublic` mit `root`, Vite-Proxy `/email`). **Annahme, nicht live geprüft:** ein Nutzer ohne bestätigte Adresse bekommt bei `/api/login` ein Token, jede Konto-Route (`verified`) antwortet dann 403. **Offen:** „Mail erneut senden“ (die Route verlangt eine Sitzung, ein Bearer-Token genügt nicht, Befund G10) und der Hinweis nach der Registrierung (hängt an 1.1). **Stand 2. Oktober 2026:** die Seite `/signin/:id/:hash` ist jetzt die Anmeldeseite mit dem Ergebnis über dem Formular, wie im Legacy (`Login.vue`). |
| 1.3 | Passwort vergessen / zurücksetzen | `/reset`, `/resetlink`, `/auth/requestreset`, `/auth/passwordreset/:token/:email`, `/passwordchanged`; API `password/email`, `password/reset` | vorhanden (auf `main`; Browser-Prüfung offen, Zustellung der Bestätigungsmail auf der Testumgebung war am 2. Oktober ungeklärt): „Passwort vergessen?“ auf der Anmeldung, Seite `/passwort-vergessen`, Seite `/auth/passwordreset/:token/:email` (derselbe Pfad wie die Mail, schon verschickte Mails gehen weiter), danach Anmeldung mit Hinweis | — | **Abweichungen:** „zu dieser Adresse gibt es kein Konto“ wird nicht angezeigt (neutrale Meldung, sonst lässt sich über das Formular prüfen, wer ein Konto hat); Mindestlänge 8 prüft der Rewrite selbst, weil das Backend sie dort nicht prüft. Backend liefert rohe Schlüssel (`passwords.token`), die der Rewrite übersetzt. **Bereitstellung:** die Mail verlinkt auf `KULIBRI_FRONTEND_URL`; welche Oberfläche dort läuft, entscheidet, wer den Link bekommt. **Stand 2. Oktober 2026:** Aussehen und Ablauf wie im Legacy (Titel, Erklärung, Abbrechen/Abschicken, Bestätigung mit Adresse, **ein** Passwortfeld, danach „Dein Passwort wurde geändert“); die Pfade `/auth/requestreset`, `/reset`, `/passwordchanged` gehen weiter. |
| 1.4 | Social Login (Google) | `LoginGoogle.vue`, `/auth/{provider}/redirect` und `/callback` | vorhanden (auf `main`; Anmeldung mit echtem Google auf der Testumgebung am 2. Oktober 2026 von Mitko bestätigt; braucht `VITE_GOOGLE_CLIENT_ID` und das Backend mit `POST /api/auth/google`): der Knopf (Aussehen wie im Legacy) öffnet das Google-Fenster und meldet mit einem Token an, der Nutzer bleibt im Rewrite | P2 | Bearer-Token-Auth des Rewrites passt nicht zum Session-Redirect-Flow des Legacy. **Stand 2. Oktober 2026:** Backend-Anforderung A3 ist umgesetzt (`POST /api/auth/google`, Code-Fluss mit Socialite, siehe `docs/kulibri-backend-anforderungen-auth.md`). Der Knopf lädt beim Anzeigen das Google-Skript, öffnet beim Klick das Fenster (Popup, `initCodeClient`) und schickt den Code an das Backend; danach gelten dieselben Schritte wie nach der Passwort-Anmeldung (Sprache des Kontos, `?url=` oder Dashboard). **Abweichung vom Legacy:** ein Fenster statt Seitenwechsel zu Google und zurück. Ohne `VITE_GOOGLE_CLIENT_ID` wird der Knopf nicht gezeigt (vorher: Link in die alte Oberfläche). Fehlertexte: abgelehnt, Konto deaktiviert, Fenster vom Browser blockiert, Google-Skript nicht ladbar; ein geschlossenes Fenster bleibt ohne Meldung. Registrieren und Anmelden sind mit Google dasselbe: ein neues Google-Konto legt das Backend selbst an. Offen: Prüfung der einzelnen Fälle („neues Google-Konto“, „bestehendes Konto“, Fenster schließen) und auf Telefonbreite; bestätigt ist bisher eine erfolgreiche Anmeldung. Flutter ist nicht Teil davon. |
| 1.5 | Profil bearbeiten (Name, E-Mail, Sprache), Passwort ändern | `EditProfile.vue`, `EditPassword.vue`, `PUT /user`, `PUT /user/password` | vorhanden — wie in der klassischen Oberfläche zwei Seitenleisten (`SidePanel`), geöffnet aus dem `UserMenu`: „Mein Profil“ (`ProfilePanel`) und „Passwort ändern“ (`PasswordPanel`, eigener Menüpunkt) | — | **Stand 3. Oktober 2026 (Branch `feat/profile-side-panel`):** „Mein Profil“ hat Persönliche Daten (Name, E-Mail, Sprache) und Rechnungsdaten (siehe 2.2) mit **einem** gemeinsamen Abbrechen und Speichern in der Fußzeile. Gespeichert wird nur, was sich geändert hat; beide Abschnitte werden vor dem Senden geprüft, und klappt ein Teil nicht, bleibt die Leiste offen, der gespeicherte Teil wird nicht noch einmal gesendet. Bei Erfolg schließt die Leiste. „Passwort ändern“ ist eine eigene Leiste mit eigenem Speichern; nach der Änderung ersetzt die Bestätigung das Formular, wie in der klassischen UI. **Abweichungen:** die Sprache gilt nach dem Speichern sofort (klassisch schon beim Auswählen); zur Auswahl stehen nur Deutsch und Englisch (vorher auch Französisch ohne Übersetzung); ein fehlgeschlagenes Speichern zeigt eine Meldung (klassisch wird es verschluckt); zusätzlich zur Wiederholung des neuen Passworts (klassisch nur zwei Felder); Passwort ist ein eigener Menüpunkt statt eines Links im Profil; auf dem Handy ein Sheet von unten. Texte in Deutsch und Englisch. Die Sprachwahl hat sonst kaum Wirkung (siehe 1.10). |
| 1.6 | Konto löschen | `NavUser.vue` (Menüpunkt und Dialog), `store/modules/login.js` (`deleteUser`), `POST /user/delete` | vorhanden auf Branch `feat/delete-account` (Browser-Prüfung offen): **Admin-Konten** wie im Legacy (Menüpunkt „Konto löschen“, beliebige Adresse); **jeder Nutzer** über den Gefahrenbereich in „Mein Profil“ (Verbesserung, PO-08) | — | **Legacy:** der Punkt steht nur bei `isAdmin` (`roles & 256`); normale Nutzer hatten keinen Weg. **Selbstbedienung (neu, Entscheidung PO/Mitko 6. Oktober):** (1) eine laufende Mitgliedschaft (`membershipView` = premium) blockiert mit „Bitte kündigen Sie zuerst Ihre Mitgliedschaft“ und Weg nach „Mein Account“, eine gekündigte oder fehlende nicht; kann sie nicht geprüft werden, wird nicht gelöscht; (2) Folgen werden genannt; (3) die eigene E-Mail-Adresse muss getippt werden, bis dahin ist „Löschen“ grau; (4) freiwillige Gründe (Mehrfachauswahl) und Text; (5) nach erfolgreichem Löschen geht die Rückmeldung per `POST /api/feedback` an den Support (nie davor, nie blockierend, nur wenn etwas gewählt wurde), dann Abmeldung und Hinweis auf der Anmeldeseite. **Offen:** Texte und Gründe vom PO bestätigen lassen; Datenschutz-Hinweis (die Rückmeldung enthält die Adresse, wird nicht gespeichert, nur gemailt); Backend-Befund G14 (Status, Tokens, Abo kündigen, Name und Passwort-Hash entfernen) als eigener Punkt. |
| 1.7 | Newsletter / Abmelden | `UnsubscribeView.vue` (`/unsubscribe/:id?`), `POST /users/{id}/unsubscribe` | vorhanden auf Branch `feat/unsubscribe` (Browser-Prüfung offen) | — | Wie im Legacy öffentlich und ohne Anmeldung; bestellt beim Öffnen sofort ab (Links aus bereits versendeten Mails gehen weiter), zeigt den Namen und die Bestätigung auf Deutsch und Englisch. **Abweichung:** wo das Legacy eine leere Seite zeigt (fehlende Kennung, unbekannte Kennung 404, Fehler), steht eine Meldung („Link ungültig“, „Abmeldung fehlgeschlagen“ mit „Erneut versuchen“). **Risiko wie im Legacy:** ein Mail-Scanner, der Links vorab abruft, kann die Abmeldung auslösen; ein Bestätigungsknopf wäre sicherer, ändert aber das Verhalten bereits versendeter Links (Entscheidung offen). **Backend-Befund G15:** `opt_out` wird gesetzt, aber nirgends ausgewertet. Text und Weiterbestand: PO-11. |
| 1.8 | Seite „Umfrage gelöscht" | `/deleted`, `DeletedPoll.vue`; Weiterleitung in `Poll.vue`/`TablePoll.vue` bei `poll.type == 0` und bei gelöschter Einladung | teilweise: vorhanden (auf `main`; Browser-Prüfung offen) für gelöschte Umfragen (`type 0`): Umfrage-, Bearbeiten- und Teilen-Seite leiten auf `/deleted` um, Text wie im Legacy | P1 | Angehoben von P2: seit „Umfrage löschen“ (6.4) gibt es gelöschte Umfragen im Rewrite, und das Backend liefert sie weiter aus (`getPoll` prüft `type` nicht), der Link blieb also offen. **Offen:** der zweite Auslöser im Legacy, eine gelöschte Einladung bzw. ein entfernter Teilnehmer (2. Link-Segment gehört keiner Einladung mehr). Der Rewrite deutet das Segment bewusst nicht selbst; dafür müsste das Backend für ein unbekanntes Segment einen Fehler liefern (wie bei 1.12). |
| 1.9 | AGB / Datenschutz / (Impressum) | `/agb`, `/dataprivacy` (`router.js`, `agb.vue`, `DataPrivacy.vue`) | vorhanden auf Branch `feat/legal-links` (Browser-Prüfung offen) | — | Entscheidung PO-10 (6. Oktober): **Weiterleitung auf die bestehenden Seiten der Marketing-Seite**, kein Text in der App. `/agb` und `/dataprivacy` leiten in der aktuellen Sprache auf `/de/agb`, `/de/datenschutz` bzw. `/en/terms-of-use`, `/en/privacy` um (`VITE_MARKETING_URL`; ohne sie geht es auf die Startseite). **Korrektur zur Zeile:** im Legacy sind beide Seiten leere Platzhalter („This is the T&C page“) und die Fußzeile ist abgeschaltet (`App.vue`: `<app-footer v-if="false">`); erreichbar waren sie nur über alte Links. Die Einwilligungszeile bei der Registrierung verlinkt dieselben Seiten schon. Einen Impressum-Link gibt es im Legacy nicht. **Offen:** ob die englischen Adressen (`/en/terms-of-use`, `/en/privacy`) auf der Marketing-Seite existieren (aus den Legacy-Texten übernommen, nicht geprüft). |
| 1.10 | Sprachumschaltung (de/en) | `locales/de.json`, `en.json`, `LangOptions.vue`, WPML-Cookie | teilweise — Profil bietet de/en/fr zur Auswahl, aber `i18n` enthält nur `de`, und die meisten Texte in den Views sind fest auf Deutsch geschrieben | P1 | Runde 3: `$t(...)` wird nur sparsam genutzt (z. B. 39 Stellen in `PollDetailView`, sonst kaum). Englisch und Französisch brauchen sowohl die Übersetzungen als auch das Herausziehen der festen Texte. Legacy-Locale `en.json` als Vorlage nutzbar. **Stand 2. Oktober 2026:** auf den Anmelde- und Konto-Seiten gibt es den Sprachumschalter (de/en, wie `LangOptions.vue`) mit den Legacy-Texten; alle anderen Seiten bleiben deutsch. |
| 1.11 | Promotion-Hinweis | `POST /user/promotion-shown` | prüfen | P3 | |
| 1.12 | Alte Link-Formate bleiben gültig | `/signin/:id?/:hash?`, `/pr/:id/:key?/:publicKey?`, `/vpr/:id/:key?` | teilweise — `/pr/:id/:segment` vorhanden, Auflösung des 2./3. Segments offen; `/signin/:id/:hash` (E-Mail bestätigen) und `/auth/passwordreset/:token/:email` auf Branch `feat/account-recovery` | P1 | Alle bereits versendeten Links müssen weiter funktionieren. Siehe `docs/kulibri-teilnahme-und-links.md`. |

## 2 · Abrechnung und Mitgliedschaft

| # | Funktion | Legacy | Rewrite-Status | Prio | Anmerkung |
|---|---|---|---|---|---|
| 2.1 | Mitgliedschaft / Abo buchen (Mollie) | `/membership`, `MembershipHome.vue`, `POST /subscription/{user_id}`, Webhooks | vorhanden (auf `main`; ein Upgrade mit dem Mollie-Testschlüssel auf der Testumgebung am 2. Oktober 2026 von Mitko bestätigt): Status, Upgrade, Rückkehr von Mollie (`?order_id=`), Kündigen, Erneuern | P1 | **Entscheidung (Mitko, 2. Oktober 2026):** das Abo wird im Rewrite abgewickelt, Aussehen zunächst wie die klassische Oberfläche. `CLAUDE.md` sagt „Kein Zahlungsmodul“; gemeint ist kein Geldfluss *zwischen Teilnehmern*, das Abo von kulibri selbst (36 €/Jahr) ist etwas anderes. **Gebaut:** Menüpunkt „Mein Account“, Seite `/membership` (Pfad wie im Legacy, weil Mollie dorthin zurückleitet), Statuskarten „Registered User“ und „Premium User“ mit Preis, Leistungen, Zahlungsart und nächster Rechnung, gekündigter Zustand mit Enddatum, alte Testzeilen. **Abweichungen:** der Preis kommt aus `GET /api/memberships` statt aus einer Konstante im Code (Legacy: 30,25 + 5,75 fest); die Zahlungsart zuerst nach den Angaben von Mollie, weil `subscription.payment_method` in der Datenbank mit „Paypal“ vorbelegt ist; kein Testphasen-Knopf (im Legacy auskommentiert); der feste Link auf kulibri.com in den Leistungstexten kommt aus `VITE_MARKETING_URL`. Backend: die Zahlungs-Routen verlangen seit SEC-011 eine Anmeldung und gelten nur für den eigenen Nutzer (`SECURITY-BACKLOG.md` im Backend). **Abo (zweite Scheibe):** Knopf „Upgrade auf Premium User“ (`POST /subscription/{id}` liefert die Bezahlseite von Mollie; nur https-Adressen, ein Klick sendet nur eine Anfrage); Rückkehr auf `/membership?order_id=…` (`POST /subscribe/{id}`, danach Nutzer und Status neu laden, die Adresse ohne `order_id`) mit Dialog für bezahlt (Glückwunsch, weiter zu „Neues Projekt“), fehlgeschlagen (Grund von Mollie) und nicht beendet; Kündigen (`POST /user/{id}/cancel-subscription`) und Erneuern (Hinweis auf eine E-Mail an den Support, wie im Legacy); Fehlertexte für „schon ein aktives Abo“, zu viele Versuche, Netz und alles andere. **Abweichungen:** Kündigen ist ein Dialog mit Erklärung und Sicherheitsfrage statt zwei Dialogen hintereinander; bei fehlgeschlagener oder nicht beendeter Zahlung nur „Schließen“ (im Legacy führte auch dort „Jetzt neues Projekt anlegen“ weiter); die HTML-Teile der Legacy-Texte (`<br/>`, `<strong>`) sind in einfache Zeilen zerlegt, zwei Sätze des Legacy waren fehlerhaft („erfolgreich“ doppelt) und sind neu formuliert; wer noch in einer alten Testphase ist, kann upgraden. **Offen:** Prüfung der übrigen Fälle im Browser mit dem Mollie-Testschlüssel (Abbruch, fehlgeschlagene Zahlung, Kündigen, Rechnung nach dem Upgrade) und auf Telefonbreite. **Testumgebung:** ein Konto mit der Kunden-ID aus einer Kopie der Produktion (`cst_…` aus dem Live-Modus) scheitert beim Upgrade mit „Customer … exists, but the wrong mode is used“, weil der Server den Testschlüssel nutzt; die Kunden-ID des Kontos auf `NULL` setzen (siehe G11 Punkt 9). Backend, nur notiert: ein Klick auf Upgrade legt jedes Mal eine neue lokale Abo-Zeile an, solange kein bezahltes Abo läuft (wie im Legacy); Kündigen ohne Mollie-Abo antwortet im Backend vermutlich mit einem Fehler 500, weil der Antwort-Encoder einen reinen Text nicht verarbeitet (zu prüfen); `user.plan` und `user.roles` sind zwei Quellen für „ist Premium“ (der Editor nutzt `plan`, Aufgaben nutzen `roles`), siehe `docs/kulibri-backend-befunde_1.md`. Mollie leitet auf `{frontend_url}/membership` zurück; welche Oberfläche dort läuft, bestimmt `KULIBRI_FRONTEND_URL` des Servers. |
| 2.2 | Rechnungsliste, Rechnung drucken | `Invoice.vue`, `PrintInvoiceDetails.vue`, `GET /invoices/{user_id}`, `/print-invoice-details/{id}` | vorhanden (auf `main`; Browser-Prüfung der Rechnungen offen): Reiter „Rechnungen“ mit Tabelle (Datum als Link in einem neuen Tab, Beschreibung, Zeitraum, Betrag mit MwSt., Summe) und die druckbare Rechnung unter `/membership/print-invoice-details/:id` | — | Wie im Legacy zeigt die Liste das letzte Abo mit seinen Rechnungen (höchstens ein Jahr zurück). **Abweichungen:** eine alte Rechnung ohne Kennung ist nur Text (Legacy: Warnung „Key not found“); Knopf „Drucken" auf der Rechnung (im Druck ausgeblendet); Zahlungsart wie bei 2.1. Fremde und unbekannte Rechnungen antwortet das Backend mit 404, die Seite zeigt „nicht gefunden“. Die Anschrift des Anbieters steht wie im Legacy in den Übersetzungen (`invoiceDetails.business*`), nicht im Code der Seite. **Firmenrechnung (neu, 3. Oktober 2026, Branch `feat/company-invoices`; braucht das Backend mit `GET/PUT /api/billing-profile`, Branch `feat/company-invoices` im Backend):** Im Profil („Mein Profil“) gibt es den Abschnitt „Rechnungsdaten“: Name auf der Rechnung, Firma, Straße, PLZ, Ort, Land, USt-IdNr. Mit Firmenname wird die nächste Rechnung eine Firmenrechnung (dann ist die Anschrift Pflicht, eine USt-IdNr. gehört zu einer Firma); ohne Firma bleibt es eine Rechnung an den Namen. Das Land ist mit Deutschland vorbelegt und gilt nur zusammen mit anderen Angaben der Anschrift: ein leeres Profil bleibt leer (die Vorbelegung ist keine Änderung, Speichern bleibt aus), eine deutsche Firma braucht keinen Klick auf das Land, ein anderes Land wählt man mit einem Klick. Neue Rechnungen halten beim Ausstellen fest: laufende Nummer `KU-<Jahr>-NNNNN`, Datum, Empfänger und die Beträge (netto, MwSt., Satz, brutto); sie ändern sich nicht, wenn sich Rechnungsdaten oder Preis später ändern. Die Rechnung zeigt Anbieter (Angaben vom Impressum https://kulibri.com/en/imprint: i-ruck GmbH, Mangstr. 15, 80997 München, Geschäftsführer Mark Nibbe und Andreas Kutzer, HRB 165067, support@kulibri.com; die Nummer DE 253313357 ist die USt-IdNr. und heißt jetzt so statt „Steuer ID“, auch auf dem alten Kaufbeleg), Empfänger, Nummer, Datum, Leistungszeitraum, Netto, MwSt. und Gesamtbetrag. Die Liste zeigt die Rechnungsnummer, auf der Statusseite steht ein Hinweis, die Firmendaten **vor** dem Upgrade einzutragen. **Nicht Teil davon (Entscheidung Mitko, 3. Oktober 2026):** bestehende Rechnungen (sie behalten das Aussehen des Kaufbelegs), Änderungen an der Steuerbehandlung (weiter ein Satz für alle, kein Reverse-Charge). **Offen:** Prüfung der Angaben durch die Steuerberatung (Pflichtangaben, Nummernkreis, Steuer für Firmen im Ausland), Rechnung per E-Mail und als gespeicherte PDF-Datei. Das Registergericht wird vorerst nicht gebraucht (Mitko, 3. Oktober 2026). |
| 2.3 | Abo kündigen, Testphase | `POST /user/{id}/cancel-subscription`, `/start-trial`, `/delete-trial` | Kündigen und Erneuern vorhanden (auf `main`, siehe 2.1), Testphase bewusst nicht | P1 | **Testphase: bewusst nicht übernommen** (Mitko, 2. Oktober 2026; im Legacy ist der Knopf auskommentiert, und `start-trial` ist im Backend seit SEC-011 entfernt, weil es jedem 14 Tage Premium gab). `delete-trial` bleibt im Backend als Admin-Werkzeug („Delete Subscription" im Legacy-Menü) und hat im Rewrite keine Oberfläche. Alte Testzeilen zeigt der Status noch an. |
| 2.4 | Feature-Sperren nach Plan (Premium-Marker) | z. B. Branding, Limits | prüfen | ENTSCHEIDUNG | Zusammen mit 2.1 klären, was kostenpflichtig bleibt. |

## 3 · Umfrage-Typen und Antwort-Ansichten

| # | Funktion | Legacy | Rewrite-Status | Prio | Anmerkung |
|---|---|---|---|---|---|
| 3.1 | Terminumfrage (Typ 1) | `Renderer`, `RenderData`, `RenderEvent`, Mobile-Varianten | vorhanden (`PollDetailView`) | — | Verhalten in Vertiefung prüfen (Abschnitt 8). |
| 3.2 | Gästeliste / Event (Typ 2) | `RenderEvent`, `RenderMobileEventParticipants` | vorhanden | — | Mehrtägige Events, Gästezahl pro Teilnehmer prüfen. |
| 3.3 | Textumfrage (Typ 3) | | vorhanden | — | |
| 3.4 | Workshop / Kursplaner (Typ 4) | `RenderWorkshop*`, `WorkshopCapacityBar`, `WorkshopFooterBar`, `WorkshopSelectionSummary`, `ModalChoiceDialog` | teilweise — `CourseView.vue` mit Kapazität, Warteliste, Auswahlübersicht | P1 | Eigene Zuordnung zwischen Legacy `bookingModePoll`/Typ 4 und Rewrite klären; nach `poll.type` verzweigen (API-Vertrag Z. 397–399). Mehrfachtermine pro Workshop, Teilnehmerliste pro Termin, Storno durch Admin (`POST /participant/cancelBooking/{id}`) prüfen. |
| 3.5 | Antwortoptionen Ja / Nein / Wenn nötig / Weiß nicht | `mayBeAnswers`, `allowDoNotKnowAnswer` | vorhanden | — | |
| 3.6 | „Nicht beantwortet = Nein" | `setDefaultAsNoForNotAnswered` | vorhanden | — | Auswirkung auf Zählung prüfen. |
| 3.7 | Verdeckte Umfrage + Statistik für Teilnehmer | `hiddenPoll`, `showStatisticToParticipants` | vorhanden | — | Cookie-Wiedererkennung anonymer Mehrfachzeilen offen (Rewrite-requirements 3i). |
| 3.8 | Zähler ausblenden | `showCounts` / `hideCounts` | fehlt — Feld nur in `api.ts`/i18n, kein Schalter, keine Auswertung | P3 | Runde 3 bestätigt. |
| 3.9 | Ein Termin pro Teilnehmer | `oneChoicePerParticipant` | vorhanden | — | |
| 3.10 | Limit pro Option (global und je Option) | `limitedVotesPerChoice`, `limitedVotesPerChoiceSpecific`, `ChoiceLimit.vue`, `POST /{id}/choice-configure` | vorhanden — Editor kennt „Plätze pro Termin“ (`platzMode` slot/all/day) und schreibt `choice.limit` | — | Runde 3: Der globale Wert `votesLimit` wird nirgends gesetzt. Das ist unkritisch, solange immer `choice.limit` genutzt wird; Poll-Daten aus Legacy mit `votesLimit` werden aber angezeigt, nicht bearbeitet. |
| 3.11 | Limit pro Termin / pro Teilnehmer | `limitedVotesPerEvent`, `votesLimit`, `signupLimit` | teilweise — nur Felder in `api.ts` | P2 | |
| 3.12 | Gäste-Limit pro Eintrag | `guestsLimit` | vorhanden | — | |
| 3.13 | Zusätzliche Antworten pro Zelle | `PreferenceAnswer`, `POST /{id}/remove-choice-answers`, `ConfigureAnswer.vue`, `RenderCustomAnswers` | fehlt | P2 | Anforderung „Zweiphasige Zuteilung" (docs) baut darauf auf. Datenspeicherung vorher mit Backend klären. |
| 3.14 | Zeitzone | `options.timeZone` | teilweise — Wert wird gelesen und mitgeschickt, kein Auswahlfeld | P3 | Runde 3. |
| 3.15 | Mehrere Termine mit Uhrzeitspannen, Tage + Text kombiniert | `OptionDay`, `OptionDayText`, `OptionTime`, `TimeRange` | prüfen | P1 | Editor-Parität: alle Optionstypen aus `Create/` einzeln abhaken. |
| 3.16 | Sortieren und Filtern im Raster | `RenderSortingIcons.vue`, `options.filters` / `find` | fehlt — kein Sortieren/Filtern im Raster gefunden (nur eigene Zeile oben, vergangene Termine ausblenden) | P3 | Runde 3. Bei großen Umfragen (Produktreihenfolge Punkt 0: Grid-Performance) vermutlich wichtiger. |
| 3.17 | Mobile Darstellung | `RenderMobile*` (Header, Participants, Comments, Tasks, Invitations) | prüfen | P1 | Responsives Verhalten je Ansicht im Browser prüfen. |
| 3.18 | Antwort bearbeiten: Warnhinweis, Teilantwort | `warningMessageWhenEditResponse`, `partialAnswer*` | prüfen | P2 | Meldungstexte und Fälle in Legacy-Locale abgleichen. Doppelte E-Mail und Limit-Fehler: siehe 9.2 und 9.4. |
| 3.19 | Live-Aktualisierung | — (Legacy ohne) | vorhanden (`usePollSync`) | — | Neu im Rewrite, kein Legacy-Bedarf. |

## 4 · Teilnehmer, Kommentare, Aufgaben

| # | Funktion | Legacy | Rewrite-Status | Prio | Anmerkung |
|---|---|---|---|---|---|
| 4.1 | Teilnehmer anlegen, umbenennen, löschen, Antworten ändern | `/participant/*`, `/poll/{id}` | vorhanden | — | Umbenennen blockiert durch Server-Bug G6 (500). |
| 4.2 | Benutzerdefinierte Teilnehmerfelder (Attribute) | `EditParticipantAttributes.vue`, `PUT /participantattributes` | vorhanden / prüfen | P1 | Alle Feldtypen und Pflicht/sichtbar-nur-Admin prüfen. |
| 4.3 | Kommentare lesen / schreiben / löschen | `Comments.vue`, `/comments`, `/comment` | vorhanden | — | Löschen: Server-Bug laut Rewrite-docs. Gezielte Kommentare brauchen Backend-Änderung. |
| 4.4 | Comment notification on/off | toggle "Bei neuen Kommentaren benachrichtigen" in `ModalParticipant.vue`; `/participant/comments/activate`, `/deactivate` | partial (branch `feat/sub-admins`) | P2 | The toggle is in "Teilnehmer bearbeiten" for rows with an address: stored value, on when missing, saved with the participant (`receive_comment_reminder`, PUT /participant/{id}). New rows keep sending `true` (default on). Not used yet: the activate/deactivate routes (where the classic UI calls them is still to check). QA B18.8. |
| 4.5 | **Aufgaben** (anlegen, übernehmen, zuweisen, Details) | `Tasks.vue`, `RenderTaskDetails`, `ModalAddTaskDialog`, `ModalAssignTaskDialog`, `/task`, `/resource` | vorhanden (auf `main`; erster Browser-Test am 1. Oktober positiv, Bolt-Review offen): Reiter „Aufgaben“ mit Karten, Seitenleisten zum Anlegen/Bearbeiten und Zuweisen, 10-Aufgaben-Limit ohne Premium, Editor-Schalter „Aufgaben erlauben“ | — | Spezifikation und Abweichungen: `docs/kulibri-aufgaben-spec.md`. Backend unverändert (Berechtigung und Kapazität werden dort nicht geprüft, Entscheidung: nach dem MVP). |
| 4.6 | Ressourcen | `/resource`, Mails `NewResource*` | vorhanden — **kein eigenes Feature**, sondern die Zuweisungen von Teilnehmern zu Aufgaben (siehe 4.5) | — | Die E-Mails an Ersteller und Teilnehmer sendet weiter das Backend. |
| 4.7 | Sub-admins / co-admins | `participants.is_sub_admin`, `PollAccess`, toggle "Teilnehmer ist Subadmin" in `ModalParticipant.vue` (Premium, only for the creator, only participants with email) | present (branch `feat/sub-admins`, browser check open) | P2 | Built after `docs/requirements.md` 3n: toggle "Teilnehmer ist Subadmin" in "Teilnehmer bearbeiten", visible only to the creator and only for rows with an address (not the creator's own row), switchable only when the creator has Premium (otherwise shown locked with "PREMIUM"). A sub-admin, recognised by the row's personal link or by login, gets the admin view (`isAdmin` = creator or sub-admin) but no toggle. Rules in `utils/subAdmin.ts`. Notifications with the sub-admin's links come from the backend as before. Not in the rewrite: picking sub-admins from a list in the settings (option). Open in the backend: removing the flags when the creator's subscription ends (finding F9). QA B18. |
| 4.8 | Nachricht an Admin | `SendMessageToAdmin.vue`, `POST /message-to-admin` | fehlt | P2 | |
| 4.9 | Erinnerung an Teilnehmer senden | `SendParticipantReminder.vue`, `POST /reminder-to-participant` | vorhanden auf Branch `feat/participant-reminder` (Browser-Prüfung offen) | — | **Korrektur (6. Oktober):** das Legacy hat keinen Premium-Teaser; der Menüpunkt „Nachricht“ steht jedem Ersteller (`v-if="isOwner"`) zur Verfügung und die Funktion arbeitet. PO-01 blockiert den Bau also nicht (offen bleibt nur, ob sie später Premium vorbehalten wird). Gebaut wie im Legacy: Seitenleiste „Nachrichten an Teilnehmende schicken“ mit den Teilnehmern dieser Umfrage (mit E-Mail-Adresse), Schnellauswahl Alle, Ohne Antwort, Teil-Antwort (nicht bei der Gästeliste), Alle abwählen, Betreff und Text mit den Vorgaben des Legacy, Knopf „Nachricht senden (n)“, Bestätigung. **Abweichungen:** „Teil-Antwort“ wählt nur Teilnehmer mit einigen, aber nicht allen Antworten (Legacy: jeden mit mindestens einer Antwort, `selectedMissingAnswered`); der Knopf steht für angemeldete Ersteller (das Backend verlangt ein Token); in großen Umfragen, die ihre Antworten seitenweise laden, ist der Stand unbekannt und „Ohne Antwort“ wählt niemanden. **Backend-Befund G16:** die Route prüft nicht, ob der Absender die Umfrage besitzt, und schickt an jede Adresse. |
| 4.10 | Admin storniert Buchung | `POST /participant/cancelBooking/{id}` | fehlt | P2 | Teil von Workshop (3.4). |

## 5 · Einladen und Teilen

| # | Funktion | Legacy | Rewrite-Status | Prio | Anmerkung |
|---|---|---|---|---|---|
| 5.1 | Einladen per E-Mail (Freitext) | `EmailInvite.vue`, `POST /invitation` | vorhanden (`InvitePanel`) | — | `{link}`-Platzhalter und 50er-Limit offen (requirements Punkt #16). |
| 5.2 | Einladen aus Kontakten | `SelectContacts`, `SelectPollContacts` | vorhanden / prüfen | P1 | |
| 5.3 | Link-Einladung, Übersicht der Einladungen | `LinkInvite.vue`, `OverviewInvite.vue`, `RenderInvitations` | teilweise | P2 | Einladungsstatus („besucht", `invitation/visit`) und Einladung löschen (`DELETE /invitation/{id}`) prüfen. |
| 5.4 | Persönlicher Link je Teilnehmer | `/pr/{id}/{participantId}` | vorhanden | — | Auflösung des polymorphen Segments siehe 1.12. |
| 5.5 | Admin-Link senden / anzeigen | `GET send-admin-links`, `Poll.key` | teilweise | P1 | Blockiert durch Server-Bug G8 (`key` = null bei Bearer-Auth). **Hinweis (6. Oktober 2026):** `GET /send-admin-links` im Backend ist ein Überbleibsel für Umfrage 1, kein Feature; die Admin-Mail geht beim Anlegen der Umfrage hinaus. Was die Zeile genau meint, ist neu zu klären. |

## 6 · Umfrage-Editor und Aussehen

| # | Funktion | Legacy | Rewrite-Status | Prio | Anmerkung |
|---|---|---|---|---|---|
| 6.1 | Titel, Beschreibung, Typ | `EditDetails`, `EditKulibri` | vorhanden | — | |
| 6.2 | Frist (Deadline), „keine Frist", Erinnerungen | `EditPollDeadline`, `EditPollReminders`, `delay_interval`, `delay_type` | vorhanden / prüfen | P1 | Auswirkung der Frist auf Bearbeitung prüfen. |
| 6.3 | Umfrage schließen/öffnen (für alle) / archivieren | `EditClose.vue`, `PUT /poll/{id}/archive` | vorhanden — Menüpunkt „Schließen“ bzw. „Öffnen“ in der oberen Leiste öffnet das Panel `ClosePollPanel` (Offen/Geschlossen, Abbrechen, Fertig), gespeichert in `polls.closed` wie Legacy | — | Der frühere Schalter im Editor wurde entfernt. Texte mit „Umfrage“ statt „Projekt“ (Bolt-Wortwahl). Das Backend erzwingt Schließen und Frist nicht (wie Legacy), siehe B3. |
| 6.12 | Antworten nach dem Absenden sperren (jeder antwortet nur einmal) | — (nur im Rewrite, Legacy hat das nicht) | vorhanden — `options.lockAfterSubmit`, sperrt nach dem Abschicken die eigene Zeile für Nicht-Admins | — | Neues Feature, keine Paritätslücke. Nur Oberflächensperre: wer die API direkt aufruft oder einen zweiten Browser nutzt, kann weiter antworten. Zusätzliche Zeilen (Geschwister mit derselben E-Mail) sind nicht gesperrt. Im Editor als PREMIUM markiert, Entscheidung beim PO. |
| 6.13 | Finale Option(en) beim Schließen auswählen | `EditClose.vue` (Liste „Wähle die finale(n) Option(en) aus“, nicht bei Typ 2), `choices.final`, Hervorhebung bei geschlossener Umfrage (`bg-closed`) | vorhanden — Auswahl im `ClosePollPanel` (Mehrfachauswahl), Speichern über `POST /poll/choices` mit allen Optionen; Hervorhebung im Raster (grüner Rahmen der Spaltenüberschrift) und in der Termin- und Teilnehmeransicht; Hinweis „finale Option(en) stehen fest“; der Editor behält `final` beim Speichern bei | — | Auch im Kursplaner (`CourseView`): Termine mit finaler Option erhalten den grünen Rahmen. Die Mobil-Ansicht nutzt dieselben Vorlagen wie der Desktop (nur die Standardansicht unterscheidet sich), also gilt die Hervorhebung dort ebenso. Im Browser prüfen. Das Backend löscht jede Option samt Antworten, die nicht in der Liste steht, deshalb sendet das Panel immer alle Optionen. **Zusätzlich im Kursplaner:** Eine geschlossene Umfrage sperrte Buchungen dort bisher nicht. Jetzt entfallen für Teilnehmer „Auswählen“, „Buchung ändern“ und „Weiteren Teilnehmer hinzufügen“, es erscheint ein Hinweis; vorhandene Anmeldungen bleiben sichtbar. |
| 6.4 | **Umfrage löschen** | `Headline.vue` (`removePoll`), `DELETE /poll/{id}` | vorhanden (auf `main`; Browser-Prüfung offen): Schaltfläche „Löschen“ in der Kopfleiste, Rückfrage „Willst Du das Projekt wirklich löschen?“, danach Dashboard | — | Das Backend löscht nicht wirklich: es setzt `type = 0` (weg aus dem Dashboard) und löscht nur die **Aufgaben** endgültig; Teilnehmer, Antworten und Kommentare bleiben. Deshalb steht in der Rückfrage ein Satz zu den Aufgaben (Abweichung vom Legacy). Der Endpunkt prüft keine Berechtigung, siehe Befund G9. Gelöschte Umfragen werden vom Backend weiter ausgeliefert; der Link führt jetzt auf die Seite „Umfrage gelöscht“ (siehe 1.8). |
| 6.5 | Umfrage mit Passwort schützen | `EditPassword.vue` | kein Legacy-Feature (Zeile entfällt) | — | Runde 3 bestätigt. **Korrektur (6. Oktober 2026):** im Legacy kein Feature. `EditPassword.vue` ist das Passwort-ändern-Fenster des Benutzermenüs (`NavUser.vue`); `options.privacy` ist nur der Standardwert `'closed'` und wird nirgends gelesen. |
| 6.6 | Hintergrundbild: Standardbilder, eigener Upload | `EditBackground`, `POST /image/upload`, `/image/delete/{id}` | vorhanden | — | Verwaltung eigener Bilder (Löschen, Limit `maxImageLimit`) prüfen. |
| 6.7 | Logo hochladen, Logo-Link | `EditLogo`, `POST /logo/upload`, `DELETE /logo/delete/{id}`, `options.logoLink` | vorhanden — Screen „Logo wählen“: Hochladen, Löschen, Auswahl (Standard-Logo oder `user.logos`) mit Live-Vorschau; gespeichert wird mit dem Design (`logoDetails.logoImg`). Logo-Höhe wirkt jetzt in der Kopfzeile | P2 | Browser-Prüfung offen. Legacy erlaubt die Logo-Auswahl nur mit Premium; im Rewrite noch ohne Sperre (Entscheidung PO). Der Logo-Link wird gespeichert, aber die Kopfzeile verlinkt nicht darauf. |
| 6.8 | Branding-Vorlagen / Design speichern | `CustomBranding`, `CustomBrandingTemplates`, `/designs` | teilweise — „Design verwalten“: Auswahl wirkt sofort als Vorschau auf der Umfrageseite (Kopfbereich, Logo, Kachelmodus); „Speichern“ speichert und ordnet zu, „Löschen“, bei Änderungen zusätzlich „Speichern unter“; Zurück ohne Speichern verwirft | P2 | Standard-Designs werden nur zugeordnet, nie überschrieben. Welche Design-Einstellungen sichtbar sind, prüfen. |
| 6.9 | Dunkelmodus der Umfrage | `darkMode` | vorhanden | — | |
| 6.10 | Benachrichtigungen an Admin (neue Antwort, neuer Teilnehmer) | `notifyOnNewAnswer`, `notifyOnNewUser` | vorhanden | — | |
| 6.11 | Umfrage kopieren | `Headline.vue` (`onDuplicate`, `confirmCopy`), Menüpunkt „Kopieren“ nur für den Ersteller | vorhanden (auf `main`; Browser-Prüfung offen): Menüpunkt „Kopieren“, Dialog „Willst Du eine Kopie dieses Projektes erstellen?“ mit Schalter „Projekt Optionen kopieren“ (aus; bei Gästelisten ohne Schalter), Kopie öffnet im Editor | — | **Korrektur:** frühere Fassung „in Legacy nicht gefunden“ war falsch. Übernommen: Titel (unverändert), Beschreibung, Typ, alle Einstellungen, Hintergrund; Termine/Optionen nur mit Schalter (samt Ort und Plätzen). Nicht übernommen: Teilnehmer, Antworten, Kommentare, Einladungen, Aufgaben, Frist, geschlossen, Design, Zusatzfelder, Endergebnis. Legacy arbeitet nur im Browser (localStorage); hier legt `usePollCopy` die Umfrage über `POST /new` an, Termine über `POST /poll/choices` ohne IDs (`/new` würde Ort und Plätze verwerfen). Die Logik steht in `utils/pollCopy.ts` und `composables/usePollCopy.ts` und lässt sich ins Vue-3-Redesign kopieren. |

## 7 · Dashboard, Export, Betrieb

| # | Funktion | Legacy | Rewrite-Status | Prio | Anmerkung |
|---|---|---|---|---|---|
| 7.1 | Dashboard: eigene / eingeladene Umfragen | `MyDashboard`, `MyPolls` | vorhanden (`HomeView`); Zählung der Gästeliste weicht ab | — | **Fund aus dem Probelauf (6. Oktober 2026):** die Karte der Gästeliste zeigt „Personen kommen“ als `confirmations + guests`. Das Backend zählt die Gäste in `confirmations` schon mit (`Poll::updateDashboardKPI`: Person plus Gäste), die Gäste sind also doppelt gezählt. Die klassische Oberfläche zeigt `confirmations − guests` (die Gäste in Klammern, wenn Stimmen je Termin begrenzt sind), `frontend-v3` hat das schon so korrigiert. Soll: wie klassisch (A-23): Terminumfrage und Textumfrage zeigen die Zahl der Teilnehmenden, die Gästeliste „offen“, „kommen“, „abgesagt“. Wird beim Schnitt in `frontend-v3` korrigiert. **Removed until the backend sends it (6 Oct 2026, PO):** the "answered" count (date and text polls) and "Zusagen" (courses) on the dashboard cards: `/api/polls` fills `confirmations`/`declines` from `participants.choice`, which only guest lists use, so both always showed 0. They need an answered count from the backend (`getMyPolls`), which needs Mitko's agreement. |
| 7.2 | Kontakte: Liste, Suche, CRUD | `Contacts/*`, `/contacts*` | vorhanden / prüfen | P1 | CSV-Export clientseitig noch offen. |
| 7.3 | Kontakte: Import (CSV/XLSX) | `ContactsUpload`, `POST /contacts/upload` | vorhanden | — | |
| 7.4 | Kontakte: Tags | — | fehlt (Backend fehlt auch) | P3 | Neu, kein Legacy-Feature. |
| 7.5 | **Excel-Export der Umfrage** | `POST download-excel/{id}`, `PollExportService` | vorhanden (Korrektur: stand fälschlich auf „fehlt“); Überarbeitung auf `main` (Browser-Prüfung offen) | — | Der Rewrite baut die Datei im Browser (`utils/excelExport.ts`, Datei schreibt `composables/useExcelExport.ts`, die Bibliothek `xlsx` wird erst beim Export geladen). Kein Backend-Aufruf, kein `export/`-Ordner auf dem Server. **Behoben auf dem Branch:** Textumfragen exportierten die Optionstexte nicht (Spaltenköpfe fehlten); Gästeliste hat jetzt ihren Aufbau wie im Legacy (Offen/Zusagen/Absagen, Kommt/Bringt mit/Anmerkung, Termin und Ort); Typname in der Kopfzeile je Umfrageart (Terminabstimmung, Eventeinladung, Allgemeine Abstimmung, Kursplaner); „Anzahl JA“ zählt Ja und Wenn nötig wie das „(JA)“ im Legacy. **Bewusst anders:** keine Zellfarben (die freie `xlsx`-Bibliothek kann keine Formate schreiben; bräuchte `xlsx-js-style` oder `exceljs`), nur Deutsch statt Sprache des Erstellers, Zusagen bei der Gästeliste zählen jede Person plus Gäste. Kursplaner im Legacy nicht vorgesehen, hier als Terminraster. |
| 7.6 | Druckansicht | (Rechnung) | fehlt | P3 | Nur Rechnung (2.2); keine Umfrage-Druckansicht in Legacy gefunden. |
| 7.7 | Feedback-Formular | `POST /feedback` | fehlt | P3 |  **Backend vorhanden:** `POST /api/feedback` (öffentlich); es fehlt nur das Frontend. |
| 7.8 | Chat | `/chat/token`, `/chat/me`, `ChatTest.vue` | fehlt | P3 | Vermutlich Test-Feature, Nutzung klären. |
| 7.9 | Admin-Berichte | `/admin/reports/polls` | fehlt | P3 | Betrieb, nicht Endnutzer. Bleibt vorerst im Legacy-Backend. |
| 7.10 | E-Mails (Einladung, Erinnerung, Kommentar, Verifizierung …) | `app/Mail/*` | Backend — unverändert | — | Nur betroffen, wenn der Rewrite Payloads/Links ändert. Mail-Links verweisen auf Legacy-Pfade (siehe 1.12). |

## 8 · Offene Backend-Blocker

| ID | Problem | Betrifft |
|---|---|---|
| G6 | `PUT /api/participant/name/{id}` liefert 500 auf Test | Teilnehmer umbenennen (4.1) |
| G8 | `key` / `adminKey` bei Bearer-Token-Auth `null` | Admin-Link (5.5) |
| #16 | Platzhalter für den persönlichen Link in der Einladung unklar | 5.1 |
| — | Server speichert kein Tags-Konzept für Kontakte | 7.4 |
| — | Gezielte Kommentare brauchen neue Spalten | 4.3 |

Siehe auch `docs/kulibri-backend-befunde_1.md`.

## 9 · Vertiefung Runde 2: Teilnehmer-Identifikation und Antwort-Regeln (1. Oktober 2026)

Abgleich von `ModalParticipantMixins.js` (Legacy) mit `PollDetailView.vue` (Rewrite). Nur Code gelesen, nicht im Browser getestet.

**Gleichwertig vorhanden**
- Wiedererkennung desselben Browsers über `localStorage['kulibri.participants']` (Liste aus `poll` + `participant`). Der Rewrite nutzt denselben Schlüssel, bestehende Geräte-Zuordnungen aus Legacy bleiben also gültig.
- „Geschwister"-Zeilen: mehrere Teilnehmer mit derselben E-Mail oder vom selben Gerät (`linkedParticipants`).
- Angemeldeter Nutzer wird über `user.id` als „eigene Zeile" erkannt.
- Temp-Teilnehmer („Deine Antwort") beim Öffnen und Abschicken. Bewusste Abweichung (Temp-Zeile wird umbenannt statt ersetzt) steht in `docs/requirements.md` Abschnitt 1.
- Das Server-Limit (pro Option, global) liest das Backend aus der Datenbank, nicht aus dem Request. Dass der Rewrite `limit: null` mitsendet, ist daher unkritisch (geprüft in `ParticipantController`).

**Abweichungen, die wir als neue Lücken führen**

| # | Funktion | Legacy | Rewrite | Prio | Anmerkung |
|---|---|---|---|---|---|
| 9.1 | Einladung „besucht" melden | `POST /invitation/visit/{id}`, `updateCurrentInvitation` | fehlt (kein Aufruf in `api.ts`) | P2 | Ohne den Aufruf bleibt der Einladungsstatus („besucht") im Admin-Bereich leer. Semantik offen, siehe `Design.md` im Backend-Repo. |
| 9.2 | Address already takes part (classic: dialog "Teilnehmer mit gleicher E-Mail": continue as an existing participant or create a new one) | `ModalSameEmailDialog`, `onSaveModal`, `POST /participant/email/{id}` | partial (branch `feat/teilnahme-code`): code step built, waits for backend F7 stage 2 | P1 | **Replaced by a code step (spec of 06.10.2026, `docs/requirements.md` 3m).** The classic UI jumps into the rows of an address without any proof; anyone with the public link can take them over. Now the server refuses such an address without proof (409 `participant.emailTaken`) and mails a code; the screen says only "Um sicher zu gehen, dass dir diese E-Mail-Adresse gehört, haben wir dir einen Code geschickt …" (never whether the address takes part). After the code: continue as one of the participants of the address (old and new answers compared before overwriting) or create a new one. Draft and form stay while the code is fetched; nothing is created before the code matches. Admins are exempt. The rewrite opts in with `requireEmailProof` (the classic UI is unaffected, decision 06.10.2026). Until the backend has it, a dev-only cheat code (`VITE_DEV_EMAIL_CODE`, `.env.example`) simulates the server; remove it after F7 stage 2. Course planner and guest list: wording/UI follow separately. QA B12.32 to B12.35. |
| 9.3 | Email compared without case | `participantEmail.toLowerCase()` | partial | P2 | The rewrite compares addresses trimmed and lower-cased (`normalizeEmail` in `utils/participantAccess.ts`) for grouping rows and for the code step. Whether the address is stored lower-cased is still to check (backend). |
| 9.4 | Fehlerdialog bei Limit-Überschreitung mit Details | `dlgMaximumVotesExceed`, `createLimitErrorString`, `oneOptionError` | vorhanden auf Branch `feat/limit-errors` (Browser-Prüfung offen) | — | **Korrektur (6. Oktober):** die Zeile war veraltet. `saveChecked` (`utils/choiceLimit.ts`) prüft seit dem 4. Oktober alle fünf Speicherwege der Terminumfrage (`PollDetailView`) und nennt die vollen Optionen oder „nur ein Ja“; die Antworten werden neu geladen. **Neu:** auch die beiden Speicherwege des Kursplaners (Buchung und Ändern einer Buchung, `CourseView`) werden geprüft; vorher meldete die Buchung Erfolg, obwohl ein Termin voll war. Die Texte stehen jetzt in den Übersetzungen (`pollLimit`, deutsch und englisch) statt fest im Code. **Abweichung:** eine Meldung in der Fehlerzeile statt eines Dialogs mit Liste (gleicher Inhalt). **Offen:** wie die Warteliste im Kursplaner mit „Termin voll“ zusammenspielt (10.4.2), und ob die Antwort für Kurse dieselbe Form hat (nicht gegen das Backend geprüft). |
| 9.5 | Personal link (invitation link) opens its row | `curInvitation`, `changeUrlToInvitationLink`, `findParticipantsWithSameEmail` via `invitation.participant_id` | partial (branch `feat/teilnahme-code`) | P1 | The rewrite now resolves the second URL segment as invitation id first (the id in the mailed links), then as participant id (links the rewrite made). Rows opened via a link are remembered on the device, so a second link keeps the first row accessible (bug 3). Still open: the rewrite builds its own links from the participant id, which every visitor can read in the poll response (backend finding F8); links should use the invitation id once the backend no longer serves other participants' invitations. Depends on 1.12. |
| 9.6 | Teilnahme-Link per E-Mail an Teilnehmer | `participationLinkSubject`, `participationLinkMessage` | vorhanden | P2 | Legacy bietet nach dem Abschicken an, den persönlichen Link per Mail zu erhalten. Rewrite zeigt den Link im „Geschafft!“-Dialog. Seit 06.10.2026 schickt es beim Abschicken mit E-Mail Betreff und Text wie im Legacy mit (vorher leer, der Server verschickte eine leere Mail), und der Dialog nennt die Adresse. Ebenso beim Hinzufügen durch den Admin. QA B12.31. Der Link in der Mail ist der Einladungslink, siehe 9.5. |
| 9.7 | `PUT /participant/{id}` vs. `POST /v2/participant/{id}` | Legacy nutzt beide | Rewrite nutzt v2 und `PUT /participant/name/{id}` | — | Nur Hinweis: Verhalten bei Wartelisten-Nachrücken (Backend rückt automatisch nach, wenn jemand storniert) in Workshops im Browser verifizieren. |
| 9.8 | Eigene Zeile bei verdeckter Umfrage | `isCurrentUser` über E-Mail und Gerät | teilweise | P2 | Offen laut requirements 3i (Cookie-Wiedererkennung). |

**Offene Bereiche aus dieser Runde** wurden in Abschnitt 10 abgearbeitet.

## 10 · Runde 3: Editor, Workshop, Mobil, Sprache (1. Oktober 2026)

Repo-Stand `e2f06e2` (Abgleich der 29 neuen Dateien seit `0ad7c25` und der in Abschnitt 9 offenen Bereiche). Nur Code gelesen, nicht im Browser getestet. Seitdem erledigt: Umfrage schließen mit finalen Optionen (6.3, 6.13), Aufgaben (4.5, 4.6, 10.3.3), gemeinsame Seitenleiste (`REFACTORING-ROADMAP.md` R-01).

**Seit dem ersten Abgleich geschlossene Paritätslücken:** keine. Geändert hatten sich Testlogin entfernt, Architektur- und Sicherheitstests, Vitest, Design-Bereich im Editor (Live-Vorschau, Farben, Logo-Höhe), Hilfedialog beim Kontaktimport, Supabase-Reste entfernt.

### 10.1 Statuskorrekturen aus Runde 3 (Zeilen oben angepasst)

| # | Vorher | Jetzt | Grund |
|---|---|---|---|
| 1.1 | teilweise | teilweise (präzisiert) | Registrieren ist im Login-Dialog; Fehlertexte vom Backend fehlen. |
| 1.5 | prüfen | vorhanden | Profil in der Seitenleiste `ProfilePanel` (Name, E-Mail, Sprache, Rechnungsdaten), Passwort in `PasswordPanel`. |
| 3.10 | teilweise | vorhanden | Editor setzt `choice.limit` (Plätze pro Termin: je Slot / alle / je Tag). |
| 3.8, 3.16, 6.5 | prüfen | fehlt | Kein Schalter bzw. keine Funktion gefunden. |
| 4.7 | teilweise | fehlt | Co-Admin-Schalter ist nur ein Teaser. |
| 4.9 | vorhanden (Branch) | fehlt | Im Legacy kein Teaser, sondern eine Funktion für jeden Ersteller; im Rewrite gebaut, Browser-Prüfung offen. |

### 10.2 Editor-Einstellungen

Alle Optionen der Legacy-Umfrage (`options.*`) gegen die Schalter in `PollEditorView.vue`:

| # | Einstellung | Legacy | Rewrite | Prio | Anmerkung |
|---|---|---|---|---|---|
| 10.3.1 | „Nur eine Option pro Teilnehmer“ | `oneChoicePerParticipant`, `EditPollOptions.vue` | vorhanden auf Branch `feat/one-option-setting` (Browser-Prüfung offen) | — | Schalter „Teilnehmende können nur 1 Option wählen“ im Editor (Texte aus dem Legacy, deutsch und englisch über `t()`), bei Termin- und Textumfragen; wird geladen und gespeichert. Wie im Legacy schaltet er „Weiß noch nicht“ aus und sperrt diesen Schalter, solange er an ist; eine ältere Umfrage mit beiden Einstellungen speichert „Weiß noch nicht“ als aus. Die Teilnahme-Seite beachtete den Wert schon (`PollDetailView`: ein „Ja“ löscht die anderen, `saveChecked` meldet „nur ein Ja“). **Nicht angeboten bei Gästeliste (Typ 2) und Kursplaner:** der gespeicherte Wert bleibt dort unberührt; ob das Legacy ihn bei Kursen anbietet, ist nicht gegen die laufende klassische Oberfläche geprüft. **Backend-Befund G17:** mit Platzlimits prüft das Backend die Einstellung nicht (absichtlich übersprungen, Kommentar im Code). |
| 10.3.2 | „Nicht beantwortet = Nein“ | `setDefaultAsNoForNotAnswered` | fehlt im Editor (Detailansicht wertet aus) | P2 | Gleiches Muster wie 10.3.1. |
| 10.3.3 | Aufgaben erlauben | `allowTasks` | vorhanden (auf `main`) — Schalter im Editor, nicht bei Kursumfragen | — | Siehe 4.5. |
| 10.3.4 | Buchungsmodus | `bookingModePoll` | wird nicht verwendet; Verzweigung läuft über `poll.type` | — | Bewusst so (API-Vertrag Z. 397–399). Nur prüfen, ob Legacy-Umfragen mit `bookingModePoll` und Typ ≠ 4 korrekt dargestellt werden. |
| 10.3.5 | Passwortschutz | `privacy` | kein Legacy-Feature (siehe 6.5) | — |  Entfällt wie 6.5 (kein Legacy-Feature). |
| 10.3.6 | Zähler ausblenden | `showCounts` | fehlt (siehe 3.8) | P3 | |
| 10.3.7 | Zeitzone wählen | `timeZone` | fehlt (siehe 3.14) | P3 | |
| 10.3.8 | Löschen der Umfrage im Editor | `DELETE /poll/{id}` | vorhanden (auf `main`, siehe 6.4; die Schaltfläche sitzt in der Kopfleiste der Umfrage, nicht im Editor) | — | |
| 10.3.9 | Archivieren aus dem Editor | | nur im Dashboard (`HomeView`) | P3 | |

Vorhanden und im Editor einstellbar: Wenn-nötig/Weiß-nicht-Antworten, Pflicht-E-Mail, Benachrichtigungen (neue Antwort, neuer Teilnehmer), Antworten nach dem Absenden sperren, Kommentare, Dunkelmodus, Plätze pro Termin, Gäste-Limit, Verdeckt, Statistik für Teilnehmer, Frist, automatische Erinnerungen (5/3/1 Tage), Meldefrist vor dem Termin, Teilnehmerfelder (Pflicht, öffentlich/nur Admin), Hintergrund, Logo, Design speichern.

### 10.3 Workshop / Kursplaner (`CourseView.vue`)

Gleichwertig vorhanden: Termine als Karten, Kapazitätsbalken, Auswahl mit Zusammenfassung, Warteliste (Teilnehmer wird bei Kapazität oder abgelaufener Meldefrist auf die Warteliste gesetzt), „Meldefrist vor dem Termin“.

| # | Funktion | Legacy | Rewrite | Prio | Anmerkung |
|---|---|---|---|---|---|
| 10.4.1 | Admin entfernt einen Teilnehmer aus einem Termin | `cancelAttendeeBooking` → `POST /participant/cancelBooking/{id}` mit `canceled_choice_id` | fehlt | P1 | Siehe 4.10. Das Backend rückt dabei automatisch Wartelisten-Teilnehmer nach (`ParticipantController`, Kommentar zum Nachrücken). Das Frontend muss die Liste danach neu laden. |
| 10.4.2 | Warteliste wird im Frontend entschieden | Backend entscheidet über Limit und Nachrücken | Frontend setzt `waitlist` selbst (Kapazität, Meldefrist) | P1 | Risiko: Zwei Personen buchen gleichzeitig den letzten Platz, und die Ansicht kann veraltet sein. Das Backend prüft das Limit zwar selbst, aber der Rewrite muss dessen Antwort auswerten. Im Browser mit zwei Sitzungen verifizieren, was das Backend bei Überbuchung zurückgibt (HTTP 200 + `{ error }`, siehe 9.4). |
| 10.4.3 | Terminwiederholung | — (in Legacy nicht gefunden) | „Termine automatisch wiederholen“ im Editor | — | Neu im Rewrite. |
| 10.4.4 | Admin-Teilnehmerliste je Termin (inkl. Statistik) | `RenderAdminStats`, `RenderWorkshopAtendees` | prüfen | P2 | Im Browser abgleichen. |

### 10.4 Mobil

Legacy hat eigene Mobil-Komponenten (`RenderMobile*`). Der Rewrite schaltet in `PollDetailView` per `matchMedia('(max-width: 768px)')` die Standardansicht auf „Termine“ um, nutzt sonst Tailwind-Breakpoints. Ob alle Funktionen mobil erreichbar sind (Kommentare, Einladen, Teilnehmer bearbeiten, Editor), ist nur im Browser prüfbar: **Zeile 3.17 bleibt `prüfen`, Priorität P1.**

### 10.5 Sprache und Übersetzung

- UI-Texte sind überwiegend fest auf Deutsch in den Views verdrahtet; `$t(...)` kommt in `PollDetailView` 39-mal vor, in den anderen Views je 1 bis 9-mal. Neue Bausteine (Schließen-Panel, Aufgaben, Seitenleiste) nutzen `t()`.
- Das Profil bietet de/en/fr zur Auswahl, ohne dass diese Sprachen existieren. Entweder die Auswahl auf vorhandene Sprachen begrenzen oder Übersetzungen nachliefern (Legacy hat `de.json` und `en.json`).
- Die Texte des Backends (E-Mails, Fehlermeldungen) richten sich nach `user.locale` und sind davon unabhängig.

### 10.6 Premium-Teaser ohne Funktion

Im Rewrite gibt es 13 „PREMIUM“-Marker. Mindestens „Erinnerung senden“ und „Co-Admins“ (Panel) sind Attrappen, „Erinnerungsmail an Teilnehmer“ im Editor ist dagegen funktional. Zusammen mit 2.4 entscheiden, welche Marker bleiben. Kunden, die im Legacy diese Funktionen nutzen, finden sie im Rewrite nicht.

## 12 · Anmeldeseiten: Vergleich mit der klassischen Oberfläche (2. Oktober 2026)

Beschluss: Anmelde- und Konto-Seiten übernehmen zuerst **Aussehen und Ablauf der klassischen Oberfläche**, in Vue 3 mit Tailwind (nicht als Portierung des Vue-2-Codes). Texte stammen wortgleich aus `frontend/src/locales/de.json` und `en.json` (`src/i18n/auth.ts`). Jede Zeile ist durch einen Test gedeckt (`authPages.spec.ts`, `authValidation.spec.ts`, `authErrors.spec.ts`, `authRoutes.spec.ts`, `useLocale.spec.ts`, `auth.spec.ts`, `accountRecovery.spec.ts`).

Stand: auf `main`. Der Vergleich mit Bildschirmfotos der alten Seite ist für Reiter, Schrift und Knopf erfolgt, die übrige Browser-Prüfung ist offen.

### Aussehen

Die Werte stammen aus dem Quelltext der klassischen Oberfläche (`views/user/Login.vue`, `components/Login/*`, `components/UI/NavigationTabs.vue`, `~Global/Buttons/Btn.vue` und `BtnLink.vue`, `~Global/InputFields/InputTextValidated.vue`, `styles/*.scss`), nicht aus dem Gedächtnis. Frühere Fassung dieses Abschnitts (Branch `feat/auth-classic-ui`) stand bei Reitern, Feldern und Knöpfen auf „gleich“, ohne dass sie geprüft waren; das war falsch und ist hier behoben (Branch `fix/auth-classic-details`).

| Merkmal | Legacy | Rewrite | Abweichung |
|---|---|---|---|
| Seite und Karte | hellgrau `#f5f5f7`, weiße Karte, höchstens 355 px, Radius 10 px, Schatten, 20 px Innenabstand, Abstand 10 % von oben | gleich (`.k-auth` in `index.css`) | feste helle Farben statt `--k-*`, mit Absicht: die klassischen Seiten haben keinen Dunkelmodus |
| Schrift | Roboto; Bootstrap-Maße in rem (Knopf .875 rem, Abstände) bei **16 px Wurzelgröße**: `layout.scss` enthält zwar eine mit dem Fenster wachsende Größe (14 bis 20 px), die alte Seite zeigt Knopf und Link-Knopf aber mit 14 px, das ergab der Vergleich der Bildschirmfotos | gleich: Roboto, `--u` = 16 px als „1 rem“ | Roboto kommt zusätzlich zu Inter von Google Fonts (die App lädt dort schon Schriften). Frühere Fassung (fließend bis 20 px) war etwa 25 % zu groß |
| Primärfarbe | `rgb(238,69,64)`, Hover `#eb231d` | gleich, nur auf diesen Seiten | Die Bolt-Seiten verwenden Orange |
| Logo | 76 × 23 px, mittig, 1,5 rem Abstand darunter | gleich | |
| Sprache | Weltkugel, Kürzel, Bootstrap-Menü Deutsch/Englisch oben rechts, 16 px | gleich (Bootstrap-Standardwerte für Menü und Einträge) | übersetzt sind nur diese Seiten |
| Reiter | Schrift der Klasse `app-standardpage-tab`: **Gewicht 600**, 14 px (ab 480 px Fensterbreite 16 px); kein Innenabstand, 0,5 rem (plus 5 px) Abstand, **nur der aktive Reiter hat eine 2 px rote Linie** unter sich, beide Reiter gleich dunkel | gleich | früher: Linie über die ganze Karte, aktiver Reiter rot und fett, größerer Innenabstand; danach (Fassung vom 2. Oktober) noch Gewicht 400 und 1 rem statt 600 und 14/16 px, bis ein Vergleich der Bildschirmfotos es zeigte |
| Felder | Beschriftung 12 px, darunter **nur eine 1 px graue Linie** (`#adb0b6`, bei Fokus 2 px rot), kein Rahmen, kein Hintergrund, 16 px, 0,5 rem Abstand um das Feld; Inhalt wird beim Fokus markiert | gleich | früher: umrahmte Felder mit runden Ecken |
| Passwort anzeigen | Auge rechts im Feld, **dunkelgrau**, Font Awesome 22 px (etwa 27 px breit), zum Anzeigen/Verbergen | gleich (`Eye`/`EyeOff` aus lucide, 26 px, `#515154`) | Text für Screenreader ergänzt; Farbe nach dem Bildschirmfoto der alten Seite (die Klasse im Quelltext lässt Rot erwarten) |
| Fehlermeldung | 12 px, `#dc3545`, unter dem Feld | gleich | |
| Knopf | Bootstrap `btn btn-sm btn-primary btn-block`: 0,875 rem, Innenabstand .25 × .5 rem, Radius .2 rem, Text weiß, Gewicht 500 | gleich | früher: 42 px hoch, Radius 5 px |
| Ladeanzeige am Knopf | wachsender Punkt (`spinner-grow-sm`) links vom Text, Text bleibt | gleich (`.k-auth-grow`) | bei „verringerte Bewegung“ ohne Animation |
| „Passwort vergessen?“ | Link-Knopf (`btn btn-sm btn-link`), **links** unter dem Passwortfeld | gleich | früher: rechtsbündig |
| Punkt unter dem Anmelde-Text | steht immer da (`v-if="1"` im Quelltext, vermutlich ein Überbleibsel) | nur beim Bestätigen einer E-Mail | bewusst nicht übernommen; von Mitko am 2. Oktober 2026 bestätigt („den Punkt brauchen wir nicht“) |

### Anmelden

| Fähigkeit | Legacy | Rewrite | Abweichung |
|---|---|---|---|
| Intro-Text „Die Registrierung bei kulibri ist kostenlos.“ | ja | ja | |
| Prüfung E-Mail-Format („Ungültige Email Adresse“) | ja | ja | |
| Leeres Passwort („Ungültiges Passwort“) | ja | ja | |
| Leerzeichen am Rand von E-Mail und Passwort abschneiden | ja | ja | |
| Enter schickt ab, Ladeanzeige am Knopf | ja | ja | |
| Auge zum Anzeigen des Passworts | ja | ja | siehe Aussehen |
| Inhalt der Felder wird beim Fokus markiert | ja | ja | |
| Falsche Zugangsdaten | zwei Meldungen („Konto nicht vorhanden“ / „Ungültiges Passwort“) | eine Meldung | die Token-Route unterscheidet beides nicht; zugleich kein Konto-Raten |
| Unbestätigte Adresse | Meldung `auth.unverified` | „Deine Email Adresse ist noch nicht bestätigt …“ | der Rewrite erkennt es am 403 nach der Anmeldung, bis das Backend es selbst abweist (A2) |
| „Passwort vergessen?“ | ja | ja | |
| Weiter nach der Anmeldung | `?url=` oder Dashboard | gleich, `?url=` nur für Pfade dieser Seite | Schutz vor Weiterleitung auf fremde Seiten |
| Sprache des Kontos gilt nach der Anmeldung | ja | ja | `fr` u. a. ändern nichts, weil nur de/en übersetzt sind |
| „Mit Google fortfahren“ unter dem Knopf | ja | ja (`GoogleButton.vue`, Rahmen 1 px, Radius 5 px, Zeichen links) | der Knopf öffnet das Google-Fenster und meldet über `POST /api/auth/google` mit einem Token an (A3), statt zur Weiterleitung der klassischen Oberfläche zu führen; der Nutzer bleibt im Rewrite. Braucht `VITE_GOOGLE_CLIENT_ID`, abschaltbar mit `VITE_GOOGLE_LOGIN_ENABLED=false` |
| Abmelden | Sitzung beenden | `POST /api/logout` widerruft das Token, lokal wird es immer verworfen | |

### Registrieren

| Fähigkeit | Legacy | Rewrite | Abweichung |
|---|---|---|---|
| Felder Name, E-Mail, Passwort (keine Wiederholung) | ja | ja | |
| Name mehr als 2 Zeichen („Ungültiger Name. Bitte erneut versuchen.“) | ja | ja | |
| Passwort 8 bis 128 Zeichen | ja (Prüfung ab 8) | ja (8 bis 128) | |
| Hinweis „Mit Deiner Registrierung akzeptierst Du die AGB und die Datenschutzrichtlinie“ mit Links | ja | ja | Adresse der Marketing-Seite aus `VITE_MARKETING_URL`; ohne sie steht der Satz ohne Links |
| Vergebene Adresse („Ups! Deine Email Adresse ist bereits registriert.“) | Text aus der Antwort | gleich | |
| Erfolgsseite „Überprüfung der Email Adresse“ mit Erklärung | ja | ja | |
| Keine Anmeldung nach dem Registrieren | ja | ja | |
| „Mit Google fortfahren“ unter dem Knopf (nicht auf der Erfolgsseite) | ja | ja | wie bei „Anmelden“ |
| Funktioniert gegen das Backend | ja | ja (seit A1) | immer an; der frühere Schalter `VITE_REGISTER_ENABLED` und der Hinweis mit Link zur alten Registrierung sind entfernt (3. Oktober 2026) |
| „Warum sollte ich mich registrieren?“ | nur in der veralteten Ansicht `Register.depr..vue` | nicht gebaut | gehört nicht zur klassischen Oberfläche, die man heute sieht |

### Passwort vergessen, zurücksetzen, E-Mail bestätigen

| Fähigkeit | Legacy | Rewrite | Abweichung |
|---|---|---|---|
| Anfordern: Titel, Erklärung, E-Mail, Abbrechen/Abschicken | ja | ja | Tippfehler „Password“ im Titel korrigiert |
| Bestätigung „Email wurde verschickt“ mit Adresse und Hinweis „Sofern uns die Email … bekannt ist“ | ja | ja | |
| Unbekannte Adresse | zeigt die Meldung des Backends | dieselbe Bestätigung wie bei bekannter Adresse | kein Konto-Raten |
| Neues Passwort: **ein** Feld, „Passwort ändern“ (das Wiederholungsfeld ist im Legacy abgeschaltet, `v-if="0"`; ans Backend geht das Passwort zweimal) | ja | ja | frühere Fassung hatte fälschlich zwei Felder |
| Regel 8 bis 128 Zeichen, Meldung am Passwortfeld | ja | ja | |
| Ungültiger oder benutzter Link | Meldung des Backends | „Dieser Link ist nicht gültig! …“ mit „Neuen Link anfordern“ | |
| Danach „Dein Passwort wurde geändert“ auf der Anmeldeseite | ja | ja | |
| Bestätigungslink: Ergebnis über dem Anmeldeformular | ja | ja | Fehlertext deutsch statt des fest eingebauten englischen „Email verification failed“ |
| Alte Pfade `/signin`, `/signin/:id/:hash`, `/auth/requestreset`, `/reset`, `/auth/passwordreset/:token/:email`, `/passwordchanged` | ja | ja | `/register` nur, wenn die Registrierung freigeschaltet ist (sonst zeigte der Pfad auf sich selbst) |

## 11 · Stand und nächste Schritte (6. Oktober 2026)

**Für neue Entwickler:** zuerst `CLAUDE.md`, Abschnitt „Start hier“. Der manuelle Prüfstand je Funktion steht in `docs/qa-pruefliste.md`, offene Entscheidungen in `docs/po-fragen.md`.

**Seit der ersten Fassung (1. Oktober) auf `main`:** Umfrage schließen/öffnen mit finaler Option (6.3, 6.12, 6.13), Aufgaben (4.5, 4.6, 10.3.3), Umfrage kopieren (6.11), Konto löschen (1.6), Newsletter abmelden (1.7), AGB/Datenschutz als Weiterleitung (1.9), Nachricht an Teilnehmer (4.9, Branch `feat/participant-reminder`), Umfrage löschen (6.4, 10.3.8) mit Seite „Umfrage gelöscht“ (1.8, teilweise), Excel-Export je Umfrageart (7.5), Registrierung, E-Mail bestätigen, Passwort zurücksetzen und Google-Anmeldung im Aussehen der klassischen Oberfläche (1.1 bis 1.4, Abschnitt 12), Profil mit Rechnungsdaten und Firmenrechnung (1.5, 2.2), Mitgliedschaft (2.1, 2.3) und weitere Arbeit am Teilnahme-Ablauf, an großen Umfragen und an der Live-Aktualisierung (3.19) durch das Bolt-Team. Die Zeilen oben nennen den jeweiligen Stand; **„Browser-Prüfung offen“ heißt: im Code und in Tests vorhanden, aber noch nicht im Browser gegen die klassische Oberfläche abgeglichen.**

**Korrekturen an dieser Liste (6. Oktober):** „Umfrage mit Passwort schützen“ (6.5, 10.3.5) ist **kein** Legacy-Feature (`EditPassword.vue` ist das Passwort-ändern-Fenster des Benutzermenüs). „Admin-Link senden“ (5.5): `GET /send-admin-links` im Backend ist ein Überbleibsel für Umfrage 1, die Admin-Mail geht beim Anlegen der Umfrage hinaus; was die Zeile meint, ist neu zu klären. Konto löschen (1.6), Newsletter abmelden (1.7) und Feedback (7.7) brauchen **kein** Backend: die Routen gibt es (`POST /api/user/delete` mit Token, `POST /api/users/{id}/unsubscribe` und `POST /api/feedback` öffentlich); es fehlt nur das Frontend.

**Offene P1-Zeilen (Stand 6. Oktober):**

| Art | Zeilen |
|---|---|
| fehlt (Frontend) | 10.4.1 Admin storniert eine Buchung (Kursplaner) |
| teilweise | 9.2 address already takes part: code step (waits for backend F7 stage 2) · 1.2 Bestätigungsmail erneut senden (Backend braucht eine Route) · 1.8 gelöschte Einladung · 1.10 Sprache (nur die Anmelde- und Konto-Seiten sind übersetzt) · 1.12 alte Link-Formate · 3.4 Kursplaner · 5.5 Admin-Link · 10.4.2 Warteliste vom Backend entscheiden lassen |
| prüfen (Browser-Abgleich) | 3.15 · 3.17 Mobile Darstellung · 4.2 Teilnehmerfelder · 5.2 Einladen aus Kontakten · 6.2 Frist · 7.2 Kontakte · 9.5 Einladungs-Link |

**Wann wird die Zeile geschlossen? (Stand 6. Oktober, Vorschlag zur Abstimmung)**

Die klassische Oberfläche bleibt Rückfall: der Umschalter wirkt je Pfad (`v3_paths`) und je Umfrageart (`KULIBRI_UI_V3_POLL_TYPES`), und ein Pfad wird erst eingetragen, wenn `frontend-v3` ihn vollständig kann. Eine offene Zeile hält deshalb nur ihren Pfad oder ihre Umfrageart auf „klassisch“. Drei Zeitpunkte:

- **A, vor dem Port:** Bausteine, die der Port kopiert (Aufteilung der großen Ansichten, Laden in Abschnitten, Trennung der Anmeldung, Routenkarte). Liste: `REWRITE-INTEGRATION.md`, Abschnitt 7 (Repo kulibri-laravel).
- **B, nach dem Port, vor dem Umschalten des jeweiligen Pfads oder der Umfrageart:** direkt in `frontend-v3` gebaut (dortiges Übersetzungsformat, echte Sitzung, echte Mails).
- **C, nach dem Umschalten:** Rückstand, beide Oberflächen laufen.

| Zeile | Wann | Bedingung und Anmerkung |
|---|---|---|
| 1.10 Sprache | B, je Bildschirm | direkt in die JSON-Dateien von `frontend-v3` übersetzen, spart das spätere Umwandeln; im Rewrite gilt weiter „beim Anfassen übersetzen“ |
| 1.12 alte Link-Formate, 9.5 Einladungs-Link, 1.8 gelöschte Einladung | A (Routenkarte) und B | Karte jetzt, Prüfung mit alten Mails und dem `UiVersionResolver` erst im Port; vor dem Umschalten der öffentlichen Umfrage-Links |
| 9.2 Dialog „Teilnehmer mit gleicher E-Mail“ | B, vor dem Umschalten öffentlicher Links | braucht PO-21 und Backend F7 |
| 3.4, 10.4.1, 10.4.2 Kursplaner | B, eigener Schritt | Umfrageart 4 bleibt klassisch, bis er fertig ist |
| 3.15, 3.17, 4.2, 5.2, 6.2, 7.2 (prüfen) | B oder C | Vergleich mit `?classic=1` an derselben Adresse und derselben Sitzung; mobile Darstellung in der fertigen Hülle |
| 5.5 Admin-Link | zuerst klären, dann B oder C | die Zeile meint unklar etwas; Ziel der Mail-Links ist Konfiguration (`KULIBRI_FRONTEND_URL`) |
| 1.2 Bestätigungsmail erneut senden | Backend, dann C | Route fehlt im Backend (Anforderung A8/B4) |
| Befunde G6 bis G17 | Backend-Spur | unabhängig vom Frontend |

Nicht verschoben wird, was der Port kopiert (A): sonst wird derselbe Code zweimal angefasst.

**Nächste Schritte**

1. PO ergänzt/ändert die Priorität und entscheidet die Zeilen mit `ENTSCHEIDUNG` (zuerst 2.4; für 2.1 siehe `docs/po-fragen.md`).
2. Bolt-Team und Entwickler: Zeilen mit P1 aus der Tabelle oben einplanen. Dazu Tests für die Teilnahme-Logik (Abschnitt 9) nachziehen.
3. Browser-Abgleich: Legacy und Rewrite parallel auf `test.kulibri.com` durchklicken, Ergebnisse hier und in `docs/qa-pruefliste.md` eintragen. Zeilen mit `prüfen` werden dabei zu `vorhanden`, `teilweise` oder `fehlt`.
