Commit Graph
100 Commits
Author SHA1 Message Date
Codex 542650234d Девять свидетелей снова доказывают, а не падают на компиляции
Первый ночной прогон с честным критерием (#550) дал 726 из 735: девять мутантов
не доходили до заявленного теста. Причины оказались тремя, а не одной, как я
ожидал по трём случаям из #566:

- статически мёртвая ветка (`if (false && …)`, `if (true) throw`) — TypeScript
  теряет сужение, сделанное выше: `united`, `_relation`, `previous`, остаток
  функции после безусловного throw. Лечится ложью, ложной в рантайме, но не
  статически;
- пустой литерал `[]` выводится как `never[]`, и падают уже вызывающие. Лечится
  сохранением типа при потере содержимого (`slice(0, 0)`, `as string[]`);
- несовпадение типов в самой замене: `canonicalizeNumber` отдаёт `unknown` там,
  где нужен `T`; подмена резолвера литералом сужала union, и ветка
  `resolution.reason` становилась `never`; бракованная метка `case` ломала
  `switch` по union.

Смысл каждого мутанта сохранён: все девять прогнаны по `--id=` и краснят свой
заявленный тест.

Issue: #569
User-Visible: no
2026-09-14 09:51:26 +03:00
CodexandCodex 264fba0902 Отказ подготовки называет виновника
Свидетель, который не готовится к прогону, краснил гейт той задачи, чей дифф его
выбрал, даже когда причина лежала в чужом коммите: на #566 это стоило двух
кругов. Теперь при исходе `setup-failure` в дифф-режиме прогоняется ОПРЕДЕЛЕНИЕ
БАЗЫ на дереве базы, и раннер говорит прямо — отказ предсуществующий или внесён
этим диффом. Сравнение подобного с подобным здесь принципиально: с определением
из головы собственная сломанная правка реестра выглядела бы предсуществующей.

Предсуществующий гейт задачи не красит (решение владельца 14.09) и не теряется:
он назван машиночитаемой строкой и обязан покраснеть в ночном полном прогоне, у
которого есть адресат (#472). Мутанта, которого в базе нет, оправдывать нечем по
построению.

Композиция чтения реестра базы и запуска мутанта живёт отдельным модулем:
границу «запуск не зависит от отбора» держит тест #558, и CLI обязан остаться
тонким.

Issue: #568
User-Visible: no
2026-09-14 08:55:17 +03:00
CodexandCodex 733cd5b86b Три состояния владельца позиции вместо двух
Ревью r1, Medium: наблюдение о записи на удалённом пространстве говорило
«владелец жив» и про ключ, который ни во что не резолвится. Это заявление о
доказанности там, где её нет: продукт в этом состоянии ничего не удаляет не
потому, что владелец жив, а потому, что не знает — `space-reference-repair`
ведёт `live`, `absent` и `unverified`, три состояния, а не два.

Проверка ведёт те же три. Нерезолвящийся ключ получает наблюдение
`unknown_owner` с той же формулировкой, что и в живом пространстве: «владелец не
найден в конфигурации (возможно устройство HA)». Тест сверяет теперь и ТЕКСТ
причины — вид наблюдения без текста эту асимметрию пропускал, ровно так она и
проехала.

Issue: #566
User-Visible: no
2026-09-14 02:28:50 +03:00
Codex f724cca61a Третий мутант того же покроя снова компилируется
`optimizer-micro-interval-cleanup-disabled` вставлял безусловный `return` в
начало функции. Остаток функции становился недостижимым, и там TypeScript терял
сужение `profile` — компиляция падала до теста. Возврат сделан под
рантайм-условием: смысл мутанта («очистка отключена») тот же, остаток кода для
проверки типов остаётся достижимым.

Issue: #566
User-Visible: no
2026-09-14 01:56:25 +03:00
Codex 7ecdafef94 Два мутанта реестра снова компилируются
`inner-span-reads-whole-edge-thickness` и `safe-resize-legacy-midpoint-fail-open`
падали на `npx tsc -p tsconfig.test.json` до запуска заявленного теста и потому
не проверяли ничего. Причина одна: патч делал ветку статически мёртвой
(`if (false)`, `false &&`), а в мёртвой ветке TypeScript теряет сужение типов,
сделанное выше — `profile` снова `| null`, `direct` снова `| undefined`.

Условия заменены на ложные в рантайме, но не статически. Смысл мутантов тот же,
и оба теперь ловятся заявленными тестами. Обнаружено прогоном на этой ветке:
правка реестра затягивает в отбор мутантов чужие файлы, и красным стал гейт
задачи, к которой эти записи отношения не имеют. Провал такого мутанта
незаметен, пока его не выберет дифф — это отдельный пробел, #568.

Issue: #566
User-Visible: no
2026-09-14 01:40:23 +03:00
Codex dedcedc8cc Позиция на удалённом пространстве судится по владельцу
Инварианты объявляли нарушением ЛЮБУЮ запись layout, чьё пространство удалено.
Продукт так не считает: `space-reference-repair` удаляет такую запись только
когда может доказать, что владелец тоже исчез, и сознательно хранит её, когда
владелец жив или доказательств нет — удаление уносит расстановку пользователя.
Конфиг сразу после Optimize законно содержит такие записи, и проверка называла
нарушением штатное состояние.

Теперь правило то же, что у продукта: нарушение — только когда владельца нет по
самой конфигурации (комната, область или снятый маркер). Остальное —
наблюдение, как у ветки `unknown_owner` рядом.

Issue: #566
User-Visible: no
2026-09-14 01:27:58 +03:00
Codex 03983946c5 Источники радара сравниваются по смыслу, а не по тексту
Гард `keepTwoPoint` решал, сохранять ли двухточечную калибровку, сравнением
`JSON.stringify`, то есть текста. Порядок ключей `sources` при этом меняет сам
билдер: `slots`/`ranges`/`zones`/`occupancy_entity`/`count_entity` он удаляет и
дописывает заново, а `availability_entity` остаётся на месте и уезжает в начало.
Конфиг, только что записанный этим же билдером, при следующем открытии
сравнивался неравным — и радар с `availability_entity` терял `refs` и `rms_cm`,
получая `method: manual`, при первом же обычном сохранении.

Сравнение стало каноничным по порядку ключей объектов и осталось чувствительным
к порядку элементов массивов: позиция слота — это его `target_N`. Проекция не
затронута — она считается от `mount`, `cell_cm` и `mirror`.

Issue: #567
User-Visible: no
2026-09-13 23:23:30 +03:00
Codex db7eef5306 Корпус геометрий, household-сценарии и аудит доступности View
Полевая проверка основного View по #560: шесть обезличенных планов корпуса и
цепочка import → Optimize → Optimize → Resize → сохранение с численными
оракулами, семь household-путей с независимыми оракулами в двух ширинах
карточки, аудит доступности с измеренными числами. Продуктовых правок нет:
находки заведены отдельно (#564, #565, #566).

Issue: #560
User-Visible: no
2026-09-13 21:05:28 +03:00
Codex a33cf8dacb Зеркало process.yml и mutation-gate.yml из dev
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (blend) (push) Failing after 3m41s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (interaction) (push) Failing after 3m38s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (plan-snap) (push) Failing after 2m17s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (overlay) (push) Failing after 2m21s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-default) (push) Failing after 1m6s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-glow) (push) Failing after 1m4s
Проверка (CI) / Классификация изменённых файлов (push) Successful in 31s
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 1m36s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric) (push) Failing after 14m58s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 37s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (large-house) (push) Failing after 18m42s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 9m39s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric-stage3) (push) Failing after 1m52s
Оба файла исполняются из ветки по умолчанию: process.yml — по событию
`issues`, mutation-gate.yml — по расписанию. Приводятся к версии dev после
#556 (SHA-пины сторонних Actions, права по job, job-scoped токен у стадии
модели). Гейт workflow_sync в Validate сверяет оба.

