Issue: #426 Issue: #427 Issue: #428 Issue: #431 Issue: #432 Issue: #434 User-Visible: no
17 KiB
ТЗ #431 — канонизация координат kind: image
Issue: #431
Статус документа: реализовано.
Источники контракта: ТЗ #51, ТЗ #224 и ТЗ #291.
Сценарий
Автор плана добавляет собственное изображение в редакторе Подложки, двигает, масштабирует или поворачивает его и сохраняет конфигурацию. Позже он повторно сохраняет план без геометрических изменений либо запускает «Оптимизировать планы».
Сейчас kind: image проходит frontend- и backend-барьеры записи вне списка
box-декора. Незаметные хвосты чисел с плавающей точкой сохраняются, поэтому
повторный no-op жест или оптимизация способен снова дать технический diff и
лишнюю ревизию.
После исправления изображение следует тому же контракту x/y/w/h/angle, что
rect, ellipse и furniture: следующая штатная запись или Optimize приводит
его координаты к канонической форме, а повтор операции является no-op.
Что человек увидит до и после
| Ситуация | Сейчас | После исправления |
|---|---|---|
| Сохранение изображения после drag/resize/rotate | План выглядит правильно, но в конфиг могут попасть шумовые float-координаты | Геометрия сохраняется канонически без визуального сдвига |
| Повторный no-op жест или Optimize | Может появиться ещё одно изменение/ревизия того же плана | Повторная операция не создаёт нового геометрического diff |
| Старый план с шумовыми координатами изображения | Шум остаётся после обычной записи | Нормализуется при следующей записи или явном Optimize |
Новых кнопок, сообщений, настроек и визуальных состояний нет.
Подтверждённая причина
DecorKindуже содержитimage, аDecorImageиспользует box-поляx/y/w/h/angle.src/coordinate-canonicalization.tsне включаетimageни в сбор значений дляlatticeCanonicalizationReport(), ни в реальную канонизацию config.custom_components/houseplan/coordinate_canonicalization.pyсодержит то же неполное зеркало.- Общая fixture перечисляет остальные четыре вида декора, но не
image, поэтому frontend и backend согласованно подтверждают один и тот же дефект.
Скоуп
- включить
imageв box-контракт frontend-сбора статистики и канонизации; - включить
imageв Python-зеркало канонизации; - сделать полный набор box-видов явным и проверяемым, чтобы обходы не содержали независимые цепочки сравнений;
- расширить shared fixture и оба runtime-набора тестов;
- добавить отрицательные доказательства, что выпадение вида из frontend либо backend краснит соответствующий тест;
- уточнить compatibility-документацию и добавить парную changelog-запись.
Не-скоуп
- изменение формата
DecorImage, asset API, загрузки, рендера или редактора; - новая миграция, повышение model/config/export version либо запись при чтении;
- изменение точности, порога lattice snap или формулы канонизации;
- рекурсивное округление неизвестных числовых полей;
- канонизация
opacity,width_cm,flip_h,flip_v,asset_idили других presentation/content-полей; - изменение поведения неизвестных и будущих
decor.kindбез отдельной классификации их геометрии.
Контракт поведения
1. Каталог геометрических классов декора
Frontend имеет один runtime-каталог box-видов:
rect · ellipse · furniture · image
Тип DecorKind обязан получать эти варианты из того же каталога, а не повторять
отдельный независимый список. Сбор статистики и фактическая канонизация используют
один predicate/каталог. Python объявляет точное зеркало box-набора.
Shared contract перечисляет ожидаемый box-набор. Frontend unit и backend test сверяют с ним свои runtime-каталоги exact-set сравнением и прогоняют одинаковую геометрию для каждого вида. Удаление одного вида из любого runtime-каталога либо рассинхронизация shared contract обязаны дать красный тест.
2. Поля и числовой контракт
Для каждого box-вида, включая image:
x,y,w,hпроходят существующую lattice-канонизацию относительно1/240с действующим порогом;angleпроходит существующую scalar-канонизацию до девяти десятичных знаков;- near-node значения учитываются в
latticeCanonicalizationReport()какcanonicalized, а намеренно off-grid значения — какfarбез snap; - повторная канонизация результата byte-equivalent и идемпотентна.
Все остальные поля image record сохраняются без изменений. Невалидные, нечисловые и non-finite значения продолжают обрабатываться действующей схемой; эта задача не меняет её политику валидации.
3. Пути записи и Optimize
Новые специальные writer-ветки не добавляются. Исправление действует через существующие общие барьеры:
- frontend config candidate до
houseplan/config/set; - backend config schema и storage helper;
- предварительную и финальную канонизацию Optimize;
- сбор отчёта Optimize о lattice-изменениях.
Существующие route guards #291 остаются без изменений: задача исправляет полноту данных внутри барьера, а не инвентарь writer-ов.
Совместимость и миграция
- Новых полей и миграции нет; model/config/export versions не меняются.
- Старые конфиги читаются byte-for-byte как раньше. Image geometry становится канонической только при следующей штатной записи или явном Optimize.
- Уже канонические изображения не меняются.
- Старые версии House Plan продолжают читать результат как обычный
kind: image; downgrade не требует обратной миграции.
Touch, accessibility, i18n и производительность
- Touch/View/kiosk и доступность не меняются: жесты и рендер остаются прежними.
- Новых строк и ключей i18n нет.
- Новых обходов config нет. Четырёхэлементный membership-check заменяет текущую цепочку сравнений внутри уже существующих обходов; бюджеты производительности не меняются.
- Security/privacy и сетевые поверхности не затрагиваются.
Затронутые файлы и модули
src/editors/decor/types.ts— единый runtime box-каталог и производные типы;src/coordinate-canonicalization.ts— использование каталога при сборе и канонизации;custom_components/houseplan/coordinate_canonicalization.py— Python-зеркало;test/fixtures/coordinate-canonicalization.json— shared набор и image row;test/coordinate-canonicalization.test.mjs— frontend completeness, idempotency и preservation;tests_backend/test_coordinate_canonicalization.py— backend mirror и schema;scripts/mutation-gate.mjs— постоянный свидетель backend-защиты;docs/CONFIG-COMPATIBILITY.md— явный image box-контракт;docs/CHANGELOG.md,docs/CHANGELOG.ru.md— release note;- этот файл и
docs/specs/README.md— трассируемость.
User Guide, i18n, screenshots/golden, smoke и performance fixtures не меняются.
Критерии приёмки
- AC1 — frontend image canonicalization (unit).
image.x/y/w/hполучают тот же lattice-результат, аimage.angleтот же scalar-результат, что эквивалентныйfurniture; immutable и in-place API дают одинаковый результат. - AC2 — отчёт и идемпотентность (unit). Image near-node/off-grid значения
правильно входят в
latticeCanonicalizationReport(); повторная канонизация/Optimize не создаёт изменений. - AC3 — backend mirror (backend). Python helper и
CONFIG_SCHEMAдают для image record точный shared expected result и сохраняют его идемпотентно. - AC4 — полнота box-набора (unit + backend + mutation). Runtime-каталоги TS
и Python exact-set равны shared contract
rect/ellipse/furniture/image, а каждый вид реально прогоняется черезx/y/w/h/angle. Удалениеimageиз TS краснит targeted unit; удаление из Python краснит targeted backend test через зарегистрированный mutation-gate witness. - AC5 — поля вне геометрии (unit + backend).
asset_id,opacity,flip_h,flip_vи неизвестное extension-поле переживают обе канонизации без изменений; неизвестныйdecor.kindне начинает округляться рекурсивно. - AC6 — совместимость (ревью кода). Формула, пороги, версии, схемы данных, writer inventory, UI и i18n не меняются; действующие тесты #224/#248/#291 остаются зелёными.
- AC7 — документация и release (docs gate + ревью кода). Compatibility doc
называет
imageсреди box-видов; оба changelog обновлены в том жеUser-Visible: yesimplementation commit. - AC8 — гейты (commands + Linux CI). Зелёные
npm run typecheck,npm test,npm run build, targeted backend test и оба отрицательных свидетеля. Полный HA harness каноничен в Linux CI.
План автотестов и таблица защитных свидетелей
- Добавить в shared fixture
boxKindsи representativeimageс шумом во всех пяти геометрических полях и отдельными полями, которые менять нельзя. - В frontend unit сравнить runtime-каталог с
boxKinds, затем параметризованно проверить каждый вид и image preservation. - В backend test сравнить Python-зеркало с тем же
boxKinds, проверить helper иCONFIG_SCHEMAна том же expected output. - Добавить Optimize/no-op проверку для image: первый прогон очищает измеримый шум, второй возвращает отсутствие persisted changes.
- Выполнить отрицательные прогоны до передачи на код-ревью:
| Защитный AC | Чем доказан | Чем обязан краснеть |
|---|---|---|
| AC4 frontend completeness | targeted coordinate-canonicalization unit |
удалить image из TS box-каталога → unit fail |
| AC4 backend completeness | targeted backend test | mutation-gate: удалить image из Python box-каталога → backend fail |
| AC5 allowlist boundary | unit + backend preservation cases | заменить box-ветку рекурсивным округлением/задеть extension field → preservation fail |
Release-артефакты
- В
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.md— парная запись: координаты пользовательских изображений теперь проходят общий стабильный барьер записи и не создают повторный технический diff. - В
docs/CONFIG-COMPATIBILITY.md—imageявно включён в действующийx/y/w/h/anglebox-контракт. - Screenshots/golden и User Guide не обновляются: визуал и пользовательский поток не меняются.
- Release/version/tag не входят в задачу; issue остаётся открытой в S8 до беты.
Риски
- Исправлен writer, но не отчёт. Один predicate обязан использоваться обоими TS-обходами; AC2 проверяет счётчик Optimize.
- Frontend и backend снова расходятся одинаково незаметно. Exact-set сравнение обоих runtime-каталогов с одной fixture и backend-мутант делают удаление наблюдаемым.
- Случайно канонизированы presentation/content-поля. AC5 фиксирует allowlist и неизвестное extension-поле.
- Тест проверяет список, но не поведение. AC4 требует прогнать каждый вид, а не ограничиваться сравнением строк каталога.
Откат
Откат — revert implementation commit. Новых полей и миграций нет; уже канонизированные image-координаты остаются валидными. Цена отката — возврат floating-point шума для следующих записей изображений.
Принятые предположения
- Box-геометрия определяется структурой
x/y/w/h/angle; текущий полный набор —rect,ellipse,furniture,image. angleостаётся scalar, а не lattice-полем;flip_h/flip_vне кодируются отрицательными размерами и не канонизируются.- Shared fixture является языконезависимым тестовым контрактом; продуктовый runtime не читает fixture с диска.
- Исправление считается пользовательским bugfix (
User-Visible: yes), хотя визуальный кадр не меняется: оно устраняет наблюдаемые лишние сохранения и повторные Optimize-изменения.