Журнал змін
GitHubКожна значуща зміна, реліз за релізом. Той самий файл постачається в репозиторії та перекладений усіма 17 мовами.
Журнал змін
Усі важливі зміни career-ops-ui. Формат — Keep a Changelog, версії за SemVer.
Переклади: 🇬🇧 English · 🇪🇸 Español · 🇧🇷 Português · 🇰🇷 한국어 · 🇯🇵 日本語 · 🇷🇺 Русский · 🇨🇳 简体中文 · 🇹🇼 繁體中文 · 🇫🇷 Français · 🇵🇱 Polski · 🇩🇰 Dansk · 🇸🇦 العربية · 🇩🇪 Deutsch · 🇮🇹 Italiano · 🇹🇷 Türkçe · 🇮🇳 हिन्दी
Примітка щодо перекладу (v1.70.0) — українську додано як одну з трьох нових мов інтерфейсу. Цей файл перекладає найновіші записи; повна історія — в 🇬🇧 англійському CHANGELOG, який є нормативним джерелом.
[1.236.0] — 2026-09-18
Паритет із батьківським репозиторієм — career-ops main @ 6a9c84c (VERSION 1.33.0, 128 комітів). Два нові джерела та три дзеркальні виправлення — два з них тут тихо губили вакансії.
Додано
Два джерела сканера, 92 → 94 (89 EN + 5 RU). Python.org Jobs (pythonorg) читає публічну стрічку дошки; атрибуція роботодавця обов’язкова, тож рядок, чия назва не називає роботодавця, відкидається, а не добудовується назвою запису порталу. Generalist World (generalist-world) скрейпить картки дошки й гучно падає на розмітці, яку не може розпарсити — картка зі зміненою формою кидає виняток, а не читається як порожня дошка, а саме це і є той режим відмови, що робить скрейпнуте джерело ненадійним. Обидва з’являються у фільтрі Source на #/scan, обидва прив’язані до хоста (якірний regex, лише HTTPS), і обидва наскрізь керовані наборами тестів із фейковим транспортом (43 і 36 випадків).
Виправлено
Вакансію Workday, відкриту в кількох містах, відкидав фільтр локації. Список-ендпоінт Workday відповідає на багатолокаційну вакансію лічильником там, де кожна інша вакансія несе місце: "53 Locations". Це не локація, а buildLocationFilter зіставляє за регістронезалежним підрядком — тож вакансія, відкрита в Остіні й ще у 52 інших містах, не збігалася з жодним записом allow: [austin] і відкидалася, тоді як та сама вакансія, вказана поодиноко, проходила. Виміряно на батьківському боці на трьох орендарях: 53 із 291 вакансій були заглушками, тож це звичайний випадок, а не крайовий. Справжні місця вакансії існують лише в детальному документі CXS, тож кожна заглушка тепер коштує один додатковий GET до нього, з обмеженням у 200 на запис — понад ліміт заглушку лишають видимою, а не підробляють, бо невирішена вакансія ніколи не повинна виглядати як вирішена. Невдалий чи безлокаційний деталь лишає вакансію точно такою, якою її повернув список: видиме "53 Locations" чесно повідомляє про те, що не вдалося розв’язати, тоді як порожня локація мовчки пройшла б крізь будь-який фільтр. Той самий GET несе й startDate, тож збагачена вакансія датується точно, а не відносною прозою список-ендпоінта ("Posted 30+ Days Ago" → undefined); усе, що не є голим YYYY-MM-DD, лишається без змін, а не проганяється через Date.parse, чия неISO-заглушка залежить від реалізації.
Заявлений рівень більше не згортає дві вакансії в один повторний допис. "Insurance Specialist II" — це власне твердження роботодавця, що ця вакансія не є сестринською до іншої: інша оплатна вилка, обсяг і номер заявки під однією базовою назвою. Токенізатор цього не бачив: w.length > 3 відкидає кожну римську цифру аж до VIII і кожну однозначну цифру, тож "Insurance Specialist" і "Insurance Specialist II" токенізувалися однаково, отримували ідеальний коефіцієнт Жаккара, і детектор повторних дописів позначав другу як переоголошення першої. role-matcher.mjs у web-ui взагалі не мав обробки рівнів, тож це переносить увесь механізм батьківського репозиторію: рівні видобуваються зі згортанням римських і арабських цифр в одне число ("Nurse II" і "Nurse 2" — те саме твердження, і саме ця пара раніше хибно різнилася), два заявлені рівні мають збігатися, а рівень лише з одного боку трактується як вільний переказ, якщо тільки кожна назва не несе й слова, якого немає в іншій, — а це вже дві розбіжності, а не прикраса.
Дошка Avature, прив’язана до фільтра, усе одно обходила всю глобальну дошку. UI пошуку Avature генерує непрозорі параметри фасетів (?42386=[812132] — це фільтр за країною), і оператор, який закріплює звужену дошку в portals.yml, очікує, що сканування обійде саме її. resolveSearch перебудовував URL як ${origin}${path} і повністю відкидав рядок запиту — тож кожен запит, включно з першим, обходив увесь борд орендаря: більше сторінок, більше запитів і вакансії, які оператор свідомо відфільтрував. Тепер фасети супроводжують кожен пагінований запит, а власний jobOffset застосовується останнім, щоб випадковий однойменний фасет ніколи не міг перехопити пагінацію.
Безпека
devalue 5.8.1 → 5.9.2 (транзитивно через astro), що закриває єдине відкрите сповіщення Dependabot — відмову в обслуговуванні на пошкодженому вводі. Залежність лише сайту; код рантайму не змінився.
Примітки
Не портовано, з причиною для кожного. Батьківський репозиторій дав lever тайм-аут 30с, а manfred — 25с, проти спільного усталеного 10с — це непорт: транспорт сканування web-ui вже дозволяє 60с (DEFAULT_SCAN_TIMEOUT_MS), тож обидві дошки мають тут більший запас, ніж дає те виправлення там. Подвоєний префікс careers- в iCIMS тут теж неможливий: web-ui бере origin орендаря прямо з api/careers_url і ніколи не будує хост з імені орендаря; новий обхід резервного хоста — це фіча для менш строгої конфігурації батьківського репозиторію, а не виправлення дефекту. deutschebahn і manfred отримали повторну спробу при тимчасовому обриві з’єднання, що при стелі web-ui у 60с значно менш нагально — занотовано, а не портовано, тож майбутній тайм-аут довгого обходу вже має перше, що варто спробувати. Усе інше у 128-комітному дельта-наборі — це CLI, верифікатор, дашборд або web/ (власний фронтенд батьківського репозиторію): liveness-browser, check-jd-archive, verify-ats/verify-portals/verify-cv-facts, update-system, set-status, audit-portals, discover-ats, scaffolder, test-all, маніфести плаґінів.
Розбіжності форку повторно перевірено після pull: кириличне виправлення \p{L} у providers/telegram-channel.mjs, власний providers/telegram.mjs форку, hermes у web/src/lib/clis.ts. Апстрім career-ops-hq/main повністю змержено (0 пропущених комітів). 3066 → 3164 тести; довідка тепер вказує 94 адаптери (89 EN + 5 RU) в усіх 17 бандлах, а структура заголовків незмінна — 32 H2 / 122 H3.
[1.235.0] — 2026-09-16
Паритет із батьківським репозиторієм — career-ops main @ 68e6b94 (VERSION 1.33.0, 42 коміти). Два дзеркальні виправлення; одне з них тихо губило вакансії MokaHR тут.
Виправлено
Друга вакансія з тієї самої дошки MokaHR зникала замість того, щоб додаватися. url-key.mjs зводить URL оголошення до ключа порівняння і безумовно відкидав фрагмент — цілком розумно, адже майже кожен фрагмент існує лише для показу (#apply, #section-2). MokaHR — виняток у нашому власному реєстрі: її оголошення мають вигляд …/social-recruitment/{tenant}/{id}#/job/{n}, де всі вакансії одного орендаря поділяють один і той самий шлях, а ідентифікатор оголошення існує лише у фрагменті. Тож усі вони нормалізувалися до одного ключа. Виміряно до виправлення: вставте у конвеєр дві різні вакансії MokaHR — і отримаєте один рядок: без помилки, без попередження, нічого, що впадало б в очі; фільтр свіжості сканера (en-scanner.mjs) читає той самий ключ, тож щойно будь-яка вакансія цього орендаря потрапляла в історію сканування, кожну іншу відкидали як «вже бачену». Тепер normalizeUrl перетворює розпізнаний маршрут #/job/{id} чи #/jobs/{id} на ключ query-рядка, призначений лише для порівняння, перед тим як відкинути фрагмент — MokaHR зберігає своє усталене ім’я mokahr_job_id, щоб раніше записані ключі лишалися порівнюваними, а будь-який інший хост отримує внутрішнє _career_ops_fragment_job_id. Перенесений ідентифікатор додається, а не встановлюється: URL, що вже несе цей параметр, містить ідентифікатор іншого оголошення, і перезапис злив би саме ті дві речі, заради розділення яких і існує ключ. Кожен нерозпізнаний фрагмент і далі згортається — це та половина, яка й раніше була правильною.
accept-encoding тепер зафіксовано в кожному запиті до джерела. Без явного значення Node узгоджує все, що пропонує його збірка undici; починаючи з Node ≥ 23 це включає zstd, а zstd-відповідь amazon.jobs повертається обрізаною рівно на 1024 байтах зі статусом 200 — тож збій виглядає як Unterminated string in JSON at position 1024, ніби пошкоджений API, а не проблема транспорту. Тепер кожен запит надсилає gzip, deflate, br. На наших рантаймах це запобіжний захід, а не виправлення: Node 18/20/22 — матриця CI та сервер (22.22.1) — надсилають gzip, deflate, що перевірено напряму, а не припущено, тож тут нічого не обрізалося; сьогодні змінюється те, що пропонується br, який ці версії не запитують за замовчуванням, але коректно розпаковують. Усталене значення застосовується регістронезалежно, і саме цю частину варто перевірити уважніше: імена заголовків регістронезалежні, а fetch об’єднує два однойменні записи в одне значення через кому, тож викликач, який передав би Accept-Encoding: identity, інакше отримав би надіслане gzip, deflate, br, identity.
Примітки
Заголовки лишаються звичайним об’єктом на межі fetchImpl, тоді як батьківський репозиторій передає fetch екземпляр Headers. Це свідома розбіжність, а не недогляд: fetch-хелпери web-ui приймають ін’єктований fetchImpl, і 45 наборів тестів джерел читають opts.headers['User-Agent'] із того, що вони перехоплюють — екземпляр Headers не підтримує доступ через дужки, тож достовірно портовано саме поведінку (регістронезалежне перевизначення, перемагає викликач), а не реалізацію. Об’єкт викликача копіюється, а не мутується, оскільки кілька джерел ділять одну спільну константу заголовків. Супутнє виправлення батьківського репозиторію — застосування усталеного user-agent регістронезалежно — це непортована зміна: web-ui взагалі не впроваджує усталений UA, кожне джерело встановлює той, якого вимагає його дошка, тож немає усталеного значення, з яким міг би зіткнутися User-Agent викликача.
Два нові набори тестів і три нові випадки закріплюють цю пару: tests/http-accept-encoding.test.mjs (точне зафіксоване значення, кожен із чотирьох fetch-хелперів і перевизначення викликачем у трьох варіантах регістру — перевіряється, що виживає єдиний ключ accept-encoding, тоді як баг злиття породжував два) та tests/parsers-pipeline-hash-route.test.mjs, який працює зі швом, видимим користувачу, а не з ключем: обидві вакансії MokaHR мають дійти до конвеєра, повторна вставка тієї самої вакансії у чотирьох написаннях має і далі згортатися в один рядок, а косметичний фрагмент не повинен ставати ідентифікатором. Усі підтверджено як провальні до виправлення. Сама фіксація живе на рівні транспорту, а не лише в цих хелперах: 25 із 92 джерел викликають упроваджений fetchImpl напряму й через хелпери взагалі не проходять, тож її застосовує makeTimeoutFetch — обгортка, яку обидва сканери справді впроваджують, і єдина точка проходу, спільна для всіх джерел. (Батьківському проєкту еквівалент не потрібен: providers/_http.mjs — його єдиний транспорт.) Структурна перевірка тепер валить CI, якщо нове джерело викликає fetch напряму замість переданого йому транспорту, а поведінковий кейс проганяє greenhouse — одне з тих 25 — через обгортку й доводить, що заголовок доходить до дроту.
Не портовано, із причиною: liveness-browser.mjs (запобіжник повторного використання headed-Playwright — web-ui не керує headed-браузером), check-jd-archive.mjs (у web-ui немає такої ретрансляції), test-all.mjs (харнес батьківського репозиторію; розбіжність форку vpFixtureEnv лишається незмінною), web/ (власний фронтенд батьківського репозиторію, не дзеркалиться), маніфести плаґінів і SIGNATURES.md (існують лише в репозиторії). batch/batch-runner.sh (амперсанди екрановано перед підстановкою в sed) і jd-skill-gap.mjs (резолвить cv.md відносно кореня даних) — ретранслюються лише для читання: web-ui звертається через shell до копій батьківського репозиторію, тож обидва потрапляють сюди без жодної зміни коду, а зміна в jd-skill-gap робить ретрансляцію /api/jds/:id/skill-gap коректнішою: cv.md тепер резолвиться від кореня даних, а не від того каталогу, звідки був запущений процес. Розбіжності форку повторно перевірено після pull: кириличне виправлення \p{L} у providers/telegram-channel.mjs, власний providers/telegram.mjs форку, hermes у web/src/lib/clis.ts. Кількість джерел не змінилася — 92 (87 EN + 5 RU), тож довідка лишилася незмінною. 3042 → 3066 тести.
[1.234.0] — 2026-09-15
Паритет із батьківським репозиторієм — career-ops main @ 56cce8f (VERSION усе ще 1.32.0, 58 комітів після 1.32.0). Одне дзеркальне виправлення в 15 джерелах, а також запобіжник, щоб воно не могло повернутися.
Виправлено
Один пошкоджений ідентифікатор вакансії більше не спустошує всю сторінку. encodeURIComponent кидає URIError на самотньому UTF-16-сурогаті, а JSON-payload може його нести — JSON.parse('"\\uD800"') зберігає його. П’ятнадцять джерел формували URL кожного оголошення з ідентифікатора чи slug, контрольованого хостом, усередині циклу парсингу, тож одне погане оголошення переривало цикл і губило всі вакансії на цій сторінці; нічого не потрапляло в лог як збій джерела, бо виняток розгортався ще до того, як catch по кожній компанії встигав щось повідомити. Батьківський репозиторій виправив це в providers/_safe-url.mjs (safeEncodeURIComponent → null при URIError, усе інше прокидається далі) і провів через нього ідентифікатор кожного циклу, відкидаючи лише одне оголошення при null. Цей хелпер тепер — server/lib/sources/_safe-url.mjs, і та сама зміна застосована до тринадцяти дзеркальних джерел — alibaba, arbeitsagentur, bamboohr, feishu-jobs, garena, jibeapply, manfred, meituan, mokahr, phenom, thehub, tkms, vdab — і до двох джерел, наявних лише у web-ui, які новий запобіжник виявив за першого ж запуску, jobstreet і trudvsem, — вони мали ту саму форму циклу й той самий прихований кидок винятку. Обсяг обмежено свідомо: лише ідентифікатори з API проходять через хелпер. Значення, похідні від конфігурації, зберігають строге кодування й падають гучно — office у garena (сегмент portals.yml) лишається на кидаючому urlSegment, тоді як її id з API отримує idUrlSegment, що повертає null; corpName у csod і перевірений через SLUG_RE slug у 4dayweek занесені в allowlist поіменно, з поясненням причини.
Резервний варіант заголовка в rheinmetall міг кидати виняток на пошкодженому href. Коли картка не має заголовка, він відновлюється зі slug у URL через decodeURIComponent, яка кидає виняток на поганій відсоток-послідовності у зібраному зі сторінки href — та сама форма «один поганий рядок валить усю сторінку», тільки на боці декодування. Тепер декодування відбувається захищено, а при невдачі використовується сирий slug.
Реєстр джерел пропускає файли з префіксом _. Автовиявлення імпортувало кожен .mjs у server/lib/sources/, крім самого себе, і попереджало про будь-що без export const meta — тож хелпер породжував попередження під час запуску. Прийнято конвенцію батьківського репозиторію: провідне підкреслення означає не джерело; такі файли не імпортуються, і про них не попереджають. Незадекларований файл без meta й далі викликає попередження, а тест виявлення тепер перевіряє обидві половини (підтверджено як червоний на старому фільтрі). Скрипт збірки сайту sync-assets.mjs рахує файли адаптерів на диску за тим самим правилом, тож хелпер більше не спрацьовує на його захист від тихої втрати джерела.
Примітки
tests/sources-url-encoding-surrogate.test.mjs дзеркалить набір батьківського репозиторію у трьох частинах: контракт хелпера (парний сурогат — звичайний емодзі — і далі кодується; кидок винятку із власного toString значення — це вина викликача, і він поширюється далі), поведінку кожного з п’ятнадцяти джерел (один поганий ідентифікатор + один чистий → без винятку, чистий лишається, поганий відкидається, кожен прогнаний через чистий парсер, який експортує джерело, або, для thehub, через його fetcher із фейковим транспортом), і запобіжник на рівні джерела: кожен конвертований файл повинен імпортувати хелпер і викликати його, жоден файл не може посилатися на нього без імпорту, і жоден непереглянутий файл не може будувати URL вакансії голим encodeURIComponent — нове джерело зі старою формою провалює CI з номером рядка-порушника. Усі п’ятнадцять окремих для кожного джерела випадків підтверджено як провальні до порту.
Усе інше з батьківської дельти не портовано, з причиною: batch/batch-runner.sh --cli — це прапорець лише для CLI (web-ui не звертається до batch-runner через shell); invite-match.mjs тепер резолвить set-status.mjs з кореня коду — web-ui це не ретранслює; company-funded.mjs отримав ту саму зміну safe-url усередині, а форма його виводу --json, яку парсить /api/company-funded, не змінилася; test-all.mjs лише переніс бюджет таймауту (розбіжність форку vpFixtureEnv лишається незмінною); web/ — це власний фронтенд батьківського репозиторію, і він не дзеркалиться; AGENTS.md/SIGNATURES.md існують лише в репозиторії. Розбіжності форку повторно перевірено після pull: кириличне виправлення \p{L} у providers/telegram-channel.mjs, власний providers/telegram.mjs форку, hermes у web/src/lib/clis.ts. Кількість джерел не змінилася — 92 (87 EN + 5 RU), тож довідка лишилася незмінною. 3022 → 3042 тести.
[1.233.2] — 2026-09-10
Виправлено — дві знахідки CodeQL js/remote-property-injection.
Виправлено
Цикл, що переносить збережені значення в process.env, обходив Object.entries(safe) — об’єкт, зібраний із тіла запиту — із захистом від прототипних ключів усередині. Код був безпечним, бо safe наповнюється лише з KNOWN_KEYS, але аналізатор не може перенести це обмеження через два цикли. Тепер цикл іде прямо константним масивом, тож ім’я властивості доказово є літералом модуля. Поведінка ідентична.
Примітки
Тест, що закріплював стару перевірку, був грепом по тексту вихідника. Замінено структурним і поведінковим — другий шле __proto__ сирим JSON, бо літерал об’єкта задав би прототип, а не властивість. 3021 → 3022 тести.
[1.233.1] — 2026-09-10
Виправлено — посібник не описував елемент, доданий у v1.233.0, а той самий розділ ніс застарілі вказівки.
Виправлено
DOC-1 — пункт «Використовувати усталене (…)» виражає стан «ключ не задано», і відмінність від явного вибору того самого значення невидима та істотна: перше нічого не пише й далі віддає усталене проєкту, друге закріплює значення в .env. Три пункти додано до наявного списку ### Behavior в усіх 17 бандлах.
DOC-2 — рядок таблиці не називав ключа: у колонці стояло (server uses default UA), що описувало HH_USER_AGENT, вилучений у v1.19.0, і радило реєструватися на dev.hh.ru — марно з v1.65.0. Усі три застарілі згадки прибрано; рядок troubleshooting переписано, бо частина його досі правдива: з липня 2026 hh.ru віддає HTTP 451 не-російським адресам.
Таблиця ключів перелічує 6 із 45, тоді як заголовок обіцяє реєстр. Одна фраза ×17 називає її вибіркою й вказує на #/config.
Примітки
Новий гейт help-recognized-keys.test.mjs звіряє першу колонку ключових таблиць із живим KNOWN_KEYS. 3018 → 3021 тестів; довідка лишається 32 H2 / 122 H3.
[1.233.0] — 2026-09-10
Виправлено — три знахідки в шляху збереження #/config; CONFIG-3 породжений виправленням v1.232.1.
Виправлено
CONFIG-3 — свідомий вибір, що збігся із засіяним значенням, відкидався як «не чіпали». initial змішує два походження: засів із .env (рівність означає, що поле не редагували) і засів із defaultValue (рівність неоднозначна). Виправлено прапорцем seededFromFile: там, де засів із файлу, нічого не змінюється; там, де з умовчання, другою ознакою стає dirty.
Зняття ключа показувалося як «· 0» — сервер повертав лише записане. Тепер POST /api/config віддає ще й removed, а тост рахує обидва.
Select не можна було очистити — жоден із 18 не мав порожнього пункту. Тепер кожен починається з явного «Використовувати усталене (…)», і незаданий ключ обирає саме його. Один новий ключ i18n ×17.
Примітки
База юніт-тестів незмінна — 3018; браузерних 111 → 116, усі п’ять нових підтверджено падали на старому коді.
[1.232.1] — 2026-09-10
Виправлено — збереження на #/config записувало поля, яких користувач не торкався.
Виправлено
CONFIG-2. v1.57.1 засіває незадані контроли їхнім defaultValue, щоб поле показувало те, що сервер справді використовує, а save() питав dirty лише для секретів і надсилав решту полів завжди. Тож значення, підставлене для ПОКАЗУ, ішло на сервер як вибір користувача: зміна одного дропдауна надсилала 28 ключів і закріплювала 18. Налаштування переходило з «не задане — слідуй умовчанню проєкту» в «закріплене в .env», і наступна зміна умовчання мовчки не дійшла б до тих, хто хоч раз натиснув Save.
Примітки
Порівняння з cfg.values недостатньо: незаданий select показує свій defaultValue, а збережене значення порожнє — вони чесно різняться. Тепер порівнюється з тим, чим контрол було проініціалізовано. Також число записаних ключів знову видно в тості. 105 → 111 браузерних тестів.
[1.232.0] — 2026-09-09
Виправлено — два дефекти, знайдені браузерним прогоном; жоден із них не походить із v1.231.5.
Виправлено
PROFILE-1 — #/profile прокручував убік усю сторінку. Значення поля було безіменним <div> лише з інлайновими шрифтовими стилями, тож overflow-wrap лишався normal. URL LinkedIn не дає CSS жодної допустимої точки переносу, отже min-content дорівнює всьому рядку, а min-width: auto у grid-елементів не дозволяє стиснутися нижче — картка розпирала колонку, рядок і врешті документ (23 px реального скролу на 1280 px). Виправлено спільним класом .card-value з overflow-wrap: anywhere та підстраховкою .card-row > * { min-width: 0 }. Саме anywhere, а не break-word: лише anywhere враховується під час обчислення min-content.
CONFIG-1 — POST /api/config приймав будь-яке значення для select-поля. {"LLM_PROVIDER":"not-a-provider"} повертав 200 і записувався в .env, після чого резолвер мовчки йшов у фолбек. LLM_PROVIDERS увесь час експортувався з env-config.mjs — валідатор просто до нього не звертався.
Примітки
Перевіряється лише LLM_PROVIDER: справжній домен списків моделей належить вендорові й змінюється між нашими релізами. Уже збережене значення отримує поблажку — форма надсилає всі несекретні поля щоразу, тож сувора перевірка заморозила б сторінку. Також додано дескриптор OLLAMA_API_KEY. 3013 → 3018 тестів.
[1.231.5] — 2026-09-09
Безпека — закрито сім сповіщень, одне з них — критичне віддалене виконання коду. Жоден файл джерел не змінено.
Безпека
Змержено з Dependabot: multer 2.2.0 → 2.3.0 (high — відмова в обслуговуванні через витік файлових дескрипторів при обриві) і svgo 4.0.2 → 4.1.0 у сайті (high і medium — removeScripts пропускав виконувані посилання).
Потім ще п’ять, які виявило пересканування. Кожен зачеплений діапазон уже допускався наявним ^, тож потрібне було оновлення, а не правка маніфесту: astro 7.1.0 → 7.3.2 — критично, віддалене виконання коду через оптимізацію зображень AVIF (виправлено в 7.2.8) та обхід авторизації (7.2.4); sharp 0.35.3 → 0.35.4 — high, вразливості libheif; js-yaml 4.3.1 → 4.3.2 в обох маніфестах — high.
Примітки
«Лише залежності» тут не дорівнює «без ризику». js-yaml розбирає кожен конфіг, multer приймає кожне завантаження, а Astro змістився на мінорну версію. Перевірено: test:ci 3013/3013, браузерний набір 4/4, повна пересборка сайту — ті самі 86 сторінок і всі 17 дзеркал. npm audit: 0 вразливостей в обох маніфестах.
[1.231.4] — 2026-09-07
Виправлено — v1.231.3 полагодив наслідок кліку по Doctor, а не сам момент. Повідомлено знову, з телефона і з десктопа.
Виправлено
withSpinner усе ще переписував кнопку на час запиту. v1.231.3 навчив його зберігати вузли-дітей і повертати їх у finally — і вони справді поверталися, але індикатор зайнятості й далі виконував replaceChildren з текстом '⏳ ' + original. Тому на весь час роботи doctor.mjs квадрат 36 px тримав текст ⏳ 🩺Doctor і ставав широкою «пігулкою», що ламала рядок. Відновлення дітей лагодило наслідок, а не момент. Кнопка, що має дітей-елементів, тепер не переписується взагалі: її індикатор — клас .is-loading, який тепер підмінює піктограму пісковим годинником засобами CSS, тож геометрія змінитися не може: 36 px у спокої, 36 px під час, 36 px після.
Примітки
Урок про перевірку, а не про код. Гейт v1.231.3 чекав зняття aria-busy і лише тоді мірив — тобто бачив лише те, що finally вже виправив. Нова браузерна перевірка сама тримає проміс відкритим і знімає замір усередині польоту. 3012 → 3013 тестів.
[1.231.3] — 2026-09-07
Виправлено — клік по Doctor ламав верхню панель на телефоні. Повідомлено з телефона в день виходу v1.231.2.
Виправлено
UI.withSpinner знищував дітей тієї кнопки, яку оформлював. Індикатор зайнятості працював через textContent: зберегти button.textContent, записати '⏳ ' + original, записати назад. Присвоєння textContent замінює всіх дітей одним текстовим вузлом — а v1.231.2 якраз розділив кожну кнопку верхньої панелі на .btn-ico і .btn-label, сховавши підпис під max-width: 900px. Тому перший же дотик до Doctor знищував обидва <span> назавжди: мобільним правилам не стало чого зіставляти, підпис повертався голим текстом, а квадрат 36 px розростався в «пігулку» 🩺Doctor. Само не відновлювалося — лише перезавантаження сторінки повертало розмітку. Виправлення зберігає вузли-дітей і повертає їх через replaceChildren; кнопки з одним текстом, а це більшість із 24 місць виклику, поводяться точно як раніше.
- FIND-3 — поле пошуку схлопувалося до 8 px на екрані 320 px. Звівши панель до одного рядка, я лишив searchbar єдиним гнучким елементом — і минулий випуск прямо це записав: він «може стиснутися до нуля». Він і стиснувся: 8 px вводу на 320 px, 28 px на 340 px — один символ із двадцяти одного в підказці. Жоден
min-widthтут не зарадить: на 320 px панель тримає 32 px відступів, 40 px меню і 162 px дій. Тому нижче 420 px поле ховається за лупу і по дотику розгортається на всю панель: 182 px на 320 px, 222 px на 360 px, 252 px на 390 px, без переповнення. Кнопка-розкриття зберігає одне доступне ім’я і повідомляє стан черезaria-expanded; заміна 🔍/✕ зроблена чистим CSS. Вище 420 px не змінюється нічого. Знайдено браузерним регресом по живій збірці.
Примітки
Це той самий клас дефекту, що й пастка з applyI18n(), зазначена у v1.231.2 — присвоєння textContent елементу, у якого тепер є діти-елементи, — але через іншого викликача. Урок узагальнюється: розділивши вміст елемента на дочірні <span>, ви перетворюєте кожного наявного писача textContent по цьому елементу на прихований баг.
Закрито двома замками — node:vm над справжнім withSpinner і клік у справжньому браузері на 320 px. Обидва підтверджено падали на старому коді. 3009 → 3012 тестів.
[1.231.2] — 2026-09-06
Виправлено — мобільна верхня панель переносилася на другий рядок, сервер працював на старому батьківському проєкті, а редирект cvstart.ru давав 404 на будь-якому шляху, де вже вказано мову.
Виправлено
- Мобільна панель знову в один рядок, кнопки — піктограмами. Кнопки дій примусово йшли на окремий рядок на всю ширину. Тепер «Діагностика» і «Відкрити Scan» — квадрати 36 px поруч із дзвіночком і перемикачем теми, і все вміщується в один рядок при 320 px. Виміряно: ширина документа дорівнює вьюпорту при 320, 360, 390 і 430 px та на смузі 560–760 px. Локаль більше не впливає на мобільну розкладку, бо підпис прихований; вище 900 px підписи повертаються.
- Сервер працював на батьківському 1.31.0, хоча реліз зібрано під 1.32.0. Викладено 95 файлів рантайму, два видалено. Знайдено браузерним регресом на живій поверхні.
cvstart.ruприклеював/ru/до будь-якого шляху./ru/helpставав/ru/ru/help,/en/help—/ru/en/help; обидва 404. Тепер шлях, що вже називає одну з 16 локалей, проходить як є,enвідкидається (англійська живе в корені), решта отримує/ru/.
Змінено
- 98 застарілих QA-промптів перенесено до
qa/archive/superseded-prompts/— правило записане уqa/README.mdз v1.137.0 і не застосовувалося 95 релізів. - З верхнього рівня пішли ще п’ять промптів — вони називали себе актуальними, не будучи ними.
REGRESSION-FINAL.mdоголошував себе «єдиним авторитетним, версійно-незалежним регресійним промптом», але його §§11–15 — п’ять закритих журналів циклів (v1.55.x → v1.59.7), а заявлена база хелпу — 28 H2 / 103 H3 на 16 локалей проти 32 / 122 і 17 сьогодні. УQA-REGRESSION-PROMPT.mdім’я без версії, а вміст від v1.226.0.QA-REGRESSION-PROMPT-v1.76.0-FULL.mdзакріплений на 155 релізів назад,REGRESSION-PROMPT-FINAL.mdзакриває цикл v1.58.52 → v1.59.10, аQA-FULL-REGRESSION.mdдублював їх усіх на базі v1.131.1. Туди ж пішовPARENT-SYNC-WORKLIST-v1.26.0.md, а датованийUX-AUDIT-2026-07-06.mdпереїхав доreports/. Лишилося чотири драйвери, чиї бази оновлено до фактичних значень v1.231.2.qa/README.mdпереписано під ту теку, яка існує.
Примітки
- Дві пастки специфічності описано в
app.css: десктопні правила оголошені нижче блокуmax-width: 900pxі при рівній специфічності виграють. applyI18n()присвоюєel.textContent, тож ключ i18n переїхав на внутрішній<span>.- Перший rsync батьківського проєкту обірвався на півдорозі й відрапортував успіх — тепер перевіряється контрольними сумами.
[1.231.1] — 2026-09-06
Виправлено — свіп лічильників із v1.231.0 переписав імʼя акаунта в усіх посиланнях.
Виправлено
- Кожне посилання
Fighter90сталоFighter92. Свіп лічильника джерел у v1.231.0 замінював90на92під захистом цифрової межі, яка правильно пропускає290і901, але збігається з90всерединіFighter90. 562 посилання в усіх 17 README, усіх 17 наборах довідки та дзеркалах довідки на сайті, плюс 138 у вікі, вели на неіснуючий акаунт. Помітив користувач — за зниклою картинкою. - Пʼять історичних згадок релізів зсунулися. Свіп
85 → 87перетворивv1.85.0наv1.87.0, вигадавши версію, якої не випускали.
Примітки
- Цифрової межі замало: вона захищає число від сусідніх цифр, але не від того, що воно частина ідентифікатора чи номера версії. Самі лічильники правильні: 92 джерела, 87 EN-адаптерів, 3009 тестів.
- Батьківський репозиторій не постраждав.
[1.231.0] — 2026-09-05
Додано — два джерела сканування з батьківського career-ops v1.32.0 (Collage, Telegram суворий), режим REST у Gem і один дефект, який порт виявив у коді самого батька.
Додано
- Два нові джерела, 90 → 92 (87 EN + 5 RU),
ALL_ADAPTERS85 → 87. Обидва без токенів.- Collage (
provider: collage) — публічний API джоб-сайтів Collage HR. Сегмент шляху — це адреса джоб-сайту, яку орендар обрав сам, а не слаг із назви компанії, тож він читається зapi:або з careers-адресиsecure.collage.co/jobs/<адреса>і ніколи не вгадується: вгадана адреса призвела б до сканування чужої дошки. - Telegram (суворий) (
provider: telegram-channel) — читає ті самі попередні переглядиt.me/s/<канал>, що й наявне джерелоtelegram, але з протилежним розміном: допис стає рядком лише якщо називає роботодавця Й посилається на сторінку вакансії. Дайджести, дописи з прихованим роботодавцем і дописи «пишіть у личку» відкидаються. Низька повнота за задумом — вище за течією виміряно 32–77 % дописів, що проходять, — але кожен рядок несе справжнього роботодавця і справжнє посилання. Обидва лишаються, як і в батька.
- Collage (
- Gem отримав необовʼязковий режим REST поряд зі шляхом GraphQL (
api: https://api.gem.com/job_board/v0/<board>/job_posts). Він ніколи не виводиться з ідентифікатора дошки, а шлях GraphQL лишається типовим. Перенесено з батьківського #3783.
Виправлено
- Фільтр локацій у
telegram-channelне бачив кирилиці й називав міста роботодавцями. У батьківськомуLOCATIONISH_REстояла\b— межа лише за ASCII, поряд із кирилицею вона не спрацьовує ніколи. Тож усі російські варіанти в ньому були недосяжні, іSenior Engineer | Москваповертав «Москва» як роботодавця. Це саме та підміна неправильного поля, заради запобігання якій уся політика атрибуції й написана. У порті web-ui використано юнікодні перевірки — те саме, що батько вже робить рядком нижче вROLE_WORD_RE. Закріплено тестом, який падає на ASCII-формі. Вище за течією ще не виправлено.
Примітки
- Робота батька з відновлення Workday через фасети (#3851, #3874) переносу не потребувала — web-ui взагалі не гортає Workday, один POST при
offset: 0. providers/_types.jsзмінився лише в документації.- Тести: 2962 → 3009 (+47).
[1.230.0] — 2026-09-05
Змінено — три виправлення провайдерів із батьківського career-ops v1.32.0 і один дефект, який виявив сам перенос.
Додано
- Welcome to the Jungle приймає серверний вираз
filtersдля Algolia (wttj: { filters: "offices.country_code:FR AND contract_type:full_time" }). Саме лише ключове слово глобальну дошку не звужує: які самеmax_hitsрезультатів повернуться, вирішує ранжування Algolia, а не фільтри сканера — вони спрацьовують пізніше, вже по тому, що прийшло. Фільтр скорочує сам набір, а не переупорядковує його, тож ліміт на запит зростає з 200 до 1000. За наявності фільтраqueriesстає необов’язковим: порожній запит означає «усе, що проходить фільтр». Перенесено з батьківського #3757.
Виправлено
- Radancy більше не пише «truncated», коли орендар просто має менше вакансій, ніж заявляє його ж банер. Батьківський проєкт звірив
data-total-resultsіз дев’ятьма живими орендарями: п’ять збіглися, а чотири — і маленький, і дуже великий — віддали на 10–56 % менше унікальних вакансій, ніж оголошено, без жодного дубля. Банер завищує те, що насправді віддає пошуковий індекс орендаря, тож порівняння з накопиченим лічильником хибно спрацьовувало на більшості дощок. Тепер попередження виникає лише коли walk зупинив наш власнийmax_jobs/max_pages;totalResultsлишається в тексті як контекст і нічого не вирішує. Перенесено з батьківського #3839. - Radancy обходить кеш на кожному JSON-запиті фрагмента. Кешувальний шар перед цим маршрутом у частини орендарів віддає застарілі відповіді: та сама вакансія з’являється сторінками пізніше, а інша не приходить зовсім — відтворено вище за течією дев’ять прогонів із дев’яти. Заголовки
Cache-Control/Pragmaне змінили нічого, випадковий параметр на запит — виправив. Він мусить бути унікальним на виклик, а не виводитися з номера сторінки. Перенесено з батьківського #3839. - Списки офісів Greenhouse впорядковано детерміновано. Greenhouse не обіцяє порядок
offices[]між відповідями, а набір, відновлений із дерева/offices, іде в порядку обходу. Обидва потрапляють у полеlocation, тож перестановка офісів переписувала цей рядок — і вакансія без змін читалася як нова. Батьківський проєкт виправив набір (#3839); у web-ui знайшовся другий екземпляр того самого дефекту, якого в батька немає — запасний варіантoffices[0], — і відсортовано обидва.
Примітки
- Виправлення iCIMS (#3728) переносити не довелося. Воно читає всі записи
jobLocationіз JSON-LD сторінки вакансії; web-ui бере локацію з HTML сторінки списку і сторінку вакансії не відкриває — такого шляху коду тут немає. - Зміна
_http.mjsпереносу не потребувала. Вона виноситьisRefusedRedirectErrorзаради другого споживача, якого в web-ui немає; поведінкаisRetryableErrorне змінюється. - Дедуплікація за локацією (#3751) не дзеркалиться — вона в конвеєрі
scan.mjsCLI батьківського проєкту. - Реєстр без змін: 90 джерел (85 EN + 5 RU). Тести: 2956 → 2962 (+6).
[1.229.0] — 2026-09-03
Додано — чотири джерела сканування з батьківського career-ops v1.31.0: Built In, Feishu Jobs, Garena і MokaHR.
Додано
- Чотири нові джерела, 86 → 90 (85 EN + 5 RU),
ALL_ADAPTERS81 → 85, усі без токенів. Built In охоплює дев’ять ринків американської технічної дошки й не несе типового запиту: запис безqueries:таcategories:не сканує нічого і прямо про це каже, бо спільне джерело не має нести чиїсь пошукові слова; ринок поза списком відхиляється, а не підміняється тихцем національною дошкою. Feishu Jobs приймає рівно дві форми хоста, і вони мають різні шляхи до сторінки вакансії. Garena — одна компанія:officeформує посилання, а не список. MokaHR повертає зашифровану відповідь (конверт AES-128-CBC, ключ поруч) — це обфускація, не захист, але без розшифрування нічого не прочитати.
Виправлено
htmlToTextміг пропустити в текст незакритий відкривальний тег.safe <img src=x onerror=1виходило якsafe <img src=x onerror=1. Тепер за кожним декодуванням іде очищення, а кінцевий відкривач втрачає кутову дужку. Портовано від батьківського проєкту (#3491).
Примітки
- Із виправлення Workday не портовано нічого. Воно рятує тенантів, у яких бекенд зупиняє пагінацію на offset 2000 — web-ui Workday взагалі не пагінує (один POST на
offset: 0, 100 рядків). - Виправлення
verify-cv-factsіmerge-trackerпорту не потребували: web-ui запускає ці скрипти з чекауту батьківського проєкту. - Тести: 2909 → 2956 (+47).
[1.228.5] — 2026-08-30
Виправлено — повідомлена версія описувала файл на диску, а не виконуваний код.
Виправлено
-
/api/healthперечитувавpackage.jsonна кожен запит, тож застарілий процес повідомляв версію, якої не виконував. QA знайшов локальний інстанс, що відповідавversion: 1.228.4і водночас давав 404 на/api/ping— маршрут, доданий у 1.228.3: запущений до розгортання, обслуговував старий код і повідомляв версію нового файлу. Саме так виглядає успішним розгортання, яке скопіювало файли й ніколи не перезапустило процес: єдиний рядок, що його перевіряє оператор, — той, який не здатен побачити проблему. Той самий клас, що й символьне посилання з v1.228.1, де rsync уривався посеред передачі й усе одно звітував про успіх.Версія тепер фіксується під час завантаження модуля й описує виконуваний код. Застарілий процес повідомить стару версію — правду, видну одразу.
parentVersionлишається почитаним на запит: він описує чекаут батьківського проєкту, який цей процес не завантажує і який законно змінюється під працюючим сервером.
[1.228.4] — 2026-08-30
Виправлено — неавтентифікована проба живості робила дискову роботу на кожен запит.
Виправлено
GET /api/pingчитав і розбиравpackage.jsonна кожне звернення. Це єдина точка, досяжна без облікових даних, тож читання з диска плюс розбір JSON на запит — це важіль відмови в обслуговуванні, вручений будь-кому; CodeQL позначив це як брак обмеження частоти. Версія тепер читається один раз під час реєстрації маршруту; без перезапуску вона й так не зміниться, тож обробник не робить введення-виведення взагалі. Обмеження частоти вкоротило б шкоду; прибирання роботи прибирає важіль.- Маскування справжнього імені власника профілю поза loopback тепер закріплено тестом.
/api/healthуже замінював його наhidden— але лише тому, що цей рядок ділить охоронцяhidden ?? valueз коренем проєкту. Ніщо цього не перевіряло: правка, яка дала б рядку власне значення, злила б ім’я реальної людини в локальну мережу непоміченою.
[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.
buildAskPromptdidif (ctx.length + sec.body.length > MAX_CONTEXT) break— abreak, 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 addedtelegram_channelsto 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, sotelegram_channelsis 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 (продуктовая разработка, высоконагруженные системы)becameFinCore 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 (soCoca-Colasurvives), 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/healthreports 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 singleincludes(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-designwas 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.mjsalso 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 onlyreferomatches in the repo are the Polish word preferowany.
[1.228.0] — 2026-08-29
Додано — Telegram-канали як джерело сканування та посторінковий режим запиту в /api/scan-results.
Додано
- Telegram-канали стали скановним джерелом. Блок
telegram_channels:уportals.ymlїх перелічує; кожен читається з публічного вебпрев’юhttps://t.me/s/<канал>. Реєстр: 85 → 86 джерел (81 EN + 5 RU),ALL_ADAPTERS80 → 81. Telegram не публікує RSS, а Bot API не читає канал, яким бот не адмініструє — обидва очевидні шляхи закриті. Прев’ю/s/— звичайний HTML із сервера: без входу, без JS, без кук. Перевірено на 16 каналах: 15 віддали по 20 дописів на голий GET. GET /api/scan-resultsотримав посторінковий режим запиту.?q=<текст>&limit=50[®ion=ru|en][&set=filtered|fresh][&offset=N]повертає{ total, returned, offset, limit, set, rows }, деtotal— кількість ДО пагінації. Без параметрів маршрут і далі віддає знімок цілком — таблиця#/scanзалежить від цієї форми.
Виправлено
- Telegram-асистент відповідав із частки даних. Його навичка вже вказувала на
/api/scan-results, але маршрут умів віддати лише весь знімок — близько 2 МБ — що не вміщується в контекст агента. На «знайди всіх Продуктових менеджерів» він повідомив 9 збігів там, де у знімку їх були сотні, і сказав, що «працює з обмеженим локальним індексом». Це була правда: ендпоінт він бачив, а спожити не міг. З посторінковим режимом те саме питання коштує 2 КБ і повертає чеснийtotal. Навичку теж переписано: вона просила підсумувати нові дописи, тобто вказувала наfreshзамістьfiltered.
Примітки
- Допис каналу — це проза, а не запис про вакансію. Структурованих полів немає: заголовок із першого змістовного рядка, компанія/місто/вилка — з явних міток. Відділяти справжні вакансії від реклами й дайджестів залишено наявному
title_filter— тому самому, що вже налаштований для інших джерел. Це підказки, а не список. - Компанію ніколи не вгадують. Без мітки рядок приписується каналу (
@rabotaphp): хибний роботодавець потрапляє до трекера як факт. - Порожній розбір — це помилка, а не «вакансій немає». На закритий чи неіснуючий канал t.me відповідає перенаправленням; повернути нуль рядків читалося б як спокійний день і назавжди сховало б одрук у конфігу.
jobGeeksробить саме це (HTTP 302) і навмисно не налаштований. - Визначення віддаленої роботи використовує межу слова Unicode замість
\b, який працює лише з ASCII — та сама пастка, що й у фільтрі заголовків у v1.227.3, тут спіймана тестом до релізу. - 15 каналів налаштовано та перевірено наживо: 299 дописів, 15/15 каналів. 30 нових тестів. Тести: 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
Totalrow 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
Виправлено — регіональний скан вичерпував пам’ять сервера, а один Playwright-сьют падав на власному teardown.
Виправлено
- Регіональний скан убивав сервер через OOM.
ru-scannerнакопичував усі сирі влучання з усіх запитів і дедуплікував та фільтрував лише наприкінці. Список запитів навмисно складається з близьких синонімів (Golang,Go разработчик,Golang разработчик,Senior Go…), тож та сама вакансія повертається раз на запит, і масив тримав кратно більше об’єктів, ніж унікальних — разом з описами, які становлять основну масу об’єкта і які фільтри майже повністю відкидають. Виміряно: 742 МБ купи на реальному прогоні з 21 запиту. Сервер обмежує купу Node 490 МБ (V8 рахує ліміт від 956 МБ RAM машини), тож скан валив сервіс ізFATAL ERROR: Reached heap limit— чотири рази за добу. Тепер дедуплікація і фільтрація йдуть щозапиту, і той самий прогін уклався в 177 МБ, мінус 76%. Звітні числа не змінилися: унікальний підсумок несеSetз URL-рядків. net::ERR_SOCKET_NOT_CONNECTEDвипадково валила CI. Три Playwright-сьюти викликаютьwindow.stop(), щоб обірвати незавершені запити перед перевіркою «консольних помилок немає». Chromium повідомляє про цю одну навмисну дію трьома іменами залежно від стану сокета:ERR_ABORTEDпри скасуванні,ERR_EMPTY_RESPONSEу польоті таERR_SOCKET_NOT_CONNECTED, коли сокет уже зник. Спільний фільтр знав лише перші два. Виявилося на прогоні по локалях уtr, аplaywright-formsіplaywright-smokeбули за один невдалий прогін від того самого.
Примітки
- Чому саме тепер. Список виріс із 14 до 21 запиту після додавання пошуків продакт-менеджера. Пам’ять старої реалізації зростала із загальною кількістю влучань, тож +50% запитів пробили стелю машини.
- Зміна дедупу зберігає поведінку, і це перевірено, а не заявлено.
tests/ru-scanner-dedup-order.test.mjsфіксує незручний напрямок — пізніший дублікат, що НЕ проходить фільтри, зобов’язаний витіснити раніший, який пройшов — і виконує 200 рандомізованих звірок зі старою реалізацією. - Фільтр консолі лишається вузьким. Безпечна лише сім’я скасованих запитів; відмова у з’єднанні, збій DNS, скидання, будь-який 5xx і неперехоплені JS-винятки й далі валять перевірку.
- Дві гіпотези перевірено й відкинуто, а не відправлено в реліз: таймаут Caddy (конфіг чистий,
flush_interval -1для SSE правильний) і типовий 5-хвилиннийrequestTimeoutNode (зменшена репродукція показала, що він не рве довгу стрімінгову відповідь). Справжнім доказом ставReached heap limitу журналі. Тести: 2860 → 2865.
[1.227.4] — 2026-08-29
Виправлено — word: / stem: усе ще ігнорувалися в content_filter; v1.227.3 полагодила лише фільтр заголовків.
Виправлено
content_filterне враховував префікси. v1.227.3 навчилаtitle_filterбатьківських префіксівword:/stem:, алеcontent_filterлишився на власному сиромуlower.includes(k), тож запис із префіксом там і далі зіставлявся як буквальний текст"word:java"— той самий мовчазний no-op, на один фільтр далі.content_filterчитає ОПИС вакансії, і його замовчуванням завжди був простий підрядок: саме тому голий негативjavaвідкидає все, де лише згадано «JavaScript»; префікс — це спосіб вивести один запис з-під цього правила. Портовано батьківськийcompileContentKeyword: він використовує ту саму механіку префіксів, але навмисно не прив’язує короткі ключі — фільтр заголовків прив’язує абревіатури з 2–3 літер, бо «COO» всередині «Coordinator» завжди помилка, тоді як коротка послідовність усередині абзацу прози зазвичай навмисна (aws,gcp,sql,go).
Примітки
- Знайдено в AI-рев’ю PR v1.227.3, яке помітило, що код не покриває того, що заявляє його ж коментар — там було «
title_filter/content_filter», хоча підключено було лише шлях заголовків. Тепер коментар явно розділяє: префікси працюють в обох фільтрах, автоприв’язка абревіатур — лише в заголовках. - Так само перевірено за тим рев’ю і не змінилося: перехід правила для абревіатур 2–3 літер з
\bна межу Unicode поводиться однаково для ASCII і суворіший лише там, де треба —COO_leadіcoo1і далі зберігаються (підкреслення та цифра — символи слова в обох),Coordinatorзберігається, «Директор COO» і «VP «Продукт»» коректно відкидаються. Перевірено 14 межових випадків. - Записи
content_filterбез префікса зберігають попереднє зіставлення байт у байт, порожній або відсутній опис і далі проходить, а голийword:/stem:не збігається ні з чим замість усього. 6 нових кейсів уtests/content-filter.test.mjs. Тести: 2852 → 2858.
[1.227.3] — 2026-08-29
Виправлено — префікси word: / stem: у фільтрі заголовків мовчки ігнорувалися, через що ці рядки фільтра нічого не робили.
Виправлено
- web-ui не реалізовував батьківські префікси
word:/stem:.title_filter(іcontent_filter) за замовчуванням зіставляють підрядок без урахування регістру — саме тому голий негативinternвідкидає і «International Product Manager», і «Internal Tools Engineer». Замість зміни цього замовчування (що зламало б усі налаштовані встановлення) батьківський проєкт зробив точність опційною для кожного запису:word:internзбігається лише з цілим словом, аstem:agentмає починати слово й може продовжуватися (відділяючи «Agentforce» від «Reagents»). У web-ui не було ні того, ні іншого, тож запис зіставлявся як буквальний текст"word:intern", якого немає в жодному заголовку вакансії. Рядок ставав мовчазним no-op: той самийportals.ymlкоректно фільтрував через CLI і зовсім не фільтрував тут, тож усі стажерські вакансії, які користувач виключив, усе одно потрапляли до таблиці#/scan. Портовано з батьківськогоtitle-keywords.mjs, включно з межею слова з підтримкою Unicode (\p{L}\p{M}\p{N}_) замість\b, який працює лише з ASCII і збігався всередині слова в заголовках з діакритикою та кирилицею.
Примітки
- Знайдено під час перевірки живого
portals.yml, куди щойно додалиword:internсаме для того, щоб голийinternперестав відкидати «International Product Manager». У CLI префікс працював; web-ui тихо лишав усі стажування. Тепер поведінка ідентична. - Замовчування не змінилися. Запис без префікса зберігає попереднє зіставлення: підрядок для фраз і всього, що містить не-літери (
.NET,SAP,L&D), і прив’язка до меж слова для абревіатур із 2–3 літер. Голийword:/stem:вважається одруком і не збігається ні з чим — як негатив порожній шаблон збігся б із будь-яким заголовком і скасував би весь скан через одну випадкову двокрапку. - Правило для абревіатур 2–3 літер переведено на ту саму межу Unicode: раніше воно використовувало
\b, через щоcooіword:cooрозходилися на не-ASCII заголовках — два написання одного правила. - 6 нових кейсів у
tests/title-filter.test.mjs. Тести: 2846 → 2852.
[1.227.2] — 2026-08-29
Виправлено — #/config з’їжджав убік на вузьких телефонах.
Виправлено
- MOBILE-1:
#/configвиходив за екран горизонтально на в’юпортах ≤ 352 px. Кожне поле-селект несло інлайновийmin-width: 300px. Інлайновий стиль перемагає таблицю стилів, тож контрол не міг стиснутися разом із контейнером: на в’юпорті 320 px правий край селекта опинявся на 353 px, сторінка отримувала горизонтальну прокрутку, а контроли виїжджали за правий край. Зачепило всі 18 селектів провайдера/моделі (cfg-llm-providerі всіcfg-*-model). Теперmin-width: min(300px, 100%)+max-width: 100%: комфортні 300 px діють лише коли є місце. Пристрої на порозі й нижче: iPhone SE (1-ше покоління, 320 px), старі Android, зовнішній екран Galaxy Fold.
Примітки
- Чому всі попередні мобільні прогони це проґавили. На 360 і 375 px запасу рівно вистачає, щоб приховати переповнення, а набори Playwright працюють у вікні 1280 px. Жоден тест у CI ніколи не міряв в’юпорт вужчий за 375 px. Новий
tests/playwright-narrow-viewport.mjsпроходить маршрути з формами на 320 px і падає на будь-якому горизонтальному переповненні, називаючи в повідомленні найширший елемент; другий кейс фіксує, що жоден селект#/configне виходить за в’юпорт. Спершу перевірено, що він відтворює баг (scrollWidth 353 > clientWidth 320, 18 селектів із правим краєм 353). Playwright: 99 → 101. - Відомо і свідомо поки без гейта: 280 px (зовнішній екран Galaxy Fold) усе ще переповнюється приблизно на 5 px, але з іншої причини — довгий контент усередині блоків
<details>картки конфігурації та сайдбар 256 px проти в’юпорта 280 px, а не інлайновийmin-width, який лагодить цей реліз. Переверстка під ширину, якої не заявляє жоден масовий телефон, — окрема робота; списокWIDTHSу сьюті до неї готовий. - Також виправлено QA-промт v1.227.1, де маршрут фоллоу-апу названо
#/modes/followup. Mode-сторінки реєструють «голий» слаг (Router.register(cfg.slug)), тож маршрут —#/followup, а#/modes/followupкоректно віддає 404. Лише документація. - Юніт-сьют без змін — 2846 (нове покриття браузерного рівня).
[1.227.1] — 2026-08-29
Виправлено — розбивка джерел у довідці не сходилася сама з собою, а GitHub-банер друкувався в застосунку сирою розміткою.
Виправлено
- §17 давав підсумок і розбивку, які не додавалися. Усі бандли довідки писали «реєстр
server/lib/sources/постачає 85 адаптерів — 78 англомовних + 5 російськомовних». 78 + 5 = 83, а не 85; у ja/ko дрейф сягав 77 (= 82). Кожне нове джерело піднімало підсумок і не чіпало доданок, тож обидва числа розходилися вже кілька релізів. Тепер розбивка — 80 EN + 5 RU в усіх 17 локалях. Це важливіше за косметику:docs/help/<lang>.md— ЄДИНИЙ корпус, на якому ґрунтується «Ask the docs», тож на питання про кількість джерел користувач отримував два суперечливі числа з одного речення. - Те саме речення фіксувало застарілий якір версії. «Станом на v1.213.0 реєстр …» дожило в 16 локалях, поки лічильник рухався двічі (83 у v1.219.0, 85 у v1.226.0). Якір уже лагодили вручну у v1.210.1 — і він знову поплив. Його прибрано на користь формулювання без версії: сам лічильник перевіряється проти живого реєстру, який не може застаріти.
- Банер провайдерів друкувався екранованою розміткою вгорі
#/help. У v1.225.0/v1.225.1 записано, що його «вирізаєUI.md()(немає підтримки зображень)». Це змішує дві різні речі:UI.md()екранує-першим — він екранує кожен байт джерела до будь-яких markdown-перетворень, і це правильна XSS-межа, але протилежність вирізанню. Ніхто не прибирав банер, бо ніхто й не намагався, і всі 17 локалей відкривали#/help268 символами буквального<p align="center"><img src="https://…. Новий спільний вхід (server/lib/help-markdown.mjs::stripGithubOnlyBlocks) відкидає при читанні окремі HTML-блоки із зображенням — і дляGET /api/help/:lang, і для корпусу docs-assistant. У файлах банер лишається, тож на GitHub вітрина рендериться, як і раніше. - Колонка «#» на дошці каденції була порожня з v1.117.0. Вона читала
entry.appNum, тоді якfollowup-cadence.mjsвіддаєnum— поляappNumу payload не було ніколи. Тепер читаються обидва. Рядок «Next up» із v1.227.0 успадкував ту саму помилку й виправлений разом із нею.
Примітки
- Усі чотири лишалися непоміченими, бо їх ніщо не перевіряло. Гейти довідки (
canonical-docs-coverage,help-ui,help-ru-config-section) рахують ЗАГОЛОВКИ — 32 H2 / 121 H3 — і жоден тест ніколи не читав число всередині абзацу й не дивився, що насправді віддає вхід довідки. Це закривають два нові сьюти:tests/help-source-counts.test.mjs(заявлений підсумок дорівнюєSOURCES.length; доданки дорівнюють живому EN/RU-поділу й дають у сумі підсумок; абзац не фіксує версію) таtests/help-banner-strip.test.mjs(банер лишається на диску для GitHub, ніколи не переживає strip, перший відрендерений рядок кожного бандла — його заголовок, strip лишається вузьким, і обидва споживачі йдуть через нього). Обидва перевірені на падіння саме проти цих багів до коміту. Тести: 2837 → 2846. - Реєстр, сканер і контракти роутів не змінювалися; джерел і далі 85 (80 EN + 5 RU),
ALL_ADAPTERS80.
[1.227.0] — 2026-08-28
Додано — SEEK Гонконг (JobsDB), фільтр «лише термінові» для фоллоу-апів і нерозкриті роботодавці тепер показують посередника.
Додано
- JobsDB Гонконг через наявне джерело Jobstreet/SEEK. Гонконг працює під брендом JobsDB, але стоїть на тій самій платформі SEEK за тим самим ендпоінтом v5, тож окреме джерело не потрібне — достатньо
hk.jobsdb.comу списку дозволених хостів іsiteKey: HK-Main. Реєстр без змін: 85 джерел (80 EN + 5 RU),ALL_ADAPTERS80 — це новий РИНОК на наявному адаптері, а не новий адаптер. - Фільтр «лише термінові» на дошці каденції (
#/modes/followup). Новий чекбокс звужує дошку до того, що потребує дії просто зараз. Терміновість читається зurgency, яку вже обчислив батьківськийfollowup-cadence.mjs(urgent/overdue), і ніколи зіstatusтрекера — тамapplied/responded/interview, що не збіглося б ні з чим. Коли термінового немає, дошка називає найближчий запланований фоллоу-ап замість порожнього екрана. Логіка живе в чистому модуліpublic/js/lib/followup-view.js— дзеркалі батьківськогоfollowup-view.mjs. - Нерозкриті роботодавці атрибутуються агенції-посереднику. Вакансія з прихованим роботодавцем записується в трекер як
?, а рекрутер потрапляє в колонкуVia.#/trackerпоказував голий?, тож кілька нерозкритих рядків були нерозрізненні, а резолверу логотипів не було за що зачепитися. Тепер такий рядок читається як «Не розкрито · через <агенція>», логотип береться за назвою агенції, і рядок знаходиться за цією назвою в пошуку. Це лише відображення — канонічне полеcompanyніколи не перезаписується рекрутером. Новий чистий модульpublic/js/lib/company-presentation.js— дзеркало батьківськогоcompany-presentation.mjs.
Виправлено
- Jobstreet/SEEK звертався до застарілого ендпоінта. Джерело досі вказувало на chalice-search v4 (
/api/chalice-search/v4/search), який SEEK вимкнув; батько перейшов на/api/jobsearch/v5/search, а web-ui за ним не пішов. Тепер будь-який запис Jobstreet/SEEK запитує v5. Форма елемента v5 читається нативно (URL будується зid,locations[0].label,advertiser.description,salaryLabel), проєкційний параметрsolrFieldsз v4 прибрано, а запис, у якого вportals.ymlусе ще зафіксовано мертвий шлях v4, зберігає хост свого ринку, а шлях перезбирається на v5 — правок від користувача не потрібно.
Примітки
- Паритет із career-ops 1.30.0 (HEAD батька
8d64f65→6cc46a4; йогоVERSIONусе ще 1.30.0 — release-please відстає відmain). Не портовано, з причинами: заповнення локації з JSON-LD в iCIMS — у джерела iCIMS у web-ui взагалі немає хукаenrichDate()(лише сторінки списку), тож лагодити нічого; нефатальний stderr CLI (#1974) — раннер web-ui визначає збій лише за кодом виходу і ніколи не мав цієї евристики; трасування шляхів Turbopack — у web-ui немає збирача; фіксиcv-sync-check— ретранслюються лише на читання і fail-soft; послідовний Playwright уscan.mjs, відповіді Block H, запобіжники updater/doctor/update-system, рекурсивна перевірка синтаксису, дашборд на Go та додані самотестиjd-skill-gap/verify-cv-facts— лише-CLI поверхні, до яких web-ui не заходить, або самотести без зміни поведінки. Тести: 2818 → 2837.
[1.226.0] — 2026-08-27
Додано — два в’єтнамські джерела вакансій: ITviec і CareerViet.
Додано
- ITviec + CareerViet (В’єтнам). Дві в’єтнамські дошки з прив’язкою хоста підключаються до сканера записами
provider: itviec/provider: careerviet: ITviec (itviec.com, техно) і CareerViet (careerviet.vn, широке покриття). Обидва — HTML-скрапери без токенів із SSRF-прив’язкою хоста, лише-HTTPS, fail-soft по компаніях і лімітами сторінок. Реєстр тепер має 85 джерел (80 EN + 5 RU),ALL_ADAPTERS80 — обидва видно у фільтрі «Джерело» на#/scan.
Примітки
- Паритет із career-ops 1.30.0 (parentVersion 1.29.0 → 1.30.0). Не портовано (лише CLI/спільнота або вже є): Hired Wall,
/calibrate, clean-markers, паки шаблонів, doctor, pdf/LaTeX і CLI eval/tracker/scan, до яких web-ui не звертається; фікс_html-to-textіroleFuzzyMatchвже були; префіксtitle-filter stem:відкладено. Тести: 2818.
[1.225.1] — 2026-08-27
Виправлено — банер логотипів провайдерів тепер відображається в README і довіднику.
Виправлено
- Банер v1.225.0 не показувався на GitHub: у окремого
images/providers.svgбув некоректнийfont-family(вкладені подвійні лапки), який GitHub відхиляє;<img>використовував відноснийsrc(raw HTML<img>не розв’язує відносні шляхи на GitHub); а блок<p>не мав завершального порожнього рядка. Тепер він указує на відрендеренийimages/providers.pngза абсолютним raw-URL, з розділенням порожніми рядками; SVG також виправлено до валідного XML.
Примітки
- Лише доки; без змін коду й тестів. Вітрина cvstart.org і плитки в застосунку вже були коректні (вбудований SVG, не цей файл). 2784
[1.225.0] — 2026-08-27
Додано — банер логотипів провайдерів у README та довіднику.
Додано
- Банер логотипів провайдерів. Єдиний самодостатній
images/providers.svg— 18 LLM-провайдерів: справжній логотип бренду (simple-icons, CC0) для 11, що публікують іконку, і монограма в кольорі бренду для 7 інших — тепер очолює README (×17) та вбудований довідник. (Застосунок сам малює плитки зі справжніми логотипами; банер відображається на GitHub, а XSS-безпечний рендерер застосунку його прибирає.)
Примітки
- Лише доки — без змін коду й тестів; SVG вбудований (без віддалених ресурсів). Джерела сканування без змін: 83. Тести: 2784.
[1.224.0] — 2026-08-26
Змінено — плитки LLM-провайдерів тепер показують справжні логотипи брендів.
Змінено
- Справжні логотипи провайдерів. Плитки провайдера — поля API-ключів у Налаштуваннях, рядки Використання, чип дашборда, зведення «Активний» у Налаштуваннях, результат ⚡-оцінки та привітальний банер — тепер показують справжній логотип бренду для 11 провайдерів, що публікують open-source-іконку (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub · DeepSeek · Kimi · MiniMax · Mistral · Ollama; одношляхові SVG з simple-icons, CC0). Семеро без опублікованої іконки (Hermes · GLM/Z.ai · Grok · Together · Fireworks · BytePlus Ark · Volcengine Ark) зберігають монограму в кольорі бренду. Досі безпечно для CSP за побудовою: шлях логотипа — вбудована статична константа, без віддалених ресурсів і без
innerHTML.
Примітки
- Без змін маршрутів чи поведінки; той самий API
ProviderLogo.el(slug).provider-logo.jsвиріс (вбудовані шляхи логотипів). Джерела сканування без змін: 83. Тести: 2784.
[1.223.0] — 2026-08-26
Виправлено — успішна ⚡ live-оцінка фарбувалася як помилка; крім того, встановлення coworker OpenWorker прямо з його GitHub-URL або .zip.
Виправлено
- Тон бейджа результату ⚡-оцінки. Успішна live-оцінка більше не фарбується як провал. In-process провайдери (OpenRouter, DeepSeek, …) не повертають код виходу підпроцесу, тож суворе
code === 0було хибним і будь-який успіх виходив червоним (показуючи «exit 0»). Тон тепер слідує за результатом — червоним фарбує лише підпроцес із ненульовим кодом; live-результат без коду —badge-ok, а суфікс «· exit N» показується лише за наявності справжнього коду виходу.
Змінено
- Встановлення coworker OpenWorker з його GitHub-URL / .zip. Документація coworker тепер описує інсталятор однією командою (
curl … | bash, ідемпотентний — повторно використовує наявні career-ops / web-ui) та три шляхи встановлення в OpenWorker (GitHub URL ·.zip· імпорт одного файлу) уdocs/integrations/openworker.md, вбудованій довідці §32 (×17) і README.
Примітки
- Закрито давню прогалину в покритті: у джерела Recruitee з’явився наскрізний тест на фікс v1.214.2 з лапками навколо кутової дужки (
>усередині атрибута тега не має витікати в опис). Джерела сканування без змін: 83. Тести: 2783.
[1.222.0] — 2026-08-26
Змінено — підказки полів розширених провайдерів (DeepSeek … Volcengine Ark) тепер локалізовані всіма 17 мовами.
Змінено
- Локалізація підказок полів провайдерів. Кожен
config.<slug>Hintдля 11 OpenAI-сумісних провайдерів, доданих у v1.216.0–v1.217.0 (DeepSeek · GLM/Z.ai · Kimi · MiniMax · Mistral · Grok · Together · Fireworks · Ollama · BytePlus Ark · Volcengine Ark) — підказки API-ключа, моделі та базового URL у Налаштування → Ключі API — тепер перекладено всіма 17 мовами замість відкату до англійської. URL реєстрації, id моделей і маркер ⚡ live-eval збережено дослівно.
Примітки
- Поведінка не змінюється — англійський
hintFallbackуfield-specs.jsдосі страхує відсутній ключ, а новий контрактний тест перевіряє, що коженhintKeyдескрипторів полів резолвиться в усіх 17 мовах. Джерела сканування без змін: 83. Тести: 2779.
[1.221.0] — 2026-08-26
Безпека — ешелонований захист від DNS-rebinding на шляху fetch сканера.
Безпека
- HTTP-ядро сканера (
fetchJson/fetchTextуserver/lib/http-json.mjs) тепер резолвить хост кожного джерела на реальному мережевому шляху і відмовляється підключатися, якщо він резолвиться у приватну, loopback, link-local, CGNAT або адресу хмарних метаданих — закриваючи вектор DNS-rebinding. Хости сканера вже привʼязані до публічних доменів, тож це ешелонований захист; шлях користувацьких URL уже мав сильнішу привʼязку зʼєднання (safe-fetch.mjs). Guard працює лише на реальному транспортіfetch— впроваджений у тестахfetchImplніколи не резолвиться.
Примітки
- Для нормального сканування поведінка не змінюється. Джерела сканування без змін: 83. Тести: 2775.
[1.220.0] — 2026-08-26
Додано — скануйте кілька категорій Get on Board з одного запису.
Додано
- Запис
getonbrdтепер може задатиcategories: [programming, operations-management, machine-learning-ai](або одинcategory:) замість лише каналуprogramming; вакансії дедуплікуються за URL між категоріями, ліміт — 12. Наявні записи не змінюються (типово досіprogramming).
Примітки
- Лише сервер; SSRF-прив’язка хоста (www.getonbrd.com) і
redirect:'error'незмінні, а неправильний слаг категорії відхиляється до запиту. Джерела сканування без змін: 83. Тести: 2771.
[1.219.0] — 2026-08-26
Додано — Torre долучається до сканера, і тепер кожна мітка провайдера коректна.
Додано
- Torre (
provider: torre) — пан-латиноамериканський маркетплейс талантів torre.ai, публічний пошук без автентифікації з віддаленими вакансіями, яких немає в Greenhouse/Lever/Ashby; один обмежений запит на запис. Реєстр тепер містить 83 джерела (78 англійських + 5 російських). Також монограма активного провайдера на банері привітання.
Виправлено
- Сканування a16z Speedrun завершувалося зарано на дошці з ротацією (коротку сторінку в середині обходу вважали останньою, обрізаючи до 149 із ~300); тепер довіряє
total_pagesстрічки. А банер привітання визначав провайдера з карти на 4 записи (показуючи сирий слаг для решти 14) — тепер використовує мітку+монограму з 18; примітка про провайдерів у Налаштуваннях змінюється з 7 на 18.
Примітки
- Опція кількох категорій на запис у Get on Board ще не підключена (потрібен рефакторинг на рівні джерела). Тести: 2768.
[1.218.0] — 2026-08-26
Додано — значки провайдерів (і правильні назви) скрізь, де показано провайдера.
Додано
- Монограми провайдерів на чипі дашборда, у зведенні «Активний» налаштувань і в заголовку результату ⚡ оцінювання.
Виправлено
- Застарілі мітки: три екрани визначали назву з карти на 5 записів (результат оцінювання позначав будь-якого не-Anthropic провайдера як «Gemini»); тепер використовують спільний
ProviderStatus.label(18). Лічильник ключів показував «/ 7» → 18. До списку слагів підказкиLLM_PROVIDERдоданоark/arkcnв усіх 17 мовах.
Примітки
- Лише клієнт, безпечно для CSP. Без змін сервера. Джерела сканування: 82. Тести: 2758.
[1.217.1] — 2026-08-26
Посилення тестів — покриття ендпоінтів для всього списку провайдерів.
Додано
- Параметризований тест
run<Provider>, що перевіряє ендпоінт, модель за замовчуванням і Bearer-автентифікацію для Kimi, MiniMax, Mistral і Fireworks.
[1.217.0] — 2026-08-26
Додано — Ark долучається до списку LLM (18 провайдерів). BytePlus Ark і Volcengine Ark — моделі Doubao від ByteDance — тепер провайдери ⚡ живого оцінювання через той самий OpenAI-сумісний код і базовий URL за регіоном.
Додано
- BytePlus Ark (
ARK_API_KEY, міжнародний) і Volcengine Ark (ARK_CN_API_KEY, Китай) — OpenAI-сумісний Chat Completions черезrunOpenAICompatible(). ЗадайтеARK_MODEL/ARK_CN_MODEL(назву моделі Doubao або ваш id ендпоінтаep-…);ARK_BASE_URL/ARK_CN_BASE_URLперемикають регіон. Обидва стають у порядокauto(останніми), з монограмою в Налаштуваннях і «Використанні». Вебінтерфейс охоплює тепер 18 провайдерів.
Примітки
- Той самий шлях довіреної конфігурації + перевірки схеми http(s), що й в інших (ніколи не SSRF-валідатор URL вакансій). Джерела сканування без змін: 82. Тести: 2755.
[1.216.0] — 2026-08-26
Додано — ще девʼять LLM-провайдерів, у кожного власна фірмова плитка. ⚡ Живе оцінювання тепер можна запускати через DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together, Fireworks або повністю локальний Ollama — достатньо одного ключа, поряд із полем кожного в Налаштуваннях є монограма.
Додано
- Девʼять OpenAI-сумісних провайдерів. DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together AI, Fireworks AI та Ollama (повністю локально, без ключа). Задайте ключ — або
OLLAMA_BASE_URLдля Ollama — у Налаштуваннях, і провайдер стане в порядокautoпісля Hermes; закріпіть будь-який черезLLM_PROVIDER. Together також хостить Inkling від Thinking Machines (thinkingmachines/Inkling). Вебінтерфейс охоплює тепер 16 провайдерів живого оцінювання. - Монограми провайдерів. CSP-безпечна плитка з ініціалом у фірмовому кольорі позначає кожного провайдера поряд із полем ключа в Налаштуваннях і в рядку сторінки «Використання» — вбудований SVG, без зовнішніх логотипів, ніщо не залишає ваш пристрій.
Примітки
- Базові URL провайдерів — довірена конфігурація, що проходить через
runOpenAICompatible()з перевіркою схеми http(s) (петля Ollama дозволена), а не через SSRF-валідатор URL вакансій. Джерела сканування без змін: 82. Тести: 2752.
[1.215.0] — 2026-08-26
Додано — керуйте всім пошуком роботи з OpenWorker. Новий coworker керує цим пайплайном з open-source-застосунку AI-coworker від Andrew Ng, і вбудована довідка тепер його охоплює, тож асистент «Запитати документацію» може провести вас.
Додано
- Coworker для OpenWorker. Coworker без коду для OpenWorker — Fighter90/career-ops-coworker — виконує весь пошук роботи (скан, оцінка, підгонка, трекінг, follow-up) і може запустити цей дашборд, усе на вашому робочому столі. Це персона з одного файлу 17 мовами, встановлення якої перевірено власним завантажувачем OpenWorker.
- Довідка §32 «Run it from OpenWorker» (усі 17 локалей) документує це, щоб вбудований асистент «Запитати документацію» та плаваюча кнопка довідки відповідали на питання про нього. Також додано посилання у футері лендингу, README, вікі та
docs/integrations/openworker.md.
Примітки
- Лише документація + сайт — без змін коду застосунку. Джерела сканування без змін — 82. Набір тестів: 2742.
[1.214.2] — 2026-08-25
Виправлено — спільний конвеєр HTML→текст (описи Greenhouse / Recruitee) більше не протягує атрибути тегу в опис, коли значення атрибута містить символ >.
Виправлено
- HTML-описи з
>усередині атрибута очищуються коректно. Засіб видалення тегів за описами Greenhouse і Recruitee використовував наївне співпадіння, що зупинялося на першому>, тож тег на кшталт<a title="salary > 100k">залишав хвіст атрибута (100k">) в описі. Тепер він поважає значення атрибутів у лапках (одинарних чи подвійних), видаляє розмітку до декодування сутностей (щоб закодована лапка всередині атрибута не стала хибним роздільником) і досі зберігає літеральний<>у простому тексті.
Примітки
- Набір тестів: 2742. Джерела сканування без змін — 82. Цього разу нічого нового від батька: решта змін ретранслюється лише на читання (виправлення follow-up / weekly-digest / rejection-latency / salary-gap не потребують змін у web-ui) або не є поверхнями web-ui (зовнішній X/Xquik BYO-key плагін, виправлення CLI/verify). Опціональні пункти — детальні описи SmartRecruiters, пропуск агрегаторів у detect-reposts, префікси заголовків
stem:/word:, захист від DNS-rebinding — лишаються в черзі.
[1.214.1] — 2026-08-25
Виправлено — описи Ashby тепер обрізаються до тієї ж довжини, що й Greenhouse та Recruitee, тож контент-фільтр поводиться однаково на трьох дошках, і одна дошка не може роздути кеш сканування.
Виправлено
- Описи Ashby тепер дотримуються того ж ліміту довжини, що й інші дошки. v1.214.0 подавала тіло вакансії Ashby в контент-фільтр, але — на відміну від Greenhouse і Recruitee, які ріжуть до 4000 символів — у Ashby воно мапилося напряму без ліміту (деякі вчетверо довші). Тож те саме слово
content_filterмогло збігтися на Ashby, але не на Greenhouse — лише залежно від позиції в тексті, а одна дошка могла додати сотні КБ до кешу сканування, який вантажить#/scan. Тепер Ashby ріжеться до тих самих 4000 символів (його текст уже простий, тож він усікається, а не очищується від тегів).
Примітки
- Набір тестів: 2738. Джерела сканування без змін — 82.
[1.214.0] — 2026-08-25
Виправлено — описи вакансій Greenhouse, Ashby і Recruitee тепер потрапляють до контент-фільтра, тож фільтри за словами / країною / візою реально збігаються з цими дошками; а “Associate <старший титул>” (включно з академічними званнями) більше не відносять до entry-level.
Виправлено
- Контент-фільтри тепер працюють на Greenhouse, Ashby і Recruitee. Ці дошки тепер несуть повний текст кожної вакансії як її опис, тож
content_filter(або фільтр за словами країни / візи) збігається з тілом вакансії, а не пропускає дошку наосліп. Ashby і Recruitee віддають тіло в списковому payload безкоштовно; Greenhouse уже це робив. Усі три очищаються одним спільним конвеєром HTML→текст. - “Associate Director” / “Associate Professor” — це senior, а не entry-level. Класифікатор рівня (використовує
skip_tiers:) вважав слово “associate” міткою entry-level, навіть коли воно передує старшому титулу. Тепер він читає “Associate <старший іменник>” — включно з академічними званнями (Associate Professor, Associate Dean) — як senior, зберігаючи справжні junior-варіанти (Associate Attorney, Associate Editor) як entry-level, і досі відносить провідний “Intern, Associate Dean” до стажування.
Примітки
- Набір тестів: 2736. Джерела сканування без змін — 82. У черзі на потім: опціональне збагачення описів SmartRecruiters (запит деталі на кожну вакансію), пропуск дощок
aggregator: trueу detect-reposts і префікси заголовків із якірним словомstem:/word:— кожне опціональне налаштування, а не зміна поведінки за замовчуванням. Захист від DNS-rebinding досі в черзі на окремий реліз безпеки.
[1.213.0] — 2026-08-22
Додано — MyCareersFuture, національний банк вакансій Сінгапуру, як джерело сканування. Виправлено — вакансії Greenhouse тепер несуть повний текст, щоб контент-фільтри працювали, а віддалені вакансії Ashby більше не ховаються за локацією «лише місто».
Додано
- MyCareersFuture (Сінгапур) (mycareersfuture.gov.sg) — нове джерело сканування без токенів для національного державного банку вакансій Сінгапуру (Workforce Singapore). Виберіть його у фільтрі Джерело на
#/scanабо додайте компанію зprovider: mycareersfutureта необовʼязковим спискомkeywords(інакше беруться цільові ролі з профілю, як у Job Bank). Читає публічний пошуковий API, з привʼязкою до хоста, без ключа.
Виправлено
- Вакансії Greenhouse тепер можна фільтрувати за контентом. Дошки Greenhouse запитуються з повним тілом вакансії, декодованим у простий текст як опис — тож
content_filter(або фільтр за словами країни/візи), що читає опис, тепер справді збігається з вакансіями Greenhouse, а не пропускає їх наосліп. - Віддалені вакансії Ashby більше не відсікаються фільтром міста. Ashby тримає модель роботи (Remote/Hybrid/Onsite) окремо від міста офісу, тож повністю віддалена вакансія все ще читалася як, наприклад, «San Francisco» — і фільтр локації, що блокує це місто, приховував доступну вакансію. Тепер за віддаленої роботи до локації додається «Remote», а
workplaceTypeважливіший за застарілий прапорецьisRemote, щоб офісно-прив’язану гібридну вакансію не позначили хибно.
Примітки
- Джерела сканування: 82 (77 англійських + 5 російських). Набір тестів: 2724. Захист від DNS-rebinding (перевірка розвʼязаної адреси хоста перед підключенням) відкладено в окремий реліз — йому потрібен власний дизайн для web-ui, а не прямий порт.
[1.212.1] — 2026-08-21
Виправлено — лендинг cvstart.org недорахував джерела сканера (показував 80 і губив Job Bank (Канада)); тепер знову збігається з 81 у застосунку, а збірка сайту гучно падає, якщо ці числа розходяться.
Виправлено
- Лічильник «Джерела вакансій» на лендингу знову синхронний із застосунком. Після v1.212.0 cvstart.org показував 80 дощок, і бракувало нового чипа Job Bank (Канада), тоді як застосунок, випадний список сканування та довідка перелічували 81. Лендинг будує список, завантажуючи живий реєстр сканера, і одне джерело не завантажилося в тій збірці через те, як воно підтягувало YAML-залежність — тож було мовчки відкинуте. Тепер Job Bank завантажує цю залежність ліниво, так само як решта застосунку робить під час скану, тому він завжди присутній.
- Збірка сайту тепер відмовляється публікувати неузгоджений лічильник джерел. Якщо реєстр перелічить менше джерел, ніж є на диску (ознака джерела, що не завантажилося), збірка падає з чітким повідомленням замість тихої публікації хибного числа.
Примітки
- Поведінка застосунку не змінилася — у сканері завжди були всі 81 джерело; зачепило лише лендинг. Джерела сканування: 81 (76 англійських + 5 російських) — без змін. Набір тестів: 2687.
[1.212.0] — 2026-08-21
Додано — Job Bank (Канада), федеральна національна дошка вакансій. Вилучено — EchoJobs (його фід тепер за бот-захистом). Виправлено — дошки на Consider знову повертають результати, а мульти-локаційні вакансії Lever більше не приховують половину локацій.
Додано
- Job Bank (Канада) (jobbank.gc.ca) — нове джерело сканування без токенів для федеральної національної служби зайнятості Канади, високонавантаженої дошки, яку погано покриває будь-який агрегатор. Виберіть його у фільтрі Джерело на
#/scanабо додайте компанію зprovider: jobbankcaта необовʼязковим спискомkeywords(інакше беруться цільові ролі з вашого профілю). Читає публічний ATOM-фід, з привʼязкою до хоста, без ключа.
Вилучено
- EchoJobs — виведено. Його публічний фід тепер за бот-захистом і нічого не повертає, тож тримати його — лише марнувати слот сканування.
Виправлено
- Дошки на Consider знову повертають результати. Consider тепер вимагає анонімний handshake (GET, що задає сесійну куку + CSRF-токен) перед прийняттям пошукового запиту; без нього запит мовчки відхилявся, і дошка виглядала порожньою.
- Мульти-локаційні вакансії Lever більше не приховують половину локацій. Lever кладе одне основне місто в
location, а решту — уallLocations; читання лише основного робило вакансію, відкриту в Барселоні І Монтевідео, «лише-Барселона» (і її помилково відсікав фільтр локацій). Тепер обидва обʼєднуються.
Примітки
- Мʼякший темп між сторінками (250 мс замість 150) на посторінкових дошках — із ввічливості до одногостових careers-сайтів. Джерела сканування: 81 (76 англійських + 5 російських) — без змін (Job Bank додано, EchoJobs вилучено). Набір тестів: 2685.
[1.211.0] — 2026-08-19
Додано — Yourator, тайванська tech-дошка вакансій. Виправлено — акцентовані сутності в заголовках/назвах компаній тепер декодуються всюди, а компанія з акцентом у назві більше не позначається помилково.
Додано
- Yourator (yourator.co) — нове джерело сканування без токенів для tech- та digital-ринку вакансій Тайваню. Виберіть його у фільтрі Джерело на
#/scanабо додайте компанію зprovider: yourator. Воно читає публічний JSON API (без ключа, без браузера), обходить усі сторінки дошки й видає справжнє посилання роботодавця (його власний ATS) з вирізаними трекінг-параметрами.
Виправлено
- Акцентовані іменовані сутності тепер декодуються всюди. Спільний HTML-декодер отримав латинські літери Latin-1 (
é→ é,ç→ ç, …), тож європейська дошка, що пишеDéveloppeurчиFrançais, більше не лишає цей літерал у заголовку, трекері чи згенерованому документі. (Великі лишаються великими —Éце É, а не é, — а пошук на кшталт&constructor;тепер розвʼязується в себе.) - Компанія з акцентом у назві більше не позначається помилково за перебування на власному домені. «Işık» тепер згортається в «isik» і збігається з isik.com.tr; «Société Générale» збігається з societegenerale.com. Стара перевірка видаляла акцентовані літери замість згортати їх до ASCII-основи.
Примітки
- Джерела сканування: 81 (76 англійських + 5 російських). Набір тестів: 2667.
[1.210.1] — 2026-08-19
Виправлено — заголовки вакансій і назви компаній з «&» чи лапками з Habr Career більше не надходять спотвореними.
Виправлено
- Джерело Habr Career тепер декодує HTML-сутності в заголовку та назві компанії, перш ніж вони підуть далі. Серверні картки надходять екранованими («Changellenge >>», «Demand Forecasting & Inventory Optimization», «ООО "М-ТЕХ"»), тож недекодований «&» тихо не проходив ваш же фільтр заголовка з «&» — саме той симптом, який минулий реліз закрив на п’яти інших дошках, — а назви компаній доїжджали до трекера й звітів кривими. Декодування сутностей тепер завершене на всіх шести уражених джерелах.
Примітки
- Набір тестів: 2644.
[1.210.0] — 2026-08-19
Додано — Senjob, перша африканська дошка вакансій сканера (Сенегал); точніше зіставлення заголовків ще на п’яти дошках.
Додано
- Senjob (senjob.com) — нове джерело сканування без токенів для Сенегалу, перша африканська дошка сканера. Виберіть його у фільтрі Джерело на
#/scanабо додайте компанію зprovider: senjob. Воно читає публічний список звичайним HTTP (без ключа, без браузера), прив’язує кожен запит до senjob.com і — оскільки парсить HTML — вважає список, що раптом дав порожньо, зламаною дошкою (видимою помилкою), а не країною без вакансій.
Виправлено
- Заголовки з «&» більше не втрачають вакансії на п’яти дошках — на beesite, Cornerstone (csod), Hacker News «Who is hiring», Phenom і TKMS заголовки надходять з HTML-екрануванням, тож екранований «&» у посаді на кшталт “R&D Engineer” не проходив ваше ж ключове слово “r&d”, і вакансія тихо зникала (вето “sales & marketing” теж не спрацьовувало). Тепер заголовки — і локації Phenom — декодуються перед фільтруванням.
Примітки
- Джерела сканування: 80 (75 англійських + 5 російських). Набір тестів: 2643.
[1.209.0] — 2026-08-17
Додано — вбудована довідка тепер описує, як записати підсумок відгуку, а «Запитати довідку» підкаже, де це.
Додано
- У довідці Трекера (§11) усіма 17 мовами зʼявився розділ «Записати підсумок», що проводить по кнопці Підсумок: оберіть, що сталося (відмова / оффер / найнято / відхилено / ігнор / пройшов на співбесіду), перегляньте, що буде зроблено, і запишіть — це фіксує результат, архівує надіслані вами резюме та супровідний лист і синхронізує Статус рядка за вас. Плаваючий помічник «Запитати довідку» читає цей посібник, тож тепер веде вас до цієї кнопки, а не лише пропонує правити Статус вручну.
Примітки
- Кожен довідковий бандл тепер 31 H2 / 119 H3 (було 118); вартові паритету оновлено. Лише документація — без змін коду й поведінки. Набір: 2625.
[1.208.2] — 2026-08-16
Виправлено — на телефоні кнопки сповіщень і теми більше не лежать поверх поля пошуку.
Виправлено
- У v1.208.1 кнопки верхньої панелі перестали накривати заголовок сторінки, але на вузькому — хоч і не найвужчому — екрані, особливо в мовах із довшими підписами, уся панель усе одно втискалася в один рядок, тож кнопки 🔔 і 🌙 могли лягти поверх поля пошуку. Тепер кнопки дій (сповіщення, тема, Діагностика, Відкрити Scan) на телефоні завжди переходять на власний другий рядок на всю ширину, тож поле пошуку видно повністю і ніщо не перекривається.
Примітки
- На телефоні кнопки дій панелі переходять на другий рядок на всю ширину, прибираючи крихку смугу «майже повного рядка», де розкладка розподіляла залишок відʼємного місця як накладання. Playwright-вартовий тепер відтворює точний тригер — локаль із довгими підписами в смузі 565–640px — і перевіряє, що елементи панелі не ділять пікселі. Набір: 2621.
[1.208.1] — 2026-08-16
Виправлено — на телефоні кнопки верхньої панелі більше не накривають сторінку.
Виправлено
- У v1.208.0 кнопки верхньої панелі (Діагностика, Відкрити Scan, сповіщення, тема) переносилися на другий рядок на вузьких екранах, але панель зберігала фіксовану висоту, тож перенесений рядок вилазив і лягав поверх заголовка сторінки. Тепер панель росте під свої рядки, а контент іде під нею.
Примітки
- Фіксована
heightпанелі сталаmin-height, тож вона росте під контент на будь-якій ширині (десктоп без змін). Playwright-вартовий тепер також перевіряє, що панель не вилазить поверх сторінки. Набір: 2621.
[1.208.0] — 2026-08-16
Виправлено — застосунок тепер вміщається на екрані телефона: горизонтальне прокручування зникло.
Виправлено
- На вузькому екрані весь застосунок їхав убік — верхня панель, таблиці, статті довідки й вкладки налаштувань вилазили за правий край. Тепер кожна сторінка вміщається на будь-якій ширині: кнопки верхньої панелі переходять на другий рядок, широкі таблиці й блоки коду прокручуються всередині своєї рамки, довідка ставить зміст над статтею, ряди кнопок і вкладок переносяться, а довгі шляхи чи URL переносяться, а не розтягують сторінку.
Примітки
- Причина — класична пастка flex/grid min-width: auto плюс кілька необгорнутих широких елементів; виправлено через
min-width: 0на grid-елементах,overflow-wrapна markdown/заголовках, прокручувану markdown-таблицю та перебудову сітки довідки на мобільному брейкпоінті. Playwright-вартовий перевіряє 0 горизонтального переповнення на 375 px на основних маршрутах.tests/playwright-smoke.mjs. Набір: 2621.
[1.207.2] — 2026-08-16
Виправлено — AI-плани та профілі карʼєрної орієнтації більше не показуються як сирий дамп коду.
Виправлено
- Деякі моделі загортають усю відповідь в код-огорожу
markdown …. Коли так ставалося, план розвитку і профіль орієнтації показувалися моноширинним блоком коду замість документа із заголовками та списками. Тепер обгортальну огорожу знято — лише якщо вона охоплює всю відповідь і мова явноmarkdown/md, тож справжня відповідь наpython/js/без мови ``` лишається недоторканою.
Примітки
- Оброблено один раз у спільному кроці очищення LLM (
cleanLlmMarkdown), тож виграють усі AI-маршрути, а вкладені блоки коду всередині відповіді зберігаються.tests/llm-output.test.mjs(+3). Набір: 2621.
[1.207.1] — 2026-08-16
Виправлено — на маленьких телефонах лендинг більше не вилазить убік.
Виправлено
- На вузькому телефоні перший екран — заголовок, вступний рядок і термінал встановлення — міг обрізатися по правому краю, бо довга команда встановлення та колонки верстки не стискалися під екран. Тепер усе вміщається на будь-якій ширині; команда встановлення прокручується всередині свого термінала.
Примітки
- Також зміцнено нестабільну E2E-перевірку, яка могла падати через тимчасовий 404 ресурсу: тепер вона ігнорує нешкідливий мережевий шум (favicon / зʼєднання / невдалий ресурс), як і сусідні перевірки, але й далі ловить справжні помилки скриптів. Поведінка застосунку не змінилася. Набір: 2618.
[1.207.0] — 2026-08-15
Додано — записуйте підсумок відгуку прямо з трекера.
Додано
- У кожному рядку трекера зʼявилася дія Підсумок: оберіть, що сталося (відмова, офер отримано, найнятий, офер відхилено, немає відповіді, пройшов на співбесіду), додайте необовʼязкову примітку, перегляньте підсумковий статус і запишіть. Запис архівує надіслані артефакти CV та супровідного листа й синхронізує трекер до канонічного стану — одна детермінована дія замість ручного редагування.
Примітки
- Нова
POST /api/outcomeретранслює CLI підсумків:dryRun:true— перегляд лише для читання (знаходить рядок, повідомляє підсумковий стан, нічого не пише); реальний виклик записує. Безпека запису: тип підсумку обмежено відомим набором, а кожне текстове поле відхиляється за керуючих символів до виклику (аргументи-масив, spawn — без оболонки).tests/outcome-route.test.mjs. Набір: 2618.
[1.206.0] — 2026-08-15
Документація — вбудований довідник тепер описує п’ять найновіших функцій усіма 17 мовами.
Додано
- Вбудований довідник — і асистент «Запитати довідку», що відповідає на його основі — тепер описує п’ять нещодавно доданих функцій: Доктор налаштування (Налаштування — перевіряє CV і профіль на прогалини та залишені демодані), Пошук ATS-дощок (Портали — автоматично знаходить карʼєрну дошку компанії), перевірку «ще відкрита?» (Трекер — чи вакансія ще відкрита), підказку «повторно використати попереднє CV?» (CV Studio — сигналізує, коли вже адаптоване CV пасує до нової вакансії) та Журнал навичок (Аналітика — запис оцінок самоперевірки). П’ять нових підрозділів, перекладених усіма 17 мовами.
Примітки
- Структура довідника зросла до 31 H2 / 118 H3, паритет гарантовано для кожної мови. Оновлено довідкову документацію:
docs/architecture/API.mdописує п’ять маршрутів цих функцій, а лічильники маршрутів/версії уCLAUDE.mdтаdocs/sdd/CONVENTIONS.mdактуальні (36 модулів маршрутів). Набір: 2610.
[1.205.0] — 2026-08-15
Додано — журнал навичок для запису результатів тестів/оцінювань.
Додано
- Новий Журнал навичок (Аналітика → Журнал навичок) дає змогу записати самооцінку — компанія, платформа, навичка, оцінка % та необовʼязкова примітка — додається до
data/assessments.tsv, зі списком минулих записів (нові згори). Без токенів, детерміновано; формат файлу веде CLI батьківського проєкту.
Примітки
- Новий
GET /api/assessments(ретранслює типовий список JSON зassessment-log.mjs; мʼяка відмова{available:false}) +POST /api/assessments(явний запис: поля передаються як аргументи-масив доassessment-log.mjs add). Безпека запису: будь-яке текстове поле з керуючими символами відхиляється (TAB зламав би стовпець, перенесення рядка вставило б рядок) → 400 до запису; оцінка/поріг 0–100, довжини обмежені.tests/assessments-route.test.mjs. Набір: 2610.
[1.204.0] — 2026-08-15
Додано — панель «Доктор налаштування» в Налаштуваннях, що позначає незаповнені чи демо-дані CV/профілю.
Додано
- Налаштування → Доктор налаштування тепер без токенів перевіряє ваші
cv.mdіconfig/profile.ymlта перелічує блокуючі проблеми (відсутні файли/поля) і попередження (залишені демо/шаблонні дані, зашиті метрики) — щоб виявити неповне налаштування, перш ніж воно послабить скани й адаптацію. Лише читання; перезапуск одним кліком.
Примітки
- Новий route лише для читання
GET /api/cv-sync-checkретранслюєcv-sync-check.mjsз батьківського проєкту, який друкує текст + код виходу (без--json); route легко структурує його стабільні рядкиERROR:/WARN:у{ok, errors[], warnings[]}— успіх визначає банер, а не код виходу. М’яка відмова{available:false}в автономних установках.tests/cv-sync-check-route.test.mjs. Набір: 2602.
[1.203.0] — 2026-08-15
Додано — підказку «повторно використати попереднє резюме?» у CV Studio.
Додано
- Коли ви відкриваєте збережену вакансію в CV Studio, застосунок тепер порівнює її з іншими вашими збереженими вакансіями (детерміноване перекриття слів, без токенів) і підказує, чи найближча схожа настільки, щоб повторно використати те адаптоване резюме, використати його з правками чи готувати нове — щоб не створювати з нуля для ролі, над якою ви вже працювали.
Примітки
- Новий route лише для читання
GET /api/jds/:name/reuseретранслюєjd-similarity.mjsз батьківського проєкту (перекриття Жаккара + перевірка сеніорності; JSON{decision, score, reason}) по разу на кожну попередню вакансію (fan-out обмежено 25, береться найкраще); м’яка відмова{available:false}, якщо скрипта чи попередніх вакансій немає.tests/jd-similarity-reuse-route.test.mjs. Набір: 2594.
[1.202.0] — 2026-08-15
Додано — знайдіть ATS-дошку компанії просто на #/portals і почніть відстежувати її.
Додано
- На #/portals введіть назву компанії — застосунок перевірить Greenhouse, Ashby і Lever на її публічну дошку (без LLM, без браузера) і покаже дошки, які існують і зараз мають ≥1 вакансію. Один клік додає обрану дошку до компаній, за якими стежить сканер. Перевірка лише для читання; запис у
portals.ymlвідбувається тільки після натискання Додати.
Примітки
- Новий
server/lib/discover-ats.mjs(проба слага з фіксованим хостом і перевіркою charset черезsafeGetіз закріпленим DNS, ≤12 проб на запит) +POST /api/portals/discover(лише читання) таPOST /api/portals/track(явний запис:withFileLock+ текстова вставка + перевірка повторного парсингу + атомарна заміна; лише відомі ATS-хости, ідемпотентно). Повторно використовує реєстр адаптерів сканера. i18n ×17.tests/discover-ats-resolver.test.mjs+tests/discover-ats-route.test.mjs. Набір: 2588.
[1.201.0] — 2026-08-15
Виправлено — трекер із локалізованими або альтернативними заголовками стовпців більше не відображається порожнім.
Виправлено
- Якщо ваш
data/applications.mdвикористовує неанглійські чи альтернативні заголовки — іспанськіempresa/puesto/estado/fecha/enlace, абоposition/stage/link— трекер читав їх за неправильними ключами й показував порожні стовпці Компанія / Роль / Статус / Дата / Посилання. Тепер такі заголовки згортаються до канонічних імен полів, і трекер відображається правильно. Англомовний трекер обробляється точно як раніше.
Примітки
- Нова карта
HEADER_ALIASESі згортка нормалізації вparseApplications(server/lib/parsers.mjs); невідомі або вже канонічні заголовки проходять без змін.tests/tracker-header-aliases.test.mjs. Набір: 2563.
[1.200.0] — 2026-08-15
Додано — перевірка «ще активна?» для вакансій на ATS одним кліком у трекері.
Додано
- На #/tracker відгук, чий URL — вакансія Greenhouse / Lever / Ashby / Workday / SmartRecruiters, тепер має кнопку «Ще активна?». Один клік запитує публічний JSON самого ATS — без токенів, без браузера — і показує Активна / Закрита / Невідомо, щоб знаходити мертві вакансії, не відкриваючи кожну. Консервативно за замовчуванням: лише явний 404/410 читається як Закрита; усе неоднозначне лишається Невідомо (ніколи хибної Закрита).
Примітки
- Нові
server/lib/liveness-core.mjs+liveness-api.mjsі route лише для читанняGET /api/liveness?url=(без записів, без LLM). Безпечно щодо SSRF: URL проходитьisValidJobUrl, далі до API ATS звертаємось лише черезsafeGet(із закріпленим DNS), з фіксованим хостом і перевіреними сегментами.tests/liveness-core.test.mjs+tests/liveness-route.test.mjs. Набір: 2557.
[1.199.0] — 2026-08-15
Виправлено — широкі таблиці тепер прокручуються вбік, а не обрізаються.
Виправлено
- На сторінці Scan (та в усіх інших таблицях — Трекер, Статистика, Використання, Дашборд) таблиця, ширша за вікно, обрізалася без смуги прокрутки, і до правих стовпців було не дістатися. Тепер широкі таблиці показують горизонтальну смугу прокрутки за потреби, тож будь-який стовпець доступний за будь-якої ширини.
Примітки
.table-wrapуpublic/css/components.cssпереключено зoverflow: hiddenнаoverflow-x: auto(як у наявного контейнера.reports-scroll); заокруглену рамку збережено.tests/table-wrap-scroll.test.mjs. Набір: 2540.
[1.198.0] — 2026-08-15
Додано — повтори сканування тепер використовують експоненційну затримку, джитер і враховують Retry-After від обмежувача частоти.
Додано
- Коли дошка вакансій ненадовго обмежує частоту або віддає помилку (HTTP 429 / 5xx) під час сканування, повтор тепер чекає з експоненційною затримкою + джитером замість фіксованої короткої паузи — зайняту дошку не гатять з тим самим інтервалом, а одночасні повтори не стикаються синхронно. Надісланий дошкою
Retry-Afterвраховується (але з обмеженням, щоб ворожийRetry-After: 86400не застопорив усе сканування). Постійні помилки (404, відхилені перенаправлення) досі падають одразу — без змін.
Примітки
- Нові
parseRetryAfterMs()і чистаcomputeRetryDelayMs()уserver/lib/http-json.mjs;fetchJsonтепер захоплює.retryAfterна не-ok відповіді, аfetchJsonWithRetryприймає необов’язковийmaxDelayMs(типово 8000).tests/http-json.test.mjs(+9). Набір: 2536.
[1.197.0] — 2026-08-14
Додано — відстежуйте дошку вакансій VC-фонду Getro лише за її careers_url; id колекції визначається автоматично.
Додано
- Відстежуваній дошці Getro (b2venture, Earlybird, Point Nine, …) більше не потрібен знайдений вручну числовий
getro_collection. Вкажіть власнийcareers_urlдошки, і id визначиться автоматично з цієї сторінки під час першого сканування — один захищений від SSRF GET читає числовийnetwork.idпрямо з вбудованих даних сторінки. Явнийgetro_collectionдосі має пріоритет і повністю пропускає завантаження.
Примітки
- Нові
httpsCareersUrl(),extractCollectionId()та асинхроннийresolveCollectionId()уserver/lib/sources/getro.mjs; сторінка дошки завантажується черезsafeGet(закріплений DNS, обмежений розмір), а визначений id досі прив’язаний до хостаapi.getro.comчерезassertGetroUrl. Адаптер тепер збігається із записомprovider: getro, що несе httpscareers_url, навіть без id.tests/sources-getro.test.mjs(+8). Набір: 2527.
[1.196.0] — 2026-08-14
Виправлено (безпека) — адаптер Workday перевіряє ендпоінт api за іменем хоста, а не за підрядком.
Виправлено
- Значення
api:Workday уportals.ymlтепер приймається лише коли його ім’я хоста —myworkdayjobs.com(або піддомен.myworkdayjobs.com). Стара перевірка була пошуком підрядка, тож будь-який URL, що просто містив рядок — напр.https://example.com/?x=myworkdayjobs.com— проходив і міг бути використаний як ендпоінт. Справжні ендпоінти Workday не зачеплені. (Повідомлено CodeQL, #443.)
Примітки
- Новий
isWorkdayApi()парсить URL і перевіряє хост (server/lib/portals/adapters/workday.mjs).tests/workday-adapter-endpoint.test.mjs(+1). Набір: 2522.
[1.195.0] — 2026-08-14
Продуктивність (сканер) — виявлення переоприлюднень лишається швидким на великих історіях сканів.
Продуктивність
- Виявлення дублів більше не деградує в O(N²) на великому
scan-history.tsv. Групування заголовків за компанією було вкладеним циклом, що платив повнийroleFuzzyMatchна кожну пару; тепер це інвертований індекс — за один прохід бакетуємо рядки за точним заголовком, потім нечітке порівняння лише між РІЗНИМИ бакетами, що ділять розрізнювальний (небазовий) токен. Вивід ідентичний — ті самі кластери — доведено диференційним тестом проти старого алгоритму на 200+ випадкових історіях.
Примітки
groupRowsByTitleуserver/lib/detect-reposts.mjs(експортовано для диф-тесту).tests/detect-reposts-grouping.test.mjs(+2). Набір: 2521.
[1.194.0] — 2026-08-14
Виправлено (сканер) — кар’єрні сторінки Workday з URL з одного сегмента тепер скануються коректно.
Виправлено
- Адаптер Workday тепер розбирає кар’єрні URL, шлях яких — один сегмент — напр.
https://parsons.wd5.myworkdayjobs.com/Search,.../KBR_Careers,.../Careers. Раніше сайт падав уExternal, адаптер бив не в той CXS-ендпоінт, і проба могла виглядати справною, нічого не повертаючи. Тепер береться перший непорожній сегмент шляху як сайт (відкидаючи мовний префікс на кшталтen-US); документований випадок/en-US/Externalне змінюється. (Повідомлено в #255.)
Примітки
- Структурний розбір шляху в
server/lib/portals/adapters/workday.mjs.tests/workday-adapter-endpoint.test.mjs(+7). Набір: 2519.
[1.193.0] — 2026-08-14
Додано (статистика) — вкладка «Тиша після співбесіди», що показує співбесіди, яким варто нагадати.
Додано
- Вкладка Тиша після співбесіди в
#/stats: співбесіди, що замовкли після «ввічливого» вікна (типово 30 днів), поєднуючи активні співбесіди й трекер — із тим, як довго кожна мовчить, датою останньої співбесіди та причиною. М’який список нагадувань/закриття; тільки рекомендації, ніколи не заява про відмову. Без токенів.
Примітки
- Новий релей
GET /api/stats/rejection-latency(fail-soft{available:false}).tests/stats-rejection-latency-route.test.mjs(+2). +10 ключів i18n ×17; help-hint#/statsз 7→8 вкладок. Набір: 2510.
[1.192.0] — 2026-08-14
Додано (cv-studio) — запобіжник «Перевірка фактів у CV», що ловить числа, яких у вас не було.
Додано
- Картка Перевірка фактів у CV в
#/cv-studio: вставте адаптоване CV чи лист і звірте кожну метрику й факт із вашими справжніми CV, профілем та two-pager. Ви отримуєте вердикт pass / warn / block плюс точні вигадані метрики, необґрунтовані факти та заборонені / попереджувальні фрази. Без LLM; нічого не пишеться.
Примітки
- Новий релей
POST /api/cv-studio/verify-facts: пише текст у одноразовий тимчасовий файл і запускаєverify-cv-facts.mjs, довіряючи JSON-вердикту, хоча скрипт виходить з 1 на block.tests/cv-studio-verify-facts-route.test.mjs(+4). +15 ключів i18n ×17. Набір: 2508.
[1.191.0] — 2026-08-14
Додано (статистика) — вкладка «Що вчити далі», що ранжує навички для вивчення.
Додано
- Вкладка Що вчити далі в
#/stats: зведення по всьому трекеру — відсутні навички, що найчастіше топили низьку відповідність, зважені (за 5−оцінкою відповідності в кожному звіті) і за грейдами Critical / High / Medium — плюс ті, що вже покриті вашим CV/профілем. Лише читання, тільки рекомендації, без токенів.
Примітки
- Новий релей
GET /api/stats/upskill(поле{ error }за браку даних; fail-soft{available:false}).tests/stats-upskill-route.test.mjs(+3). +15 ключів i18n ×17. Набір: 2504.
[1.190.0] — 2026-08-14
Додано (трекер) — панель «Історія компанії», що показує, які компанії реально вам відповідають.
Додано
- Картка Історія компанії на
#/tracker: виберіть компанію й отримайте свідчення лише для читання — наскільки вона відповідала вам (мовчить у відповідь / змішано / відповідала раніше) і чи переоприлюднюється та сама вакансія — поєднуючи трекер, follow-up та історію сканів. Без токенів; сканер не викликається.
Примітки
- Новий релей
GET /api/stats/company-history[?company=](fail-soft{available:false}).tests/stats-company-history-route.test.mjs(+3). +18 ключів i18n ×17. Набір: 2501.
[1.189.0] — 2026-08-14
Виправлено (сканер) — рівні грейду, записані римськими цифрами, тепер враховуються й у нелатинських заголовках.
Виправлено
- Класифікатор грейду за
skip_tiersтепер читає суфікс рівня римськими цифрами (I / II / III / IV / V) після слова посади в будь-якій писемності — «Инженер III», «エンジニア I», «Ingénieur IV» — не лише після ASCII-слів. Раніше цифра рівня після нелатинського слова ігнорувалася й вакансія падала до mid, тожskip_tiers: [senior]чи[entry]їх пропускав.
Примітки
- Незалежний від писемності lookbehind у
server/lib/classify-tier.mjs; видалено мертвий дубль-матчерSr..tests/classify-tier.test.mjs(+1). Набір: 2498.
[1.188.0] — 2026-08-14
Виправлено (UI) — основні кнопки дій більше не притиснуті впритул до підзаголовка сторінки.
Виправлено
- Рядок основної дії / керування на сторінках Щотижневий дайджест співбесід, Профінансовані компанії, Портали, Кар’єрний план та Кар’єрна орієнтація тепер має верхній відступ, тож кнопка «дихає» під підзаголовком, а не впирається в нього.
Примітки
- Регрес-гард
tests/lead-row-top-margin.test.mjs(+5). Набір: 2497.
[1.187.0] — 2026-08-14
Виправлено (сканер) — налаштування skip_tiers знову працює: вакансії, які ви просили пропускати за грейдом, відкидаються.
Виправлено
- Список
skip_tiers:уportals.yml(напр.skip_tiers: [intern, entry]) тепер враховується скануванням. Заголовок кожної вакансії класифікується за грейдом (intern / entry / mid / senior) і відкидається, якщо його грейд у списку. Раніше сканування застосовувало фільтри заголовка / локації / контенту / довіри, але не мало фільтра за грейдом, тожskip_tiersмовчки ігнорувався. Заголовки без слова рівня потрапляють у mid (тожskip_tiers: [mid]відкидає й більшість звичайних вакансій), а класифікатор читає НАЙЛІВІШЕ слово рівня.
Примітки
- Новий чистий модуль
server/lib/classify-tier.mjs(classifyTier+buildTierFilter), підключений до ланцюгів фільтрів EN- та RU-сканерів.tests/classify-tier.test.mjs(+7). Набір: 2492.
[1.186.0] — 2026-08-14
Додано (CV-студія) — панель «Розрив навичок»: які з потрібних навичок вакансії ваше CV називає, натякає або яких немає.
Додано
- Нова панель Розрив навичок у CV-студії. Виберіть збережену вакансію — і кожен потрібний навик розподіляється на названо у вашому CV, мається на увазі в CV або відсутнє — порівняння слів без LLM, нічого не записується. Якщо у вакансії не було чіткого розділу вимог, з’являється позначка про низьку впевненість.
Примітки
- Новий
GET /api/jds/:name/skill-gap(ім’я вакансії проходить path-санітизацію та перевірку наявності підjds/, перш ніж стати аргументом; м’який відкат до{available:false}без скрипта). +13 ключів i18n ×17. Тести:tests/jds-skill-gap-route.test.mjs(+4, включно з відмовою на path-traversal). Набір: 2485.
[1.185.0] — 2026-08-14
Додано (статистика) — вкладка «Лійка та швидкість»: як ваша лійка співвідноситься з ринком і як швидко ви рухаєтесь між етапами.
Додано
- Нова вкладка Лійка та швидкість у Статистиці показує вашу частку відповідей і частку інтерв’ю поряд із ринковими орієнтирами (із застереженнями про малу вибірку та зміщення відбору), список очікування активних відгуків поза типовим вікном першої відповіді та медіанні дні за етапами (Відгук → Відповідь → Інтерв’ю → Оффер) — повільні рядки цензуруються справа, щоб не зміщувати медіани. Лише читання, нуль токенів; читає лише ваш трекер.
Примітки
- Новий
GET /api/stats/funnel(м’який відкат до{available:false}без скрипта). +18 ключів i18n ×17. Тести:tests/stats-funnel-route.test.mjs(+2). Набір: 2481.
[1.184.0] — 2026-08-14
Виправлено (UI) — плитки швидких дій на Дашборді тепер вишиковуються в рівну сітку.
Виправлено
- На Дашборді (Командному центрі) група з 3 плиток малювалася ширше, ніж із 4, через що секції мали рваний правий край. Тепер кожна група використовує колонки однакової ширини (4 на широкому екрані, зі зниженням до 3 / 2 / 1 у міру звуження вікна), тож усі плитки однакового розміру, а їхні праві краї вирівняні.
Примітки
- Лише CSS (
.qa-grid: фіксованеrepeat(N, minmax(0,1fr))замістьauto-fill). Захищеноtests/dashboard-grid-align.test.mjs(+2). Набір: 2479.
[1.183.0] — 2026-08-14
Додано (сканер) — розумніше виявлення дублікатів: та сама вакансія, перевикладена з посиланням-трекером, більше не показується двічі.
Додано
- Сканер тепер розпізнає вакансію за канонічним URL-ключем, тож та сама вакансія, перевикладена з параметром-трекером (
?utm_…,gclid, …), черезhttpvshttps, або з кінцевим слешем /#фрагментом, вважається однією вакансією — без дубля в результатах скану чи пайплайні й без марної оцінки вже баченої вакансії. Справді різні вакансії (збережений функціональний id на кшталтgh_jid) досі рахуються окремо.
Примітки
- Новий
server/lib/url-key.mjs, підключений до дедупу обох сканерів і до запису пайплайна. Навмисно недо-нормалізує — ніколи не зливає дві різні вакансії. Тести:tests/url-key.test.mjs(+5),tests/parsers.test.mjs(+1). Набір: 2477 (+6).
[1.182.0] — 2026-08-14
Виправлено (сканер) — діапазони зарплат тепер однаково читаються всіма мовами.
Виправлено
- Суми зарплат у рядках скану та трекера використовують локале-нейтральні символи ≥ і ≤ (напр.
≥ 120000 EUR,≤ 90000) замість англійських слів “from” / “up to”, які потрапляли без перекладу в неанглійський інтерфейс. Стосується всіх дощок з одностороннім діапазоном (Getro, Remotli, Manfred, Agentic Jobs, JustJoin, Jobicy); двосторонні діапазони (100000–150000 USD) і так були нейтральні.
Примітки
- Лише відображення — клієнтський фільтр зарплати розбирає числа незалежно від префікса, тож фільтрація не змінюється. Набір: 2471.
[1.181.0] — 2026-08-14
Додано (сканер) — дошки Getro тепер показують зарплату, усі локації та віддалені вакансії.
Додано
- Сканер Getro (дошки венчурних talent-мереж) тепер показує зарплату за кожною вакансією (річний діапазон + валюта), перелічує всі локації замість лише першої та позначає віддалені вакансії. Вакансія Getro у скані та трекері тепер несе ті самі дані про зарплату й локацію, що й інші дошки.
Примітки
- Лише сканер; без нових залежностей, без змін маршрутів / CSP / SSRF. Тести:
tests/sources-getro.test.mjs(+5). Набір: 2470 (+5).
[1.180.0] — 2026-08-14
Виправлено (СЕРЕДНЄ, звіти) — список #/reports тепер таблиця, і відновлено справжню оцінку, яку приховував заповнювач Machine Summary.
Виправлено
- Список
#/reports— це таблиця (Звіт · Дата · Легітимність · Оцінка), а не сітка з 4 карток. Довгий чип «Оцінку не виявлено» стискав колонку заголовка майже до нуля, аoverflow-wrap: anywhereу заголовку картки переносив назву звіту по одному символу. Тепер кожне поле має власну колонку, клітинка назви переноситься за словами, а на вузькому екрані таблиця прокручується горизонтально (новий контейнер.reports-scroll). Новий ключ i18nrep.colReport×17. - Справжня оцінка в тілі (
**Итоговый балл:** 1.8 / 5) більше не приховується заповнювачем Machine Summary (score: —). Коли блок## Machine Summaryмістив нечислову або поза діапазоном оцінку, вона займала слот розібраної оцінки й блокувала запасний розбір за формою-значенням у жирному, тож звіт показував «Оцінку не виявлено», попри справжнійX / 5у тілі. ТеперparseReportHeaderвідновлює форму-значення з тіла, якщо придатного числа не залишилося (крок 4.5).
Примітки
- Лише клієнт + парсер; без змін маршрутів / CSP / SSRF / запису в батьківський проєкт. Тести:
tests/reports-table.test.mjs(+5),tests/report-header-locale.test.mjs(+2). Набір: 2465 (+7).
[1.179.0] — 2026-08-13
Змінено (LOW, сканер) — 20 дубльованих декодерів HTML-сутностей зведено до спільного модуля (наслідок паритету, закриває worklist).
Змінено
- 20 скрейпінг-джерел мали кожне свій
decodeEntities/decodeXmlEntities(+ хелперfromCodePoint) — копії, що дрейфували (три могли кинутиRangeError, виправлено в v1.172.0; інші пропускали NUL/C0 або неправильно розбиралиa2;). Тепер усі проходять через єдинийserver/lib/html-entities.mjs(декодер, безпечний за XML 1.0 Char), прибрано ~237 рядків дублювання. 8 RSS-подібних джерел отримали декодування (раніше знали лише 5 сутностей); навмисне подвійне декодування cryptocurrencyjobs збережено через аліас.hhлишає свій декодер (обробляє—/–, поза спільними 6). Новий гард-тест падає, якщо джерело знову заведе локальний декодер.
Примітки
- Рефакторинг зі збереженням поведінки; без змін маршрутів / CSP / SSRF / запису в батьківський проєкт. Тести:
tests/decoder-consolidation.test.mjs(+2). Набір: 2458 (+2).
[1.178.0] — 2026-08-13
Виправлено (LOW, паритет з батьком) — дві застарілі константи оновлено під батька (PARENT-SYNC GAP #4 + #5).
Виправлено
- User-Agent браузера (GAP #4) —
BROWSER_LIKE_USER_AGENT(його шлють workable/workday/oraclecloud/a16z/eightfold для проходження WAF/бот-фільтрів) піднято з Chrome 131 до 151 під батьківськийuser-agent.mjs; застарілу збірку частіше блокують. Захищено тестомChrome major ≥ 151. - FALLBACK станів трекера (GAP #5) — резервний
FALLBACKуstates.mjs(використовується лише коли живийtemplates/states.ymlнечитабельний — свіжий клон / ізольований CI-корінь) отримав турецькі аліаси станів із батька (#2615): değerlendirildi, başvuruldu, yanıt verildi, mülakat, teklif, reddedildi, iptal edildi, uygun değil, kabul edildi/işe alındı. У проді живий файл уже їх давав.
Примітки
- Лише дві константи; без змін маршрутів / CSP / SSRF / запису в батьківський проєкт. Тести:
tests/http-json.test.mjs(+1) +tests/states.test.mjs(+1). Набір: 2456 (+2).
[1.177.0] — 2026-08-13
Виправлено (MEDIUM, сканер) — csod (Cornerstone) повертав 0 вакансій в орендарів, які закривають search-API сесійними куками (parent #2769, PARENT-SYNC GAP #1).
Виправлено
- Деякі орендарі Cornerstone виставляють сесійні куки на bootstrap-сторінці кар’єрного сайту й відповідають
401 CSOD Unauthorizedна search-API, поки ці куки не повернуться разом з анонімним bearer-токеном.sources/csod.mjsтепер читає bootstrap через новий хелперfetchResponse, будує заголовокCookieзі значеньSet-Cookie(cookieHeaderFrom— лише name=value, семантика jar) і повторює його в POST пошуку. Лише той самий origin (хост закріплено +redirect:'error'), тож сесійні куки не потраплять третій стороні; орендар без кук поводиться як раніше.
Примітки
- Новий
server/lib/http-json.mjs::fetchResponse(аддитивно; наявні джерела не зачеплено). Без змін маршрутів / CSP / SSRF / запису в батьківський проєкт. Тести:tests/sources-parity-v1118a.test.mjs(+1). Набір: 2454 (+1).
[1.176.0] — 2026-08-13
Виправлено (MEDIUM, звіти) — бал під жирною міткою, якої немає в RU-таблиці, все ще показував «Score not detected» (FIND-5).
Виправлено
- Два RU-звіти записували бал як
**Итоговый балл:** 1.8 / 5/**Скор:** 1.8 / 5— жирні мітки, якихREPORT_LABELS.ruне перелічує (знає лише «Оценка»/«Балл»), тож бал не розбирався. Замість нарощування списку синонімівparseReportHeaderтепер відкочується до форми значення: дробу за рубрикою /5 під БУДЬ-ЯКОЮ жирною міткою. Це незалежно від мови, несприйнятливо до заголовка (немає**, немає значення/5) і відхиляє дату на кшталт5/5/2026(негативний перегляд уперед за знаменником).
Примітки
- Лише парсер на сервері; без змін маршрутів / CSP / SSRF / запису в батьківський проєкт. Тести:
tests/report-header-locale.test.mjs(+2). Набір: 2453 (+2).
[1.175.0] — 2026-08-13
Виправлено (LOW, зміцнення) — регрес-гард для SEO-опису з FIND-3 + null-безпечне очищення легітимності (за наслідками AI-рев’ю).
Виправлено
- Гард паритету SEO-опису — правка v1.174.0, що замінила жорстко зашите «~55» у
meta.descкожної мови на плейсхолдер{adapters}з реєстру, не мала тесту й могла тихо регресувати при наступному редагуванні локалі. Новий CI-ізольованийtests/site-meta-desc-parity.test.mjsпадає, якщо будь-який із 17site/src/i18n/*.jsonвтрачає плейсхолдер або знову зашиває число, або якщоLanding.astroперестає підставляти його в усі три meta-описи. - Null-безпечне очищення легітимності —
stripEmphasisповертає''для nullish-входу замість рядка «undefined» (поля ініціалізуються рядком, тож це захист углиб).
Примітки
- Тест + однорядковий гард у парсері; без змін маршрутів / CSP / SSRF / запису в батьківський проєкт. Тести:
tests/site-meta-desc-parity.test.mjs(+3). Набір: 2451 (+3).
[1.174.0] — 2026-08-13
Виправлено (HIGH, звіти) — локалізовані звіти показували «Score not detected»; SEO-опис застарів.
Виправлено
- Розбір оцінки (FIND-1) — звіт не англійською, чий H1 містить слово-мітку оцінки (
# Оценка вакансии: <назва>), більше не приймає цю назву за оцінку.parseReportHeaderтепер якориться на локалізованій жирній мітці (**Оценка:** 1.5 / 5), пропускає рядки-заголовки і вимагає, щоб мітка стояла впритул до двокрапки — тож RU-звіти, що показували «Score not detected», відображають справжню оцінку. - Чип легітимності (FIND-2) — зі значення прибираються markdown-зірочки, тож чип показує «High Confidence», а не «** High Confidence».
- Переповнення оцінки — рядок оцінки з хвостовим статусним текстом («1.8, Status: Evaluated, …») стискається до самої оцінки; у
.score-pillз’являється обмеження без переносу/overflow, а колонка заголовка може стискатися — тож кольоровий чип не вилазить за край картки. - SEO-опис (FIND-3) — meta / OG / Twitter-описи cvstart.org (усі 17 мов) жорстко містили «Scan ~55 job boards», тоді як тіло сторінки рахувало реальний реєстр («~75»). Тепер опис підставляє число з реєстру, тож розсинхрон не повториться.
Примітки
- Парсер на сервері + рендер/CSS клієнта + i18n сайту; без змін маршрутів / CSP / SSRF / запису в батьківський проєкт. Тести:
tests/report-header-locale.test.mjs(+4). Набір: 2448 (+4).
[1.173.0] — 2026-08-13
Додано (LOW, налаштування) — Hermes додано до списку виявлюваних AI-CLI (паритет із career-ops).
Додано
- Вкладка
#/config→ «Інструменти AI CLI» тепер виявляє Hermes (Nous Research) — нове середовище виконання агента, яке підтримує батьківський проєкт (бінарникhermes). Фіксований список уserver/lib/routes/cli-detect.mjsзростає з 10 до 11 інструментів; виявлення лишається скануванням PATH лише для читання (жоден бінарник не запускається).
Примітки
- Без змін i18n / маршрутів / CSP / SSRF / запису в батьківський проєкт; список фіксований, не ввід. Набір: 2444 (канарка cli-detect оновлена 10 → 11).
[1.172.0] — 2026-08-13
Виправлено (MEDIUM, сканер) — некоректна HTML-сутність могла обвалити джерело сканування (паритет із career-ops #2150).
Виправлено
- Джерела
oraclecloud,gemіdassaultдекодували числові HTML-сутності зі слабкою перевіркоюNumber.isFiniteпередString.fromCodePoint— посилання понад0x10FFFF(наприклад,�з некоректного чи ворожого фіду) кидало неперехопленийRangeErrorі переривало весь розбір цього джерела. Спільний модульserver/lib/html-entities.mjs(дзеркало_html-entities.mjsбатьківського проєкту) тепер обмежує числові посилання набором Char XML 1.0 §2.2, тожString.fromCodePointне може кинути виняток, і зіставляє шістнадцяткові та десяткові окремо, томуa2;більше не розбирається неправильно. Три джерела його імпортують.
Примітки
- Для коректних фідів поведінка не змінюється; без змін JS / i18n / маршрутів / CSP / SSRF / запису в батьківський проєкт. Консолідація решти ~20 копій декодера у джерелах відстежується в
qa/PARENT-SYNC-WORKLIST-v1.26.0.md. - Тести:
tests/html-entities.test.mjs(+7). Набір: 2444 (+7).
[1.171.0] — 2026-08-13
Змінено (НИЗЬКЕ, дизайн-система) — токени типографічної шкали + шари z-index (D-4, перший крок). Розміри та накладання були літералами в кожному компоненті.
Змінено
- Шари z-index — додано токени
--z-*(--z-topbar…--z-skiplink) і мігровано кожен літерал z-index. Значення збережені, порядок накладання ідентичний; новий канарковий тест забороняє нові «магічні» числа. - Типографічна шкала — рампа
--font-size-*(xs 11…2xl 28, base = Inter 15px); мігровано основні розміри (без візуальних змін). Значення поза рампою мігрують поступово (docs/UX-ROADMAP.md).
Примітки
- Лише CSS-токен; без змін поведінки/JS/i18n/маршруту/CSP/SSRF/запису. Без піксельних змін.
tests/design-tokens-scale.test.mjs(+3). Набір: 2437 (+3).
[1.170.0] — 2026-08-13
Додано (НИЗЬКЕ) — чесні підказки ETA для довгих AI-генерацій (P4-ETA). Важкі генерації (кар’єрний план ~40 с, орієнтація / ринок / нетворкінг ~30 с, two-pager ~20 с) показували лише «Генерація…» без відчуття тривалості.
Додано
- Поруч із кожною кнопкою довгої генерації тепер показується приглушена підказка
⏱ ~N с(як ETA на#/auto). Спільний стиль.eta-hint+ два узагальнені ключі (common.eta~{n}s,common.etaTitle).
Примітки
- Лише клієнт; без змін маршруту/CSP/SSRF/запису. +2 ключі i18n ×17 (знімок 1219 → 1221).
tests/generation-eta-hint.test.mjs(+2). Набір: 2434 (+2).
[1.169.0] — 2026-08-13
Додано (НИЗЬКЕ) — inline-перегляд PDF (D-5). GET /api/output/pdfs/:name примусово ставив Content-Disposition: attachment, тож навіть кнопка «Відкрити» на #/cv завантажувала файл, а не показувала його.
Додано
?inline=1віддає ТОЙ САМИЙ перевірений файл ізContent-Disposition: inline, для 👁 Перегляду в новій вкладці; за замовчуванням — досі завантаження. Без нових маршрутів; ті самі перевірки імені.- Перша кнопка списку PDF на
#/cvтепер 👁 Перегляд поруч із ⬇ Завантажити.cv.openPdf«Відкрити» → «Перегляд» ×17.
Примітки
- Без змін CSP/SSRF — той самий
sanitizePathName. Один наявний ключ i18n переписано ×17 (знімок 1219).tests/output-pdfs.test.mjs(+3). Набір: 2432 (+3).
[1.168.0] — 2026-08-13
Виправлено (НИЗЬКЕ, a11y) — рядки чекбоксів тепер відповідають мінімуму 24×24 WCAG 2.5.8 (D-2). Мітки чекбоксів/радіо на #/scan, #/config, #/evaluate і #/cv-studio були в смузі ~22 px.
Виправлено
- Обмежене правило
label:has(> input[type="checkbox"/"radio"]) { min-height: 24px }гарантує смугу ≥24 px. Лишеmin-height— мітки вже flex, нічого не зсувається;.apply-checklist(32 px) уже відповідав.
Примітки
- Лише CSS; без змін поведінки/JS/i18n/маршруту/CSP/SSRF/запису.
tests/checkbox-target-size.test.mjs(+1). Набір: 2429 (+1).
[1.167.0] — 2026-08-13
Виправлено (НИЗЬКЕ, дизайн-система) — підняті поверхні тепер відділяються від розділювачів (D-3). Токени --panel-2 / --surface-elev1 розв’язувалися в --slate — те саме значення, що й розділювачі --line / --border — без візуального відділення.
Виправлено
- Окремий токен з урахуванням теми
--elev(світла#eef1f6/ темна#1e232e, відрізняється від--slateв обох темах) тепер стоїть за піднятими поверхнями; розділювачі лишаються на--slate. Решта знахідок (D-2, D-4, D-5, P4-ETA) — у беклозіdocs/UX-ROADMAP.md.
Примітки
- Лише CSS-токен; без змін поведінки/JS/i18n/маршруту/CSP/SSRF/запису.
tests/elevation-token.test.mjs(+2). Набір: 2428 (+2).
[1.166.0] — 2026-08-13
Виправлено (НИЗЬКЕ) — термінологія рубрики тепер збігається з канонічною документацією. career-ops.org/docs описує «п’ять вимірів плюс цілісна загальна оцінка», але web-ui, cvstart.org і вікі казали «рубрика з шести вимірів» (5 + 1 = 6, але формулювання розходилося).
Виправлено
- Прийнято формулювання документації — «п’ять вимірів плюс цілісна загальна оцінка» — узгоджено в README ×17, на сайті cvstart.org ×17, у довідці ×17, у
docs/career-ops-canonical.mdі у вікі (Home ×17 + Features).
Примітки
- Лише docs/маркетинг; без змін коду/ключа i18n/маршруту/CSP/SSRF/запису.
tests/rubric-terminology.test.mjs(+2). Набір: 2426 (+2).
[1.165.0] — 2026-08-13
Виправлено (НИЗЬКЕ) — термін «Two-pager» тепер узгоджений у межах кожної локалі. В арабській бічна панель показувала латинське «Two-pager», тоді як <h1> був повністю локалізований — єдиний латинський рядок у дзеркальній RTL-навігації.
Виправлено
- Рішення зафіксовано: для кожної локалі
nav.twoPagerіtwoPager.titleузгоджують термін (обидва латиною або обидва локалізовані). Розходилася лише арабська; її пункт навігації тепер локалізований («الصفحتان»). Новий канарковий тест падає, якщо якась локаль знову їх розведе.
Примітки
- Лише текст; без змін маршруту/CSP/SSRF/запису. Змінено 1 значення i18n (ar); без нових ключів (знімок 1219).
tests/two-pager-term-consistency.test.mjs(+2). Набір: 2424 (+2).
[1.164.0] — 2026-08-13
Виправлено (НИЗЬКЕ) — плейсхолдер пошуку у верхній панелі більше не переповнюється жодною мовою. «Find a company, role or URL…» обрізався (nowrap), коли рядок пошуку стискався; половина «…or URL» ніколи не була видима.
Виправлено
top.search(×17) тепер короткий «Пошук або вставте URL» (≤24 символи кожною мовою), вміщується навіть у вузькому рядку й зберігає підказку про URL. Резервний плейсхолдер уindex.htmlзбігається;aria-labelзберігає повний опис.
Примітки
- Лише текст; без змін маршруту/CSP/SSRF/запису. Один наявний ключ i18n переписано ×17 (нових немає; знімок 1219).
tests/search-placeholder-fit.test.mjs(+2). Набір: 2422 (+2).
[1.163.0] — 2026-08-13
Виправлено (НИЗЬКЕ) — вбудований асистент «Запитати документацію» тепер охоплює експорт звіту в PDF. Раніше відповідав, що посібник цього не описує, хоча на #/reports/:slug є робоча кнопка 📄 Generate PDF.
Виправлено
- До всіх 17 пакетів довідки під §10 «Звіти» додано H3 «Експорт звіту в PDF» (де кнопка, файл потрапляє в
output/*.pdf, потрібен Playwright, перевірити перед надсиланням). Пошук асистента тепер показує розділ «Звіти».
Примітки
- Лише docs/довідка; без змін коду/маршруту/CSP/SSRF/запису. Гейт довідки 112 → 113 H3 (31 H2 без змін).
tests/help-reports-pdf-section.test.mjs(+2). Набір: 2420 (+2).
[1.162.0] — 2026-08-13
Виправлено (СЕРЕДНЄ) — «?»-підказка тепер ≥24×24 (WCAG 2.5.8). .help-hint був 18×18 px з padding:0 — нижче мінімуму, на кожному заголовку.
Виправлено
- Бокс
.help-hintтепер 24×24 (вимірна ціль), а видиме кільце лишається 18px — малюється центрованим::before, тож гліф і базова лінія<h1>незмінні. Стани hover/активний/фокус слідують за кільцем; відступ 6→3px зберігає проміжок.
Примітки
- Лише CSS; без змін JS/i18n/маршруту/CSP/SSRF/запису.
tests/help-hint-target-size.test.mjs(+2). Набір: 2418 (+2).
[1.161.0] — 2026-08-13
Виправлено (СЕРЕДНЄ) — #/reports показує чип «Оцінку не розпізнано» замість порожнього місця. Після мовно-незалежного парсера v1.159.0 звіт без розбірної оцінки показував порожню область — неможливо відрізнити від збою.
Виправлено
- Клітинка оцінки тепер розгалужується: є оцінка → тональна плашка; немає оцінки → приглушений чип
.score-muted(«Оцінку не розпізнано», ×17) з підказкою «Відкрийте звіт…». Картка лишаєтьсяrole="link"з клавіатурним керуванням, дата відображається. - Повторно використовує наявний нейтральний токен; без нового кольору.
Примітки
- Лише клієнт; без змін маршруту/CSP/SSRF/запису. +2 ключі i18n ×17 (знімок 1217 → 1219). Набір: 2416 (+3).
[1.160.0] — 2026-08-13
Виправлено (HIGH) — текст про провайдерів більше не суперечить обіцянці 7 провайдерів. #/config казав, що жива оцінка «використовує ключ Anthropic або Gemini», а ключ OpenAI «вебінтерфейс не використовує»; дашборд показував «Anthropic-first scoring» — хибно від каскаду 7 провайдерів (v1.157.0).
Виправлено
config.providerModelNote(×17): тепер вказано, що ⚡ жива оцінка headless працює на будь-якому з семи ключів (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub Models · Hermes), з автопорядком і відкатом. Хибну фразу про OpenAI видалено.dash.quick.evaluateSub(×17): нейтрально («Оцінка відповідності 0–5»).Keys: N / 5→N / 7.
Примітки
- Лише текст; без змін маршруту/CSP/SSRF/запису. Без нових ключів i18n (знімок 1217). Набір: 2413 (+3).
[1.159.0] — 2026-08-13
Виправлено (HIGH) — метадані звіту більше не залежать від мови. Звіти, згенеровані не англійською, показували порожню смугу метаданих на #/reports, бо parseReportHeader розпізнавав лише англійські жирні мітки.
Виправлено
parseReportHeaderтепер розбирає мовно-незалежний YAML-блок## Machine Summary(score:/legitimacy:/date:— те саме джерело, що вже читаєauto-pipeline): англійські мітки → блок Machine Summary → локалізовані мітки (REPORT_LABELS, 17 локалей). Англійські звіти лишаються байт-у-байт ідентичними.- Толерантний розбір числа (
1.5/5,1,5/5,1.5 из 5,4.5 out of 5); дата відкочується до mtime файлу, якщо її немає в тілі.
Примітки
- Лише читання/розбір; без змін маршруту, CSP, SSRF чи запису в батьківський проєкт. Без нових ключів i18n. Набір: 2410 (+8).
[1.158.0] — 2026-08-12
Виправлено — два косметичні баги відображення («?» протікала в заголовок вкладки та неправильна кількість провайдерів на лендингу). Лише відображення; без змін поведінки, безпеки чи потоку даних.
Виправлено
- «?» з HelpHint більше не протікає в
document.title. Роутер брав заголовок вкладки з необробленогоh1.textContent, тож вкладка показувала «Vacancy search?» замість «Vacancy search». Теперrouter.js::focusNewViewклонує заголовок, видаляє.help-hintі лише потім читає текст; видима «?» лишається недоторканою. - cvstart.org показував «17 AI providers» замість «7». Хелпер
sub()уFeatures.astroзамінював усі{n}кількістю локалей (17) до підстановки за карткою; тепер{n}розв’язується за карткою (провайдери → 7, мови → 17).
Примітки
- Без змін сервера, маршруту, CSP, SSRF чи ключів i18n; форма
facts.jsonбез змін. Набір: 2402 тести (+1).
[1.157.0] — 2026-08-12
Виправлено — живі оцінки тепер виконуються на БУДЬ-ЯКОМУ налаштованому провайдері, не лише Anthropic/Gemini. Користувач лише з OPENROUTER_API_KEY помилково примусово переводився в ручний режим.
Виправлено
- Першопричина: пін
LLM_PROVIDERбез ключа (напр.,LLM_PROVIDER=claudeзinit) заходив у глухий кут; тепер відбувається відкат до auto-порядку серед налаштованих провайдерів (уselectActiveProvider+ обох каскадах диспетчеризації). - Клієнтський гейтинг (
#/deep+ вью mode-page) тепер використовуєwindow.ProviderStatus(/api/status/providers, усі 7) замість застарілої перевірки Anthropic/Gemini; переписані підказки (deep/eval × 17) + значок «Живі оцінки» на дашборді +config.llmProviderHint.
Примітки
- Без змін безпеки. Набір: 2401 тестів (+5).
[1.156.0] — 2026-08-12
Рефакторинг — розбити scan.js під ліміт розміру (P-16) + фікс CodeQL. scan.js мав 906 рядків; вилучено дві фабрики, що зберігають поведінку → 648. Завершує пару розбиттів вью P-15/P-16.
Змінено
- Нові
scan/runner.js(рушій виконання скану) іscan/filters.js(машина станів фільтрів) через bag-обʼєктиctx/refs;scan.jsзвʼязує обидва.
Виправлено
- CodeQL
js/useless-assignment-to-local(#428) уconfig/tab-controller.js:let n = i;→let n;.
Примітки
- Чистий рефакторинг, без зміни поведінки; 4 тести, що читають джерело, перенаправлено. Обидва великі вью тепер під 800 (P-15/P-16 готово). Набір: 2396 тестів.
[1.155.0] — 2026-08-12
Рефакторинг — розбити config.js під ліміт розміру (P-15). config.js мав 1030 рядків (понад ліміт 800); вилучено два модулі, що зберігають поведінку, — тепер 783.
Змінено
- Нові
config/field-specs.js(дані полів + списки моделей) іconfig/tab-controller.js(фабрика панелі вкладок);config.jsпосилається на них, логіка рендеру не змінюється.
Примітки
- Чистий рефакторинг, без зміни поведінки; 6 тестів, що читають джерело, перенаправлено.
scan.js(906) лишено як є (вже частково розбитий; ядро надто зв’язане для чистого механічного поділу). Набір: 2396 тестів.
[1.154.0] — 2026-08-12
Новий гайд — «Запуск усього стеку в хмарі». У career-ops немає власної розповіді про хмару/сервер, тож її додано: покроковий рецепт, як розмістити батьківський пайплайн career-ops, цей переглядач career-ops-ui і рушій ШІ (підписка Claude через Claude Code, локальний Hermes, або ключі API) на невеликому завжди увімкненому сервері. Постачається як Довідка §31 17 мовами, розділ README і вікі-сторінка.
Додано
- Довідка §31 «Запуск усього стеку в хмарі» (× 17) — три частини, підготовка + встановлення, вибір рушія, безпечна публікація (HTTPS зворотний проксі + автентифікація + інваріанти CSP/SSRF/XSS/без-секретів). Бандл довідки зростає до 31 H2 / 112 H3.
- README — розділ «Запуск усього стеку в хмарі» (× 17) + вікі-сторінка Cloud-Deployment.
Примітки
- Лише документація — без маршрутів, сервера й змін клієнта; без нових i18n-ключів. 4 тести довідки переведено на контракт 31 H2 / 112 H3. Набір: 2396 тестів (без змін).
[1.153.0] — 2026-08-12
Сканер Jobvite переведено на публічний XML-фід (синк із батьком). Батько вивів з експлуатації JSON API Jobvite (тепер нуль вакансій); source у web-ui використовував той самий мертвий ендпоінт, тож будь-яка відстежувана компанія Jobvite мовчки сканувалася порожньою. Порт батьківського фіксу (#2623): тепер читається публічний по-тенантний XML-фід із ключем companyEId.
Виправлено
- source звертався до виведеного JSON API і повертав нуль вакансій; тепер він звертається до
https://app.jobvite.com/CompanyJobs/Xml.aspx?c={companyEId}і парсить XML<result><job>…(CDATA + сутності,detail-urlпередapply-url).
Змінено
- Розвʼязання
companyEId: (1)company_eid:у порталі, (2) параметрc=явногоapi:, (3) дискавері зі сторінки борду.fetchText(http-json.mjs) додає.location/.retryAfterдо non-ok помилки (лише читання, зворотно сумісно).
Примітки
- Безпека — два хости (
jobs.jobvite.com,app.jobvite.com) закріплені черезassertJobviteUrl: лише https, суворий allowlist, редиректи не переслідуються.companyEId— лише значення?c=; кількість sources не змінилася. - Набір: 2396 тестів (+4).
[1.152.0] — 2026-08-12
Провайдер Hermes — розведення завершено + актуалізація документації. Код-рев’ю інтеграції Hermes із v1.151.0 виявило дві реальні прогалини й чотири пункти повноти; усе виправлено тут, а перелік LLM-провайдерів по всьому застосунку доведено до повних семи на всіх поверхнях документації та в 17 мовах.
Виправлено
#/configне міг форсувати Hermes — випадний списокLLM_PROVIDERперелічував лише шість провайдерів, тожHERMES_API_KEYможна було задати, але не форсувати Hermes з UI. Теперhermes— 8-й варіант, а новий тест паритету не дасть списку знову розійтися зLLM_PROVIDERS.- Короткі self-hosted ключі мовчки відхилялися — поріг у 20 символів в
isUsableKeyрозраховано на хмарні ключі;hasHermesKeyтепер використовує пом’якшений поріг у 8 (приклад у документації Hermes — 19 символів).
Змінено
- Перелік провайдерів нормалізовано до повних семи в README (× 17), вбудованій довідці (× 17), словнику
config.llmProviderHint(× 17) іdocs/sdd;hermesChatUrlдобудовує хост без шляху; текст ручного фолбеку називає Hermes.
Примітки
- Безпека без змін — нових маршрутів і правок SSRF/CSP немає; у health/doctor додано рядок
HERMES_API_KEY. - Набір: 2392 тестів (+2).
[1.151.0] — 2026-08-12
Hermes тепер підключений LLM-провайдер (Phase 5) — спайк Phase 5 підтвердив, що Hermes від Nous Research має OpenAI-сумісний API Server (hermes gateway → POST /v1/chat/completions), тож career-ops-ui тепер виконує живі оцінки через локальний Hermes так само, як OpenAI/Qwen. Задайте HERMES_API_KEY у Налаштуваннях застосунку — він входить в auto-порядок (останнім). Закриває останній відкритий пункт роадмапу — Phase 5, Shape A.
Додано
- LLM-провайдер Hermes (Shape A) —
runHermesповерх спільного клієнтаrunOpenAICompatible(server/lib/openai.mjs), у обох каскадах (llm-dispatch.mjs+routes/llm.mjs), у хвості auto-порядку + пінLLM_PROVIDER=hermes,/api/status/providersіllm-pricing.mjs. Звертається до налаштовуваного локального base URL (типовоhttp://127.0.0.1:8642/v1) з Bearer-автентифікацією — це НАЛАШТОВАНИЙ ендпойнт провайдера (як OpenRouter/Qwen), а не URL вакансії від користувача, тож SSRF-guard не задіюється. - Поля
#/config—HERMES_API_KEY(секрет) +HERMES_BASE_URL+HERMES_MODEL(типовоhermes-agent), 6 нових ключів i18n × 17 мов (snapshot 1208 → 1214).
Змінено
- Спайк вирішено:
docs/integrations/HERMES.md, вбудована довідка §30 (× 17), тизер README (× 14), навичкаhermes-bridgeі roadmap переходять з «заплановано / ще не підключено» на підключено (Shape A). Shape B (кастомний relay агентного runtime) не знадобився.
Примітки
- Безпека: запит провайдера — це налаштований ендпойнт, тієї ж категорії, що й інші OpenAI-сумісні провайдери — без нової SSRF-поверхні, без змін CSP/санітайзера.
HERMES_API_KEY— цеSECRET_KEY(ніколи не відображається). - Тести (ізольовані в CI, заглушка транспорту):
tests/hermes-provider.test.mjs(+5); канарку «немає гілки Hermes» з v1.146.0 інвертовано, щоб стверджувати, що вона Є; тести набору провайдерів оновлено до порядку з 7 провайдерів. - Набір: 2390 тестів (+5).
[1.150.0] — 2026-08-12
Узгоджені порожні стани (шліфування Phase 4) — кожна панель «поки нічого немає» тепер малюється єдиним спільним стилем .empty, замість того щоб деякі подання переоголошували вигляд інлайн із магічним 40px. Невелике виправлення візуальної узгодженості; порожні стани #/activity, #/cv-studio, #/stats і #/usage тепер збігаються з усіма іншими (токенізований відступ 48px + пунктирна рамка).
Змінено
#/activity,#/cv-studio,#/stats,#/usageприбрали свій інлайнstyle: { padding: '40px', textAlign: 'center', color: 'var(--foggy)' }на порожніх панелях — усі три властивості вже дає спільний клас.empty(--space-7= 48px, по центру, приглушено, пунктирна рамка). Тож ці чотири малюються ідентично до ~25 інших панелей.empty.- Правомірні перевизначення по поданню (
#/dashboardwidth:100%,#/pipelineborder:none) не чіпали — прибрано лише суто надлишкові переоголошення.
Примітки
- Лише чистка використання CSS на клієнті — без зміни маршруту, сервера, ключа i18n чи правил CSS (сам клас
.emptyбез змін); snapshot словника 1208. Перевірено в браузері (порожня панель#/usageобчислює 48px відступу + пунктирну рамку, 0 помилок консолі). - Новий канарковий тест
tests/empty-state-consistency.test.mjsтримає.emptyєдиним джерелом істини. Phase 5 (провайдер Hermes) лишається заблокованою. - Набір: 2385 тестів (+2:
tests/empty-state-consistency.test.mjs).
[1.149.0] — 2026-08-12
Портали перенесено до Налаштувань (Phase 4) — #/portals тепер у групі навігації Setup, поруч із Налаштуваннями застосунку, а не під Sourcing. Із v1.144.0 це екран налаштувань (увімкнення/вимкнення відстежуваних компаній + перевірка стану ATS), а не дія сорсингу — тож саме тут його місце. Зміна лише навігації; сторінка та її маршрут без змін.
Змінено
- Елемент навігації
#/portals→ група Setup (уpublic/index.html), розміщено одразу після Налаштувань застосунку. Прибрано з групи Sourcing (у ній лишаються Scan / Pipeline / Auto-pipeline / Профінансовані компанії). Маршрут#/portals, подання та підписnav.portalsбез змін — зсунулася лише позиція на бічній панелі.
Примітки
- Лише розмітка навігації — без зміни маршруту, подання, ключа i18n чи сервера. Перевірено в браузері (0 помилок консолі); захищено
tests/portals-nav-placement.test.mjs. - Набір: 2383 тестів (+2:
tests/portals-nav-placement.test.mjs).
[1.148.0] — 2026-08-12
Охайніші фільтри сканування (Phase 4) — панель фільтрів тепер акуратна сітка — панель фільтрів #/scan перейшла з рваного flex-wrap із жорстких блоків змінної ширини на адаптивну сітку, а дії «Застосувати / Скинути» тепер займають власний окремий рядок із вирівнюванням праворуч. Фільтри ті самі, поведінка та сама — просто читати зручніше. Дизайнерське шліфування (без parent-sync).
Змінено
- Панель фільтрів
#/scan→ адаптивна сітка —.scan-filtersтеперdisplay: gridзі стовпцямиrepeat(auto-fill, minmax(180px, 1fr))та рівномірними проміжками, тож 11 підписаних фільтрів вишиковуються в охайні колонки за будь-якої ширини, а не рвано переносяться в рядок. - Дії «Застосувати / Скинути» охоплюють усю сітку у власному рядку, відокремлені тонкою лінією та вирівняні праворуч. Прибрано старий трюк із прихованою міткою + внутрішній flex-обгортку в
scan.js.
Примітки
- Лише CSS + невелике прибирання DOM — усі id фільтрів (
#scan-filter-*,#scan-apply) та зв’язкуSR.render()не змінено, тож потік Playwright не зачеплено. Нових ключів i18n немає. - Перевірено в браузері (0 помилок консолі); захищено
tests/scan-filters-grid.test.mjs. - Набір: 2381 тестів (+3:
tests/scan-filters-grid.test.mjs).
[1.147.0] — 2026-08-12
Hermes & Telegram — розділ вбудованої довідки + поверхня на cvstart.org (Phase 5b, частина 2) — друга й остання частина роботи над документацією Hermes: інструкція тепер живе всередині власної довідки застосунку всіма 17 мовами, а вбудований асистент документації відповідає на її основі на запитання про Hermes. Це й досі лише документація — шлях LLM-провайдера Hermes залишається запланованим / ще не підключеним (Phase 5).
Додано
- Вбудована довідка §30 «Hermes & Telegram» × 17 мов — новий розділ (що таке Hermes + дві форми інтеграції; запуск на хмарному сервері; Telegram через Hermes + правило «що НЕ показувати»), доступний з
#/help. Grounding уdocs-assistant/DocsFabпідхоплює його автоматично, оскільки обидва читаютьdocs/help/<lang>.md. - cvstart.org — посилання на посібник з Hermes з переходом на документ у GitHub.
Змінено
- Поріг пакета довідки піднято 29 → 30 H2 / 105 → 108 H3 (
canonical-docs-coverage,help-ui,help-ru-config-section); §30 додає 3 H3.
Примітки
- Hermes і досі ніщо не викликає. Новий канарковий тест
tests/help-hermes-section.test.mjsперевіряє, що всі мови містять §30 з її незалежними від мови якорями (docs/integrations/HERMES.md,hermes-bridge,#/help,127.0.0.1, Telegram). Провайдер залишається заблокованим до контракту API з Phase 5. - Це завершує постачання документація + skill для Phase 5b; інтеграція провайдера (Phase 5) залишається окремим заблокованим пунктом.
- Набір: 2378 тестів (+2:
tests/help-hermes-section.test.mjs).
[1.146.0] — 2026-08-12
Агент Hermes + Telegram — посібник з інтеграції + skill (Phase 5b, частина 1) — можна запустити career-ops-ui на хмарному сервері та передавати його події (завершений скан, новий звіт, термінове нагадування) у Telegram через агента Hermes від Nous Research. Цей реліз додає документацію з дизайну + розгортання та skill hermes-bridge; шлях провайдера LLM Hermes залишається запланованим / ще не підключеним (заблокований спайком щодо контракту API Phase 5). Документація свідомо йде попереду коду.
Додано
docs/integrations/HERMES.md— докладний розбір: дві форми інтеграції (endpoint, сумісний з OpenAI, проти агентного runtime), розгортання на хмарному сервері (reverse proxy + HTTPS + systemd, контракт «лише читання» з parent на headless-машині), Telegram через Hermes, і список моделі загроз «що НЕ можна показувати» (жодних CV / зарплат / тіл звітів / ключів у канал).- Тизер
## Hermes agent + Telegramу README — короткий вказівник-посилання в англійському README, відображений у всіх повністю перекладених локалізованих README. - Skill
hermes-bridge(.claude/skills/hermes-bridge/), що реалізує посібник на практиці — перевірки передумов і межі охоплення (Node ≥ 18, наявність ключів, доступність endpoint через безпечний щодо SSRF шлях), ніколи не записує секрети на диск/у логи й відмовляється вигадувати endpoint Hermes або стверджувати, що провайдер підключений. - Розділ Integrations у
docs/architecture/OVERVIEW.mdпосилається на посібник.
Примітки
- Наразі ніщо не викликає Hermes. Канарковий тест (
tests/hermes-docs.test.mjs) перевіряє маркери чесності «заплановано / ще не підключено» і те, що вllm-dispatch.mjsнемає жодної гілки Hermes/Nous — тож підключення провайдера пізніше має оновити документацію + roadmap в тій самій зміні. - Відкладено до v1.147.0 (Phase 5b, частина 2): розділ довідки в застосунку «Hermes & Telegram» H2 × 17 мов та маркетингова сторінка cvstart.org.
- Набір: 2376 тестів (+4:
tests/hermes-docs.test.mjs).
[1.145.0] — 2026-08-12
Проникливі статистики (продовження): перебудовуваний графік — вкладка «Тренд цільових ролей» на #/stats тепер має віджет Побудувати графік: виберіть метрику × вимір, і він перемальовується наживо. Запит UX від користувача (без parent-sync).
Додано
- Перебудовуваний графік метрика × вимір — виберіть метрику (Вакансії / Медіанна зарплата / Середня зарплата) і вимір (За країнами / За ролями), і стовпчиковий графік миттєво перемальовується. Зарплатні метрики враховують валюту + перемикач рік ⇄ місяць; вакансії — простий підрахунок.
- 8 нових ключів i18n × 17 локалей; snapshot 1200 → 1208.
Примітки
- Перевірено в браузері (0 помилок консолі). Набір: 2372 тестів (+2).
[1.144.0] — 2026-08-12
Налаштування та фільтри (Фаза 4, частина 1): увімкнення/вимкнення відстежуваних порталів — тепер можна вмикати чи вимикати відстежувану компанію на #/portals, і сканер це враховує. Запит UX від користувача (без parent-sync).
Додано
- Перемикач Увімкнути/Вимкнути для компанії на
#/portals— один клік вимикає портал (EN-сканер уже пропускає компаніїenabled: false, тож вимкнений портал випадає з усіх майбутніх сканів) або вмикає назад, з оптимістичним тостом. POST /api/portals/toggle— явний запис користувача, який хірургічно та з перевіркою парсингу перемикає прапорецьenabledкомпанії вportals.yml(коментарі, порядок та інші поля збережено). 5 нових ключів i18n × 17 локалей; snapshot 1195 → 1200.
Примітки
- Зміна сканера — нуль:
en-scanner.mjsуже фільтруєenabled !== false. Набір: 2370 тестів (+3).
[1.143.0] — 2026-08-12
Зрозумілість (продовження): підказки ? на основних робочих сторінках — довідкове ? тепер охоплює дев’ять головних сторінок дій, усіма мовами. Правка UX за репортом користувача (без parent-sync).
Додано
- Підказка
?ще на 9 заголовках —#/scan,#/evaluate,#/cv-studio,#/tracker,#/config,#/deep,#/batch,#/auto,#/applyотримали вбудоване?(черезHelpHint.title), що відкриває локалізоване «що це / як використовувати / який результат» — той самий CSP-безпечний компонент із v1.139.0. - 9 нових ключів i18n × 17 локалей (
help.hint.scan/…/apply); snapshot 1186 → 1195.
Примітки
- Перевірено в браузері (0 помилок консолі). Набір: 2365 тестів (+1).
[1.142.0] — 2026-08-12
Виправлення: більше немає «Unknown» кар’єрного архетипу — #/orientation тепер завжди ранжує з восьми названих кар’єрних векторів, а не відповідає інколи «Unknown» і не радить «подвоїти ставку» на ньому. Правка за репортом користувача (без parent-sync).
Виправлено
#/orientation— промпт ШІ тепер забороняє архетип поза набором. Модель МУСИТЬ ранжувати топ-3 рівно з восьми названих векторів і ніколи не відповідати «Unknown»/«N/A»/«недостатньо даних» та не вигадувати мітку. За бідного резюме вона все одно називає три найближчі з меншою впевненістю і вказує, яких доказів бракує.
Примітки
- Лише зміна серверного промпту (
buildOrientationPrompt); без змін i18n/схеми. Набір: 2364 тестів (+1).
[1.141.0] — 2026-08-12
Проникливі статистики (продовження): збагачення профінансованих компаній — #/funded став наочнішим: логотипи компаній, графік за сумою фінансування та картки з раундом / сумою / оцінкою знахідки / рекомендованою дією. Правка UX за репортом користувача (без parent-sync).
Змінено
#/funded— плоска таблиця → сітка карток. Кожна нещодавно профінансована компанія тепер картка з логотипом (з назви черезCompanyLogo, запасний варіант — літерний аватар), чипами раунду + суми, оцінкою знахідки та рекомендованою дією від батьківського проєкту, а також посиланням на новину і датою.- Візуалізація суми фінансування — горизонтальний стовпчиковий графік найбільших компаній за розкритою сумою; вільний текст сум (“$120M”/“€1.5B”) парситься у величину новим
parseAmount. 3 нових ключі i18n × 17 локалей.
Примітки
- Досі лише для читання через
GET /api/company-funded; опису й вилки зарплат у стрічці фінансування немає. Набір: 2363 тестів (+2).
[1.140.0] — 2026-08-12
Проникливі статистики: багатші дані про зарплати — розбивка зарплат у «Мій пайплайн» на #/stats тепер показує середнє (не лише медіану), перемикач на рік ⇄ на місяць і таблицю мін · сер · медіана · макс за країнами. Перша частина Фази 3. Правка UX за репортом користувача (без parent-sync).
Додано
- Середня зарплата —
RoleStats.salaryStatsтепер повертаєavgUsdпоряд ізminUsd/medianUsd/maxUsd. Медіана стійка до викидів, середнє показує перекіс — разом вони читаються як розподіл. - Перемикач на рік ⇄ на місяць і таблиця мін · сер · медіана · макс за країнами в розділі зарплат, з урахуванням валюти й періоду. 8 нових ключів i18n × 17 локалей.
Примітки
- Цифри й далі беруться лише з вакансій із розпізнаваною зарплатою і нормалізуються до USD (орієнтовно). Набір: 2361 тестів (+1).
[1.139.0] — 2026-08-12
Зрозумілість: підказки ? — багаторазова, безпечна для CSP кнопка ?, яка за кліком пояснює «що це / як працює / чого очікувати» вашою мовою. Правка UX за репортом користувача (без parent-sync).
Додано
- Спливна підказка
?(window.HelpHint) — кругла?біля заголовка відкриває легке, залежне від теми та дзеркалене в RTL спливне вікно, що відображає локалізоване пояснення черезUI.md(); доступно (role="tooltip",aria-expanded, закриття Escape/кліком поза, повернення фокуса) та безпечно для CSP. ?на 5 вкладках#/statsі на 8 заголовках ШІ/аналітичних екранів (career-plan, орієнтація, two-pager, нетворкінг, мок-інтерв’ю, пам’ять, funded, щотижневий підсумок) — 14 нових ключів i18n × 17 локалей.
Примітки
- Усі екрани вже мали однорядковий підзаголовок;
?додає глибше пояснення на вимогу й робить порожні стани самоочевидними. Набір: 2360 тестів (+4).
[1.138.0] — 2026-08-12
Генерація мовою інтерфейсу — кожна AI-генерація тепер відповідає мовою, обраною в інтерфейсі, плюс посилення тестів за підсумками рев’ю. Правка UX за репортом користувача (без parent-sync).
Змінено
- AI-генерації тепер враховують мову інтерфейсу. Якщо інтерфейс російською, іспанською, японською тощо — згенерований текст повертається цією мовою, а не завжди англійською. Директиву мови виводу проброшено в усі ендпоінти генерації — кар’єрний план, орієнтацію, звіт по ринку, мок-інтерв’ю, план нетворкінгу, «запитати документацію», пропозицію нотатки пам’яті та чернетку two-pager. Код та ідентифікатори лишаються англійською (напр., ключі YAML у two-pager); локалізуються лише проза, заголовки та пункти.
Виправлено
- Вартовий CSS-ролей кольору (
tests/css-role-tokens.test.mjs) — статичний канарок, що аліас-токени темної теми з v1.137.0 ніколи не змінюють роль: текстові токени (--fg/--danger/--ok/…) ніколи якbackground, а токени поверхонь (--card/--panel/--line/…) — як текстовийcolor, у всьому CSS та inline-стилях SPA. - Самоперевірка XSS-завантажувача
UI.md()— тест, що через vm вантажитьmd()зapi.js, тепер одразу після витягання перевіряєmd('<script>…')і кидає помилку, якщо екранування зникло, — тож майбутній збій нарізки падає гучно, а не лишає security-набір зеленим на усіченій функції. - Вартовий прокрутки на
#/career-plan—scrollIntoViewпісля генерації виконується лише якщо прев’ю ще приєднане до документа.
Примітки
docs/UX-ROADMAP.mdоновлено: підказки?+ описи сторінок + порожні стани тепер v1.139.0; провайдер Nous Research / Hermes — з гайдом розгортання на хмарному сервері + Telegram і скілом Hermes — відстежується як Фаза 5 / 5b.- Набір: 2356 тестів (+5).
[1.137.0] — 2026-08-11
Виправлення читабельності та рендерингу — контраст темної теми, підписи графіків та кар’єрний план. UX-правка за повідомленням користувача (без parent-sync).
Виправлено
- Білий на білому / чорний на чорному в темній темі на багатьох екранах — п’ятнадцять кастомних властивостей CSS, на які посилалися кілька вкладок (
--fg,--panel,--panel-2,--ok,--danger,--card, …), ніколи не були оголошені, тож вони відкочувалися до захардкоджених світлих/чорних значень: у світлій темі це не заважало, але в темній робило текст нечитабельним (чіпи огляду#/pipeline, активна вкладка#/stats, «Активний / Ключі» + «✓ встановлено» на#/config, розділи#/two-pager, бульбашка питання#/mock-interview, текст помилок). Тепер вони прив’язані псевдонімами до справжніх токенів, що враховують тему, тож автоматично слідують за нею — 0 порушень контрасту WCAG-AA на всіх 29 вкладках, підтверджено автоматизованим аудитором; активна вкладка#/configотримала читабельний тонований стиль. Регресійний бар’єр (tests/dark-theme-tokens.test.mjs) утримує цю прив’язку псевдонімами. - Підписи графіків
#/statsобрізалися посередині слова («Senior Backend Engineer» → «…Enginee») — тепер вони скорочуються трьома крапками, а повний підпис зберігається як підказка при наведенні. #/career-planпоказував згенерований план як необроблений Markdown — тепер він автоматично рендериться як форматований, читабельний текст (редагований Markdown залишається в текстовому полі; кнопка «Перегляд» перемикає режим).
Примітки
#/career-plan,#/two-pager,#/statsта щотижневий дайджест інтерв’ю не зламані — вони показують порожні стани, доки ви не згенеруєте план / не матимете даних. Наступним кроком запланована зрозуміліша підказка на сторінці та довідкові підказки?(docs/UX-ROADMAP.md).
[1.136.0] — 2026-08-11
Паритет із батьківським career-ops v1.26.x (після мейнлайну v1.26.0) — одне нове джерело без авторизації плюс хвиля портів якості та надійності для дзеркал web-ui. Реєстр тепер налічує 79 джерел = 74 англомовних + 5 російськомовних (ALL_ADAPTERS 74).
Додано
eightfold(Eightfold AI, #2684) — дошки з підбору персоналу через API без авторизаціїhttps://<tenant>.eightfold.ai/api/apply/v2/jobs, прив’язане до хоста*.eightfold.ai(брендований CNAMEcareers.<company>.comсвідомо відхиляється); пагіноване з обмеженням безпеки, відмовою для неактивної дошки, дедуплікацією URL. Джерело + адаптер + CI-ізольований набір тестів; з’являється у фільтрі джерел#/scanта на лендінгу.
Виправлено
- Unicode-орієнтована дедуплікація та ключі ролей (#2569 / #2587 / #2667) — нова спільна
normalizeTextKey(NFKC, зберігає літери/знаки/цифри будь-якого письма) замінює ASCII-лише ключі:detect-repostsтепер групує варіанти назв компаній з різною шириною символів/пунктуацією («Acme, Inc.» ≡ «Acme Inc») і ніколи не зливає різних неЛатинських роботодавців, аrole-matcherзгортає повноширинні назви посад і зберігає неЛатинські токени ролей, а не стирає їх. fetchJsonWithRetryбільше не повторює відхилене перенаправлення (#2657) — захистredirect:'error', що натрапляє на 3xx, є детермінованим, тож тепер він неповторюваний і завершується швидким провалом, замість витрачання бюджету повторних спроб.- AND-групи
title_filter.positive(#2552) — розділений пробілами+усередині позитивного запису тепер вимагає, щоб усі терміни зустрічалися в назві, у будь-якому порядку. oraclecloudприймає нумеровані апекси орендаряoraclecloud1.com … oraclecloud99.com(#2683) — обмежене сімейство (без провідного нуля, ≤ 2 цифри), ніколи не символ підстановки апекса.workableпосилено (#2675) — повторні спроби, схожі на браузерні заголовки та серіалізація запитів проти хоста за Cloudflare.personioпереходить на HTML-скрейп, коли XML-стрічка вимкнена, замість повернення нічого.- Резервні псевдоніми
statesFALLBACK ресинхронізовано з батьком (#2615).
Примітки
- Не портовано (не дзеркалюється web-ui, або лише для CLI): reply-matcher (#2672), jd-similarity (#2661), jd-skill-gap (#2686), розбір env-шляху сканування (#2568) /
--flag=value(#2589), а також зміни cover-letter / шаблону CV / doctor / ollama / generate-pdf. HIGH-рекомендації webjs-yaml/nanoidуже виправлено в web-ui v1.135.0.
[1.135.0] — 2026-08-11
Паритет із батьківським career-ops v1.26.0 — п’ять нових джерел сканування без авторизації плюс виправлення коректності для чотирьох дощок, які web-ui вже підтримує. Реєстр тепер налічує 78 джерел = 73 англомовних + 5 російськомовних (ALL_ADAPTERS 73).
Додано
- П’ять нових джерел сканування (кожне — джерело + адаптер + CI-ізольований набір тестів; з’являються у фільтрі джерел
#/scanта на лендінгу cvstart.org):join(JOIN) — дошка вакансій компанії JOIN зі сторінкиjoin.com/companies/<slug>, зчитана з Next.js__NEXT_DATA__(прив’язка до хоста, обмеження за кількістю сторінок).getro(Getro) — портфельні дошки «talent-network» венчурних фондів через публічний POST APIapi.getro.com, з пагінацією від найновіших; кожна вакансія приписується компанії-портфелю, а не самому фонду.consider(Consider) — портфельні дошки венчурних фондів getconsider.com через POST-запит того ж походження; конфігурований хост прив’язаний структурним захистом від SSRF (лише публічний HTTPS-хост).joinup(JOINUP) — швейцарська дошка joinup.ch, зчитування SSR-рендерованої найновішої сторінки; при поломці скрапера — відмова закритого типу (fail-closed).remotli(Remotli) — remotli.ch, віддалені ролі у швейцарських компаніях (зарплати в CHF); видає власне посилання подання (apply URL) ATS роботодавця, тож перехресні лістинги дедуплікуються.
Виправлено
- a16z Speedrun більше не переривав весь борд через тимчасовий збій — запити сторінок тепер проходять через спільну
fetchJsonWithRetry(обмежені повторні спроби лише на тимчасових 429/5xx/таймаут, ніколи на постійних 4xx), а бюджет сторінки перерахований під сторінку з 50 вакансіями. - arbeitsagentur перенесено на API Jobsuche v6 (
/pc/v6/jobs) — старий v4-ендпоінт видає 404; форму відповіді перейменовано, а фільтрація «remote» тепер звужується на боці сервера. - thehub перенесено на API v2
jobsandfeatured; рядки не містять дати публікації і звільнені від фільтра за віком. - hackernews тепер надійно знаходить щомісячну гілку «Who is hiring?», фільтруючи пошук у Algolia за тегом облікового запису
author_whoishiringзамість довільнотекстового запиту.
Примітки
- Не портовано (web-ui вже безпечний, поглинутий через relay, або лише для CLI): ключі Unicode-дедуплікації ролей / зіставлення компаній (групування репостів у web-ui вже ключує компанію за простим нижнім регістром, тож різні неЛатинські роботодавці ніколи не зливаються); сигнал затримки відмов followup + доопрацювання company-funded (реле лише для читання, з м’яким відмовленням); середовищно-перевизначувані шляхи сканування та розбір
--flag=value(web-ui запускає сканери in-process); рефакторинг консолідації User-Agent (web-ui вже централізує його); а також елементи лише для CLI (реєстр недовіреного вмісту, oferta/offer-prep, doctor, зміни шаблонів супровідного листа/резюме).
[1.134.1] — 2026-08-05
Посилення валідації — виправлення, виявлені під час повного аудиту проєкту.
Виправлено
successfactorsбільше не відкидає зібрані вакансії при збої в середині сканування (регресія в портуванні «неактивна дошка = помилка» з v1.134.0) — цикл пагінації не мавtry/catch, тож збій на сторінці 2+ (після успішної сторінки 1) призводив до винятку й губив усе вже зібране; а якщо цей збій був404(вихідstartrowза межі діапазону),en-scannerміг на кілька днів помилково позначити живого орендаря як неактивного. Тепер дзеркалюєphenom/radancy: збій на сторінці 0 досі видає помилку (неактивна дошка), а збій на пізнішій сторінці зберігає часткові результати.- Фільтр-чипи
#/scanтепер керуються з клавіатури (WCAG 2.1.1) — чипи фасетів (і чип «очистити») були елементамиspanз обробником кліку, але безtabindex/ролі, тож користувачі клавіатури та програм читання з екрана не могли до них дістатися чи перемикати їх. Тепер вони маютьrole="button",tabindex="0",aria-pressedі активацію по Enter/Space. - Три захардкоджені англійські рядки тепер локалізовано — підказка бейджа довіри
#/scan, заголовок колонки релокації#/scanта заголовок колонки балу#/dashboardбули голими літералами, яких не бачив паритет-контроль i18n (вони ніколи не були ключами), тож вони залишалися англійськими в кожній неанглійській локалі. Теперscan.trustTip+scan.col.reloc(2 нових ключі) + повторне використанняtrack.col.score, зі статичною перевіркою на рівні коду.
[1.134.0] — 2026-08-05
Паритет із батьківським career-ops v1.25.0.
Додано
- Нове джерело сканування: getManfred (
manfred) — стрічка вакансій в іспанських/європейських технологічних компаніях по всьому борду з опублікованими зарплатами, зwww.getmanfred.com/api/v2/public/offers(без авторизації, прив’язка до хоста + лише HTTPS, повний каталог одним запитом). Джерело + адаптер + CI-ізольований набір тестів (tests/sources-manfred.test.mjs); реєстр тепер налічує 73 джерела = 68 англомовних + 5 російськомовних (ALL_ADAPTERS68). З’являється у фільтрі джерел#/scanта на лендінгу cvstart.org.
Виправлено
- Стрічка a16z Speedrun тихо обрізалася до 50 вакансій (#2404) — стрічка обмежує сторінку 50 елементами, але джерело запитувало
PER_PAGE = 100, тож пагінація зупинялася після першої сторінки. Виправлено на 50. - Неактивні дошки тепер видають помилку, замість того щоб читатися як «живі, але порожні» (#2379) —
cryptocurrencyjobs,phenom,radancy,successfactors: збій запиту, коли жоден запит так і не завершився успіхом, тепер видає помилку (тож перевірка стану#/portalsта сканування фіксують реальний збій), замість того щоб приховувати його за порожнім списком; збій у середині сканування після хоча б одного успіху зберігає часткові результати. - workable тепер використовує публічний widget API (#5ab8425) — перехід на
apply.workable.com/api/v1/widget/accounts/<slug>, який повертає повний список вакансій великого акаунту за один запит, тож великі акаунти більше не обрізаються.
Примітки
- Не портовано (лише для CLI або не дзеркалюється в web-ui): переробка продуктивності групування заголовків для виявлення репостів #2389; виправлення Unicode-ключів компаній (власна дедуплікація трекера web-ui вже безпечна для не-латиниці);
scan --since;cv-facts; аудит-прохід шаблону CV / PDF;doctor; директива щодо недовіреного вмісту в режимах.
[1.133.1] — 2026-08-02
Виправлено
#/funded(Профінансовані компанії) тепер відображає результати — через два баги таблиця завжди показувала «немає профінансованих компаній», навіть коли батьківськийcompany-funded.mjsповертав повний список. (1) Вигляд читав результати зres.candidates, а батьківський скрипт видає їх під ключемcompanies(кожен елемент —{ company, amount, round, funding: { sources: [{ source, url, observed_date }] } }); клієнт тепер читає правильний ключ і мапить реальну форму доказів. (2) Таблиця результатів передавала свої комірки вUI.el('tr', {}, …)як varargs, алеUI.el(tag, attrs, children)приймаєchildrenяк один вузол або масив, тож рендерилась лише перша колонка (Компанія) — тепер комірки передаються як масив. Перевірено в реальному браузері: 11 компаній із чотирьох стрічок відображаються з колонками Компанія / Сигнал фінансування / Джерело / Дата та робочими посиланнями на докази, нуль помилок у консолі. Порожній прохід тепер також показує діагностику по кожному джерелу, щоб тихий новинний день можна було відрізнити від заблокованої стрічки.- Регресійні бар’єри в
tests/parity-routes-v1133.test.mjs: фейковий батьківський скрипт тепер видає реальну форму виводуcompanies(початкова заготовка дзеркалила неправильну формуcandidates— саме тому баг і потрапив у реліз із зеленими тестами), плюс статичні канарки джерела, щоfunded.jsчитаєres.companies(ніколи неres.candidates) і будує рядки таблиці з дочірніми елементами-масивом (+1 → 2144).
[1.133.0] — 2026-08-01
Додано
- Виявлення профінансованих компаній (
#/funded, паритет із батьком #2117) — новий тільки-для-читання вигляд, що ретранслює батьківськийcompany-funded.mjscareer-ops черезGET /api/company-funded: список нещодавно профінансованих компаній «спершу на перегляд», знайдених у публічних стрічках про фінансування з прив’язкою до хоста (TechCrunch, PR Newswire, The Guardian, Hacker News). Ретрансляція запускає скрипт з--json --dry-run(JSON у stdout, без запису файлів), ніколи не передає користувацький ввід у--sources, має обмеження частоти запитів і запускається лише користувачем (кнопка «Знайти», ніколи при монтуванні). Новий модуль маршрутуserver/lib/routes/funded.mjs+public/js/views/funded.js, у розділі «Пошук». - Щотижневий дайджест співбесід (
#/interview-digest, паритет із батьком #2129/#2130) — новий тільки-для-читання вигляд, що ретранслює батьківськийweekly-digest.mjs(без ШІ) черезGET /api/interview/weekly-digest: механічне зведення нотаток сесій співбесід — з якими компаніями та на яких раундах ви проходили співбесіди цього тижня, повторювані компетенції та відкриті прогалини за принципом «найкраще зусилля». Необов’язковий діапазон?from=&to=передається, лише якщо ОБИДВА параметри — валідніYYYY-MM-DD; порожній діапазон — це коректний дайджестavailable:true. Додано доserver/lib/routes/interview.mjs+public/js/views/interview-digest.js, у розділі «Аналітика». - Обидві ретрансляції дотримуються усталеного контракту fail-soft
available:false, коли батьківський скрипт відсутній (CI, автономні інсталяції). 26 нових i18n-ключів ×17 локалей; CI-ізольований набірtests/parity-routes-v1133.test.mjs(+5 → 2143).
Примітки
- Батьківський career-ops просунувся далі за v1.24.0 зі сторінкою «Follow-up Tracker» застосунку Next.js
web/(#1422) та бекенд-рендером PDF (#2182) — не портовано: web-ui вже має власну ретрансляцію нагадувань і власні PDF-раннери, а посиленняfollowup-cadence.mjsнадходить безкоштовно через shell-out ретрансляцію. Зміниset-status.mjs/tracker-utils.mjs— внутрішні для CLI і не дзеркалюються.
[1.132.0] — 2026-07-31
Змінено
- Підсистему рендерингу результатів
#/scanвинесено вpublic/js/lib/scan-results.js(борг за контрактом розміру файлу —public/js/views/scan.jsрозрісся приблизно до ~1254 рядків). Підсистема (renderResults,buildChipRow,getRows, конструктори рядків/фасетів, малювальники опцій і дзеркало реєструFALLBACK_SOURCES) перенесена у фабрикуwindow.ScanResults.create(ctx), що замикається на об’єкт контексту, який надає вигляд. Без зміни поведінки — функції перенесено дослівно, змінні замикання переприв’язано доctx.*;scan.jsтепер має ~906 рядків (другий прохід виділення до цілі в 800 рядків заплановано). - Тести, що читають вихідний код статично, тепер читають обидва файли через
tests/helpers/scan-src.mjs::loadScanSrc();tests/scan-fallback-sources.test.mjsтепер читає дзеркало реєстру зscan-results.js. - Новий регресійний бар’єр у браузері —
tests/playwright-scan-filters.mjsзасіває заготовленийdata/last-scan.jsonі проганяє кожен фільтр#/scan, перевіряючи точну кількість рядків (npm run test:e2e:browser); для цього додано стабільні ідентифікатори фільтрів (#scan-filter-*,#scan-apply). - Банер «Останній реліз» у README скорочено до однорядкового підсумку + посилання на повний журнал змін (довгу багатоверсійну стіну опису прибрано). Застосовано в усіх 17 локалях.
[1.131.2] — 2026-07-31
Змінено
- Поділ
app.cssна три впорядковані таблиці стилів (борг за контрактом розміру файлу — єдиний файл розрісся приблизно до 1990 рядків, значно перевищивши жорсткий ліміт у 800 рядків). Тепер цеapp.css(~672 — a11y, дизайн-токени/тема, бічна панель, основний контент, кнопки, оболонка вмісту),components.css(~595 — картки, сітки, пагінатор, бейджі, таблиці, форми, лог/консоль, markdown, перемикач мов, фільтр-чіпи, банер з’єднання) іoverlays.css(~737 — тост, шухляда сповіщень, модальне вікно, різне/адаптивність, дзеркало[dir="rtl"], docs-fab, usage-hud) — кожен у межах жорсткого ліміту.- Розріз суцільний і в тому ж порядку, тож каскад побайтово ідентичний файлу до поділу;
index.htmlзавантажує всі три як впорядковані<link>. Без зміни поведінки, розмітки чи i18n. - Тести, що перевіряють CSS, тепер читають конкатенацію через спільний хелпер
tests/helpers/css.mjs::loadAppCss(). Новийtests/css-modularization.test.mjsфіксує поділ (файли існують · кожен ≤ 800 рядків · порядок<link>в index.html) → набір 2138. Перевірено в браузері: усі три таблиці стилів парсяться і їхні правила застосовуються.
- Розріз суцільний і в тому ж порядку, тож каскад побайтово ідентичний файлу до поділу;
[1.131.1] — 2026-07-31
Виправлено
- Узгодженість прив’язки хоста в адаптерах для двох джерел v1.130.0 (доопрацювання за код-рев’ю, багаторівневий захист; без зміни поведінки для коректних вхідних даних):
- Адаптер
a16z-speedrun-talentтепер повторно перевіряє перевизначенняapi:/a16z-speedrun-talent:уbuildEndpoint(HTTPS + точний хостspeedrun-talent-network.com) і в разі невдачі відкочується до канонічної стрічки — паритет з адаптеромcryptocurrencyjobs, тож значення з іншим хостом ніколи не потрапляє у слот запиту (раніше це покладалося виключно на перевіркуassertSpeedrunUrlпід час запиту). Перевірка точного хоста тепер — єдиний експортованийSPEEDRUN_TALENT_HOST_RE, спільний для перевірки й адаптера. - Парсер
cryptocurrencyjobs—cleanUrlтепер використовує ту саму перевірку точної відповідності хоста, що йassertCryptocurrencyJobsUrlта перевизначення адаптера (булоendsWith, яке приймало субдомени). Парсер ніколи не є поблажливішим за SSRF-перевірку: посилання елементаsub.cryptocurrencyjobs.coвідкидається. - +2 тести → 2135.
- Адаптер
[1.131.0] — 2026-07-31
Додано
- Дошка стадій-вкладок CRM
#/tracker(портовано з вигляду/pipelineбатьківського веб-застосунку). Панель чіпів воронки трекера + випадний список статусу замінено на смугу вкладок стадій: вкладка Усі плюс по одній вкладці на кожен канонічний статус — Evaluated · Applied · Responded · Interview · Offer · Rejected · Discarded · SKIP · Hired — кожна показує живий лічильник за всю історію, включно зі стадіями з нульовим лічильником, тож уся воронка завжди видима (вигляд CRM). Активна вкладка керує фільтром; повторний клік на неї скидає назад до «Усі». Рядки зберігають тональність оцінки, легітимність, можливості PDF і звіту, а комірка компанії тепер показує бренд-логотип, коли логотипи увімкнено (вимкнено за замовчуванням → без додаткових запитів).- Новий тільки-для-читання маршрут
GET /api/tracker/stagesповертає канонічну воронку (мітки за порядком) + карту згортання псевдонімів, отримані зserver/lib/states.mjs(templates/states.yml, із вбудованим фолбеком) — тож клієнт ніколи не хардкодить whitelist статусів. Застаріла відповідьGET /api/trackerбез параметрів лишається незмінною (лише{ rows }). - Нова чиста, покрита юніт-тестами клієнтська бібліотека
public/js/lib/tracker-stages.jsрозкладає рядки за стадіями сервера, толеруючи випадковий markdown bold і локалізовані псевдоніми (наприклад,aplicado→Applied). Вкладки доступні (role tablist/tab, aria-selected, зона кліку ≥44 px, лічильники в доступному імені кожної вкладки). Нових i18n-ключів немає. Набір тестів 2133.
- Новий тільки-для-читання маршрут
[1.130.0] — 2026-07-31
Додано
- Два нові джерела сканування, портовані з батьківського career-ops v1.24.0 (in-process, без нових залежностей; обидва з’являються у фільтрі джерел
#/scanі на лендінгу cvstart.org):- a16z Speedrun (
a16z-speedrun-talent, #2231) — стрічка JSON усього борду talent-network a16z Speedrun. Прив’язана до хостаspeedrun-talent-network.com, лише HTTPS, 0-індексована пагінація з обмеженням сторінок, наскрізне передаванняq/конфігурації по компанії, fail-soft. - Cryptocurrency Jobs (
cryptocurrencyjobs) — дошка вакансій Web3cryptocurrencyjobs.co, отримується через публічну стрічку RSS 2.0 (без авторизації). Дворазове декодування XML-сутностей, лише віддалені вакансії, роботодавець розбирається з хвоста заголовка"… at <Company>". - Загальна кількість у реєстрі тепер 72 джерела = 67 англомовних + 5 російськомовних (
ALL_ADAPTERS= 67 англомовних адаптерів порталів).
- a16z Speedrun (
Виправлено
echojobs— гібридні ролі лишаються відмінними від віддалених (віддзеркалює батьківський #2258). Регістронезалежний маркерhybridтепер дає"<Місто> · Гібрид"(або простоГібрид, якщо міста немає) іworkplaceType: 'Hybrid', замість згортання вRemote.radancy— застаріла розмітка TalentBrew + транспорт JSON-фрагмента результатів (віддзеркалює батьківський a3e6df9), під контролем ін’єктованогоopts.fetchJson.
Примітки
- Не портовано — CLI-специфічні можливості батька. Великий CLI/режимний шар career-ops v1.24.0 лишається поза web-ui, який є переглядачем + тонким наскрізним записом, а не хостом режимів: таблиці комплаєнсу/юрисдикції, телефонна книга контактів + vCard, транскрипт-дебриф інтерв’ю / визначення платформи дзвінків, встановлення статусу в реєстрі, запис результату, двопрохідний трайдж, jd-подібність, версійована схема артефакту CV заявки, виявлення Playwright-MCP у doctor та
portals/fix-slugs.mjs. Зміни оркестрації сканування, що живуть у батьківськомуscan.mjs— сканер Interamt.de на Playwright, повне сканування iCIMS зворотним ATS, фільтр віддаленості за прийнятністю країни, темп DNS-пошуку, дедуп StepStonerltrі нормалізована колонка компанії в історії сканування — не застосовуються: web-ui запускає сканери EN/RU in-process і не викликаєscan.mjs. - Вже покрито. Фікс згортання діакритики в
role-matcher(#2209) було портовано в v1.127.0, тож тут це no-op.
[1.129.1] — 2026-07-29
Виправлено
- Доопрацювання за AI-рев’ю портів web з v1.128/v1.129 (усі дорадчі, виправлено в корені): порядок рівня в
job-facets.js(явний модифікатор тепер перемагає управлінське слово —Senior Engineering Manager→senior, булоlead); fallback уstates.mjsбільше не пінниться (успішне читання мемоїзується, fallback повертається без кешу — тимчасово недоступний батько на старті перечитується наступним викликом) +console.warnза присутнього, але пошкодженого файлу;score-tone.js— рядок без оцінки нейтральний (muted), не червоний;domainFromName()пропускає не-ASCII slug до/api/logo; +sanity-гард ізоляції вtests/states.test.mjs. +4 тести → 2073.
[1.129.0] — 2026-07-29
Додано
- Фасет «Рівень» + колонка «Свіжість» у
#/scan— бібліотекаjob-facets.jsз v1.128.0 тепер вплетена в UI сканування (раніше лише логіка). Новий дропдаун Рівень розкладає заголовок кожної вакансії на lead/staff/senior/mid/junior/intern (JobFacets.seniorityFromTitle) і автозаповнюється тим, що реально є в результатах (як фасет «Країна»); заголовки без слова-рівня завжди проходять. Зберігається у збережених пошуках, Скиданні та Застосувати. У таблиці з’являються колонка Рівень (бейдж) і zero-token колонка Свіжість (сьогодні/Nдн, зJobFacets.daysSince). 12 i18n-ключів ×17, +3 тести → 2069.
[1.128.0] — 2026-07-29
Додано
- Чотири рішення, портовані з власного веб-застосунку батька (
../web/, Next.js) — переписані на ванільному JS/ESM, без нових залежностей: (1)server/lib/states.mjsчитаєtemplates/states.ymlнаживо як канонічний словник статусів трекера (з CI-fallback) — усуває ручний ре-синк whitelist щорелізу; POST згортає аліаси (іспанські/legacy) до канонічної мітки, funnel у GET групує за канонічним статусом; (2) логотипи компаній у ATS-хостованих рядках черезdomainFromName()(~90 бренд→домен); (3)score-tone.js— 4-рівневий тон оцінки (≥4.2/3.8/3.0 + літерний fallback); (4)job-facets.js— фасети seniority/source/days. +21 тест.
Примітки
- Не портовано (лише концепт): агентський шар дій батька (
actions/registry.ts+api/assistant/route.ts) — креслення на випадок, колиdocs-fabстане копайлотом. Нових джерел немає (реєстр 70), змін i18n/help немає.
[1.127.0] — 2026-07-29
Додано
- Три нові джерела сканування (паритет career-ops v1.23.0) — у реєстрі тепер 70 адаптерів (65 EN + 5 RU): Flowxtra (глобальний агрегатор без авторизації), VDAB (keyword-API фламандської служби зайнятості) та iCIMS (портали
careers-<tenant>.icims.com, окремо відjibeapply). Крім того Cursor повертається до ростера CLI (parent #2115):cli-detectтепер виявляєcursor(10 інструментів), ростер відновлено в help/README/config ×17.
Виправлено
- agenticjobs переведено зі скрейпінгу HTML на REST API (#2167); Greenhouse відновлює місто з
/offices, колиlocation.name— лише модель роботи (#2104); паритет role-matcher (#1933/#2164/#2009: префікс MTS, базовийproduct, згортання акцентів, незгода sub-baseline).
Примітки
- Не портовано. Більшість v1.23.0 — CLI/dashboard-поверхня, яку web-ui не викликає (batch-tailor, discover-ats, режими NL/PT, теми PDF, Go-дашборд, updater/doctor); релейні скрипти без змін. VERSION батька → 1.23.0.
[1.126.1] — 2026-07-25
Виправлено
- Дві точки дрейфу ростера CLI, які пропустив ресинк v1.126.0 — (1) інтро вкладки API keys у
#/config(config.providerModelNote, i18n ×17) перелічувало лише 7 CLI — тепер Antigravity і Grok Build вставлені після OpenCode; (2) другий рядок порівняльної таблиці в довідці (×17) і зібраний у CI help сайту все ще містилиInside Claude Code / Codex / Cursor / Gemini CLI— застарілий набір із Cursor — тепер повний ростер. Обидва рядки використовували роздільники слеш/середня крапка, які патерни свіпу v1.126.0 не покривали. i18n-снапшот перегенеровано; сьют лишається 1969.
[1.126.0] — 2026-07-25
Додано
- Вкладка інструментів AI CLI тепер виявляє всі 8 першокласних CLI career-ops — ростер
#/configсинхронізовано з батьківськимdocs/SUPPORTED_CLIS.md: уserver/lib/routes/cli-detect.mjsдодано Grok Build CLI (grok) і Kimi CLI (kimi), а Antigravity тепер спершу шукає канонічний бінарникagy. Скан PATH лише-для-читання тепер повідомляє 9 інструментів; знайдений бінарник, як і раніше, ніколи не запускається.
Змінено
- Ресинк документації з career-ops.org/docs — звірив кожну поверхню документації з живими сторінками батька (прочитано всі 31). Канонічний ростер AI-асистентів (help ×17 + README ×17) тепер перелічує 8 першокласних CLI — Claude Code, Codex, OpenCode, Antigravity CLI, Grok Build CLI, Qwen Code, Kimi, GitHub Copilot CLI — плюс Gemini CLI (застаріла обгортка). Help-бандли зберігають структуру 29 H2 / 105 H3.
[1.125.4] — 2026-07-23
Змінено
- залежності site (dependabot #151–#153) —
sharp0.34.5→0.35.3,svgo4.0.1→4.0.2,fast-uri(dev) 3.1.3→3.1.4 уsite/; збірка Astro зелена, SPA/сервер не зачеплені.
Примітки
- Паріті-свіп батьківського проєкту (career-ops
37d17ec..254764a, після v1.22.0) — портувати нічого: guard неправильного рядка вset-status(#2108) — лише CLI (у web-ui рядок трекера обирається явно в UI, і жоден маршрут не викликаєset-status.mjs); Risk Summary локалізованих режимів (#2109) — файлиmodes/<lang>/, які web-ui не читає (лише верхньорівневіmodes/*.md); перевірка маніфестуupdate-system(#2111) — лише апдейтер; решта — документація батька (турецький README, SIGNATURES ×4, SCRIPTS.md, акценти es). VERSION батька лишається 1.22.0 —parentVersionбез змін.
[1.125.3] — 2026-07-23
Виправлено
- LLM-промпти данською та гінді відповідали англійською (повідомив користувач) —
LOCALE_NAMESі всі п’ять наборівSCAFFOLD_STRINGSуserver/lib/prompts.mjsтак і не були розширені дляdaтаhi, тожresolveLocale()відкочувався доen, і кожен AI-промпт — deep research (живий і ручний), режими, оцінювання, інтерв’ю, нетворкінг, CV Studio — втрачав директиву# Output languageу цих двох локалях. Тепер обидві — повноправні: мовна директива + локалізоване обрамлення. Регресійний гейт уtests/locale-scaffold.test.mjsтепер обходить канонічний список із 17 локалей замість захардкоджених 12, а новий структурний гейт валить будь-який ключ обрамлення, що падає в англійську — локаль, яка пропуститьprompts.mjs, більше не вийде (+12 тестів, разом 1969).
[1.125.2] — 2026-07-22
Виправлено
- Deep research через Gemini: HTTP 502 (
MALFORMED_FUNCTION_CALL) (#145, внесок @Alien10140) — живий промпт/api/deepнаказував моделі «Use WebFetch / WebSearch» і зберегти бриф у файл, але безінструментальні API-провайдери не мають каналу інструментів; Gemini відповідав викликом функції замість тексту, що проявлялося як порожній HTTP 502.buildDeepPromptіbundleProjectContextотримали прапорецьheadless: живі запуски (Anthropic/Gemini/каскад резервів) отримують промпт без інструментів, який пише бриф із вбудованого контексту, а copy-paste-промпт для Claude Code зберігає інструкції щодо інструментів. +1 тест уtests/critical-fixes.test.mjs.
Змінено
- Типові моделі Gemini оновлено з застарілої
gemini-2.0-flash(#144, внесок @Alien10140) — випадний список у Конфігурації, серверний резерв уgemini.mjs(що мовчки розходився з підказкою), ланцюжок резервів OpenRouter,config.geminiModelHint×17 та довідка ×17 тепер одностайно вказуютьgemini-3.6-flash. Новий анти-дрейф ґейтtests/gemini-default-model.test.mjs(+5 тестів) фіксує всі поверхні на одному літералі — у сьюті тепер 1957 тестів.
[1.125.1] — 2026-07-21
Виправлено
- SuccessFactors: мультибрендові тенанти RMK зберігають свій шлях бренду (батьківський #2099, після v1.22.0) — холдингові компанії, які ведуть кілька придбаних брендів на одному спільному інстансі RMK, розрізняють їх за сегментом шляху (
careers.nemetschek.com/Bluebeam/проти…/Vectorworks/); адаптер раніше згортав налаштований URL до його origin, непомітно скануючи вакансії батьківського бренду. Тепер ендпоінт зберігає префікс бренду, відкидаючи лише кінцевий сегмент/search/або/tile-search-results/, тож ніщо ніколи не дублюється саме на себе; тенанти з одним доменом лишаються побайтово незмінними. Новий експортований хелперresolveTenantBase+ 1 перенесений тестовий блок уtests/sources-successfactors.test.mjs.
[1.125.0] — 2026-07-21
Додано
- cvstart.org: розділ лендінгу «Джерела вакансій» — новий розділ між скриншотами та порівнянням, який перелічує всі 67 джерел сканування у вигляді клікабельних чіпів (62 англомовні дошки/ATS + 5 російських дощок під власним підзаголовком), кожне з яких веде на публічний сайт джерела. Список синхронізується з живим реєстром адаптерів під час збірки (
sync-assets.mjs→facts.sources), тож він ніколи не може розійтися із застосунком; курований довідник посилань уSources.astroзахищений новим тестомtests/site-sources.test.mjs. У навігації шапки з’явився якір Sources; додано 4 нові ключі i18n сайту ×17. Також виправлено списокinLanguageу JSON-LD лендінгу, в якому досі бракувалоhi.
[1.124.0] — 2026-07-21
Додано
- П’ять джерел сканування (паритет із батьківським v1.22.0, #1808/#1572/#2024/#2055) — Welcome to the Jungle (JSON API для всієї дошки), Agentic Engineering Jobs (дошка агентного/AI-інжинірингу), Jobvite (ATS на тенант без автентифікації), Gem (ATS на тенант) і Alibaba Group (JSON API кар’єрної сторінки, шаблон Meituan/Tencent). Кожне джерело — прив’язана до хоста, CI-ізольована пара джерело + адаптер; реєстр тепер постачається з 67 адаптерами (62 англомовних + 5 російськомовних); резервний список випадаючого списку Source на
#/scanта його дрейф-гейт оновлено; п’ять нових наборів тестівtests/sources-{wttj,agenticjobs,jobvite,gem,alibaba}.test.mjs.
Виправлено
- Arbeitsagentur: загальнонаціональна віддалена робота лише коли
homeofficetypдорівнюєVOLLSTAENDIG(батьківський #1981) — запитhomeoffice=nv_trueтакож повертає гібридні вакансії, тож перевірка на віддаленість тепер підтверджує кожен збіг через ендпоінт деталей вакансії невеликими партіями і безвідмовно закривається (помилка пошуку зберігає справжнє місто вакансії, тож фільтри локації все одно застосовуються). - SmartRecruiters: публічні URL вакансій формувалися без
/postings/(батьківський #2047) — посилання тепер ведуть на публічну сторінку вакансії замість 404 для тенантів, чий публічний сайт відкидає цей сегмент.
Примітки
- Батьківський v1.22.0 також містив зміни на боці CLI, у які веб-інтерфейс не заходить через shell або які вже покриває: шаблон CV zh-CN + типографіка PDF, режим
/expand, налаштування кешування промптів провайдерів (Gemini/OpenAI/Ollama), розбивка токенів за кроками (у веб-інтерфейсі є власний лічильник використання), серіалізація блокування запису трекера (веб-інтерфейс скеровує записи черезwithFileLockще з v1.21), прапорці CLIvisa_filterта абсолютної дати публікації для сканування (у веб-інтерфейсі є власний фільтр віку «Опубліковано протягом») та посів дедуплікації відомих джерел (сканер веб-інтерфейсу веде власну дедуплікацію історії сканування).
[1.123.0] — 2026-07-17
Додано
- Джерело сканування Oracle Recruiting Cloud (паритет із батьківським v1.21.0, #1929) — REST API
recruitingCEJobRequisitionsбез автентифікації для кар’єрних сайтів Oracle Fusion/ORC (JPMorgan Chase, Oracle, BNY Mellon, American Express, Honeywell, …): прив’язка до хоста*.fa[.<region>][.ocs].oraclecloud.com, номер сайту визначається зcareers_urlкожної відстежуваної компанії, пагінація за зсувом із жорсткою межею кількості сторінок і WAF-обізнані заголовки на кшталт браузерних. Реєстр тепер постачається з 62 адаптерами (57 англомовних + 5 російськомовних); резервний список випадаючого списку Source на#/scanта його дрейф-гейт оновлено; новий CI-ізольований набір тестівtests/sources-oraclecloud.test.mjs.
Виправлено
- Детектор репостів: базові назви залишаються окремо від сиблінгів зі спеціалізованим суфіксом (батьківський #1922) — «Senior Analytics Engineer» більше не групується із «Senior Analytics Engineer, People Analytics»: якщо токени однієї назви є строгою підмножиною токенів іншої, а додатковий токен є справжньою спеціалізацією (а не базовим словом), обидві вакансії вважаються окремими відкритими позиціями. Позначки репосту («(Repost)», «relisted») тепер трактуються як стоп-слова інформаційного шуму. +2 перевірки в
tests/detect-reposts.test.mjs.
Примітки
- Батьківський v1.21.0 також містив зміни на боці CLI, у які веб-інтерфейс не заходить через shell або які вже покриває: попередження про повторну подачу заявки до тієї самої компанії (у веб-інтерфейсі є пауза для повторної подачі ще з v1.84.0), прапорці
--format/--reportсупровідного листа, режими промпту співбесіди для red-flag / панельної розвідки / no-show листа, персистентність довіри до джерел сканування та здоров’я порталів (веб-інтерфейс запускає власний внутрішньопроцесний сканер ізtrust-validatorі сторінкою здоров’я порталів) і розширення статистики/розриву в зарплаті (транслюються лише для читання та безвідмовно).
[1.122.0] — 2026-07-16
Додано
- Гінді (हिन्दी) — 17-та мова — повний словник інтерфейсу (~1 110 ключів), повний вбудований посібник довідки (паритет 29 H2 / 105 H3),
README.hi.md, новийCHANGELOG.hi.md(починається з v1.122.0, за прецедентом de/it/tr), лендинг cvstart.org + сторінки Методологія/Ліцензія/Журнал змін/Довідка, перемикач мов (🇮🇳), автовизначення мови браузера та локалізований знімок дашборда. Кожен гейт паритету ×16 тепер працює ×17: паритет словника i18n + знімок, гейти H2/H3 довідки, паритет CHANGELOG,check-i18nсайту та перевірка локалей Playwright.
[1.121.0] — 2026-07-16
Додано
- Сторінки Methodology, License і Changelog на cvstart.org — лендинг отримав три нові розділи всіма 16 мовами, поруч із наявним блоком Порівняння: /methodology/ (шкала оцінювання за шістьма вимірами 0.0–5.0, поріг подання заявки 4.0 і правила «ніколи не робимо» — локалізований стислий виклад career-ops.org/methodology), /license/ (канонічний текст ліцензії MIT із посиланням на NOTICE.md) і /changelog/ (цей файл, відображений для кожної локалі з 16 перекладених CHANGELOG репозиторію). Новий пункт Methodology у шапці та посилання в блоці ресурсів футера;
sync-assets.mjsтепер синхронізує CHANGELOG ×16 та LICENSE у білд сайту, тож сторінки більше не можуть розійтися з репозиторієм. - Посилання на методологію в документації — README (усіма 16 мовами), канонічний список §1 вбудованого посібника довідки (усіма 16 мовами) та вікі тепер посилаються на career-ops.org/methodology (а також FAQ і глосарій) поруч із наявними посібниками career-ops.org/docs.
Змінено
- Оновлено банер релізу та бейджі в README (1850 тестів, реліз v1.121.0) — банер досі анонсував v1.119.5.
[1.120.0] — 2026-07-16
Додано
- Маніфест CareerOps (паритет із батьком v1.20.0) — батьківський проєкт випустив Маніфест CareerOps (
MANIFESTO.md· career-ops.org/manifesto) і тепер показує його у своєму README, апдейтері та Go-дашборді. Веб-інтерфейс наслідує це: нове посилання у футері бічної панелі відкриває сторінку маніфесту (новий ключ i18nfooter.manifestoу всіх 16 локалях), вбудований посібник довідки отримав §29 «Маніфест CareerOps» усіма 16 мовами, README пояснює, що таке маніфест і як його підписати, а футер лендингу cvstart.org теж посилається на нього.
Примітки
- Батько v1.20.0 також виправив придушення відомих навичок у цільовому режимі
upskill, приглушив dotenv, щоб stdoutscan --jsonзалишався придатним для парсингу, і виправив HTML-шаблон CV, щоб заголовок ролі не відривався від своїх пунктів — це поверхні на боці CLI, у які веб-інтерфейс не заходить через shell; змін коду веб-інтерфейсу не знадобилося.
[1.119.5] — 2026-07-13
Виправлено
- Кнопка мови на лендінгу більше не переноситься — після появи прапорів у v1.119.2 підпис перемикача в шапці (наприклад «🇷🇺 Русский») міг розбиватися до трьох рядків на вузьких десктопних ширинах; підпис перемикача та всі пункти випадного списку тепер
whitespace-nowrap— прапор + ендонім завжди в один рядок. Список мов у футері переведено з жорсткої двоколонкової сітки на рядок однорядкових пунктів із переносом — «🇧🇷 Português (Brasil)» теж більше не рветься посеред назви.
[1.119.4] — 2026-07-13
Змінено
- LICENSE називає автора — рядок копірайту тепер: Sergei Emelianov (Fighter90) https://sergey-cv.com and career-ops-ui contributors (канонічний текст MIT не змінено). Новий NOTICE.md детально розписує ліцензування: хто власник копірайту, що саме покриває грант MIT (код, доки, переклади, лендінг, вікі), чого НЕ покриває (ваші дані в рантаймі, батьківський проєкт, контент джоб-бордів, торговельні марки), таблицю сторонніх компонентів (express/js-yaml — MIT; Astro/Tailwind — MIT; шрифти Figtree та JetBrains Mono — SIL OFL 1.1; sharp — Apache-2.0) і необов’язковий рядок атрибуції.
[1.119.3] — 2026-07-13
Додано
- SECURITY.md — політика безпеки, на яку посилався CONTRIBUTING, тепер існує: підтримувані версії, приватний процес репортів (у репозиторії ввімкнено private vulnerability reporting GitHub — вкладка Security → «Report a vulnerability»), модель загроз для localhost-застосунку одного користувача (у скоупі: XSS через ворожі вакансії / SSRF / path traversal / витоки секретів / послаблення CSP; поза скоупом: DoS власного localhost і проблеми батьківського проєкту) та базова лінія хардинінгу для рев’юерів.
[1.119.2] — 2026-07-13
Додано
- CONTRIBUTING.md — гід контриб’ютора, на який лендінг і README посилалися весь цей час, тепер існує: встановлення, карта проєкту, жорсткі правила безпеки/no-build, рівні тестування, walkthrough «двох реєстрів» для додавання скан-джерела, контракт i18n ×16, конвенції комітів/PR і процес релізу.
- Прапори мов на лендінгу — перемикач мов cvstart.org, сітка мов у футері та банер «читайте своєю мовою» тепер показують прапор кожної локалі поруч з її ендонімом (той самий набір регіональних індикаторів, що й у мовному
<select>застосунку; деградує до літер регіону там, де немає гліфів прапорів). - Правки футера лендінгу — неробоче посилання Discussions (функцію не ввімкнено в репозиторії) тепер веде на вікі проєкту, а у футері вказано автора: Sergei Emelianov (sergey-cv.com · LinkedIn).
[1.119.1] — 2026-07-13
Виправлено
- Фільтр джерел на
#/scanназдогнав реєстр — статичний списокFALLBACK_SOURCESза випадним списком Source (використовується лише колиGET /api/scan/sourcesнедоступний) мовчки відставав із v1.87.0: в офлайн-фолбеку бракувало 20 провайдерів (Amazon, Avature, SAP SuccessFactors, Get on Board, Dassault Systèmes, beesite, HigherEdJobs, JibeApply (iCIMS), softgarden, Cornerstone, Phenom, Radancy, Deutsche Bahn, EchoJobs, TKMS, Heckler & Koch, Rheinmetall, LaraJobs і нові Meituan / Tencent). Синхронізовано з усіма 61 і тепер закрито дрейф-тестом, що валить CI при розходженні клієнтського списку з серверним реєстром (значення І лейбли). +1 тест (1845).
[1.119.0] — 2026-07-13
Паритет із батьківським career-ops v1.19.0 + оновлення лендінгу cvstart.org.
Додано
- 2 нові сканер-провайдери — Meituan (
zhaopin.meituan.com) і Tencent (careers.tencent.com): публічні JSON-API китайських тех-дощок без авторизації, визначаються за хостом або явнимprovider:, серверний пошук за ключовими словами з пагінацією та дедуплікацією за URL — тепер 61 адаптер (56 EN + 5 RU). +20 тестів (1844). - Блок контриб’юторів на лендінгу — cvstart.org показує аватари всіх, хто долучився кодом (GitHub API
/contributorsна етапі збірки, боти відфільтровані), локалізовано всіма 16 мовами, з посиланням на повний граф контриб’юторів. - Живий лічильник зірок GitHub на лендінгу — бейдж у шапці тепер оновлюється на клієнті з GitHub API при кожному візиті (знімок збірки — як fallback), а щотижнева перезбірка Pages за розкладом не дає застаріти знімку та списку контриб’юторів; API-виклики в CI автентифіковані токеном.
Виправлено
- Запити Workday CXS несуть браузерні заголовки (батьківський #1813) — тенанти за Cloudflare (спостерігалося наживо: geico) відповідають 500 на запити без звичайних UA/
accept-language/origin/referer; фетчер тепер виводить origin і слаг сайту з самого CXS-URL. Запити Glints отримали той самий браузерний UA + origin/referer — обидва з спільної константиBROWSER_LIKE_USER_AGENTвhttp-json.mjs.
[1.118.4] — 2026-07-10
Виправлено
- Скан hh.ru давав 0 вакансій з російського IP (посилання на регіональному піддомені) — з російського резидентного IP hh.ru редиректить пошук на регіональний піддомен (
sochi.hh.ru,spb.hh.ru, …) і віддає посилання на вакансії на цьому піддомені. Парсер шукав посилання заголовка за жорстким хостомhttps://hh.ru/vacancy/і не знаходив жодного регіонального — цілком робочий скан мовчки записував 0. Тепер приймається будь-який хост*.hh.ru(рекламаadsrv.hh.ru/click?…так само відсікається — у неї немає шляху/vacancy/<id>), а URL результату канонізується вhttps://hh.ru/vacancy/<id>. Перевірено наживо: 17 реальних вакансій парсяться зі сторінкиsochi.hh.ru, де раніше було 0. +1 тест (1824).
[1.118.3] — 2026-07-10
Виправлено
- hh.ru мовчки повертав 0 вакансій (заглушка VPN-перевірки) — hh.ru тепер відповідає 302-редиректом на заглушку
/vpncheeck(«VPN мешает работе сайта») мережам, які вважає VPN/проксі (датацентрові IP); заглушка віддає HTTP 200 без жодної картки вакансії, тож скан звітував 0 без помилки. Сканер тепер розпізнає редирект за фінальним URL відповіді, вимикає hh.ru до кінця прогону й пише чесну підказку: трафік має справді виходити через резидентний IP — системний VPN/проксі може лишатися ввімкненим, навіть коли перемикач у браузері вимкнено. +1 тест (1823).
[1.118.2] — 2026-07-10
Обслуговування
- Доопрацювання лендінгу (#118) —
site/README.mdприведено до Astro 7 (security-апгрейд з #116), прибрано невикористаний імпорт, додано 4 виконувані гарди для білд-скриптів лендінгу: гейт i18n-паритету доказово падає на зламаному словнику, аsync-assetsніколи не пише позаsite/— набір 1822. Розібрано два алерти CodeQL (один виправлено в коді, один відхилено як навмисну поведінку білда).
[1.118.1] — 2026-07-10
Виправлено
- Сканування hh.ru з-поза меж Росії — hh.ru тепер віддає HTTP 451 (регіональне юридичне блокування) неросійським IP на публічних сторінках пошуку. Сканер обробляє 451 як 403: після першого блокування hh.ru вимикається до кінця прогону з чесним рядком у лозі про російську IP / VPN-вихід, тож решта запитів та інші RU-джерела не витрачаються марно. Довідку §7 оновлено всіма 16 мовами. +1 тест (1818).
[1.118.0] — 2026-07-09
Пакет паритету з батьківським career-ops v1.18.0.
Додано
- 9 нових сканер-провайдерів — Cornerstone OnDemand (
csod), Phenom (phenom), Radancy (radancy), Deutsche Bahn (deutschebahn), EchoJobs (echojobs), TKMS (tkms), Heckler & Koch (hecklerkoch), Rheinmetall (rheinmetall), LaraJobs (larajobs) — тепер 54 адаптери. Адаптер Lever додатково розпізнає дошки EU-тенанта (jobs.eu.lever.co). - Статус
Hiredу трекері (паритет зіstates.ymlбатька): прийняті офери отримують власний канонічний статус, святковий бейдж і банер «роботу знайдено» на#/tracker; воронка та конверсії рахують його як такий, що пройшов усі етапи. - Вкладка «За весь час» у
#/stats— ретрансляція лише для читання батьківськогоstats.mjs(зведення трекера за весь час, накопичені частки воронки, підсумки сканера, покриття порталів) плюс спостереження щодо компенсації зsalary-gap.mjs(бажана vs заявлена vs фактична, за кожною заявкою). Нові маршрутиGET /api/stats/lifetimeіGET /api/stats/salary-gap— shell-out з нульовою вартістю токенів, м’яка деградація{available:false}без батьківського проєкту. - 28 нових i18n-ключів у всіх 16 локалях; довідку §14/§26 оновлено всіма мовами.
Тести
- +38 юніт-тестів (три сьюти паритету провайдерів + маршрути релея/статусу) — разом 1817.
[1.117.2] — 2026-07-06
Фікс порожнього трекера для shell-out паритету. Батьківські скрипти виходять із кодом 1 і структурованим {error}-JSON, коли в трекері ще немає заявок; дошка фоллоу-апів і вкладка патернів показували це як «script-error». Обидва маршрути тепер передають це як здоровий порожній стан (available:true, empty:true), і UI показує чесне «поки порожньо». Перевірено наживо на реальному батькові.
Нове: немає.
[1.117.1] — 2026-07-06
Доналаштування v1.117.0 (розбір CodeQL). Три shell-out ендпоінти (GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns) тепер несуть спільний per-IP лімітер (кожен запит породжує дочірній процес; на loopback — no-op). Витяг тексту за URL у «Додати в CV» вичищає теги до нерухомої точки й потім видаляє всі залишкові </> — доказово повне очищення для тексту LLM-промпту. Поведінка на коректному вводі незмінна.
Нове: немає.
[1.117.0] — 2026-07-06
Пакет паритету з батьком — шість можливостей батьківського career-ops тепер в UI. (1) Дошка каденції на #/followup: терміновість за кожною заявкою (🔴/🟠/🟡/🔵) з followup-cadence.mjs, плюс кнопка «Проставити дати» (followup-seed.mjs --backfill). (2) Патерни відмов: четверта вкладка Статистики запускає analyze-patterns.mjs (лише читання) — розподіл результатів, рекомендації, частка просування за ATS-вендорами. (3) Додати в CV: картка CV Studio перетворює URL чи вставлений текст на ATS-пункти, засновані ЛИШЕ на цьому джерелі (лише пропозиції, без записів; завантаження URL захищене від SSRF). (4) 4 нові сканер-провайдери — beesite, HigherEdJobs (RSS), JibeApply (iCIMS), softgarden — реєстр тепер налічує 50 адаптерів (45 EN + 5 RU), усі в списку Scan. (5) Крок передперевірки нокаут-питань у чек-листі Apply. (6) Раннер reconcile (/api/run/reconcile). Shell-out-маршрути чесно деградують без батьківських скриптів.
- Новий модуль
server/lib/routes/followup.mjs(31-й) + нові маршрути + 8 файлів source/adapter. Тести: 6 + 7 нових; сьют 1737 → 1750. 41 новий ключ i18n ×16. Довідка §13/§17/§24/§26 розширена ×16.
Нове: GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns, POST /api/cv-studio/add-entry, POST /api/run/reconcile.
[1.116.0] — 2026-07-06
Перероблено лічильник витрат + перший наскрізний тест віджетів. Лічильник витрат ШІ (v1.114.0) виправлено й правильно закріплено: тепер він прикріплений унизу лівої бічної панелі (на всю ширину, та сама поверхня) й резервує внизу місце, що дорівнює його висоті, аби меню ніколи не перекривалося — навігація та підвал версії завжди прокручуються вільно над ним. Він оновлюється наживо (кожні 15 с, при фокусі вкладки та зміні маршруту), а кожен рядок вікна тепер показує реальні <токени> · <оцінка вартості> (смуги масштабуються відносно 30-денного вікна) замість завжди-100% «частки». Крім того: стійкий бар’єр typeof в імпортері CV закриває в першопричині повторюваний хибнопозитивний type-confusion CodeQL, а новий наскрізний тест Playwright проганяє обидва постійні віджети в реальному браузері.
public/js/lib/usage-hud.js+app.css,server/lib/cv-import.mjs. Тести:tests/playwright-widgets.mjs(2 E2E) +tests/usage-hud.test.mjs(10). Довідка §6 розширена ×16.
Нове: немає.
[1.115.0] — 2026-07-06
Шліфування дизайну (консервативне, кораловий бренд збережено). Легкий прохід удосконалення спільної дизайн-системи — без перебудови, без зміни палітри. Картки метрик на дашборді тепер трохи піднімаються та отримують коралову рамку при наведенні (як плитки швидких дій); картки контенту піднімаються ледь-ледь; кнопки primary / dark / danger отримують тінь у спокої та м’яке підняття при наведенні для глибини; великі числа вирівнюються через tabular-nums; а інтерактивні елементи отримують м’який кораловий ореол за чітким 2px-кільцем клавіатури. Уся анімація поважає prefers-reduced-motion, а ореол обмежено елементами керування — ніколи не глобальний *:focus-visible.
- Лише CSS (
public/css/app.css); без змін розмітки, i18n, маршрутів чи CSP. Тести:tests/design-polish-v1115.test.mjs(5). Перевірено вживу через Playwright.
Нове: немає.
[1.114.0] — 2026-07-06
Лічильник використання та вартості ШІ в бічній панелі (лівий нижній кут). Компактний розділ ВИКОРИСТАННЯ тепер розташований унизу бічної панелі (фіксована картка в лівому нижньому куті, якщо панелі немає; у правому нижньому для RTL) на кожній сторінці. Він показує використання токенів LLM за вікнами 24г / 7д / 30д — кожне як <токени> · <частка%> із зеленою смугою (частка від усього часу) — плюс підвал з оцінкою вартості за 24г. Дані — доступний лише для читання звід GET /api/usage із data/llm-usage.jsonl (лише локально), те саме джерело, що й сторінка #/usage; вартість оцінна, а запуски в ручному режимі безкоштовні й не враховуються. Згортається — заголовок перемикає, стан зберігається.
- Новий клієнтський віджет
public/js/lib/usage-hud.js, завантажується зindex.html, монтується в бічну панель над підвалом версії (запасний варіант — фіксований кут). Безпечний для CSP; з урахуванням теми та дзеркалом RTL. Без нового маршруту сервера. Тести:tests/usage-hud.test.mjs(8). 3 нові ключі i18n ×16.
Нове: немає.
[1.113.0] — 2026-07-06
Плаваючий помічник «Запитати довідку» на кожній сторінці. Градієнтна кнопка-робот із чатом тепер плаває в правому нижньому куті (у лівому нижньому для RTL) кожної сторінки. Натисніть, щоб відкрити компактний чат, який відповідає на питання щодо використання, спираючись ЛИШЕ на вбудовану довідку вашою мовою — той самий ендпоінт, що й сторінка #/docs-assistant (POST /api/docs-assistant/ask), тож він ніколи не читає ваше резюме, профіль чи трекер. Наживо з ключем LLM; без ключа → готовий промпт. У шапці — аватар-робот і статус онлайн; чипи підставляють часті питання; Escape або клік ззовні закриває; на сторінці #/docs-assistant кнопка прихована.
- Новий клієнтський віджет
public/js/lib/docs-fab.js, монтується глобально зindex.html; безпечний для CSP; стилі з урахуванням теми та дзеркалом RTL уapp.css. Без нового маршруту сервера. Тести:tests/docs-fab.test.mjs(8). 6 нових ключів i18n ×16. Довідка §1 розширена на місці.
Нове: немає.
[1.112.0] — 2026-07-06
Консолідація документації та QA. Без видимих користувачу змін коду. Документ угод SDD (docs/sdd/CONVENTIONS.md) оновлено до поточних 30 модулів маршрутів (було 24) та поточної базової лінії тестів; визначальний загальнопроєктний QA-промпт (qa/QA-REGRESSION-PROMPT.md) консолідовано — механіку релізу актуалізовано (v1.111, parentVersion 1.17.0, публікація за подією релізу), таблицю доповнень §14 виправлено («Виключити» в Scan перепозначено як v1.109.0) і доповнено закриттям CodeQL з v1.111 — тож він самодостатній як єдиний регресійний промпт для всієї функціональності. Додано один тест покриття для гілки завеликого завантаження.
Нове: немає.
[1.111.0] — 2026-07-06
Безпека — закриття беклогу CodeQL. Три посилення ешелонованого захисту, що закривають решту зауважень статичного аналізу в першопричині, а не через dismiss. stripDangerousMarkdown тепер екранує < у будь-якому усіченому відкритті небезпечного тега (навантаження, що закінчується на <script/<iframe/…), тож у виводі доказово не лишається живого небезпечного тега. Імпорт CV читає розмір завантаженого буфера через явне приведення Number() — бар’єр проти плутанини типів. Рядки ролі режимів тепер є шаблонними рядками, інтерпольованими через String.replace, а не збереженими функціями, що повністю прибирає виклик динамічної диспетчеризації. Видимої для користувача зміни поведінки немає.
server/lib/security.mjs,server/lib/cv-import.mjs,server/lib/prompts.mjs. Тести:tests/security-hardening-v1111.test.mjs(7) + оновлений тест-страж v1108. Без змін i18n/довідки/маршрутів.
Нове: немає.
[1.110.0] — 2026-07-06
Оновлення документації та QA (усі мови). Без змін коду. Повнопроєктний QA-промпт оновлено до v1.109.0 з новим §14 (v1.98→v1.109), а вічнозелені промпти UX-аудиту та design-export отримали поточний набір екранів. Кожен абзац довідки, доданий у v1.100–v1.109, тепер перекладено всіма 16 мовами.
Нове: немає.
[1.109.0] — 2026-07-06
Фільтр «Виключити» в Scan + огляд пайплайна (паритет з веб-версією). На #/scan поле Пошук тепер трактує коми як АБО («ролі для пошуку»), а нове поле Виключити ховає рядки, де компанія/роль/локація містить будь-яке слово через кому (напр. senior, staff); обидва запам’ятовуються у збережених пошуках. На #/pipeline компактна смуга огляду показує пайплайн з одного погляду — N у вхідних, N відстежується та лічильники Applied / Responded / Interview / Offer з трекера, кожен чип веде до #/tracker.
- Лише клієнт (без нового маршруту/записів).
public/js/views/scan.js+public/js/views/pipeline.js. Тести:tests/scan-pipeline-ui-v1109.test.mjs(2). 4 нові ключі i18n ×16. Довідка §7 + §8 розширені на місці.
Нове: немає.
[1.108.0] — 2026-07-06
Посилення безпеки (розбір CodeQL, раунд 2). Виправлено ще три знахідки низької серйозності: збирач промптів розв’язує рядок ролі локалі за власним ключем + typeof === function, щоб підроблена локаль не могла викликати метод прототипу (unvalidated-dynamic-method-call); slug імені PDF-файлу обмежено 200 символами до регулярки, щоб вхід з самих дефісів не відкочувався (поліноміальний ReDoS); а імпорт документа приводить масивний filename (повторний заголовок) до рядка (type-confusion). Без зміни поведінки для коректного вводу.
server/lib/prompts.mjs,server/lib/routes/runners.mjs,server/lib/cv-import.mjs+tests/security-hardening-v1108.test.mjs(3). За v1.106–v1.108 беклог статичного аналізу скоротився зі 167 до ~14: усі справді значущі для безпеки знахідки виправлено, решту (захищені/санітизовані хибні спрацювання + lint рівня note) відхилено з обґрунтуванням.
Нове: немає.
[1.107.0] — 2026-07-06
Посилення санітайзера (ешелонований захист від XSS у стані спокою). stripDangerousMarkdown — нейтралізує небезпечний HTML у збереженому markdown резюме/вакансії, щоб будь-який споживач, що оминає клієнт з екрануванням-при-рендері, лишався безпечним — тепер виконує видалення тегів до нерухомої точки (повторювати до стабілізації), щоб видалення, що переформовує корисне навантаження (напр. <scr<script></script>ipt>), було впіймано, збігається із закривними тегами script/style тощо зі сміттям у кінці (</script foo>) і видаляє незакритий виконуваний відкривний тег (<script …>). Поведінка для коректного markdown не змінюється — лише видаляє більше.
server/lib/security.mjs: цикл нерухомої точки (обмеження 8 проходів) + шаблони закривних тегів[^>]*>+ видалення незакритого відкривного тега. +3 регресійні випадки вtests/cv-xss-bypasses.test.mjs. Авторитетна межа XSS лишається екрануванням на виводі (UI.md); це посилює гарантію в спокої й закриває відповідні знахідки CodeQL.
Нове: немає.
[1.106.0] — 2026-07-06
Посилення безпеки (розбір CodeQL). Виправлено три реальні (хай і низької серйозності) знахідки після проходу беклогу статичного аналізу: шлях помилки рендера маршруту тепер екранує повідомлення про помилку до потрапляння в DOM (помилка сервера може відобразити введення користувача, тож вважається недовіреною — межа XSS), а записи властивостей профілю/конфігу відхиляють ключі __proto__ / constructor / prototype (захист від забруднення прототипу про всяк випадок — ключі беруться з фіксованих специфікацій полів, а не з сирого введення). Більшість решти попереджень — хибні спрацювання на легітимних читаннях/записах сканера в data/* та на маршрутах, що вже мають власний обмежувач; відхилено з обґрунтуванням.
public/js/router.jsекрануєerr.messageчерезUI.escapeHtmlпередinnerHTML;server/lib/routes/content.mjsіserver/lib/routes/config.mjsзахищають ключі прототипу. Без зміни поведінки для коректного введення. Тести:tests/security-hardening-v1106.test.mjs(3). Нових ключів i18n немає.
Нове: немає.
[1.105.0] — 2026-07-06
Сторінка витрат і вартості ШІ. Нова сторінка Витрати ШІ (бічна панель, поруч зі «Здоров’ям») показує, скільки токенів ви витратили на живі генерації ШІ — оцінки, звіти, чати — з розбивкою за провайдерами за останні 24 години, 7 днів, 30 днів і весь час, з оцінкою вартості в USD. Кожен живий виклик додає невеликий запис {provider, in, out} до data/llm-usage.jsonl (нікуди нічого не надсилається); запуски без ключа (ручний режим) нічого не коштують і не записуються.
- Новий модуль маршруту (30-й)
server/lib/routes/usage.mjs—GET /api/usage(агрегати лише для читання) +server/lib/llm-usage.mjs(recordUsageнормалізує формати usage Anthropic/OpenAI/Gemini і додає best-effort;readUsage/aggregateагрегують за вікнами 24г/7д/30д/усе × провайдер) +server/lib/llm-pricing.mjs(редагована таблиця цін за провайдерами$/1Mтокенів — токени точні, долари — приблизні прайс-ціни, які можна виправити; не виставляються до оплати). Запис підключено в точках диспетчеризації (runActiveProvider+routes/llm.mjs). - Нове представлення
public/js/views/usage.js(#/usage, вкладки вікон). Тести:tests/usage-routes.test.mjs. 17 нових ключів i18n ×16 (usage.*+nav.usage). Довідка §6 розширена на місці.
Нове: server/lib/routes/usage.mjs; server/lib/llm-usage.mjs; server/lib/llm-pricing.mjs; public/js/views/usage.js.
[1.104.0] — 2026-07-06
Логотипи компаній у таблиці сканування (без витоку приватності). Новий перемикач Вигляд у Налаштуваннях — Показувати логотипи компаній у таблиці сканування (типово вимкнено) — малює логотип кожної компанії поруч із назвою на #/scan. Логотип — це favicon компанії, узятий з її власного домену та проксі на сервері (GET /api/logo), тож жоден сторонній сервіс логотипів не дізнається, яких роботодавців ви переглядаєте. Вакансії на спільному порталі (Greenhouse, Lever, Ashby, …) показують кольоровий літерний значок замість іконки порталу, і будь-який логотип, що не завантажився, відкочується на той самий значок.
- Новий модуль маршруту (29-й)
server/lib/routes/logos.mjs—GET /api/logo?domain=. Він валідує домен (без схеми/шляху/loopback), бере/favicon.icoчерез SSRF-безпечнийsafeGet(новий режимbinaryповертає сирі байти + content-type; DNS-піннінг, перевірка редиректів і ліміт розміру не змінені), виконує сніфінг сигнатури зображення, щоб ніколи не віддати HTML-сторінку помилки як картинку, кешує влучення і промахи в LRU у пам’яті та нічого не пише на диск. - Нова клієнтська бібліотека
public/js/lib/company-logo.js(window.CompanyLogo): типово вимкнена через прапорець у localStorage; пропускає спільні ATS-хости на користь детермінованого літерного аватара; CSP-безпечний відкатimg.onerror. Тести:tests/logo-routes.test.mjs. 5 нових ключів i18n ×16 (appear.*). Довідка §2 розширена на місці.
Нове: server/lib/routes/logos.mjs; public/js/lib/company-logo.js.
[1.103.0] — 2026-07-06
Налаштування: «Інструменти CLI для ШІ» — які встановлені. career-ops працює на Claude Code, але сумісний із будь-яким агентським CLI відкритого стандарту skills. Нова вкладка Інструменти CLI для ШІ у Налаштуваннях (#/config) показує, які з них — Claude Code, Codex, Gemini CLI, OpenCode, GitHub Copilot CLI, Qwen, Antigravity — встановлені на машині, де запущено сервер, і їхні шляхи. Це сканування PATH лише для читання: перевіряється лише наявність кожного бінарника, і він ніколи не запускається (без --version, без виконання), нічого не пишеться і дані користувача не зачіпаються.
- Новий модуль маршруту (28-й)
server/lib/routes/cli-detect.mjs—GET /api/cli-detect. Визначення резолвить шлях бінарника з фіксованого списку з 7 записів заprocess.env.PATH(шими.cmd/.exe/.batу Windows; біт виконання в POSIX); ворожий файл на PATH ніколи не буде виконано цим маршрутом. - Нова вкладка «Інструменти CLI для ШІ» у
public/js/views/config.js(лінива загрузка, діплінк через#/config?tab=cli). Тести:tests/cli-detect-routes.test.mjs. 8 нових ключів i18n ×16 (cli.*+config.tabCli). Довідка §2 розширена на місці.
Нове: server/lib/routes/cli-detect.mjs.
[1.102.0] — 2026-07-05
«Запитати довідку» — чат з опорою на вбудовану довідку. Нова сторінка Запитати довідку 💬 (бічна панель, під «Довідкою»): введіть запитання на кшталт «Як сканувати портали вакансій?» і отримайте відповідь лише зі вбудованої довідки застосунку вашою мовою — показує використані розділи й ніколи не читає ваше резюме, профіль чи пошук роботи. Це про те, як користуватися застосунком, а не про вас. З ключем LLM відповідає наживо; без ключа видає готовий промпт, уже заповнений потрібними розділами довідки.
- Новий модуль маршруту (27-й)
server/lib/routes/docs-assistant.mjs—POST /api/docs-assistant/ask. Пошук без залежностей: довідка вашою мовою розбивається на розділи##й оцінюється за збігом ключових слів із запитанням; найкращі включаються, і модель має відповідати з них або сказати, що довідка це не охоплює (без вигаданих функцій/маршрутів). Спільний каскад провайдерів, ручний відкат, обмеження частоти, без записів, не читає дані користувача. - Нове представлення
public/js/views/docs-assistant.js. Тести:tests/docs-assistant-routes.test.mjs. 14 нових ключів i18n ×16 (docs.*+nav.docsAssistant). Довідка §1 розширена на місці.
Нове: server/lib/routes/docs-assistant.mjs; public/js/views/docs-assistant.js.
[1.101.0] — 2026-07-05
CV Studio: адаптуйте резюме + напишіть супровідний лист під конкретну вакансію, з рекрутерським чек-лістом. Нова картка Адаптувати під вакансію на #/cv-studio: вставте опис вакансії (і, за бажанням, цільову роль/заголовок), і CV Studio створює адаптоване під цю вакансію резюме та відповідний супровідний лист, а потім проганяє обидва через чек-лист-гейт перед видачею — error блокує (виправляється до того, як ви побачите результат), warn радить. Механіка — витяжка з практики кар’єрного консультування в загальні правила: рекрутер читає за секунди, тож релевантне йде вгору, заголовок збігається з роллю вакансії, у результатах — конкретні цифри, а лист лишається коротким тизером з єдиною «смичкою» «вимога ↔ ваш відповідний факт». Ґрунтується лише на вашому резюме, профілі та two-pager і нічого не вигадує — без зашитих компаній, ролей чи історії.
- Новий ендпоінт
POST /api/cv-studio/tailor(розширює наявний модуль cv-studio — без 27-го модуля):buildTailorPrompt+ загальний гейтTAILOR_INSTRUCTIONS, на основіbundleProjectContext, спільний каскад провайдерів, ручний промпт без ключа, з обмеженням частоти, без записів. Результат експортується у Markdown / PDF / DOCX через спільну панельreport-export.js. - Тести: +3 у
tests/cv-studio-routes.test.mjs. 10 нових ключів i18n ×16 (cvs.tailor*). Загальний референсdocs/prompts/resume-cover.md. Довідка §24 розширена на місці.
Нове: docs/prompts/resume-cover.md.
[1.100.0] — 2026-07-05
Two-pager: ШІ-автозаповнення з вашого резюме + Перегляд + експорт у PDF/DOCX/Markdown. Two-pager (#/two-pager) фіксує те, чого ви насправді хочете від наступної ролі, але раніше кожне поле доводилося писати вручну або копіювати промпт в інший інструмент. Тепер ✨ ШІ-помічник заповнення працює наживо з налаштованим провайдером — читає лише ваше резюме + профіль (через bundleProjectContext, нічого не вигадуючи), складає всі поля (хто я / що подобається / обов’язкове / що ненавиджу / стоп-фактори / непорушне / цільове середовище) і заповнює форму, щоб ви перевірили, відредагували та зберегли. Без API-ключа повертається до модалки «скопіювати промпт», як раніше. Нова кнопка 👁 Перегляд і експорт відображає two-pager як форматований документ із панеллю Завантажити .md / Зберегти як PDF / Зберегти як DOCX / Копіювати.
- Експорт
.docxбез залежностей. Новийserver/lib/docx.mjsформує мінімальний, але валідний Office Open XML.docx(ZIP із DEFLATE із чотирьох частин OOXML, CRC-32 на запис) — без нової залежності (залежності лишаютьсяexpress+js-yaml). Новий маршрутPOST /api/export/docx(server/lib/routes/export.mjs, 26-й модуль маршрутів; без стану, ліміт 200 КБ, без записів / без LLM / без fetch за URL). Під’єднано до спільногоpublic/js/lib/report-export.js, тож звіт по ринку, кар’єрний план і профорієнтація теж отримують експорт у DOCX. - Живе автозаповнення використовує спільний каскад провайдерів (
runActiveProvider/providerAvailable); повернутий YAML парситься й приводиться назад до обмеженої форми two-pager (parseYamlFields+normalizeTwoPager) — невідомі ключі відкидаються, масиви/рядки обмежуються. Ручний режим збережено. - Тести:
tests/export-routes.test.mjs. 4 нові ключі i18n ×16 (export.saveDocx,twoPager.preview,twoPager.aiFilling,twoPager.aiFilled).
Нове: server/lib/docx.mjs; server/lib/routes/export.mjs.
[1.99.0] — 2026-07-05
Сторінка здоров’я порталів (#/portals). Сканер стежить за набором компаній у portals.yml; slug ATS може тихо зламатися, і цей роботодавець зникає з усіх майбутніх сканувань. Нова сторінка Portals перелічує кожну відстежувану компанію і за кнопкою Check portal health перевіряє кожен careers_url через safeGet із закріпленим DNS (захист від SSRF), позначаючи мертві (404 = тихо відкинута) — лише читання. Також посилює репортер багів v1.98.0 за підсумками ревʼю: кільцевий буфер помилок тепер ловить мережеві збої fetch, а скрабер вичищає непозначені ключі провайдерів.
Нове: server/lib/routes/portals.mjs; public/js/views/portals.js.
[1.98.0] — 2026-07-05
Вбудований репортер багів (паритет із веб-частиною web-v0.2.0 батьківського проєкту). Кнопка 🐞 Report a bug у панелі сповіщень збирає діагностичний знімок із пріоритетом приватності — версії, ваш екран, браузер, зведення перевірок /api/health і останні 20 помилок із нового клієнтського кільцевого буфера — плюс детермінований відбиток для дедуплікації (co-web-<base36>), дає переглянути точний Markdown і потім відкриває попередньо заповнений issue на GitHub. Нічого не надсилається автоматично; він ніколи не несе ваше CV, профіль, відповіді, URL вакансій чи ключі. Нові бібліотеки logbuf.js + bug-report.js; 11 ключів i18n ×16; tests/bug-report.test.mjs.
Нове: public/js/lib/logbuf.js; public/js/lib/bug-report.js.
[1.97.1] — 2026-07-05
Посилення за підсумками рев’ю та паритет документації (продовження v1.97.0). Розбір логів AI-рев’ю виявив реальні виправлення:
fit-score.js(значок відповідності◎під час сканування).salaryFloor()більше не перетворює ставку, меншу за річну, на фіктивний річний мінімум — «at least 500 EUR/day», «$80/hr», «6000 monthly» тепер повертаютьnullзамість дилбрейкера у 500k/80k. Зіставлення країн тепер іде за цілим словом (\b…\b), тож «Germany» більше не збігається з прикметником «German» (як і «Nigeria» всередині «Nigerian») і не спрацьовує хибним порушенням «обов’язково-деінде». +3 тести уtests/fit-score.test.mjs.- Паритет документації. Кожен локалізований README тепер однаково заявляє 16 локалей — лічильник/список у рядку Help (×13) та текст розділу локалізації плюс примітка «додайте ключ до всіх N файлів» (×8) усе ще трималися на лічильниках до v1.85 (8/9). Лічильник адаптерів §17 у вбудованій довідці виправлено на 46 адаптерів — 41 англійський + 5 російських в усіх 16 бандлах.
Жодних змін поведінки, окрім евристики значка відповідності; ні нових маршрутів, ні ключів, ні додавань i18n.
[1.97.0] — 2026-07-05
Джерело сканера Dassault Systèmes + фронт покращень якості за трьома напрямами.
- Нове джерело сканування — Dassault Systèmes (паритет із батьківським career-ops, #1498).
server/lib/sources/dassault.mjs+server/lib/portals/adapters/dassault.mjsвідтворюють zero-token-провайдер «карткового пошуку» Exalead із батьківського проєкту (публічна стрічка за3ds.com/careers/jobs). Це єдина глобальна кінцева точка, тож вона обирається за провайдером (provider: dassault) або автоматично визначається за хостом3ds.com, із захистом від SSRF, прив’язкою хоста доwww.3ds.comтаredirect:'error'. XML розбирається без DOM (карти<Meta>для кожного<Hit>), місто/країна беруться з локалізованого рядка категорії, а вакансії зберігаються лише тоді, коли їхній публічний URL перебуває на*.3ds.com. Реєстр тепер постачає 46 адаптерів (41 EN + 5 RU); лічильникALL_ADAPTERS, перевірки відсортованих id та EN-набору/api/scan/sourcesпідняті з 40 → 41. Набірtests/sources-dassault.test.mjs(10 випадків). - Перенесені з батьківського проєкту виправлення надійності. Парсер Avature тепер допускає два живі варіанти розмітки орендарів (
article--resultіз суфіксом позиційного індексу + безкласовий якір заголовка JobDetail, #1541); Get on Board захищається від0/від’ємногоpublished_at(більше жодних хибних дат 1970 року); SuccessFactors обмежує останню сторінку, щоб вона не могла перевищитиMAX_JOBS(#1528). - Виправлення серверного аудиту.
safe-fetchбільше не зависає на відповіді, що перевищує ліміт розміру — шлях обмеження розміру тепер напряму завершує проміс замість очікування події'end', яку зруйнований потік ніколи не випустить (виправляє запити/api/pipeline/previewта авто-пайплайну для великих сторінок). Логування активності SSEstream.*знову досяжне (перевірку/api/stream/переміщено вище загального захисту «пропускати GET»). - Виправлення аудиту SPA. Перемикач вкладок
#/statsзахищений від гонки під час асинхронного рендерингу — результат повільної вкладки більше не може затерти новішу вкладку, на яку користувач уже перемкнувся. Підтвердження видалення у mock interview та нетворкінгу тепер передають коректні заголовок + тіло (більше жодних діалогів із порожнім тілом). - Виправлення перекладу. Виправлено неперекладені значення словника — українські
config.modes*(Adaptive Framing / Exit Narrative / Location Policy), російськийeval.jdLbl(«Job Description»), італійськийdash.quick.contactoSub(«referral» → «segnalazione») — а також англійську заготовку 16 локалей локалізовано в CHANGELOG для ru/uk/ja/ko/zh-CN/zh-TW.
Нове: server/lib/sources/dassault.mjs; server/lib/portals/adapters/dassault.mjs.
[1.96.0] — 2026-07-04
Кар’єрна орієнтація (Epic 27). Нова сторінка #/orientation відповідає на питання «які напрями мені насправді підходять?» — така оцінка, яку дав би профорієнтаційний тест, але виведена з вашого власного резюме та профілю замість опитувальника. Натисніть Згенерувати профіль, і модель поверне ваші найкращі кар’єрні вектори (які з восьми архетипів — Функціоналіст, Адміністратор, Комунікатор, Спеціаліст, Аналітик, Новатор, Менеджер, Підприємець — підходять, з доказами), схильність до кар’єрного типу, рекомендовані ролі, професійні сильні сторони, прив’язані до вашого резюме, тенденції робочого стилю та рекомендації щодо розвитку. Це ШІ-рефлексія того, як читається ваше резюме, — а не психометричний тест: воно ніколи не вигадує досягнень і ніколи не подає числових результатів так, ніби їх виміряли. Експортуйте його в Markdown або PDF; нічого не записується на диск.
- Новий маршрут
server/lib/routes/orientation.mjs(24-й модуль маршрутів) —POST /api/orientation/generateбудує промпт профілю з CV+профілю+two-pager+пам’яті через спільну каскаду провайдерів, із ручним відкатом для копіювання-вставлення та без запису файлів. - Повторно використовує
report-export.jsдля Markdown/PDF/копіювання, у навігаційній групі Зростання. - Тести:
tests/orientation-routes.test.mjs(рамка рефлексії / без вигаданих оцінок, ручний режим із засівом із CV/профілю). 7 нових ключів i18n ×16 локалей, довідка §28 ×16.
Нове: #/orientation; server/lib/routes/orientation.mjs.
[1.95.0] — 2026-07-04
План кар’єри (Epic 26). Нова сторінка #/career-plan перетворює ваше резюме та профіль на конкретний, персоналізований план розвитку. Виберіть горизонт (6/12/24 місяці) та, за бажанням, фокус, і модель — читаючи ваше CV, профіль, two-pager і нотатку пам’яті — пише знімок відправної точки, SWOT сильних сторін і зростання, цілі у форматі SMART / OKR / WOOP, альтернативні траєкторії, план hard/soft-навичок, помісячну дорожню карту, методи відстеження прогресу, підводні камені та підтримувальні кроки. Вона планує вперед від того, що дійсно показують ваші матеріали, і ніколи не вигадує факти про вашу історію. Редагуйте її на місці, Збережіть її в користувацький шар (config/career-plan.md) та експортуйте її у Markdown або PDF.
- Новий маршрут
server/lib/routes/career-plan.mjs(23-й модуль маршрутів) —GET/PUT /api/career-plan(пишеconfig/career-plan.md) +POST /api/career-plan/generate(спільна каскада провайдерів, ручний відкат, без фабрикації).PATHS.careerPlan. - Повторно використовує спільний
report-export.js(v1.94.0) для Markdown/PDF/копіювання та нову навігаційну групу Зростання. - Тести:
tests/career-plan-routes.test.mjs(обмеження, круговий обхід GET/PUT, промпт із урахуванням горизонту, засіяний із CV/профілю). 20 нових ключів i18n в усіх 16 локалей, довідка §27 ×16.
Нове: #/career-plan; server/lib/routes/career-plan.mjs; PATHS.careerPlan.
[1.94.0] — 2026-07-04
Статистика, перероблена (Epic 25). Сторінка #/stats тепер є розділом Статистика з трьома вкладками, зі справжніми графіками та значно більшою кількістю даних. Нова вкладка Звіт про ринок просить модель проаналізувати зарплати та ринок праці для ваших цільових ролей у вибраному регіоні та валюті — стисле резюме, зарплати за рівнем із перцентилями P10/P25/P75/P90, провідні роботодавці, таблиця затребуваних навичок, частота пільг, розподіл офіс/гібрид/віддалено, тренди на 12–24 місяці та поради щодо перемовин. Кожна цифра позначена як орієнтовна оцінка зі знань моделі, і ніколи не подається як зібрані дані. Нова вкладка Мій пайплайн будує графіки за вашим власним трекером: розподіл оцінок, воронка статусів, провідні компанії та ролі, заявки в часі та коефіцієнти конверсії. Початковий вигляд цільових ролей (вакансії/зарплати за країнами + тренд збережених знімків) переходить у третю вкладку, тепер із вибором валюти та оглядом вакансій за роллю.
- Експортуйте будь-який звіт у Markdown або PDF чи скопіюйте його — через спільний помічник
report-export.js(завантаження Markdown як blob; PDF через наявний вбудований PDF-раннер). - Новий маршрут
server/lib/routes/market.mjs(22-й модуль маршрутів) —POST /api/stats/marketбудує промпт аналізу ринку на основі вашого CV/профілю (щоб знати ваші цільові ролі), регіону та валюти, запускає його через спільну каскаду провайдерів і відкочується до промпту для копіювання за відсутності ключа. Без запису файлів. - Тести:
tests/market-routes.test.mjs(обмеження регіону/валюти, промпт із чесним маркуванням, ручний режим із передзаповненням із CV/профілю). 36 нових ключів i18n в усіх 16 локалей, довідка §26 ×16.
Нове: #/stats перероблено на вкладки; server/lib/routes/market.mjs; public/js/lib/report-export.js.
[1.93.0] — 2026-07-04
Шар пам’яті (Epic 24). Нова сторінка #/memory містить коротку редаговану нотатку «запам’ятай це про мене», яку асистент тримає в голові на кожному завданні:
- Одна нотатка, всюди — оскільки вона вбудована в
bundleProjectContext, нотатка автоматично доходить до кожного AI-запиту (оцінка, пробна співбесіда, нетворкінг, CV Studio) в усіх провайдерах. Напишіть її раз — вона керує всім. - Керування, а не факти — вона фіксує ваші вподобання й те, як вам зручно працювати (тон, формат, стоп-фактори, ритм), і ніколи не нові фактичні твердження про ваш досвід — ті й далі живуть лише у вашому резюме, профілі та two-pager. Зберігається в користувацький шар за адресою
config/memory.mdі ніколи не перезаписується оновленнями. - Підказки з ваших даних —
POST /api/memory/suggestаналізує ваш власний трекер заявок на поведінкові патерни й готує пункти для перегляду й редагування. Він читає ваш трекер; ніколи не вигадує фактів і не робить жодного онлайн-виклику.
Нове: server/lib/routes/memory.mjs (21-й модуль маршрутів — GET/PUT /api/memory + POST /api/memory/suggest), public/js/views/memory.js, PATHS.memory та блок config/memory.md, доданий до bundleProjectContext. 11 нових ключів i18n в усіх 16 локалей. Тести: tests/memory-routes.test.mjs.
[1.92.0] — 2026-07-04
CV Studio (Epic 21). Нова сторінка #/cv-studio дає вашому резюме три чесні, здебільшого локальні інструменти:
- Діагностика резюме — детермінована оцінка 0–100 із поясненнями за кожною перевіркою (кількісно виміряний вплив, слабкі дієслова, штампи, обсяг, ключові розділи, контактні дані). Повністю на боці клієнта (
window.CvDiagnostics) — без LLM, нічого вигаданого, кожен висновок пояснено, тож ви вирішуєте, що змінювати. - Маска приватності — вилучення PII (email, телефон, посилання/нікнейми, поштова адреса й за бажанням ваше ім’я → ініціали) перед тим, як поділитися резюме як зразком чи скриншотом. Виконується цілком у браузері (
window.CvPrivacy); повідомляє, що саме було вилучено, і ніколи не зберігає оригінал. - Make it human / збіг голосу — вставте суху фразу чи абзац і перепишіть її вашим голосом, обґрунтованим на боці сервера через
voice-dna.mdіwriting-samples/. Жорсткий запобіжник: воно може переставляти, стискати й переголошувати, але ніколи не додає факту, метрики чи досягнення, якого ще немає в тексті. Виконується наживо через спільний каскад провайдерів або повертає готовий до копіювання промпт без ключа.
Нове: server/lib/routes/cv-studio.mjs (20-й модуль маршрутів — POST /api/cv-studio/humanize), public/js/views/cv-studio.js, public/js/lib/cv-diagnostics.js, public/js/lib/cv-privacy.js, PATHS.voiceDna + PATHS.writingSamplesDir. 29 нових ключів i18n в усіх 16 локалей. Тести: tests/cv-diagnostics.test.mjs, tests/cv-studio-routes.test.mjs. (Галерея шаблонів, експорт у Word та архів PDF-оголошень відстежуються як подальша робота над CV Studio.)
[1.91.0] — 2026-07-04
Нетворкінг і глибоке дослідження компаній (Epic 16). Нова сторінка #/networking перетворює компанію на дієвий план отримання співбесіди, обґрунтований вашим резюме, профілем і two-pager:
- Досьє компанії — стислий бриф про те, чим займається компанія, нещодавні сигнали, варті згадування, і зачіпки «чому я підходжу», взяті з вашого реального досвіду.
- З ким зв’язатися — 3–5 цільових персон (наймальний менеджер, внутрішній рекрутер, старший IC з команди, теплий контакт/випускник того самого вишу) з конкретним пошуковим рядком LinkedIn для пошуку кожного. Реальні імена ніколи не вигадуються.
- Найтепліший шлях для представлення — найреалістичніший теплий вхід саме для вашого досвіду (спільний роботодавець/навчальний заклад/спільнота, шлях другого рівня або сильний за сигналом холодний DM) і чому.
- Чернетки для контакту — короткі, конкретні повідомлення для головних персон, обґрунтовані вашими реальними точками доказу.
- Наживо або вручну — виконується наживо через спільний каскад провайдерів із будь-яким ключем або повертає готовий до копіювання промпт (чесний fallback, нічого вигаданого). Зберегти план зберігає готовий план у користувацький шар (
networking/net-{company}-{role}-{date}.md); сторінка показує список, відкриває й видаляє збережені плани.
Нове: server/lib/routes/networking.mjs (19-й модуль маршрутів), public/js/views/networking.js, PATHS.networkingDir. Повторно використовує каскад server/lib/llm-dispatch.mjs з v1.90.0. 24 нові ключі i18n в усіх 16 локалей. Тести: tests/networking-routes.test.mjs.
[1.90.0] — 2026-07-04
Mock Interview 2.0 (Epic 15). Нова сторінка #/mock-interview перетворює ваше резюме, профіль, two-pager і банк історій на покрокову репетицію співбесіди:
- Діалогова практика — задайте цільову роль (+ за бажанням компанію / JD), і інтерв’юер починає з точкового запитання. На кожну надіслану відповідь ви отримуєте структуровану репліку: Feedback (сильні сторони + прогалина за STAR+R), Score (
N/5) та Next question, яке промацує найслабше місце вашої останньої відповіді. Обґрунтовано на боці сервера вашими реальними матеріалами — воно ніколи не вигадує досвід, якого у вас немає. - Враховує банк історій —
interview-prep/story-bank.mdвбудовується в промпт (той самий рівень довіри, що йcv.md), тож зворотний зв’язок може вказати вам на ваші ж найкращі історії. - Наживо або вручну — за наявності ключа провайдера хід виконується наживо через спільний каскад провайдерів (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models); без ключа ви отримуєте готовий до запуску промпт для копіювання та вставлення (чесний fallback, без вигаданих відповідей).
- Збережені сесії — натисніть Save transcript, щоб зберегти завершену співбесіду в шар користувача (
interview-prep/mock-{company}-{role}-{date}.md); сторінка показує список, відкриває та видаляє збережені сесії.
Нове: server/lib/routes/interview.mjs (18-й модуль маршрутів), public/js/views/mock-interview.js, server/lib/llm-dispatch.mjs (спільний каскад провайдерів), PATHS.storyBank, bundleProjectContext({ extraFiles }). 30 нових ключів i18n в усіх 16 локалей. Тести: tests/interview-routes.test.mjs.
[1.89.0] — 2026-07-04
Відповідність кандидата й ринку — two-pager (Epic 14). Нова сторінка #/two-pager дозволяє зафіксувати те, чого ви насправді хочете від наступної ролі, за зразком «Mnookin two-pager» із книги Never Search Alone:
- Покроковий конструктор — оповідь від першої особи «Хто я», нотатка «Цільове середовище» та п’ять редакторів списків-чипів: loves, must-haves, hates, deal-breakers і non-negotiables. Зберігається в користувацький шар батьківського проєкту (
config/two-pager.yml) черезPUT /api/two-pager— ніколи не перезаписується системними оновленнями. - ШІ-помічник заповнення (
POST /api/two-pager/draft) — збирає готовий до запуску Mnookin-промпт із вбудованими вашими CV + профілем, щоб ви запустили його в будь-якому LLM і вставили результат назад. Він використовує лише ваші власні матеріали; нічого не вигадується. - Бейдж відповідності-тому-що-ви-хочете — кожна вакансія на
#/scanтепер показує оцінку відповідності◎ N(на боці клієнта, черезwindow.FitScore), що зіставляє формат роботи, країну, мінімальну зарплату та релокацію вакансії з вашим two-pager. Чесний за задумом: якщо вакансія не дає зіставного сигналу, бейдж не показується (ніколи не вигадане число). Порушення deal-breakers важать більше, ніж легкі неприязні. - Живить кожну оцінку — збережений two-pager вбудовується в
bundleProjectContext, тож усі подальші оцінки LLM поєднують заявлені вами вподобання з відповідністю CV-проти-JD.
Нове: server/lib/routes/two-pager.mjs, public/js/views/two-pager.js, public/js/lib/fit-score.js, PATHS.twoPager. 27 нових ключів i18n в усіх 16 локалях. Тести: tests/two-pager-routes.test.mjs, tests/fit-score.test.mjs.
[1.88.0] — 2026-07-04
Доопрацювання issue #29 — прогалини i18n у Скануванні + гігієна API.
- Локалізовано останні захардкоджені рядки Сканування (дорожня карта v1.69.4): плашки зведення за джерелом (
N нових / M збігів), тостиN нових вакансійта бейджrelocтепер проходять черезt()— 4 нові ключі (scan.pillNew,scan.pillMatching,scan.newOffers,scan.relocBadge) в усіх 16 локалях. Неангломовні користувачі більше не бачать випадкової англійської в основному потоці сканування. - Вимкнено заголовок
X-Powered-By(дорожня карта v1.69.5):app.disable('x-powered-by')уcreateApp()— сервер більше не повідомляє, що працює на Express. (Решту цього епіку вже було випущено:parentVersionвирізає свій коментар release-please, перемикач світлої теми, закриття модальних вікон під час зміни маршруту та локалізація «Score» (rep.score) у Звітах.)
Тести: tests/scan-i18n-gaps.test.mjs + перевірка відсутності X-Powered-By у tests/security-headers.test.mjs.
[1.87.0] — 2026-07-04
4 нові провайдери сканування без автентифікації (паритет із батьківським career-ops v1.16.0). Реєстр сканера зростає з 41 → 45 адаптерів (40 EN + 5 RU) — усі публічні, без автентифікації, із закріпленим хостом, redirect:'error' (захист від SSRF), кожен з ізольованим для CI тестом:
- Get on Board (
getonbrd) — публічний JSON:API по всьому порталу (LATAM/віддалені технічні ролі), обирається за провайдером, із пагінацією.server/lib/sources/getonbrd.mjs. - Amazon (
amazon) — публічний JSON пошукуamazon.jobs, визначається за хостом абоprovider: amazon, пагінація за зміщенням.server/lib/sources/amazon.mjs. - Avature (
avature) — ATS*.avature.netдля кожного орендаря, парсинг HTML, визначається за хостом абоprovider: avature.server/lib/sources/avature.mjs. - SAP SuccessFactors (
successfactors) — список плиток RMK для кожного орендаря (*.successfactors.eu/.com,jobs2web.com), парсинг HTML.server/lib/sources/successfactors.mjs.
Кожен постачає sources/<slug>.mjs (автовиявлюваний meta → випадний список #/scan) та portals/adapters/<slug>.mjs у ALL_ADAPTERS (правило двох реєстрів) + tests/sources-<slug>.test.mjs. Лічильник ALL_ADAPTERS, а також перевірки відсортованих id та EN-набору /api/scan/sources зросли з 36→40; GET /api/scan/sources тепер перелічує 45.
[1.86.0] — 2026-07-03
Статистика за цільовими ролями (#/stats) — ринкова статистика вакансій і зарплат за ВАШИМИ цільовими ролями. Нова сторінка Аналітики читає ваші цільові ролі з профілю (config/profile.yml → не захардкоджені) та вакансії з останнього сканування, а потім показує для кожної ролі й країни:
- Вакансії за країнами та медіанну зарплату за країнами (USD) — агрегуються на боці клієнта (
public/js/lib/role-stats.js, повторно використовуючиwindow.Countries) з розріджених даних, які сканери вже збирають. Зарплати в будь-якій валюті нормалізуються до USD через явно приблизну таблицю FX, із застереженням щодо розміру вибірки — ніколи не вигадуються. - Фільтри ролі та країни й саморобні вбудовані SVG-графіки: стовпчикові та трендові (без нових залежностей, безпечно для CSP — лише
addEventListener). - Зберегти знімок (
POST /api/stats/snapshot) зберігає поточний агрегат уdata/role-stats.jsonl; графік тренду (GET /api/stats/trend) відстежує кількість вакансій у часі — подання «динаміки». Чесний гібрид: знімки формуються з локальних даних сканування й оновлюються за запитом. - Повністю локалізовано в усіх 16 локалях (26 нових ключів i18n).
Нове: server/lib/routes/stats.mjs (16-й модуль маршрутів), public/js/lib/role-stats.js, public/js/views/stats.js, PATHS.roleStats; тести role-stats.test.mjs (7) + stats-routes.test.mjs (5).
[1.85.0] — 2026-07-03
Локалі німецької, італійської та турецької (паритет локалей із батьківським career-ops v1.16.0). Інтерфейс тепер постачається 16 мовами — de 🇩🇪, it 🇮🇹 і tr 🇹🇷 додаються до наявних 13.
- Повний переклад інтерфейсу — усі 730 ключів i18n перекладено в
public/js/lib/locales/i18n-dict.{de,it,tr}.js; перемикач мов показує Deutsch / Italiano / Türkçe, а автовизначення мови браузера розпізнаєde/it/tr(public/js/lib/i18n.js). - Вбудований довідковий посібник —
docs/help/{de,it,tr}.mdперекладено (повна структура з 19 H2 / 75 H3), надаються черезGET /api/help/:lang. - Документація — додано
README.{de,it,tr}.mdіCHANGELOG.{de,it,tr}.md; перевірка паритету локалей CHANGELOG тепер охоплює 15 не-EN локалей. - Каркас промптів —
server/lib/prompts.mjs(LOCALE_NAMES+SCAFFOLD_STRINGS) локалізовано для трьох нових локалей, тож вивід LLM дотримується мови інтерфейсу.
Усі перевірки паритету (i18n-locale-files, i18n-coverage, check-changelog-parity, lang-switcher-rtl) розширено на набір із 16 локалей.
[1.84.0] — 2026-06-30
Кулдаун повторних заявок + компенсація в pipeline.md (паритет із батьківським career-ops v1.15.0). Два покращення сканера:
- Кулдаун повторних заявок (#1201): EN-скан тепер пропускає ролі в компаніях, до яких ви нещодавно вже подавали заявку, — результати лишаються зосередженими на НОВИХ вакансіях. Налаштуйте часові вікна на рівні компанії в
config/profile.ymlу секціїre_apply_windows:(last_apply_date,same_role_days,applied_to: [roles], необов’язковийcross_role_bucket); порівняння компаній нечутливе до пунктуації й працює за межами слів (server/lib/cooldown.mjs). Не діє, якщо ключ відсутній; до журналу сканування виводитьсяCooldown skipped: N. - Компенсація в pipeline.md (#1017): знайдені під час сканування вакансії тепер зберігають зарплату у вигляді необов’язкового кінцевого стовпця (
url | <salary>) уdata/pipeline.md. URL залишається ключем дедуплікації (стовпець| compприбирається під час читання), значення комірки санується (без ін’єкції рядка/стовпця, формульний префікс нейтралізується), а pipeline у форматі «лише URL» зберігає зворотну сумісність.
Постачається з tests/cooldown.test.mjs і тестами компенсації для pipeline. Кількість джерел не змінилась — 41 (обидва покращення стосуються логіки сканування, а не нових дошок).
[1.83.0] — 2026-06-30
Детектор повторних публікацій / фантомних вакансій (паритет із батьківським career-ops v1.15.0). На сторінці #/scan з’явилася панель 🔁 Повторні публікації / фантомні вакансії, яка позначає кластери «компанія + посада», повторно розміщені під різними URL у ковзному 90-денному вікні — сигнал застарілих конвеєрів і фантомних оголошень. Базується на нечіткому порівнянні заголовків (server/lib/role-matcher.mjs) та read-only детекторі (server/lib/detect-reposts.mjs) по data/scan-history.tsv, надається через GET /api/scan/reposts. Також: parentVersion у /api/health тепер повертає лише semver (коментар # x-release-please-version від release-please прибирається). Постачається з tests/detect-reposts.test.mjs. Кількість джерел не змінилась — 41: повторні публікації — аналітична функція, а не нова дошка.
[1.82.0] — 2026-06-30
Джерело сканування NoDesk (паритет із батьківським career-ops v1.15.0). Загальнодошкова RSS-стрічка віддаленої роботи NoDesk тепер повноцінне джерело — додайте запис provider: nodesk, і воно з’явиться у списку Source на #/scan (загалом 41 адаптер: 36 EN + 5 RU). Хост закріплено за nodesk.co із redirect:'error' (захист від SSRF); заголовки діляться за Role at Company (NoDesk не має тегу локації, поле місцеположення залишається порожнім); усі вакансії — віддалені. Постачається з CI-ізольованим набором тестів tests/sources-nodesk.test.mjs; повний набір юніт-тестів зелений, 1523.
[1.81.0] — 2026-06-29
Паритет із батьківським career-ops — 13 нових джерел сканування вакансій. Переносить останній пакет провайдерів із main Fighter90/career-ops у вбудований сканер. Загальнодоступні API дошок (вибір за provider:): Arbeitnow, Himalayas, Jobicy, Landing.jobs, 4 Day Week, The Muse, The Hub, Jobspresso (RSS) і Hacker News “Who is hiring?” (Algolia, два кроки). Польські дошки (визначаються за хостом або provider:): JustJoin.it і NoFluffJobs (POST-пошук). Per-tenant ATS (автовизначення за careers_url): Pinpoint (<slug>.pinpointhq.com/postings.json) і Rippling (ats.rippling.com/<slug> → api.rippling.com board). Кожне джерело закріплено за хостом із redirect:'error' (захист від SSRF) і доступне у списку Source на #/scan — реєстр тепер налічує 40 адаптерів сканера (35 EN + 5 RU). Додано 13 CI-ізольованих наборів тестів на джерело; повний набір юніт-тестів зелений, 1513.
[1.80.0] — 2026-06-28
П’ять покращень сканера (ідеї з job-crawler, переписані). (1) Джерело Teamtailor — сайти <slug>.teamtailor.com через публічну стрічку /jobs.rss, автовизначення за careers_url (хост закріплено + redirect:'error'); у реєстрі тепер 27 адаптерів. (2) Карантин джерел — джерело зі сталим 404/410 записується в data/scan-quarantine.json і пропускається в наступних сканах (самовідновлення: повтор через 14 днів). (3) Макс. на джерело — необов’язкове поле на #/scan, що обмежує кількість вакансій з борду (∞ за замовчуванням). (4) Опубліковано за — клієнтський фільтр за віком (24г / 7д / 30д). (5) Збережені пошуки + ★ обране — називайте й повторно використовуйте набори фільтрів і позначайте вакансії, у localStorage із захисною валідацією (пошкоджений кеш скидається чисто); кеш результатів скидається перед скануванням і наповнюється наживо.
[1.79.0] — 2026-06-28
Джерело сканування WeWorkRemotely (паритет із career-ops v1.14.0). Загальнодошкова RSS-стрічка віддаленої роботи We Work Remotely тепер повноцінне джерело — додайте запис provider: weworkremotely, і воно з’явиться у списку Source на #/scan (26 адаптерів загалом). Хост закріплено за weworkremotely.com із redirect:'error' (захист від SSRF); заголовки діляться за Company: Role. Також: ключові слова title_filter тепер обрізаються перед перевіркою довжини (parent #1261).
[1.78.2] — 2026-06-27
Зміцнення i18n та UX (виправлення після v1.78.1). Доступна назва логотипа тепер локалізована всіма 13 мовами (nav.logoHome). Enter у глобальному пошуку, коли ви вже на #/scan, примусово ре-рендерить, щоб не втратити введений термін (guard тієї ж маршрутизації). health.title тепер перекладено польською (Kondycja) та данською (Systemtilstand) — раніше англійською. Тести 1235 → 1238.
[1.78.1] — 2026-06-27
Виправлення UX на Scan. Таблиця результатів #/scan тепер автоматично оновлюється під час сканування та ще раз після завершення — без перезавантаження. Глобальний пошук у шапці показує підказку Enter і за не-URL-запитом переходить на #/scan із заповненим полем (раніше #/tracker). Логотип тепер веде на дашборд (головну).
[1.78.0] — 2026-06-27
Гео-фільтр на сторінці Scan — фільтр вакансій за країною, з прапорцями. Новий список Країна у #/scan показує кожну країну, виявлену в результатах (емодзі-прапор + лічильник), щоб лишити лише ролі, прив’язані до країни — поряд із фільтром Remote/Hybrid/Onsite, тож можна шукати як прив’язану до країни, так і віддалену роботу. На основі нового помічника countries.js, що зіставляє вільний текст локації (назви країн, аліаси та ~100 великих міст) з країною ISO + прапором; детекція консервативна й ніколи не вгадує.
[1.77.0] — 2026-06-27
Додано данську (Dansk) — 13-ту мову інтерфейсу. Повний переклад UI, вбудованого довідника Help (19 H2 / 75 H3), README та CHANGELOG. Данська додана до перемикача мов із прапорцями; механіка i18n (асемблер, аудит, перевірки паритету, знімок) тепер охоплює 13 локалей.
[1.76.0] — 2026-06-26
Паритет із career-ops v1.13.0 — шість нових джерел, зміцнення сканера та таблиця результатів без ліміту.
Додано
- Шість ATS-джерел на тенант — BambooHR, Breezy HR, Comeet, Personio, Recruitee, SolidJobs. Визначаються за хостом
careers_url(Comeet потребує повногоapi:); кожен хост закріплено якірним regex +redirect:'error'(захист від SSRF). Доступні у списку Source на#/scan— реєстр тепер має 25 адаптерів (20 EN + 5 RU). Додано помічникfetchTextдля XML-стрічки Personio. trust_filter— необов’язкова оцінка довіри (0–100, рівень high/medium/low, прапорці), лише анотує. Рядки нижчеhighотримують нейтральний значок ⚠ на#/scan; нічого не відкидається.- Arbeitsagentur
remoteMatch+remoteMaxPages— визначення віддаленої роботи з конфігу:title,filter(homeoffice=nv_trueна сервері + пагінація) абоoff.
Змінено
- Немає ліміту результатів.
MAX_STORED_RESULTS(2000) прибрано — зберігаються всі збіги, таблиця#/scanгортає їх (200/стор.). - Надійність фільтра заголовків — короткі абревіатури (COO, SDR…) збігаються за межами слів; некоректний
title_filterбільше не ламає скан. Обидва сканери.
Тести
- +32 кейси (1190 → 1222):
sources-ats-providers,title-filter,arbeitsagentur-remote,trust-validatorі переписаний стражscan-result-cap(«без ліміту»).
[1.75.2] — 2026-06-19
docs: повний паритет документації для агрегаторів сканера з v1.75.0 в усіх 12 локалях. Без зміни коду — приводить документацію для користувача у відповідність до семи джерел, що з’явилися в v1.75.0:
- Довідковий посібник (12 локалей). §5 отримує блок
content_filter(гейтинг за ключовими словами опису/сніпета, аналогlocation_filter) та примітку про агрегатори; §7 перелічує сім нових джерел у проході сканування в один клік та в повному переліку випадного списку Source; лічильник адаптерів у §17 виправлено зі застарілого «11 adapters» на «19 adapters — 14 English + 5 Russian». Заголовків##/###не додавалося, тож закріплена структура 19 H2 / 75 H3 залишається незмінною. - README (9 повних локалей). Новий пункт «Aggregator boards (v1.75.0)» під джерелами сканування, плюс значок релізу, оновлений до v1.75.2. (Скорочені README pl/uk/ar не мають списку за джерелами, і там вони навмисно не зачеплені.)
- Довідкові документи.
docs/portals-examples.mdотримує готовий до копіювання розділ «Aggregator boards» з точними блоками конфігураціїprovider:/<provider>:для всіх семи;docs/PROJECT.mdоновлено до 19 adapters;docs/sdd/CONVENTIONS.mdдокументує розрізнення двох реєстрів (sources/registry.mjsдля випадного списку протиportals/registry.mjsдля вибірки), вибір агрегатора на основіprovider:, що передається якopts.company, санітайзер запису сканування (scan-sanitize.mjs) та кількість тестів v1.75.1 (1190). - QA. Додано
qa/QA-REGRESSION-PROMPT-v1.75.2-FULL.md— драйвер релізного шлюзу за повною поверхнею, оновлений під цикл агрегаторів сканування v1.75.x.
[1.75.1] — 2026-06-19
fix(scan): доопрацювання надійності джерел на основі конфігурації з v1.75.0. Три невеликі зміцнювальні виправлення за підсумками післярелізного розбору (без зміни поведінки за штатного сканування):
- Паузи пагінації з урахуванням скасування. Міжсторінкові ввічливі паузи Glints (300 мс) та Jobstreet/SEEK (200 мс) тепер негайно завершуються, коли спрацьовує
AbortSignalсканування — через новий помічникdelay(ms, signal)уserver/lib/http-json.mjs, тож від’єднаний клієнт не може утримувати пагіноване сканування відкритим ще на одну паузу. - Описова помилка для не-JSON відповіді.
fetchJsonтепер загортає не-JSON тіло з кодом2xx(наприклад, HTML-сторінку обслуговування, віддану зі статусом 200) якnon-JSON 2xx response from <url>, замість того щоб видавати голийSyntaxError, тож журнал помилок сканера за кожним джерелом називає некоректно поводжуваний ендпоінт. - Посилена нормалізація запису сканування.
normalizeScanScalarтепер згортає вертикальну табуляцію, переведення сторінки та розділювачі рядка/абзацу Unicode (\v \f U+2028 U+2029) на додачу до\r \n \t— суворий надмножина, тож жоден розділювач запису/рядка, який могли б врахувати таблиця чи переглядач, не потрапляє доscan-history.tsv.
[1.75.0] — 2026-06-19
feat(scan): переносить паритет з батьківським career-ops v1.12.0 — сім нових джерел вакансій, фільтрація контенту та виправлення безпеки/якості. web-ui запускає власні внутрішньопроцесні сканери (він не викликає shell out до батьківського scan.mjs), тож зміни провайдерів і сканування з батьківської v1.12.0 не надходять автоматично — цей реліз переносить застосовні з них згідно з контрактом адаптерів web-ui.
- Сім нових джерел сканера. Три глобальні агрегатори віддалених вакансій — RemoteOK, Remotive, Working Nomads — вписуються в автоматично виявлюваний патерн
server/lib/sources/*.mjs(обираються черезprovider: remoteok/remotive/workingnomads). Чотири керовані конфігурацією регіональні агрегатори — careers IBM, Arbeitsagentur (німецьке Федеральне агентство зайнятості), Glints (Південно-Східна Азія), Jobstreet / SEEK — читають поелементний конфігураційний блок<provider>:; en-scanner тепер прокидає розв’язаний запис компанії аж до кожного фетчера, щоб вони могли його прочитати. Усі сім автоматично з’являються у випадному списку джерел#/scan. content_filter(батьківський #974). Необов’язковий блокportals.yml(списки ключових слівpositive/negative), що фільтрує вакансію за текстом її опису/сніпета — повторює семантикуlocation_filter; вакансії без опису завжди проходять. Підключений до обох сканерів — EN і RU.- Посилення запису під час сканування (батьківський #1098). Метадані зовнішніх стрічок тепер санітизуються перед потраплянням у
data/scan-history.tsvтаdata/pipeline.md: керівні символи згортаються (перенесення рядка в назві компанії/посади більше не може ін’єктувати рядок TSV), а провідні= + - @нейтралізуються проти ін’єкції формул електронних таблиць. secondaryLocationsAshby (батьківський #1073). Джерело Ashby тепер згортає мітку регіону кожної вторинної локації разом із поштовимиaddressLocality/addressCountryу рядок локації (з дедуплікацією), тож роль із правом працювати в ЄС, чия основна мітка читається, наприклад, як “Canada”, спливає дляlocation_filter.- Валідація форми звіту про оцінку (батьківський #819). Внутрішньопроцесні провайдери
/api/evaluate(Anthropic / OpenAI / Qwen / OpenRouter / GitHub Models) тепер позначають некоректний звіт A–G /SCORE_SUMMARYяк нефатальний масивwarnings; шлях оцінки Gemini вже успадковує цей захист від батьківськогоgemini-eval.mjs. - docs: Antigravity CLI додано до списків підтримуваних асистентів у всіх 12 README (зіставляється з провайдером Gemini).
Успадковано безкоштовно з git pull батьківського проєкту (web-ui викликає їх через shell out): резервні шрифти CJK для японського PDF (#1053), ATS-сумісні шрифти PDF (#1074), захист CJK для LaTeX (#1054), виправлення tracker/merge/followup/dashboard і китайські режими modes/zh (web-ui перелічує режими динамічно).
[1.74.3] — 2026-06-18
docs(parent-source): спрямовує батьківський репозиторій career-ops на форк Fighter90/career-ops. web-ui тепер посилається на форк супровідника як на батьківський проєкт усюди, де це реальне джерело: типове значення CAREER_OPS_REPO в інсталяторі bin/setup.sh, кожне посилання git clone / «побудовано на основі» / онбординг у всіх 12 README, а також документація агентів (CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md, docs/). Зазначення авторства santifer (та застереження про неофіційний інтерфейс) не змінилося — перемістилися лише URL джерела/клонування. tests/sh-files.test.mjs тепер перевіряє, що інсталятор клонує форк.
[1.74.2] — 2026-06-17
fix(health): відображення GITHUB_MODELS_API_KEY як необов’язкової перевірки на #/health та в /api/status/providers. Провайдер GitHub Models із v1.74.0 налаштовувався в #/config, але не мав рядка на сторінці Health і був відсутній у провайдерській поверхні keysConfigured. Додано необов’язкову перевірку (з тим самим формулюванням “set / unset (manual mode)”, що й у решти п’яти провайдерів живого оцінювання) та github (+ його GITHUB_MODELS_MODEL) до /api/status/providers, тож маршрутизація активного провайдера й сторінка Health тепер відображають усі шість. Тест рядка health у tests/api.test.mjs розширено на всі шість провайдерів.
[1.74.1] — 2026-06-17
docs + test: розділ README «Встановлення ШІ-асистента»; повне покриття гілок для конектора Gemini. До README додано таблицю встановлення/входу — посилання для встановлення Claude Code / Gemini CLI / Codex / Qwen Code / OpenCode / GitHub Copilot CLI + відповідність постачальника #/config для кожного + «увійдіть перед продовженням» (відповідає розділу швидкого старту career-ops.org/docs; пояснює, що web-ui є автономною альтернативою, яка не потребує CLI). Новий tests/gemini-connector.test.mjs (8 сценаріїв) охоплює кожну гілку runGemini — без ключа, успіх, помилка API, порожнє/заблоковане завершення, некоректне тіло відповіді, тайм-аут, мережева помилка, hasGeminiKey — доводячи server/lib/gemini.mjs до 100% за інструкціями. Загальне покриття: 96% рядків / 88% гілок / 96% функцій. Набір тестів 1126 → 1134.
[1.74.0] — 2026-06-17
feat(llm): GitHub Models (Copilot) як 6-й провайдер + канонічне вирівнювання 6 асистентів. career-ops.org/docs перераховує шість ІІ-асистентів для написання коду — Claude Code, Gemini CLI, Codex, Qwen Code, OpenCode, GitHub Copilot CLI. Тепер web-ui підтримує всі шість: п’ять відповідають наявним активним провайдерам (Anthropic / Gemini / OpenAI / Qwen / OpenRouter), а GitHub Copilot CLI отримує виділений конектор GitHub Models — runGitHubModels (OpenAI-compatible; PAT GitHub з областю models), що налаштовується в #/config (GITHUB_MODELS_API_KEY + GITHUB_MODELS_MODEL) і вибирається через LLM_PROVIDER=github; 6-й у порядку auto. Пакети довідки та README тепер перераховують канонічні шість (перейменовано Qwen CLI→Qwen Code; додано Gemini CLI + GitHub Copilot CLI), а README додає повну таблицю посилань на режими та адаптери порталів на career-ops.org/docs, щоб кожна функція відстежувалась до батьківського проекту. tests/llm-provider-context.test.mjs розширює матрицю меж отримання до всіх шести провайдерів (cv.md + profile.yml вбудовані + повернутий артефакт); нові ключі GITHUB_MODELS_* додані до всіх 12 словників локалей. Набір тестів 1125 → 1126.
[1.73.0] — 2026-06-17
feat(llm): універсальний конектор Gemini + верифікований контекст CV/профілю для всіх провайдерів. Додано server/lib/gemini.mjs (runGemini) — клієнт Gemini generateContent без зовнішніх залежностей, що повертає ту саму форму {markdown, usage, error}, що й клієнти, сумісні з Anthropic / OpenAI. Виправлення: /api/mode/:slug і /api/deep раніше спрямовували промпти через gemini-eval.mjs, призначений виключно для оцінювання вакансій, через що Gemini Run live повертав оцінку замість запитаного артефакту (супровідний лист, аутрич, бриф). Тепер вони викликають runGemini з bundleProjectContext, тому cv.md + config/profile.yml вбудовуються інлайн для Gemini так само, як і для всіх інших провайдерів — листи та брифи є детальними й персоналізованими. Новий tests/llm-provider-context.test.mjs імітує HTTP-межу кожного провайдера та перевіряє, що всі п’ять (Anthropic / Gemini / OpenAI / Qwen / OpenRouter) вбудовують cv.md + profile.yml інлайн і повертають артефакт (матриця mode + deep + evaluate, 9 випадків). /api/evaluate зберігає свій gemini-eval.mjs, налаштований під вакансії. Suite 1116 → 1125.
[1.72.0] — 2026-06-17
feat(modes): Run live тепер повертає підсумковий артефакт безпосередньо (контракт виведення за один запит). Батьківські шаблони modes/<slug>.md написані для інтерактивних сесій Claude Code — деякі з них (cover, contacto та інші) роблять паузу, щоб задати уточнювальні запитання перед формуванням результату, через що Run live у вебінтерфейсі видавав анкету замість артефакту. buildModePrompt тепер огортає кожен режим у неінтерактивний контракт виведення: виконує аналіз (розбір вакансії, нотатки про компанію, ключові слова ATS, розбіжності профілю↔вакансії, вибір тону/подачі) у фоновому режимі, обирає розумні значення за замовчуванням із cv.md / config/profile.yml для всього, про що шаблон зазвичай питав, і виводить лише підсумковий артефакт — із завершальним нагадуванням для кожного режиму «output ONLY {the cover letter / outreach message / …}». Тепер клік на Run live у #/cover повертає саме супровідний лист; те саме виправлення застосовується до всіх загальних режимів (cover, contacto, interview-prep, project, training, followup, patterns) у всіх 12 локалях (артефакт створюється мовою інтерфейсу через директиву локалі). Suite 1103 → 1116.
[1.71.2] — 2026-06-17
docs(i18n): публікує прохід з узгодженості документації. Блок “Translations of this guide” у кожному README тепер містить усі 11 мов-«сестер» (раніше деякі пропускали English/Français або мали посилання на самих себе), а порожній рядок перед роздільником розділу відновлено. Повний QA-запит регресійного тестування перейменовано на поточну версію, а документацію (CLAUDE.md, CONVENTIONS, LOCALIZATION, PROJECT-CONTEXT) синхронізовано з поточною версією та кількістю тестів (1103). Жодних змін у коді чи поведінці — лише документація, тому переклади довідки/UI та всі функції версій 1.70.0–1.71.1 залишаються без змін.
[1.71.1] — 2026-06-17
fix(i18n): вбудований посібник із довідки тепер повністю перекладено всіма 12 мовами. Додано docs/help/{pl,uk,ar}.md (кожен містить перевірену структуру 19 H2 / 75 H3), завдяки чому #/help надає нативний пакет польською, українською та арабською мовами замість переходу на англійську — GET /api/help/{pl,uk,ar} тепер повертають власну локаль. Підключено до всіх перевірок довідки (help-ui, help.test, help-ru-config-section, canonical-docs-coverage). Також завершено всі списки перекладів на 12 мов: блок «Translations of this guide» у README (9 файлів README), заголовки «Translations:» у локалізованих CHANGELOG (8 файлів) та оновлено застарілі лічильники документації. Suite 1100 → 1103.
[1.71.0] — 2026-06-16
feat(cover): генерація PDF супровідного листа безпосередньо з #/cover. Режим cover (доданий у v1.70.0) формує текст листа; тепер у результаті з’являється кнопка Generate PDF, яка рендерить його через спільний вбудований конвеєр markdown→PDF (POST /api/stream/pdf/inline → generate-pdf.mjs) — той самий шлях, що використовує interview-prep. Тепер можна створити лист і надіслати PDF, не залишаючи SPA.
test/docs: посилення перевірки v1.70.0. Додано CI-ізольоване покриття для режиму cover (список дозволів + збірка промпту), перемикача <select> прапорів + арабського RTL (dirFor/<html dir>), top.langLabel у кожній локалі, підключення PDF супровідного листа, а також директиви локалі prompts.mjs + скаффолдингу для fr/pl/uk/ar. Оновлено застарілі посилання «всі 8» → 12 локалей у docs/sdd/CONVENTIONS.md і в промпті регресійного QA всього проєкту.
[1.70.0] — 2026-06-16
feat(i18n): три нові мови інтерфейсу — польська (pl), українська (uk) та арабська (ar, з повною підтримкою RTL) — розширюють SPA до 12 локалей, що відповідає всім мовам README батьківського проекту career-ops. Кожна нова локаль постачається з повним словником із 697 ключів (public/js/lib/locales/i18n-dict.{pl,uk,ar}.js), перевіреним наявними наборами тестів паритету / покриття / відсутності витоків латиниці / відсутності персональних даних. Арабська додає справжню підтримку письма справа наліво: i18n.js встановлює <html dir="rtl"> для RTL-локалей, а блок [dir="rtl"] в app.css відображає хром (бічна панель, ящик сповіщень, таблиці та блок-цитати markdown, вбудовані відступи) — LTR-локалі залишаються незмінними побайтово. Новий ключ top.langLabel (×12) іменує перемикач для програм читання з екрана.
feat(ui): випадаючий список <select> із піктограмами прапорів замінює ряд кнопок, що переносилися. При 12 локалях старий ряд .lang-btn переносився на три рядки в бічній панелі; нативний <select> (кожен пункт з префіксом у вигляді емодзі прапора) масштабується акуратно, підтримує клавіатуру та програми читання з екрана без додаткових налаштувань, а також є безпечним для CSP (обробник зміни через addEventListener, без вбудованого JS). Там, де платформа не має гліфів прапорів, вони замінюються літерами регіону, тому мовна мітка завжди є основним ідентифікатором.
feat(cover): перенесення режиму супровідного листа з батьківського проекту (career-ops v1.10.0 + вітання v1.11.0) до SPA. Нова сторінка #/cover у групі навігації «Заявки», побудована на базовому виконавці режимів: опис вакансії + компанія/роль + необов’язкове вітання → персоналізований лист, згенерований із cv.md / modes/_profile.md. Додано cover до серверного MODE_ALLOWLIST та блок i18n cover.* (×12 локалей).
chore(compat): відстеження батьківського career-ops v1.11.0. Підтверджено, що контракт читання/запису залишається чинним — data/applications.md й надалі є джерелом істини у форматі markdown (SQLite-індекс трекера у v1.11.0 є похідним кешем), стовпці трекера й надалі зіставляються за заголовком. parentVersion тепер повідомляє 1.11.0.
fix(i18n): усунення прихованої прогалини, де французька мова (додана у v1.61.0) була відсутня в LOCALE_NAMES та SCAFFOLD_STRINGS у server/lib/prompts.mjs — LLM-виклики французькою мовою мовчки переходили на англійський вивід та англійські шаблони. fr/pl/uk/ar тепер усі підключені до шляху локалі підказок.
Відомі подальші дії: вбудований довідковий посібник (
docs/help/) повертається до англійської для pl/uk/ar (сам UI-хром повністю локалізований); інтерактивне введення для співбесід батьківського проекту, зворотне виявлення ATS та новіші постачальники сканування ще не представлені в SPA.