Issue: #556
User-Visible: no
2026-09-13 20:18:09 +03:00
CodexandCodex 2642fdb446 Мутант: запись в репозиторий у недоверенной стадии
Issue: #556
User-Visible: no
2026-09-13 19:59:59 +03:00
CodexandCodex e0098c8d00 Права модели в ревью держит job-scoped токен, а не App-обмен
Объявленные `permissions:` у `model_review` не были потолком: без переданного
`github_token` claude-code-action меняет OIDC на собственный App-токен, дефолт
которого — contents/issues/pull_requests: write, и `ghs_…` от claude[bot]
оказывался прямо в окружении Bash-инструмента модели. Ревью r1 показало это
живым доказательством в собственной же сессии.

Теперь шагу Review передан ambient `secrets.GITHUB_TOKEN`: обмена не происходит,
`id-token` не нужен, список прав становится настоящим. У модели остаётся ровно
одно право записи — `issues: write` под комментарий вердикта (§7.2) и issue по
§12; записи в репозиторий у неё больше нет.

Issue: #556
User-Visible: no
2026-09-13 19:59:59 +03:00
Codex 77a3d2607c Пересборка бандла и отпечатка доков после ребейза на dev
Ребейз на `dev` (#530) обнулил обе производные копии: хэши чанков считаются от
содержимого, а отпечаток скриншотов — от `src/**`. Пересобрано, отпечаток
принят с `--identical`: все 11 кадров попиксельно совпали.

Issue: #531
User-Visible: no
2026-09-11 14:25:35 +03:00
Codex 9d1ba1103c Ревью r1: отпечаток доков и изометрический кадр в юните
Medium: `check-docs` красный — любая правка `src/**` делает отпечаток
скриншотов стухшим (#479), а `docs`-job на обычном push идёт в режиме `warn`.
Все 11 кадров попиксельно совпали с закоммиченными, принят только отпечаток
исходников.

Наблюдение без правки закрыто заодно: во всех прежних кадрах свидетелей
`floor === view`, то есть изометрия, ради которой камера и пол проецируются
раздельно, не была тронута ничем. Новый юнит разводит виды и по сдвигу, и по
размеру.

Issue: #531
User-Visible: no
2026-09-11 14:23:53 +03:00
Codex 58e31668c9 Панорама двигает сцену трансформом, viewBox пишется по бюджету
Перезапись `viewBox` — это не сдвиг, а инвалидация растеризации всей сцены.
Кадр жеста делал её каждый раз: в профиле владельца (Firefox 155, 144 Гц) кадр
доезжал до экрана 200 мс, а драйвер пропускал 124–144 тика в секунду с пометкой
«ждём краску».

Теперь `paintLiveViewport` держит якорь — кадр, чей `viewBox` записан в DOM, и
момент записи. Кадр жеста двигает узлы сцены тем же проективным преобразованием,
которым уже двигались HTML-слои, а `viewBox` переписывается по бюджету: 100 мс
либо 15 % сдвига/масштаба. Ни атрибут, ни стиль не пишутся, если строка не
изменилась.

Issue: #531
User-Visible: yes
2026-09-11 14:23:48 +03:00
Codex 0cd5145df0 ci: считать раунды ревью по опубликованным документам
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (blend) (push) Failing after 1m56s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric) (push) Failing after 1m45s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (large-house) (push) Failing after 2m0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (overlay) (push) Failing after 1m57s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 51s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (plan-snap) (push) Failing after 1m53s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-default) (push) Failing after 1m48s
Проверка (CI) / Классификация изменённых файлов (push) Successful in 30s
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 44s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-glow) (push) Failing after 1m54s
Зеркало process.yml из dev: конвейер исполняет версию из ветки по умолчанию.

