Commit Graph
97 Commits
Author SHA1 Message Date
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
thomasandClaude Opus 4.8 d1116d522d Vor dem Generieren waehlen: Anschreiben, Lebenslauf oder beides
Auf der Bewerbungsseite laesst sich jetzt auswaehlen, welche Unterlagen die KI
erzeugt. Standard bleibt beides; die letzte Auswahl wird gemerkt und ist beim
naechsten Generieren vorbelegt. Ohne Haken bricht das Formular ab statt still
beides zu erzeugen.

Nicht gewaehlte Dokumente werden gar nicht erst angefragt: JSON-Schema und
Skelett im Prompt werden auf die gewuenschten Teile reduziert, das spart einen
Gutteil der Generierungszeit. Gerendert wird ebenfalls nur das Gewaehlte - auch
wenn das Modell sich nicht an das Schema haelt und trotzdem alles liefert.

Fuer den E-Mail-Versand mitgedacht:
- Wird kein Lebenslauf erzeugt, fuehrt ihn das Anschreiben nicht mehr unter
  "Anlagen" auf. Sonst kuendigt der Brief eine Anlage an, die nie mitgeht.
- Der Prompt bekommt zusaetzlich die Liste der Dateien, die der Bewerbungsmail
  tatsaechlich anhaengen (inkl. Anschreiben selbst), damit der Begleittext keine
  Unterlagen nennt, die nicht dabei sind.
- Die Anhang-Checkboxen im Mailformular ziehen sich aus den erzeugten Anhaengen,
  greifen also automatisch.

Die REST-API kennt das Feld ebenfalls (POST .../generate, "dokumente"), inkl.
OpenAPI-Doku.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 02:18:02 +02:00
thomasandClaude Opus 4.8 66366659d2 Social-Design: Herz-Abstaende korrigiert + Icons fuer die Kontaktdaten
Die Abschnittsueberschriften wurden mit align:'center' zentriert. jsPDF misst
dafuer mit getTextWidth(), das den charSpace ignoriert, rendert den Text aber
gesperrt - er lief nach rechts, waehrend die Herzen symmetrisch sassen. Ergebnis:
links sichtbar mehr Luft als rechts. Die Ueberschrift wird jetzt selbst zentriert,
gemessen an der tatsaechlichen Tinte (Sperrung zaehlt nur zwischen den Glyphen,
nicht nach dem letzten), und die Herzen haengen an den echten Textkanten.

Kontaktdaten bekommen passende Icons: Umschlag, Telefon, Map-Pin, Globus,
Kalender, Ausweis. Als Vektoren gezeichnet, weil kein Icon-Glyph in der Schrift
vorausgesetzt werden kann; sie skalieren mit der Seitenskalierung mit.
buildContactLines liefert dafuer jetzt {typ, text} - die klassische Sidebar nutzt
weiterhin nur den Text und bleibt unveraendert schlicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 01:30:56 +02:00
thomasandClaude Opus 4.8 0e1d9b3322 Zweites Lebenslauf-Design "Social Media" + Anschreiben folgt dem Layout
Der Lebenslauf kann jetzt zwischen zwei Layouts waehlen, und das Anschreiben
uebernimmt immer das Layout des Lebenslaufs - beide Dokumente kommen als ein Set
beim Unternehmen an.

"Social Media" ist die verspielte Variante nach der Media-Kit-Vorlage: getoente
Seite, rundes Portrait mit Blob, Schreibschrift-Zeile ueber einem grossen Namen,
Herzchen als Abschnittsmarker und Bullets, abgerundete Karten je Station,
Pill-Chips fuer Kompetenzen und Sprachen, Glitzer-Sterne als Deko. Bewusst laut -
gedacht fuer kreative und Social-Media-Stellen.

Die gesamte Farbwelt (Seite, Karten, Pills, Deko) wird aus der Akzentfarbe
abgeleitet, das Layout funktioniert also auch in Violett oder Tiefblau. Neue
Akzente Pink und Violett; das Layout bringt eigene Defaults mit (pink, rundes
Foto), sodass ein Wechsel sofort stimmig aussieht.

Neue Schriften (SIL OFL): Poppins fuer die geometrische Sans, Pacifico fuer die
Schreibschrift. Es werden nur die Fonts eingebettet, die das gewaehlte Layout
nutzt - ein klassischer Lebenslauf traegt Poppins/Pacifico nicht mit.

Herz und Sparkle sind als Bezier-Pfade gezeichnet, weil sich auf ein Herz-Glyph
in der Schrift nicht verlassen werden kann. Das neue Layout nutzt dieselbe
Mess- und Skalier-Mechanik wie das klassische, erbt also die Ein-Seiten-Garantie.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 01:15:02 +02:00
thomasandClaude Opus 4.8 9f7349af4e Design der Unterlagen unter Vorlagen einstellbar
Akzentfarbe, Sidebar-Hintergrund, Foto (an/aus, eckig/rund) und Schriftgroesse
lagen als Konstanten im Renderer. Sie kommen jetzt aus lib/design.js und sind
unter Vorlagen waehlbar, inklusive Vorschau-PDF mit Musterinhalten - die zeigt
das Ergebnis, ohne dass die KI laufen muss.

Bewusst geschlossen gehalten: die Palette ist eine kurze Liste gedeckter Toene
(kein freier Color-Picker), Grauwerte und Grundlayout bleiben fest. Die
Schriftgroesse ist nur der Startwert der Seitenskalierung - der Lebenslauf passt
weiterhin garantiert auf eine Seite.

Das Theme wird durch die Render-Funktionen gereicht statt als Modulzustand
gesetzt, damit spaeter mehrere Bewerber parallel generieren koennen, ohne sich
gegenseitig das Design umzustellen.

