# Ревью ТЗ — issue #210, цикл r2 - **Issue:** https://github.com/Matysh/houseplan-card/issues/210 - **ТЗ:** `docs/specs/210-fixed-floor-card.md` (коммит `6c3d376`, ветка `issue/210-fixed-floor`) - **Предыдущий цикл:** `docs/reviews/SPEC-REVIEW-210-r1.md` — жёлтый, High: 0, Medium: 1 (M1, в скоупе), Low: 1 (L1) - **Трек:** обычный (аналитика явно исключила `small`/`trivial`; сложность 6/10, риск 6/10, метка `small` на issue отсутствует) — лимит циклов 4 - **Вердикт:** зелёный · цикл r2/4 · High: 0 · Medium: 0 ## Скоуп ревью Проверялось ТЗ `docs/specs/210-fixed-floor-card.md` в редакции коммита `6c3d376` как артефакт этапа `S4-spec-review`, второй цикл. Задача — не переповторить r1 целиком, а (1) убедиться, что правки по M1 и L1 сделаны так, как обещано в комментарии автора, (2) не подтвердить это на слово, а независимо перечитать документ целиком и код, к которому апеллирует новый текст, и (3) заново пройти обязательные разделы §7.1 PROCESS.md и AC1…AC12 свежим взглядом, а не только смотреть на диф — чтобы не унаследовать слепое пятно предыдущего цикла. Продуктового кода по #210 всё ещё не существует (диапазон коммитов ветки — только документация), поэтому гейты §8 PROCESS.md к этому циклу неприменимы, как и в r1. ## Как проверялось 1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком (в этой сессии, без переноса контекста из r1 — ревью ТЗ и код-ревью обязаны идти в разных сессиях по §6 PROCESS.md; здесь то же требование соблюдено для независимости повторного цикла ТЗ). 2. Прочитано тело issue #210 и все шесть комментариев, включая аналитику, вердикт r1 и комментарий автора о правках (`6c3d376`, «M1: … L1: …»). 3. Получен точный диф правки: `git diff 0bd6094 6c3d376 -- docs/specs/210-fixed-floor-card.md` — 12 добавленных строк, ни одной удалённой; сверен построчно с тем, что обещано в комментарии автора и с тем, что требовало M1/L1. 4. Прочитан файл ТЗ `210-fixed-floor-card.md` целиком заново, в текущей редакции (337 строк), а не только изменённые фрагменты. 5. Перечитаны `docs/CONFIG-COMPATIBILITY.md` целиком и профильные разделы `docs/UX-MODES.md` (header/tabs, строка 81: «Header in View: space tabs, device count, zoom cluster. Nothing else.») и `docs/TOUCH-SUPPORT.md` (canonical-tag правило) — те же документы, что и в r1, чтобы проверить, не создал ли новый текст противоречие, которого не было в r1. 6. Прочитан `docs/USER-GUIDE.ru.md` в местах, которые ТЗ обязуется обновить в release-артефактах: карточка card-options (строка 54, 125 — `default_floor` уже называется «Стартовое пространство» в действующем guide, то есть новый термин ТЗ **Initial space / Стартовое пространство** не изобретён, а взят из текущего интерфейса) и раздел «Несколько карточек и стартовые пространства» (строки 1327-1341) — подтверждает, что именно это описание (`default_floor` как единственная защита) ТЗ обязано уточнить в §15, и это уже учтено (не новая находка, ещё в r1). 7. Проверено соответствие нового текста коду: - `src/editor.ts` (`_valueChanged`, объединение через `{ ...this._config, ...ev.detail.value }`) — подтверждён паттерн, на который ссылается M1 r1 («никакого существующего прецедента явной зачистки пустого поля нет»). Новый текст §6.2 закрывает именно этот пробел явным требованием «удалить собственный ключ `floor` из выдаваемого card config целиком», а не полагаться на то, что пустая строка сама попадёт в уже описанный invalid-путь. - Существующие смоки `demo/smoke_nav_persist.mjs`, `demo/smoke_kiosk.mjs` присутствуют в репозитории; `demo/smoke_fixed_floor.mjs` — нет, как и ожидается по плану тестирования (§13): новый смок создаётся в реализации, а не сейчас. 8. Проверено, что новый §9.1 «i18n» не дублирует и не противоречит существующим требованиям §9 (invalid-state accessibility) и §6.2 (GUI labels) — он их резюмирует под отдельным заголовком, не вводя новых требований к продукту. 9. Код не запускался, гейты не прогонялись — на этапе ревью ТЗ продуктового кода нет, как и в r1 (PROCESS.md §8 относит гейты к код-ревью). ## Проверка правок по M1 и L1 ### M1 (было Medium, в скоупе) — закрыто Ровно в заявленном месте (§6.2, после абзаца про dropdown/textbox fallback) добавлено: > Выбор «не закреплять» обязан удалить собственный ключ `floor` из выдаваемого > card config целиком. GUI не записывает `floor: ''` или `floor: null`: эти > значения намеренно остаются невалидными для явно заданного YAML, а > отсутствие свойства возвращает legacy navigation согласно §6.1. Это прямо снимает найденное в r1 противоречие: пустой выбор GUI теперь однозначно ведёт к обычной (legacy) навигации, а не к невалидному `floor: ''` и, как следствие, к error state. Зеркальный пункт добавлен и в §16 («Принятые предположения», п.8) той же формулировкой, как и просил r1. Оба места непротиворечивы друг другу и §6.1 п.3 (`''`/`null` остаются невалидными для **явного** YAML, что осталось неизменным и корректным — M1 просил разграничить «GUI никогда не пишет эти значения» и «явный YAML с этими значениями всё ещё невалиден», а не менять §6.1). AC8 («GUI … умеет очистить `floor`») теперь однозначно проверяем: тест должен убедиться, что после очистки ключ отсутствует в объекте конфигурации, а не равен `''`/`null`. Находка полностью устранена, без побочных противоречий с остальным текстом. ### L1 (было Low, на решение автора) — закрыто Добавлен отдельный подраздел **§9.1. i18n** сразу после §9 (invalid-state контракт), формально выполняющий требование §7.1 PROCESS.md об отдельном разделе i18n. Содержание не вводит новых по существу требований — оно называет два конкретных файла (`en.json`/`ru.json`), явно запрещает зашитые рантайм-строки и привязывает термин к уже принятому «space / пространство» — то есть закрывает найденный формальный пробел, не расширяя скоуп. ## Заново проверенные разделы (не только диф) - **Обязательные разделы §7.1 PROCESS.md** — по-прежнему все присутствуют, с добавлением отдельного i18n-подраздела структура стала полнее, чем в r1. - **AC1…AC12** — перечитаны заново; ни один не изменился текстом со времени r1, добавление в §6.2/§16 делает AC8 более однозначным (см. M1 выше), не ослабляя и не меняя ни один AC. - **Непротиворечивость нового текста остальному документу** — §6.1 (типы и invalid-список), §9 (когда рендерится error state), §10 (fixed instance не очищает и не мигрирует nav record) и §16 п.6 (invalid `floor` не откатывается к `default_floor`) не пересекаются и не конфликтуют с новым текстом об удалении ключа при очистке GUI: очистка через GUI — это отсутствие `floor`, а не невалидное значение, поэтому она не проходит через invalid-ветку вовсе, что и было целью правки. - **SCOPE.md** — правка не меняет ни персону, ни поверхность, ни объём видимого изменения по сравнению с r1; продуктовая часть контракта по-прежнему исходит от владельца («Технические детали решаются свободно на ревью»), а не является догадкой автора, выданной за факт. - **Открытые продуктовые вопросы** — отсутствуют, как и в r1; оба устранённых пункта были техническими уточнениями формулировки, не продуктовыми развилками, и не требовали обращения к владельцу. Новых находок — ни High, ни Medium, ни Low — при независимом перечтении целиком не появилось. ## Проверено и признано корректным Всё, что было проверено и признано корректным в r1 (причинная цепочка §3/§7, структура документа, соответствие `docs/UX-MODES.md`, `docs/TOUCH-SUPPORT.md`, `docs/CONFIG-COMPATIBILITY.md`, AC1…AC12, план тестирования, риски и откат), остаётся в силе — правка `6c3d376` его не затронула ни одной строкой (диф только добавляет текст, ничего не удаляет и не переформулирует). Дополнительно в этом цикле подтверждено: - точное соответствие внесённых правок тому, что было обещано в комментарии автора (ничего не забыто, ничего лишнего не изменено походя); - отсутствие побочных противоречий между новым текстом и остальными разделами документа; - термин **Initial space / Стартовое пространство** не изобретён — совпадает с уже используемым в `docs/USER-GUIDE.ru.md:54,125` названием поля `default_floor`. ## Чего не проверял - **Фактическая работоспособность будущей реализации** — на этапе ревью ТЗ продуктового кода не существует. - **`ha-form`/`ha-selector` поведение при очистке optional select** — по-прежнему не тестировалось эмпирически; текст ТЗ теперь однозначен («удалить ключ целиком»), но соответствие итоговой реализации этому контракту — предмет код-ревью, не этого цикла. - **`demo/smoke_fixed_floor.mjs`, `smoke_nav_persist.mjs`, `smoke_kiosk.mjs`** не запускались — нового смока ещё нет, существующие два не относятся к этому циклу (нет кода, который они могли бы проверить). - Гейты `typecheck`/`test`/`build`/smoke/golden не прогонялись — раздел неприменим на этапе ревью ТЗ (PROCESS.md §8 относит их к код-ревью). ## Итог Оба цикла r1 (M1, L1) закрыты именно так, как было обещано, без побочных противоречий и без сужения/расширения скоупа. Независимое повторное чтение всего документа не выявило новых блокирующих или Medium-находок. Открытых продуктовых вопросов нет. ТЗ готово к «Готово к разработке». **Вердикт: зелёный · цикл r2/4 · High: 0 · Medium: 0**