104 Commits
Author SHA1 Message Date
thomasandClaude 472259f189 Zeitplan: gesamten Zeitplan direkt in der Sektion deaktivierbar
aktiv-Hauptschalter aus der Sektion 'Automatische Suche' in die Sektion
'Zeitplan' verschoben, sodass der gesamte Zeitplan dort ein-/ausgeschaltet
werden kann. Wochentage + Uhrzeit werden beim Deaktivieren nur abgedunkelt,
die Einstellungen bleiben erhalten.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 18:26:00 +02:00
thomasandClaude ddf909502a Browser-Plugin: Multi-User/X-API-Key + Footer-Download
Plugin (Manifest V3) refactored für gehostete Multi-User-Instanz:
- Tracker-URL hardcoded https://nextjobs.cc (keine URL-Eingabe mehr)
- Authentifizierung ausschließlich über persönlichen REST-API-Key
  (Header X-API-Key), pro Benutzer in chrome.storage.sync
- Server: /api/indeed-import nutzt neuen requireApiKey-Middleware,
  ordnet Import dem Benutzerkonto mit passendem cfg:API_TOKEN zu

Download: /plugin/nextjobs-stellen-import.zip packt extension/ on the
fly (dep-freier STORE-Zip in lib/zipdir.js, deterministischer Timestamp),
route hinter requireAuth. Footer-Link im App-Footer. Dockerfile kopiert
extension/ ins Image.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 09:44:41 +02:00
thomasandClaude 1e9618c920 Gemerkte Bewerbungen: platzsparende Chip-Wolke statt Listenzeilen
Nur der Firmenname als klickbarer Chip, nebeneinander (flex-wrap), jeder
Chip öffnet die Bewerbung. Status/Datum/Stelle/Ort entfallen hier — die
Details stehen auf der Bewerbungsseite; entmerken ebenfalls dort (Stern).
Lange Firmennamen werden mit truncate abgeschnitten, der volle Name steht
im title-Tooltip. Header (Stern + Zähler) bleibt, id für Such-Einblendung
unverändert.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 09:19:15 +02:00
thomasandClaude a6a2fabf05 Modal-Scroll: Wrapper als definite-height Scroll-Container (Safari-safe)
Ursache: Tailwinds min-h-screen (min-height:100vh) am Flex-Wrapper wächst
in Safari nicht mit dem Panel – das Panel ragt sichtbar aus dem Bild, der
Container meldet aber keinen Overflow → nichts scrollt, Speichern-Button
ist unerreichbar (Chromium wächst dagegen mit, daher dort scheinbar ok).

Fix: Die innere .min-h-screen-Box wird zum Scroll-Container mit
DEFINITE height:100vh + overflow-y:auto (+ -webkit-overflow-scrolling:touch).
Der Backdrop hält overflow:hidden. margin:auto am Panel zentriert kurze
Modals und vermeidet Top-Clipping bei langen. Wirkt in Safari, Chrome,
Firefox, iOS und Android (verifiziert via Chromium desktop + iPhone-Touch).

Zusätzlich Cache-Busting für styles.css (?v=<mtime>) am <link>, damit ein
hart zwischengespeichertes altes Stylesheet nach einem Deploy garantiert
neu geladen wird.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 09:14:12 +02:00
thomasandClaude 1c913b2b28 Admin: pro-Nutzer-Rechte für Suchlauf-Start und Zeitplan
- Neue users-Spalten darf_suchlauf_starten / darf_zeitplan_aendern
  (Default 0 = deaktiviert); idempotente ALTER-Migration für Bestands-DBs.
- Admin-Detailseite: zwei Toggles (Erlaubt/Gesperrt) je Nutzer.
- /jobsuche: ohne Recht ist „Jetzt suchen" ausgeblendet und der Zeitplan
  (aktiv/Tage/Uhrzeit) read-only; Speichern und manueller Start sind
  serverseitig abgesichert. Admin (fremd) ist nie beschränkt.
- Default-Zeitplan bleibt Mo–Fr 17:00 (suchprofil-Defaults).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 08:47:48 +02:00
thomasandClaude fa07dbf90a /bewerbung: Tabs Unterlagen/E-Mail + Admin-Nutzerdetailseite
- Bewerbungsdetail: Toggle zwischen Bewerbungsunterlagen und E-Mail-Korrespondenz
  (hash-basiert), weniger Scroll auf der Seite.
- Admin: Klick auf einen Nutzer öffnet neue Verwaltungsseite
  /admin/users/:id (Passwort, Anmelden als, Jobsuche, Admin-Status, Löschen).
