Files
Webklar-Kundenbereich/docs/superpowers/plans/2026-10-16-kundenansicht-00-uebersicht.md
T
ClaudeandClaude Sonnet 5.5 6bfcaa892c Kundenansicht neu (Meine): Kurzinfo nur auf Anfrage (GET /api/funktionen?kurzinfo=1)
Der Server berechnet kurzinfo (Terminplaner, Webshop, News, Buchungskalender, Öffnungszeiten) nur noch,
wenn die neue Ansicht ?kurzinfo=1 sendet. Das tut allein die Seite "Meine"; Etagen, Einschalt-Ablauf und
Module fragen ohne den Parameter, dann fehlt das Feld und es gibt keine Abfrage auf Termine, Bestellungen,
Beiträge und Anfragen. Mit dem Parameter bleibt die Antwort wie bisher (Text oder null je Funktion).
Nur der Wert "1" gilt; doppelte oder als Liste gesendete Parameter heißen: ohne. Alte Ansicht unverändert.

Tests: kundenansichtKurzinfo (neuer Fall ohne/mit Parameter, Abfragen und Feld), knFunktionenAnsicht
(die Seite lädt genau einmal ?kurzinfo=1, auch nach "Nochmal versuchen").

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 15:46:48 +00:00

80 KiB
Raw Blame History

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<N> im jeweiligen Zweig. Nach dem Merge auf den Hauptzweig kn-p<N>-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/<datei>.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 der Zeile Co-Authored-By: … aus dem eigenen Systemhinweis der ausführenden Sitzung (kein fester Modellname, Nachtrag N50).
    • 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“.

Nachträge nach der Planrunde (verbindlich, 16.10.2026)

Die Paket-Pläne wurden parallel geschrieben; ihre Vertragsfragen sind hier entschieden. Diese Nachträge gehen V1–V13 und der Zuständigkeitstabelle vor. Wo ein Paket-Plan davon abweicht, gilt dieser Abschnitt.

Kürzel in den Fundstellen: Task n und VFn = Task bzw. Vertragsfrage n im genannten Paket-Plan (P0 bis P10), Fn = Frage n der Planrunde (/root/reports/kundenansicht-neu-2026-10-15/planrunde.json, Feld vertragsfragen).

Schalter und Sitzung

  • N1 Gültige Sitzung (V1). Kunde heißt customerId und appwriteUserId ohne role: 'admin'; Kundenansicht heißt viewAs === true mit viewId. Alte Mitarbeiter-Sitzungen (role: 'admin' ohne viewId) sind in jedem Modus false, auch bei *. Eine email, die kein Text ist, zählt als leer. (P0 VF3; F12)
  • N2 Schalter für Jobs ohne Cookie (V1). schalter.js bekommt den vierten Export export function neueAnsichtFuerKunde({ customerId, email }) // boolean. Gleiche Modus- und Listenregeln wie neueAnsichtFuerSession (off false, on true, pilot bei * oder getrimmter, klein geschriebener email in der Liste), aber ohne Sitzungsprüfung und ohne Mitarbeiter-Regel; leere oder nicht-textliche customerId und Nicht-Objekte ergeben false. P5 aboModellFuerKunde(customerId) liest das Kundendokument und ruft sie; runAboCheck und P6 (Team-Aktivierung, Widerruf des Wechsels, Tagesjob) nutzen nur sie, nie neueAnsichtFuerSession. (P0 Task 2, VF10; P5 Task 13; F12)
  • N3 Weiche (V1). server/services/kundenansicht/dashboardWeiche.js ist eine Fabrik (server/index.js startet beim Import den Server). Der Direktaufruf /neu/dashboard.html wird für Sitzungen ohne neue Ansicht per 302 auf /dashboard.html geleitet; Module und CSS unter /neu/ bleiben frei abrufbar. Bei Modus on liefert die Weiche von P10 (UMSCHALT_SEITEN, Zeile /dashboard.html) die neue Seite auch ohne Sitzung; neueAnsichtFuerSession bleibt unverändert. (P0 VF1, VF2; P10 Task 3, VF7; F12)
  • N4 /api/auth/me. ansicht steht nur in den beiden authenticated: true-Antworten von GET /me. P5 ändert in auth.js allein sanitizeCustomer (kundenart, kundenartWirksam); test/kundenansichtMe.test.js pinnt nur die Schlüssel der obersten Ebene, damit customer wachsen darf. (P0 Task 4, VF4; P5 Task 2; F13)
  • N5 Platzhalter-Marker. P0 Task 3 setzt im Platzhalter beide Marker (<meta name="kn-seite" content="neu"> und data-kn-platzhalter) und pinnt in istNeu nur die Meta-Zeile. P1 Task 12 ersetzt die Seite, ohne einen P0-Test zu ändern; die Werkzeug-Ansichten platzhalter und platzhalter-abgemeldet bleiben, bis P1 sie ersetzt. (P1 VF1, Task 12/13; P0 Task 3; F11)

