Журнал змін

GitHub

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

[1.267.1] — 2026-11-04

Виправлено

  • Ремонт релізного поїзда (гейти CI v1.267.0) — ланцюжок лічильників тестів у PROJECT-CONTEXT більше не рветься на голові v1.267.0 (формат стрілки, який матчить eval-грейдер), QA-промпти реорганізовано (один живий промпт на реліз, v1.266.0 архівовано), а пофайлове покриття піднято над ratchet 98/90: applicantpro salary-рендерер втрачає недосяжний fallthrough (salaryRangeOf доводимо видає undefined, {min}, {max} або {min,max}) із закріпленими односторонніми гілками ≥/≤ через fetch-потік, а сьют board-response-cache закріплює no-store-байпас, 304 від викликача проти незбереженої дошки та витіснення на MAX_ENTRIES.
  • Тести: живий лічильник — 7395 (сьюти хвилі v1.266.0/1.267.0 несуть більше спеків, ніж ранні примітки; ланцюг переприв’язано до виміряного рівня: 7392 після хвилі + 3 гейт-тести в цьому патчі); coverage 98.51 line / 90.35 branch.

Примітки

  • Тег v1.267.0 залишається вказувати на червоний мерж-коміт — не переприв’язується (сервер відмовляє переприв’язаним тегам); v1.267.1 — деплой-носій.

  • Патч-ремонт поїзда v1.267.0: гейти CI зелені, дерево коду суттєво не змінювалося.

[1.267.0] — 2026-11-04