User-Visible: no
Issue: #454
2026-09-04 20:45:58 +03:00
Codex 2a883217c1 chore: пересобрать бандл под новый отпечаток исходников
Отпечаток исходников включает package.json, а 28a4cb5e (#455) его изменил
без bundle:sync. dev красный с 16:27 UTC: CI пересобирает dist и сравнивает с
коммиченной копией — расходятся ровно строкой __HOUSEPLAN_BUILD_FINGERPRINT__
и производными от неё именами чанков. Код чанков побайтово тот же, проверено
diff-ом. Ветка #454 унаследовала красноту ребейзом.

User-Visible: no
Issue: #454
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33895243347
2026-09-04 20:26:24 +03:00
Codex d5e114524c fix: комментарий засчитывается вердиктом только по документу своей задачи
User-Visible: no
Issue: #454
2026-09-04 20:18:59 +03:00
Codexandclaude[bot] 4b443d8cf5 fix: не считать описание чужого раунда объявлением вердикта
User-Visible: no
Issue: #454
2026-09-04 16:58:31 +00:00
Codexandclaude[bot] dc76c5b2b8 test: контракт на вызов счётчика раундов из guard
User-Visible: no
Issue: #454
2026-09-04 16:58:31 +00:00
Codexandclaude[bot] 8d8263202f ci: считать раунды ревью по опубликованным документам
User-Visible: no
Issue: #454
2026-09-04 16:58:31 +00:00
Codexandclaude[bot] f70a973834 docs: spec #454 — уточнить AC2 по замечанию ревью
User-Visible: no
Issue: #454
2026-09-04 16:58:31 +00:00
Codexandclaude[bot] 5016aa01cc docs: specify review round counting from published artifacts
User-Visible: no
Issue: #454
2026-09-04 16:58:31 +00:00
CodexandSergey Matyunin 85a064430b docs: name the three editors as the guide does
User-Visible: no
Issue: #449
2026-09-04 18:18:14 +03:00
CodexandSergey Matyunin 1a7488d695 docs: state the touch editor status in the double fit-all spec
User-Visible: no
Issue: #449
2026-09-04 18:18:14 +03:00
Codex 411a0a20a9 docs: call the fit-all button by its real name in the Russian guide
Проверка (CI) / Классификация изменённых файлов (push) Successful in 23s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 42s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 49s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 19s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 17s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 7m1s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 9m36s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
User-Visible: no
Issue: #452
2026-09-04 17:54:42 +03:00
Codex 79e922203a chore: name the HACS listing after what it is
User-Visible: no
Issue: #444
2026-09-03 22:11:33 +03: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 4b8cf6e04d fix: drop the untyped hass parameter from the editor host port
User-Visible: no
Issue: #162
2026-09-03 19:49:10 +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 c9c22916be docs: document vacuum map-to-space routing
User-Visible: no
Issue: #162
2026-09-03 19:25:17 +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 9899d628ac feat: drop cross-space vacuum map routes from a single-space export
User-Visible: no
Issue: #162
2026-09-03 19:18:13 +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
Codex 4c56b389ca docs: define legacy run adoption rule for vacuum map routes
User-Visible: no
Issue: #162
2026-09-03 18:28:45 +03:00
Codex 086b3682e9 docs: rebase vacuum map space routing spec onto current dev
User-Visible: no
Issue: #162
2026-09-03 18:16:17 +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
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 84b5f63cbb docs: address the core budget spec review (#425)
User-Visible: no
Issue: #425
2026-09-03 00:19:07 +03:00
Codex 4eae3398a6 docs: specify the core file budget gate (#425)
User-Visible: no
Issue: #425
2026-09-03 00:09:08 +03:00
Codex a1e1748b2d docs: record the capture fingerprint of the final tree (#422)
User-Visible: no
Issue: #422
2026-09-02 22:38:51 +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 70805c9c6b docs: specify the capture and anchor gates (#422)
User-Visible: no
Issue: #422
2026-09-02 22:10:00 +03:00
Codex 921d0f4c0d docs: accept the screenshots taken with the pinned compositor (#424)
User-Visible: no
Issue: #424
2026-09-02 22:00:24 +03:00
Codex 8de9ea008a docs: restore the capture determinism spec lost in the rebase (#424)
User-Visible: no
Issue: #424
2026-09-02 21:59:43 +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
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] 6b3703f0b5 docs: switch the support sink to a maintainer channel (#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
CodexandSergey Matyunin 2c6d937d46 docs: specify draft delete awaiting and witness floor (#405)
User-Visible: no
Issue: #405
2026-09-01 22:12:13 +03:00
Codexandclaude[bot] 15ff625964 docs: specify the beta.2 polish batch (#406)
User-Visible: no
Issue: #406
2026-09-01 16:32:25 +00:00
Codexandclaude[bot] 8e7c927264 docs: specify area relocation safety (#403)
User-Visible: no
Issue: #403
2026-09-01 15:08:21 +00:00
Codex ccd870ddf7 build: refresh bundle trees and the doc capture for #402
User-Visible: no
Issue: #402
2026-08-31 16:47:53 +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
Codex 11959e4c85 docs: #402 spec revision 2 per SPEC-REVIEW-402-r1
User-Visible: no
Issue: #402
2026-08-31 16:22:50 +03:00
Codex d94db87ee7 docs: specify the branch-independent danger confirmation (#402)
User-Visible: no
Issue: #402
2026-08-31 16:08:52 +03:00
Codex bd6722ccd2 build: refresh bundle trees and the doc capture for #400
User-Visible: no
Issue: #400
2026-08-31 08:14:55 +03:00
Codex d153b20a1d fix: make the handle paint order a named decision and mutate it (#400)
CODE-REVIEW-400-r1 Medium: the registered mutant edited a comment, not
the order — it could not reproduce the regression AC1 exists to catch.
That is the same defect class this issue is fixing elsewhere, in my own
guard.

The order is now HANDLE_PAINT_ORDER, a named constant, because it IS the
hit priority rather than an accident of where the blocks sit in the
template. The mutant flips that constant, so it reproduces exactly the
behaviour the audit found.

Also: smoke_furniture picked the SE corner as handles[3], an index that
silently depended on the old paint order — CI shard 3 went red on four
checks. It now selects by role (corner handles, third of four), which is
what the test actually means.

User-Visible: no
Issue: #400
2026-08-31 08:14:55 +03:00
Codex 73ecfbd1d7 build: refresh bundle trees and the doc capture for #400
User-Visible: no
Issue: #400
2026-08-31 07:57:02 +03:00
Codex fe3b85c06b fix: the corner handle wins on small furniture, guides exclude the real drag (#400)
(1) Corner and edge handles carry the same hit radius (1.8 % of the
view), so on furniture narrower than 4·hr — a 40 cm cabinet — the two
circles overlap and whichever is painted last takes the tap. Edges were
painted last. Corners are now, because a side handle scales one axis
while a corner scales both, and the object is small exactly when
proportional resize matters most. The visible beads are unchanged.

The audit called this 'proportional resize becomes unavailable'; the
measurement says otherwise and the spec records the correction: the
corner centre lies outside the edge circle, so the corner was reachable
— its area was halved, not lost. A polish, not a bug, and worth fixing
because it is one line of ordering.

(2) Alignment guides in the devices mode excluded the dragged marker by
_drag, which has been null there since #74 moved device dragging into
_deviceDrag. So the marker being moved was among its own candidates.
Nothing looked wrong because a point always matches itself within
tolerance — the guide was drawn from the marker to itself, visually
identical to an honest one, and the smoke asserted only guides() >= 1.
The smoke now compares the candidate lists with and without the drag and
demands exactly one removed entry.

(3) The 38 settings-help strings stay in the initial chunk, and that is
now a recorded decision rather than an oversight: measured 2 654 B gzip,
0.9 % of the ceiling, against splitting a synchronous dictionary in two,
a second request on first hint, and a second source for the key type
derived from en.json (#391). docs/ARCHITECTURE.md says so, with the
number that would justify revisiting it.

Both mutants run by hand: reverting the paint order reddens the 40 cm
probe while the 160 cm one stays green; restoring _drag reddens the
guides smoke.

User-Visible: yes
Issue: #400
2026-08-31 07:56:27 +03:00
Codex 60c211c82a docs: specify the beta polish batch (#400)
User-Visible: no
Issue: #400
2026-08-31 07:36:44 +03:00
Codex 5c4d8cba9e test: prove AC5 by running the scanner, not the predicate (#399)
CODE-REVIEW-399-r1 High: the test named after AC5 called
installsPythonDeps on string literals and never executed the directory
walk it was supposed to protect. The reviewer showed what that costs:
restoring the old hardcoded pair of real names and dropping a third
workflow with unpinned installs into .github/workflows left all ten
checks green — the exact scenario AC5 describes went undetected.

The walk is now a function taking the directory, so the test can run it
for real: it builds a temporary directory with three files (a pinned
installer, a workflow that installs nothing, and a rogue one) and
asserts on what the scanner returns. Reverting the walk to a list of two
real names now reddens this test, verified by hand.

The mutant is sharpened accordingly: it substitutes the two-name list
instead of a one-name list. The old form failed on an unrelated
assertion about directory size, so it proved nothing about the scan
itself — while the two-name form is indistinguishable from correct code
on today's tree, which is what makes it the likely regression.

User-Visible: no
Issue: #399
2026-08-31 04:48:41 +03:00
Codex 98028a3093 ci: the backend gate now checks exactly what it promises (#399)
Three claims a green backend used to make, each slightly wider than the
truth — and #392 happened in exactly that gap.

The frontend pin said 20260826.1 next to homeassistant==2026.8.3, whose
package_constraints.txt requires 20260729.7: a combination that exists
in no HA release. It was never derived from anything — someone once
picked it. It is now taken from the constraints, the source is named in
the file, and a test holds both numbers together so raising HA cannot
quietly desync them.

ruff's include declared three trees while CI linted one. Narrowed the
declaration rather than widening CI: the debt in scripts/ and
tests_backend/ (56 findings, mostly E402/I001, plus 7 B023 and 5 B017)
has its own cost and its own decisions, and belongs in its own task, not
in a visibility fix. test/lint-scope.test.mjs now compares the two, so
they can only move together.

The pin check skipped a workflow when it found neither the package name
nor the requirements path — and both vanish together the moment someone
returns to Defaulting to user installation because normal site-packages is not writeable, i.e. the gate switched itself off
under precisely the change it exists to catch. It now walks the whole
.github/workflows directory and decides per file by a positive sign: if
a file installs python packages, it must install them from the pins
file. Verified by dropping a rogue workflow into the directory — it
reddens without touching any list.

Three mutants registered and each run by hand.

User-Visible: no
Issue: #399
2026-08-31 04:35:56 +03:00
Codex bb635298c1 docs: #399 spec revision 3 per SPEC-REVIEW-399-r2
User-Visible: no
Issue: #399
2026-08-31 04:26:14 +03:00
Codex 6d229c66f1 docs: #399 spec revision 2 per SPEC-REVIEW-399-r1
User-Visible: no
Issue: #399
2026-08-31 04:16:18 +03:00
Codex a1d7e0da55 docs: specify the backend gate honesty fixes (#399)
User-Visible: no
Issue: #399
2026-08-31 04:06:40 +03:00
Codex d9b6766362 test: the sys.modules guard now sees the write, not its spelling (#398)
The guard introduced by #394 matched the literal
sys.modules['custom_components... and therefore never looked at
pure_imports.py, which writes through a variable — the third instance of
the #389 class walked straight past the check created for it.

The guard now inspects the write itself and decides by the key: a whole
literal or the literal head of an f-string is safe unless it starts with
custom_components (that is how tests register homeassistant.*, hp_pure.*
and houseplan.trails); anything else — a variable, a concatenation,
setdefault/update — counts as a violation whenever the file is able to
name the package at all, i.e. contains a custom_components. literal. A
file that never names the package cannot poison it through a variable,
so restoring a snapshot stays legal.

load_pure now removes what it registered. Removing its own name is not
enough: relative imports pull neighbours in, so junction_limits leaves
wall_segment_model and coordinate_canonicalization behind. It removes
the whole custom_components difference accumulated during exec_module,
in a finally, and a repeated call still works.

pure_imports.py is a named exemption of the static guard precisely
because that guard cannot see the cleanup — so the cleanup is proven by
an executable test instead, and the mutant pure-imports-stops-cleaning
reddens it. Both mutants were run by hand.

User-Visible: no
Issue: #398
2026-08-31 03:56:01 +03:00
Codex 9fde407e4e docs: #398 spec revision 3 per SPEC-REVIEW-398-r2
User-Visible: no
Issue: #398
2026-08-31 03:40:36 +03:00
Codex 480d202fa5 docs: #398 spec revision 2 per SPEC-REVIEW-398-r1
User-Visible: no
Issue: #398
2026-08-31 03:30:10 +03:00
Codex 69dd09a7c9 docs: specify the sys.modules guard scope fix (#398)
User-Visible: no
Issue: #398
2026-08-31 03:17:55 +03:00
Codex f4c66bed3f test: prove AC3 on the reconnect path, with a probe that can fail (#397)
CODE-REVIEW-397-r1 Medium: AC3 named the second reader of the same
value — _loadFromServer via _adoptStructuralResponses — and nothing
exercised it. Adding the scenario turned out to be less mechanical than
it looked, and both obstacles are worth recording:

The reconnect path reads BOTH answers, and a differing config clears the
history for its own reason (configChanged). The fake server now echoes
the config the card already holds, so the check answers the layout
question it claims to answer.

The first version of the probe used a round 0.42, which canonicalization
leaves untouched — the check passed with and without the fix, i.e. for
the wrong reason. The probe now starts from a non-canonical position
(0.024999999999999942, which snaps to 0.025), and the scenario runs
immediately after the write, before any reload can align the two sides.

Verified by removing the fix: five checks red, now including
reconnectKeepsHistory and deleteEchoKeepsHistory. Both were green in the
weaker version — which is exactly what the reviewer's Medium was about.

User-Visible: no
Issue: #397
2026-08-31 03:03:59 +03:00
Codex 08d5612265 build: refresh bundle trees and the doc capture for #397
User-Visible: no
Issue: #397
2026-08-31 02:37:18 +03:00
Codex d87cc29804 fix: the card keeps the position it sent, so its echo is not foreign (#397)
B3: _persistDevicePlacement sent canonicalizePosition(...) to the server
and left the raw value in _layout, then recorded the fingerprint over
that raw snapshot. Canonicalization is not identity — it snaps to the
lattice — so 39 of 115 pixel-derived coordinates differ, and the next
_reloadLayoutOnly or _adoptStructuralResponses saw its own write as a
remote edit: history cleared, _layout replaced. The old _persistLayout
wrote the canonical value back; the per-device path introduced by #74
lost that line.

M1: the smoke that was supposed to prove AC10 assigned
serverLayout = structuredClone(c._layout) right before the reload —
erasing by hand the very divergence it existed to catch, so it could not
fail. The fake WS already stores what went over the wire; the
assignment is gone and the check now reddens on the unfixed code
(verified: three checks red without the fix, including this one).

Also proven, because the fix touches their neighbourhood: the echo of a
DELETE keeps the history (the branch removes a key rather than replacing
a value), and an in-flight write still wins the merge against a server
answer holding the old position.

One existing assertion was loosened deliberately: undo now restores a
position that may differ from the raw one by the lattice snap (<1e-9 of
the plan). That is the point of the fix — local and server agree — so the
equality is stated to that precision, with the snap size pinned
separately so a real drift would still fail.

User-Visible: yes
Issue: #397
2026-08-31 02:36:55 +03:00
Codex 6edcde012a docs: #397 spec revision 2 per SPEC-REVIEW-397-r1
User-Visible: no
Issue: #397
2026-08-31 02:21:20 +03:00
Codex 83692e7959 docs: #397 spec — correct the fingerprint line number
User-Visible: no
Issue: #397
2026-08-31 02:11:19 +03:00
Codex 9fcbb64633 docs: specify the device position echo fix (#397)
User-Visible: no
Issue: #397
2026-08-31 02:10:49 +03:00
Codex f3620f2b27 build: refresh bundle trees and the doc capture after the dev rebase (#396)
User-Visible: no
Issue: #396
2026-08-31 01:40:05 +03:00
Codex d6a2607692 build: refresh bundle trees and the doc capture for #396
User-Visible: no
Issue: #396
2026-08-31 01:37:12 +03:00
Codex 8f485c0b00 fix: the camera keeps the zoom you see and the point you hold (#396)
Three findings of the v1.70.0-beta.1 audit, all on the transition path
added by #82, all of the same shape — the new path did not inherit a
property the old one had.

B1: persisting the zoom moved into _settleCameraTransition only, and a
cancellation never settles. Touching the plan mid-flight — the literal
scenario of the issue — froze the shown frame and threw it away; before
which kind it is: the user one (_stagePointerDown) persists the frame
that stays on screen, the eleven structural ones keep writing nothing.
The distinction is now also written down in spec #82 §13, which had one
line for both.

B2: the anchor was read from the presented (lagging) frame while the
zoom accumulated from the target, so a six-notch trackpad series walked
the point under the cursor 17 px away — against §10's own promise. Both
now come from the same state. Spec §10 said to use the presented frame
and to keep the anchor within 0.5 px; those two are incompatible, and
the paragraph is corrected rather than left as a trap.

M2: the feather freeze keyed on the two gesture flags, which an
animated transition does not set, so every tween frame rebuilt the blur
region. It keys on 'the camera is still' now.

Guards: unit tests pin the anchor at 1e-9 across 8/16/33 ms series and
prove zoom accumulation is untouched; the smoke checks the shown zoom is
the stored one, that a structural cancellation stores nothing, and that
the anchor holds; three mutants (cancel-loses-zoom, anchor-from-
presented, feather-thaws) were run by hand and each reddens.

User-Visible: yes
Issue: #396
2026-08-31 01:36:59 +03:00
Codex 371f2e9869 docs: #396 spec revision 3 per SPEC-REVIEW-396-r2
User-Visible: no
Issue: #396
2026-08-31 01:36:53 +03:00
Codex 7cf22cb54f docs: #396 spec revision 2 per SPEC-REVIEW-396-r1
User-Visible: no
Issue: #396
2026-08-31 01:36:53 +03:00
Codex 54f9efb798 docs: specify the camera transition fixes (#396)
User-Visible: no
Issue: #396
2026-08-31 01:36:53 +03:00
Codex 7e27bc7922 fix: type-check against python 3.14, as the runner does (#42)
The new typing step failed on its first CI run — not on our code:
mypy parses the sources of the installed homeassistant, and after #392
that is HA 2026.8.3 on python 3.14, which uses 3.14-only syntax
(parenthesis-free `except`). With python_version = 3.13 mypy stopped at
a syntax error in someone else's file before reaching a single module of
ours, which is exactly the silent-nothing the gate exists to prevent —
except loud.

ruff keeps target-version py313 deliberately: it lints OUR sources and a
lower target only withholds newer-syntax suggestions, while mypy has to
read the dependency tree the runner actually installs.

User-Visible: no
Issue: #42
2026-08-30 22:05:33 +03:00
Codex dddbbe5522 ci: make the strict typing gate actually run (#42)
r6 Medium: AC4 was measurable only on a developer's machine — no
workflow invoked mypy, so a typing regression in any of the six
allowlist modules reached dev unnoticed while the issue claimed
measurable backend quality. Coverage and lint had continuous gates;
typing had a text comparison of a committed list.

The backend job now runs mypy right after ruff, from the same pinned
dependency file (mypy==2.3.1 — an unpinned checker would redden on code
that never changed). The step derives its module list from the
pyproject.toml strict allowlist instead of duplicating it, because a
drifted duplicate is a green step checking the wrong modules, and it
refuses an empty list rather than passing silently.

Guarded twice: a contract test pins all three facts (pinned checker,
a step that really invokes it, list read from pyproject) and the new
typing-gate-stops-running mutant reddens when the invocation is
neutered.

User-Visible: no
Issue: #42
2026-08-30 22:00:52 +03:00
Codex 972e708a71 build: refresh bundle trees and the doc capture after the #392 rebase (#42)
User-Visible: no
Issue: #42
2026-08-30 21:34:32 +03:00
Codex 569867487c fix: adopt the #392 dependency file as the single source (#42)
Rebase resolution: dev's #392 introduced tests_backend/requirements.txt
(python 3.14, phcc 0.13.357, homeassistant 2026.8.3) — exactly the
single-source-of-pins AC of this issue, so the duplicate
requirements_test.txt is dropped and both workflows keep installing from
the #392 file; ruff is added there for the lint step. The backend
reuse key no longer names the dead file (tests_backend/ as a root
already covers the new one); ARCHITECTURE.md points at the real path.

User-Visible: no
Issue: #42
2026-08-30 21:34:32 +03:00
Codex 9f50778df5 test: close the r4 mediums — structural tuple scan and reuse-key inputs (#42)
M1: the AC5 scanner parses the (field, code, message) literal tuple in
validation.py structurally instead of naming the two known codes — a
third tuple entry with an unregistered code now fails the registry test
(verified with an injected invalid_ghost_entity_mutant_probe), and a
tuple whose string count is not a multiple of three refuses instead of
guessing.
M2: the backend reuse key now includes its direct job inputs introduced
by this issue — scripts/backend-coverage-baseline.txt (the threshold the
comparison step reads), requirements_test.txt (the pip source) and
pyproject.toml (ruff/mypy config) — verified: the key changes when the
baseline changes and is restored byte-for-byte with the file.

User-Visible: no
Issue: #42
2026-08-30 21:34:32 +03:00
Codex 26d45cf405 build: refresh bundle trees and the doc capture after the dev rebase (#42)
Rebased onto dev with #82 (camera transitions), #74 (marker undo) and
the re-accepted goldens; bundle trees are rebuilt from the rebased
sources and the screenshot capture is retaken on this HEAD — manifest
AND all PNGs committed together.

User-Visible: no
Issue: #42
2026-08-30 21:34:32 +03:00
Codex fa1aa7a877 test: record the real backend coverage baseline (#42)
87.2% line coverage, taken from the first fully green backend CI job of
this branch (run 33321192996, coverage.xml line-rate 0.8716) — replaces
the 80.0 placeholder as promised before the verdict. The gate refuses
any run below baseline minus 0.1.

User-Visible: no
Issue: #42
2026-08-30 21:34:32 +03:00