# kulibri-rewrite — Refactoring-Roadmap

Stand: 1. Oktober 2026 · Pflege: Bolt-Team, kulibri-Team (Mitko, Claude Code), PO

Dieses Dokument sammelt Verbesserungen an der Code-Struktur, die kein neues Feature sind, aber neue Features billiger und sicherer machen. Es ist die Warteschlange zu **Regel 13** in `CLAUDE.md` („Wiederverwendung prüfen und Umbau besprechen").

## So wird es benutzt

- **Wer etwas Wiederverwendbares sieht, trägt es hier ein** (Bolt, kulibri, Claude Code), mit Fundstellen im Code. Nicht stillschweigend selbst umbauen, wenn es mehr ist als eine kleine Extraktion im eigenen Änderungsumfang.
- Jeder Eintrag ist ein **PBI** mit Nutzen, Umfang, Akzeptanzkriterien und Aufwand. Der PO setzt die Priorität und den Zeitpunkt; ein PBI wird erst gebaut, wenn beide Teams es besprochen haben.
- Reihenfolge der Spalte `Status`: `Vorschlag` → `Besprochen` → `Eingeplant` → `In Arbeit` → `Fertig` (oder `Verworfen` mit Begründung).
- **Priorität** (Vorschlag, gehört dem PO): `P1` blockiert oder verteuert ein anstehendes Feature, `P2` lohnt sich bald, `P3` irgendwann.
- Beim Fertigstellen: Status ändern, Commit/PR eintragen und, falls sich eine Regel ändert, `CLAUDE.md` anpassen.

## Übersicht

| ID | Titel | Prio | Aufwand | Status |
|---|---|---|---|---|
| R-01 | Gemeinsame Seitenleiste (`SidePanel`) für Bearbeiten, Einladen, Schließen | P1 | klein bis mittel | Umgesetzt auf `feat/tasks`, wartet auf Review und Browser-Prüfung |

Weitere Kandidaten, noch nicht als PBI ausgearbeitet, stehen am Ende.

---

## R-01 · Gemeinsame Seitenleiste (`SidePanel`) für Bearbeiten, Einladen, Schließen

**Status:** Umgesetzt auf `feat/tasks` (Bolt-Review und Browser-Prüfung offen) · **Prio (Vorschlag):** P1, weil die nächsten Features (Aufgaben: anlegen, zuweisen, Details) drei weitere Seitenleisten brauchen · **Aufwand:** klein bis mittel, kein neues Verhalten

**Weitere Nutzer (3. Oktober 2026):** „Mein Profil“ (`ProfilePanel`) und „Passwort ändern“ (`PasswordPanel`), beide aus dem Nutzermenü, nutzen `SidePanel` ebenfalls, mit der Standard-Fußzeile (Abbrechen, Speichern). Weil die Kopfzeilen sticky sind und die Leiste `fixed` ist, hängen sie dort per `Teleport` am Ende von `<body>`.

### Ist-Zustand

Die Seitenleisten, die von rechts in die Umfrage hineinfahren, sind in `PollDetailView.vue` jeweils einzeln ausgeschrieben:

| Leiste | Fundstelle | Breite | Kopfzeile |
|---|---|---|---|
| Bearbeiten (Editor) | `PollDetailView.vue` Abschnitt „Admin editor panel", enthält `PollEditorView` im Einbett-Modus | `max-w-2xl`, `overflow-hidden` | im Editor selbst |
| Einladen | `PollDetailView.vue` Abschnitt „Invite panel" | `max-w-xl`, `overflow-y-auto` | eigene Kopfzeile mit X und Titel (`admin.inviteTitle`) |
| Schließen/Öffnen | `ClosePollPanel.vue` im Branch `feat/close-panel-final-options` (Hülle in `PollDetailView.vue`) | `max-w-xl`, `overflow-y-auto` | eigene Kopfzeile mit Abbrechen, Titel, Fertig |

Jede Leiste wiederholt dieselbe Hülle: Hintergrund-Abdunklung mit Klick zum Schließen, `aside` mit Rand und Schatten, die Animation (`Transition name="panel"`, `k-slide-in`), die Farben (`--k-page`, `--k-line`) und die Kopfzeile. Die Unterschiede (Breite, Hintergrundfarbe der Abdunklung `bg-black/40` gegen `bg-black/60`, Kopfzeile) sind nicht begründet, sondern gewachsen.

Im Legacy-Frontend gab es dafür eine gemeinsame Komponente (`SidePanel.vue`, benutzt von `EditClose`, `EditInvites` und den anderen Bearbeiten-Bereichen).

### Problem

- Jede neue Leiste (Aufgabe anlegen, Aufgabe zuweisen, Aufgabendetails, später die zweiphasige Zuteilung) kopiert die Hülle erneut.
- Änderungen am Verhalten (Escape-Taste, Fokus, Scrollsperre der Seite dahinter, Rückgabe des Fokus, Animation) müssten an jeder Stelle einzeln gemacht werden. Heute gibt es in den Views weder Escape-Handling noch Scrollsperre noch `aria-modal` (Suche nach `Escape`, `keydown`, `aria-modal` ohne Treffer).
- Für die spätere Übernahme in `frontend-v3` ist eine einzelne kleine Komponente leichter zu übernehmen als drei eingebettete Blöcke in einer 4000-Zeilen-View (Regel 3).

### Ziel

Eine Komponente `src/components/SidePanel.vue`, die die Hülle übernimmt:

- Props: `open` (oder `v-if` durch den Aufrufer), `size` (`md` = `max-w-xl`, `lg` = `max-w-2xl`), `title`, optional `cancelLabel`, `confirmLabel`, `confirmDisabled`, `saving`.
- Events: `close` (Klick auf Abdunklung, Escape, Abbrechen), `confirm` (Fertig).
- Slots: Standard-Slot für den Inhalt; optional `header` für Aufrufer, die eine eigene Kopfzeile behalten wollen (Editor im Einbett-Modus).
- Verhalten, das einmal zentral gelöst wird: Escape, Klick auf die Abdunklung, Scrollsperre des Hintergrunds, Fokus in die Leiste und zurück, `role="dialog"`/`aria-modal`/`aria-labelledby`, Animation.
- Farben nur über `--k-*` (Regel 12), Texte über `t()` (Regel 7).

### Umfang

1. `SidePanel.vue` anlegen, mit Tests (Escape schließt, Klick auf Abdunklung schließt, `confirm` nur bei `confirmDisabled = false`).
2. **Schließen** auf `SidePanel` umstellen (kleinster Fall, gleiche Kopfzeile wie das Ziel).
3. **Einladen** umstellen. Die Kopfzeile wird zur Standard-Kopfzeile (X links, Titel); die Breite bleibt `md`.
4. **Bearbeiten** umstellen. Der Editor behält seine Kopfzeile über den `header`-Slot, bis ein eigener Schritt sie vereinheitlicht. Breite `lg`.
5. Nach jedem Schritt: gleiche Optik und gleiches Verhalten wie vorher, Vergleich per Bildschirmfoto.

### Nicht im Umfang

- Neue Funktionen in den Leisten.
- Die Bestätigungs-Dialoge und Modale in der Mitte des Bildschirms (Löschen-Rückfrage, Teilnehmer-Dialog, Kontaktauswahl). Das ist ein verwandter, aber eigener Kandidat (siehe unten).
- Das Aufteilen von `PollDetailView.vue` (Regel 3). Das Herauslösen der Hülle verkleinert die Datei, ersetzt den Schnitt aber nicht.

### Akzeptanzkriterien

- [x] Alle drei Leisten benutzen `SidePanel`; in `PollDetailView.vue` steht keine eigene `aside`-Hülle für sie mehr.
- [ ] Optik und Verhalten gleich wie vorher (Breite, Animation, Abdunklung, Kopfzeile), geprüft auf Desktop und Mobil. _(Browser-Prüfung offen)_
- [x] Escape und Klick neben die Leiste schließen sie. Es fragt nichts nach, auch nicht bei ungespeicherten Änderungen im Editor; das war beim Klick neben die Leiste schon so, jetzt gilt es auch für Escape.
- [x] Der Hintergrund scrollt nicht mit, solange eine Leiste offen ist.
- [x] `npm run typecheck`, `npm test` und der Architekturtest bleiben grün; neue Tests für `SidePanel` (`src/__tests__/sidePanel.spec.ts`).
- [x] Aufgaben benutzen `SidePanel` von Anfang an (`TaskFormPanel`, `AssignTaskPanel`).

**Umsetzung:** `src/components/SidePanel.vue` mit den Eigenschaften `open`, `title`, `size` (`md`/`lg`), `confirmLabel`/`cancelLabel`/`confirmDisabled`/`saving`, `backdrop` (`strong`/`soft`), `surface` (`page`/`muted`, der graue Editor-Hintergrund der Bolt-Vorlage mit festen Farben), `bodyScroll` und `showHeader` (der Editor bringt seine Kopfzeile selbst mit). Der Fokus geht beim Öffnen in die Leiste und beim Schließen zurück; eine Fokusfalle gibt es noch nicht. Unterschiede, die nicht vereinheitlicht wurden: Abdunklung `soft` für Editor und Schließen, `strong` für Einladen; die Frage an Bolt dazu bleibt offen.

**Optik-Überarbeitung (1. Oktober 2026, Browser-Prüfung offen):** `SidePanel` ist jetzt eine Spalte aus Kopfzeile, scrollendem Inhalt und Fußzeile.
- Kopfzeile: optional `icon` und `subtitle`, X immer rechts, Schatten erst nach dem Scrollen. Abbrechen und Fertig stehen in der Fußzeile (Slot `footer` ersetzt sie), nicht mehr oben. **Abweichung von EditClose im Legacy**, damit lange Formulare nicht zurückgescrollt werden müssen.
- Auf Mobil (< 640 px) fahren Leisten mit Kopfzeile als Sheet von unten herein; der Editor ohne Kopfzeile bleibt vollflächig.
- Fokusfalle (Tab bleibt in der obersten Leiste) und Animation beim Schließen sind neu; `prefers-reduced-motion` schaltet die Bewegung ab.
- Neue Bausteine: `OptionCard.vue` (Radio/Checkbox als Karte, in `ClosePollPanel` benutzt) und `PanelSection.vue`. Neue Tokens in `src/index.css`: `--k-radius-*`, `--k-shadow-panel|sheet|footer|header`. Die festen Farben der Fläche `muted` sind entfernt; `surface` ist ohne Wirkung und als veraltet markiert, weil beide Werte `--k-page` ergeben.
- Nicht umgesetzt: `aria-labelledby` (der Dialog behält `aria-label`, weil die Leiste ohne Kopfzeile keinen Titel im DOM hat). `TaskFormPanel` und `AssignTaskPanel` sind noch nicht auf `OptionCard`/`PanelSection` umgestellt; sie bekommen Kopf- und Fußzeile schon über die Hülle.

### Risiken und offene Fragen

- Der Editor läuft zusätzlich als eigene Seite (`/pr/:id/bearbeiten`) und eingebettet in der Leiste (`embed-poll-id`). Die Umstellung darf die eigenständige Seite nicht verändern.
- Mehrere Unterschiede (Abdunklung `bg-black/40` gegen `/60`, `overflow-hidden` gegen `overflow-y-auto`) wirken zufällig. **Frage an das Bolt-Team:** Gibt es einen Design-Grund, oder dürfen sie vereinheitlicht werden? Bis zur Antwort bleibt die Abdunklung über eine Eigenschaft einstellbar.
- Escape im Editor mit ungespeicherten Änderungen: heute nicht abgefangen (nicht geprüft). Erst als eigenes Thema behandeln.

### Rückmeldung Bolt

_(bitte hier eintragen: Einschätzung, Aufwand, Gegenvorschlag)_

---

## Weitere Kandidaten (noch nicht ausgearbeitet)

Fundstellen aus dem Abgleich vom 1. Oktober 2026. Zum PBI ausarbeiten, wenn sie anstehen.

| Kandidat | Fundstelle | Anmerkung |
|---|---|---|
| Schalter-Komponente (`ToggleSwitch`) | `PollEditorView.vue`: 11-mal dasselbe Markup (`w-[38px] h-[22px] …`) | **Umgesetzt (2. Oktober 2026, `feat/admin-panel-design`):** `src/components/ToggleSwitch.vue` ersetzt alle Schalter im Editor und in `CopyPollDialog.vue`. Entstanden beim Panel-Redesign zusammen mit `EditorSection.vue` (klappbarer Abschnitt). |
| Modale in der Bildschirmmitte (Rückfragen, Teilnehmer-Dialog, Kontaktauswahl) | `PollDetailView.vue`, `CourseView.vue`, `PollEditorView.vue`, `PollShareView.vue`: jeweils eigene `Transition name="panel"`-Blöcke | Verwandt mit R-01; gemeinsame `Modal`-Komponente prüfen, wenn R-01 steht. **Teilweise umgesetzt (1. Oktober 2026):** `src/components/ConfirmDialog.vue` (Titel, optionales Icon, Slot, Abbrechen/Bestätigen, `busy`, `error`, `hideCancel` für Hinweise) ersetzt die Rückfragen in `TasksTab.vue` (Löschen, Limit) und `CopyPollDialog.vue`; neue Rückfragen (z. B. Umfrage löschen) benutzen sie. **Offen:** die älteren Modale in `PollDetailView.vue`, `CourseView.vue`, `PollEditorView.vue`, `PollShareView.vue`, die auch Formulare enthalten und nicht einfach passen. |
| `PollEditorPanel.vue` entfernen oder reparieren (**erledigt, 4. Oktober 2026: gelöscht**) | 1098 Zeilen, wird nirgends importiert (steht nur auf der Liste im Architekturtest); sendet Optionen ohne `id`, was beim Speichern alle Optionen samt Antworten löschen würde (siehe Befund B3, `docs/kulibri-backend-befunde_1.md`) | Totcode mit Risiko. Entscheidung: löschen, bevor jemand ihn einbindet. |
| `PollDetailView.vue` aufteilen | über 4300 Zeilen | Regel 3; den Schnitt legt Bolt fest (Antwort-/Zeilenbearbeitung, Teilnehmerverwaltung, Teilen/Einladen, Kommentare). |
| Speichern der Umfrage-Details nur über `api.patchPollDetails` | `POST /poll/details` leert jedes fehlende Feld | Als Regel in `CLAUDE.md` aufnehmen, sobald das Bolt-Team zugestimmt hat; evtl. Test, der direkte Aufrufe von `updatePollDetails` in Views verbietet. |
| Formularfelder und Knöpfe vereinheitlichen (**größtenteils erledigt**, Oktober 2026) | `src/components/ui/`: `TextField`, `SelectField`, `TextAreaField` (mit `FormField` für Beschriftung, Fehler, Hinweis und ARIA) und `AppButton` (Knöpfe wie `.ke-btn`, Anmelde-Knöpfe). Umgestellt: Profil, Passwort, Rechnungsdaten, Anmeldung, Konto, Seitenleisten, Aufgaben, „Umfrage bearbeiten“ (Seite), Einladen, Kontakte, Kurs-Anmeldung. Eine Feldoptik (`.k-field-input`, Entwurf „Admin-Panels“: 42 px, 10 px Rundung, Fokus Orange mit Ring und Tastaturring). | **Erledigt auch:** Dialoge und Startkarte der Umfrage-Seite (Fehler am Feld, `participantForm.ts`), `CommentComposer` (statt drei Kopien), `SearchField` (Lupe und Löschknopf), `CopyField` (Link mit Kopieren-Knopf). Die zwei Such-Pillen in den Kopfzeilen sind `SearchField variant="inline"`; `PollEditorPanel.vue` ist gelöscht. **Damit gibt es keine rohen Textfelder mehr in den Views**; das Budget in `formFieldsGuard.spec.ts` ist leer. Der Test `formFieldsGuard.spec.ts` hält den Rest fest: Budget je Datei, nur kleiner. Die Beschriftungen in Großbuchstaben (`--k-ink-faint`, 2,2:1) sind damit weg. |
| Kontrast der orangen Knöpfe | `--k-coral` `#F2760A` mit weißer Schrift (Fußzeile von `SidePanel`, `ConfirmDialog`, viele Knöpfe) | 2,85:1, AA verlangt 4,5:1 für kleine Schrift (auch `--k-coral-dark` hat nur 3,5:1). Möglichkeiten: dunkleres Orange um `#BB5B08` (4,55:1 mit Weiß), die Markenfarbe behalten und dunkle Schrift darauf (`--k-ink`, etwa 5,1:1) oder so lassen. Auch Fehler- und Erfolgstext (`--k-error`, `--k-success`) liegen mit 4,2:1 knapp unter 4,5:1. **Entscheidung für PO und Bolt**, ändert das Aussehen der ganzen App. |
