Commit Graph
145 Commits
Author SHA1 Message Date
thomasandClaude Opus 4.8 d20a265ebc Neues Layout "Tupfen": dezent, mit verstreuten Punkten
Weiße Seite, Name groß und mittig in Tinte, luftiger Blocksatz — und
korallfarbene Punkte in den Rändern, teils vom Seitenrand angeschnitten,
als einzige Farbe des Blattes. Der Lebenslauf greift das Motiv auf: die
Punkte markieren die Abschnitte, führen die Bullets und bilden als
Fünfer-Reihe die Sprach-Skala (gespeist aus niveauLevel(), also aus der
Formulierung des Lebenslaufs selbst).

Die Punkte skalieren bewusst nicht mit der Schrift: sie gehören zur Seite
wie die Ränder. Das Anschreiben reserviert dafür unten ein Band, in das
der Text nicht hineinläuft.

Der Brief behält das DIN-Anschriftenfeld, das der Vorlage fehlt — es sitzt
in deren zweispaltiger Meta-Zeile (Empfänger links, Datum und Kontakt
rechts), so bleibt die Komposition erhalten und die Bewerbung deutsch.

Dazu:
- neue Akzentfarbe "Koralle"
- write() kann optional Blocksatz. jsPDF stretcht eine Zeile nur, wenn es
  weiß, dass eine weitere folgt — der Absatz geht deshalb in einem Aufruf
  rüber, wodurch die Schlusszeile ungestretcht bleibt.
- Infobox unter /vorlagen beschreibt jetzt auch Sunny (fehlte) und Tupfen

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:15:20 +02:00
thomasandClaude Opus 4.8 75813a38a4 Jobangebote: Favoriten-Funktion mit eigenem "Gemerkt"-Tab
Jobangebote lassen sich per Stern merken — eigene Spalte `favorit`,
getrennt von den Bewerbungs-Favoriten. Gemerkte Angebote stehen oben in
der offenen Liste und haben einen eigenen Tab, der Favoriten quer über
alle Status zeigt und ausgeblendet bleibt, solange es keine gibt.

Der Stern wird nur in der Weboberfläche gesetzt; ein erneutes Einspielen
desselben Angebots über die REST-API überschreibt ihn nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:45:04 +02:00
thomasandClaude cd30af3d69 Default OLLAMA_MODEL für neue Nutzer auf glm-5.2:cloud
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 20:16:10 +02:00
thomasandClaude Opus 4.8 0301a1c430 Sunny: Anschreiben dezenter — Farbe nur noch im Kopfband
Das Anschreiben war ein zweites Poster: Konfetti im Band, solides
Akzent-Badge, Betreff und Anrede beide in der Akzentfarbe, ein pastellener
Marker-Strich hinter dem Namen und eine Farbleiste als Fußzeile. Neben dem
Lebenslauf las sich das als Lärm, nicht als Set.

Jetzt trägt die Seite genau eine Farbfläche — das Kopfband —, alles darunter
ist Typografie:

- Kopfband ohne Konfetti, Name 27pt -> 22pt
- Rollen-Badge als weiße Pille mit Akzentschrift (der Lebenslauf behält sein
  solides Badge; die Umstellung sitzt in der Letter-Palette)
- Datum als schlichte rechtsbündige Zeile statt Pastell-Chip
- Betreff und Anrede in Tinte; die Bar über dem Betreff bleibt der einzige
  Farbakzent im Textkörper
- Signatur ohne Bild: Name über feinem Haarstrich statt Marker-Swipe
- Fußzeile: Haarlinie mit gedämpftem Kontakttext statt Farbleiste

suKonfetti/suDot hatten damit keinen Aufrufer mehr und fallen weg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:54:18 +02:00
thomasandClaude Opus 4.8 21608b8ca7 Sunny: Anschreiben als Gegenstück zum Poster-Lebenslauf gestalten
Der Brief trug bisher nur dieselbe Farbe wie der Lebenslauf, nicht dieselbe
Sprache. Jetzt bekommt er denselben Kopf: Name plakativ auf dem Farbband, Ziel-
position als solides Badge, Kontaktdaten als weiße Chips mit Icons rechts
gestapelt, dazu Konfetti in den Randstreifen. Das Datum ist ein Pastell-Chip
neben dem Anschriftfeld statt einer einsamen rechtsbündigen Zeile, der Betreff
bekommt Balken und Größe einer Sektionsüberschrift, die Anrede die Akzentfarbe,
die Anlagen werden zu Chips, und der getippte Name liegt auf einem Pastell-
Streifen wie mit dem Marker gezogen – der Ersatz für die fehlende Handschrift.

DIN-5008 bleibt unangetastet: Anschriftfeld, Datum, Betreff, Anrede, Fließtext,
Gruß, Unterschrift und Anlagen stehen, wo ein deutscher Leser sie erwartet.

Zwei Dinge, die erst im Render auffielen:

- Die Konfetti-Punkte saßen in der linken Rinne direkt neben dem Namen und lasen
  sich als Aufzählungszeichen davor statt als Deko. Sie liegen jetzt rechts; nur
  ein Funkeln bleibt links, tief im Band und weit weg vom Namen.
- Die Kontaktzeile der Fußzeile lief bei langer Anschrift plus langer E-Mail über
  das Band hinaus und schob sich über die Punkte. Sie skaliert jetzt passend und
  lässt notfalls die Anschrift weg, damit Telefon und E-Mail lesbar bleiben.

suBar las seine Farbe fest aus der Lebenslauf-Palette (t.rc), obwohl der Brief
t.lc benutzt – das ging nur gut, weil beide Werte zufällig gleich sind. Die
Palette wird jetzt übergeben.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:35:23 +02:00
thomasandClaude Opus 4.8 ffaaafed7e Uploads: abgewiesene Dateien melden statt still zu verwerfen, Anlagen bis 30 MB
Eine zu große oder nicht erlaubte Datei wurde von multer abgewiesen, der Fehler
nur auf die Server-Konsole geloggt und der Browser kommentarlos auf /vorlagen
zurückgeleitet. Für den Benutzer sah das aus, als sei der Upload durchgelaufen,
die Anlage tauchte nur nirgends auf ("Upload error: File too large" im Log, die
Teildatei räumt multer selbst wieder weg). Betroffen waren alle drei Uploads der
Vorlagen-Seite: Anlagen, Unterschrift und Bewerbungsfoto.

