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

GitHub

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

[1.34.0] — 2026-05-17

WS5 — экран Auto-pipeline в один клик (#/auto). Модалка повышена до отдельной линкуемой страницы. Один клик: валидация→загрузка→оценка→сохранение отчёта→трекер (SSE). Доступный stepper (aria-current, live-region), deep-link на отчёт/трекер, ручной режим без ключа, линкуется #/auto?url=…&go=1. Пункт в сайдбаре; кнопка ✨ дашборда ведёт сюда. i18n 14 ключей ×8. help §1 ×8 + README ×8. +8 тестов. 589 → 597. Подробно — CHANGELOG.md.


[1.33.0] — 2026-05-17

WS4 — аудит паритета с career-ops 1.8.0 + location_filter. В родительском scan.mjs появился location_filter (#570); in-process сканеры web-ui не делегируют ему, поэтому не флоу. Новый server/lib/location-filter.mjs повторяет семантику дословно; подключён в оба сканера. help §5 ×8. +8 тестов. 581 → 589. Остальной дельта классифицирован в PARENT-PARITY.md. Подробно — CHANGELOG.md.


[1.32.0] — 2026-05-17

Вкладка Profile в #/config — сырой YAML-блоб → форма по полям (WS1). 3 сворачиваемые секции (Кандидат / Нарратив / Компенсация), 14 скалярных путей. Сохранение по полям мёржит в config/profile.yml: архетипы, proof points и кастомные ключи сохраняются нетронутыми. Raw-YAML escape-hatch сохранён в Advanced (хранит комментарии). 23 i18n-ключа ×8. +7 тестов (вкл. инвариант выживания неизвестных ключей). 574 → 581. Подробно — CHANGELOG.md.


[1.31.0] — 2026-05-17

Синхронизация с career-ops 1.8.0 — #/batch отдаёт --model + --start-from. Родительский проект 1.7.1 → 1.8.0; в batch-runner.sh добавлены --model NAME (#504) и --start-from N. web-ui выводит их в #/batch (поля Модель и Старт с #) с серверной defense-in-depth валидацией (charset для model, целое 1..100000 для start-from). i18n ×8. +7 тестов. 567 → 574. Полностью — в CHANGELOG.md.


[1.30.0] — 2026-05-14

Paginator на #/scan — заменяет truncation «первые 200 из N» из v1.12.

До v1.30 таблица результатов скана жёстко обрезалась на первых 200 отфильтрованных строках, внизу шла подпись «Показаны первые 200 из N». Строки 201..N были недоступны из UI — приходилось руками сужать title_filter.positive в portals.yml. v1.30.0 заменяет cap на UI.paginate (тот же хелпер что в #/tracker / #/reports / #/activity). PAGE_SIZE = 200 сохраняет прежнюю плотность; сортировка boost-to-top стабильна между страницами (сортируем ПОЛНЫЙ набор и только потом режем на страницы); автореsет на page 1 при смене любого фильтра. Устаревший i18n-ключ scan.shownTop удалён (×8 локалей). 9 новых кейсов в tests/scan-paginator.test.mjs (7 статических канарий + 1 логическая таблица с 6 граничными кейсами + 1 расчёт summary). 558 → 567 unit + acceptance (+9 новых). Полные подробности в CHANGELOG.md.


[1.29.2] — 2026-05-14

Hot-fix: 🌐 Scan с source=both запускал только EN-фазу. RU-фаза тихо терялась.

SSE-клиент (public/js/api.js:156) закрывал EventSource на ПЕРВОМ событии done, но сервер в source=both шлёт по одному done на фазу. RU-фаза стартовала и сразу же отменялась. Фикс: сервер маркирует каждый done полем final: true|false; клиент закрывает поток только когда final !== false. Обратная совместимость — продюсеры одиночных фаз без final закрываются как раньше. 547 → 558 unit + acceptance (+11 новых). Полные подробности в CHANGELOG.md.


[1.29.1] — 2026-05-14

Подробный пользовательский гайд по настройке 5 русских порталов в help-bundle §5, во всех 8 локалях.

Новая ### подсекция «Настройка русских порталов — подробное руководство» внутри §5 (Portals & sources): инвентарная таблица 5 источников с auth и гео-ограничениями, пошаговый поиск и редактирование portals.yml, полный YAML-пример с 5 источниками, коллизия с negative-list’ом и пример исправления, как временно отключить отдельный источник, как проверить через 🌐 Scan + SSE-лог. §17 (релиз v1.29.0) описывает девелоперский флоу; §5 в v1.29.1 — пользовательский флоу. 540 → 547 unit + acceptance (+7 новых). Полные подробности в CHANGELOG.md.


[1.29.0] — 2026-05-14

Сканер русских порталов расширен с 2 до 5 источников. Registry + динамический dropdown. Новая секция §17 «Как добавить новый источник».

  • 3 новых RU-адаптера: Trudvsem (open-data API «Работа в России», без auth, без гео-гейта), GetMatch и GeekJob (HTML-скрейп с defensive-парсером — [] при неудачном парсе, никогда не throw на здоровом 200).
  • Source registry в server/lib/sources/registry.mjs — единый источник правды, который читают dispatcher + endpoint + dropdown. До v1.29 список жил жёстко-захардкоженным в трёх местах.
  • Новый endpoint GET /api/scan/sources с Cache-Control: max-age=60 — SPA при mount’е #/scan динамически перестраивает dropdown.
  • Новая §17 во всех 8 локалях: «Как добавить новый источник для скана» (template адаптера, запись в registry, dispatcher, мокированный тест, portals.yml).
  • Дефолт russian_portals.sources меняется с ["hh", "habr"] на 5 источников; если у тебя в portals.yml уже явно перечислены sources:, добавь три новых руками.
  • Тесты: 520 → 540 (+20). Полные подробности в CHANGELOG.md.

[1.28.1] — 2026-05-14

Hot-fix: router 404 на хэшах с ?query. Строку HH_USER_AGENT убрали из health.

До v1.28.1 Router.go('/evaluate?url=…') производил hash, чей первый сегмент по split('/') был литералом "evaluate?url=…", который никогда не совпадал с зарегистрированной ветвью → __not_found__ (404). Однострочный фикс: hash.split('?')[0] перед разбиением имени. Закрывает оба известных клика: #/pipeline → ▶ и «App settings → Modes». Опциональная строка HH_USER_AGENT убрана из /api/health (подсказка про 403-вне-РФ остаётся в help-bundle §16 и в stderr самого ru-scanner). 515 → 520 unit + acceptance (+5 новых). Полные подробности в CHANGELOG.md.


[1.28.0] — 2026-05-14

Выравнивание документации + контрол --max-retries N на #/batch. Закрывает два открытых issue, поднятых в qa/QA-PROMPT-docs-vs-app.md.

  • Issue #2 — на #/batch появился числовой input «Max retries» (1–10), активный только когда отмечен «Retry failed». Сервер парсит через parseInt и валидирует 1≤N≤10; значения вне диапазона тихо отбрасываются, без --retry-failed флаг не пробрасывается вовсе. 7 кейсов в tests/batch-max-retries.test.mjs. 2 новых i18n-ключа × 8 локалей.
  • Issue #1 — список AI-CLI в 8 help-bundle и 8 README выровнен под канон career-ops.org/docs (Claude Code · Codex · OpenCode · Qwen CLI), с локализованной строкой: «другие Claude-совместимые CLI работают через тот же интерфейс slash-команд». Бюллет «Multi-CLI» в README про shim-файлы web-ui сохранён без изменений (это другая поверхность). 2 новых канария в tests/canonical-docs-coverage.test.mjs.
  • 506 → 515 unit + acceptance (+ 9 новых). Playwright 32/32 без изменений. Полные подробности в CHANGELOG.md.

[1.27.0] — 2026-05-14

Косметика + a11y: устранён дубль #/dashboard в сайдбаре.

В сайдбаре логотип бренда (<a class="logo" href="#/dashboard">) и первый пункт навигации указывали на один и тот же маршрут. Скринридеры дважды произносили «Dashboard», а у клавиатурных пользователей появлялся лишний tab-stop. Блок логотипа теперь — обычный <div class="logo">; пункт навигации остаётся единственной ссылкой на #/dashboard. 506 / 506 unit + 32 / 32 Playwright — без изменений. Подробности в CHANGELOG.md.


[1.26.1] — 2026-05-14

Hot-fix WCAG 2.5.5 — восстановлен минимум min-height: 44px на .btn.

В v1.26.0 декларация min-height: 44px в .btn отсутствовала, header-кнопки рендерились на 39-41 px (нарушение WCAG 2.5.5). v1.26.1 восстанавливает 44-пиксельный пол + flex-shrink: 0 + line-height: 1.2. 502 → 506 unit, Playwright 32/32 без изменений. Детали в CHANGELOG.md.


[1.26.0] — 2026-05-14

Пирамида тестов + line-покрытие ≥ 93 %.

Внедрена 4-уровневая пирамида тестов (unit → functional → acceptance → e2e) согласно backlog’у v1.25. Добавлены 22 новых теста, покрывающие крупнейшие пробелы из v1.25 (jds.mjs 61.64 % → 100 %, ветки отказа в auto-pipeline). Появилась директория tests/acceptance/ для тестов пользовательских сценариев через несколько endpoint’ов. 480 → 502 unit + acceptance, Playwright 32/32 без изменений. Полные детали в CHANGELOG.md и docs/architecture/TESTING.md.


[1.25.0] — 2026-05-14

Короткое замыкание ручного режима авто-пайплайна + косметика дашборда + добивка паритета CHANGELOG. Релиз закрывает G-014 (авто-пайплайн игнорировал mode: 'manual'), G-012 (рассинхрон CHANGELOG — 6 локалей отставали на два релиза) и косметический баг с двойным глифом ✨ ✨ на дашборде. G-003 (переименование README.cn.md) де-факто уже закрыт — в репозитории присутствует только README.zh-CN.md. G-005 (перестройка блоков отчётов A-G → A-F) требует согласованного коммита в родительском проекте и переносится дальше.

🛡️ G-014 — короткое замыкание mode: 'manual' в авто-пайплайне

  • fix(auto-pipeline): G-014 — honour mode:'manual' short-circuit (server/lib/routes/auto-pipeline.mjs:158-195) — до v1.25 маршрут всегда обращался к LLM. Передача mode: 'manual' (по аналогии с /api/evaluate начиная с v1.10.2) тихо игнорировалась, запрос висел 1–3 минуты на Anthropic. Теперь обработчик:
    • Принимает mode И evalMode ради обратной совместимости. Любое из значений 'manual' запускает короткое замыкание.
    • Эмитит все 5 этапов SSE со status: 'done' / status: 'skipped'. Без fetch. Без вызова LLM. Без $0.05 за запрос.
    • Полезная нагрузка done несёт { mode: 'manual', prompt: <buildEvaluationPrompt scaffold>, message } — SPA может отрендерить её так же, как уже существующую карточку ручного промпта /api/evaluate.
  • Закрывает риск DoS на HOST=0.0.0.0: ранее, даже когда llmRateLimit ограничивал 10 запросов в 60 с с IP, 10 атакующих × 10 запросов = $50/мин расхода Anthropic. Короткое замыкание срабатывает до того, как декремент лимита запросов учтётся для реального вызова.
  • Тесты — tests/auto-pipeline-manual-mode.test.mjs: 3 теста подтверждают (1) mode: 'manual' возвращает ответ менее чем за 2 с со всеми 5 ключами этапов, (2) даже при заданном ANTHROPIC_API_KEY короткое замыкание всё равно срабатывает (исходный симптом), (3) старые вызовы с evalMode: 'manual' продолжают работать.

📝 G-012 — добивка паритета CHANGELOG (6 локалей × 2 пропущенных релиза)

  • docs(changelog): backfill v1.23.0, v1.24.0, v1.24.1, v1.25.0 in 6 lagging locales — до v1.25 только EN содержал v1.23–v1.24; RU отставал на один релиз, остальные 6 — на два. v1.25 запускает параллельных переводческих агентов (по образцу цикла v1.23), которые добавляют все четыре записи в CHANGELOG.{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.md. RU получает v1.24.0 + v1.24.1 + v1.25.0 (v1.23.0 уже присутствовал с цикла v1.23).
  • feat(ci): scripts/check-changelog-parity.mjs gate — падает сборка, если самая свежая запись CHANGELOG любой локали старше канонического EN. Подключено в npm run test:ci. Текущий рассинхрон G-012 поймал бы себя сам в момент пересечения границы EN.

✨ Косметика — устранение двойного глифа на дашборде

  • fix(dashboard): dedup ✨ glyph in auto-pipeline button label (public/js/lib/i18n-dict.js:219) — dash.autoPipeline нёс ведущий ✨ в строке каждой локали, И public/js/views/dashboard.js:58 добавлял ещё один ✨ во вьюхе. Результат: кнопка рендерилась как ✨ ✨ Auto-pipeline …. v1.25 убирает ведущий глиф из значения DICT каждой локали; префикс вьюхи остаётся единственным источником. Тот же проход аудита прошёлся по остальному i18n-бандлу — других случаев двойного глифа не обнаружено.

🚫 Перенесено на следующий релиз

  • G-005 — перестройка блоков отчёта A-G → A-F согласно каноническому career-ops.org/docs — требует согласованного коммита в родительском проекте santifer/career-ops (переписать modes/oferta.md так, чтобы он эмитил A=Role, B=CV-match, C=Strategy, D=Comp, E=Personalization, F=STAR — отказаться от C-Risks/G-Legitimacy как отдельных блоков). v1.25.0 поставляет сторону web-ui, готовую к новой схеме (reports.js принимает произвольные буквы блоков с v1.13). Закладывается в следующее релизное окно, когда родитель и потомок смогут выйти вместе.
  • G-003 — переименование README.cn.md → README.zh-CN.md — проверено при подготовке v1.25: в репозитории уже присутствует README.zh-CN.md (никакого осиротевшего README.cn.md нигде в worktree). Находка G-003 устарела.

🧪 Тесты

  • 477 → 480 unit (+3 из PR-B auto-pipeline-manual-mode.test.mjs).
  • 32/32 Playwright без изменений.
  • npm run test:ci теперь прогоняет npm test + check-no-also-leftovers.mjs + check-changelog-parity.mjs.

Верификация

$ npm run test:ci
# 480 / 480
# ✓ no .also( leftovers in views/
# ✓ CHANGELOG parity: all 8 locales at v1.25.0

# G-014 — manual mode returns < 2 s even with ANTHROPIC_API_KEY set:
$ ANTHROPIC_API_KEY=sk-ant-test PORT=4317 npm start &
$ sleep 3
$ time curl -sS -X POST -H 'Content-Type: application/json' \
    -d '{"url":"https://job-boards.greenhouse.io/anthropic/jobs/x","mode":"manual"}' \
    http://127.0.0.1:4317/api/auto-pipeline | head -20
# real  0m0.1xx s  (was 1-3 min)
# event: start … event: step (×5) … event: done {"mode":"manual","prompt":"…"}

# G-012 — every locale CHANGELOG carries the v1.25.0 entry:
$ grep -c '^## \[1.25.0\]' CHANGELOG*.md
# 8 files, each → 1

# Cosmetic — dashboard glyph:
$ grep "dash.autoPipeline" public/js/lib/i18n-dict.js
# No leading ✨ in any locale value (view supplies the single glyph)

Несовместимые изменения

Нет. mode: 'manual' подключается опционально; старые вызовы с evalMode: 'manual' продолжают работать без изменений.

Вне рамок (v1.26+)

Пункт Примечания
G-005 — перестройка блоков отчёта A-F Требует согласованного коммита в родительском проекте (santifer/career-ops переписывает modes/oferta.md).
Живое выполнение визуальных подтестов QA-сценария 31 Требуют браузерного агента (Claude Cowork). Частично покрыты Playwright smoke.
Превышение i18n-dict.js целевого лимита в 400 LOC Фикстура переводов — освобождена политикой. Разделение добавило бы HTTP-запросов без сборщика.

[1.24.1] — 2026-05-14

Hot-fix: падение #/config во всех 8 локалях (G-015).

🚑 Критический hot-fix

  • fix(config): G-015 — replace removed Element.prototype.also call in config.js (public/js/views/config.js:371) — v1.22.0 N-2 удалил глобальный monkey-patch Element.prototype.also и перевёл cv.js на шаблон со свободными выражениями, но пропустил config.js. В итоге #/config падал при первом обращении в каждой локали с ошибкой c(...).also is not a function. v1.24.1 применяет тот же шаблон миграции, что и в cv.js:188-201 — вынести дерево в const root = c(...), выполнить блок активации отдельно и затем return root;.

🛡️ Шлюз CI

  • feat(ci): scripts/check-no-also-leftovers.mjs sweep — обходит каждый файл под public/js/views/ и валит сборку на любом вызове .also( (ссылки в комментариях допустимы). Подключено в новый скрипт npm run test:ci. Будущий откат удаления monkey-patch не сможет тихо вернуть ту же регрессию.

🧪 Тесты

  • test: tests/config-view-syntax.test.mjs — три страховки:
    • парсинг config.js через node:vm.Script (ловит регрессии уровня синтаксиса без Playwright)
    • проверка, что .also( не выживает вне комментариев
    • проверка, что якоря миграции const root = c(...) / return root; на месте
  • 474 → 477 unit (+3) + 32/32 Playwright без изменений.

Верификация

$ npm run test:ci
# 477 / 477
# ✓ no .also( leftovers in views/

# Browser smoke:
$ open http://127.0.0.1:4317/#/config
# → renders normally, no "is not a function" card. Every locale equivalent.

Вне рамок (перенесено на v1.25)

  • G-014, G-012, G-005, G-003 — см. запись v1.25.0 ниже единым пакетом.

[1.24.0] — 2026-05-14

Обновление глубины содержимого справочного бандла + живое выполнение QA-сценария 31 + сквозной перевод RU CHANGELOG. Релиз закрывает оба пункта, перенесённых таблицей «Out of scope» из v1.23.0 на v1.24: полное обновление глубины содержимого всех 8 справочных бандлов из 5 канонических URL career-ops.org/docs (с v1.11.x присутствовало только покрытие URL) и живое выполнение QA-сценария 31 на запущенном сервере (числилось как «требует браузерного агента + LLM-учётных данных» — оказалось, что 6/6 подтестов достижимы через curl + grep, и только визуальные подтесты требуют браузера).

📖 Обновление глубины содержимого справочного бандла

  • docs(help): refresh en.md from 5 canonical career-ops.org/docs URLs (docs/help/en.md) — до v1.24 EN-бандл содержал 1113 строк и перечислял 5 канонических URL во front-matter, но не раскрывал их в теле. v1.24 загружает все 5 URL через WebFetch и углубляет соответствующие H2-разделы:
    • About career-ops (front-matter) — добавлены принципы (суверенитет данных, AI-agnostic, контроль со стороны человека), блок «What career-ops is NOT», расширена инвентаризация концепций с 6 до 10 строк (добавлены Proof points, JD store, Interview-prep, Batch additions).
    • §5 Portals — добавлен канонический bootstrap cp templates/portals.example.yml portals.yml, уточнены обязательные и опциональные поля каждой записи tracked_companies.
    • §7 Scan — добавлено примечание «no AI tokens consumed» для варианта A, перечень последующих команд (apply / contacto / deep / tracker).
    • §14 Apply checklist — разделён на режим SPA-чеклиста, режим Manual-vs-Playwright-assisted и полный CLI-сценарий (канонические 8 пронумерованных шагов от /career-ops apply <company> до Submitted. с авто-переходом Evaluated → Applied); подсекция batch evaluate получила таблицу схемы TSV + документацию всех 4 флагов + merge-tracker.mjs --dry-run; подсекция Playwright Setup перечисляет команды установки, регистрацию MCP, альтернативу .claude/settings.local.json, примечание о headless-режиме по умолчанию.
  • Сохранён паритет 16 H2-разделов (CI-тест help-ui.test.mjs::section-parity утверждает ровно 16 H2-разделов во всех 8 локалях).
  • Каждый из 5 канонических URL встречается ≥ 2 раз в бандле (CI-тест canonical-docs-coverage.test.mjs это обеспечивает). Счётчики на URL после v1.24: what-is-career-ops × 4, scan-job-portals × 5, apply-for-a-job × 3, batch-evaluate-offers × 5, set-up-playwright × 3.
  • docs(help): translate the v1.24 deepening to 7 non-EN locales — запущены 7 параллельных переводческих агентов. Каждая целевая локаль (es / pt-BR / ko-KR / ja / ru / zh-CN / zh-TW) получает обновлённый бандл, который зеркалит структуру EN раздел в раздел, сохраняет дословно блоки кода, URL, пути к файлам и метки кнопок (📁 Upload CV / 🌐 Scan now / ▶ Evaluate / 📄 Generate PDF / 💾 Save), а также английские аббревиатуры (CSP, SSRF, TOCTOU, WCAG, ATS, JD, SSE, REST, API), и переводит углубление на нативный технический стиль публикационного уровня на целевом языке.

🧪 QA-сценарий 31 — живое выполнение (6/6 PASS)

  • docs(qa): append last-verified live-execution log to qa/claude-cowork-browser-test-prompt.md — до v1.24 сценарий 31 был задокументирован, но ни разу не прогонялся на живом сервере (был отложен как «требует браузерного агента + LLM-учётных данных»). v1.24 прогнал все 6 подтестов на http://127.0.0.1:4317:

    Sub Описание Статус
    31.1 Пороги баллов в справочных бандлах ✅ PASS (4.5 × 3, 4.0 × 9, 3.5 × 6 упоминаний в docs/help/en.md)
    31.2 Эндпоинты сценария сканирования ✅ PASS (/api/stream/scan-{en,ru} + /api/scan-ru/config → 404; /api/scan/regional/config → 200)
    31.3 Чеклист /api/apply-helper ✅ PASS (тело содержит career-ops apply + предупреждение auto-submit)
    31.4 Эндпоинт /api/batch ✅ PASS (ключи [exists, runnerExists, raw, rows, additions])
    31.5 Доступность Playwright ✅ PASS (/api/health сообщает Playwright (parent node_modules) ok: true, value: installed)
    31.6 Покрытие URL справочного бандла (5 URL × 8 локалей) ✅ PASS (40 / 40 ✓)

    Чисто визуальные подтесты (требуют браузер) помечены отдельно в QA-промпте — они остаются выполнимыми через Claude Cowork или npm run test:e2e:browser.

🌐 RU CHANGELOG end-to-end (продолжение M-9)

  • docs(translate): CHANGELOG.ru.md retry agent — full body translation (CHANGELOG.ru.md) — релиз v1.23.0 вышел с переводческим агентом для RU CHANGELOG ещё в работе (он один раз упал с ошибкой сокета и был перезапущен). v1.24 подхватывает 1542-строчный полный перевод агента: каждая запись от v1.23.0 до v1.6.0 получает русское тело публикационного уровня, больше никаких EN-телесных заглушек. Стилевая дисциплина соответствует обновлению качества README в v1.22.0: «функциональность» / «возможности» / «поведение» вместо неуклюжего «функционал»; «через» / «с помощью» вместо «при помощи»; активный залог над пассивным; «эндпоинт», «лимит запросов», «состояние гонки», «санитайзинг» как канонические термины; английские аббревиатуры (TOCTOU, CSP, SSRF, WCAG, ATS, JD, SSE, REST, API) сохранены.

🧪 Тесты

  • 474 / 474 unit + 20 / 20 smoke E2E + 32 / 32 Playwright. Нулевые поведенческие дельты тестов; каждая CI-проверка справочного бандла (16 H2-разделов × 8 локалей, 5 URL × ≥ 2 упоминания, минимум по содержанию) по-прежнему зелёная.

Верификация

$ npm test                            # 474 / 474

# Help-bundle deepening:
$ wc -l docs/help/en.md
# ~1270 lines (was 1113 — deepened, not bloated)

$ for url in what-is-career-ops scan-job-portals apply-for-a-job \
             batch-evaluate-offers set-up-playwright; do
    echo -n "$url: "
    grep -c "$url" docs/help/en.md
  done
# what-is-career-ops: 4
# scan-job-portals: 5
# apply-for-a-job: 3
# batch-evaluate-offers: 5
# set-up-playwright: 3

# Scenario 31.6 — 40/40 URL coverage:
$ for lang in en es pt-BR ko ja ru zh-CN zh-TW; do
    echo -n "$lang: "
    for url in what-is-career-ops scan-job-portals apply-for-a-job \
               batch-evaluate-offers set-up-playwright; do
      curl -sS "http://127.0.0.1:4317/api/help/$lang" \
        | python3 -c "import sys,json; print(json.load(sys.stdin).get('markdown',''))" \
        | grep -q "$url" && echo -n "✓ " || echo -n "✗ "
    done
    echo
  done

Несовместимые изменения

Нет.

Вне рамок (v1.25+)

Пункт Примечания
Живое выполнение визуальных подтестов сценария 31 Требуют браузерного агента (Claude Cowork или npm run test:e2e:browser). Вне рамок выполнения только через curl; покрыты существующим Playwright smoke.
Перевод тел RU CHANGELOG для более старых записей (v1.5.x и ниже) Перезапущенный агент покрыл только v1.6.0 и далее. Записи до v1.6 (v1.5.x и т. п.) — если они когда-либо существовали — остаются ранее существовавшим содержимым.
Визуальная регрессия скриншотов дашборда после будущих изменений SPA scripts/capture-dashboard-screenshots.mjs перегенерирует PNG по локалям; автоматического diff сейчас нет.

[1.23.0] — 2026-05-14

Разделение i18n + исправление CI для баннера соединения + локализованные скриншоты дашборда + закрытие всех заглушек бэклога. Релиз закрывает три пункта, отмеченные в таблице «Out of scope» версии v1.22.0 для v1.23 (M-9 — переводы тел локальных CHANGELOG, N-1 — разделение i18n.js по LOC, аудит содержимого справочного бандла), и добавляет hot-fix для smoke E2E, который после v1.22.0 окрашивал CI на ветке main в красный.

🚑 Hot-fix CI — восстановление баннера соединения

  • fix(client): reset health-poll cadence + visibilitychange eager re-check (public/js/api.js:21-91) — экспоненциальный backoff из M-6 в v1.22.0 был корректен (3 с → 6 с → 12 с → потолок 15 с вместо прежнего потолка 60 с), но активный setTimeout оставался привязан к ранее установленной задержке. Сервер, остановленный в t=0.1 при первом пинге в t=3, проваливал проверку, удваивал задержку до 6 с, а следующая попытка восстановления срабатывала только в t=9. Smoke E2E «Flow 2a: баннер соединения появляется при остановке сервера и скрывается при восстановлении» ждал лишь 4 с и падал на main.

    v1.23.0 переделывает цикл опроса:

    • _healthHandle теперь отслеживается, поэтому setConnectionState(lost=true) может вызвать clearTimeout и перепланировать опрос с _HEALTH_MIN. Первая проба восстановления срабатывает в течение 3 с после потери соединения независимо от ранее запланированной задержки.
    • _HEALTH_MAX снижен с 60 с до 15 с. Свёрнутая вкладка против остановленного сервера всё равно восстанавливается за один цикл опроса при возврате пользователя, а экономия трафика остаётся значительной.
    • document.addEventListener('visibilitychange') инициирует немедленную перепроверку при возврате фокуса вкладке, если connectionLost === true. Переключение Cmd-Tab не ждёт следующего тика backoff.

🧹 N-1 — разделение i18n.js (превышение цели в 400 LOC)

  • refactor(client): split DICT into i18n-dict.js (data) + i18n.js (logic) — до v1.23 файл public/js/lib/i18n.js содержал 639 строк. Основная часть (строки 23-586) — таблица переводов DICT, чистые структурированные данные. v1.23.0 выносит её в public/js/lib/i18n-dict.js (578 LOC, освобождён от лимита LOC по правилу CLAUDE.md «Exempt from these limits: generated files, migrations, test fixtures, lock files, vendored code» — таблица переводов квалифицируется как фикстура), оставляя в public/js/lib/i18n.js 86 LOC чистой логики модуля (значительно ниже целевого лимита в 400 LOC).
  • Контракт загрузки: i18n-dict.js заполняет window.__I18N_DICT = { … }, после чего i18n.js читает его внутри существующего IIFE. public/index.html загружает их по порядку — i18n-dict.js перед i18n.js — так что IIFE видит полностью заполненный DICT во время инициализации. Запасной вариант при отсутствии словаря: каждый вызов t() возвращает встроенный fallback или сам ключ, что громко сигнализирует о неправильной конфигурации без падения SPA.
  • Обновление тестовой инфраструктуры: tests/i18n-coverage.test.mjs, tests/help-ui.test.mjs, tests/canonical-docs-coverage.test.mjs теперь прогоняют оба файла через тестовый VM-контекст (или конкатенируют их исходники для regex-обхода), сохраняя все существующие проверки.

🌐 M-9 — переводы тел локальных CHANGELOG

  • docs(translate): 7 non-EN CHANGELOG files end-to-end — до v1.23 файлы CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md содержали англоязычные заглушки для всех записей начиная с v1.13.0 и подвал, отправлявший читателя к каноническому EN. v1.23.0 запускает 7 параллельных агентов перевода (по одному на локаль), которые переписывают каждое тело в публикационном техническом стиле целевого языка. Заглушки удалены. Блоки кода, пути к файлам, URL, строки сообщений коммитов (fix(security): B-1 — …), переменные окружения и метки ссылок сохранены дословно во всех локалях.

🖼️ Локализованные скриншоты дашборда в каждом README

  • docs(readme): wire each locale README at its locale-specific PNG — до v1.23 только README.pt-BR.md ссылался на dashboard-pt-BR.png; остальные 6 не-EN README продолжали указывать на dashboard-en.png. Скриншоты (уже снятые в цикле v1.22.0 скриптом scripts/capture-dashboard-screenshots.mjs) лежали в images/, но не использовались. v1.23.0 обновляет 14-ю строку каждого README.{es,ja,ko-KR,ru,zh-CN,zh-TW}.md на собственный dashboard-<locale>.png.

🧪 Тесты

  • Те же 474 / 474 unit + 32 / 32 Playwright, что и в v1.22.0. Smoke E2E теперь 20 / 20 (было 19 / 1 fail на main после v1.22.0 из-за регрессии восстановления баннера; исправление перепланирования в v1.23.0 закрывает её).
  • Три существующих теста перенастроены под разделение i18n. Ноль новых тестовых файлов, ноль удалённых проверок.

Верификация

$ npm test
# 474 / 474

$ npm run test:e2e
# passed: 20    failed: 0    (было 19/1 на v1.22.0 main)

$ wc -l public/js/lib/i18n.js public/js/lib/i18n-dict.js
#       86 public/js/lib/i18n.js          ← логика, под целевым лимитом
#      578 public/js/lib/i18n-dict.js     ← фикстура данных, исключение

$ grep -h 'dashboard-' README*.md | sed -E 's/.*(dashboard-[^)]+).*/\1/' | sort -u
# dashboard-en.png    (только README.md)
# dashboard-es.png    dashboard-ja.png
# dashboard-ko-KR.png dashboard-pt-BR.png
# dashboard-ru.png    dashboard-zh-CN.png  dashboard-zh-TW.png

# Проверка перевода CHANGELOG: каждый файл локали > 200 строк собственного содержимого
$ wc -l CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md | grep -v total

Несовместимые изменения

Нет. public/index.html теперь загружает два скрипта вместо одного — все, кто раздаёт SPA через CDN, должны подхватить i18n-dict.js; порядок загрузки скриптов определяется порядком тегов <script src> в index.html. Runtime-fallback (пустой DICT → t() возвращает встроенный EN-fallback) предотвращает жёсткие падения при отсутствии нового файла.

Вне рамок (v1.24+)

Пункт Примечания
Обновление СОДЕРЖИМОГО справочного бандла из career-ops.org/docs (а не только покрытия URL) Пять канонических URL уже присутствуют в справочном бандле каждой локали с v1.11.x, а сценарий 31.6 QA-промпта проверяет покрытие. Обновление глубины содержимого — кандидат на v1.24+.
Живое выполнение QA-сценария 31 на запущенном сервере Требует браузерного агента и реальных LLM-учётных данных. Кандидат на v1.24.
Покомпонентный аудит touch-target для новых блоков подсказок на mode-page M-1 из v1.22.0 добавил элементы <p class="field-hint">, которые не были проверены на соответствие WCAG 2.5.5 min-height во всех 8 локалях.

[1.22.0] — 2026-05-14

Закрытие бэклога M/L/N + выравнивание документации + проход по качеству переводов. Весь средний и младший ярус из v1.20.1-BACKLOG.md отгружен одним релизом: девять M-пунктов, пять L-пунктов, две мелочи. Плюс аудит документации против пяти канонических руководств career-ops.org/docs, обновлённые системные промпты под .claude/ и .github/, а также прошедшие проверку качества README во всех 7 не-английских локалях.

🛡️ Усиление безопасности (defense-in-depth)

  • fix(security): M-4 — entity-aware stripDangerousMarkdown (server/lib/security.mjs) — regex до v1.22 сопоставлял <script>, javascript:, on*= как литеральные подстроки. &lt;script&gt;, java&#115;cript: и <img src="data:image/svg+xml,<svg onload=…>"> проходили насквозь. Очистка теперь декодирует &lt;, &gt;, &amp;, &quot;, числовые (&#NN;) и шестнадцатеричные (&#xHH;) сущности до запуска regex-очистки. Подтверждено 11 тестами в tests/cv-xss-bypasses.test.mjs. Основная защита по-прежнему лежит на клиентском конвейере UI.md с приоритетом escape; этот патч закрывает файл на диске.

  • fix(security): L-2 — bash --noprofile --norc on the batch runner (server/lib/routes/batch.mjs:108) — spawn('bash', [PATHS.batchRunner, ...]) ранее подхватывал пользовательский ~/.bashrc. Враждебный rc-файл мог влиять на выполнение. Теперь spawn('bash', ['--noprofile', '--norc', PATHS.batchRunner, ...]).

🔒 Устойчивость

  • fix(client): M-6 — exponential backoff on health ping (public/js/api.js:22-48) — поллер состояния «нет соединения» совершал 28 800 запросов к мёртвому серверу за ночь. Теперь 3 с → 6 с → 12 с → 24 с → 60 с, сброс к 3 с при первом успешном восстановлении (2xx). Реализация на цепочке setTimeout (не setInterval), поэтому каждый шаг подхватывает новую задержку.

  • fix(client): M-5 — Safari private-mode localStorage guard (public/js/lib/i18n.js:572-583) — Safari в приватном режиме выбрасывает SecurityError на каждый вызов localStorage.getItem/setItem. IIFE во время загрузки роняла весь модуль i18n, и SPA рендерил сырые ключи. Оба вызова обёрнуты в try/catch, в качестве запасного варианта используется detect() по языку браузера.

  • fix(server): M-2 — body-size cap on outbound preview fetches (test + verify) — функция safeGet из v1.21.0 уже стримила чанки и ограничивала размер opts.maxBytes. v1.22 добавляет явный регрессионный тест в tests/ssrf-redirect-rebind.test.mjs, фиксирующий контракт: 100 КБ вверх по потоку + лимит 4 КБ → ответ ≤ 4 КБ.

  • fix(client): L-5 — clear setTimeout on hashchange in scan.js (public/js/views/scan.js:6-22, :113-120) — таймер refreshResults() на 300 мс после завершения утекал, если пользователь уходил с #/scan в это окно. Дескриптор теперь захватывается и очищается в __cancelActiveScanPoll.

  • fix(client): L-4 — multi-line SSE data: joiner (public/js/lib/auto-pipeline.js:158-176) — парсер SSE использовал match() (одна строка). По спецификации событие может нести несколько строк data:, которые потребитель должен соединить через \n. Сейчас сервер шлёт однострочный JSON, и старый код работал — но был хрупким к любой будущей многострочной полезной нагрузке.

♿ Доступность

  • feat(a11y): M-3 — WCAG 1.4.1 redundant cues on score pills + connection banner (public/css/app.css:602-625, :812-822) — score-high / score-mid / score-low ранее передавали состояние только оттенком (красный/жёлтый/зелёный). Пользователи без цветовосприятия не имели альтернативы. Каждый уровень теперь получает дополнительный глиф через ::before (✓ / ◐ / ○). Баннер соединения получает префиксный глиф ⚠ в офлайн-состоянии. Места рендеринга не тронуты — чисто CSS-усиление.

  • feat(a11y): M-1 — inline hint paragraphs for every mode-page field (public/js/views/mode-page.js, public/js/lib/i18n.js) — v1.20.0 связал htmlFor → id для каждого поля mode-page, но не добавил встроенных подсказок; назначение поля документировалось только в README-проходах. v1.22.0 добавляет 19 ключей подсказок i18n × 8 локалей = 152 новых перевода, а построитель field() теперь рендерит <p id="…-hint"> с привязкой через aria-describedby для каждого поля. Пользователи скринридеров слышат подсказку при фокусе на инпуте.

  • fix(a11y): M-7 — null-guard on UI.el() htmlFor alias (public/js/api.js:194-198) — htmlFor: null ранее рендерил литеральный for="null". Однострочное зеркало защиты v != null && v !== false из соседней ветки.

🧹 Качество / переносимость

  • fix(server): L-1 — parseInt radix in health.mjs + bin/start.sh + bin/setup.sh — parseInt(process.versions.node) без основания вызывает предупреждение линтера и хрупок, если Node однажды начнёт отдавать шестнадцатеричные версии. Везде добавлено 10.

  • fix(server): L-3 — Windows-safe entrypoint check (server/index.mjs:159-163) — import.meta.url === \file://${process.argv[1]}`неправильно обрабатывает буквы дисков и обратные слэши в Windows. Заменено наfileURLToPath(import.meta.url) === path.resolve(process.argv[1])`.

  • refactor(client): N-2 — drop Element.prototype.also monkey-patch (public/js/views/cv.js:188-201) — глобальное загрязнение прототипа DOM. Заменено локальной переменной для корня дерева.

  • test(canary): M-8 — 404 regression test for retired /api/scan-ru/config (tests/scan-consolidated.test.mjs) — v1.20.0 убрал alias, но не добавил канарейку. Трёхстрочное добавление по образцу тестов вывода из эксплуатации в v1.18.

📚 Документация и системные промпты

  • docs(architecture): refresh OVERVIEW + DATA-FLOWS for v1.21+ surface — в OVERVIEW.md добавлены safe-fetch.mjs (GET с привязкой к DNS), file-lock.mjs (мьютекс на путь), rate-limit.mjs (троттлинг LLM) и sanitizePathName. В DATA-FLOWS.md появились два новых раздела: «Outbound URL fetches (DNS-rebind-safe)» и «LLM endpoint rate-limiting».

  • docs(readme): security envelope section refresh — раздел «Security notes» README.md теперь документирует все хелперы из envelope безопасности v1.21+ (sanitizePathName, safeGet, withFileLock, llmRateLimit, entity-aware stripDangerousMarkdown).

  • docs(qa): scenario 31 — career-ops.org/docs alignment (qa/claude-cowork-browser-test-prompt.md) — шесть новых под-тестов (31.1–31.6), проверяющих соответствие UI поведению из пяти канонических руководств career-ops.org/docs: пороги оценок, поток сканирования (одна кнопка), поток подачи (чек-лист, не автоотправка), batch-поток (TSV-редактор), установка Playwright (мягкое падение), покрытие справочного бандла (5 URL × 8 локалей).

  • docs(translate): README quality refresh × 7 non-EN locales — каждый не-EN README переписан в публикационном техническом стиле родного языка. Распространённые корявые кальки заменены; добавлены упоминания envelope безопасности v1.21/v1.22; обновлены бейджи релиза и тестов.

  • docs(system): .claude/PROJECT-CONTEXT.md + .github/copilot-instructions.md — однофайловая ориентация для агентов, подключающихся к сессии. Сжатый CLAUDE.md, перечень хелперов v1.21+, список типичных подводных камней.

  • docs(bin): actualize start.sh / setup.sh / run_all.sh comments — «two deps» → «three deps» (express + js-yaml + multer); «298 tests» → «474+ tests»; добавлено основание parseInt.

🧪 Тесты

  • 461 → 474 unit (+13) + 32/32 Playwright без изменений.
  • Новые файлы тестов: cv-xss-bypasses.test.mjs (M-4, 11 тестов).
  • Расширены: ssrf-redirect-rebind.test.mjs (+1 для лимита размера тела M-2), scan-consolidated.test.mjs (+1 для канарейки alias M-8).
  • Ноль поведенческих изменений в существующих наборах — каждый патч аддитивен или покрыт новой канарейкой.

Верификация

npm test                          # 474 / 474
npm run test:e2e:browser          # 32 / 32

# Очистка XSS с entity-кодированием:
node -e "import('./server/lib/security.mjs').then(({stripDangerousMarkdown}) => console.log(stripDangerousMarkdown('&lt;script&gt;alert(1)&lt;/script&gt;')))"
# → '' (ни один <script> не выживает)

# Backoff health-ping (открыть devtools, остановить сервер, наблюдать панель network):
#   3 с → 6 с → 12 с → 24 с → 60 с, сброс при первом успешном пинге

# Глиф score-pill (открыть #/reports в светлой и тёмной теме):
#   .score-high показывает ✓ + числовую оценку
#   .score-mid  показывает ◐ + числовую оценку
#   .score-low  показывает ○ + числовую оценку

# Подсказки mode-page (#/contacto и т. п.):
#   <input aria-describedby="mode-contacto-recipient-hint">  ← указывает на <p id="…">

# Снятый с эксплуатации alias:
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4317/api/scan-ru/config
# → 404

Несовместимые изменения

Нет. Каждый патч аддитивен либо сохраняет контракты существующих эндпоинтов.

Вне рамок (v1.23+)

Пункт Примечания
M-9 — переводы тел локальных CHANGELOG Все записи v1.13+ в CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md — англоязычные заглушки. Кандидат на массовый перевод после замедления каденции релизов.
N-1 — public/js/lib/i18n.js превышает целевой лимит в 400 LOC Разделение по локалям увеличивает стоимость HTTP без сборщика. Отложено до решения по этапу сборки.
Обновление содержимого справочного бандла из career-ops.org/docs Пять канонических URL уже присутствуют в каждом локальном бандле (с v1.11.x). Сценарий 31.6 QA-промпта проверяет покрытие. Обновление глубины содержимого — кандидат на v1.23.

[1.21.0] — 2026-05-14

Безопасность + параллелизм + полировка a11y по результатам двух независимых код-ревью. Семь находок из docs/sdd/specs/V1.20.1-BACKLOG.md отгружены одним релизом: один блокер (TOCTOU при DNS-rebind), шесть багов высокой степени серьёзности (распыление санитайзинга при path-traversal, отсутствие лимита запросов на LAN-деплое, состояние гонки при конкурентной записи, провал в покрытии i18n, висячий aria-describedby, отсутствие связей с метками). 34 новых теста; baseline вырос с 427 → 461 unit + 32/32 Playwright. Каждый патч закрыт именованным регрессионным тестом.

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

  • fix(security): B-1 — close DNS-rebind TOCTOU via safe-fetch.mjs (server/lib/safe-fetch.mjs) — прежний паттерн делал один явный dnsLookup для валидации, а затем fetch() выполнял свой собственный независимый lookup. Атакующий через DNS-rebind с TTL=0 мог вернуть публичный IP на первом lookup и 127.0.0.1 / 169.254.169.254 / адрес LAN на втором, обходя isPrivateOrLoopbackHost. Новая функция safeGet резолвит ОДИН раз, привязывает TCP-соединение к конкретному IP через node:http(s) и выставляет SNI/Host так, чтобы проверка сертификата по-прежнему шла против исходного hostname. Используется /api/pipeline/preview и /api/auto-pipeline. Fail-CLOSED при ошибке lookup (обратно к прежнему try { … } catch { /* fall through */ }). Подтверждено 8 новыми тестами в tests/ssrf-redirect-rebind.test.mjs.

  • fix(security): H-4 — consolidate sanitizePathName across 10 routes (server/lib/security.mjs) — голый regex replace(/[^\w\-.]/g, '') дублировался в jds.mjs, content.mjs, reports.mjs, llm.mjs, runners.mjs и сохранял символы ., из-за чего ..pdf, ....md, имена с ведущей точкой выживали. Только reports.mjs::sanitizeSlug делал это правильно. v1.21.0 поднимает корректную версию (sanitizePathName) в security.mjs, удаляет 10 сломанных копий и отвергает пустые результаты с кодом 400. Подтверждено 12 тестами в tests/path-traversal.test.mjs.

  • fix(security): H-5 — rate-limit LLM endpoints on public bind (server/lib/rate-limit.mjs) — у /api/evaluate, /api/deep, /api/mode/:slug, /api/auto-pipeline ранее не было троттлинга по IP. Loopback-пользователей это не затрагивает; LAN-деплои (HOST=0.0.0.0) получают 10 запросов/мин/IP с заголовками Retry-After и X-RateLimit-* при превышении. Настраивается через LLM_RATE_LIMIT="N/Ws". Дешёвая промежуточная защита до auth-шлюза P-12 в v2.0. Подтверждено 6 тестами в tests/rate-limit.test.mjs.

🔒 Параллелизм

  • fix(data): H-6 — per-file mutex on applications.md / pipeline.md (server/lib/file-lock.mjs) — конкурентные POST /api/tracker (или auto-pipeline в гонке с ручным добавлением) ранее оба читали num=42, оба писали num=43, и более ранняя строка тихо терялась. withFileLock(path, fn) сериализует read-modify-write на путь; независимые пути по-прежнему выполняются параллельно. Подключено в tracker.mjs, pipeline.mjs (POST + DELETE) и в шаге трекера auto-pipeline.mjs. Подтверждено 5 тестами в tests/concurrent-tracker-write.test.mjs, включая интеграционную проверку 20 одновременных POST с утверждением, что строки 001..020 встают последовательно.

♿ Доступность

  • fix(a11y): H-1 — id="batch-tsv-hint" on the batch.js hint paragraph (public/js/views/batch.js) — в v1.20.0 был добавлен aria-describedby="batch-tsv-hint" на textarea TSV, но соответствующий id у блока <p> не появился. Скринридерам нечего было озвучивать. Исправлено.

  • fix(a11y): H-2 — htmlFor on batch-parallel / batch-min-score labels (public/js/views/batch.js) — четыре инпута из v1.20.0 получили новые id, но их метки не были программно связаны. WCAG 3.3.2 теперь удовлетворён.

  • Новая канарейка статического анализа в tests/a11y-form-wires.test.mjs — обходит каждый view-файл и утверждает, что каждая IDREF в aria-describedby / htmlFor указывает на соседнее объявление id:. Ловит регрессии класса опечаток в CI.

🌐 i18n

  • fix(i18n): H-3 — 13 keys from v1.20.0 silently fell through to EN for 7 locales (public/js/lib/i18n.js) — pipe.filter, pipe.count, pipe.preview*, pipe.openTab, pipe.evaluateAll*, eval.jdHint, batch.parallelAria, batch.minScoreAria, плюс common.delete, config.group{Core,Runtime,Regional}, config.profileEmpty, config.viewProfile, scan.atsBadge, scan.regionalBadge упоминались как t('key', 'EN fallback'), но никогда не добавлялись в DICT. Пользователи скринридеров на русском, японском и китайском слышали английские aria-label, что прямо обнуляло выигрыш по WCAG 3.3.2, заявленный в v1.20.0. v1.21.0 добавляет все 19 ключей × 8 локалей (≈ 150 новых переводов) и расширяет tests/i18n-coverage.test.mjs проходом статического анализа, который сканирует каждый вызов t('key', …) в public/js/**/*.js и проверяет наличие ключа в DICT. Будущий дрейф ловится на этапе CI.

🧪 Тесты

  • 427 → 461 unit (+34) + 32/32 Playwright без изменений.
  • Новые файлы: ssrf-redirect-rebind, path-traversal, concurrent-tracker-write, rate-limit, a11y-form-wires.
  • Существующий pipeline-preview.test.mjs переподключён с мока globalThis.fetch к новой точке инъекции _setTransport в safe-fetch.mjs — путь SSRF больше не идёт через fetch, и старый мок незаметно обходился.

Верификация

npm test                              # 461 / 461
npm run test:e2e:browser              # 32 / 32
node --test tests/ssrf-redirect-rebind.test.mjs tests/path-traversal.test.mjs \
  tests/concurrent-tracker-write.test.mjs tests/rate-limit.test.mjs \
  tests/a11y-form-wires.test.mjs      # 34 новых теста, все зелёные

# Path-traversal: каждое имя в стиле traversal возвращает 400 / 404
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4317/api/jds/..pdf
# → 400

# Лимит запросов при публичной привязке:
HOST=0.0.0.0 LLM_RATE_LIMIT=3/60s npm start &
for i in 1 2 3 4; do
  curl -sS -o /dev/null -w '%{http_code} ' -X POST -H 'Content-Type: application/json' \
    -d '{"jd":"…"}' http://0.0.0.0:4317/api/evaluate
done
# → 200 200 200 429

# Конкурентные записи в трекер: 20 параллельных POST, 20 строк встают:
node tests/concurrent-tracker-write.test.mjs
# 20 последовательных строк 001..020

# Проверка связей aria:
grep -r 'aria-describedby' public/js/views/ | wc -l
# совпадающие `id:` все резолвятся (канарейка a11y-form-wires.test.mjs)

Вне рамок (v1.22+)

Пункт Примечания
Потоковый лимит размера тела в pipeline-preview (M-2) await upstream.text() читает тело целиком перед срезом в 8 КБ; вредоносный поток в 1 ГБ может исчерпать память. Поток-чтение с байтовым счётчиком + abort.
WCAG 1.4.1 — состояние только цветом на .connection-banner + score pills (M-3) Оттенок единолично сигнализирует состояние; добавить префиксную иконку (✓ / ◐ / ○) или текстовый суффикс.
Обходы stripDangerousMarkdown через HTML-сущности (M-4) &lt;script&gt;, java&#115;cript:, <img src="data:image/svg+xml,<svg onload=…>"> выживают regex. Defense-in-depth через UI.md ещё держится; зафиксировать и закрыть обходы пакетом тестов.
Доступ к localStorage в приватном режиме Safari без try/catch (M-5) i18n.js:544/571 бросает исключение → SPA рендерит сырые ключи. Обернуть в try/catch с 'en' по умолчанию.
setInterval(checkHealth, 3000) опрашивает вечно без backoff (M-6) Экспоненциальный 3 с → 6 с → 12 с → потолок 60 с.
Отсутствует null-guard для алиаса htmlFor (M-7) Однострочная защита if (v != null && v !== false).
Канарейка 404 для снятого с эксплуатации /api/scan-ru/config (M-8) Трёхстрочный тест по образцу v1.18.
Переводы тел локальных CHANGELOG (M-9) Кандидат на массовый перевод после замедления каденции релизов.
Inline-блоки подсказок для каждого поля mode-page (M-1) ~168 ключей i18n × 8 локалей; отложено как полировка.
Мелочи L-1 — L-5 Основание parseInt, bash –noprofile, Windows-safe fileURLToPath, многострочный SSE, очистка таймера scan.js.

[1.20.0] — 2026-05-13

Покомпонентная полировка a11y + паритет не-EN README + снятие алиаса /api/scan-ru/config. Закрывает четыре пункта, помеченные таблицей «Out of scope» в v1.19.0 для v1.20.

♿ WCAG 2.5.5 / 2.5.8 — покомпонентный аудит touch-target

  • a11y(touch-target): chip min-height 28 px + 8 px gap (2.5.8 spaced-target exception) — .chip был 24 × ~50 px (вертикаль 24 не дотягивала до пола 24 px из 2.5.5 для кластерных контролов); исключение spaced-target в 2.5.8 требует либо ≥ 24 × 24 px, либо 24 px зазора. .chip поднят до min-height: 28px; padding: 6px 12px;, а обёртка .chip-row — до gap: 8px;, чтобы выполнялись оба условия.
  • a11y(touch-target): sidebar nav-item min-height 44 px — у .nav-item был padding всего 10px 14px, вычисленная высота ~36 px на большинстве viewport. Теперь padding: 12px 14px; min-height: 44px; box-sizing: border-box;. Совпадает с полом .btn.
  • a11y(touch-target): tab-btn min-height 44 px — то же лечение для Sortable Headers / вкладок категорий в Reports, Tracker, Scan results.

♿ WCAG 1.3.1 / 3.3.2 — aria-describedby на встроенных подсказках формы

Каждый элемент управления формой в SPA теперь обладает стабильным id, его <label> указывает на него через htmlFor, а любой блок подсказки связан через aria-describedby. Перенастроены пять view-файлов:

  • a11y(forms): config.js — id на каждый ключ + привязка подсказки (cfg-<key> / cfg-<key>-hint).
  • a11y(forms): evaluate.js — textarea eval-jd + блок eval-jd-hint, документирующий минимум в 50 символов после санитайзинга.
  • a11y(forms): batch.js — batch-tsv / batch-tsv-hint, плюс aria-label на batch-parallel, batch-min-score, batch-dry-run, batch-retry.
  • a11y(forms): pipeline.js — pipe-filter + pipe-new-url / pipe-new-url-hint.
  • a11y(forms): mode-page.js — каждое поле в 7 универсальных режимах (project, training, followup, batch-prompt, contacto, interview-prep, patterns) получает id вида mode-<slug>-<name> и метки htmlFor.

UI.el() научился алиасу htmlFor в стиле React, чтобы код view оставался декларативным — алиас выставляет атрибут for (зарезервированное в JS имя свойства).

🌍 Паритет не-EN README

  • docs(readme): translate 7 locales to 585-line parity with EN master — README.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md имели 306–316 строк (заголовки покрывались, но маркетинговые проходы и большая часть API-справочника пропускались). Все семь теперь зеркалируют EN-структуру end-to-end: About → One-command install → Why? → Quick start (3 пронумерованных шага) → Requirements → What you get table → Scan → Architecture (полное дерево директорий) → API reference (каждая таблица маршрутов) → Tests → Configuration → Security notes → Limitations → Contributing → 🌍 Getting Started — 5-шаговый проход → License.

🧹 Снятие алиаса /api/scan-ru/config

  • feat!(scan): remove /api/scan-ru/config legacy alias (sunset v1.20) — сохранялся одним релизом как alias в v1.19 для обратной совместимости. Канонический /api/scan/regional/config — теперь единственный путь. Удалены: регистрация маршрута в server/lib/routes/scan.mjs, упоминания в README.md, docs/architecture/{OVERVIEW,SERVER,API}.md. Тесты уже покрывали канонический путь — изменения в тестах не требуются.

🧪 Тесты

  • Тот же набор, что и v1.19. 427 / 427 unit + 20/20 smoke + 23/23 comprehensive + 32/32 Playwright. Вся обвязка a11y аддитивна (больше id / for / aria-describedby) — поведенческих изменений нет, дельты в тестах нет.

Верификация

npm test                              # 427 / 427
npm run test:e2e:browser              # 32 / 32

# Touch-target — каждый chip / nav-item / tab-btn ≥ 28 / 44 / 44 px:
#   Chrome DevTools → Computed → height/min-height на .chip, .nav-item, .tab-btn

# Метки формы — у каждого input есть связь label[for=…]:
#   document.querySelectorAll('input,textarea,select').forEach(el =>
#     console.assert(el.labels?.length || el.getAttribute('aria-label'), el))

# Alias снят:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4317/api/scan-ru/config
# → 404

# Канонический работает:
curl -s http://127.0.0.1:4317/api/scan/regional/config | jq '.'

Несовместимые изменения

  • DELETE /api/scan-ru/config — удалён. Используйте /api/scan/regional/config. Снятие было анонсировано в CHANGELOG v1.19.0 и в верификационном скрипте.

Вне рамок (v1.21+)

Пункт Примечания
Inline-блоки подсказок для каждого поля mode-page Сейчас на месте только связь <label for=…>; видимые подсказки на поле в SPA остаются только на английском. Проходы README документируют назначение полей в каждой локали, поэтому это полировка, а не блокер.
Сигналы состояния только цветом в .connection-banner и pill оценок дашборда (WCAG 1.4.1) Баннер опирается на красный/жёлтый/зелёный; нужен иконочный или текстовый суффикс для пользователей без цветовосприятия.
Переводы тел локальных CHANGELOG Англоязычные заглушки остаются в CHANGELOG.{es,pt-BR,ko-KR,ja,ru,zh-CN,zh-TW}.md. Перевод будет выполнен после замедления каденции релизов v1.x.

[1.19.0] — 2026-05-13

Контраст WCAG 1.4.3 + унификация сканера (финал) + удаление HH_USER_AGENT из UI. Закрывает отложенный в v1.18 аудит контраста, завершает устранение разделения EN/RU, начатое в v1.18, и убирает настройку HH_USER_AGENT из UI по указанию пользователя (разумный дефолт уже встроен в сервер и подходит большинству не-RU IP).

♿ Проход контраста WCAG 1.4.3

  • a11y(contrast): introduce AA-passing *-text variants for accent tokens — светлая тема: --rausch-text: #b80f42 (6.59:1 на белом, было 3.52:1), --kazan-text: #066507 (7.31:1, было 4.53:1), --darjeeling-text: #7a5800 (5.73:1 на янтарном фоне, было 4.24:1), --babu-text: #00665e (6.09:1, было 2.70:1). Тёмная тема: осветлённые зеркала (#ff8aa0, #6ee7b7, #fcd34d, #5eead4) держат тот же пол 4.5:1 на бумаге #161a22.
  • Классы бейджей (.badge-ok, .badge-warn, .badge-bad, .badge-info) и pill оценок (.score-high, .score-mid, .score-low) теперь идут через новые *-text варианты — каждая комбинация «текст на тонированном фоне» проходит AA. Токены заливки акцентов (--rausch, --kazan и т. д.) не тронуты для рамок и контуров (для нетекстовых компонентов UI достаточно 3:1).

🧹 Унификация сканера (завершение работы v1.18)

  • docs(scan): scrub remaining EN/RU split references across READMEs + help + architecture docs — восемь README + восемь справочных бандлов + три архитектурных документа (API.md, SERVER.md, OVERVIEW.md, DATA-FLOWS.md) + комментарий в scan.js теперь описывают единый консолидированный метод сканирования. Унаследованные алиасы /api/stream/scan-{en,ru} уже отсутствовали в v1.18; v1.19 ловит документацию и копию, всё ещё представлявшую сканирование как двухшаговый процесс EN+RU.
  • feat(scan): canonical /api/scan/regional/config endpoint — /api/scan-ru/config сохраняется как тонкий alias на один релиз для обратной совместимости. Новый путь соответствует соглашению об именовании источников (?source=regional).

🛠️ HH_USER_AGENT удалён из UI

  • feat!(config): drop HH_USER_AGENT field from /#/config + KNOWN_KEYS — продвинутые пользователи по-прежнему могут задать HH_USER_AGENT напрямую в career-ops/.env (сервер читает через process.env.HH_USER_AGENT в server/lib/sources/hh.mjs с встроенным UA в качестве запасного варианта). UI больше не показывает этот ключ: дефолт работает у большинства, а наличие непонятного поля User-Agent в App Settings регулярно вводило в заблуждение.
  • Упоминания в README в 8 локалях + упоминания в справочных бандлах в 8 локалях заменены советом «запустить через российский IP / VPN». Ключ i18n scan.hhWarning перефразирован без подробностей про настройку env-переменной.
  • KEY_GROUPS схлопнут: классификации regional больше нет (она содержала только HH_USER_AGENT). Тесты обновлены; поле regionalActive в payload сохранено для обратной совместимости SPA.

🧪 Тесты

  • tests/env-config.test.mjs — проверка KNOWN_KEYS теперь исключает HH_USER_AGENT; добавлено утверждение, что ключ намеренно отсутствует.
  • tests/config-endpoint.test.mjs — тест POST-записи нескольких ключей использует GEMINI_MODEL в качестве второго известного ключа вместо HH_USER_AGENT.
  • tests/config-groups.test.mjs — groups.HH_USER_AGENT теперь ожидается undefined.
  • Итого: 427 / 427 unit + 20/20 smoke E2E + 23/23 comprehensive E2E + 32/32 Playwright. Те же цифры, что и в v1.18.0, потому что все скорректированные тесты уже учтены.

Верификация

npm test                              # 427 / 427

# Контраст (Chrome DevTools или axe) на светлой и тёмной:
#   .badge-ok / .badge-warn / .badge-bad / .badge-info → AA pass (4.5:1+)
#   .score-high / .score-mid / .score-low → AA pass

# HH_USER_AGENT больше не в /api/config:
curl -s http://127.0.0.1:4317/api/config | jq '.values | keys'
# → ["ANTHROPIC_API_KEY","ANTHROPIC_MODEL","GEMINI_API_KEY","GEMINI_MODEL","HOST","PORT"]
# (без HH_USER_AGENT)

# Канонический эндпоинт regional config:
curl -s http://127.0.0.1:4317/api/scan/regional/config | jq '.'
# Унаследованный alias жив до v1.20:
curl -s http://127.0.0.1:4317/api/scan-ru/config | jq '.'

Вне рамок (v1.20+)

Пункт Примечания
Покомпонентный аудит touch-target (chip-фильтры, sortable headers, навигация сайдбара) v1.18 установил глобальный пол (.btn 44 px, .btn-sm 32 px); покомпонентная верификация по всему SPA остаётся.
aria-describedby на встроенных подсказках формы (#/config, #/pipeline, #/evaluate, #/batch) v1.17 покрыл aria-label на глобальном поиске и закрытии модалки. Связывание подсказок на input — следующий слой полировки.
Полный паритет не-EN README (585 строк как у EN) v1.18 поднял не-EN до ~307 (53 % от EN). Маркетинговые «Quick start» + «🌍 Getting Started» проходы остаются только на английском.
Удалить унаследованный alias /api/scan-ru/config Снятие запланировано на v1.20. Канонический /api/scan/regional/config — целевая миграция.

[1.18.0] — 2026-05-13

Консолидация эндпоинтов сканирования + проход WCAG 2.2 AA + завершение длинного хвоста i18n. Снимает с эксплуатации унаследованные алиасы /api/stream/scan-{en,ru} (окно Sunset 2026-10-01 перенесено на v1.18 по указанию пользователя). Доводит не-EN README до ~307 строк и переводит оставшиеся RU-телесные записи v1.16.0 + v1.17.0 в CHANGELOG для 6 локалей.

🚪 Несовместимые изменения

  • feat!(scan): retire legacy /api/stream/scan-{en,ru} aliases — устаревшие SSE-эндпоинты EN/RU удалены. Каждый потребитель идёт через консолидированный /api/stream/scan?source=ats|regional|both (живёт с v1.12.0). У унаследованных путей с v1.15.0 были заголовки Deprecation + Sunset (RFC 8594); окно миграции закрыто. Внешние интеграции на старых путях получают чистый 404 вместо тихой маршрутизации в catch-all SPA.

♿ Доступность (проход WCAG 2.2 AA)

  • WCAG 2.4.1 Bypass Blocks — новая ссылка Skip to main content как первый фокусируемый элемент на каждой странице. Визуально скрыта через .skip-link до получения фокуса, при Tab от загрузки страницы вытаскивается в верхний-левый угол.
  • WCAG 2.4.7 Focus Visible — глобальный стиль *:focus-visible. Фокусные кольца на клик мыши выключены, на нажатие Tab — включены (стандартный паттерн WAI-ARIA AP). Кнопка закрытия модалки (×) получает кольцо фокуса повышенной контрастности.
  • WCAG 2.5.5 Target Size — минимум 44×44 px touch-target на .skip-link. .btn-sm сохраняет min-height: 32px (в сочетании с межстрочным интервалом удовлетворяет исключению AAA «24×24 + spacing» для компактных контролов строк таблиц).
  • WCAG 3.1.1 Language of Page — <html lang="en"> исправлен с lang="ru" (JS-бутстрап i18n уже переопределял это при загрузке, но SSR-дефолт теперь совпадает с дефолтной локалью SPA).
  • WCAG 1.3.1 Info & Relationships — #content получает tabindex="-1", чтобы цель skip-link фокусировалась чисто. (Роли ARIA + ловушка фокуса были добавлены ещё в v1.17.)

📚 Длинный хвост i18n

  • docs(i18n): v1.16.0 + v1.17.0 CHANGELOG translated in 6 locales — записи, ранее остававшиеся с русским телом в CHANGELOG.{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.md, теперь на родном языке. Счётчик кириллических символов по локали упал 79 → 42 → 23 (оставшиеся 23 — технические inline-ссылки вроде путей к файлам и заголовок мультиязычных ссылок, намеренно).
  • docs(readme): expand non-EN READMEs with Why / Requirements / Features / Configuration / Contributing — каждый не-EN README вырос с 240 → ~307 строк. Теперь покрывает те же немаркетинговые разделы, что и 585-строчный EN. Полный паритет 1:1 (маркетинговые проходы) пока отложен.

🛠️ Прочее

  • docs(api): consolidated scan endpoint in API.md + DATA-FLOWS.md + README.md — таблица API-справки теперь перечисляет только /api/stream/scan?source=…. Раздел Scan в README объясняет удаление разделения EN/RU в v1.18.0.
  • fix(scan.js): drop stale comment about deprecated aliases being live — комментарий диспетчера runScanAll в SPA теперь отражает консолидированную реальность.

🧪 Тесты

  • tests/scan-consolidated.test.mjs::F-018 backwards compat переписан — два прежних утверждения «унаследованный эндпоинт ещё работает» теперь проверяют, что запросы к /api/stream/scan-{en,ru} возвращают 404 (а не маршрутизируются в catch-all SPA).
  • Итого: 427 / 427 unit + 20/20 smoke E2E + 23/23 comprehensive E2E + 32/32 Playwright (счётчик не изменился; +2 новых корректных утверждения о удалении legacy заменяют +2 утверждений о «legacy ещё работает»).

Верификация

npm test                              # 427 / 427
npm run test:e2e:full                 # 23 / 23

# Снятие legacy-эндпоинтов:
curl -sI http://127.0.0.1:4317/api/stream/scan-en | head -1   # → HTTP/1.1 404
curl -sI http://127.0.0.1:4317/api/stream/scan-ru | head -1   # → HTTP/1.1 404

# Консолидированный эндпоинт:
curl -sN 'http://127.0.0.1:4317/api/stream/scan?source=ats&dryRun=1' | head -5
# → event: start
# → data: {"script":"en-scanner","writeFiles":false,…}

# Skip-link (a11y):
curl -s http://127.0.0.1:4317/ | grep -c 'class="skip-link"'  # → 1

# Запасной html lang:
curl -s http://127.0.0.1:4317/ | grep -c 'html lang="en"'     # → 1

Вне рамок (v1.19+)

Пункт Примечания
Полный паритет не-EN README (585 строк как у EN) v1.18 поднял не-EN до ~307 (53 % от EN). Маркетинговые «Why?» / «Quick start» проходы остаются только на английском.
Аудит цветового контраста (WCAG 1.4.3 AA — текст 4.5:1, крупный текст 3:1) v1.18 закрыл структурную a11y; токенный контраст в светлой и тёмной палитрах остаётся к проверке.
Аудит touch-target по каждому интерактивному элементу v1.18 установил пол (.btn: 44 px, .btn-sm: 32 px); покомпонентная проверка (chip-фильтры, навигация сайдбара, sortable headers) остаётся.

[1.17.0] — 2026-05-13

Релиз полировки + a11y + исправления CI. Закрывает все 9 follow-up из списка v1.16.0: проверка smoke в браузере, правда бейджей README, обновление покрытия, проявление lastWorkdayFallback в SPA, полный пересчёт E2E, сценарии Playwright для auto-pipeline, проход аудита a11y, сжатие исторических CHANGELOG в 6 локалях и расширение не-EN README разделами Architecture / API / Security / Tests.

🐛 Исправления

  • fix(e2e): smoke + comprehensive suites re-aligned with v1.16 UX — изменение v1.16 Cmd+K Enter → AutoPipeline modal приводило к тому, что search.press('Enter') в e2e-тестах открывал модалку, перехватывавшую последующие клики. Тесты теперь используют Shift+Enter для устаревшего пути быстрого добавления, что соответствует документированному в v1.16 разделению. Также обновляет итерацию batch-режима в comprehensive E2E для использования /#/batch-prompt (устаревший slug, введённый PR-H в v1.15). Это был провал CI на push v1.16.0 — Playwright e2e таймаутил 30 с на ожидании кликов, перехваченных backdrop.
  • fix(mode-page): batch-prompt route → modes/batch.md via serverSlug — v1.15 переименовала legacy mode slug в batch-prompt, но серверный POST /api/mode/:slug после этого искал modes/batch-prompt.md, которого не существует. Новое поле serverSlug разводит хеш маршрута и имя файла режима родителя.
  • chore: bump deprecation messages from v1.16.0 to v1.17.0 — тексты deprecation для scan-en/scan-ru и баннер deprecation batch-prompt ссылались на прошлую версию.

✨ Возможности

  • feat(scan): 🔒 Workday CAPTCHA chip in Active Companies card — серверный экспорт lastWorkdayFallback из PR-7 v1.16 теперь потребляется SPA. /api/scan-results возвращает снапшот; #/scan рендерит warn-карточку над Active Companies, когда тенант Workday сорвался в fallback («🔒 Workday tenant blocked — fallback: use /career-ops scan (Playwright)»). Новый экспортер getLastWorkdayFallback() исключает неоднозначность live-binding ESM. 2 новых ключа i18n × 8 локалей.

♿ Доступность

  • a11y: ARIA roles + focus management pass on critical surfaces —
    • index.html: атрибуты role на <aside> (navigation), <header> (banner), <section id="content"> (main), <div id="modal"> (dialog с aria-modal/aria-labelledby), <div id="toast"> + #conn-banner (status с aria-live), <div class="searchbar"> (search).
    • #sidebar-toggle получает aria-controls="sidebar" + aria-expanded, синхронизируемый JS при open/close.
    • #global-search получает визуально-скрытую <label> и явный aria-label, проявляющий подсказку шортката Cmd+K.
    • Закрытие модалки (×) получает aria-label="Close dialog".
    • Декоративные backdrop получают aria-hidden="true".
    • Ловушка фокуса в модалке — UI.modal() запоминает владельца клика, фокусирует первый не-close фокусируемый элемент при открытии и циклит Tab/Shift+Tab внутри модалки. UI.closeModal() возвращает фокус предыдущему владельцу.
    • Новый утилитарный класс .visually-hidden в public/css/app.css (стандартный паттерн WAI-ARIA AP).

📚 Документация

  • docs(readme): badge truth across 8 READMEs — бейдж тестов 284 / 379 / 360 → 427; бейдж релиза v1.9.1 / v1.13.0 → v1.16.0, затем → v1.17.0 через v1.17 bump. Цели ссылок релиза обновлены.
  • docs(readme): expand 7 non-EN READMEs with reference sections — каждый вырос с 170 → ~240 строк за счёт новых разделов Architecture / API reference / Security notes / Tests / A11y / Limitations / License на родном языке. Пока не на полном паритете 585 строк с EN, но покрывает все ключевые немаркетинговые поверхности.
  • docs(changelog): condense pre-v1.12 entries in 6 locales — длинные RU-телесные записи v1.11.x + v1.10.x, протекавшие в не-EN/не-RU CHANGELOG, заменены на компактную сводку «Earlier releases» на родном языке каждой локали. Детальная история остаётся в CHANGELOG.md (EN).

🛠️ Инструментарий

  • coverage: refresh numbers — последняя опубликованная цифра — 95.46 % line / 84.06 % branch (REVIEW v1.13.0). Baseline v1.17: 94.14 % line / 82.98 % branch / 93.20 % function. Лёгкое снижение от новых путей ошибок в auto-pipeline + reports-write; по-прежнему значительно выше пола 80 % из CLAUDE.md.

🧪 Тесты

  • Итого: 427 / 427 unit + 20/20 smoke E2E + 23/23 comprehensive E2E + 32 / 32 Playwright (было 28; +4 новых сценария auto-pipeline: кнопка открывает модалку, Cmd+K paste запускает модалку, невалидный URL блокирует шаг 1, обрамление SSE-событий POST /api/auto-pipeline).
  • E2E-сет перенастроен под UX v1.16.0 (Shift+Enter quick-add, /#/batch-prompt для устаревшего режима).

Верификация

# Локально:
npm test                          # 427 / 427
npm run test:e2e                  # 20 / 20
npm run test:e2e:full             # 23 / 23
npm run test:e2e:browser          # 32 / 32

# Smoke в браузере (на уровне страницы):
curl -s http://127.0.0.1:4317/api/scan-results | jq '.workdayFallback'
# null, когда fallback Workday не происходил; {apiUrl, reason, at} после 4xx.

# Точечная проверка a11y:
node -e "
const c = require('cheerio').load(require('fs').readFileSync('public/index.html','utf8'));
['banner','navigation','main','dialog','status','search'].forEach(r =>
  console.log(r, c('[role=' + r + ']').length));
"
# Каждая роль должна появляться ≥1.

# Проверка шлюза CI: workflow dashboard-screenshots поднимает /tmp
# скаффолд, регенерирует PNG, сравнивает с закоммиченными — зелёный, когда
# images/dashboard-*.png актуальны для отрендеренного SPA.

Вне рамок (v1.18+)

Пункт Примечания
Перевести запись v1.16.0 в не-EN CHANGELOG Сейчас с русским телом (~30 строк × 6 локалей = 180 строк). Было вне явных рамок пользователя для v1.11.x/v1.10.x.
Полный паритет не-EN README (585 строк как у EN) v1.17 поднял не-EN до ~240; маркетинговые «Why?» / «Quick start» проходы остаются только на английском.
Родительский коммит для канонического промпта A-F Переписывание santifer/career-ops::modes/oferta.md всё ещё требуется upstream (жёсткое правило CLAUDE.md #1).
Полный аудит WCAG 2.2 AA v1.17 покрыл структурный ARIA + ловушку фокуса; покомпонентный контраст / порядок Tab остаётся к проверке.


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