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

GitHub

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

[1.228.3] — 2026-08-30

Исправлено — ассистент по документации не отвечал по-английски, названия компаний из Telegram приходили обрезанными, а мониторинг не мог увидеть, что сервис жив.

Исправлено

  • Ассистент отвечал «подходящих разделов справки не найдено» по-английски, хотя тот же вопрос по-русски работал. Поиск был исправен — виноват сборщик контекста. В buildAskPrompt стояло if (ctx.length + sec.body.length > MAX_CONTEXT) break — именно break, поэтому один переросший раздел обрывал цикл и оставлял контекст пустым, а ассистент честно сообщал, что у него ничего нет. Английский §5 вырос до 16 081 байта при бюджете 14 336 (в том числе потому, что v1.228.0 добавила в него telegram_channels), русский весил 13 009 и влезал. Ранжирование в обоих языках одинаковое — §5 первый, — различался только размер.

    Две правки: раздел свыше 6 КБ теперь делится по границам ###, поэтому telegram_channels стал отдельным чанком на 1 340 байт и занимает первое место в обоих языках; а переросший раздел пропускается или усекается, но больше не обрывает цикл. Руководство разбилось на 75 чанков вместо 32.

  • Метка компании в Telegram давала предложение вместо названия. Компания: FinCore Technology (продуктовая разработка, высоконагруженные системы) превращалось в FinCore Technology (продуктовая разработка, высоконагруженны — обрезано на 60 символах, посреди слова. Три строки из 141. Теперь имя заканчивается на первом разделителе, вводящем описание: тире в пробелах (поэтому Coca-Cola уцелеет), запятая, точка с запятой, вертикальная черта или открывающая скобка; остаточное ограничение падает на границу слова. Этот источник приписывает компанию как факт, и обрезанное предложение неверно ровно так же, как выдуманное имя, — только менее заметно.

Добавлено

  • GET /api/ping — проба живости, не раскрывающая ничего. Только { ok, version }. /api/health сообщает абсолютные пути, настоящее имя владельца профиля и то, какие API-ключи заданы: это законно для аутентифицированного оператора и недопустимо для открытого интернета. У мониторинга за Basic-аутентификацией до сих пор вообще не было способа увидеть, что сервис жив.

[1.228.2] — 2026-08-30

Исправлено — поиск из двух слов в /api/scan-results искал фразу, а не слова, и сильно недобирал.

Исправлено

  • ?q= теперь сопоставляет каждое слово по отдельности, а не фразу целиком. Запрос был одним includes(q) по заголовку, компании и локации, поэтому продакт менеджер находил только строки, где эти два слова стоят именно рядом, — 32 из 162, которые содержат оба. «Менеджер продукта» и «Менеджер по развитию продукта» были для него невидимы: те же слова, но в другом порядке или с наполнителем между ними.

    Так узкое совпадение превращается в неверный ответ. Telegram-ассистент на просьбу найти всех продуктовых менеджеров сообщил 9 — честно, потому что 9 ему и ответил эндпоинт. Его число было правдивым, а вот число эндпоинта — нет. Порядок слов и то, что стоит между ними, человек, набирающий два слова, закреплять не собирался.

    Сопоставление идёт по И, так что расширение не стоит точности: строка обязана содержать каждое слово. менеджер разработчик вернёт ноль, а не объединение.

[1.228.1] — 2026-08-29

Исправлено — висячий симлинк в репозитории, молча обрезавший деплой.

Исправлено

  • .claude/skills/refero-design был отслеживаемым симлинком на путь, которого здесь никогда не было. Он указывал на ../../.agents/skills/refero-design, то есть на .agents/ в корне репозитория; такой директории нет, а сама ссылка попала сюда в пакете паритета v1.118.0 с машины, где вложенность папок была другой. С тех пор любой, кто клонировал репозиторий, получал битую ссылку.

    И она не была безобидной. rsync прерывается на ней посреди передачи — сегодняшний деплой отчитался об успехе, пока сервер продолжал работать на прежней версии, и расхождение всплыло лишь потому, что версию проверили после. Родительский node test-all.mjs тоже падает, если чекаут web-ui лежит внутри его дерева: копия фикстур обходит рабочую директорию и не может сделать stat по ссылке. Ссылку никто не использовал: единственные совпадения refero в репозитории — польское слово preferowany.

[1.228.0] — 2026-08-29

Добавлено — Telegram-каналы как источник сканирования и постраничный режим запроса у /api/scan-results.

Добавлено

  • Telegram-каналы стали сканируемым источником. Отдельный блок telegram_channels: в portals.yml перечисляет каналы; каждый читается через публичный веб-превью https://t.me/s/<канал>. Реестр: 85 → 86 источников (81 EN + 5 RU), ALL_ADAPTERS 80 → 81.

    У Telegram нет своего RSS, а Bot API не позволяет читать канал, которым бот неадминистрирует. Оба очевидных пути закрыты. Превью /s/ — обычный HTML с сервера: без входа, без JS, без куки. Проверено на 16 каналах: 15 отдали по 20 постов на голый GET.

    channel: принимает любую запись — rabotaphp, @rabotaphp, https://t.me/rabotaphp, даже ссылку на конкретный пост. Парсер опирается на три вещи, которые редизайн Telegram сдвинет в последнюю очередь: data-post="<канал>/<id>" (он же разбивает страницу на посты и несёт единственный стабильный URL поста), контейнер текста сообщения и ISO-дату <time datetime>.

  • У GET /api/scan-results появился постраничный режим запроса. ?q=<текст>&limit=50[&region=ru|en][&set=filtered|fresh][&offset=N] возвращает { total, returned, offset, limit, set, rows }, где total — количество ДО пагинации. Без параметров роут по-прежнему отдаёт снимок целиком: от этой формы зависит таблица #/scan.

Исправлено

  • Telegram-ассистент отвечал по доле данных. Его навык уже указывал на /api/scan-results, но роут умел отдавать только весь снимок — около 2 МБ на реальной машине, что не влезает в контекст агента. На «найди все Продуктовый менеджер» он сообщил 9 совпадений там, где в снимке их были сотни, и честно сказал, что «работает с ограниченным локальным индексом». Это было правдой: эндпоинт он видел, а съесть не мог. С постраничным режимом тот же вопрос стоит 2 КБ и возвращает честный total. Навык тоже переписан: он предлагал сводить новые посты, то есть указывал на fresh (только последний прогон) вместо filtered (всё оставленное).

Примечания

  • Пост канала — это проза, а не запись о вакансии. В отличие от всех прочих источников здесь нет структурированных полей: источник берёт заголовок из первой содержательной строки, компанию/город/вилку — по явным меткам («Компания:», «Локация:», сумма с валютой), а полный текст кладёт в описание. Отделять настоящие вакансии от рекламы и дайджестов оставлено существующему title_filter — тому же, что уже настроен для других источников. Это лиды, а не готовый список.
  • Компания никогда не угадывается. Без метки «Компания:» запись атрибутируется каналу (@rabotaphp), потому что неверный работодатель попадает в трекер как факт.
  • Пустой разбор — это ошибка, а не «вакансий нет». На закрытый или несуществующий канал t.me отвечает редиректом; вернуть ноль строк значило бы показать спокойный день и навсегда спрятать опечатку в конфиге. jobGeeks из исходного списка ведёт себя именно так (HTTP 302) и намеренно не добавлен.
  • Определение удалёнки использует Unicode-границу слова вместо \b: \b работает только с ASCII, поэтому пробел перед кириллическим словом границей не является и \bудал никогда не совпадал с «удалёнка» — та же ловушка, что и в фильтре заголовков в v1.227.3, здесь пойманная тестом до релиза.
  • Настроено и проверено живьём 15 каналов: 299 постов, 15/15 каналов, salary_pm дал 13 вилок из 20. 30 новых тестов. Тесты: 2865 → 2895.
  • Счётчики тестов в README подскочили на сотни — и нет, этот релиз их не добавил. В локализованных README лежали замороженные значения: 1856 / 1945 / 1955 / 549 / 419 / 284 / 2527 — каждое устарело со своего релиза, и некоторые противоречили бейджу на той же странице. Теперь везде 2895, и это проверено прогоном набора, а не переписыванием бейджа. Так же устарели Playwright (70 / 90 / 12 → 101), smoke e2e (20 → 21), модули маршрутов (12 / 32 → 37), потоки smoke (12 → 22) и число файлов тестов (218 → 329). Строка Итого дублировала счётчик юнитов и потому не сходилась с тремя наборами ниже — теперь там настоящая сумма (3040).

[1.227.5] — 2026-08-29

Исправлено — региональный скан выедал память сервера, и один Playwright-сьют флейкал на собственном teardown.

Исправлено

  • Региональный скан ронял сервер по OOM. ru-scanner накапливал все сырые хиты по всем запросам и дедуплицировал с фильтрацией только в самом конце. Список запросов намеренно состоит из близких синонимов (Golang, Go разработчик, Golang разработчик, Senior Go…), поэтому одна и та же вакансия возвращается по разу на запрос, и массив держал кратно больше объектов, чем уникальных — вместе с описаниями, которые составляют основную массу объекта и почти все всё равно отсеиваются фильтрами. Замерено: 742 МБ кучи на реальном прогоне из 21 запроса. Сервер ограничивает кучу Node 490 МБ (V8 считает лимит от 956 МБ RAM машины), поэтому скан валил сервис с FATAL ERROR: Reached heap limit — четыре раза за сутки. Теперь дедуп и фильтрация идут по каждому запросу, и тот же прогон уложился в 177 МБ, минус 76%. Отчётные числа не изменились: уникальный итог несёт Set из URL-строк, так что «Total found» значит ровно то же, что и раньше.
  • net::ERR_SOCKET_NOT_CONNECTED роняла CI случайным образом. Три Playwright-сьюта вызывают window.stop(), чтобы оборвать незавершённые запросы перед проверкой «консольных ошибок нет». Chromium сообщает об одном и том же намеренном действии тремя именами в зависимости от состояния сокета: ERR_ABORTED при отмене запроса, ERR_EMPTY_RESPONSE в полёте и ERR_SOCKET_NOT_CONNECTED, когда сокет уже разорван. Общий фильтр знал только первые два, поэтому один и тот же teardown ронял прогон наугад. Всплыло на прогоне по локалям в tr — сьюте с наибольшим числом навигаций, — тогда как playwright-forms и playwright-smoke, которые тоже вызывают window.stop(), были в одном неудачном прогоне от того же.

Примечания

  • Почему скан начал падать только сейчас. Список запросов вырос с 14 до 21, когда добавили поиски продакт-менеджера. Память старой реализации росла с общим числом хитов, поэтому +50% запросов пробили потолок машины. С новыми запросами всё в порядке — они лишь вскрыли схему выделения памяти, которая давно была в одном изменении конфига от отказа.
  • Правка дедупа сохраняет поведение, и это проверено, а не заявлено. tests/ru-scanner-dedup-order.test.mjs фиксирует неудобное направление — более поздний дубликат, НЕ проходящий фильтры, обязан вытеснить более ранний прошедший, потому что старый проход в конце оставлял только последнюю копию и затем её отфильтровывал — и прогоняет 200 рандомизированных сверок против прежней реализации «накопить, потом отфильтровать».
  • Фильтр консоли остаётся узким. Безобидно только семейство отменённых запросов. Отказ в соединении, сбой DNS, сброс, любой 5xx и любое необработанное JS-исключение по-прежнему валят проверку — это закреплено новыми тестами.
  • Две гипотезы были проверены и отброшены, а не отправлены в релиз: таймаут Caddy (конфиг чистый, flush_interval -1 для SSE верен) и дефолтный 5-минутный requestTimeout Node (уменьшенная репродукция показала, что он длинный стриминговый ответ не рвёт). Настоящей уликой стал Reached heap limit в журнале. Тесты: 2860 → 2865.

[1.227.4] — 2026-08-29

Исправлено — word: / stem: всё ещё игнорировались в content_filter; v1.227.3 починил только фильтр заголовков.

Исправлено

  • content_filter не учитывал префиксы. v1.227.3 научил title_filter родительским префиксам word: / stem:, но content_filter остался на собственном сыром lower.includes(k), так что запись с префиксом там по-прежнему сопоставлялась как буквальный текст "word:java" — та же молчаливая пустышка, на один фильтр дальше. content_filter читает ОПИСАНИЕ вакансии, и его умолчание всегда было простой подстрокой — именно поэтому голый негатив java отбрасывает всё, где просто упомянут «JavaScript»; префикс и есть способ отказаться от этого для одной записи. Портирован родительский compileContentKeyword: он использует ту же машинерию префиксов, но намеренно не привязывает короткие ключи к границам слова — фильтр заголовков привязывает аббревиатуры из 2–3 букв, потому что «COO» внутри «Coordinator» всегда ошибка, тогда как короткая последовательность внутри абзаца прозы обычно осмысленна (aws, gcp, sql, go).

Примечания

  • Найдено в AI-ревью PR v1.227.3, которое заметило, что код не покрывает то, что заявляет его же комментарий — там было «title_filter / content_filter», хотя подключён был только путь заголовков. Теперь комментарий явно разделяет: префиксы работают в обоих фильтрах, автопривязка аббревиатур — только в заголовках.
  • Тоже проверено по тому ревью и не изменилось: перевод правила для аббревиатур 2–3 букв с \b на Unicode-границу поведенчески идентичен для ASCII и строже только там, где нужно — COO_lead и coo1 по-прежнему сохраняются (подчёркивание и цифра — символы слова в обоих вариантах), Coordinator сохраняется, «Директор COO» и «VP «Продукт»» корректно отбрасываются. Проверено 14 краевых случаев.
  • Записи content_filter без префикса сохраняют прежнее сопоставление байт в байт, пустое/отсутствующее описание по-прежнему проходит, а голый word: / stem: не матчит ничего вместо всего. 6 новых кейсов в tests/content-filter.test.mjs. Тесты: 2852 → 2858.

[1.227.3] — 2026-08-29

Исправлено — префиксы word: / stem: в фильтре заголовков молча игнорировались, превращая эти строки фильтра в пустышку.

Исправлено

  • web-ui не реализовывал родительские префиксы word: / stem:. title_filter (и content_filter) по умолчанию сопоставляют подстроку без учёта регистра — именно поэтому голый негатив intern отбрасывает и «International Product Manager», и «Internal Tools Engineer». Вместо смены умолчания (ломающей все настроенные установки) родитель сделал точность опциональной для каждой записи: word:intern матчит только целое слово, stem:agent должен начинать слово и может продолжаться (отделяя «Agentforce» от «Reagents»). В web-ui не было ни того, ни другого, поэтому запись с префиксом сопоставлялась как буквальный текст "word:intern", которого нет ни в одном заголовке вакансии. Строка становилась молчаливой пустышкой: тот же portals.yml корректно фильтровал через CLI и совсем не фильтровал здесь, так что все стажёрские вакансии, которые пользователь исключил, всё равно попадали в таблицу #/scan. Портировано из родительского title-keywords.mjs, включая границу слова с учётом Unicode (\p{L}\p{M}\p{N}_) вместо \b, который работает только с ASCII и матчил внутри слова в заголовках с диакритикой и кириллицей.

Примечания

  • Найдено при проверке живого portals.yml, куда только что добавили word:intern именно чтобы голый intern перестал выбрасывать «International Product Manager». В CLI префикс работал; web-ui тихо оставлял все стажёрские вакансии. Теперь поведение идентично.
  • Умолчания не изменились. Запись без префикса сохраняет прежнее сопоставление: подстрока для фраз и всего, что содержит не-буквы (.NET, SAP , L&D), и привязка к границам слова для аббревиатур из 2–3 букв. Голый word: / stem: без продолжения считается опечаткой и не матчит ничего — как негатив пустой шаблон совпал бы с любым заголовком и зарубил бы весь скан из-за одного случайного двоеточия.
  • Правило для аббревиатур 2–3 букв переведено на ту же Unicode-границу: раньше оно использовало \b, из-за чего coo и word:coo расходились на не-ASCII заголовках — два написания одного правила.
  • 6 новых кейсов в tests/title-filter.test.mjs. Тесты: 2846 → 2852.

[1.227.2] — 2026-08-29

Исправлено — #/config уезжал вбок на узких телефонах.

Исправлено

  • MOBILE-1: #/config выходил за экран по горизонтали на вьюпортах ≤ 352 px. Каждое поле-селект несло инлайновый min-width: 300px. Инлайновый стиль перебивает таблицу стилей, поэтому контрол не мог ужаться вместе с контейнером: на вьюпорте 320 px правый край селекта оказывался на 353 px, страница получала горизонтальную прокрутку, а контролы уезжали за правый край. Затронуты все 18 селектов провайдеров/моделей (cfg-llm-provider и все cfg-*-model). Теперь min-width: min(300px, 100%) + max-width: 100%: комфортные 300 px применяются только когда есть место. Устройства на пороге и ниже: iPhone SE 1-го поколения (320 px), старые Android, внешний экран Galaxy Fold.

Примечания

  • Почему все прошлые мобильные прогоны это пропускали. На 360 и 375 px запаса ровно хватает, чтобы overflow не проявился, а Playwright-наборы работают в окне 1280 px. Ни один тест в CI никогда не мерил вьюпорт уже 375 px. Новый tests/playwright-narrow-viewport.mjs проходит маршруты с формами на 320 px и падает на любом горизонтальном overflow, называя в сообщении самый широкий элемент-нарушитель; второй кейс фиксирует, что ни один селект #/config не выходит за вьюпорт. Сначала было проверено, что тест воспроизводит баг (scrollWidth 353 > clientWidth 320, 18 селектов с правым краем 353). Playwright: 99 → 101.
  • Известно и намеренно пока не под гейтом: 280 px (внешний экран Galaxy Fold) всё ещё уезжает примерно на 5 px, но по другой причине — длинный контент внутри блоков <details> карточки конфига и сайдбар 256 px против вьюпорта 280 px, а не инлайновый min-width, который чинит этот релиз. Переверстка под ширину, которой не заявляет ни один массовый телефон, — отдельная работа; список WIDTHS в сьюте к ней готов.
  • Также исправлен QA-промт v1.227.1, где маршрут фоллоу-апа был назван #/modes/followup. Mode-страницы регистрируются «голым» слагом (Router.register(cfg.slug)), поэтому маршрут — #/followup, а #/modes/followup корректно отдаёт 404. Только документация.
  • Юнит-сьют без изменений — 2846 (новое покрытие браузерного уровня).

[1.227.1] — 2026-08-29

Исправлено — разбивка источников в справке не сходилась сама с собой, а GitHub-баннер печатался в приложении сырой разметкой.

Исправлено

  • §17 давал итог и разбивку, которые не складывались. Во всех бандлах справки было «registry server/lib/sources/ поставляется с 85 адаптерами — 78 английских + 5 русских». 78 + 5 = 83, а не 85; в ja/ko дрейф зашёл дальше — 77 (= 82). Каждый новый источник двигал итог и не трогал слагаемое, и числа расходились уже несколько релизов. Теперь разбивка — 80 EN + 5 RU во всех 17 локалях. Это важнее косметики: docs/help/<lang>.md — единственный корпус для «Ask the docs», поэтому на вопрос «сколько источников поддерживается» пользователь получал два противоречащих числа из одного предложения.
  • Там же был устаревший якорь версии. «По состоянию на v1.213.0 registry …» дожил в 16 локалях, пока счётчик двигали дважды (83 в v1.219.0, 85 в v1.226.0). Якорь уже чинили вручную в v1.210.1 — и он вернулся. Теперь он убран в пользу формулировки без версии: сам счётчик проверяется против живого реестра, который устареть не может.
  • Баннер провайдеров печатался экранированной разметкой вверху #/help. В v1.225.0/v1.225.1 записано, что его «вырезает UI.md() (нет поддержки картинок)». Это смешивает две разные вещи: UI.md() экранирует-первым — он экранирует каждый байт источника до любых markdown-преобразований, и это правильная XSS-граница, но противоположность вырезанию. Баннер никто не удалял, потому что этого никто и не пытался, — и все 17 локалей открывали #/help 268 символами литерального <p align="center"><img src="https://…. Новый общий вход (server/lib/help-markdown.mjs::stripGithubOnlyBlocks) убирает отдельно стоящие HTML-блоки с картинкой при чтении — и для GET /api/help/:lang, и для корпуса docs-assistant. В файлах баннер остаётся, поэтому на GitHub витрина по-прежнему рендерится.
  • Колонка «#» на доске каденции была пустой с v1.117.0. Она читала entry.appNum, тогда как followup-cadence.mjs отдаёт num — поля appNum в payload не было никогда. Теперь читаются оба. Строка «Next up» из v1.227.0 унаследовала ту же ошибку и починена вместе с ней.

Примечания

  • Все четыре прожили незамеченными, потому что их ничто не проверяло. Гейты справки (canonical-docs-coverage, help-ui, help-ru-config-section) считают ЗАГОЛОВКИ — 32 H2 / 121 H3, и ни один тест никогда не читал число внутри абзаца и не смотрел, что реально отдаёт вход справки. Это закрывают два новых сьюта: tests/help-source-counts.test.mjs (заявленный итог равен SOURCES.length; слагаемые равны живому EN/RU-разбиению и дают в сумме итог; в абзаце нет версии) и tests/help-banner-strip.test.mjs (баннер остаётся на диске для GitHub, не переживает strip, первая отрисованная строка каждого бандла — его заголовок, strip остаётся узким, и оба потребителя идут через него). Оба проверены на падение против ровно этих багов до коммита. Тесты: 2837 → 2846.
  • Реестр, сканер и контракты роутов не менялись; источников по-прежнему 85 (80 EN + 5 RU), ALL_ADAPTERS 80.

[1.227.0] — 2026-08-28

Добавлено — SEEK Гонконг (JobsDB), фильтр «только срочные» для фоллоу-апов и нераскрытые работодатели теперь показывают посредника.

Добавлено

  • JobsDB Гонконг через существующий источник Jobstreet/SEEK. Гонконг работает под брендом JobsDB, но стоит на той же платформе SEEK за тем же эндпоинтом v5, поэтому отдельный источник не нужен — достаточно hk.jobsdb.com в списке разрешённых хостов и siteKey: HK-Main. Реестр не меняется: 85 источников (80 EN + 5 RU), ALL_ADAPTERS 80 — это новый РЫНОК на существующем адаптере, а не новый адаптер.
  • Фильтр «только срочные» на доске каденции (#/modes/followup). Новый чекбокс сужает доску до того, что требует действия прямо сейчас. Срочность читается из urgency, которую уже вычислил родительский followup-cadence.mjs (urgent / overdue), и никогда из status трекера — там applied/responded/interview, что не совпало бы ни с чем. Когда срочного нет, доска называет ближайший запланированный фоллоу-ап вместо пустого экрана. Логика вынесена в чистый модуль public/js/lib/followup-view.js — зеркало родительского followup-view.mjs.
  • Нераскрытые работодатели атрибутируются агентству-посреднику. Вакансия со скрытым работодателем записывается в трекер как ?, а рекрутёр попадает в колонку Via. #/tracker показывал голый ?, поэтому несколько нераскрытых строк были неразличимы, а резолверу логотипов не за что было зацепиться. Теперь такая строка читается как «Не раскрыт · через <агентство>», логотип берётся по имени агентства, и строка находится по этому имени в поиске. Это только отображение — каноническое поле company никогда не перезаписывается рекрутёром. Новый чистый модуль public/js/lib/company-presentation.js — зеркало родительского company-presentation.mjs.

Исправлено

  • Jobstreet/SEEK обращался к устаревшему эндпоинту. Источник по-прежнему указывал на chalice-search v4 (/api/chalice-search/v4/search), который SEEK отключил; родитель перешёл на /api/jobsearch/v5/search, а web-ui за ним не последовал. Теперь любая запись Jobstreet/SEEK запрашивает v5. Форма элемента v5 читается нативно (URL строится из id, locations[0].label, advertiser.description, salaryLabel), проекционный параметр solrFields из v4 убран, а запись, у которой в portals.yml всё ещё зафиксирован мёртвый путь v4, сохраняет хост своего рынка, а путь пересобирается на v5 — правки от пользователя не нужны.

Примечания

  • Паритет с career-ops 1.30.0 (HEAD родителя 8d64f65 → 6cc46a4; его VERSION всё ещё 1.30.0 — release-please отстаёт от main). Не портировано, с причинами: заполнение локации из JSON-LD у iCIMS — у источника iCIMS в web-ui вообще нет хука enrichDate() (только страницы списка), поэтому чинить нечего; нефатальный stderr CLI (#1974) — раннер web-ui определяет сбой только по коду выхода и никогда не имел этой эвристики; трассировка путей Turbopack — в web-ui нет сборщика; фиксы cv-sync-check — ретранслируются только на чтение и fail-soft; последовательный Playwright в scan.mjs, ответы Block H, guard-ы updater/doctor/update-system, рекурсивная проверка синтаксиса, дашборд на Go и добавленные самотесты jd-skill-gap/verify-cv-facts — только-CLI поверхности, в которые web-ui не заходит, либо самотесты без изменения поведения. Тесты: 2818 → 2837.

[1.226.0] — 2026-08-27

Добавлено — два вьетнамских источника вакансий: ITviec и CareerViet.

Добавлено

  • ITviec + CareerViet (Вьетнам). Две вьетнамские площадки с привязкой хоста подключаются к сканеру записями provider: itviec / provider: careerviet: ITviec (itviec.com, техно) и CareerViet (careerviet.vn, широкий охват). Оба — HTML-скраперы без токенов с SSRF-привязкой хоста, только-HTTPS, fail-soft по компаниям и лимитами страниц. Реестр теперь несёт 85 источников (80 EN + 5 RU), ALL_ADAPTERS 80 — оба видны в фильтре «Источник» на #/scan.

Примечания

  • Паритет с career-ops 1.30.0 (parentVersion 1.29.0 → 1.30.0). Не портировано (только CLI/сообщество или уже есть): Hired Wall, /calibrate, clean-markers, паки шаблонов, doctor, pdf/LaTeX и CLI eval/tracker/scan, в которые web-ui не заходит; фикс _html-to-text и roleFuzzyMatch уже были; префикс title-filter stem: отложен. Тесты: 2818.

[1.225.1] — 2026-08-27

Исправлено — баннер логотипов провайдеров теперь отображается в README и руководстве.

Исправлено

  • Баннер v1.225.0 не показывался на GitHub: у отдельного images/providers.svg был некорректный font-family (вложенные двойные кавычки), который GitHub отклоняет; <img> использовал относительный src (raw HTML <img> не разрешает относительные пути на GitHub); а у блока <p> не было завершающей пустой строки, из-за чего он сливался со следующей строкой. Теперь он указывает на отрендеренный images/providers.png по абсолютному raw-URL, с разделением пустыми строками; SVG также исправлен до валидного XML.

Примечания

  • Только доки; без изменений кода и тестов. Витрина cvstart.org и плитки в приложении уже были корректны (встроенный SVG, не этот файл). 2784

[1.225.0] — 2026-08-27

Добавлено — баннер логотипов провайдеров в README и руководстве.

Добавлено

  • Баннер логотипов провайдеров. Единый самодостаточный images/providers.svg — 18 LLM-провайдеров: реальный логотип бренда (simple-icons, CC0) для 11, публикующих иконку, и монограмма в цвете бренда для 7 остальных — теперь возглавляет README (×17) и встроенное руководство. (Само приложение рисует плитки с реальными логотипами; баннер отображается на GitHub, а XSS-безопасный рендерер приложения его убирает.)

Примечания

  • Только доки — без изменений кода и тестов; SVG встроенный (без удалённых ресурсов). Источники сканирования без изменений: 83. Тесты: 2784.

[1.224.0] — 2026-08-26

Изменено — плитки LLM-провайдеров теперь показывают реальные логотипы брендов.

Изменено

  • Реальные логотипы провайдеров. Плитки провайдеров — поля API-ключей в Настройках, строки Использования, чип дашборда, сводка «Активен» в Настройках, результат ⚡-оценки и приветственный баннер — теперь отображают реальный логотип бренда для 11 провайдеров, публикующих open-source-иконку (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub · DeepSeek · Kimi · MiniMax · Mistral · Ollama; одно-путевые SVG из simple-icons, CC0). Семь без опубликованной иконки (Hermes · GLM/Z.ai · Grok · Together · Fireworks · BytePlus Ark · Volcengine Ark) сохраняют монограмму в цвете бренда. По-прежнему CSP-безопасно по построению: путь логотипа — встроенная статическая константа, без удалённых ресурсов и без innerHTML.

Примечания

  • Без изменений маршрутов и поведения; тот же API ProviderLogo.el(slug). provider-logo.js вырос (встроенные пути логотипов). Источники сканирования без изменений: 83. Тесты: 2784.

[1.223.0] — 2026-08-26

Исправлено — успешная ⚡ live-оценка красилась как ошибка; плюс установка coworker OpenWorker прямо из его GitHub-URL или .zip.

Исправлено

  • Тон бейджа результата ⚡-оценки. Успешная live-оценка больше не красится как провал. In-process провайдеры (OpenRouter, DeepSeek, …) не возвращают код выхода подпроцесса, поэтому строгое code === 0 было ложным и любой успех выходил красным (при этом показывая «exit 0»). Тон теперь следует за результатом — красным красит только подпроцесс с ненулевым кодом; live-результат без кода — badge-ok, а суффикс «· exit N» показывается только при реальном коде выхода.

Изменено

  • Установка coworker OpenWorker из его GitHub-URL / .zip. В документации coworker теперь описаны установщик одной командой (curl … | bash, идемпотентный — переиспользует существующие career-ops / web-ui) и три пути установки в OpenWorker (GitHub URL · .zip · импорт одного файла) в docs/integrations/openworker.md, встроенной справке §32 (×17) и README.

Примечания

  • Закрыта давняя дыра в покрытии: у источника Recruitee появился сквозной тест на фикс v1.214.2 с кавычками вокруг угловой скобки (> внутри атрибута тега не должен утекать в описание). Источники сканирования без изменений: 83. Тесты: 2783.

[1.222.0] — 2026-08-26

Изменено — подсказки полей расширенных провайдеров (DeepSeek … Volcengine Ark) теперь локализованы на все 17 языков.

Изменено

  • Локализация подсказок полей провайдеров. Каждый config.<slug>Hint для 11 OpenAI-совместимых провайдеров, добавленных в v1.216.0–v1.217.0 (DeepSeek · GLM/Z.ai · Kimi · MiniMax · Mistral · Grok · Together · Fireworks · Ollama · BytePlus Ark · Volcengine Ark) — подсказки API-ключа, модели и базового URL в разделе Настройки → API-ключи — теперь переведён на все 17 локалей вместо отката к английскому. URL регистрации, id моделей и маркер ⚡ live-eval сохранены дословно.

Примечания

  • Поведение не меняется — английский hintFallback в field-specs.js по-прежнему страхует отсутствующий ключ, а новый контрактный тест проверяет, что каждый hintKey дескрипторов полей резолвится во всех 17 локалях. Источники сканирования без изменений: 83. Тесты: 2779.

[1.221.0] — 2026-08-26

Безопасность — эшелонированная защита от DNS-rebinding на пути fetch сканера.

Безопасность

  • HTTP-ядро сканера (fetchJson / fetchText в server/lib/http-json.mjs) теперь резолвит хост каждого источника на реальном сетевом пути и отказывается подключаться, если он резолвится в приватный, loopback, link-local, CGNAT или адрес облачных метаданных — закрывая вектор DNS-rebinding. Хосты сканера уже привязаны к публичным доменам, так что это эшелонированная защита; путь пользовательских URL уже имел более сильную привязку соединения (safe-fetch.mjs). Guard работает только на реальном транспорте fetch — внедрённый в тестах fetchImpl никогда не резолвится.

Примечания

  • Для нормального сканирования поведение не меняется. Источники сканирования без изменений: 83. Тесты: 2775.

[1.220.0] — 2026-08-26

Добавлено — сканируйте несколько категорий Get on Board из одной записи.

Добавлено

  • Запись getonbrd теперь может задать categories: [programming, operations-management, machine-learning-ai] (или один category:) вместо только фида programming; вакансии дедуплицируются по URL между категориями, лимит — 12. Существующие записи не меняются (по умолчанию по-прежнему programming).

Примечания

  • Только сервер; SSRF-привязка хоста (www.getonbrd.com) и redirect:'error' неизменны, а неверный слаг категории отклоняется до запроса. Источники сканирования без изменений: 83. Тесты: 2771.

[1.219.0] — 2026-08-26

Добавлено — Torre в сканере, и теперь каждая метка провайдера корректна.

Добавлено

  • Torre (provider: torre) — пан-латиноамериканский маркетплейс талантов torre.ai, публичный поиск без авторизации с удалёнными вакансиями, которых нет в Greenhouse/Lever/Ashby; один ограниченный запрос на запись. Реестр теперь содержит 83 источника (78 английских + 5 русских). Плюс монограмма активного провайдера в приветственном баннере.

Исправлено

  • Сканирование a16z Speedrun завершалось раньше времени на доске с ротацией (короткая страница в середине обхода принималась за последнюю, обрезая до 149 из ~300); теперь доверяет total_pages фида. А приветственный баннер определял провайдера по карте из 4 записей (показывая сырой слаг для остальных 14) — теперь использует метку+монограмму из 18; заметка о провайдерах в настройках теперь 18 вместо 7.

Примечания

  • Опция нескольких категорий на запись у Get on Board ещё не подключена (нужен рефакторинг на уровне источника). Тесты: 2768.

[1.218.0] — 2026-08-26

Добавлено — значки провайдеров (и правильные имена) везде, где показан провайдер.

Добавлено

  • Монограммы провайдеров на чипе дашборда, в сводке «Активен» настроек и в заголовке результата ⚡ оценки.

Исправлено

  • Устаревшие метки: три экрана определяли имя из карты на 5 записей (результат оценки помечал любого не-Anthropic провайдера как «Gemini»); теперь используют общий ProviderStatus.label (18). Счётчик ключей показывал «/ 7» → 18. В список слагов подсказки LLM_PROVIDER добавлены ark / arkcn во всех 17 языках.

Примечания

  • Только клиент, безопасно для CSP. Без изменений сервера. Источники сканирования: 82. Тесты: 2758.

[1.217.1] — 2026-08-26

Усиление тестов — покрытие эндпоинтов для всего списка провайдеров.

Добавлено

  • Параметризованный тест run<Provider>, проверяющий эндпоинт, модель по умолчанию и Bearer-аутентификацию для Kimi, MiniMax, Mistral и Fireworks.

[1.217.0] — 2026-08-26

Добавлено — Ark присоединяется к списку LLM (18 провайдеров). BytePlus Ark и Volcengine Ark — модели Doubao от ByteDance — теперь провайдеры ⚡ живой оценки через тот же OpenAI-совместимый код и базовый URL по региону.

Добавлено

  • BytePlus Ark (ARK_API_KEY, международный) и Volcengine Ark (ARK_CN_API_KEY, Китай) — OpenAI-совместимый Chat Completions через runOpenAICompatible(). Задайте ARK_MODEL / ARK_CN_MODEL (имя модели Doubao или ваш id эндпоинта ep-…); ARK_BASE_URL / ARK_CN_BASE_URL переключают регион. Оба встают в порядок auto (последними), с монограммой в настройках и «Использовании». Веб-интерфейс охватывает теперь 18 провайдеров.

Примечания

  • Тот же путь доверенной конфигурации + проверки схемы http(s), что и у остальных (никогда не SSRF-валидатор URL вакансий). Источники сканирования без изменений: 82. Тесты: 2755.

[1.216.0] — 2026-08-26

Добавлено — ещё девять LLM-провайдеров, у каждого своя фирменная плитка. ⚡ Живую оценку теперь можно запускать через DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together, Fireworks или полностью локальный Ollama — достаточно одного ключа, рядом с полем каждого в настройках стоит монограмма.

Добавлено

  • Девять OpenAI-совместимых провайдеров. DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together AI, Fireworks AI и Ollama (полностью локально, без ключа). Задайте ключ — или OLLAMA_BASE_URL для Ollama — в Настройках, и провайдер встанет в порядок auto после Hermes; закрепите любой через LLM_PROVIDER. Together также хостит Inkling от Thinking Machines (thinkingmachines/Inkling). Веб-интерфейс охватывает теперь 16 провайдеров живой оценки.
  • Монограммы провайдеров. CSP-безопасная плитка с инициалом в фирменном цвете отмечает каждого провайдера рядом с полем ключа в настройках и в строке страницы «Использование» — встроенный SVG, без внешних логотипов, ничего не покидает вашу машину.

Примечания

  • Базовые URL провайдеров — доверенная конфигурация, проходящая через runOpenAICompatible() с проверкой схемы http(s) (петля Ollama разрешена), а не через SSRF-валидатор URL вакансий. Источники сканирования без изменений: 82. Тесты: 2752.

[1.215.0] — 2026-08-26

Добавлено — запускайте весь поиск работы из OpenWorker. Новый coworker управляет этим пайплайном из open-source-приложения AI-coworker от Andrew Ng, и встроенная справка теперь его покрывает, так что ассистент «Спросить у документации» может провести вас по шагам.

Добавлено

  • Coworker для OpenWorker. Coworker без кода для OpenWorker — Fighter90/career-ops-coworker — выполняет весь поиск работы (скан, оценка, подгонка, трекинг, follow-up) и может запустить этот дашборд, всё на вашем рабочем столе. Это персона из одного файла на 17 языках, установка которой проверена собственным загрузчиком OpenWorker.
  • Справка §32 «Run it from OpenWorker» (все 17 локалей) документирует это, чтобы встроенный ассистент «Спросить у документации» и плавающая кнопка справки могли отвечать на вопросы о нём. Также добавлены ссылки в футере лендинга, README, вики и docs/integrations/openworker.md.

Примечания

  • Только документация + сайт — без изменений кода приложения. Источники сканирования без изменений — 82. Набор тестов: 2742.

[1.214.2] — 2026-08-25

Исправлено — общий конвейер HTML→текст (описания Greenhouse / Recruitee) больше не протаскивает атрибуты тега в описание, когда значение атрибута содержит символ >.

Исправлено

  • HTML-описания с > внутри атрибута очищаются корректно. Средство удаления тегов за описаниями Greenhouse и Recruitee использовало наивное совпадение, которое останавливалось на первом >, поэтому тег вроде <a title="salary > 100k"> оставлял хвост атрибута (100k">) в описании. Теперь оно уважает значения атрибутов в кавычках (одинарных или двойных), удаляет разметку до декодирования сущностей (чтобы закодированная кавычка внутри атрибута не стала ложным разделителем) и по-прежнему сохраняет литеральный <> в простом тексте.

Примечания

  • Набор тестов: 2742. Источники сканирования без изменений — 82. Ничего нового от родителя в этот раз: остальные изменения ретранслируются только на чтение (правки follow-up / weekly-digest / rejection-latency / salary-gap не требуют изменений в web-ui) или не относятся к web-ui (внешний X/Xquik BYO-key плагин, правки CLI/verify). Опциональные пункты — детальные описания SmartRecruiters, пропуск агрегаторов в detect-reposts, префиксы заголовков stem: / word:, защита от DNS-rebinding — остаются в очереди.

[1.214.1] — 2026-08-25

Исправлено — описания вакансий Ashby теперь обрезаются до той же длины, что у Greenhouse и Recruitee, так что контент-фильтр ведёт себя одинаково на всех трёх досках и одна доска не может раздуть кэш сканирования.

Исправлено

  • Описания Ashby теперь соблюдают тот же предел длины, что и другие доски. v1.214.0 подавала тело вакансии Ashby в контент-фильтр, но — в отличие от Greenhouse и Recruitee, которые режут до 4000 символов — у Ashby оно мапилось напрямую без предела (иногда в 4 раза длиннее). Из-за этого одно и то же слово content_filter могло совпасть на Ashby, но не на Greenhouse — только в зависимости от позиции в тексте, а одна доска могла добавить сотни КБ в кэш сканирования, который грузит #/scan. Теперь Ashby режется до тех же 4000 символов (его текст уже простой, поэтому он усекается, а не очищается от тегов).

Примечания

  • Набор тестов: 2738. Источники сканирования без изменений — 82.

[1.214.0] — 2026-08-25

Исправлено — описания вакансий Greenhouse, Ashby и Recruitee теперь попадают в контент-фильтр, так что фильтры по словам / стране / визе реально совпадают с этими досками; а “Associate <старший титул>” (включая академические звания) больше не относят к entry-level.

Исправлено

  • Контент-фильтры теперь работают на Greenhouse, Ashby и Recruitee. Эти доски теперь несут полный текст каждой вакансии как её описание, поэтому content_filter (или фильтр по словам страны / визы) совпадает с телом вакансии, а не пропускает доску вслепую. Ashby и Recruitee отдают тело в списочном payload бесплатно; Greenhouse уже это делал. Все три очищаются одним общим конвейером HTML→текст.
  • “Associate Director” / “Associate Professor” — это senior, а не entry-level. Классификатор уровня (используется skip_tiers:) считал слово “associate” меткой entry-level, даже когда оно предваряет старший титул. Теперь он читает “Associate <старшее существительное>” — включая академические звания (Associate Professor, Associate Dean) — как senior, сохраняя настоящие junior-варианты (Associate Attorney, Associate Editor) как entry-level, и по-прежнему относит ведущий “Intern, Associate Dean” к стажировке.

Примечания

  • Набор тестов: 2736. Источники сканирования без изменений — 82. В очереди на потом: опциональное обогащение описаний SmartRecruiters (запрос детали по каждой вакансии), пропуск досок aggregator: true в detect-reposts и префиксы заголовков с якорным словом stem: / word: — каждое опциональная настройка, а не изменение поведения по умолчанию. Защита от DNS-rebinding по-прежнему в очереди на отдельный релиз безопасности.

[1.213.0] — 2026-08-22

Добавлено — MyCareersFuture, национальный банк вакансий Сингапура, как источник сканирования. Исправлено — вакансии Greenhouse теперь несут полный текст, чтобы работали контент-фильтры, а удалённые вакансии Ashby больше не прячутся за локацией «только город».

Добавлено

  • MyCareersFuture (Сингапур) (mycareersfuture.gov.sg) — новый источник сканирования без токенов для национального государственного банка вакансий Сингапура (Workforce Singapore). Выберите его в фильтре Источник на #/scan или добавьте компанию с provider: mycareersfuture и необязательным списком keywords (иначе берутся целевые роли из профиля, как у Job Bank). Читает публичный поисковый API, с привязкой к хосту, без ключа.

Исправлено

  • Вакансии Greenhouse теперь можно фильтровать по контенту. Доски Greenhouse запрашиваются вместе с полным телом вакансии, декодированным в простой текст как описание — так что content_filter (или фильтр по словам страны/визы), читающий описание, теперь реально совпадает с вакансиями Greenhouse, а не пропускает их вслепую.
  • Удалённые вакансии Ashby больше не отсекаются фильтром города. Ashby хранит модель работы (Remote/Hybrid/Onsite) отдельно от города офиса, поэтому полностью удалённая вакансия читалась как, например, «San Francisco» — и фильтр локации, блокирующий этот город, скрывал доступную вакансию. Теперь при удалённой работе к локации добавляется «Remote», а workplaceType важнее устаревшего флага isRemote, чтобы офисно-привязанная гибридная вакансия не была помечена неверно.

Примечания

  • Источники сканирования: 82 (77 английских + 5 русских). Набор тестов: 2724. Защита от DNS-rebinding (проверка разрешённого адреса хоста до подключения) отложена в отдельный релиз — ей нужен собственный дизайн для web-ui, а не прямой порт.

[1.212.1] — 2026-08-21

Исправлено — лендинг cvstart.org недосчитывал источники сканера (показывал 80 и терял Job Bank (Канада)); теперь снова совпадает с 81 в приложении, а сборка сайта громко падает, если эти числа расходятся.

Исправлено

  • Счётчик «Источники вакансий» на лендинге снова синхронен с приложением. После v1.212.0 cvstart.org показывал 80 досок, и не хватало нового чипа Job Bank (Канада), тогда как приложение, выпадающий список сканирования и справка перечисляли 81. Лендинг строит список, загружая живой реестр сканера, и один источник не загрузился в этой сборке из-за того, как он подтягивал YAML-зависимость — поэтому был молча отброшен. Теперь Job Bank загружает эту зависимость лениво, так же как остальное приложение делает во время скана, поэтому он всегда на месте.
  • Сборка сайта теперь отказывается публиковать несходящийся счётчик. Если реестр перечислит меньше источников, чем есть на диске (признак источника, который не загрузился), сборка падает с понятным сообщением вместо тихой публикации неверного числа.

Примечания

  • Поведение приложения не изменилось — в сканере всегда были все 81 источник; затронут был только лендинг. Источники сканирования: 81 (76 английских + 5 русских) — без изменений. Набор тестов: 2687.

[1.212.0] — 2026-08-21

Добавлено — Job Bank (Канада), федеральная национальная доска вакансий. Удалено — EchoJobs (его фид теперь за бот-защитой). Исправлено — доски на Consider снова отдают результаты, а мульти-локационные вакансии Lever больше не скрывают половину локаций.

Добавлено

  • Job Bank (Канада) (jobbank.gc.ca) — новый источник сканирования без токенов для федеральной национальной службы занятости Канады, высоконагруженной доски, которую плохо покрывает любой агрегатор. Выберите его в фильтре Источник на #/scan или добавьте компанию с provider: jobbankca и необязательным списком keywords (иначе берутся целевые роли из вашего профиля). Читает публичный ATOM-фид, с привязкой к хосту, без ключа.

Удалено

  • EchoJobs — выведен. Его публичный фид теперь за бот-защитой и не отдаёт ничего, так что держать его — просто тратить слот сканирования.

Исправлено

  • Доски на Consider снова отдают результаты. Consider теперь требует анонимный handshake (GET, который задаёт сессионную куку + CSRF-токен) перед тем, как принять поисковый запрос; без него запрос молча отклонялся и доска выглядела пустой.
  • Мульти-локационные вакансии Lever больше не скрывают половину локаций. Lever кладёт один основной город в location, а остальные — в allLocations; чтение только основного делало вакансию, открытую в Барселоне И Монтевидео, «только-Барселона» (и её ошибочно отсекал фильтр локаций). Теперь обе объединяются.

Примечания

  • Мягче темп между страницами (250 мс вместо 150) на постраничных досках — из вежливости к одиночным careers-сайтам. Источники сканирования: 81 (76 английских + 5 русских) — без изменений (Job Bank добавлен, EchoJobs удалён). Набор тестов: 2685.

[1.211.0] — 2026-08-19

Добавлено — Yourator, тайваньская tech-доска вакансий. Исправлено — акцентированные сущности в заголовках/названиях компаний теперь декодируются везде, а компания с акцентом в названии больше не помечается по ошибке.

Добавлено

  • Yourator (yourator.co) — новый источник сканирования без токенов для tech- и digital-рынка вакансий Тайваня. Выберите его в фильтре Источник на #/scan или добавьте компанию с provider: yourator. Он читает публичный JSON API (без ключа, без браузера), обходит все страницы доски и выдаёт реальную ссылку работодателя (его собственный ATS) с вырезанными трекинг-параметрами.

Исправлено

  • Акцентированные именованные сущности теперь декодируются везде. Общий HTML-декодер получил латинские буквы Latin-1 (&eacute; → é, &ccedil; → ç, …), поэтому европейская доска, пишущая D&eacute;veloppeur или Fran&ccedil;ais, больше не оставляет этот литерал в заголовке, трекере или сгенерированном документе. (Заглавные остаются заглавными — &Eacute; это É, а не é, — а поиск вроде &constructor; теперь разрешается в себя.)
  • Компания с акцентом в названии больше не помечается по ошибке за то, что находится на своём же домене. «Işık» теперь сворачивается в «isik» и совпадает с isik.com.tr; «Société Générale» совпадает с societegenerale.com. Старая проверка удаляла акцентированные буквы вместо сворачивания к их ASCII-основе.

Примечания

  • Источники сканирования: 81 (76 английских + 5 русских). Набор тестов: 2667.

[1.210.1] — 2026-08-19

Исправлено — заголовки вакансий и названия компаний с «&» или кавычками из Habr Career больше не приходят искажёнными.

Исправлено

  • Источник Habr Career теперь декодирует HTML-сущности в заголовке и названии компании до того, как они идут дальше. Серверные карточки приходят экранированными («Changellenge >>», «Demand Forecasting & Inventory Optimization», «ООО "М-ТЕХ"»), поэтому недекодированный «&» молча не проходил ваш же фильтр заголовка с «&» — ровно тот симптом, который прошлый релиз закрыл на пяти других досках, — а названия компаний доезжали до трекера и отчётов кривыми. Декодирование сущностей теперь завершено на всех шести затронутых источниках.

Примечания

  • Набор тестов: 2644.

[1.210.0] — 2026-08-19

Добавлено — Senjob, первая африканская доска вакансий сканера (Сенегал); более точное совпадение заголовков ещё на пяти досках.

Добавлено

  • Senjob (senjob.com) — новый источник сканирования без токенов для Сенегала, первая африканская доска сканера. Выберите его в фильтре Источник на #/scan или добавьте компанию с provider: senjob. Он читает публичный список по обычному HTTP (без ключа, без браузера), привязывает каждый запрос к senjob.com и — поскольку парсит HTML — считает список, внезапно давший пусто, сломанной доской (видимой ошибкой), а не страной без вакансий.

Исправлено

  • Заголовки с «&» больше не теряют вакансии на пяти досках — на beesite, Cornerstone (csod), Hacker News «Who is hiring», Phenom и TKMS заголовки приходят с HTML-экранированием, поэтому экранированный «&» в должности вроде “R&D Engineer” не проходил ваше же ключевое слово “r&d”, и вакансия молча исчезала (вето “sales & marketing” тоже не срабатывало). Теперь заголовки — и локации Phenom — декодируются перед фильтрацией.

Примечания

  • Источники сканирования: 80 (75 английских + 5 русских). Набор тестов: 2643.

[1.209.0] — 2026-08-17

Добавлено — встроенная справка теперь описывает, как записать итог отклика, а «Спросить справку» подсказывает, где это.

Добавлено

  • В справке Трекера (§11) во всех 17 языках появился раздел «Записать итог отклика», который проводит по кнопке Итог: выберите, что произошло (отказ / оффер / нанят / отклонён / игнор / прошёл на собеседование), посмотрите предпросмотр и запишите — это фиксирует результат, архивирует отправленные вами резюме и сопроводительное письмо и синхронизирует Статус строки за вас. Плавающий помощник «Спросить справку» читает это руководство, поэтому теперь ведёт вас к этой кнопке, а не только предлагает править Статус вручную.

Примечания

  • Каждый справочный бандл теперь 31 H2 / 119 H3 (было 118); стражи паритета обновлены. Только документация — без изменений кода и поведения. Набор: 2625.

[1.208.2] — 2026-08-16

Исправлено — на телефоне кнопки уведомлений и темы больше не накрывают поле поиска.

Исправлено

  • В v1.208.1 кнопки верхней панели перестали накрывать заголовок страницы, но на неширокой (хотя и не самой узкой) полоске экрана — особенно в языках с длинными подписями — вся панель по-прежнему втискивалась в одну строку, и кнопки 🔔 и 🌙 могли лечь поверх поля поиска. Теперь кнопки действий (уведомления, тема, Диагностика, Открыть Scan) на телефоне всегда уходят на собственную вторую строку во всю ширину, поэтому поле поиска видно целиком и ничего не перекрывается.

Примечания

  • На телефоне кнопки действий верхней панели переезжают на вторую строку во всю ширину, убирая хрупкую полосу «почти полной строки», где раскладка распределяла остаток отрицательного места как наложение. Playwright-страж теперь воспроизводит точный триггер — локаль с длинными подписями в полосе 565–640px — и проверяет, что элементы панели не делят пиксели. Набор: 2621.

[1.208.1] — 2026-08-16

Исправлено — на телефоне кнопки верхней панели больше не накрывают страницу.

Исправлено

  • В v1.208.0 кнопки верхней панели (Диагностика, Открыть Scan, уведомления, тема) переносились на вторую строку на узких экранах, но панель сохраняла фиксированную высоту, поэтому перенесённая строка вылезала и ложилась поверх заголовка страницы. Теперь панель растёт под свои строки, а контент идёт под ней.

Примечания

  • Фиксированная height панели стала min-height, поэтому она растёт под контент на любой ширине (десктоп не изменился). Playwright-страж теперь также проверяет, что панель не вылезает поверх страницы. Набор: 2621.

[1.208.0] — 2026-08-16

Исправлено — приложение теперь помещается на экране телефона: горизонтальная прокрутка ушла.

Исправлено

  • На узком экране всё приложение уезжало вбок — верхняя панель, таблицы, статьи справки и вкладки настроек вылезали за правый край. Теперь каждая страница помещается на любой ширине: кнопки верхней панели переносятся на вторую строку, широкие таблицы и блоки кода прокручиваются внутри своей рамки, справка ставит оглавление над статьёй, ряды кнопок и вкладок переносятся, а длинные пути и ссылки переносятся, а не растягивают страницу.

Примечания

  • Причина — классическая ловушка flex/grid min-width: auto плюс пара необёрнутых широких элементов; починено через min-width: 0 на grid-элементах, overflow-wrap на markdown/заголовках, прокручиваемую markdown-таблицу и перестроение сетки справки на мобильном брейкпоинте. Playwright-страж проверяет 0 горизонтального переполнения на 375 px по основным маршрутам. tests/playwright-smoke.mjs. Набор: 2621.

[1.207.2] — 2026-08-16

Исправлено — AI-планы и профиль карьерной ориентации больше не отображаются как сырой дамп кода.

Исправлено

  • Некоторые модели заворачивают весь ответ в код-ограду markdown … . Когда так случалось, план развития и профиль ориентации показывались моноширинным блоком кода вместо документа с заголовками и списками. Теперь обёртывающая ограда снимается — только если она охватывает весь ответ и язык явно markdown/md, так что настоящий ответ на python/js/без языка ``` остаётся нетронутым.

Примечания

  • Решено один раз в общем шаге очистки LLM (cleanLlmMarkdown), поэтому выигрывают все AI-маршруты, а вложенные блоки кода внутри ответа сохраняются. tests/llm-output.test.mjs (+3). Набор: 2621.

[1.207.1] — 2026-08-16

Исправлено — на маленьких телефонах лендинг больше не вылезает вбок.

Исправлено

  • На узком телефоне первый экран — заголовок, вводная строка и терминал установки — мог обрезаться по правому краю, потому что длинная команда установки и колонки вёрстки не сжимались под экран. Теперь всё помещается на любой ширине; команда установки прокручивается внутри своего терминала.

Примечания

  • Также укреплена нестабильная E2E-проверка, которая могла падать из-за временного 404 ресурса: теперь она игнорирует безобидный сетевой шум (favicon / соединение / несостоявшийся ресурс), как и соседние проверки, но по-прежнему ловит реальные ошибки скриптов. Поведение приложения не изменилось. Набор: 2618.

[1.207.0] — 2026-08-15

Добавлено — записывайте итог отклика прямо из трекера.

Добавлено

  • В каждой строке трекера появилось действие Итог: выберите, что произошло (отказ, оффер получен, нанят, оффер отклонён, нет ответа, прошёл на собеседование), добавьте необязательную заметку, предпросмотрите итоговый статус и запишите. Запись архивирует отправленные артефакты CV и сопроводительного письма и синхронизирует трекер до канонического состояния — одно детерминированное действие вместо ручной правки трекера.

Примечания

  • Новый POST /api/outcome ретранслирует CLI итогов: dryRun:true — предпросмотр только для чтения (находит строку, сообщает итоговый статус, ничего не пишет); реальный вызов записывает. Безопасность записи: тип итога ограничен известным набором, а каждое текстовое поле отклоняется при управляющих символах до вызова (аргументы-массив, spawn — без shell). tests/outcome-route.test.mjs. Набор: 2618.

[1.206.0] — 2026-08-15

Документация — встроенное руководство теперь описывает пять новейших функций на всех 17 языках.

Добавлено

  • Встроенное руководство — и ассистент «Спросить справку», отвечающий по нему — теперь описывает пять недавно добавленных функций: Доктор настройки (Настройки — проверяет CV и профиль на пробелы и оставшиеся демо-данные), Поиск ATS-досок (Порталы — автоматически находит карьерную доску компании), проверку «ещё открыта?» (Трекер — открыта ли вакансия), подсказку «переиспользовать прежнее CV?» (CV Studio — подсказывает, когда уже адаптированное CV подходит к новой вакансии) и Журнал навыков (Аналитика — запись оценок самопроверки). Пять новых подразделов, переведённых на все 17 языков.

Примечания

  • Структура руководства выросла до 31 H2 / 118 H3, паритет гарантирован для каждого языка. Обновлена справочная документация: docs/architecture/API.md описывает пять маршрутов этих функций, а счётчики маршрутов/версии в CLAUDE.md и docs/sdd/CONVENTIONS.md актуальны (36 модулей маршрутов). Набор: 2610.

[1.205.0] — 2026-08-15

Добавлено — журнал навыков для записи результатов тестов/оценок.

Добавлено

  • Новый Журнал навыков (Аналитика → Журнал навыков) позволяет записать самооценку — компания, платформа, навык, оценка % и необязательная заметка — добавляется в data/assessments.tsv, со списком прошлых записей (новые сверху). Без токенов, детерминированно; формат файла ведёт CLI родительского проекта.

Примечания

  • Новый GET /api/assessments (ретранслирует список JSON по умолчанию из assessment-log.mjs; мягкий отказ {available:false}) + POST /api/assessments (явная запись: поля передаются как аргументы-массив в assessment-log.mjs add). Безопасность записи: любое текстовое поле с управляющими символами отклоняется (TAB сломал бы столбец, перевод строки вставил бы строку) → 400 до записи; оценка/порог ограничены 0–100, длины ограничены. tests/assessments-route.test.mjs. Набор: 2610.

[1.204.0] — 2026-08-15

Добавлено — панель «Доктор настройки» в Настройках, отмечающая незаполненные или демо-данные в CV/профиле.

Добавлено

  • Настройки → Доктор настройки теперь без токенов проверяет ваши cv.md и config/profile.yml и перечисляет блокирующие проблемы (отсутствующие файлы/поля) и предупреждения (оставшиеся демо/шаблонные данные, зашитые метрики) — чтобы поймать неполную настройку прежде, чем она ослабит сканы и адаптацию. Только чтение; перезапуск одним кликом.

Примечания

  • Новый route только для чтения GET /api/cv-sync-check ретранслирует cv-sync-check.mjs из родительского проекта, который печатает текст + код выхода (без --json); route легко структурирует его стабильные строки ERROR: / WARN: в {ok, errors[], warnings[]} — успех определяет баннер, а не код выхода. Мягкий отказ {available:false} в автономных установках. tests/cv-sync-check-route.test.mjs. Набор: 2602.

[1.203.0] — 2026-08-15

Добавлено — подсказка «переиспользовать прошлое резюме?» в CV Studio.

Добавлено

  • Когда вы открываете сохранённую вакансию в CV Studio, приложение теперь сравнивает её с другими вашими сохранёнными вакансиями (детерминированное совпадение слов, без токенов) и подсказывает, достаточно ли похожа ближайшая, чтобы переиспользовать то адаптированное резюме, переиспользовать с правками или готовить новое — чтобы не пересоздавать с нуля для роли, над которой вы уже работали.

Примечания

  • Новый route только для чтения GET /api/jds/:name/reuse ретранслирует jd-similarity.mjs из родительского проекта (совпадение Жаккара + проверка сениорности; JSON {decision, score, reason}) по разу на каждую прошлую вакансию (fan-out ограничен 25, берётся лучшее совпадение); мягкий отказ {available:false}, если скрипта или прошлых вакансий нет. tests/jd-similarity-reuse-route.test.mjs. Набор: 2594.

[1.202.0] — 2026-08-15

Добавлено — найдите ATS-доску компании прямо на #/portals и начните отслеживать её.

Добавлено

  • На #/portals введите название компании — приложение проверит Greenhouse, Ashby и Lever на её публичную доску вакансий (без LLM, без браузера) и покажет доски, которые существуют и сейчас содержат ≥1 вакансию. Один клик добавляет выбранную доску к компаниям, за которыми следит сканер. Проверка только для чтения; запись в portals.yml происходит лишь при нажатии Добавить.

Примечания

  • Новый server/lib/discover-ats.mjs (проба слага с фиксированным хостом и проверкой charset через safeGet с закреплённым DNS, ≤12 проб на запрос, кросс-доменный редирект → не найдено) + POST /api/portals/discover (только чтение) и POST /api/portals/track (явная запись: withFileLock + текстовая вставка + перепроверка парсинга + атомарная замена; только известные ATS-хосты, идемпотентно). Переиспользует реестр адаптеров сканера для подтверждения доски и подсчёта вакансий. i18n ×17. tests/discover-ats-resolver.test.mjs + tests/discover-ats-route.test.mjs. Набор: 2588.

[1.201.0] — 2026-08-15

Исправлено — трекер с локализованными или альтернативными заголовками столбцов больше не отображается пустым.

Исправлено

  • Если ваш data/applications.md использует не-английские или альтернативные заголовки — испанские empresa / puesto / estado / fecha / enlace, либо position / stage / link — трекер читал их под неверными ключами и показывал пустые столбцы Компания / Роль / Статус / Дата / Ссылка. Теперь такие заголовки сворачиваются к каноническим именам полей, и трекер отображается правильно. Англоязычный трекер обрабатывается точно как раньше.

Примечания

  • Новая карта HEADER_ALIASES и нормализующая свёртка в parseApplications (server/lib/parsers.mjs); неизвестные или уже канонические заголовки проходят без изменений. tests/tracker-header-aliases.test.mjs. Набор: 2563.

[1.200.0] — 2026-08-15

Добавлено — проверка «ещё активна?» для вакансий на ATS одним кликом прямо в трекере.

Добавлено

  • На #/tracker у отклика, чей URL — вакансия Greenhouse / Lever / Ashby / Workday / SmartRecruiters, теперь есть кнопка «Ещё активна?». Один клик спрашивает публичный JSON самого ATS — без токенов, без браузера — и показывает Активна / Закрыта / Неизвестно, чтобы находить мёртвые вакансии, не открывая каждую. Консервативно по умолчанию: только явный 404/410 читается как Закрыта; всё неоднозначное остаётся Неизвестно (никогда ложной Закрыта).

Примечания

  • Новые server/lib/liveness-core.mjs + liveness-api.mjs и route только для чтения GET /api/liveness?url= (без записей, без LLM). Безопасно к SSRF: URL проходит isValidJobUrl, затем к API ATS обращаемся только через safeGet (с закреплённым DNS), с фиксированным хостом и проверенными по charset сегментами пути. tests/liveness-core.test.mjs + tests/liveness-route.test.mjs. Набор: 2557.

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