Rundes Foto: das Bild fuellt den Kreis formatfuellend und zentriert (Clip statt
Skalieren), sonst wuerde ein Hochformat-Foto im Quadrat gestaucht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:51:36 +02:00
thomasandClaude Opus 4.8 65a5943993 KI-Prompts unter Vorlagen editierbar statt hardcoded
Rolle, Tonfall und Regeln der KI (Unterlagen, Chat, E-Mail-Antwort,
E-Mail-Absage, gemeinsame Stilregeln) lagen fest im Code. Sie liegen jetzt
als Defaults in lib/prompts.js und lassen sich auf der Vorlagen-Seite je
Prompt anpassen und wieder zuruecksetzen.

Nur die System-Prompts sind editierbar. Die User-Prompts tragen das
JSON-Skeleton, gegen das die Antwort geparst wird - ein Tippfehler dort
wuerde die Generierung lahmlegen, also bleiben sie im Code.

Gespeichert wird nur, was abweicht: ein Override ist eine Zeile in der neuen
Tabelle `prompts`, "Zuruecksetzen" loescht sie. Damit bleiben die Defaults im
Code die Wahrheit und wandern bei Updates automatisch mit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:09:03 +02:00
thomasandClaude 0a0b0b4724 E-Mail: KI-Absage generieren (andere Stelle angenommen)
Im Antwort-Formular gibt es neben dem KI-Button jetzt ein Dropdown Antwort/Absage.
Für Absage formuliert die KI eine kurze, höfliche E-Mail: Bewerber hat eine andere
Stelle angenommen, bedankt sich, zieht sich aus dem Prozess zurück.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-10 20:51:37 +02:00
thomasandClaude 0c7502cfd7 KI-Chat: Bewerbungsdaten per Tool-Calling nachschlagen statt in Prompt stopfen
Der Systemprompt enthielt früher nur die 12 jüngsten Bewerbungen (LIMIT 12),
daher fand die KI ältere Firmen wie QBS KLIMTAX nicht. Alle Bewerbungen in den
Prompt zu laden machte den Chat sehr langsam, da der Prompt bei jedem Turn
voll neu verarbeitet wird.

Jetzt nutzt der Assistent Ollama-Tool-Calling, um Datenbankdaten on demand
nachzuschlagen. Der Systemprompt bleibt klein und konstant (nur Profil +
Datum), unabhängig von der Anzahl der Bewerbungen.

- lib/chat.js: streamChat mit tools + runChat-Tool-Schleife (max 4 Runden)
- server.js: Tools suche_bewerbungen/list_bewerbungen/bewerbung_detail/
  kommende_termine mit SQL-Queries; gatherChatContext auf Kern reduziert
- public/js/chat.js: Tool-Indikator ("durchsucht Bewerbungen…") im UI

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 05:41:14 +02:00
thomasandClaude 306026b0a1 Interne Notizen: Anhang-Upload repariert + schöneres Design
Der Upload- und Lösch-Formular war versehentlich inside des
Bewerbungsdaten-Formulars verschachtelt – HTML lässt verschachtelte
Formulare nicht zu, weshalb der Browser das multipart-Formular
ignorierte und die Datei nie gesendet wurde. Formulare jetzt outside
des Speichern-Formulars. Dazu ansprechendere Karte-Optik mit
Öffnen-/Download-/Löschen-Aktionen.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 04:46:33 +02:00
thomasandClaude 0b44f04456 Interne Notizen: eigene Anhänge (PDFs/Dateien) nur für dich
Unter den internen Notizen lassen sich nun private Anhänge hochladen.
PDFs öffnen per Klick direkt im Browser; Anhänge werden weder in den
PDF-Export noch in den Bewerbungsversand einbezogen.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 04:37:52 +02:00
thomasandClaude 40b4de12e1 KI-Chat: Verlaufs-Titel live aktualisiert + sauberes Floating-Icon
- Der automatisch generierte Verlaufs-Titel (aus der ersten Nachricht) erscheint
  jetzt sofort in der Sidebar, nicht erst nach Reload. Der titel wird im
  done-Event mitgeschickt und clientseitig gesetzt.
- Floating-Button-Icon durch eine saubere Chat-Bubble ersetzt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:57:35 +00:00
thomasandClaude 26a61aa302 KI-Chat: Verlaufs-Titel inline umbenennbar
Pro Verlauf ein Stift-Button (neben dem Löschen-Button). Klick macht den
Titel inline editierbar; Enter/Blur speichert via PATCH /chat/api/threads/:id,
Escape bricht ab. Auch für client-seitig neu angelegte Threads.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:55:06 +00:00
thomasandClaude d2545c4faf KI-Chat: Warte-Indikator als gestaffelte Bounce-Animation
Drei Punkte mit gestaffeltem Animation-Delay (klassische Tipp-Animation),
inkl. prefers-reduced-motion-Fallback (statisch).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:48:04 +00:00
thomasandClaude 61b67e0843 KI-Chat: Verlauf wird beim Laden angezeigt + einzelne Chats löschbar
- initialMessages-JSON wurde mit <%= %> HTML-escaped, wodurch JSON.parse
  fehlschlug und die Nachrichtenfläche beim Öffnen eines Verlaufs leer blieb.
  Jetzt <%- %> mit < -> < escapet, JSON bleibt parsebar.
- Pro Verlauf ein Trash-Button in der Sidebar (DELETE /chat/api/threads/:id).
  Löscht den Verlauf, entfernt die Zeile, setzt bei aktivem Verlauf auf
  neuen Chat zurück. Auch für client-seitig neu angelegte Threads.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:42:29 +00:00
thomasandClaude cf6cf9ee33 KI-Chat: reichhaltigerer Kontext (Stellenbeschreibung, Korrespondenz, Profil)
Der Assistent kannte nur Firma/Stelle/Status/Notizen und konnte daher keine
firmenspezifischen Fragen beantworten. gatherChatContext lädt jetzt pro
Bewerbung: Ort, interne Notizen, Stellenbeschreibung (bzw. verknüpftes
Jobangebot), Kontakt/Ansprechpartner und die E-Mail-Korrespondenz (Betreffe).
Zusätzlich wird der Lebenslauf des Bewerbers injiziert, damit Antworten auf
"was für mich wichtig" zugeschnitten werden können.