Додано

  • Чотири нові джерела сканування — GermanTechJobs, Darwinbox, Keka, ApplicantPro/isolved (реєстр 125 → 129 = 124 EN + 5 RU · 124 адаптери) — друга хвиля parent-sync проти career-ops @ 7d8feb6d (87 апстрім-комітів). GermanTechJobs (#6346): бордовий RSS на germantechjobs.de/rss (~626 елементів одним запитом); роботодавець — останній сегмент " @ " заголовка; заголовок без нього відкидається і ніколи не записується на борд; лише явний вибір через provider:. Darwinbox (#6416): безавтентифікаційний JSON alljobs за <tenant>.darwinbox.in, id компанії береться з careers URL, посторінковий POST-обхід (50/сторінку, max_pages 20/200) зі зупинкою на короткій сторінці, прочитаною з власної кількості рядків джерела. Keka (#6199): два запити — оболонка careers із redirect:'manual', скрипт якої називає UUID фіду (невідомий тенант відповідає 302 на TenantNotFound.html і читається як іменована 404), далі весь відкритий інвентар одним масивом embed-jobs. ApplicantPro / isolved (#6252): числовий domainId із bootstrap, потім {success, data:{jobs, jobCount}}; jobCount > rows попереджає, що список неповний; opt-in applicantpro: { fetchDetails: true } збагачує з JSON-LD у темпі 250 мс.
  • Повторне використання відповідей бордів через ETag (#6105) — у межах одного скану відповідь борду, отримана за останні 15 хвилин, використовується як є, а старіші ревалідуються з If-None-Match (304 зберігає тіло); ключ кешу покриває метод + URL + credential-заголовки + тіло, накладні читання згортаються на in-flight promise, а CAREER_OPS_HTTP_CACHE=0 / _TTL_MS це перевизначають. У пам’яті на кожен прогін скану (у web-ui немає on-disk контракту кешу) — зафіксоване відхилення.

Виправлено

  • Дати на чотирьох бордах (#6274) — remoteok, remotive, workingnomads та IBM тепер видають дату публікації, тож фільтр «Posted within» працює й там; naive-часову мітку remotive нормалізовано до UTC (її per-machine баг з віком був живий), а _source IBM тепер справді запитує dcdate.
  • Помилково написаний ключ portals.yml відхиляється (#6107) — EN-скан тепер кидає іменовану помилку (файл + ключ + підказка найближчого ключа, відстань Левенштейна ≤ 2) до будь-якого запиту, замість того, щоб мовчки ігнорувати titel_filter:.
  • getro продовжує обхід повз старий закріплений пін (#6406) — закріплена протухла вакансія як і раніше відкидається, але більше не завершує пагінацію; обхід закінчується на справжній короткій сторінці.

Примітки

  • Свідомо не зроблено: seen-фільтр RU-сканера не є мовно-чутливим, і його шлях парсингу portals не проходить ґейт на помилково написані ключі (hand-off записані у спекі хвилі — жоден RU-борд ще не видає per-language рядків); remoteok лишає свій рядок дати з offset замість нормалізації до UTC (його жива форма детермінована). Тести 7096 → 7243; Playwright 173 pass / 0 fail / 1 opt-in skip; реєстр 129 = 124 EN + 5 RU · 124 адаптери (усі лічильні ґейти регенеровані з живого реєстру).

[1.266.0] — 2026-11-01

Додано

  • Мовні версії Personio більше не з’їдають одна одну (CAR-223: батьківські #4789 + #5044 + #5285) — відкладений стек, виконаний як окремий реліз. Бік скану (#4789): scan_history.dedup_include_language: true (строгий opt-in — у стані OFF поведінка побайтно ідентична) дає мовним версіям однієї вакансії вижити в dedup через механізм варіантів у seen-history (languageFormsFromUrl / recordUrlVariant / isUnseenLanguageVersion); канонічний ключ викидає параметри language/lang/locale. preferred_languages (#5285): упорядковує рядки мовних версій вакансії preferred-first (стабільно, порядок провайдера при нічиїй) — незалежно від перемикача (OFF лишає preferred-версію, ON лишає всі версії в цьому порядку), з портом validatePreferredLanguages з validate-portals. Fan-out personio (#5044): сторінка careers береться без примусового ?language=en (власний ?language=xx тенанта шанується) і видає один рядок на мовну версію з language + URL ?language=<code>; dedupIncludeLanguage додає per-language запити (стеля 5, із паузами); фолбеки /xml і job-link без змін. recruitee отримує таку саму видачу language + preferred_languages (opts або запис). Спільні хелпери живуть у server/lib/language-preferences.mjs.

Примітки

  • Записані hand-off, свідомо не зроблено: seen-фільтр RU-сканера не є мовно-чутливим, і жоден RU-борд ще не видає per-language рядків; дзеркало url-key.js на клієнті та обв’язка валідації в portals-маршруті лишаються відкритими. Тести 7096 → 7203; реєстр без змін 125 = 120 EN + 5 RU · 120 адаптерів.

[1.265.0] — 2026-11-01

Додано

  • Два нові джерела сканування — Apple і Bulldogjob (реєстр 123 → 125 = 120 EN + 5 RU · 120 адаптерів) — хвиля parent-sync проти career-ops @ fe857ecf. Apple (jobs.apple.com, #5496 + #6196): сайт — React Router SSR-застосунок без придатного JSON API, тож джерело читає hydration-блоб звичайного пошукового маршруту (window.__staticRouterHydrationData); виявлення веде запис (apple.queries з сортуванням за релевантністю + осі apple.teams, без дефолтів — запис без обох кидає помилку), сторінки обмежені max_pages (дефолт 2, стеля 50) з паузою 200 мс, кожен сегмент URL кодується surrogate-безпечно, а вакансія, яку Apple мітилить як PIPE, несе в snippet підказку «може бути talent pipeline» — фільтрації за нею немає. Bulldogjob (bulldogjob.pl, #6253): польський tech-борд теж не має JSON API — листинги несуть __NEXT_DATA__ (50/сторінку, totalCount); careers_url запису — це збудований користувачем пошук сайту (фільтри живуть у шляху), кожен запит несе явний /page,N, а обхід зупиняється на короткій сторінці, заявленому total чи max_pages (дефолт 25, стеля 100) з тегом обрізання на часткових бордах.
  • Рework пагінації IBM (#6273) — фіксований обхід на 600 записів стає max_pages (дефолт 20, стеля 300, перевизначення на записі) з паузою 200 мс між сторінками та per-page retry через fetchJsonWithRetry; непошкодена логіка: впала перша сторінка кидає виняток, впала пізніша зберігає зібране і називає сторінку, і лише повна сторінка на власній стелі запису попереджає «raise max_pages».

Виправлено

  • justjoin датує пропозиції за lastPublishedAt (#6254) — publishedAt кандидатського API зсувається при кожному оновленні від роботодавця, тож більшість борду несла сьогоднішню дату і max_posting_age_days пропускав тижневої давності пропозиції; тепер перемагає рання дата публікації з фолбеком, коли її немає або вона не парситься.
  • jobvite складає рядки, що ділять один posting URL (#6127) — публічний фід повертає рядок <job> на місто для одного detail URL; парсер лишав перше місто, а решту лічив дубликатами. Тепер сиблінги вносять лише локації, з’єднані · у порядку фіду, перший title/date зберігається, remote перераховується на об’єднаній локації.
  • Liveness: ранг UltiPro (#6090) — UKG не має per-posting JSON-ендпоінту і відповідає 200 на закриті, тож усі 57 закритих UltiPro-вакансій читалися uncertain. Сторінка деталю вкладає запис: OpportunityIsClosed true/false → expired/active, notice «opportunity unavailable» → expired, решта лишається браузеру. Банер Built In «Sorry, this job was removed» став hard-expired (#6336) — сторінка тримає повний JD і APPLY-контрол; тепер перемагає банер, а сусідній бейдж «Job removed» без «sorry» не матчиться.

Примітки

  • Свідомо не зроблено: parent-бік половини фіксу jobvite про location-компілятор scan.mjs (web-ui документує USPS-компілятор як no-port) і поле note батька для PIPE-підказки Apple (в job-shape web-ui немає note; вона їде у snippet). Тести 7096 → 7135 (+39); Playwright 173 pass / 0 fail / 1 opt-in skip; coverage gate ≥ 98/89; реєстр 125 = 120 EN + 5 RU · 120 адаптерів (усі лічильні ґейти регенеровані з живого реєстру).

[1.264.3] — 2026-11-01

Виправлено

  • Хвиля загартування rate-limit (review LOW №1–3 з REVIEW-2026-10-31-v1.264.1) — writeRateLimit() (express-rate-limit@7.5.1, 60 req/min route×IP, 429 JSON + Retry-After) тепер покриває усі 47 мутувальних ендпоінтів (було 2): замінено 9 неправильно скоуплених гардов llmRateLimit (/api/config — перезапис .env!, PUT /api/batch, /api/batch/merge, outcome, followup/seed, assessments, stats/snapshot, mock-interview/save, networking/save), 3 гарди складено перед LLM-бакетом на дорогих шляхах (/api/evaluate, /api/deep, /api/auto-pipeline), а 33 голі реєстрації обв’язано (редактори CV/profile/modes, усі записи трекера, pipeline POST/restore/DELETE, jds, 7 /api/run/* однією правкою циклу, posting-guard, Playwright-PDF inline, portals toggle/health/track, discover, reports, export/docx, two-pager, career-plan, memory і три DELETE-маршрути). /api/apply-helper лишається голим в allowlist (чисто локальне обчислення, закріплено); 16 чисто-LLM маршрутів за дизайном тримають умовний LLM-бакет. Сторож повноти (tests/rate-limit-coverage.test.mjs, red-first при 27 голих) завалює CI на будь-якій майбутній нетротльованій мутувальній реєстрації.
  • TRUST_PROXY — opt-in + повторно заряджені валідації (review LOW №1–2) — app.set('trust proxy', N) приймає env TRUST_PROXY як кількість хопів (ніколи булеве true — воно ключувало б за лівий, керований зловмисником кінець XFF); за замовчуванням не задано, тож loopback зберігає задокументовану per-route деградацію з ключем за сокетом. validate: false стає прицільною writeValidateConfig(): виключено лише xForwardedForHeader (XFF від Caddy без trust proxy ігнорується свідомо), всі інші валідації — зокрема trustProxy — знову гучні, тож майбутня неправильна конфігурація trust proxy: true виявиться як ERR_ERL_PERMISSIVE_TRUST_PROXY, а не мовчанням. Власний keyGenerator видалено свідомо: express-rate-limit прив’язує свої proxy-валідації до генератора ключів за замовчуванням, а per-route ізоляція не змінилася (один MemoryStore на виклик фабрики).
  • Стан копіювання на cvstart.org скидається через 2 с (CAR-141) — стан «Скопировано ✓» тепер тримається 2 секунди, як вимагає приймальний критерій (було 1.5 с), red-first-пін у tests/site-copy-feedback.test.mjs. Це закриває останній критерій CAR-141, доступний агентам; оновлення демо-відео лишається відкладеним за рішенням власника (2026-11-01).

Змінено

  • Бандли довідки називають 19 мов (×19) — речення §19 «Localizing the app» і §32 «Run it from OpenWorker» більше не відстають на дві локалі: кожен бандл тепер називає 19 з தமிழ் і Tiếng Việt у власному стилі переліку (ar вживає прописане «التسع عشرة»), а власний залишок «cả-17» у §32 vi.md теж виправлено. Гейти структури (33 H2 / 125+ H3) стабільні побайтно.
  • Гігієна стану — AGENTS.md каже v1.264.2 / 7082 tests (було v1.86.x / 1566); PLAN.md позначає релізи 1–7 Shipped, а його абзац про ship-train каже docs ×19; коментар StatsBar.astro тепер описує, що насправді містить facts.adapters (123 = загальна кількість sources, історична назва ключа збережена для споживачів); шапка i18n.js каже 19 мов; QA-промпт v1.264.2 несе справжнє покриття (98.45/90.05) і арифметику Playwright (176 = 174 harness + 1 opt-in skip + 2 script-level suites).

Примітки

  • Свідомо не робимо: хвиля parent-sync за записаним списком портів лишається в BACKLOG — форк мусить спершу синхронізуватися за апстрім a9476866 (7 із 10 комітів списку портів ще не в локальному дереві батька; IBM/UltiPro/Bulldogjob/apple оцінені в S–M, personio language-dedup відкладено до власного релізу). Перезапис демо-відео CAR-141 свідомо відкладений власником («видео не надо пока», 2026-11-01).
  • Tests 7082 → 7096 (+9 сторож покриття + 5 trust-proxy); Playwright 173 pass / 0 fail / 1 opt-in skip (+2 піни шеврона всередині наявних кейсів); покриття 98.46 рядків / 90.10 гілок; evals 14/0/1skip; реєстр без змін 123 = 118 EN + 5 RU · 118 adapters.

[1.264.2] — 2026-11-01

Виправлено

  • Заголовки груп сайдбару приєднуються до контракту обрізання (CAR-219) — підписи перемикачів груп (.nav-v2-toggle > span) тепер обрізаються з ellipsis і несуть локалізований тултіп через той самий механізм syncLabelTitles(), що й елементи навігації з CAR-203; шеврон ніколи не стискається, перемикач лишається клікабельним, RTL не ламається. Red-first-пін у tests/nav-v2.test.mjs (+1 → 7082 tests).

Змінено

  • Текст cvstart.org постачається всіма 19 мовами (CAR-220) — meta.helpDesc, helpTeaser.c1.desc і faq.a2 перекладені в кожній локалі сайту (у v1.264.1 вони були свідомо лише в EN); GitHub Pages уже вивіз оновлений текст на cvstart.org.
  • Гігієна беклогу + рев’ю (CAR-221/222) — шість пунктів «port when merged upstream» закрито: усі сім апстрім-PR змержені, чотири пункти вже портовані, два живуть на боці батька. Рев’ю-документ docs/reviews/REVIEW-2026-10-31-v1.264.1.md з’являється в репозиторії (0 HIGH / 0 MED, три LOW-хвости записані в BACKLOG), а список портів parent-sync зафіксовано (6 PORT / 3 VERIFY / прогалина з джерелом apple; апстрім-хвіст 118 комітів @ VERSION 1.38.0).

Примітки

  • Виправлення версійного штампу дзеркала журналу змін (92a05fcf) увійшло в цей тег: кожне пагіноване дзеркало на cvstart.org тепер несе версію релізу, що згенерував його, тож грейдер site-build-after-fanout нарешті виконує свою справжню логіку замість того, щоб пропускатися.
  • Свідомо не робимо: тротлінг решти мутируючих POST (review LOW №3) і зв’язка бакетів із trust-proxy — записано в BACKLOG для наступної хвилі загартування. CAR-141 (демо-відео) лишається за власником.
  • Tests 7081 → 7082 (пін CAR-219); Playwright 176; реєстр без змін 123 = 118 EN + 5 RU · 118 adapters.

[1.264.1] — 2026-10-31

Виправлено

  • Пігулки сайдбару акуратно обрізають довгі локалізовані підписи (CAR-203) — span із підписом навігації тепер повноцінний учасник flex-розкладки (min-width: 0 + overflow: hidden + text-overflow: ellipsis + nowrap у v2-nav.css), а syncLabelTitles() у nav-v2.js віддзеркалює локалізований підпис у тултіп через той самий ключ data-i18n (нуль нових i18n-ключів) — довгий «Профинансированные компании» знову читається при наведенні, замість того щоб жорстко обрізатися overflow: hidden рядка. Ціль дотику 44 px, активне зафарбування та RTL-дзеркало не торкнуті; red-first-пін у tests/nav-v2.test.mjs.
  • Одна стеля оцінювання 480 с для всіх локалей (CAR-218) — remote-QA v1.264.0 зловила перехідні оцінювання 502 · 300 s · 0 ch на es і zh-CN (прицільний повторний запуск пройшов, але стіна була реальною). EVAL_TIMEOUT_MS тепер 480_000, а evalTimeoutMs(lang) повертає його безумовно — GEMINI_EVAL_TIMEOUT_MS і auto-pipeline успадковують. Локале-залежний токенний бюджет із CAR-174 (16384 / 32768) лишається єдиним розподілом за локаллю.

Змінено

  • FAQ на cvstart.org несе точні кількості джерел (CAR-217) — {sources} job-board sources: {sourcesEn} international boards plus {sourcesRu} Russian ones підставляється з facts.json, який генерується з живого реєстру (123 = 118 EN + 5 RU), тож числа більше ніколи не розсинхронізуються; локалі, які досі на старішому формулюванні ~{adapters}, зберігають округлення через adaptersRounded. Попутно — покращення англійського тексту (meta.helpDesc, helpTeaser.c1.desc, faq.a2 називають командне меню, групований пайплайн із undo та канбан-трекер).

Примітки

  • Свідомо не робимо: покращення тексту site i18n (meta.helpDesc / helpTeaser.c1.desc / faq.a2) цього релізу лишаються лише в EN — інші локалі зберігають коректні, але кругліші формулювання до наступної док-хвилі (їхній faq.a7 отримує плейсхолдери точних кількостей там, де перекладено). CAR-141 (демо-відео) лишається за власником; замінену гілку car136/m141 видалено, її ідеї записано на Linear-таску (відновлювано через тег archive/car136-m141).
  • Tests 7041 → 7081 (пін CAR-203 + переписані піни CAR-218); Playwright 176; реєстр без змін 123 = 118 EN + 5 RU · 118 adapters.

[1.264.0] — 2026-10-31

Додано

  • Inline-редагування статусу в трекері — бейдж статусу кожного рядка #/tracker став компактним <select> канонічних стадій (з GET /api/tracker/stages, жодного захардкодженого whitelist’у). POST /api/tracker/status викликає канонічний set-status.mjs батька під локом applications.md (whitelist станів через states.mjs, проброс --dry-run/--note, fail-soft 502 із санітизованою деталлю, spawn через argv-масив). Успіх оновлює рядок на місці (tone-клас + лічильники вкладок, фокус зберігається); невдача відкочує значення і показує тост. Рядки, які релей не може адресувати — вручну вписаний невідомий статус, backfill-сентинел # — лишають read-only бейдж (LOW-фікси route-review; ревʼюер: жодного HIGH/MED).
  • Попередження сервера на кожній AI-сторінці — evaluate, deep, auto-pipeline (сторінка і модалка), career-plan, market, cv-studio (humanize + tailor) тепер показують масив warnings сервера (обрізання за вихідними токенами, дрейф мови, корекції дат) смугою badge-warn (role="status"); модалка auto-pipeline більше не втрачає попередження з done-фрейму. Новий ключ llm.truncatedNotice ×19.
  • Хвиля покриття (+111 тестів) — routes/map.mjs 100/94.9, geocode.mjs 96.3/92.9, content.mjs 99/84.9, networking.mjs 96.4/89.1, sources/habr.mjs 100/98.2 (нові tests/cov-*-routes.test.mjs ×3, tests/sources-habr.test.mjs); нижня межа за гілками втримана на ≥ 90 %.
  • Бандл закриття Linear CAR-191…216 (docs/reviews/2026-10-30-linear-car191-216-closure-bundle.md) — готовий до вставки close-out для власника з SHA комітів і коментарями-доказами по кожному CAR.

Виправлено

  • Скан-джерело Trudvsem — порожня сторінка (results: {}, зокрема «за кінцем») більше не вбиває весь скан (чисті нуль рядків); конверти помилок API (HTTP-200 з status: 500) кидають виняток із текстом самого API; пагінація йде за record-зміщеннями (було за індексом сторінок — сторінки мовчки дублювалися, ~52 вакансії замість сотень); обхід зупиняється до зони відмови API на глибоких зміщеннях (offset × limit ≥ 10000). Перевірено живим end-to-end.
  • habr — заголовки/компанії очищаються від тегів після декодування сутностей (паритет із hh.mjs): &lt;script&gt; у заголовку картки більше не доїжджає до рядків скану як жива розмітка; легітимний >> у заголовках виживає.
  • PUT /api/profile (raw-шлях) — блок candidate: без непорожнього full_name відхиляється, як і на шляху злиття полів (раніше зберігався full_name: null).
  • Правдивість хінтів — .doc прибрано зі списку accept завантаження CV і з cv.uploadHint ×19 (парсер його ніколи не підтримував); config.openaiModelHint ×19 називає gpt-5 (сервер відображає будь-який *-codex-id на нього); config.anthropicModelHint ×19 і дефолт Anthropic тепер віддзеркалюють тири батька — дефолт claude-sonnet-5-5, premium claude-opus-5-5.
  • Посилання на доки Claude Code → code.claude.com (старий URL docs.anthropic.com/en/docs/claude-code падає TypeErrors у links-гейті).
  • help ×19: видалено булет Row-density (перемикач покинув UI в v1.263.0) і полагоджено 18 родин зламаних **bold**-спанів — плюс клас витоку рендера виправлено ×19 (другий порожній рядок після кожної таблиці перед абзацем; глиф pipe поза bold-комірками): 0 літеральних ** у рендері в усіх 19 бандлах довідки.

Примітки

  • Дефолтна модель Anthropic змінилася на claude-sonnet-5-5 (стандартний тир батька). Деплої, що покладалися на старий мовчазний дефолт (claude-sonnet-4-6), оновлюються автоматично — запініть ANTHROPIC_MODEL у .env, щоб лишитися на попередній.
  • Свідомо не робимо: два LOW-зауваження рев’ю лишені відкритими намірно (inline-селект не дзеркалить деградацію movesReady канбана — чесний тост на кожну невдачу є обраною поведінкою; гілка code: 'timeout' лишається покритою контрактом fake-writer, а не тестом >30 с). CAR-141 (демо-відео) лишається за власником.
  • help ×19 отримали пост-релізний прохід прозою (булет щільності видалено, клас витоку рендера виправлено) на main після виходу v1.263.0 — цей реліз перший тег, який це везе.
  • Також у цьому тезі: інфраструктура чіпа підтвердженості (парсери звітів + бейджі перегляду), раннер fix-report-links (POST /api/runners/report-links, картка «Виправити» на #/health) та оцінка/підтвердження вартості батчу із записом usage — потрапили на main між зрізом гілки й тегом.

Tests 6905 → 7041 (+136) · Playwright 176 (173 pass / 1 opt-in skip) · покриття 98.5 % рядків / 90.1 % гілок · реєстр без змін: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.263.0] — 2026-10-30

Додано

  • Шар полірування анімацій — новий motion.css (останній аркуш застосунку): появи модалок/поповерів (scale .97 + згасання, 130–180 ms), spring-поява тостів + м’який стан відходу, spring-натискання перемикача теми, scale(.97) при натисканні чипів/табів, stagger-інфраструктура (кроки 50 ms, кап 12), згасання .page-enter при зміні маршруту (роутер), переходи focus-halo на чипах/табах. Кожне правило безпечне за prefers-reduced-motion (рух падає до none, opacity/колір лишаються).
  • Коректність на тач-пристроях — 68 правил hover-підйому не були захищені: тач-пристрої лишалися з «завислою» піднятою карткою після тапу. Усі transform-ховери тепер обгорнуті в @media (hover:hover) (нейтралізатор motion.css), а підйом картки застосовується лише до справді інтерактивних карток (.card:has(a), a.card, button.card, [role=button].card) — більше жодної хибної аффордансності на статичних картках.
  • A11y: кожен контроль має програмне ім’я. Новий хелпер A11yDump дає імена всім 16 readonly-дампам виводу LLM; 12 полів лише з плейсхолдером (CV Studio ×6, config ×2, пошук у скані, селект оцінки в трекері, нотатка outcome, редактор плану, URL авто-пайплайна, баг-репорт) отримали aria-label; гейт a11y-form-wires тепер вимагає цього для всього класу (було 26 безіменних місць, стало 0 + allowlist із задокументованими причинами).

Виправлено

  • #/scan: липка шапка таблиці більше не малюється поверх пошуку в топбарі після прокрутки під нього (z збігався з топбаром, перемагав пізніший DOM — тепер token-derived нижче нього); інпути «Зарплата от/до» знову нормального розміру (правило .v2-scan-bar .input flex витікало в колонковий flex поповера і задавало flex-basis 200 px як ВИСОТУ — тепер >-скоуп + захист поповера); кнопка перемикання щільності видалена рішенням власника (обв’язка збережена, i18n-ключі видалені ×19).
  • Загартування deploy-воркфлоу (F1–F6): інпут provider валідується за набором символів до будь-якого склеювання в shell; діагностика hermes редагує значення (регістронезалежний фільтр ключів + маскування значень + обрізання до 80 знаків на всіх трьох стоках, маскуються і ?token=); крок hermes поважає inputs.app_dir; public_url пропускається лише по https; анти-даунгрейд-сторож має 6 виконуваних пінів (рефи без дефолтів, сторожа до Deploy, строгий regex рефів).
  • Хвіст рев’ю, red-first: round-trip бекслеша при CSV-імпорті (повторний імпорт знову дедуплікується), стріп <head> у cv-voice 4827 ms → 13 ms, statedYearsMin обчислюється ліниво, TOCTOU capture у posting-guard, wildcard MiniMax CN, пропуск deel-токенів, size-гарди імпорт-пікера до читання. Імпорт-модалка: зведений nav-гард, file-інпут видаляється на cancel, гардоване читання дублів, num через reduce.
  • Батч UX від власника: вертикальний ритм hero дашборда (inline-діти склеювалися в один рядковий бокс); зсув активного рядка TOC довідки на −8 px (порядковий inline gutter + колонка номерів); мобільний drawer (видима кнопка ✕, скидання scrollTop, фантомне згасання –paper обмежене .has-hud); сторінка docs-assistant більше не переповнюється при довгому URL у відповіді (ланцюг grid min-width + overflow-wrap: anywhere); «Недавні» пояснені (хінт) + очищаються; Save у career-plan зводиться знову після невдалого завантаження; URL-prefill в evaluate більше не спалює LLM-виклик на плейсхолдері вставки; Enter в auto-pipeline поважає IME-композицію.
  • Контраст (WCAG-аудит, 296 пар): градієнт docs-fab 2.43 → 6.4:1, пара toast.success → ok-soft (1.75 → AA у темній), фліп banner btn-dark → --ink, світлі варіанти scan-logbtn/reposts-badge/fab-chip/close, і межі контролов форм до 3:1 (--cv3-input-line, WCAG 1.4.11 — було 1.2:1).
  • Візуальний свіп: порожній стан трекера — картка на всю ширину (раніше обрізалося всередині таблиці 760 px при 390); рядок консолі скану еліпсифікується замість напівобрізання; плаваючий чат-FAB більше не накриває контент при 390 (ціль 44 px, inset 12); порожній стан «Application statuses» на дашборді тримається патерну v2-empty; хінт «?» у stats отримав роздільник + ціль 44 px; таб Input у career-plan пояснює, куди падає результат; markdown §1 довідки (18 зламаних bold-спанів).

Змінено

  • Акцент: синій → багряно-червоний (прохання власника, в колір логотипа, hue 15) — 5 cv3-токенів × 3 тематичні блоки; текст бульбашки чату docs-assistant іде через --accent-ink (був літеральний #fff, 2.39:1 у темній); акцентна заливка funded іде через --accent-soft. Danger лишається hue 25 зі своїми обробками.
  • Доки: лічильник у таблиці тестування README ×17 (дрейф ери v1.248), §1 довідки + 18 markdown-спанів, «118» у довідці fr/it/ta, вендорські ціни CHECKED_ON 2026-10-30, факти HANDOFF/CONTEXT/TRACEABILITY.

Примітки

  • Свідомо не робимо: переробка свіпу скелетонів лишається за бортом (контракт car112 закріплює поточне згасання дослівно); кросфейд іконки перемикача теми лишається заміною display (його спостерігає Playwright-контракт theme-toggle); scale для .btn:active не дублювався (володіє app.css); stagger — лише інфраструктура, плитки дашборда візьмуть його пізніше; проза довідки ×19 (згадка щільності, markdown §1, «118») їде наступним фан-аутом.
  • Повні докази remote-QA: прицільний повторний запуск ja зелений (376 s / A–G ✓) на v1.262.4; en/it зелені раніше, в еру токен-щільності.

Tests 6869 → 6905 (+36) · Playwright 176 · покриття 98.4 % рядків / 89.9 % гілок · реєстр без змін: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.262.4] — 2026-10-30

Виправлено

  • ja долучається до token-dense локалей оцінювань (ja → стеля 32768 токенів + годинник 480 с). П’ятилокальний прицільний повторний запуск на проді v1.262.3 розділив чотири нічні тайм-аути 502 · 300 s · 0 ch на перехідні та детерміновані: ar (250 с), ko (291 с) і zh-CN (259 с) пройшли свої повторні запуски, разовий мовний дрейф ru не повторився — але ja двічі врізалася в стандартну стелю з нульовим виводом (повний запуск ×19 40981578549 та прицільний 41012725839). Той самий клас форми, що it/pl. Red-first-пін: evalMaxTokens('ja') = 32768, evalTimeoutMs('ja') = 480_000, інші локалі зберігають 300 с.

Примітки

  • Свідомо одноцільовий патч: фікс ja — єдина зміна, яку несе цей реліз, — більше він нічого не несе. ar/ko/zh-CN залишаються на стандартній стелі за сьогоднішніми даними (вони проходять; нічні збої були перехідними збоями навантаження провайдера), а дрейф ru виявився разовим поколінневим флейком.

Tests 6868 → 6869 (+1) · Playwright 176 · реєстр без змін: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.262.3] — 2026-10-30

Виправлено

  • pl долучається до token-dense локалей оцінювань (pl → стеля 32768 токенів + годинник 480 с). Повна remote-QA на проді v1.262.1 спіймала польське оцінювання, що вмирає на стандартній стелі, — 502 · openrouter · 300 s · 0 ch — а прицільний повторний запуск, відправлений з v1.262.2, відтворив це точно (запуски 40960568766 та 40968958062): 18/19 локалей пройшли A–G, it фінішувала на 302 с всередині token-dense годинника 480 с, а pl двічі врізалася в стіну з нульовим виводом. Детермінований тайм-аут, а не флейк, — той самий клас форми, що перевів it у v1.262.1. Red-first-пін: evalMaxTokens('pl') = 32768, evalTimeoutMs('pl') = 480_000, 18 стандартних локалей зберігають 300 с.

Примітки

  • Свідомо одноцільовий патч: фікс pl — єдина зміна, яку несе цей реліз, — більше він нічого не несе; вміст review-tail v1.262.2 вийшов окремо.

Tests 6867 → 6868 (+1) · Playwright 176 · реєстр без змін: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.262.2] — 2026-10-30

Виправлено

  • Хвіст wave-рев’ю — 7 знахідок низької серйозності, кожна red-first. Round-trip бекслеша CSV-імпорту: \ у комірці записувався подвоєним і зчитувався подвоєним, тож повторний імпорт того самого CSV пропускав дедуп company+role і міг створити дублікат рядка — читач тепер розекранює \\ після розбиття (round-trip запис→читання без втрат, повторний імпорт дедуплікується). Стрип <head> у cv-voice був квадратичним на незакритих тегах — 4827 мс → 13 мс на вході 64 КБ, яким маршрут обмежується. statedYearsMin ганяв повний парсер років для кожної вакансії кожного джерела навіть тоді, коли years_min не був налаштований — тепер ліниво, секунди CPU на великий скан зникли. Захоплення, видалене між readdir і statSync, ламало лістинг posting-guard (TOCTOU — файли, що зникли, пропускаються). Дефолт MiniMax CN матчився лише з точним api.minimax.cn — будь-який базовий URL *.minimax.cn тепер отримує CN-модель. $-токени Deel, що не є hex-референсами, більше не просочуються в сніпет як літерали. Пікер імпорту перевіряє file.size ДО file.text() — серверна стеля 512 КБ відповідала 413 лише після того, як браузер розпарсив можливо величезний файл — з локалізованою бульбашкою import.tooLarge ×19.
  • Загартування deploy-воркфлоу (рев’ю воркфлоу F1–F6). provider був єдиним входом, що інтерполювався у віддалений shell без валідації кодування — ' у значенні виривався з лапок і запускав довільний код на проді; тепер вайтліст ^[A-Za-z0-9_-]+$ перед будь-яким склеюванням, а grep сторожі ключа більше не може фальшиво пройти. Крок діагностики hermes друкував сирі journalctl + дампи unit у публічний лог; його фільтр пропускав великі *_API_KEY= і нічого не маскував — тепер спільний регістронезалежний redact + маскування значень + обрізання до 80 знаків на всіх трьох стоках, рядки запитів ?token= теж маскуються. Крок hermes ігнорував inputs.app_dir і хардкодив /opt/career-ops/src/.env. public_url діставався до curl без перевірки https (ін’єкція опції через початковий -). А сторожа анти-даунгрейду — фікс інциденту v1.238.0 — не мала виконуваного гейту: шість нових пінів у ci-workflows (refs не мають дефолтів, сторожа запускається перед Deploy, строга regex для refs).
  • Хвіст import-модалки (рев’ю SPA). Модалка озброюється тим самим nav-гардом, що й модалка outcome — догуляння геть більше не залишає її сиротою поверх іншого в’ю. Прихований file input видаляється на cancel, а не лише на change — повторні закриття більше не витікають input’ами в document.body. plan.duplicates читається через гардовану форму у прев’ю. Розподіл num трекера використовує reduce замість Math.max(...spread) — spread упирається в ліміт аргументів приблизно на 100 тис. рядків.

Змінено

  • Документація: пункт «118 EN adapters» довідки тепер несе лічильник у fr/it/ta (решта 15 локалей вже мали його). Вендорські ціни на cvstart.org перевірені повторно проти офіційних сторінок прайсингу — всі чотири без змін, CHECKED_ON → 2026-10-30. Пост-релізний sweep дрейфу виправив 42 застарілі рядки лічильників/версій по README ×19, CONTEXT.md, CLAUDE.md і мануалах SDD (залишки ер 109/104 та v1.262.0).

Примітки

  • Свідомо не робимо: нативний window.confirm у cv-studio (ера CAR-179) лишається — конвертація в UI.confirm це follow-up, а не пункт патча; тултіп шаблонних фраз <mark title> лишається лише тултіпом (допустимий мінімум a11y); remote-qa.yml тримає cancel-in-progress: true — повторний dispatch свідомо витісняє running-регресію.
  • Повна remote-QA на проді v1.262.1: маршрути ×19 ✓ · сайт ×19 ✓ · посилання ✓ · живий LLM 18/19 локалей A–G ✓ — pl впіймав одиночний тайм-аут провайдера 502 · 300 s · 0 ch (не token-dense територія; прицільний повторний запуск їде з цим релізом).

Тести 6867 (6864 pass / 0 fail / 3 skip, +26 від v1.262.1) · Playwright 176 · покриття 98.42 % рядків / 89.94 % гілок · реєстр без змін: 123 джерела = 118 EN + 5 RU · 118 адаптерів.

[1.262.1] — 2026-10-29

Патч італійських оцінювань: it долучається до token-dense мов (стеля 32768 токенів + годинник 480 с) після того, як ера OpenRouter спіймала її на обрізанні звіту просто перед блоком SCORE_SUMMARY — а інструментарій перемикання провайдера, який зробив діагноз можливим, виходить в deploy-воркфлоу.

Виправлено

  • Оцінювання it обрізане перед SCORE_SUMMARY (хвіст CAR-190): remote-QA 40928864980 (перший запуск на поповнених кредитах) — оцінювання it тривало 264 с і повернулося з 15 045 знаками при tok ~15635 на стелі 16 384: блоки A–G цілі, ---SCORE_SUMMARY--- зник. Той самий клас форми, що приніс ta її стелю в v1.261.1 (CAR-174). it долучається до TOKEN_DENSE_EVAL_LOCALES (ta, hi → ta, hi, it): і бюджет 32 768 токенів, і годинник 480 с тепер застосовуються, а /api/evaluate + auto-pipeline лишаються на єдиному джерелі істини в llm.mjs.
  • Звіт en без блоків — разовий поколінневий флейк: той самий запуск побачив одне оцінювання en, яке повернулося мовно правильним без жодного з блоків A–G; прицільний перезапуск пройшов чисто (21 091 знак, A–G ✓). До token-dense набору не додається — один флейк не історія.

Додано

  • Deploy-воркфлоу: mode=hermes і mode=provider (ops-хвіст CAR-190): mode=hermes перезапускає шлюз Hermes і друкує повну діагностику (unit-файл із вилученими секретами, слухачі до/після, журнал sudo, зондування per-port, власний погляд застосунку на HERMES_BASE_URL, баланс кредитів OpenRouter); mode=provider перемикає LLM_PROVIDER зі сторожею наявності ключа (відмовляє, коли *_API_KEY цільового провайдера відсутній) і перезапускає застосунок — так прод переїхав на openrouter, коли кредити Hermes вичерпалися на льоту. Сторожа ref deploy-режиму валідується лише для mode=deploy, тож dispatch-и recon/verify знову працюють (зміна required-inputs ери v1.261.3 їх замовила).

Примітки

  • Свідомо не робимо: en не додається до token-dense набору — один звіт без блоків це поколінневий флейк, не історія обсягу.
  • Свідомо не робимо: посилена ta-директива мови не поширюється на it — мовного дрейфу в італійській не спостерігалося.
  • Свідомо не робимо: запис нового демо-відео UI-v2 лишається за власником (CAR-141) — лендінг тепер усе одно вбудовує через youtube-nocookie.com.
  • Тести 6841 → 6841 на релізній гілці.

[1.262.0] — 2026-10-29

Мінор з upstream-функціями: борд Deel приєднується до сканера (реєстр 123), CSV чужого трекера імпортується прямо в #/tracker, перевірку цілісності posting-guard і перевірку голосу CV доставлено як релеї/лінти, сканер вчиться фільтру років досвіду, а кнопки Apply ще сімома мовами перестають читатися як відсутні — плюс замінено виведену з експлуатації дефолтну модель MiniMax, і репости більше не фабрикуються між джерелами.

Додано

  • Джерело борду Deel (CAR-184): реєстр 123 джерела = 118 EN + 5 RU · 118 адаптерів — автоматично розпізнає tenant-борди jobs.deel.com/<slug> (Klarna та інші); Next.js flight-пейлоад несе весь каталог в одному запиті (посади, локації, формат роботи, дати, опис), заборонений шлях /api/ ніколи не торкається, tenant, що тримає кар’єру в іншому місці, відповідає редиректом, який джерело відхиляє пойменовано, а компенсацію свідомо не маплено (суми без інтервалу виплат були б здогадкою); гейти лічильників ×5 + help §17 ×19.
  • CSV-імпорт трекера (CAR-181): POST /api/tracker/import-csv із dry-run-прев’ю та заблокованим commit — лапки RFC-4180, дати у формах ISO / день-спочатку / назва-місяця, етапи чужого трекера згортаються через канонічні стани (невідомий етап осідає як Evaluated з нотаткою, ніколи не викидається), дедуп за нормалізованим URL вакансії, прочитаним із комірки markdown-посилання або колонки Notes, ліміти 512 КБ / 2000 рядків; #/tracker отримує кнопку Import CSV із модалкою прев’ю, i18n ×19.
  • Релей posting guard (CAR-182): POST /api/runners/posting-guard — детермінована zero-LLM брама цілісності JD з батька, розв’язується номером звіту через правило префікса захоплень jds/ (з паддінгом чи без, порівнюється як число) або явним санітизованим шляхом; дитина отримує лише argv, стелю 30 с і fail-soft-контракт; дія «Posting guard» на #/reports/:slug рендерить знахідки, i18n ×19.
  • Перевірка голосу CV (CAR-188): 27 шаблонних фраз із резюме (passionate about, proven track record, leveraged, spearheaded, delve…) плюс тире-евристики (діапазон років на кшталт 2022–2024 — не ознака; 20–30% — ознака) як read-only POST /api/cv-studio/voice-check і картка #/cv-studio, що підсвічує точні збіги; чекліст Tailor зростає до шести інваріантів; i18n ×19.
  • Скан-фільтр років досвіду (CAR-180): парсер jd-years (65 формулювань, при сумніві ніколи не відкидає) читає заявлений мінімум з описів вакансій, нова опція years_min: у portals пропускає кваліфікаційно нижчі вакансії як skipped_experience (спостережний статус історії, що ніколи не пришпилює URL), а yearsMin їде в снапшоті скану на майбутній UI.
  • Кнопки Apply ще сімома мовами (CAR-185/189): в’єтнамська Nộp đơn ứng tuyển, болгарська Кандидатствай/Кандидатстване/Външно кандидатстване (іменник заанкерований до початку мітки — посилання на статті-поради не Apply), нідерландська Solliciteer/Solliciteren, іспанська ¡Apúntame!/Postularse (мітки, не дієслово — FAQ-посилання більше не заважають), китайська 申请职位/投递 (заанкеровано до цілого контролу) — живі вакансії на цих бордах перестають читатися як no_apply_control.

Виправлено

  • Дефолтна модель MiniMax (CAR-187): виведену з експлуатації дефолтну MiniMax-Text-01 замінено на MiniMax-M3.1-Flash на api.minimax.io і MiniMax-M3 на api.minimax.cn; базовий URL запінений до вендора (невендорський хост відхиляється в рантаймі, і ключ ніколи туди не дістає); підказка налаштувань ×19 називає нові дефолти.
  • Репости вимагають того самого джерела (CAR-186): роль, виставлена на LinkedIn і на Greenhouse роботодавця, більше не звітується як репост — кластери тепер діляться за sourceHost(), який згортає регіональні субдомени лише на відомих сайтах (de.linkedin.com ≡ www.linkedin.com, але ca.example.com лишається окремо), згортає аліаси хостів Greenhouse в одне джерело і зрізає кінцеву крапку.
  • Мовний дрейф тамільських оцінювань (CAR-183): посилена тамільська директива тепер сидить у волатильному хвості промпта поруч із бюджетом об’єму (слот CAR-174) — remote-QA-звіти знову потрапляють тамільською після інтермітентного дрейфу, поміченого у v1.261.2.

Примітки

  • Свідомо не робимо: релей posting-guard виходить fail-soft — форк не несе posting-guard.mjs батька досі (upstream-PR #6146 досі відкритий), тож ендпоінт відповідає {available:false, reason:"script-not-found"}, доки синк батька його не доставить.
  • Свідомо не робимо: yearsMin не виступає UI-чіпом #/scan у цьому релізі — поле їде в снапшоті, а фільтр лише через конфіг portals.
  • Свідомо не робимо: upstream-зміни Machine Summary у modes/reports (#6161/#6160/#6157) не портовано — вони прийдуть із задеплоєним батьком, а не як web-ui-дзеркала.
  • Тести 6734 → 6841 на релізній гілці; ключ дедупу сканера недоторканий (поведінку нормалізації URL не змінено).

[1.261.3] — 2026-10-28

Патч завершення дизайну: фаза 3 дизайну Lovable (щільність + повне виведення коралового), host-pool скану (32 компанії, 10 на хост), шість виправлень локацій в upstream-провайдерах, джерело Trakstar Hire (реєстр 122), undo + unsaved-guard у CV Studio — плюс виправлення P3 фінальної регресії та паритет документації.

Виправлено

  • Підпис «related» кластерів мапи 3.16:1 → 4.68:1 (обидві теми): світлий --map-score-mid затемнено до #567b75 (запропонований #5f7f7a дав лише 4.37 — потрібен був крок темніший); темний бік вже 4.98:1, недоторканий. Закріплення red-first у map-cluster-visual.
  • Фокус-кільце skip-посилання: останнє літеральне обведення #ff385c → var(--accent-c) — усі 78 фокус-кілець тепер слідують акцентному токену.
  • Акцентна топологія .tab-btn.is-active (#584, фаза 3): контроль змішував три колірні сімейства (коралова підмальовка + danger-текст + синя рамка); тепер — єдина пара accent-soft.
  • Виправлення локацій в upstream-провайдерах ×5 (#580, CAR-176): workday прапорець Fully Remote (не місце), workable розгортання telecommuting + штат лише за відсутності міста, successfactors висяча кома, lever кордон слова для remote, icims мітки з префіксом Location.
  • http-json: без повторних спроб для вичерпаного таймауту (#580) — аборт за дедлайном нетранзитивний в обох retry-помічниках (upstream виміряв ~94 с на одній дошці Ashby при 30 с × повтори).
  • Паритет документації (#587): останні лічильники ери 17/18 у HANDOFF/CONVENTIONS/README (uk отримала бракуючу секцію Локалізація; §-історію продовжено до §32/§33 — арифметика 33 H2 перевірена).

Додано

  • Фаза 3 дизайну Lovable (#584): токени щільності живуть — рядки таблиць ≈32px, контроли 30px, чипи 24px (виняток відступів WCAG 2.5.8); кожен кораловий rgba-залишок перенесено на cv3-пари (.dash-hero, .badge-bad, гало, .tab-btn.is-active); оголошено --ease-out, TOKEN_HANDOFF порожній; контракт .callout--ok/--danger.
  • Джерело Trakstar Hire (#583, CAR-177): реєстр 122 джерела = 117 EN + 5 RU · 117 адаптерів — пін до хоста jsapi.recruiterbox.com, обхід за offset зі зупинками на коротких/порожніх/повторних сторінках, часткові результати зберігаються, мічення trakstarTruncated; гейти лічильників ×5 + help §17 ×19.
  • Host-pool скану (#579, CAR-178): до 32 компаній одночасно, ліміт 10 на хост (upstream-патерн keyed-limiter) — снапшот байт-у-байт ідентичний послідовному прогону (red-first гейт на timing-моках).
  • Undo + unsaved-guard у CV Studio (#582, CAR-179): обмежений стек undo (30) над результатами AI-прогонів із Cmd/Ctrl+Z, що працює на розкладках, відмінних від QWERTY, гард незбережених змін при полишенні маршруту (live-перевірка брудності, confirm/rewind/beforeunload), i18n ×19.
  • Виправлення upstream-провайдерів протестовано ×17 (частина CAR-176); підсумок remote-QA називає джерела скану, що впали, + tok ~N на кожне оцінювання (#567, вже у v1.261.2 — спостережуваність використано для триажу цього релізу).

Примітки

  • Акцент тепер синій (концепція Lovable): первинні дії / активні стани / фокус-кільця слідують --cv3-accent; червоний зарезервовано для danger (.btn-danger, toast.error, danger-текст). Корал лишився лише в PNG-активі логотипа (рішення власника).
  • Щільність: рядки таблиць ≈32px / контроли 30px / чипи 24px — виняток відступів WCAG 2.5.8 задокументовано; канарку 44px з v1.26.1 переписано на контракт токенів.
  • CodeQL: усі 10 відкритих алертів закрито (4 відхилено як хибні спрацювання з обґрунтуванням: задокументовані класи llmRateLimit/atomicWrite; 6 виправлено в джерелі).
  • Тести 6655 → 6734 на релізній гілці; поведінку джерел сканера, окрім нового джерела Trakstar, не змінено.

[1.261.2] — 2026-10-28

Патч стабільності та шліфування: кеш геокодування переживає крах посеред запису (CAR-173), UI.md рендерить продовження маркованих рядків і вкладені списки, витоки i18n-ключів у відрендереній довідці закрито ×17, оцінювання отримує ліміт токенів з урахуванням локалі для ta (CAR-174), хром RTL дзеркалиться логічними властивостями (85 знахідок), скріншоти мапи перекадровано ×19, а бульбашки кластерів мапи проходять контраст у темній темі — плюс перша фаза інтеграції дизайну Lovable (пасивна таблиця стилів tokens-v3.css), бекфіл документації (реліз-рев’ю + спеки) і винятки покриття для варіативності CI-нод.

Виправлено

  • Втрати кешу геокодування від розірваного запису (CAR-173, #562): обидва шляхи скидання на диск (дебаунсний insert-flush, exit-flush) писали .cache/geocode.json голим writeFileSync — truncate-then-write — тож крах, OOM чи SIGKILL посеред запису лишали розірваний файл, а JSON.parse наступного старту падав, мовчки викидаючи весь персистентний кеш (тижні бюджету Nominatim), а не лише денний. Обидва місця запису тепер ідуть через file-lock.mjs::atomicWrite (temp у тому самому каталозі → rename → unlink при невдачі) — той самий контракт, який FULL-R1/FULL-M1 застосували до pipeline.md, cv.md, profile.yml, career-plan, вхідних даних batch і memory. Саму механіку скидання CAR-142 (дебаунсний insert-flush, exit-flush, обмежене в часі завершення роботи) перевірено неушкодженою red-first: тести (a)/(b) закріплюють, що вставки досягають файлу у вікні GEOCODE_SAVE_MS і він лишається валідним JSON; (c) — механізмний гейт плюс поведінкові прогони обох шляхів скидання, які стверджують відсутність temp-сміття і парсабельний файл.
  • Продовження рядків списків у UI.md (#563): обробник списків споживав лише підряд ідучі рядки - /* , тож відступний рядок-продовження після маркера випадав з емітованого <ul>, а прохід абзаців пропускав фрагмент — продовження витікало сирою прозою з літеральними маркерами **/бектиків (~95 висячих прогонів на локаль по файлах довідки; en.md 95, ru.md 104). Рядок-продовження — провідні пробіли, не новий блок (заголовок, маркер, обгорнутий NUL-ами плейсхолдер fence, порожній рядок) — тепер приєднується до попереднього <li> як текст, склеєний <br/>, з прогоном inline() на кожен рядок, тож маркери наголосу ніколи не паруються через склейку; гард плейсхолдера fence — NUL як перший символ, а не голе збігання за токеном. Вкладені маркери -, нумеровані списки, fence всередині списків і заголовки всередині списків лишаються окремою задокументованою прогалиною (наступна хвиля документів).
  • Витоки i18n-ключів у відрендереній довідці ×17 (#561): remote-QA знайшла 85 сирих i18n-ключів, видимих у відрендереній довідці (кореневу причину доведено відтворенням: те саме споживання однорядкових маркерів у UI.md — загорнутий рядок-продовження, емітований сирим текстом, тримав бектики літеральними; секції довідки v1.260/261 писали багаторядкові маркери). 163 маркери розгорнуто в один рядок у 17 локалях — лише пробіли, жодного перекладеного слова не торкнуто (звірено старе проти нового). Новий гейт tests/help-rendered-keys.test.mjs проганяє кожен бандл через справжній ланцюг (stripGithubOnlyBlocks + vm-завантажений UI.md) і падає, якщо будь-який із 1,711 ключів з’являється в тексті; red-first 17 локалей → зелено. Інструментарій відтворення їде в репозиторії: scripts/remote-qa/help-render-scan.mjs (vm) + help-render-crosscheck.mjs (справжній DOM).
  • Remote-QA стверджує контракт палітри CAR-122 (#561): prod-regression.mjs тепер очікує палітру команд після Ctrl+K (.nav-v2-palette[role=dialog] + фокус у nav-v2-input) АБО застарілий фокус пошуку як fallback, замість того щоб мовчки приймати що завгодно; юніт tests/remote-qa-ctrl-k.test.mjs закріплює 6 сценаріїв на фейковому DOM, red-first 0/6 → 6/6.
  • Кадрування скріншотів мапи ×19 (#560): звіт власника щодо images/job-map-en.png — половина пошуку топбара + обрізаний h1 зверху, сіра смужка бічного списку біля правого краю, кластер маркерів, розрізаний рамкою біля Лондона, і біла горизонтальна лінія через мапу на ~2/3 висоти. capture-map-screenshots.mjs тепер чекає на спорожнення черги геокодування (located + noLoc === total, а не лічильник N/N — рядки Remote структурно не мають локації, тож старе очікування ніколи не завершувалося б), ховає header.topbar і #notif-drawer на час знімка, обрізає точно прямокутник .job-map-page (жодна панель не розрізається краєм кадру), віддаляє мапу на два кроки, тож жоден кластер не сидить наполовину розрізаним, фіксує висоту мапи, робить звичайний скріншот в’юпорту (captureBeyondViewport ресемплить композитний тайловий шар і малює білі смуги на межах рядів тайлів) і вирівнює контейнер мапи за цілими пікселями. Усі 19 скріншотів job-map перезнято.
  • Бульбашки кластерів мапи — контраст у темній темі + вміщення підпису (#572): owner-sweep P2/P3 — у темній темі кільця кластерів сиділи на фіксованій білій заливці (1.83:1); кільця тепер беруть var(--paper) із цифрами score-палітри (виміряно 4.98:1+, із заливкою 9.53:1; найнижчий діапазон математично неможливо зафарбувати на темному — він тримає 6.1:1 через чесний шлях data-dim), а підпис related вміщується у свою бульбашку (підбір шрифту через rAF, числа ніколи не зменшуються).
  • #/scan — перемикання мови, повторне заряджання скелетонів (довитік #534, агент у польоті, прибуває перед тегом): довитік за виправленням власника у v1.261.0 (#534) — повторно заряджені скелетони при перемальовуванні після перемикання мови більше не переживають завершене повторне отримання даних.
  • Дрейф документації (CAR-139, агент у польоті, прибуває перед тегом): лічильники README ×19, HANDOFF ×19, факти CONVENTIONS пересинхронізовано з живим реєстром/станом тестів.

Додано

  • Реліз-рев’ю + бекфіл спек (док, #559/#558): docs/reviews/REVIEW-2026-10-28-v1.261.1.md (хвиля v1.261.0/v1.261.1 прорецензована проти своїх спек) і бекфіл SDD-спек тієї ж хвилі — хвіст BACKLOG тепер називає прогалину, що лишилася (окремі тести для sources/habr.mjs).
  • Глибокий RTL-обхід (#571): AR-гернес проходить 39 маршрутів × LTR-проти-RTL (дзеркалання X, переповнення, обрізання, сигнатури text-align) — 85 знахідок виправлено: фіксований хром (toast, сайдбар, дровер + його × закриття, docs-FAB, usage-HUD, skip-посилання) переведено на inset-inline-*/border-inline-*, таблиці — на text-align: start/end у 17 файлах в’ю + 2 бібліотеках, глобальне правило [dir=rtl] text-align:right вилучено (воно суперечило центруванню колонки зірочок скану), ізоляцію score-колонки CAR-58 перенесено на span ltrNum.
  • Гернес клавіатурної a11y (#570): tests/playwright-a11y-keyboard.mjs проходить 11 маршрутів (порядок і пастки Tab, індикатори фокусу, Escape-оверлеї, Enter/Space) — 0 продових дефектів; регресійне покриття нове.
  • Обхід неперекладеного i18n (#565): 36 значень, ідентичних англійським, перекладено в 9 локалях (*.rolePh, ключі config, ta-маніфест) + гард обхіду із закритим allowlist; аудит i18n сайту (cvstart.org) (#568): 20 ключів × 18 локалях перекладено (весь блок demo.*/stats.t.*/showcase.tab* був англійським всюди), 1 мертвий ключ вилучено ×19.
  • Спостережуваність remote-QA (CAR-175, #567): підсумок називає джерела скану, що впали (stderr samples, редагування перед зістриганням), і друкує tok ~N з використання провайдера; логіка живе в чистому summary-parts.mjs з дріфт-гейтом.
  • Вкладені марковані списки в UI.md (один рівень, #564): маркери - рендеряться як вкладений <ul> (безпечно щодо плейсхолдерів fence) — останні 2 висячі фрагменти прози на локаль зникли.
  • Інтеграція дизайну Lovable — фаза 1 (#566) / фаза 2 (на гілці, прибуває перед тегом): public/css/tokens-v3.css портує концепцію Lovable дослівно (oklch-поверхні 0–3, AA-анотовані пари чорнила, сімейство акцентів, ok/warn/danger/info/neutral soft+ink, score-діапазони A–G ×2 теми, щільність + фокус + тіні); фаза 2 активує її на наявних іменах токенів (акцент розділено на blue/danger, score-діапазони + callout на cv3-токенах) з оновленими гейтами контрасту/ролі/темного.
  • Ліміт токенів оцінювання з урахуванням локалі (CAR-174, #569): evalMaxTokens(lang) — ta/hi запитують 32768 (три SDK-клієнти перетискали назад до 16384; виправлено обидва шари), плюс бюджет обсягу тамільської в промпті (≤5 речень на блок, ≤900 слів) — ta-звіт remote-QA у v1.261.1 було обрізано на ліміті вихідних токенів (без Block B–G + SCORE_SUMMARY).
  • Продовження справжніх виправлень CodeQL: новий інструментарій рендерингу QA замінив ручну обробку сутностей структурними розрізними проходами + htmlToText({cap: Infinity}); твердження редагування закріплюють точний замаскований вивід; невикористані константи RTL-гернеса вилучено.
  • Політика винятків покриття для варіативності CI-нод (habr #558, states #561): sources/habr.mjs (гілкове покриття CI node-matrix 87.23 проти локальних ≥89) і states.mjs (CI 86.49 проти локальних ≥89) приєднуються до винятків у scripts/coverage-baseline.json — файли, чиє виміряне на CI гілкове покриття структурно нижче локального бар’єру через варіативність версій node, отримують виняток замість опускання всього бар’єра.

Примітки

  • Контракт Ctrl+K (CAR-122): комбінація відкриває палітру команд — вона НЕ фокусує поле пошуку напряму; пошук фокусується з палітри. Прод-регресія remote-QA тепер стверджує саме це (#561).
  • Відхилені хибні спрацювання CodeQL ×5 (FP-dismiss): алерти js/missing-rate-limiting на main чекають токен security_events — дія оператора, а не зміна коду.
  • Прибуло після зрізання гілки: #561/#562/#563/#564/#565/#566/#567/#569/#570/#571/#572 — усі злиті в main; агенти #534-скелетонів і CAR-139 зливаються перед тегом. Вкладені марковані списки глибше одного рівня і щільна інлайн-геометрія в’ю лишаються задокументованими прогалинами.
  • Тести 6609 на релізній гілці (+15 від v1.261.1; ще ~13 від #561 при злитті — 2 нові гейт-файли); єдині червоні тести до fan-out — гейти паритету CHANGELOG ×19 (locale fan-out — наступний крок оркестратора); джерел сканера не торкнуто (реєстр 121 = 116 EN + 5 RU · 116 адаптерів).

[1.261.1] — 2026-10-28

Клієнтський патч прод-QA: виправлення P1-краху на #/two-pager, Ctrl+K на розкладках, відмінних від QWERTY, мобільна хвиля 390px (переповнення docs-assistant, перекриття чипів мапи, гернес на 39 маршрутів), завершення CAR-137 (конкретні помилки, v2empty ×5, підказки ×13 + сторінки режимів) і CAR-140, що розставляє справжні v2-скріншоти всюди.

Виправлено

  • TDZ у two-pager (CAR-170, P1, #554): Cannot access 'previewTimer' before initialization — редактор збережених тегів викликав onChange під час конструювання для кожного відновленого чипа, тоді як цикл конструювання лексично розташовувався вище за let previewTimer, тож увесь рендер #/two-pager падав (без h1, ×19 локалей). Планувальник і прив’язка тепер живуть вище за цикл; поведінковий тест виконує справжній модуль в’ю — до виправлення та сама прод-помилка, після — відрендерена сторінка — плюс дріфт-гейт лексичного порядку.
  • Ctrl+K на розкладках, відмінних від QWERTY (CAR-172, #554): обидва обробники комбінації зіставляли лише e.key — на російській розкладці комбінація приходить як key='л' code='KeyK' і нічого не робила. Тепер зіставлення за e.code === 'KeyK' у nav-v2.js та app.js; відтворено в реальному браузері синтетичною комбінацією «л»/KeyK. Контракт CAR-122 переформульовано (див. Примітки).
  • Переповнення docs-assistant на 390px (CAR-172, #554): стартові чипи CAR-136 були .btn (nowrap, flex-shrink: 0) — один нерозривний елемент 418px виштовхував #/docs-assistant на 56px за межі в’юпорту 390px; патерн стиснення CAR-146 (у v2-llm.css, гейти CSS на 800-LOC недоторкані) зводить його до 0.
  • Перекриття чипів мапи @390 (#552): на в’юпорті 390px мета-чипи мапи перекривали рядок CTA (map.css).
  • CAR-137 — конкретні помилки + v2empty ×5 (#550): п’ять генеричних fallback-ів помилок (‘Error’ / ‘failed’) стали конкретними дієвими ключами — збій evaluate POST, потік auto-pipeline без термінальної події, додавання scan→pipeline, завантаження кластерів репостів, undo у pipeline; останні п’ять плоских empty-станів (#/reports, #/usage, #/portals, #/activity, #/dashboard) приєднуються до патерну v2empty з CAR-124 (пояснення + позначений aria-hidden приклад + наперед підключена дія; 15 нових ключів v2empty.*, чотири мертві однорядкові ключі вилучено) — усе ×19 словників, снапшот перегенеровано.
  • CAR-137 — HelpHint ×13 + набір для сторінок режимів (#550): контекстна підказка «?» тепер сидить на H1 кожної з решти сторінок (pipeline, reports, dashboard, cv, portals, usage, activity, assessments, map, health, docs-assistant, settings) і на 8 сторінках режимів через набір (кожен cfg MODES несе hintKey, відрендерений через HelpHint.title).

Додано

  • Гернес Mobile-390 (#552): прогін Playwright по кожному маршруту SPA (31 канонічний + 8 сторінок режимів = 39 хешів) на 390×844 із сідованим корпусом — перевірка горизонтального переповнення на кожному маршруті (scrollWidth проти clientWidth, допуск 1px, називає найширшого порушника) плюс перевірка досяжності CTA (жодна sticky-панель/дровер/FAB не перекриває основну CTA); opt-in, коректно пропускається без бінарника браузера, працює без мережі.
  • Свіжі v2-скріншоти ×48 (CAR-140 фаза A, #551): справжні скріншоти UI v2 із сідованої CI-ізольованої фікстури — dashboard ×19 + job-map ×19 (job-map-ta — перша поява, job-map-vi — новий) + scan/pipeline/tracker/reports × en/ru — живлять README, довідку та лендінг.
  • Справжні скріншоти на cvstart.org (CAR-140 фаза B, #553): слоти-плейсхолдери CAR-140 зникли — лендінг показує фактичні v2-скріншоти (вітрина job-map + ряди скріншотів, ×19 локалей лендінгу).
  • Підказки CAR-137 (#550): 21 новий перекладений ключ ×19 (help.hint.* ×20 + hintKey сторінок режимів) — кожна сторінка тепер пояснює себе на місці.

Примітки

  • Контракт Ctrl+K (CAR-122): комбінація відкриває палітру команд — вона НЕ фокусує поле пошуку напряму; пошук фокусується з палітри. Рядок чекліста QA v1.261.0 «Ctrl+K фокусит поиск» більше не чинний; оновлену перевірку несе QA-промпт v1.261.1.
  • Справжні виправлення CodeQL (#547): 4 алерти закрито (ітерацію за literal-ключами в content.mjs зроблено структурною, lint-прохід) — приїхали пост-релізом на main v1.261.0 до цього патча; перелічені тут для повноти.
  • Тести 6582 (+51); браузерний набір — 171–172 пройшли / 0 провалено / 1 опційний пропуск; гернес mobile-390 запускається самостійно (node tests/playwright-mobile-390.mjs). Джерел сканера не торкнуто (реєстр 121 = 116 EN + 5 RU · 116 адаптерів).

[1.261.0] — 2026-10-28

Потяг повної ревізії 1.261.0 завершено (усі HIGH- і MEDIUM-знахідки приїхали), виправлення перемикача мови за повідомленням власника, виправлення якості оцінювання, розкладання довідки ×18, лендінг cvstart.org v2 — плюс 4 спеки SDD/OpenSpec, що документують потяг. Кожне виправлення приїхало через fix-forward red→green і рев’ю GLM-5.3-Flash зі свіжого контексту.

Безпека

  • FULL-R1 — CSRF-гард acting-GET + заблоковані атомарні записи pipeline (#519): гард Sec-Fetch-Site/Origin/Referer на /api/stream/*, /api/run/*, /api/geocode (експортований ACTING_GET, статичний гейт покриття); кожне читання-модифікація-запис у data/pipeline.md / data/last-scan.json — під withFileLock + новим atomicWrite (temp у тому самому каталозі → rename → unlink при невдачі).
  • FULL-R2 — SSRF-кластер у джерелах (#514): rss.mjs — https + відмова приватних адрес; ~25 джерел із голим fetchImpl → захищені транспорти; закріплені дзеркала; валідація редіректів для habr/hh/geekjob/getmatch; спільний delay(ms, signal).
  • FULL-R4 — псування прози в stripDangerousMarkdown (#513/#518): видалення схем лише в контекстах посилань/атрибутів; сутності декодуються в окрему копію лише для виявлення («JavaScript: React, Node» і «R&D» виживають).
  • FULL-M2 — зміцнення транспортів (#520): safeGet враховує timeoutMs разом із сигналом викликача (bridge controller); guardResolvedHost відмовляє ЗАКРИТО при помилці DNS. #525 додає зістригання credential-заголовків при крос-доменних редіректах, відв’язування transport-лістенера на кожному шляху завершення та виправлення краху від заздалегідь перерваного сигналу.
  • FULL-M4 — логотипи/batch (#523): логотип перевіряє magic bytes на КОЖНОМУ шляху (брехлива image/png не може нести SVG), обрізає content-type до image/* при збереженні, приватний кеш + nosniff; batch: --parallel обмежено 1–8, --min-score валідовано, BATCH_BUSY single-flight, llmRateLimit на мутаціях config/batch.

Додано

  • CAR-147 — ліміт оцінювання з урахуванням локалі (#524): evalTimeoutMs(lang) — ta/hi отримують 480 с замість 300 с (індійські писемності токенізують кілька токенів на склад; стара стіна давала 502 + порожній звіт); клієнт remote-QA чекає 540 с.
  • CAR-133 — лендінг cvstart.org v2 (#532): паралакс-герой із гейтом reduced-motion, явно підписана СИМУЛЬОВАНА демонстрація потоку scan→evaluate→tracker, секції scroll-reveal із перевіреними наживо лічильниками, слоти-плейсхолдери скріншотів CAR-140.
  • CAR-135 — довідку переписано під UI v2, розкладання ×18 завершене (#533, #540, #541, #542): усі 19 файлів довідки описують оболонку v2 (складана навігація, палітра Cmd/Ctrl+K, скелетони, empty states із прикладом і дією, chrome скану v2, pipeline з групуванням + undo, reports v2, канбан); підписи інтерфейсу цитують свої i18n-ключі; речення про 121 джерело живе з реєстру.
  • Спеки (SDD/OpenSpec) (#535): бекфіл wave-two за 27 жовтня + три перспективні спеки, що документують потяг 1.261.0 (розкладання довідки, write hand-offs, інваріанти перемикання мови).

Виправлено

  • Перемикання мови більше не знеплюює #/scan (#534) — за повідомленням власника: стан скану, локальний для в’ю (результати, чіпи фацетів), тепер живе на рівні модуля; поведінковий Playwright-гернес із відкладеним на 4 с повторним фетчем (червоний на старій формі).
  • CAR-148/149 — ru-звіти «missing Block G» (#530): промпт навчав трьом суперечливим мапам A–G; скелет + список + приклад тепер узгоджені з oferta.md (G = Posting Legitimacy); поведінковий дріфт-гейт.
  • CAR-150 — undo у pipeline зберігає метадані комп (#537): DELETE віддає comp, POST приймає опційний comp за гардом, в’ю несе {token, comp} крізь усі fallback-гілки.
  • FULL-M3 — мертві джерела звітують чесні борди (#522): getro/mycareersfuture/meituan/mokahr/glints кидають помилку на зламаній page-1 (мертвий API більше не читається як «healthy, 0 jobs»); pinnedOverride закріплює overrides company.api для 18 адаптерів.
  • FULL-M5 — один каскад провайдерів (#521): routes/llm.mjs через runActiveProvider (+cachePrefix, сигнал); перехоплення сабпроцесу Gemini; контракти відповідей ідентичні байт-у-байт.
  • FULL-M1 — записи в спільні файли під замком (#526, #539): runTrackedScript + ростер TRACKER_MUTATING; спавни outcome/followup тримають замок; tracker.mjs пише через atomicWrite; PUT зберігає locked+atomic.
  • FULL-M6 — консолідація сервер + клієнт (#531, #536, #525): dotenv з null-прототипом + відмова __proto__; isPrivateOrLoopback покриває NAT64/6to4/benchmark/reserved; mtime-кеш env-config; справжній slot GC у file-lock; safeGet timeout+signal; DNS-гард fail-closed; SSE ×4 → API.postStream; бібліотеки фокусу/підписів/статусів; clipboard з await; тінт Hired на дашборді.
  • R3 — дріфт CSS-токенів (#527): --accent розв’язано (фокус-кільця мапи мовчки не працювали); 11 позатокенових тривалостей згорнуто; дріфт-гейт.
  • R4 — відсталі завантаження (#538): assessments/mode-page/прев’ю pipeline рендерять скелетони CAR-112.
  • Пакет CodeQL + CI (#517, фікс #539): невикористані імпорти, гард ACTING_GET імпортує справжній regex, lifecycle-слайс переякірено.

Примітки

  • Навмисно не зроблено: DNS-пінінг на конекті (потребує undici — поза залежностями, задокументований залишок); скріншоти CAR-140 (потребують розгорнутого UI v2); CAR-137/138/141 (наступний прохід по доках); клас CodeQL js/missing-rate-limiting — задокументоване хибне спрацювання (кастомний лімітер не зараховано).
  • Розкладання довідки ×18: es/de у wave-1 пропустили нотатку вступу та дельту картки Active Companies (de доїхав із власним вступом; es отримає однорядковий follow-up).

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