Волна 5 эпика #674, перенос ТЗ (класс C). Из 240 файлов `docs/specs/` в `legacy/specs/` уехали 219: на них не ссылается ни один живой файл (код, тесты, скрипты, workflow, документы вне архива и ревью). Остались 21 ТЗ — на которые ссылаются код, ADR, ISOMETRIC, SUN, RADAR, LIGHT (`docs/specs/067`), DECOR-EDITOR, support-relay, и те, на которые ссылаются они сами; README каталога объясняет, где искать остальное. Открытых issue с файлом ТЗ среди перенесённых нет. Относительные ссылки перенесённых файлов переписаны (`../X` → `../../docs/X`, соседние оставшиеся ТЗ → `../../docs/specs/…`) — все 26 резолвятся. Попутно: битая ссылка в `089-isometric-view-stage1.md:8` на удалённый `089-isometric-view.md` — теперь команда `git show` по истории. Строка в `legacy/README.md`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
9.7 KiB
ТЗ #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
- (а) VACUUM.md + оба USER-GUIDE содержат абзац — доказательство: код-ревью читает три файла (check-docs содержимое не проверяет, он лишь не должен покраснеть от ссылок/фингерпринта).
- (б) юнит: путь с NaN-точкой — warn вызван ровно один раз с числом отброшенных сегментов; чистый путь — warn не вызван; возврат функции байт-в-байт как сейчас (существующие юниты без правок).
- (в) юнит: маркер без ключа
area+space+room_id→ room-ключ климата; сarea: 'kitchen'→ 'kitchen' (регресс-защита). - (г) юниты: (i) устройство с пустым реестром + active → available (как
#318); (ii) устройство с непустым
allEntityIds/ha_disabled при пустом enabled-ростере → НЕ available. Существующие тесты #318 зелёные без правки. - (д) смок: палитра мебели активна, превью видимо — нажатие Shift без
движения мыши меняет
free-флаг превью (и обратно на keyup); слушатели сняты после закрытия палитры (счётчик addEventListener/removeEventListener). - (е) смок: right-button pointerdown по стейджу с armed-мебелью — объект НЕ поставлен; левый клик — поставлен (регресс).
- (ж) grep «Matyushin» по репо пуст; pack.json/LICENSE.md/README.md несут «Sergey Matyunin»; SHA-256 архива-источника в README не пересчитывается (архив не менялся — меняются только метаданные пака; расхождение имени с внутренностями архива оговаривается строкой в README).
- Мутанты: (б) 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; конфиг/схема/контракты не меняются.