Relevante Bewerbungen = Termin-Bewerbungen + 12 jüngste (gebunden im Token-Budget).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:36:45 +00:00
thomasandClaude cc397e8f71 KI-Chat: Markdown-Rendering, Left-Alignment, kompaktere UI, "Neuer Chat" fix
- Markdown-Renderer (Headings/Listen/**bold*/*italic*/`code`/Links) für
  Assistenten-Antworten; HTML wird vorher escaped (keine Injektion).
- Antworten explizit linksbündig, Inhalt getrimmt (keine Leerzeilen oben/unten).
- Initiale Nachrichten als JSON-Blob, einheitlich via chat.js gerendert.
- Chat-Container kompakter (Höhe/Spacing reduziert).
- "Neuer Chat" setzt clientseitig auf leeren Zustand zurück (vorher lud der
  Button nur den letzten Thread neu).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:28:54 +00:00
thomasandClaude c389d3d5ec KI-Chat: Kontext-Injection repariert (SQL from-Alias) + Datum im Prompt
gatherChatContext() brach mit SQLITE_ERROR ab, weil "e.from_addr AS from"
das SQL-Schlüsselwort from als Alias nutzte. Der Fehler wurde im Route-Handler
geschluckt (context={}), sodass der Assistent keinen Zugriff auf Bewerbungen/
Termine/E-Mails hatte. Alias entfernt.

Zusätzlich erhält der System-Prompt jetzt das heutige Datum, damit der
Assistent "morgen"/"heute" aus den UTC-Termin-Zeitstempeln ableiten kann.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:21:13 +00:00
thomasandClaude 8656e92f3c KI-Chat: Eingabefeld bei neuem/leerem Chat nicht mehr disabled
Das Textfeld war ohne vorhandenen Thread deaktiviert, sodass die erste
Frage (die den Thread erst anlegt) nicht eintippbar war. disabled entfernt;
chat.js legt den Thread weiterhin beim ersten Absenden an.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:14:00 +00:00
thomasandClaude ead70efa49 KI-Chat: interaktiver Bewerbungs-Assistent (Ollama-Streaming)
Eigenes Chat-Interface mit SSE-Streaming gegen das hinterlegte Ollama-Modell,
gegroundet in den Bewerbungs-/E-Mail-/Termindaten des Nutzers.

- lib/chat.js: streamChat (Ollama stream:true, NDJSON-Token) + buildContextPrompt
- chat_threads/chat_messages Tabellen (CASCADE, Index)
- Routen: GET /chat, Thread-CRUD, POST /messages (SSE, AbortController)
- views/chat.ejs + public/js/chat.js + Floating-Button im Footer
- hasApiKey-Gating (503 ohne OLLAMA_API_KEY)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 14:07:38 +00:00
thomasandClaude Opus 4.8 17df46cb6c Reply-Mail: Zeilenumbrüche als <br> statt white-space:pre-wrap
Ausgehende Antwort-Mails (multipart/alternative) rendern den HTML-Teil
bisher über white-space:pre-wrap auf rohen Zeilenumbrüchen ohne <br>.
Beim ersten Empfang korrekt, aber sobald der Empfänger-Client (Outlook)
die Mail zitiert, wird der pre-wrap-Stil verworfen und die nackten
Newlines zu Leerzeichen kollabiert -> unser formatiertes Anschreiben wird
im Zitat zur zusammenhanglosen Textwand. Explizite <br> überleben diesen
Round-Trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 09:18:44 +00:00
thomasandClaude Opus 4.8 4e1043897d Blacklist: Firmen schreibweisen-robust matchen (eine Firma nur einmal)
sameCompany() vergleicht Firmen-Slugs jetzt per Gleichheit ODER führendem
Bindestrich-Präfix (>=2 Tokens), sodass Kurzname und voller Firmenname
derselben Firma als gleich gelten (z. B. "it-problemloeser" =
"it-problemloeser-verwaltungs-und-handels", "stadtwerke-essen" =
"stadtwerke-essen-netz"). firmaMatch() nutzt das für die Blacklist-Typen
firma/firma_stelle/auto. Distinkte Firmen mit nur gleichem ersten Wort
("meyer-it" vs "meyer-logistik") bleiben getrennt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 07:25:04 +00:00
thomasandClaude Opus 4.8 7e0c27341a CalDAV: HTML-Entities in Terminfeldern dekodieren
Manche Kalender speichern SUMMARY/LOCATION/DESCRIPTION mit wörtlichen
HTML-Entities (z. B. "Vorstellungsgespr&#228;ch &#8211; dot.haus"). Diese
wurden roh angezeigt. Beim Parsen werden sie jetzt in echte Zeichen
dekodiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:58:07 +02:00
thomasandClaude Opus 4.8 22f967c753 Carry firma_slug on job offers, supplied by the ingesting client
The blacklist company slug now lives on the offers themselves. POST /joboffers
accepts firma_slug and stores it as supplied (deriving it from firma only when
omitted); it is persisted on jobangebote (schema + ALTER/backfill + index) and
returned in the Joboffer response. Blacklist matching prefers the client-supplied
slug over one derived from firma, so the indexing client controls the identity
used to block an offer. OpenAPI request/response schemas updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:42:41 +02:00
thomasandClaude Opus 4.8 f935e00b53 Add robust company slug for job-offer blacklist matching
Company matching relied on normText(), which kept legal-form suffixes,
spacing and umlaut spelling — so "Bosch GmbH", "Bosch AG" and "bosch gmbh"
did not match the same entry, making the blacklist unreliable.