Die Routen leiten jetzt mit ?fehler=… um – dasselbe Muster wie auf der Jobsuche –
und die Seite zeigt den Grund als Banner: zu groß (mit Limit), Dateityp nicht
erlaubt, oder keine Datei gewählt. Das Limit steht zusätzlich am Datei-Feld,
bevor jemand eine zu große Datei auswählt.

Limit für Anlagen von 15 auf 30 MB angehoben; gescannte Zeugnisse liegen
regelmäßig darüber. Die Größen sind Konstanten (ANHANG_MAX_MB / BILD_MAX_MB),
aus denen sowohl multer als auch die Meldung und der Hinweis im Formular lesen –
eine Meldung, die ein anderes Limit nennt als das durchgesetzte, wäre schlimmer
als gar keine. Der Bild-Filter für Unterschrift/Foto lehnt jetzt ebenfalls mit
einem Fehler ab statt die Datei still zu verwerfen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:47:24 +02:00
thomasandClaude Opus 4.8 1f2e299c55 Design: drittes Layout "Sunny" für Anschreiben und Lebenslauf
Plakatives Layout nach Vorlage eines Referenz-Lebenslaufs: weiße Seite,
Pastellband im Kopf mit überstehendem Foto, Name groß daneben, Zielposition
als solides Badge, überdimensionale Display-Überschriften unter Pastellbalken,
die abwechselnd links und rechts stehen, zweispaltige Einträge und Level-Balken
für die Sprachen.

Die Balkenlänge kommt aus dem im Lebenslauf genannten Niveau (Muttersprache,
C1, B1 …) – es wird kein Prozentwert erfunden, der nicht in den Unterlagen
steht. Studium, Berufsausbildung und Weiterbildungen laufen unter einer
gemeinsamen Überschrift "Ausbildung": drei Überschriften dieser Größe
hintereinander würden die Seite auffressen.

Neue Akzentfarbe "Beere" als Standard für das Layout. Die Layout-Weiche steckt
jetzt in einer Tabelle (CV_LAYOUT / LETTER_LAYOUT) statt in Ternären – ein
weiteres Layout ist eine Zeile dort plus seine Palette in lib/design.js.
Sidebar und Social bleiben unverändert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:25:20 +02:00
thomasandClaude 9a40f395c7 Anlagen: user_id aus req.user statt AsyncLocalStorage im multer-Callback
In multer/busboy-Upload-Callbacks ist der per-request AsyncLocalStorage-
Kontext nicht zuverlässig propagiert, sodass currentUserId() dort null
liefern kann und basis_anhaenge-/interne_anhaenge-Zeilen mit user_id=NULL
verwaisten (für den User unsichtbar). Die INSERTs nutzen jetzt uidFromReq
(= req.user.id mit uid()-Fallback) wie bereits userStorageDirForReq für
Signatur/Foto. Zwei bestehende verwaiste Zeilen wurden repariert.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 16:49:16 +02:00
thomasandClaude ce844412e5 Jobsuche: Agent pro Benutzer in isoliertem Docker-Container
Jeder Suchlauf läuft nun in einem frischen Container pro Benutzer, dessen
pro-Benutzer-Home als ~/.claude gemountet ist — Memories und Session-Contexte
liegen damit strikt getrennt pro Benutzer. Die Such-Skills sind Shared-Code
aus dem Image und werden im Container nur nach ~/.claude/skills verlinkt.

Der Host-Runner startet pro Lauf `docker run --rm` und führt bis zu
JOBSUCHE_MAX_PARALLEL (Default 4) Läufe parallel über verschiedene Benutzer
aus (jeder hat eigenen Ollama-Key = getrennte Rate-Limits). Der alte gemeinsame
~/.claude-Pfad wird vom Runner nicht mehr beschrieben.

- source/agent/: neues Agent-Image (Dockerfile + entrypoint + drei Skills)
- scripts/jobsuche-runner.js: agentStarten als docker run, ensureAgentDir, runPool
- scripts/jobsuche-runner.sh + bin/-Kopie: Image-Guard
- package.json: docker:build-agent (lokal, ohne Registry-Push)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 15:41:22 +02:00
thomasandClaude 7c92f23351 Aktivitäten-Feed: empfangene und gesendete E-Mails einbeziehen
Der Aktivitäten-Feed (Startseite, /aktivitaeten, /statistik) führt Status-
änderungen und E-Mails per UNION zusammen, sortiert nach Erfassungszeit.
E-Mail-Einträge bekommen einen eigenen Farbpunkt (blau = erhalten, sky =
gesendet); ohne verknüpfte Bewerbung wird Absender bzw. Empfänger als
Kopfzeile gezeigt. Das UNION steckt in einem Subselect, weil SQLite ORDER BY
an einem Compound-SELECT keine Ergebnisspalten nach Namen aufloest. Die
Paginierung von /aktivitaeten zaehlt jetzt Statusaenderungen + E-Mails.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 15:03:20 +02:00
thomasandClaude fc1b68ebb4 Bewerbungen favorisieren (statt Jobangebote)
Stern zum Merken einzelner Bewerbungen auf der Startseiten-Liste, der
Bewerbungsdetailseite und einer neuen 'Gemerkte Bewerbungen'-Sektion ganz
oben — unabhaengig vom Status. Die bisherigen Jobangebot-Favoriten werden
durchgaengig entfernt (Endpoint, Migration, UI, Startseiten-Sektion), da
nur Bewerbungen gemerkt werden sollen.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 14:58:18 +02:00
thomasandClaude 3a108d3fec Jobangebote: Favoriten (Stern) und Startseiten-Sektion ganz oben
Stellen lassen sich per Stern merken — unabhängig vom Status (offen /
uebernommen / abgelehnt). Neue Spalte jobangebote.favorit (PRAGMA-Migration),
JSON-Toggle-Endpunkt /jobangebote/:id/favorit, Stern-Button auf den Karten
sowie eine "Gemerkte Stellen"-Sektion ganz oben auf der Startseite. Toggle
laeuft per fetch mit optimistischem UI; beim Entfernen auf der Startseite
verschwindet die Zeile sofort.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 14:49:47 +02:00
thomasandClaude 2566dcc98d Einrichtung: Wizard um E-Mail, Kalender und REST-API erweitert
Der Assistent deckt nun alle Bereiche aus /einstellungen ab. Neue Schritte
fuer E-Mail (SMTP/IMAP) & CalDAV sowie REST-API; Indikator auf 7 Schritte.
Secrets (Passwort, Token) werden nie vorausgefuellt und beim Speeren leerer
Felder nicht ueberschrieben. Neuer Endpunkt /api/einrichtung/config mit
SSRF-Guard (Mail-Host, CalDAV-URL, Ollama-Host) und API-Token-Kollisionspruefung.
Abschluss-Checkliste trennt Pflicht- von optionalen Verbindungen.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 12:55:20 +02:00
thomasandClaude 1bc0188fcc Einrichtung: Setup-Assistent fuer neue Nutzer auf der Startseite
Nutzer ohne Bewerbungen sehen einen mehrstufigen Wizard, der Persoenliche
Angaben, Basis-Unterlagen und Ollama-KI per fetch speichert. Dashboard bleibt
im DOM (hidden), damit main.js und "Bewerbung manuell anlegen" funktionieren.
Neue Endpunkte /api/einrichtung/{persoenlich,basis,ollama,verstecken} mit
SSRF-Guard; Ollama gezielt via setForUser, ohne Mail/Kalender/Token zu leeren.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 12:48:26 +02:00
thomasandClaude Opus 4.8 92ade10a37 Statische Dateien: JS/CSS revalidieren statt blind cachen
express.static setzte kein Cache-Control, also legte der Browser die
Lebensdauer selbst fest (heuristisch, aus dem Alter der Datei). Folge: nach
einem Deploy lief die Oberflaeche im offenen Tab weiter mit dem alten
chat.js - die frisch ausgelieferten Tabellen wurden dort weiter als
Pipe-Wueste gerendert, obwohl der Server die neue Datei schon hatte.

