17 KiB
SPEC-REVIEW-51-r1 — Пользовательские изображения в декоративном слое
- Issue: https://github.com/Matysh/houseplan-card/issues/51
- Этап: ТЗ на ревью (PROCESS.md §2.4), заход r1, блокирующих циклов израсходовано 0 из 4 (лимит на полном треке — 4)
- ТЗ:
docs/specs/051-custom-decor-images.md - Ветка:
issue/51-custom-decor-images - Материал ревью: SHA
ee658c5d6d9c00bad475e2ed59826b1c01415143(=HEAD, провереноgit rev-parse HEAD; совпадает с SHA, названным автором в хендофф-комментарии) - Вердикт: зелёный
Скоуп ревью
Первый заход по этой задаче: предыдущих документов SPEC-REVIEW-51-* в истории репозитория нет (git log --all -- docs/reviews/SPEC-REVIEW-51* пуст), поэтому раздел «Унаследовано из r0» не применяется и разбор — полный, как того требует §2.9 при первом заходе.
Задача идёт полным треком (в issue явно названы критерии small, которые не проходит: несколько поверхностей, новый UX-контракт, новые compatibility-поля/persisted-схема) — ТЗ корректно лежит в docs/specs/051-custom-decor-images.md, а не в теле issue.
Как проверялось
- Прочитан
docs/SCOPE.md,AGENTS.md,PROCESS.mdполностью. - Прочитано тело issue #51 и все 6 комментариев: закрытый дубль #46 (требование к file lifecycle), три раунда аналитики (2026-08-15, 2026-08-30, 2026-09-02 с 6 продуктовыми вопросами Q1–Q6), решения владельца по всем Q1–Q6 (2026-09-02), финальный хендофф автора.
- Прочитан весь текст
docs/specs/051-custom-decor-images.md(629 строк) от начала до конца. - Сверены с реальным кодом на этом дереве ключевые фактические утверждения ТЗ, а не приняты на веру:
src/editors/decor/types.ts—DecorKindдействительно заканчивается на'furniture',DecorImageTransform— действительно неиспользуемая заготовка{x,y,w,h,angle?,opacity?}, как заявляет «Проблема»;docs/DECOR-EDITOR.md— секция «Deferred custom images» подтверждает оба ограничения, которые ТЗ соблюдает: не переиспользовать backdrop file lifecycle implicitly, и контракт transform уже совпадает с мебелью;- таблица «Tools» / «Magnet targets» в
DECOR-EDITOR.mdподтверждает, что wall magnet — специфика мебели («Furniture: wall magnet unless Shift»), а общий decor/room magnet — отдельный, более широкий механизм; ТЗ (§4 контракта) корректно различает эти два понятия и просит убрать только первое; src/backdrop-probe.tsподтверждает точные числа, которые ТЗ называет как переиспользуемые:WARN_DECODED_BYTES = 128 * 1024 * 1024,DOWNSCALE_TARGET_PX = 4096,HARD_DIMENSION = 16384;custom_components/houseplan/websocket_api.py:1057-1082подтверждаетMAX_SIGN_PATHS = 200и существующий паттернcontent/sign— ТЗ переиспользует то же число для лимитаresolve, не выдумывает новое;custom_components/houseplan/http_api.py:125-182(HouseplanContentView) подтверждает существующийsandbox-CSP для.svgи паттерн{kind}/{sub}/{name}с плейсхолдером_для плоских директорий (как уplans) — ТЗ предлагаетassets/_/<name>тем же паттерном, не новым;custom_components/houseplan/plans.py:235-239подтверждает существующийlow_disk_space-guard, который ТЗ обещает переиспользовать, а не изобретать;custom_components/houseplan/import_export.pyподтверждает реальные имена полейcontent_manifest,exists_at_export,export_version, и чтоEXPORT_VERSION = 1вconst.pyсегодня; апгрейд до v2 с обратной читаемостью v1 — реальная работа, не фикция, и прямо помечен в ТЗ как «принято предположительно, поменять свободно»;custom_components/houseplan/validation.py:1278подтверждает существование поляhide_decor, которым ТЗ оперирует;support_package.py/import_export.pyподтверждают, что SHA-256 content-addressing — уже используемый в проекте приём, а не новый паттерн без прецедента;demo/benchmark_large_house.mjs,demo/performance/budgets-large-house-*.jsonподтверждают, что «large-house fixture» и связанный performance-гейт — существующая практика, на которую ТЗ (AC14) корректно опирается;custom_components/houseplan/validation.py:1066подтверждаетMAX_DECOR = 1000на пространство — фикстура AC14 на 1000 image records бьёт в реальный, а не придуманный потолок.
- Сверены все 6 продуктовых вопросов Q1–Q6 и defaults из аналитики 2026-09-02 построчно с соответствующими местами контракта (§1 «Единая кнопка», §4 «Первичное размещение», §4/§5 «wall magnet», §8 «Явное удаление», «Import/export», §3 «Безопасный SVG») — все решения владельца перенесены в контракт без искажений и без дополнительных недосказанных допущений.
- Проверены обязательные разделы §7.1: сценарий, что человек увидит, проблема, скоуп/не-скоуп, контракт поведения, модель данных, i18n, AC1–AC16 с доказательством, план автотестов, риски, откат, release-артефакты — все присутствуют.
- Проверено соответствие
docs/TOUCH-SUPPORT.md— см. находку L1 ниже.
Находки
L1 (Low) — отсутствует обязательная литеральная строка Touch editor: …
docs/TOUCH-SUPPORT.md («Documentation rule») требует, чтобы каждая спецификация новой функциональности редактора явно содержала одну из трёх фраз: Touch editor: supported; Touch editor: best effort / intentionally degraded; Touch editor: not exposed. Это не стилистическое пожелание, а соблюдаемая по факту конвенция — она обнаружена буквально в ~15 других файлах docs/specs/*.md (132, 137, 138, 150, 159, 164, 172, 173, 174, 178, 179, 180, 186, 193, 199, 210 и др.).
Раздел «UX, accessibility, touch и kiosk» ТЗ #51 содержит по смыслу правильное утверждение — «Background editing on touch остаётся best effort по TOUCH-SUPPORT.md, но safety floor обязателен: …» — но не использует требуемую литеральную форму **Touch editor: best effort / intentionally degraded.**, из-за чего DoR-чеклист §2.5 («влияние на touch по docs/TOUCH-SUPPORT.md») и автоматизированный грep по будущим ревью не находят её механически.
Воспроизведение: grep -n "Touch editor:" docs/specs/051-custom-decor-images.md — 0 совпадений, при том что docs/TOUCH-SUPPORT.md требует ровно такую строку.
Серьёзность: Low — по существу вопрос решён верно (best effort, safety floor описан), это дефект оформления, а не решения. Правится добавлением одной строки, не влияет ни на один AC.
Решение ревьюера: не блокирует зелёный вердикт. Правится либо в ходе реализации (замена формулировки на литеральную), либо мелкой правкой ТЗ вне цикла — записываю как принятое с пометкой «поправить при следующем прикосновении к файлу», отдельного возврата на цикл ревью не требует по §3 правило 8 (Low либо правится, либо снимается решением ревьюера с записью).
Что проверено и корректно
- Продуктовая рамка (SCOPE.md). Фича закрывает J4/J6 («from zero to a working plan… no external editors» / «keep the plan true as the home evolves»); View остаётся пассивным (никаких HA-состояний, действий, hover/click) — соответствует «View mode is the product» и правилу lock/actuation (изображение никогда не actuates).
- Правило «никогда не удалять файл по предположению» (SCOPE.md, зафиксировано 2026-07-28) соблюдено буквально: §8 контракта запрещает автоматическое удаление при замене/удалении объекта/пространства/импорте, разрешает только явное действие с подтверждением и disabled-состоянием при наличии ссылок.
- Требование из закрытого #46 («до реализации нужно закрыть file lifecycle: удаление пространства, копирование декора, экспорт») закрыто: refcount вычисляется динамически из authoritative config (удаление пространства снижает его само по себе), copy/paste сохраняет
asset_id, экспорт покрытcontent_manifest/exists_at_export/repair-placeholder веткой. - Все 6 owner-decisions (Q1–Q6, комментарий 2026-09-02) перенесены в контракт без отклонений — кнопка+palette (§1), 100 см / cap 200 см (§4, AC2), transform мебели без wall magnet (§4–§5, AC3), запрет auto-delete + явное удаление только для asset с нулём ссылок (§8, AC9), JSON без bytes + repair-placeholder (Import/export, AC11), полный отказ небезопасного SVG без молчаливой вырезки (§3, AC7).
- AC1–AC16 пронумерованы, у каждого явно назван способ доказательства (unit/backend/smoke/golden/performance/review), что удовлетворяет §2.5 DoR-чеклист.
- Раздел «Принято предположительно, поменять свободно» корректно отделяет технические решения (content-addressed id, quota, export v2, точные имена модулей) от продуктовых — соответствует §7.1: «всё, чего пользователь не наблюдает, агенты решают сами».
- Not-scope явно исключает WYSIWYG-редактирование, drag-and-drop, авто-удаление orphan-файлов, вложение bytes в архив, wall magnet — устраняет пространство для скрытого расползания скоупа при реализации.
- Backend security-контракт (§2–§3) — signature/decode-confirmed проверка форматов, строгий XML parser без DTD/entities, allowlist элементов, canonical reserialize перед хешированием,
nosniff+sandbox CSP,<image>-only рендер SVG (никогда inline) — согласуется с существующим прецедентом sandbox-CSP вhttp_api.pyи не ослабляет его. - Rollback/откат описан по обеим осям (frontend/backend) и явно запрещает lossy-миграцию или автоматическую очистку при откате — соответствует стандартному разделу «Откат» DoR.
- Одно число — один источник. Единственная пользователю видимая величина, которую вводит эта фича при размещении, — физический размер нового изображения (100 см / aspect-preserving height, cap 200 см). ТЗ явно требует (§4, AC2): «preview и committed bounds совпадают при разных
cell_cm, zoom и intrinsic ratios» и что значения «переводятся в normalized geometry через текущиеcell_cm/grid helpers» — то есть единственный источник преобразования один и тот же для preview и записи, регрессии класса #234/#233 контракт исключает на уровне формулировки.
Чего не проверял
- Код не читался и не запускался — на этом этапе (ревью ТЗ) кода ещё нет:
git diffпоsrc/**/custom_components/**для этого issue пуст, это ожидаемо дляS4-spec-review. Гейтыtypecheck/test/build/смоки/golden/performance/backend не прогонялись и не применимы: класс изменения — толькоdocs/specs/051-custom-decor-images.md(класс C), кодовых гейтов задача не требует. git diff --checkи «проверка обязательных разделов §7.1», которые упоминает хендофф автора, я не перезапускал — визуальная проверка всех разделов §7.1 сделана вручную построчно (см. «Как проверялось», п.6) и даёт тот же результат.- Не проверялась реализуемость точных backend-имён (
houseplan/assets/uploadи т.д.) на предмет коллизий с уже существующими websocket command id — это заявлено автором как техническая деталь реализации («Точные transport names являются частью реализации и покрываются contract tests»), законно оставлено на усмотрение реализации согласно §7.1. - Не проверялись содержательно 4 канонических документа, которые ТЗ обещает обновить (
DECOR-EDITOR.md,ARCHITECTURE.md,CONFIG-COMPATIBILITY.md,USER-GUIDE(.ru).md) — они меняются в реализации, не в ТЗ, и будут предметом код-ревью.
Материал раунда
- Ветка:
issue/51-custom-decor-images - SHA:
ee658c5d6d9c00bad475e2ed59826b1c01415143(=HEADна момент ревью, дерево чистое) - Файл ТЗ:
docs/specs/051-custom-decor-images.md
Материал раунда
- Ветка:
issue/51-custom-decor-images, коммитee658c5d6d9c— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
42b442d3b9dd4bb4de9e8061e6b5120f13c7d68bgit log --all --format='%H %T' | grep 42b442d3b9dd