Introduce firmaSlug(): transliterate German umlauts, strip diacritics and
trailing legal-form tokens (GmbH/AG/SE/KG/…), then kebab-join. Add a
firma_slug field to jobangebote_blacklist (schema + ALTER/backfill migration)
and match on it for typ firma/firma_stelle/auto, falling back to firma_norm
for legacy rows. POST /joboffers rejects blacklisted offers via matchBlacklist
automatically since offerSignature now carries the slug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:25:59 +02:00
thomasandClaude Opus 4.8 4e5933890f Footer: Copyright hackner.dev ergänzen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 17:50:04 +02:00
thomasandClaude Opus 4.8 3b694edac6 Dark-Mode vor erstem Paint setzen (kein Light-Mode-Flash mehr)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 17:46:49 +02:00
thomasandClaude Opus 4.8 30f83d433a Gesendete Anhänge in Korrespondenz anzeigen (email_anhaenge verknüpfen)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 15:47:07 +02:00
thomasandClaude Opus 4.8 4003279453 Anhänge (Unterlagen + Mailanhänge): PDF im neuen Tab öffnen statt Download
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 15:42:12 +02:00
thomasandClaude Opus 4.8 d99c8885fc Antwort-Betreff: kein doppeltes Re:/AW: voranstellen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:55:43 +02:00
thomasandClaude Opus 4.8 7dddbe18b1 Antwort-Mails: hinterlegte Anlagen mitschicken (Standard: keine)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 11:48:39 +02:00
thomasandClaude Opus 4.8 c2c51f9fca Neue Bewerbungs-Status: In Bearbeitung, Interessiert, Warten auf Rückmeldung/meine Antwort
Vier mittlere Pipeline-Status ergänzt (nach Eingangsbestätigung, vor
Vorstellungsgespräch), inkl. Farben/Reihenfolge in Übersicht, Detailseite
und PDF-Export sowie REST-API-Validierung und Swagger-Enum.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:06:56 +02:00
thomasandClaude Opus 4.8 03760d293f Labels für Stellen (Bewerbungen + Jobangebote), inkl. API & Swagger
Mehrere Labels pro Stelle (Regional, Remote-Deutschlandweit,
Homeoffice-Deutschlandweit), gespeichert als JSON-Array in einer neuen
labels-Spalte beider Tabellen (Migration). Geteiltes lib/labels.js mit
parse/serialize; wiederverwendbare Partials fuer Chips + Mehrfachauswahl.

- Web: setzen im Hinzufuegen-Modal, auf der Bearbeiten-Seite und im
  Jobangebot-Bearbeiten-Formular; Anzeige als Chips in den Listen.
- Uebernahme eines Angebots traegt dessen Labels in die neue Bewerbung.
- REST-API: labels[] in /applications und /joboffers (GET/POST/PUT),
  Filter ?label=…; OpenAPI/Swagger-Schemas + Enums erweitert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:23:44 +02:00
thomasandClaude Opus 4.8 d1c6743d2f PDF-Export: keine zerrissenen Zeilen mehr + HTML-Entities dekodieren
rowPageBreak 'avoid' verhindert das Splitten einer Zeile am Seitenumbruch,
das bisher Datum/Art/Status auf der Folgeseite leer liess. Firma/Stelle/Art
werden HTML-dekodiert (z.B. "&amp;" -> "&"), leere Werte zeigen "—".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:20:27 +02:00
thomasandClaude Opus 4.8 78b1ea78d0 PDF-Export: kompakte Tabelle, Filter nach letztem Status-Datum
Verbose Blöcke (Notizen + kompletter Verlauf) durch eine autoTable ersetzt:
eine Zeile pro Bewerbung mit Datum, Firma, Stelle, Art und letztem Status
(farbiges Badge). Export filtert/datiert nun nach dem effektiven Datum, also
der letzten Statusaenderung – eine im Juni gesendete, im Juli zum Gespraech
gewordene Bewerbung erscheint dadurch im Juli-Export. Modal-Auswahl nutzt
dieselben effektiven Monate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:12:24 +02:00
thomasandClaude Opus 4.8 b269dcaecc Footer immer am Seitenende (Sticky) und Text auf "Bewerbungssystem"
Body als Flex-Column, main mit flex-1 drueckt den Footer nach unten.
Footer-Jahr serverseitig gerendert (immer aktuell).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:40:55 +02:00
thomasandClaude Opus 4.8 82431490a9 Startseite: Bewerbungsliste als klappbare Sektionen (eine Zeile pro Bewerbung)
Tabelle auf Grid-Layout umgebaut. Status-Gruppen und der Detailbereich
(Verlauf/Notizen) je Bewerbung sind animiert zuklappbar, Default zugeklappt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:36:29 +02:00
thomasandClaude Opus 4.8 7cc527b013 Glocke im Header ganz nach rechts verschieben
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:48:36 +02:00
thomasandClaude Opus 4.8 a44b319e7e Benachrichtigungsglocke fuer ungelesene E-Mails
Neue Antworten werden meist automatisch einer Bewerbung zugeordnet und
tauchten daher nie im Postfach auf - man bemerkte sie nicht. Eine Glocke
im Header zeigt jetzt auf jeder Seite die Zahl ungelesener empfangener
E-Mails (inkl. zugeordneter Antworten) und listet sie in einem Dropdown
mit Absender, Betreff, Auszug und Link zur Bewerbung bzw. zum Postfach.

- GET /api/notifications: Anzahl + neueste ungelesene Nachrichten.
- POST /api/emails/mark-all-read: alle als gelesen markieren.
- Postfach-Ansicht markiert unverknuepfte Mails als gelesen (leert die
  Glocke), zugeordnete Antworten werden beim Oeffnen der Bewerbung gelesen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:39:05 +02:00
thomasandClaude Opus 4.8 b1531663bb E-Mails als HTML anzeigen; Antworten zitieren Vornachricht (auch KI)
- HTML-Mails werden in einem sandboxed iframe (ohne allow-scripts)
  gerendert statt als Quelltext angezeigt; Höhe an Inhalt angepasst,
  Skripte/Tracking laufen nicht, Layout bleibt isoliert. Reine
  Text-Mails weiterhin als pre-wrap. Gilt für Postfach und Bewerbung.
- Antwortformular ist wie im Mailclient mit der zitierten Vornachricht
  vorbelegt (Attribution + "> "-Zeilen), Cursor darüber.
- KI-Antwort hängt das Zitat unter den generierten Text und erhält den
  bereinigten Klartext (statt HTML) als Eingabe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:06:59 +02:00
