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>
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>
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>
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>
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>
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>
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>
- 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>
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>
Drei Punkte mit gestaffeltem Animation-Delay (klassische Tipp-Animation),
inkl. prefers-reduced-motion-Fallback (statisch).
Co-Authored-By: Claude <noreply@anthropic.com>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
"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>
/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>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>