Журнал змін

GitHub

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

[1.260.0] — 2026-10-26

Потяг зміцнення після ревізій: вісім злиттів (#491–#498) з раундів opus-5-5 та AI-review поверх флоту v1.259.0 — нормалізація зарплат, обмеження області дії виправлення дат звітів, стійкість геокодування, cache-busting бандла r2+r3, виправлення переповнення на #/deep — плюс два UI v2 (pipeline, reports). Кожне виправлення приїхало через червоно-зелений fix-forward.

Виправлено

  • Нормалізація порівняння зарплат (CAR-143 rev-2): маркери періоду розпізнаються лише поруч із числом, до якого вони належать — «6 month contract, $120k» більше не перетворюється на річну ×12, а форми періоду RU/ES, що визнаються лише при приєднанні до числа («за месяц» поруч із сумою), перемагають випадкові слова; родина символів $/¥ розпізнається, але не відображається на валюту, доки в тому ж рядку немає явного ISO-коду (долари AUD/CA/SG більше не зараховуються як USD, а форма ¥ RMB отримує явне застереження замість конвертації в JPY з помилкою ~20×); голі числа 19xx/20xx — завжди роки; числа діапазону успадковують період сусідньої межі; профільні minimum і target_range ніколи не зшиваються в один діапазон.
  • Обмеження області дії fixReportDate (CAR-144 rev-2): серверно закріплене виправлення дати застосовується лише до преамбули перед першим заголовком ## (звіт із заголовком-H2 отримує консервативний no-op — застаріла дата прийнятна, вигадана — ні); усередині преамбули: унікальний кандидат виправляється, інакше — унікальний жирний **Date:**, інакше — нічого (два жирні або два звичайні кандидати — нічого не перезаписується). Гейт PDF-міток FIX-5 тепер читає реальний складений словник — відсутня локаля кидає помилку замість мовчки проходити з англійським fallback.
  • Чип локації Habr + стійкість геокодування (CAR-142 rev-2): мета-чип Habr стає містом лише за структурних доказів (посилання на фільтр міста або іконка локації) — повідомлення про релокацію («Возможна релокация») і типи зайнятості ніколи не видають себе за локації; кеш геокодування тепер переживає рестарт (скидання на диск при beforeExit/exit/SIGTERM+SIGINT з повторним підняттям сигналу, брудний прапорець очищається лише після успішного запису), завершення роботи обмежене в часі (засувка exit-циклу, лічильник генерацій, LRU за свіжістю), а вичерпаний бюджет геокодування відповідає 503 + Retry-After замість загального 502.
  • Cache-busting бандла (CAR-145 r2+r3): оболонка посилається на /js/bundle.js?v=<content-hash> — immutable при збігу, no-store при розбіжності — чим убито 5-хвилинне вікно розгортання, у якому свіжа оболонка поєднувалася із закешованим старим бандлом. Хеш і потік читання спільно використовують один файловий дескриптор (без розірваних читань); коротке читання віддає null замість fallback-хешу зі stat, який міг отруїти ціле розгортання; make-bundle записує identity та компаньйони .br/.gz через temp із крапкою на початку + rename, тож убитий білд не може ані віддати напівзаписані байти, ані залишити свій тимчасовий файл.
  • Переповнення на #/deep (CAR-146): збережений рісьорч-бриф, у якого сама назва файлу перевищує в’юпорт телефона, більше не може витіснити #/deep за 390px — картка обмежується своїм рядком, назва переноситься, дата лишається в одному рядку; обхід вузьких в’юпортів тепер проходить #/deep з навмисно наддовгим підсіяним фіксчуром (єдина знахідка двох прогонів remote-QA — закрита).
  • Зміцнення pipeline v2 (ревізія CAR-116): видалення з підтвердженням та undo (з чесним текстом undo), картки більше не вигадують компанію з хоста борду вакансій, FNV-1a id рядків, справжні масові підрахунки та охорона від повторного входу для масових операцій.

Додано

  • Pipeline v2 (CAR-116): збагачені картки, групований вид і масове відхилення з undo; 17 нових ключів pipe.* постачилися одразу перекладеними в усі 19 словників.
  • Reports v2 (CAR-118): живий пошук із фільтрами за смугою оцінки / легітимністю / компанією / датою, сортувальні заголовки (aria-sort), персистентний картковий вид з кнопками дій за смугою, гістограма оцінок із фільтрацією кліком та уніфікований рендеринг «Company — Role» / «x.x/5» (window.ReportOps); 31 ключ rep2.* постачилися одразу перекладеними в усі 19 словників.

Примітки

  • Кожне виправлення пройшло раунд ревізії з червоно-зеленими доказами; дві HIGH-знахідки AI-review на першому r2 бандла (хеш при короткому читанні, атомарність компаньйонів) закриті r3, а раунди CAR-143/CAR-144/CAR-142 кожен повторно перевірив відкриті знахідки свого попередника.
  • Навмисно не зроблено: кластерний вид мапи лишається CAR-125 (у черзі); серверне відновлення метаданих pipeline — окремий тікет; без ретроактивних виправлень для контенту, збереженого в станах v1.259.0 до rev-2.
  • Тести 5988 (+165); браузерний набір — 152 пройшли / 0 провалено / 1 опційний пропуск. Джерел сканера не торкнуто (реєстр 121 = 116 EN + 5 RU · 116 адаптерів); 48 нових i18n-ключів приїхали зі своїм ×19 розкладанням всередині самих фіче-PR.

[1.259.0] — 2026-10-25

Другий потяг агентного флоту, шість виправлень: дати звітів закріплюються сервером, геокодер мапи кешує результати й читає RU-міста, довідка ×18 цитує локалізовані підписи кнопок, речення інтерфейсу перестають змішувати мови, порівняння зарплат рахуються на сервері — а JS-бандл постачається заздалегідь стиснутим (холодне завантаження 3.1 MB → 598 KB).

Виправлено

  • Вигадані дати звітів (CAR-101): промпт оцінювання тепер закріплює сьогоднішню дату серверно («поле Date: МАЄ дорівнювати рівно — скопіюй дослівно; ніколи не вигадуй»), а fixReportDate перезаписує збиту дату на сьогодні в кожному місці приймання звіту (гілка Anthropic, сабпроцес Gemini, OpenAI-сумісний хвіст, /api/deep, auto-pipeline) із попередженням + записом у журнал активності — це виправлення, а не відхилення. 349/789 звітів на проді мали вигадані дати; нові — вже не можуть.
  • Геокодування мапи (CAR-105): кириличні назви міст у рядках hh.ru/Habr тепер доходять до геокодера; персистентний обмежений кеш (data/geocode-cache.json, під withFileLock) означає, що вже розпізнане місто більше ніколи не б’є по провайдеру; виклики геокодування мають бюджет на запит, тож мапа заповнюється поступово замість блокування на 13.9 с.
  • Змішування мов в інтерфейсі (CAR-108): виправлено 23 витоки за ключами в 17 із 19 словників (85 значень) — «все matching», «только remote», «Открыть Scan», воронка «Evaluated → Applied…» та інше стали повноцінними локалізованими реченнями, що повторно використовують власний продуктовий словник кожної локалі. i18n-no-latin-leaks розширено таблицею фрагментів + пробою воронки (red→green).
  • Перевертання вердикту через валюту (CAR-109): порівняння зарплат обчислюється на сервері в comp-compare.mjs і вбудовується в канонічній формі (Candidate minimum: … per YEAR (raw: …) vs JD offer: …); місячні суми ×12, форми k/к/тыс/萬 і десяткова кома нормалізуються перед порівнянням; невпевнена конвертація сама по собі більше ніколи не переверне вердикт (кейс zh-TW «€80–100k ниже минимума» мертвий). Модель ніколи не множить.
  • Підписи кнопок у довідці ×18 (CAR-107): локалізовані довідкові пакунки цитували англійські підписи («⚡ Run live», «Save», «Scan now»), яких інтерфейс більше не показує — 263 заміни по 18 локалях, зіставлені з вихідними кодами в’ю; docs-assistant успадковує виправлення.

Додано

  • Заздалегідь стиснутий бандл + кешоване холодне завантаження (CAR-104): make-bundle.mjs у момент релізу створює bundle.js.br (brotli q11) + .gz; окремий middleware бандла домовляється br > gz із Vary: Accept-Encoding, public, max-age=300, must-revalidate + ETag/304; під гейтом свіжості (компаньйон-артефакт ніколи не віддається старішим за identity-бандл), прямі запити артефактів віддають 404, CSP байт-ідентичний. Холодне завантаження: 3.1 MB → ~0.6 MB по мережі (−80%); повторні візити протягом 5 хв передають ≈0 байтів.

Примітки

  • Усі виправлення прийшли з агентного флоту з червоно-зеленими доказами (13 + 28 + 1 + 30 + 7 нових кейсів у своїх сьютах) та пройшли раунди ревізій (CodeQL #579/#580/#584 виправлені в коді — тестові URL-заглушки парсяться через new URL().host, мертвий рядок промпту викинуто).
  • Числа дозаповнення оцінок v1.258.0 тепер записані в CAR-129 (updated: 22, reportScoreless: 159).
  • Навмисно не зроблено: без ретроактивного виправлення дат для 349 історичних звітів (дозаповнення було б ops-операцією зі згоди власника), без кластерного виду мапи (CAR-125 лишається в черзі за цим виправленням геокодування).
  • Тести 5823 (+74). Джерел сканера не торкнуто (реєстр 121 = 116 EN + 5 RU · 116 адаптерів); жодних i18n-ключів (CAR-108 змінює лише значення наявних ключів).

[1.258.0] — 2026-10-25

Шість виправлень агентного флоту одним потягом: лічильник компаній на #/scan перестає змішувати всесвіти, глибокий рісьорч вимагає джерел, помилки форм підсвічують поле, стани завантаження отримують скелетони та миттєвий h1, а оцінювання кешують свій 37k-токенний префікс промпту.

Виправлено

  • Лічильник «Активні компанії» на #/scan (CAR-100): чисельник рахував різні компанії по ВСІХ рядах результатів (1554 на проді — сам hh.ru дає сотні), тоді як знаменник рахував API-підкріплені відстежувані компанії (~90) — два всесвити в одному дробі. Обране парування (a): чисельник тепер рахує компанії серед рядків, які отримав сам API-підкріплений відстежуваний скан (збіг через _entry ?? company; рядки роботодавців hh.ru/telegram відпадають); пре-скановий fallback переїхав у той самий всесвіт (раніше він міг читатися як «104/90»).
  • Вигадування фактів у глибокому рісьорчі (CAR-110): глибокий промпт тепер несе жорсткий контракт — кожне фактичне твердження закінчується маркером [source: domain.com] (не можеш назвати джерело → не стверджуй; вигаданий маркер гірший за чесне unknown), невідомі факти пишуться як unknown, а звіт ОБОВ’ЯЗКОВО закінчується секцією ## Data freshness. Якорі свіжості: сьогоднішня дата за UTC + опційний url компанії на POST /api/deep (обмежений, сервером ніколи не отримується).
  • Помилки форм підсвічують поле (CAR-130): невдалий POST позначає перше поле форми aria-invalid + .is-error і фокусує його (WCAG 3.3.1); знімається при редагуванні. Підказка про мережеву помилку враховує розгортання — на loopback лишається підказка з dev-командою, а розгорнутий origin отримує «сервер не відповідає» замість поради bash web-ui/bin/start.md (банер з’єднання ніс той самий хардкод).
  • Стани завантаження (CAR-112): #/map і #/tracker рендерять свій h1 миттєво (ціль фокуса роутера існувала лише після прибуття даних), а спільний хелпер скелетонів (Skeleton.rows, aria-hidden мерехтливі рядки всередині регіону role="status", який повторно використовує common.loading) замінює голий текст «Завантаження…» на трекері, результатах сканування та оболонці мапи; prefers-reduced-motion зупиняє пульсацію.
  • Кешування промпту оцінювання (CAR-111): buildEvaluationPromptParts розрізає промпт на ~37k токенів на байт-стабільний префікс (CV + профіль + файли режимів + інструкції) та хвіст JD; шлях Anthropic надсилає [prefix + cache_control: ephemeral] + [JD] — префікс тарифікується за ставкою читання з кешу (≈10× дешевше) на кожному виклику після першого. Відсутній чи незбіжний префікс — безпечний відкат до байт-ідентичного старого запиту; OpenAI-сумісний каскад недоторканий (там кешування серверне).

Примітки

  • Усі шість виправлень прийшли з агентного флоту з червоно-зеленими доказами (5 + 7 + 7 + 7 + 8 нових кейсів) та раундами ревізій; перші алерти CodeQL на CAR-111 (#579/#580) виправлені в коді — тестова заглушка парсить хост URL замість пошуку підрядка, а мертвий рядок повного промпту викинуто.
  • Дозаповнення оцінок прогнано на проді (ендпоінт v1.256.0, зі згоди власника): updated: 22, missingReport: 0, reportScoreless: 159 — 159 рядків без оцінки вказують на звіти, чиї заголовки передують машинному блоку або несуть формати оцінки, які не розпарсити; відновлювані лише повторним оцінюванням або ширшим проходом парсера (відмічено в CAR-129).
  • Навмисно не зроблено: без зміни nulls-last для commute-колонки (передіснуюча примха Infinity при спадному сортуванні), без ретроактивного перерахунку цін старих записів usage, без підсвічування в стилі .is-error для форм фільтрів сканування (CAR-130 обмежений спільним хелпером сабміту mode-page + підказкою в api.js).
  • Тести 5749 (+36). Джерел сканера не торкнуто (реєстр 121 = 116 EN + 5 RU · 116 адаптерів); жодних i18n-ключів (підказка CAR-130 повторно використовує conn.down; регіон скелетона — common.loading).

[1.257.0] — 2026-10-25

Два виправлення агентного флоту: вбудований помічник довідки розуміє деванагарі та тамільську, а сторінка #/usage показує фактичну вартість Hermes.

Виправлено

  • Пошук у помічнику довідки для hi/ta (CAR-103): клас символів слова в keyword-токенізаторі docs-assistant був [\p{L}\p{N}\u30fc]+ — без комбінованих знаків. Кожна матра деванагарі (ा ै ्…) і тамільський знак голосної (ே ை ்…) розрізали слово на фрагменти; правило довжини відкидало короткі фрагменти (запит хінді знаходив 0 секцій), а довші слова розрубувалися в сміття, що збігалося з нерелевантними секціями. Тепер клас тримає \p{M}; шлях CJK-біграм недоторканий. hi «मैं स्कैन कैसे चलाऊं?» і ta «ஸ்கேன் எப்படி இயக்குவது?» знаходять правильні секції в поставлених бандлах.
  • Вартість usage для Hermes (CAR-106): normalizeUsage() згортав usage провайдера до {in, out} і мовчки втрачав передану шлюзом cost — #/usage показував $0.0000 за 9.5 M токенів Hermes (рядок у таблиці цін — 0/0). Форма запису usage отримує опційний назад-сумісний cost?; агрегатор віддає перевагу переданій провайдером вартості на кожен запис і звертається до оцінки з таблиці цін, лише коли її немає; сміттєві/негативні значення відкидаються, а не вигадуються.

Примітки

  • Обидва виправлення прийшли з агентного флоту з червоно-зеленими доказами (3 + 5 нових кейсів; 14/14 і 10/10 зелені; сусідні 78/78 та 61/61). Перші PR (#470/#471) несли застарілу базу worktree, що конфліктувала зі squash-злитим v1.256.0 — замінені чистими cherry-pick’ами (#472/#473).
  • Навмисно не зроблено: без ретроактивного переоцінювання історичних рядків data/llm-usage.jsonl (у них немає поля cost; вигоду отримують лише нові записи).
  • Тести 5713 (+8: docs-assistant ×3, usage ×5). Джерел сканера не торкнуто (реєстр 121 = 116 EN + 5 RU · 116 адаптерів); жодних i18n-ключів.

[1.256.0] — 2026-10-25

Якість даних трекера: роль більше ніколи не записується як компанія, борд вакансій ніколи не записується як роботодавець, рядки без оцінки перестають спливати першими — а одноразовий ендпоінт дозаповнює відсутні оцінки з самих звітів.

Додано

  • Дозаповнення оцінок (CAR-129): POST /api/tracker/backfill-scores заповнює клітинку Score у рядках, де пов’язаний звіт несе оцінку, яку вдається розпарсити (181 оцінений рядок мав звіт, але не мав оцінки). Оцінені рядки, явні сентинели (N/A), рядки без звіту та звіти без оцінки ніколи не чіпаються; перезаписується лише клітинка Score, під файловим блокуванням; форма оцінки валідується в місці запису та нормалізується до конвенції трекера X.X/5; прогін ідемпотентний і повертає {ok, updated, missingReport, reportScoreless}.

Виправлено

  • Роль ніколи не компанія (CAR-128): guessCompanyRole вважав ЛІВУ сторону рядка-заголовка з тире компанією, але борди пишуть в обидвох порядках — «Senior Frontend Engineer - Grafana Labs» записував роль ЯК компанію (13 рядків: Grafana, CircleCI…). Тепер евристика рольових ключових слів зі словесними межами обирає сторону ролі, а тип зайнятості ніколи не стає роботодавцем («Senior Engineer - Remote (EU)» не записує нічого, замість «Remote (EU)»).
  • Борд вакансій ніколи не роботодавець (CAR-127): fallback за доменним слагом записував рядки hh.ru як «hh» (120 рядків). Слаги hh/habr долучаються до списку не-роботодавців; висновування роботодавця з середини речення («в компании X», «X ищет») тепер працює і для хостів бордів вакансій (hh.ru/kz/uz/by, habr.com) — там, де текст сторінки називає справжнього роботодавця. Дорогою знайдено першопричину: ASCII \b/\w у цих патернах взагалі ніколи не збігалися з кирилицею — мовчки мертві з v1.248.4; їх замінюють Unicode-lookbehind’и та класи \p{L}.
  • Рядки без оцінки сортуються останніми (CAR-129): сортування трекера за оцінкою використовувало сентинел -1, тож за типовим висхідним сортуванням кожен рядок без оцінки спливав перед кожним оціненим. Тепер null-и сортуються останніми в обидва боки (фільтр за оцінкою зберігає свій -1 — «без оцінки не проходить фільтр ≥4» — коректна семантика саме там).

Примітки

  • Історичні рядки цей реліз НЕ мігрує: виправлення парсерів стосуються нових оцінювань; оцінки відновлювані через backfill-ендпоінт, тоді як 120 клітинок роботодавця «hh» потребують повторного оцінювання, а не перезапису.
  • Раунд ревізії opus-5-5 включено: незаякорений патерн «X ищет» викинуто ще до випуску (будь-яке слово з великої літери перед дієсловом записалося б як роботодавець — інваріант no-fabrication), додано якорі «В…» на початку речення, а записувач backfill повторно перевіряє цільову клітинку (дублікати рядків за # з’являються після ручних редагувань), зберігає кількість колонок короткого рядка, пошкодженого вручну, та зупиняється на першому рядку поза таблицею. Крапка всередині назви з крапкою («Сбер. IT») невиразна від крапки речення для якоря «в компании» — записується консервативний префікс («Сбер»); патерн із дієслівним якорем ловить повну форму.
  • Навмисно не зроблено: компаратор не винесено з tracker.js у бібліотеку, придатну для юніт-тестів (source-static гейт залишається; відмічено в CAR-129 як follow-up), без зміни nulls-last для commute-колонки (передіснуюча примха Infinity-on-desc), без міграції даних для рядків «hh».
  • Тести 5705 (+10: 5 identity-спек у auto-pipeline-jd-gate, 2 CAR-128, гейт сортування CAR-129, 4 backfill, включно з прив’язуванням ворожого шляху — мінус замінені кейси v1.248.x). Джерел сканера не торкнуто (реєстр 121 = 116 EN + 5 RU · 116 адаптерів); жодних i18n-ключів.

[1.255.2] — 2026-10-24

Три видимі користувачеві виправлення з live-регресії та звіту про збій на проді: збій рендеру #/scan, сліпа мовна перевірка в’єтнамської та повторювана втрата блоку summary zh-TW.

Виправлено

  • Збій #/scan (CAR-99): перегенерація count-гейтів від v1.253.0 писала літерал FALLBACK_SOURCES із подвійною комою в кінці кожного елемента — 120 дірок (пропущених елементів) у масиві. new Set(FALLBACK_SOURCES.map((s) => s.value)) у scan-в’ю тримав тоді undefined, а id.length класифікатора компаній кидав TypeError: Cannot read properties of undefined (reading 'length') на кожному рендері сторінки (спіймав logbuf на проді). Дірки видалено, а новий гейт (4 спеки, перевірений на червоному) виконує файл і валить будь-яку дірку, непридатний елемент чи undefined у Set ідентифікаторів адаптерів — текстовий гейт дрейфу цього класу не бачив.
  • Сліпа мовна перевірка vi (CAR-97): в’єтнамська — латиниця й не мала запису в таблиці стоп-слів live-QA, тож «vi стоп-слова» рахували англійські влучання, а в’єтнамська відповідь читалася як report not in vi (live-регресія: vi 2, en 2). У в’єтнамської тепер власна десятислівна таблиця службових слів — чисто в’єтнамський звіт проходить, англійський досі падає.
  • zh-TW втрачав блок SCORE_SUMMARY (CAR-98): закривний мовний reminder казав лише, що машинний формат не можна перекладати, і ніколи — що блок summary має існувати — короткий звіт zh-TW (5983 символи) ніс блоки A–G, але повністю пропускав цей блок (рецидив класу v1.239.2). Відкривна директива та закривний reminder тепер кажуть, що блок обов’язковий, а без нього звіт недійсний.

Примітки

  • Цей реліз навмисно не змінює жодного джерела сканера, лічильника реєстру (121 = 116 EN + 5 RU · 116 адаптерів) чи проводки провайдерів — це суто виправний реліз, тож count-гейти недоторкані.
  • Тести 5695 (+9: car99-fallback-holes ×4, car97-vi-lang-check ×4, інваріант промпту CAR-98 ×1). Браузер 143/143. Повторна live-перевірка vi/zh-TW їде з пост-деплойною регресією.

[1.255.1] — 2026-10-24

Два раунди виправлень за ревізіями: ревізія Opus в’єтнамської локалі (9 знахідок) і другий прохід ревізії виправлень CAR-91/92 (5 must-fix) — обидва закриті з червоно-зеленими тестами.

Виправлено

  • Раунд ревізії vi-локалі (наступний за CAR-88): гліф хангиль (신), що затік у project.subtitle / project.roleHint, виправлено (độ khớp tín hiệu), із гейтом, який валить будь-який CJK/кана-гліф у значенні vi, якого немає в словнику EN; шість розбитих пробілами маркдаун-URL у docs/help/vi.md (what-is career-ops, apply for a job, set up playwright і ще три) виправлено на дефісові справжні слаги гайда, із гейтом «без пробілів у URL».
  • Межі блокових заголовків оцінювання (CAR-92 r2 H1): &, ., /, ' тепер словесні символи лівої межі блокової літери — ## Q&A, ## Plan B, ## Ph.D. requirement, ## E-commerce більше не реєструються як блоки A/B/D/E; голий терминатор кінця рядка приймається лише після блокового слова-токена (Block/блок/블록/…); ASCII - прибрано з класів словесних терминаторів, тож заголовки E-commerce/ई-मेल виживають.
  • Звіти з українською/російською нумерацією (CAR-92 r2 H2): детектор послідовності А Б В Г Д Є Ж став спільним хелпером; коли він знаходить 3+ літери послідовності на рівні заголовків, цей рівень стає референсним для різака — літерні підрозділи ### A. більше не обрізають звіт на блоці Б.
  • Життєвий цикл перенесених рядків (CAR-92 r2 M1): рядки, які джерело, що відмовило, переносить у наступний скан, отримують штамп carriedSince, відпадають після 30 днів або 5 переносів підряд і додаються після свіжих рядків — замість того, щоб мовчки накопичуватися вічно.
  • Знаменник і легітимність gemini-synthesis (CAR-92 r2 M2) та шлях body-score сабпроцесу (M3): прочитано з файлу знахідок, виправлено з червоними тестами.
  • Гейт дрейфу бандла холодного завантаження оновлено під словник vi (90 script-тегів / 87 бандлозбірних), а його невикористане тестове прив’язування видалено (CodeQL 572).

Примітки

  • Формулювання лічильника адаптерів у банері «Latest» README.vi, яке позначила ревізія, живе в банері Previous і вже дзеркалить EN README байт у байт — залишили недоторканим заради паритету EN↔vi (задокументовано в тілі коміту).
  • Тести: 5686 (+26: eval-validate-opus-r2, scanner-carryover-r2, твердження vi-гейтів). Реєстр без змін: 121 джерел (116 EN + 5 RU) · 116 адаптерів.

[1.255.0] — 2026-10-24

В’єтнамська (Tiếng Việt) стає 19-ю мовою інтерфейсу — словник на 1525 ключів із повним паритетом до EN, розпізнавання vi/vi-VN, повний довідковий пакунок, дзеркала README та CHANGELOG, інтеграція в лендінг cvstart.org і 🇻🇳 у перемикачі мови.

Додано

  • В’єтнамська (vi) — 1525/1525 ключів із повним паритетом до en; detect() приймає vi та vi-VN; в’єтнамська — LTR (до RTL_LANGS не додається). ‘Pipeline’ залишається ‘Pipeline’ — стандартне технічне запозичення, у білому списку.
  • Повний docs/help/vi.md (33 H2 / 125 H3), плюс дзеркала README.vi.md і CHANGELOG.vi.md та в’єтнамські рядки-скафолди в prompts.mjs (LOCALE_NAMES + SCAFFOLD_STRINGS) — вивід LLM в’єтнамською працює з коробки.
  • Лендінг cvstart.org в’єтнамською — site/src/i18n/vi.json, запис локалі в sitemap і скриншот dashboard-vi.png; перемикач мови отримує 🇻🇳 (19 позицій).
  • Перелічувальні гейти 18 → 19 — LANGS, проби detect, i18n-аудит, паритет changelog, блок перекладів README, locale-sweep — плюс red-first tests/i18n-vi-locale.test.mjs (139 рядків).

Примітки

  • Лендінг cvstart.org отримує vi — навмисне відхилення від прецеденту ta (той реліз тримав лендінг на 17 спеціально); задокументовано в спесі релізу.
  • vi — латиниця, тож гейт no-latin-leaks не застосовується (він сторожить нелатинські локалі); латинська технічна лексика тут норма.
  • Тестова підлога: 5634 → 5642 (1 пропуск — опціональний браузерний capture-тест розкладки, без змін).

[1.254.0] — 2026-10-24

Реліз зміцнення з раунду ревізії максимальною моделлю: снапшот сканера більше не втрачає рядки, одинадцать інваріантних порушень LLM-шару виправлено, двадцять три SPA-must-fix ввійшли, і Paylocity ATS долучається до сканера — реєстр тепер поставляє 121 джерел (115 EN + 5 RU) зі 116 адаптерами.

Додано

  • Paylocity ATS (батько #5687): тенантні борди recruiting.paylocity.com, мертвий борд 302 → JobNotFound відображається в 404, щоб карантин виводив запис, кап max_jobs з прапорцем усічення. Реєстр: 121 джерел = 116 EN + 5 RU · 116 адаптерів; гейти лічильників перегенеровано.

Виправлено

  • Безпека снапшоту сканера (R-12): скан, що нічого не пробує, тепер ЗБЕРІГАЄ снапшот (nothing scanned); full-replace переносить старі рядки джерел, що відмовили, — зі штампом по ентрі — з повторним прогоном через ПОТОЧНІ фільтри, приземляючись лише в filtered.
  • Одинадцять інваріантів LLM-шару: мовна директива відкриває І закриває промпти deep/mode; Gemini-subprocess пересилає --lang, не-нульовий вихід → 502, не пускає stderr клієнту, тримає 300 с і пише usage; manual-fallback несе URL вакансії; truncation і warnings бандла спливають у /api/mode; spend tier більше не обходиться клієнтом; дисконнект клієнта абортить виклик провайдера в польоті; різак повторних заголовків більше не знищує валідні звіти; екстрактори score/legitimacy узгоджені з валідатором; українська послідовність та арабська ва tightened.
  • Раунд ревізії Opus 5-5 НА самих виправленнях (8 must-fix): мультибуквена реєстрація різака, ширші терминатори заголовків, abort-проводка Gemini-subprocess, штампована атрибуція переносу, перенесені рядки повторно проходять поточні фільтри, рівень посилальних заголовків за числом різних букв.
  • Двадцять три SPA-must-fix у sourcing-в’ю (append однопейджного пейджера, клавіатура рядків pipeline, відновлення фільтрів saved-search, скиди select source, відкат дефолту в config, течі листенерів, close SSE на терміналі, teardown scan-раннера, голий fetch, провальні-завантаження-виглядають-як-пусті, колізія в window, збереження фокусу) та assist-в’ю (мертвий help deep-link, течі help, повторне озброєння blank-overwrite у two-pager, single-flight платних прогонів batch/deep/mode, тихі кінці стрімів, вічні спінери на помилках, привид рядків провалених фільтрів, дублі docs-fab + мова).

Примітки

  • Весь раунд пройшов ревізію Opus 5-5 на максимальній строгості — порти до мержу (21 знахідка, 13 виправлено) і самі фікси постфактум (8 must-fix, усі ввійшли тут з червоно-зеленими тестами).
  • Тестова підлога: 5262 → 5634 (372 нові специфікації по джерелах, сканерах, LLM-шару та SPA).

[1.253.1] — 2026-10-24

Дві HIGH-знахідки безпеки з ревізії максимальною моделлю виправлено: IPv6-літерали більше не обходять SSRF-гард сканера, а escape-regex CV-маркдауну більше не бектрекає квадратично.

Виправлено

  • IPv6-літерали обходили SSRF-гард (http-json.mjs): дужковий IPv6-літерал валив dns.lookup винятком, і гард fail-open пропускав. Тепер дужки знімаються, літерал класифікується через isIP — без DNS і без fail-open.
  • ReDoS в escape-regex CV-маркдауну (security.mjs): патерн бектрекав квадратично — < + ~100k пробілів коштували ~16 с; тепер ~1 мс на плоскому обмеженому класі. Все, що екранував старий, екранує й новий.

Примітки

  • Обидва фікси несуть червоно-зелені регресійні тести (3 нові IPv6-кейси + ReDoS з часовою межею; 5511 тестів, +7).
  • Файли джерел сканера не зачеплені — гард живе в спільному транспорті.

[1.253.0] — 2026-10-24

Десять нових джерел долучаються до сканера — Atlassian, BWI, Computrabajo, Dover, Google, Kalibrr, Magneto365, Platsbanken, Tech in Asia та Zoho Recruit — реєстр тепер поставляє 120 джерел (115 EN + 5 RU) зі 115 адаптерами, а роль-матчер розділяє заголовки, що різняться лише коротким капсом-кодом.

Додано

  • 10 джерел портовано з батька 1.38.0, кожне зі своїм fetch-адаптером і CI-ізольованим набором (фейковий fetchImpl, нуль мережі): Atlassian (єдний auth-free JSON-фід, iCIMS-URL, allowlist https+icims.com), BWI (ItemList JSON-LD, JSON.parse перший), Computrabajo (LATAM, декод ентіть, іспанське «remoto» = remote), Dover (DRF-пагінація по тенанту, рендер Remote (International), UUID-валідація борд), Google Careers (перші сторінки за фразами, роботодавець картки виграє, дедуп між запитами), Kalibrr (SEA-агрегатор, фід-id як id рядка), Magneto365 (LATAM, фіксований міжсторінковий темп), Platsbanken (шведський JobTech API, вікна за ключовими словами), Tech in Asia (Algolia-ключі з білда сайту), Zoho Recruit (hidden-input борд, обмежений detail-enrichment, валюта лише ISO-4217).
  • Лічильники реєстру: 120 джерел = 115 EN + 5 RU · 115 адаптерів; п’ять гейтів лічильників перегенеровано з живого реєстру; фолбэк дропдауна #/scan, лінки cvstart.org і help §17 ×18 слідують.

Виправлено

  • Роль-матчер (#5688 порт): заголовки, що різняться лише коротким капсом-кодом («HR BP» vs «HR GP», «AWS» vs «GCP», навіть у змішаних скриптах), більше не склеюються фазз-матчем в одну вакансію; коди читаються з сирого заголовка з виключенням noise-слів, гард стоїть просто перед коефіцієнтом Жаккара.
  • Порти пережили ревізію Opus 5-5 максимальної строгості (21 знахідка): throw на тихо-пустих бордах при дрейфі розмітки (google/bwi), преабортований скан кидає замість повернення [], порядок decode-then-strip на LATAM-скреперах (XSS-форма strip-then-decode), https+allowlist на URL рядків atlassian, host-ассерти з відмовою портів, парс пізніх сторінок всередині try (партиали виживають), і чесність тестів (бюджети пораховані точно, контракти коерсції законтрактовані правдиво). 13 знахідок виправлено; [spec]-хвіст відстежується окремо.

Примітки

  • П’ять RU залишаються web-ui-only (апстрим-адаптерів немає — навмисно); кожне EN-джерело має свій адаптер.
  • Coverage-рейтча: шість найбільших портів піднято до ≥95 % branch (27 нових кейсів); оборонний мертвий код залишку задокументовано пофайлово.
  • Нові джерела вибираються через provider: <slug> у portals.yml — для наявних конфігурацій нічого не змінюється.

[1.252.1] — 2026-10-17

Банер релізу в wiki тепер перевіряється скриптом, а не ручним свіпом — регрес v1.252.0 спіймав протухлий банер лише руками — і локальна директива явно перевизначає language.output профілю та мову вакансії.

Додано

  • scripts/wiki-banner-check.mjs — гейт, якого вимагав регрес: читає кожен Home*.md у клоні wiki та звіряє банер з package.json і з ЖИВИМИ лічильниками реєстру (scanner adapters з портального реєстру — 105, а не протухлі 109 з банера; route modules з server/lib/routes/; UI locales з набору CHANGELOG). Regex банера мовно-незалежний за ФОРМОЮ: локалізовані Home-сторінки перекладають і мітку, і лічильники («Текущий релиз», «Aktuel udgivelse», «адаптеров сканера», «enhedstests»), і їх числа поїхали далі (5177), ніж EN-сторінка (5245). --tests N приймає число, яке щойно надрукував свіжий test:ci; --check-only забороняє неявний клон для офлайну. Фікстурний офлайн-тест — tests/wiki-banner-check.test.mjs.

Виправлено

  • Банер wiki Home ×18 повернуто в синхрон (виходить разом з цим релізом): свіп v1.252.0 змінював бейджі й посилання, але ніколи рядок банера, і ніщо механічне на це не вказувало. Гейт вище робить це релізоблокувальною перевіркою.
  • Локальна директива фіксує пріоритет мови (buildLocaleDirective): «This UI locale overrides the profile’s language.output setting and the job posting’s own language». Власне правило 6 батька велить писати в language.output — без фрази про пріоритет модель розв’язувала два суперечні вказівки непослідовно по локалях, що й був розкид мовної колонки v1.252.0. Закріплено tests/locale-directive-language-output.test.mjs на тексті директиви (17 не-EN локалей + порожній EN).

Примітки

  • Спостереження за ФОРМОЮ ta evaluate (§3, стежити): перший живий прогін v1.252.0 дав evaluate на 48 336 символів без Block G і SCORE_SUMMARY — схоже на зациклення, а не на мову (CAR-70 — про мову). Повториться на наступному релізі — окрема Linear-задача, не CAR-70.
  • За FIX-промптом: батько лише для читання; перевизначення цілком живе в директиві UI.

[1.252.0] — 2026-10-17

Батьківський форк синхронізовано з career-ops-hq/career-ops — 264 апстрим-коміти, батько 1.36.0 → 1.37.0 — з захистом обох форк-дивергенцій, а деплой переустановлює робочого батька на цю базу.

Додано

  • В’єтнамський (vi) ринковий режим з апстриму (modes/vi/, tuyen-dung / ung-tuyen): BHXH/BHYT/BHTN, thử việc, lương tháng 13, trợ cấp thôi việc — 21-й ринок у таблиці режимів батька.
  • Провайдер борди Tech in Asia (апстрим): читає публічний Algolia-індекс job_postings, який сам запитує UI вакансій techinasia.com — SEA-борда стартапів, ~300 живих вакансій на замірі, 273 з них індонезійські. Ключі виявляються заново на кожному прогоні з білда сайту, власне посилання роботодавця перемагає сторінку борди, а зарплата свідомо не додається (поля зарплат в індексі — прапорці, а не гроші).
  • Вісім нових документів сигналів легітимності Block G (modes/oferta-signals/08–17): країна в термінології пільг, тег локації сторонньої платформи, ліцензування агентств, надмір імміграційних вимог, заборонений контент юрисдикції, ширина вилки pay-transparency, питання юристу про мінімалку, розкриття AI-скринінгу, розкриття строкового контракту, купівельна спроможність при релокації.

Змінено

  • Зміцнення на боці батька, які їдуть з базою: перевірка story-provenance працює за одиницями та реченнями і тримає цифри user-cannot-confirm поза переговорами, ATS та interview-виводом; fact-gate CV запускається на кожному рендерері і читає переформульовані та структуровані заяви; LaTeX відкидає протухлі витягнуті слоти до запису; launchd-обхід follow-ups пропускається, коли follow-ups вимкнені; update-system утримує скан локальних правок у межах argv.
  • Дивергенції форка пережили мерж: providers/telegram.mjs (форк-єдиний парсер, ADR-0001) та Unicode-lookup LOCATIONISH_RE у telegram-channel.mjs (ADR-0002). Документ борд тепер перелічує покращений рядок telegram-channel апстриму, форк-єдиний рядок telegram, Tech in Asia і дефолт max_pages у Tencent, піднятий 20 → 100.

Примітки

  • Змін поведінки SPA в цьому релізі немає: це док-поїзд плюс перевстановлення батька. Деплой тепер працює на батькові 1.37.0; таблицю канонічних статусів дельта батька не торкається — статуси трекера рендеряться як раніше.
  • У мержі апстриму розв’язано два конфлікти: docs/SUPPORTED_JOB_BOARDS.md (обидва телеграм-рядки збережено) та test-all.mjs (walkTree-версія copyDirSync апстриму субсумує форковий ручний guard вкладених чекаутів за .git; список імпорту — об’єднання).

[1.251.1] — 2026-10-12

Фільтр спеціалізацій тепер матчить лише ВІДМІННІ слова ролі — стаж і загальні посадові іменники більше не матчать вакансію самі по собі — а remote-qa розділяє форму та мову, щоб дрейф не червонив A–G.

Виправлено

  • Фільтр спеціалізацій викидає стаж і загальні посадові іменники зі свого матчера: «Senior PHP Developer» матчив «Senior Java Developer» через спільний Senior/Developer. Матчер залишає лише відмінні токени (PHP, Go, Platform, SRE, Backend…); роль без відмінних токенів («Software Engineer») падає на матч усієї фрази. Межі слів на місці: «Go» ніколи не матчить «Golang» і «Google».
  • remote-qa розділяє форму та мову: прапор A–G судять лише за кодами блоків/SCORE_SUMMARY/looping; дрейф report not in <locale> іде у власний прапор і колонку warnings (таблиця отримує бракуючий 4-й стовпець; коди зберігаються построчно).
  • Тулбар карти — чесні 50/50 на 390 px (флекс 58/42 залишав пошуку 124 px і обрізав плейсхолдер), мертве CSS-правило .job-map-job-select видалено.
  • CodeQL 552 — невикористану змінну OVERLAYS у design-qa тесті видалено.

Примітки

  • Милі все ще відкладає власник. Мовний дрейф ta залишається ЗАДОКУМЕНТОВАНИМ обмеженням бекенду hermes (2–26 % прози між прогонами; A–G тримається): remote-qa повідомляє його як очікувану знахідку. Список загальних токенів навмисно мінімальний — розширити — рядок на мову.

[1.251.0] — 2026-10-12

Селект на карті тепер фільтрує за СПЕЦІАЛІЗАЦІЄЮ з налаштувань (config/profile.yml → target_roles.primary): вибір «Product Manager» або «Backend Engineer (Go / PHP)» залишає на карті лише вакансії, чий тайтл несе слова цієї ролі. Локатор списком вакансій з v1.250.0 видалено — це був невірний інструмент (правка власника).

Додано

  • Фільтр спеціалізації: select перелічує спеціалізації з налаштувань (target_roles.primary); вибір залишає лише вакансії, чий тайтл несе один із відмінних токенів ролі (межі слів, без регістру — «Go» ніколи не матчить «Google»); Reset повертає всі.
  • Локатор списком вакансій з v1.250.0 видалено — заміна: фільтр за спеціалізацією, не список вакансій (правка власника).

Змінено

  • Two mobile-map height specs: the floor moves 600 -> 540 px (the toolbar takes ~90 px of the viewport).

Примітки

  • Милі все ще відкладає власник. Матчинг свідомо ЛИШЕ за тайтлом словами з налаштувань: компанії чи описи затягли б у спеціалізацію нерелевантні вакансії. Вакансія поза всіма налаштованими ролями видима за «All».

[1.250.0] — 2026-10-12

Сторінка карты стає робочим інструментом: локатор вакансій над картою наводить на будь-яку видиму вакансію і відкриває її пік-картку (зокрема на десктопі), а текстовий пошук звужує карту за компанією або посадою — і те, й інше переживає перезавантаження.

Додано

  • Локатор вакансій: select над картою перелічує видимі (пості фільтри) вакансії за компанією+посадою; вибір летить до кружка і відкриває пік-картку — зокрема на десктопі.
  • Текстовий пошук: звужує карту за підрядком компанія+посада (порівняння без регістру; поле зберігає введений регістр), персистить у map-prefs, Reset чистить.
  • FAB більше не перекриває атрибуцію на #/map на всіх ширинах.

Виправлено

  • Два spec-и висоти мобільної карти: поріг стає 540 px (тулбар займає ~90 px вьюпорта).

Примітки

  • Милі власник відкладає на наступний реліз. 4 нові i18n-ключі входять до 18 словників, зібраний снапшот перегенеровано. Локатор завжди відкриває пік (навігація знецінила б його); кружок знаходиться через зворотне посилання __job — НЕ _point, який L.Circle вже займає.

[1.249.0] — 2026-10-11

Мапа вакансій тепер ставить релевантність на перше місце: спокійна шкала від бірюзового до сірого виділяє найкращі вакансії, фільтри оцінки й часу в дорозі зберігаються, на телефоні мапа займає доступну висоту та показує картку за першим дотиком, а трекер використовує ті самі фільтр і сортування поїздки.

[1.248.8] — 2026-10-11

Паритет з батьком: форк і web-ui синхронізовані зі 115-комітною дельтою апстріму — нове джерело PageUp, URL-комірки трекера парсяться як markdown-посилання, стейт Assessment — плюс ta/ja мовна робота: зациклені звіти зрізаються і називаються, скелет заголовків тримає A–G стабільним, а частка письма рахується лише по прозі.

Додано

Виправлено

  • Нове джерело PageUp (105 EN-адаптерів / 110 джерел); URL-комірки трекера парсяться як markdown-посилання (href — ключ дедуплікації); стейт Assessment приходить через states.yml.
  • Зациклений звіт зрізається і називається (report looping (block B repeated)), скелет заголовків ## A) … ## G) для неанглійських.
  • Частка письма рахується лише по прозі (таблиці, SCORE_SUMMARY, код і URL виключено) — японський звіт з латинськими таблицями більше не флакує.
  • /api/evaluate попереджає report not in <locale>, а remote-qa друкує коди колонкою.

-

[1.248.7] — 2026-10-11

Роль із шапки звіту тепер читає реальну конвенцію батька — # Evaluation: {Company} — {Role} — замість припущення # <Роль> — <Компанія>, тож у трекер більше не потрапляє «Evaluation: <Компанія>» як роль; звіт не тією мовою попереджає замість мовчазного збереження; а балачки моделі до H1 зрізано зі збережених звітів.

Виправлено

  • Локалізована мітка знімається першою (Evaluation:, Оценка вакансии: та ще 16 варіантів); після мітки Company — Role → роль — права сторона; без мітки Role — Company → ліва; частина, що називає компанію, вирішує; роль ніколи не дорівнює компанії чи слову мітки — запас це чищений рядок посту.
  • cleanup-plan сумує N із рядків (— N dirty-URL line(s)), не рахує файли — файл із 5 сміттєвими URL давав 1, а не 5.
  • Звіт не тією мовою попереджає: промпт відкривається ТА закривається обов’язковою мовною інструкцією (весь звіт, включно з таблицями), а конвеєр перевіряє мову збереженого звіту — дрейф спливає попередженням report not in <locale>.
  • Без балачок моделі до H1 — звіт починається зі своєї заголовкової лінії.

Примітки

  • Не портовано і не чіпано (перевірено в регресії v1.248.6): embed-фетч, рядкова структура посту («Компания : X»), дедуп «## Rejected», відмова кореня домену до fetch, чесність recon/link-check і cleanup-режими. Мовна перевірка — попередження: звіт (вміст уже валідовано A–G + оцінка) зберігається; оператор бачить дрейф і може повторити оцінку.

[1.248.6] — 2026-10-10

Пострегресійне шліфування телеграм-ланцюга: реальний пост зберігає рядкову структуру (мітка «Компания : X» і рядок ролі знаходяться там, де вони реально є), роль у трекер приходить із шапки звіту, а не сирим рядком посту, і операційні поверхні (recon, cleanup-plan, link-check) повідомляють те, що заявляють.

Виправлено

  • Пост t.me більше не склеюється в один рядок: <br> стає переводом рядка до зняття тегів, числові HTML-сутності декодуються; шаблон мітки компанії приймає форму з пробілом перед двокрапкою.
  • Роль у трекері береться з шапки звіту (H1 # <Роль> — <Компанія>); рядок-запасний варіант чиститься — без хештегів, без емодзі, ≤80 символів.
  • ## Rejected більше не дублюється: розділ створюється один раз; кожна відмова дописує в нього свій рядок.
  • Корінь домену відхиляється до запиту — сміттєвий URL більше нічого не коштує.
  • Recon обчислює сигнал контракту, а не стверджує його (honours rejected: перевіряє markPipelineRejected у задеплоєному коді); лічильник міток більше не друкує «0» двічі.
  • cleanup-plan повідомляє числа за видами (t-role до переміщення, рядки dirty-URL, рядки test-row) замість одного grep -c, що рахував власний рядок успіху.
  • link-check вважає 5xx від github.com неперевіреними — за бото-стіною, не як биті посилання.

Примітки

  • Не портовано і не чіпано (перевірено в регресії v1.248.5): сам embed-фетч, серверний skip-контракт, офлайн-тести _setLookup, прийом uk-порядкових, межі слів у гейті ролі та режим cleanup-apply. Підрахунок за видами в cleanup-plan читає власні мітки виводу скрипту очищення — їх перейменування мовчки зламало б лічильники.

[1.248.5] — 2026-10-10

Зміцнення після регресії: одиночні записи t.me тепер отримують сам текст посту (embed-форма), відхилений запис покидає пул eval-таймера на боці сервера, а рідна нумерація блоків в українському звіті приймається — плюс один тест, який потай ходив у живу мережу, назавжди офлайн.

Виправлено

  • Записи t.me нарешті оцінюються: звичайна сторінка одного посту віддає в HTML лише шапку каналу (~0,2 КБ — «View in Telegram» і нічого більше), модель отримувала порожній опис, і кожен телеграм-запис умирав на evaluate. Фетч тепер переписує t.me/<канал>/<id> (і форму стрічки t.me/s/…) в embed-форму (?embed=1&mode=tme) — той самий хост, SSRF-перевірки без змін — і бере текст посту з .tgme_widget_message_text (запасний варіант: og:description). Немає тексту віджета → відмова на кроці fetch (telegram post has no text, rejected: true) до витрати токенів LLM.
  • Eval-таймер перестає повторно платити за відхилені записи (серверний контракт): відхилений запис позначається в data/pipeline.md — її рядок покидає URL-паркан і потрапляє до розділу ## Rejected з короткою причиною (ідемпотентно, без дублів). Таймер бере голі URL з паркану, тому позначений запис пропускається природно — без залежності від нутрощів скрипту таймера. Recon друкує pipeline rejected marks: N + honours rejected: yes (server-side) замість ґрепу по скрипту.
  • Українські звіти знову проходять валідацію A–G: uk-модель нумерує блоки за порядком власного алфавіту (А Б В Г Д Є Ж), а прохід двійників рахував лише А/В/Е — на проді бракувало рівно блоків C, D, F, G (2/2 відтворення). Валідатор приймає українську порядкову послідовність, коли заголовки йдуть по порядку (та сама дисципліна, що абджад для ar і деванагарі для hi), а промпт оцінки для всіх локалей тепер явно вимагає латинські A–G.
  • Гейт рольових ключових слів спрацьовує на слова, а не підрядки: корені linkedin.com/ і x.com/ проходили гейт, бо lead/it жили в службовому тексті сторінки. Ключові слова зіставляються за межами слів Unicode, а корінь домену (URL без шляху) відхиляється до виклику LLM.
  • Юніт-тесту більше не потрібна жива мережа: тест no-hints (tests/auto-pipeline-jd-gate.test.mjs) підмінював HTTP-транспорт, але робив справжній DNS-запит — у CI був зеленим лише тому, що раннер мав мережу. У safe-fetch з’явився хук _setLookup() (сам резолвер замінний у тестах; продовий код його не чіпає), тест резолвить офлайн.

Додано

  • Режими cleanup-plan / cleanup-apply у deploy.yml: продова чистка QA-сміття (post-qa-cleanup) запускається на сервері без SSH з ноутбука. У лозі лише числа — ні імен файлів, ні прев’ю рядків: лог публічний, а в іменах назви компаній. Крок Verify для cleanup-режимів пропускається.

Примітки

  • Не портовано: компілятор локацій за межами слів і таблиця штатів USPS з батька (задокументовано з v1.248.1). Контракт пропуску відхилених записів тримається на сервері (розмітка в data/pipeline.md), а не на скрипті таймера — той поза репозиторієм і свідомо не правиться.

[1.248.4] — 2026-10-10

Продовження регрес-раунду: eval-таймер більше ні за яких умов не архівує безіменні Telegram-заглушки як звіти, QA-очищення більше не видаляє справжні Telegram-вакансії, атрибуція карти знову видима, а дашборд-чіпи читаються з ≥ 4.5:1.

Виправлено

  • Ланцюг ролі працює для кириличних постів: список ключових слів отримав разработчик/программист/инженер/аналитик/менеджер/руководитель/тимлид/лид/дизайнер/тестировщик/QA/архитектор/специалист/директор/DevOps/SRE (старий був лише EN: 0 з 40 реальних постів отримували роль), консультується шапка звіту (parseReportHeader — модель пише роль у H1), остання інстанція — перший осмислений рядок посту.
  • Ворота ідентичності переїхали ДО виклику LLM: запис без компанії та без зачіпки ролі падає на кроці fetch (rejected: true) — токени не витрачаються на оцінку, яку все одно було б відхилено.
  • t.me/s/<канал>/<id> більше не називає компанію «S» — сегмент веб-прев’ю пропускається; обидві форми посилань резолвляться в канал.
  • Самоябели «Компания: X» на Telegram-постах враховуються (і «…в компании X» всередині речення для мессенджер-хостів).

Додано

  • Фейли auto-pipeline несуть rejected: true у SSE-помилці — серверний eval-таймер використовує флаг, щоб пропустити запис pipeline замість повторів кожні 2 години.

Примітки

  • Не портовано: батьківський компілятор локацій з word-boundary та USPS-таблиця (документовано з v1.248.1).
  • Блок «Translations» у README генерується з одного канонічного списку, з паритет-тестом у CI (17 посилань на файл, своя не лінкується) — ar/pl/uk отримали блок; дрейф роздільників |/· усунуто.

[1.248.3] — 2026-10-10

Продовження регрес-раунду: eval-таймер більше за жодних умов не оформити безіменні Telegram-заглушки у звіти, QA-очищення більше не видалить справжні Telegram-вакансії, атрибуція карти знову видима, а дашборд-чіпи читаються з ≥ 4.5:1.

Виправлено

  • Auto-pipeline валідує ДО збереження: звіт без оцінки 0–5 або без блоків A–G завершує ран на кроці evaluate (rejected: true у SSE-навантаженні) — ні файла звіту, ні рядка трекера. t.me-записи, що проходили довжину-гейт v1.248.2, після того релізу дали ще два сміттєвих звіти; тепер безіменні записи відхиляються з company/role not identifiable.
  • guessCompanyRole більше не вигадує компанію з мессенджер-хостів — t.me, telegram.me, vk.com, linkedin.com та інші в EMPTY_DOMAINS: Telegram-пост падає на ім’я каналу, а безіменний запис відхиляється замість unknown-role.
  • post-qa-cleanup спершу читає оцінку з шапки: авто-пайплайн вирізує блок SCORE_SUMMARY перед збереженням, тож стара перевірка лише-блоку маркувала кожен збережений Telegram-пост сміттям — на проді 8 з 41 мають оцінку в шапці, і --apply видалив би їх. Оцінка з шапки (через parseReportHeader) тепер первинний сигнал, блок — запасний. Dry-run друкує причину по кожному файлу.
  • Атрибуція Leaflet видима на #/map — docs-FAB її перекривав (порушення ліцензії OSM); кут піднято на 96px.
  • Дашборд-чіпи читаються з ≥ 4.5:1 — підписи dash-chip і варіант --manual переведено на --foggy-strong (було 4.31:1).

Додано

  • Фейли auto-pipeline несуть rejected: true у SSE-помилці — серверний eval-таймер використовує флаг, щоб пропустити запис pipeline замість повторних спроб кожні 2 години.

Примітки

  • Дрейф ar/ja не повторився на перезапуску (див. v1.248.2).
  • Саме очищення проду досі чекає явного «так» користувача (CAR-61c); з цим фіксом очищення більше не може видалити справжні Telegram-вакансії.

[1.248.2] — 2026-10-10

Регрес-раунд: eval-таймер більше не перетворює сміття pipeline на «Last evaluation», розбір SCORE виживає у реальних форматах, а трекер нарешті говорить вашою мовою.

Виправлено

  • Звіти з порожнім JD більше не пишуться. Ворота JD авто-пайплайну піднялися з 50 до 200 символів до виклику LLM — сторінки-заглушки (example.com/qa-v167-…) завершуються SSE-помилкою + попередженням у консоль замість звіту …-t-role-<ts>.md, який дашборд показував як «Last evaluation» (на проді 42 таких файли).
  • Розбір SCORE_SUMMARY виживає у реальних форматах: «4,2/5», «4.2 / 5», «4.2», «SCORE :» — все валідно; кома з 1–2 цифрами — десяткова, з 3 — тисячні (старий код читав «4,200» як 4.2 і пропускав ворота 0–5). Провал пише в лог лише рядок SCORE: — ніколи вміст звіту (логи CI публічні).
  • Статуси трекера перекладені: таби стадій і бейджі рендерять локалізовані підписи в усіх 18 локалях; канонічне значення в даних, фільтрах і URL не чіпається.
  • Чесний ETA: «⏱ ~30s» став «~2–4 хв» у evaluate/advisor/ринковому звіті (реальний діапазон 86–285 с), а підзаголовок evaluate читається A–G.
  • Доступність: лічильник API-keys у config і лічильник pipeline тепер ≥ 4.5:1 (було 4.31:1); чип фасету з порожнім значенням більше не рендериться без доступного імені.
  • UI-полірування: кнопка Delete збережених пошуків виглядає деструктивною лише при вибраному пошуку (інакше disabled); картка EMAIL профілю не обрізається на 1440 px; у пігулці live-evals з’явився відступ іконка/текст включно з RTL; docs-FAB не перекриває атрибуцію Leaflet; «?» help не переноситься під табами stats; usage отримав повітря між підзаголовком і табами; health тепер каже career-ops-ui.

Додано

  • scripts/post-qa-cleanup.mjs: dry-run за замовчуванням, --apply з автоматичними бекапами (*.bak-<ts>, звіти переносяться до qa/cleanup-backup-<ts>/): рядки ZZ-QA-TEST у трекері, звіти *-t-role-* без валідного SCORE, записи example.com у pipeline.

Примітки

  • Дрейф ar/ja з регрес-раунду не повторився на перезапуску (обидва: lang ✓ + A–G ✓) — запис ta у language-check і посилений парсер SCORE йдуть як defense-in-depth.
  • Саме очищення проду чекає явного «так» користувача: спершу dry-run-план, потім --apply.

[1.248.1] — 2026-10-09

location_filter отримує батьківські тири always_allow та block_hard (CAR-60) — мультикраїновий рядок локації виживає при блок-листі сусідів, а явний жорсткий блок більше не можна врятувати.

Виправлено

  • always_allow сильніше за block: при block: [romania, poland, croatia] і always_allow: [united kingdom] workable-merged комірка «London, United Kingdom · Romania · Poland · Croatia» проходить — домашній регіон є в опціях (споживчий кейс workable #4806 тепер у батьківській формі).
  • block_hard сильніше за always_allow: странові терміни, які не можна врятувати збігом always_allow десь у рядку.
  • strict: true враховує block_hard як обмежувальний тир; секція location_filter у docs/help документує обидва тири ×18.

Примітки

  • Свідомо не перенесено цього разу: батьківський компілятор локаційних ключів із word-boundary та його таблиця штатів USPS (веб-морда тримає case-insensitive substring; таблиця служить US-центричним конфігам — відкладіть під CAR-60, якщо знадобиться). Перехідна allow-list форма споживчого тесту workable лишається другим вердиктом поруч із новою батьківською.

[1.248.0] — 2026-10-09

Паритет з батьком: п’ять апстрім-фіксів провайдерів/liveness перенесено — борди, що переїхали на нові API, працюють далі, а банер закритої вакансії на окремому рядку більше не пропускається.

Виправлено

  • Gupy слідує за API на portal.gupy.io і шле імена фільтрів, які API реально читає (workplaceType/type) — старі плюральні імена мовчки ігнорувалися; country більше не надсилається (сконфігурована країна малює warning з ім’ям запису). Конфіденційні роботодавці дропаються за точним збігом ярлика («Confidencial Seguros» виживає), а max_pages обмежений 100 — API відповідає 400 після offset 10 000, і truncation-warning тепер радить звузити ключові слова.
  • Workable зберігає всі краї мультикраїнової вакансії: сіблінги під одним job-URL згортаються в один рядок («London, United Kingdom · Romania · Poland · Croatia») замість перемоги першої країни.
  • Liveness: банер на своєму рядку — не частина рядка вище — «Sign in if you want to apply» / «Get notified when this position is filled» під вакансією читається як клауза закриття, а справжнє «until filled» через рядки досі читається активним.
  • Radancy читає сіблінг-елемент job-location у legacy-розмітці картки (розкладки родини Barclays кладуть локацію після </a>) — рядки без власної локації залишаються порожніми, а не займають локацію сусіда.
  • SmartRecruiters fallback-посилання зберігають сконфігурований slug — вакансія з відсутнім/недовіреним ref синтезує публічний URL зі slug тенанта, ніколи з відображуваного імені.

Додано

  • No-ports задокументовано: апстрім-фікс _http proxy fetchImpl (у веб-морди немає проксі-гілки — порт зламав би identity-перевірку DNS-rebinding-гварда й додав undici-залежність, якої репо свідомо уникає); апстрімні verify-ats (+226 рядків) і cv-facts (веб-морда реалізує власний ATS-скоринг і CV-парсинг); батьківський дашборд Go TUI та CLI-скрипти; перестановка імпортів у Teamtailor (косметика).

Примітки

  • Відома прогалина як follow-up: батьківський tier always_allow в location-фільтрі (споживчий тест workable виживає завдяки йому UK-блок) — в location-фільтрі веб-морди цього tier немає; порт виразив той самий вердикт споживача через allow-list тест. Заведено окремою задачею.

[1.247.0] — 2026-10-09

Хвіст дизайн-свипу CAR-49…58 закрито — всі решта minor/polish-знахідки плюс темна карта, яку минулий реліз навмисно відклав.

Виправлено

  • Карта рендерить справжній темний режим. Зум/шари/атрибуція потемніли ще у v1.246.0; тепер і тайли слідують темі: темний режим малює OSM-шар із CSS-інверсією (без вендорних ключів), а keyed-пресет Carto-dark доступний через MAP_TILE_DARK_URL (його хост додано до дозволу CSP img-src). Шар перемонтується наживо при зміні теми.
  • Жовті callout’и мають темні варіанти (config, batch, hero-попередження) через токен-механіку v1.246.0 — і попутно спійманий регрес: .callout a забивав .btn-primary; тепер скоупиться :not(.btn).
  • URL в інфо-картці apply більше не обрізається посеред слова.
  • Кнопки заголовка CV — одна іконочна система (текст; мікс 📁/📄/💾 прибрано).
  • Таби статусів трекера в одному регістрі — sentence case з локальними особливостями (CJK/ar/hi/ta без родових форм).
  • Іконки топбара — єдиний stroke-SVG набір — мікс 🔔/🌙/🩺 прибрано; кнопка теми малює sun/moon через CSS.
  • Картка Pipeline на дашборді позбулася рожевого highlight’у (кільце лише hover/focus).
  • Видалення збережених пошуків явно деструктивне (.btn-danger, а не нейтральний ghost).
  • У preview pipeline з’явився справжній порожній стан (заголовок + підказка + CTA, патерн трекера).
  • Мета часу/вартості — один патерн: UI.pageMeta малює ⏱ + вартість одним рядком під заголовком в’ю (evaluate/deep/auto/mode-page/orientation).
  • Auto-pipeline позбувся ✨ — єдиний декорований h1 повернувся на спільний лівий край.
  • Batch docs URL — інлайн-посилання.
  • interview-digest / orientation мають спроєктовані порожні стани (заголовок + підказка + CTA).
  • Export career-plan неактивний, доки план порожній, дії — в один рядок.
  • stats: дублюючий заголовок «Market report» прибрано, плейсхолдер регіону вміщується (коротший ×18, фікс-ширина).
  • Config більше не тікає абсолютними шляхами — серверний displayPath() показує ~/… або голе ім’я файлу.
  • Хелпер LLM_PROVIDER — три короткі рядки (×18 + pre-line) замість 150-символьної mono-стіни.
  • EMAIL-картка профілю тримає адресу в один рядок (ellipsis; перенос лише ≤480px).
  • Текст Health більше не ламає слова («Run buttons» лишається цілим; overflow-wrap anywhere).
  • Інлайн-код у help виживає в RTL (LTR-ізоляція, nowrap, скрол).
  • Пігулка «Live evals» у hero більше не стикається в ru/ar (flex-wrap, нестисливі іконки).

Додано

  • Пресет темних тайлів (MAP_TILE_DARK_URL + attribution-env) задокументований у коді; додання до CSP img-src покриває хост Carto.

Примітки

  • Дефолт темних тайлів — CSS-інверсія OSM: keyless Carto зачинив базові мапи за API-ключ (перевірено запитом), тож keyed-пресет — опція, а не дефолт.

[1.246.0] — 2026-10-09

Усі десять топ-знахідок сеньйор-дизайн проходу CAR-48 виправлено — включно з блокером і всіма сьома мажорами.

Виправлено

  • Apply dark: посилання на інфо-картці знову видиме. Картка тримала світлу поверхню в темному режимі; посилання «Need Playwright? See …» сиділо на ≈1.0:1. Theme-aware .callout--info (токени surface/border/text/link) піднімає темне посилання до ≈7.3:1.
  • RTL більше не перевертає числові композиції. Score-пігулки («1.5 / 5»), бенди звітів («≥ 4.5», «< 3.5») і дашбордне «/ 5.0» отримують напрямлену ізоляцію (LTR-span’и); ar-словник там, де прозі краще, переписує діапазони словами.
  • CV-markdown лишається LTR. Textarea і прев’ю рендеряться dir="ltr" арабською; обв’язка навколо залишається RTL.
  • Колонка DATE у звітах більше ніколи не переносить ISO-дату (nowrap + табличні цифри).
  • Сайдбар закінчується над USAGE HUD (резерв скроллпорту + fade) — останній пункт навігації більше не їде під панель; RTL-дзеркалання враховано.
  • Мобільний скан @390: «Save search» повністю видимий — ряд контролов переноситься і стискається замість обрізання на краї.
  • Контроли карти слідують темній темі (zoom, шари, атрибуція). Самі тайли навмисно не чіпали: їх origin закріплений серверним CSP img-src.
  • Chat FAB більше не перекриває інтерактив — скрол-паддінг сторінок розчищає його (кнопки Disable у Portals, score-пігулка у Reports, інпут two-pager, атрибуція Leaflet).
  • Журнал активності розмовляє мовою інтерфейсу: 22 слаги дій локалізовано ×18 локалей (з fallback на сирий id для невідомих) і чіпи фільтрів перекладаються; колонка TARGET залишається — її заповнює auto-pipeline.
  • Форми assessments отримують справжні лейбли (sentence case, зв’язка htmlFor/id) замість полів лише з placeholder’ами.

Примітки

  • Не в цьому релізі: темні тайли карти (потрібне розширення CSP img-src — серверна правка, навмисно не клієнтська); темні варіанти жовтих callout’ів (config, batch) — механіка токенів уже готова для продовження.

[1.245.0] — 2026-10-09

Тамільська (தமிழ்) приєднується як 18-а локаль інтерфейсу — перша нова мова після хвилі з 17 локалей, з повним паритетом ключів.

Додано

  • Тамільська (ta) — 1449/1449 ключів із повним паритетом до en, без загублених плейсхолдерів, у хінді-стилі indic-tech-конвенцій (провайдери, моделі, шляхи та команди лишаються латиницею). detect() приймає ta, ta-IN, ta-LK; тамільська — LTR (закріплено тестом).
  • Повний docs/help/ta.md (33 H2 / 125 H3, лічильники джерел звірені з живим реєстром, канаркові якорі Hermes, дослівний YAML), плюс дзеркала README.ta.md і CHANGELOG.ta.md.
  • Перелічувальні ворота 17 → 18: LANGS, detect-проби, i18n-аудит, changelog-паритет, RTL-страж, CI langs — заодно підтягує hi, якого раніше не було в CI-списку.

Примітки

  • Лендінг cvstart.org поки лишається на 17 локалях — переклад лендінгу окреме завдання; синк контенту сайту вже віддає docs/help/ta.md і ta-changelog.
  • report-header.js REPORT_LABELS залишається на 17 — парсинг тамільських звітів прийде разом із генерацією тамільських звітів у батьківському репо.

[1.244.2] — 2026-10-08

Полірування сторінки сканування: лаунчер, рядок стану, панель репостів і фільтри перероблені в один консистентний хром — і таблицю результатів більше не можна розтягнути даними бордів.

Виправлено

  • Клітинки зарплати показують лише грошовий діапазон. Деякі борди кладуть увесь текст пільг у поле зарплати — видима клітинка тепер показує лише грошову частину, а текст переміщує в підказку (раніше: 6-рядкова клітинка, що розтягувала кожен рядок).
  • Рядки з порожнім заголовком показують компанію (потім нейтральне тире) замість гігантської порожньої клітинки.
  • Бейдж seniority ніколи не переноситься у своїй колонці фіксованої ширини.
  • Рядки без оцінки показують притлумлене «◎ —» із підказкою, що порівняння з two-pager не знайшло збігів (раніше: нічого, що виглядало зламаним поруч з оціненими рядками).

Додано

  • Редизайн хрому: картка-лаунчер (вирівняний ряд контролів, домінантна primary-кнопка), рядок стану терміналу з крапкою стану (бездія/виконання/готово/помилка) підтримує reduced-motion, панель репостів має стелю висоти зі sticky-заголовком (датасет із 1,560 кластерів рендерив панель 155,000 px), і блок фільтрів як рівномірна адаптивна сітка з вирівняним футером.

Примітки

  • Решта #/scan ідентична v1.244.1 — змінилися лише полірування хрому та чотири виправлення вище. Тести 5045 → 5068 unit.

[1.244.1] — 2026-10-08

Виправлено

  • Scan icons announce real values. The boost/fit/score icons’ accessible names leaked the raw dict templates (“Fit: {band}”, “Boosted by {by}”) — {band}/{by}/{score} are now substituted (band word, booster keyword, numeric score). [HIGH a11y, caught live in the v1.244.0 regression round]
  • Title pipes split correctly. A 4-segment title like «C++ | Rust | Go Developer | Onsite» no longer lifts “Go Developer” out as a “country” — the split demands the segment resolves to a real country (conservative Countries.detectCountry).
  • Meta dates render for every row. daysSince accepted date-only ISO, but the API serves full timestamps for ~83% of dated rows — those rows showed no date segment.

Примітки

  • Решта #/scan ідентична v1.244.0 — змінилися лише три виправлення вище.
  • Свідомо не зроблено: стан фільтрів у hash URL, навігація рядків клавіатурою та парсинг 5 lakhs INR (існуючі хвости).

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