Commit Graph
509 Commits
Author SHA1 Message Date
Sergey Matyuninandclaude[bot] 582066d017 test: harden marker rollback review gates
Issue: #442
User-Visible: no
2026-09-03 21:42:57 +00:00
Sergey Matyuninandclaude[bot] 3a88f3ad6c fix: roll back rejected marker writes atomically
Issue: #442
User-Visible: yes
2026-09-03 21:42:57 +00:00
Claude f6cd99cef4 feat(tools): страница /convert для конвертера Sweet Home 3D, этап 2
Артефакт один — scripts/sh3d-convert/dist/index.html, самодостаточный
файл, как и лендинг: раскладывается копированием, без сборщиков и CDN.
Собирается склейкой тех же модулей, которые проверяют гейты этапа 1;
относительных импортов на странице нет, поэтому она работает из любого
каталога и без сети.

Три обещания страницы проверяются тестами, а не текстом:

1. Ни одного сетевого вызова и ни одной внешней загрузки. Ищутся формы
   ВЫЗОВА, а не имена API: первая редакция теста краснела на собственном
   комментарии, где эти API перечислены как запрещённые. Шрифты
   системные — <link> к шрифтам это внешний запрос при загрузке, а
   обещание «файл не покидает браузер» должно быть буквальным. Заодно
   поэтому на странице нет аналитики.
2. Конвертер внутри страницы даёт тот же результат, что модули
   репозитория: код извлекается из собранного артефакта, прогоняется на
   фикстуре и сравнивается с golden. Склейка, потерявшая модуль,
   краснеет.
3. Собранная страница не отстала от исходников: build-page.mjs --check
   сверяет закоммиченный артефакт с пересборкой.

Плюс полнота переводов: у каждой подписи есть ru и en, словари
совпадают по ключам, у каждого кода отчёта и каждого кода отказа есть
человеческий текст. Иначе пользователь увидит пустую надпись или
`not_zip`.

Отчёт показывается ДО скачивания и по этажам: сколько комнат, стен и
проёмов, какая клетка сетки, и полный список мест, где конвертер принял
решение, — спрямлённая дуга, обрезанная толщина, непривязанный проём,
пропущенный этаж без комнат. Это требование задачи: искать чужие ошибки
в готовом плане из сотен объектов хуже, чем прочитать их заранее.

Гайды (ru/en) получили раздел про импорт из Sweet Home 3D с прямой
записью, что это необязательный ярлык, а не шаг настройки: SCOPE обещает
«no external editors», и обходить это молчанием нельзя.

Свидетели этапа 2, все проверены отрицательным прогоном: добавленный в
страницу fetch, потерянный русский перевод, правка dist руками, склейка
без модуля sh3d.mjs — каждая краснит свой тест.

Гейты: npm test 1871 tests, 1870 pass, 0 fail; pytest без HA 378 passed,
3 skipped.

Осталось владельцу: раскладка на хост (из песочницы SSH недоступен) и
проверка на настоящем файле Sweet Home 3D.

Issue: #446
User-Visible: no
2026-09-04 00:41:40 +03:00
Claude 2695307ef0 feat(tools): конвертер Sweet Home 3D → документ импорта, этап 1
Решение владельца по итогам исследования: из чужих форматов планировок
работать имеет смысл только с .sh3d, и конвертер живёт на сайте, а не в
карточке. Продуктового кода задача не касается вовсе — документ импорта
не подписан и не привязан к инстансу, поэтому сторонний генератор это
легальный сценарий уже сегодня.

Этап 1 — всё, что должно жить в репозитории и проверяться в CI:

- scripts/sh3d-convert/{xml,zip}.mjs — читатели XML и zip без единой
  зависимости, работают и в Node, и в браузере (DecompressionStream либо
  node:zlib). Недоверенный ввод отбивается на входе: DOCTYPE
  пропускается и не загружается, объявления сущностей отвергаются,
  шифрованные записи, zip64 и распаковка сверх предела — отказ с кодом;
- sh3d.mjs — уровни, комнаты, стены, двери и окна в сантиметрах;
  мебель, материалы, свет, камеры не читаются вовсе;
- convert.mjs — маппинг в документ kind=space, plan-only, model 7.

