- GET /zeiten?ohne=<Buchung>: Leistung, Dauer und Person der Buchung wie beim
Verschieben (verschiebeZeiten exportiert), unbekannte/fremde/Sperre 404 (R42)
- Schreib-Begrenzung als gemeinsamer Baustein (writeLimiter.js) fuer News und
Terminplaner statt kopierter Funktion (R43)
- Unlesbares Einstellungs-JSON: Kontext traegt die echte Version, Speichern
ueberschreibt den Schaden statt dauerhaft 409
- U+FEFF als Escape statt als rohes Zeichen
- Tests: Mandantentrennung mit echtem zweitem Kunden, R42, Limiter, kaputtes JSON
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
/api/termine: Einstellungen (Vorlage, PATCH mit Version, Feed-Link), Buchungen
(Liste, Zeiten, von Hand, Sperre, Aktionen), CSV-Export, Protokoll der
Kundenansicht. Pflichtangaben fuer Gaeste (Telefon immer, Anschrift bei
Widerrufsbelehrung) in normalisiereEinstellung, Entwurf aus Vorlagen,
kontextPerSiteKey ohne Pflichtangaben nicht buchbar; speichereEinstellung
nimmt eine Funktion unter der Sperre.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Router /api/public/termine/v1 (config, tage, zeiten, buchungen, widerruf, Token-Seiten /termin/*, Kalender-Abo) mit
eigenem 16-KB-Parser und Fehlermiddleware, CORS nach Herkunft, Honeypot und Formular-Token, Grenzen je IP (IPv6 je /64),
siteKey und Adresse in limits.js, Mindestantwortzeit (TERMINE_MIN_ANTWORT_MS) und 10-Sekunden-Cache fuer tage/zeiten.
Vor dem globalen express.json eingehaengt; /api/public/ ist vom Kundenansicht-Waechter ausgenommen.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Neues Modul ablaufBetrieb.js: aktion (annehmen, ablehnen, absagen, verschieben,
nichterschienen, anonymisieren), manuell, sperre, entsperre. ablauf.js exportiert
die gemeinsamen Bausteine (Pruefungen, Meldungen, mailSicher, pruefeSlot,
verschiebeKern). R31: brauchtZustimmungVorzeitig folgt der Berliner Widerrufsfrist
(withdrawalDeadline ab now + Haltezeit); bei einer Anfrage mit Preis immer.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
bestaetige zieht unter der Buchungssperre fehlende eigene Zellen nach (belegeZellen mit ausser). Hat ein anderer
Gast sie uebernommen, wird die Buchung verfallen und es kommt dieselbe 410-Antwort wie bei abgelaufener Haltung,
ohne Mails.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Gast-Ablauf in ablauf.js: halte (Eingabe pruefen, Slot neu berechnen, Buchung gehalten vor den Zellen,
Grenze offener Termine mit G01 zu_viele), vorschau (Bestaetigungs- und Verwaltungstoken) und bestaetige
(gehalten -> bestaetigt/angefragt, G02/G03 und K20). einstellungen.js exportiert text und emailAdresse,
mails.js dauerText zur Wiederverwendung.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
appwriteAdmin: adminFetch kennt die Option raw (Body unverpackt), createDocuments
(Bulk-Create, alles oder nichts, 409 bei vorhandener ID), listAllDocuments (seitenweise
per cursorAfter, sortiert nach $id, Fehler ueber max) und Query.between/greaterThanEqual/
lessThanEqual/cursorAfter.
Nachbau: Bulk-Create mit Alles-oder-nichts (409/400, hoechstens 100), die neuen Abfragen
samt cursorAfter, Zeitpunktvergleich fuer ISO-Datumswerte, Optionen datetimes (Normalisierung
auf +00:00, leeres Datum => 400) und defaultLimit (Standard-Limit wie Appwrite). Ohne die
Optionen unveraendert. testApp exportiert send(method, path, opts).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Schalter TERMINE_ENABLED (off|pilot|on) mit TERMINE_PILOT_EMAILS, config.termine
und die vier Collection-IDs, Schema der Termine-Collections als reine Daten
(server/services/termine/schema.js), Setup-Skript scripts/termine-setup.mjs mit
--check und schemaKeys() fuer den Appwrite-Nachbau der Tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Knopf zum Wiederoeffnen des Banners ist jetzt ein Keks-Symbol. Die Zusatzseite
(runner/src/templates/datenschutz-analyse.html) sagt deshalb "Klicke auf
„Datenschutz-Einstellungen“ (das Keks-Symbol, in der Regel unten rechts auf jeder Seite)"
und im Widerspruchsrecht "über „Datenschutz-Einstellungen“ (das Keks-Symbol)".
Fassung bleibt 2026-10-02 (Banner-Text unveraendert). Archiv: neue Seite unter
datenschutz-analyse-2026-10-02.html, die vorige Formulierung (nur auf der WEBklar-Testseite
ausgeliefert) als datenschutz-analyse-2026-10-02-erstfassung.html; README und Banner-Archiv
beschreiben die Aenderung. Die Leistungsbeschreibung bleibt unveraendert ("Schaltflaeche
„Datenschutz-Einstellungen“ unten rechts" stimmt weiter).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Heatmap im Portal (Funktionen -> Analyse): die echte Website laeuft jetzt in realistischen
Geraeterahmen, gross MacBook oder iPad (Umschalter oben), daneben das iPhone, jeweils in echter
Breite (1512 / 820 / 402 px), verkleinert und im Geraet scrollbar, mit den Klicks der
passenden Geraeteklasse als Overlay. MacBook und iPhone stammen aus den Rahmen der neuen
Website (device-mockups), das iPad ist im gleichen Stil nachgebaut (public/geraete.css/.js).
- heatmap-link: embed: true haengt __wk_embed=1 an; w.js reicht das als data-embed an h.js.
- h.js eingebettet (nur in einem Rahmen): keine Leiste, Links und Formulare gesperrt (kein
Seitenwechsel, also keine Zaehlung und kein Banner), Scrollleisten aus, Klickzahl per
postMessage nur an die Herkunft von h.js (project.webklar.com).
- iframe mit Sandbox allow-scripts allow-same-origin (keine Formulare, Pop-ups, keine
Navigation des Portals); Laden erst, wenn die Heatmap in Sicht kommt. Laesst sich eine
Website nicht einbetten, kommt nach 20 s ein Hinweis mit "In neuem Tab oeffnen" (alter Weg).
Banner-Skript: der Einstellungsknopf nach der Wahl ist jetzt ein angeknabberter Keks unten
rechts (Inline-SVG, 44 px). Er dreht sich langsam, haelt beim Darueberfahren/Fokus an und
zeigt dann "Datenschutz-Einstellungen" (auch sein Name fuer Screenreader, passend zum
Bannertext); bei reduzierter Bewegung dreht er sich nicht.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Tab "Rechnungen" las nur InvoiceNinja. Seit dem Finanzen-Livegang laufen Rechnungen
aber ueber Stripe; die bezahlte Abo-Rechnung eines Kunden fehlte deshalb im Portal.
- /api/invoices fuehrt Stripe (alle Stripe-Kunden des Kunden: Abo-Spiegel und Suche nach
metadata.webklarCustomerId) und InvoiceNinja zusammen, neueste zuerst; Entwuerfe und
0-EUR-Rechnungen bleiben verborgen. Faellt eine Quelle aus, kommt der Rest mit incomplete.
- /api/invoices/:id/pdf laedt Stripe-PDFs frisch ueber den Server, nur fuer eigene Rechnungen
(sonst 404) und nur von *.stripe.com.
- Seite: Status-Farben fuer Bezahlt/Offen/Ueberfaellig/Storniert, "Ansehen" statt
"Ansehen / Zahlen" bei bezahlten Rechnungen, Hinweis bei unvollstaendiger Liste; der
Abo-Hinweis verweist nur noch fuer die Zahlungsart auf "Abo verwalten & Rechnungen".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>