15 KiB
Executable File
ТЗ #400 — Полиш v1.70.0-beta.1: ручки мебели, вес помощи, мёртвое исключение
- Issue: https://github.com/Matysh/houseplan-card/issues/400
- Приоритет: P3, polish; полный трек — три несвязанные поверхности (SVG-рамка
редактора декора, состав ленивых чанков, направляющие выравнивания), критерий
«одна поверхность» для
smallне выполняется — прецеденты #369, #385 - Ревизия: 1 (2026-08-31)
Сценарий
Три шероховатости свежей беты, каждая мала по отдельности. Владелец расставляет мелкую мебель (тумба 40 см) и замечает, что за угол рамки ухватиться труднее, чем за бок. Пользователь на медленном канале грузит карточку чуть тяжелее, чем нужно: тексты подсказок редактора приезжают вместе с холодным View. Разработчик читает код направляющих и видит исключение, которое ничего не исключает.
Что человек увидит до и после
До: (1) на мебели уже примерно 80 см осевая ручка лежит поверх угловой и забирает часть её зоны — угол остаётся доступен, но площадь попадания меньше; (2) 38 строк помощи (EN 3,0 КБ + RU 6,3 КБ сырого текста) едут в initial-чанке, хотя видны только в редакторе; (3) видимой разницы нет — это внутренняя правка. После: угол мелкой мебели тянется так же уверенно, как бок; вес initial не несёт редакторских текстов (либо решение зафиксировано осознанно); направляющие опираются на актуальный источник перетаскивания.
Проблема и контракты по пунктам
(1) M5 — осевые ручки перекрывают угловые на мелкой мебели
src/houseplan-card.ts:8439 задаёт единый радиус хита
hr = max(view.w, view.h) * 0.018, одинаковый для угловых (:8467) и осевых
(:8471) ручек. Осевые рисуются после угловых, поэтому в зоне пересечения
хит забирают они.
Замер, а не расчёт (браузер, режим декора, .dtframe на реальной мебели):
радиус обеих ручек — 14 px на экране; перекрытие возникает, когда
полуширина объекта меньше 2·hr, то есть примерно до 80 см.
Уточнение серьёзности против формулировки аудита. В отчёте было сказано, что «пропорциональный ресайз углом становится недоступен». Это неверно: центр угловой ручки лежит вне круга осевой (для 40 см — 20 единиц плана против радиуса 18), попасть в угол можно, сужается лишь зона. Поэтому пункт остаётся полишем, а не багом. Честная формулировка: на мебели до ~80 см зона угловой ручки урезана примерно вдвое, потому что осевая лежит поверх неё.
Контракт: у угловой ручки приоритет хита над осевой. Достигается порядком
отрисовки (угловые кладутся последними) и/или скрытием осевых, когда сторона
меньше 4·hr — на такой стороне осевая ручка всё равно неотличима от угловых.
Выбор — за реализацией; критерий один: на мебели 40 см клик в геометрический
угол рамки достаётся угловой ручке.
(2) Low «б» — тексты помощи в холодном чанке
38 ключей помощи #86 лежат в dist/houseplan-assets/houseplan-card-*.js
(проверено grep'ом по собранному дереву), а не в ленивом чанке редактора. Это
не дефект #86: словари en и ru по архитектуре ленивой поставки всегда в
initial, лениво грузятся только de и fr. Но текст, который виден
исключительно в редакторе, оплачивается каждым холодным просмотром: запас
бюджета сейчас 15 945 Б при потолке 300 000, а за сутки беты израсходовано
6,1 КБ.
Контракт: решение принимается явно и записывается. Два честных исхода:
- вынести
*.help*-ключи в ленивый чанк редактора (он и так грузится, когда помощь может понадобиться) — тогда initial худеет на величину, которую нужно замерить и указать в отчёте; - зафиксировать, что
en/ruнеделимы, и планировать бюджет с этим знанием — тогда вdocs/ARCHITECTURE.mdпоявляется строка о том, что стоимость редакторских текстов входит в initial по построению.
Исход (1) предпочтителен, если механика ленивых словарей допускает раздельные секции без второго сетевого запроса на каждое открытие подсказки; если нет — честнее (2), чем ломать поставку ради 9 КБ.
(3) Low «г» — исключение по мёртвому источнику
src/houseplan-editor-runtime.ts:10970 в ветке _mode === 'devices'
пропускает перетаскиваемый маркер по this.host._drag?.id. Но перетаскивание
устройств после #74 живёт в отдельном состоянии _deviceDrag
(houseplan-card.ts:2548, заполняется на :6736), а _drag (:2547) в этом
режиме остаётся null — проверено чтением обоих объявлений и всех присваиваний.
Следствие: маркер, который тянут, попадает в собственный список кандидатов
выравнивания. Видимого сбоя нет только потому, что alignGuides
(src/logic.ts:1981) сравнивает точку с кандидатами по допуску, и точка
совпадает сама с собой — направляющая рисуется «от себя к себе» и визуально
неотличима от честной. demo/smoke_align_guides.mjs после a6b0850a
проверяет guides() >= 1 и такую подмену не различает.
Контракт: исключение опирается на актуальный источник перетаскивания
(_deviceDrag?.id в режиме устройств), а смок различает направляющую от
другого маркера и направляющую от самого себя.
Скоуп / не-скоуп
В скоупе: _renderTextFrame в src/houseplan-card.ts (порядок и/или
видимость ручек), состав ленивых чанков для help-ключей либо запись решения,
_alignCandidates в src/houseplan-editor-runtime.ts,
demo/smoke_align_guides.mjs, тесты и мутанты.
Не в скоупе: радиус ручек (hr, решение владельца 2026-08-05 «уменьшить в
4 раза»), механика ресайза мебели (#383), содержание текстов помощи (#86),
архитектура ленивой поставки словарей (#352–#355).
UX
Пункт (1) видим: угол мелкой мебели становится так же надёжен, как бок. Пункты (2) и (3) видимого поведения не меняют.
Модель данных и миграция
Не применимо: ни конфиг, ни layout не затрагиваются.
i18n
Новых строк нет. При исходе (1) пункта (2) ключи не меняются — меняется чанк, в котором они лежат.
Критерии приёмки
- AC1. На мебели 40 см клик в геометрический угол рамки достаётся угловой
ручке, а не осевой. Доказательство: браузерный смок с синтетическим
pointerdownв точку угла и перехватом обработчика —elementFromPointчерез shadow root недостаточно (проверено: возвращает саму карточку). - AC2. На крупной мебели (160 см) поведение не меняется: доступны и угловые, и осевые ручки. Доказательство: тот же смок, вторая ветка.
- AC3. Пункт (2) закрыт явным решением: либо help-ключи не попадают в
initial-чанк (доказательство:
grepпо собранному дереву + замер бюджета до и после), либо решение «en/ruнеделимы» записано вdocs/ARCHITECTURE.mdс причиной. - AC4. В режиме устройств перетаскиваемый маркер исключён из кандидатов
выравнивания по
_deviceDrag. Доказательство: юнит на_alignCandidates— при активном_deviceDragсписок не содержит позицию тянущегося маркера. - AC5. Смок направляющих различает «от другого маркера» и «от себя»: проверка падает, если исключение убрать. Доказательство: отрицательный прогон, зафиксированный в документе ревью.
- AC6. Бюджет initial не растёт: при исходе (1) он уменьшается, при
исходе (2) остаётся прежним. Доказательство:
npm run bundle:budgetдо и после.
План автотестов
Unit (test/align-candidates.test.mjs либо существующий файл):
_alignCandidatesв режимеdevicesпри активном_deviceDragне возвращает позицию тянущегося маркера (AC4).- Тот же вызов без активного перетаскивания возвращает все маркеры пространства — исключение не «съедает» лишнего.
Browser smoke (demo/smoke_furniture_polish.mjs — дополнение,
demo/smoke_align_guides.mjs — правка):
- Мебель 40 см:
pointerdownв геометрический угол рамки вызывает обработчик угловой ручки (AC1). - Мебель 160 см: доступны обе ручки, поведение прежнее (AC2).
- Направляющие: при перетаскивании маркера направляющая строится от ДРУГОГО маркера, а не от самого себя (AC5).
Мутанты (scripts/mutation-gate.mjs):
furniture-edge-handles-steal-the-corner: вернуть прежний порядок отрисовки → смок AC1 красный.align-guides-exclude-dead-source: вернуть_drag?.idв ветке устройств → юнит AC4 и смок AC5 красные.
Риски
- Порядок отрисовки меняет и видимую рамку. Бусины (
.dtknob) рисуются рядом с хит-кругами; перестановка не должна менять картинку. Смягчение: golden-кадры рамки мебели — если они есть в матрице, они это и покажут; при расхождении кадр пересматривается осознанно. - Скрытие осевых ручек ниже порога может удивить. Пользователь, привыкший тянуть за бок, на мелкой мебели его не найдёт. Смягчение: предпочтителен порядок отрисовки, а не скрытие; скрытие — только если порядок не решает.
- Вынос help-ключей ломает ленивую загрузку словаря. Второй сетевой запрос на открытие подсказки хуже, чем 9 КБ в initial. Смягчение: исход (2) остаётся честным ответом, если механика не позволяет.
Откат
Три независимые правки. Пункт (1) — порядок двух блоков в шаблоне; пункт (2) при исходе (1) — состав чанка, при исходе (2) — строка документации; пункт (3) — одно поле в условии. Данные пользователя не затрагиваются.
Release-артефакты
docs/CHANGELOG.md/docs/CHANGELOG.ru.md: один пункт про угол мелкой мебели (User-Visible: yes). Пункты (2) и (3) —User-Visible: no.- Скриншоты: пересматриваются, только если golden покажет расхождение рамки.