Форма v7 выбрана намеренно и это главное техническое решение задачи.
При v8+ схема требует полный каталог сегментов: wall_ids по числу рёбер,
один-два владельца у каждого сегмента, проекция walls, совпадающая с
сегментами. Всё это на стороне сайта означало бы повторить серверный
алгоритм и разойтись с ним на первом изменении модели. В форме v7 ту же
работу делает commit_wall_segment_model — тот же путь, которым едут
старые бэкапы: сегменты собираются сами, общая граница двух комнат
склеивается в один сегмент с двумя владельцами, проёмы получают хозяина.
Проверено прогоном: v7 → валидный v9.

Второе решение — план строится по комнатам. Стена в нашей модели
существует как ребро контура комнаты, поэтому стены Sweet Home 3D дают
рёбрам только толщину, а уровень без комнат конвертировать нечем: это
отказ с объяснением, а не пустой план.

Геометрия: вершины комнат привязываются к осевым линиям стен (Sweet Home
3D обводит комнаты по внутренним граням, «как есть» получились бы две
параллельные стены вместо общей), затем сваривются с точностью до
сантиметра. Проёмы проецируются на ребро, угол берётся у ребра, длина
обрезается до ребра — серверная привязка допускает 8° и 0.02 шага
решётки, поэтому ни угол, ни центр из файла доверия не заслуживают.

Гейт против дрейфа версий (AC5) — две половины:
- test/sh3d-convert.test.mjs: фикстуры → конвертер → сравнение с
  закоммиченными golden. Правка конвертера без пересборки golden красная;
- tests_backend/test_sh3d_convert.py: golden проверяются настоящими
  CONFIG_SCHEMA и commit_wall_segment_model, плюс кросс-рантаймовый пин
  формул _wall_key и канонизации решётки. Правка модели, не отражённая в
  конвертере, красная — до того, как это увидит пользователь;
- tests_backend/test_ha_sh3d_convert.py: golden проходят настоящий
  create_preview (нужен HA, идёт в Linux CI). Там же отрицательная
  проверка: документ с приватным полем обязан получить отказ.

Свидетели, все проверены отрицательным прогоном: снятое выравнивание
вершин, отключённая сварка, угол проёма из файла, непроецированный
центр, необрезанная длина, снятая обрезка толщины, объявленная модель
v9, разошедшийся порт _wall_key, правка golden руками, поднятая
PLAN_MODEL_VERSION, изменённая серверная формула, изменённая канонизация
— каждая краснит свой тест.

Две фикстуры пришлось усилить именно из-за таких прогонов: углы и центры
проёмов в первой редакции совпадали со стенами случайно, и мутации
проходили молча; появилась и фикстура с общей границей без стены и шумом
в доли сантиметра — иначе сварка вершин не исполнялась ни разу.

Фикстуры синтетические, собраны генератором по опубликованному формату:
настоящих .sh3d в сборке нет и взять их автоматически негде. Проверка на
реальном файле — ручная приёмка владельца, записана в issue.

Гейты: npm test 1867 tests, 1866 pass, 0 fail; pytest без HA 378 passed,
3 skipped.

Этап 2 (страница /convert на houseplan.tech, ru/en) — следующим шагом.

Issue: #446
User-Visible: no
2026-09-04 00:35:21 +03:00
Sergey Matyunin cebd20a9b5 fix: polish vacuum map routes
Treat explicit empty route lists as authoritative, preserve them through single-space export, group deleted-space routes, and render vacuums from the immutable vacuum-only snapshot subset.

