17 KiB
SPEC-REVIEW-687-r1
Issue: #687 — редактор плана: показывать значки устройств ориентирами, «1 в 1
как в редакторе подложки».
Этап: spec (ревью ТЗ, PROCESS.md §2.4).
Заход: r1 · блокирующих циклов израсходовано 0 из 2 (лёгкий трек, §4).
Материал: тело issue #687, раздел ## ТЗ, состояние на момент чтения
(2026-09-28T06:34Z), код на HEAD a0b471ca1bbea96eb978936860d8250dfd83cd68.
Продуктовый вопрос (0.2 vs «1 в 1 как в подложке») снят решением владельца от
2026-09-28, зафиксирован в теле issue до раздела ## ТЗ — открытых вопросов к
владельцу в тексте не осталось.
Скоуп
Задача обслуживает J6 из docs/SCOPE.md («Keep the plan true as the home
evolves» — расстановка стен/проёмов/лестниц) через устранение переключений в
View за ориентирами устройств; это явное расширение уже принятого прецедента
J6/#362 (ориентиры устройств в редакторе подложки), а не новая работа. Видимое
поведение меняется: User-Visible: yes в release-артефактах корректен.
Как проверялось
Ревью текста ТЗ плюс построчная сверка каждого фактического утверждения о
текущем коде с исходниками на HEAD, чтобы отличить проверяемый факт от
недоказанной догадки:
src/styles/plan.styles.ts:1179—.stage.markup .devlayer .dev { display: none; }— подтверждено дословно.src/styles/plan.styles.ts:1002–1012— правило подложки.stage.mode-decor .devlayer, .stage.mode-decor .devlayer *, .stage.mode-decor .dev::before { pointer-events: none; }с комментарием именно про 44px псевдо-зону — подтверждено.src/styles/plan.styles.ts:1013–1026— групповаяopacity: var(--hp-mode- architecture-opacity, 1)на.room, .devlayer, .opening, …; значение 0.35 дляdecor— подтверждено вsrc/houseplan-card.ts(переменная--hp-mode-architecture-opacityставится в шаблоне сцены, точная строка сдвинута на несколько строк от заявленной:10868, но выражение и значение найдены и совпадают).src/houseplan-card.ts:5594,5736,6966—_clickDevice,_keyDevice,_pointerDown— все три уже начинаются сif (this._mode !== 'view' && this._mode !== 'devices') return;— гварды действительно уже стоят вне CSS, как и заявляет раздел «Как сейчас». Это существенно снижает риск задачи: AC2 в основном проверяет уже существующую защиту плюс новую CSS-развязку для передачи клика/курсора инструменту.src/houseplan-card.ts:11780–11829—interactive = mode === 'view' || mode === 'devices',role/tabindexусловны на этом же флаге — подтверждено.src/styles/devices.styles.ts:176–186—.dev::before(44px,pointer- events: auto,z-index: 1) — подтверждено; ровно этот элемент требует явного упоминания в новом правиле pointer-events, что ТЗ и делает по аналогии с подложкой.src/styles/devices.styles.ts:356–360—.dev.unavail { opacity: 0.35; … }— подтверждает математику контракта п.2: 0.35 (множитель) × 0.35 (собственная) = 0.1225 для недоступного маркера.src/styles/devices.styles.ts:369–376—.dev.ghost { opacity: 0.6; … }— показывает, зачем контракт умножает на свою непрозрачность маркера (filter), а не переопределяет общийopacityтем же селектором: живой.devуже несёт разные собственныеopacityпо состоянию, которые нельзя просто затереть одним правилом той же CSS-property.src/houseplan-card.ts:10692–10693—showGhosts = mode === 'devices' && this._showAll,devs = this._renderDevices.filter(d => d.space === space.id && (!d.hidden || showGhosts))— набор маркеров в Plan (markup) и во View идентичен (обаshowGhosts === false), что делает AC1 «то же множество, что во View» проверяемым фактом, а не предположением.src/houseplan-card.ts:12060,6679,6702—.roomlabel— отдельный класс, сосед.devвнутри.devlayer, а не его потомок и не сам.dev. Значит правило pointer-events, если оно (по аналогии с подложкой) будет нацелено на.dev/.dev */.dev::before, а не на весь.devlayer, не заденет подписи комнат — контракт п.3 и п.4 совместимы, конфликта нет. Сам выбор конкретного селектора — техническая деталь реализации, а не продуктовое решение (§7.1), и она уже дважды перепроверяется тестами (AC2 на неинтерактивность маркера, AC3 на интерактивность подписи) — конфликт, если бы он был, проявился бы там.docs/DECOR-EDITOR.md:25(«Context emphasis») — «Rooms, labels, devices, openings … render at 35% opacity … pointer-inert» — подтверждает, что контракт п.2 корректно переносит уже задокументированное поведение подложки, а не изобретает число 0.35.docs/USER-GUIDE.ru.md:258–261— таблица режимов; строка «Редактор плана» сегодня действительно «Устройства: Скрыты», строка «Редактор подложки» — «Не редактируются и полупрозрачны». Release-артефакт («как в подложке») указывает на готовую, не придуманную формулировку.demo/smoke_feedback_v2.mjs:17— комментарий// (в редакторе плана .dev скрыты display:none — сравнивать не с чем)найден дословно; ТЗ верно определяет его как устаревший и требующий правки.demo/smoke_decor.mjs:21–50— существующий паттерн смок-теста подложки (elementFromPoint, сверка вычисленнойopacity.devlayer) — подтверждает техническую посильность плана автотестов для новогоdemo/ smoke_plan_device_landmarks.mjs, хотя для плана сверяется неopacityгруппы, а составноеopacity × filter: opacity()на самом маркере — ТЗ это явно называет.scripts/mutation-registry.mjs— find/replace-мутанты наdisplay: noneиpointer-events: none— уже устоявшийся паттерн в файле; два новых мутанта, описанных в плане автотестов, механически однотипны существующим.- PROCESS.md §7.1 (обязательные разделы), §2.5 (DoR).
Гейты не запускались: код не менялся (это ревью текста ТЗ, а не реализации), гейты этапа spec не входят в объём ревью ТЗ (PROCESS.md §2.4, §8).
Находки
Нет. High: 0, Medium: 0, Low: 0.
Что проверено и корректно
Обязательные разделы §7.1 присутствуют и в осмысленном порядке: сценарий →
что человек увидит до/после → скоуп/не-скоуп → контракт поведения (5 пунктов)
→ UX/модель данных/i18n (одним разделом, содержательно — «ничего нового») →
критерии приёмки AC1–AC3 с колонкой доказательства → план автотестов → риски →
откат → release-артефакты → «принято предположительно». «Проблема» вынесена в
раздел ## Зачем перед ## ТЗ — формально вне заголовка ## ТЗ, но по
содержанию это ровно требуемый раздел, а не пропуск; отделять его как
находку было бы придиркой к форме, а не к содержанию (§2.4: искать, где ТЗ не
выполнимо или не проверяемо).
Продуктовая развилка закрыта решением владельца, а не подставлена предположением. Исходные 0.2/выбор частей маркера заменены явным «1 в 1 как в подложке» — ТЗ верно определяет, что это снимает оба вопроса из исходного тела, и не пытается решить их самостоятельно.
Контракт непротиворечив и переносит уже работающий прецедент, а не
изобретает новый. Числа (0.35, 0.35 × 0.35 для .dev.unavail), состав
маркера (ядро/кольцо/капсула/бейджи/LQI/анимации), объём неинтерактивности
(44px-зона, капсула, курсор, Tab, привязка) — всё это дословно повторяет уже
существующий и задокументированный контракт подложки (DECOR-EDITOR.md,
plan.styles.ts:1002–1026, devices.styles.ts:356–376), перепроверено против
кода, а не принято на слово автора.
Раздел «принято предположительно» разрешает именно ту техническую
развилку, которая реально важна для числа, видимого пользователю — почему
множитель ставится через filter: opacity() на .dev, а не через групповую
opacity на .devlayer (иначе подписи комнат, лежащие в том же .devlayer,
потеряли бы обязательную по контракту п.4 непрозрачность/интерактивность).
Аналогичная развилка для pointer-events (там же нужно не задеть .roomlabel)
в тексте не разобрана отдельно, но это чисто техническая деталь реализации
(§7.1 — не наблюдается пользователем, значит не требует записи в ТЗ), и она
уже покрыта тестами AC2+AC3 с двух сторон.
AC1–AC3 однозначны и указывают способ доказательства (smoke / smoke +
«ревью кода»), защитные пункты AC2 сформулированы как конкретные пробы
(прямая отправка событий, elementFromPoint, вычисленный курсор, отсутствие
tabindex), а не общими словами «должно работать».
План автотестов называет мутанты для обоих защитных срезов (возврат
display: none → AC1, снятие pointer-events: none → AC2) и явно чинит
единственный найденный устаревший комментарий-регрессию.
Риски и откат достаточны для CSS-only задачи класса small: откат — вернуть
одну строку CSS; риск golden — учтён через существующий предрелизный механизм
приёмки (golden:accept --reviewed --expect-change, §11.4), а не оставлен
как открытый вопрос.
Release-артефакты названы конкретно и совпадают с реальным состоянием
документов: строка таблицы USER-GUIDE.ru.md действительно сегодня
«Скрыты» и нуждается в правке; UX-MODES.md › Plan существует как секция для
дополнения.
Чего не проверял
- Не проверял golden-снимки и реальный рендер маркеров в редакторе плана исполнением — кода ещё нет, это гейт этапа code, а не spec (PROCESS.md §8).
- Не проверял точный будущий текст CSS-селектора для pointer-events (см. выше
про
.roomlabel) — это решает автор при реализации; отмечено как непроблемное, а не как «принято на веру». - Не проверял влияние на layout/paint-бюджеты производительности исполнением
(профиль не запускался) — риск назван текстом («это только редактор, View
не затронут»), но не измерен числом; для задачи класса
small, чисто CSS, без новых DOM-узлов (маркеры и так в DOM) это не блокирует ревью ТЗ — измерение, если оно понадобится, ляжет на этап code (AC явно не требует performance-профиля). - Не проверял состояние других открытых issue на пересечение скоупа, кроме явно названного в оценке владельца #362 (ориентиры подложки, закрыт, контракт которого переносится).
Вердикт
Зелёный. ТЗ содержит все обязательные разделы, однозначные и проверяемые
AC1–AC3 со способом доказательства, продуктовая развилка закрыта решением
владельца, а не догадкой автора, и каждое проверяемое утверждение о текущем
коде подтверждено чтением HEAD. Технические детали, оставленные вне текста
(конкретный CSS-селектор pointer-events), не наблюдаются пользователем и не
требуют ответа владельца; риск, который они несут, уже закрыт пересекающимися
AC. Замечаний, требующих возврата автору, нет.
Материал раунда
- Issue: #687, тело на момент ревью —
2026-09-28T06:28:35Z(последний комментарий автора, статус на момент чтенияS4-spec-review). - Код: без изменений,
HEADa0b471ca1bbea96eb978936860d8250dfd83cd68. - Ветка задачи не создана — код в этом раунде не пишется (spec-этап).
Материал раунда
- Ветка:
dev, коммитa0b471ca1bbe— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
d8cdbaac038e822d048c65fa20e5e1837946cf95git log --all --format='%H %T' | grep d8cdbaac038e - Тело issue:
266af7ca2b7a21e08ab181761390dc4bab1425862dc2c6e48811d7777da34daa - Вердикт конвейера:
green· High 0