Files
houseplan-card/docs/specs/400-beta-polish.md
T
2026-08-31 07:36:44 +03:00

192 lines
15 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ #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 КБ.
**Контракт**: решение принимается явно и записывается. Два честных исхода:
1. вынести `*.help*`-ключи в ленивый чанк редактора (он и так грузится, когда
помощь может понадобиться) — тогда initial худеет на величину, которую
нужно замерить и указать в отчёте;
2. зафиксировать, что `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` либо существующий файл):
1. `_alignCandidates` в режиме `devices` при активном `_deviceDrag` не
возвращает позицию тянущегося маркера (AC4).
2. Тот же вызов без активного перетаскивания возвращает все маркеры
пространства — исключение не «съедает» лишнего.
**Browser smoke** (`demo/smoke_furniture_polish.mjs` — дополнение,
`demo/smoke_align_guides.mjs` — правка):
3. Мебель 40 см: `pointerdown` в геометрический угол рамки вызывает
обработчик угловой ручки (AC1).
4. Мебель 160 см: доступны обе ручки, поведение прежнее (AC2).
5. Направляющие: при перетаскивании маркера направляющая строится от ДРУГОГО
маркера, а не от самого себя (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 покажет расхождение рамки.