Issue: #443
User-Visible: yes
2026-09-03 23:24:08 +03:00
Sergey Matyuninandclaude[bot] fa41d2981c fix: keep vacuum routes pending until a floor is chosen
Issue: #441
User-Visible: yes
2026-09-03 19:22:55 +00:00
Codex 49f69f60db fix: refine a high-residual calibration on the map's own floor
User-Visible: no
Issue: #162
2026-09-03 19:55:52 +03:00
Codex 8e2892b95f fix: type the new vacuum routing code and add map_routes to the config schema
User-Visible: no
Issue: #162
2026-09-03 19:41:55 +03:00
Codex 4032b810b7 fix: calibrate a robot map against the space it is assigned to
User-Visible: no
Issue: #162
2026-09-03 19:30:42 +03:00
Codex 26b0b58320 test: prove the multi-floor overlay in the production bundle
User-Visible: no
Issue: #162
2026-09-03 19:21:39 +03:00
Codex 9809ec7a04 feat: validate vacuum map routes and clean them up with their space
User-Visible: no
Issue: #162
2026-09-03 19:18:13 +03:00
Codex 51b4cc7e2c chore: raise the initial-view ceiling for vacuum map routing
User-Visible: no
Issue: #162
2026-09-03 19:18:13 +03:00
Codex 567b456890 feat: warn on the dock when a moving robot has no floor to draw on
User-Visible: yes
Issue: #162
2026-09-03 19:18:13 +03:00
Codex ca6ebc3035 feat: assign each robot map to a floor in the device editor
User-Visible: yes
Issue: #162
2026-09-03 19:18:13 +03:00
Codex fec99b907b feat: record vacuum trails under the route that produced them
User-Visible: no
Issue: #162
2026-09-03 19:18:13 +03:00
Codex c38501ef50 feat: route live vacuum overlays to the active map space
User-Visible: yes
Issue: #162
2026-09-03 19:18:13 +03:00
Codex 9126e5b430 feat: pure map-to-space routing contracts for multi-floor vacuums
User-Visible: no
Issue: #162
2026-09-03 19:18:13 +03:00
Sergey Matyunin 5409f0b2e3 refactor: isolate room pointer preflight
Issue: #440
User-Visible: no
2026-09-03 16:59:31 +03:00
Sergey Matyunin 53847e3997 fix: harden beta 2 audit paths
Issue: #440
User-Visible: yes
2026-09-03 16:50:54 +03:00
Claude 5f90eaf4ef fix(gates): потолок initial-графа с полосой вместо разового храповика
#429 снял `SUPPORT_LAZY_INITIAL_BASELINE_BYTES = 291046` — порог,
привязанный к критерию приёмки #423, с пятнадцатью байтами запаса и
сообщением про копирайт формы поддержки. Снять было правильно. Но снят
он оказался ровно на том релизе, где сработал бы:

  beta.1 291 031 · снятый порог 291 046 · beta.2 291 069

Рост случился внутри той же беты, уже после снятия, и заметить его стало
нечем: единственным сигналом остался `lowHeadroomWarning`, который горит
постоянно — третий аудит подряд, и третий раз без реакции.

