Auf dem iPhone sprang die Seite beim Tippen ins Chatfeld weg (nur die
Fusszeile war zu sehen), zoomte hinein und liess sich danach seitlich
schieben. Beides sind bekannte Safari-Effekte:
- window.scrollTo(0, 0) bei offener Tastatur kaempft gegen Safaris eigenes
Hochschieben des Feldes und ist entfernt, ebenso das Verkleinern des
Chats auf die sichtbare Hoehe, das das schon hochgeschobene Feld wieder
aus dem Bild rutschen liess. Der Chat nutzt jetzt 100dvh: auf Android
folgt das der Tastatur (interactive-widget=resizes-content), auf iOS
bleibt die Hoehe stabil.
- Auf iOS aendert sich beim Fokus keine Hoehe mehr (die Tab-Leiste wird
nur optisch ausgeblendet). Ist die Chat-Eingabe nach der
Tastatur-Animation doch verdeckt, wird sie gezielt nachgeschoben
(gemessen am visualViewport).
- iOS zoomt beim Fokus, wenn die Schrift effektiv unter 16 px liegt, z. B.
bei Safari-Seitenzoom unter 100 %. maximum-scale=1 nur auf iOS verhindert
diesen automatischen Zoom; mit zwei Fingern zoomen bleibt moeglich
(WebKit ignoriert die Sperre dafuer seit iOS 10). Android bleibt
unveraendert, dort wuerde es das Zoomen sperren.
Im Ruhezustand pixelgleich zu main (28 Ansichten).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Chat-Composer bringt den runden Senden-Knopf fuer die App-Ansicht selbst
mit (0d47b7c). Die alten Regeln der App-Ansicht fuer .chat-send und
.chat-input-row waren dadurch wirkungslos und sind entfernt; die
Eingabe-Pille bleibt.
- Verlauf: der ResizeObserver nutzt data-stick aus ChatComposer.trackScroll
statt eines eigenen Scroll-Zaehlers
- Hinweis unter dem Chat nur noch in der App-Ansicht mit Touch ausgeblendet
(vorher auf allen Touch-Geraeten, also auch auf Tablets quer)
- Handy quer: feste Hoehe der Eingabe entfernt, der Composer waechst selbst mit
Handy, Tablet hochkant und Desktop sind pixelgleich zu main (28 Ansichten).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Die App-Ansicht (kundenbereich.css) gilt bis 880 px und fuer Handys quer,
der runde Senden-Knopf mit Symbol galt bisher erst unter 640 px. Dazwischen
stand "Senden" in einem 46-px-Kreis und lief ueber den Rand (auch waehrend
der Sprachaufnahme). Jetzt gilt der runde Knopf unter derselben Bedingung
wie die App-Ansicht.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auf dem Handy (unter 880 px und Handys quer) sieht der Kundenbereich aus wie
eine App; der Desktop bleibt pixelgleich.
- Kopfzeile als App-Leiste: Logo, beim Scrollen der Seitentitel, Avatar
- Navigation als Tab-Leiste unten mit Symbolen und Zaehlern; neuer Tab
"Wartung" mit der Abo-Karte, Stundenkonto/Bestellung und
Uebernahmegutschrift als Unterseiten mit Zurueck (auch Android-Geste)
- Konto als Bottom-Sheet (Ansprechpartner, Vertraege kuendigen/widerrufen,
Rechtliches, Abmelden), am Desktop als Popover unter dem Avatar
- Rechnungen als Karten, Fortschritt als Zeitleiste, Chat fuellt den
Bildschirm; bei offener Tastatur verschwindet die Tab-Leiste (kommt auch
nach der Android-Zurueck-Geste wieder)
- Installierbar: Manifest, App-Icons (WEBklar-Logo), Favicon, Hinweis
"Zum Home-Bildschirm"; Service Worker nur fuer die Offline-Seite,
Kundendaten werden nicht zwischengespeichert
- Installierte App: Kuendigen/Widerrufen mit Link zurueck in den
Kundenbereich (nur dort sichtbar, Texte unveraendert); Login-Links zu
Bedingungen/Datenschutz/Impressum oeffnen wie im Dashboard ein neues Fenster
- Login mit App-Icon und Abstaenden fuer Statusleiste/Home-Balken
- Hinweise "in der Seitenleiste" haben auf dem Handy eine eigene Fassung
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Kamerafotos in anderen Formaten (z. B. HEIF von Android-Kameras) werden
wie Dateien angenommen statt abgelehnt
- Dateinamen nach Zeichen kuerzen: ein halbiertes Emoji machte den Anhang
unabrufbar (encodeURIComponent)
- Endet eine Aufnahme von selbst (Headset getrennt, Anruf), bleibt die
Sprachnachricht erhalten statt "zu kurz"
- Kamera-App nur auf iOS/iPadOS/Android; Touch-Laptops bekommen die Kamera
im Browser statt eines Dateidialogs
- Knapp neben dem Chat abgelegte Dateien oeffnet der Browser nicht mehr
selbst (Portal bliebe sonst stehen); Datei-Felder bleiben Ablagen
- Fehler beim Ausliefern kommt als JSON statt mit dem Dateityp
- Server liest hoechstens 4 Uploads gleichzeitig ein (Speicher), weitere
warten; abgebrochene Wartende geben ihren Platz frei
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Kundenbereich und Admin-Chat: Bueroklammer-Menue (Bild, Kamera, Datei),
Mikrofon fuer Sprachnachrichten, Vorschau vor dem Senden mit
Upload-Fortschritt, Einfuegen (Strg+V) und Hineinziehen von Dateien
- Kamera: am Handy/Tablet die Kamera-App, am Rechner die Kamera im Browser
mit Vorschau, Kamerawechsel und "Nochmal"
- Sprachnachricht: Aufnahme mit Zeit und Pegel, vorher anhoerbar oder
direkt senden, hoechstens 5 Minuten
- Verlauf: Bilder (einzeln bzw. Raster, Grossansicht), Player fuer
Sprachnachrichten, Datei-Kacheln mit Download
- Server: jede Datei einzeln roh hochgeladen (max. 25 MB, 10 je Nachricht,
max. 3 gleichzeitig), Typ aus dem Inhalt, nur erlaubte Endungen, keine
Programme; Ablage unter upload-data/chat/<Kunde>/ ausserhalb von public/,
Auslieferung nur mit Login; Bilder, Audio und PDF inline, alles andere
als Download (nosniff, CSP sandbox)
- Nicht gesendete Anhaenge werden nach 24 Stunden geloescht
- Benachrichtigungs-Mail nennt nur Art und Namen der Anhaenge
- Appwrite: neues Attribut portalMessages.attachments
(scripts/setup-chat-collection.mjs, vor dem Deploy ausfuehren)
- Datenschutzerklaerung Abschnitt 6 ergaenzt (Stand 26.09.2026), PDF neu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Seitenleiste: Ueberschrift "Ihre Wartung" statt "Ihr Wartungs-Abo"
- Kein "aktiv"-Label mehr; ein Status steht nur noch da, wenn gekuendigt
- Zahlungsproblem bleibt ueber den roten Hinweis sichtbar, spaeterer
Beginn ueber die Datumszeile ("Beginn und erste Abbuchung am ...")
- Bei Kuendigung vor Leistungsbeginn keine "erste Abbuchung" mehr
- Ungenutzte Status-Styles entfernt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Admin-Tab "Abo-Angebote" und Button "Angebot" in der Kundenliste: Preis, Monate,
Gueltigkeit, Hinweis fuer den Kunden, interne Notiz, Vorschau vor dem Anlegen
- Je Angebot sofort ein Stripe-Coupon (amount_off, repeating ueber die
Angebotsmonate, max. 1 Einloesung); danach automatisch wieder Normalpreis
- Kunde ohne Abo: Angebot im Portal, Angebotspreis in der Bestelluebersicht,
Coupon im Checkout; Angebot schaltet die Bestellung auch ausserhalb der
Pilotliste frei; Bestellung prueft, dass das angezeigte Angebot noch gilt
- Laufendes Abo: Coupon sofort am Abo fuer die naechsten Abbuchungen
- Testkonten: Testrabatt im Checkout, Angebot per Webhook zusaetzlich am Abo
- Angebot in Abo-Metadaten, Vertragsbestaetigung, Admin-Mail und Abo-Karte
- Zurueckziehen loescht den Coupon; abgelaufene Angebote nicht mehr bestellbar
- Stundenkontingent nach invoice.subtotal: auch voll rabattierte oder per
Guthaben bezahlte Monate bringen die Stunde, die Probezeit nicht
- setup-abo-collections.mjs: aboOffers, subscriptions.offerId/offerText,
Attribut-Abfrage mit Limit (sonst Haenger ab 26 Attributen)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Kunde (Dashboard, nur mit laufendem Abo): Antrag mit Leistung, Anbieter,
Domain, Rechnungsbetrag/-zeitraum, Grund, Rechnungsnachweis (PDF/JPG/PNG)
und E-Mail fuer die Antwort; Eingangsbestaetigung per Mail, Status und
Antwort auf Rueckfragen im Portal
- Admin-Mail mit Link admin.html?gutschrift=<id> (Login leitet zurueck)
- Admin-Tab "Gutschriften": Kunde und Vertrag, Nachweise, Verlauf,
automatische Pruefungen (Duplikate per Datei-Hash/Domain, Abo, Zeitraum,
DNS-Abgleich Nameserver/MX mit Anbieter), Pflicht-Checkliste, Rechner
(tagesgenau anteilig), Genehmigen/Rueckfrage/Ablehnen mit Kundenmail
- Genehmigte Gutschrift als Stripe-Kundenguthaben (idempotent, Nachbuchen
bei Stripe-Stoerung)
- Nachweise in upload-data/ (gitignored), Loeschung abgelehnter nach 6 Monaten
- Stundenkontingent auch fuer Monate, die ein Guthaben komplett deckt
(invoice.total statt amount_paid)
- Datenschutzerklaerung Abschnitt 7 ergaenzt, PDF neu erzeugt
- Collection takeoverCredits in setup-abo-collections.mjs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Webhook customer.subscription.updated erkennt neu gesetzte Kuendigungen.
Kuendigungs- und Widerrufsseite markieren ihre Kuendigung per Metadatum
cancelSource, damit keine doppelte Bestaetigung verschickt wird.
Admin-Hinweis auch bei zurueckgenommener Kuendigung.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bestellseite: Pflichtangaben direkt ueber dem Button, Zahlungsarten,
Wahl des Leistungsbeginns (sofort / nach Widerrufsfrist via 15 Tage
Stripe-Probezeit), keine Vorauswahl
- Zustimmungsnachweis: Zeitpunkt, Version, Wahl, IP, Browser in Stripe-
Metadaten und Appwrite (neue Attribute, Setup-Skript ergaenzt)
- Checkout: Karte + SEPA (Fallback nur Karte), korrigierter Hinweistext
- Vertragsbestaetigung mit PDF der Vertragsunterlagen statt HTML
- Kuendigen/Widerrufen: Formular direkt (keine Zwischenseite), Wunsch-
termin, Druck/PDF der Bestaetigung, Vorbefuellung bei Login
- Widerrufsbutton im Abo waehrend der Frist, 'beginnt am'-Status
- Pilotbetrieb per ABO_PILOT_EMAILS
- Vertragsbedingungen v2, Datenschutzerklaerung, Kalkulator ohne MwSt
und mit 100/80 EUR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Noch nicht freigegeben, nicht nach main pushen. Enthaelt amtliche
Muster-Widerrufsbelehrung (Fassung ab 19.06.2026), AVV mit TOM und
Unterauftragsverarbeitern, Stundenkontingent statt Guthaben, Wahl des
Leistungsbeginns, Sperre erst ab 100 EUR, Hetzner-Backup-Formulierung.
Platzhalter: E-Mail-Anbieter und Postfachgroesse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bestellung auf der Portal-Startseite: Zusammenfassung, AGB- und
Vorab-Leistungs-Zustimmung, Button 'Zahlungspflichtig bestellen',
danach Stripe Checkout (Abo, Kleinunternehmer-Fusszeile)
- Stripe-Webhook /api/abo/webhook spiegelt Abos nach Appwrite
(subscriptions) und schreibt je bezahlter Rechnung 60 min gut
(hourCredits, idempotent ueber Invoice-ID, 12 Monate gueltig)
- Stundenkonto: FIFO-Verbrauch aus Worksheets mit countsToPlan,
15-Minuten-Takt, Mehraufwand separat ausgewiesen
- Stripe-Kundenportal fuer Zahlungsart/Rechnungen/Kuendigung
- Oeffentlicher Kuendigungsbutton (§ 312k) und Widerrufsbutton
(§ 356a) mit Eingangsbestaetigung per Mail
- Abo-Bedingungen inkl. Leistungsbeschreibung und Widerrufsbelehrung,
als Anhang der Vertragsbestaetigung
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ohne Query.limit liefert Appwrite nur 25 Dokumente. Bei 110 Kunden fehlten
in WOMS alle Kunden ab 'Autowerkstatt ...' alphabetisch - neu angelegte
Leads wirkten dadurch, als waeren sie nie angelegt worden.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Server-Container laeuft mit NODE_ENV=production, dadurch setzt npm
automatisch omit=dev. Build-Toolchains wie Next, Vite, Tailwind oder
TypeScript stehen aber in den devDependencies, weshalb jeder node_build
mit "Cannot find module ..." abbrach und die Preview auf den statischen
Platzhalter zurueckfiel. npm ci/npm install laufen jetzt mit
--include=dev.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher liess /api/auth/login nur Appwrite-User mit dem Label "admin" in die
Admin-Ansicht; alle anderen fielen in den Kunden-Login und bekamen "Kein
Kundenkonto fuer diesen Login gefunden". Mitarbeiter ohne Admin-Haken (z.B.
nico@webklar.com) kamen damit nicht auf /admin.html.
Jetzt gilt: Wer in der employees-Collection steht, ist Backoffice-Nutzer. Das
Label "admin" ist nur noch die WOMS-Adminrolle (Mitarbeiterverwaltung) und wird
als isAdmin-Flag in der Portal-Session bzw. in den Preview-Token-Labels
mitgefuehrt - inklusive "Als Kunde ansehen" und zurueck.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der im Gespraech vereinbarte Termin wird mit einem Klick am Akquise-Ticket
eingetragen (startDate/startTime/endTime/assignedTo/assignedName) - genau die
Felder, die das WOMS-Planboard liest. Kein zweiter Datensatz, damit Termin und
Ticket nicht auseinanderlaufen koennen.
- server/services/appointments.js: Ticket des Kunden finden, Termin schreiben,
belegte Zeiten lesen; Umrechnung ISO <-> "dd.mm.yyyy" und "hh:mm" <-> "hhmm"
- Routen GET /api/portal-admin/appointments und
POST /api/portal-admin/customers/:id/appointment
- Bereits vergebene Zeiten sind in der Terminauswahl gesperrt ("belegt"), der
eigene Termin bleibt waehlbar; steht am Ticket schon einer, ist er beim
Oeffnen vorausgewaehlt
- Zeiten, die jemand von Hand in WOMS gesetzt hat, tauchen mit auf
Sicherung gegen Datenverlust: bevorzugt wird das Akquise-Ticket. Gibt es keins,
kommt nur ein anderes offenes Ticket OHNE Datum in Frage - bei Webpage- und
Projekttickets bedeutet startDate den Projektstart, den wuerden wir sonst still
ueberschreiben. Die Rueckmeldung nennt immer Nummer und Art des Tickets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Button "← Admin" und die Lead-Zeile in der Kopfleiste sind raus. Das
webklar-Logo ist jetzt selbst der Link zurueck ins Admin-Panel (mit title
als Hinweis). Der Lead steht weiterhin ueber dem Preis ("Angebot für …").
Bei Leads (customerStatus=lead) steht in der Kundenliste jetzt der Button
"Rechnungsanalyse". Er oeffnet /kalkulation.html?customer=<id> - die Seite,
die im Gespraech vor Ort oder am Telefon ausgefuellt wird.
- Positionen nach Bereich (Website, Onlineshop, Softwareprojekt,
Automatisierung/KI, laufende Kosten) mit Stundenwerten und Mengen
- Preis ueber die Stundenstaffel (150/130/115/100), Brutto mit 19 % MwSt.,
Zahlungsmodalitaeten ab 1.000 / 3.000 EUR
- Hinweis, ob selbst zusagbar (<= 5 Std), Termin abzustimmen oder ob Kenso
freigeben muss (Shop, Software, Automatisierung)
- Termin mit Justin/Kenso aus den hinterlegten Zeitfenstern, Notizen,
Kundenportal-Zugang mit QR-Code
- Zusammenfassung als Text zum Kopieren -> gehoert ins Akquise-Ticket
Bewusst ohne eigenen Datensatz: dokumentiert wird im Ticket. Der Stand liegt
pro Kunde nur im localStorage, damit ein angefangenes Gespraech nicht
verloren geht.
Fonts (Caprasimo, Figtree) liegen lokal unter public/fonts, kein Abruf beim
Google-CDN. Neue Route GET /api/portal-admin/customers/:customerId liefert
die Stammdaten zum Vorbefuellen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Portal-Session (webklar_portal_session) lebt unabhaengig vom preview_session-JWT.
Fehlte das Preview-Cookie (WebView-Datenverlust, Ablauf), liefen alle
*.project.webklar.com-Hosts in eine 302-Login-Schleife (schwarze Seite im
Kundenportal), obwohl der Nutzer eingeloggt war - Re-Login war der einzige
Ausweg. Jetzt heilt jeder Aufruf der Projektuebersicht das Cookie automatisch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- runDeploy: Repos mit package.json-build-Script ohne index.html werden
automatisch als node_build gebaut (Expo/Vite/React zeigen sonst nur den
Platzhalter); Build-Fehler fallen auf static+Platzhalter zurueck
- deployNodeBuild: npm-ci-Fallback auf npm install, Output-Erkennung
dist/build/out/web-build, maxBuffer+Timeout
- updateWebsiteProject: bestehenden type/index aus .webklar-preview.json
erhalten statt bei jedem Edit auf static zurueckzusetzen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fehlt im Repo eine index.html, zeigte die Preview nur die
'wird vorbereitet'-Platzhalterseite, obwohl das eigentliche Projekt
(z.B. prototyp-app.html in knso/videogen) laengst deployt war.
ensureIndexHtml befoerdert jetzt die beste vorhandene HTML-Datei
(bevorzugte Namen, sonst einzige/groesste) per Kopie zur index.html;
der Platzhalter kommt nur noch, wenn gar keine HTML-Datei existiert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Anonymer git clone scheiterte bei privaten Gitea-Repos
("could not read Username"). Clone nutzt jetzt config.gitea.apiToken
via oauth2-Basic-Auth; Token wird aus Fehlermeldungen maskiert.
- runDeploy schreibt Platzhalter-index.html, wenn das Deploy-Verzeichnis
keine index.html enthaelt (leergeraeumtes Repo/Fehlbuild) -> nie mehr
nginx 403 Forbidden, Subdomain bleibt erreichbar.
- deployStatic: Fallback auf Repo-Wurzel bei fehlendem index-Unterordner,
.git wird nicht mehr mit ausgeliefert.
- Webhook: fehlt .webklar-preview.json im Repo, wird die Hosting-Config aus
dem Appwrite-Projektstand rekonstruiert, ins Repo zurueckgeschrieben und
normal deployt (ensureRepoPreviewConfig).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Statt nur Name + Link zeigt jede Projektkarte jetzt ein kleines,
skaliertes Live-Fenster der echten Webseite (iframe der previewUrl,
responsiv per ResizeObserver auf 1280px-Basisbreite herunterskaliert).
Klick aufs Fenster oeffnet die Seite im neuen Tab. Nicht-fertige
Projekte zeigen einen Platzhalter statt iframe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- archiveRepo: fehlt das Gitea-Repo (404), wird nur das Appwrite-Dokument
(ent)archiviert statt hart zu scheitern -> orphaned Projekte lassen sich
aus der Liste entfernen
- Reaktivierungs-Redeploy wird bei fehlendem Repo uebersprungen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- hasPreview existiert im Live-Schema nicht -> Appwrite lehnte Update ab
- syncPreviewTls muss NACH updateDocument laufen, sonst liest es isPublic
noch im alten Zustand (oeffentlicher Router bekam faelschlich preview-auth)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sanitizeCustomer liefert jetzt customers.archived, damit das Ticketsystem den
Archiviert-Status ueber /api/admin/customers erhaelt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Webhook: Subdomain-Fallback auf Repo-Name nur noch bei aktivierter Preview,
Projekte ohne Website behalten leere Subdomain/previewUrl
- generateNextWoid: numerisches Maximum statt String-Sortierung (gemischt
5-/6-stellige WOIDs im Bestand)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Login: Appwrite-User mit Label 'admin' (Ticketsystem-Mitarbeiter) bekommen
eine Admin-Session und landen auf /admin.html
- Admin-Dashboard: Statistik-Uebersicht, Projektsuche ueber alle Projekte
(inkl. Preview-/Gitea-Links, Vorschau ohne Kunden-Login), Kundenliste mit
Portal-Status, letztem Login und Projektanzahl
- Kundennummer (code) direkt im Admin-Bereich pflegbar
- 'Als Kunde ansehen': Admin sieht das Portal exakt wie der Kunde (Banner
mit Rueckweg zur Admin-Uebersicht)
- Kunden-Dashboard: neue Bereiche 'Meine Rechnungen' (InvoiceNinja inkl.
Link ins Rechnungsportal) und 'Was wurde gemacht' (Tickets + Worksheets
aus dem Ticketsystem, ohne interne Akquise-Tickets); Kundennummer im Header
- Admin-API /api/portal-admin/* (Session-basiert), Kunden-API /api/invoices
und /api/activity
- appwriteClient: uebrig gebliebene Debug-Instrumentierung entfernt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gitea sendet das Webhook-Secret als HMAC-SHA256-Signatur des Bodys, nicht als
Klartext-Header. Bisher liefen alle per Secret konfigurierten Repo-Hooks auf
401 - Push-Deploys funktionierten nur ueber ?token= in der URL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- previewDeploy.syncPreviewTls(): schreibt Traefik-Router (Host-Regeln) fuer alle
Preview-Subdomains nach preview-hosts.yml, damit Let's Encrypt Zertifikate
ausstellt. Laeuft nach jedem Deploy und beim Serverstart (ersetzt das nie
aufgerufene bash-Script sync-preview-tls.sh).
- createProjectFromTemplate: Subdomain optional. Ohne Subdomain wird nur das
Gitea-Repo angelegt (projectType 'repo', Status 'created'), kein Deploy.
- customerAdmin: Portal-Passwort liegt jetzt im Feld customers.portalPassword
statt als __portalPassword__-Prefix in den Notizen; Legacy-Notizen werden
beim naechsten Update bereinigt. CRM-Stufe (lead/customer) wird beim Setzen
des Portal-Passworts nicht mehr auf 'active' ueberschrieben.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sichert den bisher uncommitteten Produktionsstand des Kundenbereich-Servers,
damit kuenftige Deploys (git reset --hard) nichts mehr verwerfen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>