- Admin-Tabelle: Löschen und Jobsuche aus den Aktionen entfernt
  (jetzt auf der Nutzerdetailseite).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 08:37:54 +02:00
thomasandClaude ada69eb482 /bewerbung/🆔 zweispaltiges Layout, dezente Eigene Anhänge
- Zweispalter auf >=lg (links: Bewerbungsdaten+Status+Termine,
  rechts: Unterlagen+Korrespondenz), vorher alles untereinander auf
  max-w-3xl. Seite deutlich kürzer, keine Grid-Row-Lücken dank
  unabhängiger Spalten-Container. Unter lg weiter einspaltig.
- Eigene Anhänge: amber durch neutral grau ersetzt, Papierclip durch
  Schloss-Icon ersetzt (signalisiert „nur für dich / nicht im PDF").
- Größte Textareas etwas kürzer (notizen 6->4, Begleit-E-Mail 12->10,
  Versand-Nachricht 10->8).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 08:23:37 +02:00
thomasandClaude 7d5dc582e4 Bewerbungen: Zeile öffnet /bewerbung/:id statt Inline-Aufklappen
Entfernt den ausklappbaren Detailbereich (Status-Verlauf, Notizen,
interne Notizen) unter jeder Bewerbungs-Zeile auf der Startseite. Ein
Klick auf die Zeile öffnet stattdessen die ganze Bewerbung unter
/bewerbung/:id. Die Aktions-Buttons (Merken, Bearbeiten, Löschen)
bleiben separat bedienbar.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 08:10:51 +02:00
thomasandClaude 1ba615fa80 UI fixes: modal scroll, Bewerbungen-Aktionen, E-Mail-Uhrzeit Berlin
- Modal „Bewerbung hinzufügen" lässt sich auf allen Bildschirmgrößen scrollen
  (nicht nur ≤640px), sonst war Speichern abgeschnitten
- Aktionen-Spalte der Bewerbungen von 4.5rem auf 5.5rem (passt zur
  Cluster-Breite, Icons wandern nicht mehr nach rechts)
- Merken-Stern im Compact-Modus ohne Rahmen
- E-Mail-Uhrzeiten in Postfach, Bewerbung und Reply-Quote in Europe/Berlin

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 08:02:37 +02:00
thomasandClaude 96fd0e282d Jobangebote: Gemerkte nicht mehr unter Offen anzeigen
Favorisierte Angebote erscheinen ausschließlich im Gemerkt-Tab und werden
aus der Offen-Liste, dem Offen-Zähler (Header-Badge) und dem Tab-Zähler
ausgeblendet. Auf der Offen-Seite wird die Zeile beim Merken live entfernt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 00:01:50 +02:00
thomasandClaude d9ba8698e0 Termine: alle anstehenden auf Startseite + Bearbeiten; Status Telefonat
- Startseite zeigt alle anstehenden Termine (vorher auf 6 begrenzt)
- Termine lassen sich inline bearbeiten (CalDAV-Update, Neu-Anlage bei 404/412)
- Neuer Bewerbungsstatus Telefonat (Listen, Farben, API, PDF, Statistik)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 15:27:19 +02:00
thomasandClaude ce5fa1eab1 Startseite: Gemerkte Bewerbungen unter die anstehenden Termine, dezenter & modern
Favoriten-Sektion wird nicht mehr ganz oben gerendert, sondern direkt
unter der Terminkarte; bei aktiver Suche wird sie nun ebenfalls ausge-
blendet. Karte bewusst dezenter gehalten (Border statt Schatten,
kleinere Überschrift, gedämpfte Akzente) mit modernen weichen Kanten.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-15 10:55:20 +02:00
thomasandClaude Opus 4.8 1dd4807ca6 Einstellungen: Vorauswahl für KI-Generierung (Dokumente + Anlagen)
Zwei neue, pro Benutzer gespeicherte Konfigurationswerte:
GEN_DOKUMENTE_DEFAULT (Standard: Anschreiben + Lebenslauf) und
GEN_ANLAGEN_DEFAULT (Standard: keine). Sie belegen die Häkchen auf der
Bewerbungsseite vor; eine bereits getroffene Dokumentenauswahl der
Bewerbung gewinnt weiterhin. Die REST-API nutzt die Vorauswahl, wenn
dokumente/anlagen im Body fehlen.

Die Einstellungsseite kennt dafür jetzt Checkbox-Gruppen, deren Optionen
auch erst zur Renderzeit feststehen dürfen (die eigenen Anlagen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:31:19 +02:00
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 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 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 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 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 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 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 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 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