Changelog

GitHub

Hver væsentlig ændring, udgivelse for udgivelse. Samme fil findes i repository'et og er oversat til alle 19 sprog.

[1.116.0] — 2026-07-06

Forbrugsmåler omarbejdet + første ende-til-ende widget-test. AI-forbrugsmåleren (v1.114.0) er rettet og fastgjort korrekt: den sidder nu fastgjort nederst i venstre sidebjælke (fuld sidebjælkebredde, samme overflade) og reserverer nederst plads svarende til sin egen højde, så menuen aldrig dækkes — nav + versionsfod ruller altid frit ovenover. Den opdateres live (hvert 15. sekund, ved fanefokus og ruteskift), og hver vinduesrække viser nu de rigtige <tokens> · <estimeret omkostning> (bjælker skaleres mod 30-dages-vinduet) i stedet for en altid-100% “andel”. Desuden: en holdbar typeof-barriere i CV-importøren lukker den tilbagevendende CodeQL type-confusion-falske positiv ved kilden, og en ny Playwright ende-til-ende-test kører begge vedvarende widgets i en rigtig browser.

  • public/js/lib/usage-hud.js + app.css, server/lib/cv-import.mjs. Tests: tests/playwright-widgets.mjs (2 E2E) + tests/usage-hud.test.mjs (10). Hjælp §6 udvidet ×16.

Nyt: intet.

[1.115.0] — 2026-07-06

Designfinpudsning (konservativ, koralbrand bevaret). En let forfinelsesrunde over det delte designsystem — ingen omstrukturering, ingen paletændring. Dashboardets metrikkort løfter sig nu og får en koralkant ved hover (som hurtighandlingsfliserne); indholdskort løfter sig en anelse; primary / dark / danger-knapper får en hvileskygge og et blidt hover-løft for dybde; store tal justeres via tabular-nums; og interaktive kontroller får en blød koral-fokusglorie bag den skarpe 2px-tastaturring. Al bevægelse respekterer prefers-reduced-motion, og glorien er afgrænset til kontroller — aldrig en global *:focus-visible.

  • Kun CSS (public/css/app.css); ingen ændringer i markup, i18n, ruter eller CSP. Tests: tests/design-polish-v1115.test.mjs (5). Verificeret live med Playwright.

Nyt: intet.

[1.114.0] — 2026-07-06

AI-forbrugs- og omkostningsmåler i sidebjælken (nederst til venstre). En kompakt FORBRUG-sektion sidder nu nederst i sidebjælken (et fast kort nederst til venstre hvis der ingen sidebjælke er; nederst til højre i RTL) på hver side. Den viser dit LLM-tokenforbrug over 24t / 7d / 30d vinduer — hvert som <tokens> · <andel%> med en grøn måler (andel af hele perioden) — plus en fod med den estimerede 24t-omkostning. Data er den skrivebeskyttede GET /api/usage-opsummering af data/llm-usage.jsonl (kun lokalt), samme kilde som #/usage-siden; omkostningen er et estimat, og manuelle kørsler er gratis og tælles ikke. Sammenfoldelig — overskriften skifter, og tilstanden bevares.

  • Ny klient-widget public/js/lib/usage-hud.js indlæst fra index.html, monteret i sidebjælken over versionsfoden (fast-hjørne-fallback). CSP-sikker; tema-bevidst + RTL-spejlet. Ingen ny serverrute. Tests: tests/usage-hud.test.mjs (8). 3 nye i18n-nøgler ×16.

Nyt: intet.

[1.113.0] — 2026-07-06

Flydende “Spørg hjælpen”-assistent på hver side. En gradient-robot-chatknap svæver nu i nederste højre hjørne (nederste venstre i RTL) på hver side. Tryk for at åbne en kompakt chat, der svarer på brugsspørgsmål udelukkende ud fra den indbyggede hjælpeguide på dit sprog — samme endpoint som #/docs-assistant-siden (POST /api/docs-assistant/ask), så den læser aldrig dit CV, din profil eller din tracker. Live med en LLM-nøgle; uden nøgle → en klar-til-brug prompt. Overskriften viser en robot-avatar + onlinestatus; chips foreslår almindelige spørgsmål; Esc eller klik udenfor lukker; den skjuler sig på #/docs-assistant-siden.

  • Ny klient-widget public/js/lib/docs-fab.js monteret globalt fra index.html; CSP-sikker; tema-bevidste + RTL-spejlede stilarter i app.css. Ingen ny serverrute. Tests: tests/docs-fab.test.mjs (8). 6 nye i18n-nøgler ×16. Hjælp §1 udvidet på stedet.

Nyt: intet.

[1.112.0] — 2026-07-06

Docs- og QA-konsolidering. Ingen brugersynlig kodeændring. SDD-konventionsdokumentet (docs/sdd/CONVENTIONS.md) er opdateret til de nuværende 30 rute-moduler (var 24) og den nuværende testbaseline; den definitive projektomfattende QA-prompt (qa/QA-REGRESSION-PROMPT.md) er konsolideret — release-mekanik afstøvet (v1.111, parentVersion 1.17.0, release-udløst udgivelse), §14-tilføjelsestabellen rettet (Scan Udeluk om-mærket v1.109.0) og udvidet med v1.111-CodeQL-lukningen — så den står alene som den ene regressionsprompt for al funktionalitet. Tilføjer én dækningstest for den overstore upload-gren.

Nyt: intet.

[1.111.0] — 2026-07-06

Sikkerhed — lukning af CodeQL-backlog. Tre defense-in-depth-hærdninger, der lukker de resterende fund fra statisk analyse ved kilden i stedet for at afvise dem. stripDangerousMarkdown escaper nu < i enhver afkortet farlig tag-åbning (en payload, der ender på <script/<iframe/…), så dens output beviseligt ikke indeholder nogen levende farlig tag. CV-import læser den uploadede buffers størrelse via en eksplicit Number()-tvang — en barriere mod typeforvirring. Modus-rollelinjer er nu skabelon-strenge interpoleret med String.replace i stedet for gemte funktioner, hvilket helt fjerner det dynamiske dispatch-kald. Ingen brugersynlig adfærdsændring.

  • server/lib/security.mjs, server/lib/cv-import.mjs, server/lib/prompts.mjs. Tests: tests/security-hardening-v1111.test.mjs (7) + opdateret v1108-vagttest. Ingen i18n-/hjælpe-/rute-ændringer.

Nyt: intet.

[1.110.0] — 2026-07-06

Docs- & QA-opdatering (alle sprog). Ingen kodeændring. Hele-projekt-QA-prompten er opdateret til v1.109.0 med et nyt §14 (v1.98→v1.109), og de vedvarende UX-audit- og design-export-prompts fik den aktuelle sideflade. Hvert hjælpeafsnit tilføjet i v1.100–v1.109 er nu oversat til alle 16 sprog.

Nyt: intet.

[1.109.0] — 2026-07-06

Scan Udeluk-filter + pipeline-overblik (web-layout-paritet). På #/scan behandler Søg-feltet nu kommaer som ELLER (“roller at finde”), og et nyt Udeluk-felt skjuler enhver række, hvis firma/rolle/lokation indeholder et af de kommaseparerede ord (f.eks. senior, staff); begge huskes af dine gemte søgninger. På #/pipeline viser en kompakt overbliksstribe din pipeline med ét blik — N i indbakke, N sporet og antallene Applied / Responded / Interview / Offer fra trackeren, hver chip linker til #/tracker.

  • Kun klient (ingen ny rute/skrivninger). public/js/views/scan.js + public/js/views/pipeline.js. Tests: tests/scan-pipeline-ui-v1109.test.mjs (2). 4 nye i18n-nøgler ×16. Hjælp §7 + §8 udvidet på stedet.

Nyt: intet.

[1.108.0] — 2026-07-06

Sikkerhedshærdning (CodeQL-triage, runde 2). Tre yderligere lavseverititets-fund rettet: prompt-byggeren opslår locale-rollelinjen via egen nøgle + typeof === function, så en manipuleret locale ikke kan dispatche til en prototype-metode (unvalidated-dynamic-method-call); PDF-filnavns-sluggen begrænses til 200 tegn før regex’en, så et input med kun bindestreger ikke backtracker (polynomiel ReDoS); og dokumentimport tvinger et array-filename (gentaget header) til en streng (type-confusion). Ingen adfærdsændring for gyldigt input.

  • server/lib/prompts.mjs, server/lib/routes/runners.mjs, server/lib/cv-import.mjs + tests/security-hardening-v1108.test.mjs (3). Over v1.106–v1.108 gik den statiske analyses efterslæb fra 167 til ~14, hvor hvert reelt sikkerhedsrelevant fund blev rettet og resten (værnede/rensede falske positiver + note-niveau-lint) afvist med begrundelse.

Nyt: intet.

[1.107.0] — 2026-07-06

Sanitizer-hærdning (XSS-forsvar i dybden i hviletilstand). stripDangerousMarkdown — som neutraliserer farlig HTML i gemt CV/job-markdown, så enhver forbruger, der omgår escape-ved-rendering-klienten, stadig er sikker — kører nu sin tag-strip til et fikspunkt (gentag til stabil), så en fjernelse, der gendanner en payload (f.eks. <scr<script></script>ipt>), fanges, matcher script/style-slutmærker med efterfølgende skrald (</script foo>) og fjerner en uafsluttet eksekverbar åbner (<script …>). Adfærd for gyldig markdown er uændret — den fjerner kun mere.

  • server/lib/security.mjs: fikspunkt-løkke (begrænset til 8 gennemløb) + [^>]*>-slutmærke-mønstre + fjernelse af uafsluttet åbner. +3 regressionstilfælde i tests/cv-xss-bypasses.test.mjs. Den autoritative XSS-grænse er fortsat output-escaping (UI.md); dette styrker hvile-garantien og lukker de tilsvarende CodeQL-fund.

Nyt: intet.

[1.106.0] — 2026-07-06