Hülle, Routen, Reiter, Zähler

  • N6 Routen (V2, „Pflichtinhalt von ROUTEN“). Die Zeile „vertraege, abo, wartung → vertraege, reiter abo“ entfällt. Neu: vertraege → Bereich vertraege ohne reiter (die Ansicht wählt den Standardreiter, bei offener Rechnung rechnungen, sonst abo); abo und wartung → Bereich vertraege, reiter abo, kanonischer Hash #abo (#wartung wird #abo, #vertraege bleibt #vertraege); ?abo=success|cancel → #abo. waehleReiter (P2) entscheidet nur über route.reiter; Reiterklicks navigieren mit ersetzen: true zu #abo, #rechnungen, #stunden, #speicher, und ankerKette/elternVon liefern weiter #vertraege als Bereichs-Anker. (P1 Task 3/4, VF4; P2 Task 16, VF8; F17)
  • N7 Routenfelder und Exporte (V2). Route-Felder zusätzlich produkt ('hosting' | 'wartung') und scroll ('aktivitaet'); query ist immer ein String. Zusätzliche Exporte von routen.js: hashFuerAnsicht(name) (die data-view-Brücke der gemeinsamen Module), startHash, ankerKette, elternVon, titelFuer, FUNKTIONS_ETAGEN. Die Projekt-ID heißt einheitlich #projekt?projekt=<id> (Lesen von id= bleibt Rückfall); ein data-project an einem [data-view]-Element wird zu ?projekt=<id>. (P1 Task 3, VF4; P2 VF4; F17)
  • N8 Kleinere Festlegungen der Hülle (V2). api(…, { roh }) meint einen Rohdaten-Body (application/octet-stream) wie fn-util.js request({ raw }), nicht eine rohe Antwort; der V2-Kommentar ist so zu lesen. ?abo= bleibt in location.search; P2 räumt mit history.replaceState(history.state, '', location.pathname + location.hash) auf, und P4 ändert die history.replaceState-Aufrufe der Module nicht (stempeln() aus P1 stempelt danach neu). public/neu/manifest.webmanifest hat dieselbe id, start_url und scope wie die alte; sw.js bleibt gemeinsam und wird in P1 Task 11 registriert. Ansichten rendern keine eigene h1; der Titel kommt aus titelFuer(route) oder kn:titel (P7: meldeTitel). (P1 VF7 bis VF11; P7 Task 10/11/13; F20)
  • N9 Reiter-Baustein und Meldungen (V2, Hülle). baueReiter({ eintraege: [{ key, label, kurz? }], aktiv, art, ariaLabel, bei(key) }) in public/neu/reiter.js liefert direkt ein HTMLElement mit setzeAktiv, setzeAnzeige, messen; P2 und P3 passen sich an. Inhalte in Etage 2 melden Reiter und Titel ohne Import per CustomEvent mit bubbles: true: kn:reiter (detail { eintraege, aktiv, bei }, leere Liste = keine Reiter) und kn:titel (detail { text }). (P1 Task 5, VF5; P2 Task 16; P3 Task 5; F18)
  • N10 Reiter der Funktionsmodule in Etage 2. Terminplaner, Webshop, Buchungskalender und Analyse zeichnen im Abo-Modell keine eigene Leiste, sondern melden kn:reiter (die Analyse aus mountCockpit mit bei: pickTab: Übersicht, Heatmap, Einstellungen). Die Ausnahme „außer analyse“ im Gate p4 entfällt; der Satz „die Analyse behält ihre eigene Leiste“ in F18 ist überholt. Kurzlabels stehen in KURZLABELS (public/fn-reiter.js: Team, Start, Recht, Preise), die Vorschau prüft sie (C2). (P4 Task 1/8/15, VF2; P1 VF5; F4, F18)
  • N11 Zähler-Ereignis (V3). kn:zaehler-neu wird am document gesendet (bubbles: true), detail.was ist 'chat' | 'vertraege' | 'alle'. zaehler.stand() liefert nur { chat, vertraege }; die Hülle setzt den Chat-Zähler beim Öffnen des Chats auf 0. (P1 Task 7/11, VF6; P2 VF2; F19)
  • N12 Zeile „Rechnung offen“. P1 zeichnet sie nicht und reicht kein zaehler an die Ansichten; zeigeX(wurzel, { route, me, nav }) bleibt wie in V2. P2 liefert public/neu/rechnung-offen.js (offeneRechnungen, rechnungOffenZeile, ladeOffeneRechnungen, optional ladeOffeneRechnungen(apiFn = api)); P3 zeigeFunktionen lädt GET /api/invoices selbst über abh.api und zeichnet die Zeile in Meine und Entdecken. Fällt der Abruf aus, fehlt nur die Zeile. (P3 Task 5, VF5; P2 Task 3, VF3; P1 VF6; F1, F19)
  • N13 Option einschalten für die Funktionsmodule (V2, Absatz „Funktionsmodule“). Die Hülle ruft initNews, initTermine, initShop, initBuchungen, initZeiten als init*(wurzel, { einschalten }) mit einschalten(platz, { key, projectId, onFertig, viewAs? }) → Promise, das lazy zeigeEinschalten aus public/neu/funktion-einschalten.js (P3) lädt (MIT_EINSCHALTEN, einschaltenOption(lade) in huelle.js); initAnalyse(wurzel) bleibt unverändert, die Projektwahl bleibt wurzel.dataset.wunschProjekt. test/knHuelleStart.test.js („Option einschalten“) pinnt die Verdrahtung; P4 Task 15 ersetzt seine Zeile command grep -a -n einschalten public/neu/huelle.js durch den Lauf dieses Tests. (P1 Task 11, VF12; P3 VF1; P4 VF1; F1, F21)
  • N14 Overlay. Ansichten dürfen oeffneOverlay und beendeOverlay aus router.js rufen (P3 für die Bestätigungsseite). Es ist nie mehr als ein Overlay offen; P3 und das Konto-Sheet schließen vor dem Öffnen ein offenes Overlay. (P3 Task 6, VF5; P1 R6; F22)
  • N15 Chat-Composer. Die neue Seite lädt /neu/chat-composer.css, eine lilafreie Kopie von public/chat-composer.css (Akzent #111/Grau, weder #9d3dfe noch Gewicht 800 noch backdrop-filter); /chat-composer.js bleibt gemeinsam, das alte CSS unverändert. P2 Task 7 legt die Kopie an und ändert dafür genau die zwei Composer-Zeilen in dashboard.html; test/knHuelleSeite.test.js akzeptiert /neu/chat-composer.css statt /chat-composer.css. (P1 VF3; P2 Task 7, VF10; F23)
  • N16 Analyse-Bild und Alt-Klassen. public/neu/bilder/analyse.webp erzeugt P4 über die Werkzeug-Ansicht kn-modul-analyse (Fall m-analyse-aktiv); bis dahin entfernt der error-Handler in „Entdecken“ das fehlende Bild. public/neu/module.css Abschnitt 0 (P4) definiert kb-panel, kb-btn*, kb-empty, kb-link, kb-checks, abo-actions, abo-btn, hidden, muted, error, außer eine Datei unter public/neu/ tut es schon; P2 und P3 nutzen eigene kn-*-Klassen. (P3 VF2; P4 Task 9/14, VF3, VF5; F24)

Katalog, Einschalten, Analyse

  • N17 Katalogfelder (V5). Jeder katalog[]-Eintrag trägt consent: { version, knopf: 'Einschalten', items: [{ id, text, haken, nurPrivatInFrist? }] } (null bei analyse) in der Reihenfolge einschalten-zeile, hinweis, beginn und dazu hinweis: string | null, den Text des Items hinweis (nur shop, unterkunft, termine: die Pflichten der alten Häkchen verantwortung, stripe, gesundheit). Der Server liefert beides, nie der Client; der Nachweis speichert den Hinweis als Text hinweis (haken: false). P3 renderEinschalten zeichnet p.kn-fn-hinweis direkt über dem Knopf (nach der Linkzeile, vor beginn), die P0-Fixtures und vertragNeu tragen beide Felder. (P5 Task 6/10/11, VF2; P3 Task 3; P0 Task 6/7, VF11; P8 VF3; F3, F27)
  • N18 anfrageStatus (V5). Der Pfad ist projects[].features.<shop|unterkunft>.anfrageStatus ('' | 'offen' | 'angenommen' | 'abgelehnt' | 'zurueckgezogen'), gebaut von anfrageStatusJeProjekt in server/services/aboKatalog.js; P3 liest ihn nur über anfrageStatusVon(projekt, key). (P0 VF9; P5 Task 11; P3 Task 1; F16)
  • N19 Antwortcodes der Einschalt-Route (V5). Die Prüfreihenfolge nach P5 Task 10: 400 zustimmung_fehlt; Projektprüfung (403, 409 archiviert); dann 409 nicht_verfuegbar (danach 503 unterlagen_fehlen), abo_fehlt, nur_betrieb, bestellung_offen, beginn_noetig, bereits_eingeschaltet, bestellung_laeuft (Zustand ordered), abgeschaltet (Zustand disabled), fassung_veraltet. 503 kommt zusätzlich bei nicht lesbarem Abo oder gescheitertem Ticket; P3 zeigt den Text des Servers. (P5 Task 10, VF3; P3 VF4; F27)
  • N20 Probezeit (V5). aboDeckt kennt zusätzlich den Grund beginnt_spaeter mit ab (ISO, = trialEnd) für trialing mit offener Probezeit (Privatkunde ohne Sofortbeginn-Häkchen). abo_fehlt trägt dann body.ab; der Text lautet „Dein Abo beginnt am TT.MM.JJJJ. Ab dann kannst du einschalten.“, und P3 (TEXTE, fehlerAktion) zeigt Text und Datum im Knopf. P6 (adminActivate) und P9 kennen den Grund; die Analyse-Eignung meldet in der Probezeit weiter kein_abo. (P5 Task 4/9/10, VF9; P3 VF4; F8)
  • N21 Abrechnung am Funktionsdokument (V7). Das Attribut portalFeatures.einrichtungAbrechnung (string 8, 'abo' | 'projekt' | '', angelegt in scripts/kundenansicht-setup.mjs) hält die Abrechnung am Dokument, damit ticketNachlegen und holeZeitenNach den Marker workorders.einrichtungAbrechnung erneut setzen. P6 nutzt es für ticketNachlegen (Dokumente mit unlockedBy abo oder projekt ohne Stripe-Sitzung heilen über einschalten) und für die Ticket-Zeilen in termine/ticketDetails.js, shop/ticketDetails.js, unterkunft/ticketDetails.js; K27 bleibt die Einschalt-Mail der Zeiten. (P5 Task 1, VF5; P6 VF3, VF4; F27)
  • N22 Analyse im Abo-Modell. In der neuen Ansicht verlangt POST /api/funktionen/analyse/activate audienceConfirmed === true (sonst 400 zielgruppe_fehlt, du-Text) und schreibt einen Nachweis in featureConsents: Linkzeile A1, Zielgruppen-Zeile A2 (haken: true), Knopf Analyse einschalten, Kundenart und Fassungen; bei einem Fehler wird alles zurückgenommen. P4 sendet audienceConfirmed: true, die alte Ansicht bleibt unverändert. (P5 Task 13, VF8; P4 Task 8, VF4; F29)

Kundenart und Push-Reihenfolge

  • N23 Kundenart-Pflicht beim Anlegen (V4; Entscheidung des Controllers, F2 und F28 widersprachen sich). createCustomerWithPortalAccess verlangt kundenart nur bei neueAnsichtModus() !== 'off' (sonst 400 kundenart_fehlt); bei off ändert sich das Anlegen im WOMS und im Portal nicht. updateCustomerWithPortalAccess nimmt kundenart weiter optional mit gleicher Prüfung. Die unbedingte Pflicht aus F28 gilt nicht. (P5 Task 3, VF1; P9 VF3; F2, F28)
  • N24 Push-Reihenfolge (Entscheidung des Controllers). Der Inhaber pusht in dieser Reihenfolge: (1) fix/akquise-sichtbar, falls gewünscht; (2) WOMS-Tag kn-p9-kundenart; (3) Portal ab P5; (4) WOMS kn-p9 zusammen mit P6. Vor dem Setzen von KUNDENANSICHT_NEU=pilot muss die WOMS-Kundenart (kn-p9-kundenart) live sein. Wird fix/akquise-sichtbar später gepusht, rebased P9 darauf, nur auf Anweisung des Controllers. Die Übergabe-Notiz nennt die Reihenfolge und vermerkt, dass die Abo-Vorgabe in WOMS zunächst nur für Pilotkunden wirkt (P6 wertet abrechnung nur mit aboModellFuerKunde aus). (P5 VF1, VF10; P9 VF3, VF4; F2, F28)

Stundenkonto, Team, Mails

  • N25 Antwortformen des Stundenkontos (V8). nextExpiry ist { minutes, at } | null; bei produkt: 'hosting' und ohne Abo ist ledger gleich null. workorders.einrichtungFuer und einrichtungAbrechnung sind für Mitarbeiter lesbar (Setup-Skript P5). T24 trägt den Block vorschuss, wenn beim Einschalten balanceMinutes <= 0 gilt oder schon vorschussMinutes > 0 besteht; eine Schätzung je Funktion gibt es nicht. (P6 Task 2/4, VF6; P9 VF2; F33)
  • N26 Geltung der Team-Aktivierung (V10). Das neue Verhalten (aboDeckt-Prüfung, einschalten mit Ticket) gilt nur, wenn aboModellFuerKunde(customerId) wahr ist; sonst wird abrechnung im Body ignoriert und alles läuft wie heute, mit on (P10) für alle. P9 sendet abrechnung immer. Die 409-Antwort bei fehlendem Abo ist flach: { error, code: 'abo_fehlt', grund, wechsel } mit grund aus kein_abo | falsches_abo | gekuendigt | nicht_aktiv | beginnt_spaeter; WOMS zeigt eigene Texte, nie den Kundentext in „du“-Form. Erfolg wie bisher projects[0].features[key] mit state: 'setup' und ticketWoid. (P6 Task 7, VF2; P9 VF2; F32)
  • N27 Abo-Modell-Dokument (V10, V11). Ein Dokument gehört zum Abo-Modell, wenn unlockedBy in abo | projekt steht und einrichtungAbrechnung in abo | projekt (istAboModell in server/services/aboModell.js); die Gratis-Zeiten der alten Ansicht (unlockedBy: 'abo' ohne Marker) bleiben unberührt. V11 ändert sich: Der Widerruf des Wechsels beendet alle Abo-Modell-Dokumente, deren Schlüssel nur in wartung steckt, also auch projekt-Dokumente; die Analyse eines Hosting-Kunden bleibt. (P6 Task 5/13, VF4; F32)
  • N28 Team-Einschalten „im Projekt“ ohne Abo (E9/E21; Entscheidung des Controllers, dem Inhaber in der Übergabe zur Bestätigung vorlegen). Es gilt die Aufbauphasen-Regel von P6 (server/services/aboAufbauphase.js, ohneAboBisher(customerId), P6 Task 7, 8, 10). abrechnung: 'abo' braucht ein deckendes Abo, sonst 409 abo_fehlt. abrechnung: 'projekt' braucht es auch, außer in der Aufbauphase: Hatte der Kunde noch nie ein Abo (abgebrochene Bestellungen zählen nicht), wird eingeschaltet und mit 200 plus warnung: { code: 'abo_fehlt', grund: 'kein_abo' } geantwortet, flach neben projects. Das boolesche warnung: true für ein fehlendes K16 bleibt für die alte Ansicht. WOMS zeigt die Warnung (P9), der Wortlaut sagt dem Team: „Wartungs-Abo vor dem Livegang vereinbaren“. Der Tagesabgleich lässt projekt-Dokumente in der Aufbauphase in Ruhe. Ab dem ersten Abo gilt die Abo-Regel wie für abo: Deckt das Abo die Funktion nicht (Hosting, beendet), endet sie mit K39. Hat der Kunde schon ein nicht deckendes Abo, bleibt es auch im Projekt bei 409. (P6 Task 7/8/10, VF8; P9 Task 10/11; F5)
  • N29 Mailkennungen (V13). Belegt sind K38 „Deine Funktion wird eingerichtet“ (Einschalten im Abo-Modell, nicht für zeiten, dort bleibt K27); K39 „Deine Funktion wurde beendet“ (News, Terminplaner mit Block termine, Webshop, Unterkunft; Platzhalter funktion, projekt, loeschtag); K40 und K41 (Zeiten: Abo-Ende mit „läuft noch bis {{frist}}“, abgeschaltet; ohne Preis, ohne Behalten); T24 ans Team mit Block vorschuss; K42 (Webshop-Zusage) und K43 (Buchungskalender-Zusage) im Abo-Modell ohne Preiszeile, ohne „€“ und ohne „bestell…“ (Builder anfrageEntschiedenMail, Weiche in sendeEntscheidung über aboModellFuerKunde). K24 und K34 bleiben bytegleich. K44 und T25 bleiben frei; eine Rückkehr-Mail gibt es nicht (die Rückkehr geschieht still, K39 kündigt sie an), außer bei Zeiten im bestehenden Modul. K02 und K03 bekommen den Block funktionen_abo; K22, K29, K30 und T16 bleiben bytegleich. Alle neuen Vorlagen in „du“-Form und im Layout von K16. (P6 Task 3/4/5/15, VF1, VF5; F31)
  • N30 Behalten-Weg der Zeiten. Für Abo-Modell-Dokumente ist POST /api/funktionen/zeiten/behalten gesperrt (409, P6). Die Route und die Mailtexte K29/K30 (Builder k29Mail, k30Mail auf K40/K41 umgestellt, Vorlagen-Dateien bleiben) räumt P10 Task 20/21 auf. Die WOMS-Texte (zeitenStatusText, Bestätigungstexte „Aktivieren (ohne Zahlung)“) ändert ein eigener WOMS-Nachzug nach P10 Task 22 (Controller, Push durch den Inhaber, geführt in der Checkliste Task 6). Alt-Zweige hinter neueAnsicht( bleiben als toter Code; ihre Entfernung entscheidet der Inhaber. (P6 VF7; P9 VF5; P10 VF11, VF12; F34)
  • N31 Abo-Ende-Verhalten (Rechtstexte = Server). Terminplaner, News, Webshop und Buchungskalender schalten spätestens am Folgetag ab (Tagesjob). Öffnungszeiten live läuft nach dem Abo-Ende 30 Tage unverändert weiter (Zeiten-Modul, K40, P4 Text Z2), danach 30 Tage gespeicherte Eingaben; kein Behalten gegen 30 €. Löschfristen: 30 Tage (Terminplaner, News, Öffnungszeiten), 90 Tage (Webshop, Buchungskalender), 60 Tage (Analyse-Messdaten); ein neues passendes Abo in der Frist holt die Funktion zurück, P6 baut den News-Löschjob. P8 passt Texte und Tests an (R8 „mit P4/P6 abgestimmt“). (P8 Task 4/7, VF5; P6 Task 8/10; P4 Text Z2; F39)

Bestellseiten, Fassung 6, Nachweis

  • N32 Pfade der Fassung 6 (V12). config.abo.termsVersionNeu = '2026-10-16-v6' (Entwurfstag, bei der Freigabe wird das Datum gesetzt); FUNKTIONEN_FASSUNG_V6 = '2026-10-16'. Bis zum Umschalten gelten die Abo-Seite /neu/rechtliches/abo-bedingungen.html, das PDF /neu/rechtliches/WEBklar-Abo-Vertragsunterlagen.pdf und die Funktionsunterlagen /neu/rechtliches/<slug>-2026-10-16.html|pdf; termsDocFor liefert sie, /abo-bedingungen-v6.html aus V12 entfällt. P7-Fixtures und -Tests nutzen termsDocFor(config.abo.termsVersionNeu).url und config.abo.termsVersionNeu statt Literalen wie '2026-10-30-v6'. P10 Task 14 verschiebt Seite und PDF nach public/ (/abo-bedingungen.html), stellt in FASSUNGEN v5 auf die archiv-Adressen (public/vertragsunterlagen/v5/) und v6 auf die Wurzel-Adressen, setzt 301-Weiterleitungen und passt DOKUMENTE_V6, den Modus abo6 und die festen Links an. (P8 Task 1/5, VF1; P10 VF3; P7 Task 16; F35)
  • N33 Fassung 6 verdrahten: ein Task, zwei Pläne (V5, V12). P5 Task 15 und P7 Task 0 beschreiben dieselbe Arbeit und laufen einmal, als erste Aufgabe von P7 im Hauptzweig nach dem Merge von kn/server und kn/recht, mit den Dateien beider Tasks. Quellen sind WORTLAUT_V6, FUNKTIONEN_FASSUNG_V6, FUNKTIONEN_UNTERLAGEN_V6 und mitFassung aus server/services/products.js (P8 liefert, verdrahtet nicht). Verdrahtet werden config.aboFunktionen.fassung und texte.beginn (Literale, config.js importiert keine Dienste), consentAbo mit dem Item hinweis, aboKatalog.js (unterlagen, hinweis), aboUnterlagen.js und sichereAdresse in funktion-einschalten.js (lässt /neu/rechtliches/ zu). bestelltexte.js bleibt importfrei, test/knBestelltexte.test.js pinnt Linkzeile, Häkchen und Fassungsangabe gleich zu WORTLAUT_V6 (eine einheitliche Form der Fassungsangabe), danach läuft test/aboWortlautV6.test.js grün. Die Analyse (config.analyse.avvVersion, legalPaths, K18-Weiche mit den PDFs aus public/neu/rechtliches/) folgt dieser Verdrahtung erst nach der Freigabe; wo P5 Task 15 sie sofort auf Fassung 6 stellt, gilt diese Regel. (P5 Task 15, VF7, VF10; P7 Task 0/1, VF12; P8 VF2; F3, F29, F30)
  • N34 Vertragsnachweis (V12). speichereVertragsNachweis({ customerId, art, bezug = '', texte, fassungen, kundenart, req, at }) schreibt selbst mit createDocument und texteHash (aus featureConsents.js), weil speichereZustimmung projectId = '' ablehnt; featureKey = art, projectId = ''. bezug und wortlaut (WORTLAUT_STATUS) stehen als Schlüssel in fassungen (höchstens 1000 Zeichen), texte bleibt die reine Anzeigereihenfolge. stripeSessionId bleibt leer, weil der Nachweis vor der Stripe-Sitzung, dem Wechsel, der Speicheränderung und dem Antrag entsteht (kein Zustand ohne Nachweis); die Zuordnung läuft über Zeit, Konto und bezug. (P7 Task 2, VF3 bis VF5; P8 VF4; F36)
  • N35 Fassung in den Bestellrouten. P7 übernimmt die Stellen, die config.abo.termsVersion fest lesen: stripeBilling.js:606-607 (createAboCheckout: Fassung aus consent.termsVersion, Vorgabe unverändert), abo.js:236, :328, :388 (Prüfung) und abo.js:201 (GET /api/abo liefert termsVersion über aktuelleAboFassung(req)). fassungDerAnfrage(req, termsVersion) prüft die Neu-Fassung zuerst; bei gleichen Werten von termsVersion und termsVersionNeu gilt neueAnsicht(req) ? 'neu' : null, sodass P10 Task 14 hier nichts ändert. server/routes/takeoverCredits.js POST / schreibt den Nachweis bei Fassung 6. (P7 VF2; P8 VF4; P10 VF4; F36, F42)
  • N36 Speicher und Gutschrift (Fassung 6). P7 übernimmt ansicht-speicher.js (Plus-Block) und ansicht-gutschrift.js (Zustimmungsblock) wie ansicht-bestellen.js und ansicht-wechsel.js: P2 baut sie 1:1 (Fassung 5), P7 baut sie auf das schlanke Muster um. Die Orakel-Vergleiche gegen app.js werden durch Pins auf die Fassung-6-Texte ersetzt; der Hash-Pin des Blocks TAKEOVER_CONDITIONS (conditionsVersion 2026-09-30) bleibt. Die Gutschrift-Bedingungen werden die eigene Ansicht #gutschrift?bedingungen=1 mit unverändertem Rechtstext; test/support/knWortlautOrakel.js entfernt P10 Task 17 erst mit app.js. (P2 VF5; P7 VF1, VF8; P10 Task 17; F25)
  • N37 Texte der Bestellseiten. Fehlertexte der Dienste (planSwitch.js, mailStorage.js, stripeBilling.js, Abo-Routen) bleiben im Quelltext in Sie-Form und werden für Sitzungen mit neuer Ansicht über server/services/duFehler.js in du übersetzt; ein Quelltext-Test meldet jeden neuen Sie-Satz ohne Übersetzung. Die Zeilen „Anbieter“ und „Abrechnung“ entfallen aus dem Block der Bestellseiten (der Anbieter steht im Impressum im Konto) und gehen als Kanzlei-Frage in die Rechtsrunde (N46). K01 und K12 behaupten für Betriebe kein „ausdrücklich verlangt“ mehr (Block sofortverlangen). Bewusst der Mail-/Textrunde (Spec 5.7) überlassen: Validierungssätze in takeoverCredits.js, custom_text der Stripe-Zahlungsseite, die übrigen Mails. (P7 Task 6/14, VF6, VF7, VF11; F37)

Öffentliche Seiten und Login

  • N38 Kündigen und Widerrufen in du. P7 Task 15 stellt public/neu/oeffentlich/kuendigen.html und widerrufen.html auf du um; public/legal-form.js und public/auftrag-widerruf.js zeigen du nur im Zweig data-anrede="du" (ohne Attribut unverändert). Die Seiten speichern keinen Wortlaut und keine termsVersion, es gibt also keine neue Version (C14). Text-Pins in test/knOeffentlichDu.test.js ersetzen für diese zwei Seiten den Paarvergleich aus P2 Task 17 (Feldattribute und Skripte werden weiter verglichen); P10 Task 17 stellt die Sie-Pins in test/publicForms.test.js:1439-1450, test/auftraegeKundenportalWiderruf.test.js:9 und test/auftraegeAblauf.test.js:288 auf diese du-Sätze um. (P7 Task 15, VF13; P2 Task 17; F6)
  • N39 Neue öffentliche Seiten im Pilot. Angemeldete Pilot-Sitzungen (neueAnsicht(req)) bekommen /kuendigen.html und /widerrufen.html schon im Pilot aus public/neu/oeffentlich/; ausgeloggte Besucher, der Login (/login.html) und /offline.html bleiben bis on alt. P0 setzt das mit zwei weiteren Zeilen in dashboardWeiche.js um (sie liefert nur, wenn die Datei existiert, sonst next()); P10 Task 3 übernimmt die Zeilen in UMSCHALT_SEITEN und passt den Test „off und pilot liefern die Datei von heute“ für diese zwei Seiten an. (P10 Task 3; P0 Task 3; P2 Task 17; F7)
  • N40 Login, Service Worker, Auftragsseite. P10 Task 16 (public/neu/oeffentlich/login.js) übernimmt initLoginPage samt Helfern (app.js:421-568) vor dem Löschen von app.js; die neue Hülle registriert /sw.js selbst (P1 Task 11), P10 zieht in sw.js die OFFLINE_URL mit. iOS-Fokuszoom: 16-px-Felder in P1/P2-CSS genügen, ein JS-Schutz nur im Login; der doppelte beforeinstallprompt-Hörer (Hinweis P2, Konto P1) darf P10 zusammenlegen. P10 nimmt /auftrag.html in UMSCHALT_SEITEN und in die Nachprüf-Marker auf (Marker: /neu/oeffentlich/oeffentlich.css im Quelltext, kein /auftrag.css); P2-Tests, die alte Dateien lesen (app.js, alte Seiten, auftrag.css, chat-composer.css), friert P10 vor dem Löschen ein oder entfernt sie. (P2 VF6; P10 Task 16/17, VF5; F26)

Werkzeug und Vorschau

  • N41 Werkzeug-Erweiterungen durch P1 bis P4 und P7. Erlaubt sind nur views.mjs, gates.mjs (Gate p<N>), fixtures/endpunkte-p<N>.mjs mit je einem Import in fixtures/endpunkte.mjs, fixtures/faelle-p<N>.mjs (P4: fixtures/module.mjs) mit einem Import in fixtures/faelle.mjs, vertrag-endpunkte.mjs und der Marker in test/statik.test.mjs. Neue Fälle hängen nur in eigenen Dateien hinter den vierzehn Kernfällen; P0 lockert den Pin „Fälle in fester Reihenfolge“ auf FALL_NAMEN.slice(0, 14) und den boot.js-Test auf diese vierzehn (boot.js bekommt FALL_MUSTER = /^[a-z0-9-]{1,40}$/, die Leiste zeigt nur Kernfälle). P4 (17 Fälle m-*) und P7 (privat-ohne-abo, hosting-privat) ändern Pin und boot.js nicht; ändert ein Paket eine Antwortform, ergänzt es den Vertragseintrag im selben Commit. (P0 Task 6/11, VF5; P1 VF2; P4 VF5; P7 Task 16, VF10; F14)
  • N42 Strang-Umgebung und Ansicht-IDs. Die Strang-Umgebung kommt nur über KN_STRANG_ENV=…/tmp/strang-ui.env, nie über KW/strang.env. Ansicht-IDs tragen das Paketpräfix (kn-huelle-, kn-p2-, kn-funktionen-, kn-modul-, b7-); wer fremde Ansichten aufruft, nennt die echten IDs (P7: --only kn-p2-bestellen,kn-p2-bestellen-hosting,kn-p2-wechsel,kn-p2-email,kn-p2-email-plus,kn-p2-gutschrift,kn-p2-gutschrift-frage). Fixtures für V5, V6 und V8 folgen den Nachträgen oben. Die Ansicht für /auftrag.html ergänzt P2 Task 19 mit einem Eintrag in vorschau/seiten.mjs. (P3 VF3; P2 VF7; P7 Task 16; F14)
  • N43 Vorschau-Technik. dark-surface erlaubt genau #111 (Akzent) für Elemente bis 25 % der Viewport-Fläche; andere dunkle Flächen brauchen ignorieren je Ansicht. In der Vorschau hat public/neu/oeffentlich/<seite> Vorrang vor public/<seite>; boot.js feuert nach dem Laden einmal DOMContentLoaded und load, Code prüft trotzdem document.readyState. Der Arbeitsauftrag an den Inhaber (WOMS „Neues Projekt“, Subdomain kundenansicht-neu, Status ready oder deployed) steht in KW/UEBERGABE-P0.md und blockiert Phase B nicht; KN_ERSTLAUF_FREMDES_OK=ja nur auf ausdrückliche Anweisung des Controllers. (P0 Task 10/11/14, VF6 bis VF8; F15)
  • N44 WOMS-Werkzeug. „Playwright nur mit dem Werkzeug aus P0“ gilt für das Portal; für das WOMS gilt der eigene Strang werkzeug-kundenart (P9 Task 1). Ein kleiner Auftrag „WOMS-Werkzeug auf test heben“ (Aufträge/Projektkonten, Unterkunft, arbeitsblaetter/abgerechnet, Reiter „Abgesagt“) läuft vor Phase E. Beide Werkzeuge halten die Speicherbremse (free -m über 2000 MB) und starten nie mehr als einen Browser zugleich. (P9 Task 1, VF1; F41)

Rechtsrunde

  • N45 Rechtsrunden-Technik. Die drei fehlenden WOMS-Karten (leistungsbeschreibung-news, leistungsbeschreibung-website-analyse, avv-website-analyse) legt scripts/rechtliches-hochladen.mjs --karten-anlegen mit OK des Inhabers vor dem ersten Upload an; --probe meldet fehlende als STOPP: Karte fehlt: <key>, das Hochladen ohne Karte bricht ab. Die Karte der Analyse-Leistungsbeschreibung bekommt eine abgeleitete ID (kartenId, ihr Schlüssel hat 37 Zeichen, erlaubt sind 36), WOMS liest Karten über das Attribut key, und die drei Karten werden nicht in setup-legal.mjs eingetragen. Die Entwurfsseiten unter /neu/rechtliches/ gehen mit dem normalen Push live (der Pilot braucht die Links), tragen Entwurfsmarke und <meta name="robots" content="noindex"> (P8 Task 5 bis 10 ergänzen) und werden bei der Freigabe ersetzt. (P8 Task 11/13, VF6, VF7; F40)
  • N46 Entscheidungsliste. docs/rechtliches/kundenansicht-entscheidungen.md führt R1 bis R17 (statt R1 bis R15; test/kundenansichtEntscheidungen.test.js entsprechend): R16 ist die Du-Anrede der Seiten Kündigen und Widerrufen (N38), R17 sind die Zeilen „Anbieter“ und „Abrechnung“ im Block der Bestellseiten (N37), je mit Vorschlag, Alternative und Kanzlei-Frage. (P8 Task 11; P7 VF7; F6, F37)

Livegang und P10

  • N47 Vor dem Pilot (Ablaufhinweis 4 ergänzt). Zwei Schritte vor dem Push des Inhabers, beide mit Sicherung und OK des Inhabers: (1) Compose- und .env-Zeilen KUNDENANSICHT_NEU (Modus pilot) und KUNDENANSICHT_NEU_PILOT_EMAILS (Pilot-Liste) in /home/webklar/apps/docker-compose.yml (zwei Zeilen - KUNDENANSICHT_NEU=${KUNDENANSICHT_NEU:-off} und - KUNDENANSICHT_NEU_PILOT_EMAILS=${KUNDENANSICHT_NEU_PILOT_EMAILS:-} bei den ZEITEN_*-Zeilen) und /home/webklar/apps/.env, Sicherung .bak-<Datum>-kundenansicht, dann docker compose up -d --no-deps kundenbereich-server, nie preview-portal; (2) scripts/kundenansicht-setup.mjs --pruefen, nach OK des Inhabers ausführen (sonst scheitert jeder Nachweis ohne featureConsents.kundenart mit 500). UEBERGABE-P0 und die Übergabe-Notiz tragen beide Punkte, P10 Task 9 prüft sie nur noch. (P0 UEBERGABE-P0; P7 VF9; P10 VF2, VF13, Task 9; Befund zur Übersicht; F9, F38)
  • N48 Vor on. P10 Task 1 und 9 prüfen: WORTLAUT_STATUS steht auf 'freigegeben' (E20); die Gleichheitstests test/knBestelltexte.test.js, test/knVerdrahtung.test.js und test/aboWortlautV6.test.js sind grün; scripts/rechtliches-hochladen.mjs --probe meldet keine fehlende Karte und --pruefen endet mit Exit 0 (alle elf documentKey aus DOKUMENTE_V6 haben eine aktuelle Version mit passender source, deshalb prüft Task 9 Step 2 mit --pruefen und nicht nur mit --probe); scripts/kundenart-bestand.mjs (P10 Task 8) und scripts/kundenansicht-setup.mjs sind gelaufen. (P7 VF9; P8 VF9; P10 VF9, VF13; F38)
  • N49 Bestätigungen zu P10. Die Alt-Zweige der P4-Dateien entfernt P10 Task 21, die Playwright-Skripte der alten Seite löscht Task 17. Zwischen Task 11 und Task 14 liegen sieben Tage Beobachtung; Task 14 ist der Punkt ohne Rückweg per Schalter und braucht ein eigenes OK des Inhabers. Der Rückbau (Task 20) beschränkt sich auf die beiden Kaufrouten und ihre Helfer; Alt-Preise in config.js und die Antwortfelder von GET /api/funktionen bleiben. (P10 VF4, VF6, VF8, VF10, VF11; F42)
  • N50 Commit-Zeile (Global Constraints). Jede ausführende Sitzung nutzt die Co-Authored-By-Zeile aus ihrem eigenen Systemhinweis. Commit-Beispiele in den Plänen mit Claude Opus 5.5 und Formulierungen wie „wörtlich laut Übersicht“ stehen dafür als Platzhalter. (alle Pläne, Global Constraints „Commits“; P8 Schlussvermerk; F43)

Pilot-Nachträge

  • N52 Kurzinfo in „Meine“ (V5, Pilot-Feedback 2026-10-17, Strang kn/meine-pilot). Mit neuer Ansicht und nur auf Anfrage (GET /api/funktionen?kurzinfo=1, sendet allein „Meine“; Etagen, Einschalt-Ablauf und Module fragen ohne den Parameter, dann fehlt das Feld und nichts wird gelesen; Runde 1, 17.10.) trägt jede Funktion unter projects[].features.<news|termine|shop|unterkunft|zeiten> zusätzlich kurzinfo: string | null, den Text der zweiten Zeile in „Meine“. Nur aktive Funktionen bekommen einen Text, alle anderen null; die Analyse trägt kein Feld (ihre Zahlen kommen aus dem Auswertungsdienst, nicht aus Appwrite). Quelle server/services/kundenansicht/kurzinfo.js: Terminplaner „3 Termine heute · nächster 10:30“ bzw. „Heute keine Termine“ (bestätigte und erledigte Termine ohne Sperren, Berliner Tag), Webshop „2 neue Bestellungen“ bzw. „Keine offenen Bestellungen“ (Status bezahlt), News „4 Beiträge sichtbar“ bzw. „Keine Beiträge sichtbar“ (isHanging), Buchungskalender „1 offene Anfrage“ (Status neu), sonst „Nächste Anreise 24.10.“ („Anreise heute“, „Anreise morgen“), sonst „Keine offenen Anfragen“, Öffnungszeiten live der Status der Website (zeitenStatus.text, ohne eigene Abfrage; ohne gespeicherte Zeiten „Noch keine Öffnungszeiten eingetragen“). Je Funktion höchstens zwei schmale Abfragen (limit, select, Filter auf Index-Felder), alle gleichzeitig; jede Funktion einzeln auf kurzinfoDeps.limitMs (1500 ms) begrenzt, Fehler oder Zeitüberschreitung ergibt null, eine volle Seite (Zahl unbekannt) ebenfalls. Die Texte enthalten nie Namen, Adressen oder Beträge. Die Oberfläche zeigt bei null „Aktiv“ und setzt die Zeile nur als Text (höchstens 80 Zeichen); eine Pille trägt nur noch eine Zeile, deren Funktion nicht läuft. Ohne neue Ansicht bleibt die Antwort bytegleich und es wird nichts gelesen. (Pilot-Feedback 2026-10-17; test/kundenansichtKurzinfo.test.js, test/knFunktionen*.test.js)

Zuständigkeit

  • N51 Zusätzliche Dateien und geteilte Stellen. Die folgende Untertabelle ergänzt die Zuständigkeitstabelle um alle in Plänen und Vertragsfragen genannten Zusatzdateien und legt für die Hotspots eine feste Reihenfolge fest. Stubs aus P1 gehören nach der Anlage P2, P3 und P4; P7 übernimmt ansicht-speicher.js und ansicht-gutschrift.js; P0 besitzt server/services/kundenansicht/ ganz. (P0 VF1; P1 VF3; P2 VF1; P4 Dateiübersicht; P5 VF4; P6 VF3; P7 VF1, VF2; P10 VF1; Befund zur Übersicht; F10)
Datei / Stelle Besitzer Reihenfolge, Hinweis
server/services/kundenansicht/ ganz (schalter.js, dashboardWeiche.js), test/kundenansichtSchalter.test.js, kundenansichtDashboard.test.js, kundenansichtMe.test.js P0 andere Pakete legen dort nichts ab
Werkzeug views.mjs, gates.mjs, fixtures/*, vertrag-endpunkte.mjs, vorschau/seiten.mjs (eigenes Git) P0 legt an P1 bis P4 und P7 ergänzen nur nach N41
public/neu/manifest.webmanifest, test/support/knHuelle.js, test/knHuelle*.test.js P1 P2 bis P4 dürfen die Testhilfe nutzen
Stubs aus P1: public/neu/ansicht-{website,chat,vertraege,passwort,gutschrift,wechsel,bestellen}.js, ansichten.css; ansicht-funktionen.js, funktionen.css; module.css P1 legt an; danach P2, P3, P4 Besitz geht nach der Anlage an das Paket der Tabelle oben
public/neu/dashboard.html P1 P2 ändert genau zwei Zeilen (Composer-Skript, Stil /neu/chat-composer.css)
public/neu/ansicht-hilfen.js, rechnung-offen.js, install-hinweis.js, website-details.js, vertraege-rechnungen.js, vertraege-abo.js, chat-composer.css, oeffentlich/oeffentlich.css, oeffentlich/auftrag.html, test/support/knDaten.js, knWortlautOrakel.js, test/knAnsicht*.test.js P2 knWortlautOrakel.js friert P10 vor dem Löschen von app.js ein
public/neu/ansicht-bestellen.js, ansicht-wechsel.js, ansicht-speicher.js, ansicht-gutschrift.js P2 baut 1:1, P7 baut um P7 erst nach dem Merge von UI und Recht
public/neu/funktion-einschalten.js, test/support/knFunktionenDaten.js, test/knEinschalten*.test.js, knFunktionen*.test.js P3 P7 Task 0 ändert nur sichereAdresse
public/neu/bilder/* P3 analyse.webp erzeugt P4
public/fn-reiter.js (neu), test/support/modulAbo.js, test/kundenansichtNeuModul*.test.js, Pins test/shopWiring.test.js:31, test/unterkunftWiring.test.js:130 P4
server/services/aboUnterlagen.js, aboKatalog.js, funktionEinschalten.js (neu), featureRegistry.js (auch consentAbo*, abowegAnalyse), test/featureSetup.test.js:191-192, test/aboFassung6Verdrahtung.test.js P5 Verdrahtung der Fassung 6 siehe N33
P5, minimal in fremden Dateien: server/routes/admin/customers.js (code), server/services/ticketAdmin.js (abrechnung), server/services/zeiten/einschalten.js (abrechnung, vorpruefung), server/services/analyse.js, server/services/shop/anfrage.js (stelleAnfrage), server/routes/shop.js (Anfrage) P5 P6 vermeidet diese Funktionen
server/services/aboModell.js, aboFunktionenMails.js, aboFunktionenMeldung.js, aboAufbauphase.js, aboFunktionenAbgleich.js (alle neu) P6 Aufbauphasen-Regel siehe N28
Mailvorlagen K38 bis K43, T24; Block funktionen_abo in K02 und K03 P6 K44 und T25 frei (N29)
P6, minimal in fremden Dateien: server/routes/admin/funktionenAdmin.js, termine/aboAbgleich.js, zeiten/aboAbgleich.js, zeiten/mails.js, zeiten/behalten.js, termine/ticketDetails.js, termine/einbauanleitung.js, shop/ticketDetails.js, unterkunft/ticketDetails.js, shop/anfrage.js und unterkunft/anfrageFunktion.js (nur sendeEntscheidung) P6 nur die in Plan 07 benannten Stellen
server/services/bestelltexte.js, duFehler.js, vertragsNachweis.js, public/neu/bestell-bausteine.js, test/support/knBestellDaten.js, test/kn*.test.js (Plan 08) P7
P7, minimal in fremden Dateien: server/services/stripeBilling.js (createAboCheckout), server/routes/takeoverCredits.js, server/mail-templates/K12 Wechsel Wartungs-Abo.html (Block sofortverlangen), public/neu/oeffentlich/kuendigen.html, widerrufen.html, public/legal-form.js, public/auftrag-widerruf.js (nur data-anrede="du") P7
test/aboWortlautV6.test.js, aboBedingungenV6.test.js, legalV6Build.test.js, funktionenRechtV6.test.js, kundenansichtEntscheidungen.test.js, rechtlichesHochladen.test.js P8
server/routes/umschaltSeiten.js, scripts/umschalten-nachpruefung.mjs, scripts/kauf-bestand-pruefen.mjs, docs/umschalten-kundenansicht.md, test/support/dateiListe.js, modulKlassen.json, oeffentlichStand.json, public/neu/oeffentlich/login.js, public/sw.js:5, Tests knUmschalt*, knFassung6Fest, knLoginModul, knAufraeumen, knKaufRueckbau, knAltZweigeWeg, knSchalterWeg, knOfflineCache, kundenartBestand, kaufBestandPruefen P10 Rückbau der Kaufpfade und Alt-Zweige erst in Phase F
geteilt: server/routes/abo.js P6, dann P7 P6: K02/K03, POST /switch, GET /, withdrawOrder, Widerruf des Wechsels; P7: nur die Pfade der Fassung 6 und router.use für duFehler
geteilt: server/services/planSwitch.js P6, dann P7 P6: details.funktion, completeSwitch, markSwitchFailed; P7: switchToWartung (Z. 347-351, 388, 483), performSwitch (details, K12-Aufruf)
geteilt: server/services/siteFeatures.js P5, dann P6 P5: featureView, legeTicketAn; P6: adminActivate, ticketNachlegen
geteilt: server/index.js je eine eigene Zeile P0 Weiche vor express.static; P5 1 Import, 1 Mount; P6 1 Import, 1 Intervallblock; P10 eine Zeile direkt vor app.use(express.static(publicDir))
geteilt: server/services/products.js P8, dann P10 P8: Fassungstabelle, WORTLAUT_V6 und Konstanten; P10: Zeilen v6 und v5 der Tabelle
geteilt: server/config.js je eigener Abschnitt P0 kundenansichtNeu, P5 aboFunktionen, P8 abo.termsVersionNeu; Ausnahme: die Literale aboFunktionen.fassung und .texte.beginn setzt die Verdrahtung (N33)
geteilt: server/services/featureRegistry.js, funktionEinschalten.js P5 consentAbo ändert nur die Verdrahtung (N33); P6 fügt in funktionEinschalten.js genau einen Aufruf meldeEingeschaltet ein
geteilt: public/neu/ansichten.css P2, dann P7 P7 hängt einen Abschnitt an
geteilt: test/mailTemplates.test.js P6, dann P7 P6: Z. 9, 11, 718; P7: Z. 379

Verträge

V1 Pilot-Schalter (P0)

server/config.js:

kundenansichtNeu: { mode: 'off'|'pilot'|'on', pilotEmails: string[] }   // aus KUNDENANSICHT_NEU, KUNDENANSICHT_NEU_PILOT_EMAILS (commaList)

server/services/kundenansicht/schalter.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 (<script type="module" src="/neu/huelle.js">), kein Build.
  • Wurzel <body class="kn">. Klassen und Tokens tragen das Präfix kn- bzw. --kn-.
  • Die gemeinsamen Funktionsmodule behalten ihre Klassen (tm-*, sh-*, bk-*, zt-*, an-*, fn-*, kb-*). Ihr neues Aussehen steht in public/neu/module.css (P4) unter .kn.

public/neu/dom.js

export function h(tag, attrs = {}, ...kinder)     // wie termine-dom.js/shop-dom.js; attrs.on = { click: fn }
export function leeren(el)
export function text(el, wert)

public/neu/api.js

export async function api(pfad, { method = 'GET', body, roh = false } = {})
// Wirft ApiFehler { status, code, message, field, body }. Bei code 'session_revoked' Redirect auf /login.html.
// Netzwerkfehler: status 0.
export class ApiFehler extends Error {}

public/neu/routen.js (rein, ohne DOM, testbar)

export const BEREICHE = ['website', 'funktionen', 'chat', 'vertraege']
export const ROUTEN    // hash → { bereich, reiter?: string, etage?: string }
export function routeAusHash(hash)   // '#termine?bestellt=1' → { bereich:'funktionen', etage:'termine', query:'bestellt=1' }
export function hashFuerRoute(route) // Umkehrung

Pflichtinhalt von ROUTEN:

  • ''/'#' → website
  • projekt → website, etage projekt
  • fortschritt → website (scrollt zur Aktivität)
  • funktionen → funktionen, reiter meine; entdecken → funktionen, reiter entdecken
  • news, termine, webshop, buchungen, zeiten, analyse → funktionen, etage = gleicher Name
  • chat → chat
  • vertraege, abo, wartung → vertraege, reiter abo
  • rechnungen → vertraege, reiter rechnungen
  • stunden → vertraege, reiter stunden
  • speicher (neu) → vertraege, reiter email
  • gutschrift → vertraege, etage gutschrift
  • wechsel → vertraege, etage wechsel
  • bestellen-hosting, bestellen-wartung → vertraege, etage bestellen, plus Produkt
  • passwort → website, etage passwort (Rückweg = Herkunft)

public/neu/router.js

export function starteRouter({ zeige })        // liest location.hash, hört auf popstate, ruft zeige(route, { nav: 'tab'|'push'|'back'|'same' })
export function geheZu(hash, { ersetzen = false } = {})   // Reiterwechsel: replaceState; Etage 2, Sheet, Seiten: pushState

Hülle

  • huelle.js (Einstieg): lädt /api/auth/me und leitet ab wie heute.
  • leiste.js: Fahrstuhl-Zeile Handy, Menü links am Desktop.
  • konto.js: Konto-Sheet bzw. Popover.
  • fristen-streifen.js, zaehler.js.
  • etage.js: Etage 2 mit Zurück und Reitern.
  • reiter.js: gemeinsame Unterstrich- und Balken-Reiter.

Ansichten. Jede exportiert:

export async function zeigeX(wurzel, { route, me, nav })   // rendert in wurzel, darf Daten selbst laden

Funktionsmodule werden von der neuen Hülle wie heute aufgerufen: initTermine(document.getElementById('termine-body')) usw. Die Projektwahl übergibt die Hülle einheitlich als wurzel.dataset.wunschProjekt (C1). News und Analyse lesen dasselbe Attribut zusätzlich zu ihren bisherigen Modulvariablen (P4).

V3 Fahrstuhl-Zeile und Hülle (P1)

  • Referenz ist /root/reports/kundenansicht-neu-2026-10-15/handy-leiste/final-2-frei.html: Balken mit Overlay-Kopie und Gegenbewegung, Rückfall fb, Etage 2 als reel mit translateY(-56px), Blatt t2-dp.
  • Handy-Layout gilt mit derselben Medienabfrage wie heute: (max-width: 880px), (pointer: coarse) and (max-height: 500px).
  • Leiste:
    • Wörter: Website, Funktionen, Chat, Verträge. Schrift 15 px/600, bei Platzmangel 14 px.
    • Balken: Radius 6, Höhe 36 px.
    • Tap-Ziele mindestens 44 px. Unten env(safe-area-inset-bottom).
    • Bei offener Tastatur wird die Leiste ausgeblendet. Die visualViewport-Logik von heute wird übernommen.
  • Etage 2: „Zurück“ und die Reiter der Unterseite. Hat die Unterseite keine Reiter, füllt „Zurück“ die ganze Zeile. Bei Platzmangel scrollt die Zeile waagerecht, und der aktive Reiter wird in den sichtbaren Bereich gerückt (C2).
  • Desktop: Menü links mit 232 px und Haarlinie rechts, aktiver Eintrag schwarz. Unterseiten zeigen eine Brotkrume „Funktionen / Terminplaner“. Oben rechts das Profilbild, das Konto öffnet als Popover.
  • Konto (Reihenfolge laut Spec 5.2):
    1. Name und E-Mail
    2. Passwort ändern
    3. Datenschutz
    4. Impressum
    5. App installieren (nur wenn sinnvoll)
    6. Verträge kündigen (/kuendigen.html)
    7. Vertrag widerrufen (/widerrufen.html, immer da; hervorgehoben mit der Pille „Frist läuft“, wenn GET /api/fristen anzeigen liefert)
    8. Abmelden
  • Widerrufsstreifen: erscheint, wenn anzeigen === true, mit dem Text Widerrufsfrist bis TT.MM. · Widerrufen (früheste Frist, V6). Nicht wegtippbar, bei offener Tastatur ausgeblendet.
  • Zähler:
    • Chat: GET /api/chat/unread-count, Poll alle 45 s wie heute.
    • Verträge: Zahl der Rechnungen mit balance > 0 aus GET /api/invoices plus Rückfragen aus GET /api/abo/takeover-credits (status === 'rueckfrage'). Beides wird nach einer Zahlung, bei der Rückkehr in den Tab und beim Reiterwechsel neu geladen.
    • „!“ steht bei subscription.paymentProblem. Nicht bei switchPending.
  • Bewegung reduziert: keine Federn, nur 150 ms Überblenden.

V4 Kundenart (P5)

server/services/kundenart.js:

export const KUNDENARTEN = ['unternehmen', 'privat']       // Re-Export aus auftraege/konstanten.js
export function kundenartGespeichert(customerDoc)        // 'unternehmen' | 'privat' | ''
export function kundenartWirksam(customerDoc)            // 'unternehmen' | 'privat'  (leer/unbekannt → 'privat')

Wo die Kundenart erscheint

  • GET /api/auth/me liefert in customer zusätzlich kundenart: kundenartGespeichert(doc) und kundenartWirksam: kundenartWirksam(doc). Das gilt für alle drei Zweige (Login, Kundenansicht, Kunde).

Anlegen und Ändern (customerAdmin.js)

  • createCustomerWithPortalAccess(payload) nimmt kundenart. Pflichtwert ist 'unternehmen' | 'privat', sonst 400 mit code: 'kundenart_fehlt'.
  • updateCustomerWithPortalAccess nimmt kundenart optional, mit gleicher Prüfung.
  • sanitizeCustomer gibt kundenart zurück.

Nachweis

  • featureConsents bekommt das Attribut kundenart (string 12). Es wird in scripts/kundenansicht-setup.mjs angelegt und von test/support/schema.js gelesen.
  • Jede neue Zustimmung speichert kundenartWirksam zum Klickzeitpunkt.

V5 Katalog und Einschalten (P5)

Abo-Zuordnung. server/services/aboFunktionen.js:

export const ENTHALTEN_IN = Object.freeze({ analyse: ['hosting','wartung'], news: ['wartung'], termine: ['wartung'], shop: ['wartung'], unterkunft: ['wartung'], zeiten: ['wartung'] })
export async function aboProdukt(customerId)   // { produkt: 'hosting'|'wartung'|null, laeuft: boolean, gekuendigt: boolean, widerrufsfristBis: string|null /*ISO, nur Abschluss/Wechsel*/ }
export async function aboDeckt(customerId, key) // { ok: true } | { ok: false, grund: 'kein_abo'|'falsches_abo'|'gekuendigt'|'nicht_aktiv' }
  • laeuft gilt für die Status active | trialing | past_due, wie ACTIVE_STATUSES.
  • gekuendigt bedeutet „Kündigung vorgemerkt“. aboDeckt liefert dann gekuendigt, eingeschaltet wird nicht.

GET /api/funktionen. Ohne neue Ansicht bleibt die Antwort unverändert. Mit neuer Ansicht kommen hinzu:

modell: 'abo',
kundenart: 'unternehmen'|'privat',
abo: { produkt, laeuft, gekuendigt, widerrufsfristBis },
katalog: [{
  key,                           // Reihenfolge: news, termine, shop, unterkunft, zeiten, analyse
  name, beschreibung,            // beschreibung: höchstens 2 Sätze, du-Form
  bild: '/neu/bilder/<key>.webp',
  enthaltenIn: 'hosting'|'wartung',   // kleinstes Abo, das die Funktion enthält
  verfuegbar: boolean,           // Schalter der Funktion für diesen Kunden (off → false, Pilot → Liste)
  ablauf: 'direkt'|'fragebogen'|'anfrage',   // termine: fragebogen; shop, unterkunft: anfrage; sonst direkt
  einrichtung: string,           // Dauer, z. B. 'meist in 1–2 Wochen'
  unterlagen: { leistungsbeschreibung: {version,url}|null, avv: {version,url}|null },
  nurBetrieb: boolean            // shop: true (C15)
}]
  • Je Projekt und Funktion zusätzlich anfrageStatus: ''|'offen'|'angenommen'|'abgelehnt'|'zurueckgezogen' (nur Shop und Unterkunft).
  • Auf Anfrage (?kurzinfo=1, nur „Meine“) je Projekt und Funktion zusätzlich kurzinfo: string | null (Live-Detail der Zeile in „Meine“, nur aktive Funktionen, Fehler → null; ohne den Parameter fehlt das Feld; N52).
  • Funktionen mit verfuegbar: false stehen trotzdem im Katalog. Die Oberfläche blendet sie aus (C9).
  • Die Analyse erscheint im Katalog nur, wenn ANALYSE_ENABLED === 'on'.

POST /api/funktionen/:key/einschalten. Ohne neue Ansicht → 404. In der Kundenansicht → 403. Für analyse → 404, die Analyse nutzt weiter POST /api/funktionen/analyse/activate.

Body:

{ projectId, zustimmung: { version, haken: [] | ['beginn'] } }

Antwort 200:

{ feature: featureView(doc, key), ticketWoid }

Fehler 409 mit code, Prüfreihenfolge:

  1. nicht_verfuegbar (Schalter)
  2. abo_fehlt mit body.grund und body.wechsel: true, wenn Hosting läuft und die Funktion Wartung braucht
  3. nur_betrieb (Shop bei privat)
  4. bestellung_offen mit body.offen, wie heute: Fragebogen beim Terminplaner, Anfrage nicht angenommen bei Shop und Unterkunft
  5. beginn_noetig, wenn kundenartWirksam === 'privat', die Widerrufsfrist des Abos läuft und haken nicht 'beginn' enthält; body.ab nennt das ISO-Datum nach Fristende
  6. bereits_eingeschaltet (Zustand setup oder active)
  7. fassung_veraltet

Erfolg:

  • einschalten({ customerId, projectId, key, by: 'kunde', abrechnung: 'abo', nachweis, now }) (V7).
  • Der Nachweis kommt in featureConsents mit Texten. Pflicht sind einschalten-zeile (Linkzeile) und knopf (Einschalten). Das Häkchen beginn kommt nur dazu, wenn es nötig war.

Zustimmungs-Register für das Abo-Modell. Jede Funktion im Register bekommt consentAbo:

{ version, knopf: 'Einschalten', items: [{ id: 'einschalten-zeile', text, haken: false }, { id: 'beginn', text, haken: true, nurPrivatInFrist: true }] }
  • Den Wortlaut von beginn legt P8 fest. Bis dahin gilt der Entwurf aus Spec 5.6, Punkt 3, für die Einrichtung.
  • Das Item gesundheit entfällt im Abo-Modell. Der Fragebogen sperrt den Gesundheitsbereich weiter.

Anfragen im Abo-Modell

  • Shop: unternehmer wird nicht mehr vom Client verlangt, sondern aus kundenartWirksam === 'unternehmen' gesetzt. Privat führt zu 409 nur_betrieb.
  • keineAusgeschlossenen bleibt Pflicht als Satz über dem Knopf. Der Client sendet true beim Klick auf „Anfrage senden“, der Nachweis kommt in die Anfrage.

V6 Fristen (P5)

server/services/fristen.js:

export async function sammleFristen(customerId, { now = new Date() } = {})
// → { fristen: [{ quelle: 'abschluss'|'wechsel'|'speicher'|'funktion'|'projekt', bezeichnung, bis /*ISO*/, featureKey?, projektName? }],
//     fruehestesEnde: string|null, anzeigen: boolean }
  • anzeigen ist wahr, wenn kundenartWirksam(customer) === 'privat' gilt und fristen.length > 0.
  • Quellen: openWithdrawals (Abo), paidFeatureOrders + withdrawalDeadline (gekaufte Funktionen) und die Aufträge (nur bei AUFTRAEGE_ENABLED und privat).

Route GET /api/fristen (server/routes/fristen.js, Mount /api/fristen, requireSession). Antwort wie oben. In der Kundenansicht ist der Aufruf erlaubt, weil er lesend ist.

V7 Generisches Einschalten und Tickets (P5, P6, P9)

server/services/funktionEinschalten.js:

export async function einschalten({ customerId, projectId, key, by, abrechnung, nachweis = null, staff = null, now = new Date() })
// by: 'kunde'|'team'; abrechnung: 'abo'|'projekt'
// → { doc, ticketWoid }
  • Setzt state: 'setup', unlockedBy: abrechnung === 'projekt' ? 'projekt' : 'abo' und unlockedByPurchase: false.
  • Legt das Ticket über createFeatureTicket an, unter der Sperre feature-order:${customerId}:${projectId}.
  • Für zeiten nutzt die Funktion den bestehenden Weg (einschaltenZeiten, mit unlockedBy je nach abrechnung).

Ticket-Attribute (workorders, angelegt in scripts/kundenansicht-setup.mjs):

  • einrichtungFuer (string 16): Funktionsschlüssel
  • einrichtungAbrechnung (string 8): 'abo' | 'projekt' | ''

Gesetzt werden sie von createFeatureTicket, sobald der Aufrufer abrechnung übergibt (neuer optionaler Parameter). Der Ticket-Text sagt bei abo: „Zeit im Arbeitsblatt erfassen und aufs Abo-Stundenkonto anrechnen (Einrichtung aus dem Abo, Vorschuss möglich).“ Bei projekt: „Zeit im Arbeitsblatt erfassen, nicht aufs Abo anrechnen (im Projekt enthalten).“

unlockedBy hat die Werte 'kauf'|'abo'|'team'|'projekt'. unlockedByVon und featureView liefern unlockedBy künftig für alle Funktionen, nicht nur für aboInklusive.

V8 Stundenkonto mit Vorschuss (P6)

computeLedger(credits, usage, opts):

  • Ein Eintrag mit einrichtung === true ist gesetzt, wenn das Arbeitsblatt zu einem Ticket mit einrichtungAbrechnung === 'abo' gehört. Den Join macht listPlanUsage, auch für Git-Blätter.
  • Der ungedeckte Rest eines solchen Eintrags zählt als vorschussMinutes, nicht als overageMinutes.
  • Gedeckt wird wie heute: FIFO, rückwirkend, expiresAt nach der Arbeitszeit. Neu: Gutschriften, die erst nach der Arbeitszeit gebucht wurden, decken Einrichtungseinträge vor jedem anderen Eintrag. So gleicht eine spätere Gutschrift zuerst den Vorschuss aus.
  • Ergebnis zusätzlich:
{ vorschussMinutes, usage: [{ ..., einrichtung: boolean, vorschussMinutes }] }
  • Bei active === false bleibt vorschussMinutes stehen. Die Oberfläche zeigt dann „verfällt, wird nicht berechnet“.
  • Team-Endpunkt GET /api/team/customers/:id/stundenkonto (requireStaffJwt):
{ produkt: 'hosting'|'wartung'|null, ledger: { balanceMinutes, vorschussMinutes, overageMinutes, nextExpiry } | null }

V9 Kundenart und Abo-Vorgabe im WOMS (P9 gegen V4, V7, V8)

  • Pflichtfeld Kundenart, ohne Vorgabe. Die Auswahl erfolgt per Segment-Schalter .seg mit den Labels „Unternehmen“ und „Privatperson“, Werte aus KUNDENART_OPTIONEN ohne den Leer-Eintrag. Es steht an diesen Stellen:
    • CreateLeadForm: createLead schreibt kundenart.
    • CreateCustomerForm: Body an POST /api/admin/customers mit kundenart.
    • „Zum festen Kunden machen“: Upgrade nur mit gesetzter Kundenart.
    • Arbeitsblatt-Fluss „Zugesagt“ (worksheetFlow.js): Ohne Kundenart wird nicht hochgestuft, das Team bekommt einen Hinweis.
  • Kundenliste: Schalter „Kundenart fehlt“ in der Toolbar, kein neuer Reiter.
  • Arbeitsblätter: Bei Tickets mit einrichtungAbrechnung === 'abo' steht countsToPlan standardmäßig auf an, im Modal und beim Nachtragen der Zeit eines Git-Blatts. Bei 'projekt' steht es auf aus.
  • Ticket-Seite eines Einrichtungs-Tickets: zeigt Kontostand und Vorschuss aus V8.
  • Funktionen-Reiter:
    • Kein Preis mehr (funktionenView.js:22).
    • „Aktivieren“ fragt „aus dem Abo“ oder „im Projekt enthalten“ und sendet abrechnung.
    • Die Abo-Prüfung macht der Server (V10). Antwortet er mit abo_fehlt, zeigt WOMS den Grund.

V10 Team-Aktivierung (P6)

POST /api/team/funktionen/:customerId/:key mit action: 'activate' nimmt zusätzlich abrechnung: 'abo'|'projekt' (Vorgabe 'abo').

  • Ohne aboDeckt(customerId, key).ok → 409 mit code: 'abo_fehlt' und grund (E21). Ausnahme: Dokumente mit unlockedBy === 'kauf' (Bestand).
  • Die Aktivierung läuft über einschalten({ by: 'team', abrechnung, staff }) mit Ticket. Wie bisher wird nicht direkt active gesetzt. Ausnahme: action: 'activate' auf einen Zustand disabled mit disabledReason === 'team' setzt wie heute direkt active.
  • Die Textform-Regeln bleiben, wie sie heute sind (textformFuerAktivierung).

V11 Wechsel mit gemerkter Funktion (P6)

  • POST /api/abo/switch nimmt optional funktion: { key, projectId }, aber nur mit neuer Ansicht. Ohne neue Ansicht wird das Feld ignoriert. Es wird in details.funktion des Ereignisses wechsel gespeichert.
  • GET /api/abo liefert in subscription zusätzlich wechselFunktion: { key, projectId } | null, nur mit neuer Ansicht:
    • gesetzt, wenn das jüngste wechsel-Ereignis erledigt ist, details.funktion hat und diese Funktion für das Projekt noch off ist, längstens 14 Tage nach doneAt;
    • bei gescheitert oder widerrufen → null.
  • Ein Widerruf des Wechsels beendet zusätzlich alle Funktionen mit unlockedBy === 'abo', deren Schlüssel nur in wartung steckt (disable(..., { reason: 'abo_ende' })).
  • Client (P3 → P2/P7): Antwortet „Einschalten“ mit abo_fehlt und wechsel: true, schreibt die Oberfläche sessionStorage['kn-wechsel-funktion'] = JSON.stringify({ key, projectId }) in einem try/catch und geht zu #wechsel. ansicht-wechsel.js sendet den Wert als funktion mit und löscht ihn nach Erfolg. Steht in GET /api/abo ein subscription.wechselFunktion, öffnet ansicht-funktionen.js beim nächsten Aufruf den Einschalt-Ablauf dieser Funktion.

V12 Abo-Fassung 6 und Bestellseiten (P8, P7)

server/config.js (P8):

abo.termsVersionNeu = '<JJJJ-MM-TT>-v6'   // das Datum setzt P8; config.abo.termsVersion bleibt v5

server/services/products.js (P8): Statt endsWith steht dort eine Fassungstabelle.

export const FASSUNGEN    // [{ muster: /-v6$/, url:'/abo-bedingungen-v6.html'?, pdfFile, endParagraph }, { muster: /-v5$/, ... v5-Archiv }, { muster: /-v4$/, ... }, { sonst: v3 }]
export function aktuelleAboFassung(req)   // neueAnsicht(req) ? config.abo.termsVersionNeu : config.abo.termsVersion
export function isTermsV4OrNewer(t)        // v4, v5, v6
  • termsDocFor und contractAttachmentFor lesen aus der Tabelle.
  • Die Seite der Fassung 6 entsteht unter einem eigenen Pfad, solange der Schalter nicht on ist. Erst P10 macht sie zu /abo-bedingungen.html, nach Archivierung von v5 unter public/vertragsunterlagen/v5/. Den Pfad legt P8 fest und trägt ihn in diese Übersicht nach.

Server-Regeln für Fassung 6 (P7). Sie gelten nur, wenn der Body termsVersion === config.abo.termsVersionNeu trägt und neueAnsicht(req) gilt:

POST /api/abo/checkout

  • acceptTerms === true (der Client sendet es beim Klick auf „Zahlungspflichtig bestellen“), dazu earlyStart: boolean.
  • unternehmen → startChoice: 'sofort', earlyStartConsentAt: '', Grund kundenart: 'unternehmen' im Ereignis.
  • privat und earlyStart → sofort mit earlyStartConsentAt.
  • privat ohne earlyStart → nach_frist.

POST /api/abo/switch

  • earlyStart ist nur bei privat Pflicht.
  • Bei unternehmen gilt der Wechsel ohne earlyStartConsentAt, der Grund steht im Ereignis. planSwitch.js:351 bekommt dafür den Parameter kundenart.

POST /api/abo/storage: unverändert. Das Plus sendet acceptTerms: true beim Klick.

Nachweis. server/services/vertragsNachweis.js:

export async function speichereVertragsNachweis({ customerId, art: 'abo'|'wechsel'|'speicher'|'gutschrift', bezug, texte, fassungen, kundenart, req, at })
  • Speichert in featureConsents mit featureKey = art und projectId = ''.
  • stripeSessionId ist die Sitzungs-ID, wenn vorhanden.
  • texte = die angezeigten Zeilen, Häkchen und Knopf.

V13 Mails (P6)

  • Neue Kennungen K38–K41 und T24–T25 (frei laut test/mailTemplates.test.js). Belegt werden:
    • K38 „Deine Funktion wird eingerichtet“ (Einschalten im Abo-Modell)
    • K39 „Deine Funktion wurde beendet“ (Abo-Ende für News, Shop, Unterkunft im Abo-Modell; Platzhalter funktion, projekt, loeschtag)
    • T24 „Funktion eingeschaltet (Abo)“ ans Team, mit Block vorschuss, wenn das Guthaben nicht reicht
  • K40, K41 und T25 bleiben frei für P6, falls nötig. P6 trägt die Belegung nach.
  • Alle neuen Vorlagen sind in „du“-Form und im Layout von K16.

Ablaufhinweise für den Controller

  1. P0 zuerst. P0 endet mit dem Tag kn-p0 und dem Arbeitsauftrag an den Inhaber: In WOMS „Neues Projekt“ (Website, interner Kunde) mit Subdomain kundenansicht-neu anlegen, damit https://kundenansicht-neu.project.webklar.com existiert. Vorher wird die Vorschau nicht ausgeliefert.
  2. Phase B: vier Stränge parallel. P9 baut gegen V4, V7, V8 und V10, mit dem Testbackend der WOMS-Vorschau (Whitelist wie customerAdmin.js, siehe Karte woms-vorschau Fund 2).
  3. Merges in den Hauptzweig nach der Reihenfolge oben. Danach jeweils die betroffenen Testdateien mit den Befehlen aus den Global Constraints.
  4. Phase E:
    • ganze Suite Portal und WOMS
    • Werkzeug-Gates aller Ansichten auf Handy, Tablet und Desktop
    • Vorschau aktualisieren (nur cp)
    • Abschlussprüfung mit vier Blickwinkeln: Recht und Nachweise, alte Ansicht unverändert, Handy und Barrierefreiheit, Abo-Modell und Stundenkonto
    • Korrekturen
    • Vor dem Push des Inhabers, beide mit Sicherung und OK des Inhabers (Nachtrag N47):
      • Compose- und .env-Zeilen KUNDENANSICHT_NEU (Modus pilot) und KUNDENANSICHT_NEU_PILOT_EMAILS (Pilot-Liste) anlegen, Sicherung .bak-<Datum>-kundenansicht, dann docker compose up -d --no-deps kundenbereich-server, nie preview-portal. pilot erst setzen, wenn WOMS kn-p9-kundenart live ist (Nachtrag N24).
      • scripts/kundenansicht-setup.mjs --pruefen, nach OK des Inhabers ausführen (sonst scheitert jeder Nachweis ohne featureConsents.kundenart mit 500).
    • Übergabe-Notiz unter /root/reports/kundenansicht-neu-2026-10-15/uebergabe/ mit den Push-Befehlen für den Inhaber, der Push-Reihenfolge (N24) und den beiden Schritten oben
  5. Rechtsrunde: P8 legt docs/rechtliches/kundenansicht-entscheidungen.md an. Der Controller geht die Liste mit dem Inhaber durch (E20). Hochgeladen wird erst nach seiner Freigabe, mit scripts/rechtliches-hochladen.mjs, zuerst --probe.
  6. P10 startet erst, wenn der Inhaber ausdrücklich „umschalten“ sagt.