thomasandClaude 7ceff39364 Ignore local Indeed test fixture (indeed_example.txt)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 20:10:27 +02:00
thomasandClaude Opus 4.8 95b5b8510d Add a two-way application calendar (CalDAV / SOGo)
New lib/caldav.js speaks CalDAV over Basic auth (shared mail account):
reads events in a window, and creates/updates/deletes iCalendar VEVENTs
with a reminder alarm. DST-correct Europe/Berlin <-> UTC handling.

Appointments (Vorstellungsgespräch / general) are managed per application
in a new "Termine" section and mirrored to the SOGo calendar; recording a
"Vorstellungsgespräch" status suggests a prefilled calendar entry
(confirm + click). A dashboard widget lists upcoming appointments. A
background poller reconciles remote edits/deletions via the collection
ctag. Config: CALDAV_URL (+ CALDAV_ALARM_MIN, CALDAV_POLL_MS); disabled
gracefully when unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:06:38 +02:00
thomasandClaude Opus 4.8 89c8c6db97 Delete e-mails from the Postfach; searchable assignment picker
Each Postfach e-mail gets a Delete button (POST /postfach/:id/delete),
which also removes stored attachment files. The plain assignment
dropdown is replaced by a searchable combobox that filters applications
by company, position and city, with keyboard navigation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 18:55:23 +02:00
thomasandClaude Opus 4.8 26872ece08 Choose enclosed attachments before generating; drop instant-generate
The generate form now lets the user pick which extra attachments
(basis_anhaenge) to enclose — none selected by default. Only the chosen
ones are attached, and their names are passed to the LLM so the cover
letter's "Anlagen" list and wording reflect exactly what is enclosed.

Job offers keep only "Als Bewerbung übernehmen" (draft first); the
"Übernehmen & KI-Unterlagen" button and its route are removed.