Sikkerhedshærdning (CodeQL-triage). Tre reelle (om end lavseverititets) fund rettet efter en gennemgang af den statiske analyses efterslæb: rute-renderingens fejlsti escaper nu fejlmeddelelsen, før den når DOM’en (en serverfejl kan gengive brugerinput, så den behandles som utroværdig — XSS-grænse), og profil/config-egenskabsskrivninger afviser __proto__ / constructor / prototype-nøgler (prototype-forureningsværn for en sikkerheds skyld — nøglerne kommer fra faste feltspecifikationer, ikke rå request-input). Størstedelen af de resterende advarsler er falske positiver på scannerens legitime data/*-læsninger/-skrivninger og på ruter, der allerede har den egne rate-limiter; afvist med begrundelse.

  • public/js/router.js escaper err.message via UI.escapeHtml før innerHTML; server/lib/routes/content.mjs og server/lib/routes/config.mjs værner prototype-nøgler. Ingen adfærdsændring for gyldigt input. Tests: tests/security-hardening-v1106.test.mjs (3). Ingen nye i18n-nøgler.

Nyt: intet.

[1.105.0] — 2026-07-06

AI-forbrug og -omkostningsside. En ny AI-forbrug-side (sidebjælke, ved siden af Helbred) viser, hvor mange tokens du har brugt på live AI-genereringer — evalueringer, rapporter, chats — opdelt pr. udbyder over de sidste 24 timer, 7 dage, 30 dage og al tid, med en estimeret USD-omkostning. Hvert live-kald tilføjer en lille {provider, in, out}-post til data/llm-usage.jsonl (intet sendes nogen steder hen); kørsler uden nøgle (manuel tilstand) koster intet og registreres ikke.

  • Nyt rutemodul (det 30.) server/lib/routes/usage.mjs — GET /api/usage (skrivebeskyttede opsummeringer) + server/lib/llm-usage.mjs (recordUsage normaliserer Anthropic/OpenAI/Gemini-forbrugsformerne og tilføjer best-effort; readUsage/aggregate opsummerer pr. vindue 24t/7d/30d/alt × udbyder) + server/lib/llm-pricing.mjs (en redigerbar pris-tabel pr. udbyder $/1M tokens — tokens er præcise, dollars er omtrentlige listepriser, du kan rette; faktureres aldrig). Registreringen er koblet på dispatch-punkterne (runActiveProvider + routes/llm.mjs).
  • Ny visning public/js/views/usage.js (#/usage, vinduesfaner). Tests: tests/usage-routes.test.mjs. 17 nye i18n-nøgler ×16 (usage.* + nav.usage). Hjælp §6 udvidet på stedet.

Nyt: server/lib/routes/usage.mjs; server/lib/llm-usage.mjs; server/lib/llm-pricing.mjs; public/js/views/usage.js.

[1.104.0] — 2026-07-06

Firmalogoer i scanningstabellen (privatlivsbevarende). En ny Udseende-kontakt i App-indstillinger — Vis firmalogoer i scanningstabellen (fra som standard) — tegner hvert firmas logo ved siden af navnet på #/scan. Logoet er firmaets favicon hentet fra dets eget domæne og proxyet på serveren (GET /api/logo), så ingen tredjeparts logotjeneste får at vide, hvilke arbejdsgivere du kigger på. Opslag på en delt jobportal (Greenhouse, Lever, Ashby, …) viser et farvet bogstavmærke i stedet for portalens ikon, og ethvert logo, der ikke kan indlæses, falder tilbage til samme mærke.

  • Nyt rutemodul (det 29.) server/lib/routes/logos.mjs — GET /api/logo?domain=. Det validerer domænet (ingen skema/sti/loopback), henter /favicon.ico via den SSRF-sikre safeGet (en ny binary-tilstand returnerer de rå bytes + content-type; DNS-pinning, redirect-validering og størrelsesloftet er uændrede), udfører billed-magic-sniffing, så en HTML-fejlside aldrig serveres som et billede, cacher hits og misses i en in-memory LRU og skriver intet til disk.
  • Nyt klientbibliotek public/js/lib/company-logo.js (window.CompanyLogo): fra som standard via et localStorage-flag; springer delte ATS-værter over til fordel for en deterministisk bogstav-avatar; CSP-sikker img.onerror-fallback. Tests: tests/logo-routes.test.mjs. 5 nye i18n-nøgler ×16 (appear.*). Hjælp §2 udvidet på stedet.

Nyt: server/lib/routes/logos.mjs; public/js/lib/company-logo.js.

[1.103.0] — 2026-07-06

Indstillinger: “AI CLI-værktøjer” — hvilke er installeret. career-ops er Claude Code-drevet, men virker med enhver agent-CLI på den åbne skill-standard. En ny AI CLI-værktøjer-fane i App-indstillinger (#/config) viser hvilke — Claude Code, Codex, Gemini CLI, OpenCode, GitHub Copilot CLI, Qwen, Antigravity — der er installeret på maskinen, der kører serveren, og deres stier. Det er en skrivebeskyttet PATH-scanning: den tjekker kun, om hver binær findes, og kører den aldrig (ingen --version, ingen kørsel), skriver intet og rører ingen brugerdata.

  • Nyt rutemodul (det 28.) server/lib/routes/cli-detect.mjs — GET /api/cli-detect. Registreringen slår en binærs sti op fra en fast 7-punkts allowlist via process.env.PATH (.cmd/.exe/.bat-shims på Windows; execute-bit på POSIX); en fjendtlig fil på PATH kan aldrig køres af denne rute.
  • Ny “AI CLI-værktøjer”-fane i public/js/views/config.js (lazy-loaded, dyb-linkbar via #/config?tab=cli). Tests: tests/cli-detect-routes.test.mjs. 8 nye i18n-nøgler ×16 (cli.* + config.tabCli). Hjælp §2 udvidet på stedet.

Nyt: server/lib/routes/cli-detect.mjs.

[1.102.0] — 2026-07-05

“Spørg guiden” — en forankret chat over den indbyggede hjælpeguide. En ny Spørg guiden 💬-side (sidebjælke, under Hjælp): skriv et spørgsmål som “Hvordan scanner jeg jobportaler?” og få et svar hentet kun fra appens egen hjælpeguide på dit sprog — den viser hvilke afsnit den brugte og læser aldrig dit CV, din profil eller din jobsøgning. Det handler om at bruge appen, ikke om dig. Med en LLM-nøgle svarer den live; uden nøgle giver den dig en klar-til-brug prompt, allerede udfyldt med de relevante hjælpeafsnit.

  • Nyt rutemodul (det 27.) server/lib/routes/docs-assistant.mjs — POST /api/docs-assistant/ask. Afhængighedsfri hentning: hjælpedokumentet på dit sprog opdeles i sine ##-afsnit og scores efter nøgleordsoverlap med dit spørgsmål; de bedste indlejres, og modellen skal svare ud fra dem eller sige, at guiden ikke dækker det (ingen opdigtede funktioner/ruter). Delt udbyderkaskade, manuel fallback, rate-begrænset, ingen skrivninger, læser ingen brugerdata.
  • Ny visning public/js/views/docs-assistant.js. Tests: tests/docs-assistant-routes.test.mjs. 14 nye i18n-nøgler ×16 (docs.* + nav.docsAssistant). Hjælp §1 udvidet på stedet.

Nyt: server/lib/routes/docs-assistant.mjs; public/js/views/docs-assistant.js.

[1.101.0] — 2026-07-05

CV Studio: tilpas dit CV + skriv en ansøgning til et bestemt job, gennem en rekrutterings-checkliste. Nyt Tilpas til et job-kort på #/cv-studio: indsæt en jobbeskrivelse (og evt. en mål-rolle/overskrift), og CV Studio laver et CV tilpasset det opslag plus en matchende ansøgning, og kører dem derefter gennem en checkliste-gate, før de afleveres — error blokerer (rettes, før du ser resultatet), warn rådgiver. Mekanikken er destilleret fra karrierecoaching-praksis til generiske regler — en rekrutter læser på sekunder, så relevant erfaring kommer øverst, overskriften matcher stillingens rolle, resultater bærer konkrete tal, og ansøgningen forbliver en kort teaser med én “krav ↔ din matchende kendsgerning”-bro. Den bygger kun på dit eget CV, din profil og din two-pager og opdigter aldrig — ingen hardkodede virksomheder, roller eller historik.

  • Nyt endpoint POST /api/cv-studio/tailor (udvider det eksisterende cv-studio-modul — intet 27. modul): buildTailorPrompt + en generisk TAILOR_INSTRUCTIONS-gate, bygget på bundleProjectContext, delt udbyderkaskade, manuel fallback uden nøgle, rate-begrænset, ingen skrivninger. Resultatet eksporteres til Markdown / PDF / DOCX via den delte report-export.js-bjælke.
  • Tests: +3 i tests/cv-studio-routes.test.mjs. 10 nye i18n-nøgler ×16 (cvs.tailor*). Generisk reference docs/prompts/resume-cover.md. Hjælp §24 udvidet på stedet.

Nyt: docs/prompts/resume-cover.md.

[1.100.0] — 2026-07-05

Two-pager: AI-autoudfyldning fra dit CV + Forhåndsvisning + eksport til PDF/DOCX/Markdown. Two-pageren (#/two-pager) fanger, hvad du faktisk vil have af din næste rolle, men før skulle hvert felt skrives i hånden eller en prompt kopieres til et andet værktøj. Nu kører ✨ AI-udfyldningsassistenten live mod din konfigurerede udbyder — den læser kun dit CV + profil (via bundleProjectContext, intet opdigtet), udarbejder alle felter (hvem jeg er / kan lide / skal-haves / hader / dealbreakers / ufravigelige / målmiljø) og udfylder formularen, så du kan gennemgå, redigere og gemme. Uden API-nøgle falder den tilbage til kopiér-prompt-modalen som før. En ny 👁 Forhåndsvis og eksportér-knap gengiver two-pageren som et formateret dokument med en Download .md / Gem som PDF / Gem som DOCX / Kopiér-bjælke.

  • Afhængighedsfri .docx-eksport. Ny server/lib/docx.mjs udsender en minimal, men gyldig Office Open XML-.docx (en DEFLATE-ZIP af de fire OOXML-dele, CRC-32 pr. post) — ingen ny runtime-afhængighed (deps forbliver express + js-yaml). Ny rute POST /api/export/docx (server/lib/routes/export.mjs, det 26. rutemodul; tilstandsløs, begrænset til 200 KB, ingen skrivninger / ingen LLM / ingen URL-fetch). Koblet ind i den delte public/js/lib/report-export.js, så markedsrapporten, karriereplanen og karriereorienteringen også får DOCX-eksport.
  • Live-autoudfyldning bruger den delte udbyderkaskade (runActiveProvider / providerAvailable); den returnerede YAML parses og tvinges tilbage til den afgrænsede two-pager-form (parseYamlFields + normalizeTwoPager) — ukendte nøgler droppes, arrays/strenge begrænses. Manuel tilstand bevaret.
  • Tests: tests/export-routes.test.mjs. 4 nye i18n-nøgler ×16 (export.saveDocx, twoPager.preview, twoPager.aiFilling, twoPager.aiFilled).

Nyt: server/lib/docx.mjs; server/lib/routes/export.mjs.

[1.99.0] — 2026-07-05

Portalsundhedsside (#/portals). Scanneren holder øje med en række virksomheder i portals.yml; et ATS-slug kan gå i stykker i stilhed, og den arbejdsgiver forsvinder fra alle fremtidige scanninger. Den nye Portals-side viser hver overvåget virksomhed og prober ved Check portal health hver careers_url gennem det DNS-fastgjorte safeGet (SSRF-sikkert) og markerer de døde (et 404 = droppet i stilhed) — skrivebeskyttet. Hærder også v1.98.0-fejlrapportøren efter review: fejl-ringbufferen fanger nu netværks-fetch-fejl, og scrubberen skjuler umærkede udbydernøgler.

Nyt: server/lib/routes/portals.mjs; public/js/views/portals.js.

[1.98.0] — 2026-07-05

Indbygget fejlrapportør (paritet med moderprojektets web-v0.2.0-web). En 🐞 Report a bug-knap i notifikationsskuffen samler et privatlivsbegrænset diagnostisk øjebliksbillede — versioner, din skærm, browser, et /api/health-tjekresumé og de sidste 20 fejl fra en ny klientside-ringbuffer — plus et deterministisk dedupe-fingeraftryk (co-web-<base36>), lader dig gennemse den præcise Markdown og åbner derefter en forududfyldt GitHub-issue. Intet indsendes automatisk; det bærer aldrig dit CV, din profil, svar, job-URLer eller nøgler. Nye libs logbuf.js + bug-report.js; 11 i18n-nøgler ×16; tests/bug-report.test.mjs.

Nyt: public/js/lib/logbuf.js; public/js/lib/bug-report.js.

[1.97.1] — 2026-07-05

Gennemgangsdrevet hærdning & dokumentationsparitet (opfølgning på v1.97.0). En gennemgang af AI-review-loggene afdækkede reelle rettelser:

  • fit-score.js (scan-◎-fit-badge). salaryFloor() ophøjer ikke længere en sats under årsniveau til et falsk årligt gulv — “at least 500 EUR/day”, “$80/hr”, “6000 monthly” returnerer nu null i stedet for en 500k/80k-dealbreaker. Landematchning sker nu på hele ord (\b…\b), så “Germany” ikke længere matcher tillægsordet “German” (ej heller “Nigeria” inde i “Nigerian”) og udløser en falsk skal-haves-andetsteds-overtrædelse. +3 tests i tests/fit-score.test.mjs.
  • Dokumentationsparitet. Hver lokaliseret README averterer nu konsekvent 16 lokaliteter — Help-rækkens tælling/liste (×13) og Lokaliserings-sektionens tekst plus “tilføj nøglen til alle N filer”-noten (×8) sad stadig på tallene fra før v1.85 (8/9). Den in-app hjælps §17-adaptertælling er rettet til 46 adaptere — 41 engelske + 5 russiske på tværs af alle 16 bundter.

Ingen adfærdsændring ud over fit-badge-heuristikken; ingen nye ruter, nøgler eller i18n-tilføjelser.

[1.97.0] — 2026-07-05

Dassault Systèmes-scannerkilde + en trefronts kvalitetsgennemgang.

  • Ny scannerkilde — Dassault Systèmes (paritet med forælderens career-ops, #1498). server/lib/sources/dassault.mjs + server/lib/portals/adapters/dassault.mjs spejler forælderens zero-token Exalead-“card search”-udbyder (det offentlige feed bag 3ds.com/careers/jobs). Det er ét enkelt globalt endpoint, så det vælges via udbyder (provider: dassault) eller autodetekteres fra en 3ds.com-vært, SSRF-værtsfastgjort til www.3ds.com med redirect:'error'. XML’en parses uden en DOM (<Meta>-maps pr. <Hit>), by/land trækkes fra den lokaliserede kategoristreng, og opslag beholdes kun, når deres offentlige URL er på *.3ds.com. Registret leverer nu 46 adaptere (41 EN + 5 RU); ALL_ADAPTERS-tælling, sorteret-id- og /api/scan/sources EN-sæt-assertions hævet 40 → 41. Suite tests/sources-dassault.test.mjs (10 tilfælde).
  • Overførte robusthedsrettelser fra forælderen. Avature-parseren tolererer nu to live tenant-markup-varianter (article--result med et positionsindeks-suffiks + et klasseløst JobDetail-titelanker, #1541); Get on Board værner mod en 0/negativ published_at (ikke flere fejlagtige 1970-datoer); SuccessFactors begrænser den sidste side, så den ikke kan overskride MAX_JOBS (#1528).
  • Serveraudit-rettelser. safe-fetch hænger ikke længere på et svar over grænsen — størrelsesgrænse-stien afgør nu løftet direkte i stedet for at vente på en 'end'-hændelse, som en ødelagt stream aldrig udsender (retter store-side /api/pipeline/preview + auto-pipeline-fetches). SSE stream.*-aktivitetslogning er nåbar igen (/api/stream/-tjekket blev flyttet over den generelle “spring GET over”-værn).
  • SPA-audit-rettelser. #/stats-fanevælgeren værner mod et asynkront render-kapløb — en langsom fanes resultat kan ikke længere overskrive en nyere fane, som brugeren allerede har skiftet til. Sletbekræftelserne i mock interview og networking sender nu en korrekt titel + brødtekst (ikke flere dialoger med tom brødtekst).
  • Oversættelsesrettelser. Uoversatte ordbogsværdier rettet — ukrainsk config.modes* (Adaptive Framing / Exit Narrative / Location Policy), russisk eval.jdLbl (“Job Description”), italiensk dash.quick.contactoSub (“referral” → “segnalazione”) — plus den engelske 16 lokaliteter-standardtekst lokaliseret i ru/uk/ja/ko/zh-CN/zh-TW-CHANGELOG’erne.

Nyt: server/lib/sources/dassault.mjs; server/lib/portals/adapters/dassault.mjs.

[1.96.0] — 2026-07-04

Karriereorientering (Epic 27). En ny side #/orientation besvarer “hvilke retninger passer faktisk til mig?” — den læsning, du ville få af en erhvervstest, men udledt af dit eget CV og din profil frem for et spørgeskema. Klik på Generér profil, og modellen returnerer dine bedst passende karrierevektorer (hvilke af de otte arketyper — Funktionalist, Administrator, Kommunikator, Specialist, Analytiker, Innovator, Leder, Iværksætter — der passer, med belæg), en karrieretype-tendens, anbefalede roller, professionelle styrker knyttet til dit CV, arbejdsstilstendenser og udviklingsanbefalinger. Den er en AI-refleksion af, hvordan dit CV læses — ikke en psykometrisk test: den opfinder aldrig resultater og rapporterer aldrig numeriske scorer, som om de var målt. Eksportér den til Markdown eller PDF; intet skrives til disk.

  • Ny rute server/lib/routes/orientation.mjs (24. rutemodul) — POST /api/orientation/generate bygger profil-prompten ud fra CV+profil+two-pager+hukommelse via den delte udbyderkaskade, med en kopiér-indsæt-manuel-fallback og ingen filskrivninger.
  • Genbruger report-export.js til Markdown/PDF/kopiér, under Vækst-navigationsgruppen.
  • Tests: tests/orientation-routes.test.mjs (refleksionsramme / ingen fabrikerede scorer, CV/profil-seedet manuel tilstand). 7 nye i18n-nøgler ×16 locales, Hjælp §28 ×16.

Nyt: #/orientation; server/lib/routes/orientation.mjs.

[1.95.0] — 2026-07-04

Karriereplan (Epic 26). En ny side #/career-plan forvandler dit CV og din profil til en konkret, personlig udviklingsplan. Vælg en horisont (6/12/24 måneder) og et valgfrit fokus, og modellen — der læser dit CV, din profil, din to-pager og din hukommelsesnote — skriver et udgangspunkts-snapshot, en styrke/vækst-SWOT, mål som SMART / OKR / WOOP, alternative udviklingsspor, en plan for hårde/bløde kompetencer, et måned-for-måned-vejkort, metoder til at følge fremskridt, faldgruber og støttende tiltag. Den planlægger fremad ud fra det, dine materialer faktisk viser, og opfinder aldrig fakta om din historik. Redigér den inline, Gem den til brugerlaget (config/career-plan.md), og eksportér den til Markdown eller PDF.

  • Ny rute server/lib/routes/career-plan.mjs (23. rutemodul) — GET/PUT /api/career-plan (skriver config/career-plan.md) + POST /api/career-plan/generate (den delte udbyderkaskade, manuel fallback, ingen fabrikation). PATHS.careerPlan.
  • Genbruger den delte report-export.js (v1.94.0) til Markdown/PDF/kopiér, og en ny Vækst-navigationsgruppe.
  • Tests: tests/career-plan-routes.test.mjs (afgrænsning, GET/PUT-rundtur, horisont-bevidst CV/profil-seedet prompt). 20 nye i18n-nøgler ×16 locales, Hjælp §27 ×16.

Nyt: #/career-plan; server/lib/routes/career-plan.mjs; PATHS.careerPlan.

[1.94.0] — 2026-07-04

Statistik, omarbejdet (Epic 25). Siden #/stats er nu en Statistik-sektion med tre faner, med rigtige grafer og langt flere data. En ny fane Markedsrapport beder modellen om en løn- og arbejdsmarkedsanalyse af dine målroller i en region og valuta, du vælger — ledelsesresumé, løn efter niveau med P10/P25/P75/P90-percentiler, de største arbejdsgivere, en tabel over efterspurgte kompetencer, frekvens af personalegoder, fordelingen kontor/hybrid/fjernarbejde, tendenser over 12–24 måneder og vejledning til forhandling. Hvert tal er mærket som et retningsgivende estimat fra modellens viden, aldrig præsenteret som skrabede data. En ny fane Min pipeline grafer din egen tracker: scorefordeling, statustragt, de største virksomheder og roller, ansøgninger over tid og konverteringsrater. Den oprindelige målrollevisning (vakance/løn efter land + gemt snapshot-tendens) flytter ind under en tredje fane, nu med en valgvælger til valuta og et overblik over opslag-efter-rolle.

  • Eksportér enhver rapport til Markdown eller PDF, eller kopiér den — via den delte hjælper report-export.js (Markdown-blob-download; PDF gennem den eksisterende inline-PDF-runner).
  • Ny rute server/lib/routes/market.mjs (22. rutemodul) — POST /api/stats/market bygger en markedsanalyse-prompt ud fra dit CV/din profil (så den kender dine målroller), region og valuta, kører den gennem den delte udbyderkaskade og falder tilbage til en kopiér-og-indsæt-prompt uden nøgle. Ingen filskrivninger.
  • Tests: tests/market-routes.test.mjs (region/valuta-afgrænsning, ærligheds-mærket prompt, CV/profil-seedet manuel tilstand). 36 nye i18n-nøgler ×16 locales, Hjælp §26 ×16.

Nyt: #/stats omarbejdet til faner; server/lib/routes/market.mjs; public/js/lib/report-export.js.

[1.93.0] — 2026-07-04

Hukommelseslag (Epic 24). En ny #/memory-side rummer en kort, redigerbar “husk dette om mig”-note, som assistenten holder sig for øje ved hver opgave:

  • Én note, overalt — fordi den indlejres i bundleProjectContext, når noten automatisk frem til hver eneste AI-forespørgsel (evaluering, jobsamtale-øvelse, networking, CV Studio) på tværs af alle udbydere. Skriv den én gang; den styrer alt.
  • Styring, ikke fakta — den indfanger dine præferencer og hvordan du kan lide at arbejde (tone, format, deal-breakers, kadence), aldrig nye faktuelle påstande om din erfaring — dem lever der stadig kun i dit CV, din profil og din two-pager. Gemmes i brugerlaget i config/memory.md og overskrives aldrig af opdateringer.
  • Foreslå ud fra dine data — POST /api/memory/suggest udvinder adfærdsmønstre fra din egen ansøgnings-tracker og udkaster punkter, du kan gennemse og redigere. Den læser din tracker; den opdigter aldrig fakta og foretager intet live-kald.

Nyt: server/lib/routes/memory.mjs (21. rutemodul — GET/PUT /api/memory + POST /api/memory/suggest), public/js/views/memory.js, PATHS.memory og en config/memory.md-blok tilføjet til bundleProjectContext. 11 nye i18n-nøgler på tværs af alle 16 locales. Tests: tests/memory-routes.test.mjs.

[1.92.0] — 2026-07-04

CV Studio (Epic 21). En ny #/cv-studio-side giver dit CV tre ærlige, overvejende lokale værktøjer:

  • CV-diagnostik — en deterministisk 0–100-score med forklaringer pr. tjek (kvantificeret effekt, svage verber, buzzwords, længde, kerneafsnit, kontaktoplysninger). Rent på klientsiden (window.CvDiagnostics) — ingen LLM, intet opdigtet, hvert fund forklaret, så du afgør, hvad der skal ændres.
  • Privatlivsmaske — sløring af PII (e-mail, telefon, links/handles, gadeadresse og valgfrit dit navn → initialer), før du deler dit CV som en prøve eller et skærmbillede. Kører fuldstændigt i browseren (window.CvPrivacy); den rapporterer nøjagtigt, hvad den slørede, og gemmer aldrig originalen.
  • Make it human / stemmematch — indsæt en stiv linje eller et afsnit, og skriv den om i din stemme, forankret på serversiden i voice-dna.md og writing-samples/. Hårdt værn: den må omrokere, stramme og genstemme, men aldrig introducere et faktum, en metrik eller en præstation, der ikke allerede står i teksten. Kører live gennem den delte udbyder-kaskade eller giver et copy-paste-prompt tilbage uden nøgle.

Nyt: server/lib/routes/cv-studio.mjs (20. rutemodul — POST /api/cv-studio/humanize), public/js/views/cv-studio.js, public/js/lib/cv-diagnostics.js, public/js/lib/cv-privacy.js, PATHS.voiceDna + PATHS.writingSamplesDir. 29 nye i18n-nøgler på tværs af alle 16 locales. Tests: tests/cv-diagnostics.test.mjs, tests/cv-studio-routes.test.mjs. (Skabelongalleri, Word-eksport og opslags-PDF-arkiv spores som opfølgende CV Studio-arbejde.)

[1.91.0] — 2026-07-04

Networking og dybdegående virksomhedsresearch (Epic 16). En ny #/networking-side forvandler en virksomhed til en handlingsorienteret plan for at få en samtale, forankret i dit CV, din profil og din two-pager:

  • Virksomhedsdossier — et stramt brief om, hvad virksomheden laver, nylige signaler værd at citere, og “hvorfor jeg passer”-kroge trukket fra din reelle baggrund.
  • Hvem du skal kontakte — 3–5 målpersonaer (hiring manager, intern rekrutterer, en senior IC på teamet, en varm/alumne-forbindelse) med en konkret LinkedIn-søgestreng til at finde hver enkelt. Den opdigter aldrig rigtige navne.
  • Den varmeste introvej — den mest realistiske varme indgang for din baggrund (fælles arbejdsgiver/skole/fællesskab, en andengrads-vej eller en kold DM med højt signal) og hvorfor.
  • Kontaktudkast — korte, specifikke beskeder til de vigtigste personaer, forankret i dine reelle bevispunkter.
  • Live eller manuel — kører live gennem den delte udbyder-kaskade med en hvilken som helst nøgle, eller returnerer et copy-paste-prompt (ærlig fallback, intet opdigtet). Gem plan bevarer en færdig plan i brugerlaget (networking/net-{company}-{role}-{date}.md); siden viser, åbner og sletter gemte planer.

Nyt: server/lib/routes/networking.mjs (19. rutemodul), public/js/views/networking.js, PATHS.networkingDir. Genbruger server/lib/llm-dispatch.mjs-kaskaden fra v1.90.0. 24 nye i18n-nøgler på tværs af alle 16 locales. Tests: tests/networking-routes.test.mjs.

[1.90.0] — 2026-07-04

Mock Interview 2.0 (Epic 15). En ny #/mock-interview-side forvandler dit CV, din profil, din two-pager og din historiebank til en tur-for-tur jobsamtaleøvelse:

  • Samtalebaseret øvelse — angiv en målrolle (+ valgfri virksomhed / JD), og intervieweren åbner med et fokuseret spørgsmål. Hvert svar, du sender, får et struktureret svar tilbage: Feedback (styrker + STAR+R-hullet), en Score (N/5) og et Next question, der undersøger den svageste del af dit sidste svar. Forankret på serversiden i dine faktiske materialer — det opdigter aldrig erfaring, du ikke har.
  • Historiebank-bevidst — interview-prep/story-bank.md indlejres i prompten (samme tillidsniveau som cv.md), så feedbacken kan pege dig hen på dine egne bedste historier.
  • Live eller manuel — med en udbydernøgle kører turen live gennem den delte udbyderkaskade (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models); uden nøgle får du en klar-til-brug prompt til at kopiere og indsætte (ærlig fallback, ingen opdigtede svar).
  • Gemte sessioner — klik på Save transcript for at gemme en færdig samtale i brugerlaget (interview-prep/mock-{company}-{role}-{date}.md); siden viser, åbner og sletter gemte sessioner.

Nyt: server/lib/routes/interview.mjs (18. rutemodul), public/js/views/mock-interview.js, server/lib/llm-dispatch.mjs (delt udbyderkaskade), PATHS.storyBank, bundleProjectContext({ extraFiles }). 30 nye i18n-nøgler på tværs af alle 16 locales. Tests: tests/interview-routes.test.mjs.

[1.89.0] — 2026-07-04

Kandidat-marked-fit — the two-pager (Epic 14). En ny #/two-pager-side lader dig indfange, hvad du faktisk vil have af din næste rolle, modelleret efter “Mnookin two-pager” fra Never Search Alone:

  • Guidet builder — en førstepersons “Hvem jeg er”-fortælling, en “Målmiljø”-note og fem chip-liste-editorer: loves, must-haves, hates, deal-breakers og non-negotiables. Gemmes i forældreprojektets brugerlag (config/two-pager.yml) via PUT /api/two-pager — overskrives aldrig af systemopdateringer.
  • AI-udfyldningsassistent (POST /api/two-pager/draft) — bygger en klar-til-kørsel Mnookin-prompt med dit CV + profil indlejret, som du kan køre i en hvilken som helst LLM og indsætte resultatet tilbage. Den bruger kun dit eget materiale; intet opdigtes.
  • Fit-til-det-du-vil-have-badge — hvert opslag på #/scan viser nu en ◎ N fit-score (klientside, via window.FitScore), der holder opslagets arbejdstype, land, lønbund og relokation op mod din two-pager. Ærlig af design: når et opslag ikke giver noget matchbart signal, vises intet badge (aldrig et opdigtet tal). Deal-breaker-overtrædelser vejer tungere end bløde uenigheder.
  • Nærer hver evaluering — den gemte two-pager indlejres i bundleProjectContext, så alle efterfølgende LLM-evalueringer blander dine erklærede præferencer med CV-vs-JD-matchet.

Nyt: server/lib/routes/two-pager.mjs, public/js/views/two-pager.js, public/js/lib/fit-score.js, PATHS.twoPager. 27 nye i18n-nøgler på tværs af alle 16 locales. Tests: tests/two-pager-routes.test.mjs, tests/fit-score.test.mjs.

[1.88.0] — 2026-07-04

Finpudsning af issue #29 — i18n-huller i Scan + API-hygiejne.

  • Lokaliserede de sidste hardkodede Scan-strenge (køreplan v1.69.4): kilde-opsummeringspillerne (N nye / M matchende), N nye stillinger-toasterne og reloc-badget flyder nu gennem t() — 4 nye nøgler (scan.pillNew, scan.pillMatching, scan.newOffers, scan.relocBadge) på tværs af alle 16 locales. Ikke-engelske brugere ser ikke længere strøet engelsk i kerne-scan-flowet.
  • Deaktiverede X-Powered-By-headeren (køreplan v1.69.5): app.disable('x-powered-by') i createApp() — serveren annoncerer ikke længere Express. (Resten af den epic var allerede leveret: parentVersion fjerner sin release-please-kommentar, lystilstands-temaskifteren, luk-modal-ved-ruteskift og lokaliseringen af “Score” (rep.score) i Rapporter.)

Tests: tests/scan-i18n-gaps.test.mjs + en assertion om fravær af X-Powered-By i tests/security-headers.test.mjs.

[1.87.0] — 2026-07-04

4 nye scan-udbydere uden godkendelse (paritet med forælder-career-ops v1.16.0). Scannerregistret vokser fra 41 → 45 adaptere (40 EN + 5 RU) — alle offentlige, uden godkendelse, host-fastlåst, redirect:'error' (SSRF-sikre), hver med en CI-isoleret test:

  • Get on Board (getonbrd) — portaldækkende offentligt JSON:API (LATAM/fjern-tech), udbyder-valgt, pagineret. server/lib/sources/getonbrd.mjs.
  • Amazon (amazon) — amazon.jobs offentlige søge-JSON, host-registreret eller provider: amazon, offset-pagineret. server/lib/sources/amazon.mjs.
  • Avature (avature) — pr.-lejer *.avature.net ATS, HTML-parset, host-registreret eller provider: avature. server/lib/sources/avature.mjs.
  • SAP SuccessFactors (successfactors) — pr.-lejer RMK-fliseliste (*.successfactors.eu/.com, jobs2web.com), HTML-parset. server/lib/sources/successfactors.mjs.

Hver leverer et sources/<slug>.mjs (auto-opdaget meta → #/scan-rullemenu) og et portals/adapters/<slug>.mjs i ALL_ADAPTERS (reglen om de to registre) + tests/sources-<slug>.test.mjs. ALL_ADAPTERS-antallet samt assertions for sorteret id og EN-sættet i /api/scan/sources steg fra 36→40; GET /api/scan/sources viser nu 45.

[1.86.0] — 2026-07-03

Statistik efter målroller (#/stats) — markedsstatistik for stillinger og løn for DINE målroller. En ny Analyse-side læser dine målroller fra profilen (config/profile.yml → ikke hardkodet) og opslagene fra den seneste scanning og viser derefter, pr. rolle og land:

  • Stillinger pr. land og medianløn pr. land (USD) — aggregeret på klientsiden (public/js/lib/role-stats.js, som genbruger window.Countries) ud fra de sparsomme data, scannerne allerede indsamler. Lønninger i enhver valuta normaliseres til USD via en eksplicit tilnærmet FX-tabel, med et forbehold om stikprøvestørrelse — aldrig opdigtet.
  • Rolle- og landefiltre samt håndlavede inline-SVG-søjle- og trenddiagrammer (ingen nye afhængigheder, CSP-sikre — kun addEventListener).
  • Gem øjebliksbillede (POST /api/stats/snapshot) bevarer det aktuelle aggregat i data/role-stats.jsonl; trenddiagrammet (GET /api/stats/trend) følger antallet af stillinger over tid — “dynamik”-visningen. Ærlig hybrid: øjebliksbilleder kommer fra lokale scanningsdata og opdateres efter behov.
  • Fuldt lokaliseret i alle 16 locales (26 nye i18n-nøgler).

Nyt: server/lib/routes/stats.mjs (16. rutemodul), public/js/lib/role-stats.js, public/js/views/stats.js, PATHS.roleStats; tests role-stats.test.mjs (7) + stats-routes.test.mjs (5).

[1.85.0] — 2026-07-03

Tysk, italiensk og tyrkisk locale (locale-paritet med forælderens career-ops v1.16.0). UI’et leveres nu på 16 sprog — de 🇩🇪, it 🇮🇹 og tr 🇹🇷 slutter sig til de eksisterende 13.

  • Fuld UI-oversættelse — alle 730 i18n-nøgler oversat i public/js/lib/locales/i18n-dict.{de,it,tr}.js; sprogvælgeren viser Deutsch / Italiano / Türkçe, og automatisk registrering af browsersprog genkender de/it/tr (public/js/lib/i18n.js).
  • Indbygget hjælpeguide — docs/help/{de,it,tr}.md oversat (fuld 19 H2 / 75 H3-struktur), serveret af GET /api/help/:lang.
  • Dokumentation — README.{de,it,tr}.md og CHANGELOG.{de,it,tr}.md tilføjet; CHANGELOG-locale-paritetsporten dækker nu 15 ikke-EN-locales.
  • Prompt-stillads — server/lib/prompts.mjs (LOCALE_NAMES + SCAFFOLD_STRINGS) lokaliseret til de tre nye locales, så LLM-outputtet følger UI-sproget.

Alle paritetsporte (i18n-locale-files, i18n-coverage, check-changelog-parity, lang-switcher-rtl) udvidet til sættet på 16 locales.

[1.84.0] — 2026-06-30

Genansøgnings-cooldown + kompensation i pipeline.md (paritet med forælderens career-ops v1.15.0). To scan-opgraderinger:

  • Genansøgnings-cooldown (#1201): EN-scanningen springer nu over roller hos virksomheder, du har søgt hos for nylig, så resultaterne forbliver fokuserede på NYE opslag. Konfigurér vinduer pr. virksomhed i config/profile.yml under re_apply_windows: (last_apply_date, same_role_days, applied_to: [roles], valgfri cross_role_bucket); virksomhedsmatchning er tegnsætningsuafhængig + ordgrænsebaseret (server/lib/cooldown.mjs). Slået fra, når nøglen mangler; scanloggen viser Cooldown skipped: N.
  • Kompensation i pipeline.md (#1017): scannede opslag gemmer nu deres løn som en valgfri trailingkolonne (url | <salary>) i data/pipeline.md. URL’en forbliver dedup-nøgle (| comp-kolonnen strippes ved læsning), cellen saniteres (ingen række-/kolonneindsprøjtning, formel-led neutraliseres), og bare-URL-pipelines bevarer bagudkompatibiliteten. Medfølger tests/cooldown.test.mjs + pipeline-kompensationstests. Kildeantallet uændret på 41 (begge er scan-logik-opgraderinger, ikke nye boards).

[1.83.0] — 2026-06-30

Genopslags-/spøgelsesstillings-detektor (paritet med forælderens career-ops v1.15.0). Et nyt panel 🔁 Genopslåede / spøgelsesstillinger på #/scan markerer firma+stilling-klynger, der er genopslået under forskellige URL’er inden for et rullende 90-dages vindue — et signal om forældede pipelines og spøgelsesstillinger. Understøttet af en fuzzy stillingstitel-matcher (server/lib/role-matcher.mjs) og en skrivebeskyttet detektor (server/lib/detect-reposts.mjs) over data/scan-history.tsv, eksponeret via GET /api/scan/reposts. Desuden: parentVersion i /api/health rapporterer nu blot semver-nummeret (release-please-kommentaren # x-release-please-version fjernes). Medfølger tests/detect-reposts.test.mjs. Kildeantallet uændret på 41 — genopslag er en analysefunktion, ikke et nyt board.

[1.82.0] — 2026-06-30

NoDesk-scanningskilde (paritet med forælderens career-ops v1.15.0). Det boards-dækkende NoDesk RSS-feed med fjernjob er nu en førsteklasses scanningskilde — tilføj en provider: nodesk-post, og den vises i Source-dropdownen på #/scan (41 adaptere i alt: 36 EN + 5 RU). Host-låst til nodesk.co med redirect:'error' (SSRF-sikker); titler opdeles på Role at Company (NoDesk har ingen lokationstag, så lokation forbliver tom); alle rækker er fjernjob. Medfølger en CI-isoleret tests/sources-nodesk.test.mjs-suite; den fulde unittest-suite er grøn ved 1523.

[1.81.0] — 2026-06-29

Paritet med forælderens career-ops — 13 nye jobscanningskilder. Overfører den seneste providerbatch fra Fighter90/career-ops main til in-process-scanneren. Boards-dækkende offentlige API’er (provider-valgt): Arbeitnow, Himalayas, Jobicy, Landing.jobs, 4 Day Week, The Muse, The Hub, Jobspresso (RSS) og Hacker News “Who is hiring?” (Algolia to-trins). Polske boards (host- eller provider:-detekteret): JustJoin.it og NoFluffJobs (POST-søgning). Per-tenant ATS (auto-detekteret fra careers_url): Pinpoint (<slug>.pinpointhq.com/postings.json) og Rippling (ats.rippling.com/<slug> → api.rippling.com board). Alle kilder er host-låste med redirect:'error' (SSRF-sikrede) og kan vælges i Source-dropdownen på #/scan — registret leverer nu 40 scanneradaptere (35 EN + 5 RU). Tilføjer 13 CI-isolerede testsuiter pr. kilde; den fulde unittest-suite er grøn ved 1513.

[1.80.0] — 2026-06-28

Fem scan-forbedringer (idéer fra job-crawler, genimplementeret). (1) Teamtailor-kilde — per-tenant <slug>.teamtailor.com via det offentlige /jobs.rss-feed, auto-detekteret fra careers_url (host-låst + redirect:'error'); registret har nu 27 adaptere. (2) Kilde-karantæne — en kilde med permanent 404/410 gemmes i data/scan-quarantine.json og springes over ved senere scanninger (selvhelende: forsøges igen efter 14 dage). (3) Maks. pr. kilde — et valgfrit #/scan-felt der begrænser antallet af jobs pr. board (∞ som standard). (4) Slået op inden for — et klient-side aldersfilter (24t / 7d / 30d). (5) Gemte søgninger + ★ favoritter — navngiv og genbrug filtersæt og marker job, gemt i localStorage med defensiv validering (en korrupt cache nulstilles rent); resultat-cachen nulstilles før hver scan og fyldes live.

[1.79.0] — 2026-06-28

WeWorkRemotely-scanningskilde (paritet med career-ops v1.14.0). Det boards-dækkende RSS-feed fra We Work Remotely er nu en førsteklasses scanningskilde — tilføj en provider: weworkremotely-post, og den vises i Source-dropdownen på #/scan (26 adaptere i alt). Host-låst til weworkremotely.com med redirect:'error' (SSRF-sikker); titler opdeles på Company: Role. Desuden: title_filter-nøgleord trimmes nu før længdetjekket (parent #1261).

[1.78.2] — 2026-06-27

i18n- og UX-hærdning (opfølgning på v1.78.1). Logoets tilgængelige navn er nu lokaliseret på alle 13 sprog (nav.logoHome). Enter i den globale søgning, mens du allerede er på #/scan, fremtvinger en gen-rendering, så den udfyldte søgeterm aldrig mistes (same-route guard). health.title er nu oversat til polsk (Kondycja) og dansk (Systemtilstand) — tidligere på engelsk. Tests 1235 → 1238.

[1.78.1] — 2026-06-27

Scan-UX-rettelser. Resultattabellen på #/scan opdateres nu automatisk under en scanning og igen, når den er færdig — ingen manuel genindlæsning. Den globale søgning i topbjælken viser et Enter-hint og hopper ved en ikke-URL-forespørgsel til #/scan med søgefeltet udfyldt (før #/tracker). Logoet linker nu til dashboardet (hjem).

[1.78.0] — 2026-06-27

Geografifilter på Scan-siden — filtrér jobresultater efter land, med flag. En ny Land-dropdown i #/scan viser hvert land, der er fundet i dine scanningsresultater (flag-emoji + antal), så du kan beholde kun roller knyttet til et bestemt land — sammen med Remote/Hybrid/Onsite-filteret, så du kan søge både landebundet og fjernarbejde. Drevet af en ny countries.js-hjælper, der mapper en annonces fritekst-lokation (landenavne, aliasser og ~100 store jobmarkedsbyer) til et ISO-land + flag; detektionen er konservativ og gætter aldrig.

[1.77.0] — 2026-06-27

Dansk tilføjet som det 13. sprog i grænsefladen. Fuld dansk oversættelse af brugerfladen, den indbyggede hjælpeguide (19 H2 / 75 H3), README og CHANGELOG. Dansk er nu med i sprogvælgeren med flag; i18n-maskineriet (assembler, audit, paritetsporte, snapshot) dækker nu 13 sprog.

[1.76.0] — 2026-06-26

Paritet med forælderens career-ops v1.13.0 — seks nye jobkilder, hærdning af scanneren og en resultattabel uden loft.

Tilføjet

  • Seks ATS-kilder pr. tenant — BambooHR, Breezy HR, Comeet, Personio, Recruitee, SolidJobs. De registreres automatisk ud fra careers_url-værten (Comeet kræver den fulde api: careers-api-URL), og hver enkelt fastlåser sin vært med et forankret regex + redirect:'error' (SSRF-sikret). Alle kan vælges i Source-rullemenuen på #/scan — registret leverer nu 25 adaptere (20 EN + 5 RU). Tilføjer en fetchText-hjælper til Personios XML-feed.
  • trust_filter — valgfri tillidsscoring, der kun annoterer (0–100, niveau high/medium/low, flag), for hver scannet opslag. Rækker under high får et sprogneutralt ⚠-badge i #/scan; intet kasseres nogensinde.
  • Arbeitsagentur remoteMatch + remoteMaxPages — konfigurationsstyret registrering af fjernarbejde: title (regex), filter (server-side homeoffice=nv_true + paginering) eller off.

Ændret

  • Intet loft på scanresultater. Visningsloftet MAX_STORED_RESULTS (2000) er fjernet — hvert matchet opslag gemmes, og tabellen på #/scan pagerer gennem dem (200/side). Store gennemløb mister ikke længere deres hale.
  • Robusthed i titelfilteret — korte akronymer kun med bogstaver (COO, SDR …) matcher nu på ordgrænser (ikke længere “COO” i “Coordinator”); fejlbehæftet title_filter-konfiguration kan ikke længere få en scanning til at gå ned. Gælder både ATS- og den regionale scanner.

Tests

  • +32 cases (1190 → 1222): sources-ats-providers, title-filter, arbeitsagentur-remote, trust-validator og en omskrevet scan-result-cap-vagt (“intet loft”).

[1.75.2] — 2026-06-19

docs: fuld dokumentationsparitet for v1.75.0-scanneraggregatorerne på tværs af alle 12 lokaliteter. Ingen kodeændring — bringer den brugervendte dokumentation på linje med de syv kilder, der kom i v1.75.0:

  • Hjælpevejledning (12 lokaliteter). §5 får en content_filter-blok (gating på nøgleord i beskrivelse/uddrag, søskende til location_filter) og en note om aggregatorer; §7 oplister de syv nye kilder i ét-klik-scangennemløbet og i den fulde Source-rullemenu; adaptertallet i §17 er rettet fra de forældede “11 adapters” til “19 adapters — 14 English + 5 Russian”. Ingen ##/###-overskrift blev tilføjet, så den fastlåste struktur med 19 H2 / 75 H3 er uændret.
  • README (9 fulde lokaliteter). Nyt punkt “Aggregator boards (v1.75.0)” under scankilderne, plus release-badget hævet til v1.75.2. (De forkortede README’er for pl/uk/ar har ingen liste pr. kilde og er bevidst urørte der.)
  • Referencedokumenter. docs/portals-examples.md får et “Aggregator boards”-afsnit klar til kopiering med korrekte provider: / <provider>:-konfigurationsblokke for alle syv; docs/PROJECT.md opdateret til 19 adapters; docs/sdd/CONVENTIONS.md dokumenterer skellet mellem de to registre (sources/registry.mjs til rullemenuen kontra portals/registry.mjs til hentning), det provider:-baserede aggregatorvalg, der videreføres som opts.company, scan-skrivesanitizeren (scan-sanitize.mjs) og v1.75.1-testtallet (1190).
  • QA. Tilføjet qa/QA-REGRESSION-PROMPT-v1.75.2-FULL.md — release-gate-driveren for hele fladen, opdateret til v1.75.x-scanaggregatorcyklussen.

[1.75.1] — 2026-06-19

fix(scan): robusthedsforbedring på de konfigurationsstyrede kilder fra v1.75.0. Tre små hærdningsrettelser fra gennemgangen efter udgivelsen (ingen adfærdsændring for en sund scanning):

  • Pagineringspauser med afbrydelseshensyn. Glints’ (300 ms) og Jobstreet/SEEK’s (200 ms) høflighedspauser mellem sider afsluttes nu øjeblikkeligt, når scanningens AbortSignal udløses, via en ny delay(ms, signal)-hjælper i server/lib/http-json.mjs, så en frakoblet klient ikke kan holde en paginerende scanning åben en ekstra pause.
  • Beskrivende fejl ved ikke-JSON. fetchJson ombryder nu et ikke-JSON 2xx-svar (f.eks. en HTML-vedligeholdelsesside leveret med status 200) som non-JSON 2xx response from <url> i stedet for at vise en bar SyntaxError, så scannerens fejllog pr. kilde navngiver det fejlbehæftede endpoint.
  • Stærkere normalisering ved scan-skrivning. normalizeScanScalar sammenfolder nu lodret tabulator, sideskift og Unicode-linje-/afsnitsseparatorerne (\v \f U+2028 U+2029) ud over \r \n \t — en streng overmængde, så ingen post-/linjeseparator, som et regneark eller en fremviser måtte respektere, overlever ind i scan-history.tsv.

[1.75.0] — 2026-06-19

feat(scan): overfør forælderens career-ops v1.12.0 — syv nye jobkilder, indholdsfiltrering og sikkerheds-/kvalitetsrettelser. web-ui kører sine egne in-process-scannere (den kalder ikke forælderens scan.mjs), så forælderens provider- og scanændringer fra v1.12.0 forplanter sig ikke automatisk — denne udgivelse genimplementerer de relevante i web-ui’ens adapterkontrakt.

  • Syv nye scannerkilder. Tre board-brede aggregatorer for fjernarbejde — RemoteOK, Remotive, Working Nomads — passer ind i det automatisk opdagede server/lib/sources/*.mjs-mønster (vælges med provider: remoteok / remotive / workingnomads). Fire konfigurationsstyrede regionale aggregatorer — IBM careers, Arbeitsagentur (det tyske føderale arbejdsformidlingsagentur), Glints (Sydøstasien), Jobstreet / SEEK — læser en <provider>:-konfigurationsblok pr. post; en-scanner videregiver nu den løste virksomhedspost til hver fetcher, så de kan læse den. Alle syv vises automatisk i kilde-rullemenuen på #/scan.
  • content_filter (forælder #974). Valgfri portals.yml-blok (nøgleordslister positive / negative), der gater et opslag på dets beskrivelses-/uddragstekst — afspejler location_filter-semantikken; opslag uden en beskrivelse passerer altid. Tilkoblet både EN- og RU-scanneren.
  • Hærdning ved scan-skrivning (forælder #1098). Metadata fra eksterne feeds saniteres nu, før de havner i data/scan-history.tsv og data/pipeline.md: kontroltegn sammenfoldes (et linjeskift i virksomhed/titel kan ikke længere indsætte en TSV-række), og et indledende = + - @ neutraliseres mod formelinjektion i regneark.
  • Ashby secondaryLocations (forælder #1073). Ashby-kilden folder nu hver sekundær lokations regionsmærkat plus postnummer-addressLocality / addressCountry ind i lokationsstrengen (dedupliceret), så en EU-berettiget rolle, hvis primære mærkat f.eks. lyder “Canada”, dukker op for location_filter.
  • Validering af evalueringsrapportens form (forælder #819). In-process-providerne i /api/evaluate (Anthropic / OpenAI / Qwen / OpenRouter / GitHub Models) markerer nu en fejlbehæftet A–G- / SCORE_SUMMARY-rapport som et ikke-fatalt warnings-array; Gemini-evalueringsstien arver allerede beskyttelsen fra forælderens gemini-eval.mjs.
  • docs: Antigravity CLI tilføjet til listerne over understøttede assistenter på tværs af alle 12 README’er (afbildes til Gemini-provideren).

Arvet gratis fra git pull af forælderen (web-ui kalder ud til disse): japansk CJK-PDF-fontfallback (#1053), ATS-sikre PDF-fonte (#1074), LaTeX-CJK-vagt (#1054), rettelser i tracker/merge/followup/dashboard og de kinesiske modes/zh-modes (web-ui oplister modes dynamisk).


[1.74.3] — 2026-06-18

docs(parent-source): point the parent career-ops repo at the Fighter90/career-ops fork. The web-ui now references the maintainer’s fork as the parent project everywhere it is a live source: the bin/setup.sh installer’s CAREER_OPS_REPO clone default, every git clone / “sits on top of” / onboarding link across all 12 READMEs, and the agent docs (CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md, docs/). Author credit to santifer (and the unofficial-UI disclaimer) is unchanged — only the source/clone URLs moved. tests/sh-files.test.mjs now asserts the installer clones the fork.


[1.74.2] — 2026-06-17

fix(health): surface GITHUB_MODELS_API_KEY as an optional check on #/health and in /api/status/providers. The v1.74.0 GitHub Models provider was configurable in #/config but had no row on the Health page and was missing from the keysConfigured provider surface. Added the optional check (same “set / unset (manual mode)” wording as the other five live-eval providers) and github (+ its GITHUB_MODELS_MODEL) to /api/status/providers, so the active-provider routing and Health page now reflect all six. tests/api.test.mjs health-row test extended to all six providers.


[1.74.1] — 2026-06-17

docs + test: README “Install an AI assistant” section; full branch coverage for the Gemini connector. Added an install/login table to the README — install links for Claude Code / Gemini CLI / Codex / Qwen Code / OpenCode / GitHub Copilot CLI + each one’s #/config provider mapping + “log in before continuing” (mirrors the career-ops.org/docs Quick Start; clarifies the web-ui is the standalone, no-CLI-needed alternative). New tests/gemini-connector.test.mjs (8 cases) covers every runGemini branch — no-key, success, API error, empty/blocked completion, malformed body, timeout, network error, hasGeminiKey — taking server/lib/gemini.mjs to 100% statements. Overall coverage 96% lines / 88% branch / 96% funcs. Suite 1126 → 1134.


[1.74.0] — 2026-06-17

feat(llm): GitHub Models (Copilot) as the 6th provider + canonical 6-assistant alignment. career-ops.org/docs lists six AI coding assistants — Claude Code, Gemini CLI, Codex, Qwen Code, OpenCode, GitHub Copilot CLI. The web-ui now supports all six: five map to existing live providers (Anthropic / Gemini / OpenAI / Qwen / OpenRouter), and GitHub Copilot CLI gains a dedicated GitHub Models connector — runGitHubModels (OpenAI-compatible; a GitHub PAT with the models scope), configurable in #/config (GITHUB_MODELS_API_KEY + GITHUB_MODELS_MODEL) and selectable via LLM_PROVIDER=github; 6th in the auto order. Help bundles + READMEs now list the canonical six (renamed Qwen CLI→Qwen Code; added Gemini CLI + GitHub Copilot CLI), and the README adds a full mode-reference + portal-adapter link table to career-ops.org/docs so every feature traces back to the parent. tests/llm-provider-context.test.mjs extends the fetch-boundary matrix to all six providers (cv.md + profile.yml inlined + artifact returned); new GITHUB_MODELS_* keys added to all 12 locale dictionaries. Suite 1125 → 1126.


[1.73.0] — 2026-06-17

feat(llm): generic Gemini connector + verified CV/profile context across all providers. Added server/lib/gemini.mjs (runGemini) — a zero-dependency Gemini generateContent client returning the same {markdown, usage, error} shape as the Anthropic / OpenAI-compatible clients. Fix: /api/mode/:slug and /api/deep previously piped their prompts through the oferta-only gemini-eval.mjs, so Gemini Run live returned an evaluation instead of the requested artifact (cover letter, outreach, brief). They now call runGemini with bundleProjectContext, so cv.md + config/profile.yml are inlined for Gemini exactly like every other provider — letters/briefs are detailed and personalized. New tests/llm-provider-context.test.mjs mocks each provider’s HTTP boundary and asserts all five (Anthropic / Gemini / OpenAI / Qwen / OpenRouter) inline cv.md + profile.yml and return the artifact (mode + deep + evaluate matrix, 9 cases). /api/evaluate keeps its oferta-tuned gemini-eval.mjs. Suite 1116 → 1125.


[1.72.0] — 2026-06-17

feat(modes): “Run live” now returns the final artifact directly (single-shot output contract). The parent modes/<slug>.md templates are written for interactive Claude Code sessions — several (cover, contacto, …) pause to ask clarifying questions before producing the result, which made the web-ui’s single-shot Run live emit a questionnaire instead of the artifact. buildModePrompt now wraps every mode in a non-interactive output contract: do the analysis (JD breakdown, company notes, ATS keywords, profile↔JD gaps, tone/angle choices) silently, pick sensible defaults from cv.md / config/profile.yml for anything the template would normally ask, and output only the final artifact — closed with a per-mode “output ONLY {the cover letter / outreach message / …}” reminder. So clicking Run live on #/cover now returns the cover letter itself; the same fix applies to every generic mode (cover, contacto, interview-prep, project, training, followup, patterns) in all 12 locales (the artifact is written in the UI language via the locale directive). Suite 1103 → 1116.


[1.71.2] — 2026-06-17

docs(i18n): publish the documentation-consistency pass. Every README “Translations of this guide” block now lists all 11 sibling languages (previously some omitted English/Français and self-linked), with the blank line before the section break restored. The full QA regression prompt is renamed to the current version, and the docs (CLAUDE.md, CONVENTIONS, LOCALIZATION, PROJECT-CONTEXT) are synced to the current version and test count (1103). No code or behavior change — docs only, so the help/UI translations and all features from 1.70.0–1.71.1 are unchanged.


[1.71.1] — 2026-06-17

fix(i18n): the in-app help guide is now fully translated in all 12 languages. Added docs/help/{pl,uk,ar}.md (each holding the gated 19 H2 / 75 H3 structure) so #/help serves a native bundle for Polish, Ukrainian, and Arabic instead of falling back to English — GET /api/help/{pl,uk,ar} now return their own locale. Wired into every help gate (help-ui, help.test, help-ru-config-section, canonical-docs-coverage). Also completed every 12-language translation list: the README “Translations of this guide” block (9 READMEs), the localized CHANGELOG “Translations:” headers (8 files), and refreshed stale doc counts (CONVENTIONS test count → 1100; help “fall back” notes → 12 translated bundles). Suite 1100 → 1103.


[1.71.0] — 2026-06-16

feat(cover): generate a cover-letter PDF straight from #/cover. The cover mode (added in v1.70.0) produces the letter text; the result now offers a Generate PDF button that renders it through the shared inline markdown→PDF pipeline (POST /api/stream/pdf/inline → generate-pdf.mjs), the same path interview-prep uses. You can now produce the letter and ship a PDF without leaving the SPA.

test/docs: v1.70.0 review hardening. Added CI-isolated coverage for the cover mode (allowlist + prompt assembly, via modes-endpoints.test.mjs), the flag <select> switcher + Arabic RTL (dirFor/<html dir>, new tests/lang-switcher-rtl.test.mjs), top.langLabel in every locale, the cover-letter PDF wiring (tests/cover-letter-pdf.test.mjs), and the prompts.mjs locale directive + scaffolding for fr/pl/uk/ar (tests/locale-scaffold.test.mjs — locking the latent French gap closed in v1.70.0). Refreshed three stale “all 8” → 12-locale / 9-help-bundle references in docs/sdd/CONVENTIONS.md. Refreshed the full-project QA regression prompt (qa/QA-REGRESSION-PROMPT-v1.71.0-FULL.md).


[1.70.0] — 2026-06-16

feat(i18n): three new UI languages — Polish (pl), Ukrainian (uk), and Arabic (ar, with full RTL) — bringing the SPA to 12 locales, matching every language in the parent career-ops README. Each new locale ships a complete 697-key dictionary (public/js/lib/locales/i18n-dict.{pl,uk,ar}.js), gated by the existing parity / coverage / no-latin-leak / no-personal-data suites. Arabic adds genuine right-to-left support: i18n.js sets <html dir="rtl"> for RTL locales and a scoped [dir="rtl"] block in app.css mirrors the chrome (sidebar, notifications drawer, markdown tables/blockquotes, inline spacing) — LTR locales are byte-for-byte unchanged. New top.langLabel key (×12) names the picker for screen readers.

feat(ui): flag-icon <select> language switcher replaces the wrapping button row. With 12 locales the old .lang-btn row wrapped to three lines in the sidebar; a native <select> (each option prefixed with a flag emoji) scales cleanly, is keyboard- and screen-reader-friendly out of the box, and stays CSP-safe (change handler via addEventListener, no inline JS). Flags degrade to region letters where the platform lacks flag glyphs, so the language label is always the load-bearing identifier.

feat(cover): port the parent’s cover-letter mode (career-ops v1.10.0 + v1.11.0 greeting) into the SPA. New #/cover page under the Application nav group, built on the generic mode runner: Job description + Company/Role + an optional salutation → a tailored letter generated from cv.md / modes/_profile.md. Added cover to the server MODE_ALLOWLIST and a cover.* i18n block (×12 locales).

chore(compat): track parent career-ops v1.11.0. Verified the read/write contract is intact — data/applications.md stays the markdown source of truth (the v1.11.0 SQLite tracker index is a derived cache), tracker columns are still header-mapped. parentVersion now reports 1.11.0.

fix(i18n): close a latent gap where French (added v1.61.0) was missing from server/lib/prompts.mjs LOCALE_NAMES and SCAFFOLD_STRINGS — French LLM calls silently defaulted to English output and English scaffolding. fr/pl/uk/ar are now all wired into the prompt-locale path.

Known follow-ups (tracked in qa/v1.70-regression/): the in-app help guide (docs/help/) falls back to English for pl/uk/ar (the UI chrome itself is fully localized); the parent’s interactive interview onboarding, reverse-ATS discovery, and newer scan providers are not yet surfaced in the SPA.


[1.69.2] — 2026-06-12

fix(test): close a test-isolation leak that let npm test overwrite the user’s real config/profile.yml and data/scan-history.tsv. tests/critical-fixes.test.mjs statically imported prompts.mjs at the top of the file; prompts.mjs transitively imports paths.mjs, which resolves PROJECT_ROOT eagerly at module load (PATHS resolves once per process). Because that import ran before the before() hook set CAREER_OPS_ROOT to a temp dir, PATHS pinned the real parent — so the F-008 PUT /api/profile wrote the “Acceptance Test” fixture into the user’s real profile on every run (and similar writes escaped the temp root). Fix: load prompts.mjs via dynamic import() inside before(), after the env is set (the server was already loaded that way). New tests/test-root-isolation.test.mjs (2 cases) guards every isolation-needing test against statically importing a paths.mjs carrier (server/index.mjs, prompts.mjs, store.mjs, en-scanner.mjs, ru-scanner.mjs, paths.mjs). No production-code change. Suite 1084 → 1086.


[1.69.1] — 2026-06-12

fix(scan): raise the #/scan result display cap 500 → 2000 per region so large regional sweeps are no longer silently truncated. A real RU scan produced 1352 matching jobs, but only the first 500 were stored in data/last-scan.json and rendered in the results table — 852 relevant jobs were hidden (the 2000 scanned → ~600 shown symptom: 139 EN + 500 RU). Both server/lib/en-scanner.mjs and server/lib/ru-scanner.mjs now cap the stored filtered set at a shared, env-overridable constant MAX_STORED_RESULTS (default 2000, override via SCAN_MAX_RESULTS). This is display-only — appending to pipeline.md and scan-history.tsv already used the uncapped fresh (new-since-last-scan) set and was never truncated. New tests/scan-result-cap.test.mjs (3 cases) locks the default, the env override, and that neither scanner hard-codes slice(0, 500).

fix(health/ui): #/health check cards no longer overflow. A long check name/value (e.g. PROFILE CUSTOMIZED · still on template …, PLAYWRIGHT (PARENT NODE_MODULES)) collided with the right-hand Fix → button + status badge and spilled out of the card, because the generic .flex-between flex children default to min-width: auto and never shrink. The row is now tagged .health-check-row with scoped CSS: the left text shrinks + wraps (min-width: 0), the action group keeps its size (flex: 0 0 auto) and wraps below on narrow cards. New tests/health-card-overflow.test.mjs (2 cases). Suite 1079 → 1084.


[1.69.0] — 2026-06-12

feat(scan): P-14 plug-in scanner auto-discovery — drop a .mjs in server/lib/sources/ to register a new source. Pre-v1.69 the source list in server/lib/sources/registry.mjs was a static hand-maintained array — adding a new adapter required editing both <id>.mjs AND registry.mjs. Closes the partial half of the roadmap item P-14 (docs/ROADMAP.md). Now every *.mjs in server/lib/sources/ is auto-loaded at module boot; each adapter contributes its identity via a self-describing export const meta = { value, label, region, configKey? } block. The 12 shipped adapters (ashby / greenhouse / lever / rss / smartrecruiters / workable / workday + geekjob / getmatch / habr / hh / trudvsem) each grew a meta export; registry.mjs now uses readdirSync + dynamic import() resolved at module-eval via top-level await (Node 18+ ESM standard). The public API (SOURCES, SOURCES_BY_REGION, RU_CONFIG_KEYS, getRegionalSources) is unchanged — every existing import keeps working. Validation rejects malformed meta (missing value/label/region, RU without configKey, region outside 'en'|'ru') and logs a single console.warn per offending file so half-migrated branches stay diagnostic-friendly. The bundled registry.mjs is excluded from self-import. New tests/sources-registry-discovery.test.mjs adds 14 cases covering shipped-adapter coverage, drop-in adapter discovery, helper-module skip, malformed-meta rejection, self-import exclusion, missing-directory tolerance, and deterministic ordering. Suite 1065 → 1079.


[1.68.2] — 2026-06-07

fix(bin): npx / npm link CLI verbs were broken — resolve the bin path through symlinks. npm and npx expose career-ops-ui as a symlink under node_modules/.bin/, where the old dirname "${BASH_SOURCE[0]}" resolved to .bin instead of the package root — so npx career-ops-ui init ran node node_modules/scripts/init.mjs and crashed with MODULE_NOT_FOUND (local runs after npm install were unaffected, which hid the bug). Both bin/career-ops-ui.sh and bin/start.sh now canonicalize SCRIPT_DIR through the symlink chain (readlink loop + cd -P), so every verb works from the repo, via npm link, and via npx. Adds a regression lock in tests/sh-files.test.mjs that runs a verb through a .bin-style symlink. Suite 1065/1065.


[1.68.1] — 2026-05-29

fix(scan): per-source fetch timeout 10s → 60s. v1.67.1’s 10s fail-fast also cut off slow-but-alive Ashby boards that just needed more time. Raise the default to one minute so those return. Trade-off: a genuinely dead/hung source now holds a concurrency slot for the full 60s (slower worst-case scan), and the chronic hangers (Perplexity, Supabase, Resend, …) likely still time out — a per-source / lower-Ashby-concurrency fix would address those properly. Override with SCAN_FETCH_TIMEOUT_MS. Suite 1063/1063.


[1.68.0] — 2026-05-29

feat(scan): reworked the result-filter panel — labelled fields, an Apply button, an On-site option, and a working salary filter. Every filter on #/scan is now a labelled field (label above the control, not a placeholder): Search · Work type · Salary from · Salary to · Source · Scope. An explicit Apply button (plus Reset, and Enter in any field) re-runs the filter; an on-page hint explains how it works. The salary range now actually filters — once you set a from/to value, jobs whose listed pay falls outside the range and jobs with no listed salary at all are dropped (overlapping-range match; currency ignored). The Work type filter gains an On-site option alongside Remote / Hybrid / Relocation. New i18n keys ×9; salaryInRange made strict; suite 1063/1063.


[1.67.1] — 2026-05-29

fix(scan): per-source fetch timeout 30s → 10s (fail-fast). v1.67.0’s 30s raise recovered only ~half the slow Ashby boards; the rest (Perplexity, Supabase, Resend, DeepL, Ramp, …) hang regardless of the deadline, so a longer timeout just stalled every scan waiting on dead slots. 10s fails fast on the chronic hangers and keeps scans responsive. Override with SCAN_FETCH_TIMEOUT_MS. Suite 1060/1060.


[1.67.0] — 2026-05-29

feat(scan): salary range filter (from / to) on #/scan, plus a longer per-source fetch timeout. The results table gains two numeric inputs — salary from / to — beside the text and remote filters. Each row’s free-text salary (от 100 000 до 200 000 ₽, 120000-150000 USD, $120K–$150K, …) is parsed to a numeric range and matched with overlapping-range semantics; rows with no published salary are kept, so the filter narrows the list instead of gutting it (comparison is currency-agnostic — no FX conversion). Also raises the per-source scan fetch timeout 15s → 30s (override: SCAN_FETCH_TIMEOUT_MS) — Ashby’s includeCompensation payloads routinely took >15s under 8-way concurrency, so ~30 Ashby boards were timing out every scan. New window.Skills.parseSalaryRange/salaryInRange + i18n ×9; 13 new tests; suite 1060/1060.


[1.66.0] — 2026-05-28

feat(scan): RU sources now walk ALL result pages, not just the first. hh.ru, Habr Career and Trudvsem each only paged the first ~50 hits per query; they now follow pagination to the end — &page=N for hh.ru/Habr, offset/meta.total for Trudvsem — deduping across pages and stopping when a page adds nothing new (or at a 50-page safety cap). A query like “Backend разработчик” now returns the full result set instead of one page (e.g. hh.ru PHP 17 → 55+ across 3 pages; Trudvsem returns all 72). Per-page fetches keep the existing timeout + AbortSignal. 4 new tests; suite 1045/1045.


[1.65.0] — 2026-05-28

feat(scan): hh.ru is now scraped from its public website instead of the JSON API — works from any IP, no proxy. api.hh.ru started returning a bare 403 forbidden to every programmatic client regardless of IP (US, RU datacenter, RU residential, RU mobile) or User-Agent — an edge anti-bot block, not a documented API error. The website (hh.ru/search/vacancy) still serves full server-rendered results to any browser-like client, so the adapter now parses that HTML (like Habr Career). Removes the HH_PROXY env added in 1.64.0 and the undici dependency — no proxy, key, or User-Agent setup needed. Tests rewritten for the HTML parser; suite 1041/1041.


[1.64.0] — 2026-05-27

feat(scan): route the hh.ru request through a Russian proxy via HH_PROXY. hh.ru geo-blocks its API by IP, not User-Agent — so HH_USER_AGENT alone never lifted a 403 from a non-RU exit node. Set HH_PROXY to a Russian HTTP/HTTPS proxy URL (e.g. http://user:pass@ru-host:port) and only the hh.ru request is routed through it; every other source keeps its direct connection. Built on undici’s ProxyAgent (new runtime dep); the dispatcher is omitted entirely when HH_PROXY is unset, and a changed value is picked up on restart. 3 new tests; suite 1041/1041.


[1.63.2] — 2026-05-27

feat(scan): live % progress + per-source detail in the #/scan console. The progress bar is now determinate — scanners emit progress events (EN: per company; RU: per query) forwarded over SSE, and the bar fills with a live “Scanning… NN%” label (animated indeterminate stripe only until the first event). Each source’s first failure (timeout / 403 / network) is now logged to the console in detail (e.g. ⚠ hh timed out: …), then repeats are suppressed. 1 new test; suite 1040/1040.


[1.63.1] — 2026-05-27

style(scan): make the #/scan progress bar more prominent. Wrapped the in-flight indicator with a visible “Scanning…” caption and bumped the bar to 8px (was a thin 4px) so it’s clearly noticeable while a scan runs. No behavior change.


[1.63.0] — 2026-05-27

feat(scan): per-request fetch timeout + #/scan progress bar. Scanner source requests had no deadline, so a stalled upstream (e.g. api.hh.ru from a blocked IP) could hang the whole scan. A new server/lib/fetch-timeout.mjs wraps the scanners’ fetchImpl (makeTimeoutFetch, default 15s, override via SCAN_FETCH_TIMEOUT_MS) so every source request has a hard deadline — a timed-out source is recorded as a non-fatal error and the scan continues (Habr keeps working even when hh.ru is unreachable). The #/scan page also shows an indeterminate progress bar while a scan is in flight (localized scan.progress across all 9 locales). 7 new tests; suite 1039/1039.


[1.62.3] — 2026-05-27

docs: clarify install (career-ops-ui runs inside career-ops/web-ui/) + init troubleshooting, across all 9 locales. Rewrote the install section into Option 1 (one curl) / Option 2 (clone the UI inside an existing career-ops project as web-ui) + CLI verbs + provider setup + a Troubleshooting init block — addressing the common confusion of cloning career-ops-ui standalone and running init without the parent project. Also added the nested-layout note to the /help §1 Setup section and summarized the whole v1.62.* line in the README highlight. Docs-only; no code change.


[1.62.2] — 2026-05-27

fix(help): #/help filter is now full-text (finds H3 subsections like RSS). The help-page search/TOC filter previously matched only H2 section titles, so the v1.62.x RSS docs (an H3 under §5 Portals & sources) weren’t findable. Each section’s body text is now indexed into the filter, so searching e.g. “RSS” surfaces §5. Pure client-side; no API change.


[1.62.1] — 2026-05-27

feat(scan): RSS in the source filter + RSS location fix. The #/scan source-filter dropdown now lists RSS (added to server/lib/sources/registry.mjs + the SPA fallback list), so RSS-board results (LaraJobs, WeWorkRemotely, …) are filterable like any ATS source. The RSS adapter no longer maps the feed <category> tag onto location — non-location tags there made location_filter wrongly drop remote roles; location is now empty so feeds pass location filtering. Scan-button tooltips/labels and the source-list i18n string (dash.quick.scanSub) updated across all 9 locales to include Workable / SmartRecruiters / Workday / RSS. i18n snapshot + scan-sources endpoint test (6 → 7 EN sources) updated.


[1.62.0] — 2026-05-27

feat(scan): generic RSS adapter for non-ATS job boards. A new rss portal adapter (server/lib/portals/adapters/rss.mjs + server/lib/sources/rss.mjs) lets the scanner pull jobs from any RSS feed — LaraJobs, WeWorkRemotely, RemoteOK, golangprojects and other boards outside Greenhouse/Ashby/Lever. Zero new dependencies: feed parsing is regex-based with CDATA + HTML-entity support (titles/companies are tag-stripped, astral code points decoded safely). Activated per-company via provider: rss / rss: / feed_url: in portals.yml, so it never intercepts ATS-matched companies. ALL_ADAPTERS grows 6 → 7. 29 new tests; documented across all 9 README locales.


[1.61.1] — 2026-05-22

fix(i18n): localize the theme-toggle title + aria-label across all 9 locales (MINOR-001). The dark/light theme button (#theme-toggle) hardcoded title="Toggle theme" and aria-label="Toggle theme" in index.html — the tooltip and screen-reader text never translated, on any locale. A new top.themeToggle key + a data-i18n-title handler in applyI18n() (mirroring the v1.58.15 search-aria-label fix) localize both attributes on boot and on every language switch. Locked by tests/playwright-theme-toggle-i18n.mjs (9 locales + runtime-switch) and two static guards in tests/qa-report-fixes.test.mjs. The lone LOW finding from the v1.61.0 French sign-off. (MINOR-001)


[1.61.0] — 2026-05-22

feat(i18n): add French as the 9th UI language. New per-locale dictionary public/js/lib/locales/i18n-dict.fr.js (window.__I18N_DICT_FR), at full 668-key parity with English; new help bundle docs/help/fr.md (19 H2 / 73 H3, exact structural parity with en). fr is registered in the language switcher and browser auto-detect (i18n.js), the assembler (i18n-dict.js), index.html (a <script> tag before the assembler), the test snapshot, and every test locale list. The initial translation table came from PR #9 (community contribution). No logic change: t() and every view are unchanged. 1001 / 1001 unit tests; the Playwright locale-sweep grows to 9 subtests. (FR-LOCALE)


[1.60.0] — 2026-05-22

refactor(i18n): split the 8-column megafile into per-locale files (I18N-SPLIT). The translation dictionary lived in one 849-line public/js/lib/i18n-dict.js (key → { en, es, … } across 8 locales). It is now one file per locale — public/js/lib/locales/i18n-dict.{en,es,pt-BR,ko,ja,ru,zh-CN,zh-TW}.js (each assigns window.__I18N_DICT_<LANG> = { key: string }) plus a shared i18n-dict.aliases.js — so a translator edits a single language in isolation (the i18next / OpenWA layout). i18n-dict.js is now a small assembler that merges the per-locale tables back into the exact same window.__I18N_DICT shape, so t(), every view, and every call-site are byte-for-byte unchanged. Everything loads synchronously via <script src> — no build step, no runtime fetch (both hard rules preserved). A captured snapshot proves the migration is lossless (assembled dict ≡ pre-split dict, 678 keys). Tooling + tests made split-aware via a shared tests/helpers/i18n-vm.mjs: tools/i18n-audit.mjs, the CI inline coverage check (which had silently become a no-op against the post-v1.23 split — it now actually loads the dictionary), and ~25 dictionary tests. New tests/i18n-locale-files.test.mjs (snapshot equality · per-locale key parity · alias integrity · index.html load order) and tests/playwright-locale-sweep.mjs (every page × 8 locales renders + localizes in real Chromium). 994 → 1000 unit · 62 → 70 Playwright. No user-facing behaviour change. (I18N-SPLIT)


[1.59.13] — 2026-05-21

fix(i18n): collapse TRUE duplicate keys via @alias + final personal-data purge (pre-fr, follow-up to v1.59.12). Two parts. (1) Personal data: swept the WHOLE repo, not just the dict — the maintainer’s real name was used as incidental test-fixture data (tests/health-doctor-unify.test.mjs, tests/llm-output.test.mjs) and in archived QA reports; all replaced with neutral examples (Jane Doe). LICENSE copyright + package.json author switched from the real legal name to the public Fighter90 handle. Repo now greps clean for the real name/email everywhere. (2) Duplicate keys (I18N-CL3, properly): added an @alias mechanism to i18n.t() — a key { '@alias': 'x.y' } resolves to a canonical key. Collapsed the 10 keys that are byte-identical across all 8 locales (nav.help/dash.quick.helpCta → help.title; nav.cv → cv.title; nav.health/dash.quick.healthCta → health.title; nav.reports/dash.reports/dash.quick.reportsCta → rep.title; nav.apply → apply.title; nav.interviewPrep → interviewPrep.title). The contributor’s own nav.config/config.title example is deliberately NOT aliased — they diverge in Spanish (short sidebar Configuración vs full title Configuración de la aplicación); aliasing would change a rendered label. Coverage test, CI inline check, and tools/i18n-audit.mjs are all alias-aware (skip alias keys for parity, enforce every target exists, resolve before dup-counting). New tests/i18n-alias.test.mjs (3 cases) locks integrity + behaviour. 991 → 994 unit. Reduces the French translator’s surface — true dupes are now translated once. (I18N-CL3, personal-data-sweep)


[1.59.12] — 2026-05-21

fix(i18n): i18n-dict.js cleanup — pre-fr ship (I18N-CL1, I18N-CL2, I18N-CL4). External French-locale contributor audit. I18N-CL1 (privacy): training.coursePh carried a vendor-specific cert name across all 8 locales — replaced with a neutral generic placeholder (Cloud architecture certification + native equivalents). I18N-CL2 (hygiene): the followup.lastPh dict fallback was a frozen 2026-04-21 literal — replaced with a localized format hint (YYYY-MM-DD / AAAA-MM-DD / ГГГГ-ММ-ДД); the live dynamic today − 14d placeholder (U-3, v1.58.23) in mode-page.js was already working and is now lock-tested. I18N-CL4 (audit): new tools/i18n-audit.mjs + npm run audit:i18n, wired into CI — hard-fails on personal data, locale-parity gaps, empty values, and bare-calendar-date placeholders. I18N-CL3 (decision): the ~50 duplicate-value key groups (e.g. nav.scan / scan.btnRun / scan.col.company) are intentional — distinct UI roles that non-English locales translate differently; deduping them would remove i18n flexibility, so they are reported as informational warnings, not failures (documented in the dict header). 3 new tests (988 → 991 unit). Unblocks the upcoming French locale PR. (I18N-CL1, I18N-CL2, I18N-CL4)



career-ops-ui v1.267.1 · page 5/9 · mirror of the project changelog for cvstart.org