diff --git a/docs/superpowers/plans/2026-10-16-kundenansicht-00-uebersicht.md b/docs/superpowers/plans/2026-10-16-kundenansicht-00-uebersicht.md new file mode 100644 index 0000000..1913d1e --- /dev/null +++ b/docs/superpowers/plans/2026-10-16-kundenansicht-00-uebersicht.md @@ -0,0 +1,501 @@ +# Kundenansicht neu: Umsetzungsplan, Übersicht und Verträge + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +> Diese Übersicht ist der Vertrag zwischen den Paketen. Namen, Signaturen, Datenformen, Endpunkte und Dateizuständigkeiten stehen nur hier und gelten für alle Paket-Pläne. Weicht ein Paket-Plan davon ab, gilt diese Übersicht. + +**Goal:** Die Kundenansicht des Portals wird weiß und schlank wie das Ticketsystem, bekommt die Handy-Leiste „Fahrstuhl-Zeile“ und die Funktionen mit „Meine/Entdecken“. Die Funktionen stecken im Abo und werden eingeschaltet statt gekauft. Bestätigungen schrumpfen auf das rechtlich Nötige. Alles liegt hinter einem Pilot-Schalter, bis der Inhaber umschaltet. + +**Architecture:** +- Die neue Oberfläche ist eine eigene Seite `public/neu/dashboard.html` mit eigenen ES-Modulen und CSS unter `public/neu/`. Der Server liefert sie unter derselben Adresse `/dashboard.html` aus, wenn der Pilot-Schalter für die Sitzung gilt. +- Die Funktionsmodule (`public/termine.js`, `shop.js` …) bleiben gemeinsam. Ihr Aussehen kommt aus dem CSS der jeweils geladenen Seite, ihr Verhalten unterscheidet sich nur über das Feld `modell` der Server-Antwort. +- Neue Server-Logik ist nur für Sitzungen mit neuer Ansicht aktiv (`neueAnsicht(req)`) oder rein additiv. Die alte Ansicht verhält sich bis zum Umschalten exakt wie heute. + +**Tech Stack:** +- Portal: Node 20 (Prod-Container `node:20-alpine`, Host 24), Express 4, Appwrite-REST (`server/services/appwriteAdmin.js`), Stripe SDK 19, Vanilla JS ohne Build. +- Tests Portal: `node --test` mit den Nachbauten aus `test/support/` (`appwriteMock`, `fakeStripe`, `testApp`, `fixtures`, `fakeDom`, `mailSink`) und Playwright-Skripte `test/ui/*.ui.mjs`. +- WOMS: React 18, Vite, Tests `node --test` über `tests/unit/load.mjs`. + +**Spec:** `docs/superpowers/specs/2026-10-15-kundenansicht-neu-design.md` (Commit d3d2849, Abschnitte 1–8 mit E1–E21 und C1–C15). Jeder Implementierer liest die Spec, diese Übersicht und seinen Paket-Plan. + +**Code-Karten** (Stand 16.10., nur gelesen): `/root/reports/kundenansicht-neu-2026-10-15/karten/` +- `huelle-tests.md`: dashboard, app.js, Router, Tests, Pins +- `funktionen-server.md`: Funktionen, Zustände, Bestellen, Aktivieren, Abo-Abgleich +- `abo-server.md`: Stundenkonto, Abo, Wechsel, Widerruf, Fassungen, Kundenart, Mails +- `woms-vorschau.md`: WOMS-Stellen, Vorschau + +Dazu die Navigations-Demo `/root/reports/kundenansicht-neu-2026-10-15/handy-leiste/final-2-frei.html`. + +--- + +## Ausführungsmodell + +``` +Phase A P0 Werkzeug, Pilot-Schalter, Vorschau-Gerüst (1 Agent, Hauptzweig) +Phase B parallel in eigenen Worktrees: + Strang UI: P1 Fundament+Hülle → P2 Website/Chat/Verträge + Strang Server: P5 Abo-Modell Kern → P6 Abo-Modell Betrieb + Strang Recht: P8 Rechtsrunde Fassung 6 (Entwürfe + Fassungs-Technik) + Strang WOMS: P9 WOMS (gegen die Verträge V4/V9; Portal-Teile von P9 erst nach Merge von P5) +Phase C Merge Server → Hauptzweig, dann Strang UI weiter: P3 Funktionen-Rahmen → P4 Funktionsmodule +Phase D Merge Recht + UI → P7 Bestellseiten schlank (Hauptzweig) +Phase E Integration, ganze Suite, Vorschau aktualisieren, Abschlussprüfung (mehrere Blickwinkel), Übergabe + → der Inhaber pusht (Schalter off = für Kunden unsichtbar), Pilot fürs Team, Rechtsrunde mit dem Inhaber +Phase F P10 Umschalten und Aufräumen (nur mit ausdrücklichem OK des Inhabers) +``` + +- **Ein Implementierer je Strang**, Modell `sonnet`. Die Tasks eines Strangs laufen der Reihe nach mit TDD. +- **Prüfungen:** keine Reviewer je Task. Jeder Paket-Plan endet mit einer Selbstprüfung: eigene Tests grün, Verträge abgehakt, `git status` sauber. Eine gemeinsame Abschlussprüfung gibt es in Phase E. Kleine Nachprüfungen mit `haiku`. Kein `opus`, kein `fable` (Memory „feedback-billige-modelle“). +- **Merge-Reihenfolge in den Hauptzweig:** P0 → Server (P5, P6) → Recht (P8) → UI (P1–P4) → P7. Konflikte entstehen nur in den Dateien, die die Zuständigkeitstabelle als „geteilt“ markiert. +- **Tags:** nach jedem Paket `kn-p` im jeweiligen Zweig. Nach dem Merge auf den Hauptzweig `kn-p-haupt`. + +### Pakete, Plan-Dateien, Worktrees + +| Paket | Plan-Datei (`docs/superpowers/plans/`) | Worktree | Branch | Start | +|---|---|---|---|---| +| P0 Werkzeug, Schalter, Vorschau | `2026-10-16-kundenansicht-01-werkzeug-vorschau.md` | `/home/webklar/apps/kundenbereich.webklar.com/dev-kundenansicht` | `feature/kundenansicht-neu` | sofort | +| P1 Fundament und Hülle | `…-02-fundament-huelle.md` | `…/dev-kn-ui` | `kn/ui` | nach P0 | +| P2 Website, Chat, Verträge | `…-03-website-chat-vertraege.md` | `…/dev-kn-ui` | `kn/ui` | nach P1 | +| P3 Funktionen-Rahmen | `…-04-funktionen-rahmen.md` | `…/dev-kn-ui` | `kn/ui` (rebased auf Hauptzweig nach Server-Merge) | nach P2 und Server-Merge | +| P4 Funktionsmodule | `…-05-funktionsmodule.md` | `…/dev-kn-ui` | `kn/ui` | nach P3 | +| P5 Abo-Modell Kern | `…-06-abo-modell-kern.md` | `…/dev-kn-server` | `kn/server` | nach P0 | +| P6 Abo-Modell Betrieb | `…-07-abo-modell-betrieb.md` | `…/dev-kn-server` | `kn/server` | nach P5 | +| P7 Bestellseiten schlank | `…-08-bestellseiten.md` | `…/dev-kundenansicht` | `feature/kundenansicht-neu` | nach Merge von UI und Recht | +| P8 Rechtsrunde Fassung 6 | `…-09-rechtsrunde.md` | `…/dev-kn-recht` | `kn/recht` | nach P0 | +| P9 WOMS | `…-10-woms.md` | `/home/webklar/apps/ticket.webklar.com/dev-kundenart` | `feature/kundenart` (WOMS, von `origin/test`) | nach P0 | +| P10 Umschalten und Aufräumen | `…-11-umschalten-aufraeumen.md` | `…/dev-kundenansicht` | `feature/kundenansicht-neu` | nach Phase E und OK des Inhabers | + +**Worktrees der Stränge** entstehen aus dem Stand nach P0, zum Beispiel: +`git -C /home/webklar/apps/kundenbereich.webklar.com/dev-kundenansicht worktree add ../dev-kn-ui -b kn/ui kn-p0` +- Danach `ln -s ../build/node_modules node_modules` im neuen Worktree. Der Symlink ist unversioniert, deshalb **nie `git add -A`**, Dateien immer einzeln hinzufügen. +- Der WOMS-Worktree entsteht von `origin/test`: `git -C /home/webklar/apps/ticket.webklar.com/build worktree add ../dev-kundenart -b feature/kundenart origin/test`. Danach `npm ci` mit Speicherbremse, siehe Global Constraints. +- Der WOMS-Zweig `fix/akquise-sichtbar` (`5f13553`, Worktree `dev-akquise-sichtbar`) ist nicht gepusht und ändert `CreateLeadForm` und `createLead`. P9 baut auf `origin/test`. Beim Merge in `test` gilt die Reihenfolge, die der Inhaber festlegt. Ist `fix/akquise-sichtbar` bis dahin in `test`, rebased P9 darauf. + +### Dateizuständigkeit (Konfliktschutz) + +Jede Datei hat genau einen Besitzer. Wer etwas aus einer fremden Datei braucht, nutzt die Verträge V1–V12. + +| Datei / Bereich | Besitzer | +|---|---| +| `server/services/kundenansicht/schalter.js` (neu), Route `/dashboard.html` in `server/index.js`, Konfig `config.kundenansichtNeu`, Feld `ansicht` in `/api/auth/me`; ein **Platzhalter** `public/neu/dashboard.html` für den Routentest (ab P1 gehört die Datei P1) | P0 | +| Werkzeug `/root/reports/kundenansicht-neu-2026-10-15/werkzeug/` (eigenes Git), `test/ui/kundenansicht-neu*.ui.mjs` | P0 legt an, P1–P4 und P7 ergänzen nur eigene Ansichten und Fixtures | +| `public/neu/dashboard.html`, `public/neu/tokens.css`, `basis.css`, `huelle.css`, `public/neu/dom.js`, `api.js`, `router.js`, `routen.js`, `huelle.js`, `leiste.js`, `konto.js`, `fristen-streifen.js`, `zaehler.js`, `etage.js`, `reiter.js` | P1 | +| `public/neu/ansicht-website.js`, `ansicht-chat.js`, `ansicht-vertraege.js`, `ansicht-stunden.js`, `ansicht-speicher.js`, `ansicht-gutschrift.js`, `ansicht-passwort.js`, `public/neu/ansichten.css`; `public/neu/ansicht-bestellen.js` und `ansicht-wechsel.js` als **1:1-Übernahme** der heutigen Bestell- und Wechselseiten im neuen Aussehen (alte Texte, Fassung 5, C14); öffentliche Seiten im neuen Stil unter `public/neu/oeffentlich/` (`login.html`, `kuendigen.html`, `widerrufen.html`, `offline.html`), die erst P10 ausliefert | P2 | +| `public/neu/ansicht-funktionen.js`, `funktion-einschalten.js`, `public/neu/funktionen.css`, `public/neu/bilder/*` | P3 | +| `public/neu/module.css` (neues Aussehen aller Funktionsmodule). In `public/termine*.js`, `shop*.js`, `buchungen*.js`, `zeiten*.js`, `funktionen.js` (nur News), `analyse*.js` und `funktion-bestellen.js` nur Verzweigungen nach `modell === 'abo'` und die Kurzlabels der Reiter (C2) | P4 | +| Umbau von `public/neu/ansicht-bestellen.js` und `ansicht-wechsel.js` auf das schlanke Muster (Fassung 6). In `server/routes/abo.js` nur die Pfade für Fassung 6. `server/services/planSwitch.js:351` mit Umgebung. `server/services/vertragsNachweis.js` (neu) | P7 | +| `server/services/kundenart.js` (neu), `server/services/aboFunktionen.js` (neu), `server/services/funktionEinschalten.js` (neu), `server/services/fristen.js` (neu), `server/routes/funktionen.js` (Katalog, Einschalten), `server/routes/fristen.js` (neu), `server/routes/auth.js` (nur `sanitizeCustomer` + Kundenart), `server/services/customerAdmin.js` (Kundenart), `server/services/featureRegistry.js` (Katalogfelder), `server/services/featureConsents.js` (Attribut `kundenart`), `scripts/kundenansicht-setup.mjs` (neu), Shop-/Unterkunft-Anfrage (Unternehmer aus Kundenart), Termine-Zustimmung ohne `gesundheit` im Abo-Modell | P5 | +| `server/services/hourLedger.js` (Vorschuss), `server/routes/team/customers.js` (Stundenkonto-Endpunkt), `server/services/siteFeatures.js` (`adminActivate` mit Abo-Prüfung und Abrechnung), `server/routes/admin/funktionenAdmin.js`, `server/services/aboFunktionenAbgleich.js` (neu), Wechsel mit gemerkter Funktion in `planSwitch.js` (nur `details.funktion`, `completeSwitch`, `markSwitchFailed`) und `GET /api/abo` (`wechselFunktion`), Ticket-Texte zum Stundenkonto, neue Mailvorlagen K38–K41 und T24–T25, Anpassung K02/K03/K22/K29/K30/T16 nur für das Abo-Modell | P6 | +| `docs/rechtliches/*-v6*.md` und neue Entwürfe, `public/vertragsunterlagen/v5/*` (Archiv), `public/neu/rechtliches/*` (neue Fassungen), `server/services/products.js` (Fassungstabelle), `config.abo.termsVersionNeu`, `scripts/build-legal-*`, `scripts/rechtliches-hochladen.mjs` (neu), `docs/rechtliches/kundenansicht-entscheidungen.md` (neu) | P8 | +| WOMS-Repo komplett | P9 | +| `scripts/kundenart-bestand.mjs` (neu), Umschalten, Aufräumen alter Dateien | P10 | +| **geteilt:** `server/config.js` (P0: Abschnitt `kundenansichtNeu`; P5: Abschnitt `aboFunktionen`; P8: `abo.termsVersionNeu`; jeder nur sein eigener Abschnitt), `test/support/schema.js` (nur neue Collection-/Attribut-Quellen ergänzen), `scripts/setup-abo-collections.mjs` (nicht ändern; neue Attribute nur in `scripts/kundenansicht-setup.mjs`) | wie angegeben | + +Bestehende Testdateien ändert ein Paket nur dort, wo sein Plan sie ausdrücklich nennt, etwa für Pins auf Texte, die das Paket ändert. + +--- + +## Global Constraints + +- **Alte Ansicht unverändert.** Bis P10 ist jede Verhaltensänderung für Kunden ohne neue Ansicht verboten. Neue Server-Logik prüft `neueAnsicht(req)`, ist rein additiv (neue Felder, neue Endpunkte) oder betrifft nur Datensätze, die erst über die neue Ansicht entstehen (`unlockedBy: 'abo' | 'projekt'`). +- **Schalter:** `KUNDENANSICHT_NEU` ist `off | pilot | on`, Vorgabe `off`. `KUNDENANSICHT_NEU_PILOT_EMAILS` ist eine Komma-Liste, `*` heißt alle. Der Modus `off` schlägt jede Liste. +- **Aussehen** (Spec 5.1, Werte wörtlich aus dem Ticketsystem `src/styles/global.css:7-51`): + - Farben: `#fff` Fläche, Text `#1a1f36`, Zweittext `#5e6678`, Tertiär `#8a93a3` nur für Nebensächliches, Linien `#ebeef1` und `#d8dde3`, Hover `#f6f8fa`, Fläche `#f8f9fb`, Akzent `#111`. + - Status-Pillen (Fläche/Schrift): grau `#ebeef1`/`#414552`, blau `#e0f2fe`/`#075985`, orange `#fcedb9`/`#a82c00`, violett `#efeafe`/`#5b21b6`, grün `#d7f7c2`/`#006908`, rot `#ffe7f2`/`#b3093c`. Fehler `#c0123c`. + - Radien 6 und 12 px. Ring-Schatten `0 0 0 1px rgba(60,66,87,.16), 0 1px 1px rgba(0,0,0,.06)`. + - Systemschrift `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`. Kein Gewicht 800, Zahlen `tabular-nums`. + - Verboten: Karten, Verläufe, Glow, Punktraster, Glas als Deko, Dunkelmodus, Lila. + - Ladezustände als Text („Wird geladen …“). Dialoge schließen nur per × und Esc. +- **Anrede „du“** in allen neuen Texten der Oberfläche. Ausnahmen: Rechtsdokumente und bestehende Mails (Spec 5.7); die Bestellseiten bekommen „du“ erst mit Fassung 6 (C14, P7). +- **Keine Preise für Funktionen** in der neuen Oberfläche, nirgends. +- **Hashes:** Alle alten Hashes bleiben gültig (`VIEW_HASHES`, Aliase `#wartung`, `#abo`, `#fortschritt`). Die Query nach `?` gelangt unverändert ins Modul. Neue Hash-Namen nur aus `[a-z]` wegen der Login-Whitelist `/^\/dashboard\.html(#[a-z]+)?$/` (`public/app.js:466`, `public/login.html`). +- **Rechtlich fest** (Spec 4): + - Kerninfo und „Zahlungspflichtig bestellen“ stehen auf demselben Bildschirm. + - Der Knopftext bleibt wörtlich. + - „Verträge hier kündigen“ ist immer erreichbar. „Vertrag widerrufen“ steht immer im Konto. + - `kuendigen.html` und `widerrufen.html` bleiben schlicht. +- **Kundenart:** Der Server ist die einzige Quelle. Er nimmt nie die Kundenart vom Client. Eine leere oder unbekannte Kundenart gilt als `privat` (E4), überall über `kundenartWirksam()` (V4). +- **Nachweise:** Wortlaut, Fassung, Zeit, IP, Browser und Konto werden weiter gespeichert, ab jetzt mit der Kundenart zum Klickzeitpunkt. +- **Tests Portal** (aus dem jeweiligen Worktree): + - Einzeldatei: `timeout 900 nice -n 10 node --max-old-space-size=1024 --test test/.test.js`. + - Mehrere Dateien in einem Aufruf nur mit `--test-concurrency=1`. + - Die ganze Suite nur in Phase E und P10: `timeout 2400 nice -n 10 systemd-run --scope -q -p MemoryMax=2500M -p MemorySwapMax=0 env NODE_OPTIONS=--max-old-space-size=1024 node --test --test-concurrency=2 test/*.test.js`. + - Vor jedem schweren Lauf `free -m`. Bei weniger als 2000 MB verfügbar warten und den Controller melden. +- **Tests WOMS:** `cd /home/webklar/apps/ticket.webklar.com/dev-kundenart && timeout 900 nice -n 10 systemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0 env NODE_OPTIONS=--max-old-space-size=1024 npm test`. +- **Playwright** nur mit dem Werkzeug aus P0 (`run-browser.sh`, eine Instanz, Speicherbremse). `chromium-1234`, `playwright-core` 1.62.1. +- **Commits:** Dateien einzeln hinzufügen. Nachrichten deutsch, mit Zeile `Co-Authored-By: Claude Opus 5.5 `. + - Kein Push, kein `--force`, kein Merge auf `main` oder `test`. Pushen tut nur der Inhaber. + - Keine Änderungen in `build/`, weder im Portal noch im WOMS. +- **Sicherheitsblock** in jedem Agent-Auftrag (Memory „feedback-destruktive-skripte“): + - Kein `rm -rf` außerhalb des eigenen Scratch-Ordners, nie `/dev`, `/etc` oder `/` anfassen. + - Kein `docker run`, keine globalen Installationen. + - Das Vorschau-Skript nie mit Ziel aufrufen, nur `--pruefen`. Nie mit Pfaden wie `..` testen. + - `TMPDIR` auf den eigenen Scratch-Ordner setzen. + - Nach großen Läufen prüfen, dass `ls -l /dev/null` `crw-rw-rw-` zeigt. +- **Prod-Daten:** Kein Paket schreibt in die Live-Datenbank. Ausnahmen sind die Setup-Skripte (`scripts/kundenansicht-setup.mjs`) und `scripts/kundenart-bestand.mjs`. Beide laufen nur auf ausdrückliche Anweisung des Controllers nach OK des Inhabers, vorher immer mit `--pruefen`. + +## Review Focus + +1. **Kundenart leer, unbekannt oder vom Client gefälscht:** Ohne gesetzte Kundenart sieht der Kunde das eine Häkchen, und der Server verlangt es auch. Ein Body-Feld `kundenart` vom Client wird ignoriert. Tests: P5 Task „kundenartWirksam“ und „Einschalten privat in Frist“, P7 Task „Bestellung Fassung 6“. +2. **Pilot-Schalter an den Rändern:** Groß-/Kleinschreibung und Leerraum der E-Mail, `*`, leere Liste, `off` mit `*`, Mitarbeiter-Kundenansicht (`viewAs` + `viewId`) im Pilot. Ohne neue Ansicht liefern die Abo-Modell-Endpunkte 404, und `/dashboard.html` ist die alte Seite. Tests: P0 Tasks „Schalter“ und „Dashboard-Weiche“, P5 Task „Einschalten nur in neuer Ansicht“. +3. **Alte Links nach dem Umbau:** `#termine?bestellt=1&projekt=x`, `#webshop?stripe=zurueck&projekt=x`, `#rechnungen`, `#stunden`, `#gutschrift`, `#wartung`, `#fortschritt`, `?abo=success`, Login mit `next=/dashboard.html#chat`. Jeder Link landet am richtigen neuen Ort, und die Query bleibt erhalten. Test: P1 Task „Routen“. +4. **Doppeltes oder widersprüchliches Einschalten:** Zwei Taps, schon aktiv, zweite Website, Hosting-Kunde bei einer Wartungs-Funktion (Wechsel mit gemerkter Funktion), gescheiterter Wechsel, Privatkunde in der Frist ohne Häkchen, Funktion per Schalter aus. Ergebnis: höchstens ein Ticket, klare 409-Codes, kein Zustand ohne Nachweis. Tests: P5 Task „Einschalten“, P6 Task „Wechsel mit Funktion“. +5. **Vorschuss statt Mehraufwand:** Nicht gedeckte Einrichtungsminuten erscheinen nie als Mehraufwand. Normale Arbeit über dem Guthaben bleibt Mehraufwand. Eine spätere Gutschrift gleicht den Vorschuss zuerst aus. Bei Vertragsende bleibt ein Restminus stehen, wird aber nicht berechnet. Gilt auch für Git-Push-Arbeitsblätter auf Einrichtungs-Tickets. Tests: P6 Task „Vorschuss im Stundenkonto“, P9 Task „Abo-Vorgabe bei Einrichtungs-Tickets“. + +--- + +## Verträge + +### V1 Pilot-Schalter (P0) + +`server/config.js`: +```js +kundenansichtNeu: { mode: 'off'|'pilot'|'on', pilotEmails: string[] } // aus KUNDENANSICHT_NEU, KUNDENANSICHT_NEU_PILOT_EMAILS (commaList) +``` + +`server/services/kundenansicht/schalter.js`: +```js +export function neueAnsichtModus() // 'off' | 'pilot' | 'on' +export function neueAnsichtFuerSession(session) // boolean +export function neueAnsicht(req) // = neueAnsichtFuerSession(req.session ?? parsePortalSession(req)) +``` + +Regeln für `neueAnsichtFuerSession`: +- `off` → false. +- `on` → true für jede gültige Kunden- oder Kundenansicht-Sitzung. +- `pilot` → true, wenn eine dieser Bedingungen gilt: + - `session.viewAs === true && session.viewId` (Mitarbeiter-Kundenansicht), + - die Liste enthält `'*'`, + - die Liste enthält die getrimmte, klein geschriebene `session.email`. +- Ohne Sitzung → false. + +Auslieferung und Clients: +- `app.get('/dashboard.html')` steht **vor** `express.static` (`server/index.js:154`). Gilt die neue Ansicht, liefert die Route `res.sendFile(public/neu/dashboard.html)` mit `Cache-Control: no-cache`. Sonst `next()`, und die alte Seite kommt wie heute. +- `GET /api/auth/me` bekommt zusätzlich `ansicht: 'neu' | 'alt'`. +- Die alte `app.js` lädt ohne Weiche. Die neue Seite lädt nur `/neu/*`-Dateien und die gemeinsamen Funktionsmodule. + +### V2 Dateien und Module der neuen Oberfläche (P1, P2, P3) + +**Grundregeln** +- Alle neuen Dateien liegen unter `public/neu/`. ES-Module (`