Changelog

GitHub

Jede nennenswerte Änderung, Release für Release. Dieselbe Datei liegt im Repository und ist in alle 19 Sprachen übersetzt.

[1.235.0] — 2026-09-16

Eltern-Parität — career-ops main @ 68e6b94 (VERSION 1.33.0, 42 Commits). Zwei gespiegelte Fixes; einer davon verlor hier still MokaHR-Anzeigen.

Behoben

Ein zweiter Job vom selben MokaHR-Board verschwand, statt hinzugefügt zu werden. url-key.mjs reduziert eine Anzeigen-URL auf einen Vergleichsschlüssel und verwarf das Fragment bisher bedingungslos — vernünftig, denn fast jedes Fragment ist rein darstellungsbezogen (#apply, #section-2). MokaHR ist die Ausnahme in unserer eigenen Registry: Seine Anzeigen lauten …/social-recruitment/{tenant}/{id}#/job/{n}, wobei sich alle Jobs eines Tenants den Pfad teilen und die Anzeigen-ID nur im Fragment existiert. Also normalisierten sie sich alle auf denselben Schlüssel. Vor dem Fix gemessen: Zwei verschiedene MokaHR-Jobs in die Pipeline eingefügt, und am Ende steht eine Zeile — kein Fehler, keine Warnung, nichts, was auffällt; der Aktualitätsfilter des Scanners (en-scanner.mjs) liest denselben Schlüssel, sodass, sobald ein Job dieses Tenants im Scan-Verlauf stand, jeder andere als „schon gesehen“ verworfen wurde. normalizeUrl hebt jetzt eine erkannte #/job/{id}- oder #/jobs/{id}-Route in einen reinen Vergleichs-Query-Schlüssel, bevor das Fragment verworfen wird — MokaHR behält seinen etablierten Namen mokahr_job_id, damit bereits geschriebene Schlüssel vergleichbar bleiben, jeder andere Host erhält ein internes _career_ops_fragment_job_id. Die angehobene ID wird angehängt, nicht gesetzt: Eine URL, die diesen Parameter bereits trägt, hält die ID einer anderen Anzeige, und ihn zu überschreiben würde genau die zwei Dinge zusammenführen, die der Schlüssel eigentlich auseinanderhalten soll. Jedes nicht erkannte Fragment fällt weiterhin weg — die Hälfte, die schon vorher richtig war. accept-encoding wird jetzt bei jeder Quellen-Anfrage fest vorgegeben. Bleibt er ungesetzt, handelt Node aus, was sein undici-Build eben anbietet; ab Node ≥ 23 gehört zstd dazu, und die zstd-Antwort von amazon.jobs kommt exakt bei 1024 Bytes abgeschnitten mit Status 200 zurück — der Fehler liest sich dann als Unterminated string in JSON at position 1024, wie eine fehlerhafte API statt eines Transportfehlers. Jede Anfrage sendet jetzt gzip, deflate, br. Auf unseren Laufzeiten ist das vorbeugend, keine Reparatur: Node 18/20/22 — die CI-Matrix und der Server (22.22.1) — senden gzip, deflate, direkt geprüft statt nur angenommen, hier wurde also nichts abgeschnitten; was sich heute ändert, ist, dass br angeboten wird, was diese Versionen standardmäßig nicht anfordern, aber korrekt dekodieren. Der Standardwert wird groß-/kleinschreibungsunabhängig angewendet, und das ist der Teil, der eine genauere Prüfung verdient: Header-Namen sind groß-/kleinschreibungsunabhängig, und fetch fügt zwei gleichnamige Einträge zu einem einzigen, kommagetrennten Wert zusammen — ein Aufrufer, der Accept-Encoding: identity übergibt, hätte sonst gzip, deflate, br, identity gesendet bekommen.

Anmerkungen

Headers bleiben an der fetchImpl-Grenze ein einfaches Objekt, während das Elternprojekt fetch eine Headers-Instanz übergibt. Das ist eine bewusste Abweichung, kein Versehen: Die Fetch-Helfer von web-ui nehmen ein injizierbares fetchImpl, und 45 Quellen-Suiten lesen opts.headers['User-Agent'] aus dem, was sie einfangen — eine Headers-Instanz kennt keinen eckigen-Klammer-Zugriff, die originalgetreue Portierung gilt daher dem Verhalten (groß-/kleinschreibungsunabhängiges Überschreiben, der Aufrufer gewinnt) statt der Implementierung. Das Objekt des Aufrufers wird kopiert, nie mutiert, da mehrere Quellen sich eine Header-Konstante teilen. Der begleitende Fix des Elternprojekts — das Anwenden eines Standard-User-Agent groß-/kleinschreibungsunabhängig — ist ein No-Port: web-ui injiziert überhaupt keinen Standard-UA, jede Quelle setzt den, den ihr Board verlangt, es gibt also keinen Standardwert, mit dem der User-Agent eines Aufrufers kollidieren könnte. Zwei neue Suiten und drei neue Fälle schreiben das Paar fest: tests/http-accept-encoding.test.mjs (der exakte fest vorgegebene Wert, jeder der vier Fetch-Helfer und eine Aufrufer-Überschreibung in drei Schreibweisen — geprüft wird, dass ein einziger accept-encoding-Schlüssel überlebt, wovon der Merge-Bug genau zwei erzeugte) sowie tests/parsers-pipeline-hash-route.test.mjs, das die für Nutzer sichtbare Nahtstelle statt des Schlüssels prüft: Zwei MokaHR-Jobs müssen beide in der Pipeline ankommen, ein erneutes Einfügen desselben Jobs unter vier Schreibweisen muss weiterhin zu einer Zeile zusammenfallen, und ein rein kosmetisches Fragment darf nicht zu einer Kennung werden. Alle wurden zunächst nachweislich als fehlschlagend bestätigt. Die Fixierung sitzt im Transport, nicht nur in diesen Helfern: 25 der 92 Quellen rufen das injizierte fetchImpl direkt auf und laufen nie durch sie, also wendet makeTimeoutFetch sie an — der Wrapper, den beide Scanner tatsächlich injizieren, und der einzige Durchgangspunkt, den alle Quellen teilen. (Das Elternprojekt braucht kein Gegenstück: providers/_http.mjs ist sein einziger Transport.) Eine strukturelle Wache lässt die CI nun scheitern, wenn eine neue Quelle fetch direkt aufruft statt den ihr übergebenen Transport zu nutzen, und ein Verhaltensfall führt greenhouse — eine der 25 — durch den Wrapper und belegt, dass der Header tatsächlich hinausgeht. Nicht portiert, mit Begründung: liveness-browser.mjs (Wächter für die Wiederverwendung eines Headed-Playwright — web-ui steuert keinen Headed-Browser), check-jd-archive.mjs (kein Relay in web-ui), test-all.mjs (Testharness des Elternprojekts; die eigene vpFixtureEnv-Abweichung des Forks bleibt intakt), web/ (das eigene Frontend des Elternprojekts, wird nicht gespiegelt), Plugin-Manifeste und SIGNATURES.md (reine Repository-Dateien). batch/batch-runner.sh (Kaufmanns-Und-Zeichen vor der sed-Interpolation escaped) und jd-skill-gap.mjs (löst cv.md gegen die Datenwurzel auf) werden read-only weitergereicht — web-ui shellt in die Kopien des Elternprojekts, sodass beide hier ohne Code-Änderung landen, und jd-skill-gap macht das /api/jds/:id/skill-gap-Relay korrekter: cv.md wird jetzt von der Datenwurzel aufgelöst statt von dem Verzeichnis, in dem der Prozess gestartet wurde. Fork-Abweichungen nach dem Pull erneut geprüft: der kyrillische \p{L}-Fix in providers/telegram-channel.mjs, das eigene providers/telegram.mjs des Forks, hermes in web/src/lib/clis.ts. Quellenzahl unverändert bei 92 (87 EN + 5 RU), die Hilfe bleibt also unangetastet. 3042 → 3066 Tests.

[1.234.0] — 2026-09-15

Eltern-Parität — career-ops main @ 56cce8f (VERSION weiterhin 1.32.0, 58 Commits nach 1.32.0). Ein gespiegelter Fix über 15 Quellen, dazu ein Wächter, der seine Rückkehr verhindert.

Behoben

Eine einzige fehlerhafte Job-ID leert nicht mehr die gesamte Seite. encodeURIComponent wirft URIError bei einem einzelnen, unpaarigen UTF-16-Surrogat, und ein JSON-Payload kann genau das transportieren — JSON.parse('"\\uD800"') erhält es. Fünfzehn Quellen bauten die URL jeder Anzeige aus einer host-kontrollierten ID oder einem Slug innerhalb der Parse-Schleife, sodass eine einzige defekte Anzeige die Schleife abbrach und jede Stelle auf dieser Seite verlorenging; nichts wurde als Quellenfehler protokolliert, weil die Exception abwickelte, bevor der Catch-Block pro Unternehmen etwas zu melden hatte. Das Elternprojekt behob es in providers/_safe-url.mjs (safeEncodeURIComponent → null bei URIError, alles andere wird weitergeworfen) und leitete die ID jeder Schleife dadurch, wobei bei null nur die eine Anzeige verworfen wird. Dieser Helfer ist jetzt server/lib/sources/_safe-url.mjs, und dieselbe Änderung wird auf die dreizehn gespiegelten Quellen angewendet — alibaba, arbeitsagentur, bamboohr, feishu-jobs, garena, jibeapply, manfred, meituan, mokahr, phenom, thehub, tkms, vdab — sowie auf zwei nur in web-ui vorhandene Quellen, die der neue Wächter beim ersten Lauf zutage förderte, jobstreet und trudvsem, die dieselbe Schleifenform und denselben latenten Throw hatten. Der Umfang ist bewusst gewählt: Nur API-stammende IDs laufen durch den Helfer. Aus der Konfiguration stammende Werte behalten ihre strikte Kodierung und scheitern laut — das office von garena (ein portals.yml-Segment) bleibt beim werfenden urlSegment, während seine API-id ein null-zurückgebendes idUrlSegment erhält; corpName von csod und der SLUG_RE-validierte Slug von 4dayweek sind namentlich mit Begründung auf der Allowlist. Der Titel-Fallback von rheinmetall konnte bei einem fehlerhaften href werfen. Fehlt einer Karte die Überschrift, wird der Titel aus dem URL-Slug über decodeURIComponent rekonstruiert, das bei einer defekten Prozent-Sequenz im gescrapten href wirft — dieselbe Form wie beim Encode-Fall: Eine schlechte Zeile reißt die ganze Seite mit, hier eben beim Dekodieren. Es dekodiert jetzt defensiv und fällt auf den rohen Slug zurück. Die Quellen-Registry überspringt Dateien mit _-Präfix. Die Auto-Discovery importierte jede .mjs-Datei in server/lib/sources/ außer sich selbst und warnte bei allem ohne export const meta — der Helfer erzeugte also eine Warnung beim Boot. Die Konvention des Elternprojekts wird übernommen: Ein führender Unterstrich bedeutet keine Quelle; solche Dateien werden weder importiert noch beanstandet. Eine nicht deklarierte Datei ohne meta warnt weiterhin, und der Discovery-Test prüft nun beide Hälften (nachweislich rot gegen den alten Filter bestätigt). Das Site-Build-Skript sync-assets.mjs zählt Adapterdateien auf der Platte nach derselben Regel, sodass der Helfer seine Wache gegen stilles Wegfallen nicht mehr auslöst.

Anmerkungen

tests/sources-url-encoding-surrogate.test.mjs spiegelt die Suite des Elternprojekts in drei Teilen: den Vertrag des Helfers (ein gepaartes Surrogat — ein gewöhnliches Emoji — wird weiterhin kodiert; ein Throw aus dem eigenen toString des Werts ist der Fehler der aufrufenden Seite und wird durchgereicht), das Verhalten pro Quelle für alle fünfzehn (eine defekte ID + eine saubere ID → kein Throw, die saubere bleibt erhalten, die defekte wird verworfen, jeweils getrieben durch den exportierten reinen Parser der Quelle oder, bei thehub, durch dessen Fetcher mit einem simulierten Transport), sowie einen Wächter auf Quellenebene: Jede konvertierte Datei muss den Helfer importieren und aufrufen, keine Datei darf ihn referenzieren, ohne ihn zu importieren, und keine ungeprüfte Datei darf eine Job-URL mit einem nackten encodeURIComponent bauen — eine neue Quelle mit der alten Form lässt die CI mit der betroffenen Zeilennummer scheitern. Alle fünfzehn Quellen-Fälle wurden vor der Portierung nachweislich als fehlschlagend bestätigt. Alles andere im Eltern-Delta wird nicht portiert, mit Begründung: batch/batch-runner.sh --cli ist ein reines CLI-Flag (web-ui shellt nicht in den Batch-Runner); invite-match.mjs löst set-status.mjs jetzt von der Code-Wurzel auf — web-ui reicht das nicht weiter; company-funded.mjs erhielt intern dieselbe Safe-URL-Änderung, und die Form seiner --json-Ausgabe, die /api/company-funded parst, bleibt unverändert; test-all.mjs verschob nur ein Timeout-Budget (die eigene vpFixtureEnv-Abweichung des Forks bleibt intakt); web/ ist das eigene Frontend des Elternprojekts und wird nicht gespiegelt; AGENTS.md/SIGNATURES.md sind reine Repository-Dateien. Fork-Abweichungen nach dem Pull erneut geprüft: der kyrillische \p{L}-Fix in providers/telegram-channel.mjs, das eigene providers/telegram.mjs des Forks, hermes in web/src/lib/clis.ts. Quellenzahl unverändert bei 92 (87 EN + 5 RU), die Hilfe bleibt also unangetastet. 3022 → 3042 Tests.

[1.233.2] — 2026-09-10

Behoben — zwei CodeQL-Warnungen js/remote-property-injection hoher Schwere.

Behoben

Die Schleife, die gespeicherte Werte nach process.env überträgt, lief über Object.entries(safe) — ein aus dem Request-Body gebautes Objekt — mit einer expliziten Wache gegen Prototyp-Schlüssel darin. Der Code war sicher, denn safe wird zwanzig Zeilen früher ausschließlich aus KNOWN_KEYS befüllt; für sich gelesen sagt die Schleife jedoch schreibe in eine Eigenschaft, deren Name aus einem aus dem Request gebauten Objekt stammt, und CodeQL kann diese Einschränkung nicht über zwei Schleifen hinweg verfolgen. Sie läuft nun direkt über das konstante Array, der Eigenschaftsname ist damit nachweislich ein Modul-Literal. Das Verhalten ist identisch.

Anmerkungen

Der Test, der die alte Wache festschrieb, war ein Grep über den Quelltext. Ersetzt durch einen strukturellen (nachweislich rot gegen die alte Form) und einen Verhaltenstest, der __proto__ als rohes JSON sendet — ein Objektliteral hätte den Prototyp gesetzt statt die Eigenschaft anzulegen. 3021 → 3022 Tests.

[1.233.1] — 2026-09-10

Behoben — die Anleitung beschrieb das in v1.233.0 hinzugefügte Bedienelement nicht, und derselbe Abschnitt trug noch Anweisungen, die seit 200 Releases falsch sind.

Behoben

DOC-1 — der Eintrag „Standard verwenden (…)“ drückt den Zustand „Schlüssel nicht gesetzt“ aus; der Unterschied zur ausdrücklichen Wahl desselben Werts ist unsichtbar und folgenreich. Drei Punkte in der bestehenden ### Behavior-Liste ×17. DOC-2 — eine Tabellenzeile nannte gar keinen Schlüssel: In der Zelle stand (server uses default UA) und beschrieb HH_USER_AGENT, den v1.19.0 entfernt hat, und riet zur Registrierung auf dev.hh.ru — nutzlos seit v1.65.0. Alle drei veralteten Erwähnungen sind fort; die Fehlerbehebungszeile wurde umgeschrieben, nicht gelöscht: Seit Juli 2026 liefert hh.ru HTTP 451 an nicht-russische IPs. Die Schlüsseltabelle listet 6 von 45, während die Überschrift ein Verzeichnis verspricht. Ein Satz ×17 erklärt sie zur Auswahl und verweist auf #/config.

Anmerkungen

Das neue help-recognized-keys.test.mjs prüft die erste Spalte der Schlüsseltabellen gegen die lebende KNOWN_KEYS. 3018 → 3021 Tests; die Hilfe bleibt bei 32 H2 / 122 H3.

[1.233.0] — 2026-09-10

Behoben — drei Funde im Speicherpfad von #/config; CONFIG-3 wurde durch den Fix von v1.232.1 selbst verursacht.

Behoben

CONFIG-3 — eine bewusste Wahl, die zufällig dem vorbelegten Wert entsprach, wurde als „nicht angefasst” verworfen. initial vermengt zwei Herkünfte: aus der .env vorbelegt (Gleichheit heißt, das Feld wurde nicht bearbeitet — zu Recht übersprungen) und aus defaultValue vorbelegt (Gleichheit ist mehrdeutig: nie geöffnet, oder geöffnet und zugestimmt). Bei fehlendem HOST zeigt das Feld 127.0.0.1; genau diesen Wert einzutippen und zu speichern schickte {}, und der Schlüssel blieb aus — der einzige Weg führte über einen falschen Wert, Speichern, und dann den richtigen zurück. Behoben, indem neben initial nun seededFromFile festgehalten wird; stammt die Vorbelegung aus der Datei, ändert sich nichts, stammt sie vom Standard, wird dirty zum gültigen zweiten Signal. Das Entfernen eines Schlüssels meldete „· 0”, weil der Server nur Geschriebenes zurückgab. Er antwortet nun auch mit removed, und der Hinweis zählt beides. Ein Auswahlfeld ließ sich setzen, aber nie leeren: keines der 18 bot eine leere Option. Jedes beginnt jetzt mit „Standard verwenden (…)“. Ein neuer i18n-Schlüssel ×17.

Anmerkungen

Unit-Basislinie unverändert (3018); Browser 111 → 116, die fünf neuen Fälle fielen nachweislich gegen den alten Code durch.

[1.232.1] — 2026-09-10

Behoben — Speichern auf #/config schrieb Felder, die niemand angefasst hatte.

Behoben

CONFIG-2. v1.57.1 füllt ein nicht gesetztes Steuerelement mit seinem defaultValue vor, damit das Feld zeigt, was der Server tatsächlich verwendet; save() fragte dirty jedoch nur für Geheimnisse ab und schickte alle übrigen Felder immer mit. Der zur ANZEIGE hinterlegte Wert reiste damit als Wahl des Benutzers: Eine geänderte Auswahlliste schickte 28 Schlüssel und pinnte 18 fest, die niemand geöffnet hatte. Was sich ändert, ist der Status jeder Einstellung — von „nicht gesetzt, folge dem Projektstandard” zu „in .env festgeschrieben”.

Anmerkungen

Der Vergleich mit cfg.values genügt nicht: Ein nicht gesetztes Select zeigt seinen defaultValue, während der gespeicherte Wert '' ist — der Rumpf sank von 27 Schlüsseln auf 19, nicht auf einen. Verglichen wird nun mit dem, womit jedes Steuerelement befüllt wurde. Die Zahl geschriebener Schlüssel steht wieder im Hinweis. 105 → 111 Browsertests.

[1.232.0] — 2026-09-09

Behoben — zwei Defekte aus einem Browser-QA-Durchlauf; keiner stammt aus v1.231.5.

Behoben

PROFILE-1 — #/profile scrollte die ganze Seite seitwärts. Der Wert war ein anonymes <div> mit nur inline gesetzten Schriftstilen, sein berechnetes overflow-wrap blieb also normal. Eine LinkedIn-URL bietet CSS keine zulässige Trennstelle, folglich ist die min-content-Breite die gesamte Zeichenkette; Grid-Elemente haben min-width: auto und weigern sich, darunter zu schrumpfen — die Karte sprengte ihre Spur, dann die Zeile, die Hauptspalte und das Dokument (23 px echtes Scrollen bei 1280 px). Behoben mit einer gemeinsamen Klasse .card-value mit overflow-wrap: anywhere sowie .card-row > * { min-width: 0 }. anywhere, nicht break-word: nur anywhere zählt bei der min-content-Berechnung. CONFIG-1 — POST /api/config akzeptierte jeden Wert für ein Select-Feld. {"LLM_PROVIDER":"not-a-provider"} lieferte 200 und landete in der .env; danach schlug nichts fehl, weil der Resolver still auf einen Ersatz auswich. LLM_PROVIDERS wurde von env-config.mjs längst exportiert — der Validator zog es nur nie heran.

Anmerkungen

Nur LLM_PROVIDER wird geprüft, eine bewusste Verengung des Berichts: Die echte Domäne der Modelle gehört dem Anbieter und ändert sich zwischen unseren Releases. Ein bereits gespeicherter Wert wird geduldet — das Formular sendet bei jedem Speichern alle nicht geheimen Felder mit, die strenge Fassung fror die Seite also ein. Zudem wurde der Deskriptor für OLLAMA_API_KEY ergänzt. 3013 → 3018 Tests.

[1.231.5] — 2026-09-09

Sicherheit — sieben Hinweise geschlossen, darunter eine kritische Remote-Code-Ausführung. Keine Quelldatei wurde geändert.

Sicherheit

Aus Dependabot übernommen: multer 2.2.0 → 2.3.0 (hoch — Denial of Service durch ein Dateideskriptor-Leck beim Abbruch) und svgo 4.0.2 → 4.1.0 auf der Website (hoch und mittel — removeScripts ließ ausführbare Links durch Namensraum- und Inhaltsbehandlung passieren und bereinigte ausführbares HTML in foreignObject nur unvollständig). Dann fünf weitere, die der durch diese Merges ausgelöste Rescan zutage förderte. Jeder betroffene Bereich war bereits vom vorhandenen ^ abgedeckt, es brauchte also ein Update statt einer Manifeständerung: astro 7.1.0 → 7.3.2 — kritisch, Remote-Code-Ausführung über die AVIF-Bildoptimierung (behoben in 7.2.8), dazu eine Autorisierungsumgehung wegen fehlender Pfadsegment-Grenzprüfung (7.2.4); sharp 0.35.3 → 0.35.4 — hoch, libheif-Schwachstellen; und js-yaml 4.3.1 → 4.3.2 in beiden Manifesten — hoch.

Anmerkungen

„Nur Abhängigkeiten“ heißt hier nicht „risikofrei“. js-yaml parst jede Konfiguration, die die Anwendung liest, multer nimmt jeden Upload entgegen, und Astro ist eine Minor-Version weitergerückt. Geprüft: npm run test:ci 3013/3013, die Browser-Suite 4/4 und ein vollständiger Website-Neubau mit denselben 86 Seiten, allen 17 Changelog-Spiegeln und korrekter facts.json. npm audit: 0 Schwachstellen in beiden Manifesten.

[1.231.4] — 2026-09-07

Behoben — v1.231.3 hat die Folgen des Doctor-Klicks behoben, nicht den Moment. Erneut gemeldet, vom Handy und vom Desktop.

Behoben

withSpinner schrieb die Schaltfläche während der Anfrage weiterhin um. v1.231.3 ließ es die Kindknoten sichern und im finally wiederherstellen, und sie kamen auch zurück — doch der Beschäftigt-Hinweis führte nach wie vor replaceChildren mit dem Text '⏳ ' + original aus. Für die gesamte Dauer eines doctor.mjs-Laufs enthielt das 36-px-Quadrat also ⏳ 🩺Doctor und wurde zu einer breiten Pille, die die Zeile sprengte; auf dem Desktop erschien ein zusammengedrängtes ⏳🩺Doctor. Eine Schaltfläche mit Kindelementen wird nun gar nicht mehr umgeschrieben: Ihr Hinweis ist die Klasse .is-loading, die das Symbol jetzt per CSS gegen eine Sanduhr tauscht, sodass sich die Geometrie nicht ändern kann: 36 px im Ruhezustand, 36 px im Flug, 36 px danach.

Anmerkungen

Die Lehre betrifft die Zusicherung, nicht den Code. Die Prüfung aus v1.231.3 wartete, bis aria-busy verschwand, und maß erst dann — sie konnte also nur den Zustand sehen, den der finally-Block bereits repariert hatte. Die neue Prüfung hält das Promise selbst offen und misst mitten im Flug. 3012 → 3013 Tests.

[1.231.3] — 2026-09-07

Behoben — ein Klick auf Doctor zerlegte die obere Leiste auf dem Handy. Am Erscheinungstag von v1.231.2 vom Telefon aus gemeldet.

Behoben

UI.withSpinner plättete die Kinder der Schaltfläche, die es gerade dekorierte. Sein Beschäftigt-Hinweis lief über textContent: button.textContent sichern, '⏳ ' + original schreiben, zurückschreiben. Eine Zuweisung an textContent ersetzt alle Kinder durch einen einzigen Textknoten — und v1.231.2 hatte gerade jede Aktion der oberen Leiste in .btn-ico + .btn-label aufgeteilt und die Beschriftung unter max-width: 900px versteckt. Der erste Tipp auf Doctor zerstörte damit beide <span> endgültig: Den mobilen Regeln blieb nichts mehr zum Greifen, die Beschriftung kam als nackter Text zurück, und das 36-px-Quadrat wuchs zu einer 🩺Doctor-Pille, die über den Themenschalter ragte. Es heilte nicht von selbst — nur ein Neuladen stellte das Markup wieder her. Die Korrektur sichert die Kind-Knoten und stellt sie mit replaceChildren wieder her; reine Textschaltflächen, also die Mehrheit der 24 Aufrufstellen, verhalten sich exakt wie zuvor.

  • FIND-3 — das Suchfeld war auf einem 320-px-Telefon auf 8 px zusammengefallen. Die einreihige Leiste ließ den Searchbar als einziges flexibles Element übrig, und das stand im letzten Changelog wörtlich: „darf auf nichts schrumpfen“. Es schrumpfte auf nichts: 8 px Feld bei 320 px, 28 px bei 340 px — ein Zeichen eines 21 Zeichen langen Platzhalters. Klickbar und beschreibbar, aber als Feld nicht mehr lesbar. Kein min-width hilft: Bei 320 px trägt die Leiste 32 px Polsterung, ein 40-px-Menü und 162 px Aktionen, ohne 120 px zu vergeben. Deshalb versteckt es sich unter 420 px — der Breite, ab der das Feld von allein 100 px überschreitet — hinter einer Lupe und klappt beim Tippen über die ganze Leiste auf: 182 px bei 320 px, 222 px bei 360 px, 252 px bei 390 px, ohne Überlauf bei irgendeiner Breite. Die Schaltfläche behält einen einzigen zugänglichen Namen und meldet den Zustand über aria-expanded; der 🔍/✕-Tausch ist reines CSS — kein JS schreibt in diese Schaltfläche, genau das hatte Doctor in derselben Version zerlegt. Über 420 px ändert sich nichts. Gefunden von einem Browser-Regressionslauf gegen den Live-Build.

Anmerkungen

Dies ist dieselbe Fehlerklasse wie die in v1.231.2 vermerkte applyI18n()-Falle — Code, der textContent einem Element zuweist, das nun Kindelemente hat — nur über einen anderen Aufrufer erreicht. Die Lehre verallgemeinert sich: Wer den Inhalt eines Elements in Kind-<span> aufteilt, macht jeden bereits vorhandenen textContent-Schreiber auf diesem Element zu einem latenten Fehler — und diese Schreiber stehen nicht immer in der Datei, die man gerade bearbeitet. Zweifach abgesichert: node:vm über dem echten withSpinner und ein Klick in einem echten Browser bei 320 px. Beide fielen nachweislich gegen den alten Code durch. 3009 → 3012 Tests.

[1.231.2] — 2026-09-06

Behoben — die mobile Kopfleiste brach auf zwei Zeilen um, der Server lief mit einem älteren Elternprojekt, und die cvstart.ru-Weiterleitung lieferte 404 für jeden Pfad, der bereits eine Sprache nannte.

Behoben

  • Die mobile Kopfleiste ist wieder einzeilig, mit reinen Icon-Aktionen. Die Aktionsbuttons wurden auf eine zweite Zeile über die volle Breite gezwungen: Ein Telefon zeigte [☰ · Suche] über [🔔 🌙 Doctor Scan öffnen] — zwei breite Pillen auf eigener Zeile. Jetzt sind es 36-px-Quadrate (🩺 / ⚡) neben Glocke und Themenumschalter, und bei 320 px passt alles in eine Zeile. Gemessen, nicht geschätzt: Die Dokumentbreite entspricht dem Viewport bei 320, 360, 390 und 430 px sowie im gesamten Band 560–760 px, wo die Buttons zuvor die Suchleiste überlappten. Die Sprache beeinflusst das mobile Layout nicht mehr, weil das Label verborgen und der Button ein festes Quadrat ist; oberhalb von 900 px kehren die Labels unverändert zurück.
  • Der Server lief mit Elternprojekt 1.31.0, obwohl das Release gegen 1.32.0 gebaut wurde. /api/health auf dem Produktionsserver meldete parentVersion: 1.31.0. 95 Laufzeitdateien synchronisiert, zwei entfernt. Von Browser-QA auf der Live-Oberfläche gefunden.
  • cvstart.ru stellte jedem Pfad /ru/ voran, ohne zu prüfen, was schon da war. /ru/help wurde zu /ru/ru/help, /en/help zu /ru/en/help; beide 404. Das Erste bekommt man, wenn man eine URL von der Hauptseite kopiert; das Zweite sperrte englischsprachige Besucher vollständig von der englischen Fassung aus. Ein Pfad, der bereits eines der 16 Sprachpräfixe nennt, geht jetzt unverändert durch, en wird entfernt (Englisch liegt in der Wurzel), alles andere bekommt weiterhin /ru/.

Geändert

  • 98 überholte QA-Prompts nach qa/archive/superseded-prompts/ verschoben. qa/README.md dokumentiert seit v1.137.0, dass nur der Prompt des aktuellen Release oben liegt — die Regel wurde 95 Releases lang nicht angewandt.
  • Fünf weitere Prompts haben die oberste Ebene verlassen — sie bezeichneten sich als aktuell und waren es nicht. REGRESSION-FINAL.md nannte sich „der einzige maßgebliche, versionsunabhängige Regressions-Prompt“, doch seine §§11–15 sind fünf abgeschlossene Zyklus-Register (v1.55.x → v1.59.7), und seine angegebene Hilfe-Basislinie lautete 28 H2 / 103 H3 über 16 Sprachen gegenüber 32 / 122 und 17 heute: Ein QA-Agent, der ihm folgt, wäre bei §9 schon im ersten Schritt gescheitert. QA-REGRESSION-PROMPT.md trug einen Namen ohne Version und Inhalte von v1.226.0. QA-REGRESSION-PROMPT-v1.76.0-FULL.md war 155 Releases zurück fixiert, REGRESSION-PROMPT-FINAL.md schließt den Zyklus v1.58.52 → v1.59.10, und QA-FULL-REGRESSION.md duplizierte sie alle auf einer v1.131.1-Basis. Mit ihnen ging PARENT-SYNC-WORKLIST-v1.26.0.md (das Elternprojekt steht inzwischen bei 1.32.0), und das datierte UX-AUDIT-2026-07-06.md wanderte nach reports/. Vier Treiber bleiben, deren eigene Basislinien auf die tatsächlichen Werte von v1.231.2 aktualisiert wurden. qa/README.md ist neu geschrieben und beschreibt den Ordner, den es gibt.

Hinweise

  • Zwei Spezifitätsfallen sind jetzt in app.css kommentiert: Die Desktop-Regeln stehen weiter unten als der max-width: 900px-Block, gewinnen also bei gleicher Spezifität — der mobile gap blieb stumm bei 24 px (was 320 px überlaufen ließ) und das Icon blieb verborgen (leere quadratische Buttons).
  • applyI18n() weist el.textContent zu, deshalb wanderte der i18n-Schlüssel auf ein inneres <span>; am Button hätte er das Icon bei jedem Sprachwechsel gelöscht.
  • Der erste rsync des Elternprojekts brach mitten im Transfer ab an einer lokal nicht mehr vorhandenen Datei und meldete trotzdem Erfolg. Eltern-Deploys werden nun per Prüfsumme verifiziert, nie per Exit-Code.

[1.231.1] — 2026-09-06

Behoben — der Zählerdurchlauf aus v1.231.0 hat den Kontonamen in allen Links überschrieben.

Behoben

  • Jeder Fighter90-Link wurde zu Fighter92. Der Quellenzähler-Durchlauf in v1.231.0 ersetzte 90 durch 92 unter einer Ziffern-Grenzprüfung, die 290 und 901 korrekt auslässt, aber die 90 in Fighter90 trifft. 562 Links in allen 17 READMEs, allen 17 Hilfepaketen und den Hilfe-Spiegeln der Website, dazu 138 im Wiki, zeigten auf ein nicht existierendes Konto. Von einem Nutzer bemerkt — am fehlenden Bild.
  • Fünf historische Release-Verweise verrutschten. Der 85 → 87-Durchlauf machte aus v1.85.0 ein v1.87.0 und erfand damit eine nie veröffentlichte Version.

Hinweise

  • Eine Ziffern-Grenze genügt nicht: Sie schützt eine Zahl vor benachbarten Ziffern, nicht davor, Teil eines Bezeichners oder einer Versionszeichenkette zu sein. Die Zähler selbst stimmen: 92 Quellen, 87 EN-Adapter, 3009 Tests.
  • Das Elternrepository blieb unberührt.

[1.231.0] — 2026-09-05

Hinzugefügt — zwei Scan-Quellen aus dem Eltern-career-ops v1.32.0 (Collage, Telegram streng), Gems REST-Modus und ein Defekt, den die Portierung im Code des Elternprojekts selbst zutage förderte.

Hinzugefügt

  • Zwei neue Quellen, 90 → 92 (87 EN + 5 RU), ALL_ADAPTERS 85 → 87. Beide ohne Token.
    • Collage (provider: collage) — die öffentliche Job-Site-API von Collage HR. Das Pfadsegment ist die Adresse, die der Mandant selbst gewählt hat, kein Slug aus dem Firmennamen: Sie wird aus api: oder aus einer secure.collage.co/jobs/<Adresse>-URL gelesen und nie geraten — eine geratene Adresse würde das Board eines anderen scannen.
    • Telegram (streng) (provider: telegram-channel) — liest dieselben t.me/s/<Kanal>-Vorschauen wie die vorhandene Quelle telegram, geht aber den umgekehrten Handel ein: Ein Beitrag wird nur dann zur Zeile, wenn er einen Arbeitgeber nennt UND auf eine Stellenseite verlinkt. Digests, Beiträge mit verborgenem Arbeitgeber und reine Kontaktbeiträge fallen weg. Geringe Abdeckung mit Absicht — stromaufwärts gemessen passieren 32-77% der Beiträge — aber jede Zeile trägt einen echten Arbeitgeber und einen echten Link. Beide werden ausgeliefert, wie beim Elternprojekt.
  • Gem erhielt einen optionalen REST-Modus neben dem GraphQL-Pfad (api: https://api.gem.com/job_board/v0/<board>/job_posts). Er wird nie aus einer Board-ID abgeleitet, und der GraphQL-Pfad bleibt die Vorgabe. Portiert aus #3783.

Behoben

  • Der Ortsfilter von telegram-channel sah kein Kyrillisch und nannte Städte als Arbeitgeber. Das LOCATIONISH_RE des Elternprojekts nutzte \b, eine reine ASCII-Wortgrenze, die neben Kyrillisch nie greift: Alle russischen Alternativen darin waren unerreichbar, und Senior Engineer | Москва lieferte „Москва“ als Arbeitgeber. Genau die Feldverwechslung, die die ganze Zuordnungsregel verhindern soll — und sie traf die Hauptzielgruppe des Providers: Ein russischsprachiger Kanal schreibt den Ort kyrillisch. Die web-ui-Portierung nutzt Unicode-Prüfungen (\p{L} / \p{N}), dasselbe, was das Elternprojekt eine Konstante tiefer in ROLE_WORD_RE bereits tut. Durch einen Test festgehalten, der an der ASCII-Form scheitert. Stromaufwärts noch nicht behoben.

Hinweise

  • Die Workday-Facetten-Wiederherstellung des Elternprojekts (#3851, #3874) brauchte keine Portierung — web-ui paginiert Workday überhaupt nicht, ein einziger POST bei offset: 0.
  • providers/_types.js änderte sich nur in der Dokumentation.
  • Tests: 2962 → 3009 (+47).

[1.230.0] — 2026-09-05

Geändert — drei Provider-Korrekturen aus dem Eltern-career-ops v1.32.0 sowie ein Defekt, den die Portierung selbst zutage gefördert hat.

Hinzugefügt

  • Welcome to the Jungle nimmt einen serverseitigen Algolia-filters-Ausdruck entgegen (wttj: { filters: "offices.country_code:FR AND contract_type:full_time" }). Ein Stichwort allein grenzt ein globales Board nicht ein: Welche max_hits Treffer zurückkommen, entscheidet Algolias Relevanzranking und nicht die Filter des Scanners — unsere greifen erst danach, auf dem bereits Eingetroffenen, sodass alles jenseits der Obergrenze unsichtbar bleibt, wie gut es auch passt. Ein Filter verkleinert die Menge, statt sie neu zu sortieren, und die Obergrenze je Abfrage steigt deshalb von 200 auf 1000. Mit einem Filter wird queries optional: Die leere Abfrage bedeutet „alles, was den Filter passiert“. Portiert aus dem Elternprojekt #3757.

Behoben

  • Radancy warnt nicht mehr „truncated“, wenn ein Mandant schlicht weniger Stellen ausliefert, als sein eigenes Banner behauptet. Das Elternprojekt hat data-total-results gegen neun reale Mandanten geprüft: Fünf stimmten überein, doch vier — ein kleiner und ein sehr großer — lieferten 10 bis 56 % weniger eindeutige Stellen als angekündigt, ohne eine einzige doppelte Zeile. Das Banner überzeichnet, was der Suchindex des Mandanten tatsächlich ausliefert; der Abgleich mit dem gesammelten Zähler war deshalb bei der Mehrzahl der Boards ein Fehlalarm. Die Warnung erscheint nur noch, wenn unser eigenes max_jobs/max_pages den Durchlauf gestoppt hat — der einzige Fall, auf den der Aufrufer reagieren kann; totalResults bleibt als Kontext in der Meldung und entscheidet nie. Portiert aus #3839.
  • Radancy umgeht den Cache bei jeder JSON-Fragment-Anfrage. Eine Cache-Schicht vor dieser Route wiederholt bei manchen Mandanten veraltete Antworten: Dieselbe Stelle taucht Seiten später erneut auf, während eine andere nie ausgeliefert wird — stromaufwärts neun von neun Läufen reproduziert. Cache-Control/Pragma-Header änderten nichts; ein zufälliger Parameter je Anfrage behob es. Er muss je Aufruf eindeutig sein, niemals aus der Seitenzahl abgeleitet: Ein deterministischer Parameter gäbe dem Cache weiterhin einen stabilen Schlüssel und damit dieselbe stabile, falsche Zuordnung. Portiert aus #3839.
  • Greenhouse-Bürolisten sind deterministisch sortiert. Greenhouse sichert die Reihenfolge von offices[] zwischen Antworten nicht zu, und die aus dem /offices-Baum gewonnene Menge folgt ihrer Durchlaufreihenfolge. Beide landen im Feld location der Stelle, sodass ein Board, das seine Büros umordnet, diese Zeichenkette neu schrieb — und eine unveränderte Stelle als neu gelesen wurde. Das Elternprojekt korrigierte die Menge (#3839); web-ui hatte eine zweite Ausprägung desselben Defekts, die es dort nicht gibt — den offices[0]-Rückfall, der greift, wenn eine Stelle keine eigene Ortsangabe trägt — und beide sind nun sortiert.

Hinweise

  • Die iCIMS-Korrektur des Elternprojekts (#3728) brauchte keine Portierung. Sie liest alle jobLocation-Einträge aus dem JSON-LD einer Detailseite, weil manche Mandanten einen komplett UNAVAILABLE-Eintrag vor die echte Adresse setzen. web-uis iCIMS nimmt den Ort aus dem HTML der Listenseite und öffnet nie eine Detailseite.
  • Die Änderung an _http.mjs brauchte keine Portierung. Sie zieht isRefusedRedirectError für einen zweiten Konsumenten heraus, den web-ui nicht hat; das Verhalten von isRetryableError bleibt gleich.
  • Die ortsbewusste Deduplizierung (#3751) wird nicht gespiegelt — sie liegt in der scan.mjs-Pipeline der Eltern-CLI.
  • Registry unverändert: 90 Quellen (85 EN + 5 RU). Tests: 2956 → 2962 (+6).

[1.229.0] — 2026-09-03

Hinzugefügt — vier Scan-Quellen aus dem Eltern-career-ops v1.31.0: Built In, Feishu Jobs, Garena und MokaHR.

Hinzugefügt

  • Vier neue Quellen, 86 → 90 (85 EN + 5 RU), ALL_ADAPTERS 81 → 85, alle ohne Token. Built In deckt alle neun Märkte des US-Tech-Boards ab und bringt keine Standardabfrage mit: ein Eintrag ohne queries: und categories: scannt nichts und sagt es — eine geteilte Quelle darf niemandes Suchbegriffe tragen; ein nicht gelisteter Markt wird abgelehnt, nie stillschweigend durch das nationale Board ersetzt. Feishu Jobs akzeptiert genau zwei Hostformen, die unterschiedliche Pfade zur Stellenseite verwenden. Garena ist Einzelunternehmen: office formt den Link, nicht die Liste. MokaHR liefert eine verschlüsselte Antwort (AES-128-CBC-Umschlag, Schlüssel reist mit) — Obfuskation, keine Sicherheit, aber ohne Entschlüsselung liest man nichts.

Behoben

  • htmlToText konnte einen unvollständigen Tag-Öffner in den Text durchlassen. safe &lt;img src=x onerror=1 kam als safe <img src=x onerror=1 heraus. Jetzt folgt auf jede Dekodierung ein Strip, und der abschließende Öffner verliert seine spitze Klammer. Vom Elternprojekt portiert (#3491).

Hinweise

  • Aus dem Workday-Fix des Elternprojekts wurde nichts portiert. Er rettet Tenants, deren CXS-Backend die Paginierung bei Offset 2000 deckelt — web-ui paginiert Workday gar nicht (ein POST bei offset: 0, 100 Zeilen).
  • Die Fixes an verify-cv-facts und merge-tracker brauchten keinen Port: web-ui führt diese Skripte aus dem Eltern-Checkout aus.
  • Tests: 2909 → 2956 (+47).

[1.228.5] — 2026-08-30

Behoben — die gemeldete Version beschrieb die Datei auf der Festplatte, nicht den laufenden Code.

Behoben

  • /api/health las package.json bei jeder Anfrage neu, sodass ein veralteter Prozess eine Version meldete, die er nicht ausführte. Die QA fand eine lokale Instanz, die version: 1.228.4 antwortete und zugleich 404 für /api/ping lieferte — eine in 1.228.3 hinzugefügte Route: vor dem Deployment gestartet, den alten Code ausliefernd und die Version der neuen Datei meldend. Genau so sieht ein Deployment aus, das Dateien kopiert und nie neu startet: die eine Zeichenkette, die eine Betreiberin prüft, ist die, die das Problem nicht sehen kann. Dieselbe Klasse wie der Symlink aus v1.228.1, bei dem rsync mitten im Transfer abbrach und dennoch Erfolg meldete.

    Die Version wird jetzt beim Laden des Moduls erfasst und beschreibt damit den laufenden Code. Ein veralteter Prozess meldet die alte Version — die Wahrheit, sofort sichtbar. parentVersion bleibt pro Anfrage: Es beschreibt den Checkout des Elternprojekts, den dieser Prozess nicht lädt und der sich unter einem laufenden Server legitim ändern kann.

[1.228.4] — 2026-08-30

Behoben — die nicht authentifizierte Lebendigkeitssonde arbeitete bei jeder Anfrage auf der Festplatte.

Behoben

  • GET /api/ping las und parste package.json bei jedem Aufruf. Es ist der einzige ohne Anmeldedaten erreichbare Endpunkt, also ist ein Festplattenzugriff plus JSON-Parsing pro Anfrage ein Denial-of-Service-Hebel, den man jedem in die Hand gibt — CodeQL meldete fehlende Ratenbegrenzung. Die Version wird jetzt einmal bei der Routenregistrierung gelesen; sie kann sich ohne Neustart ohnehin nicht ändern, also macht der Handler gar keine E/A. Ratenbegrenzung hätte den Schaden gedeckelt; die Arbeit zu entfernen entfernt den Hebel.
  • Die Maskierung des echten Namens der Profilinhaberin oder des Profilinhabers außerhalb von Loopback ist jetzt durch einen Test festgehalten. /api/health ersetzte ihn bereits durch hidden — aber nur, weil diese Zeile die hidden ?? value-Wache mit dem Projektstamm teilt. Nichts prüfte das, also hätte eine Änderung mit eigenem Wert den Namen einer realen Person unbemerkt ins LAN gegeben.

[1.228.3] — 2026-08-30

Fixed — the docs assistant could not answer in English, Telegram company names arrived truncated, and a monitor had no way to see the service was up.

Fixed

  • The docs assistant answered “no matching help sections were found” in English while the same question worked in Russian. Retrieval was fine; the context builder was not. buildAskPrompt did if (ctx.length + sec.body.length > MAX_CONTEXT) break — a break, so one oversized section ended the loop and left the context empty, and the assistant truthfully reported it had nothing. The English §5 had grown to 16 081 bytes against a 14 336-byte budget (partly because v1.228.0 added telegram_channels to it); the Russian §5 was 13 009 and fit. Same ranking in both languages — §5 was first — only the size differed.

    Two changes: a section past 6 KB is now split at its ### boundaries, so telegram_channels is its own retrievable chunk (1 340 bytes) that ranks first in both languages; and an oversized section is skipped or truncated rather than ending the loop. The guide went from 32 chunks to 75.

  • A Telegram company label yielded the sentence after it, not the name. Компания: FinCore Technology (продуктовая разработка, высоконагруженные системы) became FinCore Technology (продуктовая разработка, высоконагруженны — capped at 60 characters, cut mid-word. Three of 141 rows. The name now ends at the first separator that introduces a description — a spaced dash (so Coca-Cola survives), a comma, semicolon, pipe or opening parenthesis — and any remaining cap falls on a word boundary. This source attributes a company as fact; a truncated sentence is as wrong as a guessed name, only less obviously so.

Added

  • GET /api/ping — a liveness probe that leaks nothing. { ok, version } and no more. /api/health reports absolute paths, the profile owner’s real name and which API keys are set: legitimate for an authenticated operator, never for the public internet. A monitor behind a Basic-auth gate previously had no way to see the service was up at all.

[1.228.2] — 2026-08-30

Fixed — a two-word search in /api/scan-results matched the phrase, not the words, and under-reported badly.

Fixed

  • ?q= now matches every term independently instead of the literal phrase. The query was a single includes(q) over title + company + location, so продакт менеджер found only rows carrying those two words in that exact adjacency — 32 of the 162 that carry both. Менеджер продукта and Менеджер по развитию продукта were invisible to it: the same words, reordered or with filler between them.

    This is how a narrow match becomes a wrong answer. The Telegram assistant asked for all Продуктовый менеджер reported 9 — faithfully, because 9 was what the endpoint said. The count it quoted was honest; the endpoint’s was not. Word order and the words between are not something a person typing two words intended to pin.

    Matching is AND across terms, so widening precision costs nothing: a row must still carry every term. менеджер разработчик returns zero rather than the union.

[1.228.1] — 2026-08-29

Fixed — a dangling symlink in the repo that silently truncated deploys.

Fixed

  • .claude/skills/refero-design was a tracked symlink to a path that has never existed here. It pointed at ../../.agents/skills/refero-design, which resolves to .agents/ at the repo root; there is no such directory, and the link arrived in the v1.118.0 parity pack from a machine where the surrounding folders nested differently. Anyone cloning the repo has been getting a broken link ever since.

    It was not inert. rsync aborts on it mid-transfer — a deploy today reported success while the server kept running the previous version, and the mismatch only surfaced because the version was checked afterwards. The parent project’s node test-all.mjs also crashes outright when a web-ui checkout sits inside its tree, because its fixture copy walks the working directory and cannot stat the link. Nothing referenced it: the only refero matches in the repo are the Polish word preferowany.

[1.228.0] — 2026-08-29

Hinzugefügt — Telegram-Kanäle als Scan-Quelle und ein paginierter Abfragemodus für /api/scan-results.

Hinzugefügt

  • Telegram-Kanäle sind jetzt eine scannbare Quelle. Ein telegram_channels:-Block in portals.yml listet sie; jeder wird über die öffentliche Vorschau https://t.me/s/<Kanal> gelesen. Registry: 85 → 86 Quellen (81 EN + 5 RU), ALL_ADAPTERS 80 → 81.

    Telegram veröffentlicht kein RSS, und die Bot-API kann keinen Kanal lesen, den der Bot nicht administriert — beide naheliegenden Wege scheiden aus. Die /s/-Vorschau ist schlichtes servergerendertes HTML: ohne Anmeldung, ohne JS, ohne Cookie. An 16 Kanälen geprüft: 15 lieferten je 20 Beiträge auf ein blankes GET.

    channel: akzeptiert jede Schreibweise. Der Parser verankert sich an den drei Dingen, die ein Telegram-Redesign zuletzt verschieben würde: data-post, den Textcontainer und das ISO-Datum <time datetime>.

  • GET /api/scan-results erhält einen paginierten Abfragemodus. ?q=<Text>&limit=50[&region=ru|en][&set=filtered|fresh][&offset=N] liefert { total, returned, offset, limit, set, rows }, wobei total die Zahl VOR der Paginierung ist. Ohne Parameter gibt die Route weiterhin den vollen Snapshot zurück — die #/scan-Tabelle hängt an dieser Form.

Behoben

  • Der Telegram-Assistent antwortete aus einem Bruchteil der Daten. Sein Skill zeigte bereits auf /api/scan-results, doch die Route konnte nur den ganzen Snapshot liefern — rund 2 MB — was nicht in den Kontext eines Agenten passt. Auf „finde alle Product Manager“ meldete er 9 Treffer, wo der Snapshot Hunderte hielt, und sagte, er „arbeite mit einem begrenzten lokalen Index“. Das stimmte: Er sah den Endpunkt, konnte ihn aber nicht verarbeiten. Mit dem paginierten Modus kostet dieselbe Frage 2 KB und liefert ein ehrliches total. Der Skill wurde ebenfalls neu geschrieben: Er verlangte, die neuen Beiträge zusammenzufassen, zeigte also auf fresh statt auf filtered.

Hinweise

  • Ein Kanalbeitrag ist Prosa, kein Stellendatensatz. Es gibt keine strukturierten Felder: Der Titel kommt aus der ersten inhaltlichen Zeile, Firma/Ort/Gehalt aus ausdrücklichen Labels. Echte Stellen von Werbung und Digests zu trennen bleibt dem vorhandenen title_filter überlassen — demselben, der für andere Quellen bereits eingestellt ist. Das sind Hinweise, keine Liste.
  • Die Firma wird nie geraten. Ohne Label wird die Zeile dem Kanal zugeschrieben (@rabotaphp): Ein falscher Arbeitgeber landet im Tracker als Tatsache.
  • Ein leeres Parse-Ergebnis ist ein Fehler, kein „keine Stellen“. t.me antwortet bei einem privaten oder fehlenden Kanal mit einer Weiterleitung; null Zeilen zurückzugeben läse sich als ruhiger Tag und verstecke einen Tippfehler in der Konfiguration für immer. jobGeeks tut genau das (HTTP 302) und ist bewusst nicht konfiguriert.
  • Die Remote-Erkennung nutzt eine Unicode-Wortgrenze statt \b, das nur ASCII kennt — dieselbe Falle wie beim Titelfilter in v1.227.3, hier vor der Auslieferung von einem Test gefangen.
  • 15 Kanäle konfiguriert und live geprüft: 299 Beiträge, 15/15 Kanäle. 30 neue Tests. Tests: 2865 → 2895.
  • The README test counts jumped by hundreds, and no, this release did not add them. The localized READMEs carried unit-test totals frozen at 1856 / 1945 / 1955 / 549 / 419 / 284 / 2527 depending on the file — each stale since a different release, and several contradicting the badge at the top of the same page. They now all read 2895, verified by running the suite rather than by copying a badge. Playwright (70 / 90 / 12 → 101), e2e smoke (20 → 21), route modules (12 / 32 → 37), smoke flows (12 → 22) and the test-file count (218 → 329) were stale in the same way. The Total row also repeated the unit count, so it under-summed the three suites below it; it now reads the real sum (3040).

[1.227.5] — 2026-08-29

Behoben — der regionale Scan trieb den Server in den Speicherüberlauf, und eine Playwright-Suite flackerte an ihrem eigenen Abbau.

Behoben

  • Der regionale Scan riss den Server per OOM um. ru-scanner sammelte jeden Rohtreffer aus jeder Abfrage und dedupliziert und filterte erst ganz am Ende. Die Abfrageliste besteht bewusst aus Beinahe-Synonymen (Golang, Go разработчик, Golang разработчик, Senior Go…), also kommt dieselbe Stelle einmal pro Abfrage zurück und das Array hielt ein Vielfaches der eindeutigen Zahl — samt Beschreibungen, die den Großteil eines Job-Objekts ausmachen und von den Filtern ohnehin fast vollständig verworfen werden. Gemessen: 742 MB Heap bei einem echten Lauf mit 21 Abfragen. Der Server deckelt Nodes Heap bei 490 MB (V8 bemisst ihn an den 956 MB RAM der Maschine), also riss der Scan den Dienst mit FATAL ERROR: Reached heap limit um — viermal an einem Tag. Deduplizierung und Filterung laufen jetzt pro Abfrage, was denselben Lauf auf 177 MB brachte, minus 76 %. Die ausgewiesenen Zahlen bleiben gleich: die eindeutige Gesamtzahl trägt ein Set aus URL-Strings.
  • net::ERR_SOCKET_NOT_CONNECTED ließ die CI zufällig scheitern. Drei Playwright-Suites rufen window.stop() auf, um laufende Requests abzubrechen, bevor sie prüfen, dass keine Konsolenfehler auftraten. Chromium meldet diesen einen Vorgang unter drei Namen, je nach Zustand des Sockets: ERR_ABORTED beim Abbruch, ERR_EMPTY_RESPONSE unterwegs, ERR_SOCKET_NOT_CONNECTED, wenn der Socket schon weg war. Der gemeinsame Filter kannte nur die ersten beiden. Aufgetaucht im Locale-Durchlauf bei tr, während playwright-forms und playwright-smoke einen unglücklichen Lauf vom selben Fehler entfernt waren.

Hinweise

  • Warum erst jetzt. Die Abfrageliste wuchs von 14 auf 21, als Product-Manager-Suchen hinzukamen. Der Speicherbedarf der alten Implementierung skalierte mit der Gesamttrefferzahl, also sprengten +50 % Abfragen die Obergrenze der Maschine.
  • Die Dedup-Änderung erhält das Verhalten, und das ist getestet. tests/ru-scanner-dedup-order.test.mjs nagelt die heikle Richtung fest — ein späteres Duplikat, das die Filter NICHT besteht, muss ein früheres verdrängen, das bestanden hatte — und fährt 200 randomisierte Äquivalenzprüfungen gegen die alte Implementierung.
  • Der Konsolenfilter bleibt eng. Nur die Familie abgebrochener Requests ist harmlos; abgelehnte Verbindung, DNS-Fehler, Reset, 5xx und JS-Ausnahmen lassen die Zusicherung weiterhin scheitern.
  • Zwei Hypothesen wurden geprüft und verworfen statt ausgeliefert: ein Caddy-Timeout (Konfiguration sauber, flush_interval -1 für SSE korrekt) und Nodes voreingestelltes 5-Minuten-requestTimeout (eine verkleinerte Reproduktion zeigte, dass es eine lange Streaming-Antwort nicht abbricht). Der eigentliche Beleg war Reached heap limit im Journal. Tests: 2860 → 2865.

[1.227.4] — 2026-08-29

Behoben — word: / stem: wurden von content_filter weiterhin ignoriert; v1.227.3 hatte nur den Titelfilter behoben.

Behoben

  • content_filter beachtete die Präfixe nicht. v1.227.3 brachte title_filter die word: / stem:-Präfixe des übergeordneten Projekts bei, ließ content_filter aber bei seinem eigenen rohen lower.includes(k) — ein präfigierter Eintrag wurde dort weiterhin mit dem wörtlichen Text "word:java" verglichen: derselbe stille No-op, einen Filter weiter. content_filter liest die BESCHREIBUNG, und sein Standard war immer ein einfacher Teilstring; deshalb verwirft ein blankes negatives java alles, was „JavaScript“ auch nur erwähnt. Das Präfix ist der Ausstieg für einen einzelnen Eintrag. Portiert wurde compileContentKeyword des übergeordneten Projekts: dieselbe Präfix-Maschinerie, aber bewusst ohne automatische Verankerung kurzer Schlüsselwörter — der Titelfilter verankert 2–3-Buchstaben-Abkürzungen, weil „COO“ in „Coordinator“ immer falsch ist, während eine kurze Folge in einem Fließtext regelmäßig gemeint ist (aws, gcp, sql, go).

Hinweise

  • Im KI-Review der v1.227.3-PR gefunden, das bemerkte, dass der Code nicht abdeckte, was sein eigener Kommentar behauptete: dort stand „title_filter / content_filter“, verdrahtet war nur der Titelpfad. Der Kommentar trennt das jetzt ausdrücklich: beide Filter beachten die Präfixe, nur der Titelfilter verankert Abkürzungen.
  • Ebenfalls aus jenem Review geprüft und unverändert: der Wechsel der 2–3-Buchstaben-Regel von \b zur Unicode-Grenze verhält sich für ASCII identisch und ist nur dort strenger, wo er es sein soll — COO_lead und coo1 bleiben erhalten (Unterstrich und Ziffer sind in beiden Fällen Wortzeichen), Coordinator bleibt, „Директор COO“ und „VP «Продукт»“ werden korrekt verworfen. 14 Grenzfälle geprüft.
  • Unpräfigierte content_filter-Einträge behalten ihr bisheriges Matching Byte für Byte, eine leere oder fehlende Beschreibung passiert weiterhin, und ein leeres word: / stem: trifft nichts statt alles. 6 neue Fälle in tests/content-filter.test.mjs. Tests: 2852 → 2858.

[1.227.3] — 2026-08-29

Behoben — die Titelfilter-Präfixe word: / stem: wurden stillschweigend ignoriert, wodurch diese Filterzeilen wirkungslos blieben.

Behoben

  • web-ui implementierte die word: / stem:-Präfixe des übergeordneten Projekts nicht. title_filter (und content_filter) vergleichen standardmäßig Teilstrings ohne Groß-/Kleinschreibung — deshalb verwirft ein blankes negatives intern auch „International Product Manager“ und „Internal Tools Engineer“. Statt diesen Standard umzudrehen — ein Bruch für jede konfigurierte Installation — machte das übergeordnete Projekt Genauigkeit pro Eintrag optional: word:intern trifft nur das ganze Wort, stem:agent muss ein Wort beginnen und darf weiterlaufen (trennt „Agentforce“ von „Reagents“). web-ui hatte keines von beiden und verglich den Eintrag mit dem wörtlichen Text "word:intern", der in keinem Stellentitel vorkommt. Die Zeile wurde zum stillen No-op: dieselbe portals.yml filterte über die CLI korrekt und hier gar nicht, sodass jede ausgeschlossene Praktikumsanzeige weiterhin in der #/scan-Tabelle landete. Aus dem title-keywords.mjs des übergeordneten Projekts portiert, samt der Unicode-fähigen Wortgrenze (\p{L}\p{M}\p{N}_) statt \b, das nur ASCII kennt und in akzentuierten oder kyrillischen Titeln mitten im Wort traf.

Hinweise

  • Gefunden beim Prüfen einer echten portals.yml, die word:intern gerade genau deshalb übernommen hatte, damit ein blankes intern „International Product Manager“ nicht mehr verwirft. In der CLI wirkte das Präfix; web-ui behielt still jede Praktikumsanzeige. Das Verhalten ist jetzt identisch.
  • Die Standards bleiben unverändert. Ein Schlüsselwort ohne Präfix behält sein bisheriges Matching: Teilstring für Phrasen und alles mit Nicht-Buchstaben (.NET, SAP , L&D), Wortgrenzen-Anker für 2–3-Buchstaben-Abkürzungen. Ein leeres word: / stem: gilt als Tippfehler und trifft nichts — als Negativ träfe ein leeres Muster jeden Titel und würde einen ganzen Scan wegen eines verirrten Doppelpunkts kippen.
  • Die Abkürzungsregel nutzt nun dieselbe Unicode-Grenze: zuvor \b, weshalb coo und word:coo bei Nicht-ASCII-Titeln auseinanderliefen — zwei Schreibweisen einer Regel.
  • 6 neue Fälle in tests/title-filter.test.mjs. Tests: 2846 → 2852.

[1.227.2] — 2026-08-29

Behoben — #/config scrollte auf schmalen Telefonen seitwärts.

Behoben

  • MOBILE-1: #/config lief auf Viewports ≤ 352 px horizontal über. Jedes Select-Feld trug ein Inline-min-width: 300px. Ein Inline-Stil schlägt das Stylesheet, also konnte das Control nicht mit seinem Container schrumpfen: Bei 320 px Viewport lag die rechte Kante des Selects bei 353 px, die Seite bekam eine horizontale Scrollleiste und Controls wanderten über den rechten Rand hinaus. Betroffen waren alle 18 Provider-/Modell-Selects (cfg-llm-provider und sämtliche cfg-*-model). Jetzt min-width: min(300px, 100%) + max-width: 100%, sodass die komfortablen 300 px nur greifen, wenn Platz ist. Geräte an oder unter der Schwelle: iPhone SE (1. Gen., 320 px), ältere Android-Geräte, Außendisplay des Galaxy Fold.

Hinweise

  • Warum alle früheren Mobil-Durchläufe das übersahen. 360 px und 375 px haben gerade genug Spielraum, den Überlauf zu verbergen, und die Playwright-Suites laufen in einem 1280 px breiten Fenster. Nichts in der CI hatte je einen Viewport schmaler als 375 px vermessen. Das neue tests/playwright-narrow-viewport.mjs läuft die formulartragenden Routen bei 320 px ab und schlägt bei jedem horizontalen Überlauf fehl — mit dem breitesten Störenfried in der Meldung; ein zweiter Fall pinnt fest, dass kein #/config-Select über den Viewport hinausragt. Zuerst wurde verifiziert, dass er den Fehler reproduziert (scrollWidth 353 > clientWidth 320, 18 Selects mit rechter Kante 353). Playwright: 99 → 101.
  • Bekannt und bewusst noch nicht abgesichert: 280 px (Außendisplay des Galaxy Fold) läuft weiterhin um ~5 px über, aus anderer Ursache — langer Inhalt in den <details>-Blöcken der Konfigurationskarte und die 256 px breite Sidebar gegen einen 280-px-Viewport, nicht das Inline-min-width, das dieses Release behebt. Ein Umbruch auf eine Breite, die kein verbreitetes Telefon meldet, ist eigene Arbeit; die WIDTHS-Liste der Suite ist dafür vorbereitet.
  • Ebenfalls korrigiert: der QA-Prompt von v1.227.1 nannte die Follow-up-Route #/modes/followup. Mode-Seiten registrieren den nackten Slug (Router.register(cfg.slug)), die Route heißt also #/followup; #/modes/followup liefert korrekt 404. Nur Dokumentation.
  • Unit-Suite unverändert bei 2846 (die neue Abdeckung liegt auf Browser-Ebene).

[1.227.1] — 2026-08-29

Behoben — die Quellen-Aufschlüsselung in der Hilfe ging nicht auf, und das GitHub-Banner wurde in der App als rohes Markup ausgegeben.

Behoben

  • §17 nannte eine Summe und eine Aufschlüsselung, die nicht zusammenpassten. Alle Hilfe-Bundles sagten „die server/lib/sources/-Registry liefert 85 Adapter — 78 englische + 5 russische“. 78 + 5 = 83, nicht 85; in ja/ko war die Abweichung schon bei 77 (= 82). Jede neue Quelle erhöhte die Summe und ließ den Summanden stehen, sodass beide Zahlen seit mehreren Releases auseinanderliefen. Die Aufschlüsselung lautet jetzt 80 EN + 5 RU in allen 17 Locales. Das ist mehr als Kosmetik: docs/help/<lang>.md ist der EINZIGE Korpus, auf dem „Ask the docs“ gründet — wer nach der Zahl der Quellen fragte, bekam zwei widersprüchliche Zahlen aus einem Satz.
  • Derselbe Satz nagelte einen veralteten Versionsanker fest. „Seit v1.213.0 liefert die Registry …“ überlebte in 16 Locales, während der Zähler zweimal wanderte (83 in v1.219.0, 85 in v1.226.0). Der Anker war in v1.210.1 schon einmal von Hand repariert worden und driftete einfach erneut. Er entfällt zugunsten versionsfreier Formulierung: Der Zähler wird gegen die lebende Registry geprüft, die nicht veralten kann.
  • Das Provider-Banner erschien als escapetes Markup am Kopf von #/help. v1.225.0/v1.225.1 hielten fest, es werde „in der App von UI.md() entfernt (keine Bildunterstützung)“. Das vermengt zwei Dinge: UI.md() escapet zuerst — jedes Quellbyte wird vor jeder Markdown-Transformation escapet, was die richtige XSS-Grenze ist, aber das Gegenteil von Entfernen. Niemand entfernte das Banner, weil es niemand versuchte, und alle 17 Locales öffneten #/help mit 268 Zeichen wörtlichem <p align="center"><img src="https://…. Ein neuer gemeinsamer Eingang (server/lib/help-markdown.mjs::stripGithubOnlyBlocks) verwirft beim Lesen alleinstehende HTML-Bildblöcke — für GET /api/help/:lang wie für den Korpus des docs-assistant. Die Dateien behalten das Banner, GitHub zeigt die Vitrine also weiterhin.
  • Die Spalte „#“ des Kadenz-Boards war seit v1.117.0 leer. Sie las entry.appNum, während followup-cadence.mjs num liefert — appNum gab es nie im Payload. Beide werden nun gelesen. Die „Next up“-Zeile aus v1.227.0 hatte denselben Fehler geerbt und wird mit behoben.

Hinweise

  • Alle vier blieben unentdeckt, weil nichts sie absicherte. Die Hilfe-Gates (canonical-docs-coverage, help-ui, help-ru-config-section) zählen ÜBERSCHRIFTEN — 32 H2 / 121 H3 — und kein Test hatte je eine Zahl innerhalb eines Absatzes gelesen oder geprüft, was der Hilfe-Eingang tatsächlich ausliefert. Zwei neue Suites schließen das: tests/help-source-counts.test.mjs (die genannte Summe entspricht SOURCES.length; die Summanden entsprechen der lebenden EN/RU-Aufteilung und ergeben die Summe; der Absatz nagelt keine Version fest) und tests/help-banner-strip.test.mjs (das Banner bleibt für GitHub auf der Platte, übersteht das Strippen nie, die erste gerenderte Zeile jedes Bundles ist seine Überschrift, das Strippen bleibt eng, und beide Konsumenten laufen darüber). Beide wurden vor dem Commit gegen genau diese Fehler auf Rot geprüft. Testsuite: 2837 → 2846.
  • Keine Änderung an Registry, Scanner oder Route-Verträgen; Quellen bleiben bei 85 (80 EN + 5 RU), ALL_ADAPTERS 80.

[1.227.0] — 2026-08-28

Hinzugefügt — SEEK Hongkong (JobsDB), ein Nur-fällige-Filter für Follow-ups, und vertrauliche Arbeitgeber zeigen jetzt die vermittelnde Agentur.

Hinzugefügt

  • JobsDB Hongkong über die bestehende Jobstreet/SEEK-Quelle. Hongkong läuft unter der Marke JobsDB, steht aber auf derselben SEEK-Plattform hinter demselben v5-Endpunkt und braucht daher keine eigene Quelle — nur hk.jobsdb.com in der Host-Allowlist und siteKey: HK-Main. Die Registry bleibt unverändert: 85 Quellen (80 EN + 5 RU) und ALL_ADAPTERS 80 — das ist ein neuer MARKT auf einem bestehenden Adapter, kein neuer Adapter.
  • Nur-fällige-Filter auf dem Kadenz-Board (#/modes/followup). Eine neue Checkbox verengt das Board auf das, was jetzt zu tun ist. Die Fälligkeit wird aus der urgency gelesen, die das followup-cadence.mjs des übergeordneten Projekts bereits berechnet hat (urgent / overdue) — nie aus dem Tracker-status, der applied/responded/interview lautet und auf nichts passen würde. Ist nichts fällig, benennt das Board das nächste geplante Follow-up statt einen leeren Zustand zu zeigen. Die Logik liegt im reinen Modul public/js/lib/followup-view.js, Spiegel des übergeordneten followup-view.mjs.
  • Vertrauliche Arbeitgeber werden der vermittelnden Agentur zugeschrieben. Eine Ausschreibung mit zurückgehaltenem Arbeitgeber wird im Tracker als ? erfasst, der Recruiter steht in der Spalte Via. #/tracker zeigte ein blankes ?, sodass mehrere vertrauliche Zeilen ununterscheidbar waren und der Logo-Resolver nichts zum Abgleichen hatte. Eine solche Zeile liest sich nun „Vertraulich · über <Agentur>“, löst ihr Logo über den Agenturnamen auf und ist über diesen Namen im Suchfeld auffindbar. Nur Darstellung — das kanonische Feld company wird nie mit dem Recruiter überschrieben. Neues reines Modul public/js/lib/company-presentation.js, Spiegel des übergeordneten company-presentation.mjs.

Behoben

  • Jobstreet/SEEK rief einen abgekündigten Endpunkt auf. Die Quelle zeigte weiterhin auf chalice-search v4 (/api/chalice-search/v4/search), das SEEK abgeschaltet hat; das übergeordnete Projekt migrierte auf /api/jobsearch/v5/search, web-ui folgte nie. Jetzt fragt jeder Jobstreet/SEEK-Eintrag v5 ab. Die v5-Item-Form wird nativ gelesen (Job-URL aus id gebaut, locations[0].label, advertiser.description, salaryLabel), der v4-eigene Projektionsparameter solrFields entfällt, und ein Eintrag, dessen portals.yml noch den toten v4-Pfad festnagelt, behält seinen Markt-Hostnamen, während der Pfad auf v5 neu gebaut wird — ohne Zutun der Nutzenden.

Hinweise

  • career-ops-1.30.0-Parität (HEAD des übergeordneten Projekts 8d64f65 → 6cc46a4; dessen VERSION steht noch auf 1.30.0 — release-please hinkt seiner main hinterher). Nicht portiert, mit Gründen: iCIMS-JSON-LD-Ortsergänzung — die iCIMS-Quelle von web-ui hat überhaupt keinen enrichDate()-Hook (nur Listenseiten), es gibt also keinen Codepfad zu reparieren; nicht-fataler CLI-stderr (#1974) — der Runner von web-ui entscheidet den Fehlschlag allein am Exit-Code und hatte diese Heuristik nie; Turbopack-Pfad-Tracing — web-ui hat keinen Bundler; cv-sync-check-Fixes — schreibgeschützt und fail-soft weitergereicht; Playwright-Sequenzierung in scan.mjs, Block-H-Antworten, updater/doctor/update-system-Guards, die rekursive Syntaxprüfung, das Go-Dashboard und die ergänzten jd-skill-gap/verify-cv-facts-Selbsttests — reine CLI-Flächen, die web-ui nicht aufruft, oder Selbsttests ohne Verhaltensänderung. Testsuite: 2818 → 2837.

[1.226.0] — 2026-08-27

Hinzugefügt — zwei vietnamesische Jobquellen: ITviec und CareerViet.

Hinzugefügt

  • ITviec + CareerViet (Vietnam). Zwei host-fixierte vietnamesische Boards treten dem Scanner über provider: itviec / provider: careerviet-Einträge bei: ITviec (itviec.com, Tech) und CareerViet (careerviet.vn, breite Abdeckung). Beide sind token-freie HTML-Scraper mit SSRF-Host-Pinning, Nur-HTTPS, Fail-soft pro Firma und Seitenlimits. Das Registry führt nun 85 Quellen (80 EN + 5 RU), ALL_ADAPTERS 80 — beide erscheinen im #/scan-Quellenfilter.

Hinweise

  • Parität mit career-ops 1.30.0 (parentVersion 1.29.0 → 1.30.0). Nicht portiert (nur CLI/Community oder bereits abgedeckt): die Hired Wall, /calibrate, clean-markers, Template-Packs, doctor, pdf/LaTeX und die eval/tracker/scan-CLIs, die web-ui nicht aufruft; der _html-to-text-Fix und roleFuzzyMatch waren bereits da; das title-filter stem:-Präfix ist zurückgestellt. Testsuite: 2818.

[1.225.1] — 2026-08-27

Behoben — das Anbieterlogo-Banner wird jetzt im README und in der Hilfe angezeigt.

Behoben

  • Das Banner aus v1.225.0 wurde auf GitHub nicht angezeigt: die eigenständige images/providers.svg hatte ein ungültiges font-family (verschachtelte doppelte Anführungszeichen), das GitHub ablehnt, das <img> nutzte einen repo-relativen src (rohes HTML <img> löst relative Pfade auf GitHub nicht auf), und dem <p>-Block fehlte eine abschließende Leerzeile. Es verweist jetzt über eine absolute URL auf ein gerendertes images/providers.png, mit Leerzeilen-Trennung; das SVG ist zudem auf gültiges XML korrigiert.

Hinweise

  • Nur Docs; keine Code- oder Teständerung. Die cvstart.org-Showcase und die Kacheln der App waren bereits korrekt (Inline-SVG, nicht diese Datei). 2784

[1.225.0] — 2026-08-27

Hinzugefügt — ein Anbieterlogo-Banner in README und Hilfe-Guide.

Hinzugefügt

  • Anbieterlogo-Banner. Ein einzelnes, in sich geschlossenes images/providers.svg — die 18 LLM-Anbieter, mit dem echten Markenlogo (simple-icons, CC0) für die 11, die eins veröffentlichen, und einem Monogramm in der Markenfarbe für die 7 übrigen — steht jetzt am Kopf des README (×17) und des In-App-Hilfe-Guides. (Die App zeichnet die Kacheln mit dem echten Logo selbst; das Banner wird auf GitHub gerendert und in der App vom XSS-sicheren Renderer entfernt.)

Hinweise

  • Nur Docs — keine Code- oder Teständerung; das SVG ist inline (keine Remote-Ressource). Scan-Quellen unverändert: 83. Testsuite: 2784.

[1.224.0] — 2026-08-26

Geändert — die LLM-Anbieterkacheln zeigen jetzt echte Markenlogos.

Geändert

  • Echte Anbieterlogos. Die Anbieterkacheln — API-Schlüssel-Felder in den Einstellungen, die Nutzungs-Zeilen, der Dashboard-Chip, die „Aktiv”-Zusammenfassung der Einstellungen, das ⚡ Eval-Ergebnis und das Onboarding-Banner — zeigen jetzt das echte Markenlogo für die 11 Anbieter, die ein Open-Source-Icon veröffentlichen (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub · DeepSeek · Kimi · MiniMax · Mistral · Ollama; Ein-Pfad-SVGs von simple-icons, CC0). Die 7 ohne veröffentlichtes Icon (Hermes · GLM/Z.ai · Grok · Together · Fireworks · BytePlus Ark · Volcengine Ark) behalten das Monogramm in der Markenfarbe. Weiterhin CSP-sicher per Konstruktion: der Logo-Pfad ist eine eingebettete statische Konstante — keine Remote-Ressource, kein innerHTML.

Hinweise

  • Keine Routen- oder Verhaltensänderung; dieselbe ProviderLogo.el(slug)-API. provider-logo.js ist gewachsen (die eingebetteten Logo-Pfade). Scan-Quellen unverändert: 83. Testsuite: 2784.

[1.223.0] — 2026-08-26

Behoben — eine erfolgreiche ⚡ Live-Bewertung wurde als Fehler eingefärbt; außerdem den OpenWorker-Coworker direkt aus seiner GitHub-URL oder .zip installieren.

Behoben

  • Farbton des ⚡ Bewertungs-Badges. Eine erfolgreiche Live-Bewertung wird nicht mehr als Fehlschlag eingefärbt. Die In-Process-Anbieter (OpenRouter, DeepSeek, …) liefern keinen Subprozess-code, also war das strikte code === 0 falsch und jeder Erfolg erschien rot (bei gleichzeitig “exit 0”). Der Farbton folgt jetzt dem Ergebnis — nur ein Subprozess, der ungleich 0 endet, färbt rot; ein Live-Ergebnis ohne Code ist badge-ok, und das “· exit N”-Suffix erscheint nur bei einem echten Exit-Code.

Geändert

  • Installiere den OpenWorker-Coworker aus seiner GitHub-URL / .zip. Die Coworker-Doku beschreibt jetzt den Ein-Befehl-Installer (curl … | bash, idempotent — nutzt ein vorhandenes career-ops / web-ui weiter) und die drei OpenWorker-Installationswege (GitHub-URL · .zip · Einzeldatei-Import) in docs/integrations/openworker.md, der In-App-Hilfe §32 (×17) und der README.

Hinweise

  • Eine langjährige Abdeckungslücke geschlossen: die Recruitee-Quelle hat jetzt einen End-to-End-Test für den Quoted-Angle-Fix aus v1.214.2 (ein > in einem Tag-Attribut darf nicht in die Beschreibung lecken). Scan-Quellen unverändert: 83. Testsuite: 2783.

[1.222.0] — 2026-08-26

Geändert — die Feldhinweise der erweiterten Anbieter (DeepSeek … Volcengine Ark) sind jetzt in allen 17 Sprachen lokalisiert.

Geändert

  • Lokalisierte Anbieter-Feldhinweise. Jeder config.<slug>Hint der 11 in v1.216.0–v1.217.0 hinzugefügten OpenAI-kompatiblen Anbieter (DeepSeek · GLM/Z.ai · Kimi · MiniMax · Mistral · Grok · Together · Fireworks · Ollama · BytePlus Ark · Volcengine Ark) — die API-Schlüssel-, Modell- und Basis-URL-Hinweise unter Einstellungen → API-Schlüssel — ist jetzt in alle 17 Sprachen übersetzt, statt auf Englisch zurückzufallen. Anmelde-URLs, Modell-ids und der ⚡ Live-Eval-Marker bleiben wortgetreu erhalten.

Hinweise

  • Keine Verhaltensänderung — der englische hintFallback in field-specs.js schützt weiterhin einen fehlenden Schlüssel, und ein neuer Vertragstest prüft, dass jeder hintKey der Feldbeschreibungen in allen 17 Sprachen auflösbar ist. Scan-Quellen unverändert: 83. Testsuite: 2779.

[1.221.0] — 2026-08-26

Sicherheit — Verteidigung in der Tiefe gegen DNS-Rebinding auf dem Scanner-Fetch-Pfad.

Sicherheit

  • Der HTTP-Kern des Scanners (fetchJson / fetchText in server/lib/http-json.mjs) löst nun den Host jeder Quelle auf dem realen Netzwerkpfad auf und verweigert die Verbindung, wenn er auf eine private, Loopback-, Link-Local-, CGNAT- oder Cloud-Metadaten-Adresse auflöst — und schließt einen DNS-Rebinding-Vektor. Scanner-Hosts sind bereits an öffentliche Domains gepinnt, also Verteidigung in der Tiefe; der Benutzer-URL-Pfad hatte bereits stärkeres Verbindungs-Pinning (safe-fetch.mjs). Der Guard läuft nur auf dem echten fetch-Transport — ein injizierter Test-fetchImpl wird nie aufgelöst.

Hinweise

  • Keine Verhaltensänderung bei einem gesunden Scan. Scan-Quellen unverändert: 83. Tests: 2775.

[1.220.0] — 2026-08-26

Hinzugefügt — mehrere Get-on-Board-Kategorien aus einem Eintrag scannen.

Hinzugefügt

  • Ein getonbrd-Eintrag kann nun categories: [programming, operations-management, machine-learning-ai] (oder ein einzelnes category:) statt nur des programming-Feeds setzen; Stellen werden über Kategorien hinweg nach URL dedupliziert, begrenzt auf 12. Bestehende Einträge bleiben identisch (Standard weiterhin programming).

Hinweise

  • Nur serverseitig; das SSRF-Host-Pinning (www.getonbrd.com) und redirect:'error' sind unverändert, und ein ungültiger Kategorie-Slug wird vor jeder Anfrage abgelehnt. Scan-Quellen unverändert: 83. Tests: 2771.

[1.219.0] — 2026-08-26

Hinzugefügt — Torre kommt zum Scanner, und jedes Anbieter-Label ist jetzt korrekt.

Hinzugefügt

  • Torre (provider: torre) — der pan-lateinamerikanische Talentmarktplatz von torre.ai, eine öffentliche Suche ohne Authentifizierung mit Remote-Stellen, die es nicht bis Greenhouse/Lever/Ashby schaffen; eine begrenzte Anfrage pro Eintrag. Das Registry umfasst nun 83 Quellen (78 englische + 5 russische). Außerdem ein Monogramm des aktiven Anbieters im Onboarding-Banner.

Behoben

  • Der a16z-Speedrun-Scan brach auf einem rotierenden Board zu früh ab (eine kurze Seite mitten im Durchlauf galt als letzte und kürzte auf 149 von ~300); er vertraut jetzt dem total_pages des Feeds. Und das Onboarding-Banner löste den Anbieter aus einer 4-Einträge-Map auf (und zeigte den rohen Slug für die übrigen 14) — es nutzt jetzt Label+Monogramm der 18; der Anbieter-Hinweis in den Einstellungen geht von 7 auf 18.

Hinweise

  • Die neue Mehr-Kategorien-pro-Eintrag-Option von Get on Board ist hier noch nicht verdrahtet (erfordert einen Refactor auf Quellebene). Tests: 2768.

[1.218.0] — 2026-08-26

Hinzugefügt — Anbieter-Badges (und korrekte Namen) überall, wo ein Anbieter angezeigt wird.

Hinzugefügt

  • Anbieter-Monogramme auf dem Dashboard-Chip, der „Aktiv”-Übersicht in den Einstellungen und der Kopfzeile des ⚡ Bewertungsergebnisses.

Behoben

  • Veraltete Labels: Drei Ansichten lösten den Namen aus einer 5-Einträge-Map auf (das Bewertungsergebnis kennzeichnete jeden Nicht-Anthropic-Anbieter als „Gemini”); sie nutzen nun ProviderStatus.label (18). Die Schlüsselanzahl zeigte „/ 7” → 18. Die Slug-Liste des LLM_PROVIDER-Hinweises erhielt ark / arkcn in allen 17 Sprachen.

Hinweise

  • Nur clientseitig, CSP-sicher. Keine Serveränderung. Scan-Quellen: 82. Tests: 2758.

[1.217.1] — 2026-08-26

Test-Härtung — Endpunkt-Abdeckung für das gesamte Anbieterverzeichnis.

Hinzugefügt

  • Ein parametrisierter run<Provider>-Test, der Endpunkt, Standardmodell und Bearer-Authentifizierung für Kimi, MiniMax, Mistral und Fireworks prüft.

[1.217.0] — 2026-08-26

Hinzugefügt — Ark kommt zur LLM-Liste dazu (18 Anbieter). BytePlus Ark und Volcengine Ark — ByteDances Doubao-Modelle — sind jetzt ⚡ Live-Anbieter über denselben OpenAI-kompatiblen Kern und eine Basis-URL pro Region.

Hinzugefügt

  • BytePlus Ark (ARK_API_KEY, international) und Volcengine Ark (ARK_CN_API_KEY, China) — OpenAI-kompatible Chat Completions über runOpenAICompatible(). Setze ARK_MODEL / ARK_CN_MODEL (ein Doubao-Modellname oder deine ep-…-Endpunkt-id); ARK_BASE_URL / ARK_CN_BASE_URL wechseln die Region. Beide reihen sich in die auto-Reihenfolge (zuletzt) ein, mit ihrem Monogramm in Einstellungen und Nutzung. Die Web-Oberfläche umfasst nun 18 Anbieter live.

Hinweise

  • Gleicher Pfad aus vertrauenswürdiger Konfiguration + http(s)-Schema-Prüfung wie beim Rest (nie der SSRF-Validator für Job-URLs). Scan-Quellen unverändert: 82. Tests: 2755.

[1.216.0] — 2026-08-26

Hinzugefügt — neun weitere LLM-Anbieter, jeder mit eigener Markenkachel. Deine ⚡ Live-Bewertungen können jetzt über DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together, Fireworks oder ein vollständig lokales Ollama laufen — nur einen Schlüssel entfernt, jeder mit einem Monogramm neben seinem Feld in den Einstellungen.

Hinzugefügt

  • Neun OpenAI-kompatible Anbieter. DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together AI, Fireworks AI und Ollama (vollständig lokal, kein Schlüssel). Setze einen Schlüssel — oder OLLAMA_BASE_URL für Ollama — in den Einstellungen, und er reiht sich nach Hermes in die auto-Reihenfolge ein; einen beliebigen mit LLM_PROVIDER festpinnen. Together hostet auch Thinking Machines’ Inkling (thinkingmachines/Inkling). Die Web-Oberfläche umfasst nun 16 Anbieter live.
  • Anbieter-Monogramme. Eine CSP-sichere Initial-Kachel in der Markenfarbe kennzeichnet jeden Anbieter neben seinem Schlüsselfeld in den Einstellungen und in seiner Zeile auf der Nutzungsseite — Inline-SVG, keine externen Logos, nichts verlässt deinen Rechner.

Hinweise

  • Die Basis-URLs der Anbieter sind vertrauenswürdige Konfiguration, die über runOpenAICompatible() mit einer http(s)-Schema-Prüfung läuft (Ollamas Loopback ist erlaubt), nie über den SSRF-Validator für Job-URLs. Scan-Quellen unverändert: 82. Tests: 2752.

[1.215.0] — 2026-08-26

Hinzugefügt — führe deine ganze Jobsuche aus OpenWorker aus. Ein neuer Coworker steuert diese Pipeline aus Andrew Ngs quelloffener KI-Coworker-App, und die In-App-Hilfe deckt ihn nun ab, sodass der Frag-die-Docs-Assistent dich durchführen kann.

Hinzugefügt

  • OpenWorker-Coworker. Ein code-freier OpenWorker-Coworker — Fighter90/career-ops-coworker — führt die gesamte Jobsuche aus (Scan, Bewertung, Zuschnitt, Tracking, Follow-up) und kann dieses Dashboard starten, alles auf deinem Desktop. Es ist eine Ein-Datei-Persona in 17 Sprachen, deren Installierbarkeit mit OpenWorkers eigenem Loader verifiziert ist.
  • Hilfe §32 „Run it from OpenWorker” (alle 17 Sprachen) dokumentiert ihn, damit der In-App-Assistent Frag die Docs und die schwebende Hilfe-Schaltfläche Fragen dazu beantworten. Auch verlinkt aus der Landing-Fußzeile, dem README, dem Wiki und docs/integrations/openworker.md.

Hinweise

  • Nur Docs + Site — keine App-Code-Änderung. Scan-Quellen unverändert bei 82. Test-Suite: 2742.

[1.214.2] — 2026-08-25

Behoben — die geteilte HTML→Text-Pipeline (Greenhouse- / Recruitee-Beschreibungen) leckt keine Tag-Attribute mehr in die Beschreibung, wenn ein Attributwert ein >-Zeichen enthält.

Behoben

  • HTML-Beschreibungen mit > in einem Attribut werden sauber entfernt. Der Tag-Entferner hinter den Greenhouse- und Recruitee-Beschreibungen nutzte ein naives Match, das beim ersten > stoppte, sodass ein Tag wie <a title="salary > 100k"> den nachfolgenden Attributtext (100k">) in der Beschreibung ließ. Er respektiert nun in Anführungszeichen gesetzte Attributwerte (einfach oder doppelt), entfernt Markup vor dem Dekodieren von Entities (damit ein kodiertes Anführungszeichen in einem Attribut kein falscher Trenner wird) und bewahrt weiterhin ein literales <> in Klartext.

Hinweise

  • Test-Suite: 2742. Scan-Quellen unverändert bei 82. Sonst nichts Neues vom Eltern-Repo diese Runde: die übrigen Änderungen werden schreibgeschützt weitergereicht (follow-up / weekly-digest / rejection-latency / salary-gap-Fixes brauchen keine web-ui-Änderung) oder sind keine web-ui-Flächen (ein externes X/Xquik-BYO-Key-Plugin, CLI/verify-Fixes). Die optionalen Punkte — SmartRecruiters-Detailbeschreibungen, detect-reposts-Aggregator-Überspringen, stem: / word:-Titelpräfixe, DNS-Rebinding-Härtung — bleiben eingereiht.

[1.214.1] — 2026-08-25

Behoben — Ashby-Beschreibungen werden nun auf dieselbe Länge wie Greenhouse und Recruitee begrenzt, sodass ein Inhaltsfilter auf allen drei Boards gleich wirkt und ein Board den Scan-Cache nicht aufblähen kann.

Behoben

  • Ashby-Beschreibungen halten nun dieselbe Längengrenze wie die anderen Boards ein. v1.214.0 speiste Ashbys Stellentext in den Inhaltsfilter, aber — anders als Greenhouse und Recruitee, die auf 4000 Zeichen kappen — wurde Ashbys direkt ohne Grenze durchgereicht (manche 4× länger). So konnte dasselbe content_filter-Wort auf Ashby treffen, auf Greenhouse aber nicht — allein je nach Position im Text, und ein Board konnte dem Scan-Cache, den #/scan lädt, hunderte KB hinzufügen. Ashby wird nun auf dieselben 4000 Zeichen gekappt (sein Text ist bereits Klartext, wird also abgeschnitten, nie von Tags befreit).

Hinweise

  • Test-Suite: 2738. Scan-Quellen unverändert bei 82.

[1.214.0] — 2026-08-25

Behoben — Stellenbeschreibungen von Greenhouse, Ashby und Recruitee speisen jetzt den Inhaltsfilter, sodass Wort- / Land- / Visum-Filter diese Boards tatsächlich treffen; und “Associate ” (inkl. akademischer Ränge) wird nicht mehr als Entry-Level fehleinsortiert.

Behoben

  • Inhaltsfilter greifen jetzt bei Greenhouse, Ashby und Recruitee. Diese Boards tragen nun den vollen Text jeder Stelle als Beschreibung, sodass ein content_filter (oder ein Land- / Visum-Wortfilter) den Stellentext trifft, statt das Board blind durchzulassen. Ashby und Recruitee liefern den Text in ihrer Listen-Payload kostenlos; Greenhouse tat es bereits. Alle drei laufen durch eine gemeinsame HTML→Text-Pipeline.
  • “Associate Director” / “Associate Professor” ist senior, nicht Entry-Level. Der Senioritäts-Klassifizierer (von skip_tiers: genutzt) behandelte das Wort “associate” als Entry-Level-Marker, selbst als Präfix eines Senior-Titels. Er liest nun “Associate ” — inkl. akademischer Ränge (Associate Professor, Associate Dean) — als senior, behält echte Junior-Varianten (Associate Attorney, Associate Editor) als Entry-Level und ordnet ein führendes “Intern, Associate Dean” weiterhin als Praktikum ein.

Hinweise

  • Test-Suite: 2736. Scan-Quellen unverändert bei 82. Für später eingereiht: optionale SmartRecruiters-Beschreibungsanreicherung (ein Detail-Abruf pro Stelle), das Überspringen von aggregator: true-Boards in detect-reposts und die verankerten Schlüsselwort-Titelpräfixe stem: / word: — je eine optionale Konfiguration, keine Änderung des Standardverhaltens. Die DNS-Rebinding-Härtung bleibt für ihre eigene Sicherheitsversion eingeplant.

[1.213.0] — 2026-08-22

Hinzugefügt — MyCareersFuture, Singapurs nationale Jobbank, als Scan-Quelle. Behoben — Greenhouse-Stellen tragen nun ihren vollen Text, damit Inhaltsfilter greifen, und Remote-Ashby-Stellen verstecken sich nicht mehr hinter einem reinen Stadt-Standort.

Hinzugefügt

  • MyCareersFuture (Singapur) (mycareersfuture.gov.sg) — eine neue token-freie Scan-Quelle für Singapurs nationale öffentliche Jobbank, betrieben von Workforce Singapore. Wähle sie im Quelle-Filter auf #/scan, oder füge ein Unternehmen mit provider: mycareersfuture und einer optionalen keywords-Liste hinzu (fällt sonst auf die Zielrollen deines Profils zurück, wie Job Bank). Liest die öffentliche Such-API, host-gepinnt, ohne Schlüssel.

Behoben

  • Greenhouse-Stellen sind jetzt inhaltsfilterbar. Greenhouse-Boards werden mit dem vollen Stellentext geholt, als Beschreibung in Klartext dekodiert — so trifft ein content_filter (oder ein Land-/Visum-Wortfilter), der die Beschreibung liest, jetzt tatsächlich Greenhouse-Stellen, statt sie blind durchzulassen.
  • Remote-Ashby-Stellen werden nicht mehr von einem Stadtfilter verworfen. Ashby hält das Arbeitsmodell (Remote/Hybrid/Onsite) getrennt von der Bürostadt, sodass eine voll remote Stelle weiterhin z. B. „San Francisco“ las — und ein Standortfilter, der diese Stadt blockt, verbarg eine annehmbare Stelle. „Remote“ wird nun an den Standort angehängt, wenn die Stelle remote ist, und workplaceType gewinnt über ein veraltetes isRemote-Flag, damit eine büro-verankerte Hybrid-Stelle nicht falsch etikettiert wird.

Hinweise

  • Scan-Quellen: 82 (77 englische + 5 russische). Test-Suite: 2724. Eine DNS-Rebinding-Härtung (die aufgelöste Adresse eines Hosts vor dem Verbinden prüfen) ist für eine eigene Version eingeplant — sie braucht ein web-ui-spezifisches Design statt eines direkten Ports.

[1.212.1] — 2026-08-21

Behoben — die cvstart.org-Landingpage zählte die Job-Quellen des Scanners zu niedrig (sie zeigte 80 und ließ Job Bank (Kanada) aus); sie stimmt nun wieder mit den 81 der App überein, und der Site-Build schlägt laut fehl, falls beide je auseinanderlaufen.

Behoben

  • Der „Job-Quellen”-Zähler der Landingpage ist wieder mit der App synchron. Nach v1.212.0 zeigte cvstart.org 80 Boards und der neue Job Bank (Kanada)-Chip fehlte, während App, Scan-Dropdown und Hilfe-Guide alle 81 auflisteten. Die Landingpage baut ihre Liste, indem sie das Live-Scanner-Register lädt, und eine Quelle ließ sich in diesem Build nicht laden — wegen der Art, wie sie eine YAML-Abhängigkeit einband — und wurde still verworfen. Job Bank lädt diese Abhängigkeit nun verzögert, genau wie der Rest der App zur Scan-Zeit, sodass sie immer erscheint.
  • Der Site-Build weigert sich nun, eine nicht übereinstimmende Quellenzahl auszuliefern. Zählt das Register je weniger Quellen auf, als auf der Platte existieren (die Signatur einer nicht geladenen Quelle), schlägt der Build mit einer klaren Meldung fehl, statt still die falsche Zahl zu veröffentlichen.

Hinweise

  • Das App-Verhalten ist unverändert — der Scanner hatte stets alle 81 Quellen; nur die Landingpage war betroffen. Scan-Quellen: 81 (76 englische + 5 russische) — unverändert. Test-Suite: 2687.

[1.212.0] — 2026-08-21

Hinzugefügt — Job Bank (Kanada), das föderale nationale Job-Board. Entfernt — EchoJobs (sein Feed ist nun bot-blockiert). Behoben — Consider-Boards liefern wieder Ergebnisse, und Lever-Stellen mit mehreren Standorten verbergen nicht mehr die Hälfte ihrer Standorte.

Hinzugefügt

  • Job Bank (Kanada) (jobbank.gc.ca) — eine neue token-freie Scan-Quelle für Kanadas föderalen nationalen Arbeitsvermittlungsdienst, ein Board mit hohem Volumen, das kein Aggregator gut abdeckt. Wähle sie im Quelle-Filter auf #/scan, oder füge ein Unternehmen mit provider: jobbankca und einer optionalen keywords-Liste hinzu (fällt sonst auf die Zielrollen deines Profils zurück). Liest den öffentlichen ATOM-Feed, host-gepinnt, ohne Schlüssel.

Entfernt

  • EchoJobs — stillgelegt. Sein öffentlicher Feed liegt nun hinter Bot-Schutz und liefert nichts, sodass ihn zu behalten nur einen Scan-Slot verschwendete.

Behoben

  • Consider-Boards liefern wieder Ergebnisse. Consider verlangt jetzt einen anonymen Handshake (ein GET, der ein Session-Cookie + CSRF-Token setzt), bevor es die Suche annimmt; ohne ihn wurde die Anfrage still abgelehnt und das Board wirkte leer.
  • Lever-Stellen mit mehreren Standorten verbergen nicht mehr die Hälfte ihrer Standorte. Lever legt eine primäre Stadt in location und den Rest in allLocations; nur die primäre zu lesen ließ eine in Barcelona UND Montevideo offene Stelle Barcelona-only erscheinen (und wurde von einem Standortfilter fälschlich verworfen). Beide werden nun zusammengeführt.

Hinweise

  • Sanfteres Tempo zwischen Seiten (250 ms statt 150) auf den paginierten Boards, aus Höflichkeit gegenüber Single-Host-Karriereseiten. Scan-Quellen: 81 (76 englische + 5 russische) — unverändert (Job Bank rein, EchoJobs raus). Test-Suite: 2685.

[1.211.0] — 2026-08-19

Hinzugefügt — Yourator, ein taiwanisches Tech-Job-Board. Behoben — akzentuierte Titel-/Firmennamen-Entities werden nun überall dekodiert, und ein Unternehmen mit einem Akzent im Namen wird nicht mehr fälschlich markiert.

Hinzugefügt

  • Yourator (yourator.co) — eine neue token-freie Scan-Quelle für den taiwanischen Tech- und Digital-Arbeitsmarkt. Wähle sie im Quelle-Filter auf #/scan, oder füge ein Unternehmen mit provider: yourator hinzu. Sie liest die öffentliche JSON-API (kein Schlüssel, kein Browser), durchläuft jede Seite des Boards und gibt den echten Arbeitgeber-Link jeder Anzeige (dessen eigenes ATS) mit entfernten Tracking-Parametern aus.

Behoben

  • Akzentuierte benannte Entities werden nun überall dekodiert. Der geteilte HTML-Decoder erhielt die Latin-1-Buchstaben (&eacute; → é, &ccedil; → ç, …), sodass ein europäisches Board, das D&eacute;veloppeur oder Fran&ccedil;ais schreibt, diesen Literal nicht mehr in einem Titel, dem Tracker oder einem generierten Dokument hinterlässt. (Großbuchstaben bleiben groß — &Eacute; ist É, nicht é — und eine Suche wie &constructor; löst sich nun auf sich selbst auf.)
  • Ein Unternehmen mit einem Akzent im Namen wird nicht mehr fälschlich markiert, weil es auf seiner eigenen Domain liegt. „Işık” faltet sich nun zu „isik” und passt zu isik.com.tr; „Société Générale” passt zu societegenerale.com. Die alte Prüfung löschte akzentuierte Buchstaben, statt sie auf ihre ASCII-Basis zu falten.

Hinweise

  • Scan-Quellen: 81 (76 englische + 5 russische). Test-Suite: 2667.

[1.210.1] — 2026-08-19

Behoben — Habr-Career-Stellentitel und Firmennamen mit „&” oder Anführungszeichen kommen nicht mehr verstümmelt an.

Behoben

  • Die Habr-Career-Quelle dekodiert nun HTML-Entities im Titel und im Firmennamen, bevor sie weiterlaufen. Die serverseitig gerenderten Karten kommen escaped an („Changellenge >>“, „Demand Forecasting & Inventory Optimization”, „ООО "М-ТЕХ"“), sodass ein nicht dekodiertes „&” still an deinem eigenen „&“-Titelfilter scheiterte — genau das Symptom, das die vorige Version auf fünf anderen Boards geschlossen hat — und Firmennamen verstümmelt im Tracker und in Berichten ankamen. Die Entity-Dekodierung ist nun über alle sechs betroffenen Quellen vollständig.

Hinweise

  • Test-Suite: 2644.

[1.210.0] — 2026-08-19

Hinzugefügt — Senjob, das erste afrikanische Job-Board des Scanners (Senegal); präziserer Titelabgleich auf fünf weiteren Boards.

Hinzugefügt

  • Senjob (senjob.com) — eine neue token-freie Scan-Quelle für den Senegal, das erste afrikanische Board des Scanners. Wähle sie im Quelle-Filter auf #/scan, oder füge ein Unternehmen mit provider: senjob hinzu. Sie liest die öffentliche Liste über einfaches HTTP (kein Schlüssel, kein Browser), pinnt jede Anfrage an senjob.com und behandelt — da sie HTML parst — eine Liste, die plötzlich nichts mehr liefert, als kaputtes Board (ein sichtbarer Fehler) statt als Land ohne Jobs.

Behoben

  • Titel mit „&” lassen auf fünf Boards keine Jobs mehr fallen — auf beesite, Cornerstone (csod), Hacker News „Who is hiring”, Phenom und TKMS kommen Titel HTML-escaped an, sodass ein escaptes „&” in einer Rolle wie “R&D Engineer” an deinem eigenen Stichwort “r&d” scheiterte und die Anzeige still verschwand (ein “sales & marketing”-Veto löste ebenfalls nie aus). Titel — und Phenom-Standorte — werden nun vor dem Filtern dekodiert.

Hinweise

  • Scan-Quellen: 80 (75 englische + 5 russische). Test-Suite: 2643.

[1.209.0] — 2026-08-17

Hinzugefügt — die In-App-Hilfe deckt jetzt das Festhalten des Ergebnisses einer Bewerbung ab, und „Frag die Doku” kann dich dorthin führen.

Hinzugefügt

  • Die Tracker-Hilfe (§11) erhielt in allen 17 Sprachen einen Abschnitt „Ein Ergebnis festhalten”, der die Schaltfläche Ergebnis durchgeht: Wähle, was passiert ist (abgelehnt / Angebot / eingestellt / abgelehnt / geghostet / zum Interview vorgerückt), sieh in der Vorschau, was sie tun wird, und halte es fest — das notiert das Ergebnis, archiviert den eingereichten Lebenslauf und das Anschreiben und synchronisiert den Status der Zeile für dich. Der schwebende „Frag die Doku”-Assistent liest den Leitfaden, also führt er dich jetzt zu dieser Schaltfläche, statt nur vorzuschlagen, den Status von Hand zu bearbeiten.

Hinweise

  • Jedes Hilfe-Bundle ist jetzt 31 H2 / 119 H3 (vorher 118); die Paritäts-Wächter wurden angehoben. Nur Dokumentation — keine Code- oder Verhaltensänderung. Suite: 2625.

[1.208.2] — 2026-08-16

Behoben — auf dem Handy liegen die Benachrichtigungs- und Design-Buttons nicht mehr über dem Suchfeld.

Behoben

  • v1.208.1 sorgte dafür, dass die Buttons der oberen Leiste die Seitenüberschrift nicht mehr überlappen, aber auf einem schmalen — wenn auch nicht schmalsten — Handy, besonders in Sprachen mit längeren Button-Texten, quetschte sich die ganze Leiste weiter in eine Zeile, sodass die Buttons 🔔 und 🌙 über dem Suchfeld landen konnten. Die Aktions-Buttons (Benachrichtigungen, Design, Diagnose, Scan öffnen) rücken auf dem Handy nun stets in ihre eigene, volle Breite einnehmende zweite Zeile, sodass das Suchfeld voll lesbar bleibt und nichts überlappt.

Hinweise

  • Auf dem Handy wandern die Aktions-Buttons der Leiste in eine zweite Zeile über die volle Breite und beseitigen das fragile Band der „fast vollen Zeile“, in dem das Layout den übrigen negativen Raum als Überlappung verteilte. Ein Playwright-Wächter reproduziert nun den genauen Auslöser — eine Sprache mit langen Texten über das Band 565–640px — und stellt sicher, dass sich die Bedienelemente der Leiste nie Pixel teilen. Suite: 2621.

[1.208.1] — 2026-08-16

Behoben — auf dem Handy überlappen die Buttons der oberen Leiste die Seite nicht mehr.

Behoben

  • v1.208.0 brach die Buttons der oberen Leiste (Diagnose, Scan öffnen, Benachrichtigungen, Design) auf schmalen Bildschirmen in eine zweite Zeile um, aber die Leiste behielt eine feste Höhe, sodass die umgebrochene Zeile herausquoll und über der Seitenüberschrift lag. Die Leiste wächst nun, um ihre Zeilen aufzunehmen, und der Inhalt fließt darunter.

Hinweise

  • Die feste height der Leiste wurde zu min-height, sodass sie bei jeder Breite mit ihrem Inhalt wächst (Desktop unverändert). Ein Playwright-Wächter prüft nun auch, dass die Leiste nicht über die Seite quillt. Suite: 2621.

[1.208.0] — 2026-08-16

Behoben — die App passt jetzt auf einen Handy-Bildschirm: kein seitliches Scrollen mehr.

Behoben

  • Auf einem schmalen Bildschirm rutschte die ganze App zur Seite — die obere Leiste, Tabellen, Hilfeartikel und Einstellungs-Tabs liefen über den rechten Rand hinaus. Jetzt passt jede Seite in jede Breite: die Buttons der oberen Leiste brechen in eine zweite Zeile um, breite Tabellen und Codeblöcke scrollen in ihrer eigenen Box, die Hilfe stapelt das Inhaltsverzeichnis über dem Artikel, Button-/Tab-Reihen brechen um, und lange Pfade oder URLs brechen um, statt die Seite zu strecken.

Hinweise

  • Ursache war die klassische Flex/Grid-min-width: auto-Falle plus ein paar nicht umschlossene breite Elemente; behoben mit min-width: 0 auf Grid-Items, overflow-wrap auf Markdown/Titeln, einer scrollbaren Markdown-Tabelle und dem Stapeln des Hilfe-Grids am Mobile-Breakpoint. Ein Playwright-Wächter prüft 0 horizontalen Überlauf bei 375 px auf den Hauptrouten. tests/playwright-smoke.mjs. Suite: 2621.

[1.207.2] — 2026-08-16

Behoben — KI-Pläne und Karriere-Orientierungsprofile werden nicht mehr als roher Code-Dump dargestellt.

Behoben

  • Manche Modelle packen ihre ganze Antwort in einen markdown … Code-Zaun. Wenn das passierte, erschienen der Entwicklungsplan und das Orientierungsprofil als Monospace-Codeblock statt als Dokument mit Überschriften und Listen. Der umschließende Zaun wird jetzt entfernt — nur wenn er die gesamte Antwort umschließt und die Sprache ausdrücklich markdown/md ist, sodass eine echte python/js/``` -ohne-Sprache-Codeantwort unangetastet bleibt.

Hinweise

  • Einmalig im gemeinsamen LLM-Aufräumschritt (cleanLlmMarkdown) behandelt, sodass alle KI-Routen profitieren und innere Codeblöcke in der umschlossenen Antwort erhalten bleiben. tests/llm-output.test.mjs (+3). Suite: 2621.

[1.207.1] — 2026-08-16

Behoben — die Landingpage läuft auf kleinen Handys nicht mehr seitlich über.

Behoben

  • Auf einem schmalen Handy konnte der Hero-Bereich — die Überschrift, die Einleitungszeile und das Installations-Terminal — am rechten Rand abgeschnitten werden, weil ein langer Installationsbefehl und die Layout-Spalten nicht auf den Bildschirm schrumpften. Sie passen jetzt in jede Breite; der Installationsbefehl scrollt in seinem eigenen Terminal-Kasten.

Hinweise

  • Außerdem wurde ein instabiler E2E-Smoke-Check gehärtet, der an einem vorübergehenden Ressourcen-404 scheitern konnte: er ignoriert nun harmloses Netzwerkrauschen (Favicon / Verbindung / fehlgeschlagene Ressource) wie die Nachbar-Checks, fängt aber weiterhin echte Skriptfehler. Kein geändertes App-Verhalten. Suite: 2618.

[1.207.0] — 2026-08-15

Hinzugefügt — halte das Ergebnis einer Bewerbung direkt im Tracker fest.

Hinzugefügt

  • Jede Tracker-Zeile erhält eine Ergebnis-Aktion: wähle, was passiert ist (abgelehnt, Angebot erhalten, eingestellt, Angebot abgelehnt, keine Antwort, ins Interview vorgerückt), füge eine optionale Notiz hinzu, sieh dir den resultierenden Status als Vorschau an und erfasse ihn. Das Erfassen archiviert die eingereichten CV- und Anschreiben-Artefakte und synchronisiert den Tracker auf den kanonischen Status — eine deterministische Aktion statt manuellem Bearbeiten.

Hinweise

  • Neue POST /api/outcome leitet die Ergebnis-CLI weiter: dryRun:true ist eine schreibgeschützte Vorschau (findet die Zeile, meldet den resultierenden Status, schreibt nichts); ein echter Aufruf erfasst ihn. Schreibsicherheit: der Ergebnistyp ist auf die bekannte Menge beschränkt und jedes Textfeld wird bei Steuerzeichen vor dem Aufruf abgelehnt (Array-Argumente, spawn — keine Shell). tests/outcome-route.test.mjs. Suite: 2618.

[1.206.0] — 2026-08-15

Dokumentation — der integrierte Hilfe-Guide deckt jetzt die fünf neuesten Funktionen in allen 17 Sprachen ab.

Hinzugefügt

  • Der integrierte Hilfe-Guide — und der «Hilfe fragen»-Assistent, der daraus antwortet — dokumentiert jetzt fünf kürzlich ergänzte Funktionen: Setup-Doktor (Einstellungen — prüft dein CV und Profil auf Lücken und übrig gebliebene Beispieldaten), ATS-Boards finden (Portale — findet automatisch das Karriere-Board eines Unternehmens), die «noch offen?»-Prüfung (Tracker — ob eine Stelle noch offen ist), den «früheres CV wiederverwenden?»-Hinweis (CV Studio — meldet, wenn ein bereits angepasstes CV zu einer neuen Stelle passt) und das Skill-Log (Analyse — Selbsteinschätzungs-Scores erfassen). Fünf neue Unterabschnitte, übersetzt in alle 17 Sprachen.

Hinweise

  • Die Guide-Struktur wächst auf 31 H2 / 118 H3, Parität in jeder Sprache garantiert. Referenzdokumentation aktualisiert: docs/architecture/API.md dokumentiert die fünf Routen dieser Funktionen, und die Routen-/Versionszähler in CLAUDE.md und docs/sdd/CONVENTIONS.md sind aktuell (36 Routenmodule). Suite: 2610.

[1.205.0] — 2026-08-15

Hinzugefügt — ein Skill-Log zum Festhalten von Test-/Assessment-Ergebnissen.

Hinzugefügt

  • Ein neues Skill-Log (Analyse → Skill-Log) lässt dich eine Selbsteinschätzung festhalten — Firma, Plattform, Skill, Score % und eine optionale Notiz — angehängt an data/assessments.tsv, mit einer Liste früherer Einträge (neueste zuerst). Tokenfrei, deterministisch; das Dateiformat verwaltet die CLI des Elternprojekts.

Hinweise

  • Neue GET /api/assessments (leitet die Standard-JSON-Liste von assessment-log.mjs weiter; weiches Scheitern {available:false}) + POST /api/assessments (explizites Schreiben: Felder als Array-Argumente an assessment-log.mjs add). Schreibsicherheit: jedes Textfeld mit einem Steuerzeichen wird abgewiesen (ein TAB würde eine Spalte zerstören, ein Zeilenumbruch eine Zeile einschleusen) → 400 vor dem Schreiben; Score/Schwelle auf 0–100 begrenzt, Längen beschränkt. tests/assessments-route.test.mjs. Suite: 2610.

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