Журнал змін

GitHub

Кожна значуща зміна, реліз за релізом. Той самий файл постачається в репозиторії та перекладений усіма 19 мовами.

[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 (&eacute; → é, &ccedil; → ç, …), тож європейська дошка, що пише D&eacute;veloppeur чи Fran&ccedil;ais, більше не лишає цей літерал у заголовку, трекері чи згенерованому документі. (Великі лишаються великими — &Eacute; це É, а не é, — а пошук на кшталт &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, що несе https careers_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, …), через http vs https, або з кінцевим слешем / #фрагментом, вважається однією вакансією — без дубля в результатах скану чи пайплайні й без марної оцінки вже баченої вакансії. Справді різні вакансії (збережений функціональний 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). Новий ключ i18n rep.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 або неправильно розбирали &#1a2;). Тепер усі проходять через єдиний server/lib/html-entities.mjs (декодер, безпечний за XML 1.0 Char), прибрано ~237 рядків дублювання. 8 RSS-подібних джерел отримали декодування &nbsp; (раніше знали лише 5 сутностей); навмисне подвійне декодування cryptocurrencyjobs збережено через аліас. hh лишає свій декодер (обробляє &mdash;/&ndash;, поза спільними 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 падає, якщо будь-який із 17 site/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 (наприклад, &#99999999; з некоректного чи ворожого фіду) кидало неперехоплений RangeError і переривало весь розбір цього джерела. Спільний модуль server/lib/html-entities.mjs (дзеркало _html-entities.mjs батьківського проєкту) тепер обмежує числові посилання набором Char XML 1.0 §2.2, тож String.fromCodePoint не може кинути виняток, і зіставляє шістнадцяткові та десяткові окремо, тому &#1a2; більше не розбирається неправильно. Три джерела його імпортують.

Примітки

  • Для коректних фідів поведінка не змінюється; без змін 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).

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