# SPEC-REVIEW-363-r1 Issue: #363 · Этап: spec (PROCESS.md §2.4) · Трек: `small` (лёгкий) · Заход r1 · блокирующих циклов израсходовано 0 из 2 (лимит лёгкого трека — 2, §5) ## Скоуп ТЗ в теле issue #363: возврат кнопки «Добавить» в основной тулбар Device editor рядом с «Устройства» — короткий путь к уже существующему диалогу нового устройства (`_openMarkerDialog()` без аргумента), убранной в `cab8d128` (#29) вместе с каталогом «Устройства». Каталог не трогается. Класс изменения — A (`src/houseplan-editor-runtime.ts`, три словаря `src/i18n/*.json`) плюс C (`docs/USER-GUIDE*.md`, оба changelog). Метка `small` подтверждена в аналитике владельца (S2): сложность 2/10, одна поверхность, миграций нет, нового UX-контракта нет (контракт — тот же, что до #29). ## Важное обстоятельство состояния (проверить перед чтением остального) В issue уже есть более ранняя попытка этого же захода: комментарий от `claude` (2026-08-29T09:16:10Z, жёлтый вердикт, `Заход r1 · блокирующих циклов 1/2 · High: 0 · Medium: 1`) и закоммиченный на `dev` `docs/reviews/SPEC-REVIEW-363-r1.md` (коммит `9d6d6eb9`, тот же SHA, что и текущий HEAD). Тот документ нашёл **M1**: раздел «Release-артефакты» не называл обязательный прогон workflow «Скриншоты документации» и `docs:accept --reviewed`, из-за чего `docs`-job в CI Validate стал бы красным по расхождению `sourceFingerprint`, даже если ни один PNG не изменился (сценарий #230/#234). Задача даёт этому заходу метаданные `Заход r1 · 0 из 2` — то есть с точки зрения конвейера предыдущая попытка цикл не потратила: судя по всему, тот прогон не дошёл до финального структурированного ответа (без него, по инструкции конвейера, метка не переставляется), поэтому официально не засчитан, а issue не уходил в `S3-spec`. Тем не менее тело issue уже переписано в «ТЗ · revision 2» и текстуально закрывает M1 (см. ниже) — то есть автор (владелец, пишущий ТЗ прямо в issue на лёгком треке) увидел незасчитанный комментарий и поправил текст до этого захода. Я не доверяю этому на слово и ниже перепроверяю сам, что revision 2 действительно закрывает M1 и что остальные утверждения черновика остаются верными на текущем дереве. Раздела «Унаследовано из r» не завожу — по счётчику конвейера это первый действительный заход, и я перепроверил всё заново, а не по дельте. ## Как проверялось Ничего не принято на слово из текста ТЗ или прежнего черновика — каждое утверждение перепроверено чтением дерева на `HEAD=9d6d6eb9`: 1. **M1 закрыт.** AC7 сейчас: «...полный артефакт принимается через `npm run docs:accept -- --reviewed --from=`, а обновлённый `docs/images/screenshots.json` коммитится... Все PNG канонического набора (сейчас 10) ожидаются пиксельно неизменными». Раздел «Release-артефакты» дублирует то же требование и явно называет workflow «Скриншоты документации» обязательной «даже если видимая композиция кадров не меняется». Проверил, что названные команда и workflow существуют: `.github/workflows/docs-screenshots.yml` (шаг публикует `docs/images/screenshots.json` артефактом), `package.json:16` — `"docs:accept": "node scripts/docs-accept.mjs"`. Прежний черновик просил именно это, только называл «9 PNG» — сейчас в дереве `demo/docs/screenshots.mjs` действительно **10** сценариев (проверил `grep -n "id:" demo/docs/screenshots.mjs` и `docs/images/screenshots.json → scenarios.length`), то есть текущее ТЗ точнее черновика, а не расходится с деревом задним числом. 2. **`_openMarkerDialog()` жив, контракт не меняется.** `src/houseplan-editor-runtime.ts:7628` `public _openMarkerDialog(d?: DevItem)`; вызов без аргумента — путь диалога нового устройства. Кнопка «Устройства» вызывает другой метод, `_openDeviceInbox()` (`houseplan-editor-runtime.ts:11545`), а «Добавить виртуальное устройство» внутри каталога вызывает тот же `_openMarkerDialog()` без аргумента (`houseplan-editor-runtime.ts:11578`, `openVirtual`). Значит новая кнопка получает ровно тот же путь, что уже используется каталогом для того же исхода — вторая, независимая проверка того, что AC2 просит воспроизводимую, а не новую логику. 3. **i18n-таблица побайтово совпадает с состоянием до `cab8d128`.** `git show cab8d128^:src/i18n/en.json` и `...ru.json` дают ровно `"devbar.add": "Add"` / `"Добавить"` и `"title.add_device": "Add a device to the plan"` / `"Добавить устройство на план"` — совпадает посимвольно с таблицей ТЗ. Оба ключа сейчас отсутствуют во всех трёх словарях (`grep -n "devbar.add\|title.add_device" src/i18n/{en,ru,de}.json` — пусто), значит работа по их возврату реальна, а не фиктивна. 4. **Немецкий текст — согласованное предположение, не догадка.** `de.json` уже использует `"device_inbox.add": "Hinzufügen"` для того же действия и паттерн «существительное + hinzufügen» для похожих подписей (`"title.add_space": "Bereich hinzufügen"`). Предложенные `"Hinzufügen"` / `"Gerät zum Plan hinzufügen"` этому соответствуют. Помечено в ТЗ как принятое предположение, которое ревьюер вправе оспорить — не оспариваю. 5. **Паритет словарей ловит забытую локаль.** `test/i18n.test.mjs:109-112`: `assert.deepEqual(Object.keys(dictionary).sort(), enKeys)` для каждого языка реестра. Пропуск любого из 6 значений (2 ключа × 3 локали) красит `npm test`. AC4 доказуемо этим тестом, и тест умеет падать (сейчас ключей нет ни в одном словаре — не найти это на голом дереве, если ключ забыт, тест обязан споткнуться). 6. **Место вставки и порядок.** Текущий `_renderDevicesBar()` (`houseplan-editor-runtime.ts:11541-11553`) начинается с кнопки `device_inbox.button` («Устройства»), затем `devbar.rules` («Правила иконок»). До #29 первой кнопкой была «Добавить» (`git show cab8d128^:src/houseplan-card.ts` — фрагмент, приведённый в теле issue, подтверждён отдельно). Вставка новой кнопки перед «Устройства» восстанавливает этот порядок; номера строк в S2-аналитике (`:11491`) немного разошлись с текущими из-за более поздних коммитов — не находка, метод и разметка на месте. 7. **Каталог и его «Добавить виртуальное устройство» не задеты.** `_renderDeviceInbox()` / `openVirtual()` вне диапазона правок ТЗ. `demo/smoke_device_inbox.mjs` существует, его заголовок прямо ссылается на #29 («one lifecycle catalog replaces the separate Add / hidden-device paths») — подходящий существующий регресс-щуп для AC3. 8. **RU/EN руководство действительно потеряло строку.** `docs/USER-GUIDE.ru.md:775-786` — таблица «Редактор устройств» содержит «Устройства» и «Добавить виртуальное устройство», строки про «Добавить» нет. `docs/USER-GUIDE.md:519-529` — список без пункта «Add». Оба файла названы в «Затронутых файлах». 9. **Golden-сцены разведены верно.** `demo/golden/matrix.mjs:381` — `geometry-devices-editor-dark`: `mode: 'devices'`, **без** ключа `dialog` — тулбар виден целиком, третья кнопка попадёт в кадр. `device-inbox-*` (строки 383-390) все три несут `dialog: 'device-inbox'` — модальный каталог перекрывает тулбар, эти кадры не должны измениться. `--expect-change=geometry-devices-editor-dark` в AC8 — точная и единственная нужная пометка. 10. **UX-MODES.md / TOUCH-SUPPORT.md** — персистентный инструмент в основном тулбаре, `barclose` остаётся в правом торце (`editbar-end`, не тронут); новых touch-целей и жестов не вводится. Конфликта с «редакторы desktop-first, touch — best effort» нет. 11. **Обязательные разделы §7.1** — сценарий+результат, скоуп/не-скоуп, контракт поведения и UX, данные/совместимость (модель не меняется), i18n, AC1-AC8 с доказательством каждого, план тестов и мутанты, риски и откат, release-артефакты — все присутствуют в теле issue. 12. **Продуктовые вопросы, которые могли уйти владельцу как технические** — не найдены: три «принятых предположения» (CSS-классы/wrap, отсутствие отдельного disabled-state, немецкий перевод) — все технические/оформ- ительские, ни одно не требует продуктового решения. ## Гейты этого захода Материал — только текст issue, продуктового кода по #363 в дереве ещё нет (`git log --all --oneline | grep 363` и `git branch -a | grep 363` не находят ветки/коммитов задачи, кроме случайных числовых совпадений `3633a3db` (#159), `9d6d6eb9`/это же ревью). Прогонять `typecheck`/`test`/`build`/ `check-docs` не над чем — это ревью текста (§2.4), а не кода (§2.7); их место в коде-ревью после реализации. Отдельно перечитал и вручную проверил исполняемость `test/i18n.test.mjs` (пункт 5) и существование `demo/smoke_device_inbox.mjs`, `.github/workflows/docs-screenshots.yml`, `scripts/docs-accept.mjs` чтением, не запуском — их сегодняшнее содержимое, а не будущее поведение, было предметом проверки. ## Находки Нет. M1 предыдущего (незасчитанного) захода закрыт текстом ТЗ (см. пункт 1 выше) и проверен по дереву, а не на слово. Догадок, выданных за решение, не найдено. Технических вопросов, которые следовало решить самому вместо эскалации владельцу, не найдено — их и не было. ## Что проверено и корректно - Обработчик `_openMarkerDialog()` и его контракт (пункт 2). - Побайтовое совпадение i18n-таблицы с состоянием до `cab8d128` и паритет-тест (пункты 3, 5). - Согласованность предложенного немецкого текста с действующим словарём (пункт 4). - Место вставки и восстановленный порядок кнопок (пункт 6). - Независимость каталога и его «Добавить виртуальное устройство» (пункт 7). - Пропуск строки тулбара в RU/EN руководстве (пункт 8). - Разведение golden-сцен `geometry-devices-editor-dark` vs `device-inbox-*` (пункт 9). - Отсутствие конфликта с UX-MODES.md / TOUCH-SUPPORT.md (пункт 10). - Полнота обязательных разделов ТЗ §7.1 и отсутствие невынесенных продуктовых вопросов (пункты 11-12). - Закрытие M1 предыдущего захода текстом revision 2 (пункт 1). ## Чего не проверял - Реализацию — её ещё нет; код-ревью (§2.7) проверит diff, а не текст ТЗ. - Фактический прогон `typecheck`/`test`/`build`/`check-docs`/golden — не применимо к этому заходу, материала для прогона (diff) не существует. - Browser-смоки и golden-захват — то же самое, нечего запускать. - Реальный CI-прогон workflow «Скриншоты документации» — оценивалась только корректность его упоминания в ТЗ, а не его будущее исполнение. ## Вердикт Зелёный. High: 0, Medium: 0. ТЗ полно, однозначно, каждый AC привязан к способу доказательства, единственная находка предыдущей (незасчитанной) попытки закрыта текстом и подтверждена чтением дерева. Трек `small` подтверждён повторно.