Механизм по образцу ядер (#425): потолок 292 000 Б с полосой 2 000 Б,
двусторонний. Рост выше потолка — отказ с числом и указанием, что делать
(поднять потолок в том же коммите с объяснением либо вынести код в
ленивый граф, #367). Падение ниже полосы — тоже отказ: незафиксированный
выигрыш отыгрывается молча, именно так запас бюджета ушёл с 26 КБ до
8.3 КБ за сутки.

Полоса, а не точное число, — по измерению, а не из осторожности. На
beta.2 initial-чанк стал МЕНЬШЕ на 344 сырых байта и при этом на 40 байт
больше в сжатом виде: gzip не монотонен по исходнику. Точный храповик по
gzip краснел бы на коммитах, которые код сокращают, — та же лотерея, о
которой предупреждает комментарий к бюджету 300 000. Потолок поставлен
так, чтобы факт лежал ближе к середине полосы: 931 Б до отказа сверху,
1 069 Б снизу.

Предупреждение о запасе стало погашаемым: `LOW_HEADROOM_ACKNOWLEDGED_CEILING`
привязан к ЗНАЧЕНИЮ потолка, поэтому признание долга перестаёт покрывать
ровно тогда, когда потолок поднимут. Сейчас `null` — не погашено, и текст
говорит, чем гасится. Превышение самого бюджета признанием не гасится:
там отказ, а не тревога.

Свидетели. Девять мутаций, каждая краснит свой тест: снятие верхней
стороны, снятие нижней, потолок выше бюджета, полоса 50 Б, признание
поверх превышения бюджета, молчащее устаревшее признание, и две — про
подключение: вызов потолка убран из main и отказ понижен до console.log.
Последние две прошли молча на первой редакции тестов — статическая
проверка «в main есть вызов» была бы циклическим доказательством, за
которое #430 снял циклический тест гарда benchmark, поэтому добавлен
прогон настоящего CLI в подставном дереве.

Захардкоженный `lowHeadroomWarning(9058)` из #429 заменён на число из
поставляемого манифеста: константа в тесте выглядела измерением, не
будучи им.

Мутант `initial-view-ceiling-unplugged` в реестре.

Гейты: npm test 1819 tests, 1818 pass, 0 fail; node scripts/bundle-budget.mjs
зелёный — 291 069 внутри полосы, запас до бюджета 8 931 Б.

Issue: #438
User-Visible: no
2026-09-03 16:18:14 +03:00
Codex d4dd027b0a build: prepare v1.71.0-beta.2 candidate
Issue: #426
Issue: #427
Issue: #428
Issue: #431
Issue: #432
Issue: #434
User-Visible: no
2026-09-03 15:23:40 +03:00
Sergey Matyuninandclaude[bot] a265830e3b chore: refresh custom image config schema
Issue: #51
User-Visible: no
2026-09-02 21:56:04 +00:00
Sergey Matyuninandclaude[bot] dc95563d96 feat: add reusable custom decor images
Issue: #51
User-Visible: yes
2026-09-02 21:56:04 +00:00
Codex d4ecace64c test: cap the two frontend cores with a ratchet (#425)
User-Visible: no
Issue: #425
2026-09-03 00:26:47 +03:00
Codex 0f5161164f test: prove the drift gate itself can fail (#422)
User-Visible: no
Issue: #422
2026-09-02 22:38:51 +03:00
Codex 653b9a7632 docs: refresh the capture fingerprint after the clip extraction (#422)
User-Visible: no
Issue: #422
2026-09-02 22:14:41 +03:00
Codex d59ceee905 feat(gates): measure capture drift between runs and anchor reachability (#422)
User-Visible: no
Issue: #422
2026-09-02 22:10:16 +03:00
Codex 09c410000a fix: pin the compositor so a frame depends only on the commit (#424)
User-Visible: no
Issue: #424
2026-09-02 21:59:43 +03:00
Sergey Matyunin 2038ab91b9 fix: harden support feedback pipeline
Issue: #423
User-Visible: yes
2026-09-02 21:25:30 +03:00
Sergey Matyunin f4b425f3c0 test: prove three defensive contracts fail red
Issue: #421
User-Visible: no
2026-09-02 20:13:58 +03:00
Sergey Matyunin 489e289f79 fix: guard Area snapshot cleanup
Issue: #419
User-Visible: yes
2026-09-02 19:18:28 +03:00
Sergey Matyunin 2b978d63ef Fix support preview and retry races
Issue: #418
User-Visible: yes
2026-09-02 17:10:05 +03:00
Sergey Matyunin e46220f3eb Fix confirmation guards for unrenderable branches
Issue: #417
User-Visible: yes
2026-09-02 16:25:24 +03:00
Matysh 6fb7bbb6ab ci: anchor the review material to content, not to history
#413 закрыл класс «SHA мёртв уже в момент публикации». Остаётся более частый:
SHA был жив, а умер потом — по корпусу таких объявлений 98 из 804, потому что
ветку задачи после ревью перебазируют, сквошат или удаляют.

SHA коммита — свойство истории, а история переписывается. Содержимое не
переписывается: git адресует деревья и блобы их хешем. На #403 спец-коммит
переехал из 83005c3c в 94502d3d, а блоб ТЗ у обоих один — 56a92e12; по нему
материал находится одной командой независимо от ребейза.

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

Блок машинный и помечен как машинный. Ревьюер его не заполняет: дисциплина
ручного переписывания SHA здесь уже подвела, и заменять её другой ручной
дисциплиной смысла нет.

Гейт #413 смягчён ровно там, где обязан: осиротевший SHA при живых якорях —
предупреждение, а не отказ. Ронять раунд, который воспроизводим, было бы той
же ошибкой в другую сторону. Отказ остаётся, когда не работает ни один
объявленный способ найти материал.

Проверено на настоящем осиротевшем случае: блок, собранный для 94502d3d,
находит и дерево, и блоб ТЗ; тот же документ с якорями даёт предупреждение
вместо отказа, без якорей — отказ.

Issue: #416
User-Visible: no
2026-09-02 08:01:15 +03:00
Matysh 206732e9f5 ci: refuse a review round that cites an unreachable SHA
SPEC-REVIEW-403-r2 объявил материал раунда на `HEAD = 83005c3c`, и тот же SHA
независимо назвал автор ТЗ в комментарии issue. Разбор подтвердил находку и
уточнил её: коммит существовал, но к моменту публикации был осиротевшим.
Ветку перебазировали за пятнадцать минут ДО публикации документа — спец-коммит
переехал в 94502d3d с тем же сообщением и тем же содержимым (блоб ТЗ у обоих
56a92e12). Через раунд команда `git diff 83005c3c..HEAD` из §2.10 буквально не
работала, и r3 восстанавливал коммит по содержимому диффа руками.

Гейт судит только объявление материала в шапке документа, а не каждое
шестнадцатеричное слово: в прозе SHA упоминаются исторически, и обещания
воспроизводимости на них нет. Границы кандидата подобраны по корпусу — 7–40
знаков, хотя бы одна буква, не после `#`, не внутри длинного хеша; это
отсекает sha256, цвета и номера прогонов.

Достижимость считается от refs/remotes/origin, а не от локальных ссылок.
Разница не теоретическая: осиротевший 83005c3c до сих пор достижим в клоне
автора из необновлённой локальной ветки — локальная проверка сказала бы «всё в
порядке» ровно на той машине, где ошибку и совершили.

Шаг стоит ПОСЛЕ публикации и ДО перестановки метки. Артефакт ревью терялся
здесь трижды (#171, #220), и «вердикт без документа» дороже мёртвой ссылки:
документ сначала спасается, потом судится. Инвариант «метка не сменилась =
прогон упал» при этом сохраняется.

Проверено на настоящих документах: SPEC-REVIEW-403-r2 отказ, CODE-REVIEW-390-r1
проходит, документ без объявления материала не судится.

Issue: #413
User-Visible: no
2026-09-02 07:45:39 +03:00
Sergey Matyuninandclaude[bot] 1a953718a7 test: cover help and feedback browser flows (#43)
Issue: #43
User-Visible: no
2026-09-02 00:05:37 +00:00
Codexandclaude[bot] c12ecad3e3 docs: require the trusted-proxy switch for the rate-limit source (#43)
User-Visible: no
Issue: #43
2026-09-02 00:05:36 +00:00
Codexandclaude[bot] 3bbe555ad8 docs: pin the rate-limit source and the webhook recipe (#43)
User-Visible: no
Issue: #43
2026-09-02 00:05:36 +00:00
Codexandclaude[bot] ca86e79501 feat(relay): deliver through the maintainer's Home Assistant webhook (#43)
User-Visible: no
Issue: #43
2026-09-02 00:05:36 +00:00
Codexandclaude[bot] 736a6c143e fix(relay): pin the rate-limit source to the proxy-supplied address (#43)
User-Visible: no
Issue: #43
2026-09-02 00:05:36 +00:00
Codexandclaude[bot] f36ded7fc2 feat(relay): receive support reports on the project stand (#43)
User-Visible: no
Issue: #43
2026-09-02 00:05:36 +00:00
Sergey Matyunin b4809a12ff fix: await draft deletion through the card facade (#405)
Issue: #405
User-Visible: no
2026-09-01 22:23:57 +03:00
Sergey Matyuninandclaude[bot] 372d838ec2 fix: close beta 2 polish gaps
Issue: #406
User-Visible: yes
2026-09-01 16:32:25 +00:00
Matysh 2903374b72 fix: the exception guard reads its counter after delivery, not before
Гард «uncaught exception внутри карточки» жил в demo/serve.mjs с 2026-07-27 и
не срабатывал ни разу в самом частом случае. Счётчик читался синхронно, а
Playwright доставляет pageerror асинхронно по CDP: если исключение возникло
после последнего обращения смока к странице, счётчик к моменту проверки
нулевой, а browser.close() уносит недоставленное событие. В логе это видно
дословно — EXC печатается после результата и до OK.

finish() теперь делает round-trip по открытым страницам перед чтением
счётчика. Страницы регистрируются там, где создаются: ссылок на них у
finish(browser, out) нет, а менять сигнатуру нельзя — так её зовут 205
смоков.

Medium-1 жёлтого ревью ТЗ закрыт расширением, а не оговоркой. Страницы,
созданные смоком после launch(), регистрация в launchInternal не покрывает:
smoke_zoom_flash печатал своё EXC2 мимо счётчика, три страницы
smoke_svg_sandbox не имели слушателя вовсе. Документировать слепую зону в
задаче, которая существует ради устранения слепой зоны, значит закрыть issue,
оставив дефект. Наружу отдана одна функция watchPage(page): подписка и
регистрация неразделимы, иначе появится страница, чьи исключения считаются, а
доставки не ждёт никто.

Разрыв оказался шире, чем в ревью: проверка по всему набору нашла ещё два
файла со своей подпиской — smoke_cold_view_toggle и smoke_cold_view_vacuum.
Они не слепая зона, их страница приходит из launchColdView и уже
зарегистрирована, а свой счётчик они превращают в отдельное утверждение.
Поэтому инвариант сформулирован как «ни одна страница не создаётся мимо
гарда» и закреплён по всему набору, а не по двум названным файлам.

reportPageErrors() из #407 стал асинхронным: второй читатель счётчика обязан
ждать доставку так же, как finish(). Пять смоков получили await.

Фикстура smoke_danger_confirmation приведена к объявленному типу: без binding
и bindingMode _bindingHasHaPage падал на undefined.split(':') — два
исключения, которых гард не видел. Дефекта поведения нет, все 15 мест в src/,
создающих диалог, binding пишут; врала фикстура.

Два отступления от ТЗ, каждое по измеренной причине. Пробы лежат в
demo/guard/, а не demo/fixtures/: последний входит в корпус sourceFingerprint,
и каждый файл там объявил бы устаревшими бандл, скриншот-индекс и
golden-индекс — пробы же не касаются ни одного пикселя. Поведение
доказывается в job со браузером, а не в npm test: job «Фронтенд» браузеры не
ставит, и тест молча скипался бы — тот самый тихий успех, против которого вся
задача.

Issue: #404
User-Visible: no
2026-09-01 19:08:07 +03:00
Sergey Matyuninandclaude[bot] 355b0516c7 fix: preserve area relocation state
Issue: #403
User-Visible: yes
2026-09-01 15:08:22 +00:00
Matysh 8119c523fa fix: the screenshot witness floor comes from the set, not from survivors
Тот же дефект, что #408 у golden, и в моём же коде из #401. Порог свидетелей
считался от числа кадров, уцелевших на диске: rm docs/images/*.png плюс
объявить все десять через --expect-change — уцелевших ноль, порог ноль,
причины никто не спрашивает, а в манифесте остаётся {"witnesses":0,"floor":0}
без единого слова о произошедшем. Щель была описана в комментарии над самой
функцией и оставлена открытой.

Порог теперь от набора сценариев, свидетели — из тех, с кем есть что
сравнить. Разделение принципиальное: удаление кадров лишает доказательств, но
не должно снижать планку. Первичная съёмка идёт через --no-witnesses
--reason, как теперь и в golden.

Отдельного параметра размера, как в golden, здесь не нужно, и это не
небрежность: `ids` и есть набор — docs-accept.mjs передаёт DOC_SCREENSHOTS, а
verifyDocsCandidate до того отказывает, если набор сцен в кандидате не совпал
с ожидаемым. Пустой ids — отказ, а не ноль.

Попутно Low из #405: повторная приёмка неизменённого набора затирала
acceptance.declared пустым списком. След приёмки отвечает на вопрос «когда
эти пиксели приняли и что тогда объявляли», а обновление отпечатка пикселей
не меняет — значит и стирать ответ не должно. Прежний след сохраняется и
помечается lastWriteWasFingerprintOnly.

Заодно отказ по свидетелям теперь возвращает сами числа: вызывающий печатает
свой вердикт, и сочинять их заново ему не из чего.

Issue: #409
User-Visible: no
2026-09-01 17:54:48 +03:00
Matysh cd0ecf4db2 fix: the witness floor comes from the matrix, not from surviving baselines
Порог свидетелей считался от числа сцен со статусом не missing-baseline, то
есть от эталонов, уцелевших на диске. Обход в одну команду: git rm
demo/golden/baselines/*.png — все сцены становятся missing-baseline, порог
обращается в ноль, свидетелей никто не требует, и чужая съёмка всей матрицы
принимается без единого следа причины в манифесте. Отказ
goldenAcceptanceRefusal этого не ловит: он требует объявить каждую новую сцену
в --expect-new, а объявить их все ничто не мешает.

Прежняя редакция объясняла ноль тем, что первичная съёмка свидетелей иметь не
может. Верно по факту и неверно по выводу: невозможность доказать среду не
отменяет требования, она требует сказать это вслух. Теперь и первичная съёмка
идёт через --no-witnesses --reason, а причина уезжает в манифест эталонов.

Размер матрицы стал обязательным параметром, а не выводится из отчёта: у
частичного прогона (run.mjs --only=…) results короче матрицы, и порог просел
бы молча — тот же дефект в другой одежде. Отсутствие параметра — отказ.

Формула не менялась: она общая с docsWitnessFloor и обязана такой остаться.
Менялся источник счётчика. На обычной приёмке ничего не меняется: при 143
эталонах порог был и остался 10.

Issue: #408
User-Visible: no
2026-09-01 17:25:54 +03:00
Codex 00b6f41233 fix: the danger confirmation lives outside render()'s branches (#402)
hp-confirm sat at the end of a chain of early returns, so in onboarding
(«no spaces yet»), in the fixed-floor states and without a space it did
not exist at all: the trash button next to a saved plan was dead and the
promise hung forever, because the decision event had no source in the
DOM. An already open dialog vanished the moment the card slipped into
one of those branches, leaving the caller waiting for a resolution that
could never come. Before #32 a browser confirm() worked there.

render() is now a wrapper: it takes the body — the old chain, unchanged,
as _renderBody — and renders the confirmation beside it. That fixes the
class rather than the instance: a branch added later cannot lose the
dialog again. noChange and nothing are passed through untouched, since
neither may be wrapped in a template; in those states _confirmDanger
refuses the request outright instead of leaving it pending, which is the
honest answer while the card is not on screen and the user has pressed
nothing.

_tapConfirm and _vacCalConfirm deliberately stay where they are. They
share the same final branch, but they have no promise (a synchronous
exec, a dialog closed by hp-close), so the defect cannot occur there,
and their entry points require a drawn plan.

Proven by a separate smoke rather than an addition to
smoke_danger_confirmation: that file keeps deliberately incomplete
dialog fixtures open, and the extra re-renders this change needs make
them throw. The new smoke runs under touch emulation, because
TOUCH-SUPPORT § Safety floor forbids bypassing a destructive
confirmation and the broken branch pierced that floor on finger as
surely as on mouse. Reverting the wrapper reddens it.

User-Visible: yes
Issue: #402
2026-08-31 16:47:05 +03:00
Matysh 633cb20e59 fix: follow Home Assistant Area marker moves
Issue: #126
User-Visible: yes
2026-08-31 09:45:53 +03:00
Matysh 5c8cb58e1f ci: prove the screenshot environment instead of trusting the place
Правило приёмки скриншотов было про место: снимать только в CI. Обоснование
измерено — съёмка в другом окружении переписывает файлы без содержательных
изменений, в #231 два из девяти на 7–8 байт, набор с беты все девять. Но
держалось правило на комментарии, а не на механизме: кандидат проверялся на
самосогласованность и принимался целиком, ни разу не сравниваясь с тем, что
лежит в репозитории.

Цена видна на #390: правка типов, которая физически не может сдвинуть
пиксель, потребовала прогона workflow, а затем правки одиннадцати полей
манифеста руками.

Теперь правило про доказательство, и оно то же, что у golden с #334: среда
доказана, если каждый кадр, который менять не собирались, совпал с
закоммиченным байт-в-байт. Расхождение растеризации спрятать нельзя — оно
задевает все кадры с текстом сразу. Снимать можно где угодно, включая WSL;
принять получится только оттуда, где кадры воспроизводятся, и перестанет
получаться в тот день, когда обновятся шрифты.

Остальное следует из того же правила: намерение объявляется
--expect-change, необъявленное расхождение останавливает приёмку,
объявленное без расхождения — тоже (ложная декларация обесценивает список),
заменяются ровно объявленные файлы, а тотальная перерисовка требует
--no-witnesses --reason, и причина уезжает в манифест.

Частый случай закрылся сам: ничего не объявлено, все кадры совпали —
принимается один манифест, руками ничего писать не надо.

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

Issue: #401
User-Visible: no
2026-08-31 09:40:52 +03:00