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>
80 KiB
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.htmlmit eigenen ES-Modulen und CSS unterpublic/neu/. Der Server liefert sie unter derselben Adresse/dashboard.htmlaus, 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 Feldmodellder 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 --testmit den Nachbauten austest/support/(appwriteMock,fakeStripe,testApp,fixtures,fakeDom,mailSink) und Playwright-Skriptetest/ui/*.ui.mjs. - WOMS: React 18, Vite, Tests
node --testübertests/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, Pinsfunktionen-server.md: Funktionen, Zustände, Bestellen, Aktivieren, Abo-Abgleichabo-server.md: Stundenkonto, Abo, Wechsel, Widerruf, Fassungen, Kundenart, Mailswoms-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 statussauber. Eine gemeinsame Abschlussprüfung gibt es in Phase E. Kleine Nachprüfungen mithaiku. Keinopus, keinfable(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 Hauptzweigkn-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_modulesim neuen Worktree. Der Symlink ist unversioniert, deshalb niegit 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. Danachnpm cimit Speicherbremse, siehe Global Constraints. - Der WOMS-Zweig
fix/akquise-sichtbar(5f13553, Worktreedev-akquise-sichtbar) ist nicht gepusht und ändertCreateLeadFormundcreateLead. P9 baut auforigin/test. Beim Merge intestgilt die Reihenfolge, die der Inhaber festlegt. Istfix/akquise-sichtbarbis dahin intest, 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_NEUistoff | pilot | on, Vorgabeoff.KUNDENANSICHT_NEU_PILOT_EMAILSist eine Komma-Liste,*heißt alle. Der Modusoffschlägt jede Liste. - Aussehen (Spec 5.1, Werte wörtlich aus dem Ticketsystem
src/styles/global.css:7-51):- Farben:
#fffFläche, Text#1a1f36, Zweittext#5e6678, Tertiär#8a93a3nur für Nebensächliches, Linien#ebeef1und#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, Zahlentabular-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.
- Farben:
- 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.htmlundwiderrufen.htmlbleiben 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 überkundenartWirksam()(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.
- Einzeldatei:
- 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-core1.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 aufmainodertest. Pushen tut nur der Inhaber. - Keine Änderungen in
build/, weder im Portal noch im WOMS.
- Kein Push, kein
- Sicherheitsblock in jedem Agent-Auftrag (Memory „feedback-destruktive-skripte“):
- Kein
rm -rfaußerhalb des eigenen Scratch-Ordners, nie/dev,/etcoder/anfassen. - Kein
docker run, keine globalen Installationen. - Das Vorschau-Skript nie mit Ziel aufrufen, nur
--pruefen. Nie mit Pfaden wie..testen. TMPDIRauf den eigenen Scratch-Ordner setzen.- Nach großen Läufen prüfen, dass
ls -l /dev/nullcrw-rw-rw-zeigt.
- Kein
- Prod-Daten: Kein Paket schreibt in die Live-Datenbank. Ausnahmen sind die Setup-Skripte (
scripts/kundenansicht-setup.mjs) undscripts/kundenart-bestand.mjs. Beide laufen nur auf ausdrückliche Anweisung des Controllers nach OK des Inhabers, vorher immer mit--pruefen.
Review Focus
- 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
kundenartvom Client wird ignoriert. Tests: P5 Task „kundenartWirksam“ und „Einschalten privat in Frist“, P7 Task „Bestellung Fassung 6“. - Pilot-Schalter an den Rändern: Groß-/Kleinschreibung und Leerraum der E-Mail,
*, leere Liste,offmit*, Mitarbeiter-Kundenansicht (viewAs+viewId) im Pilot. Ohne neue Ansicht liefern die Abo-Modell-Endpunkte 404, und/dashboard.htmlist die alte Seite. Tests: P0 Tasks „Schalter“ und „Dashboard-Weiche“, P5 Task „Einschalten nur in neuer Ansicht“. - Alte Links nach dem Umbau:
#termine?bestellt=1&projekt=x,#webshop?stripe=zurueck&projekt=x,#rechnungen,#stunden,#gutschrift,#wartung,#fortschritt,?abo=success, Login mitnext=/dashboard.html#chat. Jeder Link landet am richtigen neuen Ort, und die Query bleibt erhalten. Test: P1 Task „Routen“. - 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“.
- 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
customerIdundappwriteUserIdohnerole: 'admin'; Kundenansicht heißtviewAs === truemitviewId. Alte Mitarbeiter-Sitzungen (role: 'admin'ohneviewId) sind in jedem Modusfalse, auch bei*. Eineemail, die kein Text ist, zählt als leer. (P0 VF3; F12) - N2 Schalter für Jobs ohne Cookie (V1).
schalter.jsbekommt den vierten Exportexport function neueAnsichtFuerKunde({ customerId, email }) // boolean. Gleiche Modus- und Listenregeln wieneueAnsichtFuerSession(offfalse,ontrue,pilotbei*oder getrimmter, klein geschriebeneremailin der Liste), aber ohne Sitzungsprüfung und ohne Mitarbeiter-Regel; leere oder nicht-textlichecustomerIdund Nicht-Objekte ergebenfalse. P5aboModellFuerKunde(customerId)liest das Kundendokument und ruft sie;runAboCheckund P6 (Team-Aktivierung, Widerruf des Wechsels, Tagesjob) nutzen nur sie, nieneueAnsichtFuerSession. (P0 Task 2, VF10; P5 Task 13; F12) - N3 Weiche (V1).
server/services/kundenansicht/dashboardWeiche.jsist eine Fabrik (server/index.jsstartet beim Import den Server). Der Direktaufruf/neu/dashboard.htmlwird für Sitzungen ohne neue Ansicht per 302 auf/dashboard.htmlgeleitet; Module und CSS unter/neu/bleiben frei abrufbar. Bei Modusonliefert die Weiche von P10 (UMSCHALT_SEITEN, Zeile/dashboard.html) die neue Seite auch ohne Sitzung;neueAnsichtFuerSessionbleibt unverändert. (P0 VF1, VF2; P10 Task 3, VF7; F12) - N4
/api/auth/me.ansichtsteht nur in den beidenauthenticated: true-Antworten vonGET /me. P5 ändert inauth.jsalleinsanitizeCustomer(kundenart,kundenartWirksam);test/kundenansichtMe.test.jspinnt nur die Schlüssel der obersten Ebene, damitcustomerwachsen 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">unddata-kn-platzhalter) und pinnt inistNeunur die Meta-Zeile. P1 Task 12 ersetzt die Seite, ohne einen P0-Test zu ändern; die Werkzeug-Ansichtenplatzhalterundplatzhalter-abgemeldetbleiben, 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, reiterabo“ entfällt. Neu:vertraege→ Bereichvertraegeohnereiter(die Ansicht wählt den Standardreiter, bei offener Rechnungrechnungen, sonstabo);aboundwartung→ Bereichvertraege, reiterabo, kanonischer Hash#abo(#wartungwird#abo,#vertraegebleibt#vertraege);?abo=success|cancel→#abo.waehleReiter(P2) entscheidet nur überroute.reiter; Reiterklicks navigieren mitersetzen: truezu#abo,#rechnungen,#stunden,#speicher, undankerKette/elternVonliefern weiter#vertraegeals Bereichs-Anker. (P1 Task 3/4, VF4; P2 Task 16, VF8; F17) - N7 Routenfelder und Exporte (V2). Route-Felder zusätzlich
produkt('hosting' | 'wartung') undscroll('aktivitaet');queryist immer ein String. Zusätzliche Exporte vonrouten.js:hashFuerAnsicht(name)(diedata-view-Brücke der gemeinsamen Module),startHash,ankerKette,elternVon,titelFuer,FUNKTIONS_ETAGEN. Die Projekt-ID heißt einheitlich#projekt?projekt=<id>(Lesen vonid=bleibt Rückfall); eindata-projectan 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) wiefn-util.js request({ raw }), nicht eine rohe Antwort; der V2-Kommentar ist so zu lesen.?abo=bleibt inlocation.search; P2 räumt mithistory.replaceState(history.state, '', location.pathname + location.hash)auf, und P4 ändert diehistory.replaceState-Aufrufe der Module nicht (stempeln()aus P1 stempelt danach neu).public/neu/manifest.webmanifesthat dieselbeid,start_urlundscopewie die alte;sw.jsbleibt gemeinsam und wird in P1 Task 11 registriert. Ansichten rendern keine eigeneh1; der Titel kommt austitelFuer(route)oderkn: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) })inpublic/neu/reiter.jsliefert direkt einHTMLElementmitsetzeAktiv,setzeAnzeige,messen; P2 und P3 passen sich an. Inhalte in Etage 2 melden Reiter und Titel ohne Import perCustomEventmitbubbles: true:kn:reiter(detail { eintraege, aktiv, bei }, leere Liste = keine Reiter) undkn: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 ausmountCockpitmitbei: pickTab: Übersicht, Heatmap, Einstellungen). Die Ausnahme „außer analyse“ im Gatep4entfällt; der Satz „die Analyse behält ihre eigene Leiste“ in F18 ist überholt. Kurzlabels stehen inKURZLABELS(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-neuwird amdocumentgesendet (bubbles: true),detail.wasist'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
zaehleran die Ansichten;zeigeX(wurzel, { route, me, nav })bleibt wie in V2. P2 liefertpublic/neu/rechnung-offen.js(offeneRechnungen,rechnungOffenZeile,ladeOffeneRechnungen, optionalladeOffeneRechnungen(apiFn = api)); P3zeigeFunktionenlädtGET /api/invoicesselbst überabh.apiund 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
einschaltenfür die Funktionsmodule (V2, Absatz „Funktionsmodule“). Die Hülle ruftinitNews,initTermine,initShop,initBuchungen,initZeitenalsinit*(wurzel, { einschalten })miteinschalten(platz, { key, projectId, onFertig, viewAs? }) → Promise, das lazyzeigeEinschaltenauspublic/neu/funktion-einschalten.js(P3) lädt (MIT_EINSCHALTEN,einschaltenOption(lade)inhuelle.js);initAnalyse(wurzel)bleibt unverändert, die Projektwahl bleibtwurzel.dataset.wunschProjekt.test/knHuelleStart.test.js(„Option einschalten“) pinnt die Verdrahtung; P4 Task 15 ersetzt seine Zeilecommand grep -a -n einschalten public/neu/huelle.jsdurch den Lauf dieses Tests. (P1 Task 11, VF12; P3 VF1; P4 VF1; F1, F21) - N14 Overlay. Ansichten dürfen
oeffneOverlayundbeendeOverlayausrouter.jsrufen (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 vonpublic/chat-composer.css(Akzent#111/Grau, weder#9d3dfenoch Gewicht 800 nochbackdrop-filter);/chat-composer.jsbleibt gemeinsam, das alte CSS unverändert. P2 Task 7 legt die Kopie an und ändert dafür genau die zwei Composer-Zeilen indashboard.html;test/knHuelleSeite.test.jsakzeptiert/neu/chat-composer.cssstatt/chat-composer.css. (P1 VF3; P2 Task 7, VF10; F23) - N16 Analyse-Bild und Alt-Klassen.
public/neu/bilder/analyse.webperzeugt P4 über die Werkzeug-Ansichtkn-modul-analyse(Fallm-analyse-aktiv); bis dahin entfernt dererror-Handler in „Entdecken“ das fehlende Bild.public/neu/module.cssAbschnitt 0 (P4) definiertkb-panel,kb-btn*,kb-empty,kb-link,kb-checks,abo-actions,abo-btn,hidden,muted,error, außer eine Datei unterpublic/neu/tut es schon; P2 und P3 nutzen eigenekn-*-Klassen. (P3 VF2; P4 Task 9/14, VF3, VF5; F24)
Katalog, Einschalten, Analyse
- N17 Katalogfelder (V5). Jeder
katalog[]-Eintrag trägtconsent: { version, knopf: 'Einschalten', items: [{ id, text, haken, nurPrivatInFrist? }] }(nullbeianalyse) in der Reihenfolgeeinschalten-zeile,hinweis,beginnund dazuhinweis: string | null, den Text des Itemshinweis(nurshop,unterkunft,termine: die Pflichten der alten Häkchenverantwortung,stripe,gesundheit). Der Server liefert beides, nie der Client; der Nachweis speichert den Hinweis als Texthinweis(haken: false). P3renderEinschaltenzeichnetp.kn-fn-hinweisdirekt über dem Knopf (nach der Linkzeile, vorbeginn), die P0-Fixtures undvertragNeutragen 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 istprojects[].features.<shop|unterkunft>.anfrageStatus('' | 'offen' | 'angenommen' | 'abgelehnt' | 'zurueckgezogen'), gebaut vonanfrageStatusJeProjektinserver/services/aboKatalog.js; P3 liest ihn nur überanfrageStatusVon(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 409nicht_verfuegbar(danach 503unterlagen_fehlen),abo_fehlt,nur_betrieb,bestellung_offen,beginn_noetig,bereits_eingeschaltet,bestellung_laeuft(Zustandordered),abgeschaltet(Zustanddisabled),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).
aboDecktkennt zusätzlich den Grundbeginnt_spaetermitab(ISO, =trialEnd) fürtrialingmit offener Probezeit (Privatkunde ohne Sofortbeginn-Häkchen).abo_fehltträgt dannbody.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 weiterkein_abo. (P5 Task 4/9/10, VF9; P3 VF4; F8) - N21 Abrechnung am Funktionsdokument (V7). Das Attribut
portalFeatures.einrichtungAbrechnung(string 8,'abo' | 'projekt' | '', angelegt inscripts/kundenansicht-setup.mjs) hält die Abrechnung am Dokument, damitticketNachlegenundholeZeitenNachden Markerworkorders.einrichtungAbrechnungerneut setzen. P6 nutzt es fürticketNachlegen(Dokumente mitunlockedByabooderprojektohne Stripe-Sitzung heilen übereinschalten) und für die Ticket-Zeilen intermine/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/activateaudienceConfirmed === true(sonst 400zielgruppe_fehlt, du-Text) und schreibt einen Nachweis infeatureConsents: Linkzeile A1, Zielgruppen-Zeile A2 (haken: true), KnopfAnalyse einschalten, Kundenart und Fassungen; bei einem Fehler wird alles zurückgenommen. P4 sendetaudienceConfirmed: 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).
createCustomerWithPortalAccessverlangtkundenartnur beineueAnsichtModus() !== 'off'(sonst 400kundenart_fehlt); beioffändert sich das Anlegen im WOMS und im Portal nicht.updateCustomerWithPortalAccessnimmtkundenartweiter 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-Tagkn-p9-kundenart; (3) Portal ab P5; (4) WOMSkn-p9zusammen mit P6. Vor dem Setzen vonKUNDENANSICHT_NEU=pilotmuss die WOMS-Kundenart (kn-p9-kundenart) live sein. Wirdfix/akquise-sichtbarspä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 wertetabrechnungnur mitaboModellFuerKundeaus). (P5 VF1, VF10; P9 VF3, VF4; F2, F28)
Stundenkonto, Team, Mails
- N25 Antwortformen des Stundenkontos (V8).
nextExpiryist{ minutes, at } | null; beiprodukt: 'hosting'und ohne Abo istledgergleichnull.workorders.einrichtungFuerundeinrichtungAbrechnungsind für Mitarbeiter lesbar (Setup-Skript P5). T24 trägt den Blockvorschuss, wenn beim EinschaltenbalanceMinutes <= 0gilt oder schonvorschussMinutes > 0besteht; 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,einschaltenmit Ticket) gilt nur, wennaboModellFuerKunde(customerId)wahr ist; sonst wirdabrechnungim Body ignoriert und alles läuft wie heute, miton(P10) für alle. P9 sendetabrechnungimmer. Die 409-Antwort bei fehlendem Abo ist flach:{ error, code: 'abo_fehlt', grund, wechsel }mitgrundauskein_abo | falsches_abo | gekuendigt | nicht_aktiv | beginnt_spaeter; WOMS zeigt eigene Texte, nie den Kundentext in „du“-Form. Erfolg wie bisherprojects[0].features[key]mitstate: 'setup'undticketWoid. (P6 Task 7, VF2; P9 VF2; F32) - N27 Abo-Modell-Dokument (V10, V11). Ein Dokument gehört zum Abo-Modell, wenn
unlockedByinabo | projektsteht undeinrichtungAbrechnunginabo | projekt(istAboModellinserver/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 inwartungsteckt, also auchprojekt-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 409abo_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 pluswarnung: { code: 'abo_fehlt', grund: 'kein_abo' }geantwortet, flach nebenprojects. Das booleschewarnung: truefü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ässtprojekt-Dokumente in der Aufbauphase in Ruhe. Ab dem ersten Abo gilt die Abo-Regel wie fürabo: 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 Blocktermine, Webshop, Unterkunft; Platzhalterfunktion,projekt,loeschtag); K40 und K41 (Zeiten: Abo-Ende mit „läuft noch bis {{frist}}“, abgeschaltet; ohne Preis, ohne Behalten); T24 ans Team mit Blockvorschuss; K42 (Webshop-Zusage) und K43 (Buchungskalender-Zusage) im Abo-Modell ohne Preiszeile, ohne „€“ und ohne „bestell…“ (BuilderanfrageEntschiedenMail, Weiche insendeEntscheidungüberaboModellFuerKunde). 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 Blockfunktionen_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/behaltengesperrt (409, P6). Die Route und die Mailtexte K29/K30 (Builderk29Mail,k30Mailauf 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 hinterneueAnsicht(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.pdfund die Funktionsunterlagen/neu/rechtliches/<slug>-2026-10-16.html|pdf;termsDocForliefert sie,/abo-bedingungen-v6.htmlaus V12 entfällt. P7-Fixtures und -Tests nutzentermsDocFor(config.abo.termsVersionNeu).urlundconfig.abo.termsVersionNeustatt Literalen wie'2026-10-30-v6'. P10 Task 14 verschiebt Seite und PDF nachpublic/(/abo-bedingungen.html), stellt inFASSUNGENv5 auf diearchiv-Adressen (public/vertragsunterlagen/v5/) und v6 auf die Wurzel-Adressen, setzt 301-Weiterleitungen und passtDOKUMENTE_V6, den Modusabo6und 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/serverundkn/recht, mit den Dateien beider Tasks. Quellen sindWORTLAUT_V6,FUNKTIONEN_FASSUNG_V6,FUNKTIONEN_UNTERLAGEN_V6undmitFassungausserver/services/products.js(P8 liefert, verdrahtet nicht). Verdrahtet werdenconfig.aboFunktionen.fassungundtexte.beginn(Literale,config.jsimportiert keine Dienste),consentAbomit dem Itemhinweis,aboKatalog.js(unterlagen,hinweis),aboUnterlagen.jsundsichereAdresseinfunktion-einschalten.js(lässt/neu/rechtliches/zu).bestelltexte.jsbleibt importfrei,test/knBestelltexte.test.jspinnt Linkzeile, Häkchen und Fassungsangabe gleich zuWORTLAUT_V6(eine einheitliche Form der Fassungsangabe), danach läufttest/aboWortlautV6.test.jsgrün. Die Analyse (config.analyse.avvVersion,legalPaths, K18-Weiche mit den PDFs auspublic/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 mitcreateDocumentundtexteHash(ausfeatureConsents.js), weilspeichereZustimmungprojectId = ''ablehnt;featureKey = art,projectId = ''.bezugundwortlaut(WORTLAUT_STATUS) stehen als Schlüssel infassungen(höchstens 1000 Zeichen),textebleibt die reine Anzeigereihenfolge.stripeSessionIdbleibt 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 undbezug. (P7 Task 2, VF3 bis VF5; P8 VF4; F36) - N35 Fassung in den Bestellrouten. P7 übernimmt die Stellen, die
config.abo.termsVersionfest lesen:stripeBilling.js:606-607(createAboCheckout: Fassung ausconsent.termsVersion, Vorgabe unverändert),abo.js:236,:328,:388(Prüfung) undabo.js:201(GET /api/abolieferttermsVersionüberaktuelleAboFassung(req)).fassungDerAnfrage(req, termsVersion)prüft die Neu-Fassung zuerst; bei gleichen Werten vontermsVersionundtermsVersionNeugiltneueAnsicht(req) ? 'neu' : null, sodass P10 Task 14 hier nichts ändert.server/routes/takeoverCredits.jsPOST /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) undansicht-gutschrift.js(Zustimmungsblock) wieansicht-bestellen.jsundansicht-wechsel.js: P2 baut sie 1:1 (Fassung 5), P7 baut sie auf das schlanke Muster um. Die Orakel-Vergleiche gegenapp.jswerden durch Pins auf die Fassung-6-Texte ersetzt; der Hash-Pin des BlocksTAKEOVER_CONDITIONS(conditionsVersion2026-09-30) bleibt. Die Gutschrift-Bedingungen werden die eigene Ansicht#gutschrift?bedingungen=1mit unverändertem Rechtstext;test/support/knWortlautOrakel.jsentfernt P10 Task 17 erst mitapp.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 überserver/services/duFehler.jsin 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 (Blocksofortverlangen). Bewusst der Mail-/Textrunde (Spec 5.7) überlassen: Validierungssätze intakeoverCredits.js,custom_textder 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.htmlundwiderrufen.htmlauf du um;public/legal-form.jsundpublic/auftrag-widerruf.jszeigen du nur im Zweigdata-anrede="du"(ohne Attribut unverändert). Die Seiten speichern keinen Wortlaut und keinetermsVersion, es gibt also keine neue Version (C14). Text-Pins intest/knOeffentlichDu.test.jsersetzen für diese zwei Seiten den Paarvergleich aus P2 Task 17 (Feldattribute und Skripte werden weiter verglichen); P10 Task 17 stellt die Sie-Pins intest/publicForms.test.js:1439-1450,test/auftraegeKundenportalWiderruf.test.js:9undtest/auftraegeAblauf.test.js:288auf 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.htmlund/widerrufen.htmlschon im Pilot auspublic/neu/oeffentlich/; ausgeloggte Besucher, der Login (/login.html) und/offline.htmlbleiben bisonalt. P0 setzt das mit zwei weiteren Zeilen indashboardWeiche.jsum (sie liefert nur, wenn die Datei existiert, sonstnext()); P10 Task 3 übernimmt die Zeilen inUMSCHALT_SEITENund 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) übernimmtinitLoginPagesamt Helfern (app.js:421-568) vor dem Löschen vonapp.js; die neue Hülle registriert/sw.jsselbst (P1 Task 11), P10 zieht insw.jsdieOFFLINE_URLmit. iOS-Fokuszoom: 16-px-Felder in P1/P2-CSS genügen, ein JS-Schutz nur im Login; der doppeltebeforeinstallprompt-Hörer (Hinweis P2, Konto P1) darf P10 zusammenlegen. P10 nimmt/auftrag.htmlinUMSCHALT_SEITENund in die Nachprüf-Marker auf (Marker:/neu/oeffentlich/oeffentlich.cssim 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(Gatep<N>),fixtures/endpunkte-p<N>.mjsmit je einem Import infixtures/endpunkte.mjs,fixtures/faelle-p<N>.mjs(P4:fixtures/module.mjs) mit einem Import infixtures/faelle.mjs,vertrag-endpunkte.mjsund der Marker intest/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“ aufFALL_NAMEN.slice(0, 14)und denboot.js-Test auf diese vierzehn (boot.jsbekommtFALL_MUSTER = /^[a-z0-9-]{1,40}$/, die Leiste zeigt nur Kernfälle). P4 (17 Fällem-*) und P7 (privat-ohne-abo,hosting-privat) ändern Pin undboot.jsnicht; ä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 überKW/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.htmlergänzt P2 Task 19 mit einem Eintrag invorschau/seiten.mjs. (P3 VF3; P2 VF7; P7 Task 16; F14) - N43 Vorschau-Technik.
dark-surfaceerlaubt genau#111(Akzent) für Elemente bis 25 % der Viewport-Fläche; andere dunkle Flächen brauchenignorierenje Ansicht. In der Vorschau hatpublic/neu/oeffentlich/<seite>Vorrang vorpublic/<seite>;boot.jsfeuert nach dem Laden einmalDOMContentLoadedundload, Code prüft trotzdemdocument.readyState. Der Arbeitsauftrag an den Inhaber (WOMS „Neues Projekt“, Subdomainkundenansicht-neu, Statusreadyoderdeployed) steht inKW/UEBERGABE-P0.mdund blockiert Phase B nicht;KN_ERSTLAUF_FREMDES_OK=janur 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 auftestheben“ (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) legtscripts/rechtliches-hochladen.mjs --karten-anlegenmit OK des Inhabers vor dem ersten Upload an;--probemeldet fehlende alsSTOPP: 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 Attributkey, und die drei Karten werden nicht insetup-legal.mjseingetragen. 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.mdführt R1 bis R17 (statt R1 bis R15;test/kundenansichtEntscheidungen.test.jsentsprechend): 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-ZeilenKUNDENANSICHT_NEU(Moduspilot) undKUNDENANSICHT_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 denZEITEN_*-Zeilen) und/home/webklar/apps/.env, Sicherung.bak-<Datum>-kundenansicht, danndocker compose up -d --no-deps kundenbereich-server, niepreview-portal; (2)scripts/kundenansicht-setup.mjs --pruefen, nach OK des Inhabers ausführen (sonst scheitert jeder Nachweis ohnefeatureConsents.kundenartmit 500).UEBERGABE-P0und 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_STATUSsteht auf'freigegeben'(E20); die Gleichheitsteststest/knBestelltexte.test.js,test/knVerdrahtung.test.jsundtest/aboWortlautV6.test.jssind grün;scripts/rechtliches-hochladen.mjs --probemeldet keine fehlende Karte und--pruefenendet mit Exit 0 (alle elfdocumentKeyausDOKUMENTE_V6haben eine aktuelle Version mit passendersource, deshalb prüft Task 9 Step 2 mit--pruefenund nicht nur mit--probe);scripts/kundenart-bestand.mjs(P10 Task 8) undscripts/kundenansicht-setup.mjssind 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.jsund die Antwortfelder vonGET /api/funktionenbleiben. (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 mitClaude Opus 5.5und 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 unterprojects[].features.<news|termine|shop|unterkunft|zeiten>zusätzlichkurzinfo: string | null, den Text der zweiten Zeile in „Meine“. Nur aktive Funktionen bekommen einen Text, alle anderennull; die Analyse trägt kein Feld (ihre Zahlen kommen aus dem Auswertungsdienst, nicht aus Appwrite). Quelleserver/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“ (Statusbezahlt), News „4 Beiträge sichtbar“ bzw. „Keine Beiträge sichtbar“ (isHanging), Buchungskalender „1 offene Anfrage“ (Statusneu), 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 aufkurzinfoDeps.limitMs(1500 ms) begrenzt, Fehler oder Zeitüberschreitung ergibtnull, eine volle Seite (Zahl unbekannt) ebenfalls. Die Texte enthalten nie Namen, Adressen oder Beträge. Die Oberfläche zeigt beinull„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.jsundansicht-gutschrift.js; P0 besitztserver/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 vorexpress.static(server/index.js:154). Gilt die neue Ansicht, liefert die Routeres.sendFile(public/neu/dashboard.html)mitCache-Control: no-cache. Sonstnext(), und die alte Seite kommt wie heute.GET /api/auth/mebekommt zusätzlichansicht: 'neu' | 'alt'.- Die alte
app.jslä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äfixkn-bzw.--kn-. - Die gemeinsamen Funktionsmodule behalten ihre Klassen (
tm-*,sh-*,bk-*,zt-*,an-*,fn-*,kb-*). Ihr neues Aussehen steht inpublic/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:
''/'#'→ websiteprojekt→ website, etageprojektfortschritt→ website (scrollt zur Aktivität)funktionen→ funktionen, reitermeine;entdecken→ funktionen, reiterentdeckennews,termine,webshop,buchungen,zeiten,analyse→ funktionen, etage = gleicher Namechat→ chatvertraege,abo,wartung→ vertraege, reiteraborechnungen→ vertraege, reiterrechnungenstunden→ vertraege, reiterstundenspeicher(neu) → vertraege, reiteremailgutschrift→ vertraege, etagegutschriftwechsel→ vertraege, etagewechselbestellen-hosting,bestellen-wartung→ vertraege, etagebestellen, plus Produktpasswort→ website, etagepasswort(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/meund 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ückfallfb, Etage 2 alsreelmittranslateY(-56px), Blattt2-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.
- Wörter:
- 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):
- Name und E-Mail
- Passwort ändern
- Datenschutz
- Impressum
- App installieren (nur wenn sinnvoll)
- Verträge kündigen (
/kuendigen.html) - Vertrag widerrufen (
/widerrufen.html, immer da; hervorgehoben mit der Pille „Frist läuft“, wennGET /api/fristenanzeigenliefert) - Abmelden
- Widerrufsstreifen: erscheint, wenn
anzeigen === true, mit dem TextWiderrufsfrist 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 > 0ausGET /api/invoicesplus Rückfragen ausGET /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 beiswitchPending.
- Chat:
- 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/meliefert incustomerzusätzlichkundenart: kundenartGespeichert(doc)undkundenartWirksam: kundenartWirksam(doc). Das gilt für alle drei Zweige (Login, Kundenansicht, Kunde).
Anlegen und Ändern (customerAdmin.js)
createCustomerWithPortalAccess(payload)nimmtkundenart. Pflichtwert ist'unternehmen' | 'privat', sonst 400 mitcode: 'kundenart_fehlt'.updateCustomerWithPortalAccessnimmtkundenartoptional, mit gleicher Prüfung.sanitizeCustomergibtkundenartzurück.
Nachweis
featureConsentsbekommt das Attributkundenart(string 12). Es wird inscripts/kundenansicht-setup.mjsangelegt und vontest/support/schema.jsgelesen.- Jede neue Zustimmung speichert
kundenartWirksamzum 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' }
laeuftgilt für die Statusactive | trialing | past_due, wieACTIVE_STATUSES.gekuendigtbedeutet „Kündigung vorgemerkt“.aboDecktliefert danngekuendigt, 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ätzlichkurzinfo: string | null(Live-Detail der Zeile in „Meine“, nur aktive Funktionen, Fehler →null; ohne den Parameter fehlt das Feld; N52). - Funktionen mit
verfuegbar: falsestehen 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:
nicht_verfuegbar(Schalter)abo_fehltmitbody.grundundbody.wechsel: true, wenn Hosting läuft und die Funktion Wartung brauchtnur_betrieb(Shop bei privat)bestellung_offenmitbody.offen, wie heute: Fragebogen beim Terminplaner, Anfrage nicht angenommen bei Shop und Unterkunftbeginn_noetig, wennkundenartWirksam === 'privat', die Widerrufsfrist des Abos läuft undhakennicht'beginn'enthält;body.abnennt das ISO-Datum nach Fristendebereits_eingeschaltet(Zustandsetupoderactive)fassung_veraltet
Erfolg:
einschalten({ customerId, projectId, key, by: 'kunde', abrechnung: 'abo', nachweis, now })(V7).- Der Nachweis kommt in
featureConsentsmit Texten. Pflicht sindeinschalten-zeile(Linkzeile) undknopf(Einschalten). Das Häkchenbeginnkommt 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
beginnlegt P8 fest. Bis dahin gilt der Entwurf aus Spec 5.6, Punkt 3, für die Einrichtung. - Das Item
gesundheitentfällt im Abo-Modell. Der Fragebogen sperrt den Gesundheitsbereich weiter.
Anfragen im Abo-Modell
- Shop:
unternehmerwird nicht mehr vom Client verlangt, sondern auskundenartWirksam === 'unternehmen'gesetzt. Privat führt zu 409nur_betrieb. keineAusgeschlossenenbleibt Pflicht als Satz über dem Knopf. Der Client sendettruebeim 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 }
anzeigenist wahr, wennkundenartWirksam(customer) === 'privat'gilt undfristen.length > 0.- Quellen:
openWithdrawals(Abo),paidFeatureOrders+withdrawalDeadline(gekaufte Funktionen) und die Aufträge (nur beiAUFTRAEGE_ENABLEDund 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'undunlockedByPurchase: false. - Legt das Ticket über
createFeatureTicketan, unter der Sperrefeature-order:${customerId}:${projectId}. - Für
zeitennutzt die Funktion den bestehenden Weg (einschaltenZeiten, mitunlockedByje nachabrechnung).
Ticket-Attribute (workorders, angelegt in scripts/kundenansicht-setup.mjs):
einrichtungFuer(string 16): FunktionsschlüsseleinrichtungAbrechnung(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 === trueist gesetzt, wenn das Arbeitsblatt zu einem Ticket miteinrichtungAbrechnung === 'abo'gehört. Den Join machtlistPlanUsage, auch für Git-Blätter. - Der ungedeckte Rest eines solchen Eintrags zählt als
vorschussMinutes, nicht alsoverageMinutes. - Gedeckt wird wie heute: FIFO, rückwirkend,
expiresAtnach 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 === falsebleibtvorschussMinutesstehen. 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
.segmit den Labels „Unternehmen“ und „Privatperson“, Werte ausKUNDENART_OPTIONENohne den Leer-Eintrag. Es steht an diesen Stellen:CreateLeadForm:createLeadschreibtkundenart.CreateCustomerForm: Body anPOST /api/admin/customersmitkundenart.- „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'stehtcountsToPlanstandardmäß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.
- Kein Preis mehr (
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 mitcode: 'abo_fehlt'undgrund(E21). Ausnahme: Dokumente mitunlockedBy === 'kauf'(Bestand). - Die Aktivierung läuft über
einschalten({ by: 'team', abrechnung, staff })mit Ticket. Wie bisher wird nicht direktactivegesetzt. Ausnahme:action: 'activate'auf einen ZustanddisabledmitdisabledReason === 'team'setzt wie heute direktactive. - Die Textform-Regeln bleiben, wie sie heute sind (
textformFuerAktivierung).
V11 Wechsel mit gemerkter Funktion (P6)
POST /api/abo/switchnimmt optionalfunktion: { key, projectId }, aber nur mit neuer Ansicht. Ohne neue Ansicht wird das Feld ignoriert. Es wird indetails.funktiondes Ereignisseswechselgespeichert.GET /api/aboliefert insubscriptionzusätzlichwechselFunktion: { key, projectId } | null, nur mit neuer Ansicht:- gesetzt, wenn das jüngste
wechsel-Ereigniserledigtist,details.funktionhat und diese Funktion für das Projekt nochoffist, längstens 14 Tage nachdoneAt; - bei
gescheitertoderwiderrufen→null.
- gesetzt, wenn das jüngste
- Ein Widerruf des Wechsels beendet zusätzlich alle Funktionen mit
unlockedBy === 'abo', deren Schlüssel nur inwartungsteckt (disable(..., { reason: 'abo_ende' })). - Client (P3 → P2/P7): Antwortet „Einschalten“ mit
abo_fehltundwechsel: true, schreibt die OberflächesessionStorage['kn-wechsel-funktion'] = JSON.stringify({ key, projectId })in einem try/catch und geht zu#wechsel.ansicht-wechsel.jssendet den Wert alsfunktionmit und löscht ihn nach Erfolg. Steht inGET /api/aboeinsubscription.wechselFunktion, öffnetansicht-funktionen.jsbeim 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
termsDocForundcontractAttachmentForlesen aus der Tabelle.- Die Seite der Fassung 6 entsteht unter einem eigenen Pfad, solange der Schalter nicht
onist. Erst P10 macht sie zu/abo-bedingungen.html, nach Archivierung von v5 unterpublic/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“), dazuearlyStart: boolean.unternehmen→startChoice: 'sofort',earlyStartConsentAt: '', Grundkundenart: 'unternehmen'im Ereignis.privatundearlyStart→sofortmitearlyStartConsentAt.privatohneearlyStart→nach_frist.
POST /api/abo/switch
earlyStartist nur beiprivatPflicht.- Bei
unternehmengilt der Wechsel ohneearlyStartConsentAt, der Grund steht im Ereignis.planSwitch.js:351bekommt dafür den Parameterkundenart.
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
featureConsentsmitfeatureKey = artundprojectId = ''. stripeSessionIdist 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
- P0 zuerst. P0 endet mit dem Tag
kn-p0und dem Arbeitsauftrag an den Inhaber: In WOMS „Neues Projekt“ (Website, interner Kunde) mit Subdomainkundenansicht-neuanlegen, damithttps://kundenansicht-neu.project.webklar.comexistiert. Vorher wird die Vorschau nicht ausgeliefert. - 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). - Merges in den Hauptzweig nach der Reihenfolge oben. Danach jeweils die betroffenen Testdateien mit den Befehlen aus den Global Constraints.
- 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-ZeilenKUNDENANSICHT_NEU(Moduspilot) undKUNDENANSICHT_NEU_PILOT_EMAILS(Pilot-Liste) anlegen, Sicherung.bak-<Datum>-kundenansicht, danndocker compose up -d --no-deps kundenbereich-server, niepreview-portal.piloterst setzen, wenn WOMSkn-p9-kundenartlive ist (Nachtrag N24). scripts/kundenansicht-setup.mjs --pruefen, nach OK des Inhabers ausführen (sonst scheitert jeder Nachweis ohnefeatureConsents.kundenartmit 500).
- Compose- und
- Ü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
- Rechtsrunde: P8 legt
docs/rechtliches/kundenansicht-entscheidungen.mdan. Der Controller geht die Liste mit dem Inhaber durch (E20). Hochgeladen wird erst nach seiner Freigabe, mitscripts/rechtliches-hochladen.mjs, zuerst--probe. - P10 startet erst, wenn der Inhaber ausdrücklich „umschalten“ sagt.