REST API: POST /applications/:id/generate accepts an optional `anlagen`
array of attachment IDs (Swagger updated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 17:36:14 +02:00
thomasandClaude Opus 4.8 360e04d677 Confirm before sending; add address + contact to job offers
"Bewerbung senden" now asks for an extra confirmation (with the
recipient) before dispatching.

Job offers gain adresse and ansprechpartner fields — shown, editable,
and carried through the REST API upsert (Swagger updated). Taking an
offer over now writes the employer address (street, number, city) and
the contact person into the application's AI notes (llm_notizen), so the
LLM can use them for the letter's Anschriftfeld and salutation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 17:10:13 +02:00
thomasandClaude Opus 4.8 6a5bdfa6f8 Split job offers into Open and Taken-over pages
/jobangebote now lists only open offers; taken-over offers move to a
dedicated /jobangebote/uebernommen page showing which applications were
created (with link, status and date). A tab bar switches between both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 14:43:12 +02:00
thomasandClaude Opus 4.8 bc112e2627 Let job offers carry a full description into AI generation
Job offers can now be edited in the UI — a large description textarea
(any length) plus the other fields — so a full job posting can be pasted
before turning it into an application. Taking an offer over copies its
complete description into the application's stellenbeschreibung, which is
exactly the text handed to the LLM.

New primary action "Übernehmen & KI-Unterlagen" creates the application
and immediately starts generation; "Nur als Entwurf" keeps the plain
draft path. New routes: POST /jobangebote/:id/bearbeiten and
/jobangebote/:id/uebernehmen-generieren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 14:07:30 +02:00
thomasandClaude Opus 4.8 dae077486f Add a job-offer blacklist with URL-based de-duplication
Offers are now de-duplicated by normalized URL (tracking params stripped)
in addition to (quelle, external_id), so the same posting never lands
twice — even re-scraped under a new id. Deleting an offer (web or API)
auto-blacklists it, so it can never reappear.

lib/blacklist.js provides shared normalization + matching. Manual entries
can block a URL, a whole domain, a company, or a company+title posting
(gender-marker tolerant). New /blacklist page lists and manages entries.

REST API: GET/POST /joboffers/blacklist, DELETE /joboffers/blacklist/{id};
POST /joboffers returns 409 when blacklisted; DELETE /joboffers/{id}
auto-blacklists (opt out with ?blacklist=false). Swagger updated with the
new paths and schemas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 13:33:12 +02:00
thomasandClaude Opus 4.8 1655427b4c Drop the underline rules under the CV sidebar headings
The sidebar section labels no longer carry a short accent underline;
tracking and weight provide the structure, keeping the left column clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 12:50:50 +02:00
thomasandClaude Opus 4.8 fa25307791 Flatten a multi-line address in the cover-letter footer
doc.text breaks on newlines, which stacked the street above the city in
the footer contact strip. Split the address on newlines and commas and
rejoin as one line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 12:39:36 +02:00
thomasandClaude Opus 4.8 8281128adc Restyle the cover letter to match the CV; refine the résumé head
Cover letter: a big confident name + role in tracked caps, the shared
hairline-with-accent motif, and a centred contact footer strip (moved
out of the header) so it reads as one set with the monochrome résumé.

Résumé: drop the divider rule above the Profil heading — the section
headings carry the structure on their own.

Prompt: forbid the headline from using a protected professional title
the applicant has not verifiably earned (e.g. Fachinformatiker,
Ingenieur, Meister); fall back to a neutral activity label.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 12:33:32 +02:00
thomasandClaude Opus 4.8 390f9a643b Redesign the Lebenslauf as a monochrome two-column layout
Replace the single-column résumé with a two-column layout: a tinted
grey sidebar (photo, contact, Kernkompetenzen, Sprachen, Interessen)
and a wide main column with a large name header, Profil, a timeline
Berufserfahrung, and education. Strictly black-and-white — the former
navy accent becomes near-black across both the résumé and the cover
letter so the set stays consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 12:12:48 +02:00
thomasandClaude 35b60bc2ca Stop long company names from pushing the actions column off-screen
The overview table used auto layout with whitespace-nowrap on Firma/Stelle,
so a long name expanded the columns beyond w-full and the overflow-hidden
container clipped the right edge — hiding Bearbeiten/Löschen. Switch to
table-fixed with a colgroup for stable column widths, allow Firma/Stelle to
wrap (break-words, align-top), and make the wrapper overflow-x-auto so the
table scrolls horizontally on narrow viewports instead of clipping.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 00:44:45 +02:00
thomasandClaude 89991d5c06 Show job offer anzeige_datum prominently on the card
The posting date was only a small gray span at the bottom and easy to
miss. Promote it to a labeled "Anzeige:" line right under firma/stelle/ort,
and add "Bewerbung vom: <datum>" in the footer row for offers that have
been turned into an application.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 23:29:07 +02:00
thomasandClaude 955e975ace Show job offer kontakt_email prominently on the card
Move the mailto: link up next to firma/stelle/ort as a labeled
"Kontakt:" line so the contact email is directly visible on each job
offer, instead of a small link below the description.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 22:29:55 +02:00
thomasandClaude cb1115ef95 Add kontakt_email to job offers
- New kontakt_email TEXT column on jobangebote (+ ALTER TABLE migration)
- Accept kontakt_email in POST /api/v1/joboffers (create + upsert) and
  document it in the OpenAPI spec
- Render a mailto: link on the /jobangebote page and include it in the
  curl example

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 22:16:55 +02:00
thomasandClaude faa3a45815 Clarify the two job-offer dates in the page docs
The curl example only showed anzeige_datum, which read as if there's a
single date. Add an explicit list distinguishing Anzeige (anzeige_datum,
sender-provided) from Einspielung (created_at, server-set automatically),
and a comment in the curl body that created_at is not to be sent.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 21:58:24 +02:00
thomasandClaude 5d6fed7041 Show job offer posting date (anzeige_datum) alongside ingestion date
- Add anzeige_datum DATE column to jobangebote (with ALTER TABLE migration
  for pre-existing tables) — the original posting date supplied by the
  third party
- Accept anzeige_datum in POST /api/v1/joboffers (create + upsert) and
  document it in the OpenAPI spec
- Render both on /jobangebote: "Anzeige: <anzeige_datum>" and
  "Einspielung: <created_at>"

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 21:46:12 +02:00
thomasandClaude 674dba40fa Fix empty Swagger UI: drop invalid presets/layout config
SwaggerUIBundle.presets.apisAndSaver does not exist (undefined), which broke
initialization and rendered nothing. Use the minimal canonical config (url,
dom_id, deepLinking, persistAuthorization) and drop the now-unused standalone
preset script.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 21:37:00 +02:00
thomasandClaude 88875dbc33 Add third-party REST API (/api/v1) + Swagger UI and Jobangebote page
- REST API under /api/v1 with X-API-Key auth (API_TOKEN), documented with
  OpenAPI 3.0; Swagger UI at /swagger, spec at /swagger.json
- Endpoints: applications CRUD + timeline, attachments/emails download,
  generation trigger/status, settings, statistics, export, templates, joboffers
- Jobangebote page (/jobangebote) listing offers ingested via the REST API,
  with "Als Bewerbung übernehmen" and delete actions; header nav + badge
- jobangebote table with (quelle, external_id) upsert for third-party ingestion

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 21:30:33 +02:00
thomasandClaude Opus 4.8 0af731695a Size CV heading accent bar to the word width
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:26:28 +02:00
thomasandClaude 482d8d86fa Shorten cover email + strip non-Latin script from AI text
The generated Begleit-E-Mail was too long for an HR reader and sometimes
contained stray non-Latin characters (e.g. CJK "包括"). Make the email a
minimal accompanying note (no repetition of the cover letter, max ~3
sentences), add a German-only/Latin-script instruction to both the
document and reply prompts, and add a backstop sanitizer in str() that
strips any non-Latin script characters the model still emits.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 19:28:34 +02:00
thomasandClaude fd11facadd Add centered live company search to the overview
A modern search bar at the top center of the index page filters the
applications table live by firma/stelle. Status group headers hide when
their group has no visible rows, per-group counts update with the
filtered result, and an empty-state row shows when nothing matches.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 19:17:54 +02:00
thomasandClaude 106b184fda Let postfach e-mails expand to full body
The unlinked-e-mail cards clipped the body to a preview height. Add a
toggle to show the complete body inline and collapse it back.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 19:12:24 +02:00
thomasandClaude 4418c15553 Add Postfach page to manually assign unlinked e-mails
Incoming e-mails that IMAP matching could not link to an application
(bewerbung_id IS NULL) were invisible. Add a dedicated page listing
them with a dropdown of existing applications to assign each one by
hand. A header badge on every page shows the count of unlinked e-mails
via a small /api/emails/unassigned-count endpoint.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 19:07:54 +02:00
thomasandClaude 931bb85c6a Don't name current employer in cover letter
The LLM was naming the current employer (e.g. "IT-Problemlöser GmbH")
in the cover letter text. Add a prompt rule to keep the company name out
of the Anschreiben; the CV still lists employers as before.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 18:50:59 +02:00
thomasandClaude 57eb19632d Always include languages (esp. English) in CV
Languages were dropped when not explicitly required by the job posting.
Sprachkenntnisse are standard CV content in Germany, so always include
them from the base documents regardless of job relevance.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 18:43:55 +02:00
thomasandClaude Opus 4.8 b1c92dd77b Guard against duplicate applications + capture jobs on any website
Duplicate safeguard: before creating an application (manual add and
browser import) the server checks for an existing one for the same job —
matched on a normalised source URL (job-id params like Indeed's jk pin
the posting across paths/tracking) or an identical company + role
(case/umlaut/whitespace-insensitive). On a match it returns 409 with the
matches; the web form and the extension show the existing entry and
re-submit with force=true only if the user confirms. Not a hard block, so
legitimate re-applications stay possible.

Universal capture: the extension popup becomes an editable capture form
that works on any site. It extracts the active page on demand (schema.org
JobPosting JSON-LD -> OpenGraph/meta -> h1/title/selection -> canonical
URL), lets the user review/correct, and sends. The import route is now
source-agnostic and derives the application source (art) from the URL
instead of hardcoding Indeed; the Indeed on-page button remains as a fast
path. Adds scripting/activeTab permissions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:32:17 +02:00
thomasandClaude Opus 4.8 d8b552e57d Ban hollow filler and self-labels in generated prose
The model produced awkward AI-tells like "Als Muttersprachler kommuniziere
ich serviceorientiert und klar - sowohl mit Anwendern als auch im Team".
Add an explicit style rule (system prompt, shared prose guidance, and the
AI-reply prompt) that forbids empty buzzword pairs and self-labels such as
"Als Muttersprachler ...", quoting the offending sentence as a negative
example. Language proficiency belongs only in the CV's Sprachen section;
every trait must come out of a concrete task or result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:11:31 +02:00
thomasandClaude Opus 4.8 4a896e72e3 Restyle cover letter to match the CV as one application set
Bring the Anschreiben into the résumé's visual language: navy name,
tracked role line, the shared hairline-plus-accent-bar rule under the
letterhead, and a navy bold subject line as the letter's headline. Keep
strict DIN-5008 structure (recipient block, right-aligned date, subject,
salutation/closing, signature space, enclosures) and near-black body for
readability.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:04:52 +02:00
thomasandClaude Opus 4.8 ddcf5edbc3 Restyle CV: navy accent system + competency-first structure
Rework the résumé as an HR-first document. Move Kernkompetenzen (renamed
from Fähigkeiten) directly under the Profil so the role-match is visible
in the first seconds, ahead of the reverse-chronological experience.

Introduce one restrained deep navy-slate accent used only for structure:
the name, section labels, a short accent bar starting each section rule,
and small square bullet markers — a coherent, precise motif. Body text
stays near-black for a crisp, ATS-safe read. Verified on the with-photo
and no-photo layouts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:58:22 +02:00
thomasandClaude Opus 4.8 05357acddb Fix monthly PDF export to include only the selected month
The month dropdown carried only the month (e.g. "07") while the year was
a separate select; the export required month AND year, so a month-only
selection fell through to exporting every application. Encode the year in
the month option value ("YYYY-MM"), parse it client-side to always send
the exact month+year, and harden /api/export so a month can never fall
through to "export all".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 16:39:46 +02:00
thomasandClaude Opus 4.8 0b44f61410 Add e-mail system: send applications, receive & reply to responses
Send the complete application (body + generated attachments) to a
user-entered address via authenticated SMTP submission through the
account's own server, so it applies DKIM and uses its reputable IP/PTR —
required for deliverability here (domain publishes SPF -all, DMARC
p=reject). From/Return-Path stay aligned on the sending domain.

Reply e-mails are polled over IMAP, parsed, stored and matched to the
right application (via In-Reply-To/References, then sender address);
their attachments are saved and downloadable. The detail page gains a
correspondence thread with compose, threaded reply, and an AI-drafted
reply the user can edit before sending.

New: lib/mailer.js (nodemailer + imapflow + mailparser), generateEmailReply
in lib/documents.js, emails/email_anhaenge/app_state tables, background
poller + manual fetch. Credentials come from MAIL_* env vars (.env, not
committed / not in the image).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:59:32 +02:00
thomasandClaude Opus 4.8 3c553cadf5 Generate an editable norm-compliant cover email per application
Alongside the tailored PDFs, the LLM now produces a short, DIN-style
Begleit-E-Mail (subject + body) grounded in the same data: same
salutation as the cover letter, the target role, a line or two of core
motivation, a pointer to the attached documents, and a friendly close.

Persisted in new bewerbungen columns (email_betreff, email_anschreiben)
and surfaced in an editable subject field + textarea on the detail page,
so the applicant can tweak it before sending; a copy button puts subject
and body on the clipboard. Values are stored raw and HTML-escaped on
render to avoid double-escaping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:20:56 +02:00
thomasandClaude Opus 4.8 0bfa5683fb CV: tuck Profil up and flow it around the photo
Drop the dead gap between the contact block and the first section: the
Profil heading and its opening lines now sit beside the photo's lower
flank (hairline stopping short of the photo) and spill to full width
once past the photo's bottom edge. No-photo layout unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:10:25 +02:00
thomasandClaude 5090dbbeb6 Anrede: keep full surname incl. particles (Al/El/van/von/de)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 14:52:31 +02:00
thomasandClaude 7d2640f5d5 CV: enlarge photo 20%, drop header rule above Profil
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 14:45:12 +02:00
thomasandClaude 990a663cdd Optional applicant photo in CV header
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 14:39:13 +02:00
thomasandClaude e914dee855 Anrede: last name only, strip first name (keep titles/particles)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 14:18:02 +02:00
thomasandClaude 86f94ce642 Embed Lato typeface for CV and cover letter (ATS-safe, German glyphs)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 13:50:39 +02:00
thomasandClaude 2a12ab54d0 CV date format MM/YYYY instead of MM.YYYY
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 13:22:50 +02:00
thomasandClaude 798abf67d5 Prompt: hard skills only; neutral unfinished-degree handling (no fake grade)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 13:11:06 +02:00
thomasandClaude 7d46032378 Redesign Lebenslauf: monochrome single-column HR layout
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:55:34 +02:00
thomasandClaude 6cf4fb58b0 CV: single-column layout, drop personal fields (Geburtsdatum/Führerschein/Schulbildung)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:38:24 +02:00
thomasandClaude a28018f31c CV: contact icons, larger meta text, removed heading rules
Draw small vector icons (phone, mail, pin, globe) in front of each
contact row instead of a pipe-separated single line; drop the title
highlight box and the per-section hair rules for a cleaner header.
Bump experience/education meta to 9.5pt ink and titles to 11.5pt bold,
and tighten section spacing.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 12:20:38 +02:00
thomasandClaude Opus 4.8 3f57474bd1 Redesign résumé: white single-page layout (centered header + two columns)
Replaces the navy-sidebar CV with a white layout modelled on the reference:
centered position title (soft highlight), name, contact row, a tailored Profil
section, then two columns — experience (with bullet points) and education on the
left; skills, languages, interests and personal details on the right.
Restores the profil field and switches experience back to bullet points.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:02:50 +02:00
thomasandClaude Opus 4.8 3fc8b76d39 Enlarge cover-letter signature by 20%
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:41:52 +02:00
thomasandClaude Opus 4.8 78128db422 Signature image in cover letter + fix per-paragraph capitalization
- Only the first body paragraph continues the salutation (lowercase safe
  opener); every following paragraph is a new sentence and is capitalized.
  Handled deterministically so it no longer depends on the model.
- Signature: upload a signature image on the Vorlagen page (preview / replace /
  remove). It is embedded in the cover letter directly under the closing
  salutation, replacing the typed name; falls back to the name when absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:37:03 +02:00
thomasandClaude Opus 4.8 cbdbbc07e3 Manual generation, LLM notes with company address, and static attachments
- Static extra attachments: upload files (e.g. Zeugnisse) once on the Vorlagen
  page; they are attached to every generated application and listed under
  "Anlagen" in the cover letter (via multer upload + basis_anhaenge table).
- Import no longer auto-generates: an imported job is saved as a draft
  ("nicht_gestartet"); generation is triggered manually on the application page.
- Per-application "LLM-Notizen" field: free text (company address, contact
  person, extra context) that is fed to the model at generation time. Saving the
  notes and (re)generating happens in one action.
- Cover letter recipient block is now a structured empfaenger (firma, address,
  city, contact person) the model fills from the job + notes; the salutation
  adapts to a named contact. Falls back to the imported company + city.
- Raise default OLLAMA_TIMEOUT_MS to 300s for slower cloud models.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:22:14 +02:00
thomasandClaude Opus 4.8 5f02413410 Norm-compliant CV education sections + redesigned monochrome cover letter
CV:
- Split education into proper German sections (Studium, Berufsausbildung,
  Schulbildung, Weiterbildungen) instead of one lumped "Ausbildung".
- Keep the timeline gap-free (lückenlos) and strip HR red-flag phrases
  ("nicht beendet", "abgebrochen", "arbeitslos", …) defensively.

Cover letter:
- Full refactor to a clean, single-column DIN-5008 business letter: letterhead
  (name left, contact right, hairline), recipient block, right-aligned date,
  bold subject, body, signature space, enclosure note ("Anlage: Lebenslauf").
- Monochrome (grayscale only), no accent colours; own single-page auto-fit.
- Prompt hardened against red flags (no salary, no reasons for leaving, no gaps).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:36:33 +02:00
thomasandClaude Opus 4.8 cd66e2ef4c Restyle PDFs as a two-column template (dark sidebar + main column)
- Fixed, high-quality CV/cover-letter template modelled on the user's reference:
  navy sidebar (name, headline, contact, skills with bullets, languages, hobbys)
  and a white main column with accent-underlined section headings and dated
  entries (date gutter, bold title, blue company line, description).
- The LLM now supplies only the text; layout/design is owned by the renderer.
- Extended the extracted fields with headline, birth date, driving licence and
  hobbys (grounded in the base documents, empty when absent).
- Two-column auto-fit keeps each document on a single A4 page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:13:17 +02:00
thomasandClaude Opus 4.8 27734c77e5 Redesign generated PDFs: structured AI output + single-page A4 layout
- AI now returns structured data (contact, cover-letter paragraphs, CV
  experience/education/skills/languages) instead of flowing text.
- Modern CV renderer: header with name + contact, tracked section titles with
  hairlines, entries with right-aligned dates and bullets, skill chips, languages.
- Clean DIN-style cover letter with matching header, recipient block,
  right-aligned date, subject, salutation, body, closing.
- Auto-fit engine scales fonts/spacing so each document always fits one A4 page.
- Tolerant parsing (key synonyms, object date ranges, markdown fences) + an
  explicit JSON skeleton in the prompt, since the model honours the schema loosely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 22:45:25 +02:00
thomasandClaude Opus 4.8 fd20ca0daf Add AI application assistant: Indeed import + Ollama document generation
- Browser extension (Chromium MV3) injecting a "send to tracker" button next
  to the Indeed job description; scrapes job info and posts it to a new
  /api/indeed-import endpoint (CORS-enabled), configurable tracker URL via popup.
- New "Entwurf" status. Imports create a draft and trigger background AI
  generation of tailored Anschreiben + Lebenslauf (PDF attachments) via the
  Ollama Cloud API, grounded strictly in user-provided base documents.
- Vorlagen page to manage base documents; attachments UI, generation status
  polling, regenerate and download routes on the application page.
- Schema: ort/stellenbeschreibung/quelle_url/generierung_* columns, plus
  basis_dokumente and anhaenge tables (with migrations).
- Config via .env (OLLAMA_API_KEY/OLLAMA_MODEL/OLLAMA_HOST); dependency-free
  .env loader. Dockerfile copies lib/, .dockerignore added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 22:21:28 +02:00
thomas 6952309de9 update 2026-06-19 04:32:02 +02:00
thomasandClaude Opus 4.8 c2a629e2c0 Refactor UI/views, rework Docker build, untrack local data
- Views umstrukturiert: einstellungen.ejs -> bewerbung.ejs, neues
  partials/head.ejs, header/footer/index angepasst
- CSS umbenannt: style.css -> styles.css
- server.js und public/js/main.js ueberarbeitet
- Dockerfile auf schlankes Multi-Stage-Setup umgestellt;
  docker-compose.yml und .dockerignore entfernt
- npm-Scripts docker:build/push/deploy ergaenzt
- SQLite-DB und .idea aus Git entfernt und via .gitignore ignoriert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 04:01:37 +02:00
thomasandClaude Sonnet 4.6 34cfcecc1e Add Docker setup with multi-stage build and Compose deploy config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 18:29:58 +02:00
thomasandClaude Sonnet 4.6 c65c9f1751 Initial commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 18:15:11 +02:00