Pixel Art, Outdoor und Cyberpunk legen jetzt eine Grafik hinter die obere
Leiste und die Seitenleiste: 8-bit-Himmel mit Wolken und Huegelkante
sowie eine Steinmauer, blauer Himmel mit Schleierwolken sowie eine Wiese,
Neonroehren im Regen sowie eine regnerische Gasse. perinco bleibt
einfarbig.
Sechs SVG in public/themes/, von Hand gezeichnet - stilisierte Grafik
statt Foto, damit sie in jeder Aufloesung scharf bleibt; zusammen 36 kB.
Die Pixel-Grafiken haben einen absichtlich winzigen viewBox (eine Einheit
= ein Bildpunkt) und shape-rendering="crispEdges", damit die Kanten beim
Hochskalieren hart bleiben; die Mauer blendet nach oben stufenweise aus
und nicht mit einem Verlauf - Pixelgrafik kennt keine Zwischentoene.
Im Markup nur zwei Anker: app-header und app-sidebar. Den Rest machen
zwei Variablen mit Rueckfall auf none, weshalb perinco unberuehrt bleibt.
Die Variable enthaelt zwei Ebenen: einen Schleier aus der Flaechenfarbe
des Themes und darunter die Grafik. Das erledigt zwei Dinge zugleich -
derselbe blaue Himmel wird im Dunkelmodus zur Nacht (keine zweite Datei
noetig), und die Schrift bleibt lesbar, weil der Schleier in der Leiste an
den Raendern dicht liegt und in der Seitenleiste oben ueber den
Menuepunkten.
Aus dem ersten Versuch gelernt: die Cyberpunk-Grafik hatte eine deckende
Nachtflaeche. Im hellen Modus wurde daraus unter dem weissen Schleier ein
grauer Schmutzstreifen. Sie ist jetzt durchsichtig und zeigt nur Licht -
ueber Dunkel leuchtet das Neon, ueber Weiss bleiben blasse farbige
Linien. Ebenso zurueckgenommen wurde der Schleier ueber der Steinmauer:
"Einstellungen" und der Benutzername stehen darauf.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neben dem Design des Hauses ("perinco") gibt es "Pixel Art" und
"Outdoor", jedes in hell und dunkel. Die Wahl steht neben dem
Hell/Dunkel-Schalter im Kopf der App.
Sie gehoert dem BENUTZER und nicht dem Browser: crm_users.theme und
crm_users.theme_mode, gesetzt ueber PATCH /api/auth/me, das jeder fuer
sich selbst darf. Wer sich an einem anderen Rechner anmeldet, findet sein
Design wieder, und zwei Benutzer an derselben Maschine kommen sich nicht
in die Quere. Der localStorage bleibt als Zwischenspeicher daneben, damit
das Inline-Skript in index.html die Klassen vor dem ersten Frame setzen
kann - ohne das blitzt beim Laden das Standarddesign auf. Das hat eine
sichtbare Folge: melden sich zwei Leute an demselben Rechner an, sieht
der zweite fuer einen Augenblick das Design des ersten.
Der ThemeProvider steht deshalb in main.tsx jetzt unter dem AuthProvider
- er liest den Benutzer.
Keine Komponente wurde umgefaerbt. Im Markup steht nirgends eine feste
Farbe, sondern durchgaengig ink-*, brand-*, canvas und surface; Tailwind
v4 loest auch --radius-*, --shadow-* und die Schriften ueber Variablen
auf. Ein Theme tauscht also nur Werte, und deshalb kann Pixel Art alle
380 abgerundeten Ecken eckig machen, ohne eine Klasse anzufassen.
Zwei Dinge waren Voraussetzung:
- Der helle Block eines Themes braucht :not(.dark). html.theme-x hat
eine Klasse mehr in der Spezifitaet als .dark und wuerde den
Dunkelmodus sonst ueberstimmen.
- Auf brand-500/600 steht 69 Mal weisse Schrift. Jedes Theme braucht
dort einen ausreichend dunklen Ton - ein helles Pastell waere
unlesbar.
Die Pixelschrift (Silkscreen) sitzt nur in --font-display, also in den
Ueberschriften und den Feldbeschriftungen; labelClass tragt dafuer die
neue Klasse "field-label". Der Fliesstext bleibt Nunito Sans: die Listen
haben bis zu acht Spalten bei 11-13 px, und eine Pixelschrift waere darin
nicht mehr zu lesen. Outdoor bringt Cabin fuer beides mit.
server/test/userFields.test.mjs prueft die Werte und dass die Liste der
Farbwelten in userFields.js und src/types.ts nicht auseinanderlaeuft -
sonst zeigt die Auswahl ein Design an, dessen Speichern das Backend
abweist, und niemand weiss warum.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Das Loeschsymbol gab es in der Kachelansicht schon, es war aber bis zum
Ueberfahren durchsichtig - ein Knopf, den man nur findet, wenn man ihn kennt.
Jetzt ist er sichtbar, in der Liste ebenso, und die Pipeline-Karte hat ihn
ueberhaupt zum ersten Mal.
Geloescht wird nicht mehr endgueltig: DELETE setzt deleted_at, die Zeile bleibt.
An einer Opportunity haengen Ansprechpartner, Verlauf und Mailzuordnungen, und
ein Fehlklick soll das nicht kosten. Der Papierkorb ist ein Unterpunkt unter
Opportunities und zeigt, wann und von wem geloescht wurde und wie lange der
Eintrag noch liegt. Zurueckholen darf, wer auch loeschen darf; das Leeren jeder
Angemeldete - was dort liegt, hat schon jemand mit den noetigen Rechten
weggeworfen. Beides fragt vorher nach.
Nach 90 Tagen raeumt das Backend selbst auf, beim Abruf der Liste und hoechstens
einmal pro Stunde je Prozess - dasselbe Muster wie in der Akquise. Ein eigener
Zeitplan waere eine weitere Stelle, die ausfallen kann, und die Abfragen filtern
Faelliges ohnehin heraus.
Im Papierkorb liegende Opportunities sind aus allen Listen, Summen und
Zuordnungsdialogen heraus und lassen sich nicht bearbeiten (409) - erst
zurueckholen, dann aendern.
Nebenbei: der Header bekommt seinen Titel jetzt ueber viewTitle(). Der
Ansichtsname musste eindeutig sein ("Opportunity-Papierkorb", weil die Mails
schon einen Papierkorb haben), angezeigt wird beides als "Papierkorb".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Menüs, Dialoge, Schaltflächen, Platzhalter, Hinweise und die
Fehlermeldungen der API standen in Ersatzschreibweise (oe/ae/ue). Sie
erscheinen als Toast oder Beschriftung direkt in der Oberfläche und
gehören damit richtig geschrieben; sechs sichtbare Texte mit ss statt ß
sind gleich mit erledigt ("Schließen", "Straße", "zu groß").
Unangetastet bleibt bewusst:
- Kommentare im Quelltext - die sind durchgängig ASCII, und der Diff wäre
dreimal so groß ohne Nutzen für den Benutzer
- Protokollzeilen (console.*) - die landen im journal, nicht in der App
- Datenbankwerte und interne Kennungen: 'uebernommen', 'empfaenger',
'vorschlaege', 'laeuft-bereits' stecken in Enums, SQL und
Zustandsvergleichen. Ein Umlaut darin hätte still falsche Ergebnisse
geliefert
- server/src/mail/extract.js - die Muster decken absichtlich beide
Schreibweisen ab: /gr(ü|ue)(ß|ss)e/
- scanner/utils/crypto.js - der Ersatzschlüssel darf sich nicht ändern,
sonst sind gespeicherte Portal-Passwörter nicht mehr lesbar
Geprüft mit einem Rundgang durch die Ansichten und Dialoge: sichtbarer
Text plus title/aria-label/placeholder enthalten keine
Ersatzschreibweisen mehr.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der eigenstaendige Projekt Scanner zieht komplett ins CRM ein und laeuft
parallel zur bestehenden App weiter - jede geteilte Ressource ist verdoppelt
statt geteilt, damit die alte Installation unberuehrt bleibt:
- eigene Datenbank perinco_scanner, legt sich beim Start selbst an
- eigener SCANNER_ENCRYPTION_KEY, eigenes Upload-Verzeichnis
- eigener Scheduler mit Zeitversatz, damit nicht beide Instanzen gleichzeitig
mit denselben Portal-Cookies scrapen (Rate-Limits, CAPTCHA, doppelter RAM)
- Sperre gegen ueberlappende Laeufe: Scheduler und "Jetzt scannen" teilen sie,
ein zweiter Aufruf bekommt 409 statt doppelter OpenAI-Kosten
Backend unter /api/scanner hinter der CRM-Anmeldung; die HTTP-Basic-Auth des
Originals entfaellt. CV-PDFs kommen als Stream mit Token statt aus einem
statischen Verzeichnis. Bestandsdaten holt "npm run scanner:import" aus der
alten Datenbank, ohne sie zu veraendern.
Oberflaeche in Tailwind und CI-Farben neu gebaut: Menuepunkt Scanner mit
aufklappbaren Unterpunkten, Einstellungen als eigener Reiter.
Whisper hat keinen eigenen Menuepunkt mehr, sondern ist ein Reiter in den
Einstellungen. Damit diese Geraeteeinstellung jeder Rolle offensteht, sind die
Einstellungen jetzt fuer alle sichtbar - welche Reiter erscheinen, entscheidet
die Rolle.
Ausserdem: kein festes Admin-Passwort mehr im Quelltext, playwright exakt auf
1.60.0 gepinnt (nutzt den vorhandenen Chromium-Build des Servers), technische
Dokumentation mit Funktionszeichnungen unter docs/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>