no-cache heisst nicht "nicht cachen": der Browser behaelt die Datei, fragt
aber per ETag nach, ob sie noch gilt (304, kein erneuter Download).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:33:07 +02:00
thomasandClaude Opus 4.8 090228479a Jobsuche: Berufsbezeichnungen steuern die Suche, nicht mehr Faehigkeiten
Der Feinschliff schlug per KI Technologien vor ("Proxmox", "Rechenzentrum")
und verbot Rollenbezeichnungen ausdruecklich - die sollte die Suche selbst
aus dem Lebenslauf ableiten. Das war schief: Welche Anzeigen ueberhaupt
gefunden werden, entscheidet die Suchanfrage, also der Stellentitel.
Technologien gewichten nur, was schon da ist. Damit schlug die KI genau das
vor, was der Such-Agent ohnehin selbst ableitet, waehrend die eine Sache,
die der Nutzer weder sieht noch steuern kann - unter welchen Titeln gesucht
wird -, bei jedem Lauf neu improvisiert wurde. Faellt "Fachinformatiker
Systemintegration" weg, merkt das niemand; es kommt nur wenig zurueck.

- Neues Profilfeld berufsbezeichnungen: die KI schlaegt es vor, der Nutzer
  editiert es, der Such-Agent fragt jeden Titel einzeln ab.
- zusatz_begriffe bleibt, aber als Kontext (Branche, Umfeld, Arbeitszeit),
  nicht als Stellentitel; ausschluesse unveraendert.
- Leeres Feld = altes Verhalten (Titel aus dem Lebenslauf), niemandes
  Profil aendert sich durch das Update.
- Spalte wird fuer bestehende Installationen nachgezogen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:27:06 +02:00
thomasandClaude Opus 4.8 67b8e5735f KI-Chat: Thread-Titel vom Modell formulieren lassen
Der Titel in der Seitenleiste waren bisher die ersten 60 Zeichen der
ersten Nachricht - abgeschnitten mitten im Wort ("Ich habe mich bei der
Hetzner Online GmbH bewor..."). Jetzt formuliert das Modell daraus einen
kurzen Titel, wie man es aus ChatGPT kennt.

- Der Aufruf laeuft NACH dem done-Event: die Antwort wartet nicht auf die
  Titelgenerierung (1-3 s), der Titel kommt als eigenes Event nach und die
  Seitenleiste zieht live nach.
- Der abgeschnittene Titel bleibt als Platzhalter, solange die Antwort
  streamt - die Seitenleiste ist also nie leer, und faellt die
  Titelgenerierung aus, bleibt es schlicht dabei.
- Nur beim ersten Schlagabtausch und nur, wenn der Thread noch keinen
  Titel hatte: ein selbst vergebener Name wird nicht ueberschrieben.
- Eigener Mini-Systemprompt statt des Chat-Prompts, der will beraten,
  nicht betiteln. Anfuehrungszeichen/"Titel:"-Praefixe werden abgeraeumt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:19:27 +02:00
thomasandClaude Opus 4.8 4b3e66b4bd KI-Chat: Markdown-Tabellen und Trennlinien werden gerendert
Der Mini-Renderer kannte weder Tabellen noch ---, also landeten beide als
roher Text in einem Absatz: aus einer Anforderung/Erfahrung-Tabelle wurde
eine einzeilige Pipe-Wueste, aus --- blieb ---. Das Modell nutzt aber
gerade fuer Passungs-Vergleiche gern Tabellen.

- GFM-Tabellen inkl. Ausrichtung (:--:), auch ohne Rand-Pipes.
- Krumme Zeilen werden auf die Kopfbreite gezogen (fehlende Zellen leer,
  ueberzaehlige raus), sonst zerreisst eine Zeile das Raster.
- Wrapper scrollt horizontal, damit eine breite Tabelle die Chat-Blase
  auf dem Handy nicht auseinanderzieht.
- Zellinhalte laufen durch dieselbe Escape-/inlineFmt-Kette wie der Rest,
  HTML aus dem Modell kann also weiterhin nichts injizieren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:15:25 +02:00
thomasandClaude Opus 4.8 4a036fe215 KI-Chat: Web-Recherche ueber Ollama Web Search
Der Assistent kannte bisher nur die Datenbank. Fragen wie "Was muss ich
ueber die Firma wissen?" konnte er damit nicht beantworten - Groesse,
Produkte, News, Kultur stehen nirgends in der Bewerbung.

Zwei neue Werkzeuge (web_suche, web_seite_lesen) ueber die Web-Search-API
von ollama.com. Sie authentifizieren sich mit demselben OLLAMA_API_KEY,
den der Benutzer fuer Chat und Dokumente ohnehin hinterlegt hat: keine
zusaetzliche Konfiguration. Anders als /api/chat haengt die Websuche
nicht an OLLAMA_HOST - wer rein lokal chattet, hat schlicht keine
Websuche, der Rest laeuft unveraendert weiter.

- Tool-Runden 4 -> 8: Bewerbung holen, suchen, Seiten lesen, antworten
  lief vorher ins Limit, statt zu antworten.
- Antworten der Websuche hart gedeckelt, sie wandern sonst in jeder
  weiteren Runde erneut ins Kontextfenster.
- Status-Label zeigt, wonach gesucht wird - sonst sieht der Nutzer nicht,
  ob die richtige Firma erwischt wurde.
- Nackte URLs im Chat sind klickbar: Belege nennt das Modell meist als
  reine URL, nicht als Markdown-Link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:02:29 +02:00
thomasandClaude Opus 4.8 9fc4225204 Aktivitaeten: eigene Seite mit Pagination, kaputte Eintragslinks repariert
Der Link unter "Letzte Aktivitaeten" heisst jetzt "Alle" und fuehrt auf
/aktivitaeten - den vollstaendigen Verlauf ueber alle Bewerbungen, 25 Eintraege
je Seite. Die Blaetterleiste sind echte Links (kein JS), damit Zurueck-Taste,
Lesezeichen und Neuer-Tab funktionieren; eine Seitenzahl ausserhalb des Bereichs
wird auf den gueltigen Rand geklemmt statt eine leere Seite zu zeigen.

Dabei ein Fehler von mir aus dem letzten Commit behoben: Ich hatte den Tag-Namen
samt href per <%= %> zusammengesetzt, was die Anfuehrungszeichen escaped -
heraus kam href="&quot;/bewerbung/121&quot;" und damit die URL
/%22/bewerbung/121%22. Jetzt zwei ausgeschriebene Zweige (Link bzw. Block).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:42:02 +02:00
thomasandClaude Opus 4.8 7b60fe734a Startseite: Letzte Aktivitaeten und Gesamtzahl der Bewerbungen
Unter der Liste stehen jetzt die acht juengsten Statusaenderungen quer ueber
alle Bewerbungen - mit Firma, Stelle, Status, Datum und Kommentar, verlinkt auf
die Bewerbung. Sortiert nach Erfassungszeit statt nach fachlichem Datum, damit
ein nachgetragener alter Eintrag nicht oben steht.

Der Kopf der Listen-Karte nennt die Gesamtzahl. Er sitzt bewusst ausserhalb des
md:min-w-Rahmens, damit er auf dem Handy nicht horizontal mitscrollt.

Beides verschwindet waehrend einer Suche, wie Liste und Termine auch. Die
Status-Farbkarte ist eine Ebene hoeher gewandert, weil Liste und Aktivitaeten
sie jetzt beide brauchen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:33:39 +02:00
thomasandClaude Opus 4.8 ab62399f6f Mobile: Glocken-Panel und Bewerbungsliste liefen aus dem Bild
Benachrichtigungen: Das Panel haengt per right:0 am Wrapper der Glocke, rechts
von ihr stehen aber noch Konto und Burger - die 20rem Breite wuchsen dadurch
nach links aus dem Viewport (gemessen -51px bei 375px Breite). Unter 768px
spannt es sich jetzt ueber den Header: `position: static` auf dem Anker macht
den Header zum Bezugsrahmen, was auch die vertikale Lage inklusive
Impersonation-Banner richtig haelt.

Bewerbungsliste: Das gestapelte Karten-Layout griff erst unter 640px, die
minmax()-Spalten des Rasters brauchen aber ~764px und die Karte bekommt ihre
min-w-[760px] erst ab md (768px). Im Band dazwischen - Handy quer, grosse
Phones - galt also das Desktop-Raster, und Stelle, Art und Status lagen
ausserhalb (132px Ueberhang bei 667px). Grenze auf 767px gezogen, damit sie an
md anschliesst.

Nachgemessen im Headless-Browser bei 375/390/412/667/740/820px: nichts ragt
mehr heraus, die Liste scrollt nicht mehr horizontal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:27:22 +02:00
thomasandClaude Opus 4.8 e462fa18ad Beispieldaten entpersonalisiert: generische Staedte statt Gladbeck & Co.
Platzhalter und Doku-Beispiele nannten durchgehend den Wohnort des
Entwicklers (Gladbeck, Bottrop, Gelsenkirchen, Essen), dessen Vorwahl
(02043), PLZ und Strasse. Ersetzt durch Berlin/Hamburg/Muenchen sowie eine
zum Beispielort passende Vorwahl (030). Betrifft die Platzhalter fuer
Telefon/Ort (Vorlagen), die Staedte-Eingabe (Jobsuche), das Adressbeispiel
in den LLM-Notizen, die OpenAPI-Beispiele und zwei Code-Kommentare.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:14:06 +02:00
thomasandClaude Opus 4.8 5ce835ff3a Bewerbungsstatus "Vertragsverhandlung" ergaenzt
Neue Station zwischen Vorstellungsgespraech und Einstellung - in der
Statusliste, der Gruppierung auf der Startseite, den Farbskalen (Lime,
zwischen dem Gelb des Gespraechs und dem Gruen der Einstellung), im
PDF-Export, in der Statistik und in der REST-API/OpenAPI.

Der Statistik-Funnel zaehlte bisher ever('Vorstellungsgespraech') +
ever('Einstellung') und damit eine Bewerbung, die beide Stationen
durchlaufen hat, doppelt. Mit einer weiteren Stufe waere das schlimmer
geworden, deshalb zaehlt der Funnel jetzt pro Bewerbung statt pro
Statuszeile: jede Stufe zaehlt eine Bewerbung genau einmal, ein spaeterer
Status impliziert die frueheren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:10:02 +02:00
thomasandClaude Opus 4.8 2b90e4bbad Login: "Angemeldet bleiben" waehlbar, Multi-User-Hinweis entfernt
Bisher bekam jeder Login pauschal ein 30-Tage-Cookie. Die Checkbox ist per
Default gesetzt (unveraendertes Verhalten); wird sie abgewaehlt, gilt ein
Session-Cookie ohne Ablaufdatum (weg beim Schliessen des Browsers) und
serverseitig eine Frist von 12 Stunden Inaktivitaet - die serverseitige Frist
ist die verbindliche, ein Client kann sein Cookie manipulieren.

Bestehende Sessions behalten per Spalten-Default die 30 Tage, niemand wird
durch das Update ausgeloggt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:03:33 +02:00
thomasandClaude Opus 4.8 d86e40a6ff KI-Feinschliff, Volltextsuche über E-Mails, Prompt für die Antwort-Mail
Drei Erweiterungen, die sich server.js und lib/documents.js teilen und
deshalb zusammen liegen:

Jobsuche: Der Feinschliff (Zusatzbegriffe/Ausschluesse) laesst sich per KI
aus den eigenen Basis-Unterlagen vorschlagen. Der Rollen-Prompt ist wie die
uebrigen unter Vorlagen editierbar; gespeichert wird erst auf Klick.

Suche: Startseite und KI-Chat durchsuchen jetzt per SQLite-FTS5 auch die
E-Mail-Korrespondenz (Absender, Betreff, Text), Notizen und
Stellenbeschreibung - ein Name aus einer Mail findet die Bewerbung. Ranking
per bm25, Fundstellen mit Snippet, Feldfilter (firma:, von:), Phrasen,
Ausschluesse, Umlaut-Varianten (mueller/muller/mueller). Der Index wird von
SQL-Triggern gepflegt, weil Bewerbungen und Mails aus UI, REST-API, IMAP und
KI-Import geschrieben werden.

E-Mail: Vor dem Generieren laesst sich vorgeben, was in der Antwort stehen
soll; die Vorgabe geht als verbindliche Anweisung in den Prompt. Steht schon
ein Entwurf im Feld, ueberarbeitet die KI ihn, statt neu anzufangen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 10:56:41 +02:00
thomasandClaude 3c7f5d7c74 Mobile-Support: Smartphones für die ganze Seite
- Dashboard-Bewerbungsliste auf Handy als gestapelte Karten statt horizontal gescrolltem 760px-Raster
- Modals auf Handy scrollbar (Inhalt wurde bisher unten abgeschnitten)
- Chat: Thread-Aktionen auf Touch sichtbar, Sidebar/Chat-Höhen für mobil
- Bewerbungs-Antwort-Toolbar & Admin-Stat-Boxen umbruchfähig

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 10:24:49 +02:00
thomasandClaude 295c447d58 PDF-Export: /api/export monats/jahresbezogen gab 500 (ungueltiges SQL)
Die gefilterten Export-Zweige bauten `SELECT * FROM (<subquery>) AND
strftime(...)` — ohne WHERE nach der abgeleiteten Tabelle. SQLite
quittierte das mit "near \"AND\": syntax error" => HTTP 500, der Client
erhielt {error:'Serverfehler'} statt des Bewerbungs-Arrays und
generatePdfDocument crashte mit "applications.map is not a function".

Fix: `AND` => `WHERE` in den drei gefilterten Zweigen (month+year,
month, year). Die Per-User-Isolation bleibt unberuehrt (user_id-Filter
steht weiterhin in der Subquery). Zusaetzlich prueft generatePDF jetzt
Array.isArray(applications) und zeigt eine klare Fehlermeldung statt
des .map-Crashs.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 02:38:47 +02:00
thomasandClaude 69e6a29d87 PDF-Export: Autotable-Plugin zuverlaessig laden vor der Generierung
loadPdfLibraries() hat im "bereits ladend"-Zweig aufgeloest, sobald jsPDF
vorhanden war — das Autotable-Plugin (zweites Script) war dann aber noch
nicht angehaengt, sodass doc.autoTable undefined war und die Generierung
mit "doc.autoTable is not a function" fehlschlug. Die Polling-Bedingung
prueft jetzt auch jsPDF.API.autoTable, und generatePDF wartet zusaetzlich
auf loadPdfLibraries(), bevor das Dokument gebaut wird.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 02:35:37 +02:00
thomasandClaude 167960b91a Benutzerverwaltung: übersichtliches Layout mit klaren Aktionen
- Header mit Icon + Statistikleiste (Benutzer / Admins / Bewerbungen)
- Karten-Layout (rund, Shadow), Sektionen mit Icons
- Aktionen als einheitliche Icon+Label Buttons (Jobsuche, Anmelden als,
  Passwort, Löschen) statt gequetschter Textlinks, je eigene Farbe
- Avatar-Initial + Admin-Badge mit Icon in der Tabelle
- Passwort-Reset als eingeblendete Karte mit Abbrechen + Hinweis
- Relative Zeitangaben (Erstellt, Audit) mit Tooltips
- Audit-Sektion als eigene Karte

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 02:21:32 +02:00
thomasandClaude e0733666ae Admin kann Nutzer-Konto übernehmen (Impersonation) mit Rückwechsel
- sessions.impersonator_id: Admin-Session behält Token, schaltet user_id
  aufs Ziel, Admin bleibt in impersonator_id gespeichert (Stack, keine
  Verschachtelung). uid()/Config/Dateien laufen als Ziel-Nutzer.
- Admin sieht in /admin pro Nutzer "Anmelden als"; Bestätigungsdialog.
- Dauerhaftes amber Banner im Header mit "Zurück zum Admin" (POST, kein JS
  nötig) erscheint auf jeder Seite während Impersonation.
- requireAdmin verweigert während Impersonation -> keine Admin-Aktionen als
  fremder Nutzer; Stop-Route prüft impersonator_id (kein Escalation-Pfad für
  Normalnutzer). Selbst-Imitation blockiert.
- audit_log-Tabelle protokolliert Start/Stop persistent; Admin-Seite zeigt
  Audit-Liste (/admin/audit/impersonations).
- Migration: idempotentes ALTER ADD COLUMN impersonator_id fuer Bestand.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 02:17:50 +02:00
thomasandClaude fd1db3970f Sicherheitscheck: Schwachstellen behoben
- Pfad-Traversal: safeFilename/containedPath-Helper, storeAnhang sanitizes
  filename, alle Download/Delete/Mail-Send-Routen pruefen Containment
- Stored XSS: serveInline entscheidet Viewable-Typ nur nach Extension,
  nicht nach client/seitigem MIME; nicht viewbare Typen werden als Download
  erzwungen. Upload fileFilter (Basis/Interne) + Extension-Validierung
  (Signatur/Foto leiten Ext aus MIME, blockieren .html)
- URL-Scheme-Allowlist (safeUrl) fuer quelle_url-hrefs gegen javascript:-XSS
- E-Mail-Iframe: Sandbox auf allow-same-only (kein allow-popups-to-escape)
- Sicherheits-Header: CSP, X-Content-Type-Options, X-Frame-Options,
  Referrer-Policy, COOP; x-powered-by aus; jsPDF self-hosted unter /vendor
- Session: Secure-Flag bei TLS, serverseitige absoluteexpiry, Scrypt async
  + Dummy-Verify gegen Timing/Enumerate + Login-Rate-Limit
- Open Redirect: /email/fetch nur same-origin Redirects
- SSRF: Validierung von OLLAMA_HOST/CALDAV_URL/MAIL_HOST gegen
  Metadata/Link-Local-BLock (localhost/LAN bleibt erlaubt)
- Globaler Error-Handler ohne Interna-Leak, env=production

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 02:09:17 +02:00
thomasandClaude da0b497972 Wortmarke in der Top-Navi einfarbig
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 01:43:49 +02:00
thomasandClaude bf789af62a Rebranding zu NextJobs (nextjobs.cc)
- Markenname 'Bewerbungs-Tracker' -> 'NextJobs' im gesamten UI,
  Footer (incl. nextjobs.cc Copyright), Login, Wortmarke in der
  Top-Navi (Next + Jobs-Gradient), Favicon, Swagger/OpenAPI-Titel,
  CalDAV-PRODID, Browser-Erweiterung (manifest, content, background),
  Runner-Log und Doku.
- Funktionale Begriffe (Bewerbungsunterlagen, -datum etc.) bleiben.
- Docker-Registry (git.hackner.dev) als Infrastruktur unberuehrt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 01:40:26 +02:00
thomasandClaude cc2df90653 Favicon und moderner Schriftzug fuer die Top-Navi
- Passendes SVG-Favicon (Gradient-Mark wie in der Top-Navi) +
  theme-color, registriert im <head>
- 'Bewerbungs-Tracker' als zweifarbiger Schriftzug: Bewerbungs in
  Slate, Tracker im Sky->Blue-Gradient

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 01:33:26 +02:00
thomasandClaude fc28bee55f Footer dezenter, an Top-Navi angelehnt
Keine eigene Oberflaeche mehr (nur Seitenhintergrund + Haarlinie),
kleinere und stummere Schrift — gleiche ruhige Sprache wie die
Top-Navigation.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 01:27:46 +02:00
thomasandClaude 17d438d102 Jobsuche-Runner: nvm laden, damit Cron node findet
Node laeuft unter nvm, dessen bin-Verzeichnis nicht im Cron-PATH
lag. Jeder Cron-Tick schlug mit `node: command not found` fehl,
weshalb manuell angeforderte Suchlaeufe ewig im Status 'angefordert'
(wartet) haengen blieben. nvm.sh wird jetzt vor dem Aufruf gesourced.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 01:04:59 +02:00
thomasandClaude 9f000e2719 Filterbereich entfernt, Suche + Aktionen als Toolbar zusammengefasst
- Monat/Jahr-Filter geloescht (Server-Filter bleibt via URL nutzbar)
- Suchfeld und die Buttons 'PDF Export' und 'Bewerbung hinzufuegen'
  in einer gemeinsamen Toolbar zusammengefasst, als Pill-Buttons
- Tote filterForm-CSS und -JS entfernt

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 00:56:24 +02:00
thomasandClaude 98a644524b Statistik auf eigene Seite ausgelagert, umfangreich und modern
- Eigene Route /statistik mit umfassender Auswertung pro Benutzer
- KPI-Karten, Monats-Trend (SVG), Verteilung nach Status/Art/Ort,
  Bewerbungs-Funnel und letzte Aktivitaet als Timeline
- Statistik-Eintrag in der Top-Navigation (Desktop + Mobil)
- Kurzsstatistik vom Dashboard entfernt

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 00:48:08 +02:00
thomasandClaude 4fd843318f Header/Navigation-Styling ueberarbeitet
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 00:34:49 +02:00
thomasandClaude Opus 4.8 1aa902bdce Top-Navigation: neu strukturiert, mit Aktiv-Zustand und Dropdowns
Die Leiste war eine flache Reihe aus sieben gleichrangigen Pillen. Zwei
Probleme: sie zeigte nie an, wo man gerade ist (es gab schlicht keinen
Aktiv-Zustand), und auf dem Handy fielen nur die Beschriftungen weg - uebrig
blieben sieben unbeschriftete Icons.

Struktur folgt jetzt dem tatsaechlichen Ablauf (finden -> bewerben ->
antworten) statt der Reihenfolge, in der die Seiten entstanden sind:

  Bewerbungen | Jobangebote | Stellensuche v | Postfach | Vorlagen

"Stellensuche" buendelt als Dropdown, was Angebote erzeugt und filtert:
Suchprofil & Laeufe, Uebernommene Angebote, Blacklist - jeweils mit einer Zeile,
die erklaert, wozu der Punkt gut ist. Einstellungen, Benutzerverwaltung und
Abmelden wandern ins Konto-Menue rechts, wo Systemkram hingehoert. Die
taeglichen Wege (Bewerbungen, Jobangebote, Postfach) bleiben ein Klick weit weg.

Gestaltung: Die Leiste behaelt in beiden Themes ihr dunkles Navy - sie ist der
Rahmen des Werkzeugs und die einzige Flaeche, die sich beim Navigieren nie
aendert. Alles darin ist bewusst still (Slate auf Navy); der einzige Akzent ist
der aktive Punkt, markiert durch eine schmale leuchtende Schiene an der
Unterkante. Beim Hover deutet sich dieselbe Schiene gedaempft an.

Zaehler (ungelesene Post, offene Angebote) haengen jetzt an Klassen statt IDs,
weil sie in Leiste UND Mobilmenue erscheinen. Ein Controller bedient alle
Dropdowns: Klick oeffnet, andere schliessen, Escape und Klick daneben schliessen,
aria-expanded bleibt ehrlich. Fokus ist sichtbar, prefers-reduced-motion wird
respektiert, das Mobilmenue zeigt alle Punkte ausgeschrieben und gruppiert.

Der aktive Pfad kommt aus res.locals.pfad (Auth-Middleware). Die Navigation liegt
weiterhin in genau einem Partial - sie ist damit auf allen Seiten identisch;
nur die Login-Seite hat bewusst keine.

Geprueft mit echtem Browser (Screenshots): hell/dunkel, Dropdowns, Mobilmenue,
und der Aktiv-Zustand je Seite - Unterseiten wie /blacklist oder /jobsuche heben
korrekt "Stellensuche" hervor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 23:55:58 +02:00
thomasandClaude Opus 4.8 9418750061 Jobsuche als Feature der App: Suchprofil pro Benutzer statt Cron-Prompt
Die Jobsuche lag in zwei Host-Skripten (jobsuche-cron.sh, jobsuche-remote-cron.sh)
aus der Ein-Benutzer-Zeit: Die Suchkriterien (sieben Staedte, Rollen, Buzzwords)
standen fest im Prompt, und importiert wurde mit EINEM globalen API-Token aus der
.env - der zufaellig dem Admin gehoerte. Auf der Multi-User-Plattform ist beides
hinfaellig.

Jetzt legt jeder Benutzer sein Suchprofil selbst fest (/jobsuche):
- Modus: regional / 100 % Remote / beides
- Staedte (die erste gilt als Wohnort und wird hoechstpriorisiert, max. 12)
- Feinschliff: zusaetzliche Begriffe, Ausschluesse
- Zeitplan: Wochentage + Uhrzeit, plus Button "Jetzt suchen"
Rollen und Technologien bleiben abgeleitet - aus dem Lebenslauf des Benutzers
(basis_dokumente), nicht aus einer gepflegten Liste. Admins koennen Profil und
Zeitplan eines Benutzers ueber /jobsuche?user=<id> mitpflegen (Link im Admin-Panel).

Aufteilung App/Host: Der Container hat weder claude noch ollama. Die App reiht
Laeufe daher nur in die Warteschlange ein (Tabelle suchlaeufe); der neue Runner
auf dem Host (scripts/jobsuche-runner.js, Cron alle 5 Min) arbeitet sie ab, baut
den Prompt je Benutzer aus dessen Profil + Lebenslauf und laeuft mit DESSEN
Zugangsdaten:
- BEWERBUNG_API_KEY = eigener API-Token des Benutzers (wird beim ersten Lauf
  automatisch erzeugt), damit Treffer im richtigen Konto landen und durch dieselbe
  Dedup-/Blacklist-Logik gehen,
- ANTHROPIC_BASE_URL/AUTH_TOKEN = eigener Ollama-Key des Benutzers ueber die
  Anthropic-kompatible Schnittstelle von Ollama Cloud (https://ollama.com/v1/messages,
  verifiziert: gueltiger Key -> 200, ungueltiger -> 401). Damit zahlt jeder seine
  eigene Suche, statt alles ueber die Host-Subscription zu buchen
  (JOBSUCHE_KI_AUTH=host stellt das alte Verhalten wieder her).

Ohne eigenen Ollama-Key oder ohne Lebenslauf bricht der Lauf mit klarer Meldung ab
statt still nichts zu tun; die Oberflaeche warnt vorab. Kein Stapeln: solange ein
Lauf offen ist, erzeugt ein weiterer Klick keinen zweiten. Verwaiste Laeufe
(Prozess weg) werden nach Zeitlimit als Fehler freigegeben.

Verifiziert mit zwei Benutzern: Zugriffsschutz (fremdes Profil -> 403), Speichern,
Warteschlange, automatische Key-Erzeugung, Prompt-Aufbau aus Profil + CV,
Ergebnis-Ruecklauf in die Oberflaeche, Fehlerpfade und die Faelligkeitslogik des
Zeitplans (Tag/Uhrzeit/bereits gelaufen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 23:28:56 +02:00
thomasandClaude Opus 4.8 1e14738258 API: Zugriff strikt auf den Besitzer des API-Keys, Key generieren/anzeigen
Der X-API-Key identifiziert den Benutzer; alle Endpunkte waren bereits auf
dessen user_id gescoped (27 Operationen geprueft). Zwei Luecken blieben:

- Die Aufloesung Token -> Benutzer nahm bei mehrdeutigem Token per LIMIT 1
  einfach den ersten Treffer. Haetten zwei Benutzer denselben Token, saehe
  der eine die Daten des anderen. Jetzt: fail closed (401 + Logeintrag),
  und /einstellungen weist einen bereits vergebenen Token mit 409 ab.
- Ein leerer Token galt als Wert: wer seine Einstellungen einmal gespeichert
  hatte, besass eine API_TOKEN-Zeile mit ''. Leere Werte matchen jetzt nie.

Neu in den Einstellungen (Abschnitt REST-API):
- "Neu generieren" erzeugt einen zufaelligen Token (32 Byte, crypto.get-
  RandomValues); er wird nur ins Feld gefuellt und erst beim Speichern
  aktiv, ein Fehlklick laesst sich also verwerfen.
- "Kopieren" legt den Token in die Zwischenablage; das Auge blendet ihn ein
  (bestand bereits fuer Secret-Felder).

API aktualisiert:
- PUT /settings kannte nur name/adresse/kundennummer, GET lieferte aber alle
  acht Felder. Jetzt schreibt PUT alle (email, telefon, ort, webseite,
  geburtsdatum) und aendert nur die im Body uebergebenen Felder; die Antwort
  enthaelt den neuen Stand.

Swagger:
- Beschreibung sagte "konfiguriert via Umgebungsvariable API_TOKEN" - das
  gilt seit der Multi-User-Umstellung nicht mehr. Jetzt dokumentiert:
  Token pro Benutzer aus den Einstellungen, Zugriff nur auf eigene Daten,
  fremde id -> 404, unbekannter/leerer/mehrdeutiger Token -> 401.
- Settings-Schema um die fehlenden fuenf Felder ergaenzt.

Verifiziert mit zwei Benutzern und je eigenem Key: Lesen, Aendern, Loeschen,
Timeline, Anhaenge, E-Mails, Generierung, Jobangebote und Blacklist des
jeweils anderen liefern durchgaengig 404; Listen, Export und Statistik
zeigen nur eigene Daten; kollidierender Token -> 409; mehrdeutiger Token in
der DB -> 401 fuer beide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 22:56:21 +02:00
thomasandClaude Opus 4.8 b6cb94fdb5 Einstellungen strikt pro Benutzer: kein process.env-Fallback mehr
config.get() ist bei einem nicht gesetzten Schluessel auf process.env
zurueckgefallen. Da die Env-Variablen die Konfiguration des Admins
enthalten (Docker-Env: MAIL_*, CALDAV_URL, OLLAMA_API_KEY, API_TOKEN),
hat damit JEDER neu angelegte Benutzer ohne eigene Einstellungen
stillschweigend die Zugangsdaten des Admins geerbt:

- /einstellungen zeigte ihm die Zugangsdaten des Admins an.
- mailer/caldav isConfigured() war true -> der IMAP-Poller hat fuer den
  neuen Benutzer das Postfach des Admins abgerufen und dessen E-Mails in
  sein Konto einsortiert; CalDAV synchronisierte den Kalender des Admins.
- Der bezahlte Ollama-Key des Admins wurde mitbenutzt.

Jetzt:
- config.get() loest ausschliesslich die Zeilen des aktuellen Benutzers auf,
  sonst den eingebauten Standard (nicht-geheime Werte wie Modell, Host,
  Ports, Intervalle). Alle Credentials sind bei neuen Benutzern leer,
  d. h. Ollama/E-Mail/CalDAV/API sind fuer sie aus, bis sie sich selbst
  etwas eintragen.
- Noch per Env gesetzte Konfiguration wird einmalig in die Zeilen des
  ADMIN uebernommen (importEnvIntoAdmin, Aufruf beim Boot nachdem
  app_state existiert - in runMigration war das bei Neuinstallationen ein
  No-op, weil die Tabelle dort noch nicht angelegt ist).
- config.ensureLoaded(user.id) beim Aufloesen der Session bzw. des
  X-API-Key. config.get() ist synchron und liest den Per-User-Cache; ohne
  Warmladen las ein Web-Request die Werte als "nicht konfiguriert". Das
  hat bisher der env-Fallback verdeckt (er hielt zufaellig die Werte des
  Admins) - ohne ihn muss die Config pro Request wirklich geladen werden.

Verifiziert gegen eine Kopie der Produktions-DB mit Sentinel-Env-Werten:
Admin behaelt seine kompletten Einstellungen, der zweite Benutzer sieht
ueberall leere Credentials, Mail/CalDAV sind fuer ihn inaktiv, und der
Env-API-Token wird nicht mehr als gueltiger X-API-Key akzeptiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 22:31:08 +02:00
thomasandClaude Opus 4.8 b2e884d9b3 Persoenliche Angaben: fehlende settings-Zeile ist ein gueltiger Zustand
Neu angelegte Benutzer hatten keine settings-Zeile - die legte bisher nur
die Migration von Hand fuer den Admin an, POST /admin/users dagegen nicht.
Der Code ging aber davon aus, dass die Zeile immer existiert:

- /vorlagen lieferte fuer neue Benutzer 500: die View greift auf
  settings.name zu, bekam aber undefined.
- PUT /api/v1/settings verwarf Schreibzugriffe stillschweigend: das blanke
  UPDATE traf null Zeilen und meldete trotzdem success.

Statt Platzhalter-Zeilen zu provisionieren ist "keine Zeile" jetzt ueberall
ein gueltiger Zustand - genau wie bei prompts, design und app_state:

- loadSettings() (neben loadPrompts()/loadDesign()) liefert {} statt
  undefined; alle sechs Lesestellen gehen darueber.
- PUT /api/v1/settings ist ein Upsert, GET liefert {} statt leerem Body.
- Chat-Tool bewerbung_detail: JOIN auf jobangebote zusaetzlich ueber
  j.user_id = b.user_id, wie derselbe JOIN an anderer Stelle.

Repariert auch bereits angelegte Benutzer ohne Backfill-Migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 22:16:45 +02:00
thomasandClaude 0371aa85a5 Multi-User-Plattform: jeder Benutzer hat eigene, isolierte Daten
- Auth via Session-Cookie + Login-Seite (scrypt, lib/password.js, sessions-Tabelle)
- AsyncLocalStorage (lib/context.js) propagiert aktuellen Benutzer durch alle Libs
- user_id auf allen Datentabellen (FK->users ON DELETE CASCADE), per-user PK/UNIQUE
  (app_state, settings, prompts, design, jobangebote) und per-user Dateispeicher
  (data/<dir>/<userId>/)
- Alle Queries in server.js + lib/api.js nach user_id scope-iert
- Pro-Benutzer-Konfiguration (Ollama/Mail/CalDAV/API-Token) in app_state,
  Live gelesen via config.get(); Hintergrund-Loops (IMAP/CalDAV) iterieren alle Benutzer
- REST-API /api/v1: X-API-Key loest den Token zu einem Benutzer auf, Anfragen
  operieren nur auf dessen Daten
- Admin-Panel /admin: Benutzer anlegen, Passwort zuruecksetzen, loeschen (mit Daten)
- Idempotente Migration (lib/migrate-multiuser.js + scripts/migrate-to-multiuser.js):
  bestehende Daten werden dem Benutzer admin:admin zugeordnet

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-13 21:57:50 +02:00
thomasandClaude 97b48f9841 Einstellungen-Seite: .env-Werte in DB (app_state), keine .env mehr
Neue /einstellungen-Seite (Zahnrad im Header) mit allen bisherigen .env-Werten
(Ollama, E-Mail, CalDAV, REST-API), gespeichert in SQLite (app_state, cfg:-Prefix).
Libs lesen per lib/config.js zur Laufzeit statt beim Start -> Aenderungen
wirken sofort, kein Neustart. Bestehende .env wird beim ersten Start einmalig
migriert.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-13 20:57:31 +02:00
thomasandClaude 41887dd56c Status "Absage von meiner Seite" ergaenzt
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-13 12:51:37 +02:00
thomasandClaude 0c866805d3 Dark Mode folgt Browser-Einstellung (prefers-color-scheme), Toggle entfernt
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-13 12:34:56 +02:00
thomasandClaude e97d1b3687 Persoenliche Angaben unter Vorlagen statt hardcoded/Settings-Modal
- settings-Tabelle um email/telefon/ort/webseite/geburtsdatum erweitert
  (CREATE + ALTER-Migration + Default-INSERT)
- Vorlagen-Seite: neuer Bereich "Persoenliche Angaben" (8 Felder),
  POST /vorlagen/persoenlich; Settings-Modal + Zahnrad-Button entfernt
- Generierung: Kontakt kommt verbindlich aus den Settings (buildHeader
  laesst gespeicherte Werte ueber die KI-Extraktion gewinnen), bewerber-
  Fakten + Prompt angepasst; Design-Vorschau zeigt echte Kontaktdaten
- main.js: Settings-Modal-Handling entfernt; /api/settings bleibt bestehen

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-13 11:41:32 +02:00