/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>
- 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>
- 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>
- 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>