Files
houseplan-card/docs/specs/369-audit-lows.md
T
2026-08-29 10:41:40 +03:00

122 lines
9.7 KiB
Markdown
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.
# ТЗ #369 — Пачка Low аудита 29.08 (а–ж)
- Issue: https://github.com/Matysh/houseplan-card/issues/369
- Приоритет: P3, polish; полный трек (семь несвязанных поверхностей — критерий
«одна поверхность» §5 не выполняется, при том что каждая правка тривиальна)
- Ревизия: 2 (2026-08-29) — по SPEC-REVIEW-369-r1 (M1–M3)
## Сценарий
Семь мелких шероховатостей, найденных аудитом beta.2/beta.3 — каждая по
отдельности не тянет на issue, вместе дают заметный полиш. Пользователь видит:
честное предупреждение в логе вместо молча пропавшего следа пылесоса; климат
в старых конфигурациях без HA Area; отсутствие «живых» маркеров у сознательно
отключённых устройств; превью мебели, реагирующее на Shift мгновенно; никаких
случайных установок мебели правой кнопкой. **До**: семь тихих странностей.
**После**: каждая либо починена, либо честно задокументирована.
## Контракты по пунктам
**(а) Undelete пылесоса теряет след — документируется.** Поведение осознанное
(#335: purge tombstone-трейлов на `config/set`), правится документацией:
абзац в `docs/VACUUM.md` («удаление маркера пылесоса стирает его серверный
след немедленно; восстановление маркера начинает след заново») + зеркальная
строка в USER-GUIDE (en/ru) в теме пылесосов. Кода нет.
**(б) NaN-калибровка молча прячет след.** `smoothVacPath` (`src/vacuum.ts`)
отбрасывает сегмент с нефинитной точкой без следа в консоли. Добавить
`console.warn('[houseplan] vacuum trail segment dropped: non-finite point', …)`
**однократно на вызов** (не на сегмент — файл может содержать сотни), с
идентификацией количества отброшенных сегментов. Функция остаётся чистой по
возврату; warn — единственный побочный эффект, отключаемый параметром
`warn = console.warn` (юнит подменяет).
**(в) Легаси-маркер с `area === undefined` не получает климат #317.**
`markerClimateTarget` (`src/devices.ts:1406`) проверяет `marker.area === null`;
экспортные/легаси конфиги несут отсутствие ключа (undefined). Заменить на
`marker.area == null` (ловит оба) — существующий кейс «маркер в Area» не
задет (`marker.area` строка). Юнит: маркер `{space, room_id}` БЕЗ ключа area
получает `roomClimateKey`, как и с явным `null`.
**(г) «Живой» маркер при полностью отключённых сущностях.** #318
(`controllerAvailability`, `src/device-presentation.ts:213`) считает
entityless-active устройство доступным. Уточнение: если у устройства В
РЕЕСТРЕ есть собственные сущности, но все они отключены пользователем
(`d.entities` пуст, а `d.bindingStatus.kind === 'ha_disabled'` ЛИБО
`allEntityIds` непуст при пустом enabled-ростере) — это не «нет свидетельств
offline», а «пользователь сознательно выключил»: возвращать текущее поведение
ДО #318 (unavailable/приглушён). Истинно безростерные контроллеры (#318,
реестр пуст) остаются available. Юнит рядом с тестами #318 на оба случая.
**(д) Превью мебели не реагирует на Shift без движения мыши.** Пока палитра
мебели активна и превью видимо, runtime держит keydown/keyup-слушатели
(`Shift`) на `window` (снятие — вместе с деактивацией инструмента/палитры и в
`disconnected`-очистке жестов), обновляющие `free`-флаг превью и
`requestUpdate`. Паритет с кликом сохраняется — клик уже читает
`ev.shiftKey`.
**(е) Правый клик ставит мебель/декор.** В точке принятия placement-жеста
(`_stagePointerDown` → decor/furniture ветки) добавить фильтр
`ev.pointerType === 'mouse' && ev.button !== 0 → return` — правая/средняя
кнопка не ставит объект. Touch/pen (`button` 0/-1) не задеты; контекстное
меню браузера над стейджем ведёт себя как раньше.
**(ж) Имя правообладателя furniture-pack.** Решение владельца 2026-08-29:
автор — **Sergey Matyunin / Сергей Матюнин**. Исправить
`assets/furniture/houseplan-0.3.0/LICENSE.md`, `README.md`, `pack.json`
(поле author) и `docs/FURNITURE.md:41` с «Sergey Matyushin» на
«Sergey Matyunin». Root `LICENSE` (JB/justbusiness) не трогается.
## AC
1. (а) VACUUM.md + оба USER-GUIDE содержат абзац — доказательство: код-ревью
читает три файла (check-docs содержимое не проверяет, он лишь не должен
покраснеть от ссылок/фингерпринта).
2. (б) юнит: путь с NaN-точкой — warn вызван ровно один раз с числом
отброшенных сегментов; чистый путь — warn не вызван; возврат функции
байт-в-байт как сейчас (существующие юниты без правок).
3. (в) юнит: маркер без ключа `area` + `space`+`room_id` → room-ключ климата;
с `area: 'kitchen'` → 'kitchen' (регресс-защита).
4. (г) юниты: (i) устройство с пустым реестром + active → available (как
#318); (ii) устройство с непустым `allEntityIds`/ha_disabled при пустом
enabled-ростере → НЕ available. Существующие тесты #318 зелёные без правки.
5. (д) смок: палитра мебели активна, превью видимо — нажатие Shift без
движения мыши меняет `free`-флаг превью (и обратно на keyup); слушатели
сняты после закрытия палитры (счётчик addEventListener/removeEventListener).
6. (е) смок: right-button pointerdown по стейджу с armed-мебелью — объект НЕ
поставлен; левый клик — поставлен (регресс).
7. (ж) grep «Matyushin» по репо пуст; pack.json/LICENSE.md/README.md несут
«Sergey Matyunin»; SHA-256 архива-источника в README не пересчитывается
(архив не менялся — меняются только метаданные пака; расхождение имени с
внутренностями архива оговаривается строкой в README).
8. Мутанты: (б) warn выброшен → юнит красный; (в) `== null` → `=== null` →
юнит красный; (г) уточнение снято → юнит красный; (д) подписка
keydown/keyup не вешается (attach выброшен) → смок AC5 красный по
отсутствию смены free-флага и по счётчику слушателей; (е) фильтр кнопки
снят → смок красный.
## Риски
- (г) меняет видимое поведение недавнего #318 — сужение честное (реестр
непуст ⇒ свидетельство есть), но требует внимательного ревью формулировки
условия; откат точечный.
- (д) слушатели на window — риск утечки; лечится симметричным снятием и
проверкой счётчиком в смоке.
- Остальные пункты — точечные, риск минимальный.
## Release-артефакты
- CHANGELOG en+ru: одна запись пачкой (User-Visible: yes — пункты в, г, д, е).
- Документация: VACUUM.md, USER-GUIDE en/ru (пункт а).
- i18n: не требуется (новых строк UI нет).
- golden/performance: не задеты (проверить прогоном обычного гейта).
- security: не затрагивается; (ж) — правка метаданных лицензии.
## Вне скоупа
Изменение семантики purge (а) кодом; любые новые пороги/поведение trails.
## Откат
Один revert; конфиг/схема/контракты не меняются.