Журнал изменений

GitHub

Каждое значимое изменение, релиз за релизом. Тот же файл публикуется в репозитории и переведён на все 19 языков.

[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-подобных источников получили декодирование   (раньше знали только 5 сущностей); намеренное двойное декодирование cryptocurrencyjobs сохранено через алиас. hh оставляет свой декодер (обрабатывает —/–, вне общих 6). Новый гард-тест падает, если источник снова заведёт локальный декодер.

Примечания

  • Рефакторинг с сохранением поведения; без изменений маршрутов / CSP / SSRF / записи в родителя. Тесты: tests/decoder-consolidation.test.mjs (+2). Набор: 2458 (+2).

[1.178.0] — 2026-08-13

Исправлено (LOW, паритет с родителем) — две устаревшие константы обновлены под родителя (PARENT-SYNC GAP #4 + #5).

Исправлено

  • User-Agent браузера (GAP #4) — BROWSER_LIKE_USER_AGENT (его шлют workable/workday/oraclecloud/a16z/eightfold для прохождения WAF/бот-фильтров) поднят с Chrome 131 до 151 под родительский user-agent.mjs; устаревшую сборку чаще блокируют. Защищено тестом Chrome major ≥ 151.
  • FALLBACK статусов трекера (GAP #5) — резервный FALLBACK в states.mjs (используется только когда живой templates/states.yml нечитаем — свежий клон / изолированный CI-корень) получил турецкие алиасы статусов из родителя (#2615): değerlendirildi, başvuruldu, yanıt verildi, mülakat, teklif, reddedildi, iptal edildi, uygun değil, kabul edildi/işe alındı. В проде живой файл уже их давал.

Примечания

  • Только две константы; без изменений маршрутов / CSP / SSRF / записи в родителя. Тесты: tests/http-json.test.mjs (+1) + tests/states.test.mjs (+1). Набор: 2456 (+2).

[1.177.0] — 2026-08-13

Исправлено (MEDIUM, сканер) — csod (Cornerstone) возвращал 0 вакансий у арендаторов, закрывающих search-API сессионными куками (parent #2769, PARENT-SYNC GAP #1).

Исправлено

  • Некоторые арендаторы Cornerstone выставляют сессионные куки на bootstrap-странице карьерного сайта и отвечают 401 CSOD Unauthorized на search-API, пока эти куки не вернутся вместе с анонимным bearer-токеном. sources/csod.mjs теперь читает bootstrap через новый хелпер fetchResponse, собирает заголовок Cookie из значений Set-Cookie (cookieHeaderFrom — только name=value, семантика jar) и повторяет его в POST поиска. Только тот же origin (хост закреплён + redirect:'error'), так что сессионные куки не уйдут третьей стороне; арендатор без кук ведёт себя как раньше.

Примечания

  • Новый server/lib/http-json.mjs::fetchResponse (аддитивно; существующие источники не затронуты). Без изменений маршрутов / CSP / SSRF / записи в родителя. Тесты: tests/sources-parity-v1118a.test.mjs (+1). Набор: 2454 (+1).

[1.176.0] — 2026-08-13

Исправлено (MEDIUM, отчёты) — балл под жирной меткой, которой нет в RU-таблице, всё ещё показывал «Score not detected» (FIND-5).

Исправлено

  • Два RU-отчёта записывали балл как **Итоговый балл:** 1.8 / 5 / **Скор:** 1.8 / 5 — жирные метки, которых нет в REPORT_LABELS.ru (знает только «Оценка»/«Балл»), поэтому балл не разбирался. Вместо наращивания списка синонимов parseReportHeader теперь откатывается к форме значения: дробь по рубрике /5 под ЛЮБОЙ жирной меткой. Язык-независимо, невосприимчиво к заголовку (нет **, нет значения /5) и отвергает дату вроде 5/5/2026 (негативный просмотр вперёд по знаменателю).

Примечания

  • Только парсер на сервере; без изменений маршрутов / CSP / SSRF / записи в родителя. Тесты: tests/report-header-locale.test.mjs (+2). Набор: 2453 (+2).

[1.175.0] — 2026-08-13

Исправлено (LOW, укрепление) — регресс-гард для SEO-описания из FIND-3 + null-безопасная очистка легитимности (по следам AI-ревью).

Исправлено

  • Гард паритета SEO-описания — правка v1.174.0, заменившая жёстко зашитое «~55» в meta.desc каждого языка на плейсхолдер {adapters} из реестра, не имела теста и могла молча регрессировать при следующем редактировании локали. Новый CI-изолированный tests/site-meta-desc-parity.test.mjs падает, если любой из 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

Изменено (LOW, дизайн-система) — токены типографической шкалы + слои 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

Добавлено (LOW) — честные подсказки 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

Добавлено (LOW) — 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

Исправлено (LOW, 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

Исправлено (LOW, дизайн-система) — приподнятые поверхности теперь отделяются от разделителей (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

Исправлено (LOW) — терминология рубрики теперь совпадает с канонической документацией. 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

Исправлено (LOW) — термин «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

Исправлено (LOW) — плейсхолдер поиска в верхней панели больше не переполняется ни на одном языке. «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

Исправлено (LOW) — встроенный ассистент «Спросить документацию» теперь охватывает экспорт отчёта в 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

Исправлено (MEDIUM) — «?»-подсказка теперь ≥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

Исправлено (MEDIUM) — #/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), и противоречие на том же экране (чип Active: OpenRouter).

Исправлено

  • config.providerModelNote (×17): теперь сказано, что ⚡ живая оценка headless работает на любом из семи ключей провайдеров (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub Models · Hermes), с автопорядком и откатом. Ложная фраза про OpenAI удалена.
  • dash.quick.evaluateSub (×17): нейтрально («Оценка соответствия 0–5»). Keys: N / 5 → N / 7.

Примечания

  • Только текст; без изменений маршрута/CSP/SSRF/записи. Новых ключей i18n нет (снапшот 1217). Набор: 2413 (+3).

[1.159.0] — 2026-08-13

Исправлено (HIGH) — метаданные отчёта больше не зависят от языка. Отчёты на не-английском языке показывали пустую полосу метаданных на #/reports (без оценки, даты и легитимности), потому что parseReportHeader распознавал только английские жирные метки.

Исправлено

  • parseReportHeader теперь разбирает языко-независимый YAML-блок ## Machine Summary (score: / legitimacy: / date: — тот же источник, что уже читает auto-pipeline). Приоритет: английские метки → блок Machine Summary → локализованные метки (REPORT_LABELS, 17 локалей). Английские отчёты остаются байт-в-байт идентичными.
  • Толерантный разбор числа (1.5/5, 1,5/5, 1.5 из 5, 4.5 out of 5); дата откатывается к mtime файла, если в теле её нет.

Примечания

  • Только чтение/разбор; без изменений маршрута, CSP, SSRF и записи в родитель. Новых ключей i18n нет. Набор: 2410 (+8).

[1.158.0] — 2026-08-12

Исправлено — два косметических бага отображения («?» протекала в заголовок вкладки и неверное число провайдеров на лендинге). Только отображение; без изменений поведения, безопасности и потока данных.

Исправлено

  • «?» из HelpHint больше не протекает в document.title. Роутер брал заголовок вкладки из необработанного h1.textContent, из-за чего вкладка показывала «Vacancy search?» вместо «Vacancy search». Теперь router.js::focusNewView клонирует заголовок, удаляет .help-hint и лишь затем читает текст; видимая «?» в заголовке не затрагивается.
  • cvstart.org показывал «17 AI providers» вместо «7». Хелпер sub() в Features.astro заменял все {n} числом локалей (17) до покарточной подстановки; теперь {n} разрешается покарточно (провайдеры → 7, языки → 17).

Примечания

  • Без изменений сервера, маршрута, CSP, SSRF и ключей i18n; форма facts.json не изменилась. Набор: 2402 теста (+1).

[1.157.0] — 2026-08-12

Исправлено — живые оценки теперь выполняются на ЛЮБОМ настроенном провайдере, а не только Anthropic/Gemini. Пользователь с одним лишь OPENROUTER_API_KEY ошибочно принудительно переводился в ручной режим.

Исправлено

  • Первопричина: пин LLM_PROVIDER без ключа (например, LLM_PROVIDER=claude из init) заходил в тупик; теперь происходит откат к auto-порядку среди настроенных провайдеров (в selectActiveProvider + обеих каскадах диспетчеризации).
  • Клиентский гейтинг (#/deep + вью mode-page) теперь использует window.ProviderStatus (/api/status/providers, все 7) вместо устаревшей проверки Anthropic/Gemini; переформулированы подсказки (deep/eval × 17) + значок «Живые оценки» на дашборде + config.llmProviderHint.

Примечания

  • Без изменений безопасности. Набор: 2401 тестов (+5).

[1.156.0] — 2026-08-12

Рефакторинг — разбить scan.js под лимит размера (P-16) + фикс CodeQL. scan.js был 906 строк; извлечены две сохраняющие поведение фабрики → 648. Завершает пару разбиений вью P-15/P-16.

Изменено

  • Новые scan/runner.js (движок выполнения скана) и scan/filters.js (машина состояний фильтров) через bag-объекты ctx/refs; scan.js связывает оба.

Исправлено

  • CodeQL js/useless-assignment-to-local (#428) в config/tab-controller.js: let n = i; → let n;.

Примечания

  • Чистый рефакторинг, без изменения поведения; 4 теста, читающих исходник, перенаправлены. Оба крупных вью теперь под 800 (P-15/P-16 готово). Набор: 2396 тестов.

[1.155.0] — 2026-08-12

Рефакторинг — разбить config.js под лимит размера (P-15). config.js был 1030 строк (сверх лимита 800); извлечены два модуля, сохраняющих поведение, — теперь 783.

Изменено

  • Новые config/field-specs.js (данные полей + списки моделей) и config/tab-controller.js (фабрика панели вкладок); config.js ссылается на них, логика рендера не меняется.

Примечания

  • Чистый рефакторинг, без изменения поведения; 6 тестов, читающих исходник, перенаправлены. scan.js (906) оставлен как есть (уже частично разбит; ядро слишком связано для чистого механического разбиения). Набор: 2396 тестов.

[1.154.0] — 2026-08-12

Новый гайд — «Запуск всего стека в облаке». У career-ops нет собственного рассказа про облако/сервер, поэтому добавлен: пошаговый рецепт, как разместить родительский пайплайн career-ops, этот вьюер career-ops-ui и движок ИИ (подписка Claude через Claude Code, локальный Hermes или ключи API) на небольшом постоянно включённом сервере. Поставляется как Справка §31 на 17 языках, раздел README и вики-страница.

Добавлено

  • Справка §31 «Запуск всего стека в облаке» (× 17) — три части, подготовка + установка, выбор движка и безопасная публикация (HTTPS обратный прокси + аутентификация + инварианты CSP/SSRF/XSS/без-секретов). Бандл справки растёт до 31 H2 / 112 H3.
  • README — раздел «Запуск всего стека в облаке» (× 17) + вики-страница Cloud-Deployment.

Примечания

  • Только документация — без маршрутов, сервера и изменений клиента; без новых i18n-ключей. 4 теста справки переведены на контракт 31 H2 / 112 H3. Набор: 2396 тестов (без изменений).

[1.153.0] — 2026-08-12

Сканер Jobvite переведён на публичный XML-фид (синк с родителем). Родитель вывел из эксплуатации JSON API Jobvite (теперь ноль вакансий); source в web-ui использовал тот же мёртвый эндпоинт, так что любая отслеживаемая компания Jobvite молча сканировалась пустой. Порт родительского фикса (#2623): теперь читается публичный по-тенантный XML-фид с ключом companyEId.

Исправлено

  • source обращался к выведенному JSON API и возвращал ноль вакансий; теперь он обращается к https://app.jobvite.com/CompanyJobs/Xml.aspx?c={companyEId} и парсит XML <result><job>… (CDATA + сущности, detail-url предпочтительнее apply-url).

Изменено

  • Разрешение companyEId: (1) company_eid: в портале, (2) параметр c= явного api:, (3) дискавери по странице борда. fetchText (http-json.mjs) добавляет .location/.retryAfter к non-ok ошибке (только чтение, обратно совместимо).

Примечания

  • Безопасность — два хоста (jobs.jobvite.com, app.jobvite.com) закреплены через assertJobviteUrl: только https, строгий allowlist, редиректы не следуются. companyEId — только значение ?c=; число sources не изменилось.
  • Набор: 2396 тестов (+4).

[1.152.0] — 2026-08-12

Провайдер Hermes — проводка завершена + актуализация документации. Код-ревью интеграции Hermes из v1.151.0 нашло два реальных пробела и четыре пункта полноты; всё исправлено здесь, а список LLM-провайдеров по всему приложению приведён к полным семи на всех поверхностях документации и в 17 языках.

Исправлено

  • #/config не мог форсировать Hermes — выпадающий список LLM_PROVIDER перечислял лишь шесть провайдеров, поэтому HERMES_API_KEY можно было задать, но не форсировать Hermes из UI. Теперь hermes — 8-й вариант, а новый тест паритета не даёт списку снова разойтись с LLM_PROVIDERS.
  • Короткие self-hosted ключи молча отклонялись — порог в 20 символов у isUsableKey был рассчитан на облачные ключи; hasHermesKey теперь использует смягчённый порог в 8 (пример в документации Hermes — 19 символов).

Изменено

  • Список провайдеров нормализован до полных семи в README (× 17), встроенной справке (× 17), словаре config.llmProviderHint (× 17) и docs/sdd; hermesChatUrl достраивает хост без пути; текст ручного фолбэка называет Hermes.

Примечания

  • Безопасность без изменений — новых маршрутов и правок SSRF/CSP нет; в health/doctor добавлена строка HERMES_API_KEY.
  • Набор: 2392 тестов (+2).

[1.151.0] — 2026-08-12

Hermes теперь подключённый LLM-провайдер (Phase 5) — спайк Phase 5 подтвердил, что Hermes от Nous Research включает OpenAI-совместимый API Server (hermes gateway → POST /v1/chat/completions), так что career-ops-ui выполняет живые оценки через локальный Hermes так же, как OpenAI/Qwen. Задайте HERMES_API_KEY в Настройках приложения — он входит в auto-порядок (последним). Закрывает последний открытый пункт роадмапа — Phase 5, Shape A.

Добавлено

  • LLM-провайдер Hermes (Shape A) — runHermes поверх общего клиента runOpenAICompatible (server/lib/openai.mjs), в обеих каскадах (llm-dispatch.mjs + routes/llm.mjs), в хвосте auto-порядка + пин LLM_PROVIDER=hermes, /api/status/providers и llm-pricing.mjs. Обращается к настраиваемому локальному base URL (по умолчанию http://127.0.0.1:8642/v1) с Bearer-аутентификацией — это НАСТРОЕННЫЙ эндпоинт провайдера (как OpenRouter/Qwen), а не пользовательский URL вакансии, поэтому SSRF-guard не задействуется.
  • Поля в #/config — HERMES_API_KEY (секрет) + HERMES_BASE_URL + HERMES_MODEL (по умолчанию hermes-agent), 6 новых ключей i18n × 17 локалей (snapshot 1208 → 1214).

Изменено

  • Спайк разрешён: docs/integrations/HERMES.md, встроенная справка §30 (× 17), тизер README (× 14), скилл hermes-bridge и roadmap переходят с «запланировано / пока не подключено» на подключено (Shape A). Shape B (кастомный relay агентного runtime) не понадобился.

Примечания

  • Безопасность: запрос провайдера — это настроенный эндпоинт, той же категории, что и другие OpenAI-совместимые провайдеры — без новой SSRF-поверхности, без изменения CSP/санитайзера. HERMES_API_KEY — это SECRET_KEY (никогда не отображается).
  • Тесты (изолированы в CI, заглушка транспорта): tests/hermes-provider.test.mjs (+5); канарейка «нет ветки Hermes» из v1.146.0 инвертирована, чтобы утверждать, что она ЕСТЬ; тесты набора провайдеров обновлены до порядка из 7 провайдеров.
  • Набор: 2390 тестов (+5).

[1.150.0] — 2026-08-12

Единообразные пустые состояния (полировка Phase 4) — каждая панель «пока ничего нет» теперь отрисовывается единым общим стилем .empty, вместо того чтобы некоторые представления переобъявляли вид инлайн с магическим 40px. Небольшое исправление визуальной согласованности; пустые состояния #/activity, #/cv-studio, #/stats и #/usage теперь совпадают со всеми остальными (токенизированный отступ 48px + пунктирная рамка).

Изменено

  • #/activity, #/cv-studio, #/stats, #/usage убрали свой инлайн style: { padding: '40px', textAlign: 'center', color: 'var(--foggy)' } на пустых панелях — все три свойства уже даёт общий класс .empty (--space-7 = 48px, по центру, приглушённо, пунктирная рамка). Теперь эти четыре отрисовываются идентично ~25 другим панелям .empty.
  • Правомерные переопределения по представлению (#/dashboard width:100%, #/pipeline border:none) не тронуты — убраны только чисто избыточные переобъявления.

Примечания

  • Только чистка использования CSS на клиенте — без изменения маршрута, сервера, ключа i18n или правил CSS (сам класс .empty не менялся); snapshot словаря 1208. Проверено в браузере (пустая панель #/usage вычисляет 48px отступа + пунктирную рамку, 0 ошибок консоли).
  • Новый канареечный тест tests/empty-state-consistency.test.mjs держит .empty единственным источником истины. Phase 5 (провайдер Hermes) остаётся заблокированной.
  • Набор: 2385 тестов (+2: tests/empty-state-consistency.test.mjs).

[1.149.0] — 2026-08-12

Порталы перенесены в Настройки (Phase 4) — #/portals теперь находится в группе навигации Setup рядом с Настройками приложения, а не под Sourcing. С v1.144.0 это экран настроек (включение/отключение отслеживаемых компаний + проверка состояния ATS), а не действие сорсинга — значит, ему здесь и место. Изменение только навигации; страница и её маршрут не меняются.

Изменено

  • Элемент навигации #/portals → группа Setup (в public/index.html), сразу после Настроек приложения. Убран из группы Sourcing (в ней остаются Scan / Pipeline / Auto-pipeline / Профинансированные компании). Маршрут #/portals, представление и подпись nav.portals не меняются — сдвинулась только позиция в боковой панели.

Примечания

  • Только разметка навигации — без изменения маршрута, представления, ключа i18n или сервера. Проверено в браузере (0 ошибок консоли); защищено tests/portals-nav-placement.test.mjs.
  • Набор: 2383 тестов (+2: tests/portals-nav-placement.test.mjs).

[1.148.0] — 2026-08-12

Аккуратные фильтры сканирования (Phase 4) — панель фильтров теперь опрятная сетка — панель фильтров #/scan перешла с рваной flex-обёртки из жёстких блоков переменной ширины на адаптивную сетку, а действия «Применить / Сбросить» теперь занимают собственную отдельную строку с выравниванием вправо. Фильтры те же, поведение то же — просто читать удобнее. Дизайнерская правка (без parent-sync).

Изменено

  • Панель фильтров #/scan → адаптивная сетка — .scan-filters теперь display: grid со столбцами repeat(auto-fill, minmax(180px, 1fr)) и равномерными отступами, так что 11 подписанных фильтров выстраиваются в аккуратные колонки при любой ширине, а не рвано переносятся в ряд.
  • Действия «Применить / Сбросить» занимают всю ширину сетки в собственной строке, отделены тонкой линией и выровнены вправо. Удалён старый приём со скрытой меткой + внутренняя flex-обёртка в scan.js.

Примечания

  • Только CSS + небольшая чистка DOM — все id фильтров (#scan-filter-*, #scan-apply) и связка SR.render() не менялись, так что поток Playwright не затронут. Новых ключей i18n нет.
  • Проверено в браузере (0 ошибок консоли); защищено tests/scan-filters-grid.test.mjs.
  • Набор: 2381 тестов (+3: tests/scan-filters-grid.test.mjs).

[1.147.0] — 2026-08-12

Hermes & Telegram — раздел встроенной справки + поверхность на cvstart.org (Phase 5b, часть 2) — вторая и последняя часть работы над документацией Hermes: инструкция теперь живёт внутри собственной справки приложения на всех 17 языках, и встроенный ассистент документации отвечает на вопросы о Hermes на её основе. Всё ещё только документация — путь LLM-провайдера Hermes остаётся запланированным / пока не подключённым (Phase 5).

Добавлено

  • Встроенная справка §30 «Hermes & Telegram» × 17 языков — новый раздел (что такое Hermes + две формы интеграции; запуск на облачном сервере; Telegram через Hermes + правило «что НЕ раскрывать»), доступный из #/help. Grounding у docs-assistant / DocsFab подхватывает его автоматически, так как оба читают docs/help/<lang>.md.
  • cvstart.org — ссылка на руководство по Hermes с переходом на документ в GitHub.

Изменено

  • Порог пакета справки поднят 29 → 30 H2 / 105 → 108 H3 (canonical-docs-coverage, help-ui, help-ru-config-section); §30 добавляет 3 H3.

Примечания

  • Hermes по-прежнему никто не вызывает. Новый канареечный тест tests/help-hermes-section.test.mjs проверяет, что во всех языках есть §30 с языконезависимыми якорями (docs/integrations/HERMES.md, hermes-bridge, #/help, 127.0.0.1, Telegram). Провайдер остаётся заблокированным до контракта API из Phase 5.
  • Это закрывает поставку документация + skill для Phase 5b; интеграция провайдера (Phase 5) остаётся отдельным заблокированным пунктом.
  • Набор: 2378 тестов (+2: tests/help-hermes-section.test.mjs).

[1.146.0] — 2026-08-12

Агент Hermes + Telegram — руководство по интеграции + skill (Phase 5b, часть 1) — вы можете запустить career-ops-ui на облачном сервере и связать его события (завершённый скан, новый отчёт, срочный follow-up) с Telegram через агента Hermes от Nous Research. Этот релиз добавляет документацию по дизайну + развёртыванию и skill hermes-bridge; путь провайдера LLM Hermes остаётся запланированным / пока не подключённым (заблокирован спайком по контракту API Phase 5). Документация опережает код осознанно.

Добавлено

  • docs/integrations/HERMES.md — подробный разбор: две формы интеграции (OpenAI-совместимый endpoint против агентного runtime), развёртывание на облачном сервере (reverse proxy + HTTPS + systemd, контракт «только чтение» с parent на headless-машине), Telegram через Hermes, и список модели угроз «что НЕЛЬЗЯ раскрывать» (никаких CV / зарплат / тел отчётов / ключей в канал).
  • Тизер ## Hermes agent + Telegram в README — короткая ссылка-указатель в англоязычном README, отражённая во всех полностью переведённых локализованных README.
  • Skill hermes-bridge (.claude/skills/hermes-bridge/), реализующий руководство на практике — проверки предпосылок и границ (Node ≥ 18, наличие ключей, доступность endpoint через SSRF-безопасный путь), никогда не пишет секреты на диск/в логи и отказывается выдумывать endpoint Hermes или заявлять, что провайдер подключён.
  • Раздел Integrations в docs/architecture/OVERVIEW.md ссылается на руководство.

Примечания

  • Hermes пока никто не вызывает. Канареечный тест (tests/hermes-docs.test.mjs) проверяет маркеры честности «запланировано / пока не подключено» и то, что в llm-dispatch.mjs нет ветки Hermes/Nous — так что подключение провайдера позже должно обновить документацию + roadmap в том же изменении.
  • Отложено до v1.147.0 (Phase 5b, часть 2): раздел справки в приложении «Hermes & Telegram» H2 × 17 языков и маркетинговая страница cvstart.org.
  • Набор: 2376 тестов (+4: tests/hermes-docs.test.mjs).

[1.145.0] — 2026-08-12

Полезная статистика (продолжение): перестраиваемый график — на вкладке «Тренд целевых ролей» на #/stats появился виджет Построить график: выберите метрику × измерение, и он перерисовывается вживую. Запрос UX от пользователя (без parent-sync).

Добавлено

  • Перестраиваемый график метрика × измерение — выберите метрику (Вакансии / Медианная зарплата / Средняя зарплата) и измерение (По странам / По ролям), и столбчатый график мгновенно перерисовывается. Зарплатные метрики учитывают валюту + переключатель год ⇄ месяц; вакансии — простой счёт.
  • 8 новых ключей i18n × 17 локалей; snapshot 1200 → 1208.

Примечания

  • Проверено в браузере (0 ошибок консоли). Набор: 2372 тестов (+2).

[1.144.0] — 2026-08-12

Настройки и фильтры (Фаза 4, часть 1): включение/отключение отслеживаемых порталов — теперь можно включать и выключать отслеживаемую компанию на #/portals, и сканер это учитывает. Запрос UX от пользователя (без parent-sync).

Добавлено

  • Переключатель Включить/Отключить по компании на #/portals — один клик отключает портал (EN-сканер уже пропускает компании с enabled: false, так что отключённый портал выпадает из всех будущих сканов) или включает обратно, с оптимистичным тостом.
  • POST /api/portals/toggle — явная пользовательская запись, которая хирургически и с проверкой парсинга переставляет флаг enabled компании в portals.yml (комментарии, порядок и остальные поля сохраняются). 5 новых ключей i18n × 17 локалей; snapshot 1195 → 1200.

Примечания

  • Изменений в сканере — ноль: en-scanner.mjs уже фильтрует enabled !== false. Набор: 2370 тестов (+3).

[1.143.0] — 2026-08-12

Понятность (продолжение): подсказки ? на основных рабочих страницах — аффорданс ? теперь охватывает девять главных страниц действий, на всех языках. Правка UX по репорту пользователя (без parent-sync).

Добавлено

  • Подсказка ? ещё на 9 заголовках — #/scan, #/evaluate, #/cv-studio, #/tracker, #/config, #/deep, #/batch, #/auto, #/apply получили встроенный ? (через HelpHint.title), открывающий локализованное «что это / как использовать / какой результат» — тот же CSP-безопасный компонент из v1.139.0.
  • 9 новых ключей i18n × 17 локалей (help.hint.scan/…/apply); snapshot 1186 → 1195.

Примечания

  • Проверено в браузере (0 ошибок консоли). Набор: 2365 тестов (+1).

[1.142.0] — 2026-08-12

Исправление: больше нет «Unknown» карьерного архетипа — #/orientation теперь всегда ранжирует из восьми названных карьерных векторов, а не отвечает иногда «Unknown» и не советует «удвоить ставку» на нём. Правка по репорту пользователя (без parent-sync).

Исправлено

  • #/orientation — промпт ИИ теперь запрещает архетип вне набора. Модель ОБЯЗАНА ранжировать топ-3 ровно из восьми названных векторов и никогда не отвечать «Unknown»/«N/A»/«недостаточно данных» и не выдумывать метку. При скудном резюме она всё равно называет три ближайших с меньшей уверенностью и указывает, каких доказательств не хватает.

Примечания

  • Только изменение серверного промпта (buildOrientationPrompt); без изменений i18n/схемы. Набор: 2364 тестов (+1).

[1.141.0] — 2026-08-12

Полезная статистика (продолжение): обогащение раздела профинансированных компаний — #/funded стал нагляднее: логотипы компаний, график по сумме финансирования и карточки с раундом / суммой / оценкой находки / рекомендуемым действием. Правка UX по репорту пользователя (без parent-sync).

Изменено

  • #/funded — плоская таблица → сетка карточек. Каждая недавно профинансированная компания теперь карточка с логотипом (по названию через CompanyLogo, запасной вариант — буквенный аватар), чипами раунда + суммы, оценкой находки и рекомендуемым действием от родительского проекта, а также ссылкой на новость о финансировании и датой.
  • Визуализация суммы финансирования — горизонтальный график топ-компаний по раскрытой сумме; свободный текст сумм (“$120M”/“€1.5B”) парсится в величину новым parseAmount. 3 новых ключа i18n × 17 локалей.

Примечания

  • По-прежнему только чтение через GET /api/company-funded; описания и вилки зарплат в ленте финансирования нет. Набор: 2363 тестов (+2).

[1.140.0] — 2026-08-12

Полезная статистика: более богатые данные по зарплатам — разбивка зарплат во вкладке «Мой пайплайн» на #/stats теперь показывает среднее (не только медиану), переключатель в год ⇄ в месяц и таблицу мин · сред · медиана · макс по странам. Первый кусок Фазы 3. Правка UX по репорту пользователя (без parent-sync).

Добавлено

  • Среднее (mean) значение зарплаты — RoleStats.salaryStats теперь возвращает avgUsd вместе с minUsd/medianUsd/maxUsd. Медиана устойчива к выбросам, среднее показывает перекос — вместе они читаются как распределение.
  • Переключатель в год ⇄ в месяц и таблица мин · сред · медиана · макс по странам в разделе зарплат, с учётом выбора валюты и периода. 8 новых ключей i18n × 17 локалей.

Примечания

  • Цифры по-прежнему берутся только из вакансий с распознаваемой зарплатой и нормализуются к USD (ориентировочно). Набор: 2361 тестов (+1).

[1.139.0] — 2026-08-12

Понятность: подсказки ? — переиспользуемая CSP-безопасная кнопка ?, которая по клику объясняет «что это / как работает / чего ожидать» на вашем языке. Правка UX по репорту пользователя (без parent-sync).

Добавлено

  • Всплывающая подсказка ? (window.HelpHint) — круглая ? рядом с заголовком открывает лёгкое, зависящее от темы и зеркалируемое в RTL всплывающее окно, которое отрисовывает локализованное пояснение через UI.md(); доступно (role="tooltip", aria-expanded, закрытие по Escape/клику вне, возврат фокуса) и CSP-безопасно.
  • ? на 5 вкладках #/stats и на 8 заголовках ИИ/аналитических экранов (career-plan, ориентация, two-pager, нетворкинг, мок-интервью, память, funded, еженедельная сводка) — 14 новых ключей i18n × 17 локалей.

Примечания

  • У всех экранов уже был однострочный подзаголовок; ? добавляет более глубокое пояснение по требованию и делает пустые состояния самоочевидными. Набор: 2360 тестов (+4).

[1.138.0] — 2026-08-12

Генерация на языке интерфейса — каждая AI-генерация теперь отвечает на языке, выбранном в интерфейсе, плюс усиление тестов по итогам ревью. Правка UX по репорту пользователя (без parent-sync).

Изменено

  • AI-генерации теперь учитывают язык интерфейса. Если интерфейс на русском, испанском, японском и т. д. — сгенерированный текст возвращается на этом языке, а не всегда на английском. Директива языка вывода проброшена во все эндпоинты генерации — карьерный план, ориентацию, отчёт по рынку, мок-интервью, план нетворкинга, «спросить документацию», предложение заметки памяти и черновик two-pager. Код и идентификаторы остаются на английском (например, ключи YAML в two-pager); локализуются только проза, заголовки и пункты.

Исправлено

  • Страж CSS-ролей цвета (tests/css-role-tokens.test.mjs) — статический канарей, что алиас-токены тёмной темы из v1.137.0 никогда не меняют роль: текстовые токены (--fg/--danger/--ok/…) никогда не используются как background, а токены поверхностей (--card/--panel/--line/…) — как текстовый color, во всём CSS и inline-стилях SPA.
  • Самопроверка XSS-загрузчика UI.md() — тест, который через vm загружает md() из api.js, теперь сразу после извлечения проверяет md('<script>…') и бросает ошибку, если экранирование пропало, — так будущий сбой нарезки падает громко, а не оставляет security-набор зелёным на усечённой функции.
  • Страж прокрутки на #/career-plan — scrollIntoView после генерации выполняется только если превью всё ещё присоединено к документу.

Примечания

  • docs/UX-ROADMAP.md обновлён: подсказки ? + описания страниц + пустые состояния теперь v1.139.0; провайдер Nous Research / Hermes — с гайдом по развёртыванию на облачном сервере + Telegram и скиллом Hermes — отслеживается как Фаза 5 / 5b.
  • Набор: 2356 тестов (+5).

[1.137.0] — 2026-08-11

Исправления читаемости и отображения — контраст в тёмной теме, подписи графиков и карьерный план. Правка UX по репорту пользователя (без parent-sync).

Исправлено

  • Белый текст на белом / чёрный на чёрном в тёмной теме на многих экранах — пятнадцать пользовательских CSS-свойств, на которые ссылались несколько видов (--fg, --panel, --panel-2, --ok, --danger, --card, …), никогда не были объявлены, поэтому они откатывались на захардкоженные светлые/чёрные значения: в светлой теме всё было нормально, а в тёмной — нечитаемо (чипы обзора на #/pipeline, активная вкладка на #/stats, «Активный / Ключей» + “✓ set” на #/config, разделы #/two-pager, пузырь вопроса на #/mock-interview, текст ошибок). Теперь они псевдонимизированы на настоящие тема-зависимые токены, поэтому автоматически следуют за темой — 0 нарушений контраста WCAG-AA во всех 29 видах, подтверждено автоматическим аудитором; активная вкладка на #/config перешла на читаемый затонированный стиль. Регрессионная защита (tests/dark-theme-tokens.test.mjs) удерживает их псевдонимизированными.
  • Подписи графиков на #/stats обрывались посреди слова («Senior Backend Engineer» → «…Enginee») — теперь они обрезаются с эллипсисом, а полная подпись сохраняется в tooltip при наведении.
  • #/career-plan показывал сгенерированный план как необработанный Markdown — теперь он автоматически отрисовывается как форматированный, читаемый текст (редактируемый Markdown остаётся в текстовом поле; кнопка «Предпросмотр» переключает его отображение).

Примечания

  • #/career-plan, #/two-pager, #/stats и еженедельный дайджест интервью не сломаны — они показывают пустые состояния до тех пор, пока вы не сгенерируете план / не появятся данные. Более понятные подсказки на странице и хелп-подсказки ? запланированы следующим шагом (docs/UX-ROADMAP.md).

[1.136.0] — 2026-08-11

Паритет с родительским career-ops v1.26.x (после мейнлайна v1.26.0) — один новый источник сканирования без авторизации, а также волна портов качества и надёжности в код, зеркалируемый web-ui. Реестр теперь насчитывает 79 источников = 74 EN + 5 RU (ALL_ADAPTERS 74).

Добавлено

  • eightfold (Eightfold AI, #2684) — доски по найму талантов через API без авторизации https://<tenant>.eightfold.ai/api/apply/v2/jobs, закреплённый по хосту *.eightfold.ai (фирменный CNAME careers.<company>.com намеренно отклоняется); с пагинацией и защитным лимитом, dead-board-throw, дедупликацией URL. Источник + адаптер + CI-изолированный сьют; появляется в фильтре Source на #/scan и на лендинге.

Исправлено

  • Unicode-совместимые ключи дедупликации и сопоставления ролей (#2569 / #2587 / #2667) — новая общая normalizeTextKey (NFKC, сохраняет буквы/диакритику/цифры любой письменности) заменяет ключи, работавшие только с ASCII: detect-reposts теперь группирует варианты компаний с разной шириной символов/пунктуацией («Acme, Inc.» ≡ «Acme Inc») и никогда не схлопывает разных не-латинских работодателей в один, а role-matcher сворачивает заголовки полной ширины и сохраняет не-латинские токены роли, вместо того чтобы их стирать.
  • fetchJsonWithRetry больше не повторяет запрос при отклонённом редиректе (#2657) — срабатывание guard’а redirect:'error' на 3xx детерминировано, поэтому теперь оно не подлежит повтору и приводит к быстрому отказу вместо расхода бюджета повторов.
  • AND-группы в title_filter.positive (#2552) — разделённое пробелами + внутри позитивной записи теперь требует, чтобы каждый термин встречался в заголовке, в любом порядке.
  • oraclecloud принимает пронумерованные apex-домены тенантов oraclecloud1.com … oraclecloud99.com (#2683) — ограниченное семейство (без ведущего нуля, ≤ 2 цифр), никогда не wildcard-apex.
  • workable укреплён (#2675) — повторы, заголовки, имитирующие браузер, и сериализация запросов против хоста, защищённого Cloudflare.
  • personio откатывается на HTML-скрейп, когда XML-фид отключён, вместо того чтобы возвращать пустой результат.
  • Алиасы FALLBACK в states пересинхронизированы с родителем (#2615).

Примечания

  • Не портировано (не зеркалируется в web-ui или относится только к CLI): reply-matcher (#2672), jd-similarity (#2661), jd-skill-gap (#2686), переопределяемые через env пути скана (#2568) / разбор --flag=value (#2589), а также изменения cover-letter / CV-template / doctor / ollama / generate-pdf. HIGH-уязвимости js-yaml/nanoid в веб-компоненте уже были исправлены в web-